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 lines 379 kB
{"version":3,"sources":["../../src/web/index.ts","../../src/errors/IdentityKitError.ts","../../src/types/crypto.ts","../../src/cache/InMemoryLRUDIDDocumentCache.ts","../../src/vdr/VDRRegistry.ts","../../src/multibase/base.ts","../../src/utils/bytes.ts","../../src/multibase/key.ts","../../src/multibase/did.ts","../../src/utils/did.ts","../../src/utils/DebugLogger.ts","../../src/vdr/abstractVDR.ts","../../src/vdr/keyVDR.ts","../../src/vdr/roochVDR.ts","../../src/crypto/providers/ed25519.ts","../../src/crypto/providers/secp256k1.ts","../../src/crypto/providers/ecdsa_r1.ts","../../src/crypto/factory.ts","../../src/crypto/utils.ts","../../src/crypto/EcdsaR1PublicKey.ts","../../src/signers/keyStoreUtils.ts","../../src/signers/types.ts","../../src/signers/didAccountSigner.ts","../../src/vdr/roochVDRTypes.ts","../../src/utils/sessionScopes.ts","../../src/vdr/index.ts","../../src/keys/KeyStore.ts","../../src/keys/StoredKeyCodec.ts","../../src/keys/KeyManager.ts","../../src/IdentityEnv.ts","../../src/IdentityKit.ts","../../src/CadopIdentityKit.ts","../../src/auth/v1/index.ts","../../src/auth/v1/utils.ts","../../src/auth/v1/nonceStore.ts","../../src/auth/index.ts","../../src/web/keystore/LocalStorageKeyStore.ts","../../src/web/keystore/IndexedDBKeyStore.ts","../../src/deeplink/shared.ts","../../src/web/deeplink/DeepLinkManager.ts","../../src/web/IdentityKitWeb.ts","../../src/web/react/useIdentityKit.ts"],"sourcesContent":["/**\n * Web-specific functionality for @nuwa-ai/identity-kit\n *\n * This module provides browser-specific implementations including:\n * - LocalStorage and IndexedDB KeyStore implementations\n * - DeepLink management for CADOP integration\n * - High-level IdentityKitWeb API\n * - React hooks (when React is available)\n *\n * Note: All exports include runtime environment checks to ensure\n * they only work in appropriate environments (browser for web features,\n * React available for hooks, etc.)\n */\n\n// Always export types for better TypeScript support\nexport type { IdentityKitWebOptions } from './IdentityKitWeb';\nexport type { ConnectOptions, AuthResult } from './deeplink/DeepLinkManager';\nexport type {\n IdentityKitState,\n IdentityKitHook,\n UseIdentityKitOptions,\n} from './react/useIdentityKit';\n\n// Re-export core functionality\nexport { VDRRegistry } from '../index';\n\n// KeyStore implementations (with runtime browser checks)\nexport { LocalStorageKeyStore } from './keystore/LocalStorageKeyStore';\nexport { IndexedDBKeyStore } from './keystore/IndexedDBKeyStore';\n\n// DeepLink functionality (with runtime browser checks)\nexport { DeepLinkManager } from './deeplink/DeepLinkManager';\n\n// Main IdentityKitWeb class (with runtime browser checks)\nexport { IdentityKitWeb } from './IdentityKitWeb';\n\n// React hooks (with runtime React availability checks)\nexport { useIdentityKit } from './react/useIdentityKit';\n\n// Create registry instance\nimport { VDRRegistry } from '../index';\nexport const registry = VDRRegistry.getInstance();\n","/**\n * Unified error handling for IdentityKit\n */\n\n/**\n * Error codes for IdentityKit operations\n */\nexport enum IdentityKitErrorCode {\n // Configuration and initialization\n INVALID_CONFIGURATION = 'INVALID_CONFIGURATION',\n ENVIRONMENT_NOT_SUPPORTED = 'ENVIRONMENT_NOT_SUPPORTED',\n INITIALIZATION_FAILED = 'INITIALIZATION_FAILED',\n\n // DID operations\n DID_NOT_FOUND = 'DID_NOT_FOUND',\n DID_INVALID_FORMAT = 'DID_INVALID_FORMAT',\n DID_METHOD_NOT_SUPPORTED = 'DID_METHOD_NOT_SUPPORTED',\n DID_CREATION_FAILED = 'DID_CREATION_FAILED',\n DID_RESOLUTION_FAILED = 'DID_RESOLUTION_FAILED',\n DID_NOT_SET = 'DID_NOT_SET',\n DID_SERVICE_NOT_FOUND = 'DID_SERVICE_NOT_FOUND',\n DID_VERIFICATION_METHOD_NOT_FOUND = 'DID_VERIFICATION_METHOD_NOT_FOUND',\n\n // VDR (Verifiable Data Registry) operations\n VDR_NOT_AVAILABLE = 'VDR_NOT_AVAILABLE',\n VDR_OPERATION_FAILED = 'VDR_OPERATION_FAILED',\n VDR_NETWORK_ERROR = 'VDR_NETWORK_ERROR',\n VDR_INVALID_RESPONSE = 'VDR_INVALID_RESPONSE',\n\n // Key management\n KEY_NOT_FOUND = 'KEY_NOT_FOUND',\n KEY_INVALID_FORMAT = 'KEY_INVALID_FORMAT',\n KEY_GENERATION_FAILED = 'KEY_GENERATION_FAILED',\n KEY_IMPORT_FAILED = 'KEY_IMPORT_FAILED',\n KEY_STORAGE_ERROR = 'KEY_STORAGE_ERROR',\n KEY_PERMISSION_DENIED = 'KEY_PERMISSION_DENIED',\n KEY_VALIDATION_FAILED = 'KEY_VALIDATION_FAILED',\n KEY_PRIVATE_KEY_NOT_AVAILABLE = 'KEY_PRIVATE_KEY_NOT_AVAILABLE',\n KEY_DID_MISMATCH = 'KEY_DID_MISMATCH',\n KEY_ID_MISMATCH = 'KEY_ID_MISMATCH',\n KEY_TYPE_NOT_SUPPORTED = 'KEY_TYPE_NOT_SUPPORTED',\n KEY_ALREADY_EXISTS = 'KEY_ALREADY_EXISTS',\n\n // Signing operations\n SIGNING_FAILED = 'SIGNING_FAILED',\n SIGNATURE_VERIFICATION_FAILED = 'SIGNATURE_VERIFICATION_FAILED',\n SIGNER_NOT_AVAILABLE = 'SIGNER_NOT_AVAILABLE',\n SIGNER_INVALID_DID = 'SIGNER_INVALID_DID',\n SIGNER_NO_KEYS = 'SIGNER_NO_KEYS',\n\n // Authentication (from existing AuthErrorCode)\n AUTH_INVALID_HEADER = 'AUTH_INVALID_HEADER',\n AUTH_INVALID_BASE64 = 'AUTH_INVALID_BASE64',\n AUTH_INVALID_JSON = 'AUTH_INVALID_JSON',\n AUTH_MISSING_SIGNATURE = 'AUTH_MISSING_SIGNATURE',\n AUTH_TIMESTAMP_OUT_OF_WINDOW = 'AUTH_TIMESTAMP_OUT_OF_WINDOW',\n AUTH_NONCE_REPLAYED = 'AUTH_NONCE_REPLAYED',\n AUTH_DID_DOCUMENT_NOT_FOUND = 'AUTH_DID_DOCUMENT_NOT_FOUND',\n AUTH_VERIFICATION_METHOD_NOT_FOUND = 'AUTH_VERIFICATION_METHOD_NOT_FOUND',\n AUTH_INVALID_PUBLIC_KEY = 'AUTH_INVALID_PUBLIC_KEY',\n AUTH_DID_MISMATCH = 'AUTH_DID_MISMATCH',\n\n // Web-specific operations\n WEB_BROWSER_NOT_SUPPORTED = 'WEB_BROWSER_NOT_SUPPORTED',\n WEB_STORAGE_NOT_AVAILABLE = 'WEB_STORAGE_NOT_AVAILABLE',\n WEB_DEEPLINK_FAILED = 'WEB_DEEPLINK_FAILED',\n WEB_CADOP_CONNECTION_FAILED = 'WEB_CADOP_CONNECTION_FAILED',\n WEB_OAUTH_CALLBACK_FAILED = 'WEB_OAUTH_CALLBACK_FAILED',\n WEB_NOT_CONNECTED = 'WEB_NOT_CONNECTED',\n WEB_CALLBACK_FAILED = 'WEB_CALLBACK_FAILED',\n\n // React-specific operations\n REACT_NOT_AVAILABLE = 'REACT_NOT_AVAILABLE',\n REACT_HOOK_MISUSE = 'REACT_HOOK_MISUSE',\n\n // Crypto operations\n CRYPTO_PROVIDER_NOT_FOUND = 'CRYPTO_PROVIDER_NOT_FOUND',\n CRYPTO_OPERATION_FAILED = 'CRYPTO_OPERATION_FAILED',\n CRYPTO_KEY_DERIVATION_FAILED = 'CRYPTO_KEY_DERIVATION_FAILED',\n\n // Multibase operations\n MULTIBASE_DECODE_FAILED = 'MULTIBASE_DECODE_FAILED',\n MULTIBASE_ENCODE_FAILED = 'MULTIBASE_ENCODE_FAILED',\n MULTIBASE_INVALID_FORMAT = 'MULTIBASE_INVALID_FORMAT',\n\n // Validation operations\n SCOPE_VALIDATION_FAILED = 'SCOPE_VALIDATION_FAILED',\n SCOPE_INVALID_FORMAT = 'SCOPE_INVALID_FORMAT',\n VALIDATION_FAILED = 'VALIDATION_FAILED',\n INVALID_INPUT_FORMAT = 'INVALID_INPUT_FORMAT',\n\n // DeepLink operations\n DEEPLINK_INVALID_STATE = 'DEEPLINK_INVALID_STATE',\n DEEPLINK_CALLBACK_FAILED = 'DEEPLINK_CALLBACK_FAILED',\n\n // Storage operations\n STORAGE_QUOTA_EXCEEDED = 'STORAGE_QUOTA_EXCEEDED',\n STORAGE_OPERATION_FAILED = 'STORAGE_OPERATION_FAILED',\n\n // Signer operations\n SIGNER_CONVERSION_FAILED = 'SIGNER_CONVERSION_FAILED',\n SIGNER_OPERATION_FAILED = 'SIGNER_OPERATION_FAILED',\n\n // Permission operations\n PERMISSION_DENIED = 'PERMISSION_DENIED',\n OPERATION_NOT_PERMITTED = 'OPERATION_NOT_PERMITTED',\n\n // Generic errors\n OPERATION_NOT_SUPPORTED = 'OPERATION_NOT_SUPPORTED',\n INVALID_PARAMETER = 'INVALID_PARAMETER',\n INTERNAL_ERROR = 'INTERNAL_ERROR',\n NETWORK_ERROR = 'NETWORK_ERROR',\n TIMEOUT_ERROR = 'TIMEOUT_ERROR',\n}\n\n/**\n * Base error class for all IdentityKit errors\n */\nexport class IdentityKitError extends Error {\n public readonly code: IdentityKitErrorCode;\n public readonly category: string;\n public readonly details?: unknown;\n public readonly cause?: Error;\n\n constructor(\n code: IdentityKitErrorCode,\n message: string,\n options?: {\n category?: string;\n details?: unknown;\n cause?: Error;\n }\n ) {\n super(message);\n this.name = 'IdentityKitError';\n this.code = code;\n this.category = options?.category || this.inferCategory(code);\n this.details = options?.details;\n this.cause = options?.cause;\n\n // Maintain proper stack trace\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, this.constructor);\n }\n\n // Chain the original error stack if available\n if (options?.cause) {\n this.stack = `${this.stack}\\nCaused by: ${options.cause.stack}`;\n }\n }\n\n /**\n * Infer error category from error code\n */\n private inferCategory(code: IdentityKitErrorCode): string {\n if (code.startsWith('AUTH_')) return 'authentication';\n if (code.startsWith('DID_')) return 'did';\n if (code.startsWith('VDR_')) return 'vdr';\n if (code.startsWith('KEY_')) return 'key-management';\n if (code.startsWith('WEB_')) return 'web';\n if (code.startsWith('REACT_')) return 'react';\n if (code.startsWith('CRYPTO_')) return 'crypto';\n if (code.startsWith('MULTIBASE_')) return 'multibase';\n if (code.startsWith('SCOPE_') || code.startsWith('VALIDATION_')) return 'validation';\n if (code.startsWith('DEEPLINK_')) return 'deeplink';\n if (code.startsWith('STORAGE_')) return 'storage';\n if (code.startsWith('SIGNER_')) return 'signer';\n if (code.includes('SIGNING') || code.includes('SIGNATURE')) return 'signing';\n if (code.includes('NETWORK')) return 'network';\n if (code.includes('PERMISSION')) return 'permission';\n return 'general';\n }\n\n /**\n * Convert to a plain object for serialization\n */\n toJSON(): {\n name: string;\n code: string;\n category: string;\n message: string;\n details?: unknown;\n stack?: string;\n } {\n return {\n name: this.name,\n code: this.code,\n category: this.category,\n message: this.message,\n details: this.details,\n stack: this.stack,\n };\n }\n\n /**\n * Get a user-friendly error message with suggestions\n */\n getUserMessage(): string {\n const suggestions = this.getSuggestions();\n let message = this.message;\n\n if (suggestions.length > 0) {\n message += '\\n\\nSuggestions:\\n' + suggestions.map(s => `• ${s}`).join('\\n');\n }\n\n return message;\n }\n\n /**\n * Get contextual suggestions based on error code\n */\n private getSuggestions(): string[] {\n switch (this.code) {\n case IdentityKitErrorCode.WEB_BROWSER_NOT_SUPPORTED:\n return [\n 'Use a modern browser that supports required Web APIs',\n \"Check if you're running in a browser environment\",\n ];\n\n case IdentityKitErrorCode.WEB_STORAGE_NOT_AVAILABLE:\n return [\n 'Enable localStorage or IndexedDB in your browser',\n \"Check if you're in private/incognito mode\",\n 'Consider using memory storage as fallback',\n ];\n\n case IdentityKitErrorCode.DID_METHOD_NOT_SUPPORTED:\n return [\n 'Check if the DID method is registered with VDRRegistry',\n 'Verify the DID format is correct',\n ];\n\n case IdentityKitErrorCode.VDR_NETWORK_ERROR:\n return [\n 'Check your network connection',\n 'Verify the RPC URL is correct and accessible',\n 'Check if the VDR service is running',\n ];\n\n case IdentityKitErrorCode.KEY_STORAGE_ERROR:\n return [\n 'Check browser storage permissions',\n 'Verify storage quota is not exceeded',\n 'Try clearing browser storage and retry',\n ];\n\n case IdentityKitErrorCode.REACT_NOT_AVAILABLE:\n return [\n 'Ensure React is properly installed and imported',\n \"Check if you're using the hook in a React component\",\n ];\n\n case IdentityKitErrorCode.CRYPTO_PROVIDER_NOT_FOUND:\n return [\n 'Check if the key type is supported',\n 'Verify the crypto provider factory configuration',\n ];\n\n case IdentityKitErrorCode.MULTIBASE_DECODE_FAILED:\n return [\n 'Verify the encoded string format is correct',\n 'Check if the multibase prefix is valid',\n 'Ensure the input is not corrupted',\n ];\n\n case IdentityKitErrorCode.SCOPE_VALIDATION_FAILED:\n return [\n 'Check the scope format: address::module::function',\n 'Verify the address format is valid',\n 'Ensure module and function names are correct',\n ];\n\n case IdentityKitErrorCode.DEEPLINK_INVALID_STATE:\n return [\n 'Check if the state parameter matches the stored value',\n 'Verify the callback URL parameters are correct',\n 'Ensure the session storage is available',\n ];\n\n case IdentityKitErrorCode.STORAGE_QUOTA_EXCEEDED:\n return [\n 'Clear unused data from browser storage',\n 'Check available storage quota',\n 'Consider using a different storage strategy',\n ];\n\n case IdentityKitErrorCode.SIGNER_CONVERSION_FAILED:\n return [\n 'Verify the signer implements the required interface',\n 'Check if the key ID is available in the signer',\n 'Ensure the DID account signer is properly configured',\n ];\n\n default:\n return [];\n }\n }\n}\n\n/**\n * Factory functions for creating specific error types\n */\n\nexport function createConfigurationError(\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(IdentityKitErrorCode.INVALID_CONFIGURATION, message, {\n category: 'configuration',\n details,\n cause,\n });\n}\n\nexport function createDIDError(\n code: IdentityKitErrorCode,\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(code, message, {\n category: 'did',\n details,\n cause,\n });\n}\n\nexport function createVDRError(\n code: IdentityKitErrorCode,\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(code, message, {\n category: 'vdr',\n details,\n cause,\n });\n}\n\nexport function createKeyManagementError(\n code: IdentityKitErrorCode,\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(code, message, {\n category: 'key-management',\n details,\n cause,\n });\n}\n\nexport function createAuthenticationError(\n code: IdentityKitErrorCode,\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(code, message, {\n category: 'authentication',\n details,\n cause,\n });\n}\n\nexport function createWebError(\n code: IdentityKitErrorCode,\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(code, message, {\n category: 'web',\n details,\n cause,\n });\n}\n\nexport function createReactError(\n code: IdentityKitErrorCode,\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(code, message, {\n category: 'react',\n details,\n cause,\n });\n}\n\nexport function createCryptoError(\n code: IdentityKitErrorCode,\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(code, message, {\n category: 'crypto',\n details,\n cause,\n });\n}\n\nexport function createMultibaseError(\n code: IdentityKitErrorCode,\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(code, message, {\n category: 'multibase',\n details,\n cause,\n });\n}\n\nexport function createStorageError(\n code: IdentityKitErrorCode,\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(code, message, {\n category: 'storage',\n details,\n cause,\n });\n}\n\nexport function createValidationError(\n code: IdentityKitErrorCode,\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(code, message, {\n category: 'validation',\n details,\n cause,\n });\n}\n\nexport function createSignerError(\n code: IdentityKitErrorCode,\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(code, message, {\n category: 'signer',\n details,\n cause,\n });\n}\n\nexport function createDeepLinkError(\n code: IdentityKitErrorCode,\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(code, message, {\n category: 'deeplink',\n details,\n cause,\n });\n}\n\n/**\n * Wrap unknown errors into IdentityKitError\n */\nexport function wrapUnknownError(\n error: unknown,\n context: string,\n code: IdentityKitErrorCode = IdentityKitErrorCode.INTERNAL_ERROR\n): IdentityKitError {\n const originalError = error instanceof Error ? error : new Error(String(error));\n\n return new IdentityKitError(code, `${context}: ${originalError.message}`, {\n cause: originalError,\n details: { context },\n });\n}\n\n/**\n * Type guard to check if an error is an IdentityKitError\n */\nexport function isIdentityKitError(error: unknown): error is IdentityKitError {\n return error instanceof IdentityKitError;\n}\n\n/**\n * Legacy compatibility: map old AuthErrorCode to new IdentityKitErrorCode\n */\nexport const AuthErrorCodeMapping = {\n INVALID_HEADER: IdentityKitErrorCode.AUTH_INVALID_HEADER,\n INVALID_BASE64: IdentityKitErrorCode.AUTH_INVALID_BASE64,\n INVALID_JSON: IdentityKitErrorCode.AUTH_INVALID_JSON,\n MISSING_SIGNATURE: IdentityKitErrorCode.AUTH_MISSING_SIGNATURE,\n TIMESTAMP_OUT_OF_WINDOW: IdentityKitErrorCode.AUTH_TIMESTAMP_OUT_OF_WINDOW,\n NONCE_REPLAYED: IdentityKitErrorCode.AUTH_NONCE_REPLAYED,\n SIGNATURE_VERIFICATION_FAILED: IdentityKitErrorCode.SIGNATURE_VERIFICATION_FAILED,\n DID_DOCUMENT_NOT_FOUND: IdentityKitErrorCode.AUTH_DID_DOCUMENT_NOT_FOUND,\n VERIFICATION_METHOD_NOT_FOUND: IdentityKitErrorCode.AUTH_VERIFICATION_METHOD_NOT_FOUND,\n INVALID_PUBLIC_KEY: IdentityKitErrorCode.AUTH_INVALID_PUBLIC_KEY,\n DID_MISMATCH: IdentityKitErrorCode.AUTH_DID_MISMATCH,\n} as const;\n","import { SignatureScheme } from '@roochnetwork/rooch-sdk';\nimport { IdentityKitErrorCode, createValidationError } from '../errors';\n\n// Cryptographic types and constants\n\n/**\n * Readable enum for supported verification key suites.\n *\n * NOTE: Replaces the old `KEY_TYPE` const object. Use this in new code to\n * improve clarity. Values keep the same long-form strings to avoid breaking\n * existing behavior when serialized into DID Documents.\n */\nexport enum KeyType {\n ED25519 = 'Ed25519VerificationKey2020',\n SECP256K1 = 'EcdsaSecp256k1VerificationKey2019',\n ECDSAR1 = 'EcdsaSecp256r1VerificationKey2019',\n}\n\n/**\n * @deprecated Will be removed in the next major version. Use `KeyType` enum instead.\n */\nexport const KEY_TYPE = KeyType;\n\n/**\n * Type guard to check if a string is a valid KeyType\n */\nexport function isKeyType(value: string): value is KeyType {\n return Object.values(KeyType).includes(value as KeyType);\n}\n\n/**\n * Convert a string to KeyType, with runtime validation\n * @throws Error if the string is not a valid KeyType\n */\nexport function toKeyType(value: string): KeyType {\n if (isKeyType(value)) {\n return value;\n }\n throw createValidationError(\n IdentityKitErrorCode.KEY_TYPE_NOT_SUPPORTED,\n `Invalid key type: ${value}`,\n { value, supportedTypes: Object.values(KeyType) }\n );\n}\n\nexport function roochSignatureSchemeToKeyType(scheme: SignatureScheme): KeyType {\n if (scheme === 'Secp256k1') {\n return KeyType.SECP256K1;\n } else if (scheme === 'ED25519') {\n return KeyType.ED25519;\n } else if (scheme === 'EcdsaR1') {\n return KeyType.ECDSAR1;\n }\n throw createValidationError(\n IdentityKitErrorCode.KEY_TYPE_NOT_SUPPORTED,\n `Unsupported Rooch signature scheme: ${scheme}`,\n { scheme, supportedSchemes: ['Secp256k1', 'ED25519', 'EcdsaR1'] }\n );\n}\n\nexport function keyTypeToRoochSignatureScheme(keyType: KeyType): SignatureScheme {\n if (keyType === KeyType.SECP256K1) {\n return 'Secp256k1';\n } else if (keyType === KeyType.ED25519) {\n return 'ED25519';\n } else if (keyType === KeyType.ECDSAR1) {\n return 'EcdsaR1';\n }\n throw createValidationError(\n IdentityKitErrorCode.KEY_TYPE_NOT_SUPPORTED,\n `Unsupported key type: ${keyType}`,\n { keyType, supportedTypes: Object.values(KeyType) }\n );\n}\n\n/**\n * https://www.w3.org/TR/webauthn-2/#typedefdef-cosealgorithmidentifier\n * Convert a WebAuthn public key algorithm to KeyType, with runtime validation\n * @throws Error if the string is not a valid KeyType\n */\nexport function algorithmToKeyType(algorithm: number): KeyType | undefined {\n switch (algorithm) {\n case -8:\n return KeyType.ED25519;\n case -7:\n return KeyType.ECDSAR1;\n default:\n return undefined;\n }\n}\n\n/**\n * Convert a KeyType to WebAuthn algorithm identifier\n */\nexport function keyTypeToAlgorithm(keyType: KeyType): number | undefined {\n switch (keyType) {\n case KeyType.ED25519:\n return -8;\n case KeyType.ECDSAR1:\n return -7;\n default:\n return undefined;\n }\n}\n\n/**\n * Get list of supported WebAuthn algorithms\n */\nexport function getSupportedAlgorithms(): number[] {\n return [-8, -7];\n}\n\n/**\n * Type that represents either a KeyType or a string\n * Useful for functions that need to accept both strict KeyType and general string values\n */\nexport type KeyTypeInput = KeyType | string;\n\n/**\n * Represents the information needed to create a new operational key.\n */\nexport interface OperationalKeyInfo {\n idFragment?: string; // Optional fragment for the key id (e.g., 'key-2'). If not provided, one might be generated.\n type: string; // Cryptographic suite of the key, e.g., Ed25519VerificationKey2020\n publicKeyMaterial: Uint8Array | JsonWebKey; // The public key material\n controller?: string; // Defaults to the master DID if not provided\n}\n","import { DIDDocument } from '../types';\nimport { DIDDocumentCache } from './index';\n\n/**\n * A lightweight in-memory LRU cache implementation for DID Documents.\n * It is intentionally dependency-free so that the SDK does not pull in\n * additional packages by default. You can replace it with your own\n * implementation (Redis, IndexedDB, etc.) by implementing the\n * `DIDDocumentCache` interface and providing it to `VDRRegistry.setCache()`.\n */\nexport class InMemoryLRUDIDDocumentCache implements DIDDocumentCache {\n private readonly capacity: number;\n private readonly map: Map<string, DIDDocument | null>;\n\n constructor(maxEntries = 1000) {\n this.capacity = maxEntries;\n this.map = new Map<string, DIDDocument | null>();\n }\n\n get(did: string): DIDDocument | null | undefined {\n if (!this.map.has(did)) return undefined;\n const value = this.map.get(did) ?? null;\n // Refresh the recently used key to the end.\n this.map.delete(did);\n this.map.set(did, value);\n return value;\n }\n\n set(did: string, doc: DIDDocument | null): void {\n if (this.map.has(did)) {\n this.map.delete(did);\n } else if (this.map.size >= this.capacity) {\n // Evict the least-recently-used entry (Map iteration order is insertion order).\n const lruKey = this.map.keys().next().value;\n if (lruKey !== undefined) {\n this.map.delete(lruKey);\n }\n }\n this.map.set(did, doc);\n }\n\n has(did: string): boolean {\n return this.map.has(did);\n }\n\n delete(did: string): void {\n this.map.delete(did);\n }\n\n clear(): void {\n this.map.clear();\n }\n}\n","import { DIDDocument, DIDResolver } from '../types';\nimport { DIDDocumentCache } from '../cache';\nimport {\n VDRInterface,\n DIDCreationRequest,\n DIDCreationResult,\n CADOPCreationRequest,\n CADOPControllerCreationRequest,\n} from './types';\n\nimport { InMemoryLRUDIDDocumentCache } from '../cache/InMemoryLRUDIDDocumentCache';\nimport { IdentityKitErrorCode, createVDRError } from '../errors';\n\n/**\n * Global registry for VDR (Verifiable Data Registry) implementations.\n * This singleton manages all registered VDRs and maintains a DID Document cache.\n */\nexport class VDRRegistry implements DIDResolver {\n private static instance: VDRRegistry;\n private vdrs: Map<string, VDRInterface> = new Map();\n\n private cache: DIDDocumentCache;\n\n private constructor() {\n // Use the default in-memory cache unless overridden by the developer.\n this.cache = new InMemoryLRUDIDDocumentCache();\n }\n\n static getInstance(): VDRRegistry {\n if (!this.instance) {\n this.instance = new VDRRegistry();\n }\n return this.instance;\n }\n\n /** Register a VDR implementation for its DID method (e.g., 'key', 'rooch'). */\n registerVDR(vdr: VDRInterface) {\n this.vdrs.set(vdr.getMethod(), vdr);\n }\n\n /** Retrieve a previously registered VDR implementation by its method. */\n getVDR(method: string): VDRInterface | undefined {\n return this.vdrs.get(method);\n }\n\n /**\n * Override the default cache implementation.\n * This allows developers to provide their own cache (e.g., Redis, browser storage).\n */\n setCache(cache: DIDDocumentCache) {\n this.cache = cache;\n }\n\n /** Returns the currently configured cache instance. */\n getCache(): DIDDocumentCache {\n return this.cache;\n }\n\n async resolveDID(did: string, options?: { forceRefresh?: boolean }): Promise<DIDDocument | null> {\n const method = did.split(':')[1];\n const vdr = this.vdrs.get(method);\n if (!vdr) {\n throw createVDRError(\n IdentityKitErrorCode.VDR_NOT_AVAILABLE,\n `No VDR available for method: ${method}`,\n { method, availableMethods: Array.from(this.vdrs.keys()) }\n );\n }\n\n // Attempt to serve from cache if allowed.\n if (!options?.forceRefresh) {\n const cached = this.cache.get(did);\n if (cached !== undefined) {\n return cached;\n }\n }\n\n const resolved = await vdr.resolve(did);\n // Cache the resolution result (including null for negative caching).\n this.cache.set(did, resolved);\n return resolved;\n }\n\n async createDID(\n method: string,\n creationRequest: DIDCreationRequest,\n options?: Record<string, any>\n ): Promise<DIDCreationResult> {\n const vdr = this.vdrs.get(method);\n if (!vdr) {\n throw createVDRError(\n IdentityKitErrorCode.VDR_NOT_AVAILABLE,\n `No VDR available for method: ${method}`,\n { method, availableMethods: Array.from(this.vdrs.keys()) }\n );\n }\n const result = await vdr.create(creationRequest, options);\n if (result.success && result.didDocument) {\n this.cache.set(result.didDocument.id, result.didDocument);\n }\n return result;\n }\n\n async createDIDViaCADOP(\n method: string,\n creationRequest: CADOPCreationRequest,\n options?: Record<string, any>\n ): Promise<DIDCreationResult> {\n const vdr = this.vdrs.get(method);\n if (!vdr) {\n throw createVDRError(\n IdentityKitErrorCode.VDR_NOT_AVAILABLE,\n `No VDR available for method: ${method}`,\n { method, availableMethods: Array.from(this.vdrs.keys()) }\n );\n }\n const result = await vdr.createViaCADOP(creationRequest, options);\n if (result.success && result.didDocument) {\n this.cache.set(result.didDocument.id, result.didDocument);\n }\n return result;\n }\n\n async createDIDViaCADOPWithController(\n method: string,\n creationRequest: CADOPControllerCreationRequest,\n options?: Record<string, any>\n ): Promise<DIDCreationResult> {\n const vdr = this.vdrs.get(method);\n if (!vdr) {\n throw createVDRError(\n IdentityKitErrorCode.VDR_NOT_AVAILABLE,\n `No VDR available for method: ${method}`,\n { method, availableMethods: Array.from(this.vdrs.keys()) }\n );\n }\n const result = await vdr.createViaCADOPWithController(creationRequest, options);\n if (result.success && result.didDocument) {\n this.cache.set(result.didDocument.id, result.didDocument);\n }\n return result;\n }\n\n async exists(did: string): Promise<boolean> {\n const method = did.split(':')[1];\n const vdr = this.vdrs.get(method);\n if (!vdr) {\n throw createVDRError(\n IdentityKitErrorCode.VDR_NOT_AVAILABLE,\n `No VDR available for method: ${method}`,\n { method, availableMethods: Array.from(this.vdrs.keys()) }\n );\n }\n\n // If we have a positive cache entry, short-circuit the call.\n if (this.cache.has(did)) {\n const doc = this.cache.get(did);\n return doc !== null;\n }\n\n const exists = await vdr.exists(did);\n // We don't cache the existence check result here to avoid stale data.\n return exists;\n }\n}\n","import { base58btc } from 'multiformats/bases/base58';\nimport { base64pad, base64, base64url, base64urlpad } from 'multiformats/bases/base64';\nimport { base16 } from 'multiformats/bases/base16';\nimport { bytesToString, stringToBytes } from '../utils/bytes';\nimport { IdentityKitErrorCode, createMultibaseError } from '../errors';\n\n/**\n * Supported multibase names – use these with the generic `encode()` API.\n */\nexport type MultibaseName =\n | 'base58btc'\n | 'base64pad'\n | 'base64'\n | 'base64url'\n | 'base64urlpad'\n | 'base16';\n\ntype MultibaseCodecImpl = {\n encode: (bytes: Uint8Array) => string;\n decode: (encoded: string) => Uint8Array;\n};\n\nconst ENCODER_MAP: Record<MultibaseName, MultibaseCodecImpl> = {\n base58btc,\n base64pad,\n base64,\n base64url,\n base64urlpad,\n base16,\n};\n\n/**\n * Base multibase codec implementation\n * Provides basic encoding/decoding functionality without key type awareness\n */\nexport class MultibaseCodec {\n /**\n * Generic encode\n * Example: `MultibaseCodec.encode(bytes, 'base64url')`\n */\n static encode(data: Uint8Array | string, base: MultibaseName): string {\n const encoder = ENCODER_MAP[base];\n if (!encoder) {\n throw createMultibaseError(\n IdentityKitErrorCode.MULTIBASE_ENCODE_FAILED,\n `Unsupported multibase: ${base}`,\n { base, supportedBases: Object.keys(ENCODER_MAP) }\n );\n }\n const bytes = typeof data === 'string' ? stringToBytes(data) : data;\n return encoder.encode(bytes);\n }\n\n /**\n * Encode bytes to base58btc format\n * @param bytes The bytes to encode\n * @returns base58btc encoded string with 'z' prefix\n */\n static encodeBase58btc(bytes: Uint8Array): string {\n return this.encode(bytes, 'base58btc');\n }\n\n /**\n * Encode bytes to base64pad format\n * @param bytes The bytes to encode\n * @returns base64pad encoded string with 'M' prefix\n */\n static encodeBase64pad(data: Uint8Array | string): string {\n return this.encode(data, 'base64pad');\n }\n\n /**\n * Encode bytes to base16 (hex) format\n * @param bytes The bytes to encode\n * @returns base16 encoded string with 'f' prefix\n */\n static encodeBase16(bytes: Uint8Array): string {\n return this.encode(bytes, 'base16');\n }\n\n /**\n * Encode bytes to base64 format\n * @param bytes The bytes to encode\n * @returns base64 encoded string\n */\n static encodeBase64(data: Uint8Array | string): string {\n return this.encode(data, 'base64');\n }\n\n /**\n * Encode bytes to base64url format (RFC4648 URL-safe, no padding)\n * @param bytes The bytes to encode\n * @returns base64url encoded string with 'u' prefix\n */\n static encodeBase64url(data: Uint8Array | string): string {\n return this.encode(data, 'base64url');\n }\n\n /**\n * Encode bytes to base64urlpad format (URL-safe with padding)\n * @param bytes The bytes to encode\n * @returns base64urlpad encoded string with 'U' prefix\n */\n static encodeBase64urlpad(data: Uint8Array | string): string {\n return this.encode(data, 'base64urlpad');\n }\n\n /**\n * Decode base58btc string to bytes\n * @param encoded The base58btc encoded string\n * @returns decoded bytes\n */\n static decodeBase58btc(encoded: string): Uint8Array {\n return base58btc.decode(encoded);\n }\n\n /**\n * Decode base64pad string to bytes\n * @param encoded The base64pad encoded string\n * @returns decoded bytes\n */\n static decodeBase64pad(encoded: string): Uint8Array {\n return base64pad.decode(encoded);\n }\n\n /**\n * Decode base16 string to bytes\n * @param encoded The base16 encoded string\n * @returns decoded bytes\n */\n static decodeBase16(encoded: string): Uint8Array {\n return base16.decode(encoded);\n }\n\n /**\n * Decode base64 string to bytes\n * @param encoded The base64 encoded string\n * @returns decoded bytes\n */\n static decodeBase64(encoded: string): Uint8Array {\n return base64.decode(encoded);\n }\n\n /**\n * Decode base64url string to bytes\n * @param encoded The base64url encoded string\n * @returns decoded bytes\n */\n static decodeBase64url(encoded: string): Uint8Array {\n return base64url.decode(encoded);\n }\n\n /**\n * Decode base64url string to string\n * @param encoded The base64url encoded string\n * @returns decoded string\n */\n static decodeBase64urlToString(encoded: string): string {\n return bytesToString(this.decodeBase64url(encoded));\n }\n\n /**\n * Decode base64urlpad string to bytes\n * @param encoded The base64urlpad encoded string\n * @returns decoded bytes\n */\n static decodeBase64urlpad(encoded: string): Uint8Array {\n return base64urlpad.decode(encoded);\n }\n\n /**\n * Decode base64urlpad string to string\n * @param encoded The base64urlpad encoded string\n * @returns decoded string\n */\n static decodeBase64urlpadToString(encoded: string): string {\n return bytesToString(this.decodeBase64urlpad(encoded));\n }\n\n /**\n * Decode multibase encoded string to bytes\n * After multiformats v9, there is no longer a single \"universal base\" object;\n * the official recommendation is to manually dispatch prefixes between the few *.decoder objects you use.\n * @param encoded The multibase encoded string\n * @returns decoded bytes\n */\n static decode(encoded: string): Uint8Array {\n // Multibase prefix is always the first character\n const prefix = encoded[0];\n switch (prefix) {\n case 'z': // base58btc\n return base58btc.decode(encoded);\n case 'M': // base64pad (RFC4648 with padding)\n return base64pad.decode(encoded);\n case 'f': // base16 (hex, lowercase)\n return base16.decode(encoded);\n case 'm': // base64 (no padding)\n return base64.decode(encoded);\n case 'u': // base64url (no padding)\n return base64url.decode(encoded);\n case 'U': // base64urlpad (with padding)\n return base64urlpad.decode(encoded);\n default:\n throw createMultibaseError(\n IdentityKitErrorCode.MULTIBASE_DECODE_FAILED,\n `Unsupported multibase prefix: ${prefix}`,\n { prefix, supportedPrefixes: ['z', 'M', 'm', 'u', 'U', 'f'] }\n );\n }\n }\n}\n","import { IdentityKitErrorCode, createValidationError } from '../errors';\n\nexport function stringToBytes(str: string): Uint8Array {\n if (typeof TextEncoder !== 'undefined') {\n return new TextEncoder().encode(str);\n }\n // Node.js < 16 fallback using Buffer\n if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') {\n return Uint8Array.from(Buffer.from(str, 'utf-8'));\n }\n throw createValidationError(\n IdentityKitErrorCode.ENVIRONMENT_NOT_SUPPORTED,\n 'No TextEncoder or Buffer available in this environment.',\n {\n environment: typeof globalThis,\n textEncoderAvailable: typeof TextEncoder !== 'undefined',\n bufferAvailable: typeof Buffer !== 'undefined',\n }\n );\n}\n\nexport function bytesToString(bytes: Uint8Array): string {\n if (typeof TextDecoder !== 'undefined') {\n return new TextDecoder('utf-8').decode(bytes);\n }\n // Node.js < 16 fallback using Buffer\n if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') {\n return Buffer.from(bytes).toString('utf-8');\n }\n throw createValidationError(\n IdentityKitErrorCode.ENVIRONMENT_NOT_SUPPORTED,\n 'No TextDecoder or Buffer available in this environment.',\n {\n environment: typeof globalThis,\n textDecoderAvailable: typeof TextDecoder !== 'undefined',\n bufferAvailable: typeof Buffer !== 'undefined',\n }\n );\n}\n\nexport function base64urlToBytes(base64url: string): Uint8Array {\n // Add padding if needed\n const padding = base64url.length % 4;\n const paddedBase64url = base64url + '='.repeat(padding === 0 ? 0 : 4 - padding);\n\n // Convert base64url to base64\n const base64 = paddedBase64url.replace(/-/g, '+').replace(/_/g, '/');\n\n // Decode to bytes\n const binaryString = atob(base64);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n return bytes;\n}\n\nexport const Bytes = {\n stringToBytes,\n bytesToString,\n base64urlToBytes,\n};\n\nexport default Bytes;\n","import { KeyType, KEY_TYPE } from '../types/crypto';\nimport { MultibaseCodec } from './base';\nimport { IdentityKitErrorCode, createMultibaseError, createValidationError } from '../errors';\n\n/**\n * Key multibase codec implementation\n * Handles encoding/decoding of cryptographic keys with type information\n */\nexport class KeyMultibaseCodec {\n private static readonly ED25519_PREFIX = new Uint8Array([0xed, 0x01]);\n private static readonly SECP256K1_PREFIX = new Uint8Array([0xe7, 0x01]);\n private static readonly ECDSA_R1_PREFIX = new Uint8Array([0x12, 0x00]);\n private static readonly ED25519_KEY_LENGTH = 32;\n private static readonly SECP256K1_KEY_LENGTH = 33;\n private static readonly ECDSA_R1_KEY_LENGTH = 33;\n\n /**\n * Encode public key with multicodec prefix\n * @param bytes The public key bytes\n * @param keyType The key type\n * @returns multibase encoded string\n */\n static encodeWithType(bytes: Uint8Array, keyType: KeyType): string {\n // Validate key length\n const expectedLength = this.getExpectedKeyLength(keyType);\n if (bytes.length !== expectedLength) {\n throw createValidationError(\n IdentityKitErrorCode.INVALID_INPUT_FORMAT,\n `Invalid key length for ${keyType}. Expected ${expectedLength} bytes, got ${bytes.length}`,\n { keyType, expectedLength, actualLength: bytes.length }\n );\n }\n\n const prefix = this.getMulticodecPrefix(keyType);\n const prefixedKey = this.concatenateBytes(prefix, bytes);\n return MultibaseCodec.encodeBase58btc(prefixedKey);\n }\n\n /**\n * Decode multibase encoded key\n * @param encoded The multibase encoded string\n * @returns The key type and public key bytes\n */\n static decodeWithType(encoded: string): { keyType: KeyType; bytes: Uint8Array } {\n try {\n const decoded = MultibaseCodec.decodeBase58btc(encoded);\n if (decoded.length < 2) {\n throw createMultibaseError(\n IdentityKitErrorCode.MULTIBASE_DECODE_FAILED,\n 'Invalid key format: too short',\n { encoded, minimumLength: 2, actualLength: decoded.length }\n );\n }\n\n const keyType = this.extractKeyType(decoded);\n const bytes = this.extractBytes(decoded);\n\n // Validate key length\n const expectedLength = this.getExpectedKeyLength(keyType);\n if (bytes.length !== expectedLength) {\n throw createValidationError(\n IdentityKitErrorCode.INVALID_INPUT_FORMAT,\n `Invalid key length for ${keyType}. Expected ${expectedLength} bytes, got ${bytes.length}`,\n { keyType, expectedLength, actualLength: bytes.length }\n );\n }\n\n return { keyType, bytes };\n } catch (error) {\n if (error instanceof Error && error.message === 'Non-base58btc character') {\n throw createMultibaseError(\n IdentityKitErrorCode.MULTIBASE_DECODE_FAILED,\n 'Invalid multibase format',\n { encoded }\n );\n }\n throw error;\n }\n }\n\n private static getMulticodecPrefix(keyType: KeyType): Uint8Array {\n switch (keyType) {\n case KEY_TYPE.ED25519:\n return this.ED25519_PREFIX;\n case KEY_TYPE.SECP256K1:\n return this.SECP256K1_PREFIX;\n case KEY_TYPE.ECDSAR1:\n return this.ECDSA_R1_PREFIX;\n default:\n throw createValidationError(\n IdentityKitErrorCode.KEY_TYPE_NOT_SUPPORTED,\n `Unsupported key type: ${keyType}`,\n { keyType, supportedTypes: [KEY_TYPE.ED25519, KEY_TYPE.SECP256K1, KEY_TYPE.ECDSAR1] }\n );\n }\n }\n\n private static concatenateBytes(a: Uint8Array, b: Uint8Array): Uint8Array {\n const result = new Uint8Array(a.length + b.length);\n result.set(a);\n result.set(b, a.length);\n return result;\n }\n\n private static extractKeyType(prefixedBytes: Uint8Array): KeyType {\n if (prefixedBytes[0] === 0xed && prefixedBytes[1] === 0x01) {\n return KEY_TYPE.ED25519;\n } else if (prefixedBytes[0] === 0xe7 && prefixedBytes[1] === 0x01) {\n return KEY_TYPE.SECP256K1;\n } else if (prefixedBytes[0] === 0x12 && prefixedBytes[1] === 0x00) {\n return KEY_TYPE.ECDSAR1;\n }\n throw createMultibaseError(\n IdentityKitErrorCode.MULTIBASE_DECODE_FAILED,\n 'Unknown key type prefix',\n { prefix: Array.from(prefixedBytes.slice(0, 2)) }\n );\n }\n\n private static extractBytes(prefixedBytes: Uint8Array): Uint8Array {\n return prefixedBytes.slice(2);\n }\n\n private static getExpectedKeyLength(keyType: KeyType): number {\n switch (keyType) {\n case KEY_TYPE.ED25519:\n return this.ED25519_KEY_LENGTH;\n case KEY_TYPE.SECP256K1:\n return this.SECP256K1_KEY_LENGTH;\n case KEY_TYPE.ECDSAR1:\n return this.ECDSA_R1_KEY_LENGTH;\n default:\n throw createValidationError(\n IdentityKitErrorCode.KEY_TYPE_NOT_SUPPORTED,\n `Unsupported key type: ${keyType}`,\n { keyType, supportedTypes: [KEY_TYPE.ED25519, KEY_TYPE.SECP256K1, KEY_TYPE.ECDSAR1] }\n );\n }\n }\n}\n","import { KeyType } from '../types/crypto';\nimport { MultibaseCodec } from './base';\nimport { KeyMultibaseCodec } from './key';\nimport { IdentityKitErrorCode, createDIDError } from '../errors';\n\n/**\n * DID key codec implementation\n * Handles encoding/decoding of did:key identifiers\n */\nexport class DidKeyCodec {\n /**\n * Generate did:key from public key\n * @param publicKey The public key bytes\n * @param keyType The key type\n * @returns did:key identifier\n */\n static generateDidKey(publicKey: Uint8Array, keyType: KeyType): string {\n // KeyMultibaseCodec.encodeWithType will validate key length\n const multibase = KeyMultibaseCodec.encodeWithType(publicKey, keyType);\n return `did:key:${multibase}`;\n }\n\n /**\n * Parse did:key to get key type and public key\n * @param didKey The did:key identifier\n * @returns The key type and public key bytes\n */\n static parseDidKey(didKey: string): { keyType: KeyType; publicKey: Uint8Array } {\n if (!didKey.startsWith('did:key:')) {\n throw createDIDError(IdentityKitErrorCode.DID_INVALID_FORMAT, 'Invalid did:key format', {\n didKey,\n expectedPrefix: 'did:key:',\n });\n }\n const multibase = didKey.substring(8);\n const { keyType, bytes } = KeyMultibaseCodec.decodeWithType(multibase);\n return { keyType, publicKey: bytes };\n }\n}\n","/**\n * DID utility helpers (method, identifier & fragment parsing)\n * Used across SDK layers (VDR, Signer, KeyManager, etc.)\n */\n\nimport { IdentityKitErrorCode, createDIDError } from '../errors';\n\n/**\n * Parsed DID parts\n */\nexport interface ParsedDID {\n /** DID method, e.g. 'key', 'rooch' */\n method: string;\n /** Unique identifier part (method-specific id, without fragment) */\n identifier: string;\n /** Optional fragment (ver. method / service id) */\n fragment?: string;\n}\n\n/**\n * Parse a DID or DID-URL into its components.\n *\n * @param did Full DID string: `did:<method>:<identifier>[#fragment]`\n * @throws Error if input does not start with `did:` or lacks method / identifier parts.\n */\nexport function parseDid(did: string): ParsedDID {\n if (!did.startsWith('did:')) {\n throw createDIDError(IdentityKitErrorCode.DID_INVALID_FORMAT, `Invalid DID: ${did}`, {\n did,\n reason: 'DID must start with \"did:\"',\n });\n }\n\n // Strip leading `did:` and split once by ':'\n const afterPrefix = did.slice(4);\n const methodEnd = afterPrefix.indexOf(':');\n if (methodEnd === -1) {\n throw createDIDError(\n IdentityKitErrorCode.DID_INVALID_FORMAT,\n `Invalid DID – missing method/identifier separator: ${did}`,\n { did, reason: 'Missing method/identifier separator \":\"' }\n );\n }\n const method = afterPrefix.slice(0, methodEnd);\n const idPlusFrag = afterPrefix.slice(methodEnd + 1);\n if (!method || !idPlusFrag) {\n throw createDIDError(IdentityKitErrorCode.DID_INVALID_FORMAT, `Invalid DID: ${did}`, {\n did,\n reason: 'Empty method or identifier',\n });\n }\n\n const hashIdx = idPlusFrag.indexOf('#');\n return hashIdx === -1\n ? { method, identifier: idPlusFrag }\n : {\n method,\n identifier: idPlusFrag.slice(0, hashIdx),\n fragment: idPlusFrag.slice(hashIdx + 1),\n };\n}\n\n/** Get DID method string */\nexport function extractMethod(did: string): string {\n return parseDid(did).method;\n}\n\n/** Get method-specific identifier (without fragment) */\nexport function extractIdentifier(did: string): string {\n return parseDid(did).identifier;\n}\n\n/**\n * Extract the fragment from a DID URL or any string containing `#`.\n * Throws an error if no fragment present.\n */\nexport function extractFragment(idOrDid: string): string {\n const idx = idOrDid.indexOf('#');\n if (idx === -1) {\n throw createDIDError(\n IdentityKitErrorCode.DID_INVALID_FORMAT,\n `No fragment found in ${idOrDid}`,\n { idOrDid, reason: 'Missing fragment separator \"#\"' }\n );\n }\n return idOrDid.slice(idx + 1);\n}\n\n/** Alias kept for back-compat with existing imports */\nexport const extractFragmentFromId = extractFragment;\n\n/** Build a canonical DID string from method & identifier */\nexport function buildDid(method: string, identifier: string): string {\n return `did:${method}:${identifier}`;\n}\n\n/**\n * Compare two DIDs ignoring their fragments.\n */\nexport function sameDid(a: string, b: string): boolean {\n const pa = parseDid(a);\n const pb = parseDid(b);\n return pa.method === pb.method && pa.identifier === pb.identifier;\n}\n\n/**\n * Return the canonical DID (strip any `#fragment`).\n */\nexport function getDidWithoutFragment(did: string): string {\n const { method, identifier } = parseDid(did);\n return buildDid(method, identifier);\n}\n","/* eslint-disable no-console */\n/*\n * Lightweight environment-agnostic debug logger.\n *\n * ‑ Works in both Node.js and browser.\n * ‑ Supports level filtering (debug | info | warn | error | silent).\n * ‑ Namespaced: each module/class can request its own logger via DebugLogger.get(\"MyModule\").\n * ‑ Global level can be controlled at runtime via DebugLogger.setGlobalLevel() *or*\n * environment variable NUWA_LOG_LEVEL (node) / window.__NUWA_LOG_LEVEL__ (browser).\n */\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';\n\nconst LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n silent: 50,\n};\n\nfunction detectInitialGlobalLevel(): LogLevel {\n // Node.js: use process.env if available\n if (typeof process !== 'undefined' && (process as any).env) {\n const envLevel = (process as any).env.NUWA_LOG_LEVEL as string | undefined;\n if (envLevel && envLevel in LEVEL_ORDER) return envLevel as LogLevel;\n }\n // Browser: allow runtime override via global variable\n if (typeof window !== 'undefined' && (window as any).__NUWA_LOG_LEVEL__) {\n const envLevel = (window as any).__NUWA_LOG_LEVEL__ as string;\n if (envLevel && envLevel in LEVEL_ORDER) return envLevel as LogLevel;\n }\n return 'info';\n}\n\nexport class DebugLogger {\n // ---------------------------------------------------------------------------\n // Static section\n // ---------------------------------------------------------------------------\n private static globalLevel: LogLevel = detectInitialGlobalLevel();\n private static loggers = new Map<string, DebugLogger>();\n private static defaultNamespace = 'global';\n\n /** Acquire (or create) a logger for the given namespace. */\n static get(namespace: string): DebugLogger {\n if (!DebugLogger.loggers.has(namespace)) {\n DebugLogger.loggers.set(namespace, new DebugLogger(namespace));\n }\n return DebugLogger.loggers.get(namespace)!;\n }\n\n /** Override global log level at runtime. */\n static setGlobalLevel(level: LogLevel): void {\n DebugLogger.globalLevel = level;\n // Propagate to existing instances unless they explicitly override.\n for (const logger of DebugLogger.loggers.values()) {\n if (!logger.levelOverridden) {\n logger.level = level;\n }\n }\n }\n\n /** Read current global level. */\n static getGlobalLevel(): LogLevel {\n return DebugLogger.globalLevel;\n }\n\n /** Set defau