diff --git a/.changeset/two-moons-agree.md b/.changeset/two-moons-agree.md new file mode 100644 index 000000000..7c07d8d04 --- /dev/null +++ b/.changeset/two-moons-agree.md @@ -0,0 +1,5 @@ +--- +"@web5/dids": patch +--- + +DID:DHT - Only have . suffix for Root and Gateway Record names diff --git a/CODEOWNERS b/CODEOWNERS index 5e71ff8b1..e97b72699 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -6,7 +6,7 @@ # The format is described: https://github.blog/2017-07-06-introducing-code-owners/ # These owners will be the default owners for everything in the repo. -* @frankhinek @csuwildcat @mistermoe +* @frankhinek @csuwildcat @mistermoe @thehenrytsai # These are owners of any file in the `common`, `crypto`, `crypto-aws-kms`, `dids`, and # `credentials` packages and their sub-directories. diff --git a/packages/dids/.vscode/launch.json b/packages/dids/.vscode/launch.json index 05c7ab34d..e74b7449e 100644 --- a/packages/dids/.vscode/launch.json +++ b/packages/dids/.vscode/launch.json @@ -8,7 +8,7 @@ "type": "node", "request": "launch", "name": "test:node", - "runtimeExecutable": "${workspaceFolder:dids}/node_modules/.bin/mocha", + "runtimeExecutable": "${workspaceFolder:dids}/../../node_modules/.bin/mocha", "console": "internalConsole", "preLaunchTask": "build tests", "skipFiles": [ diff --git a/packages/dids/src/methods/did-dht.ts b/packages/dids/src/methods/did-dht.ts index ce2bd1468..00dbfafad 100644 --- a/packages/dids/src/methods/did-dht.ts +++ b/packages/dids/src/methods/did-dht.ts @@ -1255,16 +1255,11 @@ export class DidDhtDocument { // Add a DNS TXT record for the root record. dnsAnswerRecords.push({ type : 'TXT', - name : '_did.', + name : '_did.' + DidDhtDocument.getUniqueDidSuffix(didDocument.id) + '.', // name of a Root Record MUST end in `.` ttl : DNS_RECORD_TTL, data : rootRecord.join(PROPERTY_SEPARATOR) }); - // Per the DID DHT specification, the method-specific identifier must be appended as the - // Origin of all records. - const [, , identifier] = didDocument.id.split(':'); - dnsAnswerRecords.forEach(record => record.name += identifier); - // Create a DNS response packet with the authoritative answer flag set. const dnsPacket: Packet = { id : 0, @@ -1275,6 +1270,16 @@ export class DidDhtDocument { return dnsPacket; } + + /** + * Gets the unique portion of the DID identifier after the last `:` character. + * e.g. `did:dht:example` -> `example` + * + * @param did - The DID to extract the unique suffix from. + */ + private static getUniqueDidSuffix(did: string ): string { + return did.split(':')[2]; + } } /** diff --git a/packages/dids/src/types/did-core.ts b/packages/dids/src/types/did-core.ts index 1e82db6c7..fd4c501a1 100644 --- a/packages/dids/src/types/did-core.ts +++ b/packages/dids/src/types/did-core.ts @@ -563,18 +563,18 @@ export enum DidVerificationRelationship { keyAgreement = 'keyAgreement', /** - * Specifies a mechanism used by the DID subject to delegate a cryptographic capability to another - * party. This can include delegating access to a specific resource or API. + * Specifies a verification method used by the DID subject to invoke a cryptographic capability. + * This is frequently associated with authorization actions, like updating the DID Document. * - * @see {@link https://www.w3.org/TR/did-core/#capability-delegation | DID Core Specification, § Capability Delegation} + * @see {@link https://www.w3.org/TR/did-core/#capability-invocation | DID Core Specification, § Capability Invocation} */ - capabilityDelegation = 'capabilityDelegation', + capabilityInvocation = 'capabilityInvocation', /** - * Specifies a verification method used by the DID subject to invoke a cryptographic capability. - * This is frequently associated with authorization actions, like updating the DID Document. + * Specifies a mechanism used by the DID subject to delegate a cryptographic capability to another + * party. This can include delegating access to a specific resource or API. * - * @see {@link https://www.w3.org/TR/did-core/#capability-invocation | DID Core Specification, § Capability Invocation} + * @see {@link https://www.w3.org/TR/did-core/#capability-delegation | DID Core Specification, § Capability Delegation} */ - capabilityInvocation = 'capabilityInvocation' + capabilityDelegation = 'capabilityDelegation', } \ No newline at end of file diff --git a/packages/dids/tests/fixtures/test-vectors/did-dht/vector-1-did-document.json b/packages/dids/tests/fixtures/test-vectors/did-dht/vector-1-did-document.json new file mode 100644 index 000000000..3b20263c6 --- /dev/null +++ b/packages/dids/tests/fixtures/test-vectors/did-dht/vector-1-did-document.json @@ -0,0 +1,29 @@ +{ + "id": "did:dht:cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo", + "verificationMethod": [ + { + "id": "did:dht:cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo#0", + "type": "JsonWebKey", + "controller": "did:dht:cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo", + "publicKeyJwk": { + "kid": "0", + "alg": "Ed25519", + "crv": "Ed25519", + "kty": "OKP", + "x": "YCcHYL2sYNPDlKaALcEmll2HHyT968M4UWbr-9CFGWE" + } + } + ], + "authentication": [ + "did:dht:cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo#0" + ], + "assertionMethod": [ + "did:dht:cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo#0" + ], + "capabilityInvocation": [ + "did:dht:cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo#0" + ], + "capabilityDelegation": [ + "did:dht:cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo#0" + ] +} \ No newline at end of file diff --git a/packages/dids/tests/fixtures/test-vectors/did-dht/vector-1-dns-records.json b/packages/dids/tests/fixtures/test-vectors/did-dht/vector-1-dns-records.json new file mode 100644 index 000000000..a749bfd0d --- /dev/null +++ b/packages/dids/tests/fixtures/test-vectors/did-dht/vector-1-dns-records.json @@ -0,0 +1,14 @@ +[ + { + "name": "_did.cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo.", + "type": "TXT", + "ttl": 7200, + "rdata": "v=0;vm=k0;auth=k0;asm=k0;inv=k0;del=k0" + }, + { + "name": "_k0._did.", + "type": "TXT", + "ttl": 7200, + "rdata": "id=0;t=0;k=YCcHYL2sYNPDlKaALcEmll2HHyT968M4UWbr-9CFGWE" + } +] \ No newline at end of file diff --git a/packages/dids/tests/methods/did-dht.spec.ts b/packages/dids/tests/methods/did-dht.spec.ts index 61f5db026..929362888 100644 --- a/packages/dids/tests/methods/did-dht.spec.ts +++ b/packages/dids/tests/methods/did-dht.spec.ts @@ -1,14 +1,16 @@ +import type { PortableDid } from '../../src/types/portable-did.js'; + import sinon from 'sinon'; +import resolveTestVectors from '../../../../web5-spec/test-vectors/did_dht/resolve.json' assert { type: 'json' }; +import officialTestVector1DidDocument from '../fixtures/test-vectors/did-dht/vector-1-did-document.json' assert { type: 'json' }; +import officialTestVector1DnsRecords from '../fixtures/test-vectors/did-dht/vector-1-dns-records.json' assert { type: 'json' }; + import { expect } from 'chai'; import { Convert } from '@web5/common'; - -import type { PortableDid } from '../../src/types/portable-did.js'; - +import { DidDocument } from '../../src/index.js'; import { DidErrorCode } from '../../src/did-error.js'; import { DidDht, DidDhtDocument, DidDhtRegisteredDidType } from '../../src/methods/did-dht.js'; -import DidDhtResolveTestVector from '../../../../web5-spec/test-vectors/did_dht/resolve.json' assert { type: 'json' }; - // Helper function to create a mocked fetch response that fails and returns a 404 Not Found. const fetchNotFoundResponse = () => ({ status : 404, @@ -431,10 +433,15 @@ describe('DidDht', () => { it('throws an error if the resulting DID document would exceed the 1000 byte maximum', async () => { try { - // Attempt to create a DID with 6 verification methods (Identity Key plus 5 additional). + // Attempt to create a DID with DID Document that exceeds the maximum size. await DidDht.create({ options: { verificationMethods: [ + { algorithm: 'Ed25519' }, + { algorithm: 'Ed25519' }, + { algorithm: 'Ed25519' }, + { algorithm: 'Ed25519' }, + { algorithm: 'Ed25519' }, { algorithm: 'Ed25519' }, { algorithm: 'Ed25519' }, { algorithm: 'Ed25519' }, @@ -1153,10 +1160,35 @@ describe('DidDhtDocument', () => { describe('Web5TestVectorsDidDht', () => { it('resolve', async () => { - for (const vector of DidDhtResolveTestVector.vectors as any[]) { + for (const vector of resolveTestVectors.vectors as any[]) { const didResolutionResult = await DidDht.resolve(vector.input.didUri); expect(didResolutionResult.didResolutionMetadata.error).to.equal(vector.output.didResolutionMetadata.error); } }).timeout(30000); // Set timeout to 30 seconds for this test for did:dht resolution timeout test }); }); + +// vectors come from https://did-dht.com/#test-vectors +describe('Official DID:DHT Vector tests', () => { + it('vector 1', async () => { + const dnsPacket = await DidDhtDocument.toDnsPacket({ + didDocument : officialTestVector1DidDocument as DidDocument, + didMetadata : { published: false } + }); + + expect(dnsPacket.answers).to.have.length(officialTestVector1DnsRecords.length); + + // NOTE: the DNS library we use uses name `data` instead of `rdata` used in DID:DHT spec, + // but prefer to keep the naming in test vector files identical to that of the DID:DHT spec, + // hence this additional normalization step + const normalizedConstructedRecords = dnsPacket.answers!.map(record => { + const { data: rdata, ...otherProperties } = record; + return { + ...otherProperties, + rdata + }; + }); + + expect(normalizedConstructedRecords).to.deep.include.members(officialTestVector1DnsRecords); + }); +}); diff --git a/web5-js.code-workspace b/web5-js.code-workspace index 8fa18afd1..ef7448c45 100644 --- a/web5-js.code-workspace +++ b/web5-js.code-workspace @@ -66,7 +66,13 @@ "editor.codeActionsOnSave": { "source.fixAll": "always" }, - "typescript.tsdk": "root/node_modules/typescript/lib" + "typescript.tsdk": "root/node_modules/typescript/lib", + "search.exclude": { + "**/dist/**": true, + "**/coverage/**": true, + "**/compiled/**": true + + } }, "launch": { "version": "0.2.0",