Fp rename. Edwards refactor. Weierstrass Fn instead of mod

This commit is contained in:
Paul Miller 2023-01-26 02:07:45 +00:00
parent 3d77422731
commit be0b2a32a5
No known key found for this signature in database
GPG Key ID: 697079DA6878B89B
14 changed files with 347 additions and 416 deletions

@ -458,6 +458,25 @@ verify
noble x 698 ops/sec @ 1ms/op noble x 698 ops/sec @ 1ms/op
``` ```
## Upgrading
Differences from @noble/secp256k1 1.7:
1. Different double() formula (but same addition)
2. Different sqrt() function
3. DRBG supports outputLen bigger than outputLen of hmac
4. Support for different hash functions
Differences from @noble/ed25519 1.7:
1. Variable field element lengths between EDDSA/ECDH:
EDDSA (RFC8032) is 456 bits / 57 bytes, ECDH (RFC7748) is 448 bits / 56 bytes
2. Different addition formula (doubling is same)
3. uvRatio differs between curves (half-expected, not only pow fn changes)
4. Point decompression code is different (unexpected), now using generalized formula
5. Domain function was no-op for ed25519, but adds some data even with empty context for ed448
## Contributing & testing ## Contributing & testing
1. Clone the repository 1. Clone the repository

@ -129,18 +129,18 @@ export function bls<Fp2, Fp6, Fp12>(
let ell_coeff: [Fp2, Fp2, Fp2][] = []; let ell_coeff: [Fp2, Fp2, Fp2][] = [];
for (let i = BLS_X_LEN - 2; i >= 0; i--) { for (let i = BLS_X_LEN - 2; i >= 0; i--) {
// Double // Double
let t0 = Fp2.square(Ry); // Ry² let t0 = Fp2.sqr(Ry); // Ry²
let t1 = Fp2.square(Rz); // Rz² let t1 = Fp2.sqr(Rz); // Rz²
let t2 = Fp2.multiplyByB(Fp2.mul(t1, 3n)); // 3 * T1 * B let t2 = Fp2.multiplyByB(Fp2.mul(t1, 3n)); // 3 * T1 * B
let t3 = Fp2.mul(t2, 3n); // 3 * T2 let t3 = Fp2.mul(t2, 3n); // 3 * T2
let t4 = Fp2.sub(Fp2.sub(Fp2.square(Fp2.add(Ry, Rz)), t1), t0); // (Ry + Rz)² - T1 - T0 let t4 = Fp2.sub(Fp2.sub(Fp2.sqr(Fp2.add(Ry, Rz)), t1), t0); // (Ry + Rz)² - T1 - T0
ell_coeff.push([ ell_coeff.push([
Fp2.sub(t2, t0), // T2 - T0 Fp2.sub(t2, t0), // T2 - T0
Fp2.mul(Fp2.square(Rx), 3n), // 3 * Rx² Fp2.mul(Fp2.sqr(Rx), 3n), // 3 * Rx²
Fp2.negate(t4), // -T4 Fp2.neg(t4), // -T4
]); ]);
Rx = Fp2.div(Fp2.mul(Fp2.mul(Fp2.sub(t0, t3), Rx), Ry), 2n); // ((T0 - T3) * Rx * Ry) / 2 Rx = Fp2.div(Fp2.mul(Fp2.mul(Fp2.sub(t0, t3), Rx), Ry), 2n); // ((T0 - T3) * Rx * Ry) / 2
Ry = Fp2.sub(Fp2.square(Fp2.div(Fp2.add(t0, t3), 2n)), Fp2.mul(Fp2.square(t2), 3n)); // ((T0 + T3) / 2)² - 3 * T2² Ry = Fp2.sub(Fp2.sqr(Fp2.div(Fp2.add(t0, t3), 2n)), Fp2.mul(Fp2.sqr(t2), 3n)); // ((T0 + T3) / 2)² - 3 * T2²
Rz = Fp2.mul(t0, t4); // T0 * T4 Rz = Fp2.mul(t0, t4); // T0 * T4
if (ut.bitGet(CURVE.x, i)) { if (ut.bitGet(CURVE.x, i)) {
// Addition // Addition
@ -148,13 +148,13 @@ export function bls<Fp2, Fp6, Fp12>(
let t1 = Fp2.sub(Rx, Fp2.mul(Qx, Rz)); // Rx - Qx * Rz let t1 = Fp2.sub(Rx, Fp2.mul(Qx, Rz)); // Rx - Qx * Rz
ell_coeff.push([ ell_coeff.push([
Fp2.sub(Fp2.mul(t0, Qx), Fp2.mul(t1, Qy)), // T0 * Qx - T1 * Qy Fp2.sub(Fp2.mul(t0, Qx), Fp2.mul(t1, Qy)), // T0 * Qx - T1 * Qy
Fp2.negate(t0), // -T0 Fp2.neg(t0), // -T0
t1, // T1 t1, // T1
]); ]);
let t2 = Fp2.square(t1); // T1² let t2 = Fp2.sqr(t1); // T1²
let t3 = Fp2.mul(t2, t1); // T2 * T1 let t3 = Fp2.mul(t2, t1); // T2 * T1
let t4 = Fp2.mul(t2, Rx); // T2 * Rx let t4 = Fp2.mul(t2, Rx); // T2 * Rx
let t5 = Fp2.add(Fp2.sub(t3, Fp2.mul(t4, 2n)), Fp2.mul(Fp2.square(t0), Rz)); // T3 - 2 * T4 + T0² * Rz let t5 = Fp2.add(Fp2.sub(t3, Fp2.mul(t4, 2n)), Fp2.mul(Fp2.sqr(t0), Rz)); // T3 - 2 * T4 + T0² * Rz
Rx = Fp2.mul(t1, t5); // T1 * T5 Rx = Fp2.mul(t1, t5); // T1 * T5
Ry = Fp2.sub(Fp2.mul(Fp2.sub(t4, t5), t0), Fp2.mul(t3, Ry)); // (T4 - T5) * T0 - T3 * Ry Ry = Fp2.sub(Fp2.mul(Fp2.sub(t4, t5), t0), Fp2.mul(t3, Ry)); // (T4 - T5) * T0 - T3 * Ry
Rz = Fp2.mul(Rz, t3); // Rz * T3 Rz = Fp2.mul(Rz, t3); // Rz * T3
@ -176,7 +176,7 @@ export function bls<Fp2, Fp6, Fp12>(
const F = ell[j]; const F = ell[j];
f12 = Fp12.multiplyBy014(f12, F[0], Fp2.mul(F[1], Px), Fp2.mul(F[2], Py)); f12 = Fp12.multiplyBy014(f12, F[0], Fp2.mul(F[1], Px), Fp2.mul(F[2], Py));
} }
if (i !== 0) f12 = Fp12.square(f12); if (i !== 0) f12 = Fp12.sqr(f12);
} }
return Fp12.conjugate(f12); return Fp12.conjugate(f12);
} }
@ -300,7 +300,7 @@ export function bls<Fp2, Fp6, Fp12>(
const ePHm = pairing(P.negate(), Hm, false); const ePHm = pairing(P.negate(), Hm, false);
const eGS = pairing(G, S, false); const eGS = pairing(G, S, false);
const exp = Fp12.finalExponentiate(Fp12.mul(eGS, ePHm)); const exp = Fp12.finalExponentiate(Fp12.mul(eGS, ePHm));
return Fp12.equals(exp, Fp12.ONE); return Fp12.eql(exp, Fp12.ONE);
} }
// Adds a bunch of public key points together. // Adds a bunch of public key points together.
@ -365,7 +365,7 @@ export function bls<Fp2, Fp6, Fp12>(
paired.push(pairing(G1.ProjectivePoint.BASE.negate(), sig, false)); paired.push(pairing(G1.ProjectivePoint.BASE.negate(), sig, false));
const product = paired.reduce((a, b) => Fp12.mul(a, b), Fp12.ONE); const product = paired.reduce((a, b) => Fp12.mul(a, b), Fp12.ONE);
const exp = Fp12.finalExponentiate(product); const exp = Fp12.finalExponentiate(product);
return Fp12.equals(exp, Fp12.ONE); return Fp12.eql(exp, Fp12.ONE);
} catch { } catch {
return false; return false;
} }

@ -1,17 +1,8 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y² // Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y²
// Differences from @noble/ed25519 1.7:
// 1. Variable field element lengths between EDDSA/ECDH:
// EDDSA (RFC8032) is 456 bits / 57 bytes, ECDH (RFC7748) is 448 bits / 56 bytes
// 2. Different addition formula (doubling is same)
// 3. uvRatio differs between curves (half-expected, not only pow fn changes)
// 4. Point decompression code is different (unexpected), now using generalized formula
// 5. Domain function was no-op for ed25519, but adds some data even with empty context for ed448
import * as mod from './modular.js'; import * as mod from './modular.js';
import * as ut from './utils.js'; import * as ut from './utils.js';
import { ensureBytes, Hex, PrivKey } from './utils.js'; import { ensureBytes, Hex } from './utils.js';
import { Group, GroupConstructor, wNAF } from './group.js'; import { Group, GroupConstructor, wNAF } from './group.js';
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n // Be friendly to bad ECMAScript parsers by not using bigint literals like 123n
@ -22,29 +13,20 @@ const _8n = BigInt(8);
// Edwards curves must declare params a & d. // Edwards curves must declare params a & d.
export type CurveType = ut.BasicCurve<bigint> & { export type CurveType = ut.BasicCurve<bigint> & {
// Params: a, d a: bigint; // curve param a
a: bigint; d: bigint; // curve param d
d: bigint; hash: ut.FHash; // Hashing
// Hashes randomBytes: (bytesLength?: number) => Uint8Array; // CSPRNG
// The interface, because we need outputLen for DRBG adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array; // clears bits to get valid field elemtn
hash: ut.CHash; domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array; // Used for hashing
// CSPRNG uvRatio?: (u: bigint, v: bigint) => { isValid: boolean; value: bigint }; // Ratio √(u/v)
randomBytes: (bytesLength?: number) => Uint8Array; preHash?: ut.FHash; // RFC 8032 pre-hashing of messages to sign() / verify()
// Probably clears bits in a byte array to produce a valid field element mapToCurve?: (scalar: bigint[]) => AffinePoint; // for hash-to-curve standard
adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array;
// Used during hashing
domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array;
// Ratio √(u/v)
uvRatio?: (u: bigint, v: bigint) => { isValid: boolean; value: bigint };
// RFC 8032 pre-hashing of messages to sign() / verify()
preHash?: ut.CHash;
mapToCurve?: (scalar: bigint[]) => AffinePoint;
}; };
function validateOpts(curve: CurveType) { function validateOpts(curve: CurveType) {
const opts = ut.validateOpts(curve); const opts = ut.validateOpts(curve);
if (typeof opts.hash !== 'function' || !ut.isPositiveInt(opts.hash.outputLen)) if (typeof opts.hash !== 'function') throw new Error('Invalid hash function');
throw new Error('Invalid hash function');
for (const i of ['a', 'd'] as const) { for (const i of ['a', 'd'] as const) {
const val = opts[i]; const val = opts[i];
if (typeof val !== 'bigint') throw new Error(`Invalid curve param ${i}=${val} (${typeof val})`); if (typeof val !== 'bigint') throw new Error(`Invalid curve param ${i}=${val} (${typeof val})`);
@ -84,7 +66,7 @@ export interface ExtPointConstructor extends GroupConstructor<ExtPointType> {
new (x: bigint, y: bigint, z: bigint, t: bigint): ExtPointType; new (x: bigint, y: bigint, z: bigint, t: bigint): ExtPointType;
fromAffine(p: AffinePoint): ExtPointType; fromAffine(p: AffinePoint): ExtPointType;
fromHex(hex: Hex): ExtPointType; fromHex(hex: Hex): ExtPointType;
fromPrivateKey(privateKey: PrivKey): ExtPointType; // TODO: remove fromPrivateKey(privateKey: Hex): ExtPointType; // TODO: remove
} }
export type CurveFn = { export type CurveFn = {
@ -105,23 +87,19 @@ export type CurveFn = {
}; };
}; };
// NOTE: it is not generic twisted curve for now, but ed25519/ed448 generic implementation // It is not generic twisted curve for now, but ed25519/ed448 generic implementation
export function twistedEdwards(curveDef: CurveType): CurveFn { export function twistedEdwards(curveDef: CurveType): CurveFn {
const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>; const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>;
const Fp = CURVE.Fp; const { Fp, n: CURVE_ORDER, preHash, hash: cHash, randomBytes, nByteLength, h: cofactor } = CURVE;
const CURVE_ORDER = CURVE.n; const MASK = _2n ** BigInt(nByteLength * 8);
const MASK = _2n ** BigInt(CURVE.nByteLength * 8); const modP = Fp.create; // Function overrides
// Function overrides
const { randomBytes } = CURVE;
const modP = Fp.create;
// sqrt(u/v) // sqrt(u/v)
const uvRatio = const uvRatio =
CURVE.uvRatio || CURVE.uvRatio ||
((u: bigint, v: bigint) => { ((u: bigint, v: bigint) => {
try { try {
return { isValid: true, value: Fp.sqrt(u * Fp.invert(v)) }; return { isValid: true, value: Fp.sqrt(u * Fp.inv(v)) };
} catch (e) { } catch (e) {
return { isValid: false, value: _0n }; return { isValid: false, value: _0n };
} }
@ -133,35 +111,24 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
if (ctx.length || phflag) throw new Error('Contexts/pre-hash are not supported'); if (ctx.length || phflag) throw new Error('Contexts/pre-hash are not supported');
return data; return data;
}); // NOOP }); // NOOP
function inBig(n: bigint) { const inBig = (n: bigint) => typeof n === 'bigint' && 0n < n; // n in [1..]
return typeof n === 'bigint' && 0n < n; const inRange = (n: bigint, max: bigint) => inBig(n) && inBig(max) && n < max; // n in [1..max-1]
} const in0MaskRange = (n: bigint) => n === _0n || inRange(n, MASK); // n in [0..MASK-1]
function assertInMask(n: bigint) { function assertInRange(n: bigint, max: bigint) {
if (inBig(n) && n < MASK) return n; // n in [1..max-1]
throw new Error(`Expected valid scalar < MASK, got ${typeof n} ${n}`); if (inRange(n, max)) return n;
} throw new Error(`Expected valid scalar < ${max}, got ${typeof n} ${n}`);
function assertFE(n: bigint) {
if (inBig(n) && n < Fp.ORDER) return n;
throw new Error(`Expected valid scalar < P, got ${typeof n} ${n}`);
}
function assertGE(n: bigint) {
// GE = subgroup element, not full group
if (inBig(n) && n < CURVE_ORDER) return n;
throw new Error(`Expected valid scalar < N, got ${typeof n} ${n}`);
} }
function assertGE0(n: bigint) { function assertGE0(n: bigint) {
// GE = subgroup element, not full group // n in [0..CURVE_ORDER-1]
return n === _0n ? n : assertGE(n); return n === _0n ? n : assertInRange(n, CURVE_ORDER); // GE = prime subgroup, not full group
} }
const coord = (n: bigint) => _0n <= n && n < MASK; // not < P because of ZIP215
const pointPrecomputes = new Map<Point, Point[]>(); const pointPrecomputes = new Map<Point, Point[]>();
function isPoint(other: unknown) {
/** if (!(other instanceof Point)) throw new Error('ExtendedPoint expected');
* Extended Point works in extended coordinates: (x, y, z, t) (x=x/z, y=y/z, t=xy). }
* Default Point works in affine coordinates: (x, y) // Extended Point works in extended coordinates: (x, y, z, t) ∋ (x=x/z, y=y/z, t=xy).
* https://en.wikipedia.org/wiki/Twisted_Edwards_curve#Extended_coordinates // https://en.wikipedia.org/wiki/Twisted_Edwards_curve#Extended_coordinates
*/
class Point implements ExtPointType { class Point implements ExtPointType {
static readonly BASE = new Point(CURVE.Gx, CURVE.Gy, _1n, modP(CURVE.Gx * CURVE.Gy)); static readonly BASE = new Point(CURVE.Gx, CURVE.Gy, _1n, modP(CURVE.Gx * CURVE.Gy));
static readonly ZERO = new Point(_0n, _1n, _1n, _0n); // 0, 1, 1, 0 static readonly ZERO = new Point(_0n, _1n, _1n, _0n); // 0, 1, 1, 0
@ -172,10 +139,10 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
readonly ez: bigint, readonly ez: bigint,
readonly et: bigint readonly et: bigint
) { ) {
if (!coord(ex)) throw new Error('x required'); if (!in0MaskRange(ex)) throw new Error('x required');
if (!coord(ey)) throw new Error('y required'); if (!in0MaskRange(ey)) throw new Error('y required');
if (!coord(ez)) throw new Error('z required'); if (!in0MaskRange(ez)) throw new Error('z required');
if (!coord(et)) throw new Error('t required'); if (!in0MaskRange(et)) throw new Error('t required');
} }
get x(): bigint { get x(): bigint {
@ -186,9 +153,9 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
} }
static fromAffine(p: AffinePoint): Point { static fromAffine(p: AffinePoint): Point {
if (p instanceof Point) throw new Error('extended point not allowed');
const { x, y } = p || {}; const { x, y } = p || {};
if (p instanceof Point) throw new Error('fromAffine: extended point not allowed'); if (!in0MaskRange(x) || !in0MaskRange(y)) throw new Error('invalid affine point');
if (!ut.big(x) || !ut.big(y)) throw new Error('fromAffine: invalid affine point');
return new Point(x, y, _1n, modP(x * y)); return new Point(x, y, _1n, modP(x * y));
} }
static normalizeZ(points: Point[]): Point[] { static normalizeZ(points: Point[]): Point[] {
@ -209,7 +176,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
// Compare one point to another. // Compare one point to another.
equals(other: Point): boolean { equals(other: Point): boolean {
assertExtPoint(other); isPoint(other);
const { ex: X1, ey: Y1, ez: Z1 } = this; const { ex: X1, ey: Y1, ez: Z1 } = this;
const { ex: X2, ey: Y2, ez: Z2 } = other; const { ex: X2, ey: Y2, ez: Z2 } = other;
const X1Z2 = modP(X1 * Z2); const X1Z2 = modP(X1 * Z2);
@ -223,8 +190,8 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
return this.equals(Point.ZERO); return this.equals(Point.ZERO);
} }
// Inverses point to one corresponding to (x, -y) in Affine coordinates.
negate(): Point { negate(): Point {
// Flips point sign to a negative one (-x, y in affine coords)
return new Point(modP(-this.ex), this.ey, this.ez, modP(-this.et)); return new Point(modP(-this.ex), this.ey, this.ez, modP(-this.et));
} }
@ -254,7 +221,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
// https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#addition-add-2008-hwcd // https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#addition-add-2008-hwcd
// Cost: 9M + 1*a + 1*d + 7add. // Cost: 9M + 1*a + 1*d + 7add.
add(other: Point) { add(other: Point) {
assertExtPoint(other); isPoint(other);
const { a, d } = CURVE; const { a, d } = CURVE;
const { ex: X1, ey: Y1, ez: Z1, et: T1 } = this; const { ex: X1, ey: Y1, ez: Z1, et: T1 } = this;
const { ex: X2, ey: Y2, ez: Z2, et: T2 } = other; const { ex: X2, ey: Y2, ez: Z2, et: T2 } = other;
@ -302,11 +269,9 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
return wnaf.wNAFCached(this, pointPrecomputes, n, Point.normalizeZ); return wnaf.wNAFCached(this, pointPrecomputes, n, Point.normalizeZ);
} }
// Constant time multiplication. // Constant-time multiplication.
// Uses wNAF method. Windowed method may be 10% faster,
// but takes 2x longer to generate and consumes 2x memory.
multiply(scalar: bigint): Point { multiply(scalar: bigint): Point {
const { p, f } = this.wNAF(assertGE(scalar)); const { p, f } = this.wNAF(assertInRange(scalar, CURVE_ORDER));
return Point.normalizeZ([p, f])[0]; return Point.normalizeZ([p, f])[0];
} }
@ -326,10 +291,10 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
// point with torsion component. // point with torsion component.
// Multiplies point by cofactor and checks if the result is 0. // Multiplies point by cofactor and checks if the result is 0.
isSmallOrder(): boolean { isSmallOrder(): boolean {
return this.multiplyUnsafe(CURVE.h).is0(); return this.multiplyUnsafe(cofactor).is0();
} }
// Multiplies point by curve order (very big scalar CURVE.n) and checks if the result is 0. // Multiplies point by curve order and checks if the result is 0.
// Returns `false` is the point is dirty. // Returns `false` is the point is dirty.
isTorsionFree(): boolean { isTorsionFree(): boolean {
return wnaf.unsafeLadder(this, CURVE_ORDER).is0(); return wnaf.unsafeLadder(this, CURVE_ORDER).is0();
@ -340,7 +305,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
toAffine(iz?: bigint): AffinePoint { toAffine(iz?: bigint): AffinePoint {
const { ex: x, ey: y, ez: z } = this; const { ex: x, ey: y, ez: z } = this;
const is0 = this.is0(); const is0 = this.is0();
if (iz == null) iz = is0 ? _8n : (Fp.invert(z) as bigint); // 8 was chosen arbitrarily if (iz == null) iz = is0 ? _8n : (Fp.inv(z) as bigint); // 8 was chosen arbitrarily
const ax = modP(x * iz); const ax = modP(x * iz);
const ay = modP(y * iz); const ay = modP(y * iz);
const zz = modP(z * iz); const zz = modP(z * iz);
@ -348,6 +313,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
if (zz !== _1n) throw new Error('invZ was invalid'); if (zz !== _1n) throw new Error('invZ was invalid');
return { x: ax, y: ay }; return { x: ax, y: ay };
} }
clearCofactor(): Point { clearCofactor(): Point {
const { h: cofactor } = CURVE; const { h: cofactor } = CURVE;
if (cofactor === _1n) return this; if (cofactor === _1n) return this;
@ -356,58 +322,41 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
// Converts hash string or Uint8Array to Point. // Converts hash string or Uint8Array to Point.
// Uses algo from RFC8032 5.1.3. // Uses algo from RFC8032 5.1.3.
static fromHex(hex: Hex, strict = true) { static fromHex(hex: Hex, strict = true): Point {
const { d, a } = CURVE; const { d, a } = CURVE;
const len = Fp.BYTES; const len = Fp.BYTES;
hex = ensureBytes(hex, len); hex = ensureBytes(hex, len); // copy hex to a new array
// 1. First, interpret the string as an integer in little-endian const normed = hex.slice(); // copy again, we'll manipulate it
// representation. Bit 255 of this number is the least significant const lastByte = hex[len - 1]; // select last byte
// bit of the x-coordinate and denote this value x_0. The normed[len - 1] = lastByte & ~0x80; // clear last bit
// y-coordinate is recovered simply by clearing this bit. If the
// resulting value is >= p, decoding fails.
const normed = hex.slice();
const lastByte = hex[len - 1];
normed[len - 1] = lastByte & ~0x80;
const y = ut.bytesToNumberLE(normed); const y = ut.bytesToNumberLE(normed);
if (y === _0n) { if (y === _0n) {
// y=0 is allowed // y=0 is allowed
} else { } else {
if (strict) assertFE(y); // strict=true [0..CURVE.Fp.P] (2^255-19 for ed25519) // RFC8032 prohibits >= p, but ZIP215 doesn't
else assertInMask(y); // strict=false [0..MASK] (2^256 for ed25519) if (strict) assertInRange(y, Fp.ORDER); // strict=true [1..P-1] (2^255-19-1 for ed25519)
else assertInRange(y, MASK); // strict=false [1..MASK-1] (2^256-1 for ed25519)
} }
// 2. To recover the x-coordinate, the curve equation implies // Ed25519: x² = (y²-1)/(dy²+1) mod p. Ed448: x² = (y²-1)/(dy²-1) mod p. Generic case:
// Ed25519: x² = (y² - 1) / (d y² + 1) (mod p). // ax²+y²=1+dx²y² => y²-1=dx²y²-ax² => y²-1=x²(dy²-a) => x²=(y²-1)/(dy²-a)
// Ed448: x² = (y² - 1) / (d y² - 1) (mod p). const y2 = modP(y * y); // denominator is always non-0 mod p.
// For generic case: const u = modP(y2 - _1n); // u = y² - 1
// a*x²+y²=1+d*x²*y² const v = modP(d * y2 - a); // v = d y² + 1.
// -> y²-1 = d*x²*y²-a*x² let { isValid, value: x } = uvRatio(u, v); // √(u/v)
// -> y²-1 = x² (d*y²-a)
// -> x² = (y²-1) / (d*y²-a)
// The denominator is always non-zero mod p. Let u = y² - 1 and v = d y² + 1.
const y2 = modP(y * y);
const u = modP(y2 - _1n);
const v = modP(d * y2 - a);
let { isValid, value: x } = uvRatio(u, v);
if (!isValid) throw new Error('Point.fromHex: invalid y coordinate'); if (!isValid) throw new Error('Point.fromHex: invalid y coordinate');
// 4. Finally, use the x_0 bit to select the right square root. If const isXOdd = (x & _1n) === _1n; // There are 2 square roots. Use x_0 bit to select proper
// x = 0, and x_0 = 1, decoding fails. Otherwise, if x_0 != x mod const isLastByteOdd = (lastByte & 0x80) !== 0; // if x=0 and x_0 = 1, fail
// 2, set x <-- p - x. Return the decoded point (x,y). if (isLastByteOdd !== isXOdd) x = modP(-x); // if x_0 != x mod 2, set x = p-x
const isXOdd = (x & _1n) === _1n;
const isLastByteOdd = (lastByte & 0x80) !== 0;
if (isLastByteOdd !== isXOdd) x = modP(-x);
return Point.fromAffine({ x, y }); return Point.fromAffine({ x, y });
} }
static fromPrivateKey(privateKey: PrivKey) { static fromPrivateKey(privKey: Hex) {
return getExtendedPublicKey(privateKey).point; return getExtendedPublicKey(privKey).point;
} }
toRawBytes(): Uint8Array { toRawBytes(): Uint8Array {
const { x, y } = this.toAffine(); const { x, y } = this.toAffine();
const len = Fp.BYTES; const bytes = ut.numberToBytesLE(y, Fp.BYTES); // each y has 2 x values (x, -y)
const bytes = ut.numberToBytesLE(y, len); // each y has 2 x values (x, -y) bytes[bytes.length - 1] |= x & _1n ? 0x80 : 0; // when compressing, it's enough to store y
bytes[len - 1] |= x & _1n ? 0x80 : 0; // when compressing, it's enough to store y
return bytes; // and use the last byte to encode sign of x return bytes; // and use the last byte to encode sign of x
} }
toHex(): string { toHex(): string {
@ -415,105 +364,80 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
} }
} }
const { BASE: G, ZERO: I } = Point; const { BASE: G, ZERO: I } = Point;
const wnaf = wNAF(Point, CURVE.nByteLength * 8); const wnaf = wNAF(Point, nByteLength * 8);
function assertExtPoint(other: unknown) {
if (!(other instanceof Point)) throw new Error('ExtendedPoint expected');
}
// Little-endian SHA512 with modulo n // Little-endian SHA512 with modulo n
function modnLE(hash: Uint8Array): bigint { function modnLE(hash: Uint8Array): bigint {
return mod.mod(ut.bytesToNumberLE(hash), CURVE_ORDER); return mod.mod(ut.bytesToNumberLE(hash), CURVE_ORDER);
} }
function isHex(item: Hex, err: string) {
if (typeof item !== 'string' && !(item instanceof Uint8Array))
throw new Error(`${err} must be hex string or Uint8Array`);
}
/** Convenience method that creates public key and other stuff. RFC8032 5.1.5 */ /** Convenience method that creates public key and other stuff. RFC8032 5.1.5 */
function getExtendedPublicKey(key: PrivKey) { function getExtendedPublicKey(key: Hex) {
const groupLen = CURVE.nByteLength; isHex(key, 'private key');
// Normalize bigint / number / string to Uint8Array const len = nByteLength;
const keyb = typeof key === 'bigint' ? ut.numberToBytesLE(assertInMask(key), groupLen) : key;
// Hash private key with curve's hash function to produce uniformingly random input // Hash private key with curve's hash function to produce uniformingly random input
// We check byte lengths e.g.: ensureBytes(64, hash(ensureBytes(32, key))) // Check byte lengths: ensure(64, h(ensure(32, key)))
const hashed = ensureBytes(CURVE.hash(ensureBytes(keyb, groupLen)), 2 * groupLen); const hashed = ensureBytes(cHash(ensureBytes(key, len)), 2 * len);
const head = adjustScalarBytes(hashed.slice(0, len)); // clear first half bits, produce FE
// First half's bits are cleared to produce a random field element. const prefix = hashed.slice(len, 2 * len); // second half is called key prefix (5.1.6)
const head = adjustScalarBytes(hashed.slice(0, groupLen)); const scalar = modnLE(head); // The actual private scalar
// Second half is called key prefix (5.1.6) const point = G.multiply(scalar); // Point on Edwards curve aka public key
const prefix = hashed.slice(groupLen, 2 * groupLen); const pointBytes = point.toRawBytes(); // Uint8Array representation
// The actual private scalar
const scalar = modnLE(head);
// Point on Edwards curve aka public key
const point = G.multiply(scalar);
// Uint8Array representation
const pointBytes = point.toRawBytes();
return { head, prefix, scalar, point, pointBytes }; return { head, prefix, scalar, point, pointBytes };
} }
/** // Calculates EdDSA pub key. RFC8032 5.1.5. Privkey is hashed. Use first half with 3 bits cleared
* Calculates ed25519 public key. RFC8032 5.1.5 function getPublicKey(privKey: Hex): Uint8Array {
* 1. private key is hashed with sha512, then first 32 bytes are taken from the hash return getExtendedPublicKey(privKey).pointBytes;
* 2. 3 least significant bits of the first byte are cleared
*/
function getPublicKey(privateKey: PrivKey): Uint8Array {
return getExtendedPublicKey(privateKey).pointBytes;
} }
const EMPTY = new Uint8Array(); // int('LE', SHA512(dom2(F, C) || msgs)) mod N
function hashDomainToScalar(message: Uint8Array, context: Hex = EMPTY) { function hashDomainToScalar(context: Hex = new Uint8Array(), ...msgs: Uint8Array[]) {
context = ensureBytes(context); const msg = ut.concatBytes(...msgs);
return modnLE(CURVE.hash(domain(message, context, !!CURVE.preHash))); return modnLE(cHash(domain(msg, ensureBytes(context), !!preHash)));
} }
/** Signs message with privateKey. RFC8032 5.1.6 */ /** Signs message with privateKey. RFC8032 5.1.6 */
function sign(message: Hex, privateKey: Hex, context?: Hex): Uint8Array { function sign(msg: Hex, privKey: Hex, context?: Hex): Uint8Array {
message = ensureBytes(message); isHex(msg, 'message');
if (CURVE.preHash) message = CURVE.preHash(message); msg = ensureBytes(msg);
const { prefix, scalar, pointBytes } = getExtendedPublicKey(privateKey); if (preHash) msg = preHash(msg); // for ed25519ph etc.
const r = hashDomainToScalar(ut.concatBytes(prefix, message), context); const { prefix, scalar, pointBytes } = getExtendedPublicKey(privKey);
const R = G.multiply(r); // R = rG const r = hashDomainToScalar(context, prefix, msg); // r = dom2(F, C) || prefix || PH(M)
const k = hashDomainToScalar(ut.concatBytes(R.toRawBytes(), pointBytes, message), context); // k = hash(R+P+msg) const R = G.multiply(r).toRawBytes(); // R = rG
const s = mod.mod(r + k * scalar, CURVE_ORDER); // s = r + kp const k = hashDomainToScalar(context, R, pointBytes, msg); // R || A || PH(M)
const s = mod.mod(r + k * scalar, CURVE_ORDER); // S = (r + k * s) mod L
assertGE0(s); // 0 <= s < l assertGE0(s); // 0 <= s < l
return ut.concatBytes(R.toRawBytes(), ut.numberToBytesLE(s, Fp.BYTES)); const res = ut.concatBytes(R, ut.numberToBytesLE(s, Fp.BYTES));
return ensureBytes(res, nByteLength * 2); // 64-byte signature
} }
/** function verify(sig: Hex, msg: Hex, publicKey: Hex, context?: Hex): boolean {
* Verifies EdDSA signature against message and public key. isHex(sig, 'sig');
* An extended group equation is checked. isHex(msg, 'message');
* RFC8032 5.1.7 const len = Fp.BYTES; // Verifies EdDSA signature against message and public key. RFC8032 5.1.7.
* Compliant with ZIP215: sig = ensureBytes(sig, 2 * len); // An extended group equation is checked.
* 0 <= sig.R/publicKey < 2**256 (can be >= curve.P) msg = ensureBytes(msg); // ZIP215 compliant, which means not fully RFC8032 compliant.
* 0 <= sig.s < l if (preHash) msg = preHash(msg); // for ed25519ph, etc
* Not compliant with RFC8032: it's not possible to comply to both ZIP & RFC at the same time.
*/
function verify(sig: Hex, message: Hex, publicKey: Hex, context?: Hex): boolean {
const len = Fp.BYTES;
sig = ensureBytes(sig, 2 * len);
message = ensureBytes(message);
if (CURVE.preHash) message = CURVE.preHash(message);
const R = Point.fromHex(sig.slice(0, len), false); // non-strict; allows 0..MASK
const s = ut.bytesToNumberLE(sig.slice(len, 2 * len));
const A = Point.fromHex(publicKey, false); // Check for s bounds, hex validity const A = Point.fromHex(publicKey, false); // Check for s bounds, hex validity
const R = Point.fromHex(sig.slice(0, len), false); // 0 <= R < 2^256: ZIP215 R can be >= P
const s = ut.bytesToNumberLE(sig.slice(len, 2 * len)); // 0 <= s < l
const SB = G.multiplyUnsafe(s); const SB = G.multiplyUnsafe(s);
const k = hashDomainToScalar(ut.concatBytes(R.toRawBytes(), A.toRawBytes(), message), context); const k = hashDomainToScalar(context, R.toRawBytes(), A.toRawBytes(), msg);
const kA = A.multiplyUnsafe(k); const RkA = R.add(A.multiplyUnsafe(k));
const RkA = R.add(kA);
// [8][S]B = [8]R + [8][k]A' // [8][S]B = [8]R + [8][k]A'
return RkA.subtract(SB).clearCofactor().equals(Point.ZERO); return RkA.subtract(SB).clearCofactor().equals(Point.ZERO);
} }
// Enable precomputes. Slows down first publicKey computation by 20ms. G._setWindowSize(8); // Enable precomputes. Slows down first publicKey computation by 20ms.
G._setWindowSize(8);
const utils = { const utils = {
getExtendedPublicKey, getExtendedPublicKey,
/** // ed25519 private keys are uniform 32b. No need to check for modulo bias, like in secp256k1.
* Not needed for ed25519 private keys. Needed if you use scalars directly (rare).
*/
hashToPrivateScalar: (hash: Hex): bigint => ut.hashToPrivateScalar(hash, CURVE_ORDER, true),
/**
* ed25519 private keys are uniform 32-bit strings. We do not need to check for
* modulo bias like we do in secp256k1 randomPrivateKey()
*/
randomPrivateKey: (): Uint8Array => randomBytes(Fp.BYTES), randomPrivateKey: (): Uint8Array => randomBytes(Fp.BYTES),
/** /**

@ -17,7 +17,9 @@ export type GroupConstructor<T> = {
ZERO: T; ZERO: T;
}; };
export type Mapper<T> = (i: T[]) => T[]; export type Mapper<T> = (i: T[]) => T[];
// Not big, but pretty complex and it is easy to break stuff. To avoid too much copy paste
// Elliptic curve multiplication of Point by scalar. Complicated and fragile. Uses wNAF method.
// Windowed method is 10% faster, but takes 2x longer to generate & consumes 2x memory.
export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) { export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
const constTimeNegate = (condition: boolean, item: T): T => { const constTimeNegate = (condition: boolean, item: T): T => {
const neg = item.negate(); const neg = item.negate();
@ -129,7 +131,7 @@ export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
wNAFCached(P: T, precomputesMap: Map<T, T[]>, n: bigint, transform: Mapper<T>): { p: T; f: T } { wNAFCached(P: T, precomputesMap: Map<T, T[]>, n: bigint, transform: Mapper<T>): { p: T; f: T } {
// @ts-ignore // @ts-ignore
const W: number = '_WINDOW_SIZE' in P ? P._WINDOW_SIZE : 1; const W: number = P._WINDOW_SIZE || 1;
// Calculate precomputes on a first run, reuse them after // Calculate precomputes on a first run, reuse them after
let comp = precomputesMap.get(P); let comp = precomputesMap.get(P);
if (!comp) { if (!comp) {

@ -41,6 +41,7 @@ export function validateOpts(opts: Opts) {
// UTF8 to ui8a // UTF8 to ui8a
// TODO: looks broken, ASCII only, why not TextEncoder/TextDecoder? it is in hashes anyway // TODO: looks broken, ASCII only, why not TextEncoder/TextDecoder? it is in hashes anyway
export function stringToBytes(str: string) { export function stringToBytes(str: string) {
// return new TextEncoder().encode(str);
const bytes = new Uint8Array(str.length); const bytes = new Uint8Array(str.length);
for (let i = 0; i < str.length; i++) bytes[i] = str.charCodeAt(i); for (let i = 0; i < str.length; i++) bytes[i] = str.charCodeAt(i);
return bytes; return bytes;

@ -92,7 +92,7 @@ export function tonelliShanks(P: bigint) {
const p1div4 = (P + _1n) / _4n; const p1div4 = (P + _1n) / _4n;
return function tonelliFast<T>(Fp: Field<T>, n: T) { return function tonelliFast<T>(Fp: Field<T>, n: T) {
const root = Fp.pow(n, p1div4); const root = Fp.pow(n, p1div4);
if (!Fp.equals(Fp.square(root), n)) throw new Error('Cannot find square root'); if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root');
return root; return root;
}; };
} }
@ -101,24 +101,24 @@ export function tonelliShanks(P: bigint) {
const Q1div2 = (Q + _1n) / _2n; const Q1div2 = (Q + _1n) / _2n;
return function tonelliSlow<T>(Fp: Field<T>, n: T): T { return function tonelliSlow<T>(Fp: Field<T>, n: T): T {
// Step 0: Check that n is indeed a square: (n | p) should not be ≡ -1 // Step 0: Check that n is indeed a square: (n | p) should not be ≡ -1
if (Fp.pow(n, legendreC) === Fp.negate(Fp.ONE)) throw new Error('Cannot find square root'); if (Fp.pow(n, legendreC) === Fp.neg(Fp.ONE)) throw new Error('Cannot find square root');
let r = S; let r = S;
// TODO: will fail at Fp2/etc // TODO: will fail at Fp2/etc
let g = Fp.pow(Fp.mul(Fp.ONE, Z), Q); // will update both x and b let g = Fp.pow(Fp.mul(Fp.ONE, Z), Q); // will update both x and b
let x = Fp.pow(n, Q1div2); // first guess at the square root let x = Fp.pow(n, Q1div2); // first guess at the square root
let b = Fp.pow(n, Q); // first guess at the fudge factor let b = Fp.pow(n, Q); // first guess at the fudge factor
while (!Fp.equals(b, Fp.ONE)) { while (!Fp.eql(b, Fp.ONE)) {
if (Fp.equals(b, Fp.ZERO)) return Fp.ZERO; // https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm (4. If t = 0, return r = 0) if (Fp.eql(b, Fp.ZERO)) return Fp.ZERO; // https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm (4. If t = 0, return r = 0)
// Find m such b^(2^m)==1 // Find m such b^(2^m)==1
let m = 1; let m = 1;
for (let t2 = Fp.square(b); m < r; m++) { for (let t2 = Fp.sqr(b); m < r; m++) {
if (Fp.equals(t2, Fp.ONE)) break; if (Fp.eql(t2, Fp.ONE)) break;
t2 = Fp.square(t2); // t2 *= t2 t2 = Fp.sqr(t2); // t2 *= t2
} }
// NOTE: r-m-1 can be bigger than 32, need to convert to bigint before shift, otherwise there will be overflow // NOTE: r-m-1 can be bigger than 32, need to convert to bigint before shift, otherwise there will be overflow
const ge = Fp.pow(g, _1n << BigInt(r - m - 1)); // ge = 2^(r-m-1) const ge = Fp.pow(g, _1n << BigInt(r - m - 1)); // ge = 2^(r-m-1)
g = Fp.square(ge); // g = ge * ge g = Fp.sqr(ge); // g = ge * ge
x = Fp.mul(x, ge); // x *= ge x = Fp.mul(x, ge); // x *= ge
b = Fp.mul(b, g); // b *= g b = Fp.mul(b, g); // b *= g
r = m; r = m;
@ -142,7 +142,7 @@ export function FpSqrt(P: bigint) {
return function sqrt3mod4<T>(Fp: Field<T>, n: T) { return function sqrt3mod4<T>(Fp: Field<T>, n: T) {
const root = Fp.pow(n, p1div4); const root = Fp.pow(n, p1div4);
// Throw if root**2 != n // Throw if root**2 != n
if (!Fp.equals(Fp.square(root), n)) throw new Error('Cannot find square root'); if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root');
return root; return root;
}; };
} }
@ -156,7 +156,7 @@ export function FpSqrt(P: bigint) {
const nv = Fp.mul(n, v); const nv = Fp.mul(n, v);
const i = Fp.mul(Fp.mul(nv, _2n), v); const i = Fp.mul(Fp.mul(nv, _2n), v);
const root = Fp.mul(nv, Fp.sub(i, Fp.ONE)); const root = Fp.mul(nv, Fp.sub(i, Fp.ONE));
if (!Fp.equals(Fp.square(root), n)) throw new Error('Cannot find square root'); if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root');
return root; return root;
}; };
} }
@ -206,13 +206,13 @@ export interface Field<T> {
// 1-arg // 1-arg
create: (num: T) => T; create: (num: T) => T;
isValid: (num: T) => boolean; isValid: (num: T) => boolean;
isZero: (num: T) => boolean; is0: (num: T) => boolean;
negate(num: T): T; neg(num: T): T;
invert(num: T): T; inv(num: T): T;
sqrt(num: T): T; sqrt(num: T): T;
square(num: T): T; sqr(num: T): T;
// 2-args // 2-args
equals(lhs: T, rhs: T): boolean; eql(lhs: T, rhs: T): boolean;
add(lhs: T, rhs: T): T; add(lhs: T, rhs: T): T;
sub(lhs: T, rhs: T): T; sub(lhs: T, rhs: T): T;
mul(lhs: T, rhs: T | bigint): T; mul(lhs: T, rhs: T | bigint): T;
@ -222,13 +222,13 @@ export interface Field<T> {
addN(lhs: T, rhs: T): T; addN(lhs: T, rhs: T): T;
subN(lhs: T, rhs: T): T; subN(lhs: T, rhs: T): T;
mulN(lhs: T, rhs: T | bigint): T; mulN(lhs: T, rhs: T | bigint): T;
squareN(num: T): T; sqrN(num: T): T;
// Optional // Optional
// Should be same as sgn0 function in https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/ // Should be same as sgn0 function in https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/
// NOTE: sgn0 is 'negative in LE', which is same as odd. And negative in LE is kinda strange definition anyway. // NOTE: sgn0 is 'negative in LE', which is same as odd. And negative in LE is kinda strange definition anyway.
isOdd?(num: T): boolean; // Odd instead of even since we have it for Fp2 isOdd?(num: T): boolean; // Odd instead of even since we have it for Fp2
legendre?(num: T): T; // legendre?(num: T): T;
pow(lhs: T, power: bigint): T; pow(lhs: T, power: bigint): T;
invertBatch: (lst: T[]) => T[]; invertBatch: (lst: T[]) => T[];
toBytes(num: T): Uint8Array; toBytes(num: T): Uint8Array;
@ -238,9 +238,9 @@ export interface Field<T> {
} }
// prettier-ignore // prettier-ignore
const FIELD_FIELDS = [ const FIELD_FIELDS = [
'create', 'isValid', 'isZero', 'negate', 'invert', 'sqrt', 'square', 'create', 'isValid', 'is0', 'neg', 'inv', 'sqrt', 'sqr',
'equals', 'add', 'sub', 'mul', 'pow', 'div', 'eql', 'add', 'sub', 'mul', 'pow', 'div',
'addN', 'subN', 'mulN', 'squareN' 'addN', 'subN', 'mulN', 'sqrN'
] as const; ] as const;
export function validateField<T>(field: Field<T>) { export function validateField<T>(field: Field<T>) {
for (const i of ['ORDER', 'MASK'] as const) { for (const i of ['ORDER', 'MASK'] as const) {
@ -268,7 +268,7 @@ export function FpPow<T>(f: Field<T>, num: T, power: bigint): T {
let d = num; let d = num;
while (power > _0n) { while (power > _0n) {
if (power & _1n) p = f.mul(p, d); if (power & _1n) p = f.mul(p, d);
d = f.square(d); d = f.sqr(d);
power >>= 1n; power >>= 1n;
} }
return p; return p;
@ -278,15 +278,15 @@ export function FpInvertBatch<T>(f: Field<T>, nums: T[]): T[] {
const tmp = new Array(nums.length); const tmp = new Array(nums.length);
// Walk from first to last, multiply them by each other MOD p // Walk from first to last, multiply them by each other MOD p
const lastMultiplied = nums.reduce((acc, num, i) => { const lastMultiplied = nums.reduce((acc, num, i) => {
if (f.isZero(num)) return acc; if (f.is0(num)) return acc;
tmp[i] = acc; tmp[i] = acc;
return f.mul(acc, num); return f.mul(acc, num);
}, f.ONE); }, f.ONE);
// Invert last element // Invert last element
const inverted = f.invert(lastMultiplied); const inverted = f.inv(lastMultiplied);
// Walk from last to first, multiply them by inverted each other MOD p // Walk from last to first, multiply them by inverted each other MOD p
nums.reduceRight((acc, num, i) => { nums.reduceRight((acc, num, i) => {
if (f.isZero(num)) return acc; if (f.is0(num)) return acc;
tmp[i] = f.mul(acc, tmp[i]); tmp[i] = f.mul(acc, tmp[i]);
return f.mul(acc, num); return f.mul(acc, num);
}, inverted); }, inverted);
@ -294,7 +294,7 @@ export function FpInvertBatch<T>(f: Field<T>, nums: T[]): T[] {
} }
export function FpDiv<T>(f: Field<T>, lhs: T, rhs: T | bigint): T { export function FpDiv<T>(f: Field<T>, lhs: T, rhs: T | bigint): T {
return f.mul(lhs, typeof rhs === 'bigint' ? invert(rhs, f.ORDER) : f.invert(rhs)); return f.mul(lhs, typeof rhs === 'bigint' ? invert(rhs, f.ORDER) : f.inv(rhs));
} }
// This function returns True whenever the value x is a square in the field F. // This function returns True whenever the value x is a square in the field F.
@ -302,7 +302,7 @@ export function FpIsSquare<T>(f: Field<T>) {
const legendreConst = (f.ORDER - _1n) / _2n; // Integer arithmetic const legendreConst = (f.ORDER - _1n) / _2n; // Integer arithmetic
return (x: T): boolean => { return (x: T): boolean => {
const p = f.pow(x, legendreConst); const p = f.pow(x, legendreConst);
return f.equals(p, f.ZERO) || f.equals(p, f.ONE); return f.eql(p, f.ZERO) || f.eql(p, f.ONE);
}; };
} }
@ -334,12 +334,12 @@ export function Fp(
throw new Error(`Invalid field element: expected bigint, got ${typeof num}`); throw new Error(`Invalid field element: expected bigint, got ${typeof num}`);
return _0n <= num && num < ORDER; // 0 is valid element, but it's not invertible return _0n <= num && num < ORDER; // 0 is valid element, but it's not invertible
}, },
isZero: (num) => num === _0n, is0: (num) => num === _0n,
isOdd: (num) => (num & _1n) === _1n, isOdd: (num) => (num & _1n) === _1n,
negate: (num) => mod(-num, ORDER), neg: (num) => mod(-num, ORDER),
equals: (lhs, rhs) => lhs === rhs, eql: (lhs, rhs) => lhs === rhs,
square: (num) => mod(num * num, ORDER), sqr: (num) => mod(num * num, ORDER),
add: (lhs, rhs) => mod(lhs + rhs, ORDER), add: (lhs, rhs) => mod(lhs + rhs, ORDER),
sub: (lhs, rhs) => mod(lhs - rhs, ORDER), sub: (lhs, rhs) => mod(lhs - rhs, ORDER),
mul: (lhs, rhs) => mod(lhs * rhs, ORDER), mul: (lhs, rhs) => mod(lhs * rhs, ORDER),
@ -347,12 +347,12 @@ export function Fp(
div: (lhs, rhs) => mod(lhs * invert(rhs, ORDER), ORDER), div: (lhs, rhs) => mod(lhs * invert(rhs, ORDER), ORDER),
// Same as above, but doesn't normalize // Same as above, but doesn't normalize
squareN: (num) => num * num, sqrN: (num) => num * num,
addN: (lhs, rhs) => lhs + rhs, addN: (lhs, rhs) => lhs + rhs,
subN: (lhs, rhs) => lhs - rhs, subN: (lhs, rhs) => lhs - rhs,
mulN: (lhs, rhs) => lhs * rhs, mulN: (lhs, rhs) => lhs * rhs,
invert: (num) => invert(num, ORDER), inv: (num) => invert(num, ORDER),
sqrt: redef.sqrt || ((n) => sqrtP(f, n)), sqrt: redef.sqrt || ((n) => sqrtP(f, n)),
invertBatch: (lst) => FpInvertBatch(f, lst), invertBatch: (lst) => FpInvertBatch(f, lst),
// TODO: do we really need constant cmov? // TODO: do we really need constant cmov?
@ -372,11 +372,11 @@ export function Fp(
export function FpSqrtOdd<T>(Fp: Field<T>, elm: T) { export function FpSqrtOdd<T>(Fp: Field<T>, elm: T) {
if (!Fp.isOdd) throw new Error(`Field doesn't have isOdd`); if (!Fp.isOdd) throw new Error(`Field doesn't have isOdd`);
const root = Fp.sqrt(elm); const root = Fp.sqrt(elm);
return Fp.isOdd(root) ? root : Fp.negate(root); return Fp.isOdd(root) ? root : Fp.neg(root);
} }
export function FpSqrtEven<T>(Fp: Field<T>, elm: T) { export function FpSqrtEven<T>(Fp: Field<T>, elm: T) {
if (!Fp.isOdd) throw new Error(`Field doesn't have isOdd`); if (!Fp.isOdd) throw new Error(`Field doesn't have isOdd`);
const root = Fp.sqrt(elm); const root = Fp.sqrt(elm);
return Fp.isOdd(root) ? Fp.negate(root) : root; return Fp.isOdd(root) ? Fp.neg(root) : root;
} }

@ -33,8 +33,8 @@ export function validateOpts(opts: PoseidonOpts) {
const _sboxPower = BigInt(sboxPower); const _sboxPower = BigInt(sboxPower);
let sboxFn = (n: bigint) => mod.FpPow(Fp, n, _sboxPower); let sboxFn = (n: bigint) => mod.FpPow(Fp, n, _sboxPower);
// Unwrapped sbox power for common cases (195->142μs) // Unwrapped sbox power for common cases (195->142μs)
if (sboxPower === 3) sboxFn = (n: bigint) => Fp.mul(Fp.squareN(n), n); if (sboxPower === 3) sboxFn = (n: bigint) => Fp.mul(Fp.sqrN(n), n);
else if (sboxPower === 5) sboxFn = (n: bigint) => Fp.mul(Fp.squareN(Fp.squareN(n)), n); else if (sboxPower === 5) sboxFn = (n: bigint) => Fp.mul(Fp.sqrN(Fp.sqrN(n)), n);
if (opts.roundsFull % 2 !== 0) if (opts.roundsFull % 2 !== 0)
throw new Error(`Poseidon roundsFull is not even: ${opts.roundsFull}`); throw new Error(`Poseidon roundsFull is not even: ${opts.roundsFull}`);

@ -18,6 +18,7 @@ export type CHash = {
outputLen: number; outputLen: number;
create(opts?: { dkLen?: number }): any; // For shake create(opts?: { dkLen?: number }): any; // For shake
}; };
export type FHash = (message: Uint8Array | string) => Uint8Array;
// NOTE: these are generic, even if curve is on some polynominal field (bls), it will still have P/n/h // NOTE: these are generic, even if curve is on some polynominal field (bls), it will still have P/n/h
// But generator can be different (Fp2/Fp6 for bls?) // But generator can be different (Fp2/Fp6 for bls?)

@ -1,16 +1,8 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// Short Weierstrass curve. The formula is: y² = x³ + ax + b // Short Weierstrass curve. The formula is: y² = x³ + ax + b
// Differences from @noble/secp256k1 1.7:
// 1. Different double() formula (but same addition)
// 2. Different sqrt() function
// 3. truncateHash() truncateOnly mode
// 4. DRBG supports outputLen bigger than outputLen of hmac
// 5. Support for different hash functions
import * as mod from './modular.js'; import * as mod from './modular.js';
import * as ut from './utils.js'; import * as ut from './utils.js';
import { bytesToHex, Hex, PrivKey } from './utils.js'; import { Hex, PrivKey } from './utils.js';
import { Group, GroupConstructor, wNAF } from './group.js'; import { Group, GroupConstructor, wNAF } from './group.js';
type HmacFnSync = (key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array; type HmacFnSync = (key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array;
@ -52,7 +44,7 @@ const DER = {
}, },
parseInt(data: Uint8Array): { data: bigint; left: Uint8Array } { parseInt(data: Uint8Array): { data: bigint; left: Uint8Array } {
if (data.length < 2 || data[0] !== 0x02) { if (data.length < 2 || data[0] !== 0x02) {
throw new DERError(`Invalid signature integer tag: ${bytesToHex(data)}`); throw new DERError(`Invalid signature integer tag: ${ut.bytesToHex(data)}`);
} }
const len = data[1]; const len = data[1];
const res = data.subarray(2, len + 2); const res = data.subarray(2, len + 2);
@ -67,7 +59,7 @@ const DER = {
}, },
parseSig(data: Uint8Array): { r: bigint; s: bigint } { parseSig(data: Uint8Array): { r: bigint; s: bigint } {
if (data.length < 2 || data[0] != 0x30) { if (data.length < 2 || data[0] != 0x30) {
throw new DERError(`Invalid signature tag: ${bytesToHex(data)}`); throw new DERError(`Invalid signature tag: ${ut.bytesToHex(data)}`);
} }
if (data[1] !== data.length - 2) { if (data[1] !== data.length - 2) {
throw new DERError('Invalid signature: incorrect length'); throw new DERError('Invalid signature: incorrect length');
@ -75,7 +67,9 @@ const DER = {
const { data: r, left: sBytes } = DER.parseInt(data.subarray(2)); const { data: r, left: sBytes } = DER.parseInt(data.subarray(2));
const { data: s, left: rBytesLeft } = DER.parseInt(sBytes); const { data: s, left: rBytesLeft } = DER.parseInt(sBytes);
if (rBytesLeft.length) { if (rBytesLeft.length) {
throw new DERError(`Invalid signature: left bytes after parsing: ${bytesToHex(rBytesLeft)}`); throw new DERError(
`Invalid signature: left bytes after parsing: ${ut.bytesToHex(rBytesLeft)}`
);
} }
return { r, s }; return { r, s };
}, },
@ -156,7 +150,7 @@ function validatePointOpts<T>(curve: CurvePointsType<T>) {
} }
const endo = opts.endo; const endo = opts.endo;
if (endo) { if (endo) {
if (!Fp.equals(opts.a, Fp.ZERO)) { if (!Fp.eql(opts.a, Fp.ZERO)) {
throw new Error('Endomorphism can only be defined for Koblitz curves that have a=0'); throw new Error('Endomorphism can only be defined for Koblitz curves that have a=0');
} }
if ( if (
@ -194,7 +188,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
*/ */
function weierstrassEquation(x: T): T { function weierstrassEquation(x: T): T {
const { a, b } = CURVE; const { a, b } = CURVE;
const x2 = Fp.square(x); // x * x const x2 = Fp.sqr(x); // x * x
const x3 = Fp.mul(x2, x); // x2 * x const x3 = Fp.mul(x2, x); // x2 * x
return Fp.add(Fp.add(x3, Fp.mul(x, a)), b); // x3 + a * x + b return Fp.add(Fp.add(x3, Fp.mul(x, a)), b); // x3 + a * x + b
} }
@ -213,7 +207,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
* - `wrapPrivateKey` when true, executed after most checks, but before `0 < key < n` * - `wrapPrivateKey` when true, executed after most checks, but before `0 < key < n`
*/ */
function normalizePrivateKey(key: PrivKey): bigint { function normalizePrivateKey(key: PrivKey): bigint {
const { normalizePrivateKey: custom, nByteLength: groupLen, wrapPrivateKey, n: order } = CURVE; const { normalizePrivateKey: custom, nByteLength: groupLen, wrapPrivateKey, n } = CURVE;
if (typeof custom === 'function') key = custom(key); if (typeof custom === 'function') key = custom(key);
let num: bigint; let num: bigint;
if (typeof key === 'bigint') { if (typeof key === 'bigint') {
@ -230,7 +224,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
throw new Error('private key must be bytes, hex or bigint, not ' + typeof key); throw new Error('private key must be bytes, hex or bigint, not ' + typeof key);
} }
// Useful for curves with cofactor != 1 // Useful for curves with cofactor != 1
if (wrapPrivateKey) num = mod.mod(num, order); if (wrapPrivateKey) num = mod.mod(num, n);
assertGE(num); assertGE(num);
return num; return num;
} }
@ -258,7 +252,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
const { x, y } = p || {}; const { x, y } = p || {};
if (!p || !Fp.isValid(x) || !Fp.isValid(y)) throw new Error('invalid affine point'); if (!p || !Fp.isValid(x) || !Fp.isValid(y)) throw new Error('invalid affine point');
if (p instanceof ProjectivePoint) throw new Error('projective point not allowed'); if (p instanceof ProjectivePoint) throw new Error('projective point not allowed');
const is0 = (i: T) => Fp.equals(i, Fp.ZERO); const is0 = (i: T) => 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) // 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 ProjectivePoint.ZERO; if (is0(x) && is0(y)) return ProjectivePoint.ZERO;
return new ProjectivePoint(x, y, Fp.ONE); return new ProjectivePoint(x, y, Fp.ONE);
@ -319,9 +313,9 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
const { x, y } = this.toAffine(); const { x, y } = this.toAffine();
// Check if x, y are valid field elements // 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'); if (!Fp.isValid(x) || !Fp.isValid(y)) throw new Error('bad point: x or y not FE');
const left = Fp.square(y); // y² const left = Fp.sqr(y); // y²
const right = weierstrassEquation(x); // x³ + ax + b const right = weierstrassEquation(x); // x³ + ax + b
if (!Fp.equals(left, right)) throw new Error('bad point: equation left != right'); 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'); if (!this.isTorsionFree()) throw new Error('bad point: not in prime-order subgroup');
} }
hasEvenY(): boolean { hasEvenY(): boolean {
@ -337,8 +331,8 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
assertPrjPoint(other); assertPrjPoint(other);
const { px: X1, py: Y1, pz: Z1 } = this; const { px: X1, py: Y1, pz: Z1 } = this;
const { px: X2, py: Y2, pz: Z2 } = other; const { px: X2, py: Y2, pz: Z2 } = other;
const U1 = Fp.equals(Fp.mul(X1, Z2), Fp.mul(X2, Z1)); const U1 = Fp.eql(Fp.mul(X1, Z2), Fp.mul(X2, Z1));
const U2 = Fp.equals(Fp.mul(Y1, Z2), Fp.mul(Y2, Z1)); const U2 = Fp.eql(Fp.mul(Y1, Z2), Fp.mul(Y2, Z1));
return U1 && U2; return U1 && U2;
} }
@ -346,7 +340,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
* Flips point to one corresponding to (x, -y) in Affine coordinates. * Flips point to one corresponding to (x, -y) in Affine coordinates.
*/ */
negate(): ProjectivePoint { negate(): ProjectivePoint {
return new ProjectivePoint(this.px, Fp.negate(this.py), this.pz); return new ProjectivePoint(this.px, Fp.neg(this.py), this.pz);
} }
// Renes-Costello-Batina exception-free doubling formula. // Renes-Costello-Batina exception-free doubling formula.
@ -544,12 +538,12 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
const is0 = this.is0(); const is0 = this.is0();
// If invZ was 0, we return zero point. However we still want to execute // 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. // all operations, so we replace invZ with a random number, 1.
if (iz == null) iz = is0 ? Fp.ONE : Fp.invert(z); if (iz == null) iz = is0 ? Fp.ONE : Fp.inv(z);
const ax = Fp.mul(x, iz); const ax = Fp.mul(x, iz);
const ay = Fp.mul(y, iz); const ay = Fp.mul(y, iz);
const zz = Fp.mul(z, iz); const zz = Fp.mul(z, iz);
if (is0) return { x: Fp.ZERO, y: Fp.ZERO }; if (is0) return { x: Fp.ZERO, y: Fp.ZERO };
if (!Fp.equals(zz, Fp.ONE)) throw new Error('invZ was invalid'); if (!Fp.eql(zz, Fp.ONE)) throw new Error('invZ was invalid');
return { x: ax, y: ay }; return { x: ax, y: ay };
} }
isTorsionFree(): boolean { isTorsionFree(): boolean {
@ -571,7 +565,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
} }
toHex(isCompressed = true): string { toHex(isCompressed = true): string {
return bytesToHex(this.toRawBytes(isCompressed)); return ut.bytesToHex(this.toRawBytes(isCompressed));
} }
} }
const _bits = CURVE.nBitLength; const _bits = CURVE.nBitLength;
@ -743,7 +737,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const isYOdd = (y & _1n) === _1n; const isYOdd = (y & _1n) === _1n;
// ECDSA // ECDSA
const isHeadOdd = (head & 1) === 1; const isHeadOdd = (head & 1) === 1;
if (isHeadOdd !== isYOdd) y = Fp.negate(y); if (isHeadOdd !== isYOdd) y = Fp.neg(y);
return { x, y }; return { x, y };
} else if (len === uncompressedLen && head === 0x04) { } else if (len === uncompressedLen && head === 0x04) {
const x = Fp.fromBytes(tail.subarray(0, Fp.BYTES)); const x = Fp.fromBytes(tail.subarray(0, Fp.BYTES));
@ -756,15 +750,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
} }
}, },
}); });
// type Point = typeof ProjectivePoint.BASE; const numToNByteStr = (num: bigint): string =>
ut.bytesToHex(ut.numberToBytesBE(num, CURVE.nByteLength));
// Do we need these functions at all?
function numToField(num: bigint): Uint8Array {
if (typeof num !== 'bigint') throw new Error('Expected bigint');
if (!(_0n <= num && num < Fp.MASK)) throw new Error(`Expected number < 2^${Fp.BYTES * 8}`);
return Fp.toBytes(num);
}
const numToFieldStr = (num: bigint): string => bytesToHex(numToField(num));
function isBiggerThanHalfOrder(number: bigint) { function isBiggerThanHalfOrder(number: bigint) {
const HALF = CURVE_ORDER >> _1n; const HALF = CURVE_ORDER >> _1n;
@ -820,21 +807,18 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const radj = rec === 2 || rec === 3 ? r + N : r; const radj = rec === 2 || rec === 3 ? r + N : r;
if (radj >= Fp.ORDER) throw new Error('recovery id 2 or 3 invalid'); if (radj >= Fp.ORDER) throw new Error('recovery id 2 or 3 invalid');
const prefix = (rec & 1) === 0 ? '02' : '03'; const prefix = (rec & 1) === 0 ? '02' : '03';
const R = Point.fromHex(prefix + numToFieldStr(radj)); const R = Point.fromHex(prefix + numToNByteStr(radj));
const ir = mod.invert(radj, N); // r^-1 const Fn = mod.Fp(N);
const u1 = mod.mod(-h * ir, N); // -hr^-1 const ir = Fn.inv(radj); // r^-1
const u2 = mod.mod(s * ir, N); // sr^-1 const u1 = Fn.mul(-h, ir); // -hr^-1
const u2 = Fn.mul(s, ir); // sr^-1
const Q = Point.BASE.multiplyAndAddUnsafe(R, u1, u2); // (sr^-1)R-(hr^-1)G = -(hr^-1)G + (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 if (!Q) throw new Error('point at infinify'); // unsafe is fine: no priv data leaked
Q.assertValidity(); Q.assertValidity();
return Q; return Q;
} }
/** // Signatures should be low-s, to prevent malleability.
* Default signatures are always low-s, to prevent malleability.
* `sign(lowS: true)` always produces low-s sigs.
* `verify(lowS: true)` always fails for high-s.
*/
hasHighS(): boolean { hasHighS(): boolean {
return isBiggerThanHalfOrder(this.s); return isBiggerThanHalfOrder(this.s);
} }
@ -866,7 +850,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
return ut.hexToBytes(this.toCompactHex()); return ut.hexToBytes(this.toCompactHex());
} }
toCompactHex() { toCompactHex() {
return numToFieldStr(this.r) + numToFieldStr(this.s); return numToNByteStr(this.r) + numToNByteStr(this.s);
} }
} }
@ -885,7 +869,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
* Converts some bytes to a valid private key. Needs at least (nBitLength+64) bytes. * Converts some bytes to a valid private key. Needs at least (nBitLength+64) bytes.
*/ */
hashToPrivateKey: (hash: Hex): Uint8Array => hashToPrivateKey: (hash: Hex): Uint8Array =>
numToField(ut.hashToPrivateScalar(hash, CURVE_ORDER)), ut.numberToBytesBE(ut.hashToPrivateScalar(hash, CURVE_ORDER), CURVE.nByteLength),
/** /**
* Produces cryptographically secure private key from random of size (nBitLength+64) * Produces cryptographically secure private key from random of size (nBitLength+64)
@ -1014,27 +998,26 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const m = h1int; // NOTE: no need to call bits2int second time here, it is inside truncateHash! 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. // Converts signature params into point w r/s, checks result for validity.
function k2sig(kBytes: Uint8Array): Signature | undefined { function k2sig(kBytes: Uint8Array): Signature | undefined {
const { n } = CURVE; // const { n } = CURVE;
// RFC 6979 Section 3.2, step 3: k = bits2int(T) // RFC 6979 Section 3.2, step 3: k = bits2int(T)
const k = bits2int(kBytes); // Cannot use fields methods, since it is group element const k = bits2int(kBytes); // Cannot use fields methods, since it is group element
if (!isWithinCurveOrder(k)) return; if (!isWithinCurveOrder(k)) return;
// Important: all mod() calls in the function must be done over `n` // Important: all mod() calls in the function must be done over `n`
const ik = mod.invert(k, n); const Fn = mod.Fp(CURVE.n);
const q = Point.BASE.multiply(k).toAffine(); const ik = Fn.inv(k); // k^-1 mod n
// r = x mod n const q = Point.BASE.multiply(k).toAffine(); // q = Gk
const r = mod.mod(q.x, n); const r = Fn.create(q.x); // r = q.x mod n
if (r === _0n) return; if (r === _0n) return; // r=0 invalid
// s = (m + dr)/k mod n where x/k == x*inv(k) const s = Fn.mul(ik, Fn.add(m, Fn.mul(d, r))); // s = k^-1(m + rd) mod n
const s = mod.mod(ik * mod.mod(m + mod.mod(d * r, n), n), n); if (s === _0n) return; // s=0 invalid
if (s === _0n) return; let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n); // recovery bit (2 or 3, when q.x > n)
// recovery bit is usually 0 or 1; rarely it's 2 or 3, when q.x > n
let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n);
let normS = s; let normS = s;
if (lowS && isBiggerThanHalfOrder(s)) { if (lowS && isBiggerThanHalfOrder(s)) {
normS = normalizeS(s); // if lowS was passed, ensure s is always
normS = normalizeS(s); // in the bottom half of CURVE.n
recovery ^= 1; recovery ^= 1;
} }
return new Signature(r, normS, recovery); return new Signature(r, normS, recovery); // use normS, not s
} }
return { seed, k2sig }; return { seed, k2sig };
} }
@ -1049,9 +1032,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
* r = x mod n * r = x mod n
* s = (m + dr)/k mod n * s = (m + dr)/k mod n
* ``` * ```
* @param opts `lowS, extraEntropy` * @param opts `lowS, extraEntropy, prehash`
*/ */
// TODO: add opts.prehashed = True, if !opts.prehashed do hash on msg?
function sign(msgHash: Hex, privKey: PrivKey, opts = defaultSigOpts): Signature { function sign(msgHash: Hex, privKey: PrivKey, opts = defaultSigOpts): Signature {
const { seed, k2sig } = prepSig(msgHash, privKey, opts); // Steps A, D of RFC6979 3.2. const { seed, k2sig } = prepSig(msgHash, privKey, opts); // Steps A, D of RFC6979 3.2.
const genUntil = hmacDrbg<Signature>(CURVE.hash.outputLen, CURVE.nByteLength, CURVE.hmac); const genUntil = hmacDrbg<Signature>(CURVE.hash.outputLen, CURVE.nByteLength, CURVE.hmac);
@ -1108,12 +1090,13 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const { n: N } = CURVE; const { n: N } = CURVE;
const { r, s } = _sig; const { r, s } = _sig;
const h = bits2int_modN(msgHash); // Cannot use fields methods, since it is group element const h = bits2int_modN(msgHash); // Cannot use fields methods, since it is group element
const is = mod.invert(s, N); // s^-1 const Fn = mod.Fp(N);
const u1 = mod.mod(h * is, N); // u1 = hs^-1 mod n const is = Fn.inv(s); // s^-1
const u2 = mod.mod(r * is, N); // u2 = rs^-1 mod n const u1 = Fn.mul(h, is); // u1 = hs^-1 mod n
const u2 = Fn.mul(r, is); // u2 = rs^-1 mod n
const R = Point.BASE.multiplyAndAddUnsafe(P, u1, u2)?.toAffine(); // R = u1⋅G + u2⋅P const R = Point.BASE.multiplyAndAddUnsafe(P, u1, u2)?.toAffine(); // R = u1⋅G + u2⋅P
if (!R) return false; if (!R) return false;
const v = mod.mod(R.x, N); const v = Fn.create(R.x);
return v === r; return v === r;
} }
return { return {
@ -1131,7 +1114,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
// Implementation of the Shallue and van de Woestijne method for any Weierstrass curve // Implementation of the Shallue and van de Woestijne method for any Weierstrass curve
// TODO: check if there is a way to merge this with uvRation in Edwards && move to modular? // 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 = True and y = sqrt(u / v) if (u / v) is square in F, and
// b = False and y = sqrt(Z * (u / v)) otherwise. // b = False and y = sqrt(Z * (u / v)) otherwise.
export function SWUFpSqrtRatio<T>(Fp: mod.Field<T>, Z: T) { export function SWUFpSqrtRatio<T>(Fp: mod.Field<T>, Z: T) {
@ -1149,7 +1132,7 @@ export function SWUFpSqrtRatio<T>(Fp: mod.Field<T>, Z: T) {
let sqrtRatio = (u: T, v: T): { isValid: boolean; value: T } => { let sqrtRatio = (u: T, v: T): { isValid: boolean; value: T } => {
let tv1 = c6; // 1. tv1 = c6 let tv1 = c6; // 1. tv1 = c6
let tv2 = Fp.pow(v, c4); // 2. tv2 = v^c4 let tv2 = Fp.pow(v, c4); // 2. tv2 = v^c4
let tv3 = Fp.square(tv2); // 3. tv3 = tv2^2 let tv3 = Fp.sqr(tv2); // 3. tv3 = tv2^2
tv3 = Fp.mul(tv3, v); // 4. tv3 = tv3 * v tv3 = Fp.mul(tv3, v); // 4. tv3 = tv3 * v
let tv5 = Fp.mul(u, tv3); // 5. tv5 = u * tv3 let tv5 = Fp.mul(u, tv3); // 5. tv5 = u * tv3
tv5 = Fp.pow(tv5, c3); // 6. tv5 = tv5^c3 tv5 = Fp.pow(tv5, c3); // 6. tv5 = tv5^c3
@ -1158,7 +1141,7 @@ export function SWUFpSqrtRatio<T>(Fp: mod.Field<T>, Z: T) {
tv3 = Fp.mul(tv5, u); // 9. tv3 = tv5 * u tv3 = Fp.mul(tv5, u); // 9. tv3 = tv5 * u
let tv4 = Fp.mul(tv3, tv2); // 10. tv4 = tv3 * tv2 let tv4 = Fp.mul(tv3, tv2); // 10. tv4 = tv3 * tv2
tv5 = Fp.pow(tv4, c5); // 11. tv5 = tv4^c5 tv5 = Fp.pow(tv4, c5); // 11. tv5 = tv4^c5
let isQR = Fp.equals(tv5, Fp.ONE); // 12. isQR = tv5 == 1 let isQR = Fp.eql(tv5, Fp.ONE); // 12. isQR = tv5 == 1
tv2 = Fp.mul(tv3, c7); // 13. tv2 = tv3 * c7 tv2 = Fp.mul(tv3, c7); // 13. tv2 = tv3 * c7
tv5 = Fp.mul(tv4, tv1); // 14. tv5 = tv4 * tv1 tv5 = Fp.mul(tv4, tv1); // 14. tv5 = tv4 * tv1
tv3 = Fp.cmov(tv2, tv3, isQR); // 15. tv3 = CMOV(tv2, tv3, isQR) tv3 = Fp.cmov(tv2, tv3, isQR); // 15. tv3 = CMOV(tv2, tv3, isQR)
@ -1167,7 +1150,7 @@ export function SWUFpSqrtRatio<T>(Fp: mod.Field<T>, Z: T) {
for (let i = c1; i > 1; i--) { for (let i = c1; i > 1; i--) {
let tv5 = 2n ** (i - 2n); // 18. tv5 = i - 2; 19. tv5 = 2^tv5 let tv5 = 2n ** (i - 2n); // 18. tv5 = i - 2; 19. tv5 = 2^tv5
let tvv5 = Fp.pow(tv4, tv5); // 20. tv5 = tv4^tv5 let tvv5 = Fp.pow(tv4, tv5); // 20. tv5 = tv4^tv5
const e1 = Fp.equals(tvv5, Fp.ONE); // 21. e1 = tv5 == 1 const e1 = Fp.eql(tvv5, Fp.ONE); // 21. e1 = tv5 == 1
tv2 = Fp.mul(tv3, tv1); // 22. tv2 = tv3 * tv1 tv2 = Fp.mul(tv3, tv1); // 22. tv2 = tv3 * tv1
tv1 = Fp.mul(tv1, tv1); // 23. tv1 = tv1 * tv1 tv1 = Fp.mul(tv1, tv1); // 23. tv1 = tv1 * tv1
tvv5 = Fp.mul(tv4, tv1); // 24. tv5 = tv4 * tv1 tvv5 = Fp.mul(tv4, tv1); // 24. tv5 = tv4 * tv1
@ -1179,16 +1162,16 @@ export function SWUFpSqrtRatio<T>(Fp: mod.Field<T>, Z: T) {
if (Fp.ORDER % 4n === 3n) { if (Fp.ORDER % 4n === 3n) {
// sqrt_ratio_3mod4(u, v) // sqrt_ratio_3mod4(u, v)
const c1 = (Fp.ORDER - 3n) / 4n; // 1. c1 = (q - 3) / 4 # Integer arithmetic const c1 = (Fp.ORDER - 3n) / 4n; // 1. c1 = (q - 3) / 4 # Integer arithmetic
const c2 = Fp.sqrt(Fp.negate(Z)); // 2. c2 = sqrt(-Z) const c2 = Fp.sqrt(Fp.neg(Z)); // 2. c2 = sqrt(-Z)
sqrtRatio = (u: T, v: T) => { sqrtRatio = (u: T, v: T) => {
let tv1 = Fp.square(v); // 1. tv1 = v^2 let tv1 = Fp.sqr(v); // 1. tv1 = v^2
const tv2 = Fp.mul(u, v); // 2. tv2 = u * v const tv2 = Fp.mul(u, v); // 2. tv2 = u * v
tv1 = Fp.mul(tv1, tv2); // 3. tv1 = tv1 * tv2 tv1 = Fp.mul(tv1, tv2); // 3. tv1 = tv1 * tv2
let y1 = Fp.pow(tv1, c1); // 4. y1 = tv1^c1 let y1 = Fp.pow(tv1, c1); // 4. y1 = tv1^c1
y1 = Fp.mul(y1, tv2); // 5. y1 = y1 * tv2 y1 = Fp.mul(y1, tv2); // 5. y1 = y1 * tv2
const y2 = Fp.mul(y1, c2); // 6. y2 = y1 * c2 const y2 = Fp.mul(y1, c2); // 6. y2 = y1 * c2
const tv3 = Fp.mul(Fp.square(y1), v); // 7. tv3 = y1^2; 8. tv3 = tv3 * v const tv3 = Fp.mul(Fp.sqr(y1), v); // 7. tv3 = y1^2; 8. tv3 = tv3 * v
const isQR = Fp.equals(tv3, u); // 9. isQR = tv3 == u const isQR = Fp.eql(tv3, u); // 9. isQR = tv3 == u
let y = Fp.cmov(y2, y1, isQR); // 10. y = CMOV(y2, y1, isQR) 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 return { isValid: isQR, value: y }; // 11. return (isQR, y) isQR ? y : y*c2
}; };
@ -1216,16 +1199,16 @@ export function mapToCurveSimpleSWU<T>(
return (u: T): { x: T; y: T } => { return (u: T): { x: T; y: T } => {
// prettier-ignore // prettier-ignore
let tv1, tv2, tv3, tv4, tv5, tv6, x, y; let tv1, tv2, tv3, tv4, tv5, tv6, x, y;
tv1 = Fp.square(u); // 1. tv1 = u^2 tv1 = Fp.sqr(u); // 1. tv1 = u^2
tv1 = Fp.mul(tv1, opts.Z); // 2. tv1 = Z * tv1 tv1 = Fp.mul(tv1, opts.Z); // 2. tv1 = Z * tv1
tv2 = Fp.square(tv1); // 3. tv2 = tv1^2 tv2 = Fp.sqr(tv1); // 3. tv2 = tv1^2
tv2 = Fp.add(tv2, tv1); // 4. tv2 = tv2 + tv1 tv2 = Fp.add(tv2, tv1); // 4. tv2 = tv2 + tv1
tv3 = Fp.add(tv2, Fp.ONE); // 5. tv3 = tv2 + 1 tv3 = Fp.add(tv2, Fp.ONE); // 5. tv3 = tv2 + 1
tv3 = Fp.mul(tv3, opts.B); // 6. tv3 = B * tv3 tv3 = Fp.mul(tv3, opts.B); // 6. tv3 = B * tv3
tv4 = Fp.cmov(opts.Z, Fp.negate(tv2), !Fp.equals(tv2, Fp.ZERO)); // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) 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 tv4 = Fp.mul(tv4, opts.A); // 8. tv4 = A * tv4
tv2 = Fp.square(tv3); // 9. tv2 = tv3^2 tv2 = Fp.sqr(tv3); // 9. tv2 = tv3^2
tv6 = Fp.square(tv4); // 10. tv6 = tv4^2 tv6 = Fp.sqr(tv4); // 10. tv6 = tv4^2
tv5 = Fp.mul(tv6, opts.A); // 11. tv5 = A * tv6 tv5 = Fp.mul(tv6, opts.A); // 11. tv5 = A * tv6
tv2 = Fp.add(tv2, tv5); // 12. tv2 = tv2 + tv5 tv2 = Fp.add(tv2, tv5); // 12. tv2 = tv2 + tv5
tv2 = Fp.mul(tv2, tv3); // 13. tv2 = tv2 * tv3 tv2 = Fp.mul(tv2, tv3); // 13. tv2 = tv2 * tv3
@ -1239,7 +1222,7 @@ export function mapToCurveSimpleSWU<T>(
x = Fp.cmov(x, tv3, isValid); // 21. x = CMOV(x, tv3, is_gx1_square) 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) 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) const e1 = Fp.isOdd!(u) === Fp.isOdd!(y); // 23. e1 = sgn0(u) == sgn0(y)
y = Fp.cmov(Fp.negate(y), y, e1); // 24. y = CMOV(-y, y, e1) y = Fp.cmov(Fp.neg(y), y, e1); // 24. y = CMOV(-y, y, e1)
x = Fp.div(x, tv4); // 25. x = x / tv4 x = Fp.div(x, tv4); // 25. x = x / tv4
return { x, y }; return { x, y };
}; };

@ -99,25 +99,24 @@ const Fp2: mod.Field<Fp2> & Fp2Utils = {
ONE: { c0: Fp.ONE, c1: Fp.ZERO }, ONE: { c0: Fp.ONE, c1: Fp.ZERO },
create: (num) => num, create: (num) => num,
isValid: ({ c0, c1 }) => typeof c0 === 'bigint' && typeof c1 === 'bigint', isValid: ({ c0, c1 }) => typeof c0 === 'bigint' && typeof c1 === 'bigint',
isZero: ({ c0, c1 }) => Fp.isZero(c0) && Fp.isZero(c1), is0: ({ c0, c1 }) => Fp.is0(c0) && Fp.is0(c1),
equals: ({ c0, c1 }: Fp2, { c0: r0, c1: r1 }: Fp2) => Fp.equals(c0, r0) && Fp.equals(c1, r1), eql: ({ c0, c1 }: Fp2, { c0: r0, c1: r1 }: Fp2) => Fp.eql(c0, r0) && Fp.eql(c1, r1),
negate: ({ c0, c1 }) => ({ c0: Fp.negate(c0), c1: Fp.negate(c1) }), neg: ({ c0, c1 }) => ({ c0: Fp.neg(c0), c1: Fp.neg(c1) }),
pow: (num, power) => mod.FpPow(Fp2, num, power), pow: (num, power) => mod.FpPow(Fp2, num, power),
invertBatch: (nums) => mod.FpInvertBatch(Fp2, nums), invertBatch: (nums) => mod.FpInvertBatch(Fp2, nums),
// Normalized // Normalized
add: Fp2Add, add: Fp2Add,
sub: Fp2Subtract, sub: Fp2Subtract,
mul: Fp2Multiply, mul: Fp2Multiply,
square: Fp2Square, sqr: Fp2Square,
// NonNormalized stuff // NonNormalized stuff
addN: Fp2Add, addN: Fp2Add,
subN: Fp2Subtract, subN: Fp2Subtract,
mulN: Fp2Multiply, mulN: Fp2Multiply,
squareN: Fp2Square, sqrN: Fp2Square,
// Why inversion for bigint inside Fp instead of Fp2? it is even used in that context? // Why inversion for bigint inside Fp instead of Fp2? it is even used in that context?
div: (lhs, rhs) => div: (lhs, rhs) => Fp2.mul(lhs, typeof rhs === 'bigint' ? Fp.inv(Fp.create(rhs)) : Fp2.inv(rhs)),
Fp2.mul(lhs, typeof rhs === 'bigint' ? Fp.invert(Fp.create(rhs)) : Fp2.invert(rhs)), inv: ({ c0: a, c1: b }) => {
invert: ({ c0: a, c1: b }) => {
// We wish to find the multiplicative inverse of a nonzero // We wish to find the multiplicative inverse of a nonzero
// element a + bu in Fp2. We leverage an identity // element a + bu in Fp2. We leverage an identity
// //
@ -131,11 +130,11 @@ const Fp2: mod.Field<Fp2> & Fp2Utils = {
// This gives that (a - bu)/(a² + b²) is the inverse // This gives that (a - bu)/(a² + b²) is the inverse
// of (a + bu). Importantly, this can be computing using // of (a + bu). Importantly, this can be computing using
// only a single inversion in Fp. // only a single inversion in Fp.
const factor = Fp.invert(Fp.create(a * a + b * b)); const factor = Fp.inv(Fp.create(a * a + b * b));
return { c0: Fp.mul(factor, Fp.create(a)), c1: Fp.mul(factor, Fp.create(-b)) }; return { c0: Fp.mul(factor, Fp.create(a)), c1: Fp.mul(factor, Fp.create(-b)) };
}, },
sqrt: (num) => { sqrt: (num) => {
if (Fp2.equals(num, Fp2.ZERO)) return Fp2.ZERO; // Algo doesn't handles this case if (Fp2.eql(num, Fp2.ZERO)) return Fp2.ZERO; // Algo doesn't handles this case
// TODO: Optimize this line. It's extremely slow. // TODO: Optimize this line. It's extremely slow.
// Speeding this up would boost aggregateSignatures. // Speeding this up would boost aggregateSignatures.
// https://eprint.iacr.org/2012/685.pdf applicable? // https://eprint.iacr.org/2012/685.pdf applicable?
@ -143,15 +142,15 @@ const Fp2: mod.Field<Fp2> & Fp2Utils = {
// https://github.com/supranational/blst/blob/aae0c7d70b799ac269ff5edf29d8191dbd357876/src/exp2.c#L1 // https://github.com/supranational/blst/blob/aae0c7d70b799ac269ff5edf29d8191dbd357876/src/exp2.c#L1
// Inspired by https://github.com/dalek-cryptography/curve25519-dalek/blob/17698df9d4c834204f83a3574143abacb4fc81a5/src/field.rs#L99 // Inspired by https://github.com/dalek-cryptography/curve25519-dalek/blob/17698df9d4c834204f83a3574143abacb4fc81a5/src/field.rs#L99
const candidateSqrt = Fp2.pow(num, (Fp2.ORDER + 8n) / 16n); const candidateSqrt = Fp2.pow(num, (Fp2.ORDER + 8n) / 16n);
const check = Fp2.div(Fp2.square(candidateSqrt), num); // candidateSqrt.square().div(this); const check = Fp2.div(Fp2.sqr(candidateSqrt), num); // candidateSqrt.square().div(this);
const R = FP2_ROOTS_OF_UNITY; const R = FP2_ROOTS_OF_UNITY;
const divisor = [R[0], R[2], R[4], R[6]].find((r) => Fp2.equals(r, check)); const divisor = [R[0], R[2], R[4], R[6]].find((r) => Fp2.eql(r, check));
if (!divisor) throw new Error('No root'); if (!divisor) throw new Error('No root');
const index = R.indexOf(divisor); const index = R.indexOf(divisor);
const root = R[index / 2]; const root = R[index / 2];
if (!root) throw new Error('Invalid root'); if (!root) throw new Error('Invalid root');
const x1 = Fp2.div(candidateSqrt, root); const x1 = Fp2.div(candidateSqrt, root);
const x2 = Fp2.negate(x1); const x2 = Fp2.neg(x1);
const { re: re1, im: im1 } = Fp2.reim(x1); const { re: re1, im: im1 } = Fp2.reim(x1);
const { re: re2, im: im2 } = Fp2.reim(x2); const { re: re2, im: im2 } = Fp2.reim(x2);
if (im1 > im2 || (im1 === im2 && re1 > re2)) return x1; if (im1 > im2 || (im1 === im2 && re1 > re2)) return x1;
@ -280,18 +279,15 @@ const Fp6Multiply = ({ c0, c1, c2 }: Fp6, rhs: Fp6 | bigint) => {
}; };
}; };
const Fp6Square = ({ c0, c1, c2 }: Fp6) => { const Fp6Square = ({ c0, c1, c2 }: Fp6) => {
let t0 = Fp2.square(c0); // c0² let t0 = Fp2.sqr(c0); // c0²
let t1 = Fp2.mul(Fp2.mul(c0, c1), 2n); // 2 * c0 * c1 let t1 = Fp2.mul(Fp2.mul(c0, c1), 2n); // 2 * c0 * c1
let t3 = Fp2.mul(Fp2.mul(c1, c2), 2n); // 2 * c1 * c2 let t3 = Fp2.mul(Fp2.mul(c1, c2), 2n); // 2 * c1 * c2
let t4 = Fp2.square(c2); // c2² let t4 = Fp2.sqr(c2); // c2²
return { return {
c0: Fp2.add(Fp2.mulByNonresidue(t3), t0), // T3 * (u + 1) + T0 c0: Fp2.add(Fp2.mulByNonresidue(t3), t0), // T3 * (u + 1) + T0
c1: Fp2.add(Fp2.mulByNonresidue(t4), t1), // T4 * (u + 1) + T1 c1: Fp2.add(Fp2.mulByNonresidue(t4), t1), // T4 * (u + 1) + T1
// T1 + (c0 - c1 + c2)² + T3 - T0 - T4 // T1 + (c0 - c1 + c2)² + T3 - T0 - T4
c2: Fp2.sub( c2: Fp2.sub(Fp2.sub(Fp2.add(Fp2.add(t1, Fp2.sqr(Fp2.add(Fp2.sub(c0, c1), c2))), t3), t0), t4),
Fp2.sub(Fp2.add(Fp2.add(t1, Fp2.square(Fp2.add(Fp2.sub(c0, c1), c2))), t3), t0),
t4
),
}; };
}; };
type Fp6Utils = { type Fp6Utils = {
@ -312,35 +308,34 @@ const Fp6: mod.Field<Fp6> & Fp6Utils = {
ONE: { c0: Fp2.ONE, c1: Fp2.ZERO, c2: Fp2.ZERO }, ONE: { c0: Fp2.ONE, c1: Fp2.ZERO, c2: Fp2.ZERO },
create: (num) => num, create: (num) => num,
isValid: ({ c0, c1, c2 }) => Fp2.isValid(c0) && Fp2.isValid(c1) && Fp2.isValid(c2), isValid: ({ c0, c1, c2 }) => Fp2.isValid(c0) && Fp2.isValid(c1) && Fp2.isValid(c2),
isZero: ({ c0, c1, c2 }) => Fp2.isZero(c0) && Fp2.isZero(c1) && Fp2.isZero(c2), is0: ({ c0, c1, c2 }) => Fp2.is0(c0) && Fp2.is0(c1) && Fp2.is0(c2),
negate: ({ c0, c1, c2 }) => ({ c0: Fp2.negate(c0), c1: Fp2.negate(c1), c2: Fp2.negate(c2) }), neg: ({ c0, c1, c2 }) => ({ c0: Fp2.neg(c0), c1: Fp2.neg(c1), c2: Fp2.neg(c2) }),
equals: ({ c0, c1, c2 }, { c0: r0, c1: r1, c2: r2 }) => eql: ({ c0, c1, c2 }, { c0: r0, c1: r1, c2: r2 }) =>
Fp2.equals(c0, r0) && Fp2.equals(c1, r1) && Fp2.equals(c2, r2), Fp2.eql(c0, r0) && Fp2.eql(c1, r1) && Fp2.eql(c2, r2),
sqrt: () => { sqrt: () => {
throw new Error('Not implemented'); throw new Error('Not implemented');
}, },
// Do we need division by bigint at all? Should be done via order: // Do we need division by bigint at all? Should be done via order:
div: (lhs, rhs) => div: (lhs, rhs) => Fp6.mul(lhs, typeof rhs === 'bigint' ? Fp.inv(Fp.create(rhs)) : Fp6.inv(rhs)),
Fp6.mul(lhs, typeof rhs === 'bigint' ? Fp.invert(Fp.create(rhs)) : Fp6.invert(rhs)),
pow: (num, power) => mod.FpPow(Fp6, num, power), pow: (num, power) => mod.FpPow(Fp6, num, power),
invertBatch: (nums) => mod.FpInvertBatch(Fp6, nums), invertBatch: (nums) => mod.FpInvertBatch(Fp6, nums),
// Normalized // Normalized
add: Fp6Add, add: Fp6Add,
sub: Fp6Subtract, sub: Fp6Subtract,
mul: Fp6Multiply, mul: Fp6Multiply,
square: Fp6Square, sqr: Fp6Square,
// NonNormalized stuff // NonNormalized stuff
addN: Fp6Add, addN: Fp6Add,
subN: Fp6Subtract, subN: Fp6Subtract,
mulN: Fp6Multiply, mulN: Fp6Multiply,
squareN: Fp6Square, sqrN: Fp6Square,
invert: ({ c0, c1, c2 }) => { inv: ({ c0, c1, c2 }) => {
let t0 = Fp2.sub(Fp2.square(c0), Fp2.mulByNonresidue(Fp2.mul(c2, c1))); // c0² - c2 * c1 * (u + 1) let t0 = Fp2.sub(Fp2.sqr(c0), Fp2.mulByNonresidue(Fp2.mul(c2, c1))); // c0² - c2 * c1 * (u + 1)
let t1 = Fp2.sub(Fp2.mulByNonresidue(Fp2.square(c2)), Fp2.mul(c0, c1)); // c2² * (u + 1) - c0 * c1 let t1 = Fp2.sub(Fp2.mulByNonresidue(Fp2.sqr(c2)), Fp2.mul(c0, c1)); // c2² * (u + 1) - c0 * c1
let t2 = Fp2.sub(Fp2.square(c1), Fp2.mul(c0, c2)); // c1² - c0 * c2 let t2 = Fp2.sub(Fp2.sqr(c1), Fp2.mul(c0, c2)); // c1² - c0 * c2
// 1/(((c2 * T1 + c1 * T2) * v) + c0 * T0) // 1/(((c2 * T1 + c1 * T2) * v) + c0 * T0)
let t4 = Fp2.invert( let t4 = Fp2.inv(
Fp2.add(Fp2.mulByNonresidue(Fp2.add(Fp2.mul(c2, t1), Fp2.mul(c1, t2))), Fp2.mul(c0, t0)) Fp2.add(Fp2.mulByNonresidue(Fp2.add(Fp2.mul(c2, t1), Fp2.mul(c1, t2))), Fp2.mul(c0, t0))
); );
return { c0: Fp2.mul(t4, t0), c1: Fp2.mul(t4, t1), c2: Fp2.mul(t4, t2) }; return { c0: Fp2.mul(t4, t0), c1: Fp2.mul(t4, t1), c2: Fp2.mul(t4, t2) };
@ -498,11 +493,11 @@ const Fp12Square = ({ c0, c1 }: Fp12) => {
}; // AB + AB }; // AB + AB
}; };
function Fp4Square(a: Fp2, b: Fp2): { first: Fp2; second: Fp2 } { function Fp4Square(a: Fp2, b: Fp2): { first: Fp2; second: Fp2 } {
const a2 = Fp2.square(a); const a2 = Fp2.sqr(a);
const b2 = Fp2.square(b); const b2 = Fp2.sqr(b);
return { return {
first: Fp2.add(Fp2.mulByNonresidue(b2), a2), // b² * Nonresidue + a² first: Fp2.add(Fp2.mulByNonresidue(b2), a2), // b² * Nonresidue + a²
second: Fp2.sub(Fp2.sub(Fp2.square(Fp2.add(a, b)), a2), b2), // (a + b)² - a² - b² second: Fp2.sub(Fp2.sub(Fp2.sqr(Fp2.add(a, b)), a2), b2), // (a + b)² - a² - b²
}; };
} }
type Fp12Utils = { type Fp12Utils = {
@ -525,30 +520,30 @@ const Fp12: mod.Field<Fp12> & Fp12Utils = {
ONE: { c0: Fp6.ONE, c1: Fp6.ZERO }, ONE: { c0: Fp6.ONE, c1: Fp6.ZERO },
create: (num) => num, create: (num) => num,
isValid: ({ c0, c1 }) => Fp6.isValid(c0) && Fp6.isValid(c1), isValid: ({ c0, c1 }) => Fp6.isValid(c0) && Fp6.isValid(c1),
isZero: ({ c0, c1 }) => Fp6.isZero(c0) && Fp6.isZero(c1), is0: ({ c0, c1 }) => Fp6.is0(c0) && Fp6.is0(c1),
negate: ({ c0, c1 }) => ({ c0: Fp6.negate(c0), c1: Fp6.negate(c1) }), neg: ({ c0, c1 }) => ({ c0: Fp6.neg(c0), c1: Fp6.neg(c1) }),
equals: ({ c0, c1 }, { c0: r0, c1: r1 }) => Fp6.equals(c0, r0) && Fp6.equals(c1, r1), eql: ({ c0, c1 }, { c0: r0, c1: r1 }) => Fp6.eql(c0, r0) && Fp6.eql(c1, r1),
sqrt: () => { sqrt: () => {
throw new Error('Not implemented'); throw new Error('Not implemented');
}, },
invert: ({ c0, c1 }) => { inv: ({ c0, c1 }) => {
let t = Fp6.invert(Fp6.sub(Fp6.square(c0), Fp6.mulByNonresidue(Fp6.square(c1)))); // 1 / (c0² - c1² * v) let t = Fp6.inv(Fp6.sub(Fp6.sqr(c0), Fp6.mulByNonresidue(Fp6.sqr(c1)))); // 1 / (c0² - c1² * v)
return { c0: Fp6.mul(c0, t), c1: Fp6.negate(Fp6.mul(c1, t)) }; // ((C0 * T) * T) + (-C1 * T) * w return { c0: Fp6.mul(c0, t), c1: Fp6.neg(Fp6.mul(c1, t)) }; // ((C0 * T) * T) + (-C1 * T) * w
}, },
div: (lhs, rhs) => div: (lhs, rhs) =>
Fp12.mul(lhs, typeof rhs === 'bigint' ? Fp.invert(Fp.create(rhs)) : Fp12.invert(rhs)), Fp12.mul(lhs, typeof rhs === 'bigint' ? Fp.inv(Fp.create(rhs)) : Fp12.inv(rhs)),
pow: (num, power) => mod.FpPow(Fp12, num, power), pow: (num, power) => mod.FpPow(Fp12, num, power),
invertBatch: (nums) => mod.FpInvertBatch(Fp12, nums), invertBatch: (nums) => mod.FpInvertBatch(Fp12, nums),
// Normalized // Normalized
add: Fp12Add, add: Fp12Add,
sub: Fp12Subtract, sub: Fp12Subtract,
mul: Fp12Multiply, mul: Fp12Multiply,
square: Fp12Square, sqr: Fp12Square,
// NonNormalized stuff // NonNormalized stuff
addN: Fp12Add, addN: Fp12Add,
subN: Fp12Subtract, subN: Fp12Subtract,
mulN: Fp12Multiply, mulN: Fp12Multiply,
squareN: Fp12Square, sqrN: Fp12Square,
// Bytes utils // Bytes utils
fromBytes: (b: Uint8Array): Fp12 => { fromBytes: (b: Uint8Array): Fp12 => {
@ -602,7 +597,7 @@ const Fp12: mod.Field<Fp12> & Fp12Utils = {
c0: Fp6.multiplyByFp2(c0, rhs), c0: Fp6.multiplyByFp2(c0, rhs),
c1: Fp6.multiplyByFp2(c1, rhs), c1: Fp6.multiplyByFp2(c1, rhs),
}), }),
conjugate: ({ c0, c1 }): Fp12 => ({ c0, c1: Fp6.negate(c1) }), conjugate: ({ c0, c1 }): Fp12 => ({ c0, c1: Fp6.neg(c1) }),
// A cyclotomic group is a subgroup of Fp^n defined by // A cyclotomic group is a subgroup of Fp^n defined by
// GΦₙ(p) = {α ∈ Fpⁿ : α^Φₙ(p) = 1} // GΦₙ(p) = {α ∈ Fpⁿ : α^Φₙ(p) = 1}
@ -897,7 +892,7 @@ const PSI2_C1 =
0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaacn; 0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaacn;
function psi2(x: Fp2, y: Fp2): [Fp2, Fp2] { function psi2(x: Fp2, y: Fp2): [Fp2, Fp2] {
return [Fp2.mul(x, PSI2_C1), Fp2.negate(y)]; return [Fp2.mul(x, PSI2_C1), Fp2.neg(y)];
} }
function G2psi2(c: ProjConstructor<Fp2>, P: ProjPointType<Fp2>) { function G2psi2(c: ProjConstructor<Fp2>, P: ProjPointType<Fp2>) {
const affine = P.toAffine(); const affine = P.toAffine();
@ -1031,7 +1026,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
let y = Fp.sqrt(right); let y = Fp.sqrt(right);
if (!y) throw new Error('Invalid compressed G1 point'); if (!y) throw new Error('Invalid compressed G1 point');
const aflag = bitGet(compressedValue, C_BIT_POS); const aflag = bitGet(compressedValue, C_BIT_POS);
if ((y * 2n) / P !== aflag) y = Fp.negate(y); if ((y * 2n) / P !== aflag) y = Fp.neg(y);
return { x: Fp.create(x), y: Fp.create(y) }; return { x: Fp.create(x), y: Fp.create(y) };
} else if (bytes.length === 96) { } else if (bytes.length === 96) {
// Check if the infinity flag is set // Check if the infinity flag is set
@ -1149,7 +1144,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
const right = Fp2.add(Fp2.pow(x, 3n), b); // y² = x³ + 4 * (u+1) = x³ + b const right = Fp2.add(Fp2.pow(x, 3n), b); // y² = x³ + 4 * (u+1) = x³ + b
let y = Fp2.sqrt(right); let y = Fp2.sqrt(right);
const Y_bit = y.c1 === 0n ? (y.c0 * 2n) / P : (y.c1 * 2n) / P ? 1n : 0n; const Y_bit = y.c1 === 0n ? (y.c0 * 2n) / P : (y.c1 * 2n) / P ? 1n : 0n;
y = bitS > 0 && Y_bit > 0 ? y : Fp2.negate(y); y = bitS > 0 && Y_bit > 0 ? y : Fp2.neg(y);
return { x, y }; return { x, y };
} else if (bytes.length === 192 && !bitC) { } else if (bytes.length === 192 && !bitC) {
// Check if the infinity flag is set // Check if the infinity flag is set
@ -1216,7 +1211,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
const aflag1 = bitGet(z1, 381); const aflag1 = bitGet(z1, 381);
const isGreater = y1 > 0n && (y1 * 2n) / P !== aflag1; const isGreater = y1 > 0n && (y1 * 2n) / P !== aflag1;
const isZero = y1 === 0n && (y0 * 2n) / P !== aflag1; const isZero = y1 === 0n && (y0 * 2n) / P !== aflag1;
if (isGreater || isZero) y = Fp2.negate(y); if (isGreater || isZero) y = Fp2.neg(y);
const point = bls12_381.G2.ProjectivePoint.fromAffine({ x, y }); const point = bls12_381.G2.ProjectivePoint.fromAffine({ x, y });
// console.log('Signature.decode', point); // console.log('Signature.decode', point);
point.assertValidity(); point.assertValidity();

@ -101,54 +101,54 @@ const Fp = Field(ED25519_P, undefined, true);
const ELL2_C1 = (Fp.ORDER + BigInt(3)) / BigInt(8); // 1. c1 = (q + 3) / 8 # Integer arithmetic const ELL2_C1 = (Fp.ORDER + BigInt(3)) / BigInt(8); // 1. c1 = (q + 3) / 8 # Integer arithmetic
const ELL2_C2 = Fp.pow(_2n, ELL2_C1); // 2. c2 = 2^c1 const ELL2_C2 = Fp.pow(_2n, ELL2_C1); // 2. c2 = 2^c1
const ELL2_C3 = Fp.sqrt(Fp.negate(Fp.ONE)); // 3. c3 = sqrt(-1) const ELL2_C3 = Fp.sqrt(Fp.neg(Fp.ONE)); // 3. c3 = sqrt(-1)
const ELL2_C4 = (Fp.ORDER - BigInt(5)) / BigInt(8); // 4. c4 = (q - 5) / 8 # Integer arithmetic const ELL2_C4 = (Fp.ORDER - BigInt(5)) / BigInt(8); // 4. c4 = (q - 5) / 8 # Integer arithmetic
const ELL2_J = BigInt(486662); const ELL2_J = BigInt(486662);
// prettier-ignore // prettier-ignore
function map_to_curve_elligator2_curve25519(u: bigint) { function map_to_curve_elligator2_curve25519(u: bigint) {
let tv1 = Fp.square(u); // 1. tv1 = u^2 let tv1 = Fp.sqr(u); // 1. tv1 = u^2
tv1 = Fp.mul(tv1, _2n); // 2. tv1 = 2 * tv1 tv1 = Fp.mul(tv1, _2n); // 2. tv1 = 2 * tv1
let xd = Fp.add(tv1, Fp.ONE); // 3. xd = tv1 + 1 # Nonzero: -1 is square (mod p), tv1 is not let xd = Fp.add(tv1, Fp.ONE); // 3. xd = tv1 + 1 # Nonzero: -1 is square (mod p), tv1 is not
let x1n = Fp.negate(ELL2_J); // 4. x1n = -J # x1 = x1n / xd = -J / (1 + 2 * u^2) let x1n = Fp.neg(ELL2_J); // 4. x1n = -J # x1 = x1n / xd = -J / (1 + 2 * u^2)
let tv2 = Fp.square(xd); // 5. tv2 = xd^2 let tv2 = Fp.sqr(xd); // 5. tv2 = xd^2
let gxd = Fp.mul(tv2, xd); // 6. gxd = tv2 * xd # gxd = xd^3 let gxd = Fp.mul(tv2, xd); // 6. gxd = tv2 * xd # gxd = xd^3
let gx1 = Fp.mul(tv1, ELL2_J); // 7. gx1 = J * tv1 # x1n + J * xd let gx1 = Fp.mul(tv1, ELL2_J); // 7. gx1 = J * tv1 # x1n + J * xd
gx1 = Fp.mul(gx1, x1n); // 8. gx1 = gx1 * x1n # x1n^2 + J * x1n * xd gx1 = Fp.mul(gx1, x1n); // 8. gx1 = gx1 * x1n # x1n^2 + J * x1n * xd
gx1 = Fp.add(gx1, tv2); // 9. gx1 = gx1 + tv2 # x1n^2 + J * x1n * xd + xd^2 gx1 = Fp.add(gx1, tv2); // 9. gx1 = gx1 + tv2 # x1n^2 + J * x1n * xd + xd^2
gx1 = Fp.mul(gx1, x1n); // 10. gx1 = gx1 * x1n # x1n^3 + J * x1n^2 * xd + x1n * xd^2 gx1 = Fp.mul(gx1, x1n); // 10. gx1 = gx1 * x1n # x1n^3 + J * x1n^2 * xd + x1n * xd^2
let tv3 = Fp.square(gxd); // 11. tv3 = gxd^2 let tv3 = Fp.sqr(gxd); // 11. tv3 = gxd^2
tv2 = Fp.square(tv3); // 12. tv2 = tv3^2 # gxd^4 tv2 = Fp.sqr(tv3); // 12. tv2 = tv3^2 # gxd^4
tv3 = Fp.mul(tv3, gxd); // 13. tv3 = tv3 * gxd # gxd^3 tv3 = Fp.mul(tv3, gxd); // 13. tv3 = tv3 * gxd # gxd^3
tv3 = Fp.mul(tv3, gx1); // 14. tv3 = tv3 * gx1 # gx1 * gxd^3 tv3 = Fp.mul(tv3, gx1); // 14. tv3 = tv3 * gx1 # gx1 * gxd^3
tv2 = Fp.mul(tv2, tv3); // 15. tv2 = tv2 * tv3 # gx1 * gxd^7 tv2 = Fp.mul(tv2, tv3); // 15. tv2 = tv2 * tv3 # gx1 * gxd^7
let y11 = Fp.pow(tv2, ELL2_C4); // 16. y11 = tv2^c4 # (gx1 * gxd^7)^((p - 5) / 8) let y11 = Fp.pow(tv2, ELL2_C4); // 16. y11 = tv2^c4 # (gx1 * gxd^7)^((p - 5) / 8)
y11 = Fp.mul(y11, tv3); // 17. y11 = y11 * tv3 # gx1*gxd^3*(gx1*gxd^7)^((p-5)/8) y11 = Fp.mul(y11, tv3); // 17. y11 = y11 * tv3 # gx1*gxd^3*(gx1*gxd^7)^((p-5)/8)
let y12 = Fp.mul(y11, ELL2_C3); // 18. y12 = y11 * c3 let y12 = Fp.mul(y11, ELL2_C3); // 18. y12 = y11 * c3
tv2 = Fp.square(y11); // 19. tv2 = y11^2 tv2 = Fp.sqr(y11); // 19. tv2 = y11^2
tv2 = Fp.mul(tv2, gxd); // 20. tv2 = tv2 * gxd tv2 = Fp.mul(tv2, gxd); // 20. tv2 = tv2 * gxd
let e1 = Fp.equals(tv2, gx1); // 21. e1 = tv2 == gx1 let e1 = Fp.eql(tv2, gx1); // 21. e1 = tv2 == gx1
let y1 = Fp.cmov(y12, y11, e1); // 22. y1 = CMOV(y12, y11, e1) # If g(x1) is square, this is its sqrt let y1 = Fp.cmov(y12, y11, e1); // 22. y1 = CMOV(y12, y11, e1) # If g(x1) is square, this is its sqrt
let x2n = Fp.mul(x1n, tv1); // 23. x2n = x1n * tv1 # x2 = x2n / xd = 2 * u^2 * x1n / xd let x2n = Fp.mul(x1n, tv1); // 23. x2n = x1n * tv1 # x2 = x2n / xd = 2 * u^2 * x1n / xd
let y21 = Fp.mul(y11, u); // 24. y21 = y11 * u let y21 = Fp.mul(y11, u); // 24. y21 = y11 * u
y21 = Fp.mul(y21, ELL2_C2); // 25. y21 = y21 * c2 y21 = Fp.mul(y21, ELL2_C2); // 25. y21 = y21 * c2
let y22 = Fp.mul(y21, ELL2_C3); // 26. y22 = y21 * c3 let y22 = Fp.mul(y21, ELL2_C3); // 26. y22 = y21 * c3
let gx2 = Fp.mul(gx1, tv1); // 27. gx2 = gx1 * tv1 # g(x2) = gx2 / gxd = 2 * u^2 * g(x1) let gx2 = Fp.mul(gx1, tv1); // 27. gx2 = gx1 * tv1 # g(x2) = gx2 / gxd = 2 * u^2 * g(x1)
tv2 = Fp.square(y21); // 28. tv2 = y21^2 tv2 = Fp.sqr(y21); // 28. tv2 = y21^2
tv2 = Fp.mul(tv2, gxd); // 29. tv2 = tv2 * gxd tv2 = Fp.mul(tv2, gxd); // 29. tv2 = tv2 * gxd
let e2 = Fp.equals(tv2, gx2); // 30. e2 = tv2 == gx2 let e2 = Fp.eql(tv2, gx2); // 30. e2 = tv2 == gx2
let y2 = Fp.cmov(y22, y21, e2); // 31. y2 = CMOV(y22, y21, e2) # If g(x2) is square, this is its sqrt let y2 = Fp.cmov(y22, y21, e2); // 31. y2 = CMOV(y22, y21, e2) # If g(x2) is square, this is its sqrt
tv2 = Fp.square(y1); // 32. tv2 = y1^2 tv2 = Fp.sqr(y1); // 32. tv2 = y1^2
tv2 = Fp.mul(tv2, gxd); // 33. tv2 = tv2 * gxd tv2 = Fp.mul(tv2, gxd); // 33. tv2 = tv2 * gxd
let e3 = Fp.equals(tv2, gx1); // 34. e3 = tv2 == gx1 let e3 = Fp.eql(tv2, gx1); // 34. e3 = tv2 == gx1
let xn = Fp.cmov(x2n, x1n, e3); // 35. xn = CMOV(x2n, x1n, e3) # If e3, x = x1, else x = x2 let xn = Fp.cmov(x2n, x1n, e3); // 35. xn = CMOV(x2n, x1n, e3) # If e3, x = x1, else x = x2
let y = Fp.cmov(y2, y1, e3); // 36. y = CMOV(y2, y1, e3) # If e3, y = y1, else y = y2 let y = Fp.cmov(y2, y1, e3); // 36. y = CMOV(y2, y1, e3) # If e3, y = y1, else y = y2
let e4 = Fp.isOdd(y); // 37. e4 = sgn0(y) == 1 # Fix sign of y let e4 = Fp.isOdd(y); // 37. e4 = sgn0(y) == 1 # Fix sign of y
y = Fp.cmov(y, Fp.negate(y), e3 !== e4); // 38. y = CMOV(y, -y, e3 XOR e4) y = Fp.cmov(y, Fp.neg(y), e3 !== e4); // 38. y = CMOV(y, -y, e3 XOR e4)
return { xMn: xn, xMd: xd, yMn: y, yMd: 1n }; // 39. return (xn, xd, y, 1) return { xMn: xn, xMd: xd, yMn: y, yMd: 1n }; // 39. return (xn, xd, y, 1)
} }
const ELL2_C1_EDWARDS = FpSqrtEven(Fp, Fp.negate(BigInt(486664))); // sgn0(c1) MUST equal 0 const ELL2_C1_EDWARDS = FpSqrtEven(Fp, Fp.neg(BigInt(486664))); // sgn0(c1) MUST equal 0
function map_to_curve_elligator2_edwards25519(u: bigint) { function map_to_curve_elligator2_edwards25519(u: bigint) {
const { xMn, xMd, yMn, yMd } = map_to_curve_elligator2_curve25519(u); // 1. (xMn, xMd, yMn, yMd) = map_to_curve_elligator2_curve25519(u) const { xMn, xMd, yMn, yMd } = map_to_curve_elligator2_curve25519(u); // 1. (xMn, xMd, yMn, yMd) = map_to_curve_elligator2_curve25519(u)
let xn = Fp.mul(xMn, yMd); // 2. xn = xMn * yMd let xn = Fp.mul(xMn, yMd); // 2. xn = xMn * yMd
@ -157,7 +157,7 @@ function map_to_curve_elligator2_edwards25519(u: bigint) {
let yn = Fp.sub(xMn, xMd); // 5. yn = xMn - xMd let yn = Fp.sub(xMn, xMd); // 5. yn = xMn - xMd
let yd = Fp.add(xMn, xMd); // 6. yd = xMn + xMd # (n / d - 1) / (n / d + 1) = (n - d) / (n + d) let yd = Fp.add(xMn, xMd); // 6. yd = xMn + xMd # (n / d - 1) / (n / d + 1) = (n - d) / (n + d)
let tv1 = Fp.mul(xd, yd); // 7. tv1 = xd * yd let tv1 = Fp.mul(xd, yd); // 7. tv1 = xd * yd
let e = Fp.equals(tv1, Fp.ZERO); // 8. e = tv1 == 0 let e = Fp.eql(tv1, Fp.ZERO); // 8. e = tv1 == 0
xn = Fp.cmov(xn, Fp.ZERO, e); // 9. xn = CMOV(xn, 0, e) xn = Fp.cmov(xn, Fp.ZERO, e); // 9. xn = CMOV(xn, 0, e)
xd = Fp.cmov(xd, Fp.ONE, e); // 10. xd = CMOV(xd, 1, e) xd = Fp.cmov(xd, Fp.ONE, e); // 10. xd = CMOV(xd, 1, e)
yn = Fp.cmov(yn, Fp.ONE, e); // 11. yn = CMOV(yn, 1, e) yn = Fp.cmov(yn, Fp.ONE, e); // 11. yn = CMOV(yn, 1, e)

@ -59,41 +59,41 @@ const Fp = Field(ed448P, 456, true);
const ELL2_C1 = (Fp.ORDER - BigInt(3)) / BigInt(4); // 1. c1 = (q - 3) / 4 # Integer arithmetic const ELL2_C1 = (Fp.ORDER - BigInt(3)) / BigInt(4); // 1. c1 = (q - 3) / 4 # Integer arithmetic
const ELL2_J = BigInt(156326); const ELL2_J = BigInt(156326);
function map_to_curve_elligator2_curve448(u: bigint) { function map_to_curve_elligator2_curve448(u: bigint) {
let tv1 = Fp.square(u); // 1. tv1 = u^2 let tv1 = Fp.sqr(u); // 1. tv1 = u^2
let e1 = Fp.equals(tv1, Fp.ONE); // 2. e1 = tv1 == 1 let e1 = Fp.eql(tv1, Fp.ONE); // 2. e1 = tv1 == 1
tv1 = Fp.cmov(tv1, Fp.ZERO, e1); // 3. tv1 = CMOV(tv1, 0, e1) # If Z * u^2 == -1, set tv1 = 0 tv1 = Fp.cmov(tv1, Fp.ZERO, e1); // 3. tv1 = CMOV(tv1, 0, e1) # If Z * u^2 == -1, set tv1 = 0
let xd = Fp.sub(Fp.ONE, tv1); // 4. xd = 1 - tv1 let xd = Fp.sub(Fp.ONE, tv1); // 4. xd = 1 - tv1
let x1n = Fp.negate(ELL2_J); // 5. x1n = -J let x1n = Fp.neg(ELL2_J); // 5. x1n = -J
let tv2 = Fp.square(xd); // 6. tv2 = xd^2 let tv2 = Fp.sqr(xd); // 6. tv2 = xd^2
let gxd = Fp.mul(tv2, xd); // 7. gxd = tv2 * xd # gxd = xd^3 let gxd = Fp.mul(tv2, xd); // 7. gxd = tv2 * xd # gxd = xd^3
let gx1 = Fp.mul(tv1, Fp.negate(ELL2_J)); // 8. gx1 = -J * tv1 # x1n + J * xd let gx1 = Fp.mul(tv1, Fp.neg(ELL2_J)); // 8. gx1 = -J * tv1 # x1n + J * xd
gx1 = Fp.mul(gx1, x1n); // 9. gx1 = gx1 * x1n # x1n^2 + J * x1n * xd gx1 = Fp.mul(gx1, x1n); // 9. gx1 = gx1 * x1n # x1n^2 + J * x1n * xd
gx1 = Fp.add(gx1, tv2); // 10. gx1 = gx1 + tv2 # x1n^2 + J * x1n * xd + xd^2 gx1 = Fp.add(gx1, tv2); // 10. gx1 = gx1 + tv2 # x1n^2 + J * x1n * xd + xd^2
gx1 = Fp.mul(gx1, x1n); // 11. gx1 = gx1 * x1n # x1n^3 + J * x1n^2 * xd + x1n * xd^2 gx1 = Fp.mul(gx1, x1n); // 11. gx1 = gx1 * x1n # x1n^3 + J * x1n^2 * xd + x1n * xd^2
let tv3 = Fp.square(gxd); // 12. tv3 = gxd^2 let tv3 = Fp.sqr(gxd); // 12. tv3 = gxd^2
tv2 = Fp.mul(gx1, gxd); // 13. tv2 = gx1 * gxd # gx1 * gxd tv2 = Fp.mul(gx1, gxd); // 13. tv2 = gx1 * gxd # gx1 * gxd
tv3 = Fp.mul(tv3, tv2); // 14. tv3 = tv3 * tv2 # gx1 * gxd^3 tv3 = Fp.mul(tv3, tv2); // 14. tv3 = tv3 * tv2 # gx1 * gxd^3
let y1 = Fp.pow(tv3, ELL2_C1); // 15. y1 = tv3^c1 # (gx1 * gxd^3)^((p - 3) / 4) let y1 = Fp.pow(tv3, ELL2_C1); // 15. y1 = tv3^c1 # (gx1 * gxd^3)^((p - 3) / 4)
y1 = Fp.mul(y1, tv2); // 16. y1 = y1 * tv2 # gx1 * gxd * (gx1 * gxd^3)^((p - 3) / 4) y1 = Fp.mul(y1, tv2); // 16. y1 = y1 * tv2 # gx1 * gxd * (gx1 * gxd^3)^((p - 3) / 4)
let x2n = Fp.mul(x1n, Fp.negate(tv1)); // 17. x2n = -tv1 * x1n # x2 = x2n / xd = -1 * u^2 * x1n / xd let x2n = Fp.mul(x1n, Fp.neg(tv1)); // 17. x2n = -tv1 * x1n # x2 = x2n / xd = -1 * u^2 * x1n / xd
let y2 = Fp.mul(y1, u); // 18. y2 = y1 * u let y2 = Fp.mul(y1, u); // 18. y2 = y1 * u
y2 = Fp.cmov(y2, Fp.ZERO, e1); // 19. y2 = CMOV(y2, 0, e1) y2 = Fp.cmov(y2, Fp.ZERO, e1); // 19. y2 = CMOV(y2, 0, e1)
tv2 = Fp.square(y1); // 20. tv2 = y1^2 tv2 = Fp.sqr(y1); // 20. tv2 = y1^2
tv2 = Fp.mul(tv2, gxd); // 21. tv2 = tv2 * gxd tv2 = Fp.mul(tv2, gxd); // 21. tv2 = tv2 * gxd
let e2 = Fp.equals(tv2, gx1); // 22. e2 = tv2 == gx1 let e2 = Fp.eql(tv2, gx1); // 22. e2 = tv2 == gx1
let xn = Fp.cmov(x2n, x1n, e2); // 23. xn = CMOV(x2n, x1n, e2) # If e2, x = x1, else x = x2 let xn = Fp.cmov(x2n, x1n, e2); // 23. xn = CMOV(x2n, x1n, e2) # If e2, x = x1, else x = x2
let y = Fp.cmov(y2, y1, e2); // 24. y = CMOV(y2, y1, e2) # If e2, y = y1, else y = y2 let y = Fp.cmov(y2, y1, e2); // 24. y = CMOV(y2, y1, e2) # If e2, y = y1, else y = y2
let e3 = Fp.isOdd(y); // 25. e3 = sgn0(y) == 1 # Fix sign of y let e3 = Fp.isOdd(y); // 25. e3 = sgn0(y) == 1 # Fix sign of y
y = Fp.cmov(y, Fp.negate(y), e2 !== e3); // 26. y = CMOV(y, -y, e2 XOR e3) y = Fp.cmov(y, Fp.neg(y), e2 !== e3); // 26. y = CMOV(y, -y, e2 XOR e3)
return { xn, xd, yn: y, yd: Fp.ONE }; // 27. return (xn, xd, y, 1) return { xn, xd, yn: y, yd: Fp.ONE }; // 27. return (xn, xd, y, 1)
} }
function map_to_curve_elligator2_edwards448(u: bigint) { function map_to_curve_elligator2_edwards448(u: bigint) {
let { xn, xd, yn, yd } = map_to_curve_elligator2_curve448(u); // 1. (xn, xd, yn, yd) = map_to_curve_elligator2_curve448(u) let { xn, xd, yn, yd } = map_to_curve_elligator2_curve448(u); // 1. (xn, xd, yn, yd) = map_to_curve_elligator2_curve448(u)
let xn2 = Fp.square(xn); // 2. xn2 = xn^2 let xn2 = Fp.sqr(xn); // 2. xn2 = xn^2
let xd2 = Fp.square(xd); // 3. xd2 = xd^2 let xd2 = Fp.sqr(xd); // 3. xd2 = xd^2
let xd4 = Fp.square(xd2); // 4. xd4 = xd2^2 let xd4 = Fp.sqr(xd2); // 4. xd4 = xd2^2
let yn2 = Fp.square(yn); // 5. yn2 = yn^2 let yn2 = Fp.sqr(yn); // 5. yn2 = yn^2
let yd2 = Fp.square(yd); // 6. yd2 = yd^2 let yd2 = Fp.sqr(yd); // 6. yd2 = yd^2
let xEn = Fp.sub(xn2, xd2); // 7. xEn = xn2 - xd2 let xEn = Fp.sub(xn2, xd2); // 7. xEn = xn2 - xd2
let tv2 = Fp.sub(xEn, xd2); // 8. tv2 = xEn - xd2 let tv2 = Fp.sub(xEn, xd2); // 8. tv2 = xEn - xd2
xEn = Fp.mul(xEn, xd2); // 9. xEn = xEn * xd2 xEn = Fp.mul(xEn, xd2); // 9. xEn = xEn * xd2
@ -120,7 +120,7 @@ function map_to_curve_elligator2_edwards448(u: bigint) {
tv4 = Fp.mul(tv4, yd2); // 30. tv4 = tv4 * yd2 tv4 = Fp.mul(tv4, yd2); // 30. tv4 = tv4 * yd2
yEd = Fp.add(yEd, tv4); // 31. yEd = yEd + tv4 yEd = Fp.add(yEd, tv4); // 31. yEd = yEd + tv4
tv1 = Fp.mul(xEd, yEd); // 32. tv1 = xEd * yEd tv1 = Fp.mul(xEd, yEd); // 32. tv1 = xEd * yEd
let e = Fp.equals(tv1, Fp.ZERO); // 33. e = tv1 == 0 let e = Fp.eql(tv1, Fp.ZERO); // 33. e = tv1 == 0
xEn = Fp.cmov(xEn, Fp.ZERO, e); // 34. xEn = CMOV(xEn, 0, e) xEn = Fp.cmov(xEn, Fp.ZERO, e); // 34. xEn = CMOV(xEn, 0, e)
xEd = Fp.cmov(xEd, Fp.ONE, e); // 35. xEd = CMOV(xEd, 1, e) xEd = Fp.cmov(xEd, Fp.ONE, e); // 35. xEd = CMOV(xEd, 1, e)
yEn = Fp.cmov(yEn, Fp.ONE, e); // 36. yEn = CMOV(yEn, 1, e) yEn = Fp.cmov(yEn, Fp.ONE, e); // 36. yEn = CMOV(yEn, 1, e)

@ -29,10 +29,7 @@ const _2n = BigInt(2);
const divNearest = (a: bigint, b: bigint) => (a + b / _2n) / b; const divNearest = (a: bigint, b: bigint) => (a + b / _2n) / b;
/** /**
* Allows to compute square root y 2x faster. * n = n^((p+1)/4) for fields p = 3 mod 4. We unwrap the loop and multiply bit-by-bit.
* To calculate y, we need to exponentiate it to a very big number:
* `y² = x³ + ax + b; y = y² ^ (p+1)/4`
* We are unwrapping the loop and multiplying it bit-by-bit.
* (P+1n/4n).toString(2) would produce bits [223x 1, 0, 22x 1, 4x 0, 11, 00] * (P+1n/4n).toString(2) would produce bits [223x 1, 0, 22x 1, 4x 0, 11, 00]
*/ */
function sqrtMod(y: bigint): bigint { function sqrtMod(y: bigint): bigint {
@ -55,7 +52,7 @@ function sqrtMod(y: bigint): bigint {
const t1 = (pow2(b223, _23n, P) * b22) % P; const t1 = (pow2(b223, _23n, P) * b22) % P;
const t2 = (pow2(t1, _6n, P) * b2) % P; const t2 = (pow2(t1, _6n, P) * b2) % P;
const root = pow2(t2, _2n, P); const root = pow2(t2, _2n, P);
if (!Fp.equals(Fp.square(root), y)) throw new Error('Cannot find square root'); if (!Fp.eql(Fp.sqr(root), y)) throw new Error('Cannot find square root');
return root; return root;
} }
@ -108,7 +105,9 @@ export const secp256k1 = createCurve(
sha256 sha256
); );
// Schnorr // Schnorr signatures are superior to ECDSA from above.
// Below is Schnorr-specific code as per BIP0340.
// https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
const _0n = BigInt(0); const _0n = BigInt(0);
const fe = (x: bigint) => typeof x === 'bigint' && _0n < x && x < secp256k1P; const fe = (x: bigint) => typeof x === 'bigint' && _0n < x && x < secp256k1P;
const ge = (x: bigint) => typeof x === 'bigint' && _0n < x && x < secp256k1N; const ge = (x: bigint) => typeof x === 'bigint' && _0n < x && x < secp256k1N;
@ -130,8 +129,6 @@ export function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array {
} }
return sha256(concatBytes(tagP, ...messages)); return sha256(concatBytes(tagP, ...messages));
} }
// Schnorr signatures are superior to ECDSA from above.
// Below is Schnorr-specific code as per BIP0340.
const tag = taggedHash; const tag = taggedHash;
const toRawX = (point: PointType<bigint>) => point.toRawBytes(true).slice(1); const toRawX = (point: PointType<bigint>) => point.toRawBytes(true).slice(1);
@ -142,22 +139,29 @@ const Gmul = (priv: PrivKey) => _Point.fromPrivateKey(priv);
const GmulAdd = (Q: PointType<bigint>, a: bigint, b: bigint) => const GmulAdd = (Q: PointType<bigint>, a: bigint, b: bigint) =>
_Point.BASE.multiplyAndAddUnsafe(Q, a, b); _Point.BASE.multiplyAndAddUnsafe(Q, a, b);
function schnorrGetScalar(priv: bigint) { function schnorrGetScalar(priv: bigint) {
// Let d' = int(sk)
// Fail if d' = 0 or d' ≥ n
// Let P = d'⋅G
// Let d = d' if has_even_y(P), otherwise let d = n - d' .
const point = Gmul(priv); const point = Gmul(priv);
const scalar = point.hasEvenY() ? priv : modN(-priv); const scalar = point.hasEvenY() ? priv : modN(-priv);
return { point, scalar, x: toRawX(point) }; return { point, scalar, x: toRawX(point) };
} }
function lift_x(x: bigint) { function lift_x(x: bigint): PointType<bigint> {
if (!fe(x)) throw new Error('not fe'); // Fail if x ≥ p. if (!fe(x)) throw new Error('not fe'); // Fail if x ≥ p.
const c = mod(x * x * x + BigInt(7), secp256k1P); // Let c = x3 + 7 mod p. const c = mod(x * x * x + BigInt(7), secp256k1P); // Let c = x³ + 7 mod p.
let y = sqrtMod(c); // Let y = c^(p+1)/4 mod p. let y = sqrtMod(c); // Let y = c^(p+1)/4 mod p.
if (y % 2n !== 0n) y = mod(-y, secp256k1P); // Return the unique point P such that x(P) = x and if (y % 2n !== 0n) y = mod(-y, secp256k1P); // Return the unique point P such that x(P) = x and
const p = new _Point(x, y, _1n); // y(P) = y if y mod 2 = 0 or y(P) = p-y otherwise. const p = new _Point(x, y, _1n); // y(P) = y if y mod 2 = 0 or y(P) = p-y otherwise.
p.assertValidity(); p.assertValidity();
return p; return p;
} }
function challenge(...args: Uint8Array[]) { function challenge(...args: Uint8Array[]): bigint {
return modN(bytesToNum(tag(TAGS.challenge, ...args))); return modN(bytesToNum(tag(TAGS.challenge, ...args)));
} }
function schnorrGetPublicKey(privateKey: PrivKey): Uint8Array {
return toRawX(Gmul(privateKey)); // Let d' = int(sk). Fail if d' = 0 or d' ≥ n. Return bytes(d'⋅G)
}
/** /**
* Synchronously creates Schnorr signature. Improved security: verifies itself before * Synchronously creates Schnorr signature. Improved security: verifies itself before
* producing an output. * producing an output.
@ -169,18 +173,20 @@ function schnorrSign(message: Hex, privateKey: Hex, auxRand: Hex = randomBytes(3
if (message == null) throw new Error(`sign: Expected valid message, not "${message}"`); if (message == null) throw new Error(`sign: Expected valid message, not "${message}"`);
const m = ensureBytes(message); const m = ensureBytes(message);
// checks for isWithinCurveOrder // checks for isWithinCurveOrder
const { x: px, scalar: d } = schnorrGetScalar(bytesToNum(ensureBytes(privateKey, 32))); const { x: px, scalar: d } = schnorrGetScalar(bytesToNum(ensureBytes(privateKey, 32)));
const a = ensureBytes(auxRand, 32); // Auxiliary random data a: a 32-byte array const a = ensureBytes(auxRand, 32); // Auxiliary random data a: a 32-byte array
// TODO: replace with proper xor? // TODO: replace with proper xor?
const t = numTo32b(d ^ bytesToNum(tag(TAGS.aux, a))); // Let t be the byte-wise xor of bytes(d) and hashBIP0340/aux(a) const t = numTo32b(d ^ bytesToNum(tag(TAGS.aux, a))); // Let t be the byte-wise xor of bytes(d) and hash/aux(a)
const rand = tag(TAGS.nonce, t, px, m); // Let rand = hashBIP0340/nonce(t || bytes(P) || m) const rand = tag(TAGS.nonce, t, px, m); // Let rand = hash/nonce(t || bytes(P) || m)
const k_ = modN(bytesToNum(rand)); // Let k' = int(rand) mod n const k_ = modN(bytesToNum(rand)); // Let k' = int(rand) mod n
if (k_ === _0n) throw new Error('sign failed: k is zero'); // Fail if k' = 0. if (k_ === _0n) throw new Error('sign failed: k is zero'); // Fail if k' = 0.
const { point: R, x: rx, scalar: k } = schnorrGetScalar(k_); const { point: R, x: rx, scalar: k } = schnorrGetScalar(k_); // Let R = k'⋅G.
const e = challenge(rx, px, m); const e = challenge(rx, px, m); // Let e = int(hash/challenge(bytes(R) || bytes(P) || m)) mod n.
const sig = new Uint8Array(64); // Let sig = bytes(R) || bytes((k + ed) mod n). const sig = new Uint8Array(64); // Let sig = bytes(R) || bytes((k + ed) mod n).
sig.set(numTo32b(R.px), 0); sig.set(numTo32b(R.px), 0);
sig.set(numTo32b(modN(k + e * d)), 32); sig.set(numTo32b(modN(k + e * d)), 32);
// If Verify(bytes(P), m, sig) (see below) returns failure, abort
if (!schnorrVerify(sig, m, px)) throw new Error('sign: Invalid signature produced'); if (!schnorrVerify(sig, m, px)) throw new Error('sign: Invalid signature produced');
return sig; return sig;
} }
@ -200,7 +206,7 @@ function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean {
const e = challenge(numTo32b(r), toRawX(P), m); // int(challenge(bytes(r)||bytes(P)||m)) mod n const e = challenge(numTo32b(r), toRawX(P), m); // int(challenge(bytes(r)||bytes(P)||m)) mod n
const R = GmulAdd(P, s, modN(-e)); // R = s⋅G - e⋅P const R = GmulAdd(P, s, modN(-e)); // R = s⋅G - e⋅P
if (!R || !R.hasEvenY() || R.toAffine().x !== r) return false; // -eP == (n-e)P if (!R || !R.hasEvenY() || R.toAffine().x !== r) return false; // -eP == (n-e)P
return true; return true; // Fail if is_infinite(R) / not has_even_y(R) / x(R) ≠ r.
} catch (error) { } catch (error) {
return false; return false;
} }
@ -208,7 +214,7 @@ function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean {
export const schnorr = { export const schnorr = {
// Schnorr's pubkey is just `x` of Point (BIP340) // Schnorr's pubkey is just `x` of Point (BIP340)
getPublicKey: (privateKey: PrivKey): Uint8Array => toRawX(Gmul(privateKey)), getPublicKey: schnorrGetPublicKey,
sign: schnorrSign, sign: schnorrSign,
verify: schnorrVerify, verify: schnorrVerify,
}; };

@ -301,7 +301,7 @@ export function _poseidonMDS(Fp: Field<bigint>, name: string, m: number, attempt
} }
if (new Set([...x_values, ...y_values]).size !== 2 * m) if (new Set([...x_values, ...y_values]).size !== 2 * m)
throw new Error('X and Y values are not distinct'); throw new Error('X and Y values are not distinct');
return x_values.map((x) => y_values.map((y) => Fp.invert(Fp.sub(x, y)))); return x_values.map((x) => y_values.map((y) => Fp.inv(Fp.sub(x, y))));
} }
const MDS_SMALL = [ const MDS_SMALL = [