Skip to content

Commit

Permalink
feat: use multikey
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
dbluhm committed Oct 20, 2023
1 parent 2a1f9f9 commit 0d67aee
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 89 deletions.
97 changes: 38 additions & 59 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -87,6 +86,9 @@ did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrG
},
"id": "#service"
}
],
"alsoKnownAs": [
"did:peer:3zQmbRvRJgKBuubq8T9VBjrDPTmjb2Ed91f89ekW4gr6aZxa"
]
}
>>> # Let's derive the did:peer:3
Expand Down Expand Up @@ -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
[
Expand All @@ -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:
```
Expand Down Expand Up @@ -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]
Expand All @@ -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
Expand Down Expand Up @@ -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"],
}
```

Expand Down
35 changes: 9 additions & 26 deletions did_peer_2.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,15 +140,15 @@ def decode_service(self, data: str) -> Dict[str, Any]:
)


MultibaseEncodedKey = str
MultikeyEncodedKey = str


@dataclass
class KeySpec:
"""Key specification for did:peer:2."""

purpose: PurposeCode
material: MultibaseEncodedKey
material: MultikeyEncodedKey

@classmethod
def assertion(cls, material: str) -> "KeySpec":
Expand Down Expand Up @@ -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.
Expand All @@ -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:]
Expand All @@ -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,
Expand All @@ -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:
Expand All @@ -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


Expand Down
8 changes: 4 additions & 4 deletions tests/test_did_peer_2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
],
Expand All @@ -77,6 +76,7 @@ def test_generate_resolve():
"id": "#service-1",
},
],
"alsoKnownAs": ["did:peer:3zQmd6RdU6e2nDrLn1rjwdA5Buzq7wJwsv3WJ1AgrwKYJoLE"],
}


Expand Down

0 comments on commit 0d67aee

Please sign in to comment.