gun/sea/pair.js
akaoio f0cce073a8
New SEA features! (many new features) (#1400)
* feat: create pair with seed, content addressing with shorter hash

* feat: create pair using priv/epriv

* optimize SEA.pair

* feat: globalThis along with window

* white labeling

* feat: add WebAuthn example and enhance SEA.sign, SEA.verify, SEA check.pub, for WebAuthn support

* feat: enhance WebAuthn integration with new put options and improved signature handling

* polish SEA.sign and SEA.verify

* feat: localize options in SEA.check.pub to enhance security and prevent attacks

* fix: correct destructuring of user object to enhance security in SEA

* rebuild SEA

* feat: support ArrayBuffer as seed for key pair generation in SEA

* test: add unit test for hashing ArrayBuffer in SEA

* fix: create deterministic key pair from seed

* fix: add missing B parameter for ECC curve and implement point validation

* feat: add ArrayBuffer support for hashing in SEA and implement corresponding unit test

* fix: convert numeric salt to string in PBKDF2 implementation

* fix: convert numeric salt option to string in PBKDF2 implementation

* improve hashing tests

* improve sea.work

* rebuild SEA

* improve SEA.work and rebuild SEA

* enhance SEA encryption handling and improve test coverage for SEA functions

---------

Co-authored-by: noname <x@null.com>
Co-authored-by: x <x@mimiza.com>
Co-authored-by: x <null>
Co-authored-by: noname <no@name.com>
2025-03-24 11:41:36 -07:00

172 lines
6.8 KiB
JavaScript

;(function(){
var SEA = require('./root');
var shim = require('./shim');
// P-256 curve constants
const n = BigInt("0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551");
const P = BigInt("0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff");
const A = BigInt("0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc");
const B = BigInt("0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b"); // Missing B parameter
const G = {
x: BigInt("0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296"),
y: BigInt("0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5")
};
// Core ECC functions
function mod(a, m) { return ((a % m) + m) % m; }
// Constant-time modular inverse using Fermat's Little Theorem (p is prime)
function modInv(a, p) {
// a^(p-2) mod p
return modPow(a, p - BigInt(2), p);
}
// Constant-time modular exponentiation (square-and-multiply)
function modPow(base, exponent, modulus) {
if (modulus === BigInt(1)) return BigInt(0);
base = mod(base, modulus);
let result = BigInt(1);
while (exponent > BigInt(0)) {
if (exponent & BigInt(1)) {
result = mod(result * base, modulus);
}
exponent >>= BigInt(1);
base = mod(base * base, modulus);
}
return result;
}
// Verify a point is on the curve
function isOnCurve(point) {
if (!point) return false;
// y² = x³ + ax + b (mod p)
const { x, y } = point;
const left = mod(y * y, P);
const right = mod(mod(mod(x * x, P) * x, P) + mod(A * x, P) + B, P);
return left === right;
}
function pointAdd(p1, p2) {
if (p1 === null) return p2; if (p2 === null) return p1;
if (p1.x === p2.x && mod(p1.y + p2.y, P) === 0n) return null;
let lambda = p1.x === p2.x && p1.y === p2.y
? mod((3n * mod(p1.x ** 2n, P) + A) * modInv(2n * p1.y, P), P)
: mod((mod(p2.y - p1.y, P)) * modInv(mod(p2.x - p1.x, P), P), P);
const x3 = mod(lambda ** 2n - p1.x - p2.x, P);
return { x: x3, y: mod(lambda * mod(p1.x - x3, P) - p1.y, P) };
}
function pointMult(k, point) {
let r = null, a = point;
while (k > 0n) {
if (k & 1n) r = pointAdd(r, a);
a = pointAdd(a, a);
k >>= 1n;
}
return r;
}
SEA.pair = SEA.pair || (async (cb, opt) => { try {
opt = opt || {};
const subtle = shim.subtle, ecdhSubtle = shim.ossl || subtle;
let r = {};
// Helper functions
const b64ToBI = s => {
let b64 = s.replace(/-/g, '+').replace(/_/g, '/');
while (b64.length % 4) b64 += '=';
return BigInt('0x' + shim.Buffer.from(b64, 'base64').toString('hex'));
};
const biToB64 = n => shim.Buffer.from(n.toString(16).padStart(64, '0'), 'hex')
.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
const pubFromPriv = priv => {
const pub = pointMult(priv, G);
if (!isOnCurve(pub)) throw new Error("Invalid point generated");
return biToB64(pub.x) + '.' + biToB64(pub.y);
};
const seedToKey = async (seed, salt) => {
const enc = new shim.TextEncoder();
const buf = typeof seed === 'string' ? enc.encode(seed).buffer :
seed instanceof ArrayBuffer ? seed :
seed && seed.byteLength !== undefined ? (seed.buffer || seed) : null;
if (!buf) throw new Error("Invalid seed");
const combined = new Uint8Array(buf.byteLength + enc.encode(salt).buffer.byteLength);
combined.set(new Uint8Array(buf), 0);
combined.set(new Uint8Array(enc.encode(salt).buffer), buf.byteLength);
const hash = await subtle.digest("SHA-256", combined.buffer);
let priv = BigInt("0x" + Array.from(new Uint8Array(hash))
.map(b => b.toString(16).padStart(2, "0")).join("")) % n;
if (priv <= 0n || priv >= n) priv = (priv + 1n) % n;
return priv;
};
if (opt.priv) {
const priv = b64ToBI(opt.priv);
r = { priv: opt.priv, pub: pubFromPriv(priv) };
if (opt.epriv) {
r.epriv = opt.epriv;
r.epub = pubFromPriv(b64ToBI(opt.epriv));
} else {
try {
const dh = await ecdhSubtle.generateKey({name: 'ECDH', namedCurve: 'P-256'}, true, ['deriveKey'])
.then(async k => ({
epriv: (await ecdhSubtle.exportKey('jwk', k.privateKey)).d,
epub: (await ecdhSubtle.exportKey('jwk', k.publicKey)).x + '.' +
(await ecdhSubtle.exportKey('jwk', k.publicKey)).y
}));
r.epriv = dh.epriv; r.epub = dh.epub;
} catch(e) {}
}
} else if (opt.epriv) {
r = { epriv: opt.epriv, epub: pubFromPriv(b64ToBI(opt.epriv)) };
if (opt.priv) {
r.priv = opt.priv;
r.pub = pubFromPriv(b64ToBI(opt.priv));
} else {
const sa = await subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, ['sign', 'verify'])
.then(async k => ({
priv: (await subtle.exportKey('jwk', k.privateKey)).d,
pub: (await subtle.exportKey('jwk', k.publicKey)).x + '.' +
(await subtle.exportKey('jwk', k.publicKey)).y
}));
r.priv = sa.priv; r.pub = sa.pub;
}
} else if (opt.seed) {
const signPriv = await seedToKey(opt.seed, "-sign");
const encPriv = await seedToKey(opt.seed, "-encrypt");
r = {
priv: biToB64(signPriv), pub: pubFromPriv(signPriv),
epriv: biToB64(encPriv), epub: pubFromPriv(encPriv)
};
} else {
const sa = await subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, ['sign', 'verify'])
.then(async k => ({
priv: (await subtle.exportKey('jwk', k.privateKey)).d,
pub: (await subtle.exportKey('jwk', k.publicKey)).x + '.' +
(await subtle.exportKey('jwk', k.publicKey)).y
}));
r = { pub: sa.pub, priv: sa.priv };
try {
const dh = await ecdhSubtle.generateKey({name: 'ECDH', namedCurve: 'P-256'}, true, ['deriveKey'])
.then(async k => ({
epriv: (await ecdhSubtle.exportKey('jwk', k.privateKey)).d,
epub: (await ecdhSubtle.exportKey('jwk', k.publicKey)).x + '.' +
(await ecdhSubtle.exportKey('jwk', k.publicKey)).y
}));
r.epub = dh.epub; r.epriv = dh.epriv;
} catch(e) {}
}
if(cb) try{ cb(r) }catch(e){ console.log(e) }
return r;
} catch(e) {
SEA.err = e;
if(SEA.throw) throw e;
if(cb) cb();
return;
}});
module.exports = SEA.pair;
}());