Skip to content

Commit 6072830

Browse files
authored
Merge branch 'main' into feat/public-did-multi-use
2 parents 8d370c1 + 0bbfe72 commit 6072830

27 files changed

Lines changed: 1370 additions & 138 deletions

.github/workflows/pr-tests.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: PR Tests
2+
3+
on:
4+
pull_request:
5+
6+
jobs:
7+
tests:
8+
name: Tests
9+
uses: ./.github/workflows/tests.yml
10+
with:
11+
python-version: "3.6"
12+
os: "ubuntu-20.04"
13+
14+
tests-indy:
15+
name: Tests (Indy)
16+
uses: ./.github/workflows/tests-indy.yml
17+
with:
18+
python-version: "3.6"
19+
indy-version: "1.16.0"
20+
os: "ubuntu-20.04"

.github/workflows/tests-indy.yml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: Tests (Indy)
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
python-version:
7+
required: true
8+
type: string
9+
indy-version:
10+
required: true
11+
type: string
12+
os:
13+
required: true
14+
type: string
15+
16+
jobs:
17+
tests:
18+
name: Test Python ${{ inputs.python-version }} on Indy ${{ inputs.indy-version }}
19+
runs-on: ${{ inputs.os }}
20+
steps:
21+
- uses: actions/checkout@v3
22+
23+
- name: Cache image layers
24+
uses: actions/cache@v3
25+
with:
26+
path: /tmp/.buildx-cache-test
27+
key: ${{ runner.os }}-buildx-test-${{ github.sha }}
28+
restore-keys: |
29+
${{ runner.os }}-buildx-test-
30+
31+
- name: Set up Docker Buildx
32+
uses: docker/setup-buildx-action@v1
33+
34+
- name: Build test image
35+
uses: docker/build-push-action@v3
36+
with:
37+
load: true
38+
context: .
39+
file: docker/Dockerfile.indy
40+
target: acapy-test
41+
tags: acapy-test:latest
42+
build-args: |
43+
python_version=${{ inputs.python-version }}
44+
indy_version=${{ inputs.indy-version }}
45+
cache-from: type=local,src=/tmp/.buildx-cache-test
46+
cache-to: type=local,dest=/tmp/.buildx-cache-test-new,mode=max
47+
48+
# Temp fix
49+
# https://github.com/docker/build-push-action/issues/252
50+
# https://github.com/moby/buildkit/issues/1896
51+
- name: Move cache
52+
run: |
53+
rm -rf /tmp/.buildx-cache-test
54+
mv /tmp/.buildx-cache-test-new /tmp/.buildx-cache-test
55+
56+
- name: Run pytest
57+
run: |
58+
docker run --rm acapy-test:latest

.github/workflows/tests.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Tests
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
python-version:
7+
required: true
8+
type: string
9+
os:
10+
required: true
11+
type: string
12+
13+
jobs:
14+
tests:
15+
name: Test Python ${{ inputs.python-version }}
16+
runs-on: ${{ inputs.os }}
17+
steps:
18+
- uses: actions/checkout@v3
19+
- name: Set up Python ${{ inputs.python-version }}
20+
uses: actions/setup-python@v4
21+
with:
22+
python-version: ${{ inputs.python-version }}
23+
cache: 'pip'
24+
cache-dependency-path: 'requirements*.txt'
25+
- name: Install dependencies
26+
run: |
27+
python -m pip install --upgrade pip
28+
pip3 install --no-cache-dir \
29+
-r requirements.txt \
30+
-r requirements.askar.txt \
31+
-r requirements.bbs.txt \
32+
-r requirements.dev.txt
33+
- name: Tests
34+
run: |
35+
pytest

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/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: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,10 @@ 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=(
386+
"Create a credential record without "
387+
"sending (generally for use with Out-Of-Band)"
388+
),
386389
)
387390
@request_schema(V10CredentialCreateSchema())
388391
@response_schema(V10CredentialExchangeSchema(), 200, description="")

aries_cloudagent/protocols/issue_credential/v2_0/routes.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -521,7 +521,10 @@ 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=(
525+
"Create a credential record without "
526+
"sending (generally for use with Out-Of-Band)"
527+
),
525528
)
526529
@request_schema(V20IssueCredSchemaCore())
527530
@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)