Skip to content

Commit ee13c13

Browse files
authored
Merge branch 'main' into fix/public-did-mediator-routing-keys
2 parents c7aceda + 8a0d7cb commit ee13c13

33 files changed

Lines changed: 969 additions & 154 deletions

Multicredentials.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Multi-Credentials
2+
3+
It is a known fact that multiple AnonCreds can be combined to present a presentation proof with an "and" logical operator: For instance, a verifier can ask for the "name" claim from an eID and the "address" claim from a bank statement to have a single proof that is either valid or invalid. With the Present Proof Protocol v2, it is possible to have "and" and "or" logical operators for AnonCreds and/or W3C Verifiable Credentials.
4+
5+
With the Present Proof Protocol v2, verifiers can ask for a combination of credentials as proof. For instance, a Verifier can ask a claim from an AnonCreds **and** a verifiable presentation from a W3C Verifiable Credential, which would open the possibilities of Aries Cloud Agent Python being used for rather complex presentation proof requests that wouldn't be possible without the support of AnonCreds or W3C Verifiable Credentials.
6+
7+
Moreover, it is possible to make similar presentation proof requests using the or logical operator. For instance, a verifier can ask for either an eID in AnonCreds format or an eID in W3C Verifiable Credential format. This has the potential to solve the interoperability problem of different credential formats and ecosystems from a user point of view by shifting the requirement of holding/accepting different credential formats from identity holders to verifiers. Here again, using Aries Cloud Agent Python as the underlying verifier agent can tackle such complex presentation proof requests since the agent is capable of verifying both type of credential formats and proof types.
8+
9+
In the future, it would be even possible to put mDoc as an attachment with an and or or logical operation, along with AnonCreds and/or W3C Verifiable Credentials. For this to happen, Aca-Py either needs the capabilities to validate mDocs internally or to connect third-party endpoints to validate and get a response.

aries_cloudagent/admin/server.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@ async def check_multitenant_authorization(request: web.Request, handler):
370370
and not is_server_path
371371
and not is_unprotected_path(path)
372372
and not base_limited_access_path
373+
and not (request.method == "OPTIONS") # CORS fix
373374
):
374375
raise web.HTTPUnauthorized()
375376

aries_cloudagent/config/argparse.py

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ def create_argument_parser(*, prog: str = None):
7474

7575

7676
def load_argument_groups(parser: ArgumentParser, *groups: Type[ArgumentGroup]):
77-
"""Log a set of argument groups into a parser.
77+
"""
78+
Log a set of argument groups into a parser.
7879
7980
Returns:
8081
A callable to convert loaded arguments into a settings dictionary
@@ -872,32 +873,56 @@ def get_settings(self, args: Namespace) -> dict:
872873
if args.no_ledger:
873874
settings["ledger.disabled"] = True
874875
else:
875-
configured = False
876+
single_configured = False
877+
multi_configured = False
878+
update_pool_name = False
876879
if args.genesis_url:
877880
settings["ledger.genesis_url"] = args.genesis_url
878-
configured = True
881+
single_configured = True
879882
elif args.genesis_file:
880883
settings["ledger.genesis_file"] = args.genesis_file
881-
configured = True
884+
single_configured = True
882885
elif args.genesis_transactions:
883886
settings["ledger.genesis_transactions"] = args.genesis_transactions
884-
configured = True
887+
single_configured = True
885888
if args.genesis_transactions_list:
886889
with open(args.genesis_transactions_list, "r") as stream:
887890
txn_config_list = yaml.safe_load(stream)
888891
ledger_config_list = []
889892
for txn_config in txn_config_list:
890893
ledger_config_list.append(txn_config)
894+
if "is_write" in txn_config and txn_config["is_write"]:
895+
if "genesis_url" in txn_config:
896+
settings["ledger.genesis_url"] = txn_config[
897+
"genesis_url"
898+
]
899+
elif "genesis_file" in txn_config:
900+
settings["ledger.genesis_file"] = txn_config[
901+
"genesis_file"
902+
]
903+
elif "genesis_transactions" in txn_config:
904+
settings["ledger.genesis_transactions"] = txn_config[
905+
"genesis_transactions"
906+
]
907+
else:
908+
raise ArgsParseError(
909+
"No genesis information provided for write ledger"
910+
)
911+
if "id" in txn_config:
912+
settings["ledger.pool_name"] = txn_config["id"]
913+
update_pool_name = True
891914
settings["ledger.ledger_config_list"] = ledger_config_list
892-
configured = True
893-
if not configured:
915+
multi_configured = True
916+
if not (single_configured or multi_configured):
894917
raise ArgsParseError(
895918
"One of --genesis-url --genesis-file, --genesis-transactions "
896919
"or --genesis-transactions-list must be specified (unless "
897920
"--no-ledger is specified to explicitly configure aca-py to"
898921
" run with no ledger)."
899922
)
900-
if args.ledger_pool_name:
923+
if single_configured and multi_configured:
924+
raise ArgsParseError("Cannot configure both single- and multi-ledger.")
925+
if args.ledger_pool_name and not update_pool_name:
901926
settings["ledger.pool_name"] = args.ledger_pool_name
902927
if args.ledger_keepalive:
903928
settings["ledger.keepalive"] = args.ledger_keepalive

aries_cloudagent/core/conductor.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from ..config.wallet import wallet_config
2929
from ..core.profile import Profile
3030
from ..indy.verifier import IndyVerifier
31-
from ..ledger.base import BaseLedger
31+
3232
from ..ledger.error import LedgerConfigError, LedgerTransactionError
3333
from ..ledger.multiple_ledger.base_manager import (
3434
BaseMultipleLedgerManager,
@@ -144,7 +144,6 @@ async def setup(self):
144144
self.root_profile.BACKEND_NAME == "askar"
145145
and ledger.BACKEND_NAME == "indy-vdr"
146146
):
147-
context.injector.bind_instance(BaseLedger, ledger)
148147
context.injector.bind_provider(
149148
IndyVerifier,
150149
ClassProvider(
@@ -156,7 +155,6 @@ async def setup(self):
156155
self.root_profile.BACKEND_NAME == "indy"
157156
and ledger.BACKEND_NAME == "indy"
158157
):
159-
context.injector.bind_instance(BaseLedger, ledger)
160158
context.injector.bind_provider(
161159
IndyVerifier,
162160
ClassProvider(

aries_cloudagent/indy/models/cred_request.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class Meta:
4444
unknown = EXCLUDE
4545

4646
prover_did = fields.Str(
47-
requred=True,
47+
required=True,
4848
description="Prover DID",
4949
**INDY_DID,
5050
)

aries_cloudagent/messaging/decorators/attach_decorator.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from ..valid import (
3131
BASE64,
3232
BASE64URL_NO_PAD,
33+
DictOrDictListField,
3334
INDY_ISO8601_DATETIME,
3435
JWS_HEADER_KID,
3536
SHA256,
@@ -228,7 +229,7 @@ def __init__(
228229
sha256_: str = None,
229230
links_: Union[Sequence[str], str] = None,
230231
base64_: str = None,
231-
json_: dict = None,
232+
json_: Union[Sequence[dict], dict] = None,
232233
):
233234
"""
234235
Initialize decorator data.
@@ -488,7 +489,7 @@ def validate_data_spec(self, data: Mapping, **kwargs):
488489
required=False,
489490
data_key="jws",
490491
)
491-
json_ = fields.Dict(
492+
json_ = DictOrDictListField(
492493
description="JSON-serialized data",
493494
required=False,
494495
example='{"sample": "content"}',
@@ -615,7 +616,7 @@ def data_base64(
615616
@classmethod
616617
def data_json(
617618
cls,
618-
mapping: dict,
619+
mapping: Union[Sequence[dict], dict],
619620
*,
620621
ident: str = None,
621622
description: str = None,

aries_cloudagent/protocols/issue_credential/v1_0/routes.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,8 @@ async def credential_exchange_retrieve(request: web.BaseRequest):
382382

383383
@docs(
384384
tags=["issue-credential v1.0"],
385-
summary="Send holder a credential, automating entire flow",
385+
summary=("Create a credential record without "
386+
"sending (generally for use with Out-Of-Band)"),
386387
)
387388
@request_schema(V10CredentialCreateSchema())
388389
@response_schema(V10CredentialExchangeSchema(), 200, description="")

aries_cloudagent/protocols/issue_credential/v2_0/routes.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -521,7 +521,8 @@ async def credential_exchange_retrieve(request: web.BaseRequest):
521521

522522
@docs(
523523
tags=["issue-credential v2.0"],
524-
summary="Create credential from attribute values",
524+
summary=("Create a credential record without "
525+
"sending (generally for use with Out-Of-Band)"),
525526
)
526527
@request_schema(V20IssueCredSchemaCore())
527528
@response_schema(V20CredExRecordSchema(), 200, description="")

aries_cloudagent/protocols/present_proof/dif/pres_exch_handler.py

Lines changed: 97 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,7 +1230,7 @@ async def create_vp(
12301230
challenge: str = None,
12311231
domain: str = None,
12321232
records_filter: dict = None,
1233-
) -> dict:
1233+
) -> Union[Sequence[dict], dict]:
12341234
"""
12351235
Create VerifiablePresentation.
12361236
@@ -1244,78 +1244,99 @@ async def create_vp(
12441244
req = await self.make_requirement(
12451245
srs=pd.submission_requirements, descriptors=pd.input_descriptors
12461246
)
1247-
result = await self.apply_requirements(
1248-
req=req, credentials=credentials, records_filter=records_filter
1249-
)
1250-
applicable_creds, descriptor_maps = await self.merge(result)
1251-
applicable_creds_list = []
1252-
for credential in applicable_creds:
1253-
applicable_creds_list.append(credential.cred_value)
1254-
if (
1255-
not self.profile.settings.get("debug.auto_respond_presentation_request")
1256-
and not records_filter
1257-
and len(applicable_creds_list) > 1
1258-
):
1259-
raise DIFPresExchError(
1260-
"Multiple credentials are applicable for presentation_definition "
1261-
f"{pd.id} and --auto-respond-presentation-request setting is not "
1262-
"enabled. Please specify which credentials should be applied to "
1263-
"which input_descriptors using record_ids filter."
1247+
result = []
1248+
if req.nested_req:
1249+
for nested_req in req.nested_req:
1250+
res = await self.apply_requirements(
1251+
req=nested_req,
1252+
credentials=credentials,
1253+
records_filter=records_filter,
1254+
)
1255+
result.append(res)
1256+
else:
1257+
res = await self.apply_requirements(
1258+
req=req, credentials=credentials, records_filter=records_filter
12641259
)
1265-
# submission_property
1266-
submission_property = PresentationSubmission(
1267-
id=str(uuid4()), definition_id=pd.id, descriptor_maps=descriptor_maps
1268-
)
1269-
if self.is_holder:
1270-
(
1271-
issuer_id,
1272-
filtered_creds_list,
1273-
) = await self.get_sign_key_credential_subject_id(
1274-
applicable_creds=applicable_creds
1260+
result.append(res)
1261+
1262+
result_vp = []
1263+
for res in result:
1264+
applicable_creds, descriptor_maps = await self.merge(res)
1265+
applicable_creds_list = []
1266+
for credential in applicable_creds:
1267+
applicable_creds_list.append(credential.cred_value)
1268+
if (
1269+
not self.profile.settings.get("debug.auto_respond_presentation_request")
1270+
and not records_filter
1271+
and len(applicable_creds_list) > 1
1272+
):
1273+
raise DIFPresExchError(
1274+
"Multiple credentials are applicable for presentation_definition "
1275+
f"{pd.id} and --auto-respond-presentation-request setting is not "
1276+
"enabled. Please specify which credentials should be applied to "
1277+
"which input_descriptors using record_ids filter."
1278+
)
1279+
# submission_property
1280+
submission_property = PresentationSubmission(
1281+
id=str(uuid4()), definition_id=pd.id, descriptor_maps=descriptor_maps
12751282
)
1276-
if not issuer_id and len(filtered_creds_list) == 0:
1277-
vp = await create_presentation(credentials=applicable_creds_list)
1278-
vp["presentation_submission"] = submission_property.serialize()
1279-
if self.proof_type is BbsBlsSignature2020.signature_type:
1280-
vp["@context"].append(SECURITY_CONTEXT_BBS_URL)
1281-
return vp
1282-
else:
1283-
vp = await create_presentation(credentials=filtered_creds_list)
1284-
else:
1285-
if not self.pres_signing_did:
1283+
if self.is_holder:
12861284
(
12871285
issuer_id,
12881286
filtered_creds_list,
12891287
) = await self.get_sign_key_credential_subject_id(
12901288
applicable_creds=applicable_creds
12911289
)
1292-
if not issuer_id:
1290+
if not issuer_id and len(filtered_creds_list) == 0:
12931291
vp = await create_presentation(credentials=applicable_creds_list)
12941292
vp["presentation_submission"] = submission_property.serialize()
12951293
if self.proof_type is BbsBlsSignature2020.signature_type:
12961294
vp["@context"].append(SECURITY_CONTEXT_BBS_URL)
1297-
return vp
1295+
result_vp.append(vp)
1296+
continue
12981297
else:
12991298
vp = await create_presentation(credentials=filtered_creds_list)
13001299
else:
1301-
issuer_id = self.pres_signing_did
1302-
vp = await create_presentation(credentials=applicable_creds_list)
1303-
vp["presentation_submission"] = submission_property.serialize()
1304-
if self.proof_type is BbsBlsSignature2020.signature_type:
1305-
vp["@context"].append(SECURITY_CONTEXT_BBS_URL)
1306-
async with self.profile.session() as session:
1307-
wallet = session.inject(BaseWallet)
1308-
issue_suite = await self._get_issue_suite(
1309-
wallet=wallet,
1310-
issuer_id=issuer_id,
1311-
)
1312-
signed_vp = await sign_presentation(
1313-
presentation=vp,
1314-
suite=issue_suite,
1315-
challenge=challenge,
1316-
document_loader=document_loader,
1317-
)
1318-
return signed_vp
1300+
if not self.pres_signing_did:
1301+
(
1302+
issuer_id,
1303+
filtered_creds_list,
1304+
) = await self.get_sign_key_credential_subject_id(
1305+
applicable_creds=applicable_creds
1306+
)
1307+
if not issuer_id:
1308+
vp = await create_presentation(
1309+
credentials=applicable_creds_list
1310+
)
1311+
vp["presentation_submission"] = submission_property.serialize()
1312+
if self.proof_type is BbsBlsSignature2020.signature_type:
1313+
vp["@context"].append(SECURITY_CONTEXT_BBS_URL)
1314+
result_vp.append(vp)
1315+
continue
1316+
else:
1317+
vp = await create_presentation(credentials=filtered_creds_list)
1318+
else:
1319+
issuer_id = self.pres_signing_did
1320+
vp = await create_presentation(credentials=applicable_creds_list)
1321+
vp["presentation_submission"] = submission_property.serialize()
1322+
if self.proof_type is BbsBlsSignature2020.signature_type:
1323+
vp["@context"].append(SECURITY_CONTEXT_BBS_URL)
1324+
async with self.profile.session() as session:
1325+
wallet = session.inject(BaseWallet)
1326+
issue_suite = await self._get_issue_suite(
1327+
wallet=wallet,
1328+
issuer_id=issuer_id,
1329+
)
1330+
signed_vp = await sign_presentation(
1331+
presentation=vp,
1332+
suite=issue_suite,
1333+
challenge=challenge,
1334+
document_loader=document_loader,
1335+
)
1336+
result_vp.append(signed_vp)
1337+
if len(result_vp) == 1:
1338+
return result_vp[0]
1339+
return result_vp
13191340

13201341
def check_if_cred_id_derived(self, id: str) -> bool:
13211342
"""Check if credential or credentialSubjet id is derived."""
@@ -1367,7 +1388,7 @@ async def merge(
13671388
async def verify_received_pres(
13681389
self,
13691390
pd: PresentationDefinition,
1370-
pres: dict,
1391+
pres: Union[Sequence[dict], dict],
13711392
):
13721393
"""
13731394
Verify credentials received in presentation.
@@ -1376,8 +1397,24 @@ async def verify_received_pres(
13761397
pres: received VerifiablePresentation
13771398
pd: PresentationDefinition
13781399
"""
1379-
descriptor_map_list = pres["presentation_submission"].get("descriptor_map")
13801400
input_descriptors = pd.input_descriptors
1401+
if isinstance(pres, Sequence):
1402+
for pr in pres:
1403+
descriptor_map_list = pr["presentation_submission"].get(
1404+
"descriptor_map"
1405+
)
1406+
await self.__verify_desc_map_list(
1407+
descriptor_map_list, pr, input_descriptors
1408+
)
1409+
else:
1410+
descriptor_map_list = pres["presentation_submission"].get("descriptor_map")
1411+
await self.__verify_desc_map_list(
1412+
descriptor_map_list, pres, input_descriptors
1413+
)
1414+
1415+
async def __verify_desc_map_list(
1416+
self, descriptor_map_list, pres, input_descriptors
1417+
):
13811418
inp_desc_id_contraint_map = {}
13821419
inp_desc_id_schema_one_of_filter = set()
13831420
inp_desc_id_schemas_map = {}

0 commit comments

Comments
 (0)