Skip to content

Commit

Permalink
align DidResolver.dereference implementation with did-core spec and a…
Browse files Browse the repository at this point in the history
…dd tests
  • Loading branch information
mistermoe committed Dec 8, 2023
1 parent a99e05e commit c5ce5f3
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 13 deletions.
66 changes: 53 additions & 13 deletions packages/dids/src/did-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import type {
DidMethodResolver,
DidResolutionResult,
DidResolutionOptions,
DidResource
DidResource,
DidDereferenceResult
} from './types.js';

import { parseDid } from './utils.js';
Expand All @@ -14,6 +15,9 @@ export type DidResolverOptions = {
cache?: DidResolverCache;
}

/**
* argument passed to {@link DidResolver.resolve}
*/
export type DereferenceParams = {
didUrl: string
}
Expand Down Expand Up @@ -112,44 +116,80 @@ export class DidResolver {
}

/**
* Asynchronously dereferences a DID (Decentralized Identifier) URL to a corresponding DID resource. This method interprets the DID URL's components, which include the DID method, method-specific identifier, path, query, and fragment,
* and retrieves the related resource as per the DID Core specifications. The dereferencing process involves resolving the DID contained in the DID URL to a DID document, and then extracting the specific part
* of the document identified by the fragment in the DID URL. If no fragment is specified, the entire DID document is returned. This method supports resolution of different components within a DID document such
* as service endpoints and verification methods, based on their IDs. It accommodates both full and relative DID URLs as specified in the DID Core specification.
* dereferences a DID (Decentralized Identifier) URL to a corresponding DID resource. This method interprets
* the DID URL's components, which include the DID method, method-specific identifier, path, query, and fragment,
* and retrieves the related resource as per the DID Core specifications. The dereferencing process involves resolving
* the DID contained in the DID URL to a DID document, and then extracting the specific part of the document identified
* by the fragment in the DID URL. If no fragment is specified, the entire DID document is returned.
*
* More information on DID URL dereferencing can be found in the DID Core specification [here](https://www.w3.org/TR/did-core/#did-url-dereferencing)
* This method supports resolution of different components within a DID document such as
* service endpoints and verification methods, based on their IDs. It accommodates both full and relative DID URLs
* as specified in the DID Core specification.
*
* More information on DID URL dereferencing can be found in the DID Core specification
* [here](https://www.w3.org/TR/did-core/#did-url-dereferencing)
*
* @param params - An object of type `DereferenceParams` containing the `didUrl` which needs to be dereferenced.
* @returns A `Promise` that resolves to a `DidResource`, which can be the entire DID Document or a specific part of it (like a verification method or service endpoint) depending on the presence and value of the fragment in the DID URL.
* @returns a {@link DidDereferenceResult}
*/
async dereference(params: DereferenceParams): Promise<DidResource> {
async dereference(params: DereferenceParams): Promise<DidDereferenceResult> {
const { didUrl } = params;
const { didDocument } = await this.resolve(didUrl);
const { didDocument, didResolutionMetadata, didDocumentMetadata } = await this.resolve(didUrl);

if (didResolutionMetadata.error) {
return {
dereferencingMetadata : didResolutionMetadata,
contentStream : null,
contentMetadata : didDocumentMetadata
};
}

const parsedDid = parseDid(params);

// return the entire DID Document if no fragment is present on the did url
if (!parsedDid.fragment) {
return didDocument;
return {
dereferencingMetadata : didResolutionMetadata,
contentStream : didDocument,
contentMetadata : didDocumentMetadata
};
}

const { service, verificationMethod } = didDocument;
const { service = [], verificationMethod = [] } = didDocument;

// create a set of possible id matches. the DID spec allows for an id to be the entire did#fragment or just #fragment.
// See: https://www.w3.org/TR/did-core/#relative-did-urls
// using a set for fast string comparison. DIDs can be lonnng.
const idSet = new Set([didUrl, parsedDid.fragment, `#${parsedDid.fragment}`]);

let didResource: DidResource;
for (let vm of verificationMethod) {
if (idSet.has(vm.id)) {
return vm;
didResource = vm;
break;
}
}

for (let svc of service) {
if (idSet.has(svc.id)) {
return svc;
didResource = svc;
break;
}
}
if (didResource) {
return {
dereferencingMetadata : didResolutionMetadata,
contentStream : didResource,
contentMetadata : didResolutionMetadata
};
} else {
return {
dereferencingMetadata: {
error: 'notFound'
},
contentStream : null,
contentMetadata : {},
};
}
}
}
29 changes: 29 additions & 0 deletions packages/dids/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,35 @@ export type DidResolutionResult = {
didDocumentMetadata: DidDocumentMetadata
};

export type DidDereferenceResult = {
/**
* A metadata structure consisting of values relating to the results of the DID URL dereferencing process.
* This structure is REQUIRED, and in the case of an error in the dereferencing process, this MUST NOT be empty.
* Properties defined by this specification are in 7.2.2 DID URL Dereferencing Metadata. If the dereferencing is
* not successful, this structure MUST contain an error property describing the error.
*/
dereferencingMetadata: DidResolutionMetadata
/**
* If the dereferencing function was called and successful, this MUST contain a resource corresponding to the DID URL.
* The contentStream MAY be a resource such as:
* * A DID document that is serializable in one of the conformant representations
* * A Verification Method,
* * A service
* * Any other resource format that can be identified via a Media Type and obtained through the resolution process.
*
* If the dereferencing is unsuccessful, this value MUST be empty.
*/
contentStream: DidResource | null
/**
* If the dereferencing is successful, this MUST be a metadata structure, but the structure MAY be empty.
* This structure contains metadata about the contentStream. If the contentStream is a DID document,
* this MUST be a didDocumentMetadata structure as described in DID Resolution. If the dereferencing is unsuccessful,
* this output MUST be an empty metadata structure.
*/
contentMetadata: DidDocumentMetadata
}

/**
* implement this interface to provide your own cache for did resolution results. can be plugged in through Web5 API
*/
Expand Down
50 changes: 50 additions & 0 deletions packages/dids/tests/did-resolver.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { DidResolver } from '../src/did-resolver.js';
import { didResolverTestVectors } from './fixtures/test-vectors/did-resolver.js';
import { DidResolverCacheLevel } from '../src/resolver-cache-level.js';
import { DidResolverCache } from '../src/types.js';
import { isVerificationMethod } from '../src/utils.js';

describe('DidResolver', () => {
describe('resolve()', () => {
Expand Down Expand Up @@ -114,4 +115,53 @@ describe('DidResolver', () => {
});
});
});

describe('dereference()', () => {
let didResolver: DidResolver;

beforeEach(() => {
const didMethodApis = [DidKeyMethod];
didResolver = new DidResolver({ didResolvers: didMethodApis });
});

it('returns a result with contentStream set to null and dereferenceMetadata.error set if resolution fails', async () => {
const result = await didResolver.dereference({ didUrl: 'abcd123;;;' });
expect(result.contentStream).to.be.null;
expect(result.dereferencingMetadata.error).to.exist;
expect(result.dereferencingMetadata.error).to.equal('invalidDid');

});

it('returns the appropriate DID resource as the value of contentStream if found', async () => {
const did = await DidKeyMethod.create();

const result = await didResolver.dereference({ didUrl: did.document.verificationMethod[0].id });
expect(result.contentStream).to.be.not.be.null;
expect(result.dereferencingMetadata.error).to.not.exist;

const didResource = result.contentStream;
expect(isVerificationMethod(didResource)).to.be.true;
});

it('returns the entire did document as the value of contentStream if the did url contains no fragment', async () => {
const did = await DidKeyMethod.create();

const result = await didResolver.dereference({ didUrl: did.did });
expect(result.contentStream).to.be.not.be.null;
expect(result.dereferencingMetadata.error).to.not.exist;

const didResource = result.contentStream;
expect(didResource['@context']).to.exist;
expect(didResource['@context']).to.include('https://www.w3.org/ns/did/v1');
});

it('returns contentStream set to null and dereferenceMetadata.error set to notFound if resource is not found', async () => {
const did = await DidKeyMethod.create();

const result = await didResolver.dereference({ didUrl: `${did.did}#0` });
expect(result.contentStream).to.be.null;
expect(result.dereferencingMetadata.error).to.exist;
expect(result.dereferencingMetadata.error).to.equal('notFound');
});
});
});

0 comments on commit c5ce5f3

Please sign in to comment.