w3pk 0.10.2 → 0.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/zk/utils.ts","../../src/core/errors.ts"],"sourcesContent":["/**\n * ZK Utility Functions\n * Helper functions for zero-knowledge proof operations\n */\n\nimport { CryptoError } from \"../core/errors\";\n\n/**\n * Generate a random blinding factor for commitments\n */\nexport function generateBlinding(): bigint {\n const buffer = new Uint8Array(32);\n crypto.getRandomValues(buffer);\n return bufferToBigInt(buffer);\n}\n\n/**\n * Convert buffer to bigint\n */\nexport function bufferToBigInt(buffer: Uint8Array): bigint {\n let result = 0n;\n for (let i = 0; i < buffer.length; i++) {\n result = (result << 8n) | BigInt(buffer[i]);\n }\n return result;\n}\n\n/**\n * Convert bigint to buffer\n */\nexport function bigIntToBuffer(\n value: bigint,\n byteLength: number = 32\n): Uint8Array {\n const buffer = new Uint8Array(byteLength);\n let temp = value;\n\n for (let i = byteLength - 1; i >= 0; i--) {\n buffer[i] = Number(temp & 0xffn);\n temp = temp >> 8n;\n }\n\n return buffer;\n}\n\n/**\n * Hash a value using SHA-256\n */\nexport async function sha256Hash(data: string | Uint8Array): Promise<string> {\n try {\n const buffer =\n typeof data === \"string\" ? new TextEncoder().encode(data) : data;\n\n const hashBuffer = await crypto.subtle.digest(\n \"SHA-256\",\n buffer as BufferSource\n );\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n return hashArray.map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n } catch (error) {\n throw new CryptoError(\"Failed to compute hash\", error);\n }\n}\n\n/**\n * Stretch a key using PBKDF2\n * Use case: Password hashing, deterministic key derivation\n *\n * @param input - The input string to stretch (e.g., password)\n * @param salt - Salt value to prevent rainbow table attacks\n * @param iterations - Number of iterations (default: 10000)\n * @param keyLength - Output key length in bytes (default: 32)\n * @returns Hex-encoded stretched key\n */\nexport async function stretchKey(\n input: string,\n salt: string,\n iterations: number = 10000,\n keyLength: number = 32\n): Promise<string> {\n try {\n const encoder = new TextEncoder();\n const inputBuffer = encoder.encode(input);\n const saltBuffer = encoder.encode(salt);\n\n // Import the password as a key for PBKDF2\n const keyMaterial = await crypto.subtle.importKey(\n \"raw\",\n inputBuffer,\n \"PBKDF2\",\n false,\n [\"deriveBits\"]\n );\n\n // Derive bits using PBKDF2\n const derivedBits = await crypto.subtle.deriveBits(\n {\n name: \"PBKDF2\",\n salt: saltBuffer,\n iterations: iterations,\n hash: \"SHA-256\",\n },\n keyMaterial,\n keyLength * 8 // Convert bytes to bits\n );\n\n // Convert to hex string\n const hashArray = Array.from(new Uint8Array(derivedBits));\n return hashArray.map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n } catch (error) {\n throw new CryptoError(\"Failed to stretch key\", error);\n }\n}\n\n/**\n * Hash data with salt using SHA-256\n * Use case: Creating deterministic identifiers\n *\n * @param data - The data to hash\n * @param salt - Salt value for additional entropy\n * @returns Hex-encoded hash\n */\nexport async function hashWithSalt(\n data: string,\n salt: string\n): Promise<string> {\n try {\n const combined = data + salt;\n return await sha256Hash(combined);\n } catch (error) {\n throw new CryptoError(\"Failed to hash with salt\", error);\n }\n}\n\n/**\n * Hash data multiple times (key stretching)\n * Use case: Making brute-force attacks impractical\n *\n * @param data - The data to hash\n * @param salt - Salt value to prevent rainbow table attacks\n * @param iterations - Number of hash iterations\n * @returns Hex-encoded hash after multiple iterations\n */\nexport async function iterativeHash(\n data: string,\n salt: string,\n iterations: number\n): Promise<string> {\n try {\n if (iterations < 1) {\n throw new Error(\"Iterations must be at least 1\");\n }\n\n // Initial hash with salt\n let hash = await hashWithSalt(data, salt);\n\n // Iteratively hash the result\n for (let i = 1; i < iterations; i++) {\n hash = await sha256Hash(hash);\n }\n\n return hash;\n } catch (error) {\n throw new CryptoError(\"Failed to perform iterative hash\", error);\n }\n}\n\n/**\n * Build a merkle tree from leaves\n */\nexport async function buildMerkleTree(leaves: string[]): Promise<{\n root: string;\n tree: string[][];\n}> {\n try {\n // @ts-ignore - Optional dependency, may not be installed\n const circomlibjs = await import(\"circomlibjs\");\n const poseidon = await circomlibjs.buildPoseidon();\n\n if (leaves.length === 0) {\n throw new Error(\"Cannot build tree from empty leaves\");\n }\n\n const tree: string[][] = [leaves];\n let currentLevel = leaves.map((leaf) => BigInt(leaf));\n\n while (currentLevel.length > 1) {\n const nextLevel: bigint[] = [];\n\n for (let i = 0; i < currentLevel.length; i += 2) {\n if (i + 1 < currentLevel.length) {\n const hashResult = poseidon([currentLevel[i], currentLevel[i + 1]]);\n \n // Convert Uint8Array result to BigInt if needed\n const hash = hashResult instanceof Uint8Array \n ? bufferToBigInt(hashResult) \n : hashResult;\n \n nextLevel.push(hash);\n } else {\n nextLevel.push(currentLevel[i]);\n }\n }\n\n tree.push(nextLevel.map((n) => n.toString()));\n currentLevel = nextLevel;\n }\n\n return {\n root: currentLevel[0].toString(),\n tree,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"Cannot resolve module\")) {\n throw new CryptoError(\n \"ZK merkle tree requires circomlibjs. Install with: npm install circomlibjs\\n\" +\n \"For more info: https://github.com/w3hc/w3pk#zero-knowledge-proofs\",\n error\n );\n }\n throw new CryptoError(\"Failed to build merkle tree\", error);\n }\n}\n\n/**\n * Generate merkle proof for a leaf\n */\nexport function generateMerkleProof(\n tree: string[][],\n leafIndex: number\n): {\n pathIndices: number[];\n pathElements: string[];\n} {\n const pathIndices: number[] = [];\n const pathElements: string[] = [];\n let currentIndex = leafIndex;\n\n for (let level = 0; level < tree.length - 1; level++) {\n const isRightNode = currentIndex % 2 === 1;\n const siblingIndex = isRightNode ? currentIndex - 1 : currentIndex + 1;\n\n pathIndices.push(isRightNode ? 1 : 0);\n\n if (siblingIndex < tree[level].length) {\n pathElements.push(tree[level][siblingIndex]);\n } else {\n pathElements.push(tree[level][currentIndex]);\n }\n\n currentIndex = Math.floor(currentIndex / 2);\n }\n\n return { pathIndices, pathElements };\n}\n\n/**\n * Validate proof inputs\n */\nexport function validateProofInputs(inputs: Record<string, any>): void {\n for (const [key, value] of Object.entries(inputs)) {\n if (value === undefined || value === null) {\n throw new CryptoError(`Missing required input: ${key}`);\n }\n }\n}\n\n/**\n * Convert hex string to bigint\n */\nexport function hexToBigInt(hex: string): bigint {\n return BigInt(hex.startsWith(\"0x\") ? hex : `0x${hex}`);\n}\n\n/**\n * Convert bigint to hex string\n */\nexport function bigIntToHex(value: bigint, padToBytes?: number): string {\n let hex = value.toString(16);\n if (padToBytes) {\n hex = hex.padStart(padToBytes * 2, \"0\");\n }\n return `0x${hex}`;\n}\n\n/**\n * Serialize ZK proof for storage or transmission\n */\nexport function serializeProof(proof: any): string {\n return JSON.stringify(proof);\n}\n\n/**\n * Deserialize ZK proof from storage or transmission\n */\nexport function deserializeProof(serialized: string): any {\n try {\n return JSON.parse(serialized);\n } catch (error) {\n throw new CryptoError(\"Failed to deserialize proof\", error);\n }\n}\n\n/**\n * Generate a random nonce for challenges\n */\nexport function generateNonce(): bigint {\n return generateBlinding();\n}\n\n/**\n * Validate Ethereum address format\n */\nexport function isValidAddress(address: string): boolean {\n return /^0x[a-fA-F0-9]{40}$/.test(address);\n}\n\n/**\n * Build NFT holders merkle tree\n * Creates a merkle tree from a list of NFT holder addresses for a specific contract\n */\nexport async function buildNFTHoldersMerkleTree(\n holderAddresses: string[],\n contractAddress: string\n): Promise<{\n root: string;\n tree: string[][];\n holderLeaves: string[];\n}> {\n try {\n // @ts-ignore - Optional dependency, may not be installed\n const circomlibjs = await import(\"circomlibjs\");\n const poseidon = await circomlibjs.buildPoseidon();\n\n if (holderAddresses.length === 0) {\n throw new Error(\"Cannot build NFT holders tree from empty holder list\");\n }\n\n // Create leaf hashes: Hash(holderAddress, contractAddress)\n // Convert hex addresses to BigInt (remove 0x prefix first)\n const contractHash = BigInt(contractAddress.startsWith('0x') ? contractAddress : '0x' + contractAddress);\n const holderLeaves = holderAddresses.map((address) => {\n const cleanAddress = address.startsWith('0x') ? address : '0x' + address;\n const addressHash = BigInt(cleanAddress);\n const hashResult = poseidon([addressHash, contractHash]);\n \n // Convert Uint8Array result to BigInt if needed\n if (hashResult instanceof Uint8Array) {\n return bufferToBigInt(hashResult).toString();\n }\n \n return hashResult.toString();\n });\n\n // Build merkle tree from the leaves\n const { root, tree } = await buildMerkleTree(holderLeaves);\n\n return {\n root,\n tree,\n holderLeaves,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"Cannot resolve module\")) {\n throw new CryptoError(\n \"NFT holder merkle tree requires circomlibjs. Install with: npm install circomlibjs\\n\" +\n \"For more info: https://github.com/w3hc/w3pk#zero-knowledge-proofs\",\n error\n );\n }\n throw new CryptoError(\"Failed to build NFT holders merkle tree\", error);\n }\n}\n\n/**\n * Generate NFT ownership proof inputs\n * Helper to prepare all inputs needed for NFT ownership proof\n */\nexport async function generateNFTOwnershipProofInputs(\n ownerAddress: string,\n contractAddress: string,\n allHolderAddresses: string[],\n minBalance: bigint = 1n\n): Promise<{\n nftProofInput: {\n ownerAddress: string;\n holderIndex: number;\n pathIndices: number[];\n pathElements: string[];\n holdersRoot: string;\n contractAddress: string;\n minBalance: bigint;\n };\n holderLeaves: string[];\n}> {\n // Find the owner's position in the holders list\n const holderIndex = allHolderAddresses.findIndex(\n (address) => address.toLowerCase() === ownerAddress.toLowerCase()\n );\n\n if (holderIndex === -1) {\n throw new CryptoError(\n `Owner address ${ownerAddress} not found in holders list for contract ${contractAddress}`\n );\n }\n\n // Build the NFT holders merkle tree\n const { root, tree, holderLeaves } = await buildNFTHoldersMerkleTree(\n allHolderAddresses,\n contractAddress\n );\n\n // Generate merkle proof for this owner\n const { pathIndices, pathElements } = generateMerkleProof(tree, holderIndex);\n\n return {\n nftProofInput: {\n ownerAddress,\n holderIndex,\n pathIndices,\n pathElements,\n holdersRoot: root,\n contractAddress,\n minBalance,\n },\n holderLeaves,\n };\n}\n\n/**\n * Validate NFT ownership proof inputs\n */\nexport function validateNFTOwnershipProofInputs(inputs: {\n ownerAddress: string;\n contractAddress: string;\n holderIndex: number;\n pathIndices: number[];\n pathElements: string[];\n holdersRoot: string;\n minBalance?: bigint;\n}): void {\n if (!isValidAddress(inputs.ownerAddress)) {\n throw new CryptoError(`Invalid owner address: ${inputs.ownerAddress}`);\n }\n\n if (!isValidAddress(inputs.contractAddress)) {\n throw new CryptoError(`Invalid contract address: ${inputs.contractAddress}`);\n }\n\n if (inputs.holderIndex < 0 || !Number.isInteger(inputs.holderIndex)) {\n throw new CryptoError(`Invalid holder index: ${inputs.holderIndex}`);\n }\n\n if (inputs.pathIndices.length !== inputs.pathElements.length) {\n throw new CryptoError(\n `Path indices and elements length mismatch: ${inputs.pathIndices.length} vs ${inputs.pathElements.length}`\n );\n }\n\n if (inputs.pathIndices.length === 0) {\n throw new CryptoError(\"Empty merkle proof path\");\n }\n\n if (!inputs.holdersRoot || inputs.holdersRoot.length === 0) {\n throw new CryptoError(\"Invalid holders root\");\n }\n\n if (inputs.minBalance && inputs.minBalance < 1n) {\n throw new CryptoError(\"Minimum balance must be at least 1\");\n }\n}\n","/**\n * Custom error classes for better error handling\n */\n\nexport class Web3PasskeyError extends Error {\n constructor(\n message: string,\n public code: string,\n public originalError?: unknown\n ) {\n super(message);\n this.name = \"Web3PasskeyError\";\n }\n}\n\nexport class AuthenticationError extends Web3PasskeyError {\n constructor(message: string, originalError?: unknown) {\n super(message, \"AUTHENTICATION_ERROR\", originalError);\n this.name = \"AuthenticationError\";\n }\n}\n\nexport class RegistrationError extends Web3PasskeyError {\n constructor(message: string, originalError?: unknown) {\n super(message, \"REGISTRATION_ERROR\", originalError);\n this.name = \"RegistrationError\";\n }\n}\n\nexport class WalletError extends Web3PasskeyError {\n constructor(message: string, originalError?: unknown) {\n super(message, \"WALLET_ERROR\", originalError);\n this.name = \"WalletError\";\n }\n}\n\nexport class CryptoError extends Web3PasskeyError {\n constructor(message: string, originalError?: unknown) {\n super(message, \"CRYPTO_ERROR\", originalError);\n this.name = \"CryptoError\";\n }\n}\n\nexport class StorageError extends Web3PasskeyError {\n constructor(message: string, originalError?: unknown) {\n super(message, \"STORAGE_ERROR\", originalError);\n this.name = \"StorageError\";\n }\n}\n\nexport class ApiError extends Web3PasskeyError {\n constructor(\n message: string,\n public statusCode?: number,\n originalError?: unknown\n ) {\n super(message, \"API_ERROR\", originalError);\n this.name = \"ApiError\";\n }\n}\n"],"mappings":"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","names":["utils_exports","__export","bigIntToBuffer","bigIntToHex","bufferToBigInt","buildMerkleTree","buildNFTHoldersMerkleTree","deserializeProof","generateBlinding","generateMerkleProof","generateNFTOwnershipProofInputs","generateNonce","hashWithSalt","hexToBigInt","isValidAddress","iterativeHash","serializeProof","sha256Hash","stretchKey","validateNFTOwnershipProofInputs","validateProofInputs","__toCommonJS","Web3PasskeyError","message","code","originalError","CryptoError","Web3PasskeyError","message","originalError","generateBlinding","buffer","bufferToBigInt","result","i","bigIntToBuffer","value","byteLength","temp","sha256Hash","data","hashBuffer","b","error","CryptoError","stretchKey","input","salt","iterations","keyLength","encoder","inputBuffer","saltBuffer","keyMaterial","derivedBits","hashWithSalt","combined","iterativeHash","hash","buildMerkleTree","leaves","poseidon","tree","currentLevel","leaf","nextLevel","hashResult","n","generateMerkleProof","leafIndex","pathIndices","pathElements","currentIndex","level","isRightNode","siblingIndex","validateProofInputs","inputs","key","hexToBigInt","hex","bigIntToHex","padToBytes","serializeProof","proof","deserializeProof","serialized","generateNonce","isValidAddress","address","buildNFTHoldersMerkleTree","holderAddresses","contractAddress","contractHash","holderLeaves","cleanAddress","addressHash","root","generateNFTOwnershipProofInputs","ownerAddress","allHolderAddresses","minBalance","holderIndex","validateNFTOwnershipProofInputs"]}
1
+ {"version":3,"sources":["../../src/zk/utils.ts","../../src/core/errors.ts"],"sourcesContent":["/**\n * ZK Utility Functions\n * Helper functions for zero-knowledge proof operations\n */\n\nimport { CryptoError } from \"../core/errors\";\n\n/**\n * Generate a random blinding factor for commitments\n */\nexport function generateBlinding(): bigint {\n const buffer = new Uint8Array(32);\n crypto.getRandomValues(buffer);\n return bufferToBigInt(buffer);\n}\n\n/**\n * Convert buffer to bigint\n */\nexport function bufferToBigInt(buffer: Uint8Array): bigint {\n let result = 0n;\n for (let i = 0; i < buffer.length; i++) {\n result = (result << 8n) | BigInt(buffer[i]);\n }\n return result;\n}\n\n/**\n * Convert bigint to buffer\n */\nexport function bigIntToBuffer(\n value: bigint,\n byteLength: number = 32\n): Uint8Array {\n const buffer = new Uint8Array(byteLength);\n let temp = value;\n\n for (let i = byteLength - 1; i >= 0; i--) {\n buffer[i] = Number(temp & 0xffn);\n temp = temp >> 8n;\n }\n\n return buffer;\n}\n\n/**\n * Hash a value using SHA-256\n */\nexport async function sha256Hash(data: string | Uint8Array): Promise<string> {\n try {\n const buffer =\n typeof data === \"string\" ? new TextEncoder().encode(data) : data;\n\n const hashBuffer = await crypto.subtle.digest(\n \"SHA-256\",\n buffer as BufferSource\n );\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n return hashArray.map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n } catch (error) {\n throw new CryptoError(\"Failed to compute hash\", error);\n }\n}\n\n/**\n * Stretch a key using PBKDF2\n * Use case: Password hashing, deterministic key derivation\n *\n * @param input - The input string to stretch (e.g., password)\n * @param salt - Salt value to prevent rainbow table attacks\n * @param iterations - Number of iterations (default: 10000)\n * @param keyLength - Output key length in bytes (default: 32)\n * @returns Hex-encoded stretched key\n */\nexport async function stretchKey(\n input: string,\n salt: string,\n iterations: number = 10000,\n keyLength: number = 32\n): Promise<string> {\n try {\n const encoder = new TextEncoder();\n const inputBuffer = encoder.encode(input);\n const saltBuffer = encoder.encode(salt);\n\n // Import the password as a key for PBKDF2\n const keyMaterial = await crypto.subtle.importKey(\n \"raw\",\n inputBuffer,\n \"PBKDF2\",\n false,\n [\"deriveBits\"]\n );\n\n // Derive bits using PBKDF2\n const derivedBits = await crypto.subtle.deriveBits(\n {\n name: \"PBKDF2\",\n salt: saltBuffer,\n iterations: iterations,\n hash: \"SHA-256\",\n },\n keyMaterial,\n keyLength * 8 // Convert bytes to bits\n );\n\n // Convert to hex string\n const hashArray = Array.from(new Uint8Array(derivedBits));\n return hashArray.map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n } catch (error) {\n throw new CryptoError(\"Failed to stretch key\", error);\n }\n}\n\n/**\n * Hash data with salt using SHA-256\n * Use case: Creating deterministic identifiers\n *\n * @param data - The data to hash\n * @param salt - Salt value for additional entropy\n * @returns Hex-encoded hash\n */\nexport async function hashWithSalt(\n data: string,\n salt: string\n): Promise<string> {\n try {\n const combined = data + salt;\n return await sha256Hash(combined);\n } catch (error) {\n throw new CryptoError(\"Failed to hash with salt\", error);\n }\n}\n\n/**\n * Hash data multiple times (key stretching)\n * Use case: Making brute-force attacks impractical\n *\n * @param data - The data to hash\n * @param salt - Salt value to prevent rainbow table attacks\n * @param iterations - Number of hash iterations\n * @returns Hex-encoded hash after multiple iterations\n */\nexport async function iterativeHash(\n data: string,\n salt: string,\n iterations: number\n): Promise<string> {\n try {\n if (iterations < 1) {\n throw new Error(\"Iterations must be at least 1\");\n }\n\n // Initial hash with salt\n let hash = await hashWithSalt(data, salt);\n\n // Iteratively hash the result\n for (let i = 1; i < iterations; i++) {\n hash = await sha256Hash(hash);\n }\n\n return hash;\n } catch (error) {\n throw new CryptoError(\"Failed to perform iterative hash\", error);\n }\n}\n\n/**\n * Build a merkle tree from leaves\n */\nexport async function buildMerkleTree(leaves: string[]): Promise<{\n root: string;\n tree: string[][];\n}> {\n try {\n // @ts-ignore - Optional dependency, may not be installed\n const circomlibjs = await import(\"circomlibjs\");\n const poseidon = await circomlibjs.buildPoseidon();\n\n if (leaves.length === 0) {\n throw new Error(\"Cannot build tree from empty leaves\");\n }\n\n const tree: string[][] = [leaves];\n let currentLevel = leaves.map((leaf) => BigInt(leaf));\n\n while (currentLevel.length > 1) {\n const nextLevel: bigint[] = [];\n\n for (let i = 0; i < currentLevel.length; i += 2) {\n if (i + 1 < currentLevel.length) {\n const hashResult = poseidon([currentLevel[i], currentLevel[i + 1]]);\n \n // Convert Uint8Array result to BigInt if needed\n const hash = hashResult instanceof Uint8Array \n ? bufferToBigInt(hashResult) \n : hashResult;\n \n nextLevel.push(hash);\n } else {\n nextLevel.push(currentLevel[i]);\n }\n }\n\n tree.push(nextLevel.map((n) => n.toString()));\n currentLevel = nextLevel;\n }\n\n return {\n root: currentLevel[0].toString(),\n tree,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"Cannot resolve module\")) {\n throw new CryptoError(\n \"ZK merkle tree requires circomlibjs. Install with: npm install circomlibjs\\n\" +\n \"For more info: https://github.com/w3hc/w3pk#zero-knowledge-proofs\",\n error\n );\n }\n throw new CryptoError(\"Failed to build merkle tree\", error);\n }\n}\n\n/**\n * Generate merkle proof for a leaf\n */\nexport function generateMerkleProof(\n tree: string[][],\n leafIndex: number\n): {\n pathIndices: number[];\n pathElements: string[];\n} {\n const pathIndices: number[] = [];\n const pathElements: string[] = [];\n let currentIndex = leafIndex;\n\n for (let level = 0; level < tree.length - 1; level++) {\n const isRightNode = currentIndex % 2 === 1;\n const siblingIndex = isRightNode ? currentIndex - 1 : currentIndex + 1;\n\n pathIndices.push(isRightNode ? 1 : 0);\n\n if (siblingIndex < tree[level].length) {\n pathElements.push(tree[level][siblingIndex]);\n } else {\n pathElements.push(tree[level][currentIndex]);\n }\n\n currentIndex = Math.floor(currentIndex / 2);\n }\n\n return { pathIndices, pathElements };\n}\n\n/**\n * Validate proof inputs\n */\nexport function validateProofInputs(inputs: Record<string, any>): void {\n for (const [key, value] of Object.entries(inputs)) {\n if (value === undefined || value === null) {\n throw new CryptoError(`Missing required input: ${key}`);\n }\n }\n}\n\n/**\n * Convert hex string to bigint\n */\nexport function hexToBigInt(hex: string): bigint {\n return BigInt(hex.startsWith(\"0x\") ? hex : `0x${hex}`);\n}\n\n/**\n * Convert bigint to hex string\n */\nexport function bigIntToHex(value: bigint, padToBytes?: number): string {\n let hex = value.toString(16);\n if (padToBytes) {\n hex = hex.padStart(padToBytes * 2, \"0\");\n }\n return `0x${hex}`;\n}\n\n/**\n * Serialize ZK proof for storage or transmission\n */\nexport function serializeProof(proof: any): string {\n return JSON.stringify(proof);\n}\n\n/**\n * Deserialize ZK proof from storage or transmission\n */\nexport function deserializeProof(serialized: string): any {\n try {\n return JSON.parse(serialized);\n } catch (error) {\n throw new CryptoError(\"Failed to deserialize proof\", error);\n }\n}\n\n/**\n * Generate a random nonce for challenges\n */\nexport function generateNonce(): bigint {\n return generateBlinding();\n}\n\n/**\n * Validate Ethereum address format\n */\nexport function isValidAddress(address: string): boolean {\n return /^0x[a-fA-F0-9]{40}$/.test(address);\n}\n\n/**\n * Build NFT holders merkle tree\n * Creates a merkle tree from a list of NFT holder addresses for a specific contract\n */\nexport async function buildNFTHoldersMerkleTree(\n holderAddresses: string[],\n contractAddress: string\n): Promise<{\n root: string;\n tree: string[][];\n holderLeaves: string[];\n}> {\n try {\n // @ts-ignore - Optional dependency, may not be installed\n const circomlibjs = await import(\"circomlibjs\");\n const poseidon = await circomlibjs.buildPoseidon();\n\n if (holderAddresses.length === 0) {\n throw new Error(\"Cannot build NFT holders tree from empty holder list\");\n }\n\n // Create leaf hashes: Hash(holderAddress, contractAddress)\n // Convert hex addresses to BigInt (remove 0x prefix first)\n const contractHash = BigInt(contractAddress.startsWith('0x') ? contractAddress : '0x' + contractAddress);\n const holderLeaves = holderAddresses.map((address) => {\n const cleanAddress = address.startsWith('0x') ? address : '0x' + address;\n const addressHash = BigInt(cleanAddress);\n const hashResult = poseidon([addressHash, contractHash]);\n \n // Convert Uint8Array result to BigInt if needed\n if (hashResult instanceof Uint8Array) {\n return bufferToBigInt(hashResult).toString();\n }\n \n return hashResult.toString();\n });\n\n // Build merkle tree from the leaves\n const { root, tree } = await buildMerkleTree(holderLeaves);\n\n return {\n root,\n tree,\n holderLeaves,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"Cannot resolve module\")) {\n throw new CryptoError(\n \"NFT holder merkle tree requires circomlibjs. Install with: npm install circomlibjs\\n\" +\n \"For more info: https://github.com/w3hc/w3pk#zero-knowledge-proofs\",\n error\n );\n }\n throw new CryptoError(\"Failed to build NFT holders merkle tree\", error);\n }\n}\n\n/**\n * Generate NFT ownership proof inputs\n * Helper to prepare all inputs needed for NFT ownership proof\n */\nexport async function generateNFTOwnershipProofInputs(\n ownerAddress: string,\n contractAddress: string,\n allHolderAddresses: string[],\n minBalance: bigint = 1n\n): Promise<{\n nftProofInput: {\n ownerAddress: string;\n holderIndex: number;\n pathIndices: number[];\n pathElements: string[];\n holdersRoot: string;\n contractAddress: string;\n minBalance: bigint;\n };\n holderLeaves: string[];\n}> {\n // Find the owner's position in the holders list\n const holderIndex = allHolderAddresses.findIndex(\n (address) => address.toLowerCase() === ownerAddress.toLowerCase()\n );\n\n if (holderIndex === -1) {\n throw new CryptoError(\n `Owner address ${ownerAddress} not found in holders list for contract ${contractAddress}`\n );\n }\n\n // Build the NFT holders merkle tree\n const { root, tree, holderLeaves } = await buildNFTHoldersMerkleTree(\n allHolderAddresses,\n contractAddress\n );\n\n // Generate merkle proof for this owner\n const { pathIndices, pathElements } = generateMerkleProof(tree, holderIndex);\n\n return {\n nftProofInput: {\n ownerAddress,\n holderIndex,\n pathIndices,\n pathElements,\n holdersRoot: root,\n contractAddress,\n minBalance,\n },\n holderLeaves,\n };\n}\n\n/**\n * Validate NFT ownership proof inputs\n */\nexport function validateNFTOwnershipProofInputs(inputs: {\n ownerAddress: string;\n contractAddress: string;\n holderIndex: number;\n pathIndices: number[];\n pathElements: string[];\n holdersRoot: string;\n minBalance?: bigint;\n}): void {\n if (!isValidAddress(inputs.ownerAddress)) {\n throw new CryptoError(`Invalid owner address: ${inputs.ownerAddress}`);\n }\n\n if (!isValidAddress(inputs.contractAddress)) {\n throw new CryptoError(`Invalid contract address: ${inputs.contractAddress}`);\n }\n\n if (inputs.holderIndex < 0 || !Number.isInteger(inputs.holderIndex)) {\n throw new CryptoError(`Invalid holder index: ${inputs.holderIndex}`);\n }\n\n if (inputs.pathIndices.length !== inputs.pathElements.length) {\n throw new CryptoError(\n `Path indices and elements length mismatch: ${inputs.pathIndices.length} vs ${inputs.pathElements.length}`\n );\n }\n\n if (inputs.pathIndices.length === 0) {\n throw new CryptoError(\"Empty merkle proof path\");\n }\n\n if (!inputs.holdersRoot || inputs.holdersRoot.length === 0) {\n throw new CryptoError(\"Invalid holders root\");\n }\n\n if (inputs.minBalance && inputs.minBalance < 1n) {\n throw new CryptoError(\"Minimum balance must be at least 1\");\n }\n}\n","/**\n * Custom error classes for better error handling\n */\n\nexport class Web3PasskeyError extends Error {\n constructor(\n message: string,\n public code: string,\n public originalError?: unknown\n ) {\n super(message);\n this.name = \"Web3PasskeyError\";\n }\n}\n\nexport class AuthenticationError extends Web3PasskeyError {\n constructor(message: string, originalError?: unknown) {\n super(message, \"AUTHENTICATION_ERROR\", originalError);\n this.name = \"AuthenticationError\";\n }\n}\n\nexport class RegistrationError extends Web3PasskeyError {\n constructor(message: string, originalError?: unknown) {\n super(message, \"REGISTRATION_ERROR\", originalError);\n this.name = \"RegistrationError\";\n }\n}\n\nexport class WalletError extends Web3PasskeyError {\n constructor(message: string, originalError?: unknown) {\n super(message, \"WALLET_ERROR\", originalError);\n this.name = \"WalletError\";\n }\n}\n\nexport class CryptoError extends Web3PasskeyError {\n constructor(message: string, originalError?: unknown) {\n super(message, \"CRYPTO_ERROR\", originalError);\n this.name = \"CryptoError\";\n }\n}\n\nexport class StorageError extends Web3PasskeyError {\n constructor(message: string, originalError?: unknown) {\n super(message, \"STORAGE_ERROR\", originalError);\n this.name = \"StorageError\";\n }\n}\n\nexport class ApiError extends Web3PasskeyError {\n constructor(\n message: string,\n public statusCode?: number,\n originalError?: unknown\n ) {\n super(message, \"API_ERROR\", originalError);\n this.name = \"ApiError\";\n }\n}\n\n/**\n * The passkey authenticated, but this device has no credential metadata.\n * The app should start a sync or restore flow.\n */\nexport class SyncRequiredError extends Web3PasskeyError {\n constructor(public credentialId: string) {\n super(\n \"Passkey authenticated but this device has no data for it. Sync this device or restore from a backup.\",\n \"SYNC_REQUIRED\"\n );\n this.name = \"SyncRequiredError\";\n }\n}\n\n/**\n * The credential metadata is on this device, but the wallet record is gone.\n * The app should offer a restore from backup.\n */\nexport class WalletMissingError extends Web3PasskeyError {\n constructor(\n public credentialId: string,\n public ethereumAddress: string\n ) {\n super(\n \"Passkey authenticated but no wallet was found on this device. Restore it from a backup.\",\n \"WALLET_MISSING\"\n );\n this.name = \"WalletMissingError\";\n }\n}\n"],"mappings":"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","names":["utils_exports","__export","bigIntToBuffer","bigIntToHex","bufferToBigInt","buildMerkleTree","buildNFTHoldersMerkleTree","deserializeProof","generateBlinding","generateMerkleProof","generateNFTOwnershipProofInputs","generateNonce","hashWithSalt","hexToBigInt","isValidAddress","iterativeHash","serializeProof","sha256Hash","stretchKey","validateNFTOwnershipProofInputs","validateProofInputs","__toCommonJS","Web3PasskeyError","message","code","originalError","CryptoError","Web3PasskeyError","message","originalError","generateBlinding","buffer","bufferToBigInt","result","i","bigIntToBuffer","value","byteLength","temp","sha256Hash","data","hashBuffer","b","error","CryptoError","stretchKey","input","salt","iterations","keyLength","encoder","inputBuffer","saltBuffer","keyMaterial","derivedBits","hashWithSalt","combined","iterativeHash","hash","buildMerkleTree","leaves","poseidon","tree","currentLevel","leaf","nextLevel","hashResult","n","generateMerkleProof","leafIndex","pathIndices","pathElements","currentIndex","level","isRightNode","siblingIndex","validateProofInputs","inputs","key","hexToBigInt","hex","bigIntToHex","padToBytes","serializeProof","proof","deserializeProof","serialized","generateNonce","isValidAddress","address","buildNFTHoldersMerkleTree","holderAddresses","contractAddress","contractHash","holderLeaves","cleanAddress","addressHash","root","generateNFTOwnershipProofInputs","ownerAddress","allHolderAddresses","minBalance","holderIndex","validateNFTOwnershipProofInputs"]}
package/dist/zk/utils.mjs CHANGED
@@ -1,4 +1,4 @@
1
- var g=class extends Error{constructor(e,o,n){super(e);this.code=o;this.originalError=n;this.name="Web3PasskeyError"}};var i=class extends g{constructor(r,e){super(r,"CRYPTO_ERROR",e),this.name="CryptoError"}};function w(){let t=new Uint8Array(32);return crypto.getRandomValues(t),f(t)}function f(t){let r=0n;for(let e=0;e<t.length;e++)r=r<<8n|BigInt(t[e]);return r}function A(t,r=32){let e=new Uint8Array(r),o=t;for(let n=r-1;n>=0;n--)e[n]=Number(o&0xffn),o=o>>8n;return e}async function p(t){try{let r=typeof t=="string"?new TextEncoder().encode(t):t,e=await crypto.subtle.digest("SHA-256",r);return Array.from(new Uint8Array(e)).map(n=>n.toString(16).padStart(2,"0")).join("")}catch(r){throw new i("Failed to compute hash",r)}}async function v(t,r,e=1e4,o=32){try{let n=new TextEncoder,s=n.encode(t),a=n.encode(r),c=await crypto.subtle.importKey("raw",s,"PBKDF2",!1,["deriveBits"]),l=await crypto.subtle.deriveBits({name:"PBKDF2",salt:a,iterations:e,hash:"SHA-256"},c,o*8);return Array.from(new Uint8Array(l)).map(h=>h.toString(16).padStart(2,"0")).join("")}catch(n){throw new i("Failed to stretch key",n)}}async function b(t,r){try{let e=t+r;return await p(e)}catch(e){throw new i("Failed to hash with salt",e)}}async function k(t,r,e){try{if(e<1)throw new Error("Iterations must be at least 1");let o=await b(t,r);for(let n=1;n<e;n++)o=await p(o);return o}catch(o){throw new i("Failed to perform iterative hash",o)}}async function x(t){try{let e=await(await import("circomlibjs")).buildPoseidon();if(t.length===0)throw new Error("Cannot build tree from empty leaves");let o=[t],n=t.map(s=>BigInt(s));for(;n.length>1;){let s=[];for(let a=0;a<n.length;a+=2)if(a+1<n.length){let c=e([n[a],n[a+1]]),l=c instanceof Uint8Array?f(c):c;s.push(l)}else s.push(n[a]);o.push(s.map(a=>a.toString())),n=s}return{root:n[0].toString(),tree:o}}catch(r){throw r instanceof Error&&r.message.includes("Cannot resolve module")?new i(`ZK merkle tree requires circomlibjs. Install with: npm install circomlibjs
2
- For more info: https://github.com/w3hc/w3pk#zero-knowledge-proofs`,r):new i("Failed to build merkle tree",r)}}function y(t,r){let e=[],o=[],n=r;for(let s=0;s<t.length-1;s++){let a=n%2===1,c=a?n-1:n+1;e.push(a?1:0),c<t[s].length?o.push(t[s][c]):o.push(t[s][n]),n=Math.floor(n/2)}return{pathIndices:e,pathElements:o}}function P(t){for(let[r,e]of Object.entries(t))if(e==null)throw new i(`Missing required input: ${r}`)}function B(t){return BigInt(t.startsWith("0x")?t:`0x${t}`)}function T(t,r){let e=t.toString(16);return r&&(e=e.padStart(r*2,"0")),`0x${e}`}function F(t){return JSON.stringify(t)}function S(t){try{return JSON.parse(t)}catch(r){throw new i("Failed to deserialize proof",r)}}function O(){return w()}function m(t){return/^0x[a-fA-F0-9]{40}$/.test(t)}async function I(t,r){try{let o=await(await import("circomlibjs")).buildPoseidon();if(t.length===0)throw new Error("Cannot build NFT holders tree from empty holder list");let n=BigInt(r.startsWith("0x")?r:"0x"+r),s=t.map(l=>{let u=l.startsWith("0x")?l:"0x"+l,h=BigInt(u),d=o([h,n]);return d instanceof Uint8Array?f(d).toString():d.toString()}),{root:a,tree:c}=await x(s);return{root:a,tree:c,holderLeaves:s}}catch(e){throw e instanceof Error&&e.message.includes("Cannot resolve module")?new i(`NFT holder merkle tree requires circomlibjs. Install with: npm install circomlibjs
3
- For more info: https://github.com/w3hc/w3pk#zero-knowledge-proofs`,e):new i("Failed to build NFT holders merkle tree",e)}}async function N(t,r,e,o=1n){let n=e.findIndex(h=>h.toLowerCase()===t.toLowerCase());if(n===-1)throw new i(`Owner address ${t} not found in holders list for contract ${r}`);let{root:s,tree:a,holderLeaves:c}=await I(e,r),{pathIndices:l,pathElements:u}=y(a,n);return{nftProofInput:{ownerAddress:t,holderIndex:n,pathIndices:l,pathElements:u,holdersRoot:s,contractAddress:r,minBalance:o},holderLeaves:c}}function j(t){if(!m(t.ownerAddress))throw new i(`Invalid owner address: ${t.ownerAddress}`);if(!m(t.contractAddress))throw new i(`Invalid contract address: ${t.contractAddress}`);if(t.holderIndex<0||!Number.isInteger(t.holderIndex))throw new i(`Invalid holder index: ${t.holderIndex}`);if(t.pathIndices.length!==t.pathElements.length)throw new i(`Path indices and elements length mismatch: ${t.pathIndices.length} vs ${t.pathElements.length}`);if(t.pathIndices.length===0)throw new i("Empty merkle proof path");if(!t.holdersRoot||t.holdersRoot.length===0)throw new i("Invalid holders root");if(t.minBalance&&t.minBalance<1n)throw new i("Minimum balance must be at least 1")}export{A as bigIntToBuffer,T as bigIntToHex,f as bufferToBigInt,x as buildMerkleTree,I as buildNFTHoldersMerkleTree,S as deserializeProof,w as generateBlinding,y as generateMerkleProof,N as generateNFTOwnershipProofInputs,O as generateNonce,b as hashWithSalt,B as hexToBigInt,m as isValidAddress,k as iterativeHash,F as serializeProof,p as sha256Hash,v as stretchKey,j as validateNFTOwnershipProofInputs,P as validateProofInputs};
1
+ var g=class extends Error{constructor(r,o,n){super(r);this.code=o;this.originalError=n;this.name="Web3PasskeyError"}};var i=class extends g{constructor(e,r){super(e,"CRYPTO_ERROR",r),this.name="CryptoError"}};function w(){let t=new Uint8Array(32);return crypto.getRandomValues(t),f(t)}function f(t){let e=0n;for(let r=0;r<t.length;r++)e=e<<8n|BigInt(t[r]);return e}function A(t,e=32){let r=new Uint8Array(e),o=t;for(let n=e-1;n>=0;n--)r[n]=Number(o&0xffn),o=o>>8n;return r}async function p(t){try{let e=typeof t=="string"?new TextEncoder().encode(t):t,r=await crypto.subtle.digest("SHA-256",e);return Array.from(new Uint8Array(r)).map(n=>n.toString(16).padStart(2,"0")).join("")}catch(e){throw new i("Failed to compute hash",e)}}async function k(t,e,r=1e4,o=32){try{let n=new TextEncoder,s=n.encode(t),a=n.encode(e),c=await crypto.subtle.importKey("raw",s,"PBKDF2",!1,["deriveBits"]),l=await crypto.subtle.deriveBits({name:"PBKDF2",salt:a,iterations:r,hash:"SHA-256"},c,o*8);return Array.from(new Uint8Array(l)).map(h=>h.toString(16).padStart(2,"0")).join("")}catch(n){throw new i("Failed to stretch key",n)}}async function b(t,e){try{let r=t+e;return await p(r)}catch(r){throw new i("Failed to hash with salt",r)}}async function v(t,e,r){try{if(r<1)throw new Error("Iterations must be at least 1");let o=await b(t,e);for(let n=1;n<r;n++)o=await p(o);return o}catch(o){throw new i("Failed to perform iterative hash",o)}}async function x(t){try{let r=await(await import("circomlibjs")).buildPoseidon();if(t.length===0)throw new Error("Cannot build tree from empty leaves");let o=[t],n=t.map(s=>BigInt(s));for(;n.length>1;){let s=[];for(let a=0;a<n.length;a+=2)if(a+1<n.length){let c=r([n[a],n[a+1]]),l=c instanceof Uint8Array?f(c):c;s.push(l)}else s.push(n[a]);o.push(s.map(a=>a.toString())),n=s}return{root:n[0].toString(),tree:o}}catch(e){throw e instanceof Error&&e.message.includes("Cannot resolve module")?new i(`ZK merkle tree requires circomlibjs. Install with: npm install circomlibjs
2
+ For more info: https://github.com/w3hc/w3pk#zero-knowledge-proofs`,e):new i("Failed to build merkle tree",e)}}function I(t,e){let r=[],o=[],n=e;for(let s=0;s<t.length-1;s++){let a=n%2===1,c=a?n-1:n+1;r.push(a?1:0),c<t[s].length?o.push(t[s][c]):o.push(t[s][n]),n=Math.floor(n/2)}return{pathIndices:r,pathElements:o}}function P(t){for(let[e,r]of Object.entries(t))if(r==null)throw new i(`Missing required input: ${e}`)}function S(t){return BigInt(t.startsWith("0x")?t:`0x${t}`)}function T(t,e){let r=t.toString(16);return e&&(r=r.padStart(e*2,"0")),`0x${r}`}function B(t){return JSON.stringify(t)}function F(t){try{return JSON.parse(t)}catch(e){throw new i("Failed to deserialize proof",e)}}function N(){return w()}function m(t){return/^0x[a-fA-F0-9]{40}$/.test(t)}async function y(t,e){try{let o=await(await import("circomlibjs")).buildPoseidon();if(t.length===0)throw new Error("Cannot build NFT holders tree from empty holder list");let n=BigInt(e.startsWith("0x")?e:"0x"+e),s=t.map(l=>{let u=l.startsWith("0x")?l:"0x"+l,h=BigInt(u),d=o([h,n]);return d instanceof Uint8Array?f(d).toString():d.toString()}),{root:a,tree:c}=await x(s);return{root:a,tree:c,holderLeaves:s}}catch(r){throw r instanceof Error&&r.message.includes("Cannot resolve module")?new i(`NFT holder merkle tree requires circomlibjs. Install with: npm install circomlibjs
3
+ For more info: https://github.com/w3hc/w3pk#zero-knowledge-proofs`,r):new i("Failed to build NFT holders merkle tree",r)}}async function O(t,e,r,o=1n){let n=r.findIndex(h=>h.toLowerCase()===t.toLowerCase());if(n===-1)throw new i(`Owner address ${t} not found in holders list for contract ${e}`);let{root:s,tree:a,holderLeaves:c}=await y(r,e),{pathIndices:l,pathElements:u}=I(a,n);return{nftProofInput:{ownerAddress:t,holderIndex:n,pathIndices:l,pathElements:u,holdersRoot:s,contractAddress:e,minBalance:o},holderLeaves:c}}function j(t){if(!m(t.ownerAddress))throw new i(`Invalid owner address: ${t.ownerAddress}`);if(!m(t.contractAddress))throw new i(`Invalid contract address: ${t.contractAddress}`);if(t.holderIndex<0||!Number.isInteger(t.holderIndex))throw new i(`Invalid holder index: ${t.holderIndex}`);if(t.pathIndices.length!==t.pathElements.length)throw new i(`Path indices and elements length mismatch: ${t.pathIndices.length} vs ${t.pathElements.length}`);if(t.pathIndices.length===0)throw new i("Empty merkle proof path");if(!t.holdersRoot||t.holdersRoot.length===0)throw new i("Invalid holders root");if(t.minBalance&&t.minBalance<1n)throw new i("Minimum balance must be at least 1")}export{A as bigIntToBuffer,T as bigIntToHex,f as bufferToBigInt,x as buildMerkleTree,y as buildNFTHoldersMerkleTree,F as deserializeProof,w as generateBlinding,I as generateMerkleProof,O as generateNFTOwnershipProofInputs,N as generateNonce,b as hashWithSalt,S as hexToBigInt,m as isValidAddress,v as iterativeHash,B as serializeProof,p as sha256Hash,k as stretchKey,j as validateNFTOwnershipProofInputs,P as validateProofInputs};
4
4
  //# sourceMappingURL=utils.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/errors.ts","../../src/zk/utils.ts"],"sourcesContent":["/**\n * Custom error classes for better error handling\n */\n\nexport class Web3PasskeyError extends Error {\n constructor(\n message: string,\n public code: string,\n public originalError?: unknown\n ) {\n super(message);\n this.name = \"Web3PasskeyError\";\n }\n}\n\nexport class AuthenticationError extends Web3PasskeyError {\n constructor(message: string, originalError?: unknown) {\n super(message, \"AUTHENTICATION_ERROR\", originalError);\n this.name = \"AuthenticationError\";\n }\n}\n\nexport class RegistrationError extends Web3PasskeyError {\n constructor(message: string, originalError?: unknown) {\n super(message, \"REGISTRATION_ERROR\", originalError);\n this.name = \"RegistrationError\";\n }\n}\n\nexport class WalletError extends Web3PasskeyError {\n constructor(message: string, originalError?: unknown) {\n super(message, \"WALLET_ERROR\", originalError);\n this.name = \"WalletError\";\n }\n}\n\nexport class CryptoError extends Web3PasskeyError {\n constructor(message: string, originalError?: unknown) {\n super(message, \"CRYPTO_ERROR\", originalError);\n this.name = \"CryptoError\";\n }\n}\n\nexport class StorageError extends Web3PasskeyError {\n constructor(message: string, originalError?: unknown) {\n super(message, \"STORAGE_ERROR\", originalError);\n this.name = \"StorageError\";\n }\n}\n\nexport class ApiError extends Web3PasskeyError {\n constructor(\n message: string,\n public statusCode?: number,\n originalError?: unknown\n ) {\n super(message, \"API_ERROR\", originalError);\n this.name = \"ApiError\";\n }\n}\n","/**\n * ZK Utility Functions\n * Helper functions for zero-knowledge proof operations\n */\n\nimport { CryptoError } from \"../core/errors\";\n\n/**\n * Generate a random blinding factor for commitments\n */\nexport function generateBlinding(): bigint {\n const buffer = new Uint8Array(32);\n crypto.getRandomValues(buffer);\n return bufferToBigInt(buffer);\n}\n\n/**\n * Convert buffer to bigint\n */\nexport function bufferToBigInt(buffer: Uint8Array): bigint {\n let result = 0n;\n for (let i = 0; i < buffer.length; i++) {\n result = (result << 8n) | BigInt(buffer[i]);\n }\n return result;\n}\n\n/**\n * Convert bigint to buffer\n */\nexport function bigIntToBuffer(\n value: bigint,\n byteLength: number = 32\n): Uint8Array {\n const buffer = new Uint8Array(byteLength);\n let temp = value;\n\n for (let i = byteLength - 1; i >= 0; i--) {\n buffer[i] = Number(temp & 0xffn);\n temp = temp >> 8n;\n }\n\n return buffer;\n}\n\n/**\n * Hash a value using SHA-256\n */\nexport async function sha256Hash(data: string | Uint8Array): Promise<string> {\n try {\n const buffer =\n typeof data === \"string\" ? new TextEncoder().encode(data) : data;\n\n const hashBuffer = await crypto.subtle.digest(\n \"SHA-256\",\n buffer as BufferSource\n );\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n return hashArray.map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n } catch (error) {\n throw new CryptoError(\"Failed to compute hash\", error);\n }\n}\n\n/**\n * Stretch a key using PBKDF2\n * Use case: Password hashing, deterministic key derivation\n *\n * @param input - The input string to stretch (e.g., password)\n * @param salt - Salt value to prevent rainbow table attacks\n * @param iterations - Number of iterations (default: 10000)\n * @param keyLength - Output key length in bytes (default: 32)\n * @returns Hex-encoded stretched key\n */\nexport async function stretchKey(\n input: string,\n salt: string,\n iterations: number = 10000,\n keyLength: number = 32\n): Promise<string> {\n try {\n const encoder = new TextEncoder();\n const inputBuffer = encoder.encode(input);\n const saltBuffer = encoder.encode(salt);\n\n // Import the password as a key for PBKDF2\n const keyMaterial = await crypto.subtle.importKey(\n \"raw\",\n inputBuffer,\n \"PBKDF2\",\n false,\n [\"deriveBits\"]\n );\n\n // Derive bits using PBKDF2\n const derivedBits = await crypto.subtle.deriveBits(\n {\n name: \"PBKDF2\",\n salt: saltBuffer,\n iterations: iterations,\n hash: \"SHA-256\",\n },\n keyMaterial,\n keyLength * 8 // Convert bytes to bits\n );\n\n // Convert to hex string\n const hashArray = Array.from(new Uint8Array(derivedBits));\n return hashArray.map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n } catch (error) {\n throw new CryptoError(\"Failed to stretch key\", error);\n }\n}\n\n/**\n * Hash data with salt using SHA-256\n * Use case: Creating deterministic identifiers\n *\n * @param data - The data to hash\n * @param salt - Salt value for additional entropy\n * @returns Hex-encoded hash\n */\nexport async function hashWithSalt(\n data: string,\n salt: string\n): Promise<string> {\n try {\n const combined = data + salt;\n return await sha256Hash(combined);\n } catch (error) {\n throw new CryptoError(\"Failed to hash with salt\", error);\n }\n}\n\n/**\n * Hash data multiple times (key stretching)\n * Use case: Making brute-force attacks impractical\n *\n * @param data - The data to hash\n * @param salt - Salt value to prevent rainbow table attacks\n * @param iterations - Number of hash iterations\n * @returns Hex-encoded hash after multiple iterations\n */\nexport async function iterativeHash(\n data: string,\n salt: string,\n iterations: number\n): Promise<string> {\n try {\n if (iterations < 1) {\n throw new Error(\"Iterations must be at least 1\");\n }\n\n // Initial hash with salt\n let hash = await hashWithSalt(data, salt);\n\n // Iteratively hash the result\n for (let i = 1; i < iterations; i++) {\n hash = await sha256Hash(hash);\n }\n\n return hash;\n } catch (error) {\n throw new CryptoError(\"Failed to perform iterative hash\", error);\n }\n}\n\n/**\n * Build a merkle tree from leaves\n */\nexport async function buildMerkleTree(leaves: string[]): Promise<{\n root: string;\n tree: string[][];\n}> {\n try {\n // @ts-ignore - Optional dependency, may not be installed\n const circomlibjs = await import(\"circomlibjs\");\n const poseidon = await circomlibjs.buildPoseidon();\n\n if (leaves.length === 0) {\n throw new Error(\"Cannot build tree from empty leaves\");\n }\n\n const tree: string[][] = [leaves];\n let currentLevel = leaves.map((leaf) => BigInt(leaf));\n\n while (currentLevel.length > 1) {\n const nextLevel: bigint[] = [];\n\n for (let i = 0; i < currentLevel.length; i += 2) {\n if (i + 1 < currentLevel.length) {\n const hashResult = poseidon([currentLevel[i], currentLevel[i + 1]]);\n \n // Convert Uint8Array result to BigInt if needed\n const hash = hashResult instanceof Uint8Array \n ? bufferToBigInt(hashResult) \n : hashResult;\n \n nextLevel.push(hash);\n } else {\n nextLevel.push(currentLevel[i]);\n }\n }\n\n tree.push(nextLevel.map((n) => n.toString()));\n currentLevel = nextLevel;\n }\n\n return {\n root: currentLevel[0].toString(),\n tree,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"Cannot resolve module\")) {\n throw new CryptoError(\n \"ZK merkle tree requires circomlibjs. Install with: npm install circomlibjs\\n\" +\n \"For more info: https://github.com/w3hc/w3pk#zero-knowledge-proofs\",\n error\n );\n }\n throw new CryptoError(\"Failed to build merkle tree\", error);\n }\n}\n\n/**\n * Generate merkle proof for a leaf\n */\nexport function generateMerkleProof(\n tree: string[][],\n leafIndex: number\n): {\n pathIndices: number[];\n pathElements: string[];\n} {\n const pathIndices: number[] = [];\n const pathElements: string[] = [];\n let currentIndex = leafIndex;\n\n for (let level = 0; level < tree.length - 1; level++) {\n const isRightNode = currentIndex % 2 === 1;\n const siblingIndex = isRightNode ? currentIndex - 1 : currentIndex + 1;\n\n pathIndices.push(isRightNode ? 1 : 0);\n\n if (siblingIndex < tree[level].length) {\n pathElements.push(tree[level][siblingIndex]);\n } else {\n pathElements.push(tree[level][currentIndex]);\n }\n\n currentIndex = Math.floor(currentIndex / 2);\n }\n\n return { pathIndices, pathElements };\n}\n\n/**\n * Validate proof inputs\n */\nexport function validateProofInputs(inputs: Record<string, any>): void {\n for (const [key, value] of Object.entries(inputs)) {\n if (value === undefined || value === null) {\n throw new CryptoError(`Missing required input: ${key}`);\n }\n }\n}\n\n/**\n * Convert hex string to bigint\n */\nexport function hexToBigInt(hex: string): bigint {\n return BigInt(hex.startsWith(\"0x\") ? hex : `0x${hex}`);\n}\n\n/**\n * Convert bigint to hex string\n */\nexport function bigIntToHex(value: bigint, padToBytes?: number): string {\n let hex = value.toString(16);\n if (padToBytes) {\n hex = hex.padStart(padToBytes * 2, \"0\");\n }\n return `0x${hex}`;\n}\n\n/**\n * Serialize ZK proof for storage or transmission\n */\nexport function serializeProof(proof: any): string {\n return JSON.stringify(proof);\n}\n\n/**\n * Deserialize ZK proof from storage or transmission\n */\nexport function deserializeProof(serialized: string): any {\n try {\n return JSON.parse(serialized);\n } catch (error) {\n throw new CryptoError(\"Failed to deserialize proof\", error);\n }\n}\n\n/**\n * Generate a random nonce for challenges\n */\nexport function generateNonce(): bigint {\n return generateBlinding();\n}\n\n/**\n * Validate Ethereum address format\n */\nexport function isValidAddress(address: string): boolean {\n return /^0x[a-fA-F0-9]{40}$/.test(address);\n}\n\n/**\n * Build NFT holders merkle tree\n * Creates a merkle tree from a list of NFT holder addresses for a specific contract\n */\nexport async function buildNFTHoldersMerkleTree(\n holderAddresses: string[],\n contractAddress: string\n): Promise<{\n root: string;\n tree: string[][];\n holderLeaves: string[];\n}> {\n try {\n // @ts-ignore - Optional dependency, may not be installed\n const circomlibjs = await import(\"circomlibjs\");\n const poseidon = await circomlibjs.buildPoseidon();\n\n if (holderAddresses.length === 0) {\n throw new Error(\"Cannot build NFT holders tree from empty holder list\");\n }\n\n // Create leaf hashes: Hash(holderAddress, contractAddress)\n // Convert hex addresses to BigInt (remove 0x prefix first)\n const contractHash = BigInt(contractAddress.startsWith('0x') ? contractAddress : '0x' + contractAddress);\n const holderLeaves = holderAddresses.map((address) => {\n const cleanAddress = address.startsWith('0x') ? address : '0x' + address;\n const addressHash = BigInt(cleanAddress);\n const hashResult = poseidon([addressHash, contractHash]);\n \n // Convert Uint8Array result to BigInt if needed\n if (hashResult instanceof Uint8Array) {\n return bufferToBigInt(hashResult).toString();\n }\n \n return hashResult.toString();\n });\n\n // Build merkle tree from the leaves\n const { root, tree } = await buildMerkleTree(holderLeaves);\n\n return {\n root,\n tree,\n holderLeaves,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"Cannot resolve module\")) {\n throw new CryptoError(\n \"NFT holder merkle tree requires circomlibjs. Install with: npm install circomlibjs\\n\" +\n \"For more info: https://github.com/w3hc/w3pk#zero-knowledge-proofs\",\n error\n );\n }\n throw new CryptoError(\"Failed to build NFT holders merkle tree\", error);\n }\n}\n\n/**\n * Generate NFT ownership proof inputs\n * Helper to prepare all inputs needed for NFT ownership proof\n */\nexport async function generateNFTOwnershipProofInputs(\n ownerAddress: string,\n contractAddress: string,\n allHolderAddresses: string[],\n minBalance: bigint = 1n\n): Promise<{\n nftProofInput: {\n ownerAddress: string;\n holderIndex: number;\n pathIndices: number[];\n pathElements: string[];\n holdersRoot: string;\n contractAddress: string;\n minBalance: bigint;\n };\n holderLeaves: string[];\n}> {\n // Find the owner's position in the holders list\n const holderIndex = allHolderAddresses.findIndex(\n (address) => address.toLowerCase() === ownerAddress.toLowerCase()\n );\n\n if (holderIndex === -1) {\n throw new CryptoError(\n `Owner address ${ownerAddress} not found in holders list for contract ${contractAddress}`\n );\n }\n\n // Build the NFT holders merkle tree\n const { root, tree, holderLeaves } = await buildNFTHoldersMerkleTree(\n allHolderAddresses,\n contractAddress\n );\n\n // Generate merkle proof for this owner\n const { pathIndices, pathElements } = generateMerkleProof(tree, holderIndex);\n\n return {\n nftProofInput: {\n ownerAddress,\n holderIndex,\n pathIndices,\n pathElements,\n holdersRoot: root,\n contractAddress,\n minBalance,\n },\n holderLeaves,\n };\n}\n\n/**\n * Validate NFT ownership proof inputs\n */\nexport function validateNFTOwnershipProofInputs(inputs: {\n ownerAddress: string;\n contractAddress: string;\n holderIndex: number;\n pathIndices: number[];\n pathElements: string[];\n holdersRoot: string;\n minBalance?: bigint;\n}): void {\n if (!isValidAddress(inputs.ownerAddress)) {\n throw new CryptoError(`Invalid owner address: ${inputs.ownerAddress}`);\n }\n\n if (!isValidAddress(inputs.contractAddress)) {\n throw new CryptoError(`Invalid contract address: ${inputs.contractAddress}`);\n }\n\n if (inputs.holderIndex < 0 || !Number.isInteger(inputs.holderIndex)) {\n throw new CryptoError(`Invalid holder index: ${inputs.holderIndex}`);\n }\n\n if (inputs.pathIndices.length !== inputs.pathElements.length) {\n throw new CryptoError(\n `Path indices and elements length mismatch: ${inputs.pathIndices.length} vs ${inputs.pathElements.length}`\n );\n }\n\n if (inputs.pathIndices.length === 0) {\n throw new CryptoError(\"Empty merkle proof path\");\n }\n\n if (!inputs.holdersRoot || inputs.holdersRoot.length === 0) {\n throw new CryptoError(\"Invalid holders root\");\n }\n\n if (inputs.minBalance && inputs.minBalance < 1n) {\n throw new CryptoError(\"Minimum balance must be at least 1\");\n }\n}\n"],"mappings":"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","names":["Web3PasskeyError","message","code","originalError","CryptoError","Web3PasskeyError","message","originalError","generateBlinding","buffer","bufferToBigInt","result","i","bigIntToBuffer","value","byteLength","temp","sha256Hash","data","hashBuffer","b","error","CryptoError","stretchKey","input","salt","iterations","keyLength","encoder","inputBuffer","saltBuffer","keyMaterial","derivedBits","hashWithSalt","combined","iterativeHash","hash","buildMerkleTree","leaves","poseidon","tree","currentLevel","leaf","nextLevel","hashResult","n","generateMerkleProof","leafIndex","pathIndices","pathElements","currentIndex","level","isRightNode","siblingIndex","validateProofInputs","inputs","key","hexToBigInt","hex","bigIntToHex","padToBytes","serializeProof","proof","deserializeProof","serialized","generateNonce","isValidAddress","address","buildNFTHoldersMerkleTree","holderAddresses","contractAddress","contractHash","holderLeaves","cleanAddress","addressHash","root","generateNFTOwnershipProofInputs","ownerAddress","allHolderAddresses","minBalance","holderIndex","validateNFTOwnershipProofInputs"]}
1
+ {"version":3,"sources":["../../src/core/errors.ts","../../src/zk/utils.ts"],"sourcesContent":["/**\n * Custom error classes for better error handling\n */\n\nexport class Web3PasskeyError extends Error {\n constructor(\n message: string,\n public code: string,\n public originalError?: unknown\n ) {\n super(message);\n this.name = \"Web3PasskeyError\";\n }\n}\n\nexport class AuthenticationError extends Web3PasskeyError {\n constructor(message: string, originalError?: unknown) {\n super(message, \"AUTHENTICATION_ERROR\", originalError);\n this.name = \"AuthenticationError\";\n }\n}\n\nexport class RegistrationError extends Web3PasskeyError {\n constructor(message: string, originalError?: unknown) {\n super(message, \"REGISTRATION_ERROR\", originalError);\n this.name = \"RegistrationError\";\n }\n}\n\nexport class WalletError extends Web3PasskeyError {\n constructor(message: string, originalError?: unknown) {\n super(message, \"WALLET_ERROR\", originalError);\n this.name = \"WalletError\";\n }\n}\n\nexport class CryptoError extends Web3PasskeyError {\n constructor(message: string, originalError?: unknown) {\n super(message, \"CRYPTO_ERROR\", originalError);\n this.name = \"CryptoError\";\n }\n}\n\nexport class StorageError extends Web3PasskeyError {\n constructor(message: string, originalError?: unknown) {\n super(message, \"STORAGE_ERROR\", originalError);\n this.name = \"StorageError\";\n }\n}\n\nexport class ApiError extends Web3PasskeyError {\n constructor(\n message: string,\n public statusCode?: number,\n originalError?: unknown\n ) {\n super(message, \"API_ERROR\", originalError);\n this.name = \"ApiError\";\n }\n}\n\n/**\n * The passkey authenticated, but this device has no credential metadata.\n * The app should start a sync or restore flow.\n */\nexport class SyncRequiredError extends Web3PasskeyError {\n constructor(public credentialId: string) {\n super(\n \"Passkey authenticated but this device has no data for it. Sync this device or restore from a backup.\",\n \"SYNC_REQUIRED\"\n );\n this.name = \"SyncRequiredError\";\n }\n}\n\n/**\n * The credential metadata is on this device, but the wallet record is gone.\n * The app should offer a restore from backup.\n */\nexport class WalletMissingError extends Web3PasskeyError {\n constructor(\n public credentialId: string,\n public ethereumAddress: string\n ) {\n super(\n \"Passkey authenticated but no wallet was found on this device. Restore it from a backup.\",\n \"WALLET_MISSING\"\n );\n this.name = \"WalletMissingError\";\n }\n}\n","/**\n * ZK Utility Functions\n * Helper functions for zero-knowledge proof operations\n */\n\nimport { CryptoError } from \"../core/errors\";\n\n/**\n * Generate a random blinding factor for commitments\n */\nexport function generateBlinding(): bigint {\n const buffer = new Uint8Array(32);\n crypto.getRandomValues(buffer);\n return bufferToBigInt(buffer);\n}\n\n/**\n * Convert buffer to bigint\n */\nexport function bufferToBigInt(buffer: Uint8Array): bigint {\n let result = 0n;\n for (let i = 0; i < buffer.length; i++) {\n result = (result << 8n) | BigInt(buffer[i]);\n }\n return result;\n}\n\n/**\n * Convert bigint to buffer\n */\nexport function bigIntToBuffer(\n value: bigint,\n byteLength: number = 32\n): Uint8Array {\n const buffer = new Uint8Array(byteLength);\n let temp = value;\n\n for (let i = byteLength - 1; i >= 0; i--) {\n buffer[i] = Number(temp & 0xffn);\n temp = temp >> 8n;\n }\n\n return buffer;\n}\n\n/**\n * Hash a value using SHA-256\n */\nexport async function sha256Hash(data: string | Uint8Array): Promise<string> {\n try {\n const buffer =\n typeof data === \"string\" ? new TextEncoder().encode(data) : data;\n\n const hashBuffer = await crypto.subtle.digest(\n \"SHA-256\",\n buffer as BufferSource\n );\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n return hashArray.map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n } catch (error) {\n throw new CryptoError(\"Failed to compute hash\", error);\n }\n}\n\n/**\n * Stretch a key using PBKDF2\n * Use case: Password hashing, deterministic key derivation\n *\n * @param input - The input string to stretch (e.g., password)\n * @param salt - Salt value to prevent rainbow table attacks\n * @param iterations - Number of iterations (default: 10000)\n * @param keyLength - Output key length in bytes (default: 32)\n * @returns Hex-encoded stretched key\n */\nexport async function stretchKey(\n input: string,\n salt: string,\n iterations: number = 10000,\n keyLength: number = 32\n): Promise<string> {\n try {\n const encoder = new TextEncoder();\n const inputBuffer = encoder.encode(input);\n const saltBuffer = encoder.encode(salt);\n\n // Import the password as a key for PBKDF2\n const keyMaterial = await crypto.subtle.importKey(\n \"raw\",\n inputBuffer,\n \"PBKDF2\",\n false,\n [\"deriveBits\"]\n );\n\n // Derive bits using PBKDF2\n const derivedBits = await crypto.subtle.deriveBits(\n {\n name: \"PBKDF2\",\n salt: saltBuffer,\n iterations: iterations,\n hash: \"SHA-256\",\n },\n keyMaterial,\n keyLength * 8 // Convert bytes to bits\n );\n\n // Convert to hex string\n const hashArray = Array.from(new Uint8Array(derivedBits));\n return hashArray.map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n } catch (error) {\n throw new CryptoError(\"Failed to stretch key\", error);\n }\n}\n\n/**\n * Hash data with salt using SHA-256\n * Use case: Creating deterministic identifiers\n *\n * @param data - The data to hash\n * @param salt - Salt value for additional entropy\n * @returns Hex-encoded hash\n */\nexport async function hashWithSalt(\n data: string,\n salt: string\n): Promise<string> {\n try {\n const combined = data + salt;\n return await sha256Hash(combined);\n } catch (error) {\n throw new CryptoError(\"Failed to hash with salt\", error);\n }\n}\n\n/**\n * Hash data multiple times (key stretching)\n * Use case: Making brute-force attacks impractical\n *\n * @param data - The data to hash\n * @param salt - Salt value to prevent rainbow table attacks\n * @param iterations - Number of hash iterations\n * @returns Hex-encoded hash after multiple iterations\n */\nexport async function iterativeHash(\n data: string,\n salt: string,\n iterations: number\n): Promise<string> {\n try {\n if (iterations < 1) {\n throw new Error(\"Iterations must be at least 1\");\n }\n\n // Initial hash with salt\n let hash = await hashWithSalt(data, salt);\n\n // Iteratively hash the result\n for (let i = 1; i < iterations; i++) {\n hash = await sha256Hash(hash);\n }\n\n return hash;\n } catch (error) {\n throw new CryptoError(\"Failed to perform iterative hash\", error);\n }\n}\n\n/**\n * Build a merkle tree from leaves\n */\nexport async function buildMerkleTree(leaves: string[]): Promise<{\n root: string;\n tree: string[][];\n}> {\n try {\n // @ts-ignore - Optional dependency, may not be installed\n const circomlibjs = await import(\"circomlibjs\");\n const poseidon = await circomlibjs.buildPoseidon();\n\n if (leaves.length === 0) {\n throw new Error(\"Cannot build tree from empty leaves\");\n }\n\n const tree: string[][] = [leaves];\n let currentLevel = leaves.map((leaf) => BigInt(leaf));\n\n while (currentLevel.length > 1) {\n const nextLevel: bigint[] = [];\n\n for (let i = 0; i < currentLevel.length; i += 2) {\n if (i + 1 < currentLevel.length) {\n const hashResult = poseidon([currentLevel[i], currentLevel[i + 1]]);\n \n // Convert Uint8Array result to BigInt if needed\n const hash = hashResult instanceof Uint8Array \n ? bufferToBigInt(hashResult) \n : hashResult;\n \n nextLevel.push(hash);\n } else {\n nextLevel.push(currentLevel[i]);\n }\n }\n\n tree.push(nextLevel.map((n) => n.toString()));\n currentLevel = nextLevel;\n }\n\n return {\n root: currentLevel[0].toString(),\n tree,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"Cannot resolve module\")) {\n throw new CryptoError(\n \"ZK merkle tree requires circomlibjs. Install with: npm install circomlibjs\\n\" +\n \"For more info: https://github.com/w3hc/w3pk#zero-knowledge-proofs\",\n error\n );\n }\n throw new CryptoError(\"Failed to build merkle tree\", error);\n }\n}\n\n/**\n * Generate merkle proof for a leaf\n */\nexport function generateMerkleProof(\n tree: string[][],\n leafIndex: number\n): {\n pathIndices: number[];\n pathElements: string[];\n} {\n const pathIndices: number[] = [];\n const pathElements: string[] = [];\n let currentIndex = leafIndex;\n\n for (let level = 0; level < tree.length - 1; level++) {\n const isRightNode = currentIndex % 2 === 1;\n const siblingIndex = isRightNode ? currentIndex - 1 : currentIndex + 1;\n\n pathIndices.push(isRightNode ? 1 : 0);\n\n if (siblingIndex < tree[level].length) {\n pathElements.push(tree[level][siblingIndex]);\n } else {\n pathElements.push(tree[level][currentIndex]);\n }\n\n currentIndex = Math.floor(currentIndex / 2);\n }\n\n return { pathIndices, pathElements };\n}\n\n/**\n * Validate proof inputs\n */\nexport function validateProofInputs(inputs: Record<string, any>): void {\n for (const [key, value] of Object.entries(inputs)) {\n if (value === undefined || value === null) {\n throw new CryptoError(`Missing required input: ${key}`);\n }\n }\n}\n\n/**\n * Convert hex string to bigint\n */\nexport function hexToBigInt(hex: string): bigint {\n return BigInt(hex.startsWith(\"0x\") ? hex : `0x${hex}`);\n}\n\n/**\n * Convert bigint to hex string\n */\nexport function bigIntToHex(value: bigint, padToBytes?: number): string {\n let hex = value.toString(16);\n if (padToBytes) {\n hex = hex.padStart(padToBytes * 2, \"0\");\n }\n return `0x${hex}`;\n}\n\n/**\n * Serialize ZK proof for storage or transmission\n */\nexport function serializeProof(proof: any): string {\n return JSON.stringify(proof);\n}\n\n/**\n * Deserialize ZK proof from storage or transmission\n */\nexport function deserializeProof(serialized: string): any {\n try {\n return JSON.parse(serialized);\n } catch (error) {\n throw new CryptoError(\"Failed to deserialize proof\", error);\n }\n}\n\n/**\n * Generate a random nonce for challenges\n */\nexport function generateNonce(): bigint {\n return generateBlinding();\n}\n\n/**\n * Validate Ethereum address format\n */\nexport function isValidAddress(address: string): boolean {\n return /^0x[a-fA-F0-9]{40}$/.test(address);\n}\n\n/**\n * Build NFT holders merkle tree\n * Creates a merkle tree from a list of NFT holder addresses for a specific contract\n */\nexport async function buildNFTHoldersMerkleTree(\n holderAddresses: string[],\n contractAddress: string\n): Promise<{\n root: string;\n tree: string[][];\n holderLeaves: string[];\n}> {\n try {\n // @ts-ignore - Optional dependency, may not be installed\n const circomlibjs = await import(\"circomlibjs\");\n const poseidon = await circomlibjs.buildPoseidon();\n\n if (holderAddresses.length === 0) {\n throw new Error(\"Cannot build NFT holders tree from empty holder list\");\n }\n\n // Create leaf hashes: Hash(holderAddress, contractAddress)\n // Convert hex addresses to BigInt (remove 0x prefix first)\n const contractHash = BigInt(contractAddress.startsWith('0x') ? contractAddress : '0x' + contractAddress);\n const holderLeaves = holderAddresses.map((address) => {\n const cleanAddress = address.startsWith('0x') ? address : '0x' + address;\n const addressHash = BigInt(cleanAddress);\n const hashResult = poseidon([addressHash, contractHash]);\n \n // Convert Uint8Array result to BigInt if needed\n if (hashResult instanceof Uint8Array) {\n return bufferToBigInt(hashResult).toString();\n }\n \n return hashResult.toString();\n });\n\n // Build merkle tree from the leaves\n const { root, tree } = await buildMerkleTree(holderLeaves);\n\n return {\n root,\n tree,\n holderLeaves,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"Cannot resolve module\")) {\n throw new CryptoError(\n \"NFT holder merkle tree requires circomlibjs. Install with: npm install circomlibjs\\n\" +\n \"For more info: https://github.com/w3hc/w3pk#zero-knowledge-proofs\",\n error\n );\n }\n throw new CryptoError(\"Failed to build NFT holders merkle tree\", error);\n }\n}\n\n/**\n * Generate NFT ownership proof inputs\n * Helper to prepare all inputs needed for NFT ownership proof\n */\nexport async function generateNFTOwnershipProofInputs(\n ownerAddress: string,\n contractAddress: string,\n allHolderAddresses: string[],\n minBalance: bigint = 1n\n): Promise<{\n nftProofInput: {\n ownerAddress: string;\n holderIndex: number;\n pathIndices: number[];\n pathElements: string[];\n holdersRoot: string;\n contractAddress: string;\n minBalance: bigint;\n };\n holderLeaves: string[];\n}> {\n // Find the owner's position in the holders list\n const holderIndex = allHolderAddresses.findIndex(\n (address) => address.toLowerCase() === ownerAddress.toLowerCase()\n );\n\n if (holderIndex === -1) {\n throw new CryptoError(\n `Owner address ${ownerAddress} not found in holders list for contract ${contractAddress}`\n );\n }\n\n // Build the NFT holders merkle tree\n const { root, tree, holderLeaves } = await buildNFTHoldersMerkleTree(\n allHolderAddresses,\n contractAddress\n );\n\n // Generate merkle proof for this owner\n const { pathIndices, pathElements } = generateMerkleProof(tree, holderIndex);\n\n return {\n nftProofInput: {\n ownerAddress,\n holderIndex,\n pathIndices,\n pathElements,\n holdersRoot: root,\n contractAddress,\n minBalance,\n },\n holderLeaves,\n };\n}\n\n/**\n * Validate NFT ownership proof inputs\n */\nexport function validateNFTOwnershipProofInputs(inputs: {\n ownerAddress: string;\n contractAddress: string;\n holderIndex: number;\n pathIndices: number[];\n pathElements: string[];\n holdersRoot: string;\n minBalance?: bigint;\n}): void {\n if (!isValidAddress(inputs.ownerAddress)) {\n throw new CryptoError(`Invalid owner address: ${inputs.ownerAddress}`);\n }\n\n if (!isValidAddress(inputs.contractAddress)) {\n throw new CryptoError(`Invalid contract address: ${inputs.contractAddress}`);\n }\n\n if (inputs.holderIndex < 0 || !Number.isInteger(inputs.holderIndex)) {\n throw new CryptoError(`Invalid holder index: ${inputs.holderIndex}`);\n }\n\n if (inputs.pathIndices.length !== inputs.pathElements.length) {\n throw new CryptoError(\n `Path indices and elements length mismatch: ${inputs.pathIndices.length} vs ${inputs.pathElements.length}`\n );\n }\n\n if (inputs.pathIndices.length === 0) {\n throw new CryptoError(\"Empty merkle proof path\");\n }\n\n if (!inputs.holdersRoot || inputs.holdersRoot.length === 0) {\n throw new CryptoError(\"Invalid holders root\");\n }\n\n if (inputs.minBalance && inputs.minBalance < 1n) {\n throw new CryptoError(\"Minimum balance must be at least 1\");\n }\n}\n"],"mappings":"AAIO,IAAMA,EAAN,cAA+B,KAAM,CAC1C,YACEC,EACOC,EACAC,EACP,CACA,MAAMF,CAAO,EAHN,UAAAC,EACA,mBAAAC,EAGP,KAAK,KAAO,kBACd,CACF,EAuBO,IAAMC,EAAN,cAA0BC,CAAiB,CAChD,YAAYC,EAAiBC,EAAyB,CACpD,MAAMD,EAAS,eAAgBC,CAAa,EAC5C,KAAK,KAAO,aACd,CACF,EC/BO,SAASC,GAA2B,CACzC,IAAMC,EAAS,IAAI,WAAW,EAAE,EAChC,cAAO,gBAAgBA,CAAM,EACtBC,EAAeD,CAAM,CAC9B,CAKO,SAASC,EAAeD,EAA4B,CACzD,IAAIE,EAAS,GACb,QAASC,EAAI,EAAGA,EAAIH,EAAO,OAAQG,IACjCD,EAAUA,GAAU,GAAM,OAAOF,EAAOG,CAAC,CAAC,EAE5C,OAAOD,CACT,CAKO,SAASE,EACdC,EACAC,EAAqB,GACT,CACZ,IAAMN,EAAS,IAAI,WAAWM,CAAU,EACpCC,EAAOF,EAEX,QAASF,EAAIG,EAAa,EAAGH,GAAK,EAAGA,IACnCH,EAAOG,CAAC,EAAI,OAAOI,EAAO,KAAK,EAC/BA,EAAOA,GAAQ,GAGjB,OAAOP,CACT,CAKA,eAAsBQ,EAAWC,EAA4C,CAC3E,GAAI,CACF,IAAMT,EACJ,OAAOS,GAAS,SAAW,IAAI,YAAY,EAAE,OAAOA,CAAI,EAAIA,EAExDC,EAAa,MAAM,OAAO,OAAO,OACrC,UACAV,CACF,EAEA,OADkB,MAAM,KAAK,IAAI,WAAWU,CAAU,CAAC,EACtC,IAAKC,GAAMA,EAAE,SAAS,EAAE,EAAE,SAAS,EAAG,GAAG,CAAC,EAAE,KAAK,EAAE,CACtE,OAASC,EAAO,CACd,MAAM,IAAIC,EAAY,yBAA0BD,CAAK,CACvD,CACF,CAYA,eAAsBE,EACpBC,EACAC,EACAC,EAAqB,IACrBC,EAAoB,GACH,CACjB,GAAI,CACF,IAAMC,EAAU,IAAI,YACdC,EAAcD,EAAQ,OAAOJ,CAAK,EAClCM,EAAaF,EAAQ,OAAOH,CAAI,EAGhCM,EAAc,MAAM,OAAO,OAAO,UACtC,MACAF,EACA,SACA,GACA,CAAC,YAAY,CACf,EAGMG,EAAc,MAAM,OAAO,OAAO,WACtC,CACE,KAAM,SACN,KAAMF,EACN,WAAYJ,EACZ,KAAM,SACR,EACAK,EACAJ,EAAY,CACd,EAIA,OADkB,MAAM,KAAK,IAAI,WAAWK,CAAW,CAAC,EACvC,IAAKZ,GAAMA,EAAE,SAAS,EAAE,EAAE,SAAS,EAAG,GAAG,CAAC,EAAE,KAAK,EAAE,CACtE,OAASC,EAAO,CACd,MAAM,IAAIC,EAAY,wBAAyBD,CAAK,CACtD,CACF,CAUA,eAAsBY,EACpBf,EACAO,EACiB,CACjB,GAAI,CACF,IAAMS,EAAWhB,EAAOO,EACxB,OAAO,MAAMR,EAAWiB,CAAQ,CAClC,OAASb,EAAO,CACd,MAAM,IAAIC,EAAY,2BAA4BD,CAAK,CACzD,CACF,CAWA,eAAsBc,EACpBjB,EACAO,EACAC,EACiB,CACjB,GAAI,CACF,GAAIA,EAAa,EACf,MAAM,IAAI,MAAM,+BAA+B,EAIjD,IAAIU,EAAO,MAAMH,EAAaf,EAAMO,CAAI,EAGxC,QAASb,EAAI,EAAGA,EAAIc,EAAYd,IAC9BwB,EAAO,MAAMnB,EAAWmB,CAAI,EAG9B,OAAOA,CACT,OAASf,EAAO,CACd,MAAM,IAAIC,EAAY,mCAAoCD,CAAK,CACjE,CACF,CAKA,eAAsBgB,EAAgBC,EAGnC,CACD,GAAI,CAGF,IAAMC,EAAW,MADG,KAAM,QAAO,aAAa,GACX,cAAc,EAEjD,GAAID,EAAO,SAAW,EACpB,MAAM,IAAI,MAAM,qCAAqC,EAGvD,IAAME,EAAmB,CAACF,CAAM,EAC5BG,EAAeH,EAAO,IAAKI,GAAS,OAAOA,CAAI,CAAC,EAEpD,KAAOD,EAAa,OAAS,GAAG,CAC9B,IAAME,EAAsB,CAAC,EAE7B,QAAS/B,EAAI,EAAGA,EAAI6B,EAAa,OAAQ7B,GAAK,EAC5C,GAAIA,EAAI,EAAI6B,EAAa,OAAQ,CAC/B,IAAMG,EAAaL,EAAS,CAACE,EAAa7B,CAAC,EAAG6B,EAAa7B,EAAI,CAAC,CAAC,CAAC,EAG5DwB,EAAOQ,aAAsB,WAC/BlC,EAAekC,CAAU,EACzBA,EAEJD,EAAU,KAAKP,CAAI,CACrB,MACEO,EAAU,KAAKF,EAAa7B,CAAC,CAAC,EAIlC4B,EAAK,KAAKG,EAAU,IAAKE,GAAMA,EAAE,SAAS,CAAC,CAAC,EAC5CJ,EAAeE,CACjB,CAEA,MAAO,CACL,KAAMF,EAAa,CAAC,EAAE,SAAS,EAC/B,KAAAD,CACF,CACF,OAASnB,EAAO,CACd,MAAIA,aAAiB,OAASA,EAAM,QAAQ,SAAS,uBAAuB,EACpE,IAAIC,EACR;AAAA,mEAEAD,CACF,EAEI,IAAIC,EAAY,8BAA+BD,CAAK,CAC5D,CACF,CAKO,SAASyB,EACdN,EACAO,EAIA,CACA,IAAMC,EAAwB,CAAC,EACzBC,EAAyB,CAAC,EAC5BC,EAAeH,EAEnB,QAASI,EAAQ,EAAGA,EAAQX,EAAK,OAAS,EAAGW,IAAS,CACpD,IAAMC,EAAcF,EAAe,IAAM,EACnCG,EAAeD,EAAcF,EAAe,EAAIA,EAAe,EAErEF,EAAY,KAAKI,EAAc,EAAI,CAAC,EAEhCC,EAAeb,EAAKW,CAAK,EAAE,OAC7BF,EAAa,KAAKT,EAAKW,CAAK,EAAEE,CAAY,CAAC,EAE3CJ,EAAa,KAAKT,EAAKW,CAAK,EAAED,CAAY,CAAC,EAG7CA,EAAe,KAAK,MAAMA,EAAe,CAAC,CAC5C,CAEA,MAAO,CAAE,YAAAF,EAAa,aAAAC,CAAa,CACrC,CAKO,SAASK,EAAoBC,EAAmC,CACrE,OAAW,CAACC,EAAK1C,CAAK,IAAK,OAAO,QAAQyC,CAAM,EAC9C,GAA2BzC,GAAU,KACnC,MAAM,IAAIQ,EAAY,2BAA2BkC,CAAG,EAAE,CAG5D,CAKO,SAASC,EAAYC,EAAqB,CAC/C,OAAO,OAAOA,EAAI,WAAW,IAAI,EAAIA,EAAM,KAAKA,CAAG,EAAE,CACvD,CAKO,SAASC,EAAY7C,EAAe8C,EAA6B,CACtE,IAAIF,EAAM5C,EAAM,SAAS,EAAE,EAC3B,OAAI8C,IACFF,EAAMA,EAAI,SAASE,EAAa,EAAG,GAAG,GAEjC,KAAKF,CAAG,EACjB,CAKO,SAASG,EAAeC,EAAoB,CACjD,OAAO,KAAK,UAAUA,CAAK,CAC7B,CAKO,SAASC,EAAiBC,EAAyB,CACxD,GAAI,CACF,OAAO,KAAK,MAAMA,CAAU,CAC9B,OAAS3C,EAAO,CACd,MAAM,IAAIC,EAAY,8BAA+BD,CAAK,CAC5D,CACF,CAKO,SAAS4C,GAAwB,CACtC,OAAOzD,EAAiB,CAC1B,CAKO,SAAS0D,EAAeC,EAA0B,CACvD,MAAO,sBAAsB,KAAKA,CAAO,CAC3C,CAMA,eAAsBC,EACpBC,EACAC,EAKC,CACD,GAAI,CAGF,IAAM/B,EAAW,MADG,KAAM,QAAO,aAAa,GACX,cAAc,EAEjD,GAAI8B,EAAgB,SAAW,EAC7B,MAAM,IAAI,MAAM,sDAAsD,EAKxE,IAAME,EAAe,OAAOD,EAAgB,WAAW,IAAI,EAAIA,EAAkB,KAAOA,CAAe,EACjGE,EAAeH,EAAgB,IAAKF,GAAY,CACpD,IAAMM,EAAeN,EAAQ,WAAW,IAAI,EAAIA,EAAU,KAAOA,EAC3DO,EAAc,OAAOD,CAAY,EACjC7B,EAAaL,EAAS,CAACmC,EAAaH,CAAY,CAAC,EAGvD,OAAI3B,aAAsB,WACjBlC,EAAekC,CAAU,EAAE,SAAS,EAGtCA,EAAW,SAAS,CAC7B,CAAC,EAGK,CAAE,KAAA+B,EAAM,KAAAnC,CAAK,EAAI,MAAMH,EAAgBmC,CAAY,EAEzD,MAAO,CACL,KAAAG,EACA,KAAAnC,EACA,aAAAgC,CACF,CACF,OAASnD,EAAO,CACd,MAAIA,aAAiB,OAASA,EAAM,QAAQ,SAAS,uBAAuB,EACpE,IAAIC,EACR;AAAA,mEAEAD,CACF,EAEI,IAAIC,EAAY,0CAA2CD,CAAK,CACxE,CACF,CAMA,eAAsBuD,EACpBC,EACAP,EACAQ,EACAC,EAAqB,GAYpB,CAED,IAAMC,EAAcF,EAAmB,UACpCX,GAAYA,EAAQ,YAAY,IAAMU,EAAa,YAAY,CAClE,EAEA,GAAIG,IAAgB,GAClB,MAAM,IAAI1D,EACR,iBAAiBuD,CAAY,2CAA2CP,CAAe,EACzF,EAIF,GAAM,CAAE,KAAAK,EAAM,KAAAnC,EAAM,aAAAgC,CAAa,EAAI,MAAMJ,EACzCU,EACAR,CACF,EAGM,CAAE,YAAAtB,EAAa,aAAAC,CAAa,EAAIH,EAAoBN,EAAMwC,CAAW,EAE3E,MAAO,CACL,cAAe,CACb,aAAAH,EACA,YAAAG,EACA,YAAAhC,EACA,aAAAC,EACA,YAAa0B,EACb,gBAAAL,EACA,WAAAS,CACF,EACA,aAAAP,CACF,CACF,CAKO,SAASS,EAAgC1B,EAQvC,CACP,GAAI,CAACW,EAAeX,EAAO,YAAY,EACrC,MAAM,IAAIjC,EAAY,0BAA0BiC,EAAO,YAAY,EAAE,EAGvE,GAAI,CAACW,EAAeX,EAAO,eAAe,EACxC,MAAM,IAAIjC,EAAY,6BAA6BiC,EAAO,eAAe,EAAE,EAG7E,GAAIA,EAAO,YAAc,GAAK,CAAC,OAAO,UAAUA,EAAO,WAAW,EAChE,MAAM,IAAIjC,EAAY,yBAAyBiC,EAAO,WAAW,EAAE,EAGrE,GAAIA,EAAO,YAAY,SAAWA,EAAO,aAAa,OACpD,MAAM,IAAIjC,EACR,8CAA8CiC,EAAO,YAAY,MAAM,OAAOA,EAAO,aAAa,MAAM,EAC1G,EAGF,GAAIA,EAAO,YAAY,SAAW,EAChC,MAAM,IAAIjC,EAAY,yBAAyB,EAGjD,GAAI,CAACiC,EAAO,aAAeA,EAAO,YAAY,SAAW,EACvD,MAAM,IAAIjC,EAAY,sBAAsB,EAG9C,GAAIiC,EAAO,YAAcA,EAAO,WAAa,GAC3C,MAAM,IAAIjC,EAAY,oCAAoC,CAE9D","names":["Web3PasskeyError","message","code","originalError","CryptoError","Web3PasskeyError","message","originalError","generateBlinding","buffer","bufferToBigInt","result","i","bigIntToBuffer","value","byteLength","temp","sha256Hash","data","hashBuffer","b","error","CryptoError","stretchKey","input","salt","iterations","keyLength","encoder","inputBuffer","saltBuffer","keyMaterial","derivedBits","hashWithSalt","combined","iterativeHash","hash","buildMerkleTree","leaves","poseidon","tree","currentLevel","leaf","nextLevel","hashResult","n","generateMerkleProof","leafIndex","pathIndices","pathElements","currentIndex","level","isRightNode","siblingIndex","validateProofInputs","inputs","key","hexToBigInt","hex","bigIntToHex","padToBytes","serializeProof","proof","deserializeProof","serialized","generateNonce","isValidAddress","address","buildNFTHoldersMerkleTree","holderAddresses","contractAddress","contractHash","holderLeaves","cleanAddress","addressHash","root","generateNFTOwnershipProofInputs","ownerAddress","allHolderAddresses","minBalance","holderIndex","validateNFTOwnershipProofInputs"]}
@@ -43,7 +43,7 @@ interface Web3PasskeyConfig {
43
43
  debug?: boolean; // Enable debug mode (default: false)
44
44
  onError?: (error: Web3PasskeyError) => void; // Global error handler
45
45
  onAuthStateChanged?: (isAuthenticated: boolean, user?: UserInfo) => void; // Auth state callback
46
- storage?: Storage; // Custom storage backend (default: IndexedDB)
46
+ storage?: Storage; // Custom storage backend (default: localStorage)
47
47
  sessionDuration?: number; // Session duration in hours (default: 1)
48
48
  persistentSession?: Partial<PersistentSessionConfig>; // Enable "Remember Me" functionality
49
49
  stealthAddresses?: StealthAddressConfig; // Enable stealth addresses
@@ -57,7 +57,7 @@ interface PersistentSessionConfig {
57
57
  }
58
58
  ```
59
59
 
60
- **PRF requirement:** persistent sessions are encrypted under a key derived from the WebAuthn PRF extension, which the authenticator only releases during a user-verified assertion. On authenticators without PRF support, no persistent session is stored — the device falls back to in-memory sessions only (there is no weaker at-rest fallback). The session is re-keyed at every real (prompted) login; the configured `duration` is therefore also the renewal interval. With `requireReauth: true` the decryption key is never written to disk; with `requireReauth: false` it is stored as a non-extractable `CryptoKey` to allow silent restore. See [SECURITY.md](./SECURITY.md#at-rest-encryption-what-it-protects-and-what-it-doesnt) for the full model.
60
+ **PRF requirement:** persistent sessions are encrypted under a key derived from the WebAuthn PRF extension, which the authenticator only releases during a user-verified assertion. On authenticators without PRF support, no persistent session is stored — the device falls back to in-memory sessions only (there is no weaker at-rest fallback). The session is re-keyed at every real (prompted) login; the configured `duration` is therefore also the renewal interval. With `requireReauth: true` the decryption key is never written to disk; with `requireReauth: false` it is stored next to the blob in localStorage to allow silent restore. See [SECURITY.md](./SECURITY.md#at-rest-encryption-what-it-protects-and-what-it-doesnt) for the full model.
61
61
 
62
62
  **Example:**
63
63
 
@@ -114,6 +114,7 @@ Register a new user with WebAuthn passkey authentication.
114
114
  interface RegisterResult {
115
115
  address: string; // Ethereum address (account #0 derived from BIP44)
116
116
  username: string; // Registered username
117
+ hasBackup: boolean; // false for a new wallet: prompt for a backup now
117
118
  }
118
119
  ```
119
120
 
@@ -122,17 +123,21 @@ interface RegisterResult {
122
123
  2. Creates WebAuthn credential (triggers biometric prompt)
123
124
  3. Derives Ethereum wallet from mnemonic (path: m/44'/60'/0'/0/0)
124
125
  4. Encrypts mnemonic using WebAuthn credential (AES-GCM-256)
125
- 5. Stores encrypted wallet in IndexedDB
126
+ 5. Stores encrypted wallet in localStorage, next to the credential metadata
126
127
  6. Starts 1-hour session with decrypted mnemonic in memory
127
- 7. Returns Ethereum address and username
128
+ 7. Returns Ethereum address, username and whether a backup exists
128
129
 
129
130
  **Example:**
130
131
 
131
132
  ```typescript
132
- const { address, username } = await w3pk.register({
133
+ const { address, username, hasBackup } = await w3pk.register({
133
134
  username: 'alice'
134
135
  })
135
136
  console.log('Registered:', username, 'with address:', address)
137
+
138
+ if (!hasBackup) {
139
+ showBackupPrompt() // see getBackupStatus()
140
+ }
136
141
  ```
137
142
 
138
143
  ---
@@ -153,14 +158,20 @@ interface UserInfo {
153
158
  ```
154
159
 
155
160
  **What happens:**
156
- 1. Retrieves credential ID from IndexedDB
161
+ 1. Retrieves credential ID from localStorage
157
162
  2. Triggers WebAuthn authentication (biometric prompt)
158
- 3. Retrieves encrypted wallet from IndexedDB
163
+ 3. Retrieves encrypted wallet from localStorage
159
164
  4. Decrypts mnemonic using WebAuthn signature
160
165
  5. Starts new session (1 hour by default)
161
166
  6. Returns user info
162
167
  7. Subsequent operations use cached session - no repeated biometric prompts
163
168
 
169
+ **Throws:**
170
+ - `SyncRequiredError` if the passkey authenticated but this device has no credential metadata for it (carries `credentialId`); start a sync with `syncWalletWithPasskey()`
171
+ - `WalletMissingError` if the credential metadata is here but the wallet record is gone (carries `credentialId` and `ethereumAddress`); offer a restore from backup
172
+
173
+ In both cases the user is not logged in and `onAuthStateChanged(true, …)` is not called.
174
+
164
175
  **Example:**
165
176
 
166
177
  ```typescript
@@ -218,6 +229,23 @@ if (currentUser) {
218
229
 
219
230
  ---
220
231
 
232
+ ### `storagePersisted: boolean | null` (property)
233
+
234
+ Whether the browser granted persistent storage. w3pk calls `navigator.storage.persist()` after each register, login and restore without waiting for it. `true` means the origin's storage is exempt from storage-pressure eviction. `null` until the browser answers (Firefox asks the user), or where the API is missing.
235
+
236
+ Persisted storage still doesn't survive Safari's 7-day cap, clearing site data or private browsing: see [SECURITY.md → Storage durability](./SECURITY.md#storage-durability-what-can-still-wipe-the-wallet).
237
+
238
+ **Example:**
239
+
240
+ ```typescript
241
+ await w3pk.login()
242
+ if (w3pk.storagePersisted === false) {
243
+ console.warn('Storage may be evicted: make sure the user has a backup')
244
+ }
245
+ ```
246
+
247
+ ---
248
+
221
249
  ### `hasExistingCredential(): Promise<boolean>`
222
250
 
223
251
  Check if there are existing credentials (wallets) on this device.
@@ -772,6 +800,7 @@ interface SignatureResult {
772
800
  - Uses EIP-191 prefix (for EOA accounts)
773
801
  - See https://docs.login.xyz for message format
774
802
  - Use `createSiweMessage()` helper for proper formatting
803
+ - Not supported in PRIMARY mode (throws): use STANDARD, STRICT or YOLO
775
804
 
776
805
  **EIP-712 Signing:**
777
806
  - Signs structured typed data according to EIP-712 standard
@@ -2243,7 +2272,12 @@ Get comprehensive backup status showing what protects the wallet.
2243
2272
  **Returns:**
2244
2273
 
2245
2274
  ```typescript
2275
+ type BackupMethod = 'file' | 'qr' | 'phrase';
2276
+
2246
2277
  interface BackupStatus {
2278
+ hasBackup: boolean; // a backup file, QR code or confirmed phrase exists
2279
+ lastBackupAt?: string; // ISO 8601 time of the latest backup
2280
+ methods: BackupMethod[];
2247
2281
  passkeySync: PasskeySyncStatus;
2248
2282
  recoveryPhrase: RecoveryPhraseStatus;
2249
2283
  socialRecovery?: SocialRecoveryStatus;
@@ -2251,18 +2285,38 @@ interface BackupStatus {
2251
2285
  }
2252
2286
  ```
2253
2287
 
2288
+ Browser storage can be wiped (eviction, Safari's 7-day cap, clearing site data), so a wallet without a backup can be lost for good. While `hasBackup` is `false`, the security score is capped at 20 and its level is `vulnerable`.
2289
+
2254
2290
  **Example:**
2255
2291
 
2256
2292
  ```typescript
2257
2293
  const status = await w3pk.getBackupStatus()
2258
- console.log('Security Score:', status.securityScore.score) // 0-100
2259
- console.log('Passkey sync:', status.passkeySync.enabled)
2260
- console.log('Recovery phrase:', status.recoveryPhrase.status)
2294
+ if (!status.hasBackup) {
2295
+ showBackupWarning()
2296
+ }
2297
+ console.log('Security Score:', status.securityScore.total) // 0-100
2298
+ console.log('Level:', status.securityScore.level)
2299
+ console.log('Backup methods:', status.methods)
2261
2300
  console.log('Social recovery:', status.socialRecovery?.enabled)
2262
2301
  ```
2263
2302
 
2264
2303
  ---
2265
2304
 
2305
+ ### `confirmRecoveryPhraseBackup(): Promise<void>`
2306
+
2307
+ Record that the user wrote down their recovery phrase. Call it once the user confirms, so `getBackupStatus()` reports `hasBackup: true` with the `phrase` method.
2308
+
2309
+ **Example:**
2310
+
2311
+ ```typescript
2312
+ await showRecoveryPhrase()
2313
+ if (await userConfirmsPhraseWrittenDown()) {
2314
+ await w3pk.confirmRecoveryPhraseBackup()
2315
+ }
2316
+ ```
2317
+
2318
+ ---
2319
+
2266
2320
  ### `createBackupFile(encryptionType?: 'password' | 'passkey' | 'hybrid', password?: string): Promise<{ blob: Blob; filename: string }>`
2267
2321
 
2268
2322
  Create simplified backup file for wallet recovery.
@@ -2736,7 +2790,7 @@ console.log('Synced wallet address:', result2.ethereumAddress)
2736
2790
  w3pk provides two types of sessions:
2737
2791
 
2738
2792
  1. **In-Memory Sessions** (default): Cached in RAM, cleared on page refresh
2739
- 2. **Persistent Sessions** (opt-in): Encrypted in IndexedDB, survives page refresh
2793
+ 2. **Persistent Sessions** (opt-in): Encrypted in localStorage, survives page refresh
2740
2794
 
2741
2795
  **Security Modes & Persistence:**
2742
2796
  - **STANDARD mode**: Persistent sessions allowed ✅
@@ -2844,14 +2898,14 @@ Enable persistent sessions to maintain user login across page refreshes.
2844
2898
 
2845
2899
  **How it works:**
2846
2900
  1. On login, mnemonic is encrypted with WebAuthn-derived key
2847
- 2. Encrypted session stored in IndexedDB
2901
+ 2. Encrypted session stored in localStorage
2848
2902
  3. On page refresh, session is automatically restored (if not expired)
2849
2903
  4. `requireReauth: true` prompts for biometric on refresh (more secure)
2850
2904
  5. `requireReauth: false` silently restores session (more convenient)
2851
2905
 
2852
2906
  **Security (read the trade-off):**
2853
2907
  - Sessions only persist for STANDARD and YOLO modes; STRICT mode is NEVER persisted
2854
- - Time-limited expiration; origin-isolated via IndexedDB
2908
+ - Time-limited expiration; origin-isolated via localStorage
2855
2909
  - ⚠️ The "WebAuthn-derived key" is derived from **public** credential metadata stored in the same browser profile, **not** from an authenticator-held secret. A persistent session is therefore **decryptable by anyone who can read this origin's storage** (malicious extension, XSS exfil, disk image), and it keeps the seed in that state for the whole duration it's enabled. This is a UX-for-security trade-off, not hardware-backed protection.
2856
2910
  - Enabling "Remember Me" does not add a new trust dependency (you already trust your origin's code), it **widens the window** in which a compromise of your origin can reach the seed. Keep `duration` short for higher-value wallets; don't enable on shared/untrusted devices.
2857
2911
  - Full threat model: **[SECURITY.md → At-Rest Encryption](./SECURITY.md#at-rest-encryption-what-it-protects-and-what-it-doesnt)**.
@@ -3389,9 +3443,10 @@ Encrypt data using ML-KEM-1024 (post-quantum KEM) + AES-256-GCM for one or more
3389
3443
  **Returns:**
3390
3444
  ```typescript
3391
3445
  {
3446
+ version: 2; // Payload format (absent on legacy v1 payloads)
3392
3447
  recipients: Array<{
3393
3448
  publicKey: string; // Base64 recipient public key (1568 bytes)
3394
- ciphertext: string; // Base64 ML-KEM ciphertext for this recipient (1600 bytes)
3449
+ ciphertext: string; // Base64 ML-KEM ciphertext + AES-KW wrapped key (1608 bytes)
3395
3450
  }>;
3396
3451
  encryptedData: string; // Base64 AES-encrypted data (shared across all recipients)
3397
3452
  iv: string; // Base64 initialization vector (12 bytes)
@@ -3887,7 +3942,9 @@ import {
3887
3942
  WalletError,
3888
3943
  CryptoError,
3889
3944
  StorageError,
3890
- ApiError
3945
+ ApiError,
3946
+ SyncRequiredError,
3947
+ WalletMissingError
3891
3948
  } from 'w3pk'
3892
3949
  ```
3893
3950
 
@@ -3898,18 +3955,30 @@ import {
3898
3955
  - `RegistrationError` - Registration failures
3899
3956
  - `WalletError` - Wallet operations (derivation, signing)
3900
3957
  - `CryptoError` - Cryptographic operations (encryption, decryption)
3901
- - `StorageError` - Storage operations (IndexedDB)
3958
+ - `StorageError` - Storage operations (localStorage)
3902
3959
  - `ApiError` - API/network errors (RPC, chainlist)
3960
+ - `SyncRequiredError` - `login()`: passkey authenticated, but this device has no credential metadata (`credentialId`)
3961
+ - `WalletMissingError` - `login()`: credential metadata found, but the wallet record is gone (`credentialId`, `ethereumAddress`)
3903
3962
 
3904
3963
  ### Error Handling
3905
3964
 
3906
3965
  ```typescript
3907
- import { AuthenticationError, WalletError } from 'w3pk'
3966
+ import {
3967
+ AuthenticationError,
3968
+ WalletError,
3969
+ SyncRequiredError,
3970
+ WalletMissingError
3971
+ } from 'w3pk'
3908
3972
 
3909
3973
  try {
3910
3974
  await w3pk.login()
3911
3975
  } catch (error) {
3912
- if (error instanceof AuthenticationError) {
3976
+ if (error instanceof SyncRequiredError) {
3977
+ // Passkey synced to this device, wallet data isn't: ask for the backup file
3978
+ await w3pk.syncWalletWithPasskey(backupFile)
3979
+ } else if (error instanceof WalletMissingError) {
3980
+ console.error('No wallet for', error.ethereumAddress, '- restore from backup')
3981
+ } else if (error instanceof AuthenticationError) {
3913
3982
  console.error('Authentication failed:', error.message)
3914
3983
  } else if (error instanceof WalletError) {
3915
3984
  console.error('Wallet error:', error.message)
@@ -105,7 +105,7 @@ interface WebAuthnCredential {
105
105
  - Standards: BIP32, BIP39, BIP44, EIP-155
106
106
  - Curve: secp256k1 (Bitcoin/Ethereum standard)
107
107
  - Algorithm: ECDSA + Keccak-256
108
- - Storage: IndexedDB (AES-256-GCM encrypted)
108
+ - Storage: localStorage (AES-256-GCM encrypted)
109
109
  - Managed by: Application (w3pk)
110
110
 
111
111
  **Capabilities**:
@@ -231,10 +231,10 @@ STEP 3: Derive Encryption Key
231
231
 
232
232
  STEP 4: Encrypt & Store
233
233
  ┌────────────────────────────┬────────────────────────────┐
234
- │ localStorage │ IndexedDB │
234
+ │ localStorage │ localStorage │
235
235
  │ ───────────── │ ───────────── │
236
236
  │ │ │
237
- │ w3pk_credential_*: │ Web3PasskeyWallet: │
237
+ │ w3pk_credential_*: │ w3pk_wallet_<address>: │
238
238
  │ { │ { │
239
239
  │ id: "hash(abc123)", │ ethereumAddress, │
240
240
  │ publicKey (plaintext), │ encryptedMnemonic, │
@@ -268,9 +268,9 @@ STEP 1: WebAuthn Authentication
268
268
 
269
269
  STEP 2: Fetch Stored Data
270
270
  ┌────────────────────────────┬────────────────────────────┐
271
- │ localStorage │ IndexedDB │
271
+ │ localStorage │ localStorage │
272
272
  │ │ │
273
- │ Get by credentialId: │ Get by credentialId: │
273
+ │ Get by credentialId: │ Get by address: │
274
274
  │ → publicKey │ → encryptedMnemonic │
275
275
  └─────────────┬──────────────┴──────────────┬─────────────┘
276
276
  │ │
@@ -393,15 +393,16 @@ User wants to send 1 ETH
393
393
 
394
394
  **Why publicKey is plaintext**: It's needed to derive the encryption key. It's the **public** part of the WebAuthn key pair—not sensitive.
395
395
 
396
- ### IndexedDB Structure
396
+ ### Wallet Storage
397
397
 
398
- **Database**: `Web3PasskeyWallet`
399
- **Store**: `wallets`
400
- **Key Path**: `ethereumAddress`
398
+ **Storage**: localStorage, next to the credential metadata, so browser eviction removes both or neither
399
+ **Key**: `w3pk_wallet_<ethereumAddress>`
401
400
  **Purpose**: Encrypted wallet storage
402
401
 
402
+ Persistent sessions use `w3pk_session_<ethereumAddress>` and backup metadata `w3pk_backups`, in the same localStorage.
403
+
403
404
  ```typescript
404
- // Example record in IndexedDB
405
+ // Example record in localStorage
405
406
  {
406
407
  ethereumAddress: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb7",
407
408
  encryptedMnemonic: "U2FsdGVkX1+vupppZksvRf...", // AES-256-GCM blob
@@ -563,7 +564,7 @@ Layer 2: Encryption Key Derivation
563
564
  Layer 3: Encrypted Mnemonic Storage
564
565
  ┌─────────────────────────────────────┐
565
566
  │ ✅ AES-256-GCM encryption │
566
- │ ✅ IndexedDB storage │
567
+ │ ✅ localStorage storage │
567
568
  │ ✅ Cannot decrypt without Layers 1&2│
568
569
  └─────────────────────────────────────┘
569
570
  ↓ REVEALS
@@ -749,7 +750,7 @@ Passkey (Face ID) → Decrypt mnemonic → Use wallet
749
750
  |---------|----------|---------------|------------|
750
751
  | **Curve** | P-256 | secp256k1 | N/A (uses AES) |
751
752
  | **Purpose** | Authentication | Transactions | Encryption |
752
- | **Storage** | Secure Enclave | IndexedDB | None (derived) |
753
+ | **Storage** | Secure Enclave | localStorage | None (derived) |
753
754
  | **Backup** | iCloud/Google | 12-word phrase | N/A |
754
755
  | **Standards** | FIDO2, W3C | BIP32/39/44 | PBKDF2, AES-GCM |
755
756
  | **Managed By** | OS | App | App |
@@ -808,7 +809,7 @@ Scenarios:
808
809
  }
809
810
  ```
810
811
 
811
- ### What's in IndexedDB?
812
+ ### What's in `w3pk_wallet_<address>`?
812
813
 
813
814
  ```json
814
815
  {
@@ -97,6 +97,19 @@ This document details WebAuthn support across browsers, operating systems, and d
97
97
  | **VirtualBox/VMs** | No biometric passthrough | Use password fallback |
98
98
  | **Remote Desktop** | Biometrics not forwarded | Use local device |
99
99
 
100
+ #### 🗑️ **Storage That Can Be Wiped**
101
+
102
+ The encrypted wallet, persistent sessions and backup metadata live in localStorage, next to the credential metadata, so they are either all there or all gone. The passkey itself lives in the authenticator and survives all of the cases below, but the wallet does not: the mnemonic is random and can't be rebuilt from the passkey. Users need a backup file or the recovery phrase.
103
+
104
+ | Environment | What happens | Mitigation |
105
+ |-------------|--------------|------------|
106
+ | **Safari (macOS, iOS)** | localStorage deleted after 7 days of browser use without the user interacting with the site | Add the web app to the Home Screen (exempt), keep a backup |
107
+ | **Any browser: clearing site data** | localStorage deleted with cookies and site data | Keep a backup |
108
+ | **Private/Incognito windows** | localStorage discarded when the window closes | Don't use for a long-lived wallet |
109
+ | **Chrome/Firefox under storage pressure** | Origins without persistent storage can be evicted | w3pk calls `navigator.storage.persist()`; check `w3pk.storagePersisted` |
110
+
111
+ Chrome grants persistent storage from engagement heuristics (bookmarked, installed, frequently visited) without a prompt; Firefox asks the user; Safari doesn't exempt sites from its 7-day cap. See [SECURITY.md → Storage durability](./SECURITY.md#storage-durability-what-can-still-wipe-the-wallet).
112
+
100
113
  ### Hardware Security Keys
101
114
 
102
115
  | Key Type | CTAP Version | w3pk Support | Release Date |