diff --git a/acapy_agent/did/did_key.py b/acapy_agent/did/did_key.py index 2db750d139..2213e715a1 100644 --- a/acapy_agent/did/did_key.py +++ b/acapy_agent/did/did_key.py @@ -1,12 +1,14 @@ """DID Key class and resolver methods.""" -from ..vc.ld_proofs.constants import DID_V1_CONTEXT_URL +from typing import List, Optional +from ..vc.ld_proofs.constants import DID_V1_CONTEXT_URL, SECURITY_CONTEXT_MULTIKEY_URL from ..wallet.crypto import ed25519_pk_to_curve25519 from ..wallet.key_type import ( BLS12381G1, BLS12381G1G2, BLS12381G2, ED25519, + P256, X25519, KeyType, KeyTypes, @@ -276,7 +278,39 @@ def construct_did_key_ed25519(did_key: "DIDKey") -> dict: return did_doc -def construct_did_signature_key_base(*, id: str, key_id: str, verification_method: dict): +def construct_did_key_p256(did_key: "DIDKey") -> dict: + """Construct P256 did:key. + + Args: + did_key (DIDKey): did key instance to parse p256 did:key document from + + Returns: + dict: The p256 did:key did document + + """ + + did_doc = construct_did_signature_key_base( + id=did_key.did, + key_id=did_key.key_id, + verification_method={ + "id": did_key.key_id, + "type": "Multikey", + "controller": did_key.did, + "publicKeyMultibase": did_key.fingerprint, + }, + extra_context=[SECURITY_CONTEXT_MULTIKEY_URL], + ) + + return did_doc + + +def construct_did_signature_key_base( + *, + id: str, + key_id: str, + verification_method: dict, + extra_context: Optional[List[str]] = None, +): """Create base did key structure to use for most signature keys. May not be suitable for all did key types @@ -284,7 +318,7 @@ def construct_did_signature_key_base(*, id: str, key_id: str, verification_metho """ return { - "@context": DID_V1_CONTEXT_URL, + "@context": [DID_V1_CONTEXT_URL] + (extra_context or []), "id": id, "verificationMethod": [verification_method], "authentication": [key_id], @@ -298,6 +332,7 @@ def construct_did_signature_key_base(*, id: str, key_id: str, verification_metho DID_KEY_RESOLVERS = { ED25519: construct_did_key_ed25519, X25519: construct_did_key_x25519, + P256: construct_did_key_p256, BLS12381G2: construct_did_key_bls12381g2, BLS12381G1: construct_did_key_bls12381g1, BLS12381G1G2: construct_did_key_bls12381g1g2, diff --git a/acapy_agent/did/tests/test_did_key_p256.py b/acapy_agent/did/tests/test_did_key_p256.py new file mode 100644 index 0000000000..1cd6cdcb03 --- /dev/null +++ b/acapy_agent/did/tests/test_did_key_p256.py @@ -0,0 +1,61 @@ +from unittest import TestCase + +from ...wallet.key_type import P256 +from ...wallet.util import b58_to_bytes +from ..did_key import DID_KEY_RESOLVERS, DIDKey +from .test_dids import DID_P256_zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169 + +TEST_P256_BASE58_KEY = "23FF9c3MrW7NkEW6uNDvdSKQMJ4YFTBXNMEPytZfYeE33" +TEST_P256_FINGERPRINT = "zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169" +TEST_P256_DID = f"did:key:{TEST_P256_FINGERPRINT}" +TEST_P256_KEY_ID = f"{TEST_P256_DID}#{TEST_P256_FINGERPRINT}" +TEST_P256_PREFIX_BYTES = b"".join([b"\x80\x24", b58_to_bytes(TEST_P256_BASE58_KEY)]) + + +class TestDIDKey(TestCase): + def test_p256_from_public_key(self): + key_bytes = b58_to_bytes(TEST_P256_BASE58_KEY) + did_key = DIDKey.from_public_key(key_bytes, P256) + + assert did_key.did == TEST_P256_DID + + def test_p256_from_public_key_b58(self): + did_key = DIDKey.from_public_key_b58(TEST_P256_BASE58_KEY, P256) + + assert did_key.did == TEST_P256_DID + + def test_p256_from_fingerprint(self): + did_key = DIDKey.from_fingerprint(TEST_P256_FINGERPRINT) + + assert did_key.did == TEST_P256_DID + assert did_key.public_key_b58 == TEST_P256_BASE58_KEY + + def test_p256_from_did(self): + did_key = DIDKey.from_did(TEST_P256_DID) + + assert did_key.public_key_b58 == TEST_P256_BASE58_KEY + + def test_p256_properties(self): + did_key = DIDKey.from_did(TEST_P256_DID) + + assert did_key.fingerprint == TEST_P256_FINGERPRINT + assert did_key.did == TEST_P256_DID + assert did_key.public_key_b58 == TEST_P256_BASE58_KEY + assert did_key.public_key == b58_to_bytes(TEST_P256_BASE58_KEY) + assert did_key.key_type == P256 + assert did_key.key_id == TEST_P256_KEY_ID + assert did_key.prefixed_public_key == TEST_P256_PREFIX_BYTES + + def test_p256_diddoc(self): + did_key = DIDKey.from_did(TEST_P256_DID) + + resolver = DID_KEY_RESOLVERS[P256] + + assert resolver(did_key) == did_key.did_doc + + def test_p256_resolver(self): + did_key = DIDKey.from_did(TEST_P256_DID) + resolver = DID_KEY_RESOLVERS[P256] + did_doc = resolver(did_key) + + assert did_doc == DID_P256_zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169 diff --git a/acapy_agent/did/tests/test_dids.py b/acapy_agent/did/tests/test_dids.py index 9a1e246564..7c97da9217 100644 --- a/acapy_agent/did/tests/test_dids.py +++ b/acapy_agent/did/tests/test_dids.py @@ -1,5 +1,5 @@ DID_ED25519_z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th = { - "@context": "https://www.w3.org/ns/did/v1", + "@context": ["https://www.w3.org/ns/did/v1"], "id": "did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th", "verificationMethod": [ { @@ -51,8 +51,34 @@ ], } +DID_P256_zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169 = { + "@context": ["https://www.w3.org/ns/did/v1", "https://w3id.org/security/multikey/v1"], + "id": "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169", + "verificationMethod": [ + { + "id": "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169#zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169", + "type": "Multikey", + "controller": "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169", + "publicKeyMultibase": "zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169", + } + ], + "authentication": [ + "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169#zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169" + ], + "assertionMethod": [ + "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169#zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169" + ], + "capabilityDelegation": [ + "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169#zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169" + ], + "capabilityInvocation": [ + "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169#zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169" + ], + "keyAgreement": [], +} + DID_BLS12381G2_zUC71nmwvy83x1UzNKbZbS7N9QZx8rqpQx3Ee3jGfKiEkZngTKzsRoqobX6wZdZF5F93pSGYYco3gpK9tc53ruWUo2tkBB9bxPCFBUjq2th8FbtT4xih6y6Q1K9EL4Th86NiCGT = { - "@context": "https://www.w3.org/ns/did/v1", + "@context": ["https://www.w3.org/ns/did/v1"], "id": "did:key:zUC71nmwvy83x1UzNKbZbS7N9QZx8rqpQx3Ee3jGfKiEkZngTKzsRoqobX6wZdZF5F93pSGYYco3gpK9tc53ruWUo2tkBB9bxPCFBUjq2th8FbtT4xih6y6Q1K9EL4Th86NiCGT", "verificationMethod": [ { @@ -78,7 +104,7 @@ } DID_BLS12381G1_z3tEFALUKUzzCAvytMHX8X4SnsNsq6T5tC5Zb18oQEt1FqNcJXqJ3AA9umgzA9yoqPBeWA = { - "@context": "https://www.w3.org/ns/did/v1", + "@context": ["https://www.w3.org/ns/did/v1"], "id": "did:key:z3tEFALUKUzzCAvytMHX8X4SnsNsq6T5tC5Zb18oQEt1FqNcJXqJ3AA9umgzA9yoqPBeWA", "verificationMethod": [ { diff --git a/acapy_agent/utils/multiformats/multicodec.py b/acapy_agent/utils/multiformats/multicodec.py index 465d5b3ea2..7e2ed5da55 100644 --- a/acapy_agent/utils/multiformats/multicodec.py +++ b/acapy_agent/utils/multiformats/multicodec.py @@ -20,6 +20,7 @@ class SupportedCodecs(Enum): bls12381g2 = Multicodec("bls12_381-g2-pub", b"\xeb\x01") bls12381g1g2 = Multicodec("bls12_381-g1g2-pub", b"\xee\x01") secp256k1_pub = Multicodec("secp256k1-pub", b"\xe7\x01") + p256_pub = Multicodec("p256-pub", b"\x80\x24") @classmethod def by_name(cls, name: str) -> Multicodec: @@ -45,6 +46,7 @@ def for_data(cls, data: bytes) -> Multicodec: "bls12_381-g2-pub", "bls12_381-g1g2-pub", "secp256k1-pub", + "p256-pub", ] diff --git a/acapy_agent/vc/data_integrity/tests/test_cryptosuites.py b/acapy_agent/vc/data_integrity/tests/test_cryptosuites.py index 246693f29b..31e4090482 100644 --- a/acapy_agent/vc/data_integrity/tests/test_cryptosuites.py +++ b/acapy_agent/vc/data_integrity/tests/test_cryptosuites.py @@ -8,6 +8,7 @@ from acapy_agent.utils.testing import create_test_profile from acapy_agent.vc.data_integrity.cryptosuites import EddsaJcs2022 from acapy_agent.vc.data_integrity.models.options import DataIntegrityProofOptions +from acapy_agent.wallet.key_type import KeyTypes from acapy_agent.wallet.keys.manager import MultikeyManager @@ -34,6 +35,7 @@ async def asyncSetUp(self): self.resolver.register_resolver(WebDIDResolver()) self.profile = await create_test_profile() self.profile.context.injector.bind_instance(DIDResolver, self.resolver) + self.profile.context.injector.bind_instance(KeyTypes, KeyTypes()) try: async with self.profile.session() as session: await MultikeyManager(session=session).create(seed=self.seed) diff --git a/acapy_agent/vc/data_integrity/tests/test_manager.py b/acapy_agent/vc/data_integrity/tests/test_manager.py index c5b4d509ce..b47c1e5423 100644 --- a/acapy_agent/vc/data_integrity/tests/test_manager.py +++ b/acapy_agent/vc/data_integrity/tests/test_manager.py @@ -8,6 +8,7 @@ from acapy_agent.utils.testing import create_test_profile from acapy_agent.vc.data_integrity.manager import DataIntegrityManager from acapy_agent.vc.data_integrity.models.options import DataIntegrityProofOptions +from acapy_agent.wallet.key_type import KeyTypes from acapy_agent.wallet.keys.manager import MultikeyManager @@ -34,6 +35,7 @@ async def asyncSetUp(self): self.resolver.register_resolver(WebDIDResolver()) self.profile = await create_test_profile() self.profile.context.injector.bind_instance(DIDResolver, self.resolver) + self.profile.context.injector.bind_instance(KeyTypes, KeyTypes()) try: async with self.profile.session() as session: await MultikeyManager(session=session).create(seed=self.seed) diff --git a/acapy_agent/vc/ld_proofs/constants.py b/acapy_agent/vc/ld_proofs/constants.py index 2a4780f484..03af17dfc9 100644 --- a/acapy_agent/vc/ld_proofs/constants.py +++ b/acapy_agent/vc/ld_proofs/constants.py @@ -8,6 +8,7 @@ CREDENTIALS_CONTEXT_V1_URL = "https://www.w3.org/2018/credentials/v1" SECURITY_CONTEXT_BBS_URL = "https://w3id.org/security/bbs/v1" SECURITY_CONTEXT_ED25519_2020_URL = "https://w3id.org/security/suites/ed25519-2020/v1" +SECURITY_CONTEXT_MULTIKEY_URL = "https://w3id.org/security/multikey/v1" CREDENTIALS_ISSUER_URL = "https://www.w3.org/2018/credentials#issuer" SECURITY_PROOF_URL = "https://w3id.org/security#proof" diff --git a/acapy_agent/wallet/askar.py b/acapy_agent/wallet/askar.py index 69b5cb109e..4be4ce29e4 100644 --- a/acapy_agent/wallet/askar.py +++ b/acapy_agent/wallet/askar.py @@ -20,7 +20,7 @@ from .did_method import SOV, DIDMethod, DIDMethods from .did_parameters_validation import DIDParametersValidation from .error import WalletDuplicateError, WalletError, WalletNotFoundError -from .key_type import BLS12381G2, ED25519, X25519, KeyType, KeyTypes +from .key_type import BLS12381G2, ED25519, P256, X25519, KeyType, KeyTypes from .util import b58_to_bytes, bytes_to_b58 CATEGORY_DID = "did" @@ -185,18 +185,22 @@ async def get_signing_key(self, verkey: str) -> KeyInfo: if not verkey: raise WalletNotFoundError("No key identifier provided") - key = await self._session.handle.fetch_key(verkey) - if not key: + key_entry = await self._session.handle.fetch_key(verkey) + if not key_entry: raise WalletNotFoundError("Unknown key: {}".format(verkey)) - metadata = json.loads(key.metadata or "{}") + metadata = json.loads(key_entry.metadata or "{}") try: - kid = key.tags.get("kid") + kid = key_entry.tags.get("kid") except Exception: kid = None - # FIXME implement key types - return KeyInfo(verkey=verkey, metadata=metadata, key_type=ED25519, kid=kid) + key = cast(Key, key_entry.key) + key_types = self.session.inject(KeyTypes) + key_type = key_types.from_key_type(key.algorithm.value) + if not key_type: + raise WalletError(f"Unknown key type {key.algorithm.value}") + return KeyInfo(verkey=verkey, metadata=metadata, key_type=key_type, kid=kid) async def replace_signing_key_metadata(self, verkey: str, metadata: dict): """Replace the metadata associated with a signing keypair. @@ -775,6 +779,12 @@ async def verify_message( return pk.verify_signature(message, signature) except AskarError as err: raise WalletError("Exception when verifying message signature") from err + elif key_type == P256: + try: + pk = Key.from_public_bytes(KeyAlg.P256, verkey) + return pk.verify_signature(message, signature) + except AskarError as err: + raise WalletError("Exception when verifying message signature") from err # other key types are currently verified outside of Askar return verify_signed_message( @@ -869,6 +879,8 @@ def _create_keypair(key_type: KeyType, seed: Union[str, bytes, None] = None) -> elif key_type == X25519: alg = KeyAlg.X25519 method = None + elif key_type == P256: + alg = KeyAlg.P256 elif key_type == BLS12381G2: alg = KeyAlg.BLS12_381_G2 method = SeedMethod.BlsKeyGen @@ -878,7 +890,7 @@ def _create_keypair(key_type: KeyType, seed: Union[str, bytes, None] = None) -> raise WalletError(f"Unsupported key algorithm: {key_type}") if seed: try: - if key_type == ED25519: + if key_type in (ED25519, P256): # not a seed - it is the secret key seed = validate_seed(seed) return Key.from_secret_bytes(alg, seed) @@ -886,6 +898,9 @@ def _create_keypair(key_type: KeyType, seed: Union[str, bytes, None] = None) -> return Key.from_seed(alg, seed, method=method) except AskarError as err: if err.code == AskarErrorCode.INPUT: - raise WalletError("Invalid seed for key generation") from None + raise WalletError("Invalid seed for key generation") from err + else: + LOGGER.error(f"Unhandled Askar error code: {err.code}") + raise else: return Key.generate(alg) diff --git a/acapy_agent/wallet/did_method.py b/acapy_agent/wallet/did_method.py index d117d563ab..3123349945 100644 --- a/acapy_agent/wallet/did_method.py +++ b/acapy_agent/wallet/did_method.py @@ -4,7 +4,7 @@ from typing import Dict, List, Mapping, Optional from .error import BaseError -from .key_type import BLS12381G2, ED25519, X25519, KeyType +from .key_type import BLS12381G2, ED25519, P256, X25519, KeyType class HolderDefinedDid(Enum): @@ -67,7 +67,7 @@ def holder_defined_did(self) -> HolderDefinedDid: ) KEY = DIDMethod( name="key", - key_types=[ED25519, BLS12381G2], + key_types=[ED25519, P256, BLS12381G2], rotation=False, ) WEB = DIDMethod( diff --git a/acapy_agent/wallet/jwt.py b/acapy_agent/wallet/jwt.py index 33edd22593..c7f6cacbdb 100644 --- a/acapy_agent/wallet/jwt.py +++ b/acapy_agent/wallet/jwt.py @@ -2,22 +2,25 @@ import json import logging -from typing import Any, Mapping, Optional +from typing import Any, Mapping, Optional, Tuple from marshmallow import fields from pydid import DIDUrl, Resource, VerificationMethod +from pydid.verification_method import Ed25519VerificationKey2018, Multikey + +from acapy_agent.wallet.keys.manager import key_type_from_multikey, multikey_to_verkey from ..core.profile import Profile from ..messaging.jsonld.error import BadJWSHeaderError, InvalidVerificationMethod -from ..messaging.jsonld.routes import SUPPORTED_VERIFICATION_METHOD_TYPES from ..messaging.models.base import BaseModel, BaseModelSchema from ..resolver.did_resolver import DIDResolver from .base import BaseWallet from .default_verification_key_strategy import BaseVerificationKeyStrategy -from .key_type import ED25519 +from .key_type import ED25519, KeyType from .util import b64_to_bytes, bytes_to_b64 LOGGER = logging.getLogger(__name__) +SUPPORTED_JWT_ALGS = ("EdDSA", "ES256") def dict_to_b64(value: Mapping[str, Any]) -> str: @@ -67,20 +70,25 @@ async def jwt_sign( if not did: raise ValueError("DID URL must be absolute") - if not headers.get("typ", None): - headers["typ"] = "JWT" - headers = { - **headers, - "alg": "EdDSA", - "kid": verification_method, - } - encoded_headers = dict_to_b64(headers) - encoded_payload = dict_to_b64(payload) - async with profile.session() as session: wallet = session.inject(BaseWallet) - LOGGER.info(f"jwt sign: {did}") did_info = await wallet.get_local_did(did_lookup_name(did)) + + header_alg = did_info.key_type.jws_algorithm + if not header_alg: + raise ValueError(f"DID key type '{did_info.key_type}' cannot be used for JWS") + + if not headers.get("typ", None): + headers["typ"] = "JWT" + headers = { + **headers, + "alg": header_alg, + "kid": verification_method, + } + encoded_headers = dict_to_b64(headers) + encoded_payload = dict_to_b64(payload) + + LOGGER.info(f"jwt sign: {did}") sig_bytes = await wallet.sign_message( f"{encoded_headers}.{encoded_payload}".encode(), did_info.verkey ) @@ -130,8 +138,10 @@ class Meta: error = fields.Str(required=False, metadata={"description": "Error text"}) -async def resolve_public_key_by_kid_for_verify(profile: Profile, kid: str) -> str: - """Resolve public key material from a kid.""" +async def resolve_public_key_by_kid_for_verify( + profile: Profile, kid: str +) -> Tuple[str, KeyType]: + """Resolve public key verkey (base58 public key) and key type from a kid.""" resolver = profile.inject(DIDResolver) vmethod: Resource = await resolver.dereference( profile, @@ -143,33 +153,51 @@ async def resolve_public_key_by_kid_for_verify(profile: Profile, kid: str) -> st "Dereferenced resource is not a verification method" ) - if not isinstance(vmethod, SUPPORTED_VERIFICATION_METHOD_TYPES): - raise InvalidVerificationMethod( - f"Dereferenced method {type(vmethod).__name__} is not supported" - ) + if isinstance(vmethod, Ed25519VerificationKey2018): + verkey = vmethod.public_key_base58 + ktyp = ED25519 + return (verkey, ktyp) + + if isinstance(vmethod, Multikey): + multikey = vmethod.public_key_multibase + verkey = multikey_to_verkey(multikey) + ktyp = key_type_from_multikey(multikey=multikey) + return (verkey, ktyp) - return vmethod.material + # unsupported + raise InvalidVerificationMethod( + f"Dereferenced method {type(vmethod).__name__} is not supported" + ) async def jwt_verify(profile: Profile, jwt: str) -> JWTVerifyResult: """Verify a JWT and return the headers and payload.""" encoded_headers, encoded_payload, encoded_signature = jwt.split(".", 3) headers = b64_to_dict(encoded_headers) - if "alg" not in headers or headers["alg"] != "EdDSA" or "kid" not in headers: - raise BadJWSHeaderError("Invalid JWS header parameters for Ed25519Signature2018.") + if "alg" not in headers: + raise BadJWSHeaderError("Missing 'alg' parameter in JWS header") + if "kid" not in headers: + raise BadJWSHeaderError("Missing 'kid' parameter in JWS header") + if headers["alg"] not in SUPPORTED_JWT_ALGS: + raise BadJWSHeaderError( + f"Unsupported 'alg' value in JWS header: '{headers['alg']}'. " + f"Supported algorithms: {', '.join(SUPPORTED_JWT_ALGS)}" + ) payload = b64_to_dict(encoded_payload) verification_method = headers["kid"] decoded_signature = b64_to_bytes(encoded_signature, urlsafe=True) async with profile.session() as session: - verkey = await resolve_public_key_by_kid_for_verify(profile, verification_method) + (verkey, ktyp) = await resolve_public_key_by_kid_for_verify( + profile, verification_method + ) wallet = session.inject(BaseWallet) valid = await wallet.verify_message( f"{encoded_headers}.{encoded_payload}".encode(), decoded_signature, - verkey, - ED25519, + from_verkey=verkey, + key_type=ktyp, ) return JWTVerifyResult(headers, payload, valid, verification_method) diff --git a/acapy_agent/wallet/key_type.py b/acapy_agent/wallet/key_type.py index 9e16bb1dde..08ae92d336 100644 --- a/acapy_agent/wallet/key_type.py +++ b/acapy_agent/wallet/key_type.py @@ -6,11 +6,18 @@ class KeyType: """Key Type class.""" - def __init__(self, key_type: str, multicodec_name: str, multicodec_prefix: bytes): + def __init__( + self, + key_type: str, + multicodec_name: str, + multicodec_prefix: bytes, + jws_alg: Optional[str], + ): """Construct key type.""" self._type: str = key_type self._name: str = multicodec_name self._prefix: bytes = multicodec_prefix + self._jws_alg: bytes = jws_alg @property def key_type(self) -> str: @@ -27,15 +34,21 @@ def multicodec_prefix(self) -> bytes: """Get key type multicodec prefix.""" return self._prefix + @property + def jws_algorithm(self) -> Optional[str]: + """Get key type JWS Algorithm (used in the JOSE header).""" + return self._jws_alg + # NOTE: the py_multicodec library is outdated. We use hardcoded prefixes here # until this PR gets released: https://github.com/multiformats/py-multicodec/pull/14 # multicodec is also not used now, but may be used again if py_multicodec is updated -ED25519: KeyType = KeyType("ed25519", "ed25519-pub", b"\xed\x01") -X25519: KeyType = KeyType("x25519", "x25519-pub", b"\xec\x01") -BLS12381G1: KeyType = KeyType("bls12381g1", "bls12_381-g1-pub", b"\xea\x01") -BLS12381G2: KeyType = KeyType("bls12381g2", "bls12_381-g2-pub", b"\xeb\x01") -BLS12381G1G2: KeyType = KeyType("bls12381g1g2", "bls12_381-g1g2-pub", b"\xee\x01") +ED25519: KeyType = KeyType("ed25519", "ed25519-pub", b"\xed\x01", "EdDSA") +X25519: KeyType = KeyType("x25519", "x25519-pub", b"\xec\x01", None) +P256: KeyType = KeyType("p256", "p256-pub", b"\x80\x24", "ES256") +BLS12381G1: KeyType = KeyType("bls12381g1", "bls12_381-g1-pub", b"\xea\x01", None) +BLS12381G2: KeyType = KeyType("bls12381g2", "bls12_381-g2-pub", b"\xeb\x01", None) +BLS12381G1G2: KeyType = KeyType("bls12381g1g2", "bls12_381-g1g2-pub", b"\xee\x01", None) class KeyTypes: @@ -46,6 +59,7 @@ def __init__(self) -> None: self._type_registry: dict[str, KeyType] = { ED25519.key_type: ED25519, X25519.key_type: X25519, + P256.key_type: P256, BLS12381G1.key_type: BLS12381G1, BLS12381G2.key_type: BLS12381G2, BLS12381G1G2.key_type: BLS12381G1G2, @@ -53,6 +67,7 @@ def __init__(self) -> None: self._name_registry: dict[str, KeyType] = { ED25519.multicodec_name: ED25519, X25519.multicodec_name: X25519, + P256.multicodec_name: P256, BLS12381G1.multicodec_name: BLS12381G1, BLS12381G2.multicodec_name: BLS12381G2, BLS12381G1G2.multicodec_name: BLS12381G1G2, @@ -60,6 +75,7 @@ def __init__(self) -> None: self._prefix_registry: dict[bytes, KeyType] = { ED25519.multicodec_prefix: ED25519, X25519.multicodec_prefix: X25519, + P256.multicodec_prefix: P256, BLS12381G1.multicodec_prefix: BLS12381G1, BLS12381G2.multicodec_prefix: BLS12381G2, BLS12381G1G2.multicodec_prefix: BLS12381G1G2, diff --git a/acapy_agent/wallet/keys/manager.py b/acapy_agent/wallet/keys/manager.py index ba1094bf9f..bab88b3dcb 100644 --- a/acapy_agent/wallet/keys/manager.py +++ b/acapy_agent/wallet/keys/manager.py @@ -2,7 +2,7 @@ from ...core.profile import ProfileSession from ..base import BaseWallet -from ..key_type import ED25519 +from ..key_type import ED25519, P256, KeyType from ..util import b58_to_bytes, bytes_to_b58 from ...utils.multiformats import multibase from ...wallet.error import WalletNotFoundError @@ -15,20 +15,27 @@ "multikey_prefix": "z6Mk", "prefix_hex": "ed01", "prefix_length": 2, - } + }, + "p256": { + "key_type": P256, + "multikey_prefix": "zDn", + "prefix_hex": "8024", + "prefix_length": 2, + }, } -def multikey_to_verkey(multikey: str, alg: str = DEFAULT_ALG): +def multikey_to_verkey(multikey: str): """Transform multikey to verkey.""" + alg = key_type_from_multikey(multikey).key_type prefix_length = ALG_MAPPINGS[alg]["prefix_length"] public_bytes = bytes(bytearray(multibase.decode(multikey))[prefix_length:]) return bytes_to_b58(public_bytes) -def verkey_to_multikey(verkey: str, alg: str = DEFAULT_ALG): +def verkey_to_multikey(verkey: str, alg: str): """Transform verkey to multikey.""" prefix_hex = ALG_MAPPINGS[alg]["prefix_hex"] @@ -37,7 +44,7 @@ def verkey_to_multikey(verkey: str, alg: str = DEFAULT_ALG): return multibase.encode(bytes.fromhex(prefixed_key_hex), "base58btc") -def key_type_from_multikey(multikey: str): +def key_type_from_multikey(multikey: str) -> KeyType: """Derive key_type class from multikey prefix.""" for mapping in ALG_MAPPINGS: if multikey.startswith(ALG_MAPPINGS[mapping]["multikey_prefix"]): @@ -70,10 +77,12 @@ async def resolve_multikey_from_verification_method(self, kid: str): multikey = verification_method.public_key_multibase elif verification_method.type == "Ed25519VerificationKey2018": - multikey = verkey_to_multikey(verification_method.public_key_base58) + multikey = verkey_to_multikey( + verification_method.public_key_base58, alg="ed25519" + ) elif verification_method.type == "Ed25519VerificationKey2020": - multikey = verkey_to_multikey(verification_method.public_key_multibase) + multikey = verification_method.public_key_multibase else: raise MultikeyManagerError("Unknown verification method type.") @@ -107,7 +116,9 @@ async def from_kid(self, kid: str): return { "kid": key_info.kid, - "multikey": verkey_to_multikey(key_info.verkey), + "multikey": verkey_to_multikey( + key_info.verkey, alg=key_info.key_type.key_type + ), } async def from_multikey(self, multikey: str): @@ -117,7 +128,9 @@ async def from_multikey(self, multikey: str): return { "kid": key_info.kid, - "multikey": verkey_to_multikey(key_info.verkey), + "multikey": verkey_to_multikey( + key_info.verkey, alg=key_info.key_type.key_type + ), } async def create(self, seed: str = None, kid: str = None, alg: str = DEFAULT_ALG): @@ -136,7 +149,7 @@ async def create(self, seed: str = None, kid: str = None, alg: str = DEFAULT_ALG return { "kid": key_info.kid, - "multikey": verkey_to_multikey(key_info.verkey), + "multikey": verkey_to_multikey(key_info.verkey, alg=alg), } async def update(self, multikey: str, kid: str): @@ -151,5 +164,7 @@ async def update(self, multikey: str, kid: str): return { "kid": key_info.kid, - "multikey": verkey_to_multikey(key_info.verkey), + "multikey": verkey_to_multikey( + key_info.verkey, alg=key_info.key_type.key_type + ), } diff --git a/acapy_agent/wallet/keys/tests/test_key_operations.py b/acapy_agent/wallet/keys/tests/test_key_operations.py index 9bea8d54c2..6ec4264f66 100644 --- a/acapy_agent/wallet/keys/tests/test_key_operations.py +++ b/acapy_agent/wallet/keys/tests/test_key_operations.py @@ -13,9 +13,14 @@ class TestKeyOperations(IsolatedAsyncioTestCase): seed = "00000000000000000000000000000000" - multikey = "z6MkgKA7yrw5kYSiDuQFcye4bMaJpcfHFry3Bx45pdWh3s8i" - verkey = "2ru5PcgeQzxF7QZYwQgDkG2K13PRqyigVw99zMYg8eML" - kid = "did:web:example.com#key-01" + + ed25519_multikey = "z6MkgKA7yrw5kYSiDuQFcye4bMaJpcfHFry3Bx45pdWh3s8i" + ed25519_verkey = "2ru5PcgeQzxF7QZYwQgDkG2K13PRqyigVw99zMYg8eML" + ed25519_alg = "ed25519" + + p256_multikey = "zDnaeSd75MAwSRmem34MfZEzSMjQNcpWLmzkbF8Su49AuA9U2" + p256_verkey = "demmi97mhJ7JQu31git4hQz8a1PD1dETJH9TVKaynNQv" + p256_alg = "p256" async def asyncSetUp(self) -> None: self.profile = await create_test_profile() @@ -23,26 +28,40 @@ async def asyncSetUp(self) -> None: async def test_key_creation(self): async with self.profile.session() as session: - key_info = await MultikeyManager(session=session).create(seed=self.seed) - assert key_info["multikey"] == self.multikey - assert key_info["kid"] is None - - key_info = await MultikeyManager(session=session).from_multikey( - multikey=self.multikey - ) - assert key_info["multikey"] == self.multikey - assert key_info["kid"] is None - - key_info = await MultikeyManager(session=session).update( - multikey=self.multikey, kid=self.kid - ) - assert key_info["multikey"] == self.multikey - assert key_info["kid"] == self.kid - - key_info = await MultikeyManager(session=session).from_kid(kid=self.kid) - assert key_info["multikey"] == self.multikey - assert key_info["kid"] == self.kid + for i, (alg, expected_multikey) in enumerate( + [ + (self.ed25519_alg, self.ed25519_multikey), + (self.p256_alg, self.p256_multikey), + ] + ): + kid = f"did:web:example.com#key-0{i}" + + key_info = await MultikeyManager(session=session).create( + seed=self.seed, alg=alg + ) + assert key_info["multikey"] == expected_multikey + assert key_info["kid"] is None + + key_info = await MultikeyManager(session=session).from_multikey( + multikey=expected_multikey + ) + assert key_info["multikey"] == expected_multikey + assert key_info["kid"] is None + + key_info = await MultikeyManager(session=session).update( + multikey=expected_multikey, kid=kid + ) + assert key_info["multikey"] == expected_multikey + assert key_info["kid"] == kid + + key_info = await MultikeyManager(session=session).from_kid(kid=kid) + assert key_info["multikey"] == expected_multikey + assert key_info["kid"] == kid async def test_key_transformations(self): - assert multikey_to_verkey(self.multikey) == self.verkey - assert verkey_to_multikey(self.verkey) == self.multikey + for alg, multikey, verkey in [ + (self.ed25519_alg, self.ed25519_multikey, self.ed25519_verkey), + (self.p256_alg, self.p256_multikey, self.p256_verkey), + ]: + assert multikey_to_verkey(multikey) == verkey + assert verkey_to_multikey(verkey, alg=alg) == multikey diff --git a/acapy_agent/wallet/routes.py b/acapy_agent/wallet/routes.py index 0b33f1bd80..4e05a147fe 100644 --- a/acapy_agent/wallet/routes.py +++ b/acapy_agent/wallet/routes.py @@ -81,7 +81,7 @@ ) from .did_posture import DIDPosture from .error import WalletError, WalletNotFoundError -from .key_type import BLS12381G2, ED25519, KeyTypes +from .key_type import BLS12381G2, ED25519, P256, KeyTypes from .singletons import UpgradeInProgressSingleton from .util import EVENT_LISTENER_PATTERN @@ -328,7 +328,7 @@ class DIDListQueryStringSchema(OpenAPISchema): ) key_type = fields.Str( required=False, - validate=validate.OneOf([ED25519.key_type, BLS12381G2.key_type]), + validate=validate.OneOf([ED25519.key_type, BLS12381G2.key_type, P256.key_type]), metadata={"example": ED25519.key_type, "description": "Key type to query for."}, ) @@ -348,7 +348,7 @@ class DIDCreateOptionsSchema(OpenAPISchema): key_type = fields.Str( required=True, - validate=validate.OneOf([ED25519.key_type, BLS12381G2.key_type]), + validate=validate.OneOf([ED25519.key_type, BLS12381G2.key_type, P256.key_type]), metadata={ "example": ED25519.key_type, "description": ( @@ -1076,7 +1076,7 @@ async def wallet_set_did_endpoint(request: web.BaseRequest): return web.json_response({"txn": transaction.serialize()}) -@docs(tags=["wallet"], summary="Create a EdDSA jws using did keys with a given payload") +@docs(tags=["wallet"], summary="Create a jws using did keys with a given payload") @request_schema(JWSCreateSchema) @response_schema(WalletModuleResponseSchema(), description="") @tenant_authentication @@ -1114,9 +1114,7 @@ async def wallet_jwt_sign(request: web.BaseRequest): return web.json_response(jws) -@docs( - tags=["wallet"], summary="Create a EdDSA sd-jws using did keys with a given payload" -) +@docs(tags=["wallet"], summary="Create an sd-jws using did keys with a given payload") @request_schema(SDJWSCreateSchema) @response_schema(WalletModuleResponseSchema(), description="") @tenant_authentication @@ -1158,7 +1156,7 @@ async def wallet_sd_jwt_sign(request: web.BaseRequest): return web.json_response(sd_jws) -@docs(tags=["wallet"], summary="Verify a EdDSA jws using did keys with a given JWS") +@docs(tags=["wallet"], summary="Verify a jws using did keys with a given JWS") @request_schema(JWSVerifySchema()) @response_schema(JWSVerifyResponseSchema(), 200, description="") @tenant_authentication @@ -1200,7 +1198,7 @@ async def wallet_jwt_verify(request: web.BaseRequest): @docs( tags=["wallet"], - summary="Verify a EdDSA sd-jws using did keys with a given SD-JWS with " + summary="Verify an sd-jws using did keys with a given SD-JWS with " "optional key binding", ) @request_schema(SDJWSVerifySchema()) diff --git a/acapy_agent/wallet/tests/test_did_method.py b/acapy_agent/wallet/tests/test_did_method.py index 9777cef1dc..3e488ab29d 100644 --- a/acapy_agent/wallet/tests/test_did_method.py +++ b/acapy_agent/wallet/tests/test_did_method.py @@ -1,31 +1,50 @@ from unittest import TestCase -from ..key_type import BLS12381G1, BLS12381G1G2, BLS12381G2, ED25519, X25519, KeyTypes +from ..key_type import ( + BLS12381G1, + BLS12381G1G2, + BLS12381G2, + ED25519, + P256, + X25519, + KeyTypes, +) ED25519_PREFIX_BYTES = b"\xed\x01" BLS12381G1_PREFIX_BYTES = b"\xea\x01" BLS12381G1G2_PREFIX_BYTES = b"\xee\x01" BLS12381G2_PREFIX_BYTES = b"\xeb\x01" X25519_PREFIX_BYTES = b"\xec\x01" +P256_PREFIX_BYTES = b"\x80\x24" ED25519_KEY_NAME = "ed25519" X25519_KEY_NAME = "x25519" +P256_KEY_NAME = "p256" BLS12381G1_KEY_NAME = "bls12381g1" BLS12381G2_KEY_NAME = "bls12381g2" BLS12381G1G2_KEY_NAME = "bls12381g1g2" ED25519_MULTICODEC_NAME = "ed25519-pub" X25519_MULTICODEC_NAME = "x25519-pub" +P256_MULTICODEC_NAME = "p256-pub" BLS12381G1_MULTICODEC_NAME = "bls12_381-g1-pub" BLS12381G2_MULTICODEC_NAME = "bls12_381-g2-pub" BLS12381G1G2_MULTICODEC_NAME = "bls12_381-g1g2-pub" +ED25519_JWS_ALG = "EdDSA" +X25519_JWS_ALG = None +P256_JWS_ALG = "ES256" +BLS12381G1_JWS_ALG = None +BLS12381G2_JWS_ALG = None +BLS12381G1G2_JWS_ALG = None + class TestKeyType(TestCase): def test_from_multicodec_name(self): key_types = KeyTypes() assert key_types.from_multicodec_name(ED25519_MULTICODEC_NAME) == ED25519 assert key_types.from_multicodec_name(X25519_MULTICODEC_NAME) == X25519 + assert key_types.from_multicodec_name(P256_MULTICODEC_NAME) == P256 assert key_types.from_multicodec_name(BLS12381G1_MULTICODEC_NAME) == BLS12381G1 assert key_types.from_multicodec_name(BLS12381G2_MULTICODEC_NAME) == BLS12381G2 assert ( @@ -37,6 +56,7 @@ def test_from_key_type(self): key_types = KeyTypes() assert key_types.from_key_type(ED25519_KEY_NAME) == ED25519 assert key_types.from_key_type(X25519_KEY_NAME) == X25519 + assert key_types.from_key_type(P256_KEY_NAME) == P256 assert key_types.from_key_type(BLS12381G1_KEY_NAME) == BLS12381G1 assert key_types.from_key_type(BLS12381G2_KEY_NAME) == BLS12381G2 assert key_types.from_key_type(BLS12381G1G2_KEY_NAME) == BLS12381G1G2 @@ -46,6 +66,7 @@ def test_from_multicodec_prefix(self): key_types = KeyTypes() assert key_types.from_multicodec_prefix(ED25519_PREFIX_BYTES) == ED25519 assert key_types.from_multicodec_prefix(X25519_PREFIX_BYTES) == X25519 + assert key_types.from_multicodec_prefix(P256_PREFIX_BYTES) == P256 assert key_types.from_multicodec_prefix(BLS12381G1_PREFIX_BYTES) == BLS12381G1 assert key_types.from_multicodec_prefix(BLS12381G2_PREFIX_BYTES) == BLS12381G2 assert key_types.from_multicodec_prefix(BLS12381G1G2_PREFIX_BYTES) == BLS12381G1G2 @@ -65,6 +86,10 @@ def test_from_prefixed_bytes(self): ) == X25519 ) + assert ( + key_types.from_prefixed_bytes(b"".join([P256_PREFIX_BYTES, b"random-bytes"])) + == P256 + ) assert ( key_types.from_prefixed_bytes( b"".join([BLS12381G1_PREFIX_BYTES, b"random-bytes"]) @@ -94,3 +119,39 @@ def test_properties(self): assert key_type.key_type == ED25519_KEY_NAME assert key_type.multicodec_name == ED25519_MULTICODEC_NAME assert key_type.multicodec_prefix == ED25519_PREFIX_BYTES + assert key_type.jws_algorithm == ED25519_JWS_ALG + + key_type = X25519 + + assert key_type.key_type == X25519_KEY_NAME + assert key_type.multicodec_name == X25519_MULTICODEC_NAME + assert key_type.multicodec_prefix == X25519_PREFIX_BYTES + assert key_type.jws_algorithm == X25519_JWS_ALG + + key_type = P256 + + assert key_type.key_type == P256_KEY_NAME + assert key_type.multicodec_name == P256_MULTICODEC_NAME + assert key_type.multicodec_prefix == P256_PREFIX_BYTES + assert key_type.jws_algorithm == P256_JWS_ALG + + key_type = BLS12381G1 + + assert key_type.key_type == BLS12381G1_KEY_NAME + assert key_type.multicodec_name == BLS12381G1_MULTICODEC_NAME + assert key_type.multicodec_prefix == BLS12381G1_PREFIX_BYTES + assert key_type.jws_algorithm == BLS12381G1_JWS_ALG + + key_type = BLS12381G2 + + assert key_type.key_type == BLS12381G2_KEY_NAME + assert key_type.multicodec_name == BLS12381G2_MULTICODEC_NAME + assert key_type.multicodec_prefix == BLS12381G2_PREFIX_BYTES + assert key_type.jws_algorithm == BLS12381G2_JWS_ALG + + key_type = BLS12381G1G2 + + assert key_type.key_type == BLS12381G1G2_KEY_NAME + assert key_type.multicodec_name == BLS12381G1G2_MULTICODEC_NAME + assert key_type.multicodec_prefix == BLS12381G1G2_PREFIX_BYTES + assert key_type.jws_algorithm == BLS12381G1G2_JWS_ALG diff --git a/acapy_agent/wallet/tests/test_jwt.py b/acapy_agent/wallet/tests/test_jwt.py index 709477f88b..211acddaee 100644 --- a/acapy_agent/wallet/tests/test_jwt.py +++ b/acapy_agent/wallet/tests/test_jwt.py @@ -1,3 +1,4 @@ +from typing import Tuple from unittest import IsolatedAsyncioTestCase import pytest @@ -6,7 +7,7 @@ from ...resolver.tests.test_did_resolver import MockResolver from ...utils.testing import create_test_profile from ...wallet.did_method import KEY, DIDMethods -from ...wallet.key_type import ED25519, KeyTypes +from ...wallet.key_type import ED25519, P256, KeyType, KeyTypes from ..base import BaseWallet from ..default_verification_key_strategy import ( BaseVerificationKeyStrategy, @@ -19,85 +20,133 @@ class TestJWT(IsolatedAsyncioTestCase): """Tests for JWT sign and verify using dids.""" seed = "testseed000000000000000000000001" + did_key_ed25519_did = "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" + did_key_ed25519_verification_method = "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" + did_key_ed25519_doc = { + "@context": "https://www.w3.org/ns/did/v1", + "id": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", + "verificationMethod": [ + { + "id": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", + "type": "Ed25519VerificationKey2018", + "controller": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", + "publicKeyBase58": "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx", + } + ], + "authentication": [ + "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" + ], + "assertionMethod": [ + "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" + ], + "capabilityDelegation": [ + "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" + ], + "capabilityInvocation": [ + "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" + ], + "keyAgreement": [ + { + "id": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6LSbkodSr6SU2trs8VUgnrnWtSm7BAPG245ggrBmSrxbv1R", + "type": "X25519KeyAgreementKey2019", + "controller": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", + "publicKeyBase58": "5dTvYHaNaB7mk7iA9LqCJEHG2dGZQsvoi8WGzDRtYEf", + } + ], + } + did_key_p256_did = "did:key:zDnaehWviigWQQD7bqF3btFquwA5w8DX2sQwkVxnAyJ7oxdjq" + did_key_p256_verification_method = "did:key:zDnaehWviigWQQD7bqF3btFquwA5w8DX2sQwkVxnAyJ7oxdjq#zDnaehWviigWQQD7bqF3btFquwA5w8DX2sQwkVxnAyJ7oxdjq" + did_key_p256_doc = { + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/multikey/v1", + ], + "id": "did:key:zDnaehWviigWQQD7bqF3btFquwA5w8DX2sQwkVxnAyJ7oxdjq", + "verificationMethod": [ + { + "id": "did:key:zDnaehWviigWQQD7bqF3btFquwA5w8DX2sQwkVxnAyJ7oxdjq#zDnaehWviigWQQD7bqF3btFquwA5w8DX2sQwkVxnAyJ7oxdjq", + "type": "Multikey", + "controller": "did:key:zDnaehWviigWQQD7bqF3btFquwA5w8DX2sQwkVxnAyJ7oxdjq", + "publicKeyMultibase": "zDnaehWviigWQQD7bqF3btFquwA5w8DX2sQwkVxnAyJ7oxdjq", + } + ], + "authentication": [ + "did:key:zDnaehWviigWQQD7bqF3btFquwA5w8DX2sQwkVxnAyJ7oxdjq#zDnaehWviigWQQD7bqF3btFquwA5w8DX2sQwkVxnAyJ7oxdjq" + ], + "assertionMethod": [ + "did:key:zDnaehWviigWQQD7bqF3btFquwA5w8DX2sQwkVxnAyJ7oxdjq#zDnaehWviigWQQD7bqF3btFquwA5w8DX2sQwkVxnAyJ7oxdjq" + ], + "capabilityDelegation": [ + "did:key:zDnaehWviigWQQD7bqF3btFquwA5w8DX2sQwkVxnAyJ7oxdjq#zDnaehWviigWQQD7bqF3btFquwA5w8DX2sQwkVxnAyJ7oxdjq" + ], + "capabilityInvocation": [ + "did:key:zDnaehWviigWQQD7bqF3btFquwA5w8DX2sQwkVxnAyJ7oxdjq#zDnaehWviigWQQD7bqF3btFquwA5w8DX2sQwkVxnAyJ7oxdjq" + ], + "keyAgreement": [], + } async def asyncSetUp(self): self.profile = await create_test_profile() - self.resolver = DIDResolver() - self.resolver.register_resolver( - MockResolver( - ["key"], - resolved={ - "@context": "https://www.w3.org/ns/did/v1", - "id": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", - "verificationMethod": [ - { - "id": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", - "type": "Ed25519VerificationKey2018", - "controller": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", - "publicKeyBase58": "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx", - } - ], - "authentication": [ - "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" - ], - "assertionMethod": [ - "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" - ], - "capabilityDelegation": [ - "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" - ], - "capabilityInvocation": [ - "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" - ], - "keyAgreement": [ - { - "id": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6LSbkodSr6SU2trs8VUgnrnWtSm7BAPG245ggrBmSrxbv1R", - "type": "X25519KeyAgreementKey2019", - "controller": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", - "publicKeyBase58": "5dTvYHaNaB7mk7iA9LqCJEHG2dGZQsvoi8WGzDRtYEf", - } - ], - }, - native=True, - ) - ) self.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) self.profile.context.injector.bind_instance( BaseVerificationKeyStrategy, DefaultVerificationKeyStrategy() ) - self.profile.context.injector.bind_instance(DIDResolver, self.resolver) self.profile.context.injector.bind_instance(KeyTypes, KeyTypes()) - async def test_sign_with_did_key_and_verify(self): + async def setUpTestingDid(self, key_type: KeyType) -> Tuple[str, str]: async with self.profile.session() as session: wallet = session.inject(BaseWallet) - did_info = await wallet.create_local_did(KEY, ED25519, self.seed) - did = did_info.did - verification_method = None + await wallet.create_local_did(KEY, key_type, self.seed) + + if key_type == P256: + did = self.did_key_p256_did + vm_id = self.did_key_p256_verification_method + did_doc = self.did_key_p256_doc + elif key_type == ED25519: + did = self.did_key_ed25519_did + vm_id = self.did_key_ed25519_verification_method + did_doc = self.did_key_ed25519_doc + + resolver = DIDResolver() + resolver.register_resolver( + MockResolver( + ["key"], + resolved=did_doc, + native=True, + ) + ) + self.profile.context.injector.bind_instance(DIDResolver, resolver) - headers = {} - payload = {} - signed = await jwt_sign(self.profile, headers, payload, did, verification_method) + return (did, vm_id) - assert signed + async def test_sign_with_did_key_and_verify(self): + for key_type in [ED25519, P256]: + (did, _) = await self.setUpTestingDid(key_type) + verification_method = None + + headers = {} + payload = {} + signed = await jwt_sign( + self.profile, headers, payload, did, verification_method + ) - assert await jwt_verify(self.profile, signed) + assert signed + + assert await jwt_verify(self.profile, signed) async def test_sign_with_verification_method_and_verify(self): - async with self.profile.session() as session: - wallet = session.inject(BaseWallet) - await wallet.create_local_did(KEY, ED25519, self.seed) - did = None - verification_method = "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" - headers = {} - payload = {} - signed: str = await jwt_sign( - self.profile, headers, payload, did, verification_method - ) + for key_type in [ED25519, P256]: + (_, verification_method) = await self.setUpTestingDid(key_type) + did = None + headers = {} + payload = {} + signed: str = await jwt_sign( + self.profile, headers, payload, did, verification_method + ) - assert signed + assert signed - assert await jwt_verify(self.profile, signed) + assert await jwt_verify(self.profile, signed) async def test_sign_x_invalid_did(self): did = "did:key:zzzzgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" @@ -118,27 +167,36 @@ async def test_sign_x_invalid_verification_method(self): assert "Unknown DID" in str(e_info) async def test_verify_x_invalid_signed(self): - async with self.profile.session() as session: - wallet = session.inject(BaseWallet) - did_info = await wallet.create_local_did(KEY, ED25519, self.seed) - did = did_info.did - verification_method = None + for key_type in [ED25519, P256]: + (did, _) = await self.setUpTestingDid(key_type) + verification_method = None + + headers = {} + payload = {} + signed = await jwt_sign( + self.profile, headers, payload, did, verification_method + ) - headers = {} - payload = {} - signed = await jwt_sign(self.profile, headers, payload, did, verification_method) + assert signed + signed = f"{signed[:-2]}2" + + with pytest.raises(Exception): + await jwt_verify(self.profile, signed) - assert signed - signed = f"{signed[:-2]}2" + async def test_resolve_public_key_by_kid_for_verify_ed25519(self): + (_, kid) = await self.setUpTestingDid(ED25519) + (key_bs58, key_type) = await resolve_public_key_by_kid_for_verify( + self.profile, kid + ) - with pytest.raises(Exception): - await jwt_verify(self.profile, signed) + assert key_bs58 == "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx" + assert key_type == ED25519 - async def test_resolve_public_key_by_kid_for_verify(self): - async with self.profile.session() as session: - wallet = session.inject(BaseWallet) - await wallet.create_local_did(KEY, ED25519, self.seed) - kid = "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" - key_material = await resolve_public_key_by_kid_for_verify(self.profile, kid) + async def test_resolve_public_key_by_kid_for_verify_p256(self): + (_, kid) = await self.setUpTestingDid(P256) + (key_bs58, key_type) = await resolve_public_key_by_kid_for_verify( + self.profile, kid + ) - assert key_material == "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx" + assert key_bs58 == "tYbR5egjfja9D5ix1jjYGqfh5QPu73RcZ7UjQUXtargj" + assert key_type == P256