UNPKG

@did-btcr2/method

Version:

Javascript/TypeScript reference implementation of did:btcr2 method, a censorship resistant DID Method using the Bitcoin blockchain as a Verifiable Data Registry to announce changes to the DID document. Core package of the did-btcr2-js monorepo.

256 lines (223 loc) 10.8 kB
import { Bytes, HashBytes, Logger, W3C_ZCAP_V1 } from '@did-btcr2/common'; import { strings } from '@helia/strings'; import { DidDocument, DidError, DidErrorCode, DidService, DidVerificationRelationship } from '@web5/dids'; import { createHelia } from 'helia'; import { CID } from 'multiformats'; import { create as createDigest } from 'multiformats/hashes/digest'; import { RootCapability } from '../interfaces/crud.js'; import { DidVerificationMethod } from './did-document.js'; export interface DidComponents { hrp: string; idType: string; version: number; network: string; genesisBytes: Bytes; }; /** * Implements {@link https://dcdpr.github.io/did-btcr2/#appendix | 9. Appendix} methods. * * @class Appendix * @type {Appendix} */ export class Appendix { /** * Extracts a DID fragment from a given input * @param {unknown} input The input to extract the DID fragment from * @returns {string | undefined} The extracted DID fragment or undefined if not found */ public static extractDidFragment(input: unknown): string | undefined { if (typeof input !== 'string') return undefined; if (input.length === 0) return undefined; return input; } /** * Validates that the given object is a DidVerificationMethod * @param {unknown} obj The object to validate * @returns {boolean} A boolean indicating whether the object is a DidVerificationMethod */ public static isDidVerificationMethod(obj: unknown): obj is DidVerificationMethod { // Validate that the given value is an object. if (!obj || typeof obj !== 'object' || obj === null) return false; // Validate that the object has the necessary properties of a DidVerificationMethod. if (!('id' in obj && 'type' in obj && 'controller' in obj)) return false; if (typeof obj.id !== 'string') return false; if (typeof obj.type !== 'string') return false; if (typeof obj.controller !== 'string') return false; return true; } /** * Validates that the given object is a DidService * @param {unknown} obj The object to validate * @returns {boolean} A boolean indicating whether the object is a DidService */ public static isDidService(obj: unknown): obj is DidService { // Validate that the given value is an object. if (!obj || typeof obj !== 'object' || obj === null) return false; // Validate that the object has the necessary properties of a DidService. if (!('id' in obj && 'type' in obj && 'serviceEndpoint' in obj)) return false; if (typeof obj.id !== 'string') return false; if (typeof obj.type !== 'string') return false; if (typeof obj.serviceEndpoint !== 'string') return false; return true; } /** * Extracts the verification methods from a given DID Document * @param {DidDocument} params.didDocument The DID Document to extract the verification methods from * @returns {DidVerificationMethod[]} An array of DidVerificationMethod objects * @throws {TypeError} if the didDocument is not provided */ public static getVerificationMethods(didDocument: DidDocument): DidVerificationMethod[] { if (!didDocument) throw new TypeError(`Required parameter missing: 'didDocument'`); const verificationMethods: DidVerificationMethod[] = []; // Check the 'verificationMethod' array. verificationMethods.push(...didDocument.verificationMethod?.filter(Appendix.isDidVerificationMethod) ?? []); // Check verification relationship properties for embedded verification methods. Object.keys(DidVerificationRelationship).forEach((relationship) => { verificationMethods.push( ...(didDocument[relationship as keyof DidDocument] as (DidVerificationMethod)[]) ?.filter(Appendix.isDidVerificationMethod) ?? [] ); }); return verificationMethods as DidVerificationMethod[]; } /** * Implements {@link https://dcdpr.github.io/did-btcr2/#derive-root-capability-from-didbtcr2-identifier | 9.4.1 Derive Root Capability from did:btcr2 Identifier }. * * The Derive Root Capability algorithm deterministically generates a ZCAP-LD root capability from a given did:btcr2 * identifier. Each root capability is unique to the identifier. This root capability is defined and understood by the * did:btcr2 specification as the root capability to authorize updates to the specific did:btcr2 identifiers DID * document. It takes in a did:btcr2 identifier and returns a rootCapability object. It returns the root capability. * * @param {string} identifier The did-btcr2 identifier to derive the root capability from * @returns {RootCapability} The root capability object * @example Root capability for updating the DID document for * did:btcr2:k1q0rnnwf657vuu8trztlczvlmphjgc6q598h79cm6sp7c4fgqh0fkc0vzd9u * ``` * { * "@context": "https://w3id.org/zcap/v1", * "id": "urn:zcap:root:did:btcr2:k1q0rnnwf657vuu8trztlczvlmphjgc6q598h79cm6sp7c4fgqh0fkc0vzd9u", * "controller": "did:btcr2:k1q0rnnwf657vuu8trztlczvlmphjgc6q598h79cm6sp7c4fgqh0fkc0vzd9u", * "invocationTarget": "did:btcr2:k1q0rnnwf657vuu8trztlczvlmphjgc6q598h79cm6sp7c4fgqh0fkc0vzd9u" * } * ``` */ public static deriveRootCapability(identifier: string): RootCapability { // 1. Define rootCapability as an empty object. const rootCapability = {} as RootCapability; // 2. Set rootCapability.@context to ‘https://w3id.org/zcap/v1’. rootCapability['@context'] = W3C_ZCAP_V1; // 3. Set encodedIdentifier to result of calling algorithm encodeURIComponent(identifier). const encodedIdentifier = encodeURIComponent(identifier); // 4. Set rootCapability.id to urn:zcap:root:${encodedIdentifier}. rootCapability.id = `urn:zcap:root:${encodedIdentifier}`; // 5. Set rootCapability.controller to identifier. rootCapability.controller = identifier; // 6. Set rootCapability.invocationTarget to identifier. rootCapability.invocationTarget = identifier; // 7. Return rootCapability. return rootCapability; } /** * Implements {@link https://dcdpr.github.io/did-btcr2/#dereference-root-capability-identifier | 9.4.2 Dereference Root Capability Identifier}. * * This algorithm takes in capabilityId, a root capability identifier, and dereferences it to rootCapability, the root * capability object. * * @param {string} capabilityId The root capability identifier to dereference. * @returns {RootCapability} The root capability object. * @example a didUpdatePayload with an invoked ZCAP-LD capability containing a patch defining how the DID document * for did:btcr2:k1q0rnnwf657vuu8trztlczvlmphjgc6q598h79cm6sp7c4fgqh0fkc0vzd9u SHOULD be mutated. * ``` * { * "@context": [ * "https://w3id.org/zcap/v1", * "https://w3id.org/security/data-integrity/v2", * "https://w3id.org/json-ld-patch/v1" * ], * "patch": [ * { * "op": "add", * "path": "/service/4", * "value": { * "id": "#linked-domain", * "type": "LinkedDomains", * "serviceEndpoint": "https://contact-me.com" * } * } * ], * "proof": { * "type": "DataIntegrityProof", * "cryptosuite": "schnorr-secp256k1-jcs-2025", * "verificationMethod": "did:btcr2:k1q0rnnwf657vuu8trztlczvlmphjgc6q598h79cm6sp7c4fgqh0fkc0vzd9u#initialKey", * "invocationTarget": "did:btcr2:k1q0rnnwf657vuu8trztlczvlmphjgc6q598h79cm6sp7c4fgqh0fkc0vzd9u", * "capability": "urn:zcap:root:did%3Abtcr2%3Ak1q0rnnwf657vuu8trztlczvlmphjgc6q598h79cm6sp7c4fgqh0fkc0vzd9u", * "capabilityAction": "Write", * "proofPurpose": "assertionMethod", * "proofValue": "z381yXYmxU8NudZ4HXY56DfMN6zfD8syvWcRXzT9xD9uYoQToo8QsXD7ahM3gXTzuay5WJbqTswt2BKaGWYn2hHhVFKJLXaDz" * } * } */ public static derefernceRootCapabilityIdentifier(capabilityId: string): RootCapability { // 1. Set rootCapability to an empty object. const rootCapability = {} as RootCapability; // 2. Set components to the result of capabilityId.split(":"). const [urn, zcap, root, did] = capabilityId.split(':') ?? []; // 3. Validate components: // 1. Assert length of components is 4. if ([urn, zcap, root, did].length !== 4) { throw new DidError(DidErrorCode.InvalidDid, `Invalid capabilityId: ${capabilityId}`); } // 2. components[0] == urn. if (!urn || urn !== 'urn') { throw new DidError(DidErrorCode.InvalidDid, `Invalid capabilityId: ${capabilityId}`); } // 3. components[1] == zcap. if (!zcap || zcap !== 'zcap') { throw new DidError(DidErrorCode.InvalidDid, `Invalid capabilityId: ${capabilityId}`); } // 4. components[2] == root. if (!root || root !== 'root') { throw new DidError(DidErrorCode.InvalidDid, `Invalid capabilityId: ${capabilityId}`); } // 4. Set uriEncodedId to components[3]. const uriEncodedId = did; // 5. Set Identifier the result of decodeURIComponent(uriEncodedId). const Identifier = decodeURIComponent(uriEncodedId); // 6. Set rootCapability.id to capabilityId. rootCapability.id = capabilityId; // 7. Set rootCapability.controller to Identifier. rootCapability.controller = Identifier; // 8. Set rootCapability.invocationTarget to Identifier. rootCapability.invocationTarget = Identifier; // 9. Return rootCapability. return rootCapability; } /** * Implements {@link https://dcdpr.github.io/did-btcr2/#fetch-content-from-addressable-storage | 9.3. Fetch Content from Addressable Storage}. * * The Fetch Content from Addressable Storage function takes in SHA256 hash of some content, hashBytes, converts these * bytes to a IPFS v1 Content Identifier and attempts to retrieve the identified content from Content Addressable * Storage (CAS). It returns the retrieved content or null. * * @param {HashBytes} hashBytes The SHA256 hash of the content to be fetched. * @returns {string} The fetched content or null if not found. */ public static async fetchFromCas(hashBytes: HashBytes): Promise<string | undefined> { // 1. Set cid to the result of converting hashBytes to an IPFS v1 CID. const cid = CID.create(1, 1, createDigest(1, hashBytes)); // Create a Helia node connection to IPFS const helia = strings(await createHelia()); // 2. Set content to the result of fetching the cid from a CAS system. Which CAS systems checked is up to implementation. Logger.warn('// TODO: Is this right? Are implementations just supposed to check all CAS they trust?'); const content = await helia.get(cid, {}); // 3. If content for cid cannot be found, set content to null. // 4. Return content. return content; } }