Skip to content

Commit b7d27fa

Browse files
authored
Merge pull request openwallet-foundation#3279 from dbluhm/feature/vm-selection
More robust verification method selection by did
2 parents 04238e8 + f3e2706 commit b7d27fa

File tree

5 files changed

+116
-35
lines changed

5 files changed

+116
-35
lines changed

acapy_agent/protocols/present_proof/dif/pres_exch_handler.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@
4040
from ....vc.vc_di.prove import create_signed_anoncreds_presentation
4141
from ....vc.vc_ld.prove import create_presentation, derive_credential, sign_presentation
4242
from ....wallet.base import BaseWallet, DIDInfo
43-
from ....wallet.default_verification_key_strategy import BaseVerificationKeyStrategy
43+
from ....wallet.default_verification_key_strategy import (
44+
BaseVerificationKeyStrategy,
45+
ProofPurposeStr,
46+
)
4447
from ....wallet.error import WalletError, WalletNotFoundError
4548
from ....wallet.key_type import BLS12381G2, ED25519
4649
from .pres_exch import (
@@ -115,19 +118,19 @@ async def _get_issue_suite(
115118
self,
116119
*,
117120
issuer_id: str,
121+
proof_purpose: Optional[ProofPurposeStr] = None,
118122
):
119123
"""Get signature suite for signing presentation."""
124+
proof_purpose = proof_purpose or "assertionMethod"
120125
did_info = await self._did_info_for_did(issuer_id)
121-
verkey_id_strategy = self.profile.context.inject(BaseVerificationKeyStrategy)
122-
verification_method = await verkey_id_strategy.get_verification_method_id_for_did(
123-
issuer_id, self.profile, proof_purpose="assertionMethod"
126+
vm_id_strategy = self.profile.context.inject(BaseVerificationKeyStrategy)
127+
verification_method = await vm_id_strategy.get_verification_method_id_for_did(
128+
issuer_id,
129+
self.profile,
130+
proof_type=self.proof_type,
131+
proof_purpose=proof_purpose,
124132
)
125133

126-
if verification_method is None:
127-
raise DIFPresExchError(
128-
f"Unable to get retrieve verification method for did {issuer_id}"
129-
)
130-
131134
# Get signature class based on proof type
132135
SignatureClass = self.PROOF_TYPE_SIGNATURE_SUITE_MAPPING[self.proof_type]
133136

@@ -1302,8 +1305,9 @@ async def create_vp(
13021305
)
13031306
else:
13041307
vp = self.__add_dif_fields_to_vp(vp, submission_property)
1308+
assert issuer_id
13051309
issue_suite = await self._get_issue_suite(
1306-
issuer_id=issuer_id,
1310+
issuer_id=issuer_id, proof_purpose="authentication"
13071311
)
13081312
signed_vp = await sign_presentation(
13091313
presentation=vp,

acapy_agent/vc/vc_ld/manager.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -344,15 +344,13 @@ async def _get_suite_for_document(
344344
verification_method = (
345345
options.verification_method
346346
or await verkey_id_strategy.get_verification_method_id_for_did(
347-
issuer_id, self.profile, proof_purpose="assertionMethod"
347+
issuer_id,
348+
self.profile,
349+
proof_type=proof_type,
350+
proof_purpose="assertionMethod",
348351
)
349352
)
350353

351-
if verification_method is None:
352-
raise VcLdpManagerError(
353-
f"Unable to get retrieve verification method for did {issuer_id}"
354-
)
355-
356354
suite = await self._get_suite(
357355
proof_type=proof_type,
358356
verification_method=verification_method,
Lines changed: 92 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,35 @@
11
"""Utilities for specifying which verification method is in use for a given DID."""
22

33
from abc import ABC, abstractmethod
4-
from typing import List, Optional
4+
import logging
5+
from typing import Literal, Optional
56

6-
from acapy_agent.core.profile import Profile
7-
from acapy_agent.did.did_key import DIDKey
8-
from acapy_agent.wallet.key_type import KeyType
7+
from pydid import DIDDocument
8+
9+
from ..core.error import BaseError
10+
from ..core.profile import Profile
11+
from ..did.did_key import DIDKey
12+
from ..resolver.did_resolver import DIDResolver
13+
14+
LOGGER = logging.getLogger(__name__)
15+
16+
17+
ProofPurposeStr = Literal[
18+
"assertionMethod",
19+
"authentication",
20+
"capabilityDelegation",
21+
"capabilityInvocation",
22+
]
23+
PROOF_PURPOSES = (
24+
"authentication",
25+
"assertionMethod",
26+
"capabilityInvocation",
27+
"capabilityDelegation",
28+
)
29+
30+
31+
class VerificationKeyStrategyError(BaseError):
32+
"""Raised on issues with verfication method derivation."""
933

1034

1135
class BaseVerificationKeyStrategy(ABC):
@@ -15,10 +39,11 @@ class BaseVerificationKeyStrategy(ABC):
1539
async def get_verification_method_id_for_did(
1640
self,
1741
did: str,
18-
profile: Optional[Profile],
19-
allowed_verification_method_types: Optional[List[KeyType]] = None,
20-
proof_purpose: Optional[str] = None,
21-
) -> Optional[str]:
42+
profile: Profile,
43+
*,
44+
proof_type: Optional[str] = None,
45+
proof_purpose: Optional[ProofPurposeStr] = None,
46+
) -> str:
2247
"""Given a DID, returns the verification key ID in use.
2348
2449
Returns None if no strategy is specified for this DID.
@@ -29,7 +54,7 @@ async def get_verification_method_id_for_did(
2954
:params proof_purpose: the verkey relationship (assertionMethod, keyAgreement, ..)
3055
:returns Optional[str]: the current verkey ID
3156
"""
32-
pass
57+
...
3358

3459

3560
class DefaultVerificationKeyStrategy(BaseVerificationKeyStrategy):
@@ -38,13 +63,21 @@ class DefaultVerificationKeyStrategy(BaseVerificationKeyStrategy):
3863
Supports did:key: and did:sov only.
3964
"""
4065

66+
def __init__(self):
67+
"""Initialize the key types mapping."""
68+
self.key_types_mapping = {
69+
"Ed25519Signature2018": ["Ed25519VerificationKey2018"],
70+
"Ed25519Signature2020": ["Ed25519VerificationKey2020", "Multikey"],
71+
}
72+
4173
async def get_verification_method_id_for_did(
4274
self,
4375
did: str,
44-
profile: Optional[Profile],
45-
allowed_verification_method_types: Optional[List[KeyType]] = None,
46-
proof_purpose: Optional[str] = None,
47-
) -> Optional[str]:
76+
profile: Profile,
77+
*,
78+
proof_type: Optional[str] = None,
79+
proof_purpose: Optional[ProofPurposeStr] = None,
80+
) -> str:
4881
"""Given a did:key or did:sov, returns the verification key ID in use.
4982
5083
Returns None if no strategy is specified for this DID.
@@ -55,10 +88,55 @@ async def get_verification_method_id_for_did(
5588
:params proof_purpose: the verkey relationship (assertionMethod, keyAgreement, ..)
5689
:returns Optional[str]: the current verkey ID
5790
"""
91+
proof_type = proof_type or "Ed25519Signature2018"
92+
proof_purpose = proof_purpose or "assertionMethod"
93+
94+
if proof_purpose not in PROOF_PURPOSES:
95+
raise ValueError("Invalid proof purpose")
96+
5897
if did.startswith("did:key:"):
5998
return DIDKey.from_did(did).key_id
6099
elif did.startswith("did:sov:"):
61100
# key-1 is what uniresolver uses for key id
62101
return did + "#key-1"
63102

64-
return None
103+
resolver = profile.inject(DIDResolver)
104+
doc_raw = await resolver.resolve(profile=profile, did=did)
105+
doc = DIDDocument.deserialize(doc_raw)
106+
107+
methods_or_refs = getattr(doc, proof_purpose, [])
108+
# Dereference any refs in the verification relationship
109+
methods = [
110+
await resolver.dereference_verification_method(profile, method, document=doc)
111+
if isinstance(method, str)
112+
else method
113+
for method in methods_or_refs
114+
]
115+
116+
method_types = self.key_types_mapping.get(proof_type)
117+
if not method_types:
118+
raise VerificationKeyStrategyError(
119+
f"proof type {proof_type} is not supported"
120+
)
121+
122+
# Filter methods by type expected for proof_type
123+
methods = [vm for vm in methods if vm.type in method_types]
124+
if not methods:
125+
raise VerificationKeyStrategyError(
126+
f"No matching verification method found for did {did} with proof "
127+
f"type {proof_type} and purpose {proof_purpose}"
128+
)
129+
130+
if len(methods) > 1:
131+
LOGGER.info(
132+
(
133+
"More than 1 verification method matched for did %s with proof "
134+
"type %s and purpose %s; returning the first: %s"
135+
),
136+
did,
137+
proof_type,
138+
proof_purpose,
139+
methods[0].id,
140+
)
141+
142+
return methods[0].id

acapy_agent/wallet/jwt.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ async def jwt_sign(
6161
verification_method = await verkey_strat.get_verification_method_id_for_did(
6262
did, profile
6363
)
64-
if not verification_method:
65-
raise ValueError("Could not determine verification method from DID")
6664
else:
6765
# We look up keys by did for now
6866
did = DIDUrl.parse(verification_method).did

acapy_agent/wallet/tests/test_default_verification_key_strategy.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
from unittest import IsolatedAsyncioTestCase
2+
import pytest
3+
4+
from acapy_agent.resolver.did_resolver import DIDResolver
25

36
from ...did.did_key import DIDKey
47
from ...utils.testing import create_test_profile
@@ -13,6 +16,8 @@
1316
class TestDefaultVerificationKeyStrategy(IsolatedAsyncioTestCase):
1417
async def asyncSetUp(self) -> None:
1518
self.profile = await create_test_profile()
19+
resolver = DIDResolver()
20+
self.profile.context.injector.bind_instance(DIDResolver, resolver)
1621

1722
async def test_with_did_sov(self):
1823
strategy = DefaultVerificationKeyStrategy()
@@ -30,9 +35,7 @@ async def test_with_did_key(self):
3035

3136
async def test_unsupported_did_method(self):
3237
strategy = DefaultVerificationKeyStrategy()
33-
assert (
38+
with pytest.raises(Exception):
3439
await strategy.get_verification_method_id_for_did(
3540
"did:test:test", self.profile
3641
)
37-
is None
38-
)

0 commit comments

Comments
 (0)