@noble/secp256k1
Advanced tools
Comparing version 2.1.0 to 2.2.0
@@ -40,5 +40,5 @@ declare const CURVE: { | ||
toHex(isCompressed?: boolean): string; | ||
toRawBytes(isCompressed?: boolean): Uint8Array; | ||
toRawBytes(isCompressed?: boolean): Bytes; | ||
} | ||
declare function getPublicKey(privKey: PrivKey, isCompressed?: boolean): Uint8Array; | ||
declare const getPublicKey: (privKey: PrivKey, isCompressed?: boolean) => Bytes; | ||
type SignatureWithRecovery = Signature & { | ||
@@ -53,3 +53,3 @@ recovery: number; | ||
static fromCompact(hex: Hex): Signature; | ||
assertValidity(): this; | ||
assertValidity(): Signature; | ||
addRecoveryBit(rec: number): SignatureWithRecovery; | ||
@@ -59,14 +59,15 @@ hasHighS(): boolean; | ||
recoverPublicKey(msgh: Hex): Point; | ||
toCompactRawBytes(): Uint8Array; | ||
toCompactRawBytes(): Bytes; | ||
toCompactHex(): string; | ||
} | ||
type HmacFnSync = undefined | ((key: Bytes, ...msgs: Bytes[]) => Bytes); | ||
declare function signAsync(msgh: Hex, priv: PrivKey, opts?: { | ||
lowS?: boolean | undefined; | ||
extraEntropy?: boolean | Hex | undefined; | ||
}): Promise<SignatureWithRecovery>; | ||
declare function sign(msgh: Hex, priv: PrivKey, opts?: { | ||
lowS?: boolean | undefined; | ||
extraEntropy?: boolean | Hex | undefined; | ||
}): SignatureWithRecovery; | ||
type OptS = { | ||
lowS?: boolean; | ||
extraEntropy?: boolean | Hex; | ||
}; | ||
type OptV = { | ||
lowS?: boolean; | ||
}; | ||
declare const signAsync: (msgh: Hex, priv: PrivKey, opts?: OptS) => Promise<SignatureWithRecovery>; | ||
declare const sign: (msgh: Hex, priv: PrivKey, opts?: OptS) => SignatureWithRecovery; | ||
type SigLike = { | ||
@@ -76,18 +77,15 @@ r: bigint; | ||
}; | ||
declare function verify(sig: Hex | SigLike, msgh: Hex, pub: Hex, opts?: { | ||
lowS?: boolean | undefined; | ||
}): boolean; | ||
declare function getSharedSecret(privA: Hex, pubB: Hex, isCompressed?: boolean): Bytes; | ||
declare function hashToPrivateKey(hash: Hex): Bytes; | ||
declare const verify: (sig: Hex | SigLike, msgh: Hex, pub: Hex, opts?: OptV) => boolean; | ||
declare const getSharedSecret: (privA: Hex, pubB: Hex, isCompressed?: boolean) => Bytes; | ||
declare const etc: { | ||
hexToBytes: (hex: string) => Bytes; | ||
bytesToHex: (b: Bytes) => string; | ||
concatBytes: (...arrs: Bytes[]) => Uint8Array; | ||
bytesToNumberBE: (b: Bytes) => bigint; | ||
numberToBytesBE: (num: bigint) => Bytes; | ||
mod: (a: bigint, b?: bigint) => bigint; | ||
bytesToHex: (bytes: Bytes) => string; | ||
concatBytes: (...arrs: Bytes[]) => Bytes; | ||
bytesToNumberBE: (a: Bytes) => bigint; | ||
numberToBytesBE: (n: bigint) => Bytes; | ||
mod: (a: bigint, md?: bigint) => bigint; | ||
invert: (num: bigint, md?: bigint) => bigint; | ||
hmacSha256Async: (key: Bytes, ...msgs: Bytes[]) => Promise<Bytes>; | ||
hmacSha256Sync: HmacFnSync; | ||
hashToPrivateKey: typeof hashToPrivateKey; | ||
hashToPrivateKey: (hash: Hex) => Bytes; | ||
randomBytes: (len?: number) => Bytes; | ||
@@ -94,0 +92,0 @@ }; |
265
index.js
@@ -7,12 +7,13 @@ /*! noble-secp256k1 - MIT License (c) 2019 Paul Miller (paulmillr.com) */ | ||
const Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8n; // base point y | ||
const CURVE = { p: P, n: N, a: 0n, b: 7n, Gx, Gy }; // exported variables incl. a, b | ||
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 crv = (x) => mod(mod(x * x) * x + CURVE.b); // x³ + ax + b weierstrass formula; a=0 | ||
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 big = (n) => typeof n === 'bigint'; // is big integer | ||
const str = (s) => typeof s === 'string'; // is string | ||
const fe = (n) => big(n) && 0n < n && n < P; // is field element (invertible) | ||
const ge = (n) => big(n) && 0n < n && n < N; // is group element | ||
const isu8 = (a) => (a instanceof Uint8Array || | ||
(a != null && typeof a === 'object' && a.constructor.name === 'Uint8Array')); | ||
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) | ||
@@ -22,5 +23,8 @@ !isu8(a) || (typeof l === 'number' && l > 0 && a.length !== l) ? | ||
const u8n = (data) => new Uint8Array(data); // creates Uint8Array | ||
const toU8 = (a, len) => au8(str(a) ? h2b(a) : u8n(au8(a)), len); // norm(hex/u8a) to u8a | ||
const mod = (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 | ||
const toU8 = (a, len) => au8(isS(a) ? h2b(a) : u8n(au8(a)), len); // norm(hex/u8a) to u8a | ||
const M = (a, b = P) => { | ||
const r = a % b; | ||
return r >= 0n ? r : b + r; | ||
}; | ||
const aPoint = (p) => (p instanceof Point ? p : err('Point expected')); // is 3d point | ||
class Point { | ||
@@ -31,5 +35,6 @@ constructor(px, py, pz) { | ||
this.pz = pz; | ||
} //3d=less inversions | ||
Object.freeze(this); | ||
} | ||
static fromAffine(p) { | ||
return ((p.x === 0n) && (p.y === 0n)) ? Point.ZERO : new Point(p.x, p.y, 1n); | ||
return ((p.x === 0n) && (p.y === 0n)) ? I : new Point(p.x, p.y, 1n); | ||
} | ||
@@ -40,16 +45,16 @@ static fromHex(hex) { | ||
const head = hex[0], tail = hex.subarray(1); // first byte is prefix, rest is data | ||
const x = slcNum(tail, 0, fLen), len = hex.length; // next 32 bytes are x coordinate | ||
const x = slc(tail, 0, fLen), len = hex.length; // next 32 bytes are x coordinate | ||
if (len === 33 && [0x02, 0x03].includes(head)) { // 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(crv(x)); // x³ + ax + b is right side of equation | ||
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 = mod(-y); // determine proper solution | ||
y = M(-y); // determine proper solution | ||
p = new Point(x, y, 1n); // create point | ||
} // Uncompressed points: 65b, start with 0x04 | ||
if (len === 65 && head === 0x04) | ||
p = new Point(x, slcNum(tail, fLen, 2 * fLen), 1n); | ||
return p ? p.ok() : err('Point is not on curve'); // Verify the result | ||
p = new Point(x, slc(tail, fLen, 2 * fLen), 1n); | ||
return p ? p.ok() : err('Point invalid: not on curve'); // Verify the result | ||
} | ||
@@ -61,52 +66,52 @@ static fromPrivateKey(k) { return G.mul(toPriv(k)); } // Create point from a private key. | ||
const { px: X1, py: Y1, pz: Z1 } = this; | ||
const { px: X2, py: Y2, pz: Z2 } = isPoint(other); // isPoint() checks class equality | ||
const X1Z2 = mod(X1 * Z2), X2Z1 = mod(X2 * Z1); | ||
const Y1Z2 = mod(Y1 * Z2), Y2Z1 = mod(Y2 * Z1); | ||
const { px: X2, py: Y2, pz: Z2 } = aPoint(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, mod(-this.py), this.pz); } // Flip point over y coord | ||
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 { px: X2, py: Y2, pz: Z2 } = aPoint(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 = mod(b * 3n); | ||
let t0 = mod(X1 * X2), t1 = mod(Y1 * Y2), t2 = mod(Z1 * Z2), t3 = mod(X1 + Y1); // step 1 | ||
let t4 = mod(X2 + Y2); // step 5 | ||
t3 = mod(t3 * t4); | ||
t4 = mod(t0 + t1); | ||
t3 = mod(t3 - t4); | ||
t4 = mod(X1 + Z1); | ||
let t5 = mod(X2 + Z2); // step 10 | ||
t4 = mod(t4 * t5); | ||
t5 = mod(t0 + t2); | ||
t4 = mod(t4 - t5); | ||
t5 = mod(Y1 + Z1); | ||
X3 = mod(Y2 + Z2); // step 15 | ||
t5 = mod(t5 * X3); | ||
X3 = mod(t1 + t2); | ||
t5 = mod(t5 - X3); | ||
Z3 = mod(a * t4); | ||
X3 = mod(b3 * t2); // step 20 | ||
Z3 = mod(X3 + Z3); | ||
X3 = mod(t1 - Z3); | ||
Z3 = mod(t1 + Z3); | ||
Y3 = mod(X3 * Z3); | ||
t1 = mod(t0 + t0); // step 25 | ||
t1 = mod(t1 + t0); | ||
t2 = mod(a * t2); | ||
t4 = mod(b3 * t4); | ||
t1 = mod(t1 + t2); | ||
t2 = mod(t0 - t2); // step 30 | ||
t2 = mod(a * t2); | ||
t4 = mod(t4 + t2); | ||
t0 = mod(t1 * t4); | ||
Y3 = mod(Y3 + t0); | ||
t0 = mod(t5 * t4); // step 35 | ||
X3 = mod(t3 * X3); | ||
X3 = mod(X3 - t0); | ||
t0 = mod(t3 * t1); | ||
Z3 = mod(t5 * Z3); | ||
Z3 = mod(Z3 + t0); // step 40 | ||
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); | ||
@@ -118,3 +123,3 @@ } | ||
if (!ge(n)) | ||
err('invalid scalar'); // must be 0 < n < CURVE.n | ||
err('scalar invalid'); // must be 0 < n < CURVE.n | ||
if (this.equals(G)) | ||
@@ -140,6 +145,6 @@ return wNAF(n).p; // use precomputes for base point | ||
return { x, y }; // if z is 1, pass affine coordinates as-is | ||
const iz = inv(z); // z^-1: invert z | ||
if (mod(z * iz) !== 1n) | ||
err('invalid inverse'); // (z * z^-1) must be 1, otherwise bad math | ||
return { x: mod(x * iz), y: mod(y * iz) }; // x = x*z^-1; y = y*z^-1 | ||
const iz = inv(z, P); // 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 | ||
} | ||
@@ -150,3 +155,3 @@ assertValidity() { | ||
err('Point invalid: x or y'); // x and y must be in range 0 < n < P | ||
return mod(y * y) === crv(x) ? // y² = x³ + ax + b, must be equal | ||
return M(y * y) === curve(x) ? // y² = x³ + ax + b, must be equal | ||
this : err('Point invalid: not on curve'); | ||
@@ -170,22 +175,34 @@ } | ||
const padh = (n, pad) => n.toString(16).padStart(pad, '0'); | ||
const b2h = (b) => Array.from(b).map(e => padh(e, 2)).join(''); // bytes to hex | ||
const b2h = (b) => 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 }; // ASCII characters | ||
const _ch = (ch) => { | ||
if (ch >= C._0 && ch <= C._9) | ||
return ch - C._0; // '2' => 50-48 | ||
if (ch >= C.A && ch <= C.F) | ||
return ch - (C.A - 10); // 'B' => 66-(65-10) | ||
if (ch >= C.a && ch <= C.f) | ||
return ch - (C.a - 10); // 'b' => 98-(97-10) | ||
return; | ||
}; | ||
const h2b = (hex) => { | ||
const l = hex.length; // error if not string, | ||
if (!str(hex) || l % 2) | ||
err('hex invalid 1'); // or has odd length like 3, 5. | ||
const arr = u8n(l / 2); // create result array | ||
for (let i = 0; i < arr.length; i++) { | ||
const j = i * 2; | ||
const h = hex.slice(j, j + 2); // hexByte. slice is faster than substr | ||
const b = Number.parseInt(h, 16); // byte, created from string part | ||
if (Number.isNaN(b) || b < 0) | ||
err('hex invalid 2'); // byte must be valid 0 <= byte < 256 | ||
arr[i] = b; | ||
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 arr; | ||
return array; | ||
}; | ||
const b2n = (b) => BigInt('0x' + (b2h(b) || '0')); // bytes to number | ||
const slcNum = (b, from, to) => b2n(b.slice(from, to)); // slice bytes num | ||
const slc = (b, from, to) => b2n(b.slice(from, to)); // slice bytes num | ||
const n2b = (num) => { | ||
return big(num) && num >= 0n && num < B256 ? h2b(padh(num, 2 * fLen)) : err('bigint expected'); | ||
return isB(num) && num >= 0n && num < B256 ? h2b(padh(num, 2 * fLen)) : err('bigint expected'); | ||
}; | ||
@@ -199,6 +216,6 @@ const n2h = (num) => b2h(n2b(num)); // number to 32b hex | ||
}; | ||
const inv = (num, md = P) => { | ||
const inv = (num, md) => { | ||
if (num === 0n || md <= 0n) | ||
err('no inverse n=' + num + ' mod=' + md); // no neg exponent for now | ||
let a = mod(num, md), b = md, x = 0n, y = 1n, u = 1n, v = 0n; | ||
let a = M(num, md), b = md, x = 0n, y = 1n, u = 1n, v = 0n; | ||
while (a !== 0n) { // uses euclidean gcd algorithm | ||
@@ -209,3 +226,3 @@ const q = b / a, r = b % a; // not constant-time | ||
} | ||
return b === 1n ? mod(x, md) : err('no inverse'); // b is gcd at this point | ||
return b === 1n ? M(x, md) : err('no inverse'); // b is gcd at this point | ||
}; | ||
@@ -219,10 +236,10 @@ const sqrt = (n) => { | ||
} | ||
return mod(r * r) === n ? r : err('sqrt invalid'); // check if result is valid | ||
return M(r * r) === n ? r : err('sqrt invalid'); // check if result is valid | ||
}; | ||
const toPriv = (p) => { | ||
if (!big(p)) | ||
if (!isB(p)) | ||
p = b2n(toU8(p, fLen)); // convert to bigint when bytes | ||
return ge(p) ? p : err('private key out of range'); // check if bigint is in range | ||
return ge(p) ? p : err('private key invalid 3'); // check if bigint is in range | ||
}; | ||
const moreThanHalfN = (n) => n > (N >> 1n); // if a number is bigger than CURVE.n/2 | ||
const high = (n) => n > (N >> 1n); // if a number is bigger than CURVE.n/2 | ||
const getPublicKey = (privKey, isCompressed = true) => { | ||
@@ -240,3 +257,3 @@ return Point.fromPrivateKey(privKey).toRawBytes(isCompressed); // 33b or 65b output | ||
hex = toU8(hex, 64); // compact repr is (32b r)||(32b s) | ||
return new Signature(slcNum(hex, 0, fLen), slcNum(hex, fLen, 2 * fLen)); | ||
return new Signature(slc(hex, 0, fLen), slc(hex, fLen, 2 * fLen)); | ||
} | ||
@@ -247,5 +264,5 @@ assertValidity() { return ge(this.r) && ge(this.s) ? this : err(); } // 0 < r or s < CURVE.n | ||
} | ||
hasHighS() { return moreThanHalfN(this.s); } | ||
hasHighS() { return high(this.s); } | ||
normalizeS() { | ||
return this.hasHighS() ? new Signature(this.r, mod(this.s, N), this.recovery) : this; | ||
return high(this.s) ? new Signature(this.r, M(-this.s, N), this.recovery) : this; | ||
} | ||
@@ -263,4 +280,4 @@ recoverPublicKey(msgh) { | ||
const ir = inv(radj, N); // r^-1 | ||
const u1 = mod(-h * ir, N); // -hr^-1 | ||
const u2 = mod(s * ir, N); // sr^-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) | ||
@@ -273,2 +290,4 @@ } | ||
const delta = bytes.length * 8 - 256; // RFC suggests optional truncating via bits2octets | ||
if (delta > 1024) | ||
err('msg invalid'); // our CUSTOM check, "just-in-case" | ||
const num = b2n(bytes); // FIPS 186-4 4.6 suggests the leftmost min(nBitLen, outLen) bits, which | ||
@@ -278,7 +297,7 @@ return delta > 0 ? num >> BigInt(delta) : num; // matches bits2int. bits2int can produce res>N. | ||
const bits2int_modN = (bytes) => { | ||
return mod(bits2int(bytes), N); // with 0: BAD for trunc as per RFC vectors | ||
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 ? globalThis.crypto : undefined; | ||
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 | ||
@@ -288,4 +307,4 @@ const optS = { lowS: true }; // opts for sign() | ||
const prepSig = (msgh, priv, opts = optS) => { | ||
if (['der', 'recovered', 'canonical'].some(k => k in opts)) // Ban legacy options | ||
err('sign() legacy options not supported'); | ||
if (['der', 'recovered', 'canonical'].some(k => k in opts)) | ||
err('option not supported'); // legacy opts | ||
let { lowS } = opts; // generates low-s sigs by default | ||
@@ -299,10 +318,4 @@ if (lowS == null) | ||
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); | ||
} | ||
if (ent) // K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k') | ||
seed.push(ent === true ? etc.randomBytes(fLen) : toU8(ent)); // true == fetch from CSPRNG | ||
const m = h1i; // convert msg to bigint | ||
@@ -315,6 +328,6 @@ const k2sig = (kBytes) => { | ||
const q = G.mul(k).aff(); // q = Gk | ||
const r = mod(q.x, N); // r = q.x mod n | ||
const r = M(q.x, N); // r = q.x mod n | ||
if (r === 0n) | ||
return; // r=0 invalid | ||
const s = mod(ik * mod(m + mod(d * r, N), N), N); // s = k^-1(m + rd) mod n | ||
const s = M(ik * M(m + M(d * r, N), N), N); // s = k^-1(m + rd) mod n | ||
if (s === 0n) | ||
@@ -324,4 +337,4 @@ return; // s=0 invalid | ||
let rec = (q.x === r ? 0 : 2) | Number(q.y & 1n); // recovery bit | ||
if (lowS && moreThanHalfN(s)) { // if lowS was passed, ensure s is always | ||
normS = mod(-s, N); // in the bottom half of CURVE.n | ||
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; | ||
@@ -397,2 +410,3 @@ } | ||
} | ||
; | ||
// ECDSA signature generation. via secg.org/sec1-v2.pdf 4.1.2 + RFC6979 deterministic k | ||
@@ -412,3 +426,3 @@ const signAsync = async (msgh, priv, opts = optS) => { | ||
if ('strict' in opts) | ||
err('verify() legacy options not supported'); // legacy param | ||
err('option not supported'); // legacy param | ||
let sig_, h, P; // secg.org/sec1-v2.pdf 4.1.4 | ||
@@ -429,3 +443,3 @@ const rs = sig && typeof sig === 'object' && 'r' in sig; // Previous ver supported DER sigs. We | ||
const { r, s } = sig_; | ||
if (lowS && moreThanHalfN(s)) | ||
if (lowS && high(s)) | ||
return false; // lowS bans sig.s >= CURVE.n/2 | ||
@@ -435,4 +449,4 @@ let R; | ||
const is = inv(s, N); // s^-1 | ||
const u1 = mod(h * is, N); // u1 = hs^-1 mod n | ||
const u2 = mod(r * is, N); // u2 = rs^-1 mod n | ||
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 | ||
@@ -445,3 +459,3 @@ } | ||
return false; // stop if R is identity / zero point | ||
const v = mod(R.x, N); // R.x must be in N's field, not P's | ||
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 | ||
@@ -454,12 +468,15 @@ }; | ||
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 = mod(b2n(hash), N - 1n) + 1n; // takes at least n+8 bytes | ||
return n2b(num); | ||
if (hash.length < fLen + 8 || hash.length > 1024) | ||
err('expected 40-1024b'); // being neglible. | ||
const num = M(b2n(hash), N - 1n); // takes n+8 bytes | ||
return n2b(num + 1n); // returns (hash mod n-1)+1 | ||
}; | ||
const etc = { | ||
hexToBytes: h2b, bytesToHex: b2h, // share API with noble-curves. | ||
concatBytes: concatB, bytesToNumberBE: b2n, numberToBytesBE: n2b, | ||
mod, invert: inv, // math utilities | ||
hexToBytes: h2b, // share API with noble-curves. | ||
bytesToHex: b2h, | ||
concatBytes: concatB, | ||
bytesToNumberBE: b2n, | ||
numberToBytesBE: n2b, | ||
mod: M, | ||
invert: inv, // math utilities | ||
hmacSha256Async: async (key, ...msgs) => { | ||
@@ -474,3 +491,3 @@ const c = cr(); // async HMAC-SHA256, no sync built-in! | ||
hmacSha256Sync: _hmacSync, // For TypeScript. Actual logic is below | ||
hashToPrivateKey, | ||
hashToPrivateKey: hashToPrivateKey, | ||
randomBytes: (len = 32) => { | ||
@@ -477,0 +494,0 @@ const crypto = cr(); // Must be shimmed in node.js <= 18 to prevent error. See README. |
291
index.ts
@@ -7,14 +7,14 @@ /*! noble-secp256k1 - MIT License (c) 2019 Paul Miller (paulmillr.com) */ | ||
const Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8n; // base point y | ||
const CURVE = {p: P, n: N, a: 0n, b: 7n, Gx, Gy}; // exported variables incl. a, b | ||
const CURVE: { p: bigint; n: bigint; a: bigint; b: bigint; Gx: bigint; Gy: bigint } = { | ||
p: P, n: N, a: 0n, b: 7n, Gx, Gy }; // exported variables incl. a, b | ||
const fLen = 32; // field / group byte length | ||
type Bytes = Uint8Array; type Hex = Bytes | string; type PrivKey = Hex | bigint; | ||
const crv = (x: bigint) => mod(mod(x * x) * x + CURVE.b); // x³ + ax + b weierstrass formula; a=0 | ||
const curve = (x: bigint) => M(M(x * x) * x + CURVE.b); // x³ + ax + b weierstrass formula; a=0 | ||
const err = (m = ''): never => { throw new Error(m); }; // error helper, messes-up stack trace | ||
const big = (n: unknown): n is bigint => typeof n === 'bigint'; // is big integer | ||
const str = (s: unknown): s is string => typeof s === 'string'; // is string | ||
const fe = (n: bigint) => big(n) && 0n < n && n < P; // is field element (invertible) | ||
const ge = (n: bigint) => big(n) && 0n < n && n < N; // is group element | ||
const isB = (n: unknown): n is bigint => typeof n === 'bigint'; // is big integer | ||
const isS = (s: unknown): s is string => typeof s === 'string'; // is string | ||
const fe = (n: bigint) => isB(n) && 0n < n && n < P; // is field element (invertible) | ||
const ge = (n: bigint) => isB(n) && 0n < n && n < N; // is group element | ||
const isu8 = (a: unknown): a is Uint8Array => ( | ||
a instanceof Uint8Array || | ||
(a != null && typeof a === 'object' && a.constructor.name === 'Uint8Array') | ||
a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array') | ||
); | ||
@@ -25,13 +25,17 @@ const au8 = (a: unknown, l?: number): Bytes => // assert is Uint8Array (of specific length) | ||
const u8n = (data?: any) => new Uint8Array(data); // creates Uint8Array | ||
const toU8 = (a: Hex, len?: number) => au8(str(a) ? h2b(a) : u8n(au8(a)), len); // norm(hex/u8a) to u8a | ||
const mod = (a: bigint, b = P) => { let r = a % b; return r >= 0n ? r : b + r; }; // mod division | ||
const isPoint = (p: unknown) => (p instanceof Point ? p : err('Point expected')); // is 3d point | ||
const toU8 = (a: Hex, len?: number) => au8(isS(a) ? h2b(a) : u8n(au8(a)), len); // norm(hex/u8a) to u8a | ||
const M = (a: bigint, b: bigint = P) => { // mod division | ||
const r = a % b; return r >= 0n ? r : b + r; | ||
}; | ||
const aPoint = (p: unknown) => (p instanceof Point ? p : err('Point expected')); // is 3d point | ||
interface AffinePoint { x: bigint, y: bigint } // Point in 2d xy affine coordinates | ||
class Point { // Point in 3d xyz projective coordinates | ||
constructor(readonly px: bigint, readonly py: bigint, readonly pz: bigint) {} //3d=less inversions | ||
static readonly BASE = new Point(Gx, Gy, 1n); // Generator / base point | ||
static readonly ZERO = new Point(0n, 1n, 0n); // Identity / zero point | ||
static fromAffine (p: AffinePoint) { // (0, 0) => (0, 1, 0), not (0, 0, 1) | ||
return ((p.x === 0n) && (p.y === 0n)) ? Point.ZERO : new Point(p.x, p.y, 1n); | ||
constructor(readonly px: bigint, readonly py: bigint, readonly pz: bigint) { //3d=less inversions | ||
Object.freeze(this); | ||
} | ||
static readonly BASE: Point = new Point(Gx, Gy, 1n); // Generator / base point | ||
static readonly ZERO: Point = new Point(0n, 1n, 0n); // Identity / zero point | ||
static fromAffine(p: AffinePoint): Point { // (0, 0) => (0, 1, 0), not (0, 0, 1) | ||
return ((p.x === 0n) && (p.y === 0n)) ? I : new Point(p.x, p.y, 1n); | ||
} | ||
static fromHex(hex: Hex): Point { // Convert Uint8Array or hex string to Point | ||
@@ -41,53 +45,53 @@ 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 = slcNum(tail, 0, fLen), len = hex.length; // next 32 bytes are x coordinate | ||
const x = slc(tail, 0, fLen), len = hex.length; // next 32 bytes are x coordinate | ||
if (len === 33 && [0x02, 0x03].includes(head)) { // 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(crv(x)); // x³ + ax + b is right side of equation | ||
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 = mod(-y); // determine proper solution | ||
if (headOdd !== isYOdd) y = M(-y); // determine proper solution | ||
p = new Point(x, y, 1n); // create point | ||
} // Uncompressed points: 65b, start with 0x04 | ||
if (len === 65 && head === 0x04) p = new Point(x, slcNum(tail, fLen, 2 * fLen), 1n); | ||
return p ? p.ok() : err('Point is not on curve'); // Verify the result | ||
if (len === 65 && head === 0x04) p = new Point(x, slc(tail, fLen, 2 * fLen), 1n); | ||
return p ? p.ok() : err('Point invalid: not on curve'); // Verify the result | ||
} | ||
static fromPrivateKey(k: PrivKey) { return G.mul(toPriv(k)); } // Create point from a private key. | ||
get x() { return this.aff().x; } // .x, .y will call expensive toAffine: | ||
get y() { return this.aff().y; } // should be used with care. | ||
static fromPrivateKey(k: PrivKey): Point { return G.mul(toPriv(k)); } // Create point from a private key. | ||
get x(): bigint { return this.aff().x; } // .x, .y will call expensive toAffine: | ||
get y(): bigint { return this.aff().y; } // should be used with care. | ||
equals(other: Point): boolean { // Equality check: compare points | ||
const { px: X1, py: Y1, pz: Z1 } = this; | ||
const { px: X2, py: Y2, pz: Z2 } = isPoint(other); // isPoint() checks class equality | ||
const X1Z2 = mod(X1 * Z2), X2Z1 = mod(X2 * Z1); | ||
const Y1Z2 = mod(Y1 * Z2), Y2Z1 = mod(Y2 * Z1); | ||
const { px: X2, py: Y2, pz: Z2 } = aPoint(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, mod(-this.py), this.pz); } // Flip point over y coord | ||
double() { return this.add(this); } // Point doubling: P+P, complete formula. | ||
add(other: Point) { // Point addition: P+Q, complete, exception | ||
negate(): Point { return new Point(this.px, M(-this.py), this.pz); } // Flip point over y coord | ||
double(): Point { return this.add(this); } // Point doubling: P+P, complete formula. | ||
add(other: Point): Point { // Point addition: P+Q, complete, exception | ||
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 { px: X2, py: Y2, pz: Z2 } = aPoint(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 = mod(b * 3n); | ||
let t0 = mod(X1 * X2), t1 = mod(Y1 * Y2), t2 = mod(Z1 * Z2), t3 = mod(X1 + Y1); // step 1 | ||
let t4 = mod(X2 + Y2); // step 5 | ||
t3 = mod(t3 * t4); t4 = mod(t0 + t1); t3 = mod(t3 - t4); t4 = mod(X1 + Z1); | ||
let t5 = mod(X2 + Z2); // step 10 | ||
t4 = mod(t4 * t5); t5 = mod(t0 + t2); t4 = mod(t4 - t5); t5 = mod(Y1 + Z1); | ||
X3 = mod(Y2 + Z2); // step 15 | ||
t5 = mod(t5 * X3); X3 = mod(t1 + t2); t5 = mod(t5 - X3); Z3 = mod(a * t4); | ||
X3 = mod(b3 * t2); // step 20 | ||
Z3 = mod(X3 + Z3); X3 = mod(t1 - Z3); Z3 = mod(t1 + Z3); Y3 = mod(X3 * Z3); | ||
t1 = mod(t0 + t0); // step 25 | ||
t1 = mod(t1 + t0); t2 = mod(a * t2); t4 = mod(b3 * t4); t1 = mod(t1 + t2); | ||
t2 = mod(t0 - t2); // step 30 | ||
t2 = mod(a * t2); t4 = mod(t4 + t2); t0 = mod(t1 * t4); Y3 = mod(Y3 + t0); | ||
t0 = mod(t5 * t4); // step 35 | ||
X3 = mod(t3 * X3); X3 = mod(X3 - t0); t0 = mod(t3 * t1); Z3 = mod(t5 * Z3); | ||
Z3 = mod(Z3 + t0); // step 40 | ||
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: bigint, safe = true) { // Point scalar multiplication. | ||
mul(n: bigint, safe = true): Point { // Point scalar multiplication. | ||
if (!safe && n === 0n) return I; // in unsafe mode, allow zero | ||
if (!ge(n)) err('invalid scalar'); // must be 0 < n < CURVE.n | ||
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 | ||
@@ -101,3 +105,3 @@ let p = I, f = G; // init result point & fake point | ||
} | ||
mulAddQUns(R: Point, u1: bigint, u2: bigint) { // Double scalar mult. Q = u1⋅G + u2⋅R. | ||
mulAddQUns(R: Point, u1: bigint, u2: bigint): Point { // Double scalar mult. Q = u1⋅G + u2⋅R. | ||
return this.mul(u1, false).add(R.mul(u2, false)).ok(); // Unsafe: do NOT use for stuff related | ||
@@ -109,5 +113,5 @@ } // to private keys. Doesn't use Shamir trick | ||
if (z === 1n) return { x, y }; // if z is 1, pass affine coordinates as-is | ||
const iz = inv(z); // z^-1: invert z | ||
if (mod(z * iz) !== 1n) err('invalid inverse'); // (z * z^-1) must be 1, otherwise bad math | ||
return { x: mod(x * iz), y: mod(y * iz) }; // x = x*z^-1; y = y*z^-1 | ||
const iz = inv(z, P); // 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 | ||
} | ||
@@ -117,9 +121,9 @@ assertValidity(): Point { // Checks if the point is valid and on-curve | ||
if (!fe(x) || !fe(y)) err('Point invalid: x or y'); // x and y must be in range 0 < n < P | ||
return mod(y * y) === crv(x) ? // y² = x³ + ax + b, must be equal | ||
return M(y * y) === curve(x) ? // y² = x³ + ax + b, must be equal | ||
this : err('Point invalid: not on curve'); | ||
} | ||
multiply(n: bigint) { return this.mul(n); } // Aliases to compress code | ||
aff() { return this.toAffine(); } | ||
ok() { return this.assertValidity(); } | ||
toHex(isCompressed = true) { // Encode point to hex string. | ||
multiply(n: bigint): Point { return this.mul(n); } // Aliases to compress code | ||
aff(): AffinePoint { return this.toAffine(); } | ||
ok(): Point { return this.assertValidity(); } | ||
toHex(isCompressed = true): string { // Encode point to hex string. | ||
const { x, y } = this.aff(); // convert to 2d xy affine point | ||
@@ -129,3 +133,3 @@ const head = isCompressed ? ((y & 1n) === 0n ? '02' : '03') : '04'; // 0x02, 0x03, 0x04 prefix | ||
} | ||
toRawBytes(isCompressed = true) { // Encode point to Uint8Array. | ||
toRawBytes(isCompressed = true): Bytes { // Encode point to Uint8Array. | ||
return h2b(this.toHex(isCompressed)); // re-use toHex(), convert hex to bytes | ||
@@ -136,23 +140,31 @@ } | ||
const padh = (n: number | bigint, pad: number) => n.toString(16).padStart(pad, '0'); | ||
const b2h = (b: Bytes): string => Array.from(b).map(e => padh(e, 2)).join(''); // bytes to hex | ||
const b2h = (b: Bytes): string => 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 } as const; // ASCII characters | ||
const _ch = (ch: number): number | undefined => { | ||
if (ch >= C._0 && ch <= C._9) return ch - C._0; // '2' => 50-48 | ||
if (ch >= C.A && ch <= C.F) return ch - (C.A - 10); // 'B' => 66-(65-10) | ||
if (ch >= C.a && ch <= C.f) return ch - (C.a - 10); // 'b' => 98-(97-10) | ||
return; | ||
}; | ||
const h2b = (hex: string): Bytes => { // hex to bytes | ||
const l = hex.length; // error if not string, | ||
if (!str(hex) || l % 2) err('hex invalid 1'); // or has odd length like 3, 5. | ||
const arr = u8n(l / 2); // create result array | ||
for (let i = 0; i < arr.length; i++) { | ||
const j = i * 2; | ||
const h = hex.slice(j, j + 2); // hexByte. slice is faster than substr | ||
const b = Number.parseInt(h, 16); // byte, created from string part | ||
if (Number.isNaN(b) || b < 0) err('hex invalid 2'); // byte must be valid 0 <= byte < 256 | ||
arr[i] = b; | ||
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 arr; | ||
return array; | ||
}; | ||
const b2n = (b: Bytes): bigint => BigInt('0x' + (b2h(b) || '0')); // bytes to number | ||
const slcNum = (b: Bytes, from: number, to: number) => b2n(b.slice(from, to)); // slice bytes num | ||
const slc = (b: Bytes, from: number, to: number) => b2n(b.slice(from, to)); // slice bytes num | ||
const n2b = (num: bigint): Bytes => { // number to 32b. Must be 0 <= num < B256 | ||
return big(num) && num >= 0n && num < B256 ? h2b(padh(num, 2 * fLen)) : err('bigint expected'); | ||
return isB(num) && num >= 0n && num < B256 ? h2b(padh(num, 2 * fLen)) : err('bigint expected'); | ||
}; | ||
const n2h = (num: bigint): string => b2h(n2b(num)); // number to 32b hex | ||
const concatB = (...arrs: Bytes[]) => { // concatenate Uint8Array-s | ||
const concatB = (...arrs: Bytes[]): Bytes => { // concatenate Uint8Array-s | ||
const r = u8n(arrs.reduce((sum, a) => sum + au8(a).length, 0)); // create u8a of summed length | ||
@@ -163,5 +175,5 @@ let pad = 0; // walk through each array, | ||
}; | ||
const inv = (num: bigint, md = P): bigint => { // modular inversion | ||
const inv = (num: bigint, md: bigint): bigint => { // modular inversion | ||
if (num === 0n || md <= 0n) err('no inverse n=' + num + ' mod=' + md); // no neg exponent for now | ||
let a = mod(num, md), b = md, x = 0n, y = 1n, u = 1n, v = 0n; | ||
let a = M(num, md), b = md, x = 0n, y = 1n, u = 1n, v = 0n; | ||
while (a !== 0n) { // uses euclidean gcd algorithm | ||
@@ -172,3 +184,3 @@ const q = b / a, r = b % a; // not constant-time | ||
} | ||
return b === 1n ? mod(x, md) : err('no inverse'); // b is gcd at this point | ||
return b === 1n ? M(x, md) : err('no inverse'); // b is gcd at this point | ||
}; | ||
@@ -181,11 +193,11 @@ const sqrt = (n: bigint) => { // √n = n^((p+1)/4) for fields p = 3 mod 4 | ||
} | ||
return mod(r * r) === n ? r : err('sqrt invalid'); // check if result is valid | ||
return M(r * r) === n ? r : err('sqrt invalid'); // check if result is valid | ||
}; | ||
const toPriv = (p: PrivKey): bigint => { // normalize private key to bigint | ||
if (!big(p)) p = b2n(toU8(p, fLen)); // convert to bigint when bytes | ||
return ge(p) ? p : err('private key out of range'); // check if bigint is in range | ||
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 moreThanHalfN = (n: bigint): boolean => n > (N >> 1n); // if a number is bigger than CURVE.n/2 | ||
const getPublicKey = (privKey: PrivKey, isCompressed = true) => { // Make public key from priv | ||
return Point.fromPrivateKey(privKey).toRawBytes(isCompressed); // 33b or 65b output | ||
const high = (n: bigint): boolean => n > (N >> 1n); // if a number is bigger than CURVE.n/2 | ||
const getPublicKey = (privKey: PrivKey, isCompressed = true): Bytes => { // Make public key from priv | ||
return Point.fromPrivateKey(privKey).toRawBytes(isCompressed); // 33b or 65b output | ||
} | ||
@@ -197,13 +209,13 @@ type SignatureWithRecovery = Signature & { recovery: number } | ||
} // constructed outside. | ||
static fromCompact(hex: Hex) { // create signature from 64b compact repr | ||
static fromCompact(hex: Hex): Signature { // create signature from 64b compact repr | ||
hex = toU8(hex, 64); // compact repr is (32b r)||(32b s) | ||
return new Signature(slcNum(hex, 0, fLen), slcNum(hex, fLen, 2 * fLen)); | ||
return new Signature(slc(hex, 0, fLen), slc(hex, fLen, 2 * fLen)); | ||
} | ||
assertValidity() { return ge(this.r) && ge(this.s) ? this : err(); } // 0 < r or s < CURVE.n | ||
assertValidity(): Signature { return ge(this.r) && ge(this.s) ? this : err(); } // 0 < r or s < CURVE.n | ||
addRecoveryBit(rec: number): SignatureWithRecovery { | ||
return new Signature(this.r, this.s, rec) as SignatureWithRecovery; | ||
} | ||
hasHighS() { return moreThanHalfN(this.s); } | ||
normalizeS() { | ||
return this.hasHighS() ? new Signature(this.r, mod(this.s, N), this.recovery) : this | ||
hasHighS(): boolean { return high(this.s); } | ||
normalizeS(): Signature { | ||
return high(this.s) ? new Signature(this.r, M(-this.s, N), this.recovery) : this; | ||
} | ||
@@ -219,16 +231,17 @@ recoverPublicKey(msgh: Hex): Point { // ECDSA public key recovery | ||
const ir = inv(radj, N); // r^-1 | ||
const u1 = mod(-h * ir, N); // -hr^-1 | ||
const u2 = mod(s * ir, N); // sr^-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 | ||
toCompactRawBytes(): Bytes { return h2b(this.toCompactHex()); } // Uint8Array 64b compact repr | ||
toCompactHex(): string { return n2h(this.r) + n2h(this.s); } // hex 64b compact repr | ||
} | ||
const bits2int = (bytes: Uint8Array): bigint => { // RFC6979: ensure ECDSA msg is X bytes. | ||
const bits2int = (bytes: Bytes): bigint => { // RFC6979: ensure ECDSA msg is X bytes. | ||
const delta = bytes.length * 8 - 256; // RFC suggests optional truncating via bits2octets | ||
if (delta > 1024) err('msg invalid'); // our CUSTOM check, "just-in-case" | ||
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: Uint8Array): bigint => { // int2octets can't be used; pads small msgs | ||
return mod(bits2int(bytes), N); // with 0: BAD for trunc as per RFC vectors | ||
const bits2int_modN = (bytes: Bytes): bigint => { // int2octets can't be used; pads small msgs | ||
return M(bits2int(bytes), N); // with 0: BAD for trunc as per RFC vectors | ||
}; | ||
@@ -238,11 +251,12 @@ const i2o = (num: bigint): Bytes => 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 ? globalThis.crypto : undefined; | ||
typeof globalThis === 'object' && 'crypto' in globalThis && 'subtle' in globalThis.crypto ? globalThis.crypto : undefined; | ||
type HmacFnSync = undefined | ((key: Bytes, ...msgs: Bytes[]) => Bytes); | ||
let _hmacSync: HmacFnSync; // Can be redefined by use in utils; built-ins don't provide it | ||
const optS: { lowS?: boolean; extraEntropy?: boolean | Hex; } = { lowS: true }; // opts for sign() | ||
const optV: { lowS?: boolean } = { lowS: true }; // standard opts for verify() | ||
type OptS = { lowS?: boolean; extraEntropy?: boolean | Hex; }; | ||
type OptV = { lowS?: boolean }; | ||
const optS: OptS = { lowS: true }; // opts for sign() | ||
const optV: OptV = { lowS: true }; // standard opts for verify() | ||
type BC = { seed: Bytes, k2sig : (kb: Bytes) => SignatureWithRecovery | undefined }; // Bytes+predicate checker | ||
const prepSig = (msgh: Hex, priv: PrivKey, opts=optS): BC => {// prepare for RFC6979 sig generation | ||
if (['der', 'recovered', 'canonical'].some(k => k in opts)) // Ban legacy options | ||
err('sign() legacy options not supported'); | ||
const prepSig = (msgh: Hex, priv: PrivKey, opts: OptS = optS): BC => {// prepare for RFC6979 sig generation | ||
if (['der', 'recovered', 'canonical'].some(k => k in opts)) err('option not supported'); // legacy opts | ||
let { lowS } = opts; // generates low-s sigs by default | ||
@@ -255,8 +269,4 @@ if (lowS == null) lowS = true; // RFC6979 3.2: we skip step A | ||
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); | ||
} | ||
if (ent) // K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k') | ||
seed.push(ent === true ? etc.randomBytes(fLen) : toU8(ent)); // true == fetch from CSPRNG | ||
const m = h1i; // convert msg to bigint | ||
@@ -268,10 +278,10 @@ const k2sig = (kBytes: Bytes): SignatureWithRecovery | undefined => { // Transform k => Signature. | ||
const q = G.mul(k).aff(); // q = Gk | ||
const r = mod(q.x, N); // r = q.x mod n | ||
const r = M(q.x, N); // r = q.x mod n | ||
if (r === 0n) return; // r=0 invalid | ||
const s = mod(ik * mod(m + mod(d * r, N), N), N); // s = k^-1(m + rd) mod n | ||
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 && moreThanHalfN(s)) { // if lowS was passed, ensure s is always | ||
normS = mod(-s, N); // in the bottom half of CURVE.n | ||
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; | ||
@@ -341,17 +351,17 @@ } | ||
} | ||
} | ||
}; | ||
// ECDSA signature generation. via secg.org/sec1-v2.pdf 4.1.2 + RFC6979 deterministic k | ||
const signAsync = async (msgh: Hex, priv: PrivKey, opts = optS): Promise<SignatureWithRecovery> => { | ||
const signAsync = async (msgh: Hex, priv: PrivKey, opts: OptS = optS): Promise<SignatureWithRecovery> => { | ||
const { seed, k2sig } = prepSig(msgh, priv, opts); // Extract arguments for hmac-drbg | ||
return hmacDrbg<SignatureWithRecovery>(true)(seed, k2sig); // Re-run drbg until k2sig returns ok | ||
} | ||
const sign = (msgh: Hex, priv: PrivKey, opts = optS): SignatureWithRecovery => { | ||
}; | ||
const sign = (msgh: Hex, priv: PrivKey, opts: OptS = optS): SignatureWithRecovery => { | ||
const { seed, k2sig } = prepSig(msgh, priv, opts); // Extract arguments for hmac-drbg | ||
return hmacDrbg<SignatureWithRecovery>(false)(seed, k2sig); // Re-run drbg until k2sig returns ok | ||
} | ||
}; | ||
type SigLike = { r: bigint, s: bigint }; | ||
const verify = (sig: Hex | SigLike, msgh: Hex, pub: Hex, opts = optV): boolean => { | ||
const verify = (sig: Hex | SigLike, msgh: Hex, pub: Hex, opts: OptV = optV): boolean => { | ||
let { lowS } = opts; // ECDSA signature verification | ||
if (lowS == null) lowS = true; // Default lowS=true | ||
if ('strict' in opts) err('verify() legacy options not supported'); // legacy param | ||
if ('strict' in opts) err('option not supported'); // legacy param | ||
let sig_: Signature, h: bigint, P: Point; // secg.org/sec1-v2.pdf 4.1.4 | ||
@@ -363,3 +373,3 @@ const rs = sig && typeof sig === 'object' && 'r' in sig; // Previous ver supported DER sigs. We | ||
sig_ = rs ? new Signature(sig.r, sig.s).assertValidity() : Signature.fromCompact(sig); | ||
h = bits2int_modN(toU8(msgh)); // Truncate hash | ||
h = bits2int_modN(toU8(msgh)); // Truncate hash | ||
P = pub instanceof Point ? pub.ok() : Point.fromHex(pub); // Validate public key | ||
@@ -369,28 +379,31 @@ } catch (e) { return false; } // Check sig for validity in both cases | ||
const { r, s } = sig_; | ||
if (lowS && moreThanHalfN(s)) return false; // lowS bans sig.s >= CURVE.n/2 | ||
if (lowS && high(s)) return false; // lowS bans sig.s >= CURVE.n/2 | ||
let R: AffinePoint; | ||
try { | ||
const is = inv(s, N); // s^-1 | ||
const u1 = mod(h * is, N); // u1 = hs^-1 mod n | ||
const u2 = mod(r * is, N); // u2 = rs^-1 mod n | ||
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 = mod(R.x, N); // R.x must be in N's field, not P's | ||
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: Hex, pubB: Hex, isCompressed = true): Bytes => { | ||
return Point.fromHex(pubB).mul(toPriv(privA)).toRawBytes(isCompressed); // ECDH | ||
} | ||
}; | ||
const hashToPrivateKey = (hash: Hex): Bytes => { // FIPS 186 B.4.1 compliant key generation | ||
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 = mod(b2n(hash), N - 1n) + 1n; // takes at least n+8 bytes | ||
return n2b(num); | ||
if (hash.length < fLen + 8 || hash.length > 1024) err('expected 40-1024b'); // being neglible. | ||
const num = M(b2n(hash), N - 1n); // takes n+8 bytes | ||
return n2b(num + 1n); // returns (hash mod n-1)+1 | ||
} | ||
const etc = { // Not placed in utils because they | ||
hexToBytes: h2b, bytesToHex: b2h, // share API with noble-curves. | ||
concatBytes: concatB, bytesToNumberBE: b2n, numberToBytesBE: n2b, | ||
mod, invert: inv, // math utilities | ||
hexToBytes: h2b as (hex: string) => Bytes, // share API with noble-curves. | ||
bytesToHex: b2h as (bytes: Bytes) => string, | ||
concatBytes: concatB as (...arrs: Bytes[]) => Bytes, | ||
bytesToNumberBE: b2n as (a: Bytes) => bigint, | ||
numberToBytesBE: n2b as (n: bigint) => Bytes, | ||
mod: M as (a: bigint, md?: bigint) => bigint, | ||
invert: inv as (num: bigint, md?: bigint) => bigint, // math utilities | ||
hmacSha256Async: async (key: Bytes, ...msgs: Bytes[]): Promise<Bytes> => { | ||
@@ -403,4 +416,4 @@ const c = cr(); // async HMAC-SHA256, no sync built-in! | ||
}, | ||
hmacSha256Sync: _hmacSync, // For TypeScript. Actual logic is below | ||
hashToPrivateKey, | ||
hmacSha256Sync: _hmacSync as HmacFnSync, // For TypeScript. Actual logic is below | ||
hashToPrivateKey: hashToPrivateKey as (hash: Hex) => Bytes, | ||
randomBytes: (len = 32): Bytes => { // CSPRNG (random number generator) | ||
@@ -411,8 +424,8 @@ const crypto = cr(); // Must be shimmed in node.js <= 18 to prevent error. See README. | ||
}, | ||
} | ||
}; | ||
const utils = { // utilities | ||
normPrivateKeyToScalar: toPriv, | ||
isValidPrivateKey: (key: Hex) => { try { return !!toPriv(key); } catch (e) { return false; } }, | ||
normPrivateKeyToScalar: toPriv as (p: PrivKey) => bigint, | ||
isValidPrivateKey: (key: Hex): boolean => { try { return !!toPriv(key); } catch (e) { return false; } }, | ||
randomPrivateKey: (): Bytes => hashToPrivateKey(etc.randomBytes(fLen + 16)), // FIPS 186 B.4.1. | ||
precompute(w=8, p: Point = G) { p.multiply(3n); w; return p; }, // no-op | ||
precompute(w=8, p: Point = G): Point { p.multiply(3n); w; return p; }, // no-op | ||
}; | ||
@@ -419,0 +432,0 @@ Object.defineProperties(etc, { hmacSha256Sync: { // Allow setting it once, ignore then |
{ | ||
"name": "@noble/secp256k1", | ||
"version": "2.1.0", | ||
"version": "2.2.0", | ||
"description": "Fastest 4KB JS implementation of secp256k1 ECDH & ECDSA signatures compliant with RFC6979", | ||
@@ -20,4 +20,4 @@ "files": [ | ||
"build:release": "npm run --silent build:min > test/build/noble-secp256k1.min.js; npm run --silent build:mingz > test/build/noble-secp256k1.min.js.gz", | ||
"test": "node test/index.test.js", | ||
"test:webcrypto": "node test/secp256k1.webcrypto.test.js", | ||
"test": "node test/index.js", | ||
"test:webcrypto": "node test/index.webcrypto.js", | ||
"bench": "node test/benchmark.js", | ||
@@ -34,8 +34,8 @@ "loc": "echo \"`npm run --silent build:min | wc -c` symbols `wc -l < index.ts` LOC, `npm run --silent build:mingz | wc -c`B gzipped\"" | ||
"devDependencies": { | ||
"@noble/hashes": "1.4.0", | ||
"@paulmillr/jsbt": "0.1.0", | ||
"@noble/hashes": "1.6.1", | ||
"@paulmillr/jsbt": "0.2.1", | ||
"fast-check": "3.0.0", | ||
"micro-bmark": "0.3.0", | ||
"micro-bmark": "0.3.1", | ||
"micro-should": "0.4.0", | ||
"typescript": "5.3.2" | ||
"typescript": "5.5.2" | ||
}, | ||
@@ -42,0 +42,0 @@ "keywords": [ |
148
README.md
@@ -5,3 +5,3 @@ # noble-secp256k1 | ||
- ✍️ Deterministic [ECDSA](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm) | ||
- ✍️ [ECDSA](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm) | ||
signatures compliant with [RFC6979](https://www.rfc-editor.org/rfc/rfc6979) | ||
@@ -12,4 +12,5 @@ - 🤝 Elliptic Curve Diffie-Hellman [ECDH](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie–Hellman) | ||
Use larger drop-in replacement [noble-curves](https://github.com/paulmillr/noble-curves) instead, | ||
if you need additional features such as common.js, Schnorr signatures, DER encoding or support for different hash functions. To upgrade from v1 to v2, see [Upgrading](#upgrading). | ||
The module is a sister project of [noble-curves](https://github.com/paulmillr/noble-curves), | ||
focusing on smaller attack surface & better auditability. | ||
Curves are drop-in replacement and have more features: Common.js, Schnorr signatures, DER encoding or support for different hash functions. To upgrade from v1 to v2, see [Upgrading](#upgrading). | ||
@@ -33,4 +34,6 @@ ### This library belongs to _noble_ cryptography | ||
> npm install @noble/secp256k1 | ||
> `npm install @noble/secp256k1` | ||
> `deno add @noble/secp256k1` | ||
We support all major platforms and runtimes. For node.js <= 18 and React Native, additional polyfills are needed: see below. | ||
@@ -40,7 +43,6 @@ | ||
import * as secp from '@noble/secp256k1'; | ||
// import * as secp from "https://deno.land/x/secp256k1/mod.ts"; // Deno | ||
// import * as secp from "https://unpkg.com/@noble/secp256k1"; // Unpkg | ||
(async () => { | ||
// keys, messages & other inputs can be Uint8Arrays or hex strings | ||
// Uint8Array.from([0xde, 0xad, 0xbe, 0xef]) === 'deadbeef' | ||
// Uint8Arrays or hex strings are accepted: | ||
// Uint8Array.from([0xde, 0xad, 0xbe, 0xef]) is equal to 'deadbeef' | ||
const privKey = secp.utils.randomPrivateKey(); // Secure random private key | ||
@@ -66,3 +68,3 @@ // sha256 of 'hello world' | ||
import { sha256 } from '@noble/hashes/sha256'; | ||
secp.etc.hmacSha256Sync = (k, ...m) => hmac(sha256, k, secp.etc.concatBytes(...m)) | ||
secp.etc.hmacSha256Sync = (k, ...m) => hmac(sha256, k, secp.etc.concatBytes(...m)); | ||
// Sync methods can be used now: | ||
@@ -92,3 +94,3 @@ // secp.sign(msgHash, privKey); | ||
```ts | ||
type Hex = Uint8Array | string | ||
type Hex = Uint8Array | string; | ||
``` | ||
@@ -114,3 +116,3 @@ | ||
privateKey: Hex, // private key which will sign the hash | ||
opts?: { lowS: boolean, extraEntropy: boolean | Hex } // optional params | ||
opts?: { lowS: boolean; extraEntropy: boolean | Hex } // optional params | ||
): Signature; | ||
@@ -133,9 +135,9 @@ function signAsync( | ||
2. `extraEntropy: true` improves security by adding entropy, follows section 3.6 of RFC6979: | ||
- No disadvantage: if an entropy generator is broken, sigs would be the same | ||
as they are without the option | ||
- It would help a lot in case there is an error somewhere in `k` gen. | ||
Exposing `k` could leak private keys | ||
- Sigs with extra entropy would have different `r` / `s`, which means they | ||
would still be valid, but may break some test vectors if you're | ||
cross-testing against other libs | ||
- No disadvantage: if an entropy generator is broken, sigs would be the same | ||
as they are without the option | ||
- It would help a lot in case there is an error somewhere in `k` gen. | ||
Exposing `k` could leak private keys | ||
- Sigs with extra entropy would have different `r` / `s`, which means they | ||
would still be valid, but may break some test vectors if you're | ||
cross-testing against other libs | ||
@@ -238,3 +240,3 @@ ### verify | ||
} | ||
CURVE // curve prime; order; equation params, generator coordinates | ||
CURVE; // curve prime; order; equation params, generator coordinates | ||
``` | ||
@@ -245,31 +247,50 @@ | ||
The module is production-ready. | ||
It is cross-tested against [noble-curves](https://github.com/paulmillr/noble-curves), | ||
and has similar security. | ||
While [noble-curves](https://github.com/paulmillr/noble-curves) provide improved security, | ||
we cross-test against curves. | ||
1. The current version is rewrite of v1, which has been audited by cure53: | ||
[PDF](https://cure53.de/pentest-report_noble-lib.pdf) (funded by [Umbra.cash](https://umbra.cash) & community). | ||
1. The current version has not been independently audited. It is a rewrite of v1, which has been audited by cure53 in Apr 2021: | ||
[PDF](https://cure53.de/pentest-report_noble-lib.pdf) (funded by [Umbra.cash](https://umbra.cash) & community). | ||
2. It's being fuzzed by [Guido Vranken's cryptofuzz](https://github.com/guidovranken/cryptofuzz): | ||
run the fuzzer by yourself to check. | ||
you can also run the fuzzer by yourself. | ||
Our EC multiplication is hardened to be algorithmically constant time. | ||
We're using built-in JS `BigInt`, which is potentially vulnerable to | ||
[timing attacks](https://en.wikipedia.org/wiki/Timing_attack) as | ||
[per MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#cryptography). | ||
But, _JIT-compiler_ and _Garbage Collector_ make "constant time" extremely hard | ||
to achieve in a scripting language. Which means _any other JS library doesn't | ||
use constant-time bigints_. Including bn.js or anything else. | ||
Even statically typed Rust, a language without GC, | ||
### Constant-timeness | ||
_JIT-compiler_ and _Garbage Collector_ make "constant time" extremely hard to | ||
achieve [timing attack](https://en.wikipedia.org/wiki/Timing_attack) resistance | ||
in a scripting language. Which means _any other JS library can't have | ||
constant-timeness_. Even statically typed Rust, a language without GC, | ||
[makes it harder to achieve constant-time](https://www.chosenplaintext.ca/open-source/rust-timing-shield/security) | ||
for some cases. If your goal is absolute security, don't use any JS lib — | ||
including bindings to native ones. Use low-level libraries & languages. | ||
for some cases. If your goal is absolute security, don't use any JS lib — including bindings to native ones. | ||
Use low-level libraries & languages. Nonetheless we're targetting algorithmic constant time. | ||
### Supply chain security | ||
1. **Commits** are signed with PGP keys, to prevent forgery. Make sure to verify commit signatures. | ||
2. **Releases** are transparent and built on GitHub CI. Make sure to verify [provenance](https://docs.npmjs.com/generating-provenance-statements) logs | ||
3. **Rare releasing** is followed. | ||
The less often it is done, the less code dependents would need to audit | ||
4. **Dependencies** are minimal: | ||
- All deps are prevented from automatic updates and have locked-down version ranges. Every update is checked with `npm-diff` | ||
- Updates themselves are rare, to ensure rogue updates are not catched accidentally | ||
5. devDependencies are only used if you want to contribute to the repo. They are disabled for end-users: | ||
- [noble-hashes](https://github.com/paulmillr/noble-hashes) is used, by the same author, to provide hashing functionality tests | ||
- micro-bmark and micro-should are developed by the same author and follow identical security practices | ||
- fast-check (property-based testing) and typescript are used for code quality, vector generation and ts compilation. | ||
The packages are big, which makes it hard to audit their source code thoroughly and fully | ||
We consider infrastructure attacks like rogue NPM modules very important; | ||
that's why it's crucial to minimize the amount of 3rd-party dependencies & native | ||
bindings. If your app uses 500 dependencies, any dep could get hacked and you'll | ||
be downloading malware with every `npm install`. Our goal is to minimize this attack vector. | ||
that's why it's crucial to minimize the amount of 3rd-party dependencies & native bindings. | ||
If your app uses 500 dependencies, any dep could get hacked and you'll be | ||
downloading malware with every install. Our goal is to minimize this attack vector. | ||
As for key generation, we're deferring to built-in | ||
If you see anything unusual: investigate and report. | ||
### Randomness | ||
We're deferring to built-in | ||
[crypto.getRandomValues](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues) | ||
which is considered cryptographically secure (CSPRNG). | ||
In the past, browsers had bugs that made it weak: it may happen again. | ||
## Speed | ||
@@ -305,12 +326,2 @@ | ||
## Contributing | ||
1. Clone the repository. | ||
2. `npm install` to install build dependencies like TypeScript | ||
3. `npm run build` to compile TypeScript code | ||
4. `npm test` to run jest on `test/index.ts` | ||
Special thanks to [Roman Koblov](https://github.com/romankoblov), who have | ||
helped to improve scalar multiplication speed. | ||
## Upgrading | ||
@@ -337,20 +348,20 @@ | ||
- `getPublicKey` | ||
- now produce 33-byte compressed signatures by default | ||
- to use old behavior, which produced 65-byte uncompressed keys, set | ||
argument `isCompressed` to `false`: `getPublicKey(priv, false)` | ||
- now produce 33-byte compressed signatures by default | ||
- to use old behavior, which produced 65-byte uncompressed keys, set | ||
argument `isCompressed` to `false`: `getPublicKey(priv, false)` | ||
- `sign` | ||
- is now sync; use `signAsync` for async version | ||
- now returns `Signature` instance with `{ r, s, recovery }` properties | ||
- `canonical` option was renamed to `lowS` | ||
- `recovered` option has been removed because recovery bit is always returned now | ||
- `der` option has been removed. There are 2 options: | ||
1. Use compact encoding: `fromCompact`, `toCompactRawBytes`, `toCompactHex`. | ||
Compact encoding is simply a concatenation of 32-byte r and 32-byte s. | ||
2. If you must use DER encoding, switch to noble-curves (see above). | ||
- is now sync; use `signAsync` for async version | ||
- now returns `Signature` instance with `{ r, s, recovery }` properties | ||
- `canonical` option was renamed to `lowS` | ||
- `recovered` option has been removed because recovery bit is always returned now | ||
- `der` option has been removed. There are 2 options: | ||
1. Use compact encoding: `fromCompact`, `toCompactRawBytes`, `toCompactHex`. | ||
Compact encoding is simply a concatenation of 32-byte r and 32-byte s. | ||
2. If you must use DER encoding, switch to noble-curves (see above). | ||
- `verify` | ||
- `strict` option was renamed to `lowS` | ||
- `strict` option was renamed to `lowS` | ||
- `getSharedSecret` | ||
- now produce 33-byte compressed signatures by default | ||
- to use old behavior, which produced 65-byte uncompressed keys, set | ||
argument `isCompressed` to `false`: `getSharedSecret(a, b, false)` | ||
- now produce 33-byte compressed signatures by default | ||
- to use old behavior, which produced 65-byte uncompressed keys, set | ||
argument `isCompressed` to `false`: `getSharedSecret(a, b, false)` | ||
- `recoverPublicKey(msg, sig, rec)` was changed to `sig.recoverPublicKey(msg)` | ||
@@ -362,4 +373,17 @@ - `number` type for private keys have been removed: use `bigint` instead | ||
## Contributing & testing | ||
* `npm install && npm run build && npm test` will build the code and run tests. | ||
* `npm run bench` will run benchmarks, which may need their deps first (`npm run bench:install`) | ||
* `npm run loc` will count total output size, important to be less than 4KB | ||
Check out [github.com/paulmillr/guidelines](https://github.com/paulmillr/guidelines) | ||
for general coding practices and rules. | ||
See [paulmillr.com/noble](https://paulmillr.com/noble/) | ||
for useful resources, articles, documentation and demos | ||
related to the library. | ||
## License | ||
MIT (c) Paul Miller [(https://paulmillr.com)](https://paulmillr.com), see LICENSE file. |
77022
1084
377