import type { DIDDocument } from "did-resolver"; import { varint } from "multiformats"; import { base58btc } from "multiformats/bases/base58"; import type { Driver } from "./drivers/interface.ts"; import * as codec from "./codecs/jwk_jcs-pub.ts"; import { KEY_DID_METHOD_PREFIX } from "./constants.ts"; import * as jwkDriver from "./drivers/jwk_jcs-pub.ts"; import { InvalidDidError } from "./errors/index.ts"; /** * Supported drivers */ const codecToDriver: Record = { [codec.code]: jwkDriver, } as const; /** * Encodes the public key (bytes) as: * * ``` * MULTIBASE(base58-btc, MULTICODEC(public-key-type, raw-public-key-bytes)) * ``` * @see https://w3c-ccg.github.io/did-method-key/#format * @see https://github.com/multiformats/multicodec/blob/master/table.csv * @param pubKeyBytes - The "raw-public-key-bytes". * @param code - The "public-key-type" multicodec code. E.g: 0xeb51 * @returns The encoded public key. */ export const encodePublicKey = ( pubKeyBytes: Uint8Array, code: number, ): string => { const size = pubKeyBytes.byteLength; const sizeOffset = varint.encodingLength(code); const messageOffset = sizeOffset; const bytes = new Uint8Array(messageOffset + size); varint.encodeTo(code, bytes, 0); bytes.set(pubKeyBytes, messageOffset); return base58btc.encode(bytes); }; /** * Decodes the multibase-base58btc-encoded public key and the related multicodec code. * @param publicKey - The encoded public key. * @returns The decoded public key (bytes) and the multicodec code. */ export const decodePublicKey = (publicKey: string) => { const multicodecPubKey = base58btc.decode(publicKey); const [code, sizeOffset] = varint.decode(multicodecPubKey); const pubKeyBytes = multicodecPubKey.slice(sizeOffset); return { code, pubKeyBytes, }; }; /** * Resolves a DID document based on the given DID. * @param did - The DID to resolve. * @param contentType - The content type, e.g. "application/did+ld+json" or "application/did+json" (optional). * @returns The DID document decoded from the method specific identifier. */ export function resolveDidDoc(did: string, contentType?: string): DIDDocument { let pubKeyBytes: Uint8Array; let code: number; if (!did || typeof did !== "string") { throw new InvalidDidError("The DID must be a string"); } if (!did.startsWith(KEY_DID_METHOD_PREFIX)) { throw new InvalidDidError( `The DID must start with "${KEY_DID_METHOD_PREFIX}"`, ); } const methodSpecificIdentifier = did.slice(KEY_DID_METHOD_PREFIX.length); if (!methodSpecificIdentifier.startsWith(base58btc.prefix)) { throw new InvalidDidError( `The method-specific identifier must start with "${base58btc.prefix}" (multibase base58btc-encoded)`, ); } try { const decodedResult = decodePublicKey(methodSpecificIdentifier); pubKeyBytes = decodedResult.pubKeyBytes; code = decodedResult.code; } catch { throw new InvalidDidError( "The method-specific identifier is not a valid multibase base58btc-encoded string", ); } const driver = codecToDriver[code]; if (!driver) { throw new InvalidDidError(`Unsupported codec ${code.toString()}`); } const didDocument = driver.pubKeyBytesToDidDoc( pubKeyBytes, methodSpecificIdentifier, contentType, ); return didDocument; }