UNPKG

@nuwa-ai/identity-kit

Version:

SDK for NIP-1 Agent Single DID Multi-Key Model and NIP-3 CADOP (Custodian-Assisted DID Onboarding Protocol)

1,586 lines (1,565 loc) 130 kB
// src/testHelpers/env.ts import { RoochClient as RoochClient2 } from "@roochnetwork/rooch-sdk"; // src/utils/DebugLogger.ts var LEVEL_ORDER = { debug: 10, info: 20, warn: 30, error: 40, silent: 50 }; function detectInitialGlobalLevel() { if (typeof process !== "undefined" && process.env) { const envLevel = process.env.NUWA_LOG_LEVEL; if (envLevel && envLevel in LEVEL_ORDER) return envLevel; } if (typeof window !== "undefined" && window.__NUWA_LOG_LEVEL__) { const envLevel = window.__NUWA_LOG_LEVEL__; if (envLevel && envLevel in LEVEL_ORDER) return envLevel; } return "info"; } var _DebugLogger = class _DebugLogger { constructor(namespace) { this.namespace = namespace; this.levelOverridden = false; this.level = _DebugLogger.globalLevel; } /** Acquire (or create) a logger for the given namespace. */ static get(namespace) { if (!_DebugLogger.loggers.has(namespace)) { _DebugLogger.loggers.set(namespace, new _DebugLogger(namespace)); } return _DebugLogger.loggers.get(namespace); } /** Override global log level at runtime. */ static setGlobalLevel(level) { _DebugLogger.globalLevel = level; for (const logger3 of _DebugLogger.loggers.values()) { if (!logger3.levelOverridden) { logger3.level = level; } } } /** Read current global level. */ static getGlobalLevel() { return _DebugLogger.globalLevel; } /** Set default namespace used by static convenience methods. */ static setDefaultNamespace(namespace) { _DebugLogger.defaultNamespace = namespace; } // --------------------------------------------------------------------------- // Static convenience logging methods // --------------------------------------------------------------------------- /** * Log using the default namespace. Useful when callers don't need per-module loggers. * Example: DebugLogger.debug('hello') */ static debug(...args) { _DebugLogger.get(_DebugLogger.defaultNamespace).debug(...args); } static info(...args) { _DebugLogger.get(_DebugLogger.defaultNamespace).info(...args); } static warn(...args) { _DebugLogger.get(_DebugLogger.defaultNamespace).warn(...args); } static error(...args) { _DebugLogger.get(_DebugLogger.defaultNamespace).error(...args); } /** Override level for this logger only. */ setLevel(level) { this.level = level; this.levelOverridden = true; } // ------------------------------------------------------- // Logging helpers // ------------------------------------------------------- debug(...args) { this._log("debug", args); } info(...args) { this._log("info", args); } warn(...args) { this._log("warn", args); } error(...args) { this._log("error", args); } // prettier-ignore _log(level, args) { if (LEVEL_ORDER[level] < LEVEL_ORDER[this.level]) { return; } const prefix = `[${this.namespace}]`; switch (level) { case "debug": console.debug(prefix, ...args); break; case "info": console.info(prefix, ...args); break; case "warn": console.warn(prefix, ...args); break; case "error": console.error(prefix, ...args); break; } } }; // --------------------------------------------------------------------------- // Static section // --------------------------------------------------------------------------- _DebugLogger.globalLevel = detectInitialGlobalLevel(); _DebugLogger.loggers = /* @__PURE__ */ new Map(); _DebugLogger.defaultNamespace = "global"; var DebugLogger = _DebugLogger; // src/cache/InMemoryLRUDIDDocumentCache.ts var InMemoryLRUDIDDocumentCache = class { constructor(maxEntries = 1e3) { this.capacity = maxEntries; this.map = /* @__PURE__ */ new Map(); } get(did) { if (!this.map.has(did)) return void 0; const value = this.map.get(did) ?? null; this.map.delete(did); this.map.set(did, value); return value; } set(did, doc) { if (this.map.has(did)) { this.map.delete(did); } else if (this.map.size >= this.capacity) { const lruKey = this.map.keys().next().value; if (lruKey !== void 0) { this.map.delete(lruKey); } } this.map.set(did, doc); } has(did) { return this.map.has(did); } delete(did) { this.map.delete(did); } clear() { this.map.clear(); } }; // src/vdr/VDRRegistry.ts var VDRRegistry = class _VDRRegistry { constructor() { this.vdrs = /* @__PURE__ */ new Map(); this.cache = new InMemoryLRUDIDDocumentCache(); } static getInstance() { if (!this.instance) { this.instance = new _VDRRegistry(); } return this.instance; } /** Register a VDR implementation for its DID method (e.g., 'key', 'rooch'). */ registerVDR(vdr) { this.vdrs.set(vdr.getMethod(), vdr); } /** Retrieve a previously registered VDR implementation by its method. */ getVDR(method) { return this.vdrs.get(method); } /** * Override the default cache implementation. * This allows developers to provide their own cache (e.g., Redis, browser storage). */ setCache(cache) { this.cache = cache; } /** Returns the currently configured cache instance. */ getCache() { return this.cache; } async resolveDID(did, options) { const method = did.split(":")[1]; const vdr = this.vdrs.get(method); if (!vdr) { throw new Error(`No VDR available for method: ${method}`); } if (!options?.forceRefresh) { const cached = this.cache.get(did); if (cached !== void 0) { return cached; } } const resolved = await vdr.resolve(did); this.cache.set(did, resolved); return resolved; } async createDID(method, creationRequest, options) { const vdr = this.vdrs.get(method); if (!vdr) { throw new Error(`No VDR available for method: ${method}`); } const result = await vdr.create(creationRequest, options); if (result.success && result.didDocument) { this.cache.set(result.didDocument.id, result.didDocument); } return result; } async createDIDViaCADOP(method, creationRequest, options) { const vdr = this.vdrs.get(method); if (!vdr) { throw new Error(`No VDR available for method: ${method}`); } const result = await vdr.createViaCADOP(creationRequest, options); if (result.success && result.didDocument) { this.cache.set(result.didDocument.id, result.didDocument); } return result; } async exists(did) { const method = did.split(":")[1]; const vdr = this.vdrs.get(method); if (!vdr) { throw new Error(`No VDR available for method: ${method}`); } if (this.cache.has(did)) { const doc = this.cache.get(did); return doc !== null; } const exists = await vdr.exists(did); return exists; } }; // src/vdr/roochVDR.ts import { RoochClient, Transaction as Transaction2, Args, getRoochNodeUrl as sdkGetRoochNodeUrl, Signer as Signer2 } from "@roochnetwork/rooch-sdk"; // src/multibase/base.ts import { base58btc } from "multiformats/bases/base58"; import { base64pad, base64, base64url, base64urlpad } from "multiformats/bases/base64"; import { base16 } from "multiformats/bases/base16"; // src/utils/bytes.ts function stringToBytes(str) { if (typeof TextEncoder !== "undefined") { return new TextEncoder().encode(str); } if (typeof Buffer !== "undefined" && typeof Buffer.from === "function") { return Uint8Array.from(Buffer.from(str, "utf-8")); } throw new Error("No TextEncoder or Buffer available in this environment."); } function bytesToString(bytes) { if (typeof TextDecoder !== "undefined") { return new TextDecoder("utf-8").decode(bytes); } if (typeof Buffer !== "undefined" && typeof Buffer.from === "function") { return Buffer.from(bytes).toString("utf-8"); } throw new Error("No TextDecoder or Buffer available in this environment."); } function base64urlToBytes(base64url2) { const padding = base64url2.length % 4; const paddedBase64url = base64url2 + "=".repeat(padding === 0 ? 0 : 4 - padding); const base642 = paddedBase64url.replace(/-/g, "+").replace(/_/g, "/"); const binaryString = atob(base642); const bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); } return bytes; } // src/multibase/base.ts var ENCODER_MAP = { base58btc, base64pad, base64, base64url, base64urlpad, base16 }; var MultibaseCodec = class { /** * Generic encode * Example: `MultibaseCodec.encode(bytes, 'base64url')` */ static encode(data, base) { const encoder = ENCODER_MAP[base]; if (!encoder) { throw new Error(`Unsupported multibase: ${base}`); } const bytes = typeof data === "string" ? stringToBytes(data) : data; return encoder.encode(bytes); } /** * Encode bytes to base58btc format * @param bytes The bytes to encode * @returns base58btc encoded string with 'z' prefix */ static encodeBase58btc(bytes) { return this.encode(bytes, "base58btc"); } /** * Encode bytes to base64pad format * @param bytes The bytes to encode * @returns base64pad encoded string with 'M' prefix */ static encodeBase64pad(data) { return this.encode(data, "base64pad"); } /** * Encode bytes to base16 (hex) format * @param bytes The bytes to encode * @returns base16 encoded string with 'f' prefix */ static encodeBase16(bytes) { return this.encode(bytes, "base16"); } /** * Encode bytes to base64 format * @param bytes The bytes to encode * @returns base64 encoded string */ static encodeBase64(data) { return this.encode(data, "base64"); } /** * Encode bytes to base64url format (RFC4648 URL-safe, no padding) * @param bytes The bytes to encode * @returns base64url encoded string with 'u' prefix */ static encodeBase64url(data) { return this.encode(data, "base64url"); } /** * Encode bytes to base64urlpad format (URL-safe with padding) * @param bytes The bytes to encode * @returns base64urlpad encoded string with 'U' prefix */ static encodeBase64urlpad(data) { return this.encode(data, "base64urlpad"); } /** * Decode base58btc string to bytes * @param encoded The base58btc encoded string * @returns decoded bytes */ static decodeBase58btc(encoded) { return base58btc.decode(encoded); } /** * Decode base64pad string to bytes * @param encoded The base64pad encoded string * @returns decoded bytes */ static decodeBase64pad(encoded) { return base64pad.decode(encoded); } /** * Decode base16 string to bytes * @param encoded The base16 encoded string * @returns decoded bytes */ static decodeBase16(encoded) { return base16.decode(encoded); } /** * Decode base64 string to bytes * @param encoded The base64 encoded string * @returns decoded bytes */ static decodeBase64(encoded) { return base64.decode(encoded); } /** * Decode base64url string to bytes * @param encoded The base64url encoded string * @returns decoded bytes */ static decodeBase64url(encoded) { return base64url.decode(encoded); } /** * Decode base64url string to string * @param encoded The base64url encoded string * @returns decoded string */ static decodeBase64urlToString(encoded) { return bytesToString(this.decodeBase64url(encoded)); } /** * Decode base64urlpad string to bytes * @param encoded The base64urlpad encoded string * @returns decoded bytes */ static decodeBase64urlpad(encoded) { return base64urlpad.decode(encoded); } /** * Decode base64urlpad string to string * @param encoded The base64urlpad encoded string * @returns decoded string */ static decodeBase64urlpadToString(encoded) { return bytesToString(this.decodeBase64urlpad(encoded)); } /** * Decode multibase encoded string to bytes * After multiformats v9, there is no longer a single "universal base" object; * the official recommendation is to manually dispatch prefixes between the few *.decoder objects you use. * @param encoded The multibase encoded string * @returns decoded bytes */ static decode(encoded) { const prefix = encoded[0]; switch (prefix) { case "z": return base58btc.decode(encoded); case "M": return base64pad.decode(encoded); case "f": return base16.decode(encoded); case "m": return base64.decode(encoded); case "u": return base64url.decode(encoded); case "U": return base64urlpad.decode(encoded); default: throw new Error(`Unsupported multibase prefix: ${prefix}`); } } }; // src/types/crypto.ts var KeyType = /* @__PURE__ */ ((KeyType7) => { KeyType7["ED25519"] = "Ed25519VerificationKey2020"; KeyType7["SECP256K1"] = "EcdsaSecp256k1VerificationKey2019"; KeyType7["ECDSAR1"] = "EcdsaSecp256r1VerificationKey2019"; return KeyType7; })(KeyType || {}); var KEY_TYPE = KeyType; function isKeyType(value) { return Object.values(KeyType).includes(value); } function toKeyType(value) { if (isKeyType(value)) { return value; } throw new Error(`Invalid key type: ${value}`); } function roochSignatureSchemeToKeyType(scheme) { if (scheme === "Secp256k1") { return "EcdsaSecp256k1VerificationKey2019" /* SECP256K1 */; } else if (scheme === "ED25519") { return "Ed25519VerificationKey2020" /* ED25519 */; } else if (scheme === "EcdsaR1") { return "EcdsaSecp256r1VerificationKey2019" /* ECDSAR1 */; } throw new Error(`Unsupported Rooch signature scheme: ${scheme}`); } function keyTypeToRoochSignatureScheme(keyType) { if (keyType === "EcdsaSecp256k1VerificationKey2019" /* SECP256K1 */) { return "Secp256k1"; } else if (keyType === "Ed25519VerificationKey2020" /* ED25519 */) { return "ED25519"; } else if (keyType === "EcdsaSecp256r1VerificationKey2019" /* ECDSAR1 */) { return "EcdsaR1"; } throw new Error(`Unsupported key type: ${keyType}`); } // src/multibase/key.ts var KeyMultibaseCodec = class { /** * Encode public key with multicodec prefix * @param bytes The public key bytes * @param keyType The key type * @returns multibase encoded string */ static encodeWithType(bytes, keyType) { const expectedLength = this.getExpectedKeyLength(keyType); if (bytes.length !== expectedLength) { throw new Error( `Invalid key length for ${keyType}. Expected ${expectedLength} bytes, got ${bytes.length}` ); } const prefix = this.getMulticodecPrefix(keyType); const prefixedKey = this.concatenateBytes(prefix, bytes); return MultibaseCodec.encodeBase58btc(prefixedKey); } /** * Decode multibase encoded key * @param encoded The multibase encoded string * @returns The key type and public key bytes */ static decodeWithType(encoded) { try { const decoded = MultibaseCodec.decodeBase58btc(encoded); if (decoded.length < 2) { throw new Error("Invalid key format: too short"); } const keyType = this.extractKeyType(decoded); const bytes = this.extractBytes(decoded); const expectedLength = this.getExpectedKeyLength(keyType); if (bytes.length !== expectedLength) { throw new Error( `Invalid key length for ${keyType}. Expected ${expectedLength} bytes, got ${bytes.length}` ); } return { keyType, bytes }; } catch (error) { if (error instanceof Error && error.message === "Non-base58btc character") { throw new Error("Invalid multibase format"); } throw error; } } static getMulticodecPrefix(keyType) { switch (keyType) { case KEY_TYPE.ED25519: return this.ED25519_PREFIX; case KEY_TYPE.SECP256K1: return this.SECP256K1_PREFIX; case KEY_TYPE.ECDSAR1: return this.ECDSA_R1_PREFIX; default: throw new Error(`Unsupported key type: ${keyType}`); } } static concatenateBytes(a, b) { const result = new Uint8Array(a.length + b.length); result.set(a); result.set(b, a.length); return result; } static extractKeyType(prefixedBytes) { if (prefixedBytes[0] === 237 && prefixedBytes[1] === 1) { return KEY_TYPE.ED25519; } else if (prefixedBytes[0] === 231 && prefixedBytes[1] === 1) { return KEY_TYPE.SECP256K1; } else if (prefixedBytes[0] === 18 && prefixedBytes[1] === 0) { return KEY_TYPE.ECDSAR1; } throw new Error("Unknown key type prefix"); } static extractBytes(prefixedBytes) { return prefixedBytes.slice(2); } static getExpectedKeyLength(keyType) { switch (keyType) { case KEY_TYPE.ED25519: return this.ED25519_KEY_LENGTH; case KEY_TYPE.SECP256K1: return this.SECP256K1_KEY_LENGTH; case KEY_TYPE.ECDSAR1: return this.ECDSA_R1_KEY_LENGTH; default: throw new Error(`Unsupported key type: ${keyType}`); } } }; KeyMultibaseCodec.ED25519_PREFIX = new Uint8Array([237, 1]); KeyMultibaseCodec.SECP256K1_PREFIX = new Uint8Array([231, 1]); KeyMultibaseCodec.ECDSA_R1_PREFIX = new Uint8Array([18, 0]); KeyMultibaseCodec.ED25519_KEY_LENGTH = 32; KeyMultibaseCodec.SECP256K1_KEY_LENGTH = 33; KeyMultibaseCodec.ECDSA_R1_KEY_LENGTH = 33; // src/multibase/did.ts var DidKeyCodec = class { /** * Generate did:key from public key * @param publicKey The public key bytes * @param keyType The key type * @returns did:key identifier */ static generateDidKey(publicKey, keyType) { const multibase = KeyMultibaseCodec.encodeWithType(publicKey, keyType); return `did:key:${multibase}`; } /** * Parse did:key to get key type and public key * @param didKey The did:key identifier * @returns The key type and public key bytes */ static parseDidKey(didKey) { if (!didKey.startsWith("did:key:")) { throw new Error("Invalid did:key format"); } const multibase = didKey.substring(8); const { keyType, bytes } = KeyMultibaseCodec.decodeWithType(multibase); return { keyType, publicKey: bytes }; } }; // src/crypto/providers/ed25519.ts function getCrypto() { if (typeof globalThis !== "undefined" && globalThis.crypto) { return globalThis.crypto; } throw new Error("Web Crypto API is not available in the current runtime"); } var Ed25519Provider = class { constructor() { this.crypto = getCrypto(); } async generateKeyPair() { const { publicKey, privateKey } = await this.crypto.subtle.generateKey( { name: "Ed25519" }, true, ["sign", "verify"] ); const exportedPublic = new Uint8Array(await this.crypto.subtle.exportKey("raw", publicKey)); const exportedPrivate = new Uint8Array(await this.crypto.subtle.exportKey("pkcs8", privateKey)); return { publicKey: exportedPublic, privateKey: exportedPrivate }; } async sign(data, privateKey) { let key; if (privateKey instanceof Uint8Array) { key = await this.crypto.subtle.importKey( "pkcs8", privateKey, { name: "Ed25519" }, false, ["sign"] ); } else { key = privateKey; } const signature = await this.crypto.subtle.sign("Ed25519", key, data); return new Uint8Array(signature); } async verify(data, signature, publicKey) { let key; if (publicKey instanceof Uint8Array) { key = await this.crypto.subtle.importKey( "raw", publicKey, { name: "Ed25519" }, false, ["verify"] ); } else { key = await this.crypto.subtle.importKey( "jwk", publicKey, { name: "Ed25519" }, false, ["verify"] ); } return await this.crypto.subtle.verify("Ed25519", key, signature, data); } getKeyType() { return KEY_TYPE.ED25519; } async derivePublicKey(privateKey) { const cryptoKey = await this.crypto.subtle.importKey( "pkcs8", privateKey, { name: "Ed25519" }, true, ["sign"] ); const jwk = await this.crypto.subtle.exportKey("jwk", cryptoKey); if (!jwk.x) { throw new Error("Failed to derive public key from private key"); } const publicKeyBytes = base64urlToBytes(jwk.x); return publicKeyBytes; } }; // src/crypto/providers/secp256k1.ts import { secp256k1 } from "@noble/curves/secp256k1"; import { sha256 } from "@noble/hashes/sha256"; var Secp256k1Provider = class { async generateKeyPair() { const privateKey = secp256k1.utils.randomPrivateKey(); const publicKey = secp256k1.getPublicKey(privateKey, true); return { publicKey, privateKey }; } async sign(data, privateKey) { if (privateKey instanceof CryptoKey) { throw new Error("CryptoKey is not supported for Secp256k1 signing"); } const msgHash = sha256(data); const signature = secp256k1.sign(msgHash, privateKey); return signature.toCompactRawBytes(); } async verify(data, signature, publicKey) { if (!(publicKey instanceof Uint8Array)) { throw new Error("JsonWebKey is not supported for Secp256k1 verification"); } const msgHash = sha256(data); return secp256k1.verify(signature, msgHash, publicKey); } getKeyType() { return KEY_TYPE.SECP256K1; } async derivePublicKey(privateKey) { return secp256k1.getPublicKey(privateKey, true); } }; // src/crypto/providers/ecdsa_r1.ts import { p256 } from "@noble/curves/p256"; import { bytesToHex, hexToBytes } from "@noble/curves/abstract/utils"; function getCrypto2() { if (typeof globalThis !== "undefined" && globalThis.crypto) { return globalThis.crypto; } throw new Error("Web Crypto API is not available in the current runtime"); } var EcdsaR1Provider = class { constructor() { this.crypto = getCrypto2(); } async generateKeyPair() { const { publicKey, privateKey } = await this.crypto.subtle.generateKey( { name: "ECDSA", namedCurve: "P-256" }, true, ["sign", "verify"] ); const exportedPublic = new Uint8Array(await this.crypto.subtle.exportKey("raw", publicKey)); const exportedPrivate = new Uint8Array(await this.crypto.subtle.exportKey("pkcs8", privateKey)); const compressedPublicKey = this.compressPublicKey(exportedPublic); return { publicKey: compressedPublicKey, privateKey: exportedPrivate }; } compressPublicKey(publicKey) { if (publicKey.length !== 65) { throw new Error(`Invalid public key length. Expected 65 bytes, got ${publicKey.length}`); } if (publicKey[0] !== 4) { throw new Error("Invalid public key format. Expected uncompressed format (0x04)"); } const x = publicKey.slice(1, 33); const y = publicKey.slice(33, 65); const compressedFormat = y[31] % 2 === 0 ? 2 : 3; const compressed = new Uint8Array(33); compressed[0] = compressedFormat; compressed.set(x, 1); return compressed; } decompressPublicKey(compressedKey) { if (compressedKey.length !== 33) { throw new Error( `Invalid compressed public key length. Expected 33 bytes, got ${compressedKey.length}` ); } const format = compressedKey[0]; if (format !== 2 && format !== 3) { throw new Error("Invalid compressed public key format. Expected 0x02 or 0x03"); } try { const point = p256.ProjectivePoint.fromHex(compressedKey); const x = point.x; const y = point.y; const xBytes = hexToBytes(x.toString(16).padStart(64, "0")); const yBytes = hexToBytes(y.toString(16).padStart(64, "0")); const decompressed = new Uint8Array(65); decompressed[0] = 4; decompressed.set(xBytes, 1); decompressed.set(yBytes, 33); return decompressed; } catch (err) { const error = err; throw new Error(`Failed to decompress public key: ${error.message}`); } } async sign(data, privateKey) { let key; if (privateKey instanceof Uint8Array) { key = await this.crypto.subtle.importKey( "pkcs8", privateKey, { name: "ECDSA", namedCurve: "P-256" }, false, ["sign"] ); } else { key = privateKey; } const signature = await this.crypto.subtle.sign( { name: "ECDSA", hash: { name: "SHA-256" } }, key, data ); return new Uint8Array(signature); } convertDERSignatureToRaw(derSignature) { const firstByte = derSignature[0]; if (firstByte === 48) { try { const sig = p256.Signature.fromDER(derSignature); return sig.toCompactRawBytes(); } catch (e) { throw new Error("Invalid DER signature"); } } return derSignature; } convertRawToDER(raw) { if (raw.length !== 64) return raw; const r = bytesToHex(raw.slice(0, 32)); const s = bytesToHex(raw.slice(32, 64)); const der = p256.Signature.fromCompact(hexToBytes(r + s)).toDERHex(); return hexToBytes(der); } async verify(data, signature, publicKey) { let key; if (publicKey instanceof Uint8Array) { const decompressedKey = this.decompressPublicKey(publicKey); key = await this.crypto.subtle.importKey( "raw", decompressedKey, { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"] ); } else { key = await this.crypto.subtle.importKey( "jwk", publicKey, { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"] ); } return await this.crypto.subtle.verify( { name: "ECDSA", hash: { name: "SHA-256" } }, key, signature, data ); } getKeyType() { return KEY_TYPE.ECDSAR1; } async derivePublicKey(privateKey) { const cryptoKey = await this.crypto.subtle.importKey( "pkcs8", privateKey, { name: "ECDSA", namedCurve: "P-256" }, true, ["sign"] ); const jwk = await this.crypto.subtle.exportKey("jwk", cryptoKey); if (!jwk.x || !jwk.y) { throw new Error("Failed to derive public key from private key"); } const x = base64urlToBytes(jwk.x); const y = base64urlToBytes(jwk.y); const uncompressed = new Uint8Array(65); uncompressed[0] = 4; uncompressed.set(x, 1); uncompressed.set(y, 33); return this.compressPublicKey(uncompressed); } }; // src/crypto/factory.ts var DefaultCryptoProviderFactory = class { constructor() { this.providers = /* @__PURE__ */ new Map(); this.providers.set(KEY_TYPE.ED25519, new Ed25519Provider()); this.providers.set(KEY_TYPE.SECP256K1, new Secp256k1Provider()); this.providers.set(KEY_TYPE.ECDSAR1, new EcdsaR1Provider()); } createProvider(keyType) { const provider = this.providers.get(keyType); if (!provider) { throw new Error(`No provider available for key type: ${keyType}`); } return provider; } supports(keyType) { return this.providers.has(keyType); } }; var defaultCryptoProviderFactory = new DefaultCryptoProviderFactory(); // src/crypto/utils.ts var CryptoUtils = class { /** * Generates a key pair based on the specified curve * @param type The key type to generate (Ed25519VerificationKey2020 or EcdsaSecp256k1VerificationKey2019 or EcdsaSecp256r1VerificationKey2019) * @returns A key pair containing public and private keys */ static async generateKeyPair(type = "Ed25519VerificationKey2020" /* ED25519 */) { const keyType = typeof type === "string" ? toKeyType(type) : type; const provider = defaultCryptoProviderFactory.createProvider(keyType); return provider.generateKeyPair(); } /** * Signs data using the specified private key * @param data The data to sign * @param privateKey The private key to use for signing (can be Uint8Array or CryptoKey) * @param type The key type (Ed25519VerificationKey2020 or EcdsaSecp256k1VerificationKey2019) * @returns The signature as a Uint8Array */ static async sign(data, privateKey, type) { const keyType = typeof type === "string" ? toKeyType(type) : type; const provider = defaultCryptoProviderFactory.createProvider(keyType); return provider.sign(data, privateKey); } /** * Verifies a signature using the specified public key * @param data The original data * @param signature The signature to verify * @param publicKey The public key to use for verification (can be Uint8Array or JsonWebKey) * @param type The key type (Ed25519VerificationKey2020 or EcdsaSecp256k1VerificationKey2019) * @returns Whether the signature is valid */ static async verify(data, signature, publicKey, type) { const keyType = typeof type === "string" ? toKeyType(type) : type; const provider = defaultCryptoProviderFactory.createProvider(keyType); return provider.verify(data, signature, publicKey); } /** * Derive public key from private key * @param privateKey The private key bytes * @param keyType The key type * @returns The corresponding public key bytes */ static async derivePublicKey(privateKey, keyType) { const type = typeof keyType === "string" ? toKeyType(keyType) : keyType; const provider = defaultCryptoProviderFactory.createProvider(type); return provider.derivePublicKey(privateKey); } /** * Validate the consistency between a private key and public key pair * @param privateKey The private key bytes * @param publicKey The public key bytes * @param keyType The key type * @returns true if the keys are consistent, false otherwise */ static async validateKeyPairConsistency(privateKey, publicKey, keyType) { try { const derivedPublicKey = await this.derivePublicKey(privateKey, keyType); return this.areUint8ArraysEqual(derivedPublicKey, publicKey); } catch (error) { console.warn("Key pair consistency validation failed:", error); return false; } } /** * Compare two Uint8Array for equality * @param a First array * @param b Second array * @returns true if arrays are equal, false otherwise */ static areUint8ArraysEqual(a, b) { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) { if (a[i] !== b[i]) return false; } return true; } }; // src/signers/keyStoreUtils.ts async function signWithKeyStore(keyStore, data, keyId) { if (typeof keyStore.sign === "function") { return keyStore.sign(keyId, data); } const key = await keyStore.load(keyId); if (!key) { throw new Error(`Key not found: ${keyId}`); } if (!key.privateKeyMultibase) { throw new Error(`No private key available for ${keyId}`); } const privateKeyBytes = MultibaseCodec.decode(key.privateKeyMultibase); return CryptoUtils.sign(data, privateKeyBytes, key.keyType); } async function canSignWithKeyStore(keyStore, keyId) { if (typeof keyStore.sign === "function") { const keyExists = await keyStore.load(keyId); return keyExists !== null; } const key = await keyStore.load(keyId); return !!(key && key.privateKeyMultibase); } async function getKeyInfoFromKeyStore(keyStore, keyId) { const key = await keyStore.load(keyId); if (!key) return void 0; const publicKeyBytes = MultibaseCodec.decode(key.publicKeyMultibase); return { type: key.keyType, publicKey: publicKeyBytes }; } // src/signers/didAccountSigner.ts import { RoochAddress, Signer, Authenticator, Ed25519PublicKey, Secp256k1PublicKey } from "@roochnetwork/rooch-sdk"; // src/utils/did.ts function parseDid(did) { if (!did.startsWith("did:")) { throw new Error(`Invalid DID: ${did}`); } const afterPrefix = did.slice(4); const methodEnd = afterPrefix.indexOf(":"); if (methodEnd === -1) { throw new Error(`Invalid DID \u2013 missing method/identifier separator: ${did}`); } const method = afterPrefix.slice(0, methodEnd); const idPlusFrag = afterPrefix.slice(methodEnd + 1); if (!method || !idPlusFrag) { throw new Error(`Invalid DID: ${did}`); } const hashIdx = idPlusFrag.indexOf("#"); return hashIdx === -1 ? { method, identifier: idPlusFrag } : { method, identifier: idPlusFrag.slice(0, hashIdx), fragment: idPlusFrag.slice(hashIdx + 1) }; } function extractMethod(did) { return parseDid(did).method; } function extractFragment(idOrDid) { const idx = idOrDid.indexOf("#"); if (idx === -1) { throw new Error(`No fragment found in ${idOrDid}`); } return idOrDid.slice(idx + 1); } var extractFragmentFromId = extractFragment; function buildDid(method, identifier) { return `did:${method}:${identifier}`; } function getDidWithoutFragment(did) { const { method, identifier } = parseDid(did); return buildDid(method, identifier); } // src/signers/didAccountSigner.ts var DidAccountSigner = class _DidAccountSigner extends Signer { constructor(wrappedSigner, did, keyId, keyType, publicKey) { super(); this.wrappedSigner = wrappedSigner; this.keyId = keyId; this.did = did; if (!this.did.startsWith("did:rooch:")) { throw new Error("Signer DID must be a did:rooch DID"); } const didParts = parseDid(did); this.didAddress = new RoochAddress(didParts.identifier); this.keyType = keyType; this.publicKey = publicKey; } /** * Create a DidAccountSigner instance from a SignerInterface * @param signer The signer to wrap * @param keyId Optional specific keyId to use * @returns A new DidAccountSigner instance */ static async create(signer, keyId) { if (signer instanceof _DidAccountSigner) { return signer; } const actualKeyId = keyId || (await signer.listKeyIds())[0]; if (!actualKeyId) { throw new Error("No available keys in signer"); } const keyInfo = await signer.getKeyInfo(actualKeyId); if (!keyInfo) { throw new Error(`Key info not found for keyId: ${actualKeyId}`); } const did = await signer.getDid(); return new _DidAccountSigner(signer, did, actualKeyId, keyInfo.type, keyInfo.publicKey); } // Implement Rooch Signer interface getRoochAddress() { return this.didAddress; } async sign(input) { return this.wrappedSigner.signWithKeyId(input, this.keyId); } async signTransaction(input) { return Authenticator.rooch(input.hashData(), this); } getKeyScheme() { return keyTypeToRoochSignatureScheme(this.keyType); } getPublicKey() { if (this.keyType === "EcdsaSecp256k1VerificationKey2019" /* SECP256K1 */) { return new Secp256k1PublicKey(this.publicKey); } else if (this.keyType === "Ed25519VerificationKey2020" /* ED25519 */) { return new Ed25519PublicKey(this.publicKey); } else if (this.keyType === "EcdsaSecp256r1VerificationKey2019" /* ECDSAR1 */) { throw new Error("ECDSAR1 is not supported for DID account"); } else { throw new Error(`Unsupported key type: ${this.keyType}`); } } getBitcoinAddress() { throw new Error("Bitcoin address is not supported for DID account"); } // Implement SignerInterface async signWithKeyId(data, keyId) { if (keyId !== this.keyId) { throw new Error(`Key ID mismatch. Expected ${this.keyId}, got ${keyId}`); } return this.sign(data); } async canSignWithKeyId(keyId) { return keyId === this.keyId; } async listKeyIds() { return [this.keyId]; } async getDid() { return this.did; } async getKeyInfo(keyId) { if (keyId !== this.keyId) { return void 0; } return { type: this.keyType, publicKey: this.publicKey }; } }; // src/vdr/abstractVDR.ts var logger = DebugLogger.get("AbstractVDR"); var AbstractVDR = class { /** * Creates a new AbstractVDR instance * * @param method The DID method this VDR handles */ constructor(method) { this.method = method; } /** * Gets the DID method this VDR handles * * @returns The DID method string */ getMethod() { return this.method; } /** * Validates that a given DID matches the method this VDR handles * * @param did The DID to validate * @throws Error if the DID doesn't match this VDR's method */ validateDIDMethod(did) { const { method } = parseDid(did); if (method !== this.method) { throw new Error(`DID ${did} is not a valid did:${this.method} identifier`); } } /** * Validates a DID document's basic structure * * @param document The DID document to validate * @returns true if valid, throws an error otherwise */ validateDocument(document) { if (!document.id) { throw new Error("DID document must have an id"); } this.validateDIDMethod(document.id); if (!document["@context"]) { throw new Error("DID document must have a @context property"); } if (!document.verificationMethod || document.verificationMethod.length === 0) { throw new Error("DID document must have at least one verification method"); } return true; } /** * Check if a key has a specific verification relationship in a DID document * * @param didDocument The DID document to check * @param keyId The ID of the verification method * @param relationship The verification relationship to check * @returns True if the key has the relationship */ hasVerificationRelationship(didDocument, keyId, relationship) { const relationshipArray = didDocument[relationship]; if (!relationshipArray) return false; return relationshipArray.some((item) => { if (typeof item === "string") { return item === keyId; } else if (typeof item === "object" && item.id) { return item.id === keyId; } return false; }); } /** * Validates if a key has permission to perform an operation * * @param didDocument The DID document * @param keyId The ID of the key * @param requiredRelationship The required verification relationship * @returns True if the key has permission */ validateKeyPermission(didDocument, keyId, requiredRelationship) { const keyExists = didDocument.verificationMethod?.some((vm) => vm.id === keyId); if (!keyExists) { logger.error(`Key ${keyId} not found in DID document`); return false; } const isPrimaryKey = didDocument.verificationMethod?.[0]?.id === keyId; if (isPrimaryKey) { return true; } const hasPermission = didDocument[requiredRelationship]?.includes(keyId); if (!hasPermission) { logger.error(`Key ${keyId} does not have ${requiredRelationship} permission`); return false; } return true; } /** * Default create implementation - throws not implemented error for base class * Subclasses must override this method to provide actual implementation */ async create(request, options) { throw new Error(`create method not implemented for ${this.method} VDR`); } /** * Default CADOP implementation - throws not implemented error */ async createViaCADOP(request, options) { throw new Error(`createViaCADOP not implemented for ${this.method} VDR`); } /** * Build DID Document from creation request */ buildDIDDocumentFromRequest(request) { const did = request.preferredDID; const controllerForVM = Array.isArray(request.controller) ? request.controller[0] : request.controller || did; const verificationMethod = { id: `${did}#account-key`, type: request.keyType || "EcdsaSecp256k1VerificationKey2019", controller: controllerForVM, publicKeyMultibase: request.publicKeyMultibase }; const didDocument = { "@context": ["https://www.w3.org/ns/did/v1"], id: did, controller: request.controller ? Array.isArray(request.controller) ? request.controller : [request.controller] : [did], verificationMethod: [verificationMethod, ...request.additionalVerificationMethods || []], service: request.initialServices || [] }; const relationships = request.initialRelationships || [ "authentication", "assertionMethod", "capabilityInvocation", "capabilityDelegation" ]; const vmId = verificationMethod.id; relationships.forEach((rel) => { if (!didDocument[rel]) { didDocument[rel] = []; } didDocument[rel].push(vmId); }); return didDocument; } /** * Checks if a DID exists in the registry * Default implementation tries to resolve and checks if result is not null */ async exists(did) { try { const doc = await this.resolve(did); return doc !== null; } catch (error) { return false; } } /** * Add a verification method to a DID document * Default implementation that should be overridden by specific VDR implementations */ async addVerificationMethod(did, verificationMethod, relationships, options) { throw new Error(`addVerificationMethod not implemented for ${this.method} VDR`); } /** * Remove a verification method from a DID document * Default implementation that should be overridden by specific VDR implementations */ async removeVerificationMethod(did, id, options) { throw new Error(`removeVerificationMethod not implemented for ${this.method} VDR`); } /** * Add a service to a DID document * Default implementation that should be overridden by specific VDR implementations */ async addService(did, service, options) { throw new Error(`addService not implemented for ${this.method} VDR`); } /** * Remove a service from a DID document * Default implementation that should be overridden by specific VDR implementations */ async removeService(did, id, options) { throw new Error(`removeService not implemented for ${this.method} VDR`); } /** * Update verification relationships for a verification method * Default implementation that should be overridden by specific VDR implementations */ async updateRelationships(did, id, add, remove, options) { throw new Error(`updateRelationships not implemented for ${this.method} VDR`); } /** * Update the controller of a DID document * Default implementation that should be overridden by specific VDR implementations */ async updateController(did, controller, options) { throw new Error(`updateController not implemented for ${this.method} VDR`); } /** * Validates options for update operations and ensures proper permissions * * @param did The DID being operated on * @param document The resolved DID document * @param keyId The key ID used for signing * @param requiredRelationship The required verification relationship for this operation * @throws Error if validation fails */ async validateUpdateOperation(did, document, keyId, requiredRelationship) { this.validateDIDMethod(did); if (!document) { throw new Error(`DID document ${did} not found`); } if (!this.validateKeyPermission(document, keyId, requiredRelationship)) { throw new Error( `Key ${keyId} does not have ${requiredRelationship} permission required for this operation` ); } return document; } /** * Validates that inputs to addVerificationMethod are correct * * @param did The DID being operated on * @param verificationMethod The verification method to validate * @param document The current DID document * @throws Error if validation fails */ validateVerificationMethod(did, verificationMethod, document) { if (!verificationMethod.id.startsWith(did)) { throw new Error(`Verification method ID ${verificationMethod.id} must start with DID ${did}`); } if (document.verificationMethod?.some((vm) => vm.id === verificationMethod.id)) { throw new Error(`Verification method ${verificationMethod.id} already exists`); } if (!verificationMethod.type) { throw new Error("Verification method must have a type"); } if (!verificationMethod.controller) { throw new Error("Verification method must have a controller"); } if (!verificationMethod.publicKeyMultibase && !verificationMethod.publicKeyJwk) { throw new Error("Verification method must have at least one form of public key material"); } } /** * Validates that inputs to addService are correct * * @param did The DID being operated on * @param service The service to validate * @param document The current DID document * @throws Error if validation fails */ validateService(did, service, document) { if (!service.id.startsWith(did)) { throw new Error(`Service ID ${service.id} must start with DID ${did}`); } if (document.service?.some((s) => s.id === service.id)) { throw new Error(`Service ${service.id} already exists`); } if (!service.type) { throw new Error("Service must have a type"); } if (!service.serviceEndpoint) { throw new Error("Service must have a serviceEndpoint"); } } /** * Makes a deep copy of a DID document for modification * * @param document The DID document to copy * @returns A deep copy of the document */ copyDocument(document) { return JSON.parse(JSON.stringify(document)); } }; // src/vdr/roochVDRTypes.ts import { bcs, sha3_256, toHEX, stringToBytes as stringToBytes2, Serializer } from "@roochnetwork/rooch-sdk"; function simpleMapSchema(keySchema, valueSchema) { return bcs.struct("SimpleMap", { data: bcs.vector( bcs.struct("Entry", { key: keySchema, value: valueSchema }) ) }); } function formatDIDString(did) { return `did:${did.method}:${did.identifier}`; } var DIDSchema = bcs.struct("DID", { method: bcs.string(), identifier: bcs.string() }); var DIDIdSchema = bcs.struct("DIDID", { did: DIDSchema, fragment: bcs.string() }); var VerificationMethodSchema = bcs.struct("VerificationMethod", { id: DIDIdSchema, type: bcs.string(), controller: DIDSchema, public_key_multibase: bcs.string() }); var ServiceSchema = bcs.struct("Service", { id: DIDIdSchema, type: bcs.string(), service_endpoint: bcs.string(), properties: simpleMapSchema(bcs.string(), bcs.string()) }); var AccountCapSchema = bcs.struct("AccountCap", { addr: bcs.Address }); var DIDDocumentSchema = bcs.struct("DIDDocument", { id: DIDSchema, controller: bcs.vector(DIDSchema), verification_methods: simpleMapSchema(bcs.string(), VerificationMethodSchema), authentication: bcs.vector(bcs.string()), assertion_method: bcs.vector(bcs.string()), capability_invocation: bcs.vector(bcs.string()), capability_delegation: bcs.vector(bcs.string()), key_agreement: bcs.vector(bcs.string()), services: simpleMapSchema(bcs.string(), ServiceSchema), also_known_as: bcs.vector(bcs.string()), account_cap: AccountCapSchema }); var DIDCreatedEventSchema = bcs.struct("DIDCreatedEvent", { did: bcs.string(), object_id: bcs.ObjectId, controller: bcs.vector(bcs.string()), creator_address: bcs.Address }); function simpleMapToMap(simpleMap) { return new Map(simpleMap.data.map((entry) => [entry.key, entry.value])); } function convertMoveDIDDocumentToInterface(didDocObject) { let bcsHex = didDocObject.value; bcsHex = bcsHex.startsWith("0x") ? bcsHex.slice(2) : bcsHex; let bcsBytes = new Uint8Array( bcsHex.match(/.{1,2}/g)?.map((byte) => parseInt(byte, 16)) || [] ); let didDoc = DIDDocumentSchema.parse(bcsBytes); const didId = formatDIDString(didDoc.id); const controllers = didDoc.controller.map((c) => formatDIDString(c)); const verificationMethods = []; const verificationMethodsMap = simpleMapToMap(didDoc.verification_methods); verificationMethodsMap.forEach((vm) => { verificationMethods.push({ id: `${formatDIDString(vm.id.did)}#${vm.id.fragment}`, type: vm.type, controller: formatDIDString(vm.controller), publicKeyMultibase: vm.public_key_multibase }); }); const convertFragmentToDIDURL = (fragment) => `${didId}#${fragment}`; const services = []; const servicesMap = simpleMapToMap(didDoc.services); servicesMap.forEach((service) => { const serviceEndpoint = { id: `${formatDIDString(service.id.did)}#${service.id.fragment}`, type: service.type, serviceEndpoint: service.service_endpoint }; let properties = simpleMapToMap(service.properties); if (properties.size > 0) { Object.assign(serviceEndpoint, Object.fromEntries(properties)); } services.push(serviceEndpoint); }); return { "@context": ["https://www.w3.org/ns/did/v1"], id: didId, controller: controllers, verificationMethod: verificationMethods, authentication: didDoc.authentication.map(convertFragmentToDIDURL), assertionMethod: didDoc.assertion_method.map(convertFragmentToDIDURL), capabilityInvocation: didDoc.capability_invocation.map(convertFragmentToDIDURL), capabilityDelegation: didDoc.capability_delegation.map(convertFragmentToDIDURL), keyAgreement: didDoc.key_agreement.map(convertFragmentToDIDURL), service: services, alsoKnownAs: didDoc.also_known_as }; } function parseDIDCreatedEvent(eventData) { const hexData = eventData.startsWith("0x") ? eventData.slice(2) : eventData; const bytes = new Uint8Array( hexData.match(/.{1,2}/g)?.map((byte) => parseInt(byte, 16)) || [] ); return DIDCreatedEventSchema.parse(bytes); } var DIDDocumentStructTag = { address: "0x3", module: "did", name: "DIDDocument", typeParams: [] }; function resolveDidObjectID(identifier) { return customObjectID(identifier, DIDDocumentStructTag); } function customObjectID(id, str