Skip to content

Commit

Permalink
Fix anoncreds issuance and compatibility
Browse files Browse the repository at this point in the history
Signed-off-by: jamshale <jamiehalebc@gmail.com>
  • Loading branch information
jamshale committed Dec 6, 2024
1 parent 6ddb592 commit e08c47f
Show file tree
Hide file tree
Showing 12 changed files with 73 additions and 64 deletions.
4 changes: 4 additions & 0 deletions acapy_agent/anoncreds/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ async def get_revocation_list(
) -> GetRevListResult:
"""Get a revocation list from the registry."""

@abstractmethod
async def get_schema_info_by_id(self, schema_id: str) -> dict:
"""Get a schema info from the registry."""


class BaseAnonCredsRegistrar(BaseAnonCredsHandler):
"""Base Anon Creds Registrar."""
Expand Down
4 changes: 4 additions & 0 deletions acapy_agent/anoncreds/default/did_indy/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,7 @@ async def update_revocation_list(
) -> RevListResult:
"""Update a revocation list on the registry."""
raise NotImplementedError()

async def get_schema_info_by_id(self, schema_id: str) -> dict:
"""Get a schema info from the registry."""
return await super().get_schema_info_by_id(schema_id)
4 changes: 4 additions & 0 deletions acapy_agent/anoncreds/default/did_web/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,7 @@ async def update_revocation_list(
) -> RevListResult:
"""Update a revocation list on the registry."""
raise NotImplementedError()

async def get_schema_info_by_id(self, schema_id: str) -> dict:
"""Get a schema info from the registry."""
return await super().get_schema_info_by_id(schema_id)
9 changes: 9 additions & 0 deletions acapy_agent/anoncreds/default/legacy_indy/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -1229,3 +1229,12 @@ async def txn_submit(
)
except LedgerError as err:
raise AnonCredsRegistrationError(err.roll_up) from err

async def get_schema_info_by_id(self, schema_id: str) -> dict:
"""Get schema info by schema id."""
schema_id_parts = re.match(r"^(\w+):2:([^:]+):([^:]+)$", schema_id)
return {
"issuer_id": schema_id_parts.group(1),
"name": schema_id_parts.group(2),
"version": schema_id_parts.group(3),
}
27 changes: 9 additions & 18 deletions acapy_agent/anoncreds/holder.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import asyncio
import json
import logging
import re
from typing import Dict, Optional, Sequence, Tuple, Union

from anoncreds import (
Expand Down Expand Up @@ -150,8 +149,8 @@ async def create_credential_request(
) = await asyncio.get_event_loop().run_in_executor(
None,
CredentialRequest.create,
None,
holder_did,
None,
credential_definition.to_native(),
secret,
AnonCredsHolder.MASTER_SECRET_ID,
Expand Down Expand Up @@ -231,25 +230,17 @@ async def _finish_store_credential(
rev_reg_def: Optional[dict] = None,
) -> str:
credential_data = cred_recvd.to_dict()
schema_id = cred_recvd.schema_id
schema_id_parts = re.match(r"^(\w+):2:([^:]+):([^:]+)$", schema_id)
if not schema_id_parts:
raise AnonCredsHolderError(f"Error parsing credential schema ID: {schema_id}")
cred_def_id = cred_recvd.cred_def_id
cdef_id_parts = re.match(r"^(\w+):3:CL:([^:]+):([^:]+)$", cred_def_id)
if not cdef_id_parts:
raise AnonCredsHolderError(
f"Error parsing credential definition ID: {cred_def_id}"
)
registry = self.profile.inject(AnonCredsRegistry)
schema_info = await registry.get_schema_info_by_id(credential_data["schema_id"])

credential_id = credential_id or str(uuid4())
tags = {
"schema_id": schema_id,
"schema_issuer_did": schema_id_parts[1],
"schema_name": schema_id_parts[2],
"schema_version": schema_id_parts[3],
"issuer_did": cdef_id_parts[1],
"cred_def_id": cred_def_id,
"schema_id": credential_data["schema_id"],
"schema_issuer_id": schema_info["issuer_id"],
"schema_name": schema_info["name"],
"schema_version": schema_info["version"],
"issuer_id": credential_definition["issuerId"],
"cred_def_id": cred_recvd.cred_def_id,
"rev_reg_id": cred_recvd.rev_reg_id or "None",
}

Expand Down
13 changes: 12 additions & 1 deletion acapy_agent/anoncreds/models/credential_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class Meta:

def __init__(
self,
entropy: Optional[str] = None,
# For compatibility with credx agents, which uses `prover_did` instead of `entropy` # noqa
prover_did: Optional[str] = None,
cred_def_id: Optional[str] = None,
blinded_ms: Optional[Mapping] = None,
Expand All @@ -33,6 +35,7 @@ def __init__(
):
"""Initialize anoncreds credential request."""
super().__init__(**kwargs)
self.entropy = entropy
self.prover_did = prover_did
self.cred_def_id = cred_def_id
self.blinded_ms = blinded_ms
Expand All @@ -49,8 +52,16 @@ class Meta:
model_class = AnoncredsCredRequest
unknown = EXCLUDE

entropy = fields.Str(
required=False,
metadata={
"description": "Prover DID/Random String/UUID",
"example": UUID4_EXAMPLE,
},
)
# For compatibility with credx agents, which uses `prover_did` instead of `entropy`
prover_did = fields.Str(
required=True,
required=False,
metadata={
"description": "Prover DID/Random String/UUID",
"example": UUID4_EXAMPLE,
Expand Down
12 changes: 6 additions & 6 deletions acapy_agent/anoncreds/models/presentation_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class AnoncredsPresentationReqPredSpecSchema(OpenAPISchema):
fields.Dict(
keys=fields.Str(
validate=validate.Regexp(
"^schema_id|schema_issuer_did|schema_name|schema_version|issuer_did|"
"^schema_id|schema_issuer_id|schema_name|schema_version|issuer_id|"
"cred_def_id|attr::.+::value$"
),
metadata={"example": "cred_def_id"},
Expand All @@ -58,8 +58,8 @@ class AnoncredsPresentationReqPredSpecSchema(OpenAPISchema):
metadata={
"description": (
"If present, credential must satisfy one of given restrictions: specify"
" schema_id, schema_issuer_did, schema_name, schema_version,"
" issuer_did, cred_def_id, and/or attr::<attribute-name>::value where"
" schema_id, schema_issuer_id, schema_name, schema_version,"
" issuer_id, cred_def_id, and/or attr::<attribute-name>::value where"
" <attribute-name> represents a credential attribute name"
)
},
Expand Down Expand Up @@ -113,7 +113,7 @@ class AnoncredsPresentationReqAttrSpecSchema(OpenAPISchema):
fields.Dict(
keys=fields.Str(
validate=validate.Regexp(
"^schema_id|schema_issuer_did|schema_name|schema_version|issuer_did|"
"^schema_id|schema_issuer_id|schema_name|schema_version|issuer_id|"
"cred_def_id|attr::.+::value$"
),
metadata={"example": "cred_def_id"},
Expand All @@ -124,8 +124,8 @@ class AnoncredsPresentationReqAttrSpecSchema(OpenAPISchema):
metadata={
"description": (
"If present, credential must satisfy one of given restrictions: specify"
" schema_id, schema_issuer_did, schema_name, schema_version,"
" issuer_did, cred_def_id, and/or attr::<attribute-name>::value where"
" schema_id, schema_issuer_id, schema_name, schema_version,"
" issuer_id, cred_def_id, and/or attr::<attribute-name>::value where"
" <attribute-name> represents a credential attribute name"
)
},
Expand Down
5 changes: 5 additions & 0 deletions acapy_agent/anoncreds/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ async def get_credential_definition(
credential_definition_id,
)

async def get_schema_info_by_id(self, schema_id: str) -> dict:
"""Get a schema info from the registry."""
resolver = await self._resolver_for_identifier(schema_id)
return await resolver.get_schema_info_by_id(schema_id)

async def register_credential_definition(
self,
profile: Profile,
Expand Down
50 changes: 13 additions & 37 deletions acapy_agent/anoncreds/tests/test_holder.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,6 @@ def __init__(self, bad_schema=False, bad_cred_def=False):
self.schema_id = "Sc886XPwD1gDcHwmmLDeR2:2:degree schema:45.101.94"
self.cred_def_id = "Sc886XPwD1gDcHwmmLDeR2:3:CL:229975:faber.agent.degree_schema"

if bad_schema:
self.schema_id = "bad-schema-id"
if bad_cred_def:
self.cred_def_id = "bad-cred-def-id"

schema_id = "Sc886XPwD1gDcHwmmLDeR2:2:degree schema:45.101.94"
cred_def_id = "Sc886XPwD1gDcHwmmLDeR2:3:CL:229975:faber.agent.degree_schema"
rev_reg_id = None
Expand All @@ -72,15 +67,10 @@ def to_dict(self):


class MockCredReceivedW3C:
def __init__(self, bad_schema=False, bad_cred_def=False):
def __init__(self):
self.schema_id = "Sc886XPwD1gDcHwmmLDeR2:2:degree schema:45.101.94"
self.cred_def_id = "Sc886XPwD1gDcHwmmLDeR2:3:CL:229975:faber.agent.degree_schema"

if bad_schema:
self.schema_id = "bad-schema-id"
if bad_cred_def:
self.cred_def_id = "bad-cred-def-id"

def to_json_buffer(self):
return b"credential"

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


class MockCredential:
def __init__(self, bad_schema=False, bad_cred_def=False):
self.bad_schema = bad_schema
self.bad_cred_def = bad_cred_def
def __init__(self):
self.rev_reg_id = "rev-reg-id"
self.rev_reg_index = 0

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

def process(self, *args, **kwargs):
return MockCredReceived(self.bad_schema, self.bad_cred_def)
return MockCredReceived()


class MockW3Credential:
def __init__(self, bad_schema=False, bad_cred_def=False):
self.bad_schema = bad_schema
self.bad_cred_def = bad_cred_def

cred = mock.AsyncMock(auto_spec=W3cCredential)

def to_dict(self):
return MOCK_W3C_CRED

def process(self, *args, **kwargs):
return MockCredReceivedW3C(self.bad_schema, self.bad_cred_def)
return MockCredReceivedW3C()


class MockMasterSecret:
Expand Down Expand Up @@ -285,8 +269,6 @@ async def test_store_credential_fails_to_load_raises_x(self, mock_master_secret)
side_effect=[
MockCredential(),
MockCredential(),
MockCredential(bad_schema=True),
MockCredential(bad_cred_def=True),
],
)
async def test_store_credential(self, mock_load, mock_master_secret):
Expand All @@ -296,6 +278,9 @@ async def test_store_credential(self, mock_load, mock_master_secret):
commit=mock.CoroutineMock(return_value=None),
)
)
self.profile.context.injector.bind_instance(
AnonCredsRegistry, mock.MagicMock(AnonCredsRegistry, autospec=True)
)

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

# Test bad id's
with self.assertRaises(AnonCredsHolderError):
await self.holder.store_credential(
MOCK_CRED_DEF,
MOCK_PRES,
{"cred-req-meta": "cred-req-meta"},
)
with self.assertRaises(AnonCredsHolderError):
await self.holder.store_credential(
MOCK_CRED_DEF,
MOCK_CRED,
{"cred-req-meta": "cred-req-meta"},
)

@mock.patch.object(AnonCredsHolder, "get_master_secret", return_value="master-secret")
@mock.patch.object(
W3cCredential,
Expand Down Expand Up @@ -362,7 +333,9 @@ async def test_store_credential_w3c(
commit=mock.CoroutineMock(return_value=None),
)
)

self.profile.context.injector.bind_instance(
AnonCredsRegistry, mock.MagicMock(AnonCredsRegistry, autospec=True)
)
with mock.patch.object(jsonld, "expand", return_value=MagicMock()):
with mock.patch.object(JsonLdProcessor, "get_values", return_value=["type1"]):
result = await self.holder.store_credential_w3c(
Expand All @@ -384,6 +357,9 @@ async def test_store_credential_failed_trx(self, *_):
self.profile.transaction = mock.MagicMock(
side_effect=[AskarError(AskarErrorCode.UNEXPECTED, "test")]
)
self.profile.context.injector.bind_instance(
AnonCredsRegistry, mock.MagicMock(AnonCredsRegistry, autospec=True)
)

with self.assertRaises(AnonCredsHolderError):
await self.holder.store_credential(
Expand Down
5 changes: 5 additions & 0 deletions acapy_agent/indy/credx/issuer.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,11 @@ async def create_credential(
revoc = None
credential_revocation_id = None

# This is for compatibility with an anoncreds holder
if not credential_request.get("prover_did"):
credential_request["prover_did"] = credential_request["entropy"]
del credential_request["entropy"]

try:
(
credential,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
"nonce": "1234567890",
}
ANONCREDS_CRED_REQ = {
"prover_did": TEST_DID,
"entropy": TEST_DID,
"cred_def_id": CRED_DEF_ID,
"blinded_ms": {
"u": "12345",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2078,7 +2078,7 @@ async def test_receive_pres_bait_and_switch_pred(self):
"name": "highScore",
"p_type": ">=",
"p_value": 1000000,
"restrictions": [{"issuer_did": "FFFFFFFFFFFFFFFFFFFFFF"}], # fake issuer
"restrictions": [{"issuer_id": "FFFFFFFFFFFFFFFFFFFFFF"}], # fake issuer
"non_revoked": {"from": NOW, "to": NOW},
}
pres_proposal = V20PresProposal(
Expand Down

0 comments on commit e08c47f

Please sign in to comment.