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
201 lines
5.8 KiB
JavaScript
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
|
|
}; |