Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion src/idp/well-known-did-nostr.js
Original file line number Diff line number Diff line change
Expand Up @@ -492,13 +492,26 @@ export async function resolveDidNostrLocally(pubkeyHex) {
* Multikey value is computed deterministically from the pubkey via
* the f-form recipe (multibase `f` + multicodec `e701` + parity byte
* `02` + 32-byte xonly hex) — the same shape the doctor's B.2 emits.
*
* `@context` MUST lead with the DID Core context — DID Core requires
* the first value to be `https://www.w3.org/ns/did/v1`, and did:nostr
* 0.1.1 adopted that ordering (nostrcg/did-nostr#136, fixed by #139).
* `cid/v1` still follows it, because this document's Multikey
* verification method comes from the Controlled Identifiers
* vocabulary. The ordering is normative only for DID documents, so
* standalone CID resources (src/keys/provision.js) and WebID profiles
* (src/webid/profile.js) correctly keep `cid/v1` alone.
*/
function buildDidDocument({ pubkey, webId }) {
const did = `did:nostr:${pubkey.toLowerCase()}`;
const multikey = `f` + `e701` + `02` + pubkey.toLowerCase();
const vmId = `${did}#key1`;
return {
'@context': ['https://www.w3.org/ns/cid/v1', 'https://w3id.org/nostr/context'],
'@context': [
'https://www.w3.org/ns/did/v1',
'https://www.w3.org/ns/cid/v1',
'https://w3id.org/nostr/context',
],
'id': did,
'type': 'DIDNostr',
'alsoKnownAs': [webId],
Expand Down
26 changes: 25 additions & 1 deletion test/well-known-did-nostr.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,11 @@ describe('GET /.well-known/did/nostr/:pubkey (#407)', () => {
assert.ok(r.headers.get('last-modified'));

const doc = await r.json();
assert.deepStrictEqual(doc['@context'], ['https://www.w3.org/ns/cid/v1', 'https://w3id.org/nostr/context']);
assert.deepStrictEqual(doc['@context'], [
'https://www.w3.org/ns/did/v1',
'https://www.w3.org/ns/cid/v1',
'https://w3id.org/nostr/context',
]);
assert.strictEqual(doc.id, `did:nostr:${alicePk}`);
assert.strictEqual(doc.type, 'DIDNostr');
assert.ok(Array.isArray(doc.alsoKnownAs));
Expand All @@ -139,6 +143,26 @@ describe('GET /.well-known/did/nostr/:pubkey (#407)', () => {
assert.strictEqual(doc.authentication[0], `did:nostr:${alicePk}#key1`);
});

it('leads @context with the DID Core context (did:nostr 0.1.1)', async () => {
// DID Core requires a DID document's @context to lead with
// https://www.w3.org/ns/did/v1; did:nostr 0.1.1 adopted that ordering
// (nostrcg/did-nostr#136 → #139). Asserted separately from the
// full-shape check above so a reordering regression names itself.
const r = await fetch(`${baseUrl}/.well-known/did/nostr/${alicePk}.json`);
assert.strictEqual(r.status, 200);
const doc = await r.json();
// Assert the shape before indexing, so a dropped @context fails with
// this message rather than a TypeError.
const ctx = doc['@context'];
assert.ok(Array.isArray(ctx), '@context must be present and an array');
assert.strictEqual(ctx[0], 'https://www.w3.org/ns/did/v1',
'@context must lead with the DID Core context');
// The CID context must still be present — the document's Multikey
// verification method is drawn from that vocabulary.
assert.ok(ctx.includes('https://www.w3.org/ns/cid/v1'),
'@context must still include the CID v1 context');
});

it('accepts the .jsonld suffix (alias)', async () => {
const r = await fetch(`${baseUrl}/.well-known/did/nostr/${alicePk}.jsonld`);
assert.strictEqual(r.status, 200);
Expand Down