enki b3204ea07a
Some checks are pending
CI Pipeline / Run Tests (push) Waiting to run
CI Pipeline / Lint Code (push) Waiting to run
CI Pipeline / Security Scan (push) Waiting to run
CI Pipeline / Build Docker Images (push) Blocked by required conditions
CI Pipeline / E2E Tests (push) Blocked by required conditions
first commit
2025-08-18 00:40:15 -07:00

201 lines
5.8 KiB
JavaScript

// 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
};