From 0ff69ce954a77ddef28a58f880874d78cc063312 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Tue, 6 Sep 2022 16:06:45 -0700 Subject: [PATCH] add resolution of DIDs using method 1 Signed-off-by: Andrew Whitehead --- peerdid/dids.py | 423 +++++++++++++++++------ peerdid/keys.py | 55 ++- tests/test_create_peer_did_numalgo_1.py | 34 +- tests/test_resolve_peer_did_numalgo_0.py | 40 ++- tests/test_resolve_peer_did_numalgo_1.py | 73 ++++ tests/test_resolve_peer_did_numalgo_2.py | 36 +- 6 files changed, 522 insertions(+), 139 deletions(-) create mode 100644 tests/test_resolve_peer_did_numalgo_1.py diff --git a/peerdid/dids.py b/peerdid/dids.py index 80fbae7..28770ff 100644 --- a/peerdid/dids.py +++ b/peerdid/dids.py @@ -3,16 +3,19 @@ import json import re +from enum import Enum from hashlib import sha256 -from typing import Optional, Sequence, Tuple, Union +from typing import List, Optional, Sequence, Tuple, Union from pydid import ( + deserialize_document, BaseDIDDocument, DID, - DIDDocument, DIDDocumentBuilder, DIDUrl, InvalidDIDError, + Service, + VerificationMethod, ) from .core.multibase import MultibaseFormat, to_multibase @@ -28,12 +31,238 @@ from .keys import KeyFormat, KeyRelationshipType, BaseKey PEER_DID_PATTERN = re.compile( - r"^did:peer:((0(z)([1-9a-km-zA-HJ-NP-Z]+))" - r"|(1(z)([1-9a-km-zA-HJ-NP-Z]+))" + r"^did:peer:(([01](z)([1-9a-km-zA-HJ-NP-Z]+))" r"|(2((\.[AEVID](z)([1-9a-km-zA-HJ-NP-Z]+))+(\.(S)[0-9a-zA-Z]*)?)))$" ) +class InceptionMethod(Enum): + """Supported peer DID inception methods.""" + + SingleKey = 0 + GenesisDocument = 1 + MultipleKeys = 2 + + +class ResolvedBasis: + """The resolved basis for a peer DID derived using methods 0 or 2. + + This includes the keys and potentially service information defined by the peer DID. + + Reference: + """ + + did: DID + method: InceptionMethod + keys: List[BaseKey] + services: List[Service] + stored: Optional["StoredDIDDocument"] + + def __init__( + self, + did: Union[str, DID], + keys: List[BaseKey] = None, + services: List[dict] = None, + ): + """Initializer.""" + self.did = DID(did) + self.keys = keys or [] + self.services = services or [] + + def did_doc(self) -> BaseDIDDocument: + """Convert this resolved basis into a DID Document.""" + try: + builder = DIDDocumentBuilder( + self.did, context=DIDDocumentBuilder.DEFAULT_CONTEXT.copy() + ) + except InvalidDIDError as e: + raise MalformedPeerDIDError("Invalid peer DID") from e + + for key in self.keys: + ver_method_result = key.verification_method(builder.id) + builder.verification_method.methods.append(ver_method_result.method) + ver_ident = DIDUrl.parse(ver_method_result.method.id) + if ( + ver_method_result.context + and ver_method_result.context not in builder.context + ): + builder.context.append(ver_method_result.context) + if KeyRelationshipType.AUTHENTICATION in key.relationships: + builder.authentication.reference(ver_ident) + builder.assertion_method.reference(ver_ident) + builder.capability_delegation.reference(ver_ident) + builder.capability_invocation.reference(ver_ident) + if KeyRelationshipType.KEY_AGREEMENT in key.relationships: + builder.key_agreement.reference(ver_ident) + + builder.service.services.extend(self.services) + + return builder.build() + + @property + def encryption_keys(self) -> List[BaseKey]: + """Accessor for the keys supporting key exchange.""" + return [ + key + for key in self.keys + if KeyRelationshipType.KEY_AGREEMENT in key.relationships + ] + + @property + def signing_keys(self) -> List[BaseKey]: + """Accessor for the keys supporting authentication.""" + return [ + key + for key in self.keys + if KeyRelationshipType.AUTHENTICATION in key.relationships + ] + + def _populate_numalgo_0(self, basis: str, key_format: KeyFormat = None): + decoded_key = decode_multibase_numbasis(basis, key_format) + self.keys.append(decoded_key) + + def _populate_numalgo_1( + self, + basis: Union[BaseDIDDocument, dict, str, bytes], + key_format: KeyFormat = None, + stored: "StoredDIDDocument" = None, + ): + if not stored: + stored = StoredDIDDocument.create(basis) + if self.did != "did:peer:1" + stored.encoded_numbasis: + raise MalformedPeerDIDError( + "Calculated numeric basis does not correspond with peer DID" + ) + if not isinstance(basis, BaseDIDDocument): + basis = deserialize_document(_load_did_document(basis)) + idents = set() + + print(basis.authentication) + print(basis.key_agreement) + + if basis.authentication: + for keyref in basis.authentication: + try: + key = _resolve_verification_method(basis, keyref, key_format) + except ValueError as e: + print(e) + # skip unrecognized keys + continue + if key.ident and key.ident not in idents: + self.keys.append(key) + idents.add(key.ident) + + if basis.key_agreement: + for keyref in basis.key_agreement: + try: + key = _resolve_verification_method(basis, keyref, key_format) + except ValueError as e: + print(e) + # skip unrecognized keys + continue + if key.ident and key.ident not in idents: + self.keys.append(key) + idents.add(key.ident) + + if basis.service: + for svc in basis.service: + if isinstance(svc, Service): + self.services.append(svc) + else: + self.services.append(Service.make(**svc)) + + def _populate_numalgo_2( + self, basis: str, key_format: KeyFormat = None + ) -> BaseDIDDocument: + keys = basis[1:].split(".") + + for key in keys: + if not key: + raise MalformedPeerDIDError("Blank key entry") + prefix = key[0] + if prefix == Numalgo2Prefix.SERVICE.value: + for svc in decode_service(key[1:]): + self.services.append(svc) + elif prefix == Numalgo2Prefix.AUTHENTICATION.value: + decoded_key = decode_multibase_numbasis(key[1:], key_format) + if KeyRelationshipType.AUTHENTICATION not in decoded_key.relationships: + raise MalformedPeerDIDError( + "Authentication not supported for key: {}.".format(key) + ) + self.keys.append(decoded_key) + elif prefix == Numalgo2Prefix.KEY_AGREEMENT.value: + decoded_key = decode_multibase_numbasis(key[1:], key_format) + if KeyRelationshipType.KEY_AGREEMENT not in decoded_key.relationships: + raise MalformedPeerDIDError( + "Key agreement not supported for key: {}.".format(key) + ) + self.keys.append(decoded_key) + else: + raise MalformedPeerDIDError("Unknown prefix: {}.".format(prefix)) + + def __repr__(self) -> str: + """Generate printable representation.""" + return "<{} did={}>".format(self.__class__.__name__, self.did) + + +class StoredDIDDocument(bytes): + """The stored variant of a DID Document. + + Reference: + """ + + @staticmethod + def __new__(cls, stored: Union["StoredDIDDocument", bytes]) -> "StoredDIDDocument": + """Class constructor.""" + if isinstance(stored, StoredDIDDocument): + return stored + elif isinstance(stored, bytes): + inst = super().__new__(cls, stored) + inst.__init__(stored) + return inst + else: + raise TypeError("Expected bytes for stored document") + + @classmethod + def create( + cls, did_doc: Union[BaseDIDDocument, dict, str, bytes] + ) -> "StoredDIDDocument": + """ + Create the stored variant of a DID Document. + + This format is used in generating the numeric basis for a peer DID using method 1. + Reference: + + :param did_doc: the genesis DID Document + :raises TypeError: if did_doc is of an unsupported type + :raises ValueError: if the DID Document cannot be processed + :return: a tuple of the generated Peer DID and the stored variant of the DID Document + """ + if isinstance(did_doc, BaseDIDDocument): + did_doc = did_doc.serialize() + else: + did_doc = _load_did_document(did_doc) + ident = did_doc.pop("id", None) + if ident: + # remove any references to the DID (in controller attributes for example) + pass + did_doc_bytes = json.dumps(did_doc, separators=(",", ":")).encode("utf-8") + return cls(did_doc_bytes) + + def expand(self, did: DID) -> BaseDIDDocument: + """Expand the stored DID document, populating the DID.""" + doc_dict = _load_did_document(self) + doc_dict["id"] = str(did) + return deserialize_document(doc_dict) + + @property + def encoded_numbasis(self) -> bytes: + """Calculate the encoded numeric basis of the prepared document.""" + doc_hash = sha256(self).digest() + numbasis = Codec.SHA256.encode_multicodec_with_length(doc_hash) + return to_multibase(numbasis, MultibaseFormat.BASE58) + + def is_peer_did(peer_did: Union[str, DID]) -> bool: """ Check if peer_did parameter matches the Peer DID spec. @@ -48,6 +277,22 @@ def is_peer_did(peer_did: Union[str, DID]) -> bool: return bool(PEER_DID_PATTERN.match(peer_did)) +def split_peer_did(peer_did: Union[str, DID]) -> Tuple[InceptionMethod, str]: + """ + Split a peer DID into its inception method and encoded numeric basis. + + Reference: + + :param peer_did: peer_did to split + :return: a tuple of the InceptionMethod and encoded numeric basis (including the transform) + """ + match = PEER_DID_PATTERN.match(peer_did or "") + if not match: + raise MalformedPeerDIDError("Not a recognizable peer DID") + body = match[1] + return (InceptionMethod(int(body[0])), body[1:]) + + def create_peer_did_numalgo_0( inception_key: BaseKey, ) -> str: @@ -69,8 +314,8 @@ def create_peer_did_numalgo_0( def create_peer_did_numalgo_1( - did_doc: Union[BaseDIDDocument, dict, str, bytes], -) -> Tuple[str, bytes]: + did_doc: Union[BaseDIDDocument, StoredDIDDocument, dict, str, bytes], +) -> str: """ Generate a Peer DID according to the first algorithm. @@ -81,24 +326,9 @@ def create_peer_did_numalgo_1( :raises ValueError: if the DID Document cannot be processed :return: a tuple of the generated Peer DID and the stored variant of the DID Document """ - if isinstance(did_doc, BaseDIDDocument): - did_doc = did_doc.serialize() - elif isinstance(did_doc, (str, bytes)): - try: - did_doc = json.loads(did_doc) - except json.JSONDecodeError as err: - raise ValueError("DID Document is not valid JSON") from err - if not isinstance(did_doc, dict): - raise TypeError("DID Document is not a supported type") - ident = did_doc.pop("id", None) - if ident: - # remove references to the did - pass - did_doc_bytes = json.dumps(did_doc, separators=(",", ":")).encode("utf-8") - did_doc_hash = sha256(did_doc_bytes).digest() - mcodec = Codec.SHA256.encode_multicodec_with_length(did_doc_hash) - did = "did:peer:1" + to_multibase(mcodec, MultibaseFormat.BASE58) - return (did, did_doc_bytes) + if not isinstance(did_doc, StoredDIDDocument): + did_doc = StoredDIDDocument.create(did_doc) + return "did:peer:1" + did_doc.encoded_numbasis def create_peer_did_numalgo_2( @@ -149,88 +379,83 @@ def create_peer_did_numalgo_2( def resolve_peer_did( peer_did: Union[str, DID], - format: KeyFormat = KeyFormat.MULTIBASE, -) -> DIDDocument: + key_format: KeyFormat = KeyFormat.MULTIBASE, + did_doc: Union[BaseDIDDocument, StoredDIDDocument, dict, str, bytes] = None, +) -> BaseDIDDocument: """ - Resolve a DID Document from a Peer DID. + Resolve and verify a DID Document from a Peer DID. :param peer_did: Peer DID to resolve - :param format: the format of public keys in the DID Document. Default format is multibase. + :param key_format: the format of public keys in the DID Document. Default format is multibase. + :param did_doc: a DID document or stored variant representing the basis. :raises MalformedPeerDIDError: if peer_did parameter does not match Peer DID spec :return: resolved DID Document as a JSON string """ - if not is_peer_did(peer_did): - raise MalformedPeerDIDError("Does not match peer DID regexp") - if peer_did[9] == "0": - did_doc = _build_did_doc_numalgo_0(peer_did, format) - else: - did_doc = _build_did_doc_numalgo_2(peer_did, format) - return did_doc + resolved = resolve_peer_did_basis(peer_did, key_format=key_format, did_doc=did_doc) + return resolved.did_doc() -def _did_document_builder(peer_did: Union[str, DID]) -> DIDDocumentBuilder: - try: - return DIDDocumentBuilder( - peer_did, context=DIDDocumentBuilder.DEFAULT_CONTEXT.copy() - ) - except InvalidDIDError as e: - raise MalformedPeerDIDError("Invalid peer DID") from e - - -def _add_key_to_document(builder: DIDDocumentBuilder, key: BaseKey): - ver_method_result = key.verification_method(builder.id) - builder.verification_method.methods.append(ver_method_result.method) - ver_ident = DIDUrl.parse(ver_method_result.method.id) - if ver_method_result.context and ver_method_result.context not in builder.context: - builder.context.append(ver_method_result.context) - for rel in key.relationships: - if rel == KeyRelationshipType.AUTHENTICATION: - builder.authentication.reference(ver_ident) - builder.assertion_method.reference(ver_ident) - builder.capability_delegation.reference(ver_ident) - builder.capability_invocation.reference(ver_ident) - elif rel == KeyRelationshipType.KEY_AGREEMENT: - builder.key_agreement.reference(ver_ident) - - -def _build_did_doc_numalgo_0( - peer_did: Union[str, DID], format: KeyFormat -) -> DIDDocument: - decoded_key = decode_multibase_numbasis(peer_did[10:], format) - builder = _did_document_builder(peer_did) - _add_key_to_document(builder, decoded_key) - return builder.build() - - -def _build_did_doc_numalgo_2( - peer_did: Union[str, DID], format: KeyFormat -) -> DIDDocument: - keys = peer_did[11:] - keys = keys.split(".") - builder = _did_document_builder(peer_did) - - for key in keys: - if not key: - raise MalformedPeerDIDError("Blank key entry") - prefix = key[0] - if prefix == Numalgo2Prefix.SERVICE.value: - for svc in decode_service(key[1:]): - builder.service.services.append(svc) - elif prefix == Numalgo2Prefix.AUTHENTICATION.value: - decoded_key = decode_multibase_numbasis(key[1:], format) - if KeyRelationshipType.AUTHENTICATION not in decoded_key.relationships: - raise MalformedPeerDIDError( - "Authentication not supported for key: {}.".format(key) - ) - _add_key_to_document(builder, decoded_key) - elif prefix == Numalgo2Prefix.KEY_AGREEMENT.value: - decoded_key = decode_multibase_numbasis(key[1:], format) - if KeyRelationshipType.KEY_AGREEMENT not in decoded_key.relationships: - raise MalformedPeerDIDError( - "Key agreement not supported for key: {}.".format(key) - ) - _add_key_to_document(builder, decoded_key) +def resolve_peer_did_basis( + peer_did: Union[str, DID], + key_format: KeyFormat = KeyFormat.MULTIBASE, + did_doc: Union[BaseDIDDocument, StoredDIDDocument, dict, str, bytes] = None, +) -> ResolvedBasis: + """ + Resolve a Peer DID to its keys and service entries. + + :param peer_did: Peer DID to resolve + :param key_format: the format of public keys in the DID Document. Default format is multibase. + :param did_doc: a DID document or stored variant representing the basis. + :raises MalformedPeerDIDError: if peer_did parameter does not match Peer DID spec + :return: resolved DID Document basis as a ResolvedBasis instance + """ + method, basis = split_peer_did(peer_did) + resolved = ResolvedBasis(peer_did) + if method == InceptionMethod.SingleKey: + resolved._populate_numalgo_0(basis, key_format) + elif method == InceptionMethod.GenesisDocument: + if did_doc is None: + raise MalformedPeerDIDError( + "DID document is required for resolving peer DID method 1" + ) + if isinstance(did_doc, StoredDIDDocument): + stored = did_doc + did_doc = StoredDIDDocument.expand(peer_did) else: - raise MalformedPeerDIDError("Unknown prefix: {}.".format(prefix)) + stored = StoredDIDDocument.create(did_doc) + resolved._populate_numalgo_1(did_doc, key_format, stored) + elif method == InceptionMethod.MultipleKeys: + resolved._populate_numalgo_2(basis, key_format) + else: + # should not be reachable + raise MalformedPeerDIDError("Unsupported inception method") + return resolved + + +def _load_did_document(did_doc: Union[dict, str, bytes]) -> dict: + if isinstance(did_doc, BaseDIDDocument): + return did_doc + + if isinstance(did_doc, (str, bytes)): + try: + did_doc = json.loads(did_doc) + except json.JSONDecodeError as err: + raise ValueError("DID Document is not valid JSON") from err + if not isinstance(did_doc, dict): + raise TypeError("DID Document is not of a supported type") + + return did_doc + - return builder.build() +def _resolve_verification_method( + doc: BaseDIDDocument, + keyref: Union[DIDUrl, VerificationMethod, dict], + key_format: KeyFormat = None, +) -> Optional[BaseKey]: + if isinstance(keyref, DIDUrl): + # may raise ValueError + method = doc.dereference_as(VerificationMethod, keyref) + elif isinstance(keyref, (dict, VerificationMethod)): + method = keyref + # may raise ValueError + return BaseKey.from_verification_method(method, format=key_format) diff --git a/peerdid/keys.py b/peerdid/keys.py index c0d158f..ee10e56 100644 --- a/peerdid/keys.py +++ b/peerdid/keys.py @@ -5,7 +5,7 @@ from typing import List, Optional, NamedTuple, Type, Union from uuid import uuid4 -from pydid import DID, DIDUrl, VerificationMethod +from pydid import DID, DIDUrl, InvalidDIDUrlError, VerificationMethod from pydid.verification_method import ( Ed25519VerificationKey2018, Ed25519VerificationKey2020, @@ -123,6 +123,55 @@ def from_jwk( key_type_cls = cls.for_codec(codec) return key_type_cls(public_key, ident=ident, format=format or KeyFormat.JWK) + @classmethod + def from_verification_method( + cls, + method: Union[dict, VerificationMethod], + ident: Union[str, DIDUrl] = None, + format: KeyFormat = None, + ) -> "BaseKey": + """Load a key from a DID Document verification method.""" + if isinstance(method, VerificationMethod): + method = method.serialize() + method_id = method.get("id") + if not ident and method_id: + try: + method_url = DIDUrl(method_id) + except InvalidDIDUrlError: + raise ValueError("Verification method ID is not a valid DID URL") + ident = DIDUrl("#" + method_url.fragment) + method_type = method.get("type") + if method_type == Ed25519VerificationKey2018.method_type(): + return Ed25519VerificationKey.from_base58( + method.get("publicKeyBase58"), ident=ident, format=format + ) + elif method_type == Ed25519VerificationKey2020.method_type(): + return Ed25519VerificationKey.from_multibase( + method.get("publicKeyMultibase"), ident=ident, format=format + ) + elif method_type == X25519KeyAgreementKey2019.method_type(): + return X25519KeyAgreementKey.from_base58( + method.get("publicKeyBase58"), ident=ident, format=format + ) + elif method_type == X25519KeyAgreementKey2020.method_type(): + return X25519KeyAgreementKey.from_multibase( + method.get("publicKeyMultibase"), ident=ident, format=format + ) + elif method_type == JsonWebKey2020.method_type(): + jwk = method.get("publicKeyJwk") + if isinstance(jwk, dict): + if jwk.get("typ") == "OKP": + crv = jwk.get("crv") + if crv == "Ed25519": + return Ed25519VerificationKey.from_jwk( + jwk, ident=ident, format=format + ) + elif crv == "X25519": + return X25519KeyAgreementKey.from_jwk( + jwk, ident=ident, format=format + ) + raise ValueError("Unsupported key type: {}".format(method_type)) + def __init__( self, public_key: bytes, @@ -151,6 +200,10 @@ def verification_method( ) -> VerificationMethodResult: """Generate a VerificationMethod entry for this key.""" + def to_base58(self) -> str: + """Encode this key in base58 format.""" + return to_base58(self.public_key) + def to_multibase(self, format: MultibaseFormat = None) -> str: """Encode this key in multibase format.""" return to_multibase(self.codec.encode_multicodec(self.public_key), format) diff --git a/tests/test_create_peer_did_numalgo_1.py b/tests/test_create_peer_did_numalgo_1.py index c2b292d..7e0f004 100644 --- a/tests/test_create_peer_did_numalgo_1.py +++ b/tests/test_create_peer_did_numalgo_1.py @@ -1,6 +1,6 @@ import json -from peerdid.dids import create_peer_did_numalgo_1, is_peer_did +from peerdid.dids import StoredDIDDocument, create_peer_did_numalgo_1, is_peer_did from .test_vectors import ( DID_DOC_NUMALGO_1, @@ -9,26 +9,30 @@ ) -def test_create_numalgo_1_from_str(): - (peer_did_algo_1, stored_doc) = create_peer_did_numalgo_1(did_doc=DID_DOC_NUMALGO_1) - assert peer_did_algo_1 == PEER_DID_NUMALGO_1 - assert stored_doc == DID_DOC_NUMALGO_1_STORED - assert is_peer_did(peer_did_algo_1) +def test_stored_did_document_from_str(): + stored = StoredDIDDocument.create(DID_DOC_NUMALGO_1) + assert stored == DID_DOC_NUMALGO_1_STORED -def test_create_numalgo_1_from_dict(): - did_doc = json.loads(DID_DOC_NUMALGO_1) - (peer_did_algo_1, stored_doc) = create_peer_did_numalgo_1(did_doc=did_doc) - assert peer_did_algo_1 == PEER_DID_NUMALGO_1 - assert stored_doc == DID_DOC_NUMALGO_1_STORED - assert is_peer_did(peer_did_algo_1) +def test_stored_did_document_from_bytes(): + stored = StoredDIDDocument.create(DID_DOC_NUMALGO_1.encode("utf-8")) + assert stored == DID_DOC_NUMALGO_1_STORED -def test_create_numalgo_1_from_doc(): +def test_stored_did_document_from_dict(): + stored = StoredDIDDocument.create(json.loads(DID_DOC_NUMALGO_1)) + assert stored == DID_DOC_NUMALGO_1_STORED + + +def test_stored_did_document_from_doc(): from pydid import deserialize_document did_doc = deserialize_document(json.loads(DID_DOC_NUMALGO_1)) - (peer_did_algo_1, stored_doc) = create_peer_did_numalgo_1(did_doc=did_doc) + stored = StoredDIDDocument.create(did_doc) + assert stored == DID_DOC_NUMALGO_1_STORED + + +def test_create_numalgo_1_positive(): + peer_did_algo_1 = create_peer_did_numalgo_1(did_doc=DID_DOC_NUMALGO_1) assert peer_did_algo_1 == PEER_DID_NUMALGO_1 - assert stored_doc == DID_DOC_NUMALGO_1_STORED assert is_peer_did(peer_did_algo_1) diff --git a/tests/test_resolve_peer_did_numalgo_0.py b/tests/test_resolve_peer_did_numalgo_0.py index 7ff86d3..a9bb0c7 100644 --- a/tests/test_resolve_peer_did_numalgo_0.py +++ b/tests/test_resolve_peer_did_numalgo_0.py @@ -2,8 +2,9 @@ from peerdid import DIDDocument from peerdid.errors import MalformedPeerDIDError -from peerdid.dids import resolve_peer_did -from peerdid.keys import KeyFormat +from peerdid.dids import resolve_peer_did, resolve_peer_did_basis +from peerdid.keys import Ed25519VerificationKey, KeyFormat + from tests.test_vectors import ( PEER_DID_NUMALGO_0, DID_DOC_NUMALGO_O_BASE58, @@ -12,30 +13,41 @@ ) -def test_resolve_positive_default(): +def test_resolve_numalgo_0_positive_default(): did_doc = resolve_peer_did(peer_did=PEER_DID_NUMALGO_0) assert did_doc == DIDDocument.from_json(DID_DOC_NUMALGO_O_MULTIBASE) -def test_resolve_positive_base58(): - did_doc = resolve_peer_did(peer_did=PEER_DID_NUMALGO_0, format=KeyFormat.BASE58) +def test_resolve_numalgo_0_basis(): + basis = resolve_peer_did_basis(peer_did=PEER_DID_NUMALGO_0) + assert not basis.encryption_keys + assert len(basis.signing_keys) == 1 and all( + isinstance(k, Ed25519VerificationKey) for k in basis.signing_keys + ) + assert not basis.services + + +def test_resolve_numalgo_0_positive_base58(): + did_doc = resolve_peer_did(peer_did=PEER_DID_NUMALGO_0, key_format=KeyFormat.BASE58) assert did_doc == DIDDocument.from_json(DID_DOC_NUMALGO_O_BASE58) -def test_resolve_positive_multibase(): - did_doc = resolve_peer_did(peer_did=PEER_DID_NUMALGO_0, format=KeyFormat.MULTIBASE) +def test_resolve_numalgo_0_positive_multibase(): + did_doc = resolve_peer_did( + peer_did=PEER_DID_NUMALGO_0, key_format=KeyFormat.MULTIBASE + ) assert did_doc == DIDDocument.from_json(DID_DOC_NUMALGO_O_MULTIBASE) -def test_resolve_positive_jwk(): - did_doc = resolve_peer_did(peer_did=PEER_DID_NUMALGO_0, format=KeyFormat.JWK) +def test_resolve_numalgo_0_positive_jwk(): + did_doc = resolve_peer_did(peer_did=PEER_DID_NUMALGO_0, key_format=KeyFormat.JWK) assert did_doc == DIDDocument.from_json(DID_DOC_NUMALGO_O_JWK) def test_resolve_unsupported_did_method(): with pytest.raises( MalformedPeerDIDError, - match=r"Invalid peer DID provided.*Does not match peer DID regexp", + match=r"Invalid peer DID provided.*Not a recognizable peer DID", ): resolve_peer_did( peer_did="did:key:0z6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V" @@ -45,7 +57,7 @@ def test_resolve_unsupported_did_method(): def test_resolve_invalid_peer_did(): with pytest.raises( MalformedPeerDIDError, - match=r"Invalid peer DID provided.*Does not match peer DID regexp", + match=r"Invalid peer DID provided.*Not a recognizable peer DID", ): resolve_peer_did( peer_did="did:peer:0z6MkqRYqQiSBytw86Qbs2ZWUkGv22od935YF4s8M7V!" @@ -55,7 +67,7 @@ def test_resolve_invalid_peer_did(): def test_resolve_unsupported_numalgo_code(): with pytest.raises( MalformedPeerDIDError, - match=r"Invalid peer DID provided.*Does not match peer DID regexp", + match=r"Invalid peer DID provided.*Not a recognizable peer DID", ): resolve_peer_did( peer_did="did:peer:4z6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V" @@ -65,7 +77,7 @@ def test_resolve_unsupported_numalgo_code(): def test_resolve_numalgo_0_malformed_base58_encoding(): with pytest.raises( MalformedPeerDIDError, - match=r"Invalid peer DID provided.*Does not match peer DID regexp", + match=r"Invalid peer DID provided.*Not a recognizable peer DID", ): resolve_peer_did( peer_did="did:peer:0z6MkqRYqQiSgvZQd0Bytw86Qbs2ZWUkGv22od935YF4s8M7V" @@ -75,7 +87,7 @@ def test_resolve_numalgo_0_malformed_base58_encoding(): def test_resolve_numalgo_0_unsupported_transform_code(): with pytest.raises( MalformedPeerDIDError, - match=r"Invalid peer DID provided.*Does not match peer DID regexp", + match=r"Invalid peer DID provided.*Not a recognizable peer DID", ): resolve_peer_did( peer_did="did:peer:0a6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V" diff --git a/tests/test_resolve_peer_did_numalgo_1.py b/tests/test_resolve_peer_did_numalgo_1.py new file mode 100644 index 0000000..f1071db --- /dev/null +++ b/tests/test_resolve_peer_did_numalgo_1.py @@ -0,0 +1,73 @@ +import pytest + +from pydid import Service + +from peerdid.errors import MalformedPeerDIDError +from peerdid.dids import resolve_peer_did, resolve_peer_did_basis +from peerdid.keys import Ed25519VerificationKey, X25519KeyAgreementKey + +from tests.test_vectors import ( + PEER_DID_NUMALGO_1, + DID_DOC_NUMALGO_1, +) + + +def test_resolve_numalgo_1_basis(): + basis = resolve_peer_did_basis( + peer_did=PEER_DID_NUMALGO_1, did_doc=DID_DOC_NUMALGO_1 + ) + assert len(basis.encryption_keys) == 1 and all( + isinstance(k, X25519KeyAgreementKey) for k in basis.encryption_keys + ) + assert len(basis.signing_keys) == 2 and all( + isinstance(k, Ed25519VerificationKey) for k in basis.signing_keys + ) + assert len(basis.services) == 1 and isinstance(basis.services[0], Service) + + +def test_resolve_numalgo_1_doc(): + doc = resolve_peer_did(peer_did=PEER_DID_NUMALGO_1, did_doc=DID_DOC_NUMALGO_1) + assert doc.id == PEER_DID_NUMALGO_1 + + +def test_resolve_numalgo_1_numbasis_mismatch(): + with pytest.raises( + MalformedPeerDIDError, + match=r"Invalid peer DID provided.*numeric basis does not correspond", + ): + resolve_peer_did( + peer_did="did:peer:1z6666RYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V", + did_doc=DID_DOC_NUMALGO_1, + ) + + +def test_resolve_numalgo_1_missing_document(): + with pytest.raises( + MalformedPeerDIDError, + match=r"DID document is required for resolving", + ): + resolve_peer_did( + peer_did=PEER_DID_NUMALGO_1, + ) + + +def test_resolve_numalgo_1_malformed_base58_encoding(): + with pytest.raises( + MalformedPeerDIDError, + match=r"Invalid peer DID provided.*Not a recognizable peer DID", + ): + resolve_peer_did( + peer_did="did:peer:1z6MkqRYqQiSgvZQd0Bytw86Qbs2ZWUkGv22od935YF4s8M7V", + did_doc=DID_DOC_NUMALGO_1, + ) + + +def test_resolve_numalgo_1_unsupported_transform_code(): + with pytest.raises( + MalformedPeerDIDError, + match=r"Invalid peer DID provided.*Not a recognizable peer DID", + ): + resolve_peer_did( + peer_did="did:peer:1a6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V", + did_doc=DID_DOC_NUMALGO_1, + ) diff --git a/tests/test_resolve_peer_did_numalgo_2.py b/tests/test_resolve_peer_did_numalgo_2.py index e52dc1a..8768157 100644 --- a/tests/test_resolve_peer_did_numalgo_2.py +++ b/tests/test_resolve_peer_did_numalgo_2.py @@ -1,9 +1,12 @@ import pytest +from pydid import Service + from peerdid import DIDDocument from peerdid.errors import MalformedPeerDIDError -from peerdid.dids import resolve_peer_did -from peerdid.keys import KeyFormat +from peerdid.dids import resolve_peer_did, resolve_peer_did_basis +from peerdid.keys import Ed25519VerificationKey, X25519KeyAgreementKey, KeyFormat + from tests.test_vectors import ( DID_DOC_NUMALGO_2_BASE58, PEER_DID_NUMALGO_2, @@ -24,17 +27,19 @@ def test_resolve_numalgo_2_positive_default(): def test_resolve_numalgo_2_positive_base58(): - did_doc = resolve_peer_did(peer_did=PEER_DID_NUMALGO_2, format=KeyFormat.BASE58) + did_doc = resolve_peer_did(peer_did=PEER_DID_NUMALGO_2, key_format=KeyFormat.BASE58) assert did_doc == DIDDocument.from_json(DID_DOC_NUMALGO_2_BASE58) def test_resolve_numalgo_2_positive_multibase(): - did_doc = resolve_peer_did(peer_did=PEER_DID_NUMALGO_2, format=KeyFormat.MULTIBASE) + did_doc = resolve_peer_did( + peer_did=PEER_DID_NUMALGO_2, key_format=KeyFormat.MULTIBASE + ) assert did_doc == DIDDocument.from_json(DID_DOC_NUMALGO_2_MULTIBASE) def test_resolve_numalgo_2_positive_jwk(): - did_doc = resolve_peer_did(peer_did=PEER_DID_NUMALGO_2, format=KeyFormat.JWK) + did_doc = resolve_peer_did(peer_did=PEER_DID_NUMALGO_2, key_format=KeyFormat.JWK) assert did_doc == DIDDocument.from_json(DID_DOC_NUMALGO_2_JWK) @@ -55,10 +60,21 @@ def test_resolve_numalgo_2_positive_minimal_service(): ) +def test_resolve_numalgo_2_basis(): + basis = resolve_peer_did_basis(peer_did=PEER_DID_NUMALGO_2) + assert len(basis.encryption_keys) == 1 and all( + isinstance(k, X25519KeyAgreementKey) for k in basis.encryption_keys + ) + assert len(basis.signing_keys) == 2 and all( + isinstance(k, Ed25519VerificationKey) for k in basis.signing_keys + ) + assert len(basis.services) == 1 and isinstance(basis.services[0], Service) + + def test_resolve_numalgo_2_unsupported_transform_code(): with pytest.raises( MalformedPeerDIDError, - match=r"Invalid peer DID provided.*Does not match peer DID regexp", + match=r"Invalid peer DID provided.*Not a recognizable peer DID", ): resolve_peer_did( "did:peer:2.Ea6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc" @@ -70,7 +86,7 @@ def test_resolve_numalgo_2_unsupported_transform_code(): def test_resolve_numalgo_2_signing_malformed_base58_encoding(): with pytest.raises( MalformedPeerDIDError, - match=r"Invalid peer DID provided.*Does not match peer DID regexp", + match=r"Invalid peer DID provided.*Not a recognizable peer DID", ): resolve_peer_did( "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc" @@ -82,7 +98,7 @@ def test_resolve_numalgo_2_signing_malformed_base58_encoding(): def test_resolve_numalgo_2_encryption_malformed_base58_encoding(): with pytest.raises( MalformedPeerDIDError, - match=r"Invalid peer DID provided.*Does not match peer DID regexp", + match=r"Invalid peer DID provided.*Not a recognizable peer DID", ): resolve_peer_did( "did:peer:2.Ez6LSbysY2xFMRpG0hb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc" @@ -140,7 +156,7 @@ def test_resolve_numalgo_2_encryption_invalid_key_type(): def test_resolve_numalgo_2_malformed_service_encoding(): with pytest.raises( MalformedPeerDIDError, - match=r"Invalid peer DID provided.*Does not match peer DID regexp", + match=r"Invalid peer DID provided.*Not a recognizable peer DID", ): resolve_peer_did( "did:peer:2" @@ -167,7 +183,7 @@ def test_resolve_numalgo_2_malformed_service(): def test_resolve_numalgo_2_invalid_prefix(): with pytest.raises( MalformedPeerDIDError, - match=r"Invalid peer DID provided.*Does not match peer DID regexp", + match=r"Invalid peer DID provided.*Not a recognizable peer DID", ): resolve_peer_did( "did:peer:2"