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.
- package/dist/index.d.mts +124 -85
- package/dist/index.d.ts +124 -85
- package/dist/index.js +31 -31
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +31 -31
- package/dist/index.mjs.map +1 -1
- package/dist/zk/index.js +1 -1
- package/dist/zk/index.js.map +1 -1
- package/dist/zk/index.mjs.map +1 -1
- package/dist/zk/utils.js +3 -3
- package/dist/zk/utils.js.map +1 -1
- package/dist/zk/utils.mjs +3 -3
- package/dist/zk/utils.mjs.map +1 -1
- package/docs/API_REFERENCE.md +87 -18
- package/docs/ARCHITECTURE.md +14 -13
- package/docs/BROWSER_COMPATIBILITY.md +13 -0
- package/docs/INTEGRATION_GUIDELINES.md +36 -20
- package/docs/POST_QUANTUM.md +236 -1468
- package/docs/QUICK_START.md +4 -4
- package/docs/RECOVERY.md +35 -1
- package/docs/SECURITY.md +65 -58
- package/docs/notes/IFRAME_IMPLEMENTATION_PLAN.md +197 -0
- package/docs/notes/JULY_13_ROADMAP.md +29 -0
- package/docs/notes/NOTES.md +333 -0
- package/docs/notes/SIWE_IMPLEMENTATION.md +129 -0
- package/docs/notes/SYNCING_ACROSS_DEVICES.md +127 -0
- package/docs/notes/ethereum-post-quantum-roadmap.md +213 -0
- package/docs/notes/wulong-building-finding.md +109 -0
- package/package.json +3 -2
- package/dist/inspect/node.d.mts +0 -112
- package/dist/inspect/node.d.ts +0 -112
package/dist/zk/utils.js.map
CHANGED
|
@@ -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(
|
|
2
|
-
For more info: https://github.com/w3hc/w3pk#zero-knowledge-proofs`,
|
|
3
|
-
For more info: https://github.com/w3hc/w3pk#zero-knowledge-proofs`,
|
|
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
|
package/dist/zk/utils.mjs.map
CHANGED
|
@@ -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":"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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"]}
|
package/docs/API_REFERENCE.md
CHANGED
|
@@ -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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
161
|
+
1. Retrieves credential ID from localStorage
|
|
157
162
|
2. Triggers WebAuthn authentication (biometric prompt)
|
|
158
|
-
3. Retrieves encrypted wallet from
|
|
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
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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 {
|
|
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
|
|
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)
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -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:
|
|
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 │
|
|
234
|
+
│ localStorage │ localStorage │
|
|
235
235
|
│ ───────────── │ ───────────── │
|
|
236
236
|
│ │ │
|
|
237
|
-
│ w3pk_credential_*: │
|
|
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 │
|
|
271
|
+
│ localStorage │ localStorage │
|
|
272
272
|
│ │ │
|
|
273
|
-
│ Get by credentialId: │ Get by
|
|
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
|
-
###
|
|
396
|
+
### Wallet Storage
|
|
397
397
|
|
|
398
|
-
**
|
|
399
|
-
**
|
|
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
|
|
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
|
-
│ ✅
|
|
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 |
|
|
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
|
|
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 |
|