@noble/curves
Version:
Audited & minimal JS implementation of elliptic curve cryptography
1,287 lines (1,230 loc) • 78.8 kB
text/typescript
/**
* 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,
type CHash,
type HmacFn,
type Signer,
type TArg,
type TRet,
} from '../utils.ts';
import {
createCurveFields,
createKeygen,
mulEndoUnsafe,
negateCt,
normalizeZ,
wNAF,
type AffinePoint,
type CurveLengths,
type CurvePoint,
type CurvePointCons,
} from './curve.ts';
import {
FpInvertBatch,
FpIsSquare,
getMinHashLength,
mapHashToField,
validateField,
type IField,
} from './modular.ts';
/** Shared affine point shape used by Weierstrass helpers. */
export type { AffinePoint };
type EndoBasis = [[bigint, bigint], [bigint, bigint]];
/**
* When Weierstrass curve has `a=0`, it becomes Koblitz curve.
* Koblitz curves allow using **efficiently-computable GLV endomorphism ψ**.
* Endomorphism uses 2x less RAM, speeds up precomputation by 2x and ECDH / key recovery by 20%.
* For precomputed wNAF it trades off 1/2 init time & 1/3 ram for 20% perf hit.
*
* Endomorphism consists of beta, lambda and splitScalar:
*
* 1. GLV endomorphism ψ transforms a point: `P = (x, y) ↦ ψ(P) = (β·x mod p, y)`
* 2. GLV scalar decomposition transforms a scalar: `k ≡ k₁ + k₂·λ (mod n)`
* 3. Then these are combined: `k·P = k₁·P + k₂·ψ(P)`
* 4. Two 128-bit point-by-scalar multiplications + one point addition is faster than
* one 256-bit multiplication.
*
* where
* * beta: β ∈ Fₚ with β³ = 1, β ≠ 1
* * lambda: λ ∈ Fₙ with λ³ = 1, λ ≠ 1
* * splitScalar decomposes k ↦ k₁, k₂, by using reduced basis vectors.
* Gauss lattice reduction calculates them from initial basis vectors `(n, 0), (-λ, 0)`
*
* Check out `test/misc/endomorphism.js` and
* {@link https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066 | this endomorphism gist}.
*/
export type EndomorphismOpts = {
/** Cube root of unity used by the GLV endomorphism. */
beta: bigint;
/** Reduced lattice basis used for scalar splitting. */
basises?: EndoBasis;
/**
* Optional custom scalar-splitting helper.
* Receives one scalar and returns two half-sized scalar components.
*/
splitScalar?: (k: bigint) => { k1neg: boolean; k1: bigint; k2neg: boolean; k2: bigint };
};
// 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: bigint, den: bigint) => (num + (num >= 0 ? den : -den) / _2n) / den;
/** Two half-sized scalar components returned by endomorphism splitting. */
export type ScalarEndoParts = {
/** Whether the first split scalar should be negated. */
k1neg: boolean;
/** Absolute value of the first split scalar. */
k1: bigint;
/** Whether the second split scalar should be negated. */
k2neg: boolean;
/** Absolute value of the second split scalar. */
k2: bigint;
};
/** Splits scalar for GLV endomorphism. */
export function _splitEndoScalar(k: bigint, basis: EndoBasis, n: bigint): ScalarEndoParts {
// 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 };
}
/**
* Option to enable hedged signatures with improved security.
*
* * Randomly generated k is bad, because broken CSPRNG would leak private keys.
* * Deterministic k (RFC6979) is better; but is suspectible to fault attacks.
*
* We allow using technique described in RFC6979 3.6: additional k', a.k.a. adding randomness
* to deterministic sig. If CSPRNG is broken & randomness is weak, it would STILL be as secure
* as ordinary sig without ExtraEntropy.
*
* * `true` means "fetch data, from CSPRNG, incorporate it into k generation"
* * `false` means "disable extra entropy, use purely deterministic k"
* * `Uint8Array` passed means "incorporate following data into k generation"
*
* See {@link https://paulmillr.com/posts/deterministic-signatures/ | deterministic signatures}.
*/
export type ECDSAExtraEntropy = boolean | Uint8Array;
/**
* - `compact` is the default format
* - `recovered` is the same as compact, but with an extra byte indicating recovery byte
* - `der` is ASN.1 DER encoding
*/
export type ECDSASignatureFormat = 'compact' | 'recovered' | 'der';
/**
* - `prehash`: (default: true) indicates whether to do sha256(message).
* When a custom hash is used, it must be set to `false`.
*/
export type ECDSARecoverOpts = {
/** Whether to hash the message before signature recovery. */
prehash?: boolean;
};
/**
* - `prehash`: (default: true) indicates whether to do sha256(message).
* When a custom hash is used, it must be set to `false`.
* - `lowS`: (default: true) prohibits signatures with `sig.s >= CURVE.n/2n`.
* Compatible with BTC/ETH. Setting `lowS: false` allows to create malleable signatures,
* which is default openssl behavior.
* Non-malleable signatures can still be successfully verified in openssl.
* - `format`: (default: 'compact') 'compact' or 'recovered' with recovery byte
*/
export type ECDSAVerifyOpts = {
/** Whether to hash the message before verification. */
prehash?: boolean;
/** Whether to reject high-S signatures. */
lowS?: boolean;
/** Signature encoding to accept. */
format?: ECDSASignatureFormat;
};
/**
* - `prehash`: (default: true) indicates whether to do sha256(message).
* When a custom hash is used, it must be set to `false`.
* - `lowS`: (default: true) prohibits signatures with `sig.s >= CURVE.n/2n`.
* Compatible with BTC/ETH. Setting `lowS: false` allows to create malleable signatures,
* which is default openssl behavior.
* Non-malleable signatures can still be successfully verified in openssl.
* - `format`: (default: 'compact') 'compact' or 'recovered' with recovery byte
* - `extraEntropy`: (default: false) creates signatures with increased
* security, see {@link ECDSAExtraEntropy}
*/
export type ECDSASignOpts = {
/** Whether to hash the message before signing. */
prehash?: boolean;
/** Whether to normalize signatures into the low-S half-order. */
lowS?: boolean;
/** Signature encoding to produce. */
format?: ECDSASignatureFormat;
/** Optional hedging input for deterministic k generation. */
extraEntropy?: ECDSAExtraEntropy;
};
function validateSigFormat(format: string): ECDSASignatureFormat {
if (!['compact', 'recovered', 'der'].includes(format))
throw new Error('Signature format must be "compact", "recovered", or "der"');
return format as ECDSASignatureFormat;
}
function validateSigOpts<T extends ECDSASignOpts, D extends Required<ECDSASignOpts>>(
opts: T,
def: D
): D {
validateObject(opts);
const optsn = {} as D;
// 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) as (keyof D)[]) {
// @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;
}
/** Projective XYZ point used by short Weierstrass curves. */
export interface WeierstrassPoint<T> extends CurvePoint<T, WeierstrassPoint<T>> {
/** projective X coordinate. Different from affine x. */
readonly X: T;
/** projective Y coordinate. Different from affine y. */
readonly Y: T;
/** projective z coordinate */
readonly Z: T;
/** affine x coordinate. Different from projective X. */
get x(): T;
/** affine y coordinate. Different from projective Y. */
get y(): T;
/**
* Encode the point into compressed or uncompressed SEC1 bytes.
* @param isCompressed - Whether to use the compressed form.
* @returns Encoded point bytes.
*/
toBytes(isCompressed?: boolean): TRet<Uint8Array>;
/**
* Encode the point into compressed or uncompressed SEC1 hex.
* @param isCompressed - Whether to use the compressed form.
* @returns Encoded point hex.
*/
toHex(isCompressed?: boolean): string;
}
/** Constructor and metadata helpers for Weierstrass points. */
export interface WeierstrassPointCons<T> extends CurvePointCons<WeierstrassPoint<T>> {
/** Does NOT validate if the point is valid. Use `.assertValidity()`. */
new (X: T, Y: T, Z: T): WeierstrassPoint<T>;
/**
* Return the curve parameters captured by this point constructor.
* @returns Curve parameters.
*/
CURVE(): WeierstrassOpts<T>;
}
/**
* Weierstrass curve options.
*
* * p: prime characteristic (order) of finite field, in which arithmetics is done
* * n: order of prime subgroup a.k.a total amount of valid curve points
* * h: cofactor, usually 1. h*n is group order; n is subgroup order
* * a: formula param, must be in field of p
* * b: formula param, must be in field of p
* * Gx: x coordinate of generator point a.k.a. base point
* * Gy: y coordinate of generator point
*/
export type WeierstrassOpts<T> = Readonly<{
/** Base-field modulus. */
p: bigint;
/** Prime subgroup order. */
n: bigint;
/** Curve cofactor. */
h: bigint;
/** Weierstrass curve parameter `a`. */
a: T;
/** Weierstrass curve parameter `b`. */
b: T;
/** Generator x coordinate. */
Gx: T;
/** Generator y coordinate. */
Gy: T;
}>;
/**
* Optional helpers and overrides for a Weierstrass point constructor.
*
* When a cofactor != 1, there can be effective methods to:
* 1. Determine whether a point is torsion-free
* 2. Clear torsion component
*/
export type WeierstrassExtraOpts<T> = Partial<{
/** Optional base-field override. */
Fp: IField<T>;
/** Optional scalar-field override. */
Fn: IField<bigint>;
/** Whether the point constructor accepts infinity points. */
allowInfinityPoint: boolean;
/** Optional GLV endomorphism data. */
endo: EndomorphismOpts;
/** Optional torsion-check override. */
isTorsionFree: (c: WeierstrassPointCons<T>, point: WeierstrassPoint<T>) => boolean;
/** Optional cofactor-clearing override. */
clearCofactor: (c: WeierstrassPointCons<T>, point: WeierstrassPoint<T>) => WeierstrassPoint<T>;
/** Optional custom point decoder. */
fromBytes: (bytes: TArg<Uint8Array>) => AffinePoint<T>;
/** Optional custom point encoder. */
toBytes: (
c: WeierstrassPointCons<T>,
point: WeierstrassPoint<T>,
isCompressed: boolean
) => TRet<Uint8Array>;
}>;
/**
* Options for ECDSA signatures over a Weierstrass curve.
*
* * lowS: (default: true) whether produced or verified signatures occupy the
* low half of `ecdsaOpts.n`. Prevents malleability.
* * hmac: (default: noble-hashes hmac) function, would be used to init hmac-drbg for k generation.
* * randomBytes: (default: webcrypto os-level CSPRNG) custom method for fetching secure randomness.
* * bits2int, bits2int_modN: used in sigs, sometimes overridden by curves. Custom hooks are
* treated as pure functions over validated bytes and MUST NOT mutate caller-owned buffers or
* closure-captured option bags. `bits2int_modN` must also return a canonical scalar in
* `[0..Point.Fn.ORDER-1]`.
*/
export type ECDSAOpts = Partial<{
/** Default low-S policy for this ECDSA instance. */
lowS: boolean;
/** HMAC implementation used by RFC6979 DRBG. */
hmac: HmacFn;
/** RNG override used by helper constructors. */
randomBytes: (bytesLength?: number) => TRet<Uint8Array>;
/** Hash-to-integer conversion override. */
bits2int: (bytes: TArg<Uint8Array>) => bigint;
/** Hash-to-integer-mod-n conversion override. Returns a canonical scalar in `[0..Fn.ORDER-1]`. */
bits2int_modN: (bytes: TArg<Uint8Array>) => bigint;
}>;
/** Elliptic Curve Diffie-Hellman helper namespace. */
export interface ECDH {
/**
* Generate a secret/public key pair.
* @param seed - Optional seed material.
* @returns Secret/public key pair.
*/
keygen: (seed?: TArg<Uint8Array>) => { secretKey: TRet<Uint8Array>; publicKey: TRet<Uint8Array> };
/**
* Derive the public key from a secret key.
* @param secretKey - Secret key bytes.
* @param isCompressed - Whether to emit compressed SEC1 bytes.
* @returns Encoded public key.
*/
getPublicKey: (secretKey: TArg<Uint8Array>, isCompressed?: boolean) => TRet<Uint8Array>;
/**
* Compute the shared secret point from a secret key and peer public key.
* @param secretKeyA - Local secret key bytes.
* @param publicKeyB - Peer public key bytes.
* @param isCompressed - Whether to emit compressed SEC1 bytes.
* @returns Encoded shared point.
*/
getSharedSecret: (
secretKeyA: TArg<Uint8Array>,
publicKeyB: TArg<Uint8Array>,
isCompressed?: boolean
) => TRet<Uint8Array>;
/** Point constructor used by this ECDH instance. */
Point: WeierstrassPointCons<bigint>;
/** Validation and random-key helpers. */
utils: {
/** Check whether a secret key has the expected encoding. */
isValidSecretKey: (secretKey: TArg<Uint8Array>) => boolean;
/** Check whether a public key decodes to a valid point. */
isValidPublicKey: (publicKey: TArg<Uint8Array>, isCompressed?: boolean) => boolean;
/** Generate a valid random secret key. */
randomSecretKey: (seed?: TArg<Uint8Array>) => TRet<Uint8Array>;
};
/** Byte lengths for keys and signatures exposed by this curve. */
lengths: CurveLengths;
}
/**
* ECDSA interface.
* Only supported for prime fields, not Fp2 (extension fields).
*/
export interface ECDSA extends ECDH {
/**
* Sign a message with the given secret key.
* @param message - Message bytes.
* @param secretKey - Secret key bytes.
* @param opts - Optional signing tweaks. See {@link ECDSASignOpts}.
* @returns Encoded signature bytes.
*/
sign: (
message: TArg<Uint8Array>,
secretKey: TArg<Uint8Array>,
opts?: TArg<ECDSASignOpts>
) => TRet<Uint8Array>;
/**
* Verify a signature against a message and public key.
* @param signature - Encoded signature bytes.
* @param message - Message bytes.
* @param publicKey - Encoded public key.
* @param opts - Optional verification tweaks. See {@link ECDSAVerifyOpts}.
* @returns Whether the signature is valid.
*/
verify: (
signature: TArg<Uint8Array>,
message: TArg<Uint8Array>,
publicKey: TArg<Uint8Array>,
opts?: TArg<ECDSAVerifyOpts>
) => boolean;
/**
* Recover the public key encoded into a recoverable signature.
* @param signature - Recoverable signature bytes.
* @param message - Message bytes.
* @param opts - Optional recovery tweaks. See {@link ECDSARecoverOpts}.
* @returns Encoded recovered public key.
*/
recoverPublicKey(
signature: TArg<Uint8Array>,
message: TArg<Uint8Array>,
opts?: TArg<ECDSARecoverOpts>
): TRet<Uint8Array>;
/** Signature constructor and parser helpers. */
Signature: ECDSASignatureCons;
}
/**
* @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);
}
}
/** DER helper namespace used by ECDSA signature parsing and encoding. */
export type IDER = {
// asn.1 DER encoding utils
/**
* DER-specific error constructor.
* @param m - Error message.
* @returns DER-specific error instance.
*/
Err: typeof DERErr;
// Basic building block is TLV (Tag-Length-Value)
/** Low-level tag-length-value helpers used by DER encoders. */
_tlv: {
/**
* Encode one TLV record.
* @param tag - ASN.1 tag byte.
* @param data - Hex-encoded value payload.
* @returns Encoded TLV string.
*/
encode: (tag: number, data: string) => string;
// v - value, l - left bytes (unparsed)
/**
* Decode one TLV record and return the value plus leftover bytes.
* @param tag - Expected ASN.1 tag byte.
* @param data - Remaining DER bytes.
* @returns Parsed value plus leftover bytes.
*/
decode(tag: number, data: TArg<Uint8Array>): TRet<{ v: Uint8Array; l: Uint8Array }>;
};
// 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)
/** Positive-integer DER helpers used by ECDSA signature encoding. */
_int: {
/**
* Encode one positive bigint as a DER INTEGER.
* @param num - Positive integer to encode.
* @returns Encoded DER INTEGER.
*/
encode(num: bigint): string;
/**
* Decode one DER INTEGER into a bigint.
* @param data - DER INTEGER bytes.
* @returns Decoded bigint.
*/
decode(data: TArg<Uint8Array>): bigint;
};
/**
* Parse a DER signature into `{ r, s }`.
* @param bytes - DER signature bytes.
* @returns Parsed signature components.
*/
toSig(bytes: TArg<Uint8Array>): { r: bigint; s: bigint };
/**
* Encode `{ r, s }` as a DER signature.
* @param sig - Signature components.
* @returns DER-encoded signature hex.
*/
hexFromSig(sig: { r: bigint; s: bigint }): string;
};
/**
* 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: IDER = {
// asn.1 DER encoding utils
Err: DERErr,
// Basic building block is TLV (Tag-Length-Value)
_tlv: {
encode: (tag: number, data: string): string => {
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: number, data: TArg<Uint8Array>): TRet<{ v: Uint8Array; l: Uint8Array }> {
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) } as TRet<{ v: Uint8Array; l: Uint8Array }>;
},
},
// 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: bigint): string {
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: TArg<Uint8Array>): bigint {
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: TArg<Uint8Array>): { r: bigint; s: bigint } {
// 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: { r: bigint; s: bigint }): string {
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<T>(
params: WeierstrassOpts<T>,
extraOpts: WeierstrassExtraOpts<T> = {}
): WeierstrassPointCons<T> {
const validated = createCurveFields('weierstrass', params, extraOpts);
const Fp = validated.Fp as IField<T>;
const Fn = validated.Fn as IField<bigint>;
let CURVE = validated.CURVE as WeierstrassOpts<T>;
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 as TArg<IField<T>>, 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: WeierstrassPointCons<T>,
point: WeierstrassPoint<T>,
isCompressed: boolean
): TRet<Uint8Array> {
// 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) as TRet<Uint8Array>;
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) as TRet<Uint8Array>;
} else {
return concatBytes(Uint8Array.of(0x04), bx, Fp.toBytes(y)) as TRet<Uint8Array>;
}
}
function pointFromBytes(bytes: TArg<Uint8Array>) {
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: T;
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: T): T {
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: T, y: T): boolean {
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: string, n: T, banZero = false) {
if (!Fp.isValid(n) || (banZero && Fp.is0(n))) throw new Error(`bad point coordinate ${title}`);
return n;
}
function aprjpoint(other: unknown): asserts other is Point {
if (!(other instanceof Point)) throw new Error('Weierstrass Point expected');
}
function splitEndoScalarN(k: bigint) {
if (!endo || !endo.basises) throw new Error('no endo');
return _splitEndoScalar(k, endo.basises, Fn.ORDER);
}
function finishEndo(
endoBeta: EndomorphismOpts['beta'],
k1p: Point,
k2p: Point,
k1neg: boolean,
k2neg: boolean
) {
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 implements WeierstrassPoint<T> {
// base / generator point
static readonly BASE = new Point(CURVE.Gx, CURVE.Gy, Fp.ONE);
// zero / infinity / identity point
static readonly ZERO = new Point(Fp.ZERO, Fp.ONE, Fp.ZERO); // 0, 1, 0
// math field
static readonly Fp = Fp;
// scalar field
static readonly Fn = Fn;
readonly X: T;
readonly Y: T;
readonly Z: T;
/** Does NOT validate if the point is valid. Use `.assertValidity()`. */
constructor(X: T, Y: T, Z: T) {
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(): WeierstrassOpts<T> {
return CURVE;
}
/** Does NOT validate if the point is valid. Use `.assertValidity()`. */
static fromAffine(p: AffinePoint<T>): Point {
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: TArg<Uint8Array>): Point {
const P = Point.fromAffine(decodePoint(abytes(bytes, undefined, 'point')));
P.assertValidity();
return P;
}
static fromHex(hex: string): Point {
return Point.fromBytes(hexToBytes(hex));
}
get x(): T {
return this.toAffine().x;
}
get y(): T {
return this.toAffine().y;
}
/**
*
* @param windowSize
* @param isLazy - true will defer table computation until the first multiplication
* @returns
*/
precompute(windowSize: number = 8, isLazy = true): Point {
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(): void {
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(): boolean {
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: WeierstrassPoint<T>): boolean {
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(): Point {
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: WeierstrassPoint<T>): Point {
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: WeierstrassPoint<T>) {
// 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(): boolean {
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: bigint): Point {
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: Point, fake: Point; // Fake point is used to const-time mult
const mul = (n: bigint) => 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: bigint): Point {
const { endo } = extraOpts;
const p = this as Point;
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?: T): AffinePoint<T> {
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(): boolean {
const { isTorsionFree } = extraOpts;
if (cofactor === _1n) return true;
if (isTorsionFree) return isTorsionFree(Point, this);
return wnaf.unsafe(this, CURVE_ORDER).is0();
}
clearCofactor(): Point {
const { clearCofactor } = extraOpts;
if (cofactor === _1n) return this; // Fast-path
if (clearCofactor) return clearCofactor(Point, this) as Point;
// 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(): boolean {
if (cofactor === _1n) return this.is0(); // Fast-path
return this.clearCofactor().is0();
}
toBytes(isCompressed = true): TRet<Uint8Array> {
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): string {
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;
}
/** Parsed ECDSA signature with helpers for recovery and re-encoding. */
export interface ECDSASignature {
/** Signature component `r`. */
readonly r: bigint;
/** Signature component `s`. */
readonly s: bigint;
/** Optional recovery bit for recoverable signatures. */
readonly recovery?: number;
/**
* Return a copy of the signature with a recovery bit attached.
* @param recovery - Recovery bit to attach.
* @returns Signature with an attached recovery bit.
*/
addRecoveryBit(recovery: number): ECDSASignature & { readonly recovery: number };
/**
* Check whether the signature uses the high-S half-order.
* @returns Whether the signature uses the high-S half-order.
*/
hasHighS(): boolean;
/**
* Recover the public key from the hashed message and recovery bit.
* @param messageHash - Hashed message bytes.
* @returns Recovered public-key point.
*/
recoverPublicKey(messageHash: TArg<Uint8Array>): WeierstrassPoint<bigint>;
/**
* Encode the signature into bytes.
* @param format - Signature encoding to produce.
* @returns Encoded signature bytes.
*/
toBytes(format?: string): TRet<Uint8Array>;
/**
* Encode the signature into hex.
* @param format - Signature encoding to produce.
* @returns Encoded signature hex.
*/
toHex(format?: string): string;
}
/** Constructor and decoding helpers for ECDSA signatures. */
export type ECDSASignatureCons = {
/** Create a signature from `r`, `s`, and an optional recovery bit. */
new (r: bigint, s: bigint, recovery?: number): ECDSASignature;
/**
* Decode a signature from bytes.
* @param bytes - Encoded signature bytes.
* @param format - Signature encoding to parse.
* @returns Parsed signature.
*/
fromBytes(bytes: TArg<Uint8Array>, format?: ECDSASignatureFormat): ECDSASignature;
/**
* Decode a signature from hex.
* @param hex - Encoded signature hex.
* @param format - Signature encoding to parse.
* @returns Parsed signature.
*/
fromHex(hex: string, format?: ECDSASignatureFormat): ECDSASignature;
};
// Points start with byte 0x02 when y is even; otherwise 0x03
function pprefix(hasEvenY: boolean): TRet<Uint8Array> {
return Uint8Array.of(hasEvenY ? 0x02 : 0x03) as TRet<Uint8Array>;
}
/**
* 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<T>(
Fp: TArg<IField<T>>,
Z: T
): (u: T, v: T) => { isValid: boolean; value: T } {
// Fail with the usual field-shape error before touching pow/cmov on malformed field shims.
const F = validateField(Fp as IField<T>) as IField<T>;
// 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