2025-04-19 15:38:48 +08:00

1069 lines
45 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DER = void 0;
exports.weierstrassPoints = weierstrassPoints;
exports.weierstrass = weierstrass;
exports.SWUFpSqrtRatio = SWUFpSqrtRatio;
exports.mapToCurveSimpleSWU = mapToCurveSimpleSWU;
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// Short Weierstrass curve. The formula is: y² = x³ + ax + b
const curve_js_1 = require("./curve.js");
const mod = require("./modular.js");
const ut = require("./utils.js");
const utils_js_1 = require("./utils.js");
function validatePointOpts(curve) {
const opts = (0, curve_js_1.validateBasic)(curve);
ut.validateObject(opts, {
a: 'field',
b: 'field',
}, {
allowedPrivateKeyLengths: 'array',
wrapPrivateKey: 'boolean',
isTorsionFree: 'function',
clearCofactor: 'function',
allowInfinityPoint: 'boolean',
fromBytes: 'function',
toBytes: 'function',
});
const { endo, Fp, a } = opts;
if (endo) {
if (!Fp.eql(a, Fp.ZERO)) {
throw new Error('Endomorphism can only be defined for Koblitz curves that have a=0');
}
if (typeof endo !== 'object' ||
typeof endo.beta !== 'bigint' ||
typeof endo.splitScalar !== 'function') {
throw new Error('Expected endomorphism with beta: bigint and splitScalar: function');
}
}
return Object.freeze({ ...opts });
}
// ASN.1 DER encoding utilities
const { bytesToNumberBE: b2n, hexToBytes: h2b } = ut;
exports.DER = {
// asn.1 DER encoding utils
Err: class DERErr extends Error {
constructor(m = '') {
super(m);
}
},
_parseInt(data) {
const { Err: E } = exports.DER;
if (data.length < 2 || data[0] !== 0x02)
throw new E('Invalid signature integer tag');
const len = data[1];
const res = data.subarray(2, len + 2);
if (!len || res.length !== len)
throw new E('Invalid signature integer: wrong length');
// https://crypto.stackexchange.com/a/57734 Leftmost bit of first byte is 'negative' flag,
// since we always use positive integers here. It must always be empty:
// - add zero byte if exists
// - if next byte doesn't have a flag, leading zero is not allowed (minimal encoding)
if (res[0] & 0b10000000)
throw new E('Invalid signature integer: negative');
if (res[0] === 0x00 && !(res[1] & 0b10000000))
throw new E('Invalid signature integer: unnecessary leading zero');
return { d: b2n(res), l: data.subarray(len + 2) }; // d is data, l is left
},
toSig(hex) {
// parse DER signature
const { Err: E } = exports.DER;
const data = typeof hex === 'string' ? h2b(hex) : hex;
ut.abytes(data);
let l = data.length;
if (l < 2 || data[0] != 0x30)
throw new E('Invalid signature tag');
if (data[1] !== l - 2)
throw new E('Invalid signature: incorrect length');
const { d: r, l: sBytes } = exports.DER._parseInt(data.subarray(2));
const { d: s, l: rBytesLeft } = exports.DER._parseInt(sBytes);
if (rBytesLeft.length)
throw new E('Invalid signature: left bytes after parsing');
return { r, s };
},
hexFromSig(sig) {
// Add leading zero if first byte has negative bit enabled. More details in '_parseInt'
const slice = (s) => (Number.parseInt(s[0], 16) & 0b1000 ? '00' + s : s);
const h = (num) => {
const hex = num.toString(16);
return hex.length & 1 ? `0${hex}` : hex;
};
const s = slice(h(sig.s));
const r = slice(h(sig.r));
const shl = s.length / 2;
const rhl = r.length / 2;
const sl = h(shl);
const rl = h(rhl);
return `30${h(rhl + shl + 4)}02${rl}${r}02${sl}${s}`;
},
};
// Be friendly to bad ECMAScript parsers by not using bigint literals
// prettier-ignore
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _4n = BigInt(4);
function weierstrassPoints(opts) {
const CURVE = validatePointOpts(opts);
const { Fp } = CURVE; // All curves has same field / group length as for now, but they can differ
const toBytes = CURVE.toBytes ||
((_c, point, _isCompressed) => {
const a = point.toAffine();
return ut.concatBytes(Uint8Array.from([0x04]), Fp.toBytes(a.x), Fp.toBytes(a.y));
});
const fromBytes = CURVE.fromBytes ||
((bytes) => {
// const head = bytes[0];
const tail = bytes.subarray(1);
// if (head !== 0x04) throw new Error('Only non-compressed encoding is supported');
const x = Fp.fromBytes(tail.subarray(0, Fp.BYTES));
const y = Fp.fromBytes(tail.subarray(Fp.BYTES, 2 * Fp.BYTES));
return { x, y };
});
/**
* y² = x³ + ax + b: Short weierstrass curve formula
* @returns y²
*/
function weierstrassEquation(x) {
const { a, b } = CURVE;
const x2 = Fp.sqr(x); // x * x
const x3 = Fp.mul(x2, x); // x2 * x
return Fp.add(Fp.add(x3, Fp.mul(x, a)), b); // x3 + a * x + b
}
// Validate whether the passed curve params are valid.
// We check if curve equation works for generator point.
// `assertValidity()` won't work: `isTorsionFree()` is not available at this point in bls12-381.
// ProjectivePoint class has not been initialized yet.
if (!Fp.eql(Fp.sqr(CURVE.Gy), weierstrassEquation(CURVE.Gx)))
throw new Error('bad generator point: equation left != right');
// Valid group elements reside in range 1..n-1
function isWithinCurveOrder(num) {
return typeof num === 'bigint' && _0n < num && num < CURVE.n;
}
function assertGE(num) {
if (!isWithinCurveOrder(num))
throw new Error('Expected valid bigint: 0 < bigint < curve.n');
}
// Validates if priv key is valid and converts it to bigint.
// Supports options allowedPrivateKeyLengths and wrapPrivateKey.
function normPrivateKeyToScalar(key) {
const { allowedPrivateKeyLengths: lengths, nByteLength, wrapPrivateKey, n } = CURVE;
if (lengths && typeof key !== 'bigint') {
if (ut.isBytes(key))
key = ut.bytesToHex(key);
// Normalize to hex string, pad. E.g. P521 would norm 130-132 char hex to 132-char bytes
if (typeof key !== 'string' || !lengths.includes(key.length))
throw new Error('Invalid key');
key = key.padStart(nByteLength * 2, '0');
}
let num;
try {
num =
typeof key === 'bigint'
? key
: ut.bytesToNumberBE((0, utils_js_1.ensureBytes)('private key', key, nByteLength));
}
catch (error) {
throw new Error(`private key must be ${nByteLength} bytes, hex or bigint, not ${typeof key}`);
}
if (wrapPrivateKey)
num = mod.mod(num, n); // disabled by default, enabled for BLS
assertGE(num); // num in range [1..N-1]
return num;
}
const pointPrecomputes = new Map();
function assertPrjPoint(other) {
if (!(other instanceof Point))
throw new Error('ProjectivePoint expected');
}
/**
* Projective Point works in 3d / projective (homogeneous) coordinates: (x, y, z) ∋ (x=x/z, y=y/z)
* Default Point works in 2d / affine coordinates: (x, y)
* We're doing calculations in projective, because its operations don't require costly inversion.
*/
class Point {
constructor(px, py, pz) {
this.px = px;
this.py = py;
this.pz = pz;
if (px == null || !Fp.isValid(px))
throw new Error('x required');
if (py == null || !Fp.isValid(py))
throw new Error('y required');
if (pz == null || !Fp.isValid(pz))
throw new Error('z required');
}
// Does not validate if the point is on-curve.
// Use fromHex instead, or call assertValidity() later.
static fromAffine(p) {
const { x, y } = p || {};
if (!p || !Fp.isValid(x) || !Fp.isValid(y))
throw new Error('invalid affine point');
if (p instanceof Point)
throw new Error('projective point not allowed');
const is0 = (i) => Fp.eql(i, Fp.ZERO);
// fromAffine(x:0, y:0) would produce (x:0, y:0, z:1), but we need (x:0, y:1, z:0)
if (is0(x) && is0(y))
return Point.ZERO;
return new Point(x, y, Fp.ONE);
}
get x() {
return this.toAffine().x;
}
get y() {
return this.toAffine().y;
}
/**
* Takes a bunch of Projective Points but executes only one
* inversion on all of them. Inversion is very slow operation,
* so this improves performance massively.
* Optimization: converts a list of projective points to a list of identical points with Z=1.
*/
static normalizeZ(points) {
const toInv = Fp.invertBatch(points.map((p) => p.pz));
return points.map((p, i) => p.toAffine(toInv[i])).map(Point.fromAffine);
}
/**
* Converts hash string or Uint8Array to Point.
* @param hex short/long ECDSA hex
*/
static fromHex(hex) {
const P = Point.fromAffine(fromBytes((0, utils_js_1.ensureBytes)('pointHex', hex)));
P.assertValidity();
return P;
}
// Multiplies generator point by privateKey.
static fromPrivateKey(privateKey) {
return Point.BASE.multiply(normPrivateKeyToScalar(privateKey));
}
// "Private method", don't use it directly
_setWindowSize(windowSize) {
this._WINDOW_SIZE = windowSize;
pointPrecomputes.delete(this);
}
// A point on curve is valid if it conforms to equation.
assertValidity() {
if (this.is0()) {
// (0, 1, 0) aka ZERO is invalid in most contexts.
// In BLS, ZERO can be serialized, so we allow it.
// (0, 0, 0) is wrong representation of ZERO and is always invalid.
if (CURVE.allowInfinityPoint && !Fp.is0(this.py))
return;
throw new Error('bad point: ZERO');
}
// Some 3rd-party test vectors require different wording between here & `fromCompressedHex`
const { x, y } = this.toAffine();
// Check if x, y are valid field elements
if (!Fp.isValid(x) || !Fp.isValid(y))
throw new Error('bad point: x or y not FE');
const left = Fp.sqr(y); // y²
const right = weierstrassEquation(x); // x³ + ax + b
if (!Fp.eql(left, right))
throw new Error('bad point: equation left != right');
if (!this.isTorsionFree())
throw new Error('bad point: not in prime-order subgroup');
}
hasEvenY() {
const { y } = this.toAffine();
if (Fp.isOdd)
return !Fp.isOdd(y);
throw new Error("Field doesn't support isOdd");
}
/**
* Compare one point to another.
*/
equals(other) {
assertPrjPoint(other);
const { px: X1, py: Y1, pz: Z1 } = this;
const { px: X2, py: Y2, pz: Z2 } = other;
const U1 = Fp.eql(Fp.mul(X1, Z2), Fp.mul(X2, Z1));
const U2 = Fp.eql(Fp.mul(Y1, Z2), Fp.mul(Y2, Z1));
return U1 && U2;
}
/**
* Flips point to one corresponding to (x, -y) in Affine coordinates.
*/
negate() {
return new Point(this.px, Fp.neg(this.py), this.pz);
}
// Renes-Costello-Batina exception-free doubling formula.
// There is 30% faster Jacobian formula, but it is not complete.
// https://eprint.iacr.org/2015/1060, algorithm 3
// Cost: 8M + 3S + 3*a + 2*b3 + 15add.
double() {
const { a, b } = CURVE;
const b3 = Fp.mul(b, _3n);
const { px: X1, py: Y1, pz: Z1 } = this;
let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO; // prettier-ignore
let t0 = Fp.mul(X1, X1); // step 1
let t1 = Fp.mul(Y1, Y1);
let t2 = Fp.mul(Z1, Z1);
let t3 = Fp.mul(X1, Y1);
t3 = Fp.add(t3, t3); // step 5
Z3 = Fp.mul(X1, Z1);
Z3 = Fp.add(Z3, Z3);
X3 = Fp.mul(a, Z3);
Y3 = Fp.mul(b3, t2);
Y3 = Fp.add(X3, Y3); // step 10
X3 = Fp.sub(t1, Y3);
Y3 = Fp.add(t1, Y3);
Y3 = Fp.mul(X3, Y3);
X3 = Fp.mul(t3, X3);
Z3 = Fp.mul(b3, Z3); // step 15
t2 = Fp.mul(a, t2);
t3 = Fp.sub(t0, t2);
t3 = Fp.mul(a, t3);
t3 = Fp.add(t3, Z3);
Z3 = Fp.add(t0, t0); // step 20
t0 = Fp.add(Z3, t0);
t0 = Fp.add(t0, t2);
t0 = Fp.mul(t0, t3);
Y3 = Fp.add(Y3, t0);
t2 = Fp.mul(Y1, Z1); // step 25
t2 = Fp.add(t2, t2);
t0 = Fp.mul(t2, t3);
X3 = Fp.sub(X3, t0);
Z3 = Fp.mul(t2, t1);
Z3 = Fp.add(Z3, Z3); // step 30
Z3 = Fp.add(Z3, Z3);
return new Point(X3, Y3, Z3);
}
// Renes-Costello-Batina exception-free addition formula.
// There is 30% faster Jacobian formula, but it is not complete.
// https://eprint.iacr.org/2015/1060, algorithm 1
// Cost: 12M + 0S + 3*a + 3*b3 + 23add.
add(other) {
assertPrjPoint(other);
const { px: X1, py: Y1, pz: Z1 } = this;
const { px: X2, py: Y2, pz: Z2 } = other;
let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO; // prettier-ignore
const a = CURVE.a;
const b3 = Fp.mul(CURVE.b, _3n);
let t0 = Fp.mul(X1, X2); // step 1
let t1 = Fp.mul(Y1, Y2);
let t2 = Fp.mul(Z1, Z2);
let t3 = Fp.add(X1, Y1);
let t4 = Fp.add(X2, Y2); // step 5
t3 = Fp.mul(t3, t4);
t4 = Fp.add(t0, t1);
t3 = Fp.sub(t3, t4);
t4 = Fp.add(X1, Z1);
let t5 = Fp.add(X2, Z2); // step 10
t4 = Fp.mul(t4, t5);
t5 = Fp.add(t0, t2);
t4 = Fp.sub(t4, t5);
t5 = Fp.add(Y1, Z1);
X3 = Fp.add(Y2, Z2); // step 15
t5 = Fp.mul(t5, X3);
X3 = Fp.add(t1, t2);
t5 = Fp.sub(t5, X3);
Z3 = Fp.mul(a, t4);
X3 = Fp.mul(b3, t2); // step 20
Z3 = Fp.add(X3, Z3);
X3 = Fp.sub(t1, Z3);
Z3 = Fp.add(t1, Z3);
Y3 = Fp.mul(X3, Z3);
t1 = Fp.add(t0, t0); // step 25
t1 = Fp.add(t1, t0);
t2 = Fp.mul(a, t2);
t4 = Fp.mul(b3, t4);
t1 = Fp.add(t1, t2);
t2 = Fp.sub(t0, t2); // step 30
t2 = Fp.mul(a, t2);
t4 = Fp.add(t4, t2);
t0 = Fp.mul(t1, t4);
Y3 = Fp.add(Y3, t0);
t0 = Fp.mul(t5, t4); // step 35
X3 = Fp.mul(t3, X3);
X3 = Fp.sub(X3, t0);
t0 = Fp.mul(t3, t1);
Z3 = Fp.mul(t5, Z3);
Z3 = Fp.add(Z3, t0); // step 40
return new Point(X3, Y3, Z3);
}
subtract(other) {
return this.add(other.negate());
}
is0() {
return this.equals(Point.ZERO);
}
wNAF(n) {
return wnaf.wNAFCached(this, pointPrecomputes, n, (comp) => {
const toInv = Fp.invertBatch(comp.map((p) => p.pz));
return comp.map((p, i) => p.toAffine(toInv[i])).map(Point.fromAffine);
});
}
/**
* 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(n) {
const I = Point.ZERO;
if (n === _0n)
return I;
assertGE(n); // Will throw on 0
if (n === _1n)
return this;
const { endo } = CURVE;
if (!endo)
return wnaf.unsafeLadder(this, n);
// Apply endomorphism
let { k1neg, k1, k2neg, k2 } = endo.splitScalar(n);
let k1p = I;
let k2p = I;
let d = 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 Point(Fp.mul(k2p.px, endo.beta), k2p.py, k2p.pz);
return k1p.add(k2p);
}
/**
* Constant time multiplication.
* Uses wNAF method. Windowed method may be 10% faster,
* but takes 2x longer to generate and consumes 2x memory.
* Uses precomputes when available.
* Uses endomorphism for Koblitz curves.
* @param scalar by which the point would be multiplied
* @returns New point
*/
multiply(scalar) {
assertGE(scalar);
let n = scalar;
let point, fake; // Fake point is used to const-time mult
const { endo } = CURVE;
if (endo) {
const { k1neg, k1, k2neg, k2 } = endo.splitScalar(n);
let { p: k1p, f: f1p } = this.wNAF(k1);
let { p: k2p, f: f2p } = this.wNAF(k2);
k1p = wnaf.constTimeNegate(k1neg, k1p);
k2p = wnaf.constTimeNegate(k2neg, k2p);
k2p = new Point(Fp.mul(k2p.px, endo.beta), k2p.py, k2p.pz);
point = k1p.add(k2p);
fake = f1p.add(f2p);
}
else {
const { p, f } = this.wNAF(n);
point = p;
fake = f;
}
// Normalize `z` for both points, but return only real one
return Point.normalizeZ([point, fake])[0];
}
/**
* Efficiently calculate `aP + bQ`. Unsafe, can expose private key, if used incorrectly.
* Not using Strauss-Shamir trick: precomputation tables are faster.
* The trick could be useful if both P and Q are not G (not in our case).
* @returns non-zero affine point
*/
multiplyAndAddUnsafe(Q, a, b) {
const G = Point.BASE; // No Strauss-Shamir trick: we have 10% faster G precomputes
const mul = (P, a // Select faster multiply() method
) => (a === _0n || a === _1n || !P.equals(G) ? P.multiplyUnsafe(a) : P.multiply(a));
const sum = mul(this, a).add(mul(Q, b));
return sum.is0() ? undefined : sum;
}
// Converts Projective 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(iz) {
const { px: x, py: y, pz: z } = this;
const is0 = this.is0();
// If invZ was 0, we return zero point. However we still want to execute
// all operations, so we replace invZ with a random number, 1.
if (iz == null)
iz = is0 ? Fp.ONE : Fp.inv(z);
const ax = Fp.mul(x, iz);
const ay = Fp.mul(y, iz);
const zz = Fp.mul(z, iz);
if (is0)
return { x: Fp.ZERO, y: Fp.ZERO };
if (!Fp.eql(zz, Fp.ONE))
throw new Error('invZ was invalid');
return { x: ax, y: ay };
}
isTorsionFree() {
const { h: cofactor, isTorsionFree } = CURVE;
if (cofactor === _1n)
return true; // No subgroups, always torsion-free
if (isTorsionFree)
return isTorsionFree(Point, this);
throw new Error('isTorsionFree() has not been declared for the elliptic curve');
}
clearCofactor() {
const { h: cofactor, clearCofactor } = CURVE;
if (cofactor === _1n)
return this; // Fast-path
if (clearCofactor)
return clearCofactor(Point, this);
return this.multiplyUnsafe(CURVE.h);
}
toRawBytes(isCompressed = true) {
this.assertValidity();
return toBytes(Point, this, isCompressed);
}
toHex(isCompressed = true) {
return ut.bytesToHex(this.toRawBytes(isCompressed));
}
}
Point.BASE = new Point(CURVE.Gx, CURVE.Gy, Fp.ONE);
Point.ZERO = new Point(Fp.ZERO, Fp.ONE, Fp.ZERO);
const _bits = CURVE.nBitLength;
const wnaf = (0, curve_js_1.wNAF)(Point, CURVE.endo ? Math.ceil(_bits / 2) : _bits);
// Validate if generator point is on curve
return {
CURVE,
ProjectivePoint: Point,
normPrivateKeyToScalar,
weierstrassEquation,
isWithinCurveOrder,
};
}
function validateOpts(curve) {
const opts = (0, curve_js_1.validateBasic)(curve);
ut.validateObject(opts, {
hash: 'hash',
hmac: 'function',
randomBytes: 'function',
}, {
bits2int: 'function',
bits2int_modN: 'function',
lowS: 'boolean',
});
return Object.freeze({ lowS: true, ...opts });
}
function weierstrass(curveDef) {
const CURVE = validateOpts(curveDef);
const { Fp, n: CURVE_ORDER } = CURVE;
const compressedLen = Fp.BYTES + 1; // e.g. 33 for 32
const uncompressedLen = 2 * Fp.BYTES + 1; // e.g. 65 for 32
function isValidFieldElement(num) {
return _0n < num && num < Fp.ORDER; // 0 is banned since it's not invertible FE
}
function modN(a) {
return mod.mod(a, CURVE_ORDER);
}
function invN(a) {
return mod.invert(a, CURVE_ORDER);
}
const { ProjectivePoint: Point, normPrivateKeyToScalar, weierstrassEquation, isWithinCurveOrder, } = weierstrassPoints({
...CURVE,
toBytes(_c, point, isCompressed) {
const a = point.toAffine();
const x = Fp.toBytes(a.x);
const cat = ut.concatBytes;
if (isCompressed) {
return cat(Uint8Array.from([point.hasEvenY() ? 0x02 : 0x03]), x);
}
else {
return cat(Uint8Array.from([0x04]), x, Fp.toBytes(a.y));
}
},
fromBytes(bytes) {
const len = bytes.length;
const head = bytes[0];
const tail = bytes.subarray(1);
// this.assertValidity() is done inside of fromHex
if (len === compressedLen && (head === 0x02 || head === 0x03)) {
const x = ut.bytesToNumberBE(tail);
if (!isValidFieldElement(x))
throw new Error('Point is not on curve');
const y2 = weierstrassEquation(x); // y² = x³ + ax + b
let y;
try {
y = Fp.sqrt(y2); // y = y² ^ (p+1)/4
}
catch (sqrtError) {
const suffix = sqrtError instanceof Error ? ': ' + sqrtError.message : '';
throw new Error('Point is not on curve' + suffix);
}
const isYOdd = (y & _1n) === _1n;
// ECDSA
const isHeadOdd = (head & 1) === 1;
if (isHeadOdd !== isYOdd)
y = Fp.neg(y);
return { x, y };
}
else if (len === uncompressedLen && head === 0x04) {
const x = Fp.fromBytes(tail.subarray(0, Fp.BYTES));
const y = Fp.fromBytes(tail.subarray(Fp.BYTES, 2 * Fp.BYTES));
return { x, y };
}
else {
throw new Error(`Point of length ${len} was invalid. Expected ${compressedLen} compressed bytes or ${uncompressedLen} uncompressed bytes`);
}
},
});
const numToNByteStr = (num) => ut.bytesToHex(ut.numberToBytesBE(num, CURVE.nByteLength));
function isBiggerThanHalfOrder(number) {
const HALF = CURVE_ORDER >> _1n;
return number > HALF;
}
function normalizeS(s) {
return isBiggerThanHalfOrder(s) ? modN(-s) : s;
}
// slice bytes num
const slcNum = (b, from, to) => ut.bytesToNumberBE(b.slice(from, to));
/**
* ECDSA signature with its (r, s) properties. Supports DER & compact representations.
*/
class Signature {
constructor(r, s, recovery) {
this.r = r;
this.s = s;
this.recovery = recovery;
this.assertValidity();
}
// pair (bytes of r, bytes of s)
static fromCompact(hex) {
const l = CURVE.nByteLength;
hex = (0, utils_js_1.ensureBytes)('compactSignature', hex, l * 2);
return new Signature(slcNum(hex, 0, l), slcNum(hex, l, 2 * l));
}
// DER encoded ECDSA signature
// https://bitcoin.stackexchange.com/questions/57644/what-are-the-parts-of-a-bitcoin-transaction-input-script
static fromDER(hex) {
const { r, s } = exports.DER.toSig((0, utils_js_1.ensureBytes)('DER', hex));
return new Signature(r, s);
}
assertValidity() {
// can use assertGE here
if (!isWithinCurveOrder(this.r))
throw new Error('r must be 0 < r < CURVE.n');
if (!isWithinCurveOrder(this.s))
throw new Error('s must be 0 < s < CURVE.n');
}
addRecoveryBit(recovery) {
return new Signature(this.r, this.s, recovery);
}
recoverPublicKey(msgHash) {
const { r, s, recovery: rec } = this;
const h = bits2int_modN((0, utils_js_1.ensureBytes)('msgHash', msgHash)); // Truncate hash
if (rec == null || ![0, 1, 2, 3].includes(rec))
throw new Error('recovery id invalid');
const radj = rec === 2 || rec === 3 ? r + CURVE.n : r;
if (radj >= Fp.ORDER)
throw new Error('recovery id 2 or 3 invalid');
const prefix = (rec & 1) === 0 ? '02' : '03';
const R = Point.fromHex(prefix + numToNByteStr(radj));
const ir = invN(radj); // r^-1
const u1 = modN(-h * ir); // -hr^-1
const u2 = modN(s * ir); // sr^-1
const Q = Point.BASE.multiplyAndAddUnsafe(R, u1, u2); // (sr^-1)R-(hr^-1)G = -(hr^-1)G + (sr^-1)
if (!Q)
throw new Error('point at infinify'); // unsafe is fine: no priv data leaked
Q.assertValidity();
return Q;
}
// Signatures should be low-s, to prevent malleability.
hasHighS() {
return isBiggerThanHalfOrder(this.s);
}
normalizeS() {
return this.hasHighS() ? new Signature(this.r, modN(-this.s), this.recovery) : this;
}
// DER-encoded
toDERRawBytes() {
return ut.hexToBytes(this.toDERHex());
}
toDERHex() {
return exports.DER.hexFromSig({ r: this.r, s: this.s });
}
// padded bytes of r, then padded bytes of s
toCompactRawBytes() {
return ut.hexToBytes(this.toCompactHex());
}
toCompactHex() {
return numToNByteStr(this.r) + numToNByteStr(this.s);
}
}
const utils = {
isValidPrivateKey(privateKey) {
try {
normPrivateKeyToScalar(privateKey);
return true;
}
catch (error) {
return false;
}
},
normPrivateKeyToScalar: normPrivateKeyToScalar,
/**
* Produces cryptographically secure private key from random of size
* (groupLen + ceil(groupLen / 2)) with modulo bias being negligible.
*/
randomPrivateKey: () => {
const length = mod.getMinHashLength(CURVE.n);
return mod.mapHashToField(CURVE.randomBytes(length), CURVE.n);
},
/**
* Creates precompute table for an arbitrary EC point. Makes point "cached".
* Allows to massively speed-up `point.multiply(scalar)`.
* @returns cached point
* @example
* const fast = utils.precompute(8, ProjectivePoint.fromHex(someonesPubKey));
* fast.multiply(privKey); // much faster ECDH now
*/
precompute(windowSize = 8, point = Point.BASE) {
point._setWindowSize(windowSize);
point.multiply(BigInt(3)); // 3 is arbitrary, just need any number here
return point;
},
};
/**
* Computes public key for a private key. Checks for validity of the private key.
* @param privateKey private key
* @param isCompressed whether to return compact (default), or full key
* @returns Public key, full when isCompressed=false; short when isCompressed=true
*/
function getPublicKey(privateKey, isCompressed = true) {
return Point.fromPrivateKey(privateKey).toRawBytes(isCompressed);
}
/**
* Quick and dirty check for item being public key. Does not validate hex, or being on-curve.
*/
function isProbPub(item) {
const arr = ut.isBytes(item);
const str = typeof item === 'string';
const len = (arr || str) && item.length;
if (arr)
return len === compressedLen || len === uncompressedLen;
if (str)
return len === 2 * compressedLen || len === 2 * uncompressedLen;
if (item instanceof Point)
return true;
return false;
}
/**
* ECDH (Elliptic Curve Diffie Hellman).
* Computes shared public key from private key and public key.
* Checks: 1) private key validity 2) shared key is on-curve.
* Does NOT hash the result.
* @param privateA private key
* @param publicB different public key
* @param isCompressed whether to return compact (default), or full key
* @returns shared public key
*/
function getSharedSecret(privateA, publicB, isCompressed = true) {
if (isProbPub(privateA))
throw new Error('first arg must be private key');
if (!isProbPub(publicB))
throw new Error('second arg must be public key');
const b = Point.fromHex(publicB); // check for being on-curve
return b.multiply(normPrivateKeyToScalar(privateA)).toRawBytes(isCompressed);
}
// RFC6979: ensure ECDSA msg is X bytes and < N. RFC suggests optional truncating via bits2octets.
// FIPS 186-4 4.6 suggests the leftmost min(nBitLen, outLen) bits, which matches bits2int.
// bits2int can produce res>N, we can do mod(res, N) since the bitLen is the same.
// int2octets can't be used; pads small msgs with 0: unacceptatble for trunc as per RFC vectors
const bits2int = CURVE.bits2int ||
function (bytes) {
// For curves with nBitLength % 8 !== 0: bits2octets(bits2octets(m)) !== bits2octets(m)
// for some cases, since bytes.length * 8 is not actual bitLength.
const num = ut.bytesToNumberBE(bytes); // check for == u8 done here
const delta = bytes.length * 8 - CURVE.nBitLength; // truncate to nBitLength leftmost bits
return delta > 0 ? num >> BigInt(delta) : num;
};
const bits2int_modN = CURVE.bits2int_modN ||
function (bytes) {
return modN(bits2int(bytes)); // can't use bytesToNumberBE here
};
// NOTE: pads output with zero as per spec
const ORDER_MASK = ut.bitMask(CURVE.nBitLength);
/**
* Converts to bytes. Checks if num in `[0..ORDER_MASK-1]` e.g.: `[0..2^256-1]`.
*/
function int2octets(num) {
if (typeof num !== 'bigint')
throw new Error('bigint expected');
if (!(_0n <= num && num < ORDER_MASK))
throw new Error(`bigint expected < 2^${CURVE.nBitLength}`);
// works with order, can have different size than numToField!
return ut.numberToBytesBE(num, CURVE.nByteLength);
}
// Steps A, D of RFC6979 3.2
// Creates RFC6979 seed; converts msg/privKey to numbers.
// Used only in sign, not in verify.
// NOTE: we cannot assume here that msgHash has same amount of bytes as curve order, this will be wrong at least for P521.
// Also it can be bigger for P224 + SHA256
function prepSig(msgHash, privateKey, opts = defaultSigOpts) {
if (['recovered', 'canonical'].some((k) => k in opts))
throw new Error('sign() legacy options not supported');
const { hash, randomBytes } = CURVE;
let { lowS, prehash, extraEntropy: ent } = opts; // generates low-s sigs by default
if (lowS == null)
lowS = true; // RFC6979 3.2: we skip step A, because we already provide hash
msgHash = (0, utils_js_1.ensureBytes)('msgHash', msgHash);
if (prehash)
msgHash = (0, utils_js_1.ensureBytes)('prehashed msgHash', hash(msgHash));
// We can't later call bits2octets, since nested bits2int is broken for curves
// with nBitLength % 8 !== 0. Because of that, we unwrap it here as int2octets call.
// const bits2octets = (bits) => int2octets(bits2int_modN(bits))
const h1int = bits2int_modN(msgHash);
const d = normPrivateKeyToScalar(privateKey); // validate private key, convert to bigint
const seedArgs = [int2octets(d), int2octets(h1int)];
// extraEntropy. RFC6979 3.6: additional k' (optional).
if (ent != null && ent !== false) {
// K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
const e = ent === true ? randomBytes(Fp.BYTES) : ent; // generate random bytes OR pass as-is
seedArgs.push((0, utils_js_1.ensureBytes)('extraEntropy', e)); // check for being bytes
}
const seed = ut.concatBytes(...seedArgs); // Step D of RFC6979 3.2
const m = h1int; // NOTE: no need to call bits2int second time here, it is inside truncateHash!
// Converts signature params into point w r/s, checks result for validity.
function k2sig(kBytes) {
// RFC 6979 Section 3.2, step 3: k = bits2int(T)
const k = bits2int(kBytes); // Cannot use fields methods, since it is group element
if (!isWithinCurveOrder(k))
return; // Important: all mod() calls here must be done over N
const ik = invN(k); // k^-1 mod n
const q = Point.BASE.multiply(k).toAffine(); // q = Gk
const r = modN(q.x); // r = q.x mod n
if (r === _0n)
return;
// Can use scalar blinding b^-1(bm + bdr) where b ∈ [1,q1] according to
// https://tches.iacr.org/index.php/TCHES/article/view/7337/6509. We've decided against it:
// a) dependency on CSPRNG b) 15% slowdown c) doesn't really help since bigints are not CT
const s = modN(ik * modN(m + r * d)); // Not using blinding here
if (s === _0n)
return;
let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n); // recovery bit (2 or 3, when q.x > n)
let normS = s;
if (lowS && isBiggerThanHalfOrder(s)) {
normS = normalizeS(s); // if lowS was passed, ensure s is always
recovery ^= 1; // // in the bottom half of N
}
return new Signature(r, normS, recovery); // use normS, not s
}
return { seed, k2sig };
}
const defaultSigOpts = { lowS: CURVE.lowS, prehash: false };
const defaultVerOpts = { lowS: CURVE.lowS, prehash: false };
/**
* Signs message hash with a private key.
* ```
* sign(m, d, k) where
* (x, y) = G × k
* r = x mod n
* s = (m + dr)/k mod n
* ```
* @param msgHash NOT message. msg needs to be hashed to `msgHash`, or use `prehash`.
* @param privKey private key
* @param opts lowS for non-malleable sigs. extraEntropy for mixing randomness into k. prehash will hash first arg.
* @returns signature with recovery param
*/
function sign(msgHash, privKey, opts = defaultSigOpts) {
const { seed, k2sig } = prepSig(msgHash, privKey, opts); // Steps A, D of RFC6979 3.2.
const C = CURVE;
const drbg = ut.createHmacDrbg(C.hash.outputLen, C.nByteLength, C.hmac);
return drbg(seed, k2sig); // Steps B, C, D, E, F, G
}
// Enable precomputes. Slows down first publicKey computation by 20ms.
Point.BASE._setWindowSize(8);
// utils.precompute(8, ProjectivePoint.BASE)
/**
* Verifies a signature against message hash and public key.
* Rejects lowS signatures by default: to override,
* specify option `{lowS: 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
* ```
*/
function verify(signature, msgHash, publicKey, opts = defaultVerOpts) {
const sg = signature;
msgHash = (0, utils_js_1.ensureBytes)('msgHash', msgHash);
publicKey = (0, utils_js_1.ensureBytes)('publicKey', publicKey);
if ('strict' in opts)
throw new Error('options.strict was renamed to lowS');
const { lowS, prehash } = opts;
let _sig = undefined;
let P;
try {
if (typeof sg === 'string' || ut.isBytes(sg)) {
// Signature can be represented in 2 ways: compact (2*nByteLength) & DER (variable-length).
// Since DER can also be 2*nByteLength bytes, we check for it first.
try {
_sig = Signature.fromDER(sg);
}
catch (derError) {
if (!(derError instanceof exports.DER.Err))
throw derError;
_sig = Signature.fromCompact(sg);
}
}
else if (typeof sg === 'object' && typeof sg.r === 'bigint' && typeof sg.s === 'bigint') {
const { r, s } = sg;
_sig = new Signature(r, s);
}
else {
throw new Error('PARSE');
}
P = Point.fromHex(publicKey);
}
catch (error) {
if (error.message === 'PARSE')
throw new Error(`signature must be Signature instance, Uint8Array or hex string`);
return false;
}
if (lowS && _sig.hasHighS())
return false;
if (prehash)
msgHash = CURVE.hash(msgHash);
const { r, s } = _sig;
const h = bits2int_modN(msgHash); // Cannot use fields methods, since it is group element
const is = invN(s); // s^-1
const u1 = modN(h * is); // u1 = hs^-1 mod n
const u2 = modN(r * is); // u2 = rs^-1 mod n
const R = Point.BASE.multiplyAndAddUnsafe(P, u1, u2)?.toAffine(); // R = u1⋅G + u2⋅P
if (!R)
return false;
const v = modN(R.x);
return v === r;
}
return {
CURVE,
getPublicKey,
getSharedSecret,
sign,
verify,
ProjectivePoint: Point,
Signature,
utils,
};
}
/**
* Implementation of the Shallue and van de Woestijne method for any weierstrass curve.
* TODO: check if there is a way to merge this with uvRatio in Edwards; move to modular.
* b = True and y = sqrt(u / v) if (u / v) is square in F, and
* b = False and y = sqrt(Z * (u / v)) otherwise.
* @param Fp
* @param Z
* @returns
*/
function SWUFpSqrtRatio(Fp, Z) {
// Generic implementation
const q = Fp.ORDER;
let l = _0n;
for (let o = q - _1n; o % _2n === _0n; o /= _2n)
l += _1n;
const c1 = l; // 1. c1, the largest integer such that 2^c1 divides q - 1.
// We need 2n ** c1 and 2n ** (c1-1). We can't use **; but we can use <<.
// 2n ** c1 == 2n << (c1-1)
const _2n_pow_c1_1 = _2n << (c1 - _1n - _1n);
const _2n_pow_c1 = _2n_pow_c1_1 * _2n;
const c2 = (q - _1n) / _2n_pow_c1; // 2. c2 = (q - 1) / (2^c1) # Integer arithmetic
const c3 = (c2 - _1n) / _2n; // 3. c3 = (c2 - 1) / 2 # Integer arithmetic
const c4 = _2n_pow_c1 - _1n; // 4. c4 = 2^c1 - 1 # Integer arithmetic
const c5 = _2n_pow_c1_1; // 5. c5 = 2^(c1 - 1) # Integer arithmetic
const c6 = Fp.pow(Z, c2); // 6. c6 = Z^c2
const c7 = Fp.pow(Z, (c2 + _1n) / _2n); // 7. c7 = Z^((c2 + 1) / 2)
let sqrtRatio = (u, v) => {
let tv1 = c6; // 1. tv1 = c6
let tv2 = Fp.pow(v, c4); // 2. tv2 = v^c4
let tv3 = Fp.sqr(tv2); // 3. tv3 = tv2^2
tv3 = Fp.mul(tv3, v); // 4. tv3 = tv3 * v
let tv5 = Fp.mul(u, tv3); // 5. tv5 = u * tv3
tv5 = Fp.pow(tv5, c3); // 6. tv5 = tv5^c3
tv5 = Fp.mul(tv5, tv2); // 7. tv5 = tv5 * tv2
tv2 = Fp.mul(tv5, v); // 8. tv2 = tv5 * v
tv3 = Fp.mul(tv5, u); // 9. tv3 = tv5 * u
let tv4 = Fp.mul(tv3, tv2); // 10. tv4 = tv3 * tv2
tv5 = Fp.pow(tv4, c5); // 11. tv5 = tv4^c5
let isQR = Fp.eql(tv5, Fp.ONE); // 12. isQR = tv5 == 1
tv2 = Fp.mul(tv3, c7); // 13. tv2 = tv3 * c7
tv5 = Fp.mul(tv4, tv1); // 14. tv5 = tv4 * tv1
tv3 = Fp.cmov(tv2, tv3, isQR); // 15. tv3 = CMOV(tv2, tv3, isQR)
tv4 = Fp.cmov(tv5, tv4, isQR); // 16. tv4 = CMOV(tv5, tv4, isQR)
// 17. for i in (c1, c1 - 1, ..., 2):
for (let i = c1; i > _1n; i--) {
let tv5 = i - _2n; // 18. tv5 = i - 2
tv5 = _2n << (tv5 - _1n); // 19. tv5 = 2^tv5
let tvv5 = Fp.pow(tv4, tv5); // 20. tv5 = tv4^tv5
const e1 = Fp.eql(tvv5, Fp.ONE); // 21. e1 = tv5 == 1
tv2 = Fp.mul(tv3, tv1); // 22. tv2 = tv3 * tv1
tv1 = Fp.mul(tv1, tv1); // 23. tv1 = tv1 * tv1
tvv5 = Fp.mul(tv4, tv1); // 24. tv5 = tv4 * tv1
tv3 = Fp.cmov(tv2, tv3, e1); // 25. tv3 = CMOV(tv2, tv3, e1)
tv4 = Fp.cmov(tvv5, tv4, e1); // 26. tv4 = CMOV(tv5, tv4, e1)
}
return { isValid: isQR, value: tv3 };
};
if (Fp.ORDER % _4n === _3n) {
// sqrt_ratio_3mod4(u, v)
const c1 = (Fp.ORDER - _3n) / _4n; // 1. c1 = (q - 3) / 4 # Integer arithmetic
const c2 = Fp.sqrt(Fp.neg(Z)); // 2. c2 = sqrt(-Z)
sqrtRatio = (u, v) => {
let tv1 = Fp.sqr(v); // 1. tv1 = v^2
const tv2 = Fp.mul(u, v); // 2. tv2 = u * v
tv1 = Fp.mul(tv1, tv2); // 3. tv1 = tv1 * tv2
let y1 = Fp.pow(tv1, c1); // 4. y1 = tv1^c1
y1 = Fp.mul(y1, tv2); // 5. y1 = y1 * tv2
const y2 = Fp.mul(y1, c2); // 6. y2 = y1 * c2
const tv3 = Fp.mul(Fp.sqr(y1), v); // 7. tv3 = y1^2; 8. tv3 = tv3 * v
const isQR = Fp.eql(tv3, u); // 9. isQR = tv3 == u
let y = Fp.cmov(y2, y1, isQR); // 10. y = CMOV(y2, y1, isQR)
return { isValid: isQR, value: y }; // 11. return (isQR, y) isQR ? y : y*c2
};
}
// No curves uses that
// if (Fp.ORDER % _8n === _5n) // sqrt_ratio_5mod8
return sqrtRatio;
}
/**
* Simplified Shallue-van de Woestijne-Ulas Method
* https://www.rfc-editor.org/rfc/rfc9380#section-6.6.2
*/
function mapToCurveSimpleSWU(Fp, opts) {
mod.validateField(Fp);
if (!Fp.isValid(opts.A) || !Fp.isValid(opts.B) || !Fp.isValid(opts.Z))
throw new Error('mapToCurveSimpleSWU: invalid opts');
const sqrtRatio = SWUFpSqrtRatio(Fp, opts.Z);
if (!Fp.isOdd)
throw new Error('Fp.isOdd is not implemented!');
// Input: u, an element of F.
// Output: (x, y), a point on E.
return (u) => {
// prettier-ignore
let tv1, tv2, tv3, tv4, tv5, tv6, x, y;
tv1 = Fp.sqr(u); // 1. tv1 = u^2
tv1 = Fp.mul(tv1, opts.Z); // 2. tv1 = Z * tv1
tv2 = Fp.sqr(tv1); // 3. tv2 = tv1^2
tv2 = Fp.add(tv2, tv1); // 4. tv2 = tv2 + tv1
tv3 = Fp.add(tv2, Fp.ONE); // 5. tv3 = tv2 + 1
tv3 = Fp.mul(tv3, opts.B); // 6. tv3 = B * tv3
tv4 = Fp.cmov(opts.Z, Fp.neg(tv2), !Fp.eql(tv2, Fp.ZERO)); // 7. tv4 = CMOV(Z, -tv2, tv2 != 0)
tv4 = Fp.mul(tv4, opts.A); // 8. tv4 = A * tv4
tv2 = Fp.sqr(tv3); // 9. tv2 = tv3^2
tv6 = Fp.sqr(tv4); // 10. tv6 = tv4^2
tv5 = Fp.mul(tv6, opts.A); // 11. tv5 = A * tv6
tv2 = Fp.add(tv2, tv5); // 12. tv2 = tv2 + tv5
tv2 = Fp.mul(tv2, tv3); // 13. tv2 = tv2 * tv3
tv6 = Fp.mul(tv6, tv4); // 14. tv6 = tv6 * tv4
tv5 = Fp.mul(tv6, opts.B); // 15. tv5 = B * tv6
tv2 = Fp.add(tv2, tv5); // 16. tv2 = tv2 + tv5
x = Fp.mul(tv1, tv3); // 17. x = tv1 * tv3
const { isValid, value } = sqrtRatio(tv2, tv6); // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6)
y = Fp.mul(tv1, u); // 19. y = tv1 * u -> Z * u^3 * y1
y = Fp.mul(y, value); // 20. y = y * y1
x = Fp.cmov(x, tv3, isValid); // 21. x = CMOV(x, tv3, is_gx1_square)
y = Fp.cmov(y, value, isValid); // 22. y = CMOV(y, y1, is_gx1_square)
const e1 = Fp.isOdd(u) === Fp.isOdd(y); // 23. e1 = sgn0(u) == sgn0(y)
y = Fp.cmov(Fp.neg(y), y, e1); // 24. y = CMOV(-y, y, e1)
x = Fp.div(x, tv4); // 25. x = x / tv4
return { x, y };
};
}
//# sourceMappingURL=weierstrass.js.map