@noble/curves
Version:
Audited & minimal JS implementation of elliptic curve cryptography
1,120 lines • 63.5 kB
JavaScript
/**
* Short Weierstrass curve methods. The formula is: y² = x³ + ax + b.
*
* ### Design rationale for types
*
* * Interaction between classes from different curves should fail:
* `k256.Point.BASE.add(p256.Point.BASE)`
* * For this purpose we want to use `instanceof` operator, which is fast and works during runtime
* * Different calls of `curve()` would return different classes -
* `curve(params) !== curve(params)`: if somebody decided to monkey-patch their curve,
* it won't affect others
*
* TypeScript can't infer types for classes created inside a function. Classes is one instance
* of nominative types in TypeScript and interfaces only check for shape, so it's hard to create
* unique type for every function call.
*
* We can use generic types via some param, like curve opts, but that would:
* 1. Enable interaction between `curve(params)` and `curve(params)` (curves of same params)
* which is hard to debug.
* 2. Params can be generic and we can't enforce them to be constant value:
* if somebody creates curve from non-constant params,
* it would be allowed to interact with other curves with non-constant params
*
* @todo https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-7.html#unique-symbol
* @module
*/
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { hmac as nobleHmac } from '@noble/hashes/hmac.js';
import { ahash } from '@noble/hashes/utils.js';
import { abignumber, abool, abytes, aInRange, asafenumber, bitLen, bitMask, bytesToHex, bytesToNumberBE, concatBytes, createHmacDrbg, hexToBytes, isBytes, numberToHexUnpadded, validateObject, randomBytes as wcRandomBytes, } from "../utils.js";
import { createCurveFields, createKeygen, mulEndoUnsafe, negateCt, normalizeZ, wNAF, } from "./curve.js";
import { FpInvertBatch, FpIsSquare, getMinHashLength, mapHashToField, validateField, } from "./modular.js";
// We construct the basis so `den` is always positive and equals `n`,
// but the `num` sign depends on the basis, not on the secret value.
// Exact half-way cases round away from zero, which keeps the split symmetric
// around the reduced-basis boundaries used by endomorphism decomposition.
const divNearest = (num, den) => (num + (num >= 0 ? den : -den) / _2n) / den;
/** Splits scalar for GLV endomorphism. */
export function _splitEndoScalar(k, basis, n) {
// Split scalar into two such that part is ~half bits: `abs(part) < sqrt(N)`
// Since part can be negative, we need to do this on point.
// Callers must provide a reduced GLV basis whose vectors satisfy
// `a + b * lambda ≡ 0 (mod n)`; this helper only sees the basis and `n`.
// Reject unreduced scalars instead of silently treating them mod n.
aInRange('scalar', k, _0n, n);
// TODO: verifyScalar function which consumes lambda
const [[a1, b1], [a2, b2]] = basis;
const c1 = divNearest(b2 * k, n);
const c2 = divNearest(-b1 * k, n);
// |k1|/|k2| is < sqrt(N), but can be negative.
// If we do `k1 mod N`, we'll get big scalar (`> sqrt(N)`): so, we do cheaper negation instead.
let k1 = k - c1 * a1 - c2 * a2;
let k2 = -c1 * b1 - c2 * b2;
const k1neg = k1 < _0n;
const k2neg = k2 < _0n;
if (k1neg)
k1 = -k1;
if (k2neg)
k2 = -k2;
// Double check that resulting scalar less than half bits of N: otherwise wNAF will fail.
// This should only happen on wrong bases.
// Also, the math inside is complex enough that this guard is worth keeping.
const MAX_NUM = bitMask(Math.ceil(bitLen(n) / 2)) + _1n; // Half bits of N
if (k1 < _0n || k1 >= MAX_NUM || k2 < _0n || k2 >= MAX_NUM) {
throw new Error('splitScalar (endomorphism): failed for k');
}
return { k1neg, k1, k2neg, k2 };
}
function validateSigFormat(format) {
if (!['compact', 'recovered', 'der'].includes(format))
throw new Error('Signature format must be "compact", "recovered", or "der"');
return format;
}
function validateSigOpts(opts, def) {
validateObject(opts);
const optsn = {};
// Normalize only the declared option subset from `def`; unknown keys are
// intentionally ignored so shared / superset option bags stay valid here too.
// `extraEntropy` stays an opaque payload until the signing path consumes it.
for (let optName of Object.keys(def)) {
// @ts-ignore
optsn[optName] = opts[optName] === undefined ? def[optName] : opts[optName];
}
abool(optsn.lowS, 'lowS');
abool(optsn.prehash, 'prehash');
if (optsn.format !== undefined)
validateSigFormat(optsn.format);
return optsn;
}
/**
* @param m - Error message.
* @example
* Throw a DER-specific error when signature parsing encounters invalid bytes.
*
* ```ts
* new DERErr('bad der');
* ```
*/
export class DERErr extends Error {
constructor(m = '') {
super(m);
}
}
/**
* ASN.1 DER encoding utilities. ASN is very complex & fragile. Format:
*
* [0x30 (SEQUENCE), bytelength, 0x02 (INTEGER), intLength, R, 0x02 (INTEGER), intLength, S]
*
* Docs: {@link https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/ | Let's Encrypt ASN.1 guide} and
* {@link https://luca.ntop.org/Teaching/Appunti/asn1.html | Luca Deri's ASN.1 notes}.
* @example
* ASN.1 DER encoding utilities.
*
* ```ts
* const der = DER.hexFromSig({ r: 1n, s: 2n });
* ```
*/
export const DER = {
// asn.1 DER encoding utils
Err: DERErr,
// Basic building block is TLV (Tag-Length-Value)
_tlv: {
encode: (tag, data) => {
const { Err: E } = DER;
asafenumber(tag, 'tag');
if (tag < 0 || tag > 255)
throw new E('tlv.encode: wrong tag');
if (typeof data !== 'string')
throw new TypeError('"data" expected string, got type=' + typeof data);
// Internal helper: callers hand this already-validated hex payload, so we only enforce
// byte alignment here instead of re-validating every nibble.
if (data.length & 1)
throw new E('tlv.encode: unpadded data');
const dataLen = data.length / 2;
const len = numberToHexUnpadded(dataLen);
if ((len.length / 2) & 0b1000_0000)
throw new E('tlv.encode: long form length too big');
// length of length with long form flag
const lenLen = dataLen > 127 ? numberToHexUnpadded((len.length / 2) | 0b1000_0000) : '';
const t = numberToHexUnpadded(tag);
return t + lenLen + len + data;
},
// v - value, l - left bytes (unparsed)
decode(tag, data) {
const { Err: E } = DER;
data = abytes(data, undefined, 'DER data');
let pos = 0;
if (tag < 0 || tag > 255)
throw new E('tlv.encode: wrong tag');
if (data.length < 2 || data[pos++] !== tag)
throw new E('tlv.decode: wrong tlv');
const first = data[pos++];
// First bit of first length byte is the short/long form flag.
const isLong = !!(first & 0b1000_0000);
let length = 0;
if (!isLong)
length = first;
else {
// Long form: [longFlag(1bit), lengthLength(7bit), length (BE)]
const lenLen = first & 0b0111_1111;
if (!lenLen)
throw new E('tlv.decode(long): indefinite length not supported');
// This would overflow u32 in JS.
if (lenLen > 4)
throw new E('tlv.decode(long): byte length is too big');
const lengthBytes = data.subarray(pos, pos + lenLen);
if (lengthBytes.length !== lenLen)
throw new E('tlv.decode: length bytes not complete');
if (lengthBytes[0] === 0)
throw new E('tlv.decode(long): zero leftmost byte');
for (const b of lengthBytes)
length = (length << 8) | b;
pos += lenLen;
if (length < 128)
throw new E('tlv.decode(long): not minimal encoding');
}
const v = data.subarray(pos, pos + length);
if (v.length !== length)
throw new E('tlv.decode: wrong value length');
return { v, l: data.subarray(pos + 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)
_int: {
encode(num) {
const { Err: E } = DER;
abignumber(num);
if (num < _0n)
throw new E('integer: negative integers are not allowed');
let hex = numberToHexUnpadded(num);
// Pad with zero byte if negative flag is present
if (Number.parseInt(hex[0], 16) & 0b1000)
hex = '00' + hex;
if (hex.length & 1)
throw new E('unexpected DER parsing assertion: unpadded hex');
return hex;
},
decode(data) {
const { Err: E } = DER;
if (data.length < 1)
throw new E('invalid signature integer: empty');
if (data[0] & 0b1000_0000)
throw new E('invalid signature integer: negative');
// Single-byte zero `00` is the canonical DER INTEGER encoding for zero.
if (data.length > 1 && data[0] === 0x00 && !(data[1] & 0b1000_0000))
throw new E('invalid signature integer: unnecessary leading zero');
return bytesToNumberBE(data);
},
},
toSig(bytes) {
// parse DER signature
const { Err: E, _int: int, _tlv: tlv } = DER;
const data = abytes(bytes, undefined, 'signature');
const { v: seqBytes, l: seqLeftBytes } = tlv.decode(0x30, data);
if (seqLeftBytes.length)
throw new E('invalid signature: left bytes after parsing');
const { v: rBytes, l: rLeftBytes } = tlv.decode(0x02, seqBytes);
const { v: sBytes, l: sLeftBytes } = tlv.decode(0x02, rLeftBytes);
if (sLeftBytes.length)
throw new E('invalid signature: left bytes after parsing');
return { r: int.decode(rBytes), s: int.decode(sBytes) };
},
hexFromSig(sig) {
const { _tlv: tlv, _int: int } = DER;
const rs = tlv.encode(0x02, int.encode(sig.r));
const ss = tlv.encode(0x02, int.encode(sig.s));
const seq = rs + ss;
return tlv.encode(0x30, seq);
},
};
Object.freeze(DER._tlv);
Object.freeze(DER._int);
Object.freeze(DER);
// Be friendly to bad ECMAScript parsers by not using bigint literals
// prettier-ignore
const _0n = /* @__PURE__ */ BigInt(0), _1n = /* @__PURE__ */ BigInt(1), _2n = /* @__PURE__ */ BigInt(2), _3n = /* @__PURE__ */ BigInt(3), _4n = /* @__PURE__ */ BigInt(4);
/**
* Creates weierstrass Point constructor, based on specified curve options.
*
* See {@link WeierstrassOpts}.
* @param params - Curve parameters. See {@link WeierstrassOpts}.
* @param extraOpts - Optional helpers and overrides. See {@link WeierstrassExtraOpts}.
* @returns Weierstrass point constructor.
* @throws If the curve parameters, overrides, or point codecs are invalid. {@link Error}
*
* @example
* Construct a point type from explicit Weierstrass curve parameters.
*
* ```js
* const opts = {
* p: 0xfffffffffffffffffffffffffffffffeffffac73n,
* n: 0x100000000000000000001b8fa16dfab9aca16b6b3n,
* h: 1n,
* a: 0n,
* b: 7n,
* Gx: 0x3b4c382ce37aa192a4019e763036f4f5dd4d7ebbn,
* Gy: 0x938cf935318fdced6bc28286531733c3f03c4feen,
* };
* const secp160k1_Point = weierstrass(opts);
* ```
*/
export function weierstrass(params, extraOpts = {}) {
const validated = createCurveFields('weierstrass', params, extraOpts);
const Fp = validated.Fp;
const Fn = validated.Fn;
let CURVE = validated.CURVE;
const { h: cofactor, n: CURVE_ORDER } = CURVE;
validateObject(extraOpts, {}, {
allowInfinityPoint: 'boolean',
clearCofactor: 'function',
isTorsionFree: 'function',
fromBytes: 'function',
toBytes: 'function',
endo: 'object',
});
// Snapshot constructor-time flags whose later mutation would otherwise change
// validity semantics of an already-built point type.
const { endo, allowInfinityPoint } = extraOpts;
if (endo) {
// validateObject(endo, { beta: 'bigint', splitScalar: 'function' });
if (!Fp.is0(CURVE.a) || typeof endo.beta !== 'bigint' || !Array.isArray(endo.basises)) {
throw new Error('invalid endo: expected "beta": bigint and "basises": array');
}
}
const lengths = getWLengths(Fp, Fn);
function assertCompressionIsSupported() {
if (!Fp.isOdd)
throw new Error('compression is not supported: Field does not have .isOdd()');
}
// Implements IEEE P1363 point encoding
function pointToBytes(_c, point, isCompressed) {
// SEC 1 v2.0 §2.3.3 encodes infinity as the single octet 0x00. Only curves
// that opt into infinity as a public point value should expose that byte form.
if (allowInfinityPoint && point.is0())
return Uint8Array.of(0);
const { x, y } = point.toAffine();
const bx = Fp.toBytes(x);
abool(isCompressed, 'isCompressed');
if (isCompressed) {
assertCompressionIsSupported();
const hasEvenY = !Fp.isOdd(y);
return concatBytes(pprefix(hasEvenY), bx);
}
else {
return concatBytes(Uint8Array.of(0x04), bx, Fp.toBytes(y));
}
}
function pointFromBytes(bytes) {
abytes(bytes, undefined, 'Point');
const { publicKey: comp, publicKeyUncompressed: uncomp } = lengths; // e.g. for 32-byte: 33, 65
const length = bytes.length;
const head = bytes[0];
const tail = bytes.subarray(1);
if (allowInfinityPoint && length === 1 && head === 0x00)
return { x: Fp.ZERO, y: Fp.ZERO };
// SEC 1 v2.0 §2.3.4 decodes 0x00 as infinity, but §3.2.2 public-key validation
// rejects infinity. We therefore keep 0x00 rejected by default because callers
// reuse this parser as the strict public-key boundary, and only admit it when
// the curve explicitly opts into infinity as a public point value. secp256k1
// crosstests show OpenSSL raw point codecs accept 0x00 too.
// No actual validation is done here: use .assertValidity()
if (length === comp && (head === 0x02 || head === 0x03)) {
const x = Fp.fromBytes(tail);
if (!Fp.isValid(x))
throw new Error('bad point: is not on curve, wrong x');
const y2 = weierstrassEquation(x); // y² = x³ + ax + b
let y;
try {
y = Fp.sqrt(y2); // y = y² ^ (p+1)/4
}
catch (sqrtError) {
const err = sqrtError instanceof Error ? ': ' + sqrtError.message : '';
throw new Error('bad point: is not on curve, sqrt error' + err);
}
assertCompressionIsSupported();
const evenY = Fp.isOdd(y);
const evenH = (head & 1) === 1; // ECDSA-specific
if (evenH !== evenY)
y = Fp.neg(y);
return { x, y };
}
else if (length === uncomp && head === 0x04) {
// TODO: more checks
const L = Fp.BYTES;
const x = Fp.fromBytes(tail.subarray(0, L));
const y = Fp.fromBytes(tail.subarray(L, L * 2));
if (!isValidXY(x, y))
throw new Error('bad point: is not on curve');
return { x, y };
}
else {
throw new Error(`bad point: got length ${length}, expected compressed=${comp} or uncompressed=${uncomp}`);
}
}
const encodePoint = extraOpts.toBytes === undefined ? pointToBytes : extraOpts.toBytes;
const decodePoint = extraOpts.fromBytes === undefined ? pointFromBytes : extraOpts.fromBytes;
function weierstrassEquation(x) {
const x2 = Fp.sqr(x); // x * x
const x3 = Fp.mul(x2, x); // x² * x
return Fp.add(Fp.add(x3, Fp.mul(x, CURVE.a)), CURVE.b); // x³ + a * x + b
}
// TODO: move top-level
/** Checks whether equation holds for given x, y: y² == x³ + ax + b */
function isValidXY(x, y) {
const left = Fp.sqr(y); // y²
const right = weierstrassEquation(x); // x³ + ax + b
return Fp.eql(left, right);
}
// Keep constructor-time generator validation cheap: callers are responsible for supplying the
// correct prime-order base point, while eager subgroup checks here would slow heavy module imports.
// Test 1: equation y² = x³ + ax + b should work for generator point.
if (!isValidXY(CURVE.Gx, CURVE.Gy))
throw new Error('bad curve params: generator point');
// Test 2: discriminant Δ part should be non-zero: 4a³ + 27b² != 0.
// Guarantees curve is genus-1, smooth (non-singular).
const _4a3 = Fp.mul(Fp.pow(CURVE.a, _3n), _4n);
const _27b2 = Fp.mul(Fp.sqr(CURVE.b), BigInt(27));
if (Fp.is0(Fp.add(_4a3, _27b2)))
throw new Error('bad curve params: a or b');
/** Asserts coordinate is valid: 0 <= n < Fp.ORDER. */
function acoord(title, n, banZero = false) {
if (!Fp.isValid(n) || (banZero && Fp.is0(n)))
throw new Error(`bad point coordinate ${title}`);
return n;
}
function aprjpoint(other) {
if (!(other instanceof Point))
throw new Error('Weierstrass Point expected');
}
function splitEndoScalarN(k) {
if (!endo || !endo.basises)
throw new Error('no endo');
return _splitEndoScalar(k, endo.basises, Fn.ORDER);
}
function finishEndo(endoBeta, k1p, k2p, k1neg, k2neg) {
k2p = new Point(Fp.mul(k2p.X, endoBeta), k2p.Y, k2p.Z);
k1p = negateCt(k1neg, k1p);
k2p = negateCt(k2neg, k2p);
return k1p.add(k2p);
}
/**
* 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 {
// base / generator point
static BASE = new Point(CURVE.Gx, CURVE.Gy, Fp.ONE);
// zero / infinity / identity point
static ZERO = new Point(Fp.ZERO, Fp.ONE, Fp.ZERO); // 0, 1, 0
// math field
static Fp = Fp;
// scalar field
static Fn = Fn;
X;
Y;
Z;
/** Does NOT validate if the point is valid. Use `.assertValidity()`. */
constructor(X, Y, Z) {
this.X = acoord('x', X);
// This is not just about ZERO / infinity: ambient curves can have real
// finite points with y=0. Those points are 2-torsion, so they cannot lie
// in the odd prime-order subgroups this point type is meant to represent.
this.Y = acoord('y', Y, true);
this.Z = acoord('z', Z);
Object.freeze(this);
}
static CURVE() {
return CURVE;
}
/** Does NOT validate if the point is valid. Use `.assertValidity()`. */
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');
// (0, 0) would've produced (0, 0, 1) - instead, we need (0, 1, 0)
if (Fp.is0(x) && Fp.is0(y))
return Point.ZERO;
return new Point(x, y, Fp.ONE);
}
static fromBytes(bytes) {
const P = Point.fromAffine(decodePoint(abytes(bytes, undefined, 'point')));
P.assertValidity();
return P;
}
static fromHex(hex) {
return Point.fromBytes(hexToBytes(hex));
}
get x() {
return this.toAffine().x;
}
get y() {
return this.toAffine().y;
}
/**
*
* @param windowSize
* @param isLazy - true will defer table computation until the first multiplication
* @returns
*/
precompute(windowSize = 8, isLazy = true) {
wnaf.createCache(this, windowSize);
if (!isLazy)
this.multiply(_3n); // random number
return this;
}
// TODO: return `this`
/** A point on curve is valid if it conforms to equation. */
assertValidity() {
const p = this;
if (p.is0()) {
// (0, 1, 0) aka ZERO is invalid in most contexts.
// In BLS, ZERO can be serialized, so we allow it.
// Keep the accepted infinity encoding canonical: projective-equivalent (X, Y, 0) points
// like (1, 1, 0) compare equal to ZERO, but only (0, 1, 0) should pass this guard.
if (extraOpts.allowInfinityPoint && Fp.is0(p.X) && Fp.eql(p.Y, Fp.ONE) && Fp.is0(p.Z))
return;
throw new Error('bad point: ZERO');
}
// Some 3rd-party test vectors require different wording between here & `fromCompressedHex`
const { x, y } = p.toAffine();
if (!Fp.isValid(x) || !Fp.isValid(y))
throw new Error('bad point: x or y not field elements');
if (!isValidXY(x, y))
throw new Error('bad point: equation left != right');
if (!p.isTorsionFree())
throw new Error('bad point: not in prime-order subgroup');
}
hasEvenY() {
const { y } = this.toAffine();
if (!Fp.isOdd)
throw new Error("Field doesn't support isOdd");
return !Fp.isOdd(y);
}
/** Compare one point to another. */
equals(other) {
aprjpoint(other);
const { X: X1, Y: Y1, Z: Z1 } = this;
const { X: X2, Y: Y2, Z: 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.X, Fp.neg(this.Y), this.Z);
}
// 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 { X: X1, Y: Y1, Z: 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) {
aprjpoint(other);
const { X: X1, Y: Y1, Z: Z1 } = this;
const { X: X2, Y: Y2, Z: 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) {
// Validate before calling `negate()` so wrong inputs fail with the point guard
// instead of leaking a foreign `negate()` error.
aprjpoint(other);
return this.add(other.negate());
}
is0() {
return this.equals(Point.ZERO);
}
/**
* 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) {
const { endo } = extraOpts;
// Keep the subgroup-scalar contract strict instead of reducing 0 / n to ZERO.
// In key/signature-style callers, those values usually mean broken hash/scalar plumbing,
// and failing closed is safer than silently producing the identity point.
if (!Fn.isValidNot0(scalar))
throw new RangeError('invalid scalar: out of range'); // 0 is invalid
let point, fake; // Fake point is used to const-time mult
const mul = (n) => wnaf.cached(this, n, (p) => normalizeZ(Point, p));
/** See docs for {@link EndomorphismOpts} */
if (endo) {
const { k1neg, k1, k2neg, k2 } = splitEndoScalarN(scalar);
const { p: k1p, f: k1f } = mul(k1);
const { p: k2p, f: k2f } = mul(k2);
fake = k1f.add(k2f);
point = finishEndo(endo.beta, k1p, k2p, k1neg, k2neg);
}
else {
const { p, f } = mul(scalar);
point = p;
fake = f;
}
// Normalize `z` for both points, but return only real one
return normalizeZ(Point, [point, fake])[0];
}
/**
* 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 secret key e.g. sig verification, which works over *public* keys.
*/
multiplyUnsafe(scalar) {
const { endo } = extraOpts;
const p = this;
const sc = scalar;
// Public-scalar callers may need 0, but n and larger values stay rejected here too.
// Reducing them mod n would turn bad caller input into an accidental identity point.
if (!Fn.isValid(sc))
throw new RangeError('invalid scalar: out of range'); // 0 is valid
if (sc === _0n || p.is0())
return Point.ZERO; // 0
if (sc === _1n)
return p; // 1
if (wnaf.hasCache(this))
return this.multiply(sc); // precomputes
// We don't have method for double scalar multiplication (aP + bQ):
// Even with using Strauss-Shamir trick, it's 35% slower than naïve mul+add.
if (endo) {
const { k1neg, k1, k2neg, k2 } = splitEndoScalarN(sc);
const { p1, p2 } = mulEndoUnsafe(Point, p, k1, k2); // 30% faster vs wnaf.unsafe
return finishEndo(endo.beta, p1, p2, k1neg, k2neg);
}
else {
return wnaf.unsafe(p, sc);
}
}
/**
* Converts Projective point to affine (x, y) coordinates.
* (X, Y, Z) ∋ (x=X/Z, y=Y/Z).
* @param invertedZ - Z^-1 (inverted zero) - optional, precomputation is useful for invertBatch
*/
toAffine(invertedZ) {
const p = this;
let iz = invertedZ;
const { X, Y, Z } = p;
// Fast-path for normalized points
if (Fp.eql(Z, Fp.ONE))
return { x: X, y: Y };
const is0 = p.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 x = Fp.mul(X, iz);
const y = 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, y };
}
/**
* Checks whether Point is free of torsion elements (is in prime subgroup).
* Always torsion-free for cofactor=1 curves.
*/
isTorsionFree() {
const { isTorsionFree } = extraOpts;
if (cofactor === _1n)
return true;
if (isTorsionFree)
return isTorsionFree(Point, this);
return wnaf.unsafe(this, CURVE_ORDER).is0();
}
clearCofactor() {
const { clearCofactor } = extraOpts;
if (cofactor === _1n)
return this; // Fast-path
if (clearCofactor)
return clearCofactor(Point, this);
// Default fallback assumes the cofactor fits the usual subgroup-scalar
// multiplyUnsafe() contract. Curves with larger / structured cofactors
// should define a clearCofactor override anyway (e.g. psi/Frobenius maps).
return this.multiplyUnsafe(cofactor);
}
isSmallOrder() {
if (cofactor === _1n)
return this.is0(); // Fast-path
return this.clearCofactor().is0();
}
toBytes(isCompressed = true) {
abool(isCompressed, 'isCompressed');
// Same policy as pointFromBytes(): keep ZERO out of the default byte surface because
// callers use these encodings as public keys, where SEC 1 validation rejects infinity.
this.assertValidity();
return encodePoint(Point, this, isCompressed);
}
toHex(isCompressed = true) {
return bytesToHex(this.toBytes(isCompressed));
}
toString() {
return `<Point ${this.is0() ? 'ZERO' : this.toHex()}>`;
}
}
const bits = Fn.BITS;
const wnaf = new wNAF(Point, extraOpts.endo ? Math.ceil(bits / 2) : bits);
// Tiny toy curves can have scalar fields narrower than 8 bits. Skip the
// eager W=8 cache there instead of rejecting an otherwise valid constructor.
if (bits >= 8)
Point.BASE.precompute(8); // Enable precomputes. Slows down first publicKey computation by 20ms.
Object.freeze(Point.prototype);
Object.freeze(Point);
return Point;
}
// Points start with byte 0x02 when y is even; otherwise 0x03
function pprefix(hasEvenY) {
return Uint8Array.of(hasEvenY ? 0x02 : 0x03);
}
/**
* 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.
* RFC 9380 expects callers to provide `v != 0`; this helper does not enforce it.
* @param Fp - Field implementation.
* @param Z - Simplified SWU map parameter.
* @returns Square-root ratio helper.
* @example
* Build the square-root ratio helper used by SWU map implementations.
*
* ```ts
* import { SWUFpSqrtRatio } from '@noble/curves/abstract/weierstrass.js';
* import { Field } from '@noble/curves/abstract/modular.js';
* const Fp = Field(17n);
* const sqrtRatio = SWUFpSqrtRatio(Fp, 3n);
* const out = sqrtRatio(4n, 1n);
* ```
*/
export function SWUFpSqrtRatio(Fp, Z) {
// Fail with the usual field-shape error before touching pow/cmov on malformed field shims.
const F = validateField(Fp);
// Generic implementation
const q = F.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 = F.pow(Z, c2); // 6. c6 = Z^c2
const c7 = F.pow(Z, (c2 + _1n) / _2n); // 7. c7 = Z^((c2 + 1) / 2)
// RFC 9380 Appendix F.2.1.1 defines sqrt_ratio(u, v) only for v != 0.
// We keep v=0 on the regular result path with isValid=false instead of
// throwing so the helper stays closer to the RFC's fixed control flow.
let sqrtRatio = (u, v) => {
let tv1 = c6; // 1. tv1 = c6
let tv2 = F.pow(v, c4); // 2. tv2 = v^c4
let tv3 = F.sqr(tv2); // 3. tv3 = tv2^2
tv3 = F.mul(tv3, v); // 4. tv3 = tv3 * v
let tv5 = F.mul(u, tv3); // 5. tv5 = u * tv3
tv5 = F.pow(tv5, c3); // 6. tv5 = tv5^c3
tv5 = F.mul(tv5, tv2); // 7. tv5 = tv5 * tv2
tv2 = F.mul(tv5, v); // 8. tv2 = tv5 * v
tv3 = F.mul(tv5, u); // 9. tv3 = tv5 * u
let tv4 = F.mul(tv3, tv2); // 10. tv4 = tv3 * tv2
tv5 = F.pow(tv4, c5); // 11. tv5 = tv4^c5
let isQR = F.eql(tv5, F.ONE); // 12. isQR = tv5 == 1
tv2 = F.mul(tv3, c7); // 13. tv2 = tv3 * c7
tv5 = F.mul(tv4, tv1); // 14. tv5 = tv4 * tv1
tv3 = F.cmov(tv2, tv3, isQR); // 15. tv3 = CMOV(tv2, tv3, isQR)
tv4 = F.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 = F.pow(tv4, tv5); // 20. tv5 = tv4^tv5
const e1 = F.eql(tvv5, F.ONE); // 21. e1 = tv5 == 1
tv2 = F.mul(tv3, tv1); // 22. tv2 = tv3 * tv1
tv1 = F.mul(tv1, tv1); // 23. tv1 = tv1 * tv1
tvv5 = F.mul(tv4, tv1); // 24. tv5 = tv4 * tv1
tv3 = F.cmov(tv2, tv3, e1); // 25. tv3 = CMOV(tv2, tv3, e1)
tv4 = F.cmov(tvv5, tv4, e1); // 26. tv4 = CMOV(tv5, tv4, e1)
}
// RFC 9380 Appendix F.2.1.1 defines sqrt_ratio(u, v) for v != 0.
// When u = 0 and v != 0, u / v = 0 is square and the computed root is
// still 0, so widen only the final flag and keep the full control flow.
return { isValid: !F.is0(v) && (isQR || F.is0(u)), value: tv3 };
};
if (F.ORDER % _4n === _3n) {
// sqrt_ratio_3mod4(u, v)
const c1 = (F.ORDER - _3n) / _4n; // 1. c1 = (q - 3) / 4 # Integer arithmetic
const c2 = F.sqrt(F.neg(Z)); // 2. c2 = sqrt(-Z)
sqrtRatio = (u, v) => {
let tv1 = F.sqr(v); // 1. tv1 = v^2
const tv2 = F.mul(u, v); // 2. tv2 = u * v
tv1 = F.mul(tv1, tv2); // 3. tv1 = tv1 * tv2
let y1 = F.pow(tv1, c1); // 4. y1 = tv1^c1
y1 = F.mul(y1, tv2); // 5. y1 = y1 * tv2
const y2 = F.mul(y1, c2); // 6. y2 = y1 * c2
const tv3 = F.mul(F.sqr(y1), v); // 7. tv3 = y1^2; 8. tv3 = tv3 * v
const isQR = F.eql(tv3, u); // 9. isQR = tv3 == u
let y = F.cmov(y2, y1, isQR); // 10. y = CMOV(y2, y1, isQR)
return { isValid: !F.is0(v) && 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
* See {@link https://www.rfc-editor.org/rfc/rfc9380#section-6.6.2 | RFC 9380 section 6.6.2}.
* @param Fp - Field implementation.
* @param opts - SWU parameters:
* - `A`: Curve parameter `A`.
* - `B`: Curve parameter `B`.
* - `Z`: Simplified SWU map parameter.
* @returns Deterministic map-to-curve function.
* @throws If the SWU parameters are invalid or the field lacks the required helpers. {@link Error}
* @example
* Map one field element to a Weierstrass curve point with the SWU recipe.
*
* ```ts
* import { mapToCurveSimpleSWU } from '@noble/curves/abstract/weierstrass.js';
* import { Field } from '@noble/curves/abstract/modular.js';
* const Fp = Field(17n);
* const map = mapToCurveSimpleSWU(Fp, { A: 1n, B: 2n, Z: 3n });
* const point = map(5n);
* ```
*/
export function mapToCurveSimpleSWU(Fp, opts) {
const F = validateField(Fp);
const { A, B, Z } = opts;
if (!F.isValidNot0(A) || !F.isValidNot0(B) || !F.isValid(Z))
throw new Error('mapToCurveSimpleSWU: invalid opts');
// RFC 9380 §6.6.2 and Appendix H.2 require:
// 1. Z is non-square in F
// 2. Z != -1 in F
// 3. g(x) - Z is irreducible over F
// 4. g(B / (Z * A)) is square in F
// We can enforce 1, 2, and 4 with the current field API.
// Criterion 3 is not checked here because generic `IField<T>` does not expose
// polynomial-ring / irreducibility operations, and this helper is used for
// both prime and extension fields.
if (F.eql(Z, F.neg(F.ONE)) || FpIsSquare(F, Z))
throw new Error('mapToCurveSimpleSWU: invalid opts');
// RFC 9380 Appendix H.2 criterion 4: g(B / (Z * A)) is square in F.
// x = B / (Z * A)
const x = F.mul(B, F.inv(F.mul(Z, A)));
// g(x) = x^3 + A*x + B
const gx = F.add(F.add(F.mul(F.sqr(x), x), F.mul(A, x)), B);
if (!FpIsSquare(F, gx))
throw new Error('mapToCurveSimpleSWU: invalid opts');
const sqrtRatio = SWUFpSqrtRatio(F, Z);
if (!F.isOdd)
throw new Error('Field does not have .isOdd()');
// 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 = F.sqr(u); // 1. tv1 = u^2
tv1 = F.mul(tv1, Z); // 2. tv1 = Z * tv1
tv2 = F.sqr(tv1); // 3. tv2 = tv1^2
tv2 = F.add(tv2, tv1); // 4. tv2 = tv2 + tv1
tv3 = F.add(tv2, F.ONE); // 5. tv3 = tv2 + 1
tv3 = F.mul(tv3, B); // 6. tv3 = B * tv3
tv4 = F.cmov(Z, F.neg(tv2), !F.eql(tv2, F.ZERO)); // 7. tv4 = CMOV(Z, -tv2, tv2 != 0)
tv4 = F.mul(tv4, A); // 8. tv4 = A * tv4
tv2 = F.sqr(tv3); // 9. tv2 = tv3^2
tv6 = F.sqr(tv4); // 10. tv6 = tv4^2
tv5 = F.mul(tv6, A); // 11. tv5 = A * tv6
tv2 = F.add(tv2, tv5); // 12. tv2 = tv2 + tv5
tv2 = F.mul(tv2, tv3); // 13. tv2 = tv2 * tv3
tv6 = F.mul(tv6, tv4); // 14. tv6 = tv6 * tv4
tv5 = F.mul(tv6, B); // 15. tv5 = B * tv6
tv2 = F.add(tv2, tv5); // 16. tv2 = tv2 + tv5
x = F.mul(tv1, tv3); // 17. x = tv1 * tv3
const { isValid, value } = sqrtRatio(tv2, tv6); // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6)
y = F.mul(tv1, u); // 19. y = tv1 * u -> Z * u^3 * y1
y = F.mul(y, value); // 20. y = y * y1
x = F.cmov(x, tv3, isValid); // 21. x = CMOV(x, tv3, is_gx1_square)
y = F.cmov(y, value, isValid); // 22. y = CMOV(y, y1, is_gx1_square)
const e1 = F.isOdd(u) === F.isOdd(y); // 23. e1 = sgn0(u) == sgn0(y)
y = F.cmov(F.neg(y), y, e1); // 24. y = CMOV(-y, y, e1)
const tv4_inv = FpInvertBatch(F, [tv4], true)[0];
x = F.mul(x, tv4_inv); // 25. x = x / tv4
return { x, y };
};
}
function getWLengths(Fp, Fn) {
return {
secretKey: Fn.BYTES,
publicKey: 1 + Fp.BYTES,
publicKeyUncompressed: 1 + 2 * Fp.BYTES,
publicKeyHasPrefix: true,
// Raw compact `(r || s)` signature width; DER and recovered signatures use
// different lengths outside this helper.
signature: 2 * Fn.BYTES,
};
}
/**
* Sometimes users only need getPublicKey, getSharedSecret, and secret key handling.
* This helper ensures no signature functionality is present. Less code, smaller bundle size.
* @param Point - Weierstrass point constructor.
* @param ecdhOpts - Optional randomness helpers:
* - `randomBytes` (optional): Optional RNG override.
* @returns ECDH helper namespace.
* @example
* Sometimes users only need getPublicKey, getSharedSecret, and secret key handling.
*
* ```ts
* import { ecdh } from '@noble/curves/abstract/weierstrass.js';
* import { p256 } from '@noble/curves/nist.js';
* const dh = ecdh(p256.Point);
* const alice = dh.keygen();
* const shared = dh.getSharedSecret(alice.secretKey, alice.publicKey);
* ```
*/
export function ecdh(Point, ecdhOpts = {}) {
const { Fn } = Point;
const randomBytes_ = ecdhOpts.randomBytes === undefined ? wcRandomBytes : ecdhOpts.randomBytes;
// Keep the advertised seed length aligned with mapHashToField(), which keeps a hard 16-byte
// minimum even on toy curves.
const lengths = Object.assign(getWLengths(Point.Fp, Fn), {
seed: Math.max(getMinHashLength(Fn.ORDER), 16),
});
function isValidSecretKey(secretKey) {
try {
const num = Fn.fromBytes(secretKey);
return Fn.isValidNot0(num);
}
catch (error) {
return false;
}
}
function isValidPublicKey(publicKey, isCompressed) {
const { publicKey: comp, publicKeyUncompressed } = lengths;
try {
const l = publicKey.length;
if (isCompressed === true && l !== comp)
return false;
if (isCompressed === false && l !== publicKeyUncompressed)
return false;
return !!Point.fromBytes(publicKey);
}
catch (error) {
return false;
}
}
/**
* Produces cryptographically secure secret key from random of size
* (groupLen + ceil(groupLen / 2)) with modulo bias being negligible.
*/
function randomSecretKey(seed) {
seed = seed === undefined ? randomBytes_(lengths.seed) : seed;
return mapHashToField(abytes(seed, lengths.seed, 'seed'), Fn.ORDER);
}
/**
* Computes public key for a secret key. Checks for validity of the secret key.
* @param isCompressed - whether to return compact (default), or full key
* @returns Public key, full when isCompressed=false; short when isCompressed=true
*/
function getPublicKey(secretKey, isCompressed = true) {
return Point.BASE.multiply(Fn.fromBytes(secretKey)).toBytes(isCompressed);
}
/**
* Quick and dirty check for item being public key. Does not validate hex, or being on-curve.
*/
function isProbPub(item) {
const { secretKey, publicKey, publicKeyUncompressed } = lengths;
const allowedLengths = Fn._lengths;
if (!isBytes(item))
return undefined;
const l = abytes(item, undefined, 'key').length;
const isPub = l === publicKey || l === publicKeyUncompressed;
const isSec = l === secretKey || !!allowedLengths?.includes(l);
// P-521 accepts both 65- and 66-byte secret keys, so overlapping lengths stay ambiguous.
if (isPub && isSec)
return undefined;
return isPub;
}
/**
* ECDH (Elliptic Curve Diffie Hellman).
* Computes encoded shared point from secret key A and public key B.
* Checks: 1) secret key validity 2) shared key is on-curve.
* Does NOT hash the result or expose the SEC 1 x-coordinate-only `z`.
* Returns the encoded shared point on purpose: callers that need `x_P`
* can derive it from the encoded point, but `x_P` alone cannot recover the
* point/parity back.
* This helper only exposes the fully validated public-key path, not cofactor DH.
* @param isCompressed - whether to return compact (default), or full key
* @returns shared point encoding
*/
function getSharedSecret(secretKeyA, publicKeyB, isCompressed = true) {
if (isProbPub(secretKeyA) === true)
throw new Error('first arg must be private key');
if (isProbPub(publicKeyB) === false)
throw new Error('second arg must be public key');
const s = Fn.fromBytes(secretKeyA);
const b = Point.fromBytes(publicKeyB); // checks for being on-curve
return b.multiply(s).toBytes(isCompressed);
}
const utils = {
isValidSecretKey,
isValidPublicKey,
randomSecretKey,
};
const keygen = createKeygen(randomSecretKey, getPublicKey);
Object.freeze(utils);
Object.freeze(lengths);
return Object.freeze({ getPublicKey, getSharedSecret, keygen, Point, utils, lengths });
}
/**
* Creates ECDSA signing interface for given elliptic curve `Point` and `hash` function.
*
* @param Point - created using {@link weierstrass} function
* @param hash - used for 1) message prehash-ing 2) k generation in `sign`, using hmac_drbg(hash)
* @param ecdsaOpts - rarely needed, see {@link ECDSAOpts}:
* - `lowS`: Default low-S policy.
* - `hmac`: HMAC implementation used by RFC6979 DRBG.
* - `randomBytes`: Optional RNG override.
* - `bits2int`: Optional hash-to-int conversion override.
* - `bits2int_modN`: Optional hash-to-int-mod-n conversion override.
*
* @returns ECDSA helper namespace.
* @example
* Create an ECDSA signer/verifier bundle for one curve implementation.
*
* ```ts
* import { ecdsa } from '@noble/curves/abstract/weierstrass.js';
* import { p256 } from '@noble/curves/nist.js';
* import { sha256 } from '@noble/hashes/sha2.js';
* const p256ecdsa = ecdsa(p256.Point, sha256);
* const { secretKey, publicKey } = p256ecdsa.keygen();
* const msg = new TextEncoder().encode('hello noble');
* const sig = p256ecdsa.sign(msg, secretKey);
* const isValid = p256ecdsa.verify(sig, msg, publicKey);
* ```
*/
export function ecdsa(Point, hash, ecdsaOpts = {}) {
// Custom hash / bits2int hooks are treated as pure functions over validated caller-owned bytes.
const hash_ = hash;
ahash(hash_);
validateObject(ecdsaOpts, {}, {
hmac: 'function',
lowS: 'boolean',
randomBytes: 'function',
bits2int: 'function',
bits2int_modN: 'function',
});
ecdsaOpts = Object.assign({}, ecdsaOpts);
const randomBytes = ecdsaOpts.randomBytes === undefined ? wcRandomBytes : ecdsaOpts.randomBytes;
const hmac = ecdsaOpts.hmac === undefined
? (key, msg) => nobleHmac(hash_, key, msg)
: ecdsaOpts.hmac;
const { Fp, Fn } = Point;
const { ORDER: CURVE_ORDER, BITS: fnBits } = Fn;
const { keygen, getPublicKey, getSharedSecret, utils, lengths } = ecdh(Point, ecdsaOpts);
const defaultSigOpts = {
prehash: true,
lowS: typeof ecdsaOpts.lowS === 'boolean' ? ecdsaOpts.lowS : true,
format: 'compact',
extraEntropy: false,
};
// SEC 1 4.1.6 public-key recovery tries x = r + jn for j = 0..h. Our recovered-signature
// format only stores one overflow bit, so it can only distinguish q.x = r from q.x = r + n.
// A third lift would have the form q.x = r + 2n. Since valid ECDSA r is in 1..n-1, the
// smallest such lift is 1 + 2n, not 2n.
const hasLargeR