diff --git a/build-pq-bundle.js b/build-pq-bundle.js deleted file mode 100644 index 798c84a..0000000 --- a/build-pq-bundle.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Build script for the post-quantum crypto bundle. - * Bundles pq-crypto.mjs and all its dependencies into a single - * ESM file that can be loaded directly in the browser. - * - * Usage: node build-pq-bundle.js - */ - -const esbuild = require('esbuild'); -const path = require('path'); - -async function build() { - console.log('๐ง Building PQ crypto bundle...'); - - await esbuild.build({ - entryPoints: ['www/js/pq-crypto.mjs'], - bundle: true, - format: 'esm', - target: ['es2020'], - outfile: 'www/pq-crypto.bundle.js', - sourcemap: true, - minify: false, // keep readable for demo - logLevel: 'info', - // Pure JS โ no WASM files to handle - define: { - 'process.env.NODE_ENV': '"production"' - } - }); - - console.log('โ PQ crypto bundle built: www/pq-crypto.bundle.js'); -} - -build().catch(err => { - console.error('โ Build failed:', err); - process.exit(1); -}); diff --git a/www/js/pq-crypto.mjs b/www/js/pq-crypto.mjs deleted file mode 100644 index 3c20bca..0000000 --- a/www/js/pq-crypto.mjs +++ /dev/null @@ -1,351 +0,0 @@ -/** - * Post-Quantum Crypto Module for Nostr - * - * Provides: - * - BIP39 seed phrase generation - * - NIP-06 key derivation (secp256k1 from seed) - * - PQ key derivation from seed (ML-DSA-65, SLH-DSA-128s, ML-KEM-768) - * - PQ signing (ML-DSA, SLH-DSA) - * - NIP-QR event construction - * - * Uses @noble/post-quantum (pure JS, no WASM needed) - */ - -import { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from '@scure/bip39'; -import { wordlist } from '@scure/bip39/wordlists/english.js'; -import { HDKey } from '@scure/bip32'; -import { hkdf } from '@noble/hashes/hkdf.js'; -import { sha256 as sha256Hash, sha512 as sha512Hash } from '@noble/hashes/sha2.js'; -import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js'; -import { slh_dsa_sha2_128s } from '@noble/post-quantum/slh-dsa.js'; -import { ml_kem768 } from '@noble/post-quantum/ml-kem.js'; - -// ============================================================================ -// BIP39 SEED PHRASE -// ============================================================================ - -/** - * Generate a new 12-word BIP39 mnemonic. - * @returns {string} 12-word seed phrase - */ -export function generateSeedPhrase() { - return generateMnemonic(wordlist, 128); // 128 bits = 12 words -} - -/** - * Convert a mnemonic to a 64-byte BIP39 seed (PBKDF2-HMAC-SHA512). - * @param {string} mnemonic - 12/24 word seed phrase - * @param {string} [passphrase=''] - optional BIP39 passphrase - * @returns {Uint8Array} 64-byte seed - */ -export function mnemonicToSeed(mnemonic, passphrase = '') { - if (!validateMnemonic(mnemonic, wordlist)) { - throw new Error('Invalid mnemonic'); - } - return mnemonicToSeedSync(mnemonic, passphrase); -} - -/** - * Validate a BIP39 mnemonic. - * @param {string} mnemonic - * @returns {boolean} - */ -export function isValidMnemonic(mnemonic) { - return validateMnemonic(mnemonic, wordlist); -} - -// ============================================================================ -// NIP-06 KEY DERIVATION (secp256k1 from seed) -// ============================================================================ - -/** - * Derive a secp256k1 keypair from a BIP39 seed using NIP-06. - * Path: m/44'/1237'/0'/0/0 - * - * @param {Uint8Array} seed - 64-byte BIP39 seed - * @param {number} [accountIndex=0] - account index - * @returns {{privateKey: Uint8Array, publicKey: Uint8Array}} secp256k1 keypair - */ -export function deriveSecp256k1FromSeed(seed, accountIndex = 0) { - const hdKey = HDKey.fromMasterSeed(seed); - const path = `m/44'/1237'/${accountIndex}'/0/0`; - const child = hdKey.derive(path); - if (!child.privateKey) { - throw new Error('Failed to derive private key'); - } - return { - privateKey: child.privateKey, - publicKey: child.publicKey - }; -} - -// ============================================================================ -// PQ KEY DERIVATION FROM SEED -// ============================================================================ - -/** - * Derive PQ key seeds from a BIP39 seed using HKDF. - * Each algorithm gets a unique label so keys are independent. - * - * @param {Uint8Array} bip39Seed - 64-byte BIP39 seed - * @param {string} label - algorithm label (e.g. 'nostr-pq-ml-dsa-65') - * @param {number} length - output length in bytes - * @returns {Uint8Array} deterministic seed for PQ keygen - */ -function derivePQSeed(bip39Seed, label, length) { - const info = new TextEncoder().encode(label); - return hkdf(sha512Hash, bip39Seed, undefined, info, length); -} - -/** - * Derive all PQ keypairs from a BIP39 seed. - * - * @param {Uint8Array} bip39Seed - 64-byte BIP39 seed - * @returns {{ - * mlDsa: {publicKey: Uint8Array, secretKey: Uint8Array}, - * slhDsa: {publicKey: Uint8Array, secretKey: Uint8Array}, - * mlKem: {publicKey: Uint8Array, secretKey: Uint8Array} - * }} - */ -export function derivePQKeysFromSeed(bip39Seed) { - // ML-DSA-65 needs 32-byte seed - const mlDsaSeed = derivePQSeed(bip39Seed, 'nostr-pq-ml-dsa-65', 32); - const mlDsa = ml_dsa65.keygen(mlDsaSeed); - - // SLH-DSA-128s needs 48-byte seed (3 * 16 for sk seed, pk seed, etc.) - const slhDsaSeed = derivePQSeed(bip39Seed, 'nostr-pq-slh-dsa-128s', 48); - const slhDsa = slh_dsa_sha2_128s.keygen(slhDsaSeed); - - // ML-KEM-768 needs 64-byte seed - const mlKemSeed = derivePQSeed(bip39Seed, 'nostr-pq-ml-kem-768', 64); - const mlKem = ml_kem768.keygen(mlKemSeed); - - return { mlDsa, slhDsa, mlKem }; -} - -// ============================================================================ -// PQ SIGNING -// ============================================================================ - -/** - * Sign a message with ML-DSA-65. - * @param {Uint8Array} message - * @param {Uint8Array} secretKey - * @returns {Uint8Array} signature - */ -export function signWithMLDSA(message, secretKey) { - return ml_dsa65.sign(message, secretKey); -} - -/** - * Verify an ML-DSA-65 signature. - * @param {Uint8Array} signature - * @param {Uint8Array} message - * @param {Uint8Array} publicKey - * @returns {boolean} - */ -export function verifyMLDSA(signature, message, publicKey) { - return ml_dsa65.verify(signature, message, publicKey); -} - -/** - * Sign a message with SLH-DSA-128s. - * @param {Uint8Array} message - * @param {Uint8Array} secretKey - * @returns {Uint8Array} signature - */ -export function signWithSLHDSA(message, secretKey) { - return slh_dsa_sha2_128s.sign(message, secretKey); -} - -/** - * Verify an SLH-DSA-128s signature. - * @param {Uint8Array} signature - * @param {Uint8Array} message - * @param {Uint8Array} publicKey - * @returns {boolean} - */ -export function verifySLHDSA(signature, message, publicKey) { - return slh_dsa_sha2_128s.verify(signature, message, publicKey); -} - -// ============================================================================ -// UTILITIES -// ============================================================================ - -/** - * Convert Uint8Array to base64 string. - * @param {Uint8Array} bytes - * @returns {string} - */ -export function bytesToBase64(bytes) { - let binary = ''; - for (let i = 0; i < bytes.length; i++) { - binary += String.fromCharCode(bytes[i]); - } - return btoa(binary); -} - -/** - * Convert base64 string to Uint8Array. - * @param {string} base64 - * @returns {Uint8Array} - */ -export function base64ToBytes(base64) { - const binary = atob(base64); - const bytes = new Uint8Array(binary.length); - for (let i = 0; i < binary.length; i++) { - bytes[i] = binary.charCodeAt(i); - } - return bytes; -} - -/** - * Convert Uint8Array to hex string. - * @param {Uint8Array} bytes - * @returns {string} - */ -export function bytesToHex(bytes) { - return Array.from(bytes) - .map(b => b.toString(16).padStart(2, '0')) - .join(''); -} - -/** - * Convert hex string to Uint8Array. - * @param {string} hex - * @returns {Uint8Array} - */ -export function hexToBytes(hex) { - const bytes = new Uint8Array(hex.length / 2); - for (let i = 0; i < hex.length; i += 2) { - bytes[i / 2] = parseInt(hex.substr(i, 2), 16); - } - return bytes; -} - -// ============================================================================ -// NIP-QR EVENT CONSTRUCTION -// ============================================================================ - -/** - * Build the NIP-QR event content (the JSON that goes in the event's content field). - * - * The content contains: - * - A link statement - * - All PQ public keys - * - PQ signatures over the statement - * - The ML-KEM public key (no signature โ KEM can't sign) - * - * @param {string} npub - The user's Nostr npub (hex pubkey) - * @param {string} successorNpub - The successor's hex pubkey (for Path B), or null for Path A - * @param {{mlDsa: *, slhDsa: *, mlKem: *}} pqKeys - PQ keypairs - * @returns {{statement: string, content: object, statementBytes: Uint8Array}} - */ -export function buildNIPQRContent(npub, successorNpub, pqKeys) { - let statement; - if (successorNpub) { - // Path B: migration from old nsec to seed-derived key - statement = `Identity ${npub} is migrating to successor ${successorNpub}. All PQ keys listed below are derived from the same BIP39 seed as ${successorNpub}. This link is established pre-quantum.`; - } else { - // Path A: direct link (identity already seed-derived) - statement = `Identity ${npub} is linked to the following PQ keys, all derived from the same BIP39 seed. This link is established pre-quantum.`; - } - - const statementBytes = new TextEncoder().encode(statement); - - // Sign the statement with each PQ signature scheme - const mlDsaSig = signWithMLDSA(statementBytes, pqKeys.mlDsa.secretKey); - const slhDsaSig = signWithSLHDSA(statementBytes, pqKeys.slhDsa.secretKey); - - const content = { - statement, - pq_keys: [ - { - algorithm: 'ml-dsa-65', - public_key: bytesToBase64(pqKeys.mlDsa.publicKey), - signature: bytesToBase64(mlDsaSig) - }, - { - algorithm: 'slh-dsa-128s', - public_key: bytesToBase64(pqKeys.slhDsa.publicKey), - signature: bytesToBase64(slhDsaSig) - }, - { - algorithm: 'ml-kem-768', - public_key: bytesToBase64(pqKeys.mlKem.publicKey), - note: 'KEM key for encryption; ownership asserted by secp256k1 signature over this content' - } - ] - }; - - // If Path B, include successor info - if (successorNpub) { - content.successor_pubkey = successorNpub; - } - - return { statement, content, statementBytes }; -} - -/** - * Verify a NIP-QR event's PQ signatures. - * @param {object} content - The parsed content object - * @returns {{valid: boolean, results: Array}} verification results - */ -export function verifyNIPQRContent(content) { - const results = []; - - for (const keyEntry of content.pq_keys) { - if (keyEntry.algorithm === 'ml-kem-768') { - // KEM can't sign โ skip verification - results.push({ algorithm: keyEntry.algorithm, valid: true, note: 'KEM (no signature to verify)' }); - continue; - } - - const pubKey = base64ToBytes(keyEntry.public_key); - const sig = base64ToBytes(keyEntry.signature); - const msg = new TextEncoder().encode(content.statement); - - let valid = false; - if (keyEntry.algorithm === 'ml-dsa-65') { - valid = verifyMLDSA(sig, msg, pubKey); - } else if (keyEntry.algorithm === 'slh-dsa-128s') { - valid = verifySLHDSA(sig, msg, pubKey); - } - - results.push({ algorithm: keyEntry.algorithm, valid }); - } - - return { - valid: results.every(r => r.valid), - results - }; -} - -// ============================================================================ -// KEY SIZE INFO (for display) -// ============================================================================ - -export const PQ_KEY_INFO = { - 'ml-dsa-65': { - name: 'ML-DSA-65 (Dilithium)', - publicKeySize: 1952, - signatureSize: 3309, - fips: 'FIPS 204', - type: 'signature' - }, - 'slh-dsa-128s': { - name: 'SLH-DSA-128s (SPHINCS+)', - publicKeySize: 32, - signatureSize: 7856, - fips: 'FIPS 205', - type: 'signature' - }, - 'ml-kem-768': { - name: 'ML-KEM-768 (Kyber)', - publicKeySize: 1184, - ciphertextSize: 1088, - fips: 'FIPS 203', - type: 'kem' - } -}; diff --git a/www/js/version.json b/www/js/version.json index ee89121..eb1c786 100644 --- a/www/js/version.json +++ b/www/js/version.json @@ -1,5 +1,5 @@ { - "VERSION": "v0.7.94", - "VERSION_NUMBER": "0.7.94", - "BUILD_DATE": "2026-07-12T14:10:59.968Z" + "VERSION": "v0.7.95", + "VERSION_NUMBER": "0.7.95", + "BUILD_DATE": "2026-07-31T10:32:03.777Z" } diff --git a/www/post-quantum.html b/www/post-quantum.html deleted file mode 100644 index b4f5c7d..0000000 --- a/www/post-quantum.html +++ /dev/null @@ -1,1135 +0,0 @@ - - - - -
- -