mirror of
https://github.com/amark/gun.git
synced 2025-03-30 15:08:33 +00:00

* 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>
172 lines
6.8 KiB
JavaScript
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;
|
|
|
|
}()); |