Skip to content

Commit 81959a3

Browse files
jamshaleff137
authored andcommitted
Anoncreds create credential (openwallet-foundation#3369)
* Fix anoncreds issuance and compatibility Signed-off-by: jamshale <[email protected]> * Change schema info from dict to class. Signed-off-by: jamshale <[email protected]> * Add a unit test Signed-off-by: jamshale <[email protected]> * Revert holder credentail tag key changes Signed-off-by: jamshale <[email protected]> * Add get cred def info function to anoncreds registry Signed-off-by: jamshale <[email protected]> * Update anoncreds presentation handler to avoid indy parsing Signed-off-by: jamshale <[email protected]> * Fix unit tests Signed-off-by: jamshale <[email protected]> * Switch verifier away from get_info methods in handler Signed-off-by: jamshale <[email protected]> * Revert presentation request metadata and restrictions changes Signed-off-by: jamshale <[email protected]> * Fix get_cred_def usage Signed-off-by: jamshale <[email protected]> * Add profile to get_shcema_info_by_id Signed-off-by: jamshale <[email protected]> * Update unit test Signed-off-by: jamshale <[email protected]> * Remove redeclared variable Signed-off-by: jamshale <[email protected]> --------- Signed-off-by: jamshale <[email protected]>
1 parent 846ad1c commit 81959a3

File tree

14 files changed

+188
-72
lines changed

14 files changed

+188
-72
lines changed

acapy_agent/anoncreds/base.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
RevRegDefResult,
1717
)
1818
from .models.schema import AnonCredsSchema, GetSchemaResult, SchemaResult
19+
from .models.schema_info import AnoncredsSchemaInfo
1920

2021
T = TypeVar("T")
2122

@@ -130,9 +131,15 @@ async def get_revocation_list(
130131
) -> GetRevListResult:
131132
"""Get a revocation list from the registry."""
132133

134+
@abstractmethod
135+
async def get_schema_info_by_id(
136+
self, profile: Profile, schema_id: str
137+
) -> AnoncredsSchemaInfo:
138+
"""Get a schema info from the registry."""
139+
133140

134141
class BaseAnonCredsRegistrar(BaseAnonCredsHandler):
135-
"""Base Anon Creds Registrar."""
142+
"""Base Anoncreds Registrar."""
136143

137144
@abstractmethod
138145
async def register_schema(

acapy_agent/anoncreds/default/did_indy/registry.py

+7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
RevRegDefResult,
1818
)
1919
from ...models.schema import AnonCredsSchema, GetSchemaResult, SchemaResult
20+
from ...models.schema_info import AnoncredsSchemaInfo
2021

2122
LOGGER = logging.getLogger(__name__)
2223

@@ -118,3 +119,9 @@ async def update_revocation_list(
118119
) -> RevListResult:
119120
"""Update a revocation list on the registry."""
120121
raise NotImplementedError()
122+
123+
async def get_schema_info_by_id(
124+
self, profile: Profile, schema_id: str
125+
) -> AnoncredsSchemaInfo:
126+
"""Get a schema info from the registry."""
127+
return await super().get_schema_info_by_id(schema_id)

acapy_agent/anoncreds/default/did_web/registry.py

+7
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
RevRegDefResult,
1717
)
1818
from ...models.schema import AnonCredsSchema, GetSchemaResult, SchemaResult
19+
from ...models.schema_info import AnoncredsSchemaInfo
1920

2021

2122
class DIDWebRegistry(BaseAnonCredsResolver, BaseAnonCredsRegistrar):
@@ -113,3 +114,9 @@ async def update_revocation_list(
113114
) -> RevListResult:
114115
"""Update a revocation list on the registry."""
115116
raise NotImplementedError()
117+
118+
async def get_schema_info_by_id(
119+
self, profile: Profile, schema_id: str
120+
) -> AnoncredsSchemaInfo:
121+
"""Get a schema info from the registry."""
122+
return await super().get_schema_info_by_id(schema_id)

acapy_agent/anoncreds/default/legacy_indy/registry.py

+12
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
SchemaResult,
8181
SchemaState,
8282
)
83+
from ...models.schema_info import AnoncredsSchemaInfo
8384
from ...revocation import (
8485
CATEGORY_REV_LIST,
8586
CATEGORY_REV_REG_DEF,
@@ -1229,3 +1230,14 @@ async def txn_submit(
12291230
)
12301231
except LedgerError as err:
12311232
raise AnonCredsRegistrationError(err.roll_up) from err
1233+
1234+
async def get_schema_info_by_id(
1235+
self, profile: Profile, schema_id: str
1236+
) -> AnoncredsSchemaInfo:
1237+
"""Get schema info by schema id."""
1238+
schema_id_parts = re.match(r"^(\w+):2:([^:]+):([^:]+)$", schema_id)
1239+
return AnoncredsSchemaInfo(
1240+
issuer_id=schema_id_parts.group(1),
1241+
name=schema_id_parts.group(2),
1242+
version=schema_id_parts.group(3),
1243+
)

acapy_agent/anoncreds/default/legacy_indy/tests/test_registry.py

+9
Original file line numberDiff line numberDiff line change
@@ -1210,3 +1210,12 @@ async def test_sync_wallet_rev_list_with_issuer_cred_rev_records(
12101210
),
12111211
)
12121212
assert isinstance(result, RevList)
1213+
1214+
async def test_get_schem_info(self):
1215+
result = await self.registry.get_schema_info_by_id(
1216+
self.profile,
1217+
"XduBsoPyEA4szYMy3pZ8De:2:minimal-33279d005748b3cc:1.0",
1218+
)
1219+
assert result.issuer_id == "XduBsoPyEA4szYMy3pZ8De"
1220+
assert result.name == "minimal-33279d005748b3cc"
1221+
assert result.version == "1.0"

acapy_agent/anoncreds/holder.py

+11-18
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import asyncio
44
import json
55
import logging
6-
import re
76
from typing import Dict, Optional, Sequence, Tuple, Union
87

98
from anoncreds import (
@@ -150,8 +149,8 @@ async def create_credential_request(
150149
) = await asyncio.get_event_loop().run_in_executor(
151150
None,
152151
CredentialRequest.create,
153-
None,
154152
holder_did,
153+
None,
155154
credential_definition.to_native(),
156155
secret,
157156
AnonCredsHolder.MASTER_SECRET_ID,
@@ -231,25 +230,19 @@ async def _finish_store_credential(
231230
rev_reg_def: Optional[dict] = None,
232231
) -> str:
233232
credential_data = cred_recvd.to_dict()
234-
schema_id = cred_recvd.schema_id
235-
schema_id_parts = re.match(r"^(\w+):2:([^:]+):([^:]+)$", schema_id)
236-
if not schema_id_parts:
237-
raise AnonCredsHolderError(f"Error parsing credential schema ID: {schema_id}")
238-
cred_def_id = cred_recvd.cred_def_id
239-
cdef_id_parts = re.match(r"^(\w+):3:CL:([^:]+):([^:]+)$", cred_def_id)
240-
if not cdef_id_parts:
241-
raise AnonCredsHolderError(
242-
f"Error parsing credential definition ID: {cred_def_id}"
243-
)
233+
registry = self.profile.inject(AnonCredsRegistry)
234+
schema_info = await registry.get_schema_info_by_id(
235+
self.profile, credential_data["schema_id"]
236+
)
244237

245238
credential_id = credential_id or str(uuid4())
246239
tags = {
247-
"schema_id": schema_id,
248-
"schema_issuer_did": schema_id_parts[1],
249-
"schema_name": schema_id_parts[2],
250-
"schema_version": schema_id_parts[3],
251-
"issuer_did": cdef_id_parts[1],
252-
"cred_def_id": cred_def_id,
240+
"schema_id": credential_data["schema_id"],
241+
"schema_issuer_did": schema_info.issuer_id,
242+
"schema_name": schema_info.name,
243+
"schema_version": schema_info.version,
244+
"issuer_did": credential_definition["issuerId"],
245+
"cred_def_id": cred_recvd.cred_def_id,
253246
"rev_reg_id": cred_recvd.rev_reg_id or "None",
254247
}
255248

acapy_agent/anoncreds/models/credential_request.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ class Meta:
2424

2525
def __init__(
2626
self,
27+
entropy: Optional[str] = None,
28+
# For compatibility with credx agents, which uses `prover_did` instead of `entropy` # noqa
2729
prover_did: Optional[str] = None,
2830
cred_def_id: Optional[str] = None,
2931
blinded_ms: Optional[Mapping] = None,
@@ -33,6 +35,7 @@ def __init__(
3335
):
3436
"""Initialize anoncreds credential request."""
3537
super().__init__(**kwargs)
38+
self.entropy = entropy
3639
self.prover_did = prover_did
3740
self.cred_def_id = cred_def_id
3841
self.blinded_ms = blinded_ms
@@ -49,8 +52,16 @@ class Meta:
4952
model_class = AnoncredsCredRequest
5053
unknown = EXCLUDE
5154

55+
entropy = fields.Str(
56+
required=False,
57+
metadata={
58+
"description": "Prover DID/Random String/UUID",
59+
"example": UUID4_EXAMPLE,
60+
},
61+
)
62+
# For compatibility with credx agents, which uses `prover_did` instead of `entropy`
5263
prover_did = fields.Str(
53-
required=True,
64+
required=False,
5465
metadata={
5566
"description": "Prover DID/Random String/UUID",
5667
"example": UUID4_EXAMPLE,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""This class represents schema information for anoncreds."""
2+
3+
from typing import Optional
4+
5+
6+
class AnoncredsSchemaInfo:
7+
"""Represents the schema information for anonymous credentials.
8+
9+
Attributes:
10+
issuer_id (str): The identifier of the issuer.
11+
name (Optional[str]): The name of the schema. Defaults to None.
12+
version (Optional[str]): The version of the schema. Defaults to None.
13+
14+
Args:
15+
issuer_id (str): The identifier of the issuer.
16+
name (Optional[str], optional): The name of the schema. Defaults to None.
17+
version (Optional[str], optional): The version of the schema. Defaults to None.
18+
"""
19+
20+
def __init__(
21+
self, issuer_id: str, name: Optional[str] = None, version: Optional[str] = None
22+
):
23+
"""Initialize the schema information."""
24+
self.issuer_id = issuer_id
25+
self.name = name
26+
self.version = version

acapy_agent/anoncreds/registry.py

+8
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
RevRegDefResult,
2222
)
2323
from .models.schema import AnonCredsSchema, GetSchemaResult, SchemaResult
24+
from .models.schema_info import AnoncredsSchemaInfo
2425

2526
LOGGER = logging.getLogger(__name__)
2627

@@ -99,6 +100,13 @@ async def get_credential_definition(
99100
credential_definition_id,
100101
)
101102

103+
async def get_schema_info_by_id(
104+
self, profile: Profile, schema_id: str
105+
) -> AnoncredsSchemaInfo:
106+
"""Get a schema info from the registry."""
107+
resolver = await self._resolver_for_identifier(schema_id)
108+
return await resolver.get_schema_info_by_id(profile, schema_id)
109+
102110
async def register_credential_definition(
103111
self,
104112
profile: Profile,

acapy_agent/anoncreds/tests/test_holder.py

+13-37
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,6 @@ def __init__(self, bad_schema=False, bad_cred_def=False):
5555
self.schema_id = "Sc886XPwD1gDcHwmmLDeR2:2:degree schema:45.101.94"
5656
self.cred_def_id = "Sc886XPwD1gDcHwmmLDeR2:3:CL:229975:faber.agent.degree_schema"
5757

58-
if bad_schema:
59-
self.schema_id = "bad-schema-id"
60-
if bad_cred_def:
61-
self.cred_def_id = "bad-cred-def-id"
62-
6358
schema_id = "Sc886XPwD1gDcHwmmLDeR2:2:degree schema:45.101.94"
6459
cred_def_id = "Sc886XPwD1gDcHwmmLDeR2:3:CL:229975:faber.agent.degree_schema"
6560
rev_reg_id = None
@@ -72,15 +67,10 @@ def to_dict(self):
7267

7368

7469
class MockCredReceivedW3C:
75-
def __init__(self, bad_schema=False, bad_cred_def=False):
70+
def __init__(self):
7671
self.schema_id = "Sc886XPwD1gDcHwmmLDeR2:2:degree schema:45.101.94"
7772
self.cred_def_id = "Sc886XPwD1gDcHwmmLDeR2:3:CL:229975:faber.agent.degree_schema"
7873

79-
if bad_schema:
80-
self.schema_id = "bad-schema-id"
81-
if bad_cred_def:
82-
self.cred_def_id = "bad-cred-def-id"
83-
8474
def to_json_buffer(self):
8575
return b"credential"
8676

@@ -89,9 +79,7 @@ def to_dict(self):
8979

9080

9181
class MockCredential:
92-
def __init__(self, bad_schema=False, bad_cred_def=False):
93-
self.bad_schema = bad_schema
94-
self.bad_cred_def = bad_cred_def
82+
def __init__(self):
9583
self.rev_reg_id = "rev-reg-id"
9684
self.rev_reg_index = 0
9785

@@ -101,21 +89,17 @@ def to_dict(self):
10189
return MOCK_CRED
10290

10391
def process(self, *args, **kwargs):
104-
return MockCredReceived(self.bad_schema, self.bad_cred_def)
92+
return MockCredReceived()
10593

10694

10795
class MockW3Credential:
108-
def __init__(self, bad_schema=False, bad_cred_def=False):
109-
self.bad_schema = bad_schema
110-
self.bad_cred_def = bad_cred_def
111-
11296
cred = mock.AsyncMock(auto_spec=W3cCredential)
11397

11498
def to_dict(self):
11599
return MOCK_W3C_CRED
116100

117101
def process(self, *args, **kwargs):
118-
return MockCredReceivedW3C(self.bad_schema, self.bad_cred_def)
102+
return MockCredReceivedW3C()
119103

120104

121105
class MockMasterSecret:
@@ -285,8 +269,6 @@ async def test_store_credential_fails_to_load_raises_x(self, mock_master_secret)
285269
side_effect=[
286270
MockCredential(),
287271
MockCredential(),
288-
MockCredential(bad_schema=True),
289-
MockCredential(bad_cred_def=True),
290272
],
291273
)
292274
async def test_store_credential(self, mock_load, mock_master_secret):
@@ -296,6 +278,9 @@ async def test_store_credential(self, mock_load, mock_master_secret):
296278
commit=mock.CoroutineMock(return_value=None),
297279
)
298280
)
281+
self.profile.context.injector.bind_instance(
282+
AnonCredsRegistry, mock.MagicMock(AnonCredsRegistry, autospec=True)
283+
)
299284

300285
# Valid
301286
result = await self.holder.store_credential(
@@ -321,20 +306,6 @@ async def test_store_credential(self, mock_load, mock_master_secret):
321306
{"cred-req-meta": "cred-req-meta"},
322307
)
323308

324-
# Test bad id's
325-
with self.assertRaises(AnonCredsHolderError):
326-
await self.holder.store_credential(
327-
MOCK_CRED_DEF,
328-
MOCK_PRES,
329-
{"cred-req-meta": "cred-req-meta"},
330-
)
331-
with self.assertRaises(AnonCredsHolderError):
332-
await self.holder.store_credential(
333-
MOCK_CRED_DEF,
334-
MOCK_CRED,
335-
{"cred-req-meta": "cred-req-meta"},
336-
)
337-
338309
@mock.patch.object(AnonCredsHolder, "get_master_secret", return_value="master-secret")
339310
@mock.patch.object(
340311
W3cCredential,
@@ -362,7 +333,9 @@ async def test_store_credential_w3c(
362333
commit=mock.CoroutineMock(return_value=None),
363334
)
364335
)
365-
336+
self.profile.context.injector.bind_instance(
337+
AnonCredsRegistry, mock.MagicMock(AnonCredsRegistry, autospec=True)
338+
)
366339
with mock.patch.object(jsonld, "expand", return_value=MagicMock()):
367340
with mock.patch.object(JsonLdProcessor, "get_values", return_value=["type1"]):
368341
result = await self.holder.store_credential_w3c(
@@ -384,6 +357,9 @@ async def test_store_credential_failed_trx(self, *_):
384357
self.profile.transaction = mock.MagicMock(
385358
side_effect=[AskarError(AskarErrorCode.UNEXPECTED, "test")]
386359
)
360+
self.profile.context.injector.bind_instance(
361+
AnonCredsRegistry, mock.MagicMock(AnonCredsRegistry, autospec=True)
362+
)
387363

388364
with self.assertRaises(AnonCredsHolderError):
389365
await self.holder.store_credential(

acapy_agent/indy/credx/issuer.py

+5
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,11 @@ async def create_credential(
330330
revoc = None
331331
credential_revocation_id = None
332332

333+
# This is for compatibility with an anoncreds holder
334+
if not credential_request.get("prover_did"):
335+
credential_request["prover_did"] = credential_request["entropy"]
336+
del credential_request["entropy"]
337+
333338
try:
334339
(
335340
credential,

acapy_agent/protocols/issue_credential/v2_0/formats/anoncreds/tests/test_handler.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@
132132
"nonce": "1234567890",
133133
}
134134
ANONCREDS_CRED_REQ = {
135-
"prover_did": TEST_DID,
135+
"entropy": TEST_DID,
136136
"cred_def_id": CRED_DEF_ID,
137137
"blinded_ms": {
138138
"u": "12345",

0 commit comments

Comments
 (0)