// Nostr cryptographic utilities for key generation and encoding // This implements proper secp256k1 key generation and bech32 encoding // bech32 encoding implementation const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'; const GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]; function bech32Polymod(values) { let chk = 1; for (let i = 0; i < values.length; i++) { const top = chk >> 25; chk = (chk & 0x1ffffff) << 5 ^ values[i]; for (let j = 0; j < 5; j++) { chk ^= ((top >> j) & 1) ? GENERATOR[j] : 0; } } return chk; } function bech32HrpExpand(hrp) { const ret = []; for (let i = 0; i < hrp.length; i++) { ret.push(hrp.charCodeAt(i) >> 5); } ret.push(0); for (let i = 0; i < hrp.length; i++) { ret.push(hrp.charCodeAt(i) & 31); } return ret; } function bech32CreateChecksum(hrp, data) { const values = bech32HrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]); const mod = bech32Polymod(values) ^ 1; const ret = []; for (let i = 0; i < 6; i++) { ret.push((mod >> 5 * (5 - i)) & 31); } return ret; } function bech32Encode(hrp, data) { const combined = data.concat(bech32CreateChecksum(hrp, data)); let ret = hrp + '1'; for (let i = 0; i < combined.length; i++) { ret += CHARSET.charAt(combined[i]); } return ret; } function convertBits(data, fromBits, toBits, pad = true) { let acc = 0; let bits = 0; const ret = []; const maxv = (1 << toBits) - 1; const maxAcc = (1 << (fromBits + toBits - 1)) - 1; for (let i = 0; i < data.length; i++) { const value = data[i]; if (value < 0 || (value >> fromBits) !== 0) { return null; } acc = ((acc << fromBits) | value) & maxAcc; bits += fromBits; while (bits >= toBits) { bits -= toBits; ret.push((acc >> bits) & maxv); } } if (pad) { if (bits > 0) { ret.push((acc << (toBits - bits)) & maxv); } } else if (bits >= fromBits || ((acc << (toBits - bits)) & maxv)) { return null; } return ret; } // secp256k1 point operations (simplified implementation) const CURVE = { p: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2Fn, n: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141n, Gx: 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798n, Gy: 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8n }; function modInverse(a, m) { if (a < 0n) a = ((a % m) + m) % m; const egcd = (a, b) => { if (a === 0n) return [b, 0n, 1n]; const [gcd, x1, y1] = egcd(b % a, a); return [gcd, y1 - (b / a) * x1, x1]; }; const [gcd, x] = egcd(a % m, m); if (gcd !== 1n) throw new Error('Modular inverse does not exist'); return ((x % m) + m) % m; } function pointAdd(p1, p2) { if (!p1) return p2; if (!p2) return p1; const [x1, y1] = p1; const [x2, y2] = p2; if (x1 === x2) { if (y1 === y2) { // Point doubling const s = (3n * x1 * x1 * modInverse(2n * y1, CURVE.p)) % CURVE.p; const x3 = (s * s - 2n * x1) % CURVE.p; const y3 = (s * (x1 - x3) - y1) % CURVE.p; return [(x3 + CURVE.p) % CURVE.p, (y3 + CURVE.p) % CURVE.p]; } else { return null; // Point at infinity } } const s = ((y2 - y1) * modInverse(x2 - x1, CURVE.p)) % CURVE.p; const x3 = (s * s - x1 - x2) % CURVE.p; const y3 = (s * (x1 - x3) - y1) % CURVE.p; return [(x3 + CURVE.p) % CURVE.p, (y3 + CURVE.p) % CURVE.p]; } function pointMultiply(k, point = [CURVE.Gx, CURVE.Gy]) { if (k === 0n) return null; if (k === 1n) return point; let result = null; let addend = point; while (k > 0n) { if (k & 1n) { result = pointAdd(result, addend); } addend = pointAdd(addend, addend); k >>= 1n; } return result; } // Main key generation function async function generateNostrKeyPair() { // Generate random 32-byte private key const privateKeyBytes = crypto.getRandomValues(new Uint8Array(32)); // Ensure the private key is within the valid range for secp256k1 let privateKeyBigInt = 0n; for (let i = 0; i < 32; i++) { privateKeyBigInt = (privateKeyBigInt << 8n) + BigInt(privateKeyBytes[i]); } // Make sure it's less than the curve order if (privateKeyBigInt >= CURVE.n) { privateKeyBigInt = privateKeyBigInt % CURVE.n; } // Convert back to bytes const privateKeyHex = privateKeyBigInt.toString(16).padStart(64, '0'); // Generate public key using secp256k1 point multiplication const publicPoint = pointMultiply(privateKeyBigInt); if (!publicPoint) { throw new Error('Failed to generate public key'); } // Get x-coordinate only (compressed public key) const publicKeyHex = publicPoint[0].toString(16).padStart(64, '0'); // Convert to bech32 encoding const privateKeyBytes32 = []; const publicKeyBytes32 = []; for (let i = 0; i < 64; i += 2) { privateKeyBytes32.push(parseInt(privateKeyHex.substr(i, 2), 16)); publicKeyBytes32.push(parseInt(publicKeyHex.substr(i, 2), 16)); } const privateBech32Data = convertBits(privateKeyBytes32, 8, 5); const publicBech32Data = convertBits(publicKeyBytes32, 8, 5); const nsec = bech32Encode('nsec', privateBech32Data); const npub = bech32Encode('npub', publicBech32Data); return { privateKey: privateKeyHex, publicKey: publicKeyHex, npub: npub, nsec: nsec }; } // Export for use in other scripts window.NostrCrypto = { generateKeyPair: generateNostrKeyPair, bech32Encode, convertBits };