Skip to content

Instantly share code, notes, and snippets.

@paulmillr
Created November 19, 2024 09:43
Show Gist options
  • Save paulmillr/d9d5fdf8d8c9d7c954e9f26fdd30bc9d to your computer and use it in GitHub Desktop.
Save paulmillr/d9d5fdf8d8c9d7c954e9f26fdd30bc9d to your computer and use it in GitHub Desktop.
/*! noble-secp256k1 - MIT License (c) 2019 Paul Miller (paulmillr.com) */
const B256 = 2n ** 256n; // secp256k1 is short weierstrass curve
const P = B256 - 0x1000003d1n; // curve's field prime
const N = B256 - 0x14551231950b75fc4402da1732fc9bebfn; // curve (group) order
const Gx = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798n; // base point x
const Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8n; // base point y
const CURVE = { p: P, n: N, a: 0n, b: 7n, Gx, Gy }; // exported variables incl. a, b
const fLen = 32; // field / group byte length
const includes = (item, array) => {
for (let part of array) {
if (part === item)
return true;
}
return false;
};
const curve = (x) => M(M(x * x) * x + CURVE.b); // x³ + ax + b weierstrass formula; a=0
const err = (m = '') => { throw new Error(m); }; // error helper, messes-up stack trace
const isB = (n) => typeof n === 'bigint'; // is big integer
const isS = (s) => typeof s === 'string'; // is string
const fe = (n) => isB(n) && 0n < n && n < P; // is field element (invertible)
const ge = (n) => isB(n) && 0n < n && n < N; // is group element
const isu8 = (a) => (a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array'));
const au8 = (a, l) => // assert is Uint8Array (of specific length)
!isu8(a) || (typeof l === 'number' && l > 0 && a.length !== l) ?
err('Uint8Array expected') : a;
const u8n = (data) => new Uint8Array(data); // creates Uint8Array
const toU8 = (a, len) => au8(isS(a) ? h2b(a) : u8n(au8(a)), len); // norm(hex/u8a) to u8a
const M = (a, b = P) => { let r = a % b; return r >= 0n ? r : b + r; }; // mod division
const isPoint = (p) => (p instanceof Point ? p : err('Point expected')); // is 3d point
class Point {
constructor(px, py, pz) {
this.px = px;
this.py = py;
this.pz = pz;
} //3d=less inversions
get x() { return this.aff().x; } // .x, .y will call expensive toAffine:
get y() { return this.aff().y; } // should be used with care.
equals(other) {
const { px: X1, py: Y1, pz: Z1 } = this;
const { px: X2, py: Y2, pz: Z2 } = isPoint(other); // isPoint() checks class equality
const X1Z2 = M(X1 * Z2), X2Z1 = M(X2 * Z1);
const Y1Z2 = M(Y1 * Z2), Y2Z1 = M(Y2 * Z1);
return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
}
negate() { return new Point(this.px, M(-this.py), this.pz); } // Flip point over y coord
double() { return this.add(this); } // Point doubling: P+P, complete formula.
add(other) {
const { px: X1, py: Y1, pz: Z1 } = this; // free formula from Renes-Costello-Batina
const { px: X2, py: Y2, pz: Z2 } = isPoint(other); // https://eprint.iacr.org/2015/1060, algo 1
const { a, b } = CURVE; // Cost: 12M + 0S + 3*a + 3*b3 + 23add
let X3 = 0n, Y3 = 0n, Z3 = 0n;
const b3 = M(b * 3n);
let t0 = M(X1 * X2), t1 = M(Y1 * Y2), t2 = M(Z1 * Z2), t3 = M(X1 + Y1); // step 1
let t4 = M(X2 + Y2); // step 5
t3 = M(t3 * t4);
t4 = M(t0 + t1);
t3 = M(t3 - t4);
t4 = M(X1 + Z1);
let t5 = M(X2 + Z2); // step 10
t4 = M(t4 * t5);
t5 = M(t0 + t2);
t4 = M(t4 - t5);
t5 = M(Y1 + Z1);
X3 = M(Y2 + Z2); // step 15
t5 = M(t5 * X3);
X3 = M(t1 + t2);
t5 = M(t5 - X3);
Z3 = M(a * t4);
X3 = M(b3 * t2); // step 20
Z3 = M(X3 + Z3);
X3 = M(t1 - Z3);
Z3 = M(t1 + Z3);
Y3 = M(X3 * Z3);
t1 = M(t0 + t0); // step 25
t1 = M(t1 + t0);
t2 = M(a * t2);
t4 = M(b3 * t4);
t1 = M(t1 + t2);
t2 = M(t0 - t2); // step 30
t2 = M(a * t2);
t4 = M(t4 + t2);
t0 = M(t1 * t4);
Y3 = M(Y3 + t0);
t0 = M(t5 * t4); // step 35
X3 = M(t3 * X3);
X3 = M(X3 - t0);
t0 = M(t3 * t1);
Z3 = M(t5 * Z3);
Z3 = M(Z3 + t0); // step 40
return new Point(X3, Y3, Z3);
}
mul(n, safe = true) {
if (!safe && n === 0n)
return I; // in unsafe mode, allow zero
if (!ge(n))
err('scalar invalid'); // must be 0 < n < CURVE.n
if (this.equals(G))
return wNAF(n).p; // use precomputes for base point
let p = I, f = G; // init result point & fake point
for (let d = this; n > 0n; d = d.double(), n >>= 1n) { // double-and-add ladder
if (n & 1n)
p = p.add(d); // if bit is present, add to point
else if (safe)
f = f.add(d); // if not, add to fake for timing safety
}
return p;
}
mulAddQUns(R, u1, u2) {
return this.mul(u1, false).add(R.mul(u2, false)).ok(); // Unsafe: do NOT use for stuff related
} // to private keys. Doesn't use Shamir trick
toAffine() {
const { px: x, py: y, pz: z } = this; // (x, y, z) ∋ (x=x/z, y=y/z)
if (this.equals(I))
return { x: 0n, y: 0n }; // fast-path for zero point
if (z === 1n)
return { x, y }; // if z is 1, pass affine coordinates as-is
const iz = inv(z); // z^-1: invert z
if (M(z * iz) !== 1n)
err('inverse invalid'); // (z * z^-1) must be 1, otherwise bad math
return { x: M(x * iz), y: M(y * iz) }; // x = x*z^-1; y = y*z^-1
}
assertValidity() {
const { x, y } = this.aff(); // convert to 2d xy affine point.
if (!fe(x) || !fe(y))
err('Point invalid: x or y'); // x and y must be in range 0 < n < P
return M(y * y) === curve(x) ? // y² = x³ + ax + b, must be equal
this : err('Point invalid: not on curve');
}
multiply(n) { return this.mul(n); } // Aliases to compress code
aff() { return this.toAffine(); }
ok() { return this.assertValidity(); }
toHex(isCompressed = true) {
const { x, y } = this.aff(); // convert to 2d xy affine point
const head = isCompressed ? ((y & 1n) === 0n ? '02' : '03') : '04'; // 0x02, 0x03, 0x04 prefix
return head + n2h(x) + (isCompressed ? '' : n2h(y)); // prefix||x and ||y
}
toRawBytes(isCompressed = true) {
return h2b(this.toHex(isCompressed)); // re-use toHex(), convert hex to bytes
}
}
const Point_BASE = new Point(Gx, Gy, 1n); // Generator / base point
const Point_ZERO = new Point(0n, 1n, 0n); // Identity / zero point
const Point_fromAffine = (p) => {
return ((p.x === 0n) && (p.y === 0n)) ? Point_ZERO : new Point(p.x, p.y, 1n);
};
const Point_fromHex = (hex) => {
hex = toU8(hex); // convert hex string to Uint8Array
const head = hex[0], tail = hex.subarray(1); // first byte is prefix, rest is data
const x = slc(tail, 0, fLen), len = hex.length; // next 32 bytes are x coordinate
if (len === 33 && includes(head, [0x02, 0x03])) { // compressed points: 33b, start
if (!fe(x))
err('Point hex invalid: x not FE'); // with byte 0x02 or 0x03. Check if 0<x<P
let y = sqrt(curve(x)); // x³ + ax + b is right side of equation
const isYOdd = (y & 1n) === 1n; // y² is equivalent left-side. Calculate y²:
const headOdd = (head & 1) === 1; // y = √y²; there are two solutions: y, -y
if (headOdd !== isYOdd)
y = M(-y); // determine proper solution
const p = new Point(x, y, 1n); // create point
return p.ok();
} // Uncompressed points: 65b, start with 0x04
if (len === 65 && head === 0x04) {
let p = new Point(x, slc(tail, fLen, 2 * fLen), 1n);
return p.ok();
}
return err('Point invalid: not on curve'); // Verify the result
};
const Point_fromPrivateKey = (k) => { return G.mul(toPriv(k)); }; // Create point from a private key.
const b = { Point_fromAffine, Point_fromHex, Point_fromPrivateKey };
b;
const G = Point_BASE;
const I = Point_ZERO;
// const { BASE: G, ZERO: I } = Point; // Generator, identity points
const padh = (n, pad) => n.toString(16).padStart(pad, '0');
const b2h = (b) => {
let res = '';
for (let i = 0; i < b.length; i++) {
const byte = b[i];
res += padh(byte, 2);
}
return res;
// Array.from(au8(b)).map(e => padh(e, 2)).join(''); // bytes to hex
};
const C = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 };
const ch = (char) => {
if (char >= C._0 && char <= C._9)
return char - C._0; // '0' will resolve to 48-48, '1' to 49-48 (1)
if (char >= C.A && char <= C.F)
return char - (C.A - 10); // 'A' will resolve to 65-(65-10), 'F' to 70-(70-10)
if (char >= C.a && char <= C.f)
return char - (C.a - 10); // similar to upcase
return;
};
const h2b = (hex) => {
const e = 'hex invalid';
if (!isS(hex))
return err(e);
const hl = hex.length, al = hl / 2;
if (hl % 2)
return err(e);
const array = u8n(al);
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) { // treat each char as ASCII
const n1 = ch(hex.charCodeAt(hi)); // parse first char, multiply it by 16
const n2 = ch(hex.charCodeAt(hi + 1)); // parse second char
if (n1 === undefined || n2 === undefined)
return err(e);
array[ai] = n1 * 16 + n2; // example: 'A9' => 10*16 + 9
}
return array;
};
const b2n = (b) => b.length === 0 ? 0n : BigInt(b2h(b));
const slc = (b, from, to) => b2n(b.slice(from, to)); // slice bytes num
const n2b = (num) => {
return isB(num) && num >= 0n && num < B256 ? h2b(padh(num, 2 * fLen)) : err('bigint expected');
};
const n2h = (num) => b2h(n2b(num)); // number to 32b hex
const concatB = (...arrs) => {
const r = u8n(arrs.reduce((sum, a) => sum + au8(a).length, 0)); // create u8a of summed length
let pad = 0; // walk through each array,
arrs.forEach(a => { r.set(a, pad); pad += a.length; }); // ensure they have proper type
return r;
};
const inv = (num, md = P) => {
if (num === 0n || md <= 0n)
err('no inverse n=' + num + ' mod=' + md); // no neg exponent for now
let a = M(num, md), b = md, x = 0n, y = 1n, u = 1n, v = 0n;
while (a !== 0n) { // uses euclidean gcd algorithm
const q = b / a, r = b % a; // not constant-time
const m = x - u * q, n = y - v * q;
b = a, a = r, x = u, y = v, u = m, v = n;
}
return b === 1n ? M(x, md) : err('no inverse'); // b is gcd at this point
};
const sqrt = (n) => {
let r = 1n; // So, a special, fast case. Paper: "Square Roots from 1;24,51,10 to Dan Shanks".
for (let num = n, e = (P + 1n) / 4n; e > 0n; e >>= 1n) { // powMod: modular exponentiation.
if (e & 1n)
r = (r * num) % P; // Uses exponentiation by squaring.
num = (num * num) % P; // Not constant-time.
}
return M(r * r) === n ? r : err('sqrt invalid'); // check if result is valid
};
const toPriv = (p) => {
if (!isB(p))
p = b2n(toU8(p, fLen)); // convert to bigint when bytes
return ge(p) ? p : err('private key invalid 3'); // check if bigint is in range
};
const high = (n) => n > (N >> 1n); // if a number is bigger than CURVE.n/2
const getPublicKey = (privKey, isCompressed = true) => {
return Point_fromPrivateKey(privKey).toRawBytes(isCompressed); // 33b or 65b output
};
class Signature {
constructor(r, s, recovery = undefined) {
this.r = r;
this.s = s;
this.recovery = recovery;
this.assertValidity(); // recovery bit is optional when
} // constructed outside.
assertValidity() { return ge(this.r) && ge(this.s) ? this : err(); } // 0 < r or s < CURVE.n
addRecoveryBit(rec) {
return new Signature(this.r, this.s, rec);
}
hasHighS() { return high(this.s); }
normalizeS() {
return this.hasHighS() ? new Signature(this.r, M(this.s, N), this.recovery) : this;
}
recoverPublicKey(msgh) {
const { r, s, recovery: rec } = this; // secg.org/sec1-v2.pdf 4.1.6
if (!includes(rec, [0, 1, 2, 3]))
err('recovery id invalid'); // check recovery id
const h = bits2int_modN(toU8(msgh, fLen)); // Truncate hash
const radj = rec === 2 || rec === 3 ? r + N : r; // If rec was 2 or 3, q.x is bigger than n
if (radj >= P)
err('q.x invalid'); // ensure q.x is still a field element
const head = (rec & 1) === 0 ? '02' : '03'; // head is 0x02 or 0x03
const R = Point_fromHex(head + n2h(radj)); // concat head + hex repr of r
const ir = inv(radj, N); // r^-1
const u1 = M(-h * ir, N); // -hr^-1
const u2 = M(s * ir, N); // sr^-1
return G.mulAddQUns(R, u1, u2); // (sr^-1)R-(hr^-1)G = -(hr^-1)G + (sr^-1)
}
toCompactRawBytes() { return h2b(this.toCompactHex()); } // Uint8Array 64b compact repr
toCompactHex() { return n2h(this.r) + n2h(this.s); } // hex 64b compact repr
}
const Signature_fromCompact = (hex) => {
hex = toU8(hex, 64); // compact repr is (32b r)||(32b s)
return new Signature(slc(hex, 0, fLen), slc(hex, fLen, 2 * fLen), undefined);
};
Signature_fromCompact;
const bits2int = (bytes) => {
const delta = bytes.length * 8 - 256; // RFC suggests optional truncating via bits2octets
const num = b2n(bytes); // FIPS 186-4 4.6 suggests the leftmost min(nBitLen, outLen) bits, which
return delta > 0 ? num >> BigInt(delta) : num; // matches bits2int. bits2int can produce res>N.
};
const bits2int_modN = (bytes) => {
return M(bits2int(bytes), N); // with 0: BAD for trunc as per RFC vectors
};
const i2o = (num) => n2b(num); // int to octets
const cr = () => // We support: 1) browsers 2) node.js 19+ 3) deno, other envs with crypto
typeof globalThis === 'object' && 'crypto' in globalThis && 'subtle' in globalThis.crypto ? globalThis.crypto : undefined;
let _hmacSync; // Can be redefined by use in utils; built-ins don't provide it
const optS = { lowS: true }; // opts for sign()
const optV = { lowS: true }; // standard opts for verify()
const prepSig = (msgh, priv, opts = optS) => {
for (let k of ['der', 'recovered', 'canonical']) {
if (k in opts)
err('option not supported'); // legacy opts
}
let { lowS } = opts; // generates low-s sigs by default
if (lowS == null)
lowS = true; // RFC6979 3.2: we skip step A
const h1i = bits2int_modN(toU8(msgh)); // msg bigint
const h1o = i2o(h1i); // msg octets
const d = toPriv(priv); // validate private key, convert to bigint
const seed = [i2o(d), h1o]; // Step D of RFC6979 3.2
let ent = opts.extraEntropy; // RFC6979 3.6: additional k' (optional)
if (ent) { // K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
if (ent === true)
ent = etc.randomBytes(fLen); // if true, use CSPRNG to generate data
const e = toU8(ent); // convert Hex|Bytes to Bytes
if (e.length !== fLen)
err(); // Expected 32 bytes of extra data
seed.push(e);
}
const m = h1i; // convert msg to bigint
const k2sig = (kBytes) => {
const k = bits2int(kBytes); // RFC6979 method.
if (!ge(k))
return; // Check 0 < k < CURVE.n
const ik = inv(k, N); // k^-1 mod n, NOT mod P
const q = G.mul(k).aff(); // q = Gk
const r = M(q.x, N); // r = q.x mod n
if (r === 0n)
return; // r=0 invalid
const s = M(ik * M(m + M(d * r, N), N), N); // s = k^-1(m + rd) mod n
if (s === 0n)
return; // s=0 invalid
let normS = s; // normalized S
let rec = (q.x === r ? 0 : 2) | Number(q.y & 1n); // recovery bit
if (lowS && high(s)) { // if lowS was passed, ensure s is always
normS = M(-s, N); // in the bottom half of CURVE.n
rec ^= 1;
}
return new Signature(r, normS, rec); // use normS, not s
};
return { seed: concatB(...seed), k2sig };
};
function hmacDrbg(asynchronous) {
let v = u8n(fLen); // Minimal non-full-spec HMAC-DRBG from NIST 800-90 for RFC6979 sigs.
let k = u8n(fLen); // Steps B, C of RFC6979 3.2: set hashLen, in our case always same
let i = 0; // Iterations counter, will throw when over 1000
const reset = () => { v.fill(1); k.fill(0); i = 0; };
const _e = 'drbg: tried 1000 values';
if (asynchronous) { // asynchronous=true
const h = (...b) => etc.hmacSha256Async(k, v, ...b); // hmac(k)(v, ...values)
const reseed = async (seed = u8n()) => {
k = await h(u8n([0x00]), seed); // k = hmac(K || V || 0x00 || seed)
v = await h(); // v = hmac(K || V)
if (seed.length === 0)
return;
k = await h(u8n([0x01]), seed); // k = hmac(K || V || 0x01 || seed)
v = await h(); // v = hmac(K || V)
};
const gen = async () => {
if (i++ >= 1000)
err(_e);
v = await h(); // v = hmac(K || V)
return v;
};
return async (seed, pred) => {
reset(); // the returned fn, don't, it's: 1. slower (JIT). 2. unsafe (async race conditions)
await reseed(seed); // Steps D-G
let res = undefined; // Step H: grind until k is in [1..n-1]
while (!(res = pred(await gen())))
await reseed(); // test predicate until it returns ok
reset();
return res;
};
}
else {
const h = (...b) => {
const f = _hmacSync;
if (!f)
err('etc.hmacSha256Sync not set');
return f(k, v, ...b); // hmac(k)(v, ...values)
};
const reseed = (seed = u8n()) => {
k = h(u8n([0x00]), seed); // k = hmac(k || v || 0x00 || seed)
v = h(); // v = hmac(k || v)
if (seed.length === 0)
return;
k = h(u8n([0x01]), seed); // k = hmac(k || v || 0x01 || seed)
v = h(); // v = hmac(k || v)
};
const gen = () => {
if (i++ >= 1000)
err(_e);
v = h(); // v = hmac(k || v)
return v;
};
return (seed, pred) => {
reset();
reseed(seed); // Steps D-G
let res = undefined; // Step H: grind until k is in [1..n-1]
while (!(res = pred(gen())))
reseed(); // test predicate until it returns ok
reset();
return res;
};
}
}
// ECDSA signature generation. via secg.org/sec1-v2.pdf 4.1.2 + RFC6979 deterministic k
const signAsync = async (msgh, priv, opts = optS) => {
const { seed, k2sig } = prepSig(msgh, priv, opts); // Extract arguments for hmac-drbg
return hmacDrbg(true)(seed, k2sig); // Re-run drbg until k2sig returns ok
};
const sign = (msgh, priv, opts = optS) => {
const { seed, k2sig } = prepSig(msgh, priv, opts); // Extract arguments for hmac-drbg
return hmacDrbg(false)(seed, k2sig); // Re-run drbg until k2sig returns ok
};
const verify = (sig, msgh, pub, opts = optV) => {
let { lowS } = opts; // ECDSA signature verification
if (lowS == null)
lowS = true; // Default lowS=true
if ('strict' in opts)
err('option not supported'); // legacy param
let sig_, h, P; // secg.org/sec1-v2.pdf 4.1.4
const rs = sig && typeof sig === 'object' && 'r' in sig; // Previous ver supported DER sigs. We
if (!rs && (toU8(sig).length !== 2 * fLen)) // throw error when DER is suspected now.
err('signature must be 64 bytes');
try {
sig_ = rs ? new Signature(sig.r, sig.s, undefined) : Signature_fromCompact(sig);
h = bits2int_modN(toU8(msgh)); // Truncate hash
P = pub instanceof Point ? pub.ok() : Point_fromHex(pub); // Validate public key
}
catch (e) {
return false;
} // Check sig for validity in both cases
if (!sig_)
return false;
const { r, s } = sig_;
if (lowS && high(s))
return false; // lowS bans sig.s >= CURVE.n/2
let R;
try {
const is = inv(s, N); // s^-1
const u1 = M(h * is, N); // u1 = hs^-1 mod n
const u2 = M(r * is, N); // u2 = rs^-1 mod n
R = G.mulAddQUns(P, u1, u2).aff(); // R = u1⋅G + u2⋅P
}
catch (error) {
return false;
}
if (!R)
return false; // stop if R is identity / zero point
const v = M(R.x, N); // R.x must be in N's field, not P's
return v === r; // mod(R.x, n) == r
};
const getSharedSecret = (privA, pubB, isCompressed = true) => {
return Point_fromHex(pubB).mul(toPriv(privA)).toRawBytes(isCompressed); // ECDH
};
const hashToPrivateKey = (hash) => {
hash = toU8(hash); // produces private keys with modulo bias
const minLen = fLen + 8; // being neglible.
if (hash.length < minLen || hash.length > 1024)
err('expected proper params');
const num = M(b2n(hash), N - 1n) + 1n; // takes at least n+8 bytes
return n2b(num);
};
const etc = {
hexToBytes: h2b, bytesToHex: b2h, // share API with noble-curves.
concatBytes: concatB, bytesToNumberBE: b2n, numberToBytesBE: n2b,
mod: M, invert: inv, // math utilities
hmacSha256Async: async (key, ...msgs) => {
const c = cr(); // async HMAC-SHA256, no sync built-in!
const s = c && c.subtle; // For React Native support, see README.
if (!s)
return err('etc.hmacSha256Async not set'); // Uses webcrypto built-in cryptography.
const k = await s.importKey('raw', key, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign']);
return u8n(await s.sign('HMAC', k, concatB(...msgs)));
},
hmacSha256Sync: _hmacSync, // For TypeScript. Actual logic is below
hashToPrivateKey,
randomBytes: (len = 32) => {
const crypto = cr(); // Must be shimmed in node.js <= 18 to prevent error. See README.
if (!crypto || !crypto.getRandomValues)
err('crypto.getRandomValues must be defined');
return crypto.getRandomValues(u8n(len));
},
};
const utils = {
normPrivateKeyToScalar: toPriv,
isValidPrivateKey: (key) => { try {
return !!toPriv(key);
}
catch (e) {
return false;
} },
randomPrivateKey: () => hashToPrivateKey(etc.randomBytes(fLen + 16)), // FIPS 186 B.4.1.
precompute(w = 8, p = G) { p.multiply(3n); w; return p; }, // no-op
};
Object.defineProperties(etc, { hmacSha256Sync: {
configurable: false, get() { return _hmacSync; }, set(f) { if (!_hmacSync)
_hmacSync = f; },
} });
const W = 8; // Precomputes-related code. W = window size
const precompute = () => {
const points = []; // 10x sign(), 2x verify(). To achieve this,
const windows = 256 / W + 1; // app needs to spend 40ms+ to calculate
let p = G, b = p; // a lot of points related to base point G.
for (let w = 0; w < windows; w++) { // Points are stored in array and used
b = p; // any time Gx multiplication is done.
points.push(b); // They consume 16-32 MiB of RAM.
for (let i = 1; i < 2 ** (W - 1); i++) {
b = b.add(p);
points.push(b);
}
p = b.double(); // Precomputes don't speed-up getSharedKey,
} // which multiplies user point by scalar,
return points; // when precomputes are using base point
};
let Gpows = precompute(); // precomputes for base point G
const wNAF = (n) => {
// Compared to other point mult methods,
const comp = Gpows; // stores 2x less points using subtraction
const neg = (cnd, p) => { let n = p.negate(); return cnd ? n : p; }; // negate
let p = I, f = G; // f must be G, or could become I in the end
const windows = 1 + 256 / W; // W=8 17 windows
const wsize = 2 ** (W - 1); // W=8 128 window size
const mask = BigInt(2 ** W - 1); // W=8 will create mask 0b11111111
const maxNum = 2 ** W; // W=8 256
const shiftBy = BigInt(W); // W=8 8
for (let w = 0; w < windows; w++) {
const off = w * wsize;
let wbits = Number(n & mask); // extract W bits.
n >>= shiftBy; // shift number by W bits.
if (wbits > wsize) {
wbits -= maxNum;
n += 1n;
} // split if bits > max: +224 => 256-32
const off1 = off, off2 = off + Math.abs(wbits) - 1; // offsets, evaluate both
const cnd1 = w % 2 !== 0, cnd2 = wbits < 0; // conditions, evaluate both
if (wbits === 0) {
f = f.add(neg(cnd1, comp[off1])); // bits are 0: add garbage to fake point
}
else { // ^ can't add off2, off2 = I
p = p.add(neg(cnd2, comp[off2])); // bits are 1: add to result point
}
}
return { p, f }; // return both real and fake points for JIT
}; // !! you can disable precomputes by commenting-out call of the wNAF() inside Point#mul()
const a = { getPublicKey, sign, signAsync, verify, CURVE, // Remove the export to easily use in REPL
getSharedSecret, etc, utils, ProjectivePoint: Point, Signature };
a;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment