/** * DID method flexibility — pluggable identifier schemes. * * The rest of soma-heart deals in `did:key` identifiers by default: * the DID IS the public key (prefix-encoded), so there's no resolution * step. That's fine for agents minting their own keys, but it doesn't * cover three things operators keep asking about: * * 1. `did:web:example.com` — a domain controls the key, the agent * proves "I belong to example.com" by controlling what's served * at example.com/.well-known/did.json. Useful for branded agents. * 2. `did:pkh:eip155:1:0xABC…` — a blockchain account controls the * key. Useful when the agent is already a wallet on some chain. * 3. Custom / in-house DID methods that resolve to keys via whatever * mechanism the operator has (corporate directory, KMS, etc.). * * This module defines a narrow `DidMethod` interface and a registry, * so soma doesn't have to know how any specific method resolves — * it just asks "give me the verification keys for this DID." * * Built-in methods: * - DidKeyMethod: resolution is sync and key-is-the-identifier. * Matches existing behavior exactly. * - DidWebMethod: resolution fetches the did:web document over HTTPS. * HTTP is pluggable, so tests can inject a stub fetcher. * - DidPkhMethod: format-only. Parses chain + account from the DID * but cannot resolve a key from the DID alone — caller supplies * an out-of-band key binding (e.g. from a prior on-chain signature). * * This does NOT change any existing call sites. Code that wants * multi-method support constructs a `DidMethodRegistry` and calls * `registry.resolve(did)`; code that only cares about `did:key` * continues to use `publicKeyToDid`/`didToPublicKey` unchanged. */ import { type CryptoProvider } from './crypto-provider.js'; /** One verification key attached to a DID document. */ export interface DidVerificationKey { /** Raw public key bytes. */ publicKey: Uint8Array; /** Signing algorithm id (matches SigningProvider.algorithmId). */ algorithmId: string; /** Optional purpose hint (authentication, assertion, keyAgreement). */ purpose?: 'authentication' | 'assertion' | 'keyAgreement'; /** Optional stable id within the document (e.g. "#key-1"). */ keyId?: string; } /** Minimal DID document — just enough to verify signatures. */ export interface DidDocument { did: string; verificationKeys: DidVerificationKey[]; /** Method-specific metadata (e.g. chain/account for did:pkh). */ metadata?: Readonly>; } /** Pluggable DID method implementation. */ export interface DidMethod { /** Method name segment (e.g. "key", "web", "pkh"). */ readonly methodName: string; /** Does this DID belong to this method? */ matches(did: string): boolean; /** * Resolve a DID to its verification material. Always async for interface * uniformity — sync methods just return `Promise.resolve(doc)`. */ resolve(did: string): Promise; /** * Optional: resolve synchronously. Methods that can answer without I/O * (did:key, did:pkh with a sync keyBinder) implement this. Methods that * require HTTP (did:web) omit it or return null. Callers that need sync * verification (most soma-heart verifiers are sync) can use this path * while multi-method-aware code uses `resolve()`. */ resolveSync?(did: string): DidDocument | null; } /** A DID method that can mint new identifiers from a public key. */ export interface MintableDidMethod extends DidMethod { /** * Construct a DID for the given public key (+ method-specific opts). * Throws if the method cannot identify this kind of key. */ identify(publicKey: Uint8Array, opts?: Record): string; } /** * Registry of DID methods. Consumers register the methods they trust, * then `resolve(did)` dispatches by prefix matching. */ export declare class DidMethodRegistry { private readonly methods; register(method: DidMethod): void; get(methodName: string): DidMethod; has(methodName: string): boolean; /** Find the method that claims to match this DID. */ forDid(did: string): DidMethod; /** Resolve a DID through whichever registered method claims it. */ resolve(did: string): Promise; /** * Sync-only resolution. Returns null if no matching method is registered * OR if the matching method does not support sync resolution (e.g. did:web * requires HTTP). Callers must handle null by falling back to `resolve()` * or rejecting the identity. */ resolveSync(did: string): DidDocument | null; list(): string[]; size(): number; } /** * Self-referential DID method: the identifier IS the public key. * Zero network I/O, zero external state. Resolution is a pure decode. */ export declare class DidKeyMethod implements MintableDidMethod { readonly methodName = "key"; private readonly provider; constructor(provider?: CryptoProvider); matches(did: string): boolean; identify(publicKey: Uint8Array): string; resolveSync(did: string): DidDocument | null; resolve(did: string): Promise; } /** Shape of the DID document we expect at /.well-known/did.json. */ export interface DidWebDocumentJson { id?: string; verificationMethod?: { id?: string; type?: string; controller?: string; publicKeyMultibase?: string; publicKeyBase64?: string; publicKeyBase58?: string; }[]; authentication?: unknown[]; assertionMethod?: unknown[]; keyAgreement?: unknown[]; } /** Pluggable HTTP fetcher — inject a stub in tests, real fetch in prod. */ export type DidWebFetcher = (url: string) => Promise; /** * `did:web:example.com` → https://example.com/.well-known/did.json * `did:web:example.com:users:alice` → * https://example.com/users/alice/did.json * * Fetcher is pluggable so this module doesn't pull in a fetch dep and * tests can stub it. Public keys are expected in `publicKeyBase64` * (soma convention) — multibase/base58 fields are rejected. */ export declare class DidWebMethod implements DidMethod { readonly methodName = "web"; private readonly fetcher; private readonly provider; constructor(fetcher: DidWebFetcher, provider?: CryptoProvider); matches(did: string): boolean; /** Convert `did:web:example.com:users:alice` → full URL. */ static didToUrl(did: string): string; resolve(did: string): Promise; } /** Parsed chain + account from a did:pkh. CAIP-10 format. */ export interface PkhIdentifier { /** Namespace (e.g. "eip155" for EVM chains, "solana"). */ chainNamespace: string; /** Network reference (e.g. "1" for Ethereum mainnet). */ chainReference: string; /** Account address as the chain expects it. */ account: string; } /** * `did:pkh:` — blockchain account identifiers. * Parse/format only. A did:pkh doesn't reveal the public key by itself * (EVM addresses are hash(pk), Solana addresses ARE pks but without * algorithm metadata). Callers pass an optional `keyBinder` to attach * verification keys out-of-band. */ export declare class DidPkhMethod implements DidMethod { readonly methodName = "pkh"; private readonly keyBinder?; constructor(keyBinder?: (pkh: PkhIdentifier) => DidVerificationKey[]); matches(did: string): boolean; static parse(did: string): PkhIdentifier; static format(id: PkhIdentifier): string; resolveSync(did: string): DidDocument | null; resolve(did: string): Promise; } /** * Convenience: registry pre-populated with DidKeyMethod. Callers add * did:web and did:pkh as needed (they require a fetcher/keyBinder). */ export declare function createDefaultDidRegistry(provider?: CryptoProvider): DidMethodRegistry; /** * Decide whether `publicKey` is bound to `did` under the given registry. * * This is the workhorse binding check for every sync verifier in soma-heart * (delegations, revocations, sessions, spend receipts, etc.). The caller * passes in: * - the DID the credential claims * - the raw public-key bytes that actually signed the credential * - optionally a registry of trusted DID methods * * Behaviour: * - If a registry is supplied AND one of its methods can resolve the DID * synchronously, the check is "is `publicKey` present among the DID's * verification keys?" (byte-equality, timing-safe). * - If no registry is supplied, OR the matching method has no * `resolveSync`, the function falls back to did:key semantics: * `publicKeyToDid(publicKey) === did`. This is the pre-retrofit * default — it keeps every existing caller working unchanged. * * Returns `{ bound: false, reason }` to communicate why, so callers can * surface a sensible verification error. */ export declare function verifyDidBinding(did: string, publicKey: Uint8Array, registry?: DidMethodRegistry, provider?: CryptoProvider): { bound: true; via: 'did-key-fallback' | 'registry'; } | { bound: false; reason: string; }; /** * Resolve `did`, then check whether `signature` over `message` verifies * against any of the returned verification keys. Returns the matching * key on success, or null on failure. */ export declare function verifySignatureViaDid(did: string, message: Uint8Array, signature: Uint8Array, registry: DidMethodRegistry, provider?: CryptoProvider): Promise; //# sourceMappingURL=did-method.d.ts.map