More AffinePoint fixes

This commit is contained in:
Paul Miller 2023-01-24 23:07:25 +00:00
parent 2ed27da8eb
commit f14b8d2be5
No known key found for this signature in database
GPG Key ID: 697079DA6878B89B
9 changed files with 470 additions and 445 deletions

@ -286,11 +286,11 @@ export const CURVES = {
}, },
noble: () => { noble: () => {
p1 = p1 =
bls.G1.Point.BASE.multiply( bls.G1.ProjectivePoint.BASE.multiply(
0x28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4cn 0x28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4cn
); );
p2 = p2 =
bls.G2.Point.BASE.multiply( bls.G2.ProjectivePoint.BASE.multiply(
0x28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4dn 0x28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4dn
); );
bls.pairing(p1, p2); bls.pairing(p1, p2);
@ -334,12 +334,12 @@ export const CURVES = {
'hashToCurve/G1': { 'hashToCurve/G1': {
samples: 500, samples: 500,
old: () => old_bls.PointG1.hashToCurve('abcd'), old: () => old_bls.PointG1.hashToCurve('abcd'),
noble: () => bls.G1.Point.hashToCurve('abcd'), noble: () => bls.hashToCurve.G1.hashToCurve('abcd'),
}, },
'hashToCurve/G2': { 'hashToCurve/G2': {
samples: 200, samples: 200,
old: () => old_bls.PointG2.hashToCurve('abcd'), old: () => old_bls.PointG2.hashToCurve('abcd'),
noble: () => bls.G2.Point.hashToCurve('abcd'), noble: () => bls.hashToCurve.G2.hashToCurve('abcd'),
}, },
// SLOW PART // SLOW PART
// Requires points which we cannot init before (data fn same for all) // Requires points which we cannot init before (data fn same for all)
@ -353,22 +353,22 @@ export const CURVES = {
'aggregatePublicKeys/32': { 'aggregatePublicKeys/32': {
samples: 50, samples: 50,
old: ({ pub32 }) => old_bls.aggregatePublicKeys(pub32.map(old_bls.PointG1.fromHex)), 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': { 'aggregatePublicKeys/128': {
samples: 20, samples: 20,
old: ({ pub128 }) => old_bls.aggregatePublicKeys(pub128.map(old_bls.PointG1.fromHex)), 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': { 'aggregatePublicKeys/512': {
samples: 10, samples: 10,
old: ({ pub512 }) => old_bls.aggregatePublicKeys(pub512.map(old_bls.PointG1.fromHex)), 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': { 'aggregatePublicKeys/2048': {
samples: 5, samples: 5,
old: ({ pub2048 }) => old_bls.aggregatePublicKeys(pub2048.map(old_bls.PointG1.fromHex)), 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': { 'aggregateSignatures/8': {
samples: 50, samples: 50,

@ -21,6 +21,7 @@ import {
ProjectivePointType as PPointType, ProjectivePointType as PPointType,
CurvePointsRes, CurvePointsRes,
weierstrassPoints, weierstrassPoints,
AffinePoint,
} from './weierstrass.js'; } from './weierstrass.js';
type Fp = bigint; // Can be different field? type Fp = bigint; // Can be different field?
@ -72,7 +73,7 @@ export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
G2: CurvePointsRes<Fp2>; G2: CurvePointsRes<Fp2>;
Signature: SignatureCoder<Fp2>; Signature: SignatureCoder<Fp2>;
millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12; millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12;
calcPairingPrecomputes: (x: Fp2, y: Fp2) => [Fp2, Fp2, Fp2][]; calcPairingPrecomputes: (p: AffinePoint<Fp2>) => [Fp2, Fp2, Fp2][];
// prettier-ignore // prettier-ignore
hashToCurve: { hashToCurve: {
G1: ReturnType<(typeof htf.hashToCurve<Fp>)>, G1: ReturnType<(typeof htf.hashToCurve<Fp>)>,
@ -119,7 +120,8 @@ export function bls<Fp2, Fp6, Fp12>(
// Pre-compute coefficients for sparse multiplication // Pre-compute coefficients for sparse multiplication
// Point addition and point double calculations is reused for coefficients // Point addition and point double calculations is reused for coefficients
function calcPairingPrecomputes(x: Fp2, y: Fp2) { function calcPairingPrecomputes(p: AffinePoint<Fp2>) {
const { x, y } = p;
// prettier-ignore // prettier-ignore
const Qx = x, Qy = y, Qz = Fp2.ONE; const Qx = x, Qy = y, Qz = Fp2.ONE;
// prettier-ignore // prettier-ignore
@ -212,19 +214,15 @@ export function bls<Fp2, Fp6, Fp12>(
function pairingPrecomputes(point: G2): [Fp2, Fp2, Fp2][] { function pairingPrecomputes(point: G2): [Fp2, Fp2, Fp2][] {
const p = point as G2 & withPairingPrecomputes; const p = point as G2 & withPairingPrecomputes;
if (p._PPRECOMPUTES) return p._PPRECOMPUTES; if (p._PPRECOMPUTES) return p._PPRECOMPUTES;
p._PPRECOMPUTES = calcPairingPrecomputes(p.x, p.y); p._PPRECOMPUTES = calcPairingPrecomputes(point.toAffine());
return p._PPRECOMPUTES; return p._PPRECOMPUTES;
} }
function clearPairingPrecomputes(point: G2) { // TODO: export
const p = point as G2 & withPairingPrecomputes; // function clearPairingPrecomputes(point: G2) {
p._PPRECOMPUTES = undefined; // 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]);
}
// Point on G2 curve (complex numbers): (x₁, x₂+i), (y₁, y₂+i) // Point on G2 curve (complex numbers): (x₁, x₂+i), (y₁, y₂+i)
const G2 = weierstrassPoints({ const G2 = weierstrassPoints({
@ -240,13 +238,14 @@ export function bls<Fp2, Fp6, Fp12>(
const { Signature } = CURVE.G2; const { Signature } = CURVE.G2;
// Calculates bilinear pairing // Calculates bilinear pairing
function pairing(P: G1, Q: G2, withFinalExponent: boolean = true): Fp12 { function pairing(Q: G1, P: G2, withFinalExponent: boolean = true): Fp12 {
if (P.equals(G1.ProjectivePoint.ZERO) || Q.equals(G2.ProjectivePoint.ZERO)) if (Q.equals(G1.ProjectivePoint.ZERO) || P.equals(G2.ProjectivePoint.ZERO))
throw new Error('No pairings at point of Infinity'); throw new Error('pairing is not available for ZERO point');
P.assertValidity();
Q.assertValidity(); Q.assertValidity();
P.assertValidity();
// Performance: 9ms for millerLoop and ~14ms for exp. // 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; return withFinalExponent ? Fp12.finalExponentiate(looped) : looped;
} }
type G1 = typeof G1.ProjectivePoint.BASE; type G1 = typeof G1.ProjectivePoint.BASE;
@ -310,9 +309,7 @@ export function bls<Fp2, Fp6, Fp12>(
function aggregatePublicKeys(publicKeys: G1[]): G1; function aggregatePublicKeys(publicKeys: G1[]): G1;
function aggregatePublicKeys(publicKeys: G1Hex[]): Uint8Array | G1 { function aggregatePublicKeys(publicKeys: G1Hex[]): Uint8Array | G1 {
if (!publicKeys.length) throw new Error('Expected non-empty array'); if (!publicKeys.length) throw new Error('Expected non-empty array');
const agg = publicKeys const agg = publicKeys.map(normP1).reduce((sum, p) => sum.add(p), G1.ProjectivePoint.ZERO);
.map(normP1)
.reduce((sum, p) => sum.add(G1.ProjectivePoint.fromAffine(p)), G1.ProjectivePoint.ZERO);
const aggAffine = agg; //.toAffine(); const aggAffine = agg; //.toAffine();
if (publicKeys[0] instanceof G1.ProjectivePoint) { if (publicKeys[0] instanceof G1.ProjectivePoint) {
aggAffine.assertValidity(); aggAffine.assertValidity();
@ -327,9 +324,7 @@ export function bls<Fp2, Fp6, Fp12>(
function aggregateSignatures(signatures: G2[]): G2; function aggregateSignatures(signatures: G2[]): G2;
function aggregateSignatures(signatures: G2Hex[]): Uint8Array | G2 { function aggregateSignatures(signatures: G2Hex[]): Uint8Array | G2 {
if (!signatures.length) throw new Error('Expected non-empty array'); if (!signatures.length) throw new Error('Expected non-empty array');
const agg = signatures const agg = signatures.map(normP2).reduce((sum, s) => sum.add(s), G2.ProjectivePoint.ZERO);
.map(normP2)
.reduce((sum, s) => sum.add(G2.ProjectivePoint.fromAffine(s)), G2.ProjectivePoint.ZERO);
const aggAffine = agg; //.toAffine(); const aggAffine = agg; //.toAffine();
if (signatures[0] instanceof G2.ProjectivePoint) { if (signatures[0] instanceof G2.ProjectivePoint) {
aggAffine.assertValidity(); aggAffine.assertValidity();
@ -346,6 +341,9 @@ export function bls<Fp2, Fp6, Fp12>(
publicKeys: G1Hex[], publicKeys: G1Hex[],
htfOpts?: htf.htfBasicOpts htfOpts?: htf.htfBasicOpts
): boolean { ): 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 (!messages.length) throw new Error('Expected non-empty messages array');
if (publicKeys.length !== messages.length) if (publicKeys.length !== messages.length)
throw new Error('Pubkey count should equal msg count'); throw new Error('Pubkey count should equal msg count');
@ -373,9 +371,8 @@ export function bls<Fp2, Fp6, Fp12>(
} }
} }
// Pre-compute points. Refer to README. G1.ProjectivePoint.BASE._setWindowSize(4);
// TODO
// G1.ProjectivePoint.BASE._setWindowSize(4);
return { return {
CURVE, CURVE,
Fr, Fr,

@ -38,7 +38,7 @@ export type CurveType = ut.BasicCurve<bigint> & {
uvRatio?: (u: bigint, v: bigint) => { isValid: boolean; value: bigint }; uvRatio?: (u: bigint, v: bigint) => { isValid: boolean; value: bigint };
// RFC 8032 pre-hashing of messages to sign() / verify() // RFC 8032 pre-hashing of messages to sign() / verify()
preHash?: ut.CHash; preHash?: ut.CHash;
mapToCurve?: (scalar: bigint[]) => { x: bigint; y: bigint }; mapToCurve?: (scalar: bigint[]) => AffinePoint;
}; };
function validateOpts(curve: CurveType) { function validateOpts(curve: CurveType) {
@ -61,10 +61,10 @@ function validateOpts(curve: CurveType) {
} }
// 2d point in XY coords // 2d point in XY coords
export interface AffinePoint { export type AffinePoint = {
x: bigint; x: bigint;
y: bigint; y: bigint;
} } & { z?: never; t?: never };
// Instance of Extended Point with coordinates in X, Y, Z, T // Instance of Extended Point with coordinates in X, Y, Z, T
export interface ExtendedPointType extends Group<ExtendedPointType> { export interface ExtendedPointType extends Group<ExtendedPointType> {
@ -76,16 +76,16 @@ export interface ExtendedPointType extends Group<ExtendedPointType> {
multiplyUnsafe(scalar: bigint): ExtendedPointType; multiplyUnsafe(scalar: bigint): ExtendedPointType;
isSmallOrder(): boolean; isSmallOrder(): boolean;
isTorsionFree(): boolean; isTorsionFree(): boolean;
toAffine(invZ?: bigint): AffinePoint; toAffine(iz?: bigint): AffinePoint;
clearCofactor(): ExtendedPointType; clearCofactor(): ExtendedPointType;
} }
// Static methods of Extended Point with coordinates in X, Y, Z, T // Static methods of Extended Point with coordinates in X, Y, Z, T
export interface ExtendedPointConstructor extends GroupConstructor<ExtendedPointType> { export interface ExtendedPointConstructor extends GroupConstructor<ExtendedPointType> {
new (x: bigint, y: bigint, z: bigint, t: bigint): ExtendedPointType; new (x: bigint, y: bigint, z: bigint, t: bigint): ExtendedPointType;
fromAffine(p: AffinePoint): ExtendedPointType; fromAffine(p: AffinePoint): ExtendedPointType;
toAffineBatch(points: ExtendedPointType[]): AffinePoint[];
fromHex(hex: Hex): ExtendedPointType; fromHex(hex: Hex): ExtendedPointType;
fromPrivateKey(privateKey: PrivKey): ExtendedPointType; // TODO: remove fromPrivateKey(privateKey: PrivKey): ExtendedPointType; // TODO: remove
toAffineBatch(points: ExtendedPointType[]): AffinePoint[];
} }
export type CurveFn = { export type CurveFn = {
@ -161,20 +161,21 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
* https://en.wikipedia.org/wiki/Twisted_Edwards_curve#Extended_coordinates * https://en.wikipedia.org/wiki/Twisted_Edwards_curve#Extended_coordinates
*/ */
class ExtendedPoint implements ExtendedPointType { class ExtendedPoint implements ExtendedPointType {
constructor( constructor(readonly x: bigint, readonly y: bigint, readonly z: bigint, readonly t: bigint) {
readonly x: bigint, if (y == null || !ut.big(y)) throw new Error('y required');
readonly y: bigint, if (z == null || !ut.big(z)) throw new Error('z required');
readonly z = _1n, if (t == null || !ut.big(t)) throw new Error('t required');
readonly t = modP(x * y) }
) {}
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 { static fromAffine(p: AffinePoint): ExtendedPoint {
if (!(p && typeof p === 'object' && typeof p.x === 'bigint' && typeof p.y === 'bigint')) const { x, y } = p || {};
throw new Error('fromAffine error'); if (p instanceof ExtendedPoint) throw new Error('fromAffine: extended point not allowed');
if (p.x === 0n && p.y === 1n) return ExtendedPoint.ZERO; if (!ut.big(x) || !ut.big(y)) throw new Error('fromAffine: invalid affine point');
return new ExtendedPoint(p.x, p.y); 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 // Takes a bunch of Jacobian Points but executes only one
// invert on all of them. invert is very slow operation, // 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); const Y2Z1 = modP(Y2 * Z1);
return X1Z2 === X2Z1 && Y1Z2 === Y2Z1; return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
} }
protected is0(): boolean {
return this.equals(ExtendedPoint.ZERO);
}
// Inverses point to one corresponding to (x, -y) in Affine coordinates. // Inverses point to one corresponding to (x, -y) in Affine coordinates.
negate(): ExtendedPoint { negate(): ExtendedPoint {
@ -297,20 +301,20 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
// point with torsion component. // point with torsion component.
// Multiplies point by cofactor and checks if the result is 0. // Multiplies point by cofactor and checks if the result is 0.
isSmallOrder(): boolean { isSmallOrder(): boolean {
return this.multiplyUnsafe(CURVE.h).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. // Multiplies point by curve order (very big scalar CURVE.n) and checks if the result is 0.
// Returns `false` is the point is dirty. // Returns `false` is the point is dirty.
isTorsionFree(): boolean { isTorsionFree(): boolean {
return wnaf.unsafeLadder(this, CURVE_ORDER).equals(ExtendedPoint.ZERO); return wnaf.unsafeLadder(this, CURVE_ORDER).is0();
} }
// Converts Extended point to default (x, y) coordinates. // Converts Extended point to default (x, y) coordinates.
// Can accept precomputed Z^-1 - for example, from invertBatch. // Can accept precomputed Z^-1 - for example, from invertBatch.
toAffine(iz?: bigint): AffinePoint { toAffine(iz?: bigint): AffinePoint {
const { x, y, z } = this; 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 if (iz == null) iz = is0 ? _8n : (Fp.invert(z) as bigint); // 8 was chosen arbitrarily
const ax = modP(x * iz); const ax = modP(x * iz);
const ay = modP(y * iz); const ay = modP(y * iz);
@ -369,7 +373,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
const isXOdd = (x & _1n) === _1n; const isXOdd = (x & _1n) === _1n;
const isLastByteOdd = (lastByte & 0x80) !== 0; const isLastByteOdd = (lastByte & 0x80) !== 0;
if (isLastByteOdd !== isXOdd) x = modP(-x); if (isLastByteOdd !== isXOdd) x = modP(-x);
return new ExtendedPoint(x, y); return ExtendedPoint.fromAffine({ x, y });
} }
static fromPrivateKey(privateKey: PrivKey) { static fromPrivateKey(privateKey: PrivKey) {
return getExtendedPublicKey(privateKey).point; return getExtendedPublicKey(privateKey).point;
@ -526,7 +530,6 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
sign, sign,
verify, verify,
ExtendedPoint, ExtendedPoint,
// Point: ExtendedPoint,
utils, utils,
}; };
} }

@ -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 * TODO: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-7.html#unique-symbol
*/ */
export interface AffinePoint<T> { export type AffinePoint<T> = {
x: T; x: T;
y: T; y: T;
} } & { z?: never };
// Instance for 3d XYZ points // Instance for 3d XYZ points
export interface ProjectivePointType<T> extends Group<ProjectivePointType<T>> { export interface ProjectivePointType<T> extends Group<ProjectivePointType<T>> {
readonly x: T; readonly x: T;
@ -125,7 +125,8 @@ export interface ProjectivePointType<T> extends Group<ProjectivePointType<T>> {
a: bigint, a: bigint,
b: bigint b: bigint
): ProjectivePointType<T> | undefined; ): ProjectivePointType<T> | undefined;
toAffine(invZ?: T): AffinePoint<T>; _setWindowSize(windowSize: number): void;
toAffine(iz?: T): AffinePoint<T>;
isTorsionFree(): boolean; isTorsionFree(): boolean;
clearCofactor(): ProjectivePointType<T>; clearCofactor(): ProjectivePointType<T>;
assertValidity(): void; assertValidity(): void;
@ -135,19 +136,17 @@ export interface ProjectivePointType<T> extends Group<ProjectivePointType<T>> {
} }
// Static methods for 3d XYZ points // Static methods for 3d XYZ points
export interface ProjectiveConstructor<T> extends GroupConstructor<ProjectivePointType<T>> { export interface ProjectiveConstructor<T> extends GroupConstructor<ProjectivePointType<T>> {
new (x: T, y: T, z?: T): ProjectivePointType<T>; new (x: T, y: T, z: T): ProjectivePointType<T>;
fromAffine(p: AffinePoint<T>): ProjectivePointType<T>; fromAffine(p: AffinePoint<T>): ProjectivePointType<T>;
toAffineBatch(points: ProjectivePointType<T>[]): AffinePoint<T>[];
normalizeZ(points: ProjectivePointType<T>[]): ProjectivePointType<T>[];
fromAffine(ap: { x: T; y: T }): ProjectivePointType<T>;
fromHex(hex: Hex): ProjectivePointType<T>; fromHex(hex: Hex): ProjectivePointType<T>;
fromPrivateKey(privateKey: PrivKey): ProjectivePointType<T>; fromPrivateKey(privateKey: PrivKey): ProjectivePointType<T>;
toAffineBatch(points: ProjectivePointType<T>[]): AffinePoint<T>[];
normalizeZ(points: ProjectivePointType<T>[]): ProjectivePointType<T>[];
} }
export type CurvePointsType<T> = BasicCurve<T> & { export type CurvePointsType<T> = BasicCurve<T> & {
// Bytes // Bytes
fromBytes: (bytes: Uint8Array) => { x: T; y: T }; fromBytes: (bytes: Uint8Array) => AffinePoint<T>;
toBytes: ( toBytes: (
c: ProjectiveConstructor<T>, c: ProjectiveConstructor<T>,
point: ProjectivePointType<T>, point: ProjectivePointType<T>,
@ -186,7 +185,6 @@ function validatePointOpts<T>(curve: CurvePointsType<T>) {
} }
export type CurvePointsRes<T> = { export type CurvePointsRes<T> = {
// Point: PointConstructor<T>;
ProjectivePoint: ProjectiveConstructor<T>; ProjectivePoint: ProjectiveConstructor<T>;
normalizePrivateKey: (key: PrivKey) => bigint; normalizePrivateKey: (key: PrivKey) => bigint;
weierstrassEquation: (x: T) => T; weierstrassEquation: (x: T) => T;
@ -258,26 +256,56 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
throw new TypeError('Expected valid private scalar: 0 < scalar < curve.n'); throw new TypeError('Expected valid private scalar: 0 < scalar < curve.n');
} }
const pointPrecomputes = new Map<ProjectivePoint, ProjectivePoint[]>();
/** /**
* Projective Point works in 3d / projective (homogeneous) coordinates: (x, y, z) (x=x/z, y=y/z) * 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) * Default Point works in 2d / affine coordinates: (x, y)
* We're doing calculations in projective, because its operations don't require costly inversion. * We're doing calculations in projective, because its operations don't require costly inversion.
*/ */
class ProjectivePoint implements ProjectivePointType<T> { class ProjectivePoint implements ProjectivePointType<T> {
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 BASE = new ProjectivePoint(CURVE.Gx, CURVE.Gy, Fp.ONE);
static readonly ZERO = new ProjectivePoint(Fp.ZERO, Fp.ONE, Fp.ZERO); static readonly ZERO = new ProjectivePoint(Fp.ZERO, Fp.ONE, Fp.ZERO);
static fromAffine(p: AffinePoint<T>): ProjectivePoint { static fromAffine(p: AffinePoint<T>): ProjectivePoint {
// TODO: validate const { x, y } = p || {};
// if (!(p instanceof Point)) { if (!p || !Fp.isValid(x) || !Fp.isValid(y))
// throw new TypeError('ProjectivePoint#fromAffine: expected Point'); 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) // 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 (is0(x) && is0(y)) return ProjectivePoint.ZERO;
if (Fp.equals(p.x, Fp.ZERO) && Fp.equals(p.y, Fp.ZERO)) return ProjectivePoint.ZERO; return new ProjectivePoint(x, y, Fp.ONE);
return new ProjectivePoint(p.x, p.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<T>(opts: CurvePointsType<T>) {
let fake: ProjectivePoint; let fake: ProjectivePoint;
if (CURVE.endo) { if (CURVE.endo) {
const { k1neg, k1, k2neg, k2 } = CURVE.endo.splitScalar(n); const { k1neg, k1, k2neg, k2 } = CURVE.endo.splitScalar(n);
let { p: k1p, f: f1p } = wNAF_TMP_FN(this, k1); let { p: k1p, f: f1p } = this.wNAF(k1);
let { p: k2p, f: f2p } = wNAF_TMP_FN(this, k2); let { p: k2p, f: f2p } = this.wNAF(k2);
k1p = wnaf.constTimeNegate(k1neg, k1p); k1p = wnaf.constTimeNegate(k1neg, k1p);
k2p = wnaf.constTimeNegate(k2neg, k2p); k2p = wnaf.constTimeNegate(k2neg, k2p);
k2p = new ProjectivePoint(Fp.mul(k2p.x, CURVE.endo.beta), k2p.y, k2p.z); k2p = new ProjectivePoint(Fp.mul(k2p.x, CURVE.endo.beta), k2p.y, k2p.z);
point = k1p.add(k2p); point = k1p.add(k2p);
fake = f1p.add(f2p); fake = f1p.add(f2p);
} else { } else {
const { p, f } = wNAF_TMP_FN(this, n); const { p, f } = this.wNAF(n);
point = p; point = p;
fake = f; fake = f;
} }
@ -484,15 +512,15 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
// Converts Projective point to affine (x, y) coordinates. // Converts Projective point to affine (x, y) coordinates.
// Can accept precomputed Z^-1 - for example, from invertBatch. // Can accept precomputed Z^-1 - for example, from invertBatch.
// (x, y, z) ∋ (x=x/z, y=y/z) // (x, y, z) ∋ (x=x/z, y=y/z)
toAffine(invZ?: T): AffinePoint<T> { toAffine(iz?: T): AffinePoint<T> {
const { x, y, z } = this; 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 // If invZ was 0, we return zero point. However we still want to execute
// all operations, so we replace invZ with a random number, 1. // all operations, so we replace invZ with a random number, 1.
if (invZ == null) invZ = is0 ? Fp.ONE : Fp.invert(z); if (iz == null) iz = is0 ? Fp.ONE : Fp.invert(z);
const ax = Fp.mul(x, invZ); const ax = Fp.mul(x, iz);
const ay = Fp.mul(y, invZ); const ay = Fp.mul(y, iz);
const zz = Fp.mul(z, invZ); const zz = Fp.mul(z, iz);
if (is0) return { x: Fp.ZERO, y: Fp.ZERO }; if (is0) return { x: Fp.ZERO, y: Fp.ZERO };
if (!Fp.equals(zz, Fp.ONE)) throw new Error('invZ was invalid'); if (!Fp.equals(zz, Fp.ONE)) throw new Error('invZ was invalid');
return { x: ax, y: ay }; return { x: ax, y: ay };
@ -521,27 +549,27 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
a === _0n || a === _1n || !P.equals(ProjectivePoint.BASE) a === _0n || a === _1n || !P.equals(ProjectivePoint.BASE)
? P.multiplyUnsafe(a) ? P.multiplyUnsafe(a)
: P.multiply(a); : P.multiply(a);
const bQ = ProjectivePoint.fromAffine(Q).multiplyUnsafe(b); const bQ = Q.multiplyUnsafe(b);
const sum = aP.add(bQ); 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. // A point on curve is valid if it conforms to equation.
assertValidity(): void { assertValidity(): void {
const err = 'Point invalid:';
// Zero is valid point too! // Zero is valid point too!
if (this.equals(ProjectivePoint.ZERO)) { if (this.is0()) {
if (CURVE.allowInfinityPoint) return; 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` // Some 3rd-party test vectors require different wording between here & `fromCompressedHex`
const msg = 'Point is not on elliptic curve';
const { x, y } = this.toAffine(); const { x, y } = this.toAffine();
// Check if x, y are valid field elements // Check if x, y are valid field elements
if (!Fp.isValid(x) || !Fp.isValid(y)) throw new Error(msg); if (!Fp.isValid(x) || !Fp.isValid(y)) throw new Error(`${err} x or y not FE`);
const left = Fp.square(y); // y² const left = Fp.square(y); // y²
const right = weierstrassEquation(x); // x³ + ax + b const right = weierstrassEquation(x); // x³ + ax + b
if (!Fp.equals(left, right)) throw new Error(msg); if (!Fp.equals(left, right)) throw new Error(`${err} equation left != right`);
if (!this.isTorsionFree()) throw new Error('Point must be of prime-order subgroup'); if (!this.isTorsionFree()) throw new Error(`${err} not in prime-order subgroup`);
} }
hasEvenY(): boolean { hasEvenY(): boolean {
const { y } = this.toAffine(); const { y } = this.toAffine();
@ -562,10 +590,9 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
* @param hex short/long ECDSA hex * @param hex short/long ECDSA hex
*/ */
static fromHex(hex: Hex): ProjectivePoint { static fromHex(hex: Hex): ProjectivePoint {
const { x, y } = CURVE.fromBytes(ut.ensureBytes(hex)); const P = ProjectivePoint.fromAffine(CURVE.fromBytes(ut.ensureBytes(hex)));
const point = new ProjectivePoint(x, y); P.assertValidity();
point.assertValidity(); return P;
return point;
} }
// Multiplies generator point by privateKey. // Multiplies generator point by privateKey.
@ -576,27 +603,6 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
const _bits = CURVE.nBitLength; const _bits = CURVE.nBitLength;
const wnaf = wNAF(ProjectivePoint, CURVE.endo ? Math.ceil(_bits / 2) : _bits); 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) { function assertPrjPoint(other: unknown) {
if (!(other instanceof ProjectivePoint)) throw new TypeError('ProjectivePoint expected'); if (!(other instanceof ProjectivePoint)) throw new TypeError('ProjectivePoint expected');
} }
@ -976,12 +982,10 @@ export function weierstrass(curveDef: CurveType): CurveFn {
* @returns cached point * @returns cached point
*/ */
precompute(windowSize = 8, point = ProjectivePoint.BASE): typeof ProjectivePoint.BASE { precompute(windowSize = 8, point = ProjectivePoint.BASE): typeof ProjectivePoint.BASE {
return ProjectivePoint.BASE; // const cached = point === ProjectivePoint.BASE ? point : ProjectivePoint.fromAffine({x, y});
// return cache point._setWindowSize(windowSize);
// const cached = point === Point.BASE ? point : new Point(point.x, point.y); point.multiply(BigInt(3));
// cached._setWindowSize(windowSize); return point;
// cached.multiply(_3n);
// return cached;
}, },
}; };
@ -1142,7 +1146,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
} }
// Enable precomputes. Slows down first publicKey computation by 20ms. // 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. * Verifies a signature against message hash and public key.

@ -16,7 +16,7 @@ import { randomBytes } from '@noble/hashes/utils';
import { bls, CurveFn } from './abstract/bls.js'; import { bls, CurveFn } from './abstract/bls.js';
import * as mod from './abstract/modular.js'; import * as mod from './abstract/modular.js';
import { import {
concatBytes, concatBytes as concatB,
ensureBytes, ensureBytes,
numberToBytesBE, numberToBytesBE,
bytesToNumberBE, bytesToNumberBE,
@ -31,6 +31,7 @@ import {
ProjectivePointType, ProjectivePointType,
ProjectiveConstructor, ProjectiveConstructor,
mapToCurveSimpleSWU, mapToCurveSimpleSWU,
AffinePoint,
} from './abstract/weierstrass.js'; } from './abstract/weierstrass.js';
import { isogenyMap } from './abstract/hash-to-curve.js'; import { isogenyMap } from './abstract/hash-to-curve.js';
@ -169,7 +170,7 @@ const Fp2: mod.Field<Fp2> & Fp2Utils = {
if (b.length !== Fp2.BYTES) throw new Error(`fromBytes wrong length=${b.length}`); 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)) }; 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) => ({ cmov: ({ c0, c1 }, { c0: r0, c1: r1 }, c) => ({
c0: Fp.cmov(c0, r0, c), c0: Fp.cmov(c0, r0, c),
c1: Fp.cmov(c1, r1, c), c1: Fp.cmov(c1, r1, c),
@ -354,7 +355,7 @@ const Fp6: mod.Field<Fp6> & Fp6Utils = {
}; };
}, },
toBytes: ({ c0, c1, c2 }): Uint8Array => 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) => ({ cmov: ({ c0, c1, c2 }: Fp6, { c0: r0, c1: r1, c2: r2 }: Fp6, c) => ({
c0: Fp2.cmov(c0, r0, c), c0: Fp2.cmov(c0, r0, c),
c1: Fp2.cmov(c1, r1, c), c1: Fp2.cmov(c1, r1, c),
@ -557,7 +558,7 @@ const Fp12: mod.Field<Fp12> & Fp12Utils = {
c1: Fp6.fromBytes(b.subarray(Fp6.BYTES)), 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) => ({ cmov: ({ c0, c1 }, { c0: r0, c1: r1 }, c) => ({
c0: Fp6.cmov(c0, r0, c), c0: Fp6.cmov(c0, r0, c),
c1: Fp6.cmov(c1, r1, c), c1: Fp6.cmov(c1, r1, c),
@ -1018,7 +1019,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
const { x, y } = G1_SWU(Fp.create(scalars[0])); const { x, y } = G1_SWU(Fp.create(scalars[0]));
return isogenyMapG1(x, y); return isogenyMapG1(x, y);
}, },
fromBytes: (bytes: Uint8Array): { x: Fp; y: Fp } => { fromBytes: (bytes: Uint8Array): AffinePoint<Fp> => {
if (bytes.length === 48) { if (bytes.length === 48) {
const P = Fp.ORDER; const P = Fp.ORDER;
const compressedValue = bytesToNumberBE(bytes); const compressedValue = bytesToNumberBE(bytes);
@ -1034,7 +1035,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
return { x: Fp.create(x), y: Fp.create(y) }; return { x: Fp.create(x), y: Fp.create(y) };
} else if (bytes.length === 96) { } else if (bytes.length === 96) {
// Check if the infinity flag is set // Check if the infinity flag is set
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 x = bytesToNumberBE(bytes.slice(0, Fp.BYTES));
const y = bytesToNumberBE(bytes.slice(Fp.BYTES)); const y = bytesToNumberBE(bytes.slice(Fp.BYTES));
return { x: Fp.create(x), y: Fp.create(y) }; return { x: Fp.create(x), y: Fp.create(y) };
@ -1044,7 +1045,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
}, },
toBytes: (c, point, isCompressed) => { toBytes: (c, point, isCompressed) => {
const isZero = point.equals(c.ZERO); const isZero = point.equals(c.ZERO);
const { x, y } = point; const { x, y } = point.toAffine();
if (isCompressed) { if (isCompressed) {
if (isZero) return COMPRESSED_ZERO.slice(); if (isZero) return COMPRESSED_ZERO.slice();
const P = Fp.ORDER; const P = Fp.ORDER;
@ -1055,10 +1056,10 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
} else { } else {
if (isZero) { if (isZero) {
// 2x PUBLIC_KEY_LENGTH // 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; return x;
} else { } 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<Fp, Fp2, Fp6, Fp12> = bls({
const Q = t3.subtract(P); // Ψ²(2P) - Ψ(P) + [x²]P - [x]Ψ(P) + [x]P - 1P const Q = t3.subtract(P); // Ψ²(2P) - Ψ(P) + [x²]P - [x]Ψ(P) + [x]P - 1P
return Q; // [x²-x-1]P + [x-1]Ψ(P) + Ψ²(2P) return Q; // [x²-x-1]P + [x-1]Ψ(P) + Ψ²(2P)
}, },
fromBytes: (bytes: Uint8Array): { x: Fp2; y: Fp2 } => { fromBytes: (bytes: Uint8Array): AffinePoint<Fp2> => {
const m_byte = bytes[0] & 0xe0; const m_byte = bytes[0] & 0xe0;
if (m_byte === 0x20 || m_byte === 0x60 || m_byte === 0xe0) { if (m_byte === 0x20 || m_byte === 0x60 || m_byte === 0xe0) {
throw new Error('Invalid encoding flag: ' + m_byte); throw new Error('Invalid encoding flag: ' + m_byte);
@ -1128,6 +1129,8 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
const bitC = m_byte & 0x80; // compression bit const bitC = m_byte & 0x80; // compression bit
const bitI = m_byte & 0x40; // point at infinity bit const bitI = m_byte & 0x40; // point at infinity bit
const bitS = m_byte & 0x20; // sign 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) { if (bytes.length === 96 && bitC) {
const { b } = bls12_381.CURVE.G2; const { b } = bls12_381.CURVE.G2;
const P = Fp.ORDER; const P = Fp.ORDER;
@ -1140,8 +1143,8 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
} }
return { x: Fp2.ZERO, y: Fp2.ZERO }; return { x: Fp2.ZERO, y: Fp2.ZERO };
} }
const x_1 = bytesToNumberBE(bytes.slice(0, Fp.BYTES)); const x_1 = slc(bytes, 0, L);
const x_0 = bytesToNumberBE(bytes.slice(Fp.BYTES)); const x_0 = slc(bytes, L, 2 * L);
const x = Fp2.create({ c0: Fp.create(x_0), c1: Fp.create(x_1) }); 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 const right = Fp2.add(Fp2.pow(x, 3n), b); // y² = x³ + 4 * (u+1) = x³ + b
let y = Fp2.sqrt(right); let y = Fp2.sqrt(right);
@ -1153,10 +1156,10 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
if ((bytes[0] & (1 << 6)) !== 0) { if ((bytes[0] & (1 << 6)) !== 0) {
return { x: Fp2.ZERO, y: Fp2.ZERO }; return { x: Fp2.ZERO, y: Fp2.ZERO };
} }
const x1 = bytesToNumberBE(bytes.slice(0, Fp.BYTES)); const x1 = slc(bytes, 0, L);
const x0 = bytesToNumberBE(bytes.slice(Fp.BYTES, 2 * Fp.BYTES)); const x0 = slc(bytes, L, 2 * L);
const y1 = bytesToNumberBE(bytes.slice(2 * Fp.BYTES, 3 * Fp.BYTES)); const y1 = slc(bytes, 2 * L, 3 * L);
const y0 = bytesToNumberBE(bytes.slice(3 * Fp.BYTES)); const y0 = slc(bytes, 3 * L, 4 * L);
return { x: Fp2.fromBigTuple([x0, x1]), y: Fp2.fromBigTuple([y0, y1]) }; return { x: Fp2.fromBigTuple([x0, x1]), y: Fp2.fromBigTuple([y0, y1]) };
} else { } else {
throw new Error('Invalid point G2, expected 96/192 bytes'); throw new Error('Invalid point G2, expected 96/192 bytes');
@ -1164,20 +1167,20 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
}, },
toBytes: (c, point, isCompressed) => { toBytes: (c, point, isCompressed) => {
const isZero = point.equals(c.ZERO); const isZero = point.equals(c.ZERO);
const { x, y } = point; const { x, y } = point.toAffine();
if (isCompressed) { if (isCompressed) {
const P = Fp.ORDER; 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); 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?) // set compressed & sign bits (looks like different offsets than for G1/Fp?)
let x_1 = bitSet(x.c1, C_BIT_POS, flag); let x_1 = bitSet(x.c1, C_BIT_POS, flag);
x_1 = bitSet(x_1, S_BIT_POS, true); 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 { } 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: x0, im: x1 } = Fp2.reim(x);
const { re: y0, im: y1 } = Fp2.reim(y); const { re: y0, im: y1 } = Fp2.reim(y);
return concatBytes( return concatB(
numberToBytesBE(x1, Fp.BYTES), numberToBytesBE(x1, Fp.BYTES),
numberToBytesBE(x0, Fp.BYTES), numberToBytesBE(x0, Fp.BYTES),
numberToBytesBE(y1, Fp.BYTES), numberToBytesBE(y1, Fp.BYTES),
@ -1215,6 +1218,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
const isZero = y1 === 0n && (y0 * 2n) / P !== aflag1; const isZero = y1 === 0n && (y0 * 2n) / P !== aflag1;
if (isGreater || isZero) y = Fp2.negate(y); if (isGreater || isZero) y = Fp2.negate(y);
const point = bls12_381.G2.ProjectivePoint.fromAffine({ x, y }); const point = bls12_381.G2.ProjectivePoint.fromAffine({ x, y });
// console.log('Signature.decode', point);
point.assertValidity(); point.assertValidity();
return point; return point;
}, },
@ -1222,14 +1226,14 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
// NOTE: by some reasons it was missed in bls12-381, looks like bug // NOTE: by some reasons it was missed in bls12-381, looks like bug
point.assertValidity(); point.assertValidity();
if (point.equals(bls12_381.G2.ProjectivePoint.ZERO)) 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: x0, im: x1 } = Fp2.reim(point.x);
const { re: y0, im: y1 } = Fp2.reim(point.y); const { re: y0, im: y1 } = Fp2.reim(point.y);
const tmp = y1 > 0n ? y1 * 2n : y0 * 2n; const tmp = y1 > 0n ? y1 * 2n : y0 * 2n;
const aflag1 = Boolean((tmp / Fp.ORDER) & 1n); const aflag1 = Boolean((tmp / Fp.ORDER) & 1n);
const z1 = bitSet(bitSet(x1, 381, aflag1), S_BIT_POS, true); const z1 = bitSet(bitSet(x1, 381, aflag1), S_BIT_POS, true);
const z2 = x0; const z2 = x0;
return concatBytes(numberToBytesBE(z1, Fp.BYTES), numberToBytesBE(z2, Fp.BYTES)); return concatB(numberToBytesBE(z1, Fp.BYTES), numberToBytesBE(z2, Fp.BYTES));
}, },
}, },
}, },

@ -39,7 +39,7 @@ export function groupHash(tag: Uint8Array, personalization: Uint8Array) {
h.update(GH_FIRST_BLOCK); h.update(GH_FIRST_BLOCK);
h.update(tag); h.update(tag);
// NOTE: returns ExtendedPoint, in case it will be multiplied later // 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 // NOTE: cannot replace with isSmallOrder, returns Point*8
p = p.multiply(jubjub.CURVE.h); p = p.multiply(jubjub.CURVE.h);
if (p.equals(jubjub.ExtendedPoint.ZERO)) throw new Error('Point has small order'); if (p.equals(jubjub.ExtendedPoint.ZERO)) throw new Error('Point has small order');

@ -181,27 +181,32 @@ export function getAccountPath(
const PEDERSEN_POINTS_AFFINE = [ const PEDERSEN_POINTS_AFFINE = [
new ProjectivePoint( new ProjectivePoint(
2089986280348253421170679821480865132823066470938446095505822317253594081284n, 2089986280348253421170679821480865132823066470938446095505822317253594081284n,
1713931329540660377023406109199410414810705867260802078187082345529207694986n 1713931329540660377023406109199410414810705867260802078187082345529207694986n,
1n
), ),
new ProjectivePoint( new ProjectivePoint(
996781205833008774514500082376783249102396023663454813447423147977397232763n, 996781205833008774514500082376783249102396023663454813447423147977397232763n,
1668503676786377725805489344771023921079126552019160156920634619255970485781n 1668503676786377725805489344771023921079126552019160156920634619255970485781n,
1n
), ),
new ProjectivePoint( new ProjectivePoint(
2251563274489750535117886426533222435294046428347329203627021249169616184184n, 2251563274489750535117886426533222435294046428347329203627021249169616184184n,
1798716007562728905295480679789526322175868328062420237419143593021674992973n 1798716007562728905295480679789526322175868328062420237419143593021674992973n,
1n
), ),
new ProjectivePoint( new ProjectivePoint(
2138414695194151160943305727036575959195309218611738193261179310511854807447n, 2138414695194151160943305727036575959195309218611738193261179310511854807447n,
113410276730064486255102093846540133784865286929052426931474106396135072156n 113410276730064486255102093846540133784865286929052426931474106396135072156n,
1n
), ),
new ProjectivePoint( new ProjectivePoint(
2379962749567351885752724891227938183011949129833673362440656643086021394946n, 2379962749567351885752724891227938183011949129833673362440656643086021394946n,
776496453633298175483985398648758586525933812536653089401905292063708816422n 776496453633298175483985398648758586525933812536653089401905292063708816422n,
1n
), ),
]; ];
// for (const p of PEDERSEN_POINTS) p._setWindowSize(8); // 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[] { function pedersenPrecompute(p1: ProjectivePoint, p2: ProjectivePoint): ProjectivePoint[] {
const out: ProjectivePoint[] = []; const out: ProjectivePoint[] = [];

File diff suppressed because it is too large Load Diff

@ -144,7 +144,7 @@ describe('secp256k1', () => {
const { P, d, expected } = vector; const { P, d, expected } = vector;
const p = Point.fromHex(P); const p = Point.fromHex(P);
if (expected) { if (expected) {
deepStrictEqual(p.multiply(hexToNumber(d)).toHex(true), expected); deepStrictEqual(p.multiply(hexToNumber(d)).toHex(true), expected, P);
} else { } else {
throws(() => { throws(() => {
p.multiply(hexToNumber(d)).toHex(true); p.multiply(hexToNumber(d)).toHex(true);
@ -314,7 +314,7 @@ describe('secp256k1', () => {
const r = 1n; const r = 1n;
const s = 115792089237316195423570985008687907852837564279074904382605163141518162728904n; const s = 115792089237316195423570985008687907852837564279074904382605163141518162728904n;
const pub = new Point(x, y); const pub = new Point(x, y, 1n);
const signature = new secp.Signature(2n, 2n); const signature = new secp.Signature(2n, 2n);
signature.r = r; signature.r = r;
signature.s = s; signature.s = s;
@ -329,7 +329,7 @@ describe('secp256k1', () => {
const y = 32670510020758816978083085130507043184471273380659243275938904335757337482424n; const y = 32670510020758816978083085130507043184471273380659243275938904335757337482424n;
const r = 104546003225722045112039007203142344920046999340768276760147352389092131869133n; const r = 104546003225722045112039007203142344920046999340768276760147352389092131869133n;
const s = 96900796730960181123786672629079577025401317267213807243199432755332205217369n; const s = 96900796730960181123786672629079577025401317267213807243199432755332205217369n;
const pub = new Point(x, y); const pub = new Point(x, y, 1n);
const sig = new secp.Signature(r, s); const sig = new secp.Signature(r, s);
deepStrictEqual(secp.verify(sig, msg, pub), false); deepStrictEqual(secp.verify(sig, msg, pub), false);
}); });
@ -339,7 +339,7 @@ describe('secp256k1', () => {
const y = 17482644437196207387910659778872952193236850502325156318830589868678978890912n; const y = 17482644437196207387910659778872952193236850502325156318830589868678978890912n;
const r = 432420386565659656852420866390673177323n; const r = 432420386565659656852420866390673177323n;
const s = 115792089237316195423570985008687907852837564279074904382605163141518161494334n; const s = 115792089237316195423570985008687907852837564279074904382605163141518161494334n;
const pub = new Point(x, y); const pub = new Point(x, y, 1n);
const sig = new secp.Signature(r, s); const sig = new secp.Signature(r, s);
deepStrictEqual(secp.verify(sig, msg, pub, { strict: false }), true); deepStrictEqual(secp.verify(sig, msg, pub, { strict: false }), true);
}); });
@ -452,7 +452,7 @@ describe('secp256k1', () => {
should('have proper curve equation in assertValidity()', () => { should('have proper curve equation in assertValidity()', () => {
throws(() => { throws(() => {
const { Fp } = secp.CURVE; 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(); point.assertValidity();
}); });
}); });