Fp rename. Edwards refactor. Weierstrass Fn instead of mod
This commit is contained in:
parent
3d77422731
commit
be0b2a32a5
19
README.md
19
README.md
@ -458,6 +458,25 @@ verify
|
||||
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
|
||||
|
||||
1. Clone the repository
|
||||
|
@ -129,18 +129,18 @@ export function bls<Fp2, Fp6, Fp12>(
|
||||
let ell_coeff: [Fp2, Fp2, Fp2][] = [];
|
||||
for (let i = BLS_X_LEN - 2; i >= 0; i--) {
|
||||
// Double
|
||||
let t0 = Fp2.square(Ry); // Ry²
|
||||
let t1 = Fp2.square(Rz); // Rz²
|
||||
let t0 = Fp2.sqr(Ry); // Ry²
|
||||
let t1 = Fp2.sqr(Rz); // Rz²
|
||||
let t2 = Fp2.multiplyByB(Fp2.mul(t1, 3n)); // 3 * T1 * B
|
||||
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([
|
||||
Fp2.sub(t2, t0), // T2 - T0
|
||||
Fp2.mul(Fp2.square(Rx), 3n), // 3 * Rx²
|
||||
Fp2.negate(t4), // -T4
|
||||
Fp2.mul(Fp2.sqr(Rx), 3n), // 3 * Rx²
|
||||
Fp2.neg(t4), // -T4
|
||||
]);
|
||||
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
|
||||
if (ut.bitGet(CURVE.x, i)) {
|
||||
// Addition
|
||||
@ -148,13 +148,13 @@ export function bls<Fp2, Fp6, Fp12>(
|
||||
let t1 = Fp2.sub(Rx, Fp2.mul(Qx, Rz)); // Rx - Qx * Rz
|
||||
ell_coeff.push([
|
||||
Fp2.sub(Fp2.mul(t0, Qx), Fp2.mul(t1, Qy)), // T0 * Qx - T1 * Qy
|
||||
Fp2.negate(t0), // -T0
|
||||
Fp2.neg(t0), // -T0
|
||||
t1, // T1
|
||||
]);
|
||||
let t2 = Fp2.square(t1); // T1²
|
||||
let t2 = Fp2.sqr(t1); // T1²
|
||||
let t3 = Fp2.mul(t2, t1); // T2 * T1
|
||||
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
|
||||
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
|
||||
@ -176,7 +176,7 @@ export function bls<Fp2, Fp6, Fp12>(
|
||||
const F = ell[j];
|
||||
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);
|
||||
}
|
||||
@ -300,7 +300,7 @@ export function bls<Fp2, Fp6, Fp12>(
|
||||
const ePHm = pairing(P.negate(), Hm, false);
|
||||
const eGS = pairing(G, S, false);
|
||||
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.
|
||||
@ -365,7 +365,7 @@ export function bls<Fp2, Fp6, Fp12>(
|
||||
paired.push(pairing(G1.ProjectivePoint.BASE.negate(), sig, false));
|
||||
const product = paired.reduce((a, b) => Fp12.mul(a, b), Fp12.ONE);
|
||||
const exp = Fp12.finalExponentiate(product);
|
||||
return Fp12.equals(exp, Fp12.ONE);
|
||||
return Fp12.eql(exp, Fp12.ONE);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
@ -1,17 +1,8 @@
|
||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||
// 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 ut from './utils.js';
|
||||
import { ensureBytes, Hex, PrivKey } from './utils.js';
|
||||
import { ensureBytes, Hex } from './utils.js';
|
||||
import { Group, GroupConstructor, wNAF } from './group.js';
|
||||
|
||||
// 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.
|
||||
export type CurveType = ut.BasicCurve<bigint> & {
|
||||
// Params: a, d
|
||||
a: bigint;
|
||||
d: bigint;
|
||||
// Hashes
|
||||
// The interface, because we need outputLen for DRBG
|
||||
hash: ut.CHash;
|
||||
// CSPRNG
|
||||
randomBytes: (bytesLength?: number) => Uint8Array;
|
||||
// Probably clears bits in a byte array to produce a valid field element
|
||||
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;
|
||||
a: bigint; // curve param a
|
||||
d: bigint; // curve param d
|
||||
hash: ut.FHash; // Hashing
|
||||
randomBytes: (bytesLength?: number) => Uint8Array; // CSPRNG
|
||||
adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array; // clears bits to get valid field elemtn
|
||||
domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array; // Used for hashing
|
||||
uvRatio?: (u: bigint, v: bigint) => { isValid: boolean; value: bigint }; // Ratio √(u/v)
|
||||
preHash?: ut.FHash; // RFC 8032 pre-hashing of messages to sign() / verify()
|
||||
mapToCurve?: (scalar: bigint[]) => AffinePoint; // for hash-to-curve standard
|
||||
};
|
||||
|
||||
function validateOpts(curve: CurveType) {
|
||||
const opts = ut.validateOpts(curve);
|
||||
if (typeof opts.hash !== 'function' || !ut.isPositiveInt(opts.hash.outputLen))
|
||||
throw new Error('Invalid hash function');
|
||||
if (typeof opts.hash !== 'function') throw new Error('Invalid hash function');
|
||||
for (const i of ['a', 'd'] as const) {
|
||||
const val = opts[i];
|
||||
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;
|
||||
fromAffine(p: AffinePoint): ExtPointType;
|
||||
fromHex(hex: Hex): ExtPointType;
|
||||
fromPrivateKey(privateKey: PrivKey): ExtPointType; // TODO: remove
|
||||
fromPrivateKey(privateKey: Hex): ExtPointType; // TODO: remove
|
||||
}
|
||||
|
||||
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 {
|
||||
const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>;
|
||||
const Fp = CURVE.Fp;
|
||||
const CURVE_ORDER = CURVE.n;
|
||||
const MASK = _2n ** BigInt(CURVE.nByteLength * 8);
|
||||
|
||||
// Function overrides
|
||||
const { randomBytes } = CURVE;
|
||||
const modP = Fp.create;
|
||||
const { Fp, n: CURVE_ORDER, preHash, hash: cHash, randomBytes, nByteLength, h: cofactor } = CURVE;
|
||||
const MASK = _2n ** BigInt(nByteLength * 8);
|
||||
const modP = Fp.create; // Function overrides
|
||||
|
||||
// sqrt(u/v)
|
||||
const uvRatio =
|
||||
CURVE.uvRatio ||
|
||||
((u: bigint, v: bigint) => {
|
||||
try {
|
||||
return { isValid: true, value: Fp.sqrt(u * Fp.invert(v)) };
|
||||
return { isValid: true, value: Fp.sqrt(u * Fp.inv(v)) };
|
||||
} catch (e) {
|
||||
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');
|
||||
return data;
|
||||
}); // NOOP
|
||||
function inBig(n: bigint) {
|
||||
return typeof n === 'bigint' && 0n < n;
|
||||
}
|
||||
function assertInMask(n: bigint) {
|
||||
if (inBig(n) && n < MASK) return n;
|
||||
throw new Error(`Expected valid scalar < MASK, 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}`);
|
||||
const inBig = (n: bigint) => typeof n === 'bigint' && 0n < n; // n in [1..]
|
||||
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 assertInRange(n: bigint, max: bigint) {
|
||||
// n in [1..max-1]
|
||||
if (inRange(n, max)) return n;
|
||||
throw new Error(`Expected valid scalar < ${max}, got ${typeof n} ${n}`);
|
||||
}
|
||||
function assertGE0(n: bigint) {
|
||||
// GE = subgroup element, not full group
|
||||
return n === _0n ? n : assertGE(n);
|
||||
// n in [0..CURVE_ORDER-1]
|
||||
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[]>();
|
||||
|
||||
/**
|
||||
* 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)
|
||||
* https://en.wikipedia.org/wiki/Twisted_Edwards_curve#Extended_coordinates
|
||||
*/
|
||||
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).
|
||||
// https://en.wikipedia.org/wiki/Twisted_Edwards_curve#Extended_coordinates
|
||||
class Point implements ExtPointType {
|
||||
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
|
||||
@ -172,10 +139,10 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
||||
readonly ez: bigint,
|
||||
readonly et: bigint
|
||||
) {
|
||||
if (!coord(ex)) throw new Error('x required');
|
||||
if (!coord(ey)) throw new Error('y required');
|
||||
if (!coord(ez)) throw new Error('z required');
|
||||
if (!coord(et)) throw new Error('t required');
|
||||
if (!in0MaskRange(ex)) throw new Error('x required');
|
||||
if (!in0MaskRange(ey)) throw new Error('y required');
|
||||
if (!in0MaskRange(ez)) throw new Error('z required');
|
||||
if (!in0MaskRange(et)) throw new Error('t required');
|
||||
}
|
||||
|
||||
get x(): bigint {
|
||||
@ -186,9 +153,9 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
||||
}
|
||||
|
||||
static fromAffine(p: AffinePoint): Point {
|
||||
if (p instanceof Point) throw new Error('extended point not allowed');
|
||||
const { x, y } = p || {};
|
||||
if (p instanceof Point) throw new Error('fromAffine: extended point not allowed');
|
||||
if (!ut.big(x) || !ut.big(y)) throw new Error('fromAffine: invalid affine point');
|
||||
if (!in0MaskRange(x) || !in0MaskRange(y)) throw new Error('invalid affine point');
|
||||
return new Point(x, y, _1n, modP(x * y));
|
||||
}
|
||||
static normalizeZ(points: Point[]): Point[] {
|
||||
@ -209,7 +176,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
||||
|
||||
// Compare one point to another.
|
||||
equals(other: Point): boolean {
|
||||
assertExtPoint(other);
|
||||
isPoint(other);
|
||||
const { ex: X1, ey: Y1, ez: Z1 } = this;
|
||||
const { ex: X2, ey: Y2, ez: Z2 } = other;
|
||||
const X1Z2 = modP(X1 * Z2);
|
||||
@ -223,8 +190,8 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
||||
return this.equals(Point.ZERO);
|
||||
}
|
||||
|
||||
// Inverses point to one corresponding to (x, -y) in Affine coordinates.
|
||||
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));
|
||||
}
|
||||
|
||||
@ -254,7 +221,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
||||
// https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#addition-add-2008-hwcd
|
||||
// Cost: 9M + 1*a + 1*d + 7add.
|
||||
add(other: Point) {
|
||||
assertExtPoint(other);
|
||||
isPoint(other);
|
||||
const { a, d } = CURVE;
|
||||
const { ex: X1, ey: Y1, ez: Z1, et: T1 } = this;
|
||||
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);
|
||||
}
|
||||
|
||||
// Constant time multiplication.
|
||||
// Uses wNAF method. Windowed method may be 10% faster,
|
||||
// but takes 2x longer to generate and consumes 2x memory.
|
||||
// Constant-time multiplication.
|
||||
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];
|
||||
}
|
||||
|
||||
@ -326,10 +291,10 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
||||
// point with torsion component.
|
||||
// Multiplies point by cofactor and checks if the result is 0.
|
||||
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.
|
||||
isTorsionFree(): boolean {
|
||||
return wnaf.unsafeLadder(this, CURVE_ORDER).is0();
|
||||
@ -340,7 +305,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
||||
toAffine(iz?: bigint): AffinePoint {
|
||||
const { ex: x, ey: y, ez: z } = this;
|
||||
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 ay = modP(y * 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');
|
||||
return { x: ax, y: ay };
|
||||
}
|
||||
|
||||
clearCofactor(): Point {
|
||||
const { h: cofactor } = CURVE;
|
||||
if (cofactor === _1n) return this;
|
||||
@ -356,58 +322,41 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
||||
|
||||
// Converts hash string or Uint8Array to Point.
|
||||
// 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 len = Fp.BYTES;
|
||||
hex = ensureBytes(hex, len);
|
||||
// 1. First, interpret the string as an integer in little-endian
|
||||
// representation. Bit 255 of this number is the least significant
|
||||
// bit of the x-coordinate and denote this value x_0. The
|
||||
// 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;
|
||||
hex = ensureBytes(hex, len); // copy hex to a new array
|
||||
const normed = hex.slice(); // copy again, we'll manipulate it
|
||||
const lastByte = hex[len - 1]; // select last byte
|
||||
normed[len - 1] = lastByte & ~0x80; // clear last bit
|
||||
const y = ut.bytesToNumberLE(normed);
|
||||
|
||||
if (y === _0n) {
|
||||
// y=0 is allowed
|
||||
} else {
|
||||
if (strict) assertFE(y); // strict=true [0..CURVE.Fp.P] (2^255-19 for ed25519)
|
||||
else assertInMask(y); // strict=false [0..MASK] (2^256 for ed25519)
|
||||
// RFC8032 prohibits >= p, but ZIP215 doesn't
|
||||
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) / (d y² + 1) (mod p).
|
||||
// Ed448: x² = (y² - 1) / (d y² - 1) (mod p).
|
||||
// For generic case:
|
||||
// a*x²+y²=1+d*x²*y²
|
||||
// -> y²-1 = d*x²*y²-a*x²
|
||||
// -> 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);
|
||||
// Ed25519: x² = (y²-1)/(dy²+1) mod p. Ed448: x² = (y²-1)/(dy²-1) mod p. Generic case:
|
||||
// ax²+y²=1+dx²y² => y²-1=dx²y²-ax² => y²-1=x²(dy²-a) => x²=(y²-1)/(dy²-a)
|
||||
const y2 = modP(y * y); // denominator is always non-0 mod p.
|
||||
const u = modP(y2 - _1n); // u = y² - 1
|
||||
const v = modP(d * y2 - a); // v = d y² + 1.
|
||||
let { isValid, value: x } = uvRatio(u, v); // √(u/v)
|
||||
if (!isValid) throw new Error('Point.fromHex: invalid y coordinate');
|
||||
// 4. Finally, use the x_0 bit to select the right square root. If
|
||||
// x = 0, and x_0 = 1, decoding fails. Otherwise, if x_0 != x mod
|
||||
// 2, set x <-- p - x. Return the decoded point (x,y).
|
||||
const isXOdd = (x & _1n) === _1n;
|
||||
const isLastByteOdd = (lastByte & 0x80) !== 0;
|
||||
if (isLastByteOdd !== isXOdd) x = modP(-x);
|
||||
const isXOdd = (x & _1n) === _1n; // There are 2 square roots. Use x_0 bit to select proper
|
||||
const isLastByteOdd = (lastByte & 0x80) !== 0; // if x=0 and x_0 = 1, fail
|
||||
if (isLastByteOdd !== isXOdd) x = modP(-x); // if x_0 != x mod 2, set x = p-x
|
||||
return Point.fromAffine({ x, y });
|
||||
}
|
||||
static fromPrivateKey(privateKey: PrivKey) {
|
||||
return getExtendedPublicKey(privateKey).point;
|
||||
static fromPrivateKey(privKey: Hex) {
|
||||
return getExtendedPublicKey(privKey).point;
|
||||
}
|
||||
toRawBytes(): Uint8Array {
|
||||
const { x, y } = this.toAffine();
|
||||
const len = Fp.BYTES;
|
||||
const bytes = ut.numberToBytesLE(y, len); // each y has 2 x values (x, -y)
|
||||
bytes[len - 1] |= x & _1n ? 0x80 : 0; // when compressing, it's enough to store y
|
||||
const bytes = ut.numberToBytesLE(y, Fp.BYTES); // each y has 2 x values (x, -y)
|
||||
bytes[bytes.length - 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
|
||||
}
|
||||
toHex(): string {
|
||||
@ -415,105 +364,80 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
||||
}
|
||||
}
|
||||
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
|
||||
function modnLE(hash: Uint8Array): bigint {
|
||||
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 */
|
||||
function getExtendedPublicKey(key: PrivKey) {
|
||||
const groupLen = CURVE.nByteLength;
|
||||
// Normalize bigint / number / string to Uint8Array
|
||||
const keyb = typeof key === 'bigint' ? ut.numberToBytesLE(assertInMask(key), groupLen) : key;
|
||||
function getExtendedPublicKey(key: Hex) {
|
||||
isHex(key, 'private key');
|
||||
const len = nByteLength;
|
||||
// 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)))
|
||||
const hashed = ensureBytes(CURVE.hash(ensureBytes(keyb, groupLen)), 2 * groupLen);
|
||||
|
||||
// First half's bits are cleared to produce a random field element.
|
||||
const head = adjustScalarBytes(hashed.slice(0, groupLen));
|
||||
// Second half is called key prefix (5.1.6)
|
||||
const prefix = hashed.slice(groupLen, 2 * groupLen);
|
||||
// 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();
|
||||
// Check byte lengths: ensure(64, h(ensure(32, key)))
|
||||
const hashed = ensureBytes(cHash(ensureBytes(key, len)), 2 * len);
|
||||
const head = adjustScalarBytes(hashed.slice(0, len)); // clear first half bits, produce FE
|
||||
const prefix = hashed.slice(len, 2 * len); // second half is called key prefix (5.1.6)
|
||||
const scalar = modnLE(head); // The actual private scalar
|
||||
const point = G.multiply(scalar); // Point on Edwards curve aka public key
|
||||
const pointBytes = point.toRawBytes(); // Uint8Array representation
|
||||
return { head, prefix, scalar, point, pointBytes };
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates ed25519 public key. RFC8032 5.1.5
|
||||
* 1. private key is hashed with sha512, then first 32 bytes are taken from the hash
|
||||
* 2. 3 least significant bits of the first byte are cleared
|
||||
*/
|
||||
function getPublicKey(privateKey: PrivKey): Uint8Array {
|
||||
return getExtendedPublicKey(privateKey).pointBytes;
|
||||
// Calculates EdDSA pub key. RFC8032 5.1.5. Privkey is hashed. Use first half with 3 bits cleared
|
||||
function getPublicKey(privKey: Hex): Uint8Array {
|
||||
return getExtendedPublicKey(privKey).pointBytes;
|
||||
}
|
||||
|
||||
const EMPTY = new Uint8Array();
|
||||
function hashDomainToScalar(message: Uint8Array, context: Hex = EMPTY) {
|
||||
context = ensureBytes(context);
|
||||
return modnLE(CURVE.hash(domain(message, context, !!CURVE.preHash)));
|
||||
// int('LE', SHA512(dom2(F, C) || msgs)) mod N
|
||||
function hashDomainToScalar(context: Hex = new Uint8Array(), ...msgs: Uint8Array[]) {
|
||||
const msg = ut.concatBytes(...msgs);
|
||||
return modnLE(cHash(domain(msg, ensureBytes(context), !!preHash)));
|
||||
}
|
||||
|
||||
/** Signs message with privateKey. RFC8032 5.1.6 */
|
||||
function sign(message: Hex, privateKey: Hex, context?: Hex): Uint8Array {
|
||||
message = ensureBytes(message);
|
||||
if (CURVE.preHash) message = CURVE.preHash(message);
|
||||
const { prefix, scalar, pointBytes } = getExtendedPublicKey(privateKey);
|
||||
const r = hashDomainToScalar(ut.concatBytes(prefix, message), context);
|
||||
const R = G.multiply(r); // R = rG
|
||||
const k = hashDomainToScalar(ut.concatBytes(R.toRawBytes(), pointBytes, message), context); // k = hash(R+P+msg)
|
||||
const s = mod.mod(r + k * scalar, CURVE_ORDER); // s = r + kp
|
||||
function sign(msg: Hex, privKey: Hex, context?: Hex): Uint8Array {
|
||||
isHex(msg, 'message');
|
||||
msg = ensureBytes(msg);
|
||||
if (preHash) msg = preHash(msg); // for ed25519ph etc.
|
||||
const { prefix, scalar, pointBytes } = getExtendedPublicKey(privKey);
|
||||
const r = hashDomainToScalar(context, prefix, msg); // r = dom2(F, C) || prefix || PH(M)
|
||||
const R = G.multiply(r).toRawBytes(); // R = rG
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies EdDSA signature against message and public key.
|
||||
* An extended group equation is checked.
|
||||
* RFC8032 5.1.7
|
||||
* Compliant with ZIP215:
|
||||
* 0 <= sig.R/publicKey < 2**256 (can be >= curve.P)
|
||||
* 0 <= sig.s < l
|
||||
* 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));
|
||||
function verify(sig: Hex, msg: Hex, publicKey: Hex, context?: Hex): boolean {
|
||||
isHex(sig, 'sig');
|
||||
isHex(msg, 'message');
|
||||
const len = Fp.BYTES; // Verifies EdDSA signature against message and public key. RFC8032 5.1.7.
|
||||
sig = ensureBytes(sig, 2 * len); // An extended group equation is checked.
|
||||
msg = ensureBytes(msg); // ZIP215 compliant, which means not fully RFC8032 compliant.
|
||||
if (preHash) msg = preHash(msg); // for ed25519ph, etc
|
||||
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 k = hashDomainToScalar(ut.concatBytes(R.toRawBytes(), A.toRawBytes(), message), context);
|
||||
const kA = A.multiplyUnsafe(k);
|
||||
const RkA = R.add(kA);
|
||||
const k = hashDomainToScalar(context, R.toRawBytes(), A.toRawBytes(), msg);
|
||||
const RkA = R.add(A.multiplyUnsafe(k));
|
||||
// [8][S]B = [8]R + [8][k]A'
|
||||
return RkA.subtract(SB).clearCofactor().equals(Point.ZERO);
|
||||
}
|
||||
|
||||
// Enable precomputes. Slows down first publicKey computation by 20ms.
|
||||
G._setWindowSize(8);
|
||||
G._setWindowSize(8); // Enable precomputes. Slows down first publicKey computation by 20ms.
|
||||
|
||||
const utils = {
|
||||
getExtendedPublicKey,
|
||||
/**
|
||||
* 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()
|
||||
*/
|
||||
// ed25519 private keys are uniform 32b. No need to check for modulo bias, like in secp256k1.
|
||||
randomPrivateKey: (): Uint8Array => randomBytes(Fp.BYTES),
|
||||
|
||||
/**
|
||||
|
@ -17,7 +17,9 @@ export type GroupConstructor<T> = {
|
||||
ZERO: 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) {
|
||||
const constTimeNegate = (condition: boolean, item: T): T => {
|
||||
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 } {
|
||||
// @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
|
||||
let comp = precomputesMap.get(P);
|
||||
if (!comp) {
|
||||
|
@ -41,6 +41,7 @@ export function validateOpts(opts: Opts) {
|
||||
// UTF8 to ui8a
|
||||
// TODO: looks broken, ASCII only, why not TextEncoder/TextDecoder? it is in hashes anyway
|
||||
export function stringToBytes(str: string) {
|
||||
// return new TextEncoder().encode(str);
|
||||
const bytes = new Uint8Array(str.length);
|
||||
for (let i = 0; i < str.length; i++) bytes[i] = str.charCodeAt(i);
|
||||
return bytes;
|
||||
|
@ -92,7 +92,7 @@ export function tonelliShanks(P: bigint) {
|
||||
const p1div4 = (P + _1n) / _4n;
|
||||
return function tonelliFast<T>(Fp: Field<T>, n: T) {
|
||||
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;
|
||||
};
|
||||
}
|
||||
@ -101,24 +101,24 @@ export function tonelliShanks(P: bigint) {
|
||||
const Q1div2 = (Q + _1n) / _2n;
|
||||
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
|
||||
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;
|
||||
// TODO: will fail at Fp2/etc
|
||||
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 b = Fp.pow(n, Q); // first guess at the fudge factor
|
||||
|
||||
while (!Fp.equals(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)
|
||||
while (!Fp.eql(b, Fp.ONE)) {
|
||||
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
|
||||
let m = 1;
|
||||
for (let t2 = Fp.square(b); m < r; m++) {
|
||||
if (Fp.equals(t2, Fp.ONE)) break;
|
||||
t2 = Fp.square(t2); // t2 *= t2
|
||||
for (let t2 = Fp.sqr(b); m < r; m++) {
|
||||
if (Fp.eql(t2, Fp.ONE)) break;
|
||||
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
|
||||
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
|
||||
b = Fp.mul(b, g); // b *= g
|
||||
r = m;
|
||||
@ -142,7 +142,7 @@ export function FpSqrt(P: bigint) {
|
||||
return function sqrt3mod4<T>(Fp: Field<T>, n: T) {
|
||||
const root = Fp.pow(n, p1div4);
|
||||
// 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;
|
||||
};
|
||||
}
|
||||
@ -156,7 +156,7 @@ export function FpSqrt(P: bigint) {
|
||||
const nv = Fp.mul(n, v);
|
||||
const i = Fp.mul(Fp.mul(nv, _2n), v);
|
||||
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;
|
||||
};
|
||||
}
|
||||
@ -206,13 +206,13 @@ export interface Field<T> {
|
||||
// 1-arg
|
||||
create: (num: T) => T;
|
||||
isValid: (num: T) => boolean;
|
||||
isZero: (num: T) => boolean;
|
||||
negate(num: T): T;
|
||||
invert(num: T): T;
|
||||
is0: (num: T) => boolean;
|
||||
neg(num: T): T;
|
||||
inv(num: T): T;
|
||||
sqrt(num: T): T;
|
||||
square(num: T): T;
|
||||
sqr(num: T): T;
|
||||
// 2-args
|
||||
equals(lhs: T, rhs: T): boolean;
|
||||
eql(lhs: T, rhs: T): boolean;
|
||||
add(lhs: T, rhs: T): T;
|
||||
sub(lhs: T, rhs: T): T;
|
||||
mul(lhs: T, rhs: T | bigint): T;
|
||||
@ -222,13 +222,13 @@ export interface Field<T> {
|
||||
addN(lhs: T, rhs: T): T;
|
||||
subN(lhs: T, rhs: T): T;
|
||||
mulN(lhs: T, rhs: T | bigint): T;
|
||||
squareN(num: T): T;
|
||||
sqrN(num: T): T;
|
||||
|
||||
// Optional
|
||||
// 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.
|
||||
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;
|
||||
invertBatch: (lst: T[]) => T[];
|
||||
toBytes(num: T): Uint8Array;
|
||||
@ -238,9 +238,9 @@ export interface Field<T> {
|
||||
}
|
||||
// prettier-ignore
|
||||
const FIELD_FIELDS = [
|
||||
'create', 'isValid', 'isZero', 'negate', 'invert', 'sqrt', 'square',
|
||||
'equals', 'add', 'sub', 'mul', 'pow', 'div',
|
||||
'addN', 'subN', 'mulN', 'squareN'
|
||||
'create', 'isValid', 'is0', 'neg', 'inv', 'sqrt', 'sqr',
|
||||
'eql', 'add', 'sub', 'mul', 'pow', 'div',
|
||||
'addN', 'subN', 'mulN', 'sqrN'
|
||||
] as const;
|
||||
export function validateField<T>(field: Field<T>) {
|
||||
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;
|
||||
while (power > _0n) {
|
||||
if (power & _1n) p = f.mul(p, d);
|
||||
d = f.square(d);
|
||||
d = f.sqr(d);
|
||||
power >>= 1n;
|
||||
}
|
||||
return p;
|
||||
@ -278,15 +278,15 @@ export function FpInvertBatch<T>(f: Field<T>, nums: T[]): T[] {
|
||||
const tmp = new Array(nums.length);
|
||||
// Walk from first to last, multiply them by each other MOD p
|
||||
const lastMultiplied = nums.reduce((acc, num, i) => {
|
||||
if (f.isZero(num)) return acc;
|
||||
if (f.is0(num)) return acc;
|
||||
tmp[i] = acc;
|
||||
return f.mul(acc, num);
|
||||
}, f.ONE);
|
||||
// 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
|
||||
nums.reduceRight((acc, num, i) => {
|
||||
if (f.isZero(num)) return acc;
|
||||
if (f.is0(num)) return acc;
|
||||
tmp[i] = f.mul(acc, tmp[i]);
|
||||
return f.mul(acc, num);
|
||||
}, 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 {
|
||||
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.
|
||||
@ -302,7 +302,7 @@ export function FpIsSquare<T>(f: Field<T>) {
|
||||
const legendreConst = (f.ORDER - _1n) / _2n; // Integer arithmetic
|
||||
return (x: T): boolean => {
|
||||
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}`);
|
||||
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,
|
||||
negate: (num) => mod(-num, ORDER),
|
||||
equals: (lhs, rhs) => lhs === rhs,
|
||||
neg: (num) => mod(-num, ORDER),
|
||||
eql: (lhs, rhs) => lhs === rhs,
|
||||
|
||||
square: (num) => mod(num * num, ORDER),
|
||||
sqr: (num) => mod(num * num, ORDER),
|
||||
add: (lhs, rhs) => mod(lhs + rhs, ORDER),
|
||||
sub: (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),
|
||||
|
||||
// Same as above, but doesn't normalize
|
||||
squareN: (num) => num * num,
|
||||
sqrN: (num) => num * num,
|
||||
addN: (lhs, rhs) => lhs + rhs,
|
||||
subN: (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)),
|
||||
invertBatch: (lst) => FpInvertBatch(f, lst),
|
||||
// TODO: do we really need constant cmov?
|
||||
@ -372,11 +372,11 @@ export function Fp(
|
||||
export function FpSqrtOdd<T>(Fp: Field<T>, elm: T) {
|
||||
if (!Fp.isOdd) throw new Error(`Field doesn't have isOdd`);
|
||||
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) {
|
||||
if (!Fp.isOdd) throw new Error(`Field doesn't have isOdd`);
|
||||
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);
|
||||
let sboxFn = (n: bigint) => mod.FpPow(Fp, n, _sboxPower);
|
||||
// Unwrapped sbox power for common cases (195->142μs)
|
||||
if (sboxPower === 3) sboxFn = (n: bigint) => Fp.mul(Fp.squareN(n), n);
|
||||
else if (sboxPower === 5) sboxFn = (n: bigint) => Fp.mul(Fp.squareN(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.sqrN(Fp.sqrN(n)), n);
|
||||
|
||||
if (opts.roundsFull % 2 !== 0)
|
||||
throw new Error(`Poseidon roundsFull is not even: ${opts.roundsFull}`);
|
||||
|
@ -18,6 +18,7 @@ export type CHash = {
|
||||
outputLen: number;
|
||||
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
|
||||
// But generator can be different (Fp2/Fp6 for bls?)
|
||||
|
@ -1,16 +1,8 @@
|
||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||
// 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 ut from './utils.js';
|
||||
import { bytesToHex, Hex, PrivKey } from './utils.js';
|
||||
import { Hex, PrivKey } from './utils.js';
|
||||
import { Group, GroupConstructor, wNAF } from './group.js';
|
||||
|
||||
type HmacFnSync = (key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array;
|
||||
@ -52,7 +44,7 @@ const DER = {
|
||||
},
|
||||
parseInt(data: Uint8Array): { data: bigint; left: Uint8Array } {
|
||||
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 res = data.subarray(2, len + 2);
|
||||
@ -67,7 +59,7 @@ const DER = {
|
||||
},
|
||||
parseSig(data: Uint8Array): { r: bigint; s: bigint } {
|
||||
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) {
|
||||
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: s, left: rBytesLeft } = DER.parseInt(sBytes);
|
||||
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 };
|
||||
},
|
||||
@ -156,7 +150,7 @@ function validatePointOpts<T>(curve: CurvePointsType<T>) {
|
||||
}
|
||||
const endo = opts.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');
|
||||
}
|
||||
if (
|
||||
@ -194,7 +188,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
||||
*/
|
||||
function weierstrassEquation(x: T): T {
|
||||
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
|
||||
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`
|
||||
*/
|
||||
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);
|
||||
let num: 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);
|
||||
}
|
||||
// Useful for curves with cofactor != 1
|
||||
if (wrapPrivateKey) num = mod.mod(num, order);
|
||||
if (wrapPrivateKey) num = mod.mod(num, n);
|
||||
assertGE(num);
|
||||
return num;
|
||||
}
|
||||
@ -258,7 +252,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
||||
const { x, y } = p || {};
|
||||
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');
|
||||
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)
|
||||
if (is0(x) && is0(y)) return ProjectivePoint.ZERO;
|
||||
return new ProjectivePoint(x, y, Fp.ONE);
|
||||
@ -319,9 +313,9 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
||||
const { x, y } = this.toAffine();
|
||||
// Check if x, y are valid field elements
|
||||
if (!Fp.isValid(x) || !Fp.isValid(y)) throw new Error('bad point: x or y not FE');
|
||||
const left = Fp.square(y); // y²
|
||||
const left = Fp.sqr(y); // y²
|
||||
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');
|
||||
}
|
||||
hasEvenY(): boolean {
|
||||
@ -337,8 +331,8 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
||||
assertPrjPoint(other);
|
||||
const { px: X1, py: Y1, pz: Z1 } = this;
|
||||
const { px: X2, py: Y2, pz: Z2 } = other;
|
||||
const U1 = Fp.equals(Fp.mul(X1, Z2), Fp.mul(X2, Z1));
|
||||
const U2 = Fp.equals(Fp.mul(Y1, Z2), Fp.mul(Y2, Z1));
|
||||
const U1 = Fp.eql(Fp.mul(X1, Z2), Fp.mul(X2, Z1));
|
||||
const U2 = Fp.eql(Fp.mul(Y1, Z2), Fp.mul(Y2, Z1));
|
||||
return U1 && U2;
|
||||
}
|
||||
|
||||
@ -346,7 +340,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
||||
* Flips point to one corresponding to (x, -y) in Affine coordinates.
|
||||
*/
|
||||
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.
|
||||
@ -544,12 +538,12 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
||||
const is0 = this.is0();
|
||||
// If invZ was 0, we return zero point. However we still want to execute
|
||||
// all operations, so we replace invZ with a random number, 1.
|
||||
if (iz == null) iz = is0 ? Fp.ONE : Fp.invert(z);
|
||||
if (iz == null) iz = is0 ? Fp.ONE : Fp.inv(z);
|
||||
const ax = Fp.mul(x, iz);
|
||||
const ay = Fp.mul(y, iz);
|
||||
const zz = Fp.mul(z, iz);
|
||||
if (is0) return { x: Fp.ZERO, y: Fp.ZERO };
|
||||
if (!Fp.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 };
|
||||
}
|
||||
isTorsionFree(): boolean {
|
||||
@ -571,7 +565,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
||||
}
|
||||
|
||||
toHex(isCompressed = true): string {
|
||||
return bytesToHex(this.toRawBytes(isCompressed));
|
||||
return ut.bytesToHex(this.toRawBytes(isCompressed));
|
||||
}
|
||||
}
|
||||
const _bits = CURVE.nBitLength;
|
||||
@ -743,7 +737,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
||||
const isYOdd = (y & _1n) === _1n;
|
||||
// ECDSA
|
||||
const isHeadOdd = (head & 1) === 1;
|
||||
if (isHeadOdd !== isYOdd) y = Fp.negate(y);
|
||||
if (isHeadOdd !== isYOdd) y = Fp.neg(y);
|
||||
return { x, y };
|
||||
} else if (len === uncompressedLen && head === 0x04) {
|
||||
const x = Fp.fromBytes(tail.subarray(0, Fp.BYTES));
|
||||
@ -756,15 +750,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
||||
}
|
||||
},
|
||||
});
|
||||
// type Point = typeof ProjectivePoint.BASE;
|
||||
|
||||
// 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));
|
||||
const numToNByteStr = (num: bigint): string =>
|
||||
ut.bytesToHex(ut.numberToBytesBE(num, CURVE.nByteLength));
|
||||
|
||||
function isBiggerThanHalfOrder(number: bigint) {
|
||||
const HALF = CURVE_ORDER >> _1n;
|
||||
@ -820,21 +807,18 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
||||
const radj = rec === 2 || rec === 3 ? r + N : r;
|
||||
if (radj >= Fp.ORDER) throw new Error('recovery id 2 or 3 invalid');
|
||||
const prefix = (rec & 1) === 0 ? '02' : '03';
|
||||
const R = Point.fromHex(prefix + numToFieldStr(radj));
|
||||
const ir = mod.invert(radj, N); // r^-1
|
||||
const u1 = mod.mod(-h * ir, N); // -hr^-1
|
||||
const u2 = mod.mod(s * ir, N); // sr^-1
|
||||
const R = Point.fromHex(prefix + numToNByteStr(radj));
|
||||
const Fn = mod.Fp(N);
|
||||
const ir = Fn.inv(radj); // r^-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)
|
||||
if (!Q) throw new Error('point at infinify'); // unsafe is fine: no priv data leaked
|
||||
Q.assertValidity();
|
||||
return Q;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
// Signatures should be low-s, to prevent malleability.
|
||||
hasHighS(): boolean {
|
||||
return isBiggerThanHalfOrder(this.s);
|
||||
}
|
||||
@ -866,7 +850,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
||||
return ut.hexToBytes(this.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.
|
||||
*/
|
||||
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)
|
||||
@ -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!
|
||||
// Converts signature params into point w r/s, checks result for validity.
|
||||
function k2sig(kBytes: Uint8Array): Signature | undefined {
|
||||
const { n } = CURVE;
|
||||
// const { n } = CURVE;
|
||||
// RFC 6979 Section 3.2, step 3: k = bits2int(T)
|
||||
const k = bits2int(kBytes); // Cannot use fields methods, since it is group element
|
||||
if (!isWithinCurveOrder(k)) return;
|
||||
// Important: all mod() calls in the function must be done over `n`
|
||||
const ik = mod.invert(k, n);
|
||||
const q = Point.BASE.multiply(k).toAffine();
|
||||
// r = x mod n
|
||||
const r = mod.mod(q.x, n);
|
||||
if (r === _0n) return;
|
||||
// s = (m + dr)/k mod n where x/k == x*inv(k)
|
||||
const s = mod.mod(ik * mod.mod(m + mod.mod(d * r, n), n), n);
|
||||
if (s === _0n) return;
|
||||
// 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);
|
||||
const Fn = mod.Fp(CURVE.n);
|
||||
const ik = Fn.inv(k); // k^-1 mod n
|
||||
const q = Point.BASE.multiply(k).toAffine(); // q = Gk
|
||||
const r = Fn.create(q.x); // r = q.x mod n
|
||||
if (r === _0n) return; // r=0 invalid
|
||||
const s = Fn.mul(ik, Fn.add(m, Fn.mul(d, r))); // s = k^-1(m + rd) mod n
|
||||
if (s === _0n) return; // s=0 invalid
|
||||
let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n); // recovery bit (2 or 3, when q.x > n)
|
||||
let normS = s;
|
||||
if (lowS && isBiggerThanHalfOrder(s)) {
|
||||
normS = normalizeS(s);
|
||||
// if lowS was passed, ensure s is always
|
||||
normS = normalizeS(s); // in the bottom half of CURVE.n
|
||||
recovery ^= 1;
|
||||
}
|
||||
return new Signature(r, normS, recovery);
|
||||
return new Signature(r, normS, recovery); // use normS, not s
|
||||
}
|
||||
return { seed, k2sig };
|
||||
}
|
||||
@ -1049,9 +1032,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
||||
* r = x 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 {
|
||||
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);
|
||||
@ -1108,12 +1090,13 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
||||
const { n: N } = CURVE;
|
||||
const { r, s } = _sig;
|
||||
const h = bits2int_modN(msgHash); // Cannot use fields methods, since it is group element
|
||||
const is = mod.invert(s, N); // s^-1
|
||||
const u1 = mod.mod(h * is, N); // u1 = hs^-1 mod n
|
||||
const u2 = mod.mod(r * is, N); // u2 = rs^-1 mod n
|
||||
const Fn = mod.Fp(N);
|
||||
const is = Fn.inv(s); // s^-1
|
||||
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
|
||||
if (!R) return false;
|
||||
const v = mod.mod(R.x, N);
|
||||
const v = Fn.create(R.x);
|
||||
return v === r;
|
||||
}
|
||||
return {
|
||||
@ -1131,7 +1114,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
||||
|
||||
// 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 = False and y = sqrt(Z * (u / v)) otherwise.
|
||||
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 tv1 = c6; // 1. tv1 = c6
|
||||
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
|
||||
let tv5 = Fp.mul(u, tv3); // 5. tv5 = u * tv3
|
||||
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
|
||||
let tv4 = Fp.mul(tv3, tv2); // 10. tv4 = tv3 * tv2
|
||||
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
|
||||
tv5 = Fp.mul(tv4, tv1); // 14. tv5 = tv4 * tv1
|
||||
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--) {
|
||||
let tv5 = 2n ** (i - 2n); // 18. tv5 = i - 2; 19. tv5 = 2^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
|
||||
tv1 = Fp.mul(tv1, tv1); // 23. tv1 = tv1 * 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) {
|
||||
// sqrt_ratio_3mod4(u, v)
|
||||
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) => {
|
||||
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
|
||||
tv1 = Fp.mul(tv1, tv2); // 3. tv1 = tv1 * tv2
|
||||
let y1 = Fp.pow(tv1, c1); // 4. y1 = tv1^c1
|
||||
y1 = Fp.mul(y1, tv2); // 5. y1 = y1 * tv2
|
||||
const y2 = Fp.mul(y1, c2); // 6. y2 = y1 * c2
|
||||
const tv3 = Fp.mul(Fp.square(y1), v); // 7. tv3 = y1^2; 8. tv3 = tv3 * v
|
||||
const isQR = Fp.equals(tv3, u); // 9. isQR = tv3 == u
|
||||
const tv3 = Fp.mul(Fp.sqr(y1), v); // 7. tv3 = y1^2; 8. tv3 = tv3 * v
|
||||
const isQR = Fp.eql(tv3, u); // 9. isQR = tv3 == u
|
||||
let y = Fp.cmov(y2, y1, isQR); // 10. y = CMOV(y2, y1, isQR)
|
||||
return { isValid: isQR, value: y }; // 11. return (isQR, y) isQR ? y : y*c2
|
||||
};
|
||||
@ -1216,16 +1199,16 @@ export function mapToCurveSimpleSWU<T>(
|
||||
return (u: T): { x: T; y: T } => {
|
||||
// prettier-ignore
|
||||
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
|
||||
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
|
||||
tv3 = Fp.add(tv2, Fp.ONE); // 5. tv3 = tv2 + 1
|
||||
tv3 = Fp.mul(tv3, opts.B); // 6. tv3 = B * tv3
|
||||
tv4 = Fp.cmov(opts.Z, Fp.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
|
||||
tv2 = Fp.square(tv3); // 9. tv2 = tv3^2
|
||||
tv6 = Fp.square(tv4); // 10. tv6 = tv4^2
|
||||
tv2 = Fp.sqr(tv3); // 9. tv2 = tv3^2
|
||||
tv6 = Fp.sqr(tv4); // 10. tv6 = tv4^2
|
||||
tv5 = Fp.mul(tv6, opts.A); // 11. tv5 = A * tv6
|
||||
tv2 = Fp.add(tv2, tv5); // 12. tv2 = tv2 + tv5
|
||||
tv2 = Fp.mul(tv2, tv3); // 13. tv2 = tv2 * tv3
|
||||
@ -1239,7 +1222,7 @@ export function mapToCurveSimpleSWU<T>(
|
||||
x = Fp.cmov(x, tv3, isValid); // 21. x = CMOV(x, tv3, is_gx1_square)
|
||||
y = Fp.cmov(y, value, isValid); // 22. y = CMOV(y, y1, is_gx1_square)
|
||||
const e1 = Fp.isOdd!(u) === Fp.isOdd!(y); // 23. e1 = sgn0(u) == sgn0(y)
|
||||
y = Fp.cmov(Fp.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
|
||||
return { x, y };
|
||||
};
|
||||
|
@ -99,25 +99,24 @@ const Fp2: mod.Field<Fp2> & Fp2Utils = {
|
||||
ONE: { c0: Fp.ONE, c1: Fp.ZERO },
|
||||
create: (num) => num,
|
||||
isValid: ({ c0, c1 }) => typeof c0 === 'bigint' && typeof c1 === 'bigint',
|
||||
isZero: ({ c0, c1 }) => Fp.isZero(c0) && Fp.isZero(c1),
|
||||
equals: ({ c0, c1 }: Fp2, { c0: r0, c1: r1 }: Fp2) => Fp.equals(c0, r0) && Fp.equals(c1, r1),
|
||||
negate: ({ c0, c1 }) => ({ c0: Fp.negate(c0), c1: Fp.negate(c1) }),
|
||||
is0: ({ c0, c1 }) => Fp.is0(c0) && Fp.is0(c1),
|
||||
eql: ({ c0, c1 }: Fp2, { c0: r0, c1: r1 }: Fp2) => Fp.eql(c0, r0) && Fp.eql(c1, r1),
|
||||
neg: ({ c0, c1 }) => ({ c0: Fp.neg(c0), c1: Fp.neg(c1) }),
|
||||
pow: (num, power) => mod.FpPow(Fp2, num, power),
|
||||
invertBatch: (nums) => mod.FpInvertBatch(Fp2, nums),
|
||||
// Normalized
|
||||
add: Fp2Add,
|
||||
sub: Fp2Subtract,
|
||||
mul: Fp2Multiply,
|
||||
square: Fp2Square,
|
||||
sqr: Fp2Square,
|
||||
// NonNormalized stuff
|
||||
addN: Fp2Add,
|
||||
subN: Fp2Subtract,
|
||||
mulN: Fp2Multiply,
|
||||
squareN: Fp2Square,
|
||||
sqrN: Fp2Square,
|
||||
// Why inversion for bigint inside Fp instead of Fp2? it is even used in that context?
|
||||
div: (lhs, rhs) =>
|
||||
Fp2.mul(lhs, typeof rhs === 'bigint' ? Fp.invert(Fp.create(rhs)) : Fp2.invert(rhs)),
|
||||
invert: ({ c0: a, c1: b }) => {
|
||||
div: (lhs, rhs) => Fp2.mul(lhs, typeof rhs === 'bigint' ? Fp.inv(Fp.create(rhs)) : Fp2.inv(rhs)),
|
||||
inv: ({ c0: a, c1: b }) => {
|
||||
// We wish to find the multiplicative inverse of a nonzero
|
||||
// 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
|
||||
// of (a + bu). Importantly, this can be computing using
|
||||
// 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)) };
|
||||
},
|
||||
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.
|
||||
// Speeding this up would boost aggregateSignatures.
|
||||
// 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
|
||||
// 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 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 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');
|
||||
const index = R.indexOf(divisor);
|
||||
const root = R[index / 2];
|
||||
if (!root) throw new Error('Invalid 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: re2, im: im2 } = Fp2.reim(x2);
|
||||
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) => {
|
||||
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 t3 = Fp2.mul(Fp2.mul(c1, c2), 2n); // 2 * c1 * c2
|
||||
let t4 = Fp2.square(c2); // c2²
|
||||
let t4 = Fp2.sqr(c2); // c2²
|
||||
return {
|
||||
c0: Fp2.add(Fp2.mulByNonresidue(t3), t0), // T3 * (u + 1) + T0
|
||||
c1: Fp2.add(Fp2.mulByNonresidue(t4), t1), // T4 * (u + 1) + T1
|
||||
// T1 + (c0 - c1 + c2)² + T3 - T0 - T4
|
||||
c2: Fp2.sub(
|
||||
Fp2.sub(Fp2.add(Fp2.add(t1, Fp2.square(Fp2.add(Fp2.sub(c0, c1), c2))), t3), t0),
|
||||
t4
|
||||
),
|
||||
c2: Fp2.sub(Fp2.sub(Fp2.add(Fp2.add(t1, Fp2.sqr(Fp2.add(Fp2.sub(c0, c1), c2))), t3), t0), t4),
|
||||
};
|
||||
};
|
||||
type Fp6Utils = {
|
||||
@ -312,35 +308,34 @@ const Fp6: mod.Field<Fp6> & Fp6Utils = {
|
||||
ONE: { c0: Fp2.ONE, c1: Fp2.ZERO, c2: Fp2.ZERO },
|
||||
create: (num) => num,
|
||||
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),
|
||||
negate: ({ c0, c1, c2 }) => ({ c0: Fp2.negate(c0), c1: Fp2.negate(c1), c2: Fp2.negate(c2) }),
|
||||
equals: ({ c0, c1, c2 }, { c0: r0, c1: r1, c2: r2 }) =>
|
||||
Fp2.equals(c0, r0) && Fp2.equals(c1, r1) && Fp2.equals(c2, r2),
|
||||
is0: ({ c0, c1, c2 }) => Fp2.is0(c0) && Fp2.is0(c1) && Fp2.is0(c2),
|
||||
neg: ({ c0, c1, c2 }) => ({ c0: Fp2.neg(c0), c1: Fp2.neg(c1), c2: Fp2.neg(c2) }),
|
||||
eql: ({ c0, c1, c2 }, { c0: r0, c1: r1, c2: r2 }) =>
|
||||
Fp2.eql(c0, r0) && Fp2.eql(c1, r1) && Fp2.eql(c2, r2),
|
||||
sqrt: () => {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
// Do we need division by bigint at all? Should be done via order:
|
||||
div: (lhs, rhs) =>
|
||||
Fp6.mul(lhs, typeof rhs === 'bigint' ? Fp.invert(Fp.create(rhs)) : Fp6.invert(rhs)),
|
||||
div: (lhs, rhs) => Fp6.mul(lhs, typeof rhs === 'bigint' ? Fp.inv(Fp.create(rhs)) : Fp6.inv(rhs)),
|
||||
pow: (num, power) => mod.FpPow(Fp6, num, power),
|
||||
invertBatch: (nums) => mod.FpInvertBatch(Fp6, nums),
|
||||
// Normalized
|
||||
add: Fp6Add,
|
||||
sub: Fp6Subtract,
|
||||
mul: Fp6Multiply,
|
||||
square: Fp6Square,
|
||||
sqr: Fp6Square,
|
||||
// NonNormalized stuff
|
||||
addN: Fp6Add,
|
||||
subN: Fp6Subtract,
|
||||
mulN: Fp6Multiply,
|
||||
squareN: Fp6Square,
|
||||
sqrN: Fp6Square,
|
||||
|
||||
invert: ({ c0, c1, c2 }) => {
|
||||
let t0 = Fp2.sub(Fp2.square(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 t2 = Fp2.sub(Fp2.square(c1), Fp2.mul(c0, c2)); // c1² - c0 * c2
|
||||
inv: ({ c0, c1, c2 }) => {
|
||||
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.sqr(c2)), Fp2.mul(c0, c1)); // c2² * (u + 1) - c0 * c1
|
||||
let t2 = Fp2.sub(Fp2.sqr(c1), Fp2.mul(c0, c2)); // c1² - c0 * c2
|
||||
// 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))
|
||||
);
|
||||
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
|
||||
};
|
||||
function Fp4Square(a: Fp2, b: Fp2): { first: Fp2; second: Fp2 } {
|
||||
const a2 = Fp2.square(a);
|
||||
const b2 = Fp2.square(b);
|
||||
const a2 = Fp2.sqr(a);
|
||||
const b2 = Fp2.sqr(b);
|
||||
return {
|
||||
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 = {
|
||||
@ -525,30 +520,30 @@ const Fp12: mod.Field<Fp12> & Fp12Utils = {
|
||||
ONE: { c0: Fp6.ONE, c1: Fp6.ZERO },
|
||||
create: (num) => num,
|
||||
isValid: ({ c0, c1 }) => Fp6.isValid(c0) && Fp6.isValid(c1),
|
||||
isZero: ({ c0, c1 }) => Fp6.isZero(c0) && Fp6.isZero(c1),
|
||||
negate: ({ c0, c1 }) => ({ c0: Fp6.negate(c0), c1: Fp6.negate(c1) }),
|
||||
equals: ({ c0, c1 }, { c0: r0, c1: r1 }) => Fp6.equals(c0, r0) && Fp6.equals(c1, r1),
|
||||
is0: ({ c0, c1 }) => Fp6.is0(c0) && Fp6.is0(c1),
|
||||
neg: ({ c0, c1 }) => ({ c0: Fp6.neg(c0), c1: Fp6.neg(c1) }),
|
||||
eql: ({ c0, c1 }, { c0: r0, c1: r1 }) => Fp6.eql(c0, r0) && Fp6.eql(c1, r1),
|
||||
sqrt: () => {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
invert: ({ c0, c1 }) => {
|
||||
let t = Fp6.invert(Fp6.sub(Fp6.square(c0), Fp6.mulByNonresidue(Fp6.square(c1)))); // 1 / (c0² - c1² * v)
|
||||
return { c0: Fp6.mul(c0, t), c1: Fp6.negate(Fp6.mul(c1, t)) }; // ((C0 * T) * T) + (-C1 * T) * w
|
||||
inv: ({ c0, c1 }) => {
|
||||
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.neg(Fp6.mul(c1, t)) }; // ((C0 * T) * T) + (-C1 * T) * w
|
||||
},
|
||||
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),
|
||||
invertBatch: (nums) => mod.FpInvertBatch(Fp12, nums),
|
||||
// Normalized
|
||||
add: Fp12Add,
|
||||
sub: Fp12Subtract,
|
||||
mul: Fp12Multiply,
|
||||
square: Fp12Square,
|
||||
sqr: Fp12Square,
|
||||
// NonNormalized stuff
|
||||
addN: Fp12Add,
|
||||
subN: Fp12Subtract,
|
||||
mulN: Fp12Multiply,
|
||||
squareN: Fp12Square,
|
||||
sqrN: Fp12Square,
|
||||
|
||||
// Bytes utils
|
||||
fromBytes: (b: Uint8Array): Fp12 => {
|
||||
@ -602,7 +597,7 @@ const Fp12: mod.Field<Fp12> & Fp12Utils = {
|
||||
c0: Fp6.multiplyByFp2(c0, 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
|
||||
// GΦₙ(p) = {α ∈ Fpⁿ : α^Φₙ(p) = 1}
|
||||
@ -897,7 +892,7 @@ const PSI2_C1 =
|
||||
0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaacn;
|
||||
|
||||
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>) {
|
||||
const affine = P.toAffine();
|
||||
@ -1031,7 +1026,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
|
||||
let y = Fp.sqrt(right);
|
||||
if (!y) throw new Error('Invalid compressed G1 point');
|
||||
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) };
|
||||
} else if (bytes.length === 96) {
|
||||
// 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
|
||||
let y = Fp2.sqrt(right);
|
||||
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 };
|
||||
} else if (bytes.length === 192 && !bitC) {
|
||||
// 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 isGreater = y1 > 0n && (y1 * 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 });
|
||||
// console.log('Signature.decode', point);
|
||||
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_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_J = BigInt(486662);
|
||||
|
||||
// prettier-ignore
|
||||
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
|
||||
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 tv2 = Fp.square(xd); // 5. tv2 = xd^2
|
||||
let x1n = Fp.neg(ELL2_J); // 4. x1n = -J # x1 = x1n / xd = -J / (1 + 2 * u^2)
|
||||
let tv2 = Fp.sqr(xd); // 5. tv2 = xd^2
|
||||
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
|
||||
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.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
|
||||
tv2 = Fp.square(tv3); // 12. tv2 = tv3^2 # gxd^4
|
||||
let tv3 = Fp.sqr(gxd); // 11. tv3 = gxd^2
|
||||
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, gx1); // 14. tv3 = tv3 * gx1 # gx1 * gxd^3
|
||||
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)
|
||||
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
|
||||
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
|
||||
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 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
|
||||
y21 = Fp.mul(y21, ELL2_C2); // 25. y21 = y21 * c2
|
||||
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)
|
||||
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
|
||||
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
|
||||
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
|
||||
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 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
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
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
|
||||
@ -157,7 +157,7 @@ function map_to_curve_elligator2_edwards25519(u: bigint) {
|
||||
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 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)
|
||||
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)
|
||||
|
32
src/ed448.ts
32
src/ed448.ts
@ -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_J = BigInt(156326);
|
||||
function map_to_curve_elligator2_curve448(u: bigint) {
|
||||
let tv1 = Fp.square(u); // 1. tv1 = u^2
|
||||
let e1 = Fp.equals(tv1, Fp.ONE); // 2. e1 = tv1 == 1
|
||||
let tv1 = Fp.sqr(u); // 1. tv1 = u^2
|
||||
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
|
||||
let xd = Fp.sub(Fp.ONE, tv1); // 4. xd = 1 - tv1
|
||||
let x1n = Fp.negate(ELL2_J); // 5. x1n = -J
|
||||
let tv2 = Fp.square(xd); // 6. tv2 = xd^2
|
||||
let x1n = Fp.neg(ELL2_J); // 5. x1n = -J
|
||||
let tv2 = Fp.sqr(xd); // 6. tv2 = xd^2
|
||||
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.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
|
||||
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
|
||||
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)
|
||||
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
|
||||
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
|
||||
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 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
|
||||
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)
|
||||
}
|
||||
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 xn2 = Fp.square(xn); // 2. xn2 = xn^2
|
||||
let xd2 = Fp.square(xd); // 3. xd2 = xd^2
|
||||
let xd4 = Fp.square(xd2); // 4. xd4 = xd2^2
|
||||
let yn2 = Fp.square(yn); // 5. yn2 = yn^2
|
||||
let yd2 = Fp.square(yd); // 6. yd2 = yd^2
|
||||
let xn2 = Fp.sqr(xn); // 2. xn2 = xn^2
|
||||
let xd2 = Fp.sqr(xd); // 3. xd2 = xd^2
|
||||
let xd4 = Fp.sqr(xd2); // 4. xd4 = xd2^2
|
||||
let yn2 = Fp.sqr(yn); // 5. yn2 = yn^2
|
||||
let yd2 = Fp.sqr(yd); // 6. yd2 = yd^2
|
||||
let xEn = Fp.sub(xn2, xd2); // 7. xEn = xn2 - xd2
|
||||
let tv2 = Fp.sub(xEn, xd2); // 8. tv2 = 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
|
||||
yEd = Fp.add(yEd, tv4); // 31. yEd = yEd + tv4
|
||||
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)
|
||||
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)
|
||||
|
@ -29,10 +29,7 @@ const _2n = BigInt(2);
|
||||
const divNearest = (a: bigint, b: bigint) => (a + b / _2n) / b;
|
||||
|
||||
/**
|
||||
* Allows to compute square root √y 2x faster.
|
||||
* To calculate √y, we need to exponentiate it to a very big number:
|
||||
* `y² = x³ + ax + b; y = y² ^ (p+1)/4`
|
||||
* We are unwrapping the loop and multiplying it bit-by-bit.
|
||||
* √n = n^((p+1)/4) for fields p = 3 mod 4. We unwrap the loop and multiply bit-by-bit.
|
||||
* (P+1n/4n).toString(2) would produce bits [223x 1, 0, 22x 1, 4x 0, 11, 00]
|
||||
*/
|
||||
function sqrtMod(y: bigint): bigint {
|
||||
@ -55,7 +52,7 @@ function sqrtMod(y: bigint): bigint {
|
||||
const t1 = (pow2(b223, _23n, P) * b22) % P;
|
||||
const t2 = (pow2(t1, _6n, P) * b2) % 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;
|
||||
}
|
||||
|
||||
@ -108,7 +105,9 @@ export const secp256k1 = createCurve(
|
||||
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 fe = (x: bigint) => typeof x === 'bigint' && _0n < x && x < secp256k1P;
|
||||
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));
|
||||
}
|
||||
// Schnorr signatures are superior to ECDSA from above.
|
||||
// Below is Schnorr-specific code as per BIP0340.
|
||||
|
||||
const tag = taggedHash;
|
||||
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) =>
|
||||
_Point.BASE.multiplyAndAddUnsafe(Q, a, b);
|
||||
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 scalar = point.hasEvenY() ? priv : modN(-priv);
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
p.assertValidity();
|
||||
return p;
|
||||
}
|
||||
function challenge(...args: Uint8Array[]) {
|
||||
function challenge(...args: Uint8Array[]): bigint {
|
||||
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
|
||||
* 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}"`);
|
||||
const m = ensureBytes(message);
|
||||
// checks for isWithinCurveOrder
|
||||
|
||||
const { x: px, scalar: d } = schnorrGetScalar(bytesToNum(ensureBytes(privateKey, 32)));
|
||||
const a = ensureBytes(auxRand, 32); // Auxiliary random data a: a 32-byte array
|
||||
// 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 rand = tag(TAGS.nonce, t, px, m); // Let rand = hashBIP0340/nonce(t || bytes(P) || m)
|
||||
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 = hash/nonce(t || bytes(P) || m)
|
||||
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.
|
||||
const { point: R, x: rx, scalar: k } = schnorrGetScalar(k_);
|
||||
const e = challenge(rx, px, m);
|
||||
const { point: R, x: rx, scalar: k } = schnorrGetScalar(k_); // Let R = k'⋅G.
|
||||
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).
|
||||
sig.set(numTo32b(R.px), 0);
|
||||
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');
|
||||
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 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
|
||||
return true;
|
||||
return true; // Fail if is_infinite(R) / not has_even_y(R) / x(R) ≠ r.
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
@ -208,7 +214,7 @@ function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean {
|
||||
|
||||
export const schnorr = {
|
||||
// Schnorr's pubkey is just `x` of Point (BIP340)
|
||||
getPublicKey: (privateKey: PrivKey): Uint8Array => toRawX(Gmul(privateKey)),
|
||||
getPublicKey: schnorrGetPublicKey,
|
||||
sign: schnorrSign,
|
||||
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)
|
||||
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 = [
|
||||
|
Loading…
Reference in New Issue
Block a user