UNPKG

@covenance/dlc

Version:

Crypto and Bitcoin functions for Covenance DLC implementation

1,430 lines (1,305 loc) 59.8 kB
/* 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