@noble/secp256k1
Advanced tools
Comparing version 2.0.0 to 2.1.0
@@ -43,2 +43,5 @@ declare const CURVE: { | ||
declare function getPublicKey(privKey: PrivKey, isCompressed?: boolean): Uint8Array; | ||
type SignatureWithRecovery = Signature & { | ||
recovery: number; | ||
}; | ||
declare class Signature { | ||
@@ -51,4 +54,5 @@ readonly r: bigint; | ||
assertValidity(): this; | ||
addRecoveryBit(rec: number): Signature; | ||
addRecoveryBit(rec: number): SignatureWithRecovery; | ||
hasHighS(): boolean; | ||
normalizeS(): Signature; | ||
recoverPublicKey(msgh: Hex): Point; | ||
@@ -59,10 +63,10 @@ toCompactRawBytes(): Uint8Array; | ||
type HmacFnSync = undefined | ((key: Bytes, ...msgs: Bytes[]) => Bytes); | ||
declare function signAsync(msgh: Hex, priv: Hex, opts?: { | ||
declare function signAsync(msgh: Hex, priv: PrivKey, opts?: { | ||
lowS?: boolean | undefined; | ||
extraEntropy?: boolean | Hex | undefined; | ||
}): Promise<Signature>; | ||
declare function sign(msgh: Hex, priv: Hex, opts?: { | ||
}): Promise<SignatureWithRecovery>; | ||
declare function sign(msgh: Hex, priv: PrivKey, opts?: { | ||
lowS?: boolean | undefined; | ||
extraEntropy?: boolean | Hex | undefined; | ||
}): Signature; | ||
}): SignatureWithRecovery; | ||
type SigLike = { | ||
@@ -88,3 +92,3 @@ r: bigint; | ||
hashToPrivateKey: typeof hashToPrivateKey; | ||
randomBytes: (len: number) => Bytes; | ||
randomBytes: (len?: number) => Bytes; | ||
}; | ||
@@ -91,0 +95,0 @@ declare const utils: { |
87
index.js
@@ -9,3 +9,3 @@ /*! noble-secp256k1 - MIT License (c) 2019 Paul Miller (paulmillr.com) */ | ||
const fLen = 32; // field / group byte length | ||
const crv = (x) => mod(mod(x * x) * x + CURVE.b); // x³ + ax + b weierstrass formula; no a | ||
const crv = (x) => mod(mod(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 | ||
@@ -16,10 +16,11 @@ const big = (n) => typeof n === 'bigint'; // is big integer | ||
const ge = (n) => big(n) && 0n < n && n < N; // is group element | ||
const au8 = (a, l) => // is Uint8Array (of specific length) | ||
!(a instanceof Uint8Array) || (typeof l === 'number' && l > 0 && a.length !== l) ? | ||
const isu8 = (a) => (a instanceof Uint8Array || | ||
(a != null && typeof a === 'object' && 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(str(a) ? h2b(a) : u8n(a), len); // norm(hex/u8a) to u8a | ||
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 | ||
let Gpows = undefined; // precomputes for base point G | ||
class Point { | ||
@@ -31,3 +32,5 @@ constructor(px, py, pz) { | ||
} //3d=less inversions | ||
static fromAffine(p) { return new Point(p.x, p.y, 1n); } | ||
static fromAffine(p) { | ||
return ((p.x === 0n) && (p.y === 0n)) ? Point.ZERO : new Point(p.x, p.y, 1n); | ||
} | ||
static fromHex(hex) { | ||
@@ -216,5 +219,5 @@ hex = toU8(hex); // convert hex string to Uint8Array | ||
const moreThanHalfN = (n) => n > (N >> 1n); // if a number is bigger than CURVE.n/2 | ||
function getPublicKey(privKey, isCompressed = true) { | ||
const getPublicKey = (privKey, isCompressed = true) => { | ||
return Point.fromPrivateKey(privKey).toRawBytes(isCompressed); // 33b or 65b output | ||
} | ||
}; | ||
class Signature { | ||
@@ -232,4 +235,9 @@ constructor(r, s, recovery) { | ||
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); } | ||
addRecoveryBit(rec) { | ||
return new Signature(this.r, this.s, rec); | ||
} | ||
hasHighS() { return moreThanHalfN(this.s); } | ||
normalizeS() { | ||
return this.hasHighS() ? new Signature(this.r, mod(this.s, N), this.recovery) : this; | ||
} | ||
recoverPublicKey(msgh) { | ||
@@ -239,3 +247,3 @@ const { r, s, recovery: rec } = this; // secg.org/sec1-v2.pdf 4.1.6 | ||
err('recovery id invalid'); // check recovery id | ||
const h = bits2int_modN(toU8(msgh, 32)); // Truncate hash | ||
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 | ||
@@ -268,3 +276,3 @@ if (radj >= P) | ||
const optV = { lowS: true }; // standard opts for verify() | ||
function prepSig(msgh, priv, opts = optS) { | ||
const prepSig = (msgh, priv, opts = optS) => { | ||
if (['der', 'recovered', 'canonical'].some(k => k in opts)) // Ban legacy options | ||
@@ -310,3 +318,3 @@ err('sign() legacy options not supported'); | ||
return { seed: concatB(...seed), k2sig }; | ||
} | ||
}; | ||
function hmacDrbg(asynchronous) { | ||
@@ -377,11 +385,11 @@ let v = u8n(fLen); // Minimal non-full-spec HMAC-DRBG from NIST 800-90 for RFC6979 sigs. | ||
// ECDSA signature generation. via secg.org/sec1-v2.pdf 4.1.2 + RFC6979 deterministic k | ||
async function signAsync(msgh, priv, opts = optS) { | ||
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 hmac-drbg until k2sig returns ok | ||
} | ||
function sign(msgh, priv, opts = optS) { | ||
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 hmac-drbg until k2sig returns ok | ||
} | ||
function verify(sig, msgh, pub, opts = optV) { | ||
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 | ||
@@ -398,3 +406,3 @@ if (lowS == null) | ||
sig_ = rs ? new Signature(sig.r, sig.s).assertValidity() : Signature.fromCompact(sig); | ||
h = bits2int_modN(toU8(msgh, fLen)); // Truncate hash | ||
h = bits2int_modN(toU8(msgh)); // Truncate hash | ||
P = pub instanceof Point ? pub.ok() : Point.fromHex(pub); // Validate public key | ||
@@ -422,9 +430,9 @@ } | ||
return false; // stop if R is identity / zero point | ||
const v = mod(R.x, N); // <== The weird ECDSA part. R.x must be in N's field, not P's | ||
const v = mod(R.x, N); // R.x must be in N's field, not P's | ||
return v === r; // mod(R.x, n) == r | ||
} | ||
function getSharedSecret(privA, pubB, isCompressed = true) { | ||
}; | ||
const getSharedSecret = (privA, pubB, isCompressed = true) => { | ||
return Point.fromHex(pubB).mul(toPriv(privA)).toRawBytes(isCompressed); // ECDH | ||
} | ||
function hashToPrivateKey(hash) { | ||
}; | ||
const hashToPrivateKey = (hash) => { | ||
hash = toU8(hash); // produces private keys with modulo bias | ||
@@ -436,22 +444,20 @@ const minLen = fLen + 8; // being neglible. | ||
return n2b(num); | ||
} | ||
}; | ||
const etc = { | ||
hexToBytes: h2b, bytesToHex: b2h, | ||
hexToBytes: h2b, bytesToHex: b2h, // share API with noble-curves. | ||
concatBytes: concatB, bytesToNumberBE: b2n, numberToBytesBE: n2b, | ||
mod, invert: inv, | ||
mod, invert: inv, // math utilities | ||
hmacSha256Async: async (key, ...msgs) => { | ||
const crypto = cr(); // HMAC-SHA256 async. No sync built-in! | ||
if (!crypto) | ||
return err('etc.hmacSha256Async not set'); // Uses webcrypto: native cryptography. | ||
const s = crypto.subtle; | ||
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, | ||
hmacSha256Sync: _hmacSync, // For TypeScript. Actual logic is below | ||
hashToPrivateKey, | ||
randomBytes: (len) => { | ||
const crypto = cr(); // Can be shimmed in node.js <= 18 to prevent error: | ||
// import { webcrypto } from 'node:crypto'; | ||
// if (!globalThis.crypto) globalThis.crypto = webcrypto; | ||
if (!crypto) | ||
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'); | ||
@@ -469,4 +475,4 @@ return crypto.getRandomValues(u8n(len)); | ||
} }, | ||
randomPrivateKey: () => hashToPrivateKey(etc.randomBytes(fLen + 8)), | ||
precompute(w = 8, p = G) { p.multiply(3n); return p; }, // no-op | ||
randomPrivateKey: () => hashToPrivateKey(etc.randomBytes(fLen + 16)), // FIPS 186 B.4.1. | ||
precompute(w = 8, p = G) { p.multiply(3n); w; return p; }, // no-op | ||
}; | ||
@@ -493,2 +499,3 @@ Object.defineProperties(etc, { hmacSha256Sync: { | ||
}; | ||
let Gpows = undefined; // precomputes for base point G | ||
const wNAF = (n) => { | ||
@@ -495,0 +502,0 @@ // Compared to other point mult methods, |
108
index.ts
@@ -7,6 +7,6 @@ /*! 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 | ||
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; no a | ||
const crv = (x: bigint) => mod(mod(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 | ||
@@ -17,10 +17,13 @@ const big = (n: unknown): n is bigint => typeof n === 'bigint'; // is big integer | ||
const ge = (n: bigint) => big(n) && 0n < n && n < N; // is group element | ||
const au8 = (a: unknown, l?: number): Bytes => // is Uint8Array (of specific length) | ||
!(a instanceof Uint8Array) || (typeof l === 'number' && l > 0 && a.length !== l) ? | ||
const isu8 = (a: unknown): a is Uint8Array => ( | ||
a instanceof Uint8Array || | ||
(a != null && typeof a === 'object' && a.constructor.name === 'Uint8Array') | ||
); | ||
const au8 = (a: unknown, l?: number): Bytes => // assert is Uint8Array (of specific length) | ||
!isu8(a) || (typeof l === 'number' && l > 0 && a.length !== l) ? | ||
err('Uint8Array expected') : a; | ||
const u8n = (data?: any) => new Uint8Array(data); // creates Uint8Array | ||
const toU8 = (a: Hex, len?: number) => au8(str(a) ? h2b(a) : u8n(a), len); // norm(hex/u8a) to u8a | ||
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 | ||
let Gpows: Point[] | undefined = undefined; // precomputes for base point G | ||
interface AffinePoint { x: bigint, y: bigint } // Point in 2d xy affine coordinates | ||
@@ -31,3 +34,5 @@ class Point { // Point in 3d xyz projective coordinates | ||
static readonly ZERO = new Point(0n, 1n, 0n); // Identity / zero point | ||
static fromAffine(p: AffinePoint) { return new Point(p.x, p.y, 1n); } | ||
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); | ||
} | ||
static fromHex(hex: Hex): Point { // Convert Uint8Array or hex string to Point | ||
@@ -65,3 +70,3 @@ hex = toU8(hex); // convert hex string to Uint8Array | ||
const { a, b } = CURVE; // Cost: 12M + 0S + 3*a + 3*b3 + 23add | ||
let X3 = 0n, Y3 = 0n, Z3 = 0n; | ||
let X3 = 0n, Y3 = 0n, Z3 = 0n; | ||
const b3 = mod(b * 3n); | ||
@@ -86,3 +91,3 @@ let t0 = mod(X1 * X2), t1 = mod(Y1 * Y2), t2 = mod(Z1 * Z2), t3 = mod(X1 + Y1); // step 1 | ||
} | ||
mul(n: bigint, safe = true) { // Point scalar multiplication. | ||
mul(n: bigint, safe = true) { // Point scalar multiplication. | ||
if (!safe && n === 0n) return I; // in unsafe mode, allow zero | ||
@@ -145,3 +150,3 @@ if (!ge(n)) err('invalid scalar'); // must be 0 < n < CURVE.n | ||
const slcNum = (b: Bytes, from: number, to: number) => b2n(b.slice(from, to)); // slice bytes num | ||
const n2b = (num: bigint): Bytes => { // number to 32bytes. mustbe 0 <= num < B256 | ||
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'); | ||
@@ -178,7 +183,8 @@ }; | ||
}; | ||
const moreThanHalfN = (n: bigint): boolean => n > (N >> 1n) // if a number is bigger than CURVE.n/2 | ||
function getPublicKey(privKey: PrivKey, isCompressed = true) { // Make public key from priv | ||
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 | ||
} | ||
class Signature { // ECDSA Signature class | ||
type SignatureWithRecovery = Signature & { recovery: number } | ||
class Signature { // ECDSA Signature class | ||
constructor(readonly r: bigint, readonly s: bigint, readonly recovery?: number) { | ||
@@ -192,8 +198,13 @@ this.assertValidity(); // recovery bit is optional when | ||
assertValidity() { return ge(this.r) && ge(this.s) ? this : err(); } // 0 < r or s < CURVE.n | ||
addRecoveryBit(rec: number) { return new Signature(this.r, this.s, rec); } | ||
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 | ||
} | ||
recoverPublicKey(msgh: Hex): Point { // ECDSA public key recovery | ||
const { r, s, recovery: rec } = this; // secg.org/sec1-v2.pdf 4.1.6 | ||
if (![0, 1, 2, 3].includes(rec!)) err('recovery id invalid'); // check recovery id | ||
const h = bits2int_modN(toU8(msgh, 32)); // Truncate hash | ||
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 | ||
@@ -216,4 +227,4 @@ if (radj >= P) err('q.x invalid'); // ensure q.x is still a field element | ||
}; | ||
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: Uint8Array): bigint => { // int2octets can't be used; pads small msgs | ||
return mod(bits2int(bytes), N); // with 0: BAD for trunc as per RFC vectors | ||
}; | ||
@@ -228,4 +239,4 @@ const i2o = (num: bigint): Bytes => n2b(num); // int to octets | ||
const optV: { lowS?: boolean } = { lowS: true }; // standard opts for verify() | ||
type BC = { seed: Bytes, k2sig : (kb: Bytes) => Signature | undefined }; // Bytes+predicate checker | ||
function prepSig(msgh: Hex, priv: Hex, opts = optS): BC { // prepare for RFC6979 sig generation | ||
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 | ||
@@ -247,3 +258,3 @@ err('sign() legacy options not supported'); | ||
const m = h1i; // convert msg to bigint | ||
const k2sig = (kBytes: Bytes): Signature | undefined => { // Transform k into Signature. | ||
const k2sig = (kBytes: Bytes): SignatureWithRecovery | undefined => { // Transform k => Signature. | ||
const k = bits2int(kBytes); // RFC6979 method. | ||
@@ -263,3 +274,3 @@ if (!ge(k)) return; // Check 0 < k < CURVE.n | ||
} | ||
return new Signature(r, normS, rec); // use normS, not s | ||
return new Signature(r, normS, rec) as SignatureWithRecovery; // use normS, not s | ||
}; | ||
@@ -274,3 +285,3 @@ return { seed: concatB(...seed), k2sig } | ||
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 | ||
let i = 0; // Iterations counter, will throw when over 1000 | ||
const reset = () => { v.fill(1); k.fill(0); i = 0; }; | ||
@@ -294,4 +305,4 @@ const _e = 'drbg: tried 1000 values'; | ||
reset(); // the returned fn, don't, it's: 1. slower (JIT). 2. unsafe (async race conditions) | ||
await reseed(seed); // Steps D-G | ||
let res: T | undefined = undefined; // Step H: grind until k is in [1..n-1] | ||
await reseed(seed); // Steps D-G | ||
let res: T | undefined = undefined; // Step H: grind until k is in [1..n-1] | ||
while (!(res = pred(await gen()))) await reseed();// test predicate until it returns ok | ||
@@ -321,5 +332,5 @@ reset(); | ||
reset(); | ||
reseed(seed); // Steps D-G | ||
let res: T | undefined = undefined; // Step H: grind until k is in [1..n-1] | ||
while (!(res = pred(gen()))) reseed(); // test predicate until it returns ok | ||
reseed(seed); // Steps D-G | ||
let res: T | undefined = undefined; // Step H: grind until k is in [1..n-1] | ||
while (!(res = pred(gen()))) reseed(); // test predicate until it returns ok | ||
reset(); | ||
@@ -331,12 +342,12 @@ return res!; | ||
// ECDSA signature generation. via secg.org/sec1-v2.pdf 4.1.2 + RFC6979 deterministic k | ||
async function signAsync(msgh: Hex, priv: Hex, opts = optS): Promise<Signature> { | ||
const signAsync = async (msgh: Hex, priv: PrivKey, opts = optS): Promise<SignatureWithRecovery> => { | ||
const { seed, k2sig } = prepSig(msgh, priv, opts); // Extract arguments for hmac-drbg | ||
return hmacDrbg<Signature>(true)(seed, k2sig); // Re-run hmac-drbg until k2sig returns ok | ||
return hmacDrbg<SignatureWithRecovery>(true)(seed, k2sig); // Re-run drbg until k2sig returns ok | ||
} | ||
function sign(msgh: Hex, priv: Hex, opts = optS): Signature { | ||
const sign = (msgh: Hex, priv: PrivKey, opts = optS): SignatureWithRecovery => { | ||
const { seed, k2sig } = prepSig(msgh, priv, opts); // Extract arguments for hmac-drbg | ||
return hmacDrbg<Signature>(false)(seed, k2sig); // Re-run hmac-drbg until k2sig returns ok | ||
return hmacDrbg<SignatureWithRecovery>(false)(seed, k2sig); // Re-run drbg until k2sig returns ok | ||
} | ||
type SigLike = { r: bigint, s: bigint }; | ||
function verify(sig: Hex | SigLike, msgh: Hex, pub: Hex, opts = optV): boolean { | ||
const verify = (sig: Hex | SigLike, msgh: Hex, pub: Hex, opts = optV): boolean => { | ||
let { lowS } = opts; // ECDSA signature verification | ||
@@ -351,3 +362,3 @@ if (lowS == null) lowS = true; // Default lowS=true | ||
sig_ = rs ? new Signature(sig.r, sig.s).assertValidity() : Signature.fromCompact(sig); | ||
h = bits2int_modN(toU8(msgh, fLen)); // Truncate hash | ||
h = bits2int_modN(toU8(msgh)); // Truncate hash | ||
P = pub instanceof Point ? pub.ok() : Point.fromHex(pub); // Validate public key | ||
@@ -366,9 +377,9 @@ } catch (e) { return false; } // Check sig for validity in both cases | ||
if (!R) return false; // stop if R is identity / zero point | ||
const v = mod(R.x, N); // <== The weird ECDSA part. R.x must be in N's field, not P's | ||
const v = mod(R.x, N); // R.x must be in N's field, not P's | ||
return v === r; // mod(R.x, n) == r | ||
} | ||
function getSharedSecret(privA: Hex, pubB: Hex, isCompressed = true): Bytes { | ||
const getSharedSecret = (privA: Hex, pubB: Hex, isCompressed = true): Bytes => { | ||
return Point.fromHex(pubB).mul(toPriv(privA)).toRawBytes(isCompressed); // ECDH | ||
} | ||
function hashToPrivateKey(hash: Hex): Bytes { // FIPS 186 B.4.1 compliant key generation | ||
const hashToPrivateKey = (hash: Hex): Bytes => { // FIPS 186 B.4.1 compliant key generation | ||
hash = toU8(hash); // produces private keys with modulo bias | ||
@@ -380,3 +391,3 @@ const minLen = fLen + 8; // being neglible. | ||
} | ||
const etc = { // Not placed in utils because they | ||
const etc = { // Not placed in utils because they | ||
hexToBytes: h2b, bytesToHex: b2h, // share API with noble-curves. | ||
@@ -386,5 +397,5 @@ concatBytes: concatB, bytesToNumberBE: b2n, numberToBytesBE: n2b, | ||
hmacSha256Async: async (key: Bytes, ...msgs: Bytes[]): Promise<Bytes> => { | ||
const crypto = cr(); // HMAC-SHA256 async. No sync built-in! | ||
if (!crypto) return err('etc.hmacSha256Async not set'); // Uses webcrypto: native cryptography. | ||
const s = crypto.subtle; | ||
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']); | ||
@@ -395,17 +406,15 @@ return u8n(await s.sign('HMAC', k, concatB(...msgs))); | ||
hashToPrivateKey, | ||
randomBytes: (len: number): Bytes => { // CSPRNG (random number generator) | ||
const crypto = cr(); // Can be shimmed in node.js <= 18 to prevent error: | ||
// import { webcrypto } from 'node:crypto'; | ||
// if (!globalThis.crypto) globalThis.crypto = webcrypto; | ||
if (!crypto) err('crypto.getRandomValues must be defined'); | ||
randomBytes: (len = 32): Bytes => { // CSPRNG (random number generator) | ||
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 = { // utilities | ||
const utils = { // utilities | ||
normPrivateKeyToScalar: toPriv, | ||
isValidPrivateKey: (key: Hex) => { try { return !!toPriv(key); } catch (e) { return false; } }, | ||
randomPrivateKey: (): Bytes => hashToPrivateKey(etc.randomBytes(fLen + 8)), // FIPS 186 B.4.1. | ||
precompute(w=8, p: Point = G) { p.multiply(3n); return p; }, // no-op | ||
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 | ||
}; | ||
Object.defineProperties(etc, { hmacSha256Sync: { // Allow setting it once, ignore then | ||
Object.defineProperties(etc, { hmacSha256Sync: { // Allow setting it once, ignore then | ||
configurable: false, get() { return _hmacSync; }, set(f) { if (!_hmacSync) _hmacSync = f; }, | ||
@@ -426,2 +435,3 @@ } }); | ||
} | ||
let Gpows: Point[] | undefined = undefined; // precomputes for base point G | ||
const wNAF = (n: bigint): { p: Point; f: Point } => { // w-ary non-adjacent form (wNAF) method. | ||
@@ -428,0 +438,0 @@ // Compared to other point mult methods, |
{ | ||
"name": "@noble/secp256k1", | ||
"version": "2.0.0", | ||
"description": "Fastest 4KB JS implementation of secp256k1 elliptic curve. Auditable, high-security, 0-dependency ECDH & ECDSA signatures compliant with RFC6979", | ||
"version": "2.1.0", | ||
"description": "Fastest 4KB JS implementation of secp256k1 ECDH & ECDSA signatures compliant with RFC6979", | ||
"files": [ | ||
@@ -14,9 +14,12 @@ "index.js", | ||
"types": "index.d.ts", | ||
"sideEffects": false, | ||
"scripts": { | ||
"build": "tsc", | ||
"build:release": "rollup -c rollup.config.js", | ||
"test": "node test/secp256k1.test.mjs", | ||
"build:min": "cd test/build; npm install; npm run terser", | ||
"build:mingz": "npm run --silent build:min | gzip -c8", | ||
"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", | ||
"bench": "node test/benchmark.js", | ||
"min": "cd test/build; npm install; npm run terser", | ||
"loc": "echo \"`npm run --silent min | wc -c` symbols `wc -l < index.ts` LOC, `npm run --silent min | gzip -c8 | wc -c`B gzipped\"" | ||
"loc": "echo \"`npm run --silent build:min | wc -c` symbols `wc -l < index.ts` LOC, `npm run --silent build:mingz | wc -c`B gzipped\"" | ||
}, | ||
@@ -27,11 +30,12 @@ "author": "Paul Miller (https://paulmillr.com)", | ||
"type": "git", | ||
"url": "https://github.com/paulmillr/noble-secp256k1.git" | ||
"url": "git+https://github.com/paulmillr/noble-secp256k1.git" | ||
}, | ||
"license": "MIT", | ||
"devDependencies": { | ||
"@noble/hashes": "1.3.0", | ||
"@noble/hashes": "1.4.0", | ||
"@paulmillr/jsbt": "0.1.0", | ||
"fast-check": "3.0.0", | ||
"micro-bmark": "0.3.0", | ||
"micro-should": "0.4.0", | ||
"typescript": "5.0.2" | ||
"typescript": "5.3.2" | ||
}, | ||
@@ -58,8 +62,3 @@ "keywords": [ | ||
}, | ||
"funding": [ | ||
{ | ||
"type": "individual", | ||
"url": "https://paulmillr.com/funding/" | ||
} | ||
] | ||
"funding": "https://paulmillr.com/funding/" | ||
} |
226
README.md
# noble-secp256k1 | ||
[Fastest](#speed) 4KB JS implementation of [secp256k1](https://www.secg.org/sec2-v2.pdf) | ||
elliptic curve. Auditable, high-security, 0-dependency ECDH & ECDSA signatures compliant with RFC6979. | ||
Fastest 4KB JS implementation of secp256k1 signatures & ECDH. | ||
The library is a tiny single-feature version of | ||
[noble-curves](https://github.com/paulmillr/noble-curves), with some features | ||
removed. Check out curves as a drop-in replacement with | ||
Schnorr signatures, DER encoding and support for different hash functions. | ||
- ✍️ Deterministic [ECDSA](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm) | ||
signatures compliant with [RFC6979](https://www.rfc-editor.org/rfc/rfc6979) | ||
- 🤝 Elliptic Curve Diffie-Hellman [ECDH](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie–Hellman) | ||
- 📦 Pure ESM, can be imported without transpilers | ||
- 🪶 4KB gzipped, 450 lines of code | ||
Take a look at: [Upgrading](#upgrading) section for v1 to v2 transition instructions, | ||
[the online demo](https://paulmillr.com/noble/) and blog post | ||
[Learning fast elliptic-curve cryptography in JS](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/). | ||
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). | ||
### This library belongs to _noble_ crypto | ||
### This library belongs to _noble_ cryptography | ||
> **noble-crypto** — high-security, easily auditable set of contained cryptographic libraries and tools. | ||
> **noble-cryptography** — high-security, easily auditable set of contained cryptographic libraries and tools. | ||
- No dependencies, protection against supply chain attacks | ||
- Auditable TypeScript / JS code | ||
- Supported in all major browsers and stable node.js versions | ||
- All releases are signed with PGP keys | ||
- Zero or minimal dependencies | ||
- Highly readable TypeScript / JS code | ||
- PGP-signed releases and transparent NPM builds with provenance | ||
- Check out [homepage](https://paulmillr.com/noble/) & all libraries: | ||
[curves](https://github.com/paulmillr/noble-curves) | ||
(4kb versions [secp256k1](https://github.com/paulmillr/noble-secp256k1), | ||
[ed25519](https://github.com/paulmillr/noble-ed25519)), | ||
[hashes](https://github.com/paulmillr/noble-hashes) | ||
[ciphers](https://github.com/paulmillr/noble-ciphers), | ||
[curves](https://github.com/paulmillr/noble-curves), | ||
[hashes](https://github.com/paulmillr/noble-hashes), | ||
[post-quantum](https://github.com/paulmillr/noble-post-quantum), | ||
4kb [secp256k1](https://github.com/paulmillr/noble-secp256k1) / | ||
[ed25519](https://github.com/paulmillr/noble-ed25519) | ||
## Usage | ||
Browser, deno, node.js and unpkg are supported: | ||
> npm install @noble/secp256k1 | ||
We support all major platforms and runtimes. For node.js <= 18 and React Native, additional polyfills are needed: see below. | ||
```js | ||
import * as secp from '@noble/secp256k1'; // ESM-only. Use bundler for common.js | ||
import * as secp from '@noble/secp256k1'; | ||
// import * as secp from "https://deno.land/x/secp256k1/mod.ts"; // Deno | ||
@@ -45,7 +45,7 @@ // import * as secp from "https://unpkg.com/@noble/secp256k1"; // Unpkg | ||
const msgHash = 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'; | ||
const pubKey = secp.getPublicKey(privKey); // Make pubkey from the private key | ||
const signature = await secp.signAsync(msgHash, privKey); // sign | ||
const isValid = secp.verify(signature, msgHash, pubKey); // verify | ||
const pubKey = secp.getPublicKey(privKey); | ||
const signature = await secp.signAsync(msgHash, privKey); // Sync methods below | ||
const isValid = secp.verify(signature, msgHash, pubKey); | ||
const pubKey2 = getPublicKey(secp.utils.randomPrivateKey()); // Key of user 2 | ||
const alicesPubkey = secp.getPublicKey(secp.utils.randomPrivateKey()); | ||
secp.getSharedSecret(privKey, alicesPubkey); // Elliptic curve diffie-hellman | ||
@@ -56,15 +56,14 @@ signature.recoverPublicKey(msgHash); // Public key recovery | ||
Advanced examples: | ||
Additional polyfills for some environments: | ||
```ts | ||
// 1. Use the shim to enable synchronous methods. | ||
// Only async methods are available by default to keep library dependency-free. | ||
// 1. Enable synchronous methods. | ||
// Only async methods are available by default, to keep the library dependency-free. | ||
import { hmac } from '@noble/hashes/hmac'; | ||
import { sha256 } from '@noble/hashes/sha256'; | ||
secp.etc.hmacSha256Sync = (k, ...m) => hmac(sha256, k, secp.etc.concatBytes(...m)) | ||
const signature2 = secp.sign(msgHash, privKey); // Can be used now | ||
// Sync methods can be used now: | ||
// secp.sign(msgHash, privKey); | ||
// 2. Use the shim only for node.js <= 18 BEFORE importing noble-secp256k1. | ||
// The library depends on global variable crypto to work. It is available in | ||
// all browsers and many environments, but node.js <= 18 don't have it. | ||
// 2. node.js 18 and older, requires polyfilling globalThis.crypto | ||
import { webcrypto } from 'node:crypto'; | ||
@@ -74,20 +73,8 @@ // @ts-ignore | ||
// Other stuff | ||
// Malleable signatures, incompatible with BTC/ETH, but compatible with openssl | ||
// `lowS: true` prohibits signatures which have (sig.s >= CURVE.n/2n) because of | ||
// malleability | ||
const signatureMalleable = secp.sign(msgHash, privKey, { lowS: false }); | ||
// Signatures with improved security: adds additional entropy `k` for | ||
// deterministic signature, follows section 3.6 of RFC6979. When `true`, it | ||
// would be filled with 32b from CSPRNG. **Strongly recommended** to pass `true` | ||
// to improve security: | ||
// - 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 | ||
const signatureImproved = secp.sign(msgHash, privKey, { extraEntropy: true }); | ||
// 3. React Native needs crypto.getRandomValues polyfill and sha512 | ||
import 'react-native-get-random-values'; | ||
import { hmac } from '@noble/hashes/hmac'; | ||
import { sha256 } from '@noble/hashes/sha256'; | ||
secp.etc.hmacSha256Sync = (k, ...m) => hmac(sha256, k, secp.etc.concatBytes(...m)); | ||
secp.etc.hmacSha256Async = (k, ...m) => Promise.resolve(secp.etc.hmacSha256Sync(k, ...m)); | ||
``` | ||
@@ -100,15 +87,23 @@ | ||
`verify(signature, messageHash, publicKey)`. | ||
We accept Hex type everywhere: | ||
```typescript | ||
type Hex = Uint8Array | string; | ||
```ts | ||
type Hex = Uint8Array | string | ||
``` | ||
// Generates public key from 32-byte private key. | ||
// isCompressed=true by default, meaning 33-byte output. Set to false for 65b. | ||
### getPublicKey | ||
```ts | ||
function getPublicKey(privateKey: Hex, isCompressed?: boolean): Uint8Array; | ||
// Use: | ||
// - `ProjectivePoint.fromPrivateKey(privateKey)` for Point instance | ||
// - `ProjectivePoint.fromHex(publicKey)` to convert hex / bytes into Point. | ||
``` | ||
// Generates low-s deterministic-k RFC6979 ECDSA signature. | ||
// Use with `extraEntropy: true` to improve security. | ||
Generates 33-byte compressed public key from 32-byte private key. | ||
- If you need uncompressed 65-byte public key, set second argument to `false`. | ||
- Use `ProjectivePoint.fromPrivateKey(privateKey)` for Point instance. | ||
- Use `ProjectivePoint.fromHex(publicKey)` to convert Hex / Uint8Array into Point. | ||
### sign | ||
```ts | ||
function sign( | ||
@@ -125,6 +120,23 @@ messageHash: Hex, // message hash (not message) which would be signed | ||
// Verifies ECDSA signature. | ||
// lowS option Ensures a signature.s is in the lower-half of CURVE.n. | ||
// Used in BTC, ETH. | ||
// `{ lowS: false }` should only be used if you need OpenSSL-compatible signatures | ||
sign(msgHash, privKey, { lowS: false }); // Malleable signature | ||
sign(msgHash, privKey, { extraEntropy: true }); // Improved security | ||
``` | ||
Generates low-s deterministic-k RFC6979 ECDSA signature. Assumes hash of message, | ||
which means you'll need to do something like `sha256(message)` before signing. | ||
1. `lowS: false` allows to create malleable signatures, for compatibility with openssl. | ||
Default `lowS: true` prohibits signatures which have (sig.s >= CURVE.n/2n) and is compatible with BTC/ETH. | ||
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 | ||
### verify | ||
```ts | ||
function verify( | ||
@@ -136,5 +148,10 @@ signature: Hex | Signature, // returned by the `sign` function | ||
): boolean; | ||
``` | ||
// Computes ECDH (Elliptic Curve Diffie-Hellman) shared secret between | ||
// key A and different key B. | ||
Verifies ECDSA signature and ensures it has lowS (compatible with BTC/ETH). | ||
`lowS: false` turns off malleability check, but makes it OpenSSL-compatible. | ||
### getSharedSecret | ||
```ts | ||
function getSharedSecret( | ||
@@ -145,5 +162,12 @@ privateKeyA: Uint8Array | string, // Alices's private key | ||
): Uint8Array; | ||
// Use `ProjectivePoint.fromHex(publicKeyB).multiply(privateKeyA)` for Point instance | ||
``` | ||
// Recover public key from Signature instance with `recovery` bit set | ||
Computes ECDH (Elliptic Curve Diffie-Hellman) shared secret between | ||
key A and different key B. | ||
Use `ProjectivePoint.fromHex(publicKeyB).multiply(privateKeyA)` for Point instance | ||
### recoverPublicKey | ||
```ts | ||
signature.recoverPublicKey( | ||
@@ -154,2 +178,6 @@ msgHash: Uint8Array | string | ||
Recover public key from Signature instance with `recovery` bit set. | ||
### utils | ||
A bunch of useful **utilities** are also exposed: | ||
@@ -159,6 +187,6 @@ | ||
type Bytes = Uint8Array; | ||
export declare const etc: { | ||
const etc: { | ||
hexToBytes: (hex: string) => Bytes; | ||
bytesToHex: (b: Bytes) => string; | ||
concatBytes: (...arrs: Bytes[]) => Uint8Array; | ||
concatBytes: (...arrs: Bytes[]) => Bytes; | ||
bytesToNumberBE: (b: Bytes) => bigint; | ||
@@ -173,38 +201,38 @@ numberToBytesBE: (num: bigint) => Bytes; | ||
}; | ||
export declare const utils: { | ||
const utils: { | ||
normPrivateKeyToScalar: (p: PrivKey) => bigint; | ||
randomPrivateKey: () => Bytes; | ||
randomPrivateKey: () => Bytes; // Uses CSPRNG https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues | ||
isValidPrivateKey: (key: Hex) => boolean; | ||
precompute(p: Point, windowSize?: number): Point; | ||
precompute(p: ProjectivePoint, windowSize?: number): ProjectivePoint; | ||
}; | ||
class ProjectivePoint { | ||
readonly px: bigint; | ||
readonly py: bigint; | ||
readonly pz: bigint; | ||
constructor(px: bigint, py: bigint, pz: bigint); | ||
static readonly BASE: Point; | ||
static readonly ZERO: Point; | ||
static fromHex(hex: Hex): Point; | ||
static fromPrivateKey(n: PrivKey): Point; | ||
static readonly BASE: ProjectivePoint; | ||
static readonly ZERO: ProjectivePoint; | ||
static fromAffine(point: AffinePoint): ProjectivePoint; | ||
static fromHex(hex: Hex): ProjectivePoint; | ||
static fromPrivateKey(n: PrivKey): ProjectivePoint; | ||
get x(): bigint; | ||
get y(): bigint; | ||
equals(other: Point): boolean; | ||
add(other: Point): Point; | ||
multiply(n: bigint): Point; | ||
negate(): Point; | ||
add(other: ProjectivePoint): ProjectivePoint; | ||
assertValidity(): void; | ||
equals(other: ProjectivePoint): boolean; | ||
multiply(n: bigint): ProjectivePoint; | ||
negate(): ProjectivePoint; | ||
subtract(other: ProjectivePoint): ProjectivePoint; | ||
toAffine(): AffinePoint; | ||
assertValidity(): Point; | ||
toHex(isCompressed?: boolean): string; | ||
toRawBytes(isCompressed?: boolean): Uint8Array; | ||
toRawBytes(isCompressed?: boolean): Bytes; | ||
} | ||
class Signature { | ||
constructor(r: bigint, s: bigint, recovery?: number | undefined); | ||
static fromCompact(hex: Hex): Signature; | ||
readonly r: bigint; | ||
readonly s: bigint; | ||
readonly recovery?: number | undefined; | ||
constructor(r: bigint, s: bigint, recovery?: number | undefined); | ||
ok(): Signature; | ||
static fromCompact(hex: Hex): Signature; | ||
hasHighS(): boolean; | ||
normalizeS(): Signature; | ||
recoverPublicKey(msgh: Hex): Point; | ||
toCompactRawBytes(): Uint8Array; | ||
toCompactRawBytes(): Bytes; | ||
toCompactHex(): string; | ||
@@ -243,2 +271,6 @@ } | ||
As for key generation, 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). | ||
## Speed | ||
@@ -248,10 +280,10 @@ | ||
Benchmarks measured with Apple M2 on MacOS 13 with node.js 19. | ||
Benchmarks measured with Apple M2 on MacOS 13 with node.js 20. | ||
getPublicKey(utils.randomPrivateKey()) x 5,540 ops/sec @ 180μs/op | ||
sign x 3,301 ops/sec @ 302μs/op | ||
verify x 517 ops/sec @ 1ms/op | ||
getSharedSecret x 433 ops/sec @ 2ms/op | ||
recoverPublicKey x 526 ops/sec @ 1ms/op | ||
Point.fromHex (decompression) x 8,415 ops/sec @ 118μs/op | ||
getPublicKey(utils.randomPrivateKey()) x 6,430 ops/sec @ 155μs/op | ||
sign x 3,367 ops/sec @ 296μs/op | ||
verify x 600 ops/sec @ 1ms/op | ||
getSharedSecret x 505 ops/sec @ 1ms/op | ||
recoverPublicKey x 612 ops/sec @ 1ms/op | ||
Point.fromHex (decompression) x 9,185 ops/sec @ 108μs/op | ||
@@ -267,3 +299,2 @@ Compare to other libraries on M1 (`openssl` uses native bindings, not JS): | ||
ecdsa#sign x 116 ops/sec | ||
bip-schnorr#sign x 60 ops/sec | ||
@@ -274,3 +305,2 @@ elliptic#verify x 812 ops/sec | ||
ecdsa#verify x 80 ops/sec | ||
bip-schnorr#verify x 56 ops/sec | ||
@@ -277,0 +307,0 @@ elliptic#ecdh x 971 ops/sec |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
75134
1056
353
0
6