Skip to content

Commit f13526f

Browse files
fix(did): lead DID document @context with did/v1 (did:nostr 0.1.1) (#618)
* fix(did): lead DID document @context with did/v1 (did:nostr 0.1.1) 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, fixed by #139), so buildDidDocument now emits: [did/v1, cid/v1, w3id.org/nostr/context] cid/v1 is retained because the document's Multikey verification method comes from the Controlled Identifiers vocabulary. The requirement is normative for DID documents only, so the standalone Multikey resource (src/keys/provision.js) and the WebID profile (src/webid/profile.js) correctly keep cid/v1 alone and are untouched. Both orderings expand to the same terms under JSON-LD, so existing documents do not break; JSS's own consumer (src/auth/did-nostr.js) never reads @context — it keys on verificationMethod. Updates the full-shape assertion and adds a dedicated regression test for the leading context, so a reordering failure names itself. Closes #617 * test(did): assert @context shape before indexing The dedicated ordering test indexed doc['@context'] and called .includes() without first checking the field exists, so a regression that dropped @context entirely would surface as a TypeError instead of the intended assertion message — defeating the reason the check is a separate test. Bind @context to a local, assert Array.isArray first, then check the ordering. Verified: with @context removed from the builder the test now fails with '@context must be present and an array' rather than a TypeError.
1 parent 0976f3e commit f13526f

2 files changed

Lines changed: 39 additions & 2 deletions

File tree

src/idp/well-known-did-nostr.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,13 +492,26 @@ export async function resolveDidNostrLocally(pubkeyHex) {
492492
* Multikey value is computed deterministically from the pubkey via
493493
* the f-form recipe (multibase `f` + multicodec `e701` + parity byte
494494
* `02` + 32-byte xonly hex) — the same shape the doctor's B.2 emits.
495+
*
496+
* `@context` MUST lead with the DID Core context — DID Core requires
497+
* the first value to be `https://www.w3.org/ns/did/v1`, and did:nostr
498+
* 0.1.1 adopted that ordering (nostrcg/did-nostr#136, fixed by #139).
499+
* `cid/v1` still follows it, because this document's Multikey
500+
* verification method comes from the Controlled Identifiers
501+
* vocabulary. The ordering is normative only for DID documents, so
502+
* standalone CID resources (src/keys/provision.js) and WebID profiles
503+
* (src/webid/profile.js) correctly keep `cid/v1` alone.
495504
*/
496505
function buildDidDocument({ pubkey, webId }) {
497506
const did = `did:nostr:${pubkey.toLowerCase()}`;
498507
const multikey = `f` + `e701` + `02` + pubkey.toLowerCase();
499508
const vmId = `${did}#key1`;
500509
return {
501-
'@context': ['https://www.w3.org/ns/cid/v1', 'https://w3id.org/nostr/context'],
510+
'@context': [
511+
'https://www.w3.org/ns/did/v1',
512+
'https://www.w3.org/ns/cid/v1',
513+
'https://w3id.org/nostr/context',
514+
],
502515
'id': did,
503516
'type': 'DIDNostr',
504517
'alsoKnownAs': [webId],

test/well-known-did-nostr.test.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,11 @@ describe('GET /.well-known/did/nostr/:pubkey (#407)', () => {
129129
assert.ok(r.headers.get('last-modified'));
130130

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

146+
it('leads @context with the DID Core context (did:nostr 0.1.1)', async () => {
147+
// DID Core requires a DID document's @context to lead with
148+
// https://www.w3.org/ns/did/v1; did:nostr 0.1.1 adopted that ordering
149+
// (nostrcg/did-nostr#136 → #139). Asserted separately from the
150+
// full-shape check above so a reordering regression names itself.
151+
const r = await fetch(`${baseUrl}/.well-known/did/nostr/${alicePk}.json`);
152+
assert.strictEqual(r.status, 200);
153+
const doc = await r.json();
154+
// Assert the shape before indexing, so a dropped @context fails with
155+
// this message rather than a TypeError.
156+
const ctx = doc['@context'];
157+
assert.ok(Array.isArray(ctx), '@context must be present and an array');
158+
assert.strictEqual(ctx[0], 'https://www.w3.org/ns/did/v1',
159+
'@context must lead with the DID Core context');
160+
// The CID context must still be present — the document's Multikey
161+
// verification method is drawn from that vocabulary.
162+
assert.ok(ctx.includes('https://www.w3.org/ns/cid/v1'),
163+
'@context must still include the CID v1 context');
164+
});
165+
142166
it('accepts the .jsonld suffix (alias)', async () => {
143167
const r = await fetch(`${baseUrl}/.well-known/did/nostr/${alicePk}.jsonld`);
144168
assert.strictEqual(r.status, 200);

0 commit comments

Comments
 (0)