@covenance/dlc
Version:
Crypto and Bitcoin functions for Covenance DLC implementation
1,430 lines (1,305 loc) • 59.8 kB
text/typescript
/* eslint-disable */
/*! noble-secp256k1 - MIT License (c) 2019 Paul Miller (paulmillr.com) */
// https://www.secg.org/sec2-v2.pdf
// Uses built-in crypto module from node.js to generate randomness / hmac-sha256.
// In browser the line is automatically removed during build time: uses crypto.subtle instead.
import * as nodeCrypto from 'crypto';
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n
const _0n = BigInt(0);
const _1n = BigInt(1);
const _2n = BigInt(2);
const _3n = BigInt(3);
const _8n = BigInt(8);
// Curve fomula is y² = x³ + ax + b
const CURVE = Object.freeze({
// Params: a, b
a: _0n,
b: BigInt(7),
// Field over which we'll do calculations. Verify with:
// console.log(CURVE.P === (2n**256n - 2n**32n - 2n**9n - 2n**8n-2n**7n-2n**6n-2n**4n - 1n))
P: BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f'),
// Curve order, total count of valid points in the field. Verify with:
// console.log(CURVE.n === (2n**256n - 432420386565659656852420866394968145599n))
n: BigInt('0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141'),
// Cofactor. It's 1, so other subgroups don't exist, and default subgroup is prime-order
h: _1n,
// Base point (x, y) aka generator point
Gx: BigInt('55066263022277343669578718895168534326250603453777594175500187360389116729240'),
Gy: BigInt('32670510020758816978083085130507043184471273380659243275938904335757337482424'),
// Legacy, endo params are defined below
beta: BigInt('0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee'),
});
const divNearest = (a: bigint, b: bigint) => (a + b / _2n) / b;
// Endomorphism params
const endo = {
beta: BigInt('0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee'),
// Split 256-bit K into 2 128-bit (k1, k2) for which k1 + k2 * lambda = K.
// Used for endomorphism https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066
splitScalar(k: bigint) {
const { n } = CURVE;
const a1 = BigInt('0x3086d221a7d46bcde86c90e49284eb15');
const b1 = -_1n * BigInt('0xe4437ed6010e88286f547fa90abfe4c3');
const a2 = BigInt('0x114ca50f7a8e2f3f657c1108d9d44cfd8');
const b2 = a1;
const POW_2_128 = BigInt('0x100000000000000000000000000000000');
const c1 = divNearest(b2 * k, n);
const c2 = divNearest(-b1 * k, n);
let k1 = mod(k - c1 * a1 - c2 * a2, n);
let k2 = mod(-c1 * b1 - c2 * b2, n);
const k1neg = k1 > POW_2_128;
const k2neg = k2 > POW_2_128;
if (k1neg) k1 = n - k1;
if (k2neg) k2 = n - k2;
if (k1 > POW_2_128 || k2 > POW_2_128) {
throw new Error('splitScalarEndo: Endomorphism failed, k=' + k);
}
return { k1neg, k1, k2neg, k2 };
},
};
// Placeholder for non-sha256 hashes
const fieldLen = 32; // Field element: their range is 0 to CURVE.P
const groupLen = 32; // Group element: their range is 1 to CURVE.n
const hashLen = 32; // Hash used with secp256k1, sha2-256
const compressedLen = fieldLen + 1; // DER-encoded field element
const uncompressedLen = 2 * fieldLen + 1; // DER-encoded pair of field elements
// Cleaner js output if that's on a separate line.
export { CURVE };
/**
* y² = x³ + ax + b: Short weierstrass curve formula
* @returns y²
*/
function weierstrass(x: bigint): bigint {
const { a, b } = CURVE;
const x2 = mod(x * x);
const x3 = mod(x2 * x);
return mod(x3 + a * x + b);
}
// We accept hex strings besides Uint8Array for simplicity
type Hex = Uint8Array | string;
// Very few implementations accept numbers, we do it to ease learning curve
type PrivKey = Hex | bigint | number;
// compressed/uncompressed ECDSA key, or Schnorr key - not interchangeable
type PubKey = Hex | Point;
// ECDSA signature
type Sig = Hex | Signature;
/**
* Always true for secp256k1.
* We're including it here if you'll want to reuse code to support
* different curve (e.g. secp256r1) - just set it to false then.
* Endomorphism only works for Koblitz curves with a == 0.
* It improves efficiency:
* Uses 2x less RAM, speeds up precomputation by 2x and ECDH / sign key recovery by 20%.
* Should always be used for Jacobian's double-and-add multiplication.
* For affines cached multiplication, it trades off 1/2 init time & 1/3 ram for 20% perf hit.
* https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066
*/
const USE_ENDOMORPHISM = CURVE.a === _0n;
class ShaError extends Error {
constructor(message: string) {
super(message);
}
}
function assertJacPoint(other: unknown) {
if (!(other instanceof JacobianPoint)) throw new TypeError('JacobianPoint expected');
}
/**
* Jacobian Point works in 3d / jacobi coordinates: (x, y, z) ∋ (x=x/z², y=y/z³)
* Default Point works in 2d / affine coordinates: (x, y)
* We're doing calculations in jacobi, because its operations don't require costly inversion.
*/
class JacobianPoint {
constructor(readonly x: bigint, readonly y: bigint, readonly z: bigint) {}
static readonly BASE = new JacobianPoint(CURVE.Gx, CURVE.Gy, _1n);
static readonly ZERO = new JacobianPoint(_0n, _1n, _0n);
static fromAffine(p: Point): JacobianPoint {
// fromAffine(x:0, y:0) would produce (x:0, y:0, z:1), but we need (x:0, y:1, z:0)
if (p.equals(Point.ZERO)) return JacobianPoint.ZERO;
return new JacobianPoint(p.x, p.y, _1n);
}
/**
* Takes a bunch of Jacobian Points but executes only one
* invert on all of them. invert is very slow operation,
* so this improves performance massively.
*/
static toAffineBatch(points: JacobianPoint[]): Point[] {
const toInv = invertBatch(points.map((p) => p.z));
return points.map((p, i) => p.toAffine(toInv[i]));
}
static normalizeZ(points: JacobianPoint[]): JacobianPoint[] {
return JacobianPoint.toAffineBatch(points).map(JacobianPoint.fromAffine);
}
/**
* Compare one point to another.
*/
equals(other: JacobianPoint): boolean {
assertJacPoint(other);
const { x: X1, y: Y1, z: Z1 } = this;
const { x: X2, y: Y2, z: Z2 } = other;
const Z1Z1 = mod(Z1 * Z1);
const Z2Z2 = mod(Z2 * Z2);
const U1 = mod(X1 * Z2Z2);
const U2 = mod(X2 * Z1Z1);
const S1 = mod(mod(Y1 * Z2) * Z2Z2);
const S2 = mod(mod(Y2 * Z1) * Z1Z1);
return U1 === U2 && S1 === S2;
}
/**
* Flips point to one corresponding to (x, -y) in Affine coordinates.
*/
negate(): JacobianPoint {
return new JacobianPoint(this.x, mod(-this.y), this.z);
}
// Fast algo for doubling 2 Jacobian Points when curve's a=0.
// Note: cannot be reused for other curves when a != 0.
// From: https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l
// Cost: 2M + 5S + 6add + 3*2 + 1*3 + 1*8.
double(): JacobianPoint {
const { x: X1, y: Y1, z: Z1 } = this;
const A = mod(X1 * X1);
const B = mod(Y1 * Y1);
const C = mod(B * B);
const x1b = X1 + B;
const D = mod(_2n * (mod(x1b * x1b) - A - C));
const E = mod(_3n * A);
const F = mod(E * E);
const X3 = mod(F - _2n * D);
const Y3 = mod(E * (D - X3) - _8n * C);
const Z3 = mod(_2n * Y1 * Z1);
return new JacobianPoint(X3, Y3, Z3);
}
// Fast algo for adding 2 Jacobian Points.
// https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#addition-add-1998-cmo-2
// Cost: 12M + 4S + 6add + 1*2
// Note: 2007 Bernstein-Lange (11M + 5S + 9add + 4*2) is actually 10% slower.
add(other: JacobianPoint): JacobianPoint {
assertJacPoint(other);
const { x: X1, y: Y1, z: Z1 } = this;
const { x: X2, y: Y2, z: Z2 } = other;
if (X2 === _0n || Y2 === _0n) return this;
if (X1 === _0n || Y1 === _0n) return other;
// We're using same code in equals()
const Z1Z1 = mod(Z1 * Z1);
const Z2Z2 = mod(Z2 * Z2);
const U1 = mod(X1 * Z2Z2);
const U2 = mod(X2 * Z1Z1);
const S1 = mod(mod(Y1 * Z2) * Z2Z2);
const S2 = mod(mod(Y2 * Z1) * Z1Z1);
const H = mod(U2 - U1);
const r = mod(S2 - S1);
// H = 0 meaning it's the same point.
if (H === _0n) {
if (r === _0n) {
return this.double();
} else {
return JacobianPoint.ZERO;
}
}
const HH = mod(H * H);
const HHH = mod(H * HH);
const V = mod(U1 * HH);
const X3 = mod(r * r - HHH - _2n * V);
const Y3 = mod(r * (V - X3) - S1 * HHH);
const Z3 = mod(Z1 * Z2 * H);
return new JacobianPoint(X3, Y3, Z3);
}
subtract(other: JacobianPoint) {
return this.add(other.negate());
}
/**
* Non-constant-time multiplication. Uses double-and-add algorithm.
* It's faster, but should only be used when you don't care about
* an exposed private key e.g. sig verification, which works over *public* keys.
*/
multiplyUnsafe(scalar: bigint): JacobianPoint {
const P0 = JacobianPoint.ZERO;
if (typeof scalar === 'bigint' && scalar === _0n) return P0;
// Will throw on 0
let n = normalizeScalar(scalar);
if (n === _1n) return this;
// The condition is not executed unless you change global var
if (!USE_ENDOMORPHISM) {
let p = P0;
let d: JacobianPoint = this;
while (n > _0n) {
if (n & _1n) p = p.add(d);
d = d.double();
n >>= _1n;
}
return p;
}
let { k1neg, k1, k2neg, k2 } = endo.splitScalar(n);
let k1p = P0;
let k2p = P0;
let d: JacobianPoint = this;
while (k1 > _0n || k2 > _0n) {
if (k1 & _1n) k1p = k1p.add(d);
if (k2 & _1n) k2p = k2p.add(d);
d = d.double();
k1 >>= _1n;
k2 >>= _1n;
}
if (k1neg) k1p = k1p.negate();
if (k2neg) k2p = k2p.negate();
k2p = new JacobianPoint(mod(k2p.x * endo.beta), k2p.y, k2p.z);
return k1p.add(k2p);
}
/**
* Creates a wNAF precomputation window. Used for caching.
* Default window size is set by `utils.precompute()` and is equal to 8.
* Which means we are caching 65536 points: 256 points for every bit from 0 to 256.
* @returns 65K precomputed points, depending on W
*/
private precomputeWindow(W: number): JacobianPoint[] {
// splitScalarEndo could return 129-bit numbers, so we need at least 128 / W + 1
const windows = USE_ENDOMORPHISM ? 128 / W + 1 : 256 / W + 1;
const points: JacobianPoint[] = [];
let p: JacobianPoint = this;
let base = p;
for (let window = 0; window < windows; window++) {
base = p;
points.push(base);
for (let i = 1; i < 2 ** (W - 1); i++) {
base = base.add(p);
points.push(base);
}
p = base.double();
}
return points;
}
/**
* Implements w-ary non-adjacent form for calculating ec multiplication.
* @param n
* @param affinePoint optional 2d point to save cached precompute windows on it.
* @returns real and fake (for const-time) points
*/
private wNAF(n: bigint, affinePoint?: Point): { p: JacobianPoint; f: JacobianPoint } {
if (!affinePoint && this.equals(JacobianPoint.BASE)) affinePoint = Point.BASE;
const W = (affinePoint && affinePoint._WINDOW_SIZE) || 1;
if (256 % W) {
throw new Error('Point#wNAF: Invalid precomputation window, must be power of 2');
}
// Calculate precomputes on a first run, reuse them after
let precomputes = affinePoint && pointPrecomputes.get(affinePoint);
if (!precomputes) {
precomputes = this.precomputeWindow(W);
if (affinePoint && W !== 1) {
precomputes = JacobianPoint.normalizeZ(precomputes);
pointPrecomputes.set(affinePoint, precomputes);
}
}
// Initialize real and fake points for const-time
let p = JacobianPoint.ZERO;
// Should be G (base) point, since otherwise f can be infinity point in the end
let f = JacobianPoint.BASE;
const windows = 1 + (USE_ENDOMORPHISM ? 128 / W : 256 / W); // W=8 17
const windowSize = 2 ** (W - 1); // W=8 128
const mask = BigInt(2 ** W - 1); // Create mask with W ones: 0b11111111 for W=8
const maxNumber = 2 ** W; // W=8 256
const shiftBy = BigInt(W); // W=8 8
for (let window = 0; window < windows; window++) {
const offset = window * windowSize;
// Extract W bits.
let wbits = Number(n & mask);
// Shift number by W bits.
n >>= shiftBy;
// If the bits are bigger than max size, we'll split those.
// +224 => 256 - 32
if (wbits > windowSize) {
wbits -= maxNumber;
n += _1n;
}
// This code was first written with assumption that 'f' and 'p' will never be infinity point:
// since each addition is multiplied by 2 ** W, it cannot cancel each other. However,
// there is negate now: it is possible that negated element from low value
// would be the same as high element, which will create carry into next window.
// It's not obvious how this can fail, but still worth investigating later.
// Check if we're onto Zero point.
// Add random point inside current window to f.
const offset1 = offset;
const offset2 = offset + Math.abs(wbits) - 1;
const cond1 = window % 2 !== 0;
const cond2 = wbits < 0;
if (wbits === 0) {
// The most important part for const-time getPublicKey
f = f.add(constTimeNegate(cond1, precomputes[offset1]));
} else {
p = p.add(constTimeNegate(cond2, precomputes[offset2]));
}
}
// JIT-compiler should not eliminate f here, since it will later be used in normalizeZ()
// Even if the variable is still unused, there are some checks which will
// throw an exception, so compiler needs to prove they won't happen, which is hard.
// At this point there is a way to F be infinity-point even if p is not,
// which makes it less const-time: around 1 bigint multiply.
return { p, f };
}
/**
* Constant time multiplication.
* Uses wNAF method. Windowed method may be 10% faster,
* but takes 2x longer to generate and consumes 2x memory.
* @param scalar by which the point would be multiplied
* @param affinePoint optional point ot save cached precompute windows on it
* @returns New point
*/
multiply(scalar: number | bigint, affinePoint?: Point): JacobianPoint {
const n = normalizeScalar(scalar);
// Real point.
let point: JacobianPoint;
// Fake point, we use it to achieve constant-time multiplication.
let fake: JacobianPoint;
if (USE_ENDOMORPHISM) {
const { k1neg, k1, k2neg, k2 } = endo.splitScalar(n);
let { p: k1p, f: f1p } = this.wNAF(k1, affinePoint);
let { p: k2p, f: f2p } = this.wNAF(k2, affinePoint);
k1p = constTimeNegate(k1neg, k1p);
k2p = constTimeNegate(k2neg, k2p);
k2p = new JacobianPoint(mod(k2p.x * endo.beta), k2p.y, k2p.z);
point = k1p.add(k2p);
fake = f1p.add(f2p);
} else {
const { p, f } = this.wNAF(n, affinePoint);
point = p;
fake = f;
}
// Normalize `z` for both points, but return only real one
return JacobianPoint.normalizeZ([point, fake])[0];
}
// Converts Jacobian point to affine (x, y) coordinates.
// Can accept precomputed Z^-1 - for example, from invertBatch.
// (x, y, z) ∋ (x=x/z², y=y/z³)
toAffine(invZ?: bigint): Point {
const { x, y, z } = this;
const is0 = this.equals(JacobianPoint.ZERO);
// If invZ was 0, we return zero point. However we still want to execute
// all operations, so we replace invZ with a random number, 8.
if (invZ == null) invZ = is0 ? _8n : invert(z);
const iz1 = invZ;
const iz2 = mod(iz1 * iz1);
const iz3 = mod(iz2 * iz1);
const ax = mod(x * iz2);
const ay = mod(y * iz3);
const zz = mod(z * iz1);
if (is0) return Point.ZERO;
if (zz !== _1n) throw new Error('invZ was invalid');
return new Point(ax, ay);
}
}
// Const-time utility for wNAF
function constTimeNegate(condition: boolean, item: JacobianPoint) {
const neg = item.negate();
return condition ? neg : item;
}
// Stores precomputed values for points.
const pointPrecomputes = new WeakMap<Point, JacobianPoint[]>();
/**
* Default Point works in default aka affine coordinates: (x, y)
*/
export class Point {
/**
* Base point aka generator. public_key = Point.BASE * private_key
*/
static BASE: Point = new Point(CURVE.Gx, CURVE.Gy);
/**
* Identity point aka point at infinity. point = point + zero_point
*/
static ZERO: Point = new Point(_0n, _0n);
// We calculate precomputes for elliptic curve point multiplication
// using windowed method. This specifies window size and
// stores precomputed values. Usually only base point would be precomputed.
_WINDOW_SIZE?: number;
constructor(readonly x: bigint, readonly y: bigint) {}
// "Private method", don't use it directly
_setWindowSize(windowSize: number) {
this._WINDOW_SIZE = windowSize;
pointPrecomputes.delete(this);
}
// Checks for y % 2 == 0
hasEvenY() {
return this.y % _2n === _0n;
}
/**
* Supports compressed Schnorr and ECDSA points
* @param bytes
* @returns Point instance
*/
private static fromCompressedHex(bytes: Uint8Array) {
const isShort = bytes.length === 32;
const x = bytesToNumber(isShort ? bytes : bytes.subarray(1));
if (!isValidFieldElement(x)) throw new Error('Point is not on curve');
const y2 = weierstrass(x); // y² = x³ + ax + b
let y = sqrtMod(y2); // y = y² ^ (p+1)/4
const isYOdd = (y & _1n) === _1n;
if (isShort) {
// Schnorr
if (isYOdd) y = mod(-y);
} else {
// ECDSA
const isFirstByteOdd = (bytes[0] & 1) === 1;
if (isFirstByteOdd !== isYOdd) y = mod(-y);
}
const point = new Point(x, y);
point.assertValidity();
return point;
}
// Schnorr doesn't support uncompressed points, so this is only for ECDSA
private static fromUncompressedHex(bytes: Uint8Array) {
const x = bytesToNumber(bytes.subarray(1, fieldLen + 1));
const y = bytesToNumber(bytes.subarray(fieldLen + 1, fieldLen * 2 + 1));
const point = new Point(x, y);
point.assertValidity();
return point;
}
/**
* Converts hash string or Uint8Array to Point.
* @param hex schnorr or ECDSA hex
*/
static fromHex(hex: Hex): Point {
const bytes = ensureBytes(hex);
const len = bytes.length;
const header = bytes[0];
// this.assertValidity() is done inside of those two functions
// Schnorr
if (len === fieldLen) return this.fromCompressedHex(bytes);
// ECDSA
if (len === compressedLen && (header === 0x02 || header === 0x03)) {
return this.fromCompressedHex(bytes);
}
if (len === uncompressedLen && header === 0x04) return this.fromUncompressedHex(bytes);
throw new Error(
`Point.fromHex: received invalid point. Expected 32-${compressedLen} compressed bytes or ${uncompressedLen} uncompressed bytes, not ${len}`
);
}
// Multiplies generator point by privateKey.
static fromPrivateKey(privateKey: PrivKey) {
return Point.BASE.multiply(normalizePrivateKey(privateKey));
}
/**
* Recovers public key from ECDSA signature.
* https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm#Public_key_recovery
* ```
* recover(r, s, h) where
* u1 = hs^-1 mod n
* u2 = sr^-1 mod n
* Q = u1⋅G + u2⋅R
* ```
*/
static fromSignature(msgHash: Hex, signature: Sig, recovery: number): Point {
const { r, s } = normalizeSignature(signature);
if (![0, 1, 2, 3].includes(recovery)) throw new Error('Cannot recover: invalid recovery bit');
const h = truncateHash(ensureBytes(msgHash));
const { n } = CURVE;
const radj = recovery === 2 || recovery === 3 ? r + n : r;
const rinv = invert(radj, n);
// Q = u1⋅G + u2⋅R
const u1 = mod(-h * rinv, n);
const u2 = mod(s * rinv, n);
const prefix = recovery & 1 ? '03' : '02';
const R = Point.fromHex(prefix + numTo32bStr(radj));
const Q = Point.BASE.multiplyAndAddUnsafe(R, u1, u2);
if (!Q) throw new Error('Cannot recover signature: point at infinify');
Q.assertValidity();
return Q;
}
toRawBytes(isCompressed = false): Uint8Array {
return hexToBytes(this.toHex(isCompressed));
}
toHex(isCompressed = false): string {
const x = numTo32bStr(this.x);
if (isCompressed) {
const prefix = this.hasEvenY() ? '02' : '03';
return `${prefix}${x}`;
} else {
return `04${x}${numTo32bStr(this.y)}`;
}
}
// Schnorr-related function
toHexX() {
return this.toHex(true).slice(2);
}
toRawX() {
return this.toRawBytes(true).slice(1);
}
// A point on curve is valid if it conforms to equation.
assertValidity(): void {
const msg = 'Point is not on elliptic curve';
const { x, y } = this;
if (!isValidFieldElement(x) || !isValidFieldElement(y)) throw new Error(msg);
const left = mod(y * y);
const right = weierstrass(x);
if (mod(left - right) !== _0n) throw new Error(msg);
}
equals(other: Point): boolean {
return this.x === other.x && this.y === other.y;
}
// Returns the same point with inverted `y`
negate() {
return new Point(this.x, mod(-this.y));
}
// Adds point to itself
double() {
return JacobianPoint.fromAffine(this).double().toAffine();
}
// Adds point to other point
add(other: Point) {
return JacobianPoint.fromAffine(this).add(JacobianPoint.fromAffine(other)).toAffine();
}
// Subtracts other point from the point
subtract(other: Point) {
return this.add(other.negate());
}
multiply(scalar: number | bigint) {
return JacobianPoint.fromAffine(this).multiply(scalar, this).toAffine();
}
/**
* Efficiently calculate `aP + bQ`.
* Unsafe, can expose private key, if used incorrectly.
* TODO: Utilize Shamir's trick
* @returns non-zero affine point
*/
multiplyAndAddUnsafe(Q: Point, a: bigint, b: bigint): Point | undefined {
const P = JacobianPoint.fromAffine(this);
const aP = a === _0n || a === _1n || this !== Point.BASE ? P.multiplyUnsafe(a) : P.multiply(a);
const bQ = JacobianPoint.fromAffine(Q).multiplyUnsafe(b);
const sum = aP.add(bQ);
return sum.equals(JacobianPoint.ZERO) ? undefined : sum.toAffine();
}
}
function sliceDER(s: string): string {
// Proof: any([(i>=0x80) == (int(hex(i).replace('0x', '').zfill(2)[0], 16)>=8) for i in range(0, 256)])
// Padding done by numberToHex
return Number.parseInt(s[0], 16) >= 8 ? '00' + s : s;
}
function parseDERInt(data: Uint8Array) {
if (data.length < 2 || data[0] !== 0x02) {
throw new Error(`Invalid signature integer tag: ${bytesToHex(data)}`);
}
const len = data[1];
const res = data.subarray(2, len + 2);
if (!len || res.length !== len) {
throw new Error(`Invalid signature integer: wrong length`);
}
// Strange condition, its not about length, but about first bytes of number.
if (res[0] === 0x00 && res[1] <= 0x7f) {
throw new Error('Invalid signature integer: trailing length');
}
return { data: bytesToNumber(res), left: data.subarray(len + 2) };
}
function parseDERSignature(data: Uint8Array) {
if (data.length < 2 || data[0] != 0x30) {
throw new Error(`Invalid signature tag: ${bytesToHex(data)}`);
}
if (data[1] !== data.length - 2) {
throw new Error('Invalid signature: incorrect length');
}
const { data: r, left: sBytes } = parseDERInt(data.subarray(2));
const { data: s, left: rBytesLeft } = parseDERInt(sBytes);
if (rBytesLeft.length) {
throw new Error(`Invalid signature: left bytes after parsing: ${bytesToHex(rBytesLeft)}`);
}
return { r, s };
}
// Represents ECDSA signature with its (r, s) properties
export class Signature {
constructor(readonly r: bigint, readonly s: bigint) {
this.assertValidity();
}
// pair (32 bytes of r, 32 bytes of s)
static fromCompact(hex: Hex) {
const arr = isBytes(hex);
const name = 'Signature.fromCompact';
if (typeof hex !== 'string' && !arr)
throw new TypeError(`${name}: Expected string or Uint8Array`);
const str = arr ? bytesToHex(hex) : hex;
if (str.length !== 128) throw new Error(`${name}: Expected 64-byte hex`);
return new Signature(hexToNumber(str.slice(0, 64)), hexToNumber(str.slice(64, 128)));
}
// DER encoded ECDSA signature
// https://bitcoin.stackexchange.com/questions/57644/what-are-the-parts-of-a-bitcoin-transaction-input-script
static fromDER(hex: Hex) {
const arr = isBytes(hex);
if (typeof hex !== 'string' && !arr)
throw new TypeError(`Signature.fromDER: Expected string or Uint8Array`);
const { r, s } = parseDERSignature(arr ? hex : hexToBytes(hex));
return new Signature(r, s);
}
// Don't use this method
static fromHex(hex: Hex) {
return this.fromDER(hex);
}
assertValidity(): void {
const { r, s } = this;
if (!isWithinCurveOrder(r)) throw new Error('Invalid Signature: r must be 0 < r < n');
if (!isWithinCurveOrder(s)) throw new Error('Invalid Signature: s must be 0 < s < n');
}
// Default signatures are always low-s, to prevent malleability.
// sign(canonical: true) always produces low-s sigs.
// verify(strict: true) always fails for high-s.
// We don't provide `hasHighR` https://github.com/bitcoin/bitcoin/pull/13666
hasHighS(): boolean {
const HALF = CURVE.n >> _1n;
return this.s > HALF;
}
normalizeS(): Signature {
return this.hasHighS() ? new Signature(this.r, mod(-this.s, CURVE.n)) : this;
}
// DER-encoded
toDERRawBytes() {
return hexToBytes(this.toDERHex());
}
toDERHex() {
const sHex = sliceDER(numberToHexUnpadded(this.s));
const rHex = sliceDER(numberToHexUnpadded(this.r));
const sHexL = sHex.length / 2;
const rHexL = rHex.length / 2;
const sLen = numberToHexUnpadded(sHexL);
const rLen = numberToHexUnpadded(rHexL);
const length = numberToHexUnpadded(rHexL + sHexL + 4);
return `30${length}02${rLen}${rHex}02${sLen}${sHex}`;
}
// Don't use these methods. Use toDER* or toCompact* for explicitness.
toRawBytes() {
return this.toDERRawBytes();
}
toHex() {
return this.toDERHex();
}
// 32 bytes of r, then 32 bytes of s
toCompactRawBytes() {
return hexToBytes(this.toCompactHex());
}
toCompactHex() {
return numTo32bStr(this.r) + numTo32bStr(this.s);
}
}
function isBytes(a: unknown): a is Uint8Array {
return a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array');
}
function abytes(item: unknown): void {
if (!isBytes(item)) throw new Error('Uint8Array expected');
}
function concatBytes(...arrays: Uint8Array[]): Uint8Array {
arrays.every(abytes);
if (arrays.length === 1) return arrays[0];
const length = arrays.reduce((a, arr) => a + arr.length, 0);
const result = new Uint8Array(length);
for (let i = 0, pad = 0; i < arrays.length; i++) {
const arr = arrays[i];
result.set(arr, pad);
pad += arr.length;
}
return result;
}
// Convert between types
// ---------------------
// Array where index 0xf0 (240) is mapped to string 'f0'
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) =>
i.toString(16).padStart(2, '0')
);
/**
* Convert byte array to hex string. Uses built-in function, when available.
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
*/
export function bytesToHex(bytes: Uint8Array): string {
abytes(bytes);
// pre-caching improves the speed 6x
let hex = '';
for (let i = 0; i < bytes.length; i++) {
hex += hexes[bytes[i]];
}
return hex;
}
// We use optimized technique to convert hex string to byte array
const asciis = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 } as const;
function asciiToBase16(ch: number): number | undefined {
if (ch >= asciis._0 && ch <= asciis._9) return ch - asciis._0; // '2' => 50-48
if (ch >= asciis.A && ch <= asciis.F) return ch - (asciis.A - 10); // 'B' => 66-(65-10)
if (ch >= asciis.a && ch <= asciis.f) return ch - (asciis.a - 10); // 'b' => 98-(97-10)
return;
}
/**
* Convert hex string to byte array. Uses built-in function, when available.
* @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])
*/
export function hexToBytes(hex: string): Uint8Array {
if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
const hl = hex.length;
const al = hl / 2;
if (hl % 2) throw new Error('hex string expected, got unpadded hex of length ' + hl);
const array = new Uint8Array(al);
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
const n1 = asciiToBase16(hex.charCodeAt(hi));
const n2 = asciiToBase16(hex.charCodeAt(hi + 1));
if (n1 === undefined || n2 === undefined) {
const char = hex[hi] + hex[hi + 1];
throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
}
array[ai] = n1 * 16 + n2; // multiply first octet, e.g. 'a3' => 10*16+3 => 160 + 3 => 163
}
return array;
}
const POW_2_256 = BigInt('0x10000000000000000000000000000000000000000000000000000000000000000');
function numTo32bStr(num: bigint): string {
if (typeof num !== 'bigint') throw new Error('Expected bigint');
if (!(_0n <= num && num < POW_2_256)) throw new Error('Expected number 0 <= n < 2^256');
return num.toString(16).padStart(64, '0');
}
function numTo32b(num: bigint): Uint8Array {
const b = hexToBytes(numTo32bStr(num));
if (b.length !== 32) throw new Error('Error: expected 32 bytes');
return b;
}
function numberToHexUnpadded(num: number | bigint): string {
const hex = num.toString(16);
return hex.length & 1 ? `0${hex}` : hex;
}
function hexToNumber(hex: string): bigint {
if (typeof hex !== 'string') {
throw new TypeError('hexToNumber: expected string, got ' + typeof hex);
}
// Big Endian
return BigInt(`0x${hex}`);
}
// Big Endian
function bytesToNumber(bytes: Uint8Array): bigint {
return hexToNumber(bytesToHex(bytes));
}
function ensureBytes(hex: Hex): Uint8Array {
// Uint8Array.from() instead of hash.slice() because node.js Buffer
// is instance of Uint8Array, and its slice() creates **mutable** copy
return isBytes(hex) ? Uint8Array.from(hex) : hexToBytes(hex);
}
function normalizeScalar(num: number | bigint): bigint {
if (typeof num === 'number' && Number.isSafeInteger(num) && num > 0) return BigInt(num);
if (typeof num === 'bigint' && isWithinCurveOrder(num)) return num;
throw new TypeError('Expected valid private scalar: 0 < scalar < curve.n');
}
// -------------------------
// Calculates a modulo b
function mod(a: bigint, b: bigint = CURVE.P): bigint {
const result = a % b;
return result >= _0n ? result : b + result;
}
// Does x ^ (2 ^ power). E.g. 30 ^ (2 ^ 4)
function pow2(x: bigint, power: bigint): bigint {
const { P } = CURVE;
let res = x;
while (power-- > _0n) {
res *= res;
res %= P;
}
return res;
}
/**
* Allows to compute square root √y 2x faster.
* To calculate √y, we need to exponentiate it to a very big number:
* `y² = x³ + ax + b; y = y² ^ (p+1)/4`
* We are unwrapping the loop and multiplying it bit-by-bit.
* (P+1n/4n).toString(2) would produce bits [223x 1, 0, 22x 1, 4x 0, 11, 00]
*/
function sqrtMod(x: bigint): bigint {
const { P } = CURVE;
const _6n = BigInt(6);
const _11n = BigInt(11);
const _22n = BigInt(22);
const _23n = BigInt(23);
const _44n = BigInt(44);
const _88n = BigInt(88);
const b2 = (x * x * x) % P; // x^3, 11
const b3 = (b2 * b2 * x) % P; // x^7
const b6 = (pow2(b3, _3n) * b3) % P;
const b9 = (pow2(b6, _3n) * b3) % P;
const b11 = (pow2(b9, _2n) * b2) % P;
const b22 = (pow2(b11, _11n) * b11) % P;
const b44 = (pow2(b22, _22n) * b22) % P;
const b88 = (pow2(b44, _44n) * b44) % P;
const b176 = (pow2(b88, _88n) * b88) % P;
const b220 = (pow2(b176, _44n) * b44) % P;
const b223 = (pow2(b220, _3n) * b3) % P;
const t1 = (pow2(b223, _23n) * b22) % P;
const t2 = (pow2(t1, _6n) * b2) % P;
const rt = pow2(t2, _2n);
const xc = (rt * rt) % P;
if (xc !== x) throw new Error('Cannot find square root');
return rt;
}
// Inverses number over modulo
function invert(number: bigint, modulo: bigint = CURVE.P): bigint {
if (number === _0n || modulo <= _0n) {
throw new Error(`invert: expected positive integers, got n=${number} mod=${modulo}`);
}
// Eucledian GCD https://brilliant.org/wiki/extended-euclidean-algorithm/
let a = mod(number, modulo);
let b = modulo;
// prettier-ignore
let x = _0n, y = _1n, u = _1n, v = _0n;
while (a !== _0n) {
const q = b / a;
const r = b % a;
const m = x - u * q;
const n = y - v * q;
// prettier-ignore
b = a, a = r, x = u, y = v, u = m, v = n;
}
const gcd = b;
if (gcd !== _1n) throw new Error('invert: does not exist');
return mod(x, modulo);
}
/**
* Takes a list of numbers, efficiently inverts all of them.
* @param nums list of bigints
* @param p modulo
* @returns list of inverted bigints
* @example
* invertBatch([1n, 2n, 4n], 21n);
* // => [1n, 11n, 16n]
*/
function invertBatch(nums: bigint[], p: bigint = CURVE.P): bigint[] {
const scratch = new Array(nums.length);
// Walk from first to last, multiply them by each other MOD p
const lastMultiplied = nums.reduce((acc, num, i) => {
if (num === _0n) return acc;
scratch[i] = acc;
return mod(acc * num, p);
}, _1n);
// Invert last element
const inverted = invert(lastMultiplied, p);
// Walk from last to first, multiply them by inverted each other MOD p
nums.reduceRight((acc, num, i) => {
if (num === _0n) return acc;
scratch[i] = mod(acc * scratch[i], p);
return mod(acc * num, p);
}, inverted);
return scratch;
}
// Can be replaced by bytesToNumber(). Placeholder for non-sha256 hashes
function bits2int_2(bytes: Uint8Array) {
const delta = bytes.length * 8 - groupLen * 8; // 256-256=0 for sha256/secp256k1
const num = bytesToNumber(bytes);
return delta > 0 ? num >> BigInt(delta) : num;
}
// Ensures ECDSA message hashes are 32 bytes and < curve order
function truncateHash(hash: Uint8Array, truncateOnly = false): bigint {
const h = bits2int_2(hash);
if (truncateOnly) return h;
const { n } = CURVE;
return h >= n ? h - n : h;
}
// RFC6979 related code
type RecoveredSig = { sig: Signature; recovery: number };
type U8A = Uint8Array;
type Sha256FnSync = undefined | ((...messages: Uint8Array[]) => Uint8Array);
type HmacFnSync = undefined | ((key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array);
let _sha256Sync: Sha256FnSync;
let _hmacSha256Sync: HmacFnSync;
// Minimal HMAC-DRBG (NIST 800-90) for signatures
// Used only for RFC6979, does not fully implement DRBG spec.
class HmacDrbg {
k: Uint8Array;
v: Uint8Array;
counter: number;
constructor(public hashLen: number, public qByteLen: number) {
if (typeof hashLen !== 'number' || hashLen < 2) throw new Error('hashLen must be a number');
if (typeof qByteLen !== 'number' || qByteLen < 2) throw new Error('qByteLen must be a number');
// Step B, Step C: set hashLen to 8*ceil(hlen/8)
this.v = new Uint8Array(hashLen).fill(1);
this.k = new Uint8Array(hashLen).fill(0);
this.counter = 0;
}
private hmac(...values: Uint8Array[]) {
return utils.hmacSha256(this.k, ...values);
}
private hmacSync(...values: Uint8Array[]) {
return _hmacSha256Sync!(this.k, ...values);
}
private checkSync() {
if (typeof _hmacSha256Sync !== 'function') throw new ShaError('hmacSha256Sync needs to be set');
}
incr() {
if (this.counter >= 1000) throw new Error('Tried 1,000 k values for sign(), all were invalid');
this.counter += 1;
}
// We concatenate extraData into seed
async reseed(seed = new Uint8Array()) {
this.k = await this.hmac(this.v, Uint8Array.from([0x00]), seed);
this.v = await this.hmac(this.v);
if (seed.length === 0) return;
this.k = await this.hmac(this.v, Uint8Array.from([0x01]), seed);
this.v = await this.hmac(this.v);
}
reseedSync(seed = new Uint8Array()) {
this.checkSync();
this.k = this.hmacSync(this.v, Uint8Array.from([0x00]), seed);
this.v = this.hmacSync(this.v);
if (seed.length === 0) return;
this.k = this.hmacSync(this.v, Uint8Array.from([0x01]), seed);
this.v = this.hmacSync(this.v);
}
async generate(): Promise<Uint8Array> {
this.incr();
let len = 0;
const out: Uint8Array[] = [];
while (len < this.qByteLen) {
this.v = await this.hmac(this.v);
const sl = this.v.slice();
out.push(sl);
len += this.v.length;
}
return concatBytes(...out);
}
generateSync(): Uint8Array {
this.checkSync();
this.incr();
let len = 0;
const out: Uint8Array[] = [];
while (len < this.qByteLen) {
this.v = this.hmacSync(this.v);
const sl = this.v.slice();
out.push(sl);
len += this.v.length;
}
return concatBytes(...out);
}
// There is no need in clean() method
// It's useless, there are no guarantees with JS GC
// whether bigints are removed even if you clean Uint8Arrays.
}
// Valid scalars are [1, n-1]
function isWithinCurveOrder(num: bigint): boolean {
return _0n < num && num < CURVE.n;
}
// Valid field elements are [1, p-1]
function isValidFieldElement(num: bigint): boolean {
return _0n < num && num < CURVE.P;
}
/**
* Converts signature params into point & r/s, checks them for validity.
* k must be in range [1, n-1]
* @param k signature's k param: deterministic in our case, random in non-rfc6979 sigs
* @param m message that would be signed
* @param d private key
* @returns Signature with its point on curve Q OR undefined if params were invalid
*/
function kmdToSig(kBytes: Uint8Array, m: bigint, d: bigint, lowS = true): RecoveredSig | undefined {
const { n } = CURVE;
const k = truncateHash(kBytes, true);
if (!isWithinCurveOrder(k)) return;
// Important: all mod() calls in the function must be done over `n`
const kinv = invert(k, n);
const q = Point.BASE.multiply(k);
// r = x mod n
const r = mod(q.x, n);
if (r === _0n) return;
// s = (m + dr)/k mod n where x/k == x*inv(k)
const s = mod(kinv * mod(m + d * r, n), n);
if (s === _0n) return;
let sig = new Signature(r, s);
// Recovery bit is usually 0 or 1; rarely it's 2 or 3, when q.x > n
let recovery = (q.x === sig.r ? 0 : 2) | Number(q.y & _1n);
if (lowS && sig.hasHighS()) {
sig = sig.normalizeS();
recovery ^= 1;
}
return { sig, recovery };
}
function normalizePrivateKey(key: PrivKey): bigint {
let num: bigint;
if (typeof key === 'bigint') {
num = key;
} else if (typeof key === 'number' && Number.isSafeInteger(key) && key > 0) {
num = BigInt(key);
} else if (typeof key === 'string') {
if (key.length !== 2 * groupLen) throw new Error('Expected 32 bytes of private key');
num = hexToNumber(key);
} else if (isBytes(key)) {
if (key.length !== groupLen) throw new Error('Expected 32 bytes of private key');
num = bytesToNumber(key);
} else {
throw new TypeError('Expected valid private key');
}
if (!isWithinCurveOrder(num)) throw new Error('Expected private key: 0 < key < n');
return num;
}
/**
* Normalizes hex, bytes, Point to Point. Checks for curve equation.
*/
function normalizePublicKey(publicKey: PubKey): Point {
if (publicKey instanceof Point) {
publicKey.assertValidity();
return publicKey;
} else {
return Point.fromHex(publicKey);
}
}
/**
* Signatures can be in 64-byte compact representation,
* or in (variable-length)-byte DER representation.
* Since DER could also be 64 bytes, we check for it first.
*/
function normalizeSignature(signature: Sig): Signature {
if (signature instanceof Signature) {
signature.assertValidity();
return signature;
}
try {
return Signature.fromDER(signature);
} catch (error) {
return Signature.fromCompact(signature);
}
}
/**
* Computes public key for secp256k1 private key.
* @param privateKey 32-byte private key
* @param isCompressed whether to return compact, or full key
* @returns Public key, full by default; short when isCompressed=true
*/
export function getPublicKey(privateKey: PrivKey, isCompressed = false): Uint8Array {
return Point.fromPrivateKey(privateKey).toRawBytes(isCompressed);
}
/**
* Recovers public key from signature and recovery bit. Throws on invalid sig/hash.
* @param msgHash message hash
* @param signature DER or compact sig
* @param recovery 0 or 1
* @param isCompressed whether to return compact, or full key
* @returns Public key, full by default; short when isCompressed=true
*/
export function recoverPublicKey(
msgHash: Hex,
signature: Sig,
recovery: number,
isCompressed = false
): Uint8Array {
return Point.fromSignature(msgHash, signature, recovery).toRawBytes(isCompressed);
}
/**
* Quick and dirty check for item being public key. Does not validate hex, or being on-curve.
*/
function isProbPub(item: PrivKey | PubKey): boolean {
const arr = isBytes(item);
const str = typeof item === 'string';
const len = (arr || str) && (item as Hex).length;
if (arr) return len === compressedLen || len === uncompressedLen;
if (str) return len === compressedLen * 2 || len === uncompressedLen * 2;
if (item instanceof Point) return true;
return false;
}
/**
* ECDH (Elliptic Curve Diffie Hellman) implementation.
* 1. Checks for validity of private key
* 2. Checks for the public key of being on-curve
* @param privateA private key
* @param publicB different public key
* @param isCompressed whether to return compact, or full key
* @returns shared public key
*/
export function getSharedSecret(
privateA: PrivKey,
publicB: PubKey,
isCompressed = false
): Uint8Array {
if (isProbPub(privateA)) throw new TypeError('getSharedSecret: first arg must be private key');
if (!isProbPub(publicB)) throw new TypeError('getSharedSecret: second arg must be public key');
const b = normalizePublicKey(publicB);
b.assertValidity();
return b.multiply(normalizePrivateKey(privateA)).toRawBytes(isCompressed);
}
type Entropy = Hex | true;
type OptsOther = { canonical?: boolean; der?: boolean; extraEntropy?: Entropy };
type OptsRecov = { recovered: true } & OptsOther;
type OptsNoRecov = { recovered?: false } & OptsOther;
type Opts = { recovered?: boolean } & OptsOther;
type SignOutput = Uint8Array | [Uint8Array, number];
// RFC6979 methods
function bits2int(bytes: Uint8Array) {
const slice = bytes.length > fieldLen ? bytes.slice(0, fieldLen) : bytes;
return bytesToNumber(slice);
}
function bits2octets(bytes: Uint8Array): Uint8Array {
const z1 = bits2int(bytes);
const z2 = mod(z1, CURVE.n);
return int2octets(z2 < _0n ? z1 : z2);
}
function int2octets(num: bigint): Uint8Array {
return numTo32b(num); // prohibits >32 bytes
}
// Steps A, D of RFC6979 3.2
// Creates RFC6979 seed; converts msg/privKey to numbers.
function initSigArgs(msgHash: Hex, privateKey: PrivKey, extraEntropy?: Entropy) {
if (msgHash == null) throw new Error(`sign: expected valid message hash, not "${msgHash}"`);
// Step A is ignored, since we already provide hash instead of msg
const h1 = ensureBytes(msgHash);
const d = normalizePrivateKey(privateKey);
// K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
const seedArgs = [int2octets(d), bits2octets(h1)];
// RFC6979 3.6: additional k' could be provided
if (extraEntropy != null) {
if (extraEntropy === true) extraEntropy = utils.randomBytes(fieldLen);
const e = ensureBytes(extraEntropy);
if (e.length !== fieldLen) throw new Error(`sign: Expected ${fieldLen} bytes of extra data`);
seedArgs.push(e);
}
// seed is constructed from private key and message
// Step D
// V, 0x00 are done in HmacDRBG constructor.
const seed = concatBytes(...seedArgs);
const m = bits2int(h1);
return { seed, m, d };
}
// Takes signature with its recovery bit, normalizes it
// Produces DER/compact signature and proper recovery bit
function finalizeSig(recSig: RecoveredSig, opts: OptsNoRecov | OptsRecov): SignOutput {
const { sig, recovery } = recSig;
const { der, recovered } = Object.assign({ canonical: true, der: true }, opts);
const hashed = der ? sig.toDERRawBytes() : sig.toCompactRawBytes();
return recovered ? [hashed, recovery] : hashed;
}
/**
* Signs message hash (not message: you need to hash it by yourself).
* We don't auto-hash because some users would want non-SHA256 hash.
* We are always using deterministic signatures (RFC6979 3.1) instead of
* letting user specify random k.
* HMAC-DRBG generates k, then calculates sig point Q & signature r, s based on it.
* Could receive extra entropy k' as per RFC6979 3.6 Additional data.
* k' is not generated by default, because of backwards-compatibility concerns.
* We strongly recommend to pass {extraEntropy: true}.
*
* low-s signatures are generated by default. If you don't want it, use canonical: false.
*
* ```
* sign(m, d, k) where
* (x, y) = G × k
* r = x mod n
* s = (m + dr)/k mod n
* ```
* @param opts `recovered, canonical, der, extraEntropy`
*/
async function sign(msgHash: Hex, privKey: PrivKey, opts: OptsRecov): Promise<[U8A, number]>;
async function sign(msgHash: Hex, privKey: PrivKey, opts?: OptsNoRecov): Promise<U8A>;
async function sign(msgHash: Hex, privKey: PrivKey, opts: Opts = {}): Promise<SignOutput> {
// Steps A, D of RFC6979 3.2.
const { seed, m, d } = initSigArgs(msgHash, privKey, opts.extraEntropy);
// Steps B, C, D, E, F, G
const drbg = new HmacDrbg(hashLen, groupLen);
await drbg.reseed(seed);
// Step H3, repeat until k is in range [1, n-1]
let sig: RecoveredSig | undefined;
while (!(sig = kmdToSig(await drbg.generate(), m, d, opts.canonical))) await drbg.reseed();
return finalizeSig(sig, opts);
}
/**
* Signs message hash (not message: you need to hash it by yourself).
* Synchronous version of `sign()`: see its documentation.
* @param opts `recovered, canonical, der, extraEntropy`
*/
function signSync(msgHash: Hex, privKey: PrivKey, opts: OptsRecov): [U8A, number];
function signSync(msgHash: Hex, privKey: PrivKey, opts?: OptsNoRecov): U8A;
function signSync(msgHash: Hex, privKey: PrivKey, opts: Opts = {}): SignOutput {
// Steps A, D of RFC6979 3.2.
const { seed, m, d } = initSigArgs(msgHash, privKey, opts.extraEntropy);
// Steps B, C, D, E, F, G
const drbg = new HmacDrbg(hashLen, groupLen);
drbg.reseedSync(seed);
// Step H3, repeat until k is in range [1, n-1]
let sig: RecoveredSig | undefined;
while (!(sig = kmdToSig(drbg.generateSync(), m, d, opts.canonical))) drbg.reseedSync();
return finalizeSig(sig, opts);
}
export { sign, signSync };
type VOpts = { strict?: boolean };
const vopts: VOpts = { strict: true };
/**
* Verifies a signature against message hash and public key.
* Rejects non-canonical / high-s signatures by default: to override,
* specify option `{strict: false}`. Implements section 4.1.4 from https://www.secg.org/sec1-v2.pdf:
*
* ```
* verify(r, s, h, P) where
* U1 = hs^-1 mod n
* U2 = rs^-1 mod n
* R = U1⋅G - U2⋅P
* mod(R.x, n) == r
* ```
*/
export function verify(signature: Sig, msgHash: Hex, publicKey: PubKey, opts = vopts): boolean {
let sig;
try {
sig = normalizeSignature(signature);
msgHash = ensureBytes(msgHash);
} catch (error) {
return false;
}
const { r, s } = sig;
if (opts.strict && sig.hasHighS()) return false;
const h = truncateHash(msgHash);
let P;
try {
P = normalizePublicKey(publicKey);
} catch (error) {
return false;
}
const { n } = CURVE;
const sinv = invert(s, n); // s^-1
// R = u1⋅G - u2⋅P
const u1 = mod(h * sinv, n);
const u2 = mod(r * sinv, n);
// Some implementations compare R.x in jacobian, without inversion.
// The speed-up is <5%, so we don't complicate the code.
const R = Point.BASE.multiplyAndAddUnsafe(P, u1, u2);
if (!R) return false;
const v = mod(R.x, n);
return v === r;
}
// Schnorr signatures are superior to ECDSA from above.
// Below is Schnorr-specific code as per BIP0340.
function schnorrChallengeFinalize(ch: Uint8Array): bigint {
return mod(bytesToNumber(ch), CURVE.n);
}
class SchnorrSignature {
constructor(readonly r: bigint, readonly s: bigint) {
this.assertValidity();
}
static fromHex(hex: Hex) {
const bytes = ensureBytes(hex);
if (bytes.length !== 64)
throw new TypeError(`SchnorrSignature.fromHex: expected 64 bytes, not ${bytes.length}`);
const r = bytesToNumber(bytes.subarray(0, 32));
const s = bytesToNumber(bytes.subarray(32, 64));
return new SchnorrSignature(r, s);
}
assertValidity() {
const { r, s } = this;
if (!isValidFieldElement(r) || !isWithinCurveOrder(s)) throw new Error('Invalid signature');
}
toHex(): string {
return numTo32bStr(this.r) + numTo32bStr(this.s);
}
toRawBytes(): Uint8Array {
return hexToBytes(this.toHex());
}
}
// Schnorr's pubkey is just `x` of Point
// BIP340
function schnorrGetPublicKey(privateKey: PrivKey): Uint8Array {
return Point.fromPrivateKey(privateKey).toRawX();
}
// We are abstracting the signature creation process into the class
// because we need to provide two identical methods: async & sync. Usage:
// new InternalSchnorrSignature(msg, privKey, auxRand).calc()
class InternalSchnorrSignature {
private m: Uint8Array;
private px: Uint8Array;
private d: bigint;
private rand: Uint8Array;
constructor(message: Hex, privateKey: PrivKey, auxRand: Hex = utils.randomBytes()) {
if (message == null) throw new TypeError(`sign: Expected valid message, not "${message}"`);
this.m = ensureBytes(message);
// checks for isWithinCurveOrder
const { x, scalar } = this.getScalar(normalizePrivateKey(privateKey));
this.px = x;
this.d = scalar;
this.rand = ensureBytes(auxRand);
if (this.rand.length !== 32) throw new TypeError('sign: Expected 32 bytes of aux randomness');
}
private getScalar(priv: bigint) {
const point = Point.fromPrivateKey(priv);
const scalar = point.hasEvenY() ? priv : CURVE.n - p