did:nostr
Thought experiment around bringing dids to nostr.
I have no idea whether this will work, or if it's a good idea. What has me interested is:
DIDs, like URLs are fully qualified URIs. Similar to a URL, DIDs can be resolved. resolving a URL like https://snort.social/
returns html. So what does resolving a DID return? A DID document
What is a DID document? it's a JSON object that contains information about the DID e.g.
{
"@context": [
"https://www.w3.org/ns/did/v1",
],
"id": "did:nostr:e2bdaa90e96a4fafb9f1c36f9b378e4bbd6fea26e5d47063e7b30aa15de37d48",
"verificationMethod": [
{
"id": "did:nostr:e2bdaa90e96a4fafb9f1c36f9b378e4bbd6fea26e5d47063e7b30aa15de37d48#nip06-0",
"type": "SchnorrSecp256k1VerificationKey2019",
"controller": "did:nostr:e2bdaa90e96a4fafb9f1c36f9b378e4bbd6fea26e5d47063e7b30aa15de37d48",
"publicKeyHex": "e2bdaa90e96a4fafb9f1c36f9b378e4bbd6fea26e5d47063e7b30aa15de37d48"
}
],
"service": [
{
"id": "did:nostr:e2bdaa90e96a4fafb9f1c36f9b378e4bbd6fea26e5d47063e7b30aa15de37d48#nostr-relays",
"type": "Relay",
"serviceEndpoint": ["wss://relay.damus.io", "wss://relay.nostr.info"]
},
{
"id": "did:nostr:e2bdaa90e96a4fafb9f1c36f9b378e4bbd6fea26e5d47063e7b30aa15de37d48#ln",
"type": "LightningNode",
"serviceEndpoint": "ip://024bfaf0cabe7f874fd33ebf7c6f4e5385971fc504ef3f492432e9e3ec77e1b5cf@52.1.72.207:9735"
}
],
"keyAgreement": [
{
"id": "did:nostr:e2bdaa90e96a4fafb9f1c36f9b378e4bbd6fea26e5d47063e7b30aa15de37d48#keyagreement",
"type": "X25519KeyAgreement2023",
"controller": "did:nostr:e2bdaa90e96a4fafb9f1c36f9b378e4bbd6fea26e5d47063e7b30aa15de37d48",
"publicKeyHex": "75d92cea4ab8ef28a0a14acf103d6b8a2bb026120d62d1817fa5a4b11f534038"
}
]
}
💡 TODO: think of more
service
examples💡 TODO: figure out
id
property forkeyAgreement
so what do the properties in the DID document mean? | property | description | notes |
---|---|---|---|
verificationMethod |
includes crypto keys that can be used for various purposes, such as to verify digital signatures | the key shown in the example above happens to be the decoded nostr pubkey | |
service |
lists services that can be used to interact with a DID | the example includes the nostr relays that this DID publishes to. Could also include any other service e.g. a lightning node | |
keyAgreement |
lists keys that can be used to generate shared keys for encryption/decryption purposes |
💡 there are several other properties that can exist on DID documents. more info on that here
⚠️ WIP ⚠️
This NIP proposes the following:
9325
for publishing, patching, and recovering a DID.did:nostr:<nostr_hex_pubkey>
nostr_hex_pubkey
is represented as 32-bytes lowercase hex-encoded public keynostr_hex_pubkey
with did:nostr:
did:nostr:41e791de6a6f6f0b3c820c2db179c0679e2c228ae6ecb9583cb48b3e1ff354b6
💡 if desired nostr dids can be displayed using the bech32 encoded
npub
format described in nip19 e.g.did:nostr:npub1u2...
. As stated in nip19: The bech32 encodings should not be used to represent a did in nostr events.
the base DID document can be derived without sending a publish
message to a relay. It's not all that useful in and of itself, but forms the foundation of DID resolution.
💡 TODO: write out steps to derive base DID Document by reading reference implementation
// from example.ts in reference implementation
const did = pubKeyToDid(kp.public);
console.log(deriveDidDoc(did));
Output
{
"id": "did:nostr:41e791de6a6f6f0b3c820c2db179c0679e2c228ae6ecb9583cb48b3e1ff354b6",
"verificationMethod": [
{
"id": "did:nostr:41e791de6a6f6f0b3c820c2db179c0679e2c228ae6ecb9583cb48b3e1ff354b6#nip06-0",
"type": "SchnorrSecp256k1VerificationKey2019",
"controller": "did:nostr:41e791de6a6f6f0b3c820c2db179c0679e2c228ae6ecb9583cb48b3e1ff354b6",
"publicKeyHex": "41e791de6a6f6f0b3c820c2db179c0679e2c228ae6ecb9583cb48b3e1ff354b6"
}
]
}
💡 the
verificationMethod
in the example above is a schnorr pubkey. SchnorrSecp256k1VerificationKey2019 is used to describe the type of verification method. Honestly not entirely sure what the motiviation is behind including the year (aka2019
).
This event can be used to publish, patch, and recover a DID. every 9325
message should contain the o
(e.g op) and d
(e.g. did
) tags
{
"kind": 9325,
"tags": [
["o", "publish | patch | recover"],
["d", "did:nostr:e2bdaa90e96a4fafb9f1c36f9b378e4bbd6fea26e5d47063e7b30aa15de37d48"]
]
}
publish
{
"kind": 9325,
"pubkey": "e3932a8cd4ec81e1e9a41467470ec9db817accf21e1e8b939525e84da6786d9a",
"created_at": 1676957260,
"tags": [
["d","did:nostr:e3932a8cd4ec81e1e9a41467470ec9db817accf21e1e8b939525e84da6786d9a"],
["o","publish"]
],
"content": "{\"r\":\"683c867bf3dc5e7993fdd0715771d36eb6f00b8a8645795c332246ea5f19c7d5\",\"patches\":[{\"op\":\"add\",\"path\":\"/service\",\"value\":[{\"id\":\"did:nostr:e3932a8cd4ec81e1e9a41467470ec9db817accf21e1e8b939525e84da6786d9a#nostr-relays\",\"type\":\"NostrRelay\",\"serviceEndpoint\":[\"wss://relay.damus.io\"]}]}]}",
"sig": "b1bae7a64d84ce63e40dfa24d299892116694ae71215f357ddad0a0091455e0b985b45ccca6f9354223dc3f0243a9eabcb6d874ff85fac7051493fee8aab2ef9"
}
publish
event for a given DID9325
for a given DIDo
tag should have a value of publish
d
tag should be present and contain the relevant DIDcontent
is a stringified json object that contains the following properties:Property | Description |
---|---|
r |
double hashed (sha256) encoded recovery pubkey |
patches |
an array of JSON patch ops that are applied on top of the base DID Document |
event.pubkey
should match the id
of the DID found in the d
tag.sig
verificationrecover
{
"content": "{\"r\":\"0df32bb15f48f60c5d7e035bdfbc67507a8576109a422dd860ad1a7b4935b3e6\"}",
"created_at": 1676957711,
"kind": 9325,
"pubkey": "b424a3e0ea03a1450feef4cdba3dd74892c3e18726f079f89db42d597dfceb4c",
"tags": [
["d","did:nostr:89abca8adb7d5db8a4b4e90b0c0eb97dc6f3a957fc219a83bf925fb9c7bd4331"],
["o", "recover"],
["e", "bd513634c49754383d9d4110ebeaa1433467c5b70da7ae2d2873cabcd5445451", "wss://relay.damus.io", "reply"]
],
"sig": "1f3fdaf79dcf9fb7ab51834c80b28abc5e6a3850f0185bb5b616f4a5e133f188dc609be10462e551ea984fe403a32a4b0b8b492d0c9c45f63d4fdd4d38d8c46d",
}
o
tag should have a value of recover
d
tag should be present and contain the relevant DIDe
tag pointing to the most recent event of kind 9325
for the relevant DID
e
tag is allowed, it may be helpful to include an additional marked root
e
tag that points back to the initial publish evente
tag is allowed, it may be helpful to include an additional marked mention
e
tag that points back to the most recent recover
event for the relevant DIDcontent
is a stringified json object that contains the following properties:Property | Description |
---|---|
r |
new double hashed (sha256) recovery key |
💡 Note: any
r
should not be used more than once. Recovering should always include a newr
that can be used for subsequent recoveriesIntegrity Checks
sha256(sha256(event.pubkey))
should matchJSON.parse(previousEvent.content).r
of the most recentrecover
event if one exists or the initialpublish
event if no otherrecover
events exist
patch
{
"kind": 9325,
"tags": [
["d", "did:nostr:e2bdaa90e96a4fafb9f1c36f9b378e4bbd6fea26e5d47063e7b30aa15de37d48"]
["e", "event id of most recent 9325 event for did"]
["o", "patch"],
],
"content": "{\"patches\":[{\"op\":\"add\",\"path\":\"/service\",\"value\":[{\"id\":\"did:nostr:e2bdaa90e96a4fafb9f1c36f9b378e4bbd6fea26e5d47063e7b30aa15de37d48#nostr-relays\",\"type\":\"NostrRelay\",\"serviceEndpoint\":[\"wss://relay.damus.io\"]}]}]}"
}
content
is a stringified json object that contains the following properties:Property | Description |
---|---|
patches |
an array of JSON patch ops |
Given a DID (e.g. did:nostr:e2bdaa90e96a4fafb9f1c36f9b378e4bbd6fea26e5d47063e7b30aa15de37d48
):
9325
for the DID being resolvedcreated_at
publish
. perform integrity checks listed herepatch
: apply patches to DID docrecover
: perform integrity checks listed hereverificationMethod
in DID doc#d
tag should perform necessary integrity checks before trusting that the event came from the listed DID9325
event prior to storing it