From b26d071cec922bbf841af5076ac272c085608bb8 Mon Sep 17 00:00:00 2001 From: Frank Hinek Date: Tue, 6 Feb 2024 07:34:49 -0500 Subject: [PATCH] Break out BearerDid/PortableDid and add DidRegistrationResult Signed-off-by: Frank Hinek --- packages/dids/src/bearer-did.ts | 42 ++++ packages/dids/src/index.ts | 2 + packages/dids/src/methods/did-dht.ts | 182 ++++++++++-------- packages/dids/src/methods/did-ion.ts | 13 +- packages/dids/src/methods/did-jwk.ts | 4 +- packages/dids/src/methods/did-key.ts | 4 +- packages/dids/src/methods/did-method.ts | 176 ++++++----------- packages/dids/src/portable-did.ts | 106 ++++++++++ packages/dids/src/types/did-core.ts | 4 +- .../fixtures/test-vectors/did-ion/to-keys.ts | 2 +- packages/dids/tests/methods/did-dht.spec.ts | 36 +++- packages/dids/tests/methods/did-jwk.spec.ts | 2 +- packages/dids/tests/methods/did-key.spec.ts | 2 +- .../dids/tests/methods/did-method.spec.ts | 2 +- 14 files changed, 366 insertions(+), 211 deletions(-) create mode 100644 packages/dids/src/bearer-did.ts create mode 100644 packages/dids/src/portable-did.ts diff --git a/packages/dids/src/bearer-did.ts b/packages/dids/src/bearer-did.ts new file mode 100644 index 000000000..9c3023c12 --- /dev/null +++ b/packages/dids/src/bearer-did.ts @@ -0,0 +1,42 @@ +import type { CryptoApi, Signer } from '@web5/crypto'; + +import type { DidMetadata } from './portable-did.js'; +import type { DidDocument } from './types/did-core.js'; + +/** + * Represents a Decentralized Identifier (DID) along with its DID document, key manager, metadata, + * and convenience functions. + */ +export interface BearerDid { + /** + * The DID document associated with this DID. + */ + didDocument: DidDocument; + + /** + * Returns a {@link @web5/crypto#Signer} that can be used to sign messages, credentials, or + * arbitrary data. + * + * If given, the `keyUri` parameter is used to select a key from the verification methods present + * in the DID Document. If `keyUri` is not given, each DID method implementation will select a + * default verification method key from the DID Document. + * + * @param params - The parameters for the `getSigner` operation. + * @param params.keyUri - Key URI of the key that will be used for sign and verify operations. Optional. + * @returns An instantiated {@link Signer} that can be used to sign and verify data. + */ + getSigner: (params?: { keyUri?: string }) => Promise; + + /** + * Key Management System (KMS) used to manage a DIDs keys and sign data. + * + * Each DID method requires at least one key be present in the provided `keyManager`. + */ + keyManager: CryptoApi; + + /** {@inheritDoc DidMetadata} */ + metadata: DidMetadata; + + /** {@inheritDoc Did#uri} */ + uri: string; +} \ No newline at end of file diff --git a/packages/dids/src/index.ts b/packages/dids/src/index.ts index 83333c9d9..02f63d3fb 100644 --- a/packages/dids/src/index.ts +++ b/packages/dids/src/index.ts @@ -1,5 +1,7 @@ export * from './did.js'; export * from './did-error.js'; +export * from './bearer-did.js'; +export * from './portable-did.js'; export * from './methods/did-dht.js'; export * from './methods/did-ion.js'; diff --git a/packages/dids/src/methods/did-dht.ts b/packages/dids/src/methods/did-dht.ts index aaa17335a..f377a5ae2 100644 --- a/packages/dids/src/methods/did-dht.ts +++ b/packages/dids/src/methods/did-dht.ts @@ -2,6 +2,7 @@ import type { Packet, TxtAnswer, TxtData } from '@dnsquery/dns-packet'; import type { Jwk, Signer, + CryptoApi, KeyIdentifier, KmsExportKeyParams, KmsImportKeyParams, @@ -11,10 +12,12 @@ import type { import bencode from 'bencode'; import { Convert } from '@web5/common'; -import { CryptoApi, computeJwkThumbprint, Ed25519, LocalKeyManager, Secp256k1, Secp256r1 } from '@web5/crypto'; +import { computeJwkThumbprint, Ed25519, LocalKeyManager, Secp256k1, Secp256r1 } from '@web5/crypto'; import { AUTHORITATIVE_ANSWER, decode as dnsPacketDecode, encode as dnsPacketEncode } from '@dnsquery/dns-packet'; -import type { BearerDid, DidCreateOptions, DidCreateVerificationMethod, DidMetadata, PortableDid } from './did-method.js'; +import type { BearerDid } from '../bearer-did.js'; +import type { DidMetadata, PortableDid } from '../portable-did.js'; +import type { DidCreateOptions, DidCreateVerificationMethod, DidRegistrationResult } from './did-method.js'; import type { DidService, DidDocument, @@ -39,7 +42,7 @@ import { EMPTY_DID_RESOLUTION_RESULT } from '../resolver/did-resolver.js'; * * @see {@link https://www.bittorrent.org/beps/bep_0044.html | BEP44} */ -interface Bep44Message { +export interface Bep44Message { /** * The public key bytes of the Identity Key, which serves as the identifier in the DHT network for * the corresponding BEP44 message. @@ -112,8 +115,8 @@ export interface DidDhtCreateOptions extends DidCreateOptions { /** * Optional. The URI of a server involved in executing DID method operations. In the context of - * DID creation, the endpoint is expected to be a Pkarr relay. If not specified, a default gateway - * node is used. + * DID creation, the endpoint is expected to be a DID DHT Gateway or Pkarr relay. If not + * specified, a default gateway node is used. */ gatewayUri?: string; @@ -203,9 +206,10 @@ export interface DidDhtCreateOptions extends DidCreateOptions { } /** - * The default Pkarr relay server to use when publishing and resolving DID documents. + * The default DID DHT Gateway or Pkarr Relay server to use when publishing and resolving DID + * documents. */ -const DEFAULT_PKARR_RELAY = 'https://diddht.tbddev.org'; +const DEFAULT_GATEWAY_URI = 'https://diddht.tbddev.org'; /** * The version of the DID DHT specification that is implemented by this library. @@ -529,10 +533,11 @@ export class DidDht extends DidMethod { // signer convenience function, and URI. const did = await DidDht.fromPublicKeys({ keyManager, options, ...keySet }); - // By default, publish the DID document to a DHT operations endpoint unless explicitly disabled. - did.metadata.published = options.publish ?? true - ? await this.publish({ did, gatewayUri: options.gatewayUri }) - : false; + // By default, publish the DID document to a DHT Gateway unless explicitly disabled. + if (options.publish ?? true) { + const registrationResult = await DidDht.publish({ did, gatewayUri: options.gatewayUri }); + did.metadata = registrationResult.didDocumentMetadata; + } return did; } @@ -604,9 +609,10 @@ export class DidDht extends DidMethod { const did = await DidDht.fromPublicKeys({ keyManager, verificationMethods, options }); // By default, the DID document will NOT be published unless explicitly enabled. - did.metadata.published = options.publish - ? await this.publish({ did, gatewayUri: options.gatewayUri }) - : false; + if (options.publish) { + const registrationResult = await DidDht.publish({ did, gatewayUri: options.gatewayUri }); + did.metadata = registrationResult.didDocumentMetadata; + } return did; } @@ -651,30 +657,31 @@ export class DidDht extends DidMethod { * - For existing, unpublished DIDs, it can be used to publish the DID Document to Mainline DHT. * - The method relies on the specified Pkarr relay server to interface with the DHT network. * - * @param params - The parameters for the `publish` operation. - * @param params.did - The `BearerDid` object representing the DID to be published. - * @param params.gatewayUri - Optional. The URI of a server involved in executing DID - * method operations. In the context of publishing, the - * endpoint is expected to be a Pkarr relay. If not specified, - * a default relay server is used. - * @returns A Promise resolving to a boolean indicating whether the publication was successful. - * * @example * ```ts * // Generate a new DID and keys but explicitly disable publishing. * const did = await DidDht.create({ options: { publish: false } }); * // Publish the DID to the DHT. - * const isPublished = await DidDht.publish({ did }); - * // `isPublished` is true if the DID was successfully published. + * const registrationResult = await DidDht.publish({ did }); + * // `registrationResult.didDocumentMetadata.published` is true if the DID was successfully published. * ``` + * + * @param params - The parameters for the `publish` operation. + * @param params.did - The `BearerDid` object representing the DID to be published. + * @param params.gatewayUri - Optional. The URI of a server involved in executing DID method + * operations. In the context of publishing, the endpoint is expected + * to be a DID DHT Gateway or Pkarr Relay. If not specified, a default + * gateway node is used. + * @returns A promise that resolves to a {@link DidRegistrationResult} object that contains + * the result of registering the DID with a DID DHT Gateway or Pkarr relay. */ - public static async publish({ did, gatewayUri = DEFAULT_PKARR_RELAY }: { + public static async publish({ did, gatewayUri = DEFAULT_GATEWAY_URI }: { did: BearerDid; gatewayUri?: string; - }): Promise { - const isPublished = await DidDhtDocument.put({ did, relay: gatewayUri }); + }): Promise { + const registrationResult = await DidDhtDocument.put({ did, gatewayUri }); - return isPublished; + return registrationResult; } /** @@ -697,24 +704,25 @@ export class DidDht extends DidMethod { * * @param didUri - The DID to be resolved. * @param options - Optional parameters for resolving the DID. Unused by this DID method. - * @returns A Promise resolving to a {@link DidResolutionResult} object representing the result of the resolution. + * @returns A Promise resolving to a {@link DidResolutionResult} object representing the result of + * the resolution. */ public static async resolve(didUri: string, options: DidResolutionOptions = {}): Promise { - // To execute the read method operation, use the given gateway URI or a default Pkarr relay. - const relay = options?.gatewayUri ?? DEFAULT_PKARR_RELAY; + // To execute the read method operation, use the given gateway URI or a default. + const gatewayUri = options?.gatewayUri ?? DEFAULT_GATEWAY_URI; try { // Attempt to decode the z-base-32-encoded identifier. await DidDhtUtils.identifierToIdentityKey({ didUri }); // Attempt to retrieve the DID document and metadata from the DHT network. - const { didDocument, didMetadata } = await DidDhtDocument.get({ didUri, relay }); + const { didDocument, didDocumentMetadata } = await DidDhtDocument.get({ didUri, gatewayUri }); // If the DID document was retrieved successfully, return it. return { ...EMPTY_DID_RESOLUTION_RESULT, didDocument, - didDocumentMetadata: { published: true, ...didMetadata } + didDocumentMetadata }; } catch (error: any) { @@ -802,8 +810,10 @@ export class DidDht extends DidMethod { didDocument.service.push(service); }); - // Define DID Metadata, including the registered DID types (if any). + // Define DID Metadata, including the registered DID types (if any) and specify that the DID + // has not yet been published. const metadata: DidMetadata = { + published: false, ...options.types && { types: options.types } }; @@ -874,34 +884,38 @@ export class DidDht extends DidMethod { * Mainline DHT in support of DID DHT method create, resolve, update, and deactivate operations. * * This class includes methods for retrieving and publishing DID documents to and from the DHT, - * using DNS packet encoding and Pkarr relay servers. + * using DNS packet encoding and DID DHT Gateway or Pkarr Relay servers. */ -class DidDhtDocument { +export class DidDhtDocument { /** * Retrieves a DID document and its metadata from the DHT network. * * @param params - The parameters for the get operation. * @param params.didUri - The DID URI containing the Identity Key. - * @param params.relay - The Pkarr relay server URL. - * @returns A promise resolving to an object containing the DID document and its metadata. + * @param params.gatewayUri - The DID DHT Gateway or Pkarr Relay URI. + * @returns A Promise resolving to a {@link DidResolutionResult} object containing the DID + * document and its metadata. */ - public static async get({ didUri, relay }: { + public static async get({ didUri, gatewayUri }: { didUri: string; - relay: string; - }): Promise<{ didDocument: DidDocument, didMetadata: DidMetadata }> { + gatewayUri: string; + }): Promise { // Decode the z-base-32 DID identifier to public key as a byte array. const publicKeyBytes = DidDhtUtils.identifierToIdentityKeyBytes({ didUri }); - // Retrieve the signed BEP44 message from a Pkarr relay. - const bep44Message = await DidDhtDocument.pkarrGet({ relay, publicKeyBytes }); + // Retrieve the signed BEP44 message from a DID DHT Gateway or Pkarr relay. + const bep44Message = await DidDhtDocument.pkarrGet({ gatewayUri, publicKeyBytes }); // Verify the signature of the BEP44 message and parse the value to a DNS packet. const dnsPacket = await DidDhtUtils.parseBep44GetMessage({ bep44Message }); - // Convert the DNS packet to a DID document and DID metadata. - const { didDocument, didMetadata } = await DidDhtDocument.fromDnsPacket({ didUri, dnsPacket }); + // Convert the DNS packet to a DID document and metadata. + const resolutionResult = await DidDhtDocument.fromDnsPacket({ didUri, dnsPacket }); - return { didDocument, didMetadata }; + // Set the version ID of the DID document metadata to the sequence number of the BEP44 message. + resolutionResult.didDocumentMetadata.versionId = bep44Message.seq.toString(); + + return resolutionResult; } /** @@ -909,14 +923,14 @@ class DidDhtDocument { * * @param params - The parameters to use when publishing the DID document to the DHT network. * @param params.did - The DID object whose DID document will be published. - * @param params.relay - The Pkarr relay to use when publishing the DID document. - * @returns A promise that resolves to `true` if the DID document was published successfully. - * If publishing fails, `false` is returned. + * @param params.gatewayUri - The DID DHT Gateway or Pkarr Relay URI. + * @returns A promise that resolves to a {@link DidRegistrationResult} object that contains + * the result of registering the DID with a DID DHT Gateway or Pkarr relay. */ - public static async put({ did, relay }: { + public static async put({ did, gatewayUri }: { did: BearerDid; - relay: string; - }): Promise { + gatewayUri: string; + }): Promise { // Convert the DID document and DID metadata (such as DID types) to a DNS packet. const dnsPacket = await DidDhtDocument.toDnsPacket({ didDocument : did.didDocument, @@ -931,31 +945,43 @@ class DidDhtDocument { }); // Publish the DNS packet to the DHT network. - const putResult = await DidDhtDocument.pkarrPut({ relay, bep44Message }); + const putResult = await DidDhtDocument.pkarrPut({ gatewayUri, bep44Message }); + + // Update the DID metadata with the version ID and the publishing result. + const didRegistrationResult: DidRegistrationResult = { + didDocument : did.didDocument, + didDocumentMetadata : { + ...did.metadata, + published : putResult, + versionId : bep44Message.seq.toString() + }, + didRegistrationMetadata: {} + }; - return putResult; + return didRegistrationResult; } /** - * Retrieves a signed BEP44 message from a Pkarr relay server. + * Retrieves a signed BEP44 message from a DID DHT Gateway or Pkarr Relay server. * * @see {@link https://github.com/Nuhvi/pkarr/blob/main/design/relays.md | Pkarr Relay design} * - * @param relay - The Pkarr relay server URL. - * @param publicKeyBytes - The public key bytes of the Identity Key, z-base-32 encoded. + * @param params + * @param params.gatewayUri - The DID DHT Gateway or Pkarr Relay URI. + * @param params.publicKeyBytes - The public key bytes of the Identity Key, z-base-32 encoded. * @returns A promise resolving to a BEP44 message containing the signed DNS packet. */ - private static async pkarrGet({ relay, publicKeyBytes }: { + private static async pkarrGet({ gatewayUri, publicKeyBytes }: { publicKeyBytes: Uint8Array; - relay: string; + gatewayUri: string; }): Promise { // The identifier (key in the DHT) is the z-base-32 encoding of the Identity Key. const identifier = Convert.uint8Array(publicKeyBytes).toBase32Z(); - // Concatenate the Pkarr relay URL with the identifier to form the full URL. - const url = new URL(identifier, relay).href; + // Concatenate the gateway URI with the identifier to form the full URL. + const url = new URL(identifier, gatewayUri).href; - // Transmit the Get request to the Pkarr relay and get the response. + // Transmit the Get request to the DID DHT Gateway or Pkarr Relay and get the response. let response: Response; try { response = await fetch(url, { method: 'GET' }); @@ -992,23 +1018,24 @@ class DidDhtDocument { } /** - * Publishes a signed BEP44 message to a Pkarr relay server. + * Publishes a signed BEP44 message to a DID DHT Gateway or Pkarr Relay server. * * @see {@link https://github.com/Nuhvi/pkarr/blob/main/design/relays.md | Pkarr Relay design} * - * @param relay - The Pkarr relay server URL. - * @param bep44Message - The BEP44 message to be published, containing the signed DNS packet. + * @param params - The parameters to use when publishing a signed BEP44 message to a Pkarr relay server. + * @param params.gatewayUri - The DID DHT Gateway or Pkarr Relay URI. + * @param params.bep44Message - The BEP44 message to be published, containing the signed DNS packet. * @returns A promise resolving to `true` if the message was successfully published, otherwise `false`. */ - private static async pkarrPut({ relay, bep44Message }: { + private static async pkarrPut({ gatewayUri, bep44Message }: { bep44Message: Bep44Message; - relay: string; + gatewayUri: string; }): Promise { // The identifier (key in the DHT) is the z-base-32 encoding of the Identity Key. const identifier = Convert.uint8Array(bep44Message.k).toBase32Z(); - // Concatenate the Pkarr relay URL with the identifier to form the full URL. - const url = new URL(identifier, relay).href; + // Concatenate the gateway URI with the identifier to form the full URL. + const url = new URL(identifier, gatewayUri).href; // Construct the body of the request according to the Pkarr relay specification. const body = new Uint8Array(bep44Message.v.length + 72); @@ -1041,15 +1068,20 @@ class DidDhtDocument { * @param params - The parameters to use when converting a DNS packet to a DID document. * @param params.didUri - The DID URI of the DID document. * @param params.dnsPacket - The DNS packet to convert to a DID document. - * @returns A promise that resolves to a DID document. + * @returns A Promise resolving to a {@link DidResolutionResult} object containing the DID + * document and its metadata. */ private static async fromDnsPacket({ didUri, dnsPacket }: { didUri: string; dnsPacket: Packet; - }): Promise<{ didDocument: DidDocument, didMetadata: DidMetadata }> { + }): Promise { // Begin constructing the DID Document. const didDocument: DidDocument = { id: didUri }; - const didMetadata: DidMetadata = {}; + + // Since the DID document is being retrieved from the DHT, it is considered published. + const didDocumentMetadata: DidMetadata = { + published: true + }; const idLookup = new Map(); @@ -1147,7 +1179,7 @@ class DidDhtDocument { const { id: types } = DidDhtUtils.parseTxtDataToObject(answer.data); // Add the DID DHT Registered DID Types represented as numbers to DID metadata. - didMetadata.types = types.split(VALUE_SEPARATOR).map(typeInteger => Number(typeInteger)); + didDocumentMetadata.types = types.split(VALUE_SEPARATOR).map(typeInteger => Number(typeInteger)); break; } @@ -1175,7 +1207,7 @@ class DidDhtDocument { } } - return { didDocument, didMetadata }; + return { didDocument, didDocumentMetadata, didResolutionMetadata: {} }; } /** @@ -1349,7 +1381,7 @@ class DidDhtDocument { * This includes functions for creating and parsing BEP44 messages, handling identity keys, and * converting between different formats and representations. */ -class DidDhtUtils { +export class DidDhtUtils { /** * Creates a BEP44 put message, which is used to publish a DID document to the DHT network. * diff --git a/packages/dids/src/methods/did-ion.ts b/packages/dids/src/methods/did-ion.ts index 1e26ba1c9..e58ce448e 100644 --- a/packages/dids/src/methods/did-ion.ts +++ b/packages/dids/src/methods/did-ion.ts @@ -9,6 +9,9 @@ import type { import { IonDid, IonRequest } from '@decentralized-identity/ion-sdk'; import { LocalKeyManager, computeJwkThumbprint } from '@web5/crypto'; +import type { BearerDid } from '../bearer-did.js'; +import type { DidCreateOptions, DidCreateVerificationMethod } from '../methods/did-method.js'; +import type { DidMetadata, PortableDid, PortableDidVerificationMethod } from '../portable-did.js'; import type { DidService, DidDocument, @@ -16,14 +19,6 @@ import type { DidResolutionOptions, DidVerificationMethod, } from '../types/did-core.js'; -import type { - BearerDid, - DidMetadata, - PortableDid, - DidCreateOptions, - DidCreateVerificationMethod, - PortableDidVerificationMethod, -} from '../methods/did-method.js'; import { Did } from '../did.js'; import { DidMethod } from '../methods/did-method.js'; @@ -631,7 +626,7 @@ export class DidIon extends DidMethod { /** * The `DidIonUtils` class provides utility functions to support operations in the DID ION method. */ -class DidIonUtils { +export class DidIonUtils { /** * Appends a specified path to a base URL, ensuring proper formatting of the resulting URL. * diff --git a/packages/dids/src/methods/did-jwk.ts b/packages/dids/src/methods/did-jwk.ts index 70d9e0a6c..27848f7c7 100644 --- a/packages/dids/src/methods/did-jwk.ts +++ b/packages/dids/src/methods/did-jwk.ts @@ -11,7 +11,9 @@ import type { import { Convert } from '@web5/common'; import { LocalKeyManager } from '@web5/crypto'; -import type { BearerDid, DidCreateOptions, DidCreateVerificationMethod, DidMetadata, PortableDid } from './did-method.js'; +import type { BearerDid } from '../bearer-did.js'; +import type { DidMetadata, PortableDid } from '../portable-did.js'; +import type { DidCreateOptions, DidCreateVerificationMethod } from './did-method.js'; import type { DidDocument, DidResolutionOptions, DidResolutionResult, DidVerificationMethod } from '../types/did-core.js'; import { Did } from '../did.js'; diff --git a/packages/dids/src/methods/did-key.ts b/packages/dids/src/methods/did-key.ts index 5180bddbf..a42ca67b2 100644 --- a/packages/dids/src/methods/did-key.ts +++ b/packages/dids/src/methods/did-key.ts @@ -20,7 +20,9 @@ import { LocalKeyManager, } from '@web5/crypto'; -import type { BearerDid, DidCreateOptions, DidCreateVerificationMethod, DidMetadata, PortableDid } from './did-method.js'; +import type { BearerDid } from '../bearer-did.js'; +import type { DidMetadata, PortableDid } from '../portable-did.js'; +import type { DidCreateOptions, DidCreateVerificationMethod } from './did-method.js'; import type { DidDocument, DidResolutionOptions, DidResolutionResult, DidVerificationMethod } from '../types/did-core.js'; import { Did } from '../did.js'; diff --git a/packages/dids/src/methods/did-method.ts b/packages/dids/src/methods/did-method.ts index 08a884626..3699635d4 100644 --- a/packages/dids/src/methods/did-method.ts +++ b/packages/dids/src/methods/did-method.ts @@ -8,6 +8,8 @@ import type { InferKeyGeneratorAlgorithm, } from '@web5/crypto'; +import type { BearerDid } from '../bearer-did.js'; +import type { DidMetadata, PortableDid } from '../portable-did.js'; import type { DidDocument, DidResolutionResult, @@ -16,46 +18,8 @@ import type { } from '../types/did-core.js'; import { getVerificationMethodByKey } from '../utils.js'; -import { DidVerificationRelationship } from '../types/did-core.js'; import { DidError, DidErrorCode } from '../did-error.js'; - -/** - * Represents a Decentralized Identifier (DID) along with its DID document, key manager, metadata, - * and convenience functions. - */ -export interface BearerDid { - /** - * The DID document associated with this DID. - */ - didDocument: DidDocument; - - /** - * Returns a {@link @web5/crypto#Signer} that can be used to sign messages, credentials, or - * arbitrary data. - * - * If given, the `keyUri` parameter is used to select a key from the verification methods present - * in the DID Document. If `keyUri` is not given, each DID method implementation will select a - * default verification method key from the DID Document. - * - * @param params - The parameters for the `getSigner` operation. - * @param params.keyUri - Key URI of the key that will be used for sign and verify operations. Optional. - * @returns An instantiated {@link Signer} that can be used to sign and verify data. - */ - getSigner: (params?: { keyUri?: string }) => Promise; - - /** - * Key Management System (KMS) used to manage a DIDs keys and sign data. - * - * Each DID method requires at least one key be present in the provided `keyManager`. - */ - keyManager: CryptoApi; - - /** {@inheritDoc DidMetadata} */ - metadata: DidMetadata; - - /** {@inheritDoc Did#uri} */ - uri: string; -} +import { DidVerificationRelationship } from '../types/did-core.js'; /** * Represents options during the creation of a Decentralized Identifier (DID). @@ -116,14 +80,6 @@ export interface DidCreateVerificationMethod extends Pick { /** - * Express the private key in JWK format. + * Metadata about the DID Document. + * + * This structure contains information about the DID Document like creation and update timestamps, + * deactivation status, versioning information, and other details relevant to the DID Document. * - * (Optional) A JSON Web Key that conforms to {@link https://datatracker.ietf.org/doc/html/rfc7517 | RFC 7517}. + * @see {@link https://www.w3.org/TR/did-core/#dfn-diddocumentmetadata | DID Core Specification, § DID Document Metadata} */ - privateKeyJwk?: Jwk; + didDocumentMetadata: DidMetadata; /** - * Optionally specify the purposes for which a verification method is intended to be used in a DID - * document. - * - * The `purposes` property defines the specific - * {@link DidVerificationRelationship | verification relationships} between the DID subject and - * the verification method. This enables the verification method to be utilized for distinct - * actions such as authentication, assertion, key agreement, capability delegation, and others. It - * is important for verifiers to recognize that a verification method must be associated with the - * relevant purpose in the DID document to be valid for that specific use case. + * A metadata structure consisting of values relating to the results of the DID registration + * process. * - * @example - * ```ts - * const verificationMethod: PortableDidVerificationMethod = { - * publicKeyJwk: { - * kty: "OKP", - * crv: "X25519", - * x: "7XdJtNmJ9pV_O_3mxWdn6YjiHJ-HhNkdYQARzVU_mwY", - * kid: "xtsuKULPh6VN9fuJMRwj66cDfQyLaxuXHkMlmAe_v6I" - * }, - * privateKeyJwk: { - * kty: "OKP", - * crv: "X25519", - * d: "qM1E646TMZwFcLwRAFwOAYnTT_AvbBd3NBGtGRKTyU8", - * x: "7XdJtNmJ9pV_O_3mxWdn6YjiHJ-HhNkdYQARzVU_mwY", - * kid: "xtsuKULPh6VN9fuJMRwj66cDfQyLaxuXHkMlmAe_v6I" - * }, - * purposes: ['authentication', 'assertionMethod'] - * }; - * ``` + * This structure is REQUIRED, and in the case of an error in the registration process, + * this MUST NOT be empty. If the registration is not successful, this structure MUST contain an + * `error` property describing the error. */ - purposes?: (DidVerificationRelationship | keyof typeof DidVerificationRelationship)[]; + didRegistrationMetadata: DidRegistrationMetadata; } +/** + * Represents metadata related to the result of a DID registration operation. + * + * This type includes fields that provide information about the outcome of a DID registration + * process (e.g., create, update, deactivate), including any errors that occurred. + * + * This metadata typically changes between invocations of the `create`, `update`, and `deactivate` + * functions, as it represents data about the registration process itself. + */ +export type DidRegistrationMetadata = { + /** + * An error code indicating issues encountered during the DID registration process. + * + * While the DID Core specification does not define a specific set of error codes for the result + * returned by the `create`, `update`, or `deactivate` functions, it is recommended to use the + * error codes defined in the DID Specification Registries for + * {@link https://www.w3.org/TR/did-spec-registries/#error | DID Resolution Metadata }. + * + * Recommended error codes include: + * - `internalError`: An unexpected error occurred during DID registration process. + * - `invalidDid`: The provided DID is invalid. + * - `invalidDidDocument`: The provided DID document does not conform to valid syntax. + * - `invalidDidDocumentLength`: The byte length of the provided DID document does not match the expected value. + * - `invalidSignature`: Verification of a signature failed. + * - `methodNotSupported`: The DID method specified is not supported. + * - Custom error codes can also be provided as strings. + */ + error?: string; + + // Additional output metadata generated during DID registration. + [key: string]: any; +}; + /** * Base abstraction for all Decentralized Identifier (DID) method implementations. * diff --git a/packages/dids/src/portable-did.ts b/packages/dids/src/portable-did.ts new file mode 100644 index 000000000..b01a11c64 --- /dev/null +++ b/packages/dids/src/portable-did.ts @@ -0,0 +1,106 @@ +import type { Jwk } from '@web5/crypto'; + +import type { DidDocumentMetadata, DidVerificationMethod, DidVerificationRelationship } from './types/did-core.js'; + +/** + * Represents metadata about a DID resulting from create, update, or deactivate operations. + */ +export interface DidMetadata extends DidDocumentMetadata { + /** + * For DID methods that support publishing, the `published` property indicates whether the DID + * document has been published to the respective network. + * + * A `true` value signifies that the DID document is publicly accessible on the network (e.g., + * Mainline DHT), allowing it to be resolved by others. A `false` value implies the DID document + * is not published, limiting its visibility to public resolution. Absence of this property + * indicates that the DID method does not support publishing. + */ + published?: boolean; +} + +/** + * Format to document a DID identifier, along with its associated data, which can be exported, + * saved to a file, or imported. The intent is bundle all of the necessary metadata to enable usage + * of the DID in different contexts. + */ +/** + * Format that documents the key material and metadata of a Decentralized Identifier (DID) to enable + * usage of the DID in different contexts. + * + * This format is useful for exporting, saving to a file, or importing a DID across process + * boundaries or between different DID method implementations. + * + * @example + * ```ts + * // Generate a new DID. + * const did = await DidExample.create(); + * + * // Export the DID to a PortableDid. + * const portableDid = await DidExample.toKeys({ did }); + * + * // Instantiate a BearerDid object from a PortableDid. + * const didFromKeys = await DidExample.fromKeys({ ...portableDid }); + * // The `didFromKeys` object should be equivalent to the original `did` object. + * ``` + */ +export interface PortableDid { + /** {@inheritDoc Did#uri} */ + uri?: string; + + /** + * An array of verification methods, including the key material and key purpose, which are + * included in the DID document. + * + * @see {@link https://www.w3.org/TR/did-core/#verification-methods | DID Core Specification, § Verification Methods} + */ + verificationMethods: PortableDidVerificationMethod[]; +} + +/** + * Represents a verification method within a {@link PortableDid}, including the private key and + * the purposes for which the verification method can be used. + * + * This interface extends {@link DidVerificationMethod}, providing a structure to document the key + * material and metadata associated with a DID's verification methods. + */ +export interface PortableDidVerificationMethod extends Partial { + /** + * Express the private key in JWK format. + * + * (Optional) A JSON Web Key that conforms to {@link https://datatracker.ietf.org/doc/html/rfc7517 | RFC 7517}. + */ + privateKeyJwk?: Jwk; + + /** + * Optionally specify the purposes for which a verification method is intended to be used in a DID + * document. + * + * The `purposes` property defines the specific + * {@link DidVerificationRelationship | verification relationships} between the DID subject and + * the verification method. This enables the verification method to be utilized for distinct + * actions such as authentication, assertion, key agreement, capability delegation, and others. It + * is important for verifiers to recognize that a verification method must be associated with the + * relevant purpose in the DID document to be valid for that specific use case. + * + * @example + * ```ts + * const verificationMethod: PortableDidVerificationMethod = { + * publicKeyJwk: { + * kty: "OKP", + * crv: "X25519", + * x: "7XdJtNmJ9pV_O_3mxWdn6YjiHJ-HhNkdYQARzVU_mwY", + * kid: "xtsuKULPh6VN9fuJMRwj66cDfQyLaxuXHkMlmAe_v6I" + * }, + * privateKeyJwk: { + * kty: "OKP", + * crv: "X25519", + * d: "qM1E646TMZwFcLwRAFwOAYnTT_AvbBd3NBGtGRKTyU8", + * x: "7XdJtNmJ9pV_O_3mxWdn6YjiHJ-HhNkdYQARzVU_mwY", + * kid: "xtsuKULPh6VN9fuJMRwj66cDfQyLaxuXHkMlmAe_v6I" + * }, + * purposes: ['authentication', 'assertionMethod'] + * }; + * ``` + */ + purposes?: (DidVerificationRelationship | keyof typeof DidVerificationRelationship)[]; +} \ No newline at end of file diff --git a/packages/dids/src/types/did-core.ts b/packages/dids/src/types/did-core.ts index fbd8c893b..ae719605e 100644 --- a/packages/dids/src/types/did-core.ts +++ b/packages/dids/src/types/did-core.ts @@ -215,7 +215,7 @@ export interface DidDocument { * * @see {@link https://www.w3.org/TR/did-core/#did-document-metadata | DID Core Specification, § DID Document Metadata} */ -export type DidDocumentMetadata = { +export interface DidDocumentMetadata { /** * Timestamp of the Create operation. * @@ -294,7 +294,7 @@ export type DidDocumentMetadata = { // Additional output metadata generated during DID Resolution. [key: string]: any; -}; +} /** * Represents metadata related to the result of a DID resolution operation. diff --git a/packages/dids/tests/fixtures/test-vectors/did-ion/to-keys.ts b/packages/dids/tests/fixtures/test-vectors/did-ion/to-keys.ts index b69eeace1..c33bac414 100644 --- a/packages/dids/tests/fixtures/test-vectors/did-ion/to-keys.ts +++ b/packages/dids/tests/fixtures/test-vectors/did-ion/to-keys.ts @@ -2,7 +2,7 @@ import type { Jwk, LocalKeyManager } from '@web5/crypto'; import sinon from 'sinon'; -import type { BearerDid } from '../../../../src/methods/did-method.js'; +import type { BearerDid } from '../../../../src/bearer-did.js'; type TestVector = { [key: string]: { diff --git a/packages/dids/tests/methods/did-dht.spec.ts b/packages/dids/tests/methods/did-dht.spec.ts index 5e4088a28..aa1f3e0c1 100644 --- a/packages/dids/tests/methods/did-dht.spec.ts +++ b/packages/dids/tests/methods/did-dht.spec.ts @@ -5,8 +5,8 @@ import { expect } from 'chai'; import { Convert } from '@web5/common'; import { LocalKeyManager } from '@web5/crypto'; +import type { PortableDid } from '../../src/portable-did.js'; import type { DidResolutionResult } from '../../src/index.js'; -import type { PortableDid } from '../../src/methods/did-method.js'; import { DidErrorCode } from '../../src/did-error.js'; import { DidDht, DidDhtRegisteredDidType } from '../../src/methods/did-dht.js'; @@ -318,6 +318,17 @@ describe('DidDht', () => { expect(fetchStub.called).to.be.false; }); + it('returns a version ID in DID metadata when published', async () => { + const did = await DidDht.create(); + expect(did.metadata).to.have.property('versionId'); + expect(did.metadata.versionId).to.be.a.string; + }); + + it('does not return a version ID in DID metadata when not published', async () => { + const did = await DidDht.create({ options: { publish: false } }); + expect(did.metadata).to.not.have.property('versionId'); + }); + it('returns a DID with a getSigner function that can sign and verify data', async () => { const did = await DidDht.create(); @@ -1030,6 +1041,29 @@ describe('DidDht', () => { expect(didResolutionResult.didDocumentMetadata.types).to.include(DidDhtRegisteredDidType.WebApp); }); + it('returns a version ID in DID document metadata', async () => { + // Mock the response from the Pkarr relay rather than calling over the network. + fetchStub.resolves(fetchOkResponse( + Convert.hex('ea33e704f3a48a3392f54b28744cdfb4e24780699f92ba7df62fd486d2a2cda3f263e1c6bcbd' + + '75d438be7316e5d6e94b13e98151f599cfecefad0b37432bd90a0000000065b0ed1600008400' + + '0000000300000000035f6b30045f6469643439746a6f6f773435656631686b736f6f3936626d' + + '7a6b777779336d686d653935643766736933657a6a796a67686d70373571796f000010000100' + + '001c2000373669643d303b743d303b6b3d5f464d49553174425a63566145502d437536715542' + + '6c66466f5f73665332726c4630675362693239323445045f747970045f6469643439746a6f6f' + + '773435656631686b736f6f3936626d7a6b777779336d686d653935643766736933657a6a796a' + + '67686d70373571796f000010000100001c2000070669643d372c36045f6469643439746a6f6f' + + '773435656631686b736f6f3936626d7a6b777779336d686d653935643766736933657a6a796a' + + '67686d70373571796f000010000100001c20002726763d303b766d3d6b303b617574683d6b30' + + '3b61736d3d6b303b64656c3d6b303b696e763d6b30').toArrayBuffer() + )); + + const did = 'did:dht:9tjoow45ef1hksoo96bmzkwwy3mhme95d7fsi3ezjyjghmp75qyo'; + const didResolutionResult = await DidDht.resolve(did); + + expect(didResolutionResult.didDocumentMetadata).to.have.property('versionId'); + expect(didResolutionResult.didDocumentMetadata.versionId).to.be.a.string; + }); + it('returns a notFound error if the DID is not published', async () => { // Mock the response from the Pkarr relay rather than calling over the network. fetchStub.resolves(fetchNotFoundResponse()); diff --git a/packages/dids/tests/methods/did-jwk.spec.ts b/packages/dids/tests/methods/did-jwk.spec.ts index fc8c78c04..49ce27ab5 100644 --- a/packages/dids/tests/methods/did-jwk.spec.ts +++ b/packages/dids/tests/methods/did-jwk.spec.ts @@ -6,7 +6,7 @@ import { expect } from 'chai'; import { LocalKeyManager } from '@web5/crypto'; import type { DidDocument } from '../../src/types/did-core.js'; -import type { PortableDid, PortableDidVerificationMethod } from '../../src/methods/did-method.js'; +import type { PortableDid, PortableDidVerificationMethod } from '../../src/portable-did.js'; import { DidErrorCode } from '../../src/did-error.js'; import { DidJwk } from '../../src/methods/did-jwk.js'; diff --git a/packages/dids/tests/methods/did-key.spec.ts b/packages/dids/tests/methods/did-key.spec.ts index cae7b5b25..dde1e48e4 100644 --- a/packages/dids/tests/methods/did-key.spec.ts +++ b/packages/dids/tests/methods/did-key.spec.ts @@ -5,7 +5,7 @@ import { expect } from 'chai'; import { LocalKeyManager } from '@web5/crypto'; import type { DidDocument } from '../../src/types/did-core.js'; -import type { PortableDid, PortableDidVerificationMethod } from '../../src/methods/did-method.js'; +import type { PortableDid, PortableDidVerificationMethod } from '../../src/portable-did.js'; import { DidErrorCode } from '../../src/did-error.js'; import { DidKey, DidKeyUtils } from '../../src/methods/did-key.js'; diff --git a/packages/dids/tests/methods/did-method.spec.ts b/packages/dids/tests/methods/did-method.spec.ts index 7cb9daf44..efc84cc4c 100644 --- a/packages/dids/tests/methods/did-method.spec.ts +++ b/packages/dids/tests/methods/did-method.spec.ts @@ -4,7 +4,7 @@ import sinon from 'sinon'; import { expect } from 'chai'; import { LocalKeyManager } from '@web5/crypto'; -import type { BearerDid } from '../../src/methods/did-method.js'; +import type { BearerDid } from '../../src/bearer-did.js'; import type { DidDocument, DidVerificationMethod } from '../../src/types/did-core.js'; import { DidMethod } from '../../src/methods/did-method.js';