From 0d67aeeb4d70e6c68fe260e46905cc9adc26a68f Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Fri, 20 Oct 2023 12:59:23 -0400 Subject: [PATCH] feat: use multikey This addresses usage of `publicKeyMultibase` that was out of spec with what was intended in the DID Core spec. Namely, `publicKeyMultibase` only expresses the encoding of the value and does not necessarily include the multicodec binary header. Presence or absence of this header should be determined by the verification method type definition. [Multikey](https://www.w3.org/TR/vc-data-integrity/#multikey) accomplishes this goal and also cleans up some aspects of the spec and this implementation. Signed-off-by: Daniel Bluhm --- README.md | 97 ++++++++++++++++------------------------ did_peer_2.py | 35 ++++----------- tests/test_did_peer_2.py | 8 ++-- 3 files changed, 51 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index 7157a90..6278418 100644 --- a/README.md +++ b/README.md @@ -49,19 +49,18 @@ did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrG { "@context": [ "https://www.w3.org/ns/did/v1", - "https://w3id.org/security/suites/ed25519-2020/v1", - "https://w3id.org/security/suites/x25519-2020/v1" + "https://w3id.org/security/multikey/v1" ], "id": "did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9kaWRjb21tIiwiYSI6WyJkaWRjb21tL3YyIl0sInIiOlsiZGlkOmV4YW1wbGU6MTIzNDU2Nzg5YWJjZGVmZ2hpI2tleS0xIl19fQ", "verificationMethod": [ { - "type": "Ed25519VerificationKey2020", + "type": "Multikey", "id": "#key-1", "controller": "did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9kaWRjb21tIiwiYSI6WyJkaWRjb21tL3YyIl0sInIiOlsiZGlkOmV4YW1wbGU6MTIzNDU2Nzg5YWJjZGVmZ2hpI2tleS0xIl19fQ", "publicKeyMultibase": "z6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc" }, { - "type": "X25519KeyAgreementKey2020", + "type": "Multikey", "id": "#key-2", "controller": "did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9kaWRjb21tIiwiYSI6WyJkaWRjb21tL3YyIl0sInIiOlsiZGlkOmV4YW1wbGU6MTIzNDU2Nzg5YWJjZGVmZ2hpI2tleS0xIl19fQ", "publicKeyMultibase": "z6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR" @@ -87,6 +86,9 @@ did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrG }, "id": "#service" } + ], + "alsoKnownAs": [ + "did:peer:3zQmbRvRJgKBuubq8T9VBjrDPTmjb2Ed91f89ekW4gr6aZxa" ] } >>> # Let's derive the did:peer:3 @@ -117,7 +119,7 @@ service = 1*B64URL When generating a `did:peer:2`, take as inputs a set of keys and their purpose. Each key's purpose corresponds to the [Verification Relationship](https://www.w3.org/TR/did-core/#verification-relationships) it will hold in the DID Document generated from the DID. -Abstractly, these inputs may look like the following: +Abstractly, these inputs may look like the following (in this example, the keys are already multibase, multicodec encoded values): ```json [ @@ -134,7 +136,7 @@ Abstractly, these inputs may look like the following: To encode these keys: -* Construct a multibase, multicodec form of each public key to be included. +* Construct a [multibase](https://github.com/multiformats/multibase), [multicodec](https://github.com/multiformats/multicodec) form of each public key to be included. * Prefix each encoded key with a period character (.) and single character from the purpose codes table below. * Concatenate the prefixed encoded keys. The inputs above will result in: ``` @@ -236,13 +238,6 @@ did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrG | **S** | Service | -#### Multicodec Prefix Name to Verification Method Type (clarified) - -| Multicodec Prefix Name | Verification Method Type | -|------------------------------------|----------------------------| -| ed25519-pub | Ed25519VerificationKey2020 | -| x25519-pub | X25519KeyAgreementKey2020 | - ### Resolving a `did:peer:2` > [!NOTE] @@ -252,27 +247,19 @@ did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrG When Resolving the peer DID into a DID Document, the process is reversed: -* Start with an empty document with the DID Core context (clarified): +* Start with an empty document with the DID Core context and [Multikey context](https://www.w3.org/TR/vc-data-integrity/#multikey) (clarified): ```json { - "context": ["https://www.w3.org/ns/did/v1"] + "@context": ["https://www.w3.org/ns/did/v1", "https://w3id.org/security/multikey/v1"] } ``` - * If any of the keys in the DID are ed25519, add the following context: - ``` - https://w3id.org/security/suites/ed25519-2020/v1 - ``` - * If any of the keys in the DID are x25519, add the following context: - ``` - https://w3id.org/security/suites/x25519-2020/v1 - ``` * Set the `id` of the document to the DID being resolved. * Optionally, set the `alsoKnownAs` to the `did:peer:3` value corresponding to the DID being resolved. * Split the DID string into elements. * For each element with a purpose corresponding to a key, transform keys into verification methods in the DID Document: * Remove the period (.) and the Purpose prefix. Consider the remaining string the "encoded key." * Create an empty object. Consider this value the "verification method." - * Set the `type` of the verification method according to the multicodec prefix using the lookup table above (clarified). + * Set the `type` of the verification method to [`Multikey`](https://www.w3.org/TR/vc-data-integrity/#multikey) (clarified). * Set the `id` of the verification method to `#key-N` where `N` is an incrementing number starting at `1` (clarified). The keys MUST be processed in the order they appear in the DID string. For example, if you have the DID (whitespace added for example only): ``` did:peer:2 @@ -308,56 +295,48 @@ When Resolving the peer DID into a DID Document, the process is reversed: ```json { - "@context": "https://www.w3.org/ns/did/v1", + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/multikey/v1", + ], "id": "did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9kaWRjb21tIiwiYSI6WyJkaWRjb21tL3YyIl0sInIiOlsiZGlkOmV4YW1wbGU6MTIzNDU2Nzg5YWJjZGVmZ2hpI2tleS0xIl19fQ.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9hbm90aGVyIiwiYSI6WyJkaWRjb21tL3YyIl0sInIiOlsiZGlkOmV4YW1wbGU6MTIzNDU2Nzg5YWJjZGVmZ2hpI2tleS0yIl19fQ", "verificationMethod": [ - { - "id": "#key-1", - "controller": "did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9kaWRjb21tIiwiYSI6WyJkaWRjb21tL3YyIl0sInIiOlsiZGlkOmV4YW1wbGU6MTIzNDU2Nzg5YWJjZGVmZ2hpI2tleS0xIl19fQ.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9hbm90aGVyIiwiYSI6WyJkaWRjb21tL3YyIl0sInIiOlsiZGlkOmV4YW1wbGU6MTIzNDU2Nzg5YWJjZGVmZ2hpI2tleS0yIl19fQ", - "type": "Ed25519VerificationKey2020", - "publicKeyMultibase": "z6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc" - }, - { - "id": "#key-2", - "controller": "did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9kaWRjb21tIiwiYSI6WyJkaWRjb21tL3YyIl0sInIiOlsiZGlkOmV4YW1wbGU6MTIzNDU2Nzg5YWJjZGVmZ2hpI2tleS0xIl19fQ.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9hbm90aGVyIiwiYSI6WyJkaWRjb21tL3YyIl0sInIiOlsiZGlkOmV4YW1wbGU6MTIzNDU2Nzg5YWJjZGVmZ2hpI2tleS0yIl19fQ", - "type": "X25519KeyAgreementKey2020", - "publicKeyMultibase": "z6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR" - } - ], - "authentication": [ - "#key-1" - ], - "keyAgreement": [ - "#key-2" + { + "id": "#key-1", + "controller": "did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9kaWRjb21tIiwiYSI6WyJkaWRjb21tL3YyIl0sInIiOlsiZGlkOmV4YW1wbGU6MTIzNDU2Nzg5YWJjZGVmZ2hpI2tleS0xIl19fQ.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9hbm90aGVyIiwiYSI6WyJkaWRjb21tL3YyIl0sInIiOlsiZGlkOmV4YW1wbGU6MTIzNDU2Nzg5YWJjZGVmZ2hpI2tleS0yIl19fQ", + "type": "Multikey", + "publicKeyMultibase": "z6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc", + }, + { + "id": "#key-2", + "controller": "did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9kaWRjb21tIiwiYSI6WyJkaWRjb21tL3YyIl0sInIiOlsiZGlkOmV4YW1wbGU6MTIzNDU2Nzg5YWJjZGVmZ2hpI2tleS0xIl19fQ.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9hbm90aGVyIiwiYSI6WyJkaWRjb21tL3YyIl0sInIiOlsiZGlkOmV4YW1wbGU6MTIzNDU2Nzg5YWJjZGVmZ2hpI2tleS0yIl19fQ", + "type": "Multikey", + "publicKeyMultibase": "z6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR", + }, ], + "authentication": ["#key-1"], + "keyAgreement": ["#key-2"], "service": [ { "type": "DIDCommMessaging", "serviceEndpoint": { "uri": "http://example.com/didcomm", - "accept": [ - "didcomm/v2" - ], - "routingKeys": [ - "did:example:123456789abcdefghi#key-1" - ] + "accept": ["didcomm/v2"], + "routingKeys": ["did:example:123456789abcdefghi#key-1"], }, - "id": "#service" + "id": "#service", }, { "type": "DIDCommMessaging", "serviceEndpoint": { "uri": "http://example.com/another", - "accept": [ - "didcomm/v2" - ], - "routingKeys": [ - "did:example:123456789abcdefghi#key-2" - ] + "accept": ["didcomm/v2"], + "routingKeys": ["did:example:123456789abcdefghi#key-2"], }, - "id": "#service-1" - } - ] + "id": "#service-1", + }, + ], + "alsoKnownAs": ["did:peer:3zQmd6RdU6e2nDrLn1rjwdA5Buzq7wJwsv3WJ1AgrwKYJoLE"], } ``` diff --git a/did_peer_2.py b/did_peer_2.py index 39fd3cb..10e0425 100644 --- a/did_peer_2.py +++ b/did_peer_2.py @@ -140,7 +140,7 @@ def decode_service(self, data: str) -> Dict[str, Any]: ) -MultibaseEncodedKey = str +MultikeyEncodedKey = str @dataclass @@ -148,7 +148,7 @@ class KeySpec: """Key specification for did:peer:2.""" purpose: PurposeCode - material: MultibaseEncodedKey + material: MultikeyEncodedKey @classmethod def assertion(cls, material: str) -> "KeySpec": @@ -185,24 +185,6 @@ def capability_delegation(cls, material: str) -> "KeySpec": """Create a key spec for capability delegation purposes.""" return cls(PurposeCode.capability_delegation, material) - @property - def vm_type(self) -> str: - """Determine the vm type from the multibase encoded key.""" - if self.material.startswith("z6Mk"): - return "Ed25519VerificationKey2020" - if self.material.startswith("z6LS"): - return "X25519KeyAgreementKey2020" - raise ValueError(f"Unsupported key type: {self.material}") - - @property - def required_contexts(self) -> List[str]: - """Determine the required context from the multibase encoded key.""" - if self.material.startswith("z6Mk"): - return ["https://w3id.org/security/suites/ed25519-2020/v1"] - if self.material.startswith("z6LS"): - return ["https://w3id.org/security/suites/x25519-2020/v1"] - raise ValueError(f"Unsupported key type: {self.material}") - def generate(keys: Sequence[KeySpec], services: Sequence[Dict[str, Any]]): """Generate a did:peer:2 DID from keys and services. @@ -228,7 +210,10 @@ def resolve(did: str) -> Dict[str, Any]: raise ValueError(f"Invalid did:peer:2: {did}") document = {} - document["@context"] = ["https://www.w3.org/ns/did/v1"] + document["@context"] = [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/multikey/v1", + ] document["id"] = did elements = did.split(".")[1:] @@ -243,10 +228,9 @@ def resolve(did: str) -> Dict[str, Any]: assert purpose == PurposeCode.service services.append(service_encoder.decode_service(element[1:])) - additional_contexts = set() for index, key in enumerate(keys, start=1): verification_method = { - "type": key.vm_type, + "type": "Multikey", "id": f"#key-{index}", "controller": did, "publicKeyMultibase": key.material, @@ -255,9 +239,6 @@ def resolve(did: str) -> Dict[str, Any]: document.setdefault(key.purpose.verification_relationship, []).append( f"#key-{index}" ) - additional_contexts.update(key.required_contexts) - - document["@context"].extend(sorted(additional_contexts)) unidentified_index = 0 for service in services: @@ -269,6 +250,8 @@ def resolve(did: str) -> Dict[str, Any]: unidentified_index += 1 document.setdefault("service", []).append(service) + document["alsoKnownAs"] = [peer2to3(did)] + return document diff --git a/tests/test_did_peer_2.py b/tests/test_did_peer_2.py index a48c072..cd77929 100644 --- a/tests/test_did_peer_2.py +++ b/tests/test_did_peer_2.py @@ -37,21 +37,20 @@ def test_generate_resolve(): assert resolved == { "@context": [ "https://www.w3.org/ns/did/v1", - "https://w3id.org/security/suites/ed25519-2020/v1", - "https://w3id.org/security/suites/x25519-2020/v1", + "https://w3id.org/security/multikey/v1", ], "id": "did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9kaWRjb21tIiwiYSI6WyJkaWRjb21tL3YyIl0sInIiOlsiZGlkOmV4YW1wbGU6MTIzNDU2Nzg5YWJjZGVmZ2hpI2tleS0xIl19fQ.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9hbm90aGVyIiwiYSI6WyJkaWRjb21tL3YyIl0sInIiOlsiZGlkOmV4YW1wbGU6MTIzNDU2Nzg5YWJjZGVmZ2hpI2tleS0yIl19fQ", "verificationMethod": [ { "id": "#key-1", "controller": "did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9kaWRjb21tIiwiYSI6WyJkaWRjb21tL3YyIl0sInIiOlsiZGlkOmV4YW1wbGU6MTIzNDU2Nzg5YWJjZGVmZ2hpI2tleS0xIl19fQ.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9hbm90aGVyIiwiYSI6WyJkaWRjb21tL3YyIl0sInIiOlsiZGlkOmV4YW1wbGU6MTIzNDU2Nzg5YWJjZGVmZ2hpI2tleS0yIl19fQ", - "type": "Ed25519VerificationKey2020", + "type": "Multikey", "publicKeyMultibase": "z6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc", }, { "id": "#key-2", "controller": "did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9kaWRjb21tIiwiYSI6WyJkaWRjb21tL3YyIl0sInIiOlsiZGlkOmV4YW1wbGU6MTIzNDU2Nzg5YWJjZGVmZ2hpI2tleS0xIl19fQ.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9hbm90aGVyIiwiYSI6WyJkaWRjb21tL3YyIl0sInIiOlsiZGlkOmV4YW1wbGU6MTIzNDU2Nzg5YWJjZGVmZ2hpI2tleS0yIl19fQ", - "type": "X25519KeyAgreementKey2020", + "type": "Multikey", "publicKeyMultibase": "z6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR", }, ], @@ -77,6 +76,7 @@ def test_generate_resolve(): "id": "#service-1", }, ], + "alsoKnownAs": ["did:peer:3zQmd6RdU6e2nDrLn1rjwdA5Buzq7wJwsv3WJ1AgrwKYJoLE"], }