From f14b8d2be57a1cccba9b2523b552b7f5cb451a74 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Tue, 24 Jan 2023 23:07:25 +0000 Subject: [PATCH] More AffinePoint fixes --- benchmark/index.js | 16 +- src/abstract/bls.ts | 49 ++- src/abstract/edwards.ts | 47 +-- src/abstract/weierstrass.ts | 141 ++++----- src/bls12-381.ts | 50 ++-- src/jubjub.ts | 2 +- src/stark.ts | 17 +- test/bls12-381.test.js | 583 ++++++++++++++++++------------------ test/secp256k1.test.js | 10 +- 9 files changed, 470 insertions(+), 445 deletions(-) diff --git a/benchmark/index.js b/benchmark/index.js index 14e492a..23082bb 100644 --- a/benchmark/index.js +++ b/benchmark/index.js @@ -286,11 +286,11 @@ export const CURVES = { }, noble: () => { p1 = - bls.G1.Point.BASE.multiply( + bls.G1.ProjectivePoint.BASE.multiply( 0x28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4cn ); p2 = - bls.G2.Point.BASE.multiply( + bls.G2.ProjectivePoint.BASE.multiply( 0x28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4dn ); bls.pairing(p1, p2); @@ -334,12 +334,12 @@ export const CURVES = { 'hashToCurve/G1': { samples: 500, old: () => old_bls.PointG1.hashToCurve('abcd'), - noble: () => bls.G1.Point.hashToCurve('abcd'), + noble: () => bls.hashToCurve.G1.hashToCurve('abcd'), }, 'hashToCurve/G2': { samples: 200, old: () => old_bls.PointG2.hashToCurve('abcd'), - noble: () => bls.G2.Point.hashToCurve('abcd'), + noble: () => bls.hashToCurve.G2.hashToCurve('abcd'), }, // SLOW PART // Requires points which we cannot init before (data fn same for all) @@ -353,22 +353,22 @@ export const CURVES = { 'aggregatePublicKeys/32': { samples: 50, old: ({ pub32 }) => old_bls.aggregatePublicKeys(pub32.map(old_bls.PointG1.fromHex)), - noble: ({ pub32 }) => bls.aggregatePublicKeys(pub32.map(bls.G1.Point.fromHex)), + noble: ({ pub32 }) => bls.aggregatePublicKeys(pub32.map(bls.G1.ProjectivePoint.fromHex)), }, 'aggregatePublicKeys/128': { samples: 20, old: ({ pub128 }) => old_bls.aggregatePublicKeys(pub128.map(old_bls.PointG1.fromHex)), - noble: ({ pub128 }) => bls.aggregatePublicKeys(pub128.map(bls.G1.Point.fromHex)), + noble: ({ pub128 }) => bls.aggregatePublicKeys(pub128.map(bls.G1.ProjectivePoint.fromHex)), }, 'aggregatePublicKeys/512': { samples: 10, old: ({ pub512 }) => old_bls.aggregatePublicKeys(pub512.map(old_bls.PointG1.fromHex)), - noble: ({ pub512 }) => bls.aggregatePublicKeys(pub512.map(bls.G1.Point.fromHex)), + noble: ({ pub512 }) => bls.aggregatePublicKeys(pub512.map(bls.G1.ProjectivePoint.fromHex)), }, 'aggregatePublicKeys/2048': { samples: 5, old: ({ pub2048 }) => old_bls.aggregatePublicKeys(pub2048.map(old_bls.PointG1.fromHex)), - noble: ({ pub2048 }) => bls.aggregatePublicKeys(pub2048.map(bls.G1.Point.fromHex)), + noble: ({ pub2048 }) => bls.aggregatePublicKeys(pub2048.map(bls.G1.ProjectivePoint.fromHex)), }, 'aggregateSignatures/8': { samples: 50, diff --git a/src/abstract/bls.ts b/src/abstract/bls.ts index a11f43f..90a8089 100644 --- a/src/abstract/bls.ts +++ b/src/abstract/bls.ts @@ -21,6 +21,7 @@ import { ProjectivePointType as PPointType, CurvePointsRes, weierstrassPoints, + AffinePoint, } from './weierstrass.js'; type Fp = bigint; // Can be different field? @@ -72,7 +73,7 @@ export type CurveFn = { G2: CurvePointsRes; Signature: SignatureCoder; millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12; - calcPairingPrecomputes: (x: Fp2, y: Fp2) => [Fp2, Fp2, Fp2][]; + calcPairingPrecomputes: (p: AffinePoint) => [Fp2, Fp2, Fp2][]; // prettier-ignore hashToCurve: { G1: ReturnType<(typeof htf.hashToCurve)>, @@ -119,7 +120,8 @@ export function bls( // Pre-compute coefficients for sparse multiplication // Point addition and point double calculations is reused for coefficients - function calcPairingPrecomputes(x: Fp2, y: Fp2) { + function calcPairingPrecomputes(p: AffinePoint) { + const { x, y } = p; // prettier-ignore const Qx = x, Qy = y, Qz = Fp2.ONE; // prettier-ignore @@ -212,19 +214,15 @@ export function bls( function pairingPrecomputes(point: G2): [Fp2, Fp2, Fp2][] { const p = point as G2 & withPairingPrecomputes; if (p._PPRECOMPUTES) return p._PPRECOMPUTES; - p._PPRECOMPUTES = calcPairingPrecomputes(p.x, p.y); + p._PPRECOMPUTES = calcPairingPrecomputes(point.toAffine()); return p._PPRECOMPUTES; } - function clearPairingPrecomputes(point: G2) { - const p = point as G2 & withPairingPrecomputes; - p._PPRECOMPUTES = undefined; - } - clearPairingPrecomputes; - - function millerLoopG1(Q: G1, P: G2): Fp12 { - return millerLoop(pairingPrecomputes(P), [Q.x, Q.y]); - } + // TODO: export + // function clearPairingPrecomputes(point: G2) { + // const p = point as G2 & withPairingPrecomputes; + // p._PPRECOMPUTES = undefined; + // } // Point on G2 curve (complex numbers): (x₁, x₂+i), (y₁, y₂+i) const G2 = weierstrassPoints({ @@ -240,13 +238,14 @@ export function bls( const { Signature } = CURVE.G2; // Calculates bilinear pairing - function pairing(P: G1, Q: G2, withFinalExponent: boolean = true): Fp12 { - if (P.equals(G1.ProjectivePoint.ZERO) || Q.equals(G2.ProjectivePoint.ZERO)) - throw new Error('No pairings at point of Infinity'); - P.assertValidity(); + function pairing(Q: G1, P: G2, withFinalExponent: boolean = true): Fp12 { + if (Q.equals(G1.ProjectivePoint.ZERO) || P.equals(G2.ProjectivePoint.ZERO)) + throw new Error('pairing is not available for ZERO point'); Q.assertValidity(); + P.assertValidity(); // Performance: 9ms for millerLoop and ~14ms for exp. - const looped = millerLoopG1(P, Q); + const Qa = Q.toAffine(); + const looped = millerLoop(pairingPrecomputes(P), [Qa.x, Qa.y]); return withFinalExponent ? Fp12.finalExponentiate(looped) : looped; } type G1 = typeof G1.ProjectivePoint.BASE; @@ -310,9 +309,7 @@ export function bls( function aggregatePublicKeys(publicKeys: G1[]): G1; function aggregatePublicKeys(publicKeys: G1Hex[]): Uint8Array | G1 { if (!publicKeys.length) throw new Error('Expected non-empty array'); - const agg = publicKeys - .map(normP1) - .reduce((sum, p) => sum.add(G1.ProjectivePoint.fromAffine(p)), G1.ProjectivePoint.ZERO); + const agg = publicKeys.map(normP1).reduce((sum, p) => sum.add(p), G1.ProjectivePoint.ZERO); const aggAffine = agg; //.toAffine(); if (publicKeys[0] instanceof G1.ProjectivePoint) { aggAffine.assertValidity(); @@ -327,9 +324,7 @@ export function bls( function aggregateSignatures(signatures: G2[]): G2; function aggregateSignatures(signatures: G2Hex[]): Uint8Array | G2 { if (!signatures.length) throw new Error('Expected non-empty array'); - const agg = signatures - .map(normP2) - .reduce((sum, s) => sum.add(G2.ProjectivePoint.fromAffine(s)), G2.ProjectivePoint.ZERO); + const agg = signatures.map(normP2).reduce((sum, s) => sum.add(s), G2.ProjectivePoint.ZERO); const aggAffine = agg; //.toAffine(); if (signatures[0] instanceof G2.ProjectivePoint) { aggAffine.assertValidity(); @@ -346,6 +341,9 @@ export function bls( publicKeys: G1Hex[], htfOpts?: htf.htfBasicOpts ): boolean { + // @ts-ignore + // console.log('verifyBatch', ut.bytesToHex(signature as any), messages, publicKeys.map(ut.bytesToHex)); + if (!messages.length) throw new Error('Expected non-empty messages array'); if (publicKeys.length !== messages.length) throw new Error('Pubkey count should equal msg count'); @@ -373,9 +371,8 @@ export function bls( } } - // Pre-compute points. Refer to README. - // TODO - // G1.ProjectivePoint.BASE._setWindowSize(4); + G1.ProjectivePoint.BASE._setWindowSize(4); + return { CURVE, Fr, diff --git a/src/abstract/edwards.ts b/src/abstract/edwards.ts index a5922df..bc8d070 100644 --- a/src/abstract/edwards.ts +++ b/src/abstract/edwards.ts @@ -38,7 +38,7 @@ export type CurveType = ut.BasicCurve & { uvRatio?: (u: bigint, v: bigint) => { isValid: boolean; value: bigint }; // RFC 8032 pre-hashing of messages to sign() / verify() preHash?: ut.CHash; - mapToCurve?: (scalar: bigint[]) => { x: bigint; y: bigint }; + mapToCurve?: (scalar: bigint[]) => AffinePoint; }; function validateOpts(curve: CurveType) { @@ -61,10 +61,10 @@ function validateOpts(curve: CurveType) { } // 2d point in XY coords -export interface AffinePoint { +export type AffinePoint = { x: bigint; y: bigint; -} +} & { z?: never; t?: never }; // Instance of Extended Point with coordinates in X, Y, Z, T export interface ExtendedPointType extends Group { @@ -76,16 +76,16 @@ export interface ExtendedPointType extends Group { multiplyUnsafe(scalar: bigint): ExtendedPointType; isSmallOrder(): boolean; isTorsionFree(): boolean; - toAffine(invZ?: bigint): AffinePoint; + toAffine(iz?: bigint): AffinePoint; clearCofactor(): ExtendedPointType; } // Static methods of Extended Point with coordinates in X, Y, Z, T export interface ExtendedPointConstructor extends GroupConstructor { new (x: bigint, y: bigint, z: bigint, t: bigint): ExtendedPointType; fromAffine(p: AffinePoint): ExtendedPointType; - toAffineBatch(points: ExtendedPointType[]): AffinePoint[]; fromHex(hex: Hex): ExtendedPointType; fromPrivateKey(privateKey: PrivKey): ExtendedPointType; // TODO: remove + toAffineBatch(points: ExtendedPointType[]): AffinePoint[]; } export type CurveFn = { @@ -161,20 +161,21 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { * https://en.wikipedia.org/wiki/Twisted_Edwards_curve#Extended_coordinates */ class ExtendedPoint implements ExtendedPointType { - constructor( - readonly x: bigint, - readonly y: bigint, - readonly z = _1n, - readonly t = modP(x * y) - ) {} + constructor(readonly x: bigint, readonly y: bigint, readonly z: bigint, readonly t: bigint) { + if (y == null || !ut.big(y)) throw new Error('y required'); + if (z == null || !ut.big(z)) throw new Error('z required'); + if (t == null || !ut.big(t)) throw new Error('t required'); + } + + static BASE = new ExtendedPoint(CURVE.Gx, CURVE.Gy, _1n, modP(CURVE.Gx * CURVE.Gy)); + static ZERO = new ExtendedPoint(_0n, _1n, _1n, _0n); // 0, 1, 1, 0 - static BASE = new ExtendedPoint(CURVE.Gx, CURVE.Gy); - static ZERO = new ExtendedPoint(_0n, _1n); // 0, 1, 1, 0 static fromAffine(p: AffinePoint): ExtendedPoint { - if (!(p && typeof p === 'object' && typeof p.x === 'bigint' && typeof p.y === 'bigint')) - throw new Error('fromAffine error'); - if (p.x === 0n && p.y === 1n) return ExtendedPoint.ZERO; - return new ExtendedPoint(p.x, p.y); + const { x, y } = p || {}; + if (p instanceof ExtendedPoint) throw new Error('fromAffine: extended point not allowed'); + if (!ut.big(x) || !ut.big(y)) throw new Error('fromAffine: invalid affine point'); + if (p.x === _0n && p.y === _1n) return ExtendedPoint.ZERO; + return new ExtendedPoint(p.x, p.y, _1n, modP(x * y)); } // Takes a bunch of Jacobian Points but executes only one // invert on all of them. invert is very slow operation, @@ -198,6 +199,9 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { const Y2Z1 = modP(Y2 * Z1); return X1Z2 === X2Z1 && Y1Z2 === Y2Z1; } + protected is0(): boolean { + return this.equals(ExtendedPoint.ZERO); + } // Inverses point to one corresponding to (x, -y) in Affine coordinates. negate(): ExtendedPoint { @@ -297,20 +301,20 @@ 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).equals(ExtendedPoint.ZERO); + return this.multiplyUnsafe(CURVE.h).is0(); } // Multiplies point by curve order (very big scalar CURVE.n) and checks if the result is 0. // Returns `false` is the point is dirty. isTorsionFree(): boolean { - return wnaf.unsafeLadder(this, CURVE_ORDER).equals(ExtendedPoint.ZERO); + return wnaf.unsafeLadder(this, CURVE_ORDER).is0(); } // Converts Extended point to default (x, y) coordinates. // Can accept precomputed Z^-1 - for example, from invertBatch. toAffine(iz?: bigint): AffinePoint { const { x, y, z } = this; - const is0 = this.equals(ExtendedPoint.ZERO); + const is0 = this.is0(); if (iz == null) iz = is0 ? _8n : (Fp.invert(z) as bigint); // 8 was chosen arbitrarily const ax = modP(x * iz); const ay = modP(y * iz); @@ -369,7 +373,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { const isXOdd = (x & _1n) === _1n; const isLastByteOdd = (lastByte & 0x80) !== 0; if (isLastByteOdd !== isXOdd) x = modP(-x); - return new ExtendedPoint(x, y); + return ExtendedPoint.fromAffine({ x, y }); } static fromPrivateKey(privateKey: PrivKey) { return getExtendedPublicKey(privateKey).point; @@ -526,7 +530,6 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { sign, verify, ExtendedPoint, - // Point: ExtendedPoint, utils, }; } diff --git a/src/abstract/weierstrass.ts b/src/abstract/weierstrass.ts index 4091dec..5dabedc 100644 --- a/src/abstract/weierstrass.ts +++ b/src/abstract/weierstrass.ts @@ -109,10 +109,10 @@ export type SignOpts = { lowS?: boolean; extraEntropy?: Entropy }; * TODO: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-7.html#unique-symbol */ -export interface AffinePoint { +export type AffinePoint = { x: T; y: T; -} +} & { z?: never }; // Instance for 3d XYZ points export interface ProjectivePointType extends Group> { readonly x: T; @@ -125,7 +125,8 @@ export interface ProjectivePointType extends Group> { a: bigint, b: bigint ): ProjectivePointType | undefined; - toAffine(invZ?: T): AffinePoint; + _setWindowSize(windowSize: number): void; + toAffine(iz?: T): AffinePoint; isTorsionFree(): boolean; clearCofactor(): ProjectivePointType; assertValidity(): void; @@ -135,19 +136,17 @@ export interface ProjectivePointType extends Group> { } // Static methods for 3d XYZ points export interface ProjectiveConstructor extends GroupConstructor> { - new (x: T, y: T, z?: T): ProjectivePointType; + new (x: T, y: T, z: T): ProjectivePointType; fromAffine(p: AffinePoint): ProjectivePointType; - toAffineBatch(points: ProjectivePointType[]): AffinePoint[]; - normalizeZ(points: ProjectivePointType[]): ProjectivePointType[]; - - fromAffine(ap: { x: T; y: T }): ProjectivePointType; fromHex(hex: Hex): ProjectivePointType; fromPrivateKey(privateKey: PrivKey): ProjectivePointType; + toAffineBatch(points: ProjectivePointType[]): AffinePoint[]; + normalizeZ(points: ProjectivePointType[]): ProjectivePointType[]; } export type CurvePointsType = BasicCurve & { // Bytes - fromBytes: (bytes: Uint8Array) => { x: T; y: T }; + fromBytes: (bytes: Uint8Array) => AffinePoint; toBytes: ( c: ProjectiveConstructor, point: ProjectivePointType, @@ -186,7 +185,6 @@ function validatePointOpts(curve: CurvePointsType) { } export type CurvePointsRes = { - // Point: PointConstructor; ProjectivePoint: ProjectiveConstructor; normalizePrivateKey: (key: PrivKey) => bigint; weierstrassEquation: (x: T) => T; @@ -258,26 +256,56 @@ export function weierstrassPoints(opts: CurvePointsType) { throw new TypeError('Expected valid private scalar: 0 < scalar < curve.n'); } + const pointPrecomputes = new Map(); /** * Projective Point works in 3d / projective (homogeneous) coordinates: (x, y, z) ∋ (x=x/z, y=y/z) * Default Point works in 2d / affine coordinates: (x, y) * We're doing calculations in projective, because its operations don't require costly inversion. */ class ProjectivePoint implements ProjectivePointType { - constructor(readonly x: T, readonly y: T, readonly z: T = Fp.ONE) {} - + constructor(readonly x: T, readonly y: T, readonly z: T) { + if (y == null || !Fp.isValid(y)) throw new Error('ProjectivePoint: y required'); + if (z == null || !Fp.isValid(z)) throw new Error('ProjectivePoint: z required'); + } static readonly BASE = new ProjectivePoint(CURVE.Gx, CURVE.Gy, Fp.ONE); static readonly ZERO = new ProjectivePoint(Fp.ZERO, Fp.ONE, Fp.ZERO); static fromAffine(p: AffinePoint): ProjectivePoint { - // TODO: validate - // if (!(p instanceof Point)) { - // throw new TypeError('ProjectivePoint#fromAffine: expected Point'); - // } + const { x, y } = p || {}; + if (!p || !Fp.isValid(x) || !Fp.isValid(y)) + throw new Error('fromAffine: invalid affine point'); + if (p instanceof ProjectivePoint) throw new Error('fromAffine: projective point not allowed'); + const is0 = (i: T) => Fp.equals(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 (p.equals(Point.ZERO)) return ProjectivePoint.ZERO; - if (Fp.equals(p.x, Fp.ZERO) && Fp.equals(p.y, Fp.ZERO)) return ProjectivePoint.ZERO; - return new ProjectivePoint(p.x, p.y, Fp.ONE); + if (is0(x) && is0(y)) return ProjectivePoint.ZERO; + return new ProjectivePoint(x, y, Fp.ONE); + } + + // We calculate precomputes for elliptic curve point multiplication + // using windowed method. This specifies window size and + // stores precomputed values. Usually only base point would be precomputed. + _WINDOW_SIZE?: number; + + // "Private method", don't use it directly + _setWindowSize(windowSize: number) { + this._WINDOW_SIZE = windowSize; + pointPrecomputes.delete(this); + } + protected is0() { + return this.equals(ProjectivePoint.ZERO); + } + private wNAF(n: bigint): { p: ProjectivePoint; f: ProjectivePoint } { + const W = this._WINDOW_SIZE || 1; + // Calculate precomputes on a first run, reuse them after + let comp = pointPrecomputes.get(this); + if (!comp) { + comp = wnaf.precomputeWindow(this, W) as ProjectivePoint[]; + if (W !== 1) { + comp = ProjectivePoint.normalizeZ(comp); + pointPrecomputes.set(this, comp); + } + } + return wnaf.wNAF(W, comp, n); } /** @@ -465,15 +493,15 @@ export function weierstrassPoints(opts: CurvePointsType) { let fake: ProjectivePoint; if (CURVE.endo) { const { k1neg, k1, k2neg, k2 } = CURVE.endo.splitScalar(n); - let { p: k1p, f: f1p } = wNAF_TMP_FN(this, k1); - let { p: k2p, f: f2p } = wNAF_TMP_FN(this, k2); + let { p: k1p, f: f1p } = this.wNAF(k1); + let { p: k2p, f: f2p } = this.wNAF(k2); k1p = wnaf.constTimeNegate(k1neg, k1p); k2p = wnaf.constTimeNegate(k2neg, k2p); k2p = new ProjectivePoint(Fp.mul(k2p.x, CURVE.endo.beta), k2p.y, k2p.z); point = k1p.add(k2p); fake = f1p.add(f2p); } else { - const { p, f } = wNAF_TMP_FN(this, n); + const { p, f } = this.wNAF(n); point = p; fake = f; } @@ -484,15 +512,15 @@ export function weierstrassPoints(opts: CurvePointsType) { // Converts Projective point to affine (x, y) coordinates. // Can accept precomputed Z^-1 - for example, from invertBatch. // (x, y, z) ∋ (x=x/z, y=y/z) - toAffine(invZ?: T): AffinePoint { + toAffine(iz?: T): AffinePoint { const { x, y, z } = this; - const is0 = this.equals(ProjectivePoint.ZERO); + 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 (invZ == null) invZ = is0 ? Fp.ONE : Fp.invert(z); - const ax = Fp.mul(x, invZ); - const ay = Fp.mul(y, invZ); - const zz = Fp.mul(z, invZ); + if (iz == null) iz = is0 ? Fp.ONE : Fp.invert(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'); return { x: ax, y: ay }; @@ -521,27 +549,27 @@ export function weierstrassPoints(opts: CurvePointsType) { a === _0n || a === _1n || !P.equals(ProjectivePoint.BASE) ? P.multiplyUnsafe(a) : P.multiply(a); - const bQ = ProjectivePoint.fromAffine(Q).multiplyUnsafe(b); + const bQ = Q.multiplyUnsafe(b); const sum = aP.add(bQ); - return sum.equals(ProjectivePoint.ZERO) ? undefined : sum; + return sum.is0() ? undefined : sum; } // A point on curve is valid if it conforms to equation. assertValidity(): void { + const err = 'Point invalid:'; // Zero is valid point too! - if (this.equals(ProjectivePoint.ZERO)) { + if (this.is0()) { if (CURVE.allowInfinityPoint) return; - throw new Error('Point at infinity'); + throw new Error(`${err} ZERO`); } // Some 3rd-party test vectors require different wording between here & `fromCompressedHex` - const msg = 'Point is not on elliptic curve'; const { x, y } = this.toAffine(); // Check if x, y are valid field elements - if (!Fp.isValid(x) || !Fp.isValid(y)) throw new Error(msg); + if (!Fp.isValid(x) || !Fp.isValid(y)) throw new Error(`${err} x or y not FE`); const left = Fp.square(y); // y² const right = weierstrassEquation(x); // x³ + ax + b - if (!Fp.equals(left, right)) throw new Error(msg); - if (!this.isTorsionFree()) throw new Error('Point must be of prime-order subgroup'); + if (!Fp.equals(left, right)) throw new Error(`${err} equation left != right`); + if (!this.isTorsionFree()) throw new Error(`${err} not in prime-order subgroup`); } hasEvenY(): boolean { const { y } = this.toAffine(); @@ -562,10 +590,9 @@ export function weierstrassPoints(opts: CurvePointsType) { * @param hex short/long ECDSA hex */ static fromHex(hex: Hex): ProjectivePoint { - const { x, y } = CURVE.fromBytes(ut.ensureBytes(hex)); - const point = new ProjectivePoint(x, y); - point.assertValidity(); - return point; + const P = ProjectivePoint.fromAffine(CURVE.fromBytes(ut.ensureBytes(hex))); + P.assertValidity(); + return P; } // Multiplies generator point by privateKey. @@ -576,27 +603,6 @@ export function weierstrassPoints(opts: CurvePointsType) { const _bits = CURVE.nBitLength; const wnaf = wNAF(ProjectivePoint, CURVE.endo ? Math.ceil(_bits / 2) : _bits); - const { BASE: G } = ProjectivePoint; - let Gpows: ProjectivePoint[] | undefined = undefined; // precomputes for base point G - function wNAF_TMP_FN(P: ProjectivePoint, n: bigint): { p: ProjectivePoint; f: ProjectivePoint } { - const C = ProjectivePoint; - if (P.equals(G)) { - const W = 8; - if (!Gpows) { - const denorm = wnaf.precomputeWindow(P, W) as ProjectivePoint[]; - const norm = C.toAffineBatch(denorm).map(C.fromAffine); - Gpows = norm; - } - const comp = Gpows; - return wnaf.wNAF(W, comp, n); - } - const W = 1; - const denorm = wnaf.precomputeWindow(P, W) as ProjectivePoint[]; - // const norm = C.toAffineBatch(denorm).map(C.fromAffine); - const norm = denorm; - return wnaf.wNAF(W, norm, n); - } - function assertPrjPoint(other: unknown) { if (!(other instanceof ProjectivePoint)) throw new TypeError('ProjectivePoint expected'); } @@ -976,12 +982,10 @@ export function weierstrass(curveDef: CurveType): CurveFn { * @returns cached point */ precompute(windowSize = 8, point = ProjectivePoint.BASE): typeof ProjectivePoint.BASE { - return ProjectivePoint.BASE; - // return cache - // const cached = point === Point.BASE ? point : new Point(point.x, point.y); - // cached._setWindowSize(windowSize); - // cached.multiply(_3n); - // return cached; + // const cached = point === ProjectivePoint.BASE ? point : ProjectivePoint.fromAffine({x, y}); + point._setWindowSize(windowSize); + point.multiply(BigInt(3)); + return point; }, }; @@ -1142,7 +1146,8 @@ export function weierstrass(curveDef: CurveType): CurveFn { } // Enable precomputes. Slows down first publicKey computation by 20ms. - // Point.BASE._setWindowSize(8); + ProjectivePoint.BASE._setWindowSize(8); + // utils.precompute(8, ProjectivePoint.BASE) /** * Verifies a signature against message hash and public key. diff --git a/src/bls12-381.ts b/src/bls12-381.ts index 762414d..5f48372 100644 --- a/src/bls12-381.ts +++ b/src/bls12-381.ts @@ -16,7 +16,7 @@ import { randomBytes } from '@noble/hashes/utils'; import { bls, CurveFn } from './abstract/bls.js'; import * as mod from './abstract/modular.js'; import { - concatBytes, + concatBytes as concatB, ensureBytes, numberToBytesBE, bytesToNumberBE, @@ -31,6 +31,7 @@ import { ProjectivePointType, ProjectiveConstructor, mapToCurveSimpleSWU, + AffinePoint, } from './abstract/weierstrass.js'; import { isogenyMap } from './abstract/hash-to-curve.js'; @@ -169,7 +170,7 @@ const Fp2: mod.Field & Fp2Utils = { if (b.length !== Fp2.BYTES) throw new Error(`fromBytes wrong length=${b.length}`); return { c0: Fp.fromBytes(b.subarray(0, Fp.BYTES)), c1: Fp.fromBytes(b.subarray(Fp.BYTES)) }; }, - toBytes: ({ c0, c1 }) => concatBytes(Fp.toBytes(c0), Fp.toBytes(c1)), + toBytes: ({ c0, c1 }) => concatB(Fp.toBytes(c0), Fp.toBytes(c1)), cmov: ({ c0, c1 }, { c0: r0, c1: r1 }, c) => ({ c0: Fp.cmov(c0, r0, c), c1: Fp.cmov(c1, r1, c), @@ -354,7 +355,7 @@ const Fp6: mod.Field & Fp6Utils = { }; }, toBytes: ({ c0, c1, c2 }): Uint8Array => - concatBytes(Fp2.toBytes(c0), Fp2.toBytes(c1), Fp2.toBytes(c2)), + concatB(Fp2.toBytes(c0), Fp2.toBytes(c1), Fp2.toBytes(c2)), cmov: ({ c0, c1, c2 }: Fp6, { c0: r0, c1: r1, c2: r2 }: Fp6, c) => ({ c0: Fp2.cmov(c0, r0, c), c1: Fp2.cmov(c1, r1, c), @@ -557,7 +558,7 @@ const Fp12: mod.Field & Fp12Utils = { c1: Fp6.fromBytes(b.subarray(Fp6.BYTES)), }; }, - toBytes: ({ c0, c1 }): Uint8Array => concatBytes(Fp6.toBytes(c0), Fp6.toBytes(c1)), + toBytes: ({ c0, c1 }): Uint8Array => concatB(Fp6.toBytes(c0), Fp6.toBytes(c1)), cmov: ({ c0, c1 }, { c0: r0, c1: r1 }, c) => ({ c0: Fp6.cmov(c0, r0, c), c1: Fp6.cmov(c1, r1, c), @@ -1018,7 +1019,7 @@ export const bls12_381: CurveFn = bls({ const { x, y } = G1_SWU(Fp.create(scalars[0])); return isogenyMapG1(x, y); }, - fromBytes: (bytes: Uint8Array): { x: Fp; y: Fp } => { + fromBytes: (bytes: Uint8Array): AffinePoint => { if (bytes.length === 48) { const P = Fp.ORDER; const compressedValue = bytesToNumberBE(bytes); @@ -1034,7 +1035,7 @@ export const bls12_381: CurveFn = bls({ return { x: Fp.create(x), y: Fp.create(y) }; } else if (bytes.length === 96) { // Check if the infinity flag is set - if ((bytes[0] & (1 << 6)) !== 0) return bls12_381.G1.ProjectivePoint.ZERO; + if ((bytes[0] & (1 << 6)) !== 0) return bls12_381.G1.ProjectivePoint.ZERO.toAffine(); const x = bytesToNumberBE(bytes.slice(0, Fp.BYTES)); const y = bytesToNumberBE(bytes.slice(Fp.BYTES)); return { x: Fp.create(x), y: Fp.create(y) }; @@ -1044,7 +1045,7 @@ export const bls12_381: CurveFn = bls({ }, toBytes: (c, point, isCompressed) => { const isZero = point.equals(c.ZERO); - const { x, y } = point; + const { x, y } = point.toAffine(); if (isCompressed) { if (isZero) return COMPRESSED_ZERO.slice(); const P = Fp.ORDER; @@ -1055,10 +1056,10 @@ export const bls12_381: CurveFn = bls({ } else { if (isZero) { // 2x PUBLIC_KEY_LENGTH - const x = concatBytes(new Uint8Array([0x40]), new Uint8Array(2 * Fp.BYTES - 1)); + const x = concatB(new Uint8Array([0x40]), new Uint8Array(2 * Fp.BYTES - 1)); return x; } else { - return concatBytes(numberToBytesBE(x, Fp.BYTES), numberToBytesBE(y, Fp.BYTES)); + return concatB(numberToBytesBE(x, Fp.BYTES), numberToBytesBE(y, Fp.BYTES)); } } }, @@ -1120,7 +1121,7 @@ export const bls12_381: CurveFn = bls({ const Q = t3.subtract(P); // Ψ²(2P) - Ψ(P) + [x²]P - [x]Ψ(P) + [x]P - 1P return Q; // [x²-x-1]P + [x-1]Ψ(P) + Ψ²(2P) }, - fromBytes: (bytes: Uint8Array): { x: Fp2; y: Fp2 } => { + fromBytes: (bytes: Uint8Array): AffinePoint => { const m_byte = bytes[0] & 0xe0; if (m_byte === 0x20 || m_byte === 0x60 || m_byte === 0xe0) { throw new Error('Invalid encoding flag: ' + m_byte); @@ -1128,6 +1129,8 @@ export const bls12_381: CurveFn = bls({ const bitC = m_byte & 0x80; // compression bit const bitI = m_byte & 0x40; // point at infinity bit const bitS = m_byte & 0x20; // sign bit + const L = Fp.BYTES; + const slc = (b: Uint8Array, from: number, to?: number) => bytesToNumberBE(b.slice(from, to)); if (bytes.length === 96 && bitC) { const { b } = bls12_381.CURVE.G2; const P = Fp.ORDER; @@ -1140,8 +1143,8 @@ export const bls12_381: CurveFn = bls({ } return { x: Fp2.ZERO, y: Fp2.ZERO }; } - const x_1 = bytesToNumberBE(bytes.slice(0, Fp.BYTES)); - const x_0 = bytesToNumberBE(bytes.slice(Fp.BYTES)); + const x_1 = slc(bytes, 0, L); + const x_0 = slc(bytes, L, 2 * L); const x = Fp2.create({ c0: Fp.create(x_0), c1: Fp.create(x_1) }); const right = Fp2.add(Fp2.pow(x, 3n), b); // y² = x³ + 4 * (u+1) = x³ + b let y = Fp2.sqrt(right); @@ -1153,10 +1156,10 @@ export const bls12_381: CurveFn = bls({ if ((bytes[0] & (1 << 6)) !== 0) { return { x: Fp2.ZERO, y: Fp2.ZERO }; } - const x1 = bytesToNumberBE(bytes.slice(0, Fp.BYTES)); - const x0 = bytesToNumberBE(bytes.slice(Fp.BYTES, 2 * Fp.BYTES)); - const y1 = bytesToNumberBE(bytes.slice(2 * Fp.BYTES, 3 * Fp.BYTES)); - const y0 = bytesToNumberBE(bytes.slice(3 * Fp.BYTES)); + const x1 = slc(bytes, 0, L); + const x0 = slc(bytes, L, 2 * L); + const y1 = slc(bytes, 2 * L, 3 * L); + const y0 = slc(bytes, 3 * L, 4 * L); return { x: Fp2.fromBigTuple([x0, x1]), y: Fp2.fromBigTuple([y0, y1]) }; } else { throw new Error('Invalid point G2, expected 96/192 bytes'); @@ -1164,20 +1167,20 @@ export const bls12_381: CurveFn = bls({ }, toBytes: (c, point, isCompressed) => { const isZero = point.equals(c.ZERO); - const { x, y } = point; + const { x, y } = point.toAffine(); if (isCompressed) { const P = Fp.ORDER; - if (isZero) return concatBytes(COMPRESSED_ZERO, numberToBytesBE(0n, Fp.BYTES)); + if (isZero) return concatB(COMPRESSED_ZERO, numberToBytesBE(0n, Fp.BYTES)); const flag = Boolean(y.c1 === 0n ? (y.c0 * 2n) / P : (y.c1 * 2n) / P); // set compressed & sign bits (looks like different offsets than for G1/Fp?) let x_1 = bitSet(x.c1, C_BIT_POS, flag); x_1 = bitSet(x_1, S_BIT_POS, true); - return concatBytes(numberToBytesBE(x_1, Fp.BYTES), numberToBytesBE(x.c0, Fp.BYTES)); + return concatB(numberToBytesBE(x_1, Fp.BYTES), numberToBytesBE(x.c0, Fp.BYTES)); } else { - if (isZero) return concatBytes(new Uint8Array([0x40]), new Uint8Array(4 * Fp.BYTES - 1)); // bytes[0] |= 1 << 6; + if (isZero) return concatB(new Uint8Array([0x40]), new Uint8Array(4 * Fp.BYTES - 1)); // bytes[0] |= 1 << 6; const { re: x0, im: x1 } = Fp2.reim(x); const { re: y0, im: y1 } = Fp2.reim(y); - return concatBytes( + return concatB( numberToBytesBE(x1, Fp.BYTES), numberToBytesBE(x0, Fp.BYTES), numberToBytesBE(y1, Fp.BYTES), @@ -1215,6 +1218,7 @@ export const bls12_381: CurveFn = bls({ const isZero = y1 === 0n && (y0 * 2n) / P !== aflag1; if (isGreater || isZero) y = Fp2.negate(y); const point = bls12_381.G2.ProjectivePoint.fromAffine({ x, y }); + // console.log('Signature.decode', point); point.assertValidity(); return point; }, @@ -1222,14 +1226,14 @@ export const bls12_381: CurveFn = bls({ // NOTE: by some reasons it was missed in bls12-381, looks like bug point.assertValidity(); if (point.equals(bls12_381.G2.ProjectivePoint.ZERO)) - return concatBytes(COMPRESSED_ZERO, numberToBytesBE(0n, Fp.BYTES)); + return concatB(COMPRESSED_ZERO, numberToBytesBE(0n, Fp.BYTES)); const { re: x0, im: x1 } = Fp2.reim(point.x); const { re: y0, im: y1 } = Fp2.reim(point.y); const tmp = y1 > 0n ? y1 * 2n : y0 * 2n; const aflag1 = Boolean((tmp / Fp.ORDER) & 1n); const z1 = bitSet(bitSet(x1, 381, aflag1), S_BIT_POS, true); const z2 = x0; - return concatBytes(numberToBytesBE(z1, Fp.BYTES), numberToBytesBE(z2, Fp.BYTES)); + return concatB(numberToBytesBE(z1, Fp.BYTES), numberToBytesBE(z2, Fp.BYTES)); }, }, }, diff --git a/src/jubjub.ts b/src/jubjub.ts index e7a6cc0..714d3c9 100644 --- a/src/jubjub.ts +++ b/src/jubjub.ts @@ -39,7 +39,7 @@ export function groupHash(tag: Uint8Array, personalization: Uint8Array) { h.update(GH_FIRST_BLOCK); h.update(tag); // NOTE: returns ExtendedPoint, in case it will be multiplied later - let p = jubjub.ExtendedPoint.fromAffine(jubjub.ExtendedPoint.fromHex(h.digest())); + let p = jubjub.ExtendedPoint.fromHex(h.digest()); // NOTE: cannot replace with isSmallOrder, returns Point*8 p = p.multiply(jubjub.CURVE.h); if (p.equals(jubjub.ExtendedPoint.ZERO)) throw new Error('Point has small order'); diff --git a/src/stark.ts b/src/stark.ts index 3ae9142..f9ce157 100644 --- a/src/stark.ts +++ b/src/stark.ts @@ -181,27 +181,32 @@ export function getAccountPath( const PEDERSEN_POINTS_AFFINE = [ new ProjectivePoint( 2089986280348253421170679821480865132823066470938446095505822317253594081284n, - 1713931329540660377023406109199410414810705867260802078187082345529207694986n + 1713931329540660377023406109199410414810705867260802078187082345529207694986n, + 1n ), new ProjectivePoint( 996781205833008774514500082376783249102396023663454813447423147977397232763n, - 1668503676786377725805489344771023921079126552019160156920634619255970485781n + 1668503676786377725805489344771023921079126552019160156920634619255970485781n, + 1n ), new ProjectivePoint( 2251563274489750535117886426533222435294046428347329203627021249169616184184n, - 1798716007562728905295480679789526322175868328062420237419143593021674992973n + 1798716007562728905295480679789526322175868328062420237419143593021674992973n, + 1n ), new ProjectivePoint( 2138414695194151160943305727036575959195309218611738193261179310511854807447n, - 113410276730064486255102093846540133784865286929052426931474106396135072156n + 113410276730064486255102093846540133784865286929052426931474106396135072156n, + 1n ), new ProjectivePoint( 2379962749567351885752724891227938183011949129833673362440656643086021394946n, - 776496453633298175483985398648758586525933812536653089401905292063708816422n + 776496453633298175483985398648758586525933812536653089401905292063708816422n, + 1n ), ]; // for (const p of PEDERSEN_POINTS) p._setWindowSize(8); -const PEDERSEN_POINTS = PEDERSEN_POINTS_AFFINE.map(ProjectivePoint.fromAffine); +const PEDERSEN_POINTS = PEDERSEN_POINTS_AFFINE; function pedersenPrecompute(p1: ProjectivePoint, p2: ProjectivePoint): ProjectivePoint[] { const out: ProjectivePoint[] = []; diff --git a/test/bls12-381.test.js b/test/bls12-381.test.js index 135dac6..c29ab0e 100644 --- a/test/bls12-381.test.js +++ b/test/bls12-381.test.js @@ -9,6 +9,9 @@ import pairingVectors from './bls12-381/go_pairing_vectors/pairing.json' assert import { wNAF } from '../lib/esm/abstract/group.js'; const bls = bls12_381; const { Fp2 } = bls; +const G1Point = bls.G1.ProjectivePoint; +const G2Point = bls.G2.ProjectivePoint; +const G1Aff = (x, y) => G1Point.fromAffine({ x, y }); const G2_VECTORS = readFileSync('./test/bls12-381/bls12-381-g2-test-vectors.txt', 'utf-8') .trim() @@ -37,6 +40,10 @@ const B_384_40 = '40'.padEnd(384, '0'); // [0x40, 0, 0...] const getPubKey = (priv) => bls.getPublicKey(priv); +function equal(a, b, comment) { + deepStrictEqual(a.equals(b), true, `eq(${comment})`); +} + // Fp describe('bls12-381 Fp', () => { const Fp = bls.Fp; @@ -145,8 +152,8 @@ describe('bls12-381 Fp2', () => { describe('bls12-381 Point', () => { const Fp = bls.Fp; const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n); - const PointG1 = bls.G1.Point; - const PointG2 = bls.G2.Point; + const PointG1 = G1Point; + const PointG2 = G2Point; describe('with Fp coordinates', () => { should('Point equality', () => { @@ -157,8 +164,8 @@ describe('bls12-381 Point', () => { ([x1, y1, z1], [x2, y2, z2]) => { const p1 = new PointG1(Fp.create(x1), Fp.create(y1), Fp.create(z1)); const p2 = new PointG1(Fp.create(x2), Fp.create(y2), Fp.create(z2)); - deepStrictEqual(p1.equals(p1), true); - deepStrictEqual(p2.equals(p2), true); + equal(p1, p1); + equal(p2, p2); deepStrictEqual(p1.equals(p2), false); deepStrictEqual(p2.equals(p1), false); } @@ -166,27 +173,27 @@ describe('bls12-381 Point', () => { ); }); should('be placed on curve vector 1', () => { - const a = new PointG1(Fp.create(0n), Fp.create(0n)); + const a = PointG1.fromAffine({ x: Fp.create(0n), y: Fp.create(0n) }); a.assertValidity(); }); should('not be placed on curve vector 1', () => { - const a = new PointG1(Fp.create(0n), Fp.create(1n)); + const a = PointG1.fromAffine({ x: Fp.create(0n), y: Fp.create(1n) }); throws(() => a.assertValidity()); }); should('be placed on curve vector 2', () => { - const a = new PointG1( - Fp.create( + const a = PointG1.fromAffine({ + x: Fp.create( 0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bbn ), - Fp.create( + y: Fp.create( 0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1n - ) - ); + ), + }); a.assertValidity(); }); should('be placed on curve vector 3', () => { - const a = new PointG1( + const a = G1Aff( Fp.create( 3971675556538908004130084773503021351583407620890695272226385332452194486153316625183061567093226342405194446632851n ), @@ -198,7 +205,7 @@ describe('bls12-381 Point', () => { a.assertValidity(); }); should('not be placed on curve vector 3', () => { - const a = new PointG1( + const a = G1Aff( Fp.create( 622186380008502900120948444810967255157373993223369845903602988014033704418470621816206856882891545628885272576827n ), @@ -209,7 +216,7 @@ describe('bls12-381 Point', () => { throws(() => a.assertValidity()); }); should('not be placed on curve vector 2', () => { - const a = new PointG1( + const a = G1Aff( Fp.create( 0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6ban ), @@ -221,7 +228,7 @@ describe('bls12-381 Point', () => { }); should('be doubled and placed on curve vector 1', () => { - const a = new PointG1( + const a = G1Aff( Fp.create( 0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bbn ), @@ -231,9 +238,9 @@ describe('bls12-381 Point', () => { ); const double = a.double(); double.assertValidity(); - deepStrictEqual( + equal( double, - new PointG1( + G1Aff( Fp.create( 838589206289216005799424730305866328161735431124665289961769162861615689790485775997575391185127590486775437397838n ), @@ -242,11 +249,11 @@ describe('bls12-381 Point', () => { ) ) ); - deepStrictEqual(double, a.multiply(2n)); - deepStrictEqual(double, a.add(a)); + equal(double, a.multiply(2n)); + equal(double, a.add(a)); }); should('be pdoubled and placed on curve vector 2', () => { - const a = new PointG1( + const a = G1Aff( Fp.create( 3971675556538908004130084773503021351583407620890695272226385332452194486153316625183061567093226342405194446632851n ), @@ -256,9 +263,9 @@ describe('bls12-381 Point', () => { ); const double = a.double(); double.assertValidity(); - deepStrictEqual( + equal( double, - new PointG1( + G1Aff( Fp.create( 2820140907397376715097155275119328764341377106900140435029293933353987248389870417008333350832041425944924730501850n ), @@ -267,8 +274,8 @@ describe('bls12-381 Point', () => { ) ) ); - deepStrictEqual(double, a.multiply(2n)); - deepStrictEqual(double, a.add(a)); + equal(double, a.multiply(2n)); + equal(double, a.add(a)); }); should('not validate incorrect point', () => { const x = @@ -276,7 +283,7 @@ describe('bls12-381 Point', () => { const y = 3934582309586258715640230772291917282844636728991757779640464479794033391537662970190753981664259511166946374555673n; - const p = new PointG1(Fp.create(x), Fp.create(y)); + const p = PointG1.fromAffine({ x: Fp.create(x), y: Fp.create(y) }); throws(() => p.assertValidity()); }); }); @@ -331,16 +338,16 @@ describe('bls12-381 Point', () => { a.assertValidity(); }); should('be placed on curve vector 3', () => { - const a = new PointG2( - Fp2.fromBigTuple([ + const a = PointG2.fromAffine({ + x: Fp2.fromBigTuple([ 233289878585407360737561818812172281900488265436962145913969074168503452745466655442125797664134009339799716079103n, 1890785404699189181161569277356497622423785178845737858235714310995835974899880469355250933575450045792782146044819n, ]), - Fp2.fromBigTuple([ + y: Fp2.fromBigTuple([ 1215754321684097939278683023199690844646077558342794977283698289191570128272085945598449054373022460634252133664610n, 2751025411942897795042193940345989612527395984463172615380574492034129474560903255212585680112858672276592527763585n, - ]) - ); + ]), + }); a.assertValidity(); }); should('not be placed on curve vector 1', () => { @@ -398,50 +405,50 @@ describe('bls12-381 Point', () => { ); const double = a.double(); double.assertValidity(); - deepStrictEqual( + equal( double, - new PointG2( - Fp2.fromBigTuple([ + PointG2.fromAffine({ + x: Fp2.fromBigTuple([ 3419974069068927546093595533691935972093267703063689549934039433172037728172434967174817854768758291501458544631891n, 1586560233067062236092888871453626466803933380746149805590083683748120990227823365075019078675272292060187343402359n, ]), - Fp2.fromBigTuple([ + y: Fp2.fromBigTuple([ 678774053046495337979740195232911687527971909891867263302465188023833943429943242788645503130663197220262587963545n, 2374407843478705782611042739236452317510200146460567463070514850492917978226342495167066333366894448569891658583283n, - ]) - ) + ]), + }) ); - deepStrictEqual(double, a.multiply(2n)); - deepStrictEqual(double, a.add(a)); + equal(double, a.multiply(2n)); + equal(double, a.add(a)); }); should('be doubled and placed on curve vector 2', () => { - const a = new PointG2( - Fp2.fromBigTuple([ + const a = PointG2.fromAffine({ + x: Fp2.fromBigTuple([ 233289878585407360737561818812172281900488265436962145913969074168503452745466655442125797664134009339799716079103n, 1890785404699189181161569277356497622423785178845737858235714310995835974899880469355250933575450045792782146044819n, ]), - Fp2.fromBigTuple([ + y: Fp2.fromBigTuple([ 1215754321684097939278683023199690844646077558342794977283698289191570128272085945598449054373022460634252133664610n, 2751025411942897795042193940345989612527395984463172615380574492034129474560903255212585680112858672276592527763585n, - ]) - ); + ]), + }); const double = a.double(); double.assertValidity(); - deepStrictEqual( + equal( double, - new PointG2( - Fp2.fromBigTuple([ + PointG2.fromAffine({ + x: Fp2.fromBigTuple([ 725284069620738622060750301926991261102335618423691881774095602348039360646278290301007111649920759181128494302803n, 919264895242212954093039181597172832206919609583188170141409360396089107784684267919047943917330182442049667967832n, ]), - Fp2.fromBigTuple([ + y: Fp2.fromBigTuple([ 671406920027112857569775418033910829294759327652470956866749681326509356602892160214948716653598897872184523683037n, 3055998868118150255613397668970777574660658983679486410738349400795670735303668556065367873243198246660959891663772n, - ]) - ) + ]), + }) ); - deepStrictEqual(double, a.multiply(2n)); - deepStrictEqual(double, a.add(a)); + equal(double, a.multiply(2n)); + equal(double, a.add(a)); }); const wNAF_VECTORS = [ 0x28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4cn, @@ -498,59 +505,59 @@ describe('bls12-381 Point', () => { }); should('PSI cofactor cleaning same as multiplication', () => { const points = [ - new PointG2( - Fp2.fromBigTuple([ + PointG2.fromAffine({ + x: Fp2.fromBigTuple([ 3241532514922300496112840946857937535679746598786089852337901560508502000671236210655544891931896207857529462155216n, 1895396002326546958606807865184627576810563909325590769826854838265038910148901969770093629785689196200738594051207n, ]), - Fp2.fromBigTuple([ + y: Fp2.fromBigTuple([ 3837310786345067009730271578787473898123345117675361644389016087559904243233782782806882170766697501716660726009081n, 1677898256818258755710370015632820289344925154952470675147754839082216730170149764842062980567113751550792217387778n, - ]) - ), - new PointG2( - Fp2.fromBigTuple([ + ]), + }), + PointG2.fromAffine({ + x: Fp2.fromBigTuple([ 3953605227375649587553282126565793338489478933421008011676219910137022551750442290689597974472294891051907650111197n, 2357556650209231585002654467241659159063900268360871707630297623496109598089657193704186795702074478622917895656384n, ]), - Fp2.fromBigTuple([ + y: Fp2.fromBigTuple([ 2495601009524620857707705364800595215702994859258454180584354350679476916692161325761009870302795857111988758374874n, 2636356076845621042340998927146453389877292467744110912831694031602037452225656755036030562878672313329396684758868n, - ]) - ), - new PointG2( - Fp2.fromBigTuple([ + ]), + }), + PointG2.fromAffine({ + x: Fp2.fromBigTuple([ 3194353741003351193683364524319044762011830478765020903432624057794333426495229091698895944358182869251271971124925n, 3653808358303084112668893108836368862445971143336505524596401519323087809653188999580874561318367165116767192535630n, ]), - Fp2.fromBigTuple([ + y: Fp2.fromBigTuple([ 1293147983982604948417085455043456439133874729834486879326988078136905862300347946661594156148773025247657033069058n, 304385694250536139727810974742746004825746444239830780412067821920180289379490776439208435270467062610041367314353n, - ]) - ), - new PointG2( - Fp2.fromBigTuple([ + ]), + }), + PointG2.fromAffine({ + x: Fp2.fromBigTuple([ 3913848356631365121633829648186849525632038637523196801515536004438620269011326037518995697522336713513734205291154n, 2892036022910655232784554063257167431129586441456677365843329976181438736451469367386635015809932785607079312857252n, ]), - Fp2.fromBigTuple([ + y: Fp2.fromBigTuple([ 2946839687749075245408468852956330519059225766361204396943480879518352257697157665323514637184706466306723801095265n, 1858741481287683941330484703798096212920693492850150667744795312336588840954530505769251621475693006537257913664280n, - ]) - ), - new PointG2( - Fp2.fromBigTuple([ + ]), + }), + PointG2.fromAffine({ + x: Fp2.fromBigTuple([ 3241532514922300496112840946857937535679746598786089852337901560508502000671236210655544891931896207857529462155216n, 1895396002326546958606807865184627576810563909325590769826854838265038910148901969770093629785689196200738594051207n, ]), - Fp2.fromBigTuple([ + y: Fp2.fromBigTuple([ 3837310786345067009730271578787473898123345117675361644389016087559904243233782782806882170766697501716660726009081n, 1677898256818258755710370015632820289344925154952470675147754839082216730170149764842062980567113751550792217387778n, - ]) - ), + ]), + }), ]; // Use wNAF allow scalars higher than CURVE.r - const w = wNAF(bls.G2.Point, 1); + const w = wNAF(G2Point, 1); for (let p of points) { const ours = p.clearCofactor(); const shouldBe = w.unsafeLadder(p, bls.CURVE.G2.hEff); @@ -561,15 +568,15 @@ describe('bls12-381 Point', () => { // index.ts -// bls.PointG1.BASE.clearMultiplyPrecomputes(); -// bls.PointG1.BASE.calcMultiplyPrecomputes(4); +// bls.G1.ProjectivePoint.BASE.clearMultiplyPrecomputes(); +// bls.G1.ProjectivePoint.BASE.calcMultiplyPrecomputes(4); describe('bls12-381/basic', () => { should('construct point G1 from its uncompressed form (Raw Bytes)', () => { // Test Zero - const g1 = bls.G1.Point.fromHex(B_192_40); - deepStrictEqual(g1.x, bls.G1.Point.ZERO.x); - deepStrictEqual(g1.y, bls.G1.Point.ZERO.y); + const g1 = G1Point.fromHex(B_192_40); + deepStrictEqual(g1.x, G1Point.ZERO.x); + deepStrictEqual(g1.y, G1Point.ZERO.y); // Test Non-Zero const x = bls.Fp.create( BigInt( @@ -582,7 +589,7 @@ describe('bls12-381/basic', () => { ) ); - const g1_ = bls.G1.Point.fromHex( + const g1_ = G1Point.fromHex( '17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' ); @@ -592,10 +599,10 @@ describe('bls12-381/basic', () => { should('construct point G1 from its uncompressed form (Hex)', () => { // Test Zero - const g1 = bls.G1.Point.fromHex(B_192_40); + const g1 = G1Point.fromHex(B_192_40); - deepStrictEqual(g1.x, bls.G1.Point.ZERO.x); - deepStrictEqual(g1.y, bls.G1.Point.ZERO.y); + deepStrictEqual(g1.x, G1Point.ZERO.x); + deepStrictEqual(g1.y, G1Point.ZERO.y); // Test Non-Zero const x = bls.Fp.create( BigInt( @@ -608,7 +615,7 @@ describe('bls12-381/basic', () => { ) ); - const g1_ = bls.G1.Point.fromHex( + const g1_ = G1Point.fromHex( '17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' ); @@ -618,9 +625,9 @@ describe('bls12-381/basic', () => { should('construct point G2 from its uncompressed form (Raw Bytes)', () => { // Test Zero - const g2 = bls.G2.Point.fromHex(B_384_40); - deepStrictEqual(g2.x, bls.G2.Point.ZERO.x, 'zero(x)'); - deepStrictEqual(g2.y, bls.G2.Point.ZERO.y, 'zero(y)'); + const g2 = G2Point.fromHex(B_384_40); + deepStrictEqual(g2.x, G2Point.ZERO.x, 'zero(x)'); + deepStrictEqual(g2.y, G2Point.ZERO.y, 'zero(y)'); // Test Non-Zero const x = Fp2.fromBigTuple([ BigInt( @@ -639,7 +646,7 @@ describe('bls12-381/basic', () => { ), ]); - const g2_ = bls.G2.Point.fromHex( + const g2_ = G2Point.fromHex( '13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801' ); @@ -649,10 +656,10 @@ describe('bls12-381/basic', () => { should('construct point G2 from its uncompressed form (Hex)', () => { // Test Zero - const g2 = bls.G2.Point.fromHex(B_384_40); + const g2 = G2Point.fromHex(B_384_40); - deepStrictEqual(g2.x, bls.G2.Point.ZERO.x); - deepStrictEqual(g2.y, bls.G2.Point.ZERO.y); + deepStrictEqual(g2.x, G2Point.ZERO.x); + deepStrictEqual(g2.y, G2Point.ZERO.y); // Test Non-Zero const x = Fp2.fromBigTuple([ BigInt( @@ -671,7 +678,7 @@ describe('bls12-381/basic', () => { ), ]); - const g2_ = bls.G2.Point.fromHex( + const g2_ = G2Point.fromHex( '13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801' ); @@ -681,7 +688,7 @@ describe('bls12-381/basic', () => { should('get uncompressed form of point G1 (Raw Bytes)', () => { // Test Zero - deepStrictEqual(bls.G1.Point.ZERO.toHex(false), B_192_40); + deepStrictEqual(G1Point.ZERO.toHex(false), B_192_40); // Test Non-Zero const x = bls.Fp.create( BigInt( @@ -693,7 +700,7 @@ describe('bls12-381/basic', () => { '0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' ) ); - const g1 = new bls.G1.Point(x, y); + const g1 = G1Point.fromAffine({ x, y }); deepStrictEqual( g1.toHex(false), '17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' @@ -702,7 +709,7 @@ describe('bls12-381/basic', () => { should('get uncompressed form of point G1 (Hex)', () => { // Test Zero - deepStrictEqual(bls.G1.Point.ZERO.toHex(false), B_192_40); + deepStrictEqual(G1Point.ZERO.toHex(false), B_192_40); // Test Non-Zero const x = bls.Fp.create( BigInt( @@ -714,7 +721,7 @@ describe('bls12-381/basic', () => { '0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' ) ); - const g1 = new bls.G1.Point(x, y); + const g1 = G1Point.fromAffine({ x, y }); deepStrictEqual( g1.toHex(false), '17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' @@ -723,7 +730,7 @@ describe('bls12-381/basic', () => { should('get uncompressed form of point G2 (Raw Bytes)', () => { // Test Zero - deepStrictEqual(bls.G2.Point.ZERO.toHex(false), B_384_40); + deepStrictEqual(G2Point.ZERO.toHex(false), B_384_40); // Test Non-Zero const x = Fp2.fromBigTuple([ BigInt( @@ -741,7 +748,7 @@ describe('bls12-381/basic', () => { '0x0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be' ), ]); - const g2 = new bls.G2.Point(x, y); + const g2 = G2Point.fromAffine({ x, y }); deepStrictEqual( g2.toHex(false), '13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801' @@ -750,7 +757,7 @@ describe('bls12-381/basic', () => { should('get uncompressed form of point G2 (Hex)', () => { // Test Zero - deepStrictEqual(bls.G2.Point.ZERO.toHex(false), B_384_40); + deepStrictEqual(G2Point.ZERO.toHex(false), B_384_40); // Test Non-Zero const x = Fp2.fromBigTuple([ @@ -769,7 +776,7 @@ describe('bls12-381/basic', () => { '0x0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be' ), ]); - const g2 = new bls.G2.Point(x, y); + const g2 = G2Point.fromAffine({ x, y }); deepStrictEqual( g2.toHex(false), '13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801' @@ -777,51 +784,54 @@ describe('bls12-381/basic', () => { }); should('compress and decompress G1 points', async () => { - const priv = bls.G1.Point.fromPrivateKey(42n); + const priv = G1Point.fromPrivateKey(42n); const publicKey = priv.toHex(true); - const decomp = bls.G1.Point.fromHex(publicKey); + const decomp = G1Point.fromHex(publicKey); deepStrictEqual(publicKey, decomp.toHex(true)); }); should('not compress and decompress zero G1 point', () => { - throws(() => bls.G1.Point.fromPrivateKey(0n)); + throws(() => G1Point.fromPrivateKey(0n)); }); should('compress and decompress G2 points', () => { - const priv = bls.G2.Point.fromPrivateKey(42n); + const priv = G2Point.fromPrivateKey(42n); const publicKey = priv.toHex(true); - const decomp = bls.G2.Point.fromHex(publicKey); + const decomp = G2Point.fromHex(publicKey); deepStrictEqual(publicKey, decomp.toHex(true)); }); should('not compress and decompress zero G2 point', () => { - throws(() => bls.G2.Point.fromPrivateKey(0n)); + throws(() => G2Point.fromPrivateKey(0n)); }); - const VALID_G1 = new bls.G1.Point( + const VALID_G1 = new G1Point( bls.Fp.create( 3609742242174788176010452839163620388872641749536604986743596621604118973777515189035770461528205168143692110933639n ), bls.Fp.create( 1619277690257184054444116778047375363103842303863153349133480657158810226683757397206929105479676799650932070320089n - ) + ), + bls.Fp.create(1n) ); - const VALID_G1_2 = new bls.G1.Point( + const VALID_G1_2 = new G1Point( bls.Fp.create( 1206972466279728255044019580914616126536509750250979180256809997983196363639429409634110400978470384566664128085207n ), bls.Fp.create( 2991142246317096160788653339959532007292638191110818490939476869616372888657136539642598243964263069435065725313423n - ) + ), + bls.Fp.create(1n) ); - const INVALID_G1 = new bls.G1.Point( + const INVALID_G1 = new G1Point( bls.Fp.create( 499001545268060011619089734015590154568173930614466321429631711131511181286230338880376679848890024401335766847607n ), bls.Fp.create( 3934582309586258715640230772291917282844636728991757779640464479794033391537662970190753981664259511166946374555673n - ) + ), + bls.Fp.create(1n) ); should('aggregate pubkeys', () => { - const agg = bls.aggregatePublicKeys([VALID_G1, VALID_G1_2]); + const agg = bls.aggregatePublicKeys([VALID_G1, VALID_G1_2]).toAffine(); deepStrictEqual( agg.x, 2636337749883017793009944726560363863546595464242083394883491066895536780554574413337005575305023872925406746684807n @@ -840,7 +850,7 @@ describe('bls12-381/basic', () => { should(`produce correct signatures (${G2_VECTORS.length} vectors)`, async () => { for (let vector of G2_VECTORS) { const [priv, msg, expected] = vector; - const sig = await bls.sign(msg, priv); + const sig = bls.sign(msg, priv); deepStrictEqual(bls.utils.bytesToHex(sig), expected); } }); @@ -854,139 +864,17 @@ describe('bls12-381/basic', () => { const [okmAscii, expectedHex] = vector; const expected = BigInt('0x' + expectedHex); const okm = new Uint8Array(okmAscii.split('').map((c) => c.charCodeAt(0))); - const scalars = await bls.utils.hashToField(okm, 1, options); + const scalars = bls.utils.hashToField(okm, 1, options); deepStrictEqual(scalars[0][0], expected); } }); - should('verify signed message', async () => { - for (let i = 0; i < NUM_RUNS; i++) { - const [priv, msg] = G2_VECTORS[i]; - const sig = await bls.sign(msg, priv); - const pub = bls.getPublicKey(priv); - const res = await bls.verify(sig, msg, pub); - deepStrictEqual(res, true); - } - }); - should('not verify signature with wrong message', async () => { - for (let i = 0; i < NUM_RUNS; i++) { - const [priv, msg] = G2_VECTORS[i]; - const invMsg = G2_VECTORS[i + 1][1]; - const sig = await bls.sign(msg, priv); - const pub = bls.getPublicKey(priv); - const res = await bls.verify(sig, invMsg, pub); - deepStrictEqual(res, false); - } - }); - should('not verify signature with wrong key', async () => { - for (let i = 0; i < NUM_RUNS; i++) { - const [priv, msg] = G2_VECTORS[i]; - const sig = await bls.sign(msg, priv); - const invPriv = G2_VECTORS[i + 1][1].padStart(64, '0'); - const invPub = bls.getPublicKey(invPriv); - const res = await bls.verify(sig, msg, invPub); - deepStrictEqual(res, false); - } - }); - should('verify multi-signature', async () => { - await fc.assert( - fc.asyncProperty(FC_MSG_5, FC_BIGINT_5, async (messages, privateKeys) => { - privateKeys = privateKeys.slice(0, messages.length); - messages = messages.slice(0, privateKeys.length); - const publicKey = privateKeys.map(getPubKey); - const signatures = await Promise.all( - messages.map((message, i) => bls.sign(message, privateKeys[i])) - ); - const aggregatedSignature = await bls.aggregateSignatures(signatures); - deepStrictEqual(await bls.verifyBatch(aggregatedSignature, messages, publicKey), true); - }) - ); - }); - should('batch verify multi-signatures', async () => { - await fc.assert( - fc.asyncProperty( - FC_MSG_5, - FC_MSG_5, - FC_BIGINT_5, - async (messages, wrongMessages, privateKeys) => { - privateKeys = privateKeys.slice(0, messages.length); - messages = messages.slice(0, privateKeys.length); - wrongMessages = messages.map((a, i) => - typeof wrongMessages[i] === 'undefined' ? a : wrongMessages[i] - ); - const publicKey = await Promise.all(privateKeys.map(getPubKey)); - const signatures = await Promise.all( - messages.map((message, i) => bls.sign(message, privateKeys[i])) - ); - const aggregatedSignature = await bls.aggregateSignatures(signatures); - deepStrictEqual( - await bls.verifyBatch(aggregatedSignature, wrongMessages, publicKey), - messages.every((m, i) => m === wrongMessages[i]) - ); - } - ) - ); - }); - should('not verify multi-signature with wrong public keys', async () => { - await fc.assert( - fc.asyncProperty( - FC_MSG_5, - FC_BIGINT_5, - FC_BIGINT_5, - async (messages, privateKeys, wrongPrivateKeys) => { - privateKeys = privateKeys.slice(0, messages.length); - wrongPrivateKeys = privateKeys.map((a, i) => - wrongPrivateKeys[i] !== undefined ? wrongPrivateKeys[i] : a - ); - messages = messages.slice(0, privateKeys.length); - const wrongPublicKeys = await Promise.all(wrongPrivateKeys.map(getPubKey)); - const signatures = await Promise.all( - messages.map((message, i) => bls.sign(message, privateKeys[i])) - ); - const aggregatedSignature = await bls.aggregateSignatures(signatures); - deepStrictEqual( - await bls.verifyBatch(aggregatedSignature, messages, wrongPublicKeys), - wrongPrivateKeys.every((p, i) => p === privateKeys[i]) - ); - } - ) - ); - }); - should('verify multi-signature as simple signature', async () => { - await fc.assert( - fc.asyncProperty(FC_MSG, FC_BIGINT_5, async (message, privateKeys) => { - const publicKey = await Promise.all(privateKeys.map(getPubKey)); - const signatures = await Promise.all( - privateKeys.map((privateKey) => bls.sign(message, privateKey)) - ); - const aggregatedSignature = await bls.aggregateSignatures(signatures); - const aggregatedPublicKey = await bls.aggregatePublicKeys(publicKey); - deepStrictEqual(await bls.verify(aggregatedSignature, message, aggregatedPublicKey), true); - }) - ); - }); - should('not verify wrong multi-signature as simple signature', async () => { - await fc.assert( - fc.asyncProperty(FC_MSG, FC_MSG, FC_BIGINT_5, async (message, wrongMessage, privateKeys) => { - const publicKey = await Promise.all(privateKeys.map(getPubKey)); - const signatures = await Promise.all( - privateKeys.map((privateKey) => bls.sign(message, privateKey)) - ); - const aggregatedSignature = await bls.aggregateSignatures(signatures); - const aggregatedPublicKey = await bls.aggregatePublicKeys(publicKey); - deepStrictEqual( - await bls.verify(aggregatedSignature, wrongMessage, aggregatedPublicKey), - message === wrongMessage - ); - }) - ); - }); }); // Pairing describe('pairing', () => { const { pairing, Fp12 } = bls; - const G1 = bls.G1.Point.BASE; - const G2 = bls.G2.Point.BASE; + const G1 = G1Point.BASE; + const G2 = G2Point.BASE; should('creates negative G1 pairing', () => { const p1 = pairing(G1, G2); @@ -1187,7 +1075,7 @@ describe('hash-to-curve', () => { for (let i = 0; i < VECTORS.length; i++) { const t = VECTORS[i]; should(`hash_to_field/expand_message_xmd(SHA-256) (${i})`, async () => { - const p = await bls.utils.expandMessageXMD( + const p = bls.utils.expandMessageXMD( bls.utils.stringToBytes(t.msg), bls.utils.stringToBytes(DST), t.len @@ -1305,7 +1193,7 @@ describe('hash-to-curve', () => { for (let i = 0; i < VECTORS_BIG.length; i++) { const t = VECTORS_BIG[i]; should(`hash_to_field/expand_message_xmd(SHA-256) (long DST) (${i})`, async () => { - const p = await bls.utils.expandMessageXMD( + const p = bls.utils.expandMessageXMD( bls.utils.stringToBytes(t.msg), bls.utils.stringToBytes(LONG_DST), t.len @@ -1418,7 +1306,7 @@ describe('hash-to-curve', () => { for (let i = 0; i < VECTORS_SHA512.length; i++) { const t = VECTORS_SHA512[i]; should(`hash_to_field/expand_message_xmd(SHA-256) (long DST) (${i})`, async () => { - const p = await bls.utils.expandMessageXMD( + const p = bls.utils.expandMessageXMD( bls.utils.stringToBytes(t.msg), bls.utils.stringToBytes(DST_512), t.len, @@ -1463,7 +1351,7 @@ describe('hash-to-curve', () => { const p = bls.hashToCurve.G1.hashToCurve(t.msg, { DST: 'BLS12381G1_XMD:SHA-256_SSWU_RO_TESTGEN', }); - deepStrictEqual(p.toHex(), t.expected); + deepStrictEqual(p.toHex(false), t.expected); }); } const VECTORS_G1_RO = [ @@ -1518,7 +1406,7 @@ describe('hash-to-curve', () => { const p = bls.hashToCurve.G1.hashToCurve(t.msg, { DST: 'QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_', }); - deepStrictEqual(p.toHex(), t.expected); + deepStrictEqual(p.toHex(false), t.expected); }); } const VECTORS_G1_NU = [ @@ -1564,7 +1452,7 @@ describe('hash-to-curve', () => { const p = bls.hashToCurve.G1.encodeToCurve(t.msg, { DST: 'QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_NU_', }); - deepStrictEqual(p.toHex(), t.expected); + deepStrictEqual(p.toHex(false), t.expected); }); } const VECTORS_ENCODE_G1 = [ @@ -1601,7 +1489,7 @@ describe('hash-to-curve', () => { const p = bls.hashToCurve.G1.encodeToCurve(t.msg, { DST: 'BLS12381G1_XMD:SHA-256_SSWU_NU_TESTGEN', }); - deepStrictEqual(p.toHex(), t.expected); + deepStrictEqual(p.toHex(false), t.expected); }); } // Point G2 @@ -1647,7 +1535,7 @@ describe('hash-to-curve', () => { const p = bls.hashToCurve.G2.hashToCurve(t.msg, { DST: 'BLS12381G2_XMD:SHA-256_SSWU_RO_TESTGEN', }); - deepStrictEqual(p.toHex(), t.expected); + deepStrictEqual(p.toHex(false), t.expected); }); } const VECTORS_G2_RO = [ @@ -1712,7 +1600,7 @@ describe('hash-to-curve', () => { const p = bls.hashToCurve.G2.hashToCurve(t.msg, { DST: 'QUUX-V01-CS02-with-BLS12381G2_XMD:SHA-256_SSWU_RO_', }); - deepStrictEqual(p.toHex(), t.expected); + deepStrictEqual(p.toHex(false), t.expected); }); } const VECTORS_G2_NU = [ @@ -1777,7 +1665,7 @@ describe('hash-to-curve', () => { const p = bls.hashToCurve.G2.encodeToCurve(t.msg, { DST: 'QUUX-V01-CS02-with-BLS12381G2_XMD:SHA-256_SSWU_NU_', }); - deepStrictEqual(p.toHex(), t.expected); + deepStrictEqual(p.toHex(false), t.expected); }); } const VECTORS_ENCODE_G2 = [ @@ -1822,10 +1710,132 @@ describe('hash-to-curve', () => { const p = bls.hashToCurve.G2.encodeToCurve(t.msg, { DST: 'BLS12381G2_XMD:SHA-256_SSWU_NU_TESTGEN', }); - deepStrictEqual(p.toHex(), t.expected); + deepStrictEqual(p.toHex(false), t.expected); }); } }); + +describe('verify()', () => { + should('verify signed message', async () => { + for (let i = 0; i < NUM_RUNS; i++) { + const [priv, msg] = G2_VECTORS[i]; + const sig = bls.sign(msg, priv); + const pub = bls.getPublicKey(priv); + const res = bls.verify(sig, msg, pub); + deepStrictEqual(res, true, `${priv}-${msg}`); + } + }); + should('not verify signature with wrong message', async () => { + for (let i = 0; i < NUM_RUNS; i++) { + const [priv, msg] = G2_VECTORS[i]; + const invMsg = G2_VECTORS[i + 1][1]; + const sig = bls.sign(msg, priv); + const pub = bls.getPublicKey(priv); + const res = bls.verify(sig, invMsg, pub); + deepStrictEqual(res, false); + } + }); + should('not verify signature with wrong key', async () => { + for (let i = 0; i < NUM_RUNS; i++) { + const [priv, msg] = G2_VECTORS[i]; + const sig = bls.sign(msg, priv); + const invPriv = G2_VECTORS[i + 1][1].padStart(64, '0'); + const invPub = bls.getPublicKey(invPriv); + const res = bls.verify(sig, msg, invPub); + deepStrictEqual(res, false); + } + }); + describe('batch', () => { + should('verify multi-signature', async () => { + await fc.assert( + fc.asyncProperty(FC_MSG_5, FC_BIGINT_5, async (messages, privateKeys) => { + privateKeys = privateKeys.slice(0, messages.length); + messages = messages.slice(0, privateKeys.length); + const publicKey = privateKeys.map(getPubKey); + const signatures = messages.map((message, i) => bls.sign(message, privateKeys[i])); + const aggregatedSignature = bls.aggregateSignatures(signatures); + deepStrictEqual(bls.verifyBatch(aggregatedSignature, messages, publicKey), true); + }) + ); + }); + should('batch verify multi-signatures', async () => { + await fc.assert( + fc.asyncProperty( + FC_MSG_5, + FC_MSG_5, + FC_BIGINT_5, + async (messages, wrongMessages, privateKeys) => { + privateKeys = privateKeys.slice(0, messages.length); + messages = messages.slice(0, privateKeys.length); + wrongMessages = messages.map((a, i) => + typeof wrongMessages[i] === 'undefined' ? a : wrongMessages[i] + ); + const publicKey = privateKeys.map(getPubKey); + const signatures = messages.map((message, i) => bls.sign(message, privateKeys[i])); + const aggregatedSignature = bls.aggregateSignatures(signatures); + deepStrictEqual( + bls.verifyBatch(aggregatedSignature, wrongMessages, publicKey), + messages.every((m, i) => m === wrongMessages[i]) + ); + } + ) + ); + }); + should('not verify multi-signature with wrong public keys', async () => { + await fc.assert( + fc.asyncProperty( + FC_MSG_5, + FC_BIGINT_5, + FC_BIGINT_5, + async (messages, privateKeys, wrongPrivateKeys) => { + privateKeys = privateKeys.slice(0, messages.length); + wrongPrivateKeys = privateKeys.map((a, i) => + wrongPrivateKeys[i] !== undefined ? wrongPrivateKeys[i] : a + ); + messages = messages.slice(0, privateKeys.length); + const wrongPublicKeys = wrongPrivateKeys.map(getPubKey); + const signatures = messages.map((message, i) => bls.sign(message, privateKeys[i])); + const aggregatedSignature = bls.aggregateSignatures(signatures); + deepStrictEqual( + bls.verifyBatch(aggregatedSignature, messages, wrongPublicKeys), + wrongPrivateKeys.every((p, i) => p === privateKeys[i]) + ); + } + ) + ); + }); + should('verify multi-signature as simple signature', async () => { + await fc.assert( + fc.asyncProperty(FC_MSG, FC_BIGINT_5, async (message, privateKeys) => { + const publicKey = privateKeys.map(getPubKey); + const signatures = privateKeys.map((privateKey) => bls.sign(message, privateKey)); + const aggregatedSignature = bls.aggregateSignatures(signatures); + const aggregatedPublicKey = bls.aggregatePublicKeys(publicKey); + deepStrictEqual(bls.verify(aggregatedSignature, message, aggregatedPublicKey), true); + }) + ); + }); + should('not verify wrong multi-signature as simple signature', async () => { + await fc.assert( + fc.asyncProperty( + FC_MSG, + FC_MSG, + FC_BIGINT_5, + async (message, wrongMessage, privateKeys) => { + const publicKey = privateKeys.map(getPubKey); + const signatures = privateKeys.map((privateKey) => bls.sign(message, privateKey)); + const aggregatedSignature = bls.aggregateSignatures(signatures); + const aggregatedPublicKey = bls.aggregatePublicKeys(publicKey); + deepStrictEqual( + bls.verify(aggregatedSignature, wrongMessage, aggregatedPublicKey), + message === wrongMessage + ); + } + ) + ); + }); + }); +}); // Deterministic describe('bls12-381 deterministic', () => { // NOTE: Killic returns all items in reversed order, which looks strange: @@ -1838,7 +1848,7 @@ describe('bls12-381 deterministic', () => { const Fp12 = bls.Fp12; should('Killic based/Pairing', () => { - const t = bls.pairing(bls.G1.Point.BASE, bls.G2.Point.BASE); + const t = bls.pairing(G1Point.BASE, G2Point.BASE); deepStrictEqual( bls.utils.bytesToHex(Fp12.toBytes(t)), killicHex([ @@ -1858,80 +1868,81 @@ describe('bls12-381 deterministic', () => { ); }); should('Killic based/Pairing (big)', () => { - let p1 = bls.G1.Point.BASE; - let p2 = bls.G2.Point.BASE; + let p1 = G1Point.BASE; + let p2 = G2Point.BASE; for (let v of pairingVectors) { deepStrictEqual( bls.utils.bytesToHex(Fp12.toBytes(bls.pairing(p1, p2))), // Reverse order v.match(/.{96}/g).reverse().join('') ); - p1 = p1.add(bls.G1.Point.BASE); - p2 = p2.add(bls.G2.Point.BASE); + p1 = p1.add(G1Point.BASE); + p2 = p2.add(G2Point.BASE); } }); should(`zkcrypto/G1/compressed`, () => { - let p1 = bls.G1.Point.ZERO; + let p1 = G1Point.ZERO; for (let i = 0; i < zkVectors.G1_Compressed.length; i++) { const t = zkVectors.G1_Compressed[i]; - const P = bls.G1.Point.fromHex(t); + const P = G1Point.fromHex(t); deepStrictEqual(P.toHex(true), t); deepStrictEqual(P.equals(p1), true); deepStrictEqual(p1.toHex(true), t); - p1 = p1.add(bls.G1.Point.BASE); + p1 = p1.add(G1Point.BASE); if (i) { - deepStrictEqual(bls.G1.Point.BASE.multiply(BigInt(i)).toHex(true), t); - deepStrictEqual(bls.G1.Point.BASE.multiplyUnsafe(BigInt(i)).toHex(true), t); - deepStrictEqual(bls.G1.Point.BASE.multiply(BigInt(i)).toHex(true), t); + deepStrictEqual(G1Point.BASE.multiply(BigInt(i)).toHex(true), t); + deepStrictEqual(G1Point.BASE.multiplyUnsafe(BigInt(i)).toHex(true), t); + deepStrictEqual(G1Point.BASE.multiply(BigInt(i)).toHex(true), t); } } }); should(`zkcrypto/G1/uncompressed`, () => { - let p1 = bls.G1.Point.ZERO; + let p1 = G1Point.ZERO; for (let i = 0; i < zkVectors.G1_Uncompressed.length; i++) { const t = zkVectors.G1_Uncompressed[i]; - const P = bls.G1.Point.fromHex(t); - deepStrictEqual(P.toHex(), t); + const P = G1Point.fromHex(t); + deepStrictEqual(P.toHex(false), t); deepStrictEqual(P.equals(p1), true); - deepStrictEqual(p1.toHex(), t); - p1 = p1.add(bls.G1.Point.BASE); + deepStrictEqual(p1.toHex(false), t); + p1 = p1.add(G1Point.BASE); if (i) { - deepStrictEqual(bls.G1.Point.BASE.multiply(BigInt(i)).toHex(), t); - deepStrictEqual(bls.G1.Point.BASE.multiplyUnsafe(BigInt(i)).toHex(), t); - deepStrictEqual(bls.G1.Point.BASE.multiply(BigInt(i)).toHex(), t); + deepStrictEqual(G1Point.BASE.multiply(BigInt(i)).toHex(false), t); + deepStrictEqual(G1Point.BASE.multiplyUnsafe(BigInt(i)).toHex(false), t); + deepStrictEqual(G1Point.BASE.multiply(BigInt(i)).toHex(false), t); } } }); should(`zkcrypto/G2/compressed`, () => { - let p1 = bls.G2.Point.ZERO; + let p1 = G2Point.ZERO; for (let i = 0; i < zkVectors.G2_Compressed.length; i++) { const t = zkVectors.G2_Compressed[i]; - const P = bls.G2.Point.fromHex(t); + const P = G2Point.fromHex(t); deepStrictEqual(P.toHex(true), t); deepStrictEqual(P.equals(p1), true); deepStrictEqual(p1.toHex(true), t); - p1 = p1.add(bls.G2.Point.BASE); + p1 = p1.add(G2Point.BASE); if (i) { - deepStrictEqual(bls.G2.Point.BASE.multiply(BigInt(i)).toHex(true), t); - deepStrictEqual(bls.G2.Point.BASE.multiplyUnsafe(BigInt(i)).toHex(true), t); - deepStrictEqual(bls.G2.Point.BASE.multiply(BigInt(i)).toHex(true), t); + let n = BigInt(i); + deepStrictEqual(G2Point.BASE.multiply(n).toHex(true), t); + deepStrictEqual(G2Point.BASE.multiplyUnsafe(n).toHex(true), t); + deepStrictEqual(G2Point.BASE.multiply(n).toHex(true), t); } } }); should(`zkcrypto/G2/uncompressed`, () => { - let p1 = bls.G2.Point.ZERO; + let p1 = G2Point.ZERO; for (let i = 0; i < zkVectors.G2_Uncompressed.length; i++) { const t = zkVectors.G2_Uncompressed[i]; - const P = bls.G2.Point.fromHex(t); - deepStrictEqual(P.toHex(), t); + const P = G2Point.fromHex(t); + deepStrictEqual(P.toHex(false), t); deepStrictEqual(P.equals(p1), true); - deepStrictEqual(p1.toHex(), t); - p1 = p1.add(bls.G2.Point.BASE); + deepStrictEqual(p1.toHex(false), t); + p1 = p1.add(G2Point.BASE); if (i) { - deepStrictEqual(bls.G2.Point.BASE.multiply(BigInt(i)).toHex(), t); - deepStrictEqual(bls.G2.Point.BASE.multiplyUnsafe(BigInt(i)).toHex(), t); - deepStrictEqual(bls.G2.Point.BASE.multiply(BigInt(i)).toHex(), t); + deepStrictEqual(G2Point.BASE.multiply(BigInt(i)).toHex(false), t); + deepStrictEqual(G2Point.BASE.multiplyUnsafe(BigInt(i)).toHex(false), t); + deepStrictEqual(G2Point.BASE.multiply(BigInt(i)).toHex(false), t); } } }); diff --git a/test/secp256k1.test.js b/test/secp256k1.test.js index 9fda3fd..88e6731 100644 --- a/test/secp256k1.test.js +++ b/test/secp256k1.test.js @@ -144,7 +144,7 @@ describe('secp256k1', () => { const { P, d, expected } = vector; const p = Point.fromHex(P); if (expected) { - deepStrictEqual(p.multiply(hexToNumber(d)).toHex(true), expected); + deepStrictEqual(p.multiply(hexToNumber(d)).toHex(true), expected, P); } else { throws(() => { p.multiply(hexToNumber(d)).toHex(true); @@ -314,7 +314,7 @@ describe('secp256k1', () => { const r = 1n; const s = 115792089237316195423570985008687907852837564279074904382605163141518162728904n; - const pub = new Point(x, y); + const pub = new Point(x, y, 1n); const signature = new secp.Signature(2n, 2n); signature.r = r; signature.s = s; @@ -329,7 +329,7 @@ describe('secp256k1', () => { const y = 32670510020758816978083085130507043184471273380659243275938904335757337482424n; const r = 104546003225722045112039007203142344920046999340768276760147352389092131869133n; const s = 96900796730960181123786672629079577025401317267213807243199432755332205217369n; - const pub = new Point(x, y); + const pub = new Point(x, y, 1n); const sig = new secp.Signature(r, s); deepStrictEqual(secp.verify(sig, msg, pub), false); }); @@ -339,7 +339,7 @@ describe('secp256k1', () => { const y = 17482644437196207387910659778872952193236850502325156318830589868678978890912n; const r = 432420386565659656852420866390673177323n; const s = 115792089237316195423570985008687907852837564279074904382605163141518161494334n; - const pub = new Point(x, y); + const pub = new Point(x, y, 1n); const sig = new secp.Signature(r, s); deepStrictEqual(secp.verify(sig, msg, pub, { strict: false }), true); }); @@ -452,7 +452,7 @@ describe('secp256k1', () => { should('have proper curve equation in assertValidity()', () => { throws(() => { const { Fp } = secp.CURVE; - let point = new Point(Fp.create(-2n), Fp.create(-1n)); + let point = new Point(Fp.create(-2n), Fp.create(-1n), Fp.create(1n)); point.assertValidity(); }); });