w3pk 0.9.3 → 0.10.1
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/README.md +12 -0
- package/dist/index.d.mts +218 -1
- package/dist/index.d.ts +218 -1
- package/dist/index.js +107 -105
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +107 -105
- package/dist/index.mjs.map +1 -1
- package/dist/inspect/browser.d.mts +5 -0
- package/dist/inspect/browser.d.ts +5 -0
- package/dist/inspect/browser.js +20 -3
- package/dist/inspect/browser.js.map +1 -1
- package/dist/inspect/browser.mjs +20 -3
- package/dist/inspect/browser.mjs.map +1 -1
- package/dist/inspect/index.js +20 -3
- package/dist/inspect/index.js.map +1 -1
- package/dist/inspect/index.mjs +20 -3
- package/dist/inspect/index.mjs.map +1 -1
- package/dist/inspect/node.d.mts +5 -0
- package/dist/inspect/node.d.ts +5 -0
- package/dist/inspect/node.js +13 -2
- package/dist/inspect/node.js.map +1 -1
- package/dist/inspect/node.mjs +13 -2
- package/dist/inspect/node.mjs.map +1 -1
- package/docs/API_REFERENCE.md +270 -2
- package/docs/ARCHITECTURE.md +42 -45
- package/docs/BUILD_VERIFICATION.md +3 -3
- package/docs/INTEGRATION_GUIDELINES.md +2 -2
- package/docs/POST_QUANTUM.md +288 -3
- package/docs/SECURITY.md +239 -20
- package/package.json +13 -10
package/dist/inspect/node.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/inspect/node.ts"],"sourcesContent":["/**\n * Node.js Inspection Module\n *\n * Provides filesystem-based code inspection capabilities for analyzing web3 applications\n * from the server-side or during development. Scans application source code and generates\n * security reports via the Rukh API.\n *\n * @module inspect/node\n */\n\nimport { promises as fs } from 'fs';\nimport path from 'path';\n\n/**\n * Configuration options for Node.js-based application inspection\n */\nexport interface InspectOptions {\n /**\n * The root directory of the application to inspect\n * @default process.cwd()\n */\n appPath?: string;\n\n /**\n * File patterns to include (glob patterns)\n * @default ['**\\/*.ts', '**\\/*.tsx', '**\\/*.js', '**\\/*.jsx', '**\\/*.json']\n */\n includePatterns?: string[];\n\n /**\n * Directories to exclude from inspection\n * @default ['node_modules', 'dist', '.next', '.git', 'build', 'coverage']\n */\n excludeDirs?: string[];\n\n /**\n * Maximum file size in KB to include\n * @default 500\n */\n maxFileSizeKB?: number;\n\n /**\n * Focus mode: filters files to only include those relevant to specific features\n * - 'transactions': Only files with signing, transactions, or blockchain operations\n * - 'all': Include all files (default)\n * @default 'all'\n */\n focusMode?: 'transactions' | 'all';\n}\n\n/**\n * Result of the code gathering operation\n */\nexport interface InspectResult {\n /**\n * The generated markdown content with all collected code\n */\n markdown: string;\n\n /**\n * List of files that were included\n */\n includedFiles: string[];\n\n /**\n * Total size in KB of all included files\n */\n totalSizeKB: number;\n}\n\n/**\n * Checks if file content is relevant to transactions/signing operations\n *\n * Scans file content and filename for keywords related to blockchain transactions,\n * message signing, smart contracts, and web3 operations.\n *\n * @param content - The file content to analyze\n * @param filename - The name of the file being checked\n * @returns True if the file contains transaction-related code\n * @internal\n */\nfunction isTransactionRelevant(content: string, filename: string): boolean {\n const transactionKeywords = [\n // Signing operations\n 'signMessage',\n 'signTypedData',\n 'sign(',\n 'signature',\n 'personalSign',\n 'eth_sign',\n 'sendTransaction',\n\n // Transaction operations\n 'transaction',\n 'sendTx',\n 'executeTx',\n 'Contract.connect',\n 'signer.send',\n 'wallet.send',\n 'provider.send',\n 'eth_sendTransaction',\n 'eth_sendRawTransaction',\n\n // Blockchain interaction\n 'Contract(',\n 'ethers.Contract',\n 'new Contract',\n 'ContractFactory',\n 'deployContract',\n\n // W3PK specific\n 'w3pk.sign',\n 'w3pk.send',\n 'Web3Passkey',\n 'useW3PK',\n\n // Smart contract calls\n 'call(',\n 'estimateGas',\n 'gasLimit',\n 'gasPrice',\n 'maxFeePerGas',\n 'maxPriorityFeePerGas',\n\n // Web3 providers\n 'JsonRpcProvider',\n 'Web3Provider',\n 'BrowserProvider',\n\n // EIP-7702\n 'EIP7702',\n 'authorization',\n 'delegation',\n ];\n\n const lowerContent = content.toLowerCase();\n const lowerFilename = filename.toLowerCase();\n\n // Check if filename suggests transaction relevance\n if (\n lowerFilename.includes('transaction') ||\n lowerFilename.includes('sign') ||\n lowerFilename.includes('wallet') ||\n lowerFilename.includes('contract') ||\n lowerFilename.includes('blockchain') ||\n lowerFilename.includes('web3') ||\n lowerFilename.includes('ethers')\n ) {\n return true;\n }\n\n // Check content for transaction-related keywords\n return transactionKeywords.some(keyword =>\n lowerContent.includes(keyword.toLowerCase())\n );\n}\n\n/**\n * Recursively walks a directory tree and collects matching files\n *\n * Traverses the directory structure, applying filters based on file patterns,\n * size limits, and focus mode settings.\n *\n * @param dir - The directory path to walk\n * @param options - Inspection options with filters and limits\n * @param rootDir - The root directory for calculating relative paths\n * @returns Array of file objects with path, content, and relative path\n * @internal\n */\nasync function walkDirectory(\n dir: string,\n options: Required<InspectOptions>,\n rootDir: string\n): Promise<{ path: string; content: string; relativePath: string }[]> {\n const files: { path: string; content: string; relativePath: string }[] = [];\n\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n const relativePath = path.relative(rootDir, fullPath);\n\n if (entry.isDirectory()) {\n // Skip excluded directories\n if (options.excludeDirs.includes(entry.name)) {\n continue;\n }\n\n // Recursively walk subdirectory\n const subFiles = await walkDirectory(fullPath, options, rootDir);\n files.push(...subFiles);\n } else if (entry.isFile()) {\n // Check if file matches include patterns\n const ext = path.extname(entry.name);\n const shouldInclude = options.includePatterns.some(pattern => {\n // Simple pattern matching for common extensions\n if (pattern.includes('*')) {\n const patternExt = pattern.split('.').pop();\n return ext === `.${patternExt}`;\n }\n return entry.name === pattern;\n });\n\n if (!shouldInclude) {\n continue;\n }\n\n // Check file size\n const stats = await fs.stat(fullPath);\n const fileSizeKB = stats.size / 1024;\n\n if (fileSizeKB > options.maxFileSizeKB) {\n console.warn(`Skipping ${relativePath} (${fileSizeKB.toFixed(2)} KB exceeds limit)`);\n continue;\n }\n\n // Read file content\n const content = await fs.readFile(fullPath, 'utf-8');\n\n // Apply focus mode filter if enabled\n if (options.focusMode === 'transactions') {\n if (!isTransactionRelevant(content, entry.name)) {\n continue;\n }\n }\n\n files.push({ path: fullPath, content, relativePath });\n }\n }\n } catch (error) {\n console.error(`Error reading directory ${dir}:`, error);\n }\n\n return files;\n}\n\n/**\n * Gathers application code and generates a markdown document\n *\n * @param options - Configuration options for the inspection\n * @returns An object containing the markdown content and metadata\n *\n * @example\n * ```typescript\n * const result = await gatherCode({\n * appPath: '../genji-passkey',\n * includePatterns: ['**\\/*.ts', '**\\/*.tsx']\n * });\n *\n * console.log(`Collected ${result.includedFiles.length} files`);\n * console.log(`Total size: ${result.totalSizeKB} KB`);\n * ```\n */\nexport async function gatherCode(options: InspectOptions = {}): Promise<InspectResult> {\n const opts: Required<InspectOptions> = {\n appPath: options.appPath || process.cwd(),\n includePatterns: options.includePatterns || [\n '**/*.ts',\n '**/*.tsx',\n '**/*.js',\n '**/*.jsx',\n '**/*.json'\n ],\n excludeDirs: options.excludeDirs || [\n 'node_modules',\n 'dist',\n '.next',\n '.git',\n 'build',\n 'coverage',\n '.cache',\n 'out',\n '.vercel',\n '.turbo'\n ],\n maxFileSizeKB: options.maxFileSizeKB || 500,\n focusMode: options.focusMode || 'all'\n };\n\n // Resolve app path\n const appPath = path.resolve(opts.appPath);\n\n // Check if app path exists\n try {\n await fs.access(appPath);\n } catch (error) {\n throw new Error(`App path does not exist: ${appPath}`);\n }\n\n // Walk directory and collect files\n const files = await walkDirectory(appPath, opts, appPath);\n\n // Generate markdown\n let markdown = `# Application Code Inspection\\n\\n`;\n markdown += `**Inspected Path:** \\`${appPath}\\`\\n\\n`;\n markdown += `**Files Collected:** ${files.length}\\n\\n`;\n markdown += `**Focus Mode:** ${opts.focusMode}\\n\\n`;\n markdown += `**Timestamp:** ${new Date().toISOString()}\\n\\n`;\n markdown += `---\\n\\n`;\n\n if (opts.focusMode === 'transactions') {\n markdown += `## Analysis Focus\\n\\n`;\n markdown += `This inspection is focused on **transaction and signing operations**. Only files containing:\\n\\n`;\n markdown += `- Message signing (signMessage, signTypedData, etc.)\\n`;\n markdown += `- Transaction sending (sendTransaction, eth_sendTransaction, etc.)\\n`;\n markdown += `- Smart contract interactions (Contract calls, deployments)\\n`;\n markdown += `- Blockchain providers and wallets\\n`;\n markdown += `- W3PK signing/transaction methods\\n`;\n markdown += `- EIP-7702 authorization/delegation\\n\\n`;\n markdown += `---\\n\\n`;\n }\n\n // Add table of contents\n markdown += `## Table of Contents\\n\\n`;\n files.forEach(file => {\n markdown += `- [\\`${file.relativePath}\\`](#${file.relativePath.replace(/[^a-z0-9]/gi, '-').toLowerCase()})\\n`;\n });\n markdown += `\\n---\\n\\n`;\n\n // Add file contents\n markdown += `## Files\\n\\n`;\n\n for (const file of files) {\n const ext = path.extname(file.relativePath).slice(1);\n const language = ext === 'tsx' || ext === 'ts' ? 'typescript' :\n ext === 'jsx' || ext === 'js' ? 'javascript' :\n ext === 'json' ? 'json' : ext;\n\n markdown += `### \\`${file.relativePath}\\`\\n\\n`;\n markdown += '```' + language + '\\n';\n markdown += file.content;\n markdown += '\\n```\\n\\n';\n markdown += `---\\n\\n`;\n }\n\n // Calculate total size\n const totalSizeKB = files.reduce((sum, file) => {\n return sum + Buffer.byteLength(file.content, 'utf-8') / 1024;\n }, 0);\n\n return {\n markdown,\n includedFiles: files.map(f => f.relativePath),\n totalSizeKB: Math.round(totalSizeKB * 100) / 100\n };\n}\n\n/**\n * Inspects an application and returns a security report via Rukh API\n *\n * @param appPath - Path to the application to inspect\n * @param rukhUrl - The Rukh API endpoint URL\n * @param context - The context name to use (should have an instruction file with report format)\n * @param model - The AI model to use ('anthropic', 'mistral', or 'openai')\n * @param focusMode - Focus on 'transactions' or include 'all' files\n * @returns A markdown-formatted security report\n *\n * @example\n * ```typescript\n * const report = await inspect(\n * '../genji-passkey',\n * 'https://rukh.w3hc.org',\n * 'w3pk',\n * 'anthropic',\n * 'transactions'\n * );\n *\n * console.log(report);\n * // Outputs:\n * // # Genji Passkey Report\n * // ## Available Methods\n * // ### Method #1: Sign Message\n * // ...\n * ```\n */\nexport async function inspect(\n appPath: string,\n rukhUrl: string = 'https://rukh.w3hc.org',\n context: string = 'w3pk',\n model: 'anthropic' | 'mistral' | 'openai' = 'anthropic',\n focusMode: 'transactions' | 'all' = 'transactions'\n): Promise<string> {\n // Gather application code with focus mode\n const result = await gatherCode({ appPath, focusMode });\n\n console.log(`Inspected ${result.includedFiles.length} files (${result.totalSizeKB} KB) [Focus: ${focusMode}]`);\n\n // Default message that triggers the structured report\n const message = 'Analyze this application and provide a security report listing all transaction and signing methods.';\n\n // Prepare form data\n const formData = new FormData();\n formData.append('message', message);\n formData.append('model', model);\n formData.append('context', context);\n\n // Create a blob from the markdown content\n const blob = new Blob([result.markdown], { type: 'text/markdown' });\n formData.append('file', blob, 'app-code.md');\n\n // Send request to Rukh API\n const response = await fetch(`${rukhUrl}/ask`, {\n method: 'POST',\n body: formData\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Rukh API error: ${response.status} - ${error}`);\n }\n\n const data = await response.json();\n return data.response || data.message || JSON.stringify(data);\n}\n"],"mappings":"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","names":["fs","path"]}
|
|
1
|
+
{"version":3,"sources":["../../src/inspect/node.ts"],"sourcesContent":["/**\n * Node.js Inspection Module\n *\n * Provides filesystem-based code inspection capabilities for analyzing web3 applications\n * from the server-side or during development. Scans application source code and generates\n * security reports via the Rukh API.\n *\n * @module inspect/node\n */\n\nimport { promises as fs } from 'fs';\nimport path from 'path';\n\n/**\n * Configuration options for Node.js-based application inspection\n */\nexport interface InspectOptions {\n /**\n * The root directory of the application to inspect\n * @default process.cwd()\n */\n appPath?: string;\n\n /**\n * File patterns to include (glob patterns)\n * @default ['**\\/*.ts', '**\\/*.tsx', '**\\/*.js', '**\\/*.jsx', '**\\/*.json']\n */\n includePatterns?: string[];\n\n /**\n * Directories to exclude from inspection\n * @default ['node_modules', 'dist', '.next', '.git', 'build', 'coverage']\n */\n excludeDirs?: string[];\n\n /**\n * Maximum file size in KB to include\n * @default 500\n */\n maxFileSizeKB?: number;\n\n /**\n * Maximum total size in KB for all collected code (prevents exceeding API token limits)\n * @default 100\n */\n maxTotalSizeKB?: number;\n\n /**\n * Focus mode: filters files to only include those relevant to specific features\n * - 'transactions': Only files with signing, transactions, or blockchain operations\n * - 'all': Include all files (default)\n * @default 'all'\n */\n focusMode?: 'transactions' | 'all';\n}\n\n/**\n * Result of the code gathering operation\n */\nexport interface InspectResult {\n /**\n * The generated markdown content with all collected code\n */\n markdown: string;\n\n /**\n * List of files that were included\n */\n includedFiles: string[];\n\n /**\n * Total size in KB of all included files\n */\n totalSizeKB: number;\n}\n\n/**\n * Checks if file content is relevant to transactions/signing operations\n *\n * Scans file content and filename for keywords related to blockchain transactions,\n * message signing, smart contracts, and web3 operations.\n *\n * @param content - The file content to analyze\n * @param filename - The name of the file being checked\n * @returns True if the file contains transaction-related code\n * @internal\n */\nfunction isTransactionRelevant(content: string, filename: string): boolean {\n const transactionKeywords = [\n // Signing operations\n 'signMessage',\n 'signTypedData',\n 'sign(',\n 'signature',\n 'personalSign',\n 'eth_sign',\n 'sendTransaction',\n\n // Transaction operations\n 'transaction',\n 'sendTx',\n 'executeTx',\n 'Contract.connect',\n 'signer.send',\n 'wallet.send',\n 'provider.send',\n 'eth_sendTransaction',\n 'eth_sendRawTransaction',\n\n // Blockchain interaction\n 'Contract(',\n 'ethers.Contract',\n 'new Contract',\n 'ContractFactory',\n 'deployContract',\n\n // W3PK specific\n 'w3pk.sign',\n 'w3pk.send',\n 'Web3Passkey',\n 'useW3PK',\n\n // Smart contract calls\n 'call(',\n 'estimateGas',\n 'gasLimit',\n 'gasPrice',\n 'maxFeePerGas',\n 'maxPriorityFeePerGas',\n\n // Web3 providers\n 'JsonRpcProvider',\n 'Web3Provider',\n 'BrowserProvider',\n\n // EIP-7702\n 'EIP7702',\n 'authorization',\n 'delegation',\n ];\n\n const lowerContent = content.toLowerCase();\n const lowerFilename = filename.toLowerCase();\n\n // Check if filename suggests transaction relevance\n if (\n lowerFilename.includes('transaction') ||\n lowerFilename.includes('sign') ||\n lowerFilename.includes('wallet') ||\n lowerFilename.includes('contract') ||\n lowerFilename.includes('blockchain') ||\n lowerFilename.includes('web3') ||\n lowerFilename.includes('ethers')\n ) {\n return true;\n }\n\n // Check content for transaction-related keywords\n return transactionKeywords.some(keyword =>\n lowerContent.includes(keyword.toLowerCase())\n );\n}\n\n/**\n * Recursively walks a directory tree and collects matching files\n *\n * Traverses the directory structure, applying filters based on file patterns,\n * size limits, and focus mode settings.\n *\n * @param dir - The directory path to walk\n * @param options - Inspection options with filters and limits\n * @param rootDir - The root directory for calculating relative paths\n * @param cumulativeSizeKB - Tracks total size collected so far\n * @returns Array of file objects with path, content, and relative path\n * @internal\n */\nasync function walkDirectory(\n dir: string,\n options: Required<InspectOptions>,\n rootDir: string,\n cumulativeSizeKB: { value: number } = { value: 0 }\n): Promise<{ path: string; content: string; relativePath: string }[]> {\n const files: { path: string; content: string; relativePath: string }[] = [];\n\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n // Check if we've reached the total size limit\n if (cumulativeSizeKB.value >= options.maxTotalSizeKB) {\n console.warn(`Reached total size limit of ${options.maxTotalSizeKB} KB - stopping collection`);\n break;\n }\n\n const fullPath = path.join(dir, entry.name);\n const relativePath = path.relative(rootDir, fullPath);\n\n if (entry.isDirectory()) {\n // Skip excluded directories\n if (options.excludeDirs.includes(entry.name)) {\n continue;\n }\n\n // Recursively walk subdirectory\n const subFiles = await walkDirectory(fullPath, options, rootDir, cumulativeSizeKB);\n files.push(...subFiles);\n } else if (entry.isFile()) {\n // Check if file matches include patterns\n const ext = path.extname(entry.name);\n const shouldInclude = options.includePatterns.some(pattern => {\n // Simple pattern matching for common extensions\n if (pattern.includes('*')) {\n const patternExt = pattern.split('.').pop();\n return ext === `.${patternExt}`;\n }\n return entry.name === pattern;\n });\n\n if (!shouldInclude) {\n continue;\n }\n\n // Check file size\n const stats = await fs.stat(fullPath);\n const fileSizeKB = stats.size / 1024;\n\n if (fileSizeKB > options.maxFileSizeKB) {\n console.warn(`Skipping ${relativePath} (${fileSizeKB.toFixed(2)} KB exceeds limit)`);\n continue;\n }\n\n // Read file content\n const content = await fs.readFile(fullPath, 'utf-8');\n\n // Apply focus mode filter if enabled\n if (options.focusMode === 'transactions') {\n if (!isTransactionRelevant(content, entry.name)) {\n continue;\n }\n }\n\n // Calculate content size\n const contentSizeKB = Buffer.byteLength(content, 'utf-8') / 1024;\n\n // Check if adding this file would exceed the total limit\n if (cumulativeSizeKB.value + contentSizeKB > options.maxTotalSizeKB) {\n console.warn(`Skipping ${relativePath} - would exceed total size limit (${(cumulativeSizeKB.value + contentSizeKB).toFixed(2)} KB > ${options.maxTotalSizeKB} KB)`);\n break;\n }\n\n // Add file and update cumulative size\n cumulativeSizeKB.value += contentSizeKB;\n files.push({ path: fullPath, content, relativePath });\n }\n }\n } catch (error) {\n console.error(`Error reading directory ${dir}:`, error);\n }\n\n return files;\n}\n\n/**\n * Gathers application code and generates a markdown document\n *\n * @param options - Configuration options for the inspection\n * @returns An object containing the markdown content and metadata\n *\n * @example\n * ```typescript\n * const result = await gatherCode({\n * appPath: '../genji-passkey',\n * includePatterns: ['**\\/*.ts', '**\\/*.tsx']\n * });\n *\n * console.log(`Collected ${result.includedFiles.length} files`);\n * console.log(`Total size: ${result.totalSizeKB} KB`);\n * ```\n */\nexport async function gatherCode(options: InspectOptions = {}): Promise<InspectResult> {\n const opts: Required<InspectOptions> = {\n appPath: options.appPath || process.cwd(),\n includePatterns: options.includePatterns || [\n '**/*.ts',\n '**/*.tsx',\n '**/*.js',\n '**/*.jsx',\n '**/*.json'\n ],\n excludeDirs: options.excludeDirs || [\n 'node_modules',\n 'dist',\n '.next',\n '.git',\n 'build',\n 'coverage',\n '.cache',\n 'out',\n '.vercel',\n '.turbo'\n ],\n maxFileSizeKB: options.maxFileSizeKB || 500,\n maxTotalSizeKB: options.maxTotalSizeKB || 100,\n focusMode: options.focusMode || 'all'\n };\n\n // Resolve app path\n const appPath = path.resolve(opts.appPath);\n\n // Check if app path exists\n try {\n await fs.access(appPath);\n } catch (error) {\n throw new Error(`App path does not exist: ${appPath}`);\n }\n\n // Walk directory and collect files\n const files = await walkDirectory(appPath, opts, appPath);\n\n // Generate markdown\n let markdown = `# Application Code Inspection\\n\\n`;\n markdown += `**Inspected Path:** \\`${appPath}\\`\\n\\n`;\n markdown += `**Files Collected:** ${files.length}\\n\\n`;\n markdown += `**Focus Mode:** ${opts.focusMode}\\n\\n`;\n markdown += `**Timestamp:** ${new Date().toISOString()}\\n\\n`;\n markdown += `---\\n\\n`;\n\n if (opts.focusMode === 'transactions') {\n markdown += `## Analysis Focus\\n\\n`;\n markdown += `This inspection is focused on **transaction and signing operations**. Only files containing:\\n\\n`;\n markdown += `- Message signing (signMessage, signTypedData, etc.)\\n`;\n markdown += `- Transaction sending (sendTransaction, eth_sendTransaction, etc.)\\n`;\n markdown += `- Smart contract interactions (Contract calls, deployments)\\n`;\n markdown += `- Blockchain providers and wallets\\n`;\n markdown += `- W3PK signing/transaction methods\\n`;\n markdown += `- EIP-7702 authorization/delegation\\n\\n`;\n markdown += `---\\n\\n`;\n }\n\n // Add table of contents\n markdown += `## Table of Contents\\n\\n`;\n files.forEach(file => {\n markdown += `- [\\`${file.relativePath}\\`](#${file.relativePath.replace(/[^a-z0-9]/gi, '-').toLowerCase()})\\n`;\n });\n markdown += `\\n---\\n\\n`;\n\n // Add file contents\n markdown += `## Files\\n\\n`;\n\n for (const file of files) {\n const ext = path.extname(file.relativePath).slice(1);\n const language = ext === 'tsx' || ext === 'ts' ? 'typescript' :\n ext === 'jsx' || ext === 'js' ? 'javascript' :\n ext === 'json' ? 'json' : ext;\n\n markdown += `### \\`${file.relativePath}\\`\\n\\n`;\n markdown += '```' + language + '\\n';\n markdown += file.content;\n markdown += '\\n```\\n\\n';\n markdown += `---\\n\\n`;\n }\n\n // Calculate total size\n const totalSizeKB = files.reduce((sum, file) => {\n return sum + Buffer.byteLength(file.content, 'utf-8') / 1024;\n }, 0);\n\n return {\n markdown,\n includedFiles: files.map(f => f.relativePath),\n totalSizeKB: Math.round(totalSizeKB * 100) / 100\n };\n}\n\n/**\n * Inspects an application and returns a security report via Rukh API\n *\n * @param appPath - Path to the application to inspect\n * @param rukhUrl - The Rukh API endpoint URL\n * @param context - The context name to use (should have an instruction file with report format)\n * @param model - The AI model to use ('anthropic', 'mistral', or 'openai')\n * @param focusMode - Focus on 'transactions' or include 'all' files\n * @returns A markdown-formatted security report\n *\n * @example\n * ```typescript\n * const report = await inspect(\n * '../genji-passkey',\n * 'https://rukh.w3hc.org',\n * 'w3pk',\n * 'anthropic',\n * 'transactions'\n * );\n *\n * console.log(report);\n * // Outputs:\n * // # Genji Passkey Report\n * // ## Available Methods\n * // ### Method #1: Sign Message\n * // ...\n * ```\n */\nexport async function inspect(\n appPath: string,\n rukhUrl: string = 'https://rukh.w3hc.org',\n context: string = 'w3pk',\n model: 'anthropic' | 'mistral' | 'openai' = 'anthropic',\n focusMode: 'transactions' | 'all' = 'transactions'\n): Promise<string> {\n // Gather application code with focus mode\n const result = await gatherCode({ appPath, focusMode });\n\n console.log(`Inspected ${result.includedFiles.length} files (${result.totalSizeKB} KB) [Focus: ${focusMode}]`);\n\n // Default message that triggers the structured report\n const message = 'Analyze this application and provide a security report listing all transaction and signing methods.';\n\n // Prepare form data\n const formData = new FormData();\n formData.append('message', message);\n formData.append('model', model);\n formData.append('context', context);\n\n // Create a blob from the markdown content\n const blob = new Blob([result.markdown], { type: 'text/markdown' });\n formData.append('file', blob, 'app-code.md');\n\n // Send request to Rukh API\n const response = await fetch(`${rukhUrl}/ask`, {\n method: 'POST',\n body: formData\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Rukh API error: ${response.status} - ${error}`);\n }\n\n const data = await response.json();\n return data.response || data.message || JSON.stringify(data);\n}\n"],"mappings":"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","names":["fs","path"]}
|
package/dist/inspect/node.mjs
CHANGED
|
@@ -57,18 +57,22 @@ function isTransactionRelevant(content, filename) {
|
|
|
57
57
|
(keyword) => lowerContent.includes(keyword.toLowerCase())
|
|
58
58
|
);
|
|
59
59
|
}
|
|
60
|
-
async function walkDirectory(dir, options, rootDir) {
|
|
60
|
+
async function walkDirectory(dir, options, rootDir, cumulativeSizeKB = { value: 0 }) {
|
|
61
61
|
const files = [];
|
|
62
62
|
try {
|
|
63
63
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
64
64
|
for (const entry of entries) {
|
|
65
|
+
if (cumulativeSizeKB.value >= options.maxTotalSizeKB) {
|
|
66
|
+
console.warn(`Reached total size limit of ${options.maxTotalSizeKB} KB - stopping collection`);
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
65
69
|
const fullPath = path.join(dir, entry.name);
|
|
66
70
|
const relativePath = path.relative(rootDir, fullPath);
|
|
67
71
|
if (entry.isDirectory()) {
|
|
68
72
|
if (options.excludeDirs.includes(entry.name)) {
|
|
69
73
|
continue;
|
|
70
74
|
}
|
|
71
|
-
const subFiles = await walkDirectory(fullPath, options, rootDir);
|
|
75
|
+
const subFiles = await walkDirectory(fullPath, options, rootDir, cumulativeSizeKB);
|
|
72
76
|
files.push(...subFiles);
|
|
73
77
|
} else if (entry.isFile()) {
|
|
74
78
|
const ext = path.extname(entry.name);
|
|
@@ -94,6 +98,12 @@ async function walkDirectory(dir, options, rootDir) {
|
|
|
94
98
|
continue;
|
|
95
99
|
}
|
|
96
100
|
}
|
|
101
|
+
const contentSizeKB = Buffer.byteLength(content, "utf-8") / 1024;
|
|
102
|
+
if (cumulativeSizeKB.value + contentSizeKB > options.maxTotalSizeKB) {
|
|
103
|
+
console.warn(`Skipping ${relativePath} - would exceed total size limit (${(cumulativeSizeKB.value + contentSizeKB).toFixed(2)} KB > ${options.maxTotalSizeKB} KB)`);
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
cumulativeSizeKB.value += contentSizeKB;
|
|
97
107
|
files.push({ path: fullPath, content, relativePath });
|
|
98
108
|
}
|
|
99
109
|
}
|
|
@@ -125,6 +135,7 @@ async function gatherCode(options = {}) {
|
|
|
125
135
|
".turbo"
|
|
126
136
|
],
|
|
127
137
|
maxFileSizeKB: options.maxFileSizeKB || 500,
|
|
138
|
+
maxTotalSizeKB: options.maxTotalSizeKB || 100,
|
|
128
139
|
focusMode: options.focusMode || "all"
|
|
129
140
|
};
|
|
130
141
|
const appPath = path.resolve(opts.appPath);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/inspect/node.ts"],"sourcesContent":["/**\n * Node.js Inspection Module\n *\n * Provides filesystem-based code inspection capabilities for analyzing web3 applications\n * from the server-side or during development. Scans application source code and generates\n * security reports via the Rukh API.\n *\n * @module inspect/node\n */\n\nimport { promises as fs } from 'fs';\nimport path from 'path';\n\n/**\n * Configuration options for Node.js-based application inspection\n */\nexport interface InspectOptions {\n /**\n * The root directory of the application to inspect\n * @default process.cwd()\n */\n appPath?: string;\n\n /**\n * File patterns to include (glob patterns)\n * @default ['**\\/*.ts', '**\\/*.tsx', '**\\/*.js', '**\\/*.jsx', '**\\/*.json']\n */\n includePatterns?: string[];\n\n /**\n * Directories to exclude from inspection\n * @default ['node_modules', 'dist', '.next', '.git', 'build', 'coverage']\n */\n excludeDirs?: string[];\n\n /**\n * Maximum file size in KB to include\n * @default 500\n */\n maxFileSizeKB?: number;\n\n /**\n * Focus mode: filters files to only include those relevant to specific features\n * - 'transactions': Only files with signing, transactions, or blockchain operations\n * - 'all': Include all files (default)\n * @default 'all'\n */\n focusMode?: 'transactions' | 'all';\n}\n\n/**\n * Result of the code gathering operation\n */\nexport interface InspectResult {\n /**\n * The generated markdown content with all collected code\n */\n markdown: string;\n\n /**\n * List of files that were included\n */\n includedFiles: string[];\n\n /**\n * Total size in KB of all included files\n */\n totalSizeKB: number;\n}\n\n/**\n * Checks if file content is relevant to transactions/signing operations\n *\n * Scans file content and filename for keywords related to blockchain transactions,\n * message signing, smart contracts, and web3 operations.\n *\n * @param content - The file content to analyze\n * @param filename - The name of the file being checked\n * @returns True if the file contains transaction-related code\n * @internal\n */\nfunction isTransactionRelevant(content: string, filename: string): boolean {\n const transactionKeywords = [\n // Signing operations\n 'signMessage',\n 'signTypedData',\n 'sign(',\n 'signature',\n 'personalSign',\n 'eth_sign',\n 'sendTransaction',\n\n // Transaction operations\n 'transaction',\n 'sendTx',\n 'executeTx',\n 'Contract.connect',\n 'signer.send',\n 'wallet.send',\n 'provider.send',\n 'eth_sendTransaction',\n 'eth_sendRawTransaction',\n\n // Blockchain interaction\n 'Contract(',\n 'ethers.Contract',\n 'new Contract',\n 'ContractFactory',\n 'deployContract',\n\n // W3PK specific\n 'w3pk.sign',\n 'w3pk.send',\n 'Web3Passkey',\n 'useW3PK',\n\n // Smart contract calls\n 'call(',\n 'estimateGas',\n 'gasLimit',\n 'gasPrice',\n 'maxFeePerGas',\n 'maxPriorityFeePerGas',\n\n // Web3 providers\n 'JsonRpcProvider',\n 'Web3Provider',\n 'BrowserProvider',\n\n // EIP-7702\n 'EIP7702',\n 'authorization',\n 'delegation',\n ];\n\n const lowerContent = content.toLowerCase();\n const lowerFilename = filename.toLowerCase();\n\n // Check if filename suggests transaction relevance\n if (\n lowerFilename.includes('transaction') ||\n lowerFilename.includes('sign') ||\n lowerFilename.includes('wallet') ||\n lowerFilename.includes('contract') ||\n lowerFilename.includes('blockchain') ||\n lowerFilename.includes('web3') ||\n lowerFilename.includes('ethers')\n ) {\n return true;\n }\n\n // Check content for transaction-related keywords\n return transactionKeywords.some(keyword =>\n lowerContent.includes(keyword.toLowerCase())\n );\n}\n\n/**\n * Recursively walks a directory tree and collects matching files\n *\n * Traverses the directory structure, applying filters based on file patterns,\n * size limits, and focus mode settings.\n *\n * @param dir - The directory path to walk\n * @param options - Inspection options with filters and limits\n * @param rootDir - The root directory for calculating relative paths\n * @returns Array of file objects with path, content, and relative path\n * @internal\n */\nasync function walkDirectory(\n dir: string,\n options: Required<InspectOptions>,\n rootDir: string\n): Promise<{ path: string; content: string; relativePath: string }[]> {\n const files: { path: string; content: string; relativePath: string }[] = [];\n\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n const relativePath = path.relative(rootDir, fullPath);\n\n if (entry.isDirectory()) {\n // Skip excluded directories\n if (options.excludeDirs.includes(entry.name)) {\n continue;\n }\n\n // Recursively walk subdirectory\n const subFiles = await walkDirectory(fullPath, options, rootDir);\n files.push(...subFiles);\n } else if (entry.isFile()) {\n // Check if file matches include patterns\n const ext = path.extname(entry.name);\n const shouldInclude = options.includePatterns.some(pattern => {\n // Simple pattern matching for common extensions\n if (pattern.includes('*')) {\n const patternExt = pattern.split('.').pop();\n return ext === `.${patternExt}`;\n }\n return entry.name === pattern;\n });\n\n if (!shouldInclude) {\n continue;\n }\n\n // Check file size\n const stats = await fs.stat(fullPath);\n const fileSizeKB = stats.size / 1024;\n\n if (fileSizeKB > options.maxFileSizeKB) {\n console.warn(`Skipping ${relativePath} (${fileSizeKB.toFixed(2)} KB exceeds limit)`);\n continue;\n }\n\n // Read file content\n const content = await fs.readFile(fullPath, 'utf-8');\n\n // Apply focus mode filter if enabled\n if (options.focusMode === 'transactions') {\n if (!isTransactionRelevant(content, entry.name)) {\n continue;\n }\n }\n\n files.push({ path: fullPath, content, relativePath });\n }\n }\n } catch (error) {\n console.error(`Error reading directory ${dir}:`, error);\n }\n\n return files;\n}\n\n/**\n * Gathers application code and generates a markdown document\n *\n * @param options - Configuration options for the inspection\n * @returns An object containing the markdown content and metadata\n *\n * @example\n * ```typescript\n * const result = await gatherCode({\n * appPath: '../genji-passkey',\n * includePatterns: ['**\\/*.ts', '**\\/*.tsx']\n * });\n *\n * console.log(`Collected ${result.includedFiles.length} files`);\n * console.log(`Total size: ${result.totalSizeKB} KB`);\n * ```\n */\nexport async function gatherCode(options: InspectOptions = {}): Promise<InspectResult> {\n const opts: Required<InspectOptions> = {\n appPath: options.appPath || process.cwd(),\n includePatterns: options.includePatterns || [\n '**/*.ts',\n '**/*.tsx',\n '**/*.js',\n '**/*.jsx',\n '**/*.json'\n ],\n excludeDirs: options.excludeDirs || [\n 'node_modules',\n 'dist',\n '.next',\n '.git',\n 'build',\n 'coverage',\n '.cache',\n 'out',\n '.vercel',\n '.turbo'\n ],\n maxFileSizeKB: options.maxFileSizeKB || 500,\n focusMode: options.focusMode || 'all'\n };\n\n // Resolve app path\n const appPath = path.resolve(opts.appPath);\n\n // Check if app path exists\n try {\n await fs.access(appPath);\n } catch (error) {\n throw new Error(`App path does not exist: ${appPath}`);\n }\n\n // Walk directory and collect files\n const files = await walkDirectory(appPath, opts, appPath);\n\n // Generate markdown\n let markdown = `# Application Code Inspection\\n\\n`;\n markdown += `**Inspected Path:** \\`${appPath}\\`\\n\\n`;\n markdown += `**Files Collected:** ${files.length}\\n\\n`;\n markdown += `**Focus Mode:** ${opts.focusMode}\\n\\n`;\n markdown += `**Timestamp:** ${new Date().toISOString()}\\n\\n`;\n markdown += `---\\n\\n`;\n\n if (opts.focusMode === 'transactions') {\n markdown += `## Analysis Focus\\n\\n`;\n markdown += `This inspection is focused on **transaction and signing operations**. Only files containing:\\n\\n`;\n markdown += `- Message signing (signMessage, signTypedData, etc.)\\n`;\n markdown += `- Transaction sending (sendTransaction, eth_sendTransaction, etc.)\\n`;\n markdown += `- Smart contract interactions (Contract calls, deployments)\\n`;\n markdown += `- Blockchain providers and wallets\\n`;\n markdown += `- W3PK signing/transaction methods\\n`;\n markdown += `- EIP-7702 authorization/delegation\\n\\n`;\n markdown += `---\\n\\n`;\n }\n\n // Add table of contents\n markdown += `## Table of Contents\\n\\n`;\n files.forEach(file => {\n markdown += `- [\\`${file.relativePath}\\`](#${file.relativePath.replace(/[^a-z0-9]/gi, '-').toLowerCase()})\\n`;\n });\n markdown += `\\n---\\n\\n`;\n\n // Add file contents\n markdown += `## Files\\n\\n`;\n\n for (const file of files) {\n const ext = path.extname(file.relativePath).slice(1);\n const language = ext === 'tsx' || ext === 'ts' ? 'typescript' :\n ext === 'jsx' || ext === 'js' ? 'javascript' :\n ext === 'json' ? 'json' : ext;\n\n markdown += `### \\`${file.relativePath}\\`\\n\\n`;\n markdown += '```' + language + '\\n';\n markdown += file.content;\n markdown += '\\n```\\n\\n';\n markdown += `---\\n\\n`;\n }\n\n // Calculate total size\n const totalSizeKB = files.reduce((sum, file) => {\n return sum + Buffer.byteLength(file.content, 'utf-8') / 1024;\n }, 0);\n\n return {\n markdown,\n includedFiles: files.map(f => f.relativePath),\n totalSizeKB: Math.round(totalSizeKB * 100) / 100\n };\n}\n\n/**\n * Inspects an application and returns a security report via Rukh API\n *\n * @param appPath - Path to the application to inspect\n * @param rukhUrl - The Rukh API endpoint URL\n * @param context - The context name to use (should have an instruction file with report format)\n * @param model - The AI model to use ('anthropic', 'mistral', or 'openai')\n * @param focusMode - Focus on 'transactions' or include 'all' files\n * @returns A markdown-formatted security report\n *\n * @example\n * ```typescript\n * const report = await inspect(\n * '../genji-passkey',\n * 'https://rukh.w3hc.org',\n * 'w3pk',\n * 'anthropic',\n * 'transactions'\n * );\n *\n * console.log(report);\n * // Outputs:\n * // # Genji Passkey Report\n * // ## Available Methods\n * // ### Method #1: Sign Message\n * // ...\n * ```\n */\nexport async function inspect(\n appPath: string,\n rukhUrl: string = 'https://rukh.w3hc.org',\n context: string = 'w3pk',\n model: 'anthropic' | 'mistral' | 'openai' = 'anthropic',\n focusMode: 'transactions' | 'all' = 'transactions'\n): Promise<string> {\n // Gather application code with focus mode\n const result = await gatherCode({ appPath, focusMode });\n\n console.log(`Inspected ${result.includedFiles.length} files (${result.totalSizeKB} KB) [Focus: ${focusMode}]`);\n\n // Default message that triggers the structured report\n const message = 'Analyze this application and provide a security report listing all transaction and signing methods.';\n\n // Prepare form data\n const formData = new FormData();\n formData.append('message', message);\n formData.append('model', model);\n formData.append('context', context);\n\n // Create a blob from the markdown content\n const blob = new Blob([result.markdown], { type: 'text/markdown' });\n formData.append('file', blob, 'app-code.md');\n\n // Send request to Rukh API\n const response = await fetch(`${rukhUrl}/ask`, {\n method: 'POST',\n body: formData\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Rukh API error: ${response.status} - ${error}`);\n }\n\n const data = await response.json();\n return data.response || data.message || JSON.stringify(data);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/inspect/node.ts"],"sourcesContent":["/**\n * Node.js Inspection Module\n *\n * Provides filesystem-based code inspection capabilities for analyzing web3 applications\n * from the server-side or during development. Scans application source code and generates\n * security reports via the Rukh API.\n *\n * @module inspect/node\n */\n\nimport { promises as fs } from 'fs';\nimport path from 'path';\n\n/**\n * Configuration options for Node.js-based application inspection\n */\nexport interface InspectOptions {\n /**\n * The root directory of the application to inspect\n * @default process.cwd()\n */\n appPath?: string;\n\n /**\n * File patterns to include (glob patterns)\n * @default ['**\\/*.ts', '**\\/*.tsx', '**\\/*.js', '**\\/*.jsx', '**\\/*.json']\n */\n includePatterns?: string[];\n\n /**\n * Directories to exclude from inspection\n * @default ['node_modules', 'dist', '.next', '.git', 'build', 'coverage']\n */\n excludeDirs?: string[];\n\n /**\n * Maximum file size in KB to include\n * @default 500\n */\n maxFileSizeKB?: number;\n\n /**\n * Maximum total size in KB for all collected code (prevents exceeding API token limits)\n * @default 100\n */\n maxTotalSizeKB?: number;\n\n /**\n * Focus mode: filters files to only include those relevant to specific features\n * - 'transactions': Only files with signing, transactions, or blockchain operations\n * - 'all': Include all files (default)\n * @default 'all'\n */\n focusMode?: 'transactions' | 'all';\n}\n\n/**\n * Result of the code gathering operation\n */\nexport interface InspectResult {\n /**\n * The generated markdown content with all collected code\n */\n markdown: string;\n\n /**\n * List of files that were included\n */\n includedFiles: string[];\n\n /**\n * Total size in KB of all included files\n */\n totalSizeKB: number;\n}\n\n/**\n * Checks if file content is relevant to transactions/signing operations\n *\n * Scans file content and filename for keywords related to blockchain transactions,\n * message signing, smart contracts, and web3 operations.\n *\n * @param content - The file content to analyze\n * @param filename - The name of the file being checked\n * @returns True if the file contains transaction-related code\n * @internal\n */\nfunction isTransactionRelevant(content: string, filename: string): boolean {\n const transactionKeywords = [\n // Signing operations\n 'signMessage',\n 'signTypedData',\n 'sign(',\n 'signature',\n 'personalSign',\n 'eth_sign',\n 'sendTransaction',\n\n // Transaction operations\n 'transaction',\n 'sendTx',\n 'executeTx',\n 'Contract.connect',\n 'signer.send',\n 'wallet.send',\n 'provider.send',\n 'eth_sendTransaction',\n 'eth_sendRawTransaction',\n\n // Blockchain interaction\n 'Contract(',\n 'ethers.Contract',\n 'new Contract',\n 'ContractFactory',\n 'deployContract',\n\n // W3PK specific\n 'w3pk.sign',\n 'w3pk.send',\n 'Web3Passkey',\n 'useW3PK',\n\n // Smart contract calls\n 'call(',\n 'estimateGas',\n 'gasLimit',\n 'gasPrice',\n 'maxFeePerGas',\n 'maxPriorityFeePerGas',\n\n // Web3 providers\n 'JsonRpcProvider',\n 'Web3Provider',\n 'BrowserProvider',\n\n // EIP-7702\n 'EIP7702',\n 'authorization',\n 'delegation',\n ];\n\n const lowerContent = content.toLowerCase();\n const lowerFilename = filename.toLowerCase();\n\n // Check if filename suggests transaction relevance\n if (\n lowerFilename.includes('transaction') ||\n lowerFilename.includes('sign') ||\n lowerFilename.includes('wallet') ||\n lowerFilename.includes('contract') ||\n lowerFilename.includes('blockchain') ||\n lowerFilename.includes('web3') ||\n lowerFilename.includes('ethers')\n ) {\n return true;\n }\n\n // Check content for transaction-related keywords\n return transactionKeywords.some(keyword =>\n lowerContent.includes(keyword.toLowerCase())\n );\n}\n\n/**\n * Recursively walks a directory tree and collects matching files\n *\n * Traverses the directory structure, applying filters based on file patterns,\n * size limits, and focus mode settings.\n *\n * @param dir - The directory path to walk\n * @param options - Inspection options with filters and limits\n * @param rootDir - The root directory for calculating relative paths\n * @param cumulativeSizeKB - Tracks total size collected so far\n * @returns Array of file objects with path, content, and relative path\n * @internal\n */\nasync function walkDirectory(\n dir: string,\n options: Required<InspectOptions>,\n rootDir: string,\n cumulativeSizeKB: { value: number } = { value: 0 }\n): Promise<{ path: string; content: string; relativePath: string }[]> {\n const files: { path: string; content: string; relativePath: string }[] = [];\n\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n // Check if we've reached the total size limit\n if (cumulativeSizeKB.value >= options.maxTotalSizeKB) {\n console.warn(`Reached total size limit of ${options.maxTotalSizeKB} KB - stopping collection`);\n break;\n }\n\n const fullPath = path.join(dir, entry.name);\n const relativePath = path.relative(rootDir, fullPath);\n\n if (entry.isDirectory()) {\n // Skip excluded directories\n if (options.excludeDirs.includes(entry.name)) {\n continue;\n }\n\n // Recursively walk subdirectory\n const subFiles = await walkDirectory(fullPath, options, rootDir, cumulativeSizeKB);\n files.push(...subFiles);\n } else if (entry.isFile()) {\n // Check if file matches include patterns\n const ext = path.extname(entry.name);\n const shouldInclude = options.includePatterns.some(pattern => {\n // Simple pattern matching for common extensions\n if (pattern.includes('*')) {\n const patternExt = pattern.split('.').pop();\n return ext === `.${patternExt}`;\n }\n return entry.name === pattern;\n });\n\n if (!shouldInclude) {\n continue;\n }\n\n // Check file size\n const stats = await fs.stat(fullPath);\n const fileSizeKB = stats.size / 1024;\n\n if (fileSizeKB > options.maxFileSizeKB) {\n console.warn(`Skipping ${relativePath} (${fileSizeKB.toFixed(2)} KB exceeds limit)`);\n continue;\n }\n\n // Read file content\n const content = await fs.readFile(fullPath, 'utf-8');\n\n // Apply focus mode filter if enabled\n if (options.focusMode === 'transactions') {\n if (!isTransactionRelevant(content, entry.name)) {\n continue;\n }\n }\n\n // Calculate content size\n const contentSizeKB = Buffer.byteLength(content, 'utf-8') / 1024;\n\n // Check if adding this file would exceed the total limit\n if (cumulativeSizeKB.value + contentSizeKB > options.maxTotalSizeKB) {\n console.warn(`Skipping ${relativePath} - would exceed total size limit (${(cumulativeSizeKB.value + contentSizeKB).toFixed(2)} KB > ${options.maxTotalSizeKB} KB)`);\n break;\n }\n\n // Add file and update cumulative size\n cumulativeSizeKB.value += contentSizeKB;\n files.push({ path: fullPath, content, relativePath });\n }\n }\n } catch (error) {\n console.error(`Error reading directory ${dir}:`, error);\n }\n\n return files;\n}\n\n/**\n * Gathers application code and generates a markdown document\n *\n * @param options - Configuration options for the inspection\n * @returns An object containing the markdown content and metadata\n *\n * @example\n * ```typescript\n * const result = await gatherCode({\n * appPath: '../genji-passkey',\n * includePatterns: ['**\\/*.ts', '**\\/*.tsx']\n * });\n *\n * console.log(`Collected ${result.includedFiles.length} files`);\n * console.log(`Total size: ${result.totalSizeKB} KB`);\n * ```\n */\nexport async function gatherCode(options: InspectOptions = {}): Promise<InspectResult> {\n const opts: Required<InspectOptions> = {\n appPath: options.appPath || process.cwd(),\n includePatterns: options.includePatterns || [\n '**/*.ts',\n '**/*.tsx',\n '**/*.js',\n '**/*.jsx',\n '**/*.json'\n ],\n excludeDirs: options.excludeDirs || [\n 'node_modules',\n 'dist',\n '.next',\n '.git',\n 'build',\n 'coverage',\n '.cache',\n 'out',\n '.vercel',\n '.turbo'\n ],\n maxFileSizeKB: options.maxFileSizeKB || 500,\n maxTotalSizeKB: options.maxTotalSizeKB || 100,\n focusMode: options.focusMode || 'all'\n };\n\n // Resolve app path\n const appPath = path.resolve(opts.appPath);\n\n // Check if app path exists\n try {\n await fs.access(appPath);\n } catch (error) {\n throw new Error(`App path does not exist: ${appPath}`);\n }\n\n // Walk directory and collect files\n const files = await walkDirectory(appPath, opts, appPath);\n\n // Generate markdown\n let markdown = `# Application Code Inspection\\n\\n`;\n markdown += `**Inspected Path:** \\`${appPath}\\`\\n\\n`;\n markdown += `**Files Collected:** ${files.length}\\n\\n`;\n markdown += `**Focus Mode:** ${opts.focusMode}\\n\\n`;\n markdown += `**Timestamp:** ${new Date().toISOString()}\\n\\n`;\n markdown += `---\\n\\n`;\n\n if (opts.focusMode === 'transactions') {\n markdown += `## Analysis Focus\\n\\n`;\n markdown += `This inspection is focused on **transaction and signing operations**. Only files containing:\\n\\n`;\n markdown += `- Message signing (signMessage, signTypedData, etc.)\\n`;\n markdown += `- Transaction sending (sendTransaction, eth_sendTransaction, etc.)\\n`;\n markdown += `- Smart contract interactions (Contract calls, deployments)\\n`;\n markdown += `- Blockchain providers and wallets\\n`;\n markdown += `- W3PK signing/transaction methods\\n`;\n markdown += `- EIP-7702 authorization/delegation\\n\\n`;\n markdown += `---\\n\\n`;\n }\n\n // Add table of contents\n markdown += `## Table of Contents\\n\\n`;\n files.forEach(file => {\n markdown += `- [\\`${file.relativePath}\\`](#${file.relativePath.replace(/[^a-z0-9]/gi, '-').toLowerCase()})\\n`;\n });\n markdown += `\\n---\\n\\n`;\n\n // Add file contents\n markdown += `## Files\\n\\n`;\n\n for (const file of files) {\n const ext = path.extname(file.relativePath).slice(1);\n const language = ext === 'tsx' || ext === 'ts' ? 'typescript' :\n ext === 'jsx' || ext === 'js' ? 'javascript' :\n ext === 'json' ? 'json' : ext;\n\n markdown += `### \\`${file.relativePath}\\`\\n\\n`;\n markdown += '```' + language + '\\n';\n markdown += file.content;\n markdown += '\\n```\\n\\n';\n markdown += `---\\n\\n`;\n }\n\n // Calculate total size\n const totalSizeKB = files.reduce((sum, file) => {\n return sum + Buffer.byteLength(file.content, 'utf-8') / 1024;\n }, 0);\n\n return {\n markdown,\n includedFiles: files.map(f => f.relativePath),\n totalSizeKB: Math.round(totalSizeKB * 100) / 100\n };\n}\n\n/**\n * Inspects an application and returns a security report via Rukh API\n *\n * @param appPath - Path to the application to inspect\n * @param rukhUrl - The Rukh API endpoint URL\n * @param context - The context name to use (should have an instruction file with report format)\n * @param model - The AI model to use ('anthropic', 'mistral', or 'openai')\n * @param focusMode - Focus on 'transactions' or include 'all' files\n * @returns A markdown-formatted security report\n *\n * @example\n * ```typescript\n * const report = await inspect(\n * '../genji-passkey',\n * 'https://rukh.w3hc.org',\n * 'w3pk',\n * 'anthropic',\n * 'transactions'\n * );\n *\n * console.log(report);\n * // Outputs:\n * // # Genji Passkey Report\n * // ## Available Methods\n * // ### Method #1: Sign Message\n * // ...\n * ```\n */\nexport async function inspect(\n appPath: string,\n rukhUrl: string = 'https://rukh.w3hc.org',\n context: string = 'w3pk',\n model: 'anthropic' | 'mistral' | 'openai' = 'anthropic',\n focusMode: 'transactions' | 'all' = 'transactions'\n): Promise<string> {\n // Gather application code with focus mode\n const result = await gatherCode({ appPath, focusMode });\n\n console.log(`Inspected ${result.includedFiles.length} files (${result.totalSizeKB} KB) [Focus: ${focusMode}]`);\n\n // Default message that triggers the structured report\n const message = 'Analyze this application and provide a security report listing all transaction and signing methods.';\n\n // Prepare form data\n const formData = new FormData();\n formData.append('message', message);\n formData.append('model', model);\n formData.append('context', context);\n\n // Create a blob from the markdown content\n const blob = new Blob([result.markdown], { type: 'text/markdown' });\n formData.append('file', blob, 'app-code.md');\n\n // Send request to Rukh API\n const response = await fetch(`${rukhUrl}/ask`, {\n method: 'POST',\n body: formData\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Rukh API error: ${response.status} - ${error}`);\n }\n\n const data = await response.json();\n return data.response || data.message || JSON.stringify(data);\n}\n"],"mappings":"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","names":[]}
|
package/docs/API_REFERENCE.md
CHANGED
|
@@ -20,6 +20,7 @@ Complete reference for all methods, types, and utilities in the w3pk SDK.
|
|
|
20
20
|
- [Standalone Utilities](#standalone-utilities)
|
|
21
21
|
- [Validation Utilities](#validation-utilities)
|
|
22
22
|
- [Build Verification Utilities](#build-verification-utilities)
|
|
23
|
+
- [Post-Quantum Cryptography](#post-quantum-cryptography)
|
|
23
24
|
- [Wallet Generation Utilities](#wallet-generation-utilities)
|
|
24
25
|
- [Security Inspection](#security-inspection)
|
|
25
26
|
- [Browser Inspection](#browser-inspection)
|
|
@@ -1613,6 +1614,8 @@ const w3pk = createWeb3Passkey({
|
|
|
1613
1614
|
|
|
1614
1615
|
All stealth address methods are accessed via `w3pk.stealth.*`
|
|
1615
1616
|
|
|
1617
|
+
> **🔒 Security Note:** The ERC-5564 implementation properly reduces all scalars modulo the secp256k1 curve order to prevent unspendable funds. The same reduced scalar is used consistently in both public-key operations (`s_h × G`) and private-key operations (`spendingKey + s_h`) to ensure the derived stealth address always matches the derived private key.
|
|
1618
|
+
|
|
1616
1619
|
### `stealth.generateStealthAddress(options?: { requireAuth?: boolean }): Promise<StealthAddressResult>`
|
|
1617
1620
|
|
|
1618
1621
|
Generate a fresh ERC-5564 compliant stealth address for a recipient.
|
|
@@ -3072,7 +3075,7 @@ Compute IPFS CIDv1 hash for the currently installed w3pk version from unpkg CDN.
|
|
|
3072
3075
|
```typescript
|
|
3073
3076
|
const hash = await getCurrentBuildHash()
|
|
3074
3077
|
console.log('Build hash:', hash)
|
|
3075
|
-
// =>
|
|
3078
|
+
// => bafybeicce26ztznqcdn6n6j6hhnoye37nb7nt7gtubelwk2ku4txbp7acy
|
|
3076
3079
|
```
|
|
3077
3080
|
|
|
3078
3081
|
---
|
|
@@ -3207,7 +3210,89 @@ console.log(encoded) // => SGVsbG8gV29ybGQ=
|
|
|
3207
3210
|
### Cryptographic Utilities
|
|
3208
3211
|
|
|
3209
3212
|
```typescript
|
|
3210
|
-
import {
|
|
3213
|
+
import {
|
|
3214
|
+
extractRS,
|
|
3215
|
+
deriveEncryptionKeyFromWebAuthn,
|
|
3216
|
+
generateSalt
|
|
3217
|
+
} from 'w3pk'
|
|
3218
|
+
```
|
|
3219
|
+
|
|
3220
|
+
#### `deriveEncryptionKeyFromWebAuthn(prfOutput: ArrayBuffer, salt: Uint8Array): Promise<CryptoKey>`
|
|
3221
|
+
|
|
3222
|
+
**🔐 SECURE - WebAuthn PRF-based encryption**
|
|
3223
|
+
|
|
3224
|
+
Derives an AES-256-GCM encryption key from WebAuthn PRF (Pseudo-Random Function) extension output.
|
|
3225
|
+
|
|
3226
|
+
**Security:**
|
|
3227
|
+
- Uses authenticator-held secrets via PRF extension (never exposed)
|
|
3228
|
+
- Implements random unique salts (no precomputation attacks)
|
|
3229
|
+
- PBKDF2 with 210,000 iterations (OWASP 2023 recommendation)
|
|
3230
|
+
- Prevents offline decryption attacks
|
|
3231
|
+
|
|
3232
|
+
**Parameters:**
|
|
3233
|
+
- `prfOutput: ArrayBuffer` - 32-byte secret from WebAuthn PRF extension
|
|
3234
|
+
- `salt: Uint8Array` - 32-byte random salt (generate with `generateSalt()`)
|
|
3235
|
+
|
|
3236
|
+
**Returns:** `Promise<CryptoKey>` - AES-256-GCM key for encryption/decryption
|
|
3237
|
+
|
|
3238
|
+
**Example:**
|
|
3239
|
+
```typescript
|
|
3240
|
+
// 1. Enable PRF during WebAuthn registration
|
|
3241
|
+
const credential = await navigator.credentials.create({
|
|
3242
|
+
publicKey: {
|
|
3243
|
+
challenge: new Uint8Array(32),
|
|
3244
|
+
rp: { name: "Example" },
|
|
3245
|
+
user: { id: userId, name: "user", displayName: "User" },
|
|
3246
|
+
pubKeyCredParams: [{ alg: -7, type: "public-key" }],
|
|
3247
|
+
extensions: {
|
|
3248
|
+
prf: {} // Enable PRF extension
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
});
|
|
3252
|
+
|
|
3253
|
+
// 2. Get PRF output during authentication
|
|
3254
|
+
const assertion = await navigator.credentials.get({
|
|
3255
|
+
publicKey: {
|
|
3256
|
+
challenge: new Uint8Array(32),
|
|
3257
|
+
extensions: {
|
|
3258
|
+
prf: {
|
|
3259
|
+
eval: {
|
|
3260
|
+
first: new Uint8Array(32) // Salt for PRF
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
});
|
|
3266
|
+
|
|
3267
|
+
const prfOutput = assertion.getClientExtensionResults().prf.results.first;
|
|
3268
|
+
|
|
3269
|
+
// 3. Generate and store salt
|
|
3270
|
+
const salt = generateSalt(); // Store this with your ciphertext
|
|
3271
|
+
|
|
3272
|
+
// 4. Derive encryption key
|
|
3273
|
+
const encryptionKey = await deriveEncryptionKeyFromWebAuthn(
|
|
3274
|
+
prfOutput,
|
|
3275
|
+
salt
|
|
3276
|
+
);
|
|
3277
|
+
|
|
3278
|
+
// 5. Encrypt sensitive data
|
|
3279
|
+
const encrypted = await crypto.subtle.encrypt(
|
|
3280
|
+
{ name: "AES-GCM", iv: crypto.getRandomValues(new Uint8Array(12)) },
|
|
3281
|
+
encryptionKey,
|
|
3282
|
+
new TextEncoder().encode("secret data")
|
|
3283
|
+
);
|
|
3284
|
+
```
|
|
3285
|
+
|
|
3286
|
+
#### `generateSalt(): Uint8Array`
|
|
3287
|
+
|
|
3288
|
+
Generates a cryptographically secure random 32-byte salt for encryption.
|
|
3289
|
+
|
|
3290
|
+
**Returns:** `Uint8Array` - 32 random bytes
|
|
3291
|
+
|
|
3292
|
+
**Example:**
|
|
3293
|
+
```typescript
|
|
3294
|
+
const salt = generateSalt();
|
|
3295
|
+
// Store salt alongside ciphertext for later decryption
|
|
3211
3296
|
```
|
|
3212
3297
|
|
|
3213
3298
|
#### `extractRS(derSignature: Uint8Array): { r: string; s: string }`
|
|
@@ -3265,6 +3350,189 @@ This ensures compatibility with Ethereum's signature malleability protection.
|
|
|
3265
3350
|
|
|
3266
3351
|
---
|
|
3267
3352
|
|
|
3353
|
+
### Post-Quantum Cryptography
|
|
3354
|
+
|
|
3355
|
+
w3pk provides ML-KEM-1024 encryption both as instance methods (recommended) and standalone functions.
|
|
3356
|
+
|
|
3357
|
+
#### Instance Methods (Recommended)
|
|
3358
|
+
|
|
3359
|
+
Use these methods with your w3pk wallet instance - no private key exposure:
|
|
3360
|
+
|
|
3361
|
+
##### `w3pk.deriveMLKemPublicKey(options?): Promise<string>`
|
|
3362
|
+
|
|
3363
|
+
Derive your ML-KEM-1024 public key for sharing with others.
|
|
3364
|
+
|
|
3365
|
+
**Parameters:**
|
|
3366
|
+
```typescript
|
|
3367
|
+
{
|
|
3368
|
+
context?: string; // Domain separation (default: 'mlkem-v1')
|
|
3369
|
+
mode?: SecurityMode; // 'STANDARD', 'STRICT', or 'YOLO' (default: 'STANDARD')
|
|
3370
|
+
tag?: string; // Address derivation tag (default: 'MAIN')
|
|
3371
|
+
origin?: string; // Origin (default: current origin)
|
|
3372
|
+
requireAuth?: boolean; // Force re-auth (default: false, always true in STRICT)
|
|
3373
|
+
}
|
|
3374
|
+
```
|
|
3375
|
+
|
|
3376
|
+
**Returns:** Base64-encoded public key (1568 bytes)
|
|
3377
|
+
|
|
3378
|
+
**Example:**
|
|
3379
|
+
```typescript
|
|
3380
|
+
const w3pk = createWeb3Passkey();
|
|
3381
|
+
await w3pk.login();
|
|
3382
|
+
|
|
3383
|
+
const myPubKey = await w3pk.deriveMLKemPublicKey();
|
|
3384
|
+
// Share myPubKey with others
|
|
3385
|
+
```
|
|
3386
|
+
|
|
3387
|
+
**Note:** PRIMARY mode not supported (uses P-256 WebAuthn keys).
|
|
3388
|
+
|
|
3389
|
+
---
|
|
3390
|
+
|
|
3391
|
+
##### `w3pk.mlkemEncrypt(plaintext: string, recipientPublicKeys: Array<string | Uint8Array>, options?): Promise<EncryptedPayload>`
|
|
3392
|
+
|
|
3393
|
+
Encrypt data for yourself and additional recipients.
|
|
3394
|
+
|
|
3395
|
+
**Parameters:**
|
|
3396
|
+
```typescript
|
|
3397
|
+
{
|
|
3398
|
+
context?: string; // Domain separation (default: 'mlkem-v1')
|
|
3399
|
+
mode?: SecurityMode; // Security mode (default: 'STANDARD')
|
|
3400
|
+
tag?: string; // Tag (default: 'MAIN')
|
|
3401
|
+
origin?: string; // Origin (default: current)
|
|
3402
|
+
requireAuth?: boolean; // Force re-auth (default: false)
|
|
3403
|
+
}
|
|
3404
|
+
```
|
|
3405
|
+
|
|
3406
|
+
**Example:**
|
|
3407
|
+
```typescript
|
|
3408
|
+
const serverPubKey = await fetch('/api/mlkem-key').then(r => r.text());
|
|
3409
|
+
|
|
3410
|
+
const encrypted = await w3pk.mlkemEncrypt(
|
|
3411
|
+
'my secret data',
|
|
3412
|
+
[serverPubKey] // You + server can both decrypt
|
|
3413
|
+
);
|
|
3414
|
+
```
|
|
3415
|
+
|
|
3416
|
+
---
|
|
3417
|
+
|
|
3418
|
+
##### `w3pk.mlkemDecrypt(payload: EncryptedPayload, options?): Promise<string>`
|
|
3419
|
+
|
|
3420
|
+
Decrypt data encrypted for your wallet.
|
|
3421
|
+
|
|
3422
|
+
**Parameters:** Same options as `mlkemEncrypt`
|
|
3423
|
+
|
|
3424
|
+
**Example:**
|
|
3425
|
+
```typescript
|
|
3426
|
+
const plaintext = await w3pk.mlkemDecrypt(encrypted);
|
|
3427
|
+
```
|
|
3428
|
+
|
|
3429
|
+
---
|
|
3430
|
+
|
|
3431
|
+
#### Standalone Functions
|
|
3432
|
+
|
|
3433
|
+
Low-level functions for direct key management:
|
|
3434
|
+
|
|
3435
|
+
```typescript
|
|
3436
|
+
import { mlkemEncrypt, mlkemDecrypt, deriveMLKemKeypair, type EncryptedPayload } from 'w3pk'
|
|
3437
|
+
```
|
|
3438
|
+
|
|
3439
|
+
##### `deriveMLKemKeypair(privateKey: string | Uint8Array, context?: string): Promise<MLKemKeypair>`
|
|
3440
|
+
|
|
3441
|
+
Derive deterministic ML-KEM keypair from any private key using HKDF-SHA256.
|
|
3442
|
+
|
|
3443
|
+
**Parameters:**
|
|
3444
|
+
- `privateKey` - Ethereum private key (hex with optional `0x` prefix or Uint8Array)
|
|
3445
|
+
- `context` - Context string for domain separation (default: `'mlkem-v1'`)
|
|
3446
|
+
|
|
3447
|
+
**Returns:**
|
|
3448
|
+
```typescript
|
|
3449
|
+
{
|
|
3450
|
+
publicKey: Uint8Array; // 1568 bytes
|
|
3451
|
+
privateKey: Uint8Array; // 3168 bytes
|
|
3452
|
+
}
|
|
3453
|
+
```
|
|
3454
|
+
|
|
3455
|
+
**Example:**
|
|
3456
|
+
```typescript
|
|
3457
|
+
const keypair = await deriveMLKemKeypair('0x1234...', 'my-app');
|
|
3458
|
+
```
|
|
3459
|
+
|
|
3460
|
+
---
|
|
3461
|
+
|
|
3462
|
+
##### `mlkemEncrypt(plaintext: string, publicKeys: (string | Uint8Array) | Array<string | Uint8Array>): Promise<EncryptedPayload>`
|
|
3463
|
+
|
|
3464
|
+
Encrypt data using ML-KEM-1024 (post-quantum KEM) + AES-256-GCM for one or more recipients.
|
|
3465
|
+
|
|
3466
|
+
**Parameters:**
|
|
3467
|
+
- `plaintext: string` - The data to encrypt
|
|
3468
|
+
- `publicKeys: (string | Uint8Array) | Array<string | Uint8Array>` - Single public key or array of ML-KEM-1024 public keys (1568 bytes each)
|
|
3469
|
+
|
|
3470
|
+
**Returns:**
|
|
3471
|
+
```typescript
|
|
3472
|
+
{
|
|
3473
|
+
recipients: Array<{
|
|
3474
|
+
publicKey: string; // Base64 recipient public key (1568 bytes)
|
|
3475
|
+
ciphertext: string; // Base64 ML-KEM ciphertext for this recipient (1600 bytes)
|
|
3476
|
+
}>;
|
|
3477
|
+
encryptedData: string; // Base64 AES-encrypted data (shared across all recipients)
|
|
3478
|
+
iv: string; // Base64 initialization vector (12 bytes)
|
|
3479
|
+
authTag: string; // Base64 authentication tag (16 bytes)
|
|
3480
|
+
}
|
|
3481
|
+
```
|
|
3482
|
+
|
|
3483
|
+
**Example:**
|
|
3484
|
+
|
|
3485
|
+
```typescript
|
|
3486
|
+
import { mlkemEncrypt } from 'w3pk'
|
|
3487
|
+
|
|
3488
|
+
// Single recipient
|
|
3489
|
+
const encrypted = await mlkemEncrypt('my secret data', publicKey1)
|
|
3490
|
+
|
|
3491
|
+
// Multiple recipients
|
|
3492
|
+
const encrypted = await mlkemEncrypt('my secret data', [publicKey1, publicKey2, publicKey3])
|
|
3493
|
+
|
|
3494
|
+
console.log('Recipients:', encrypted.recipients.length)
|
|
3495
|
+
```
|
|
3496
|
+
|
|
3497
|
+
**Security:**
|
|
3498
|
+
- ✅ **Quantum-resistant** - ML-KEM-1024 (NIST FIPS 203)
|
|
3499
|
+
- ✅ **Multi-recipient support** - Encrypt once for multiple recipients
|
|
3500
|
+
- ✅ **Key zeroization** - All secrets securely wiped from memory
|
|
3501
|
+
- ✅ **Authenticated encryption** - AES-256-GCM with 128-bit auth tag
|
|
3502
|
+
|
|
3503
|
+
---
|
|
3504
|
+
|
|
3505
|
+
#### `mlkemDecrypt(payload: EncryptedPayload, privateKey: string | Uint8Array, publicKey?: string | Uint8Array): Promise<string>`
|
|
3506
|
+
|
|
3507
|
+
Decrypt data encrypted with `mlkemEncrypt()`.
|
|
3508
|
+
|
|
3509
|
+
**Parameters:**
|
|
3510
|
+
- `payload: EncryptedPayload` - The encrypted payload from `mlkemEncrypt()`
|
|
3511
|
+
- `privateKey: string | Uint8Array` - ML-KEM-1024 private key (3168 bytes)
|
|
3512
|
+
- `publicKey?: string | Uint8Array` - (Optional) Your public key for faster recipient lookup (1568 bytes)
|
|
3513
|
+
|
|
3514
|
+
**Returns:** `string` - Decrypted plaintext
|
|
3515
|
+
|
|
3516
|
+
**Example:**
|
|
3517
|
+
|
|
3518
|
+
```typescript
|
|
3519
|
+
import { mlkemDecrypt } from 'w3pk'
|
|
3520
|
+
|
|
3521
|
+
// Auto-detect which recipient you are
|
|
3522
|
+
const plaintext = await mlkemDecrypt(encrypted, privateKey)
|
|
3523
|
+
|
|
3524
|
+
// Or specify your public key for faster lookup
|
|
3525
|
+
const plaintext = await mlkemDecrypt(encrypted, privateKey, myPublicKey)
|
|
3526
|
+
```
|
|
3527
|
+
|
|
3528
|
+
**Throws:** Error if decryption fails (invalid key, no matching recipient, corrupted data, or tampered auth tag)
|
|
3529
|
+
|
|
3530
|
+
**Related:**
|
|
3531
|
+
- See [POST_QUANTUM.md](../docs/POST_QUANTUM.md) for full quantum readiness roadmap
|
|
3532
|
+
- See [NIST FIPS 203](https://csrc.nist.gov/pubs/fips/203/final) for ML-KEM specification
|
|
3533
|
+
|
|
3534
|
+
---
|
|
3535
|
+
|
|
3268
3536
|
### Wallet Generation Utilities
|
|
3269
3537
|
|
|
3270
3538
|
```typescript
|