From 2ed27da8eb7086260a45d650fe4046eed4b94bca Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Tue, 24 Jan 2023 05:42:44 +0000 Subject: [PATCH] weierstrass: remove affine Point --- src/abstract/bls.ts | 71 ++++--- src/abstract/edwards.ts | 1 - src/abstract/hash-to-curve.ts | 21 +- src/abstract/weierstrass.ts | 355 ++++++++++++++-------------------- src/bls12-381.ts | 13 +- src/p256.ts | 2 +- src/p384.ts | 2 +- src/p521.ts | 2 +- src/secp256k1.ts | 16 +- src/stark.ts | 15 +- test/nist.test.js | 10 +- test/secp256k1.test.js | 59 +++--- 12 files changed, 253 insertions(+), 314 deletions(-) diff --git a/src/abstract/bls.ts b/src/abstract/bls.ts index b078cf7..a11f43f 100644 --- a/src/abstract/bls.ts +++ b/src/abstract/bls.ts @@ -16,13 +16,18 @@ import * as ut from './utils.js'; // Types require separate import import { Hex, PrivKey } from './utils.js'; import * as htf from './hash-to-curve.js'; -import { CurvePointsType, PointType, CurvePointsRes, weierstrassPoints } from './weierstrass.js'; +import { + CurvePointsType, + ProjectivePointType as PPointType, + CurvePointsRes, + weierstrassPoints, +} from './weierstrass.js'; type Fp = bigint; // Can be different field? export type SignatureCoder = { - decode(hex: Hex): PointType; - encode(point: PointType): Uint8Array; + decode(hex: Hex): PPointType; + encode(point: PPointType): Uint8Array; }; export type CurveType = { @@ -73,29 +78,29 @@ export type CurveFn = { G1: ReturnType<(typeof htf.hashToCurve)>, G2: ReturnType<(typeof htf.hashToCurve)>, }, - pairing: (P: PointType, Q: PointType, withFinalExponent?: boolean) => Fp12; + pairing: (P: PPointType, Q: PPointType, withFinalExponent?: boolean) => Fp12; getPublicKey: (privateKey: PrivKey) => Uint8Array; sign: { (message: Hex, privateKey: PrivKey): Uint8Array; - (message: PointType, privateKey: PrivKey): PointType; + (message: PPointType, privateKey: PrivKey): PPointType; }; verify: ( - signature: Hex | PointType, - message: Hex | PointType, - publicKey: Hex | PointType + signature: Hex | PPointType, + message: Hex | PPointType, + publicKey: Hex | PPointType ) => boolean; aggregatePublicKeys: { (publicKeys: Hex[]): Uint8Array; - (publicKeys: PointType[]): PointType; + (publicKeys: PPointType[]): PPointType; }; aggregateSignatures: { (signatures: Hex[]): Uint8Array; - (signatures: PointType[]): PointType; + (signatures: PPointType[]): PPointType; }; verifyBatch: ( - signature: Hex | PointType, - messages: (Hex | PointType)[], - publicKeys: (Hex | PointType)[] + signature: Hex | PPointType, + messages: (Hex | PPointType)[], + publicKeys: (Hex | PPointType)[] ) => boolean; utils: { stringToBytes: typeof htf.stringToBytes; @@ -196,7 +201,7 @@ export function bls( n: Fr.ORDER, ...CURVE.G1, }); - const G1HashToCurve = htf.hashToCurve(G1.Point, CURVE.G1.mapToCurve, { + const G1HashToCurve = htf.hashToCurve(G1.ProjectivePoint, CURVE.G1.mapToCurve, { ...CURVE.htfDefaults, ...CURVE.G1.htfDefaults, }); @@ -226,7 +231,8 @@ export function bls( n: Fr.ORDER, ...CURVE.G2, }); - const G2HashToCurve = htf.hashToCurve(G2.Point, CURVE.G2.mapToCurve, { + const C = G2.ProjectivePoint as htf.H2CPointConstructor; // TODO: fix + const G2HashToCurve = htf.hashToCurve(C, CURVE.G2.mapToCurve, { ...CURVE.htfDefaults, ...CURVE.G2.htfDefaults, }); @@ -235,7 +241,7 @@ export function bls( // Calculates bilinear pairing function pairing(P: G1, Q: G2, withFinalExponent: boolean = true): Fp12 { - if (P.equals(G1.Point.ZERO) || Q.equals(G2.Point.ZERO)) + if (P.equals(G1.ProjectivePoint.ZERO) || Q.equals(G2.ProjectivePoint.ZERO)) throw new Error('No pairings at point of Infinity'); P.assertValidity(); Q.assertValidity(); @@ -243,25 +249,27 @@ export function bls( const looped = millerLoopG1(P, Q); return withFinalExponent ? Fp12.finalExponentiate(looped) : looped; } - type G1 = typeof G1.Point.BASE; - type G2 = typeof G2.Point.BASE; + type G1 = typeof G1.ProjectivePoint.BASE; + type G2 = typeof G2.ProjectivePoint.BASE; type G1Hex = Hex | G1; type G2Hex = Hex | G2; function normP1(point: G1Hex): G1 { - return point instanceof G1.Point ? (point as G1) : G1.Point.fromHex(point); + return point instanceof G1.ProjectivePoint ? (point as G1) : G1.ProjectivePoint.fromHex(point); } function normP2(point: G2Hex): G2 { - return point instanceof G2.Point ? point : Signature.decode(point); + return point instanceof G2.ProjectivePoint ? point : Signature.decode(point); } function normP2Hash(point: G2Hex, htfOpts?: htf.htfBasicOpts): G2 { - return point instanceof G2.Point ? point : (G2HashToCurve.hashToCurve(point, htfOpts) as G2); + return point instanceof G2.ProjectivePoint + ? point + : (G2HashToCurve.hashToCurve(point, htfOpts) as G2); } // Multiplies generator by private key. // P = pk x G function getPublicKey(privateKey: PrivKey): Uint8Array { - return G1.Point.fromPrivateKey(privateKey).toRawBytes(true); + return G1.ProjectivePoint.fromPrivateKey(privateKey).toRawBytes(true); } // Executes `hashToCurve` on the message and then multiplies the result by private key. @@ -272,7 +280,7 @@ export function bls( const msgPoint = normP2Hash(message, htfOpts); msgPoint.assertValidity(); const sigPoint = msgPoint.multiply(G1.normalizePrivateKey(privateKey)); - if (message instanceof G2.Point) return sigPoint; + if (message instanceof G2.ProjectivePoint) return sigPoint; return Signature.encode(sigPoint); } @@ -286,7 +294,7 @@ export function bls( ): boolean { const P = normP1(publicKey); const Hm = normP2Hash(message, htfOpts); - const G = G1.Point.BASE; + const G = G1.ProjectivePoint.BASE; const S = normP2(signature); // Instead of doing 2 exponentiations, we use property of billinear maps // and do one exp after multiplying 2 points. @@ -305,8 +313,8 @@ export function bls( const agg = publicKeys .map(normP1) .reduce((sum, p) => sum.add(G1.ProjectivePoint.fromAffine(p)), G1.ProjectivePoint.ZERO); - const aggAffine = agg.toAffine(); - if (publicKeys[0] instanceof G1.Point) { + const aggAffine = agg; //.toAffine(); + if (publicKeys[0] instanceof G1.ProjectivePoint) { aggAffine.assertValidity(); return aggAffine; } @@ -322,8 +330,8 @@ export function bls( const agg = signatures .map(normP2) .reduce((sum, s) => sum.add(G2.ProjectivePoint.fromAffine(s)), G2.ProjectivePoint.ZERO); - const aggAffine = agg.toAffine(); - if (signatures[0] instanceof G2.Point) { + const aggAffine = agg; //.toAffine(); + if (signatures[0] instanceof G2.ProjectivePoint) { aggAffine.assertValidity(); return aggAffine; } @@ -350,13 +358,13 @@ export function bls( const groupPublicKey = nMessages.reduce( (groupPublicKey, subMessage, i) => subMessage === message ? groupPublicKey.add(nPublicKeys[i]) : groupPublicKey, - G1.Point.ZERO + G1.ProjectivePoint.ZERO ); // const msg = message instanceof PointG2 ? message : await PointG2.hashToCurve(message); // Possible to batch pairing for same msg with different groupPublicKey here paired.push(pairing(groupPublicKey, message, false)); } - paired.push(pairing(G1.Point.BASE.negate(), sig, false)); + paired.push(pairing(G1.ProjectivePoint.BASE.negate(), sig, false)); const product = paired.reduce((a, b) => Fp12.mul(a, b), Fp12.ONE); const exp = Fp12.finalExponentiate(product); return Fp12.equals(exp, Fp12.ONE); @@ -366,7 +374,8 @@ export function bls( } // Pre-compute points. Refer to README. - G1.Point.BASE._setWindowSize(4); + // TODO + // G1.ProjectivePoint.BASE._setWindowSize(4); return { CURVE, Fr, diff --git a/src/abstract/edwards.ts b/src/abstract/edwards.ts index a6aa279..a5922df 100644 --- a/src/abstract/edwards.ts +++ b/src/abstract/edwards.ts @@ -93,7 +93,6 @@ export type CurveFn = { getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array; sign: (message: Hex, privateKey: Hex) => Uint8Array; verify: (sig: Hex, message: Hex, publicKey: Hex) => boolean; - // Point: PointConstructor; ExtendedPoint: ExtendedPointConstructor; utils: { randomPrivateKey: () => Uint8Array; diff --git a/src/abstract/hash-to-curve.ts b/src/abstract/hash-to-curve.ts index 888f6b9..3827f4d 100644 --- a/src/abstract/hash-to-curve.ts +++ b/src/abstract/hash-to-curve.ts @@ -178,17 +178,16 @@ export function isogenyMap>(field: F, map: [T[], T[], }; } -export interface Point extends Group> { - // readonly x: T; - // readonly y: T; - add(rhs: Point): Point; +export interface H2CPoint extends Group> { + readonly x: T; + readonly y: T; + add(rhs: H2CPoint): H2CPoint; toAffine(iz?: bigint): { x: T; y: T }; - clearCofactor(): Point; + clearCofactor(): H2CPoint; } -export interface PointConstructor extends GroupConstructor> { - // new (x: T, y: T): Point; - fromAffine(ap: { x: T; y: T }): Point; +export interface H2CPointConstructor extends GroupConstructor> { + fromAffine(ap: { x: T; y: T }): H2CPoint; } export type MapToCurve = (scalar: bigint[]) => { x: T; y: T }; @@ -198,7 +197,11 @@ export type htfBasicOpts = { DST: string; }; -export function hashToCurve(Point: PointConstructor, mapToCurve: MapToCurve, def: Opts) { +export function hashToCurve( + Point: H2CPointConstructor, + mapToCurve: MapToCurve, + def: Opts +) { validateOpts(def); if (typeof mapToCurve !== 'function') throw new Error('hashToCurve: mapToCurve() has not been defined'); diff --git a/src/abstract/weierstrass.ts b/src/abstract/weierstrass.ts index dc9b3ca..4091dec 100644 --- a/src/abstract/weierstrass.ts +++ b/src/abstract/weierstrass.ts @@ -109,48 +109,50 @@ 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 { + x: T; + y: T; +} // Instance for 3d XYZ points export interface ProjectivePointType extends Group> { readonly x: T; readonly y: T; readonly z: T; - multiply(scalar: bigint, affinePoint?: PointType): ProjectivePointType; + multiply(scalar: bigint): ProjectivePointType; multiplyUnsafe(scalar: bigint): ProjectivePointType; - toAffine(invZ?: T): PointType; + multiplyAndAddUnsafe( + Q: ProjectivePointType, + a: bigint, + b: bigint + ): ProjectivePointType | undefined; + toAffine(invZ?: T): AffinePoint; + isTorsionFree(): boolean; clearCofactor(): ProjectivePointType; -} -// Static methods for 3d XYZ points -export interface ProjectiveConstructor extends GroupConstructor> { - new (x: T, y: T, z: T): ProjectivePointType; - fromAffine(p: PointType): ProjectivePointType; - toAffineBatch(points: ProjectivePointType[]): PointType[]; - normalizeZ(points: ProjectivePointType[]): ProjectivePointType[]; -} -// Instance for 2d XY points -export interface PointType extends Group> { - readonly x: T; - readonly y: T; - _setWindowSize(windowSize: number): void; + assertValidity(): void; hasEvenY(): boolean; toRawBytes(isCompressed?: boolean): Uint8Array; toHex(isCompressed?: boolean): string; - assertValidity(): void; - multiplyAndAddUnsafe(Q: PointType, a: bigint, b: bigint): PointType | undefined; - clearCofactor(): PointType; - toAffine(iz?: bigint): { x: T; y: T }; } -// Static methods for 2d XY points -export interface PointConstructor extends GroupConstructor> { - new (x: T, y: T): PointType; - fromAffine(ap: { x: T; y: T }): PointType; - fromHex(hex: Hex): PointType; - fromPrivateKey(privateKey: PrivKey): PointType; +// Static methods for 3d XYZ points +export interface ProjectiveConstructor extends GroupConstructor> { + 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; } export type CurvePointsType = BasicCurve & { // Bytes fromBytes: (bytes: Uint8Array) => { x: T; y: T }; - toBytes: (c: PointConstructor, point: PointType, compressed: boolean) => Uint8Array; + toBytes: ( + c: ProjectiveConstructor, + point: ProjectivePointType, + compressed: boolean + ) => Uint8Array; }; function validatePointOpts(curve: CurvePointsType) { @@ -184,7 +186,7 @@ function validatePointOpts(curve: CurvePointsType) { } export type CurvePointsRes = { - Point: PointConstructor; + // Point: PointConstructor; ProjectivePoint: ProjectiveConstructor; normalizePrivateKey: (key: PrivKey) => bigint; weierstrassEquation: (x: T) => T; @@ -194,7 +196,6 @@ export type CurvePointsRes = { // Be friendly to bad ECMAScript parsers by not using bigint literals like 123n const _0n = BigInt(0); const _1n = BigInt(1); -const _3n = BigInt(3); export function weierstrassPoints(opts: CurvePointsType) { const CURVE = validatePointOpts(opts); @@ -263,17 +264,19 @@ export function weierstrassPoints(opts: CurvePointsType) { * 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) {} + constructor(readonly x: T, readonly y: T, readonly z: T = 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 fromAffine(p: Point): ProjectivePoint { - if (!(p instanceof Point)) { - throw new TypeError('ProjectivePoint#fromAffine: expected Point'); - } + static fromAffine(p: AffinePoint): ProjectivePoint { + // TODO: validate + // if (!(p instanceof Point)) { + // throw new TypeError('ProjectivePoint#fromAffine: expected Point'); + // } // 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 (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); } @@ -282,7 +285,7 @@ export function weierstrassPoints(opts: CurvePointsType) { * inversion on all of them. Inversion is very slow operation, * so this improves performance massively. */ - static toAffineBatch(points: ProjectivePoint[]): Point[] { + static toAffineBatch(points: ProjectivePoint[]): AffinePoint[] { const toInv = Fp.invertBatch(points.map((p) => p.z)); return points.map((p, i) => p.toAffine(toInv[i])); } @@ -445,25 +448,6 @@ export function weierstrassPoints(opts: CurvePointsType) { k2p = new ProjectivePoint(Fp.mul(k2p.x, CURVE.endo.beta), k2p.y, k2p.z); return k1p.add(k2p); } - - /** - * Implements w-ary non-adjacent form for calculating ec multiplication. - */ - private wNAF(n: bigint, affinePoint?: Point): { p: ProjectivePoint; f: ProjectivePoint } { - if (!affinePoint && this.equals(ProjectivePoint.BASE)) affinePoint = Point.BASE; - const W = (affinePoint && affinePoint._WINDOW_SIZE) || 1; - // Calculate precomputes on a first run, reuse them after - let precomputes = affinePoint && pointPrecomputes.get(affinePoint); - if (!precomputes) { - precomputes = wnaf.precomputeWindow(this, W) as ProjectivePoint[]; - if (affinePoint && W !== 1) { - precomputes = ProjectivePoint.normalizeZ(precomputes); - pointPrecomputes.set(affinePoint, precomputes); - } - } - return wnaf.wNAF(W, precomputes, n); - } - /** * Constant time multiplication. * Uses wNAF method. Windowed method may be 10% faster, @@ -472,7 +456,7 @@ export function weierstrassPoints(opts: CurvePointsType) { * @param affinePoint optional point ot save cached precompute windows on it * @returns New point */ - multiply(scalar: bigint, affinePoint?: Point): ProjectivePoint { + multiply(scalar: bigint): ProjectivePoint { let n = normalizeScalar(scalar); // Real point. @@ -481,15 +465,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 } = this.wNAF(k1, affinePoint); - let { p: k2p, f: f2p } = this.wNAF(k2, affinePoint); + let { p: k1p, f: f1p } = wNAF_TMP_FN(this, k1); + let { p: k2p, f: f2p } = wNAF_TMP_FN(this, 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 } = this.wNAF(n, affinePoint); + const { p, f } = wNAF_TMP_FN(this, n); point = p; fake = f; } @@ -500,7 +484,7 @@ 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): Point { + toAffine(invZ?: T): AffinePoint { const { x, y, z } = this; const is0 = this.equals(ProjectivePoint.ZERO); // If invZ was 0, we return zero point. However we still want to execute @@ -509,9 +493,9 @@ export function weierstrassPoints(opts: CurvePointsType) { const ax = Fp.mul(x, invZ); const ay = Fp.mul(y, invZ); const zz = Fp.mul(z, invZ); - if (is0) return Point.ZERO; + if (is0) return { x: Fp.ZERO, y: Fp.ZERO }; if (!Fp.equals(zz, Fp.ONE)) throw new Error('invZ was invalid'); - return new Point(ax, ay); + return { x: ax, y: ay }; } isTorsionFree(): boolean { const { h: cofactor, isTorsionFree } = CURVE; @@ -525,89 +509,33 @@ export function weierstrassPoints(opts: CurvePointsType) { if (clearCofactor) return clearCofactor(ProjectivePoint, this) as ProjectivePoint; return this.multiplyUnsafe(CURVE.h); } - } - const _bits = CURVE.nBitLength; - const wnaf = wNAF(ProjectivePoint, CURVE.endo ? Math.ceil(_bits / 2) : _bits); - - function assertPrjPoint(other: unknown) { - if (!(other instanceof ProjectivePoint)) throw new TypeError('ProjectivePoint expected'); - } - - // Stores precomputed values for points. - const pointPrecomputes = new WeakMap(); - - /** - * Default Point works in default aka affine coordinates: (x, y) - */ - class Point implements PointType { /** - * Base point aka generator. Any public_key = Point.BASE * private_key + * Efficiently calculate `aP + bQ`. + * Unsafe, can expose private key, if used incorrectly. + * TODO: Utilize Shamir's trick + * @returns non-zero affine point */ - static BASE: Point = new Point(CURVE.Gx, CURVE.Gy); - /** - * Identity point aka point at infinity. p - p = zero_p; p + zero_p = p - */ - static ZERO: Point = new Point(Fp.ZERO, Fp.ZERO); - static fromAffine(ap: { x: T; y: T }) { - return new Point(ap.x, ap.y); - } - toAffine(iz?: bigint) { - return { x: this.x, y: this.y }; + multiplyAndAddUnsafe(Q: ProjectivePoint, a: bigint, b: bigint): ProjectivePoint | undefined { + const P = this; + const aP = + a === _0n || a === _1n || !P.equals(ProjectivePoint.BASE) + ? P.multiplyUnsafe(a) + : P.multiply(a); + const bQ = ProjectivePoint.fromAffine(Q).multiplyUnsafe(b); + const sum = aP.add(bQ); + return sum.equals(ProjectivePoint.ZERO) ? undefined : sum; } - // 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; - - constructor(readonly x: T, readonly y: T) {} - - // "Private method", don't use it directly - _setWindowSize(windowSize: number) { - this._WINDOW_SIZE = windowSize; - pointPrecomputes.delete(this); - } - - // Checks for y % 2 == 0 - hasEvenY(): boolean { - if (Fp.isOdd) return !Fp.isOdd(this.y); - throw new Error("Field doesn't support isOdd"); - } - - /** - * Converts hash string or Uint8Array to Point. - * @param hex short/long ECDSA hex - */ - static fromHex(hex: Hex): Point { - const { x, y } = CURVE.fromBytes(ut.ensureBytes(hex)); - const point = new Point(x, y); - point.assertValidity(); - return point; - } - - // Multiplies generator point by privateKey. - static fromPrivateKey(privateKey: PrivKey) { - return Point.BASE.multiply(normalizePrivateKey(privateKey)); - } - - toRawBytes(isCompressed = true): Uint8Array { - this.assertValidity(); - return CURVE.toBytes(Point, this, isCompressed); - } - - toHex(isCompressed = true): string { - return bytesToHex(this.toRawBytes(isCompressed)); - } // A point on curve is valid if it conforms to equation. assertValidity(): void { // Zero is valid point too! - if (this.equals(Point.ZERO)) { + if (this.equals(ProjectivePoint.ZERO)) { if (CURVE.allowInfinityPoint) return; throw new Error('Point at infinity'); } // Some 3rd-party test vectors require different wording between here & `fromCompressedHex` const msg = 'Point is not on elliptic curve'; - const { x, y } = this; + const { x, y } = this.toAffine(); // Check if x, y are valid field elements if (!Fp.isValid(x) || !Fp.isValid(y)) throw new Error(msg); const left = Fp.square(y); // y² @@ -615,68 +543,64 @@ export function weierstrassPoints(opts: CurvePointsType) { if (!Fp.equals(left, right)) throw new Error(msg); if (!this.isTorsionFree()) throw new Error('Point must be of prime-order subgroup'); } - - equals(other: Point): boolean { - if (!(other instanceof Point)) throw new TypeError('Point#equals: expected Point'); - return Fp.equals(this.x, other.x) && Fp.equals(this.y, other.y); + hasEvenY(): boolean { + const { y } = this.toAffine(); + if (Fp.isOdd) return !Fp.isOdd(y); + throw new Error("Field doesn't support isOdd"); + } + toRawBytes(isCompressed = true): Uint8Array { + this.assertValidity(); + return CURVE.toBytes(ProjectivePoint, this, isCompressed); } - // Returns the same point with inverted `y` - negate() { - return new Point(this.x, Fp.negate(this.y)); - } - - protected toProj() { - return ProjectivePoint.fromAffine(this); - } - - // Adds point to itself - double() { - return this.toProj().double().toAffine(); - } - - add(other: Point) { - return this.toProj().add(ProjectivePoint.fromAffine(other)).toAffine(); - } - - subtract(other: Point) { - return this.add(other.negate()); - } - - multiply(scalar: bigint) { - return this.toProj().multiply(scalar, this).toAffine(); - } - - multiplyUnsafe(scalar: bigint) { - return this.toProj().multiplyUnsafe(scalar).toAffine(); - } - - clearCofactor() { - return this.toProj().clearCofactor().toAffine(); - } - - isTorsionFree(): boolean { - return this.toProj().isTorsionFree(); + toHex(isCompressed = true): string { + return bytesToHex(this.toRawBytes(isCompressed)); } /** - * Efficiently calculate `aP + bQ`. - * Unsafe, can expose private key, if used incorrectly. - * TODO: Utilize Shamir's trick - * @returns non-zero affine point + * Converts hash string or Uint8Array to Point. + * @param hex short/long ECDSA hex */ - multiplyAndAddUnsafe(Q: Point, a: bigint, b: bigint): Point | undefined { - const P = this.toProj(); - const aP = - a === _0n || a === _1n || this !== Point.BASE ? P.multiplyUnsafe(a) : P.multiply(a); - const bQ = ProjectivePoint.fromAffine(Q).multiplyUnsafe(b); - const sum = aP.add(bQ); - return sum.equals(ProjectivePoint.ZERO) ? undefined : sum.toAffine(); + static fromHex(hex: Hex): ProjectivePoint { + const { x, y } = CURVE.fromBytes(ut.ensureBytes(hex)); + const point = new ProjectivePoint(x, y); + point.assertValidity(); + return point; + } + + // Multiplies generator point by privateKey. + static fromPrivateKey(privateKey: PrivKey) { + return ProjectivePoint.BASE.multiply(normalizePrivateKey(privateKey)); } } + 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'); + } return { - Point: Point as PointConstructor, ProjectivePoint: ProjectivePoint as ProjectiveConstructor, normalizePrivateKey, weierstrassEquation, @@ -693,7 +617,7 @@ export interface SignatureType { copyWithRecoveryBit(recovery: number): SignatureType; hasHighS(): boolean; normalizeS(): SignatureType; - recoverPublicKey(msgHash: Hex): PointType; + recoverPublicKey(msgHash: Hex): ProjectivePointType; // DER-encoded toDERRawBytes(isCompressed?: boolean): Uint8Array; toDERHex(isCompressed?: boolean): string; @@ -707,7 +631,7 @@ export type SignatureConstructor = { fromDER(hex: Hex): SignatureType; }; -export type PubKey = Hex | PointType; +export type PubKey = Hex | ProjectivePointType; export type CurveType = BasicCurve & { // Default options @@ -745,14 +669,13 @@ export type CurveFn = { lowS?: boolean; } ) => boolean; - Point: PointConstructor; ProjectivePoint: ProjectiveConstructor; Signature: SignatureConstructor; utils: { _bigintToBytes: (num: bigint) => Uint8Array; _bigintToString: (num: bigint) => string; _normalizePrivateKey: (key: PrivKey) => bigint; - _normalizePublicKey: (publicKey: PubKey) => PointType; + _normalizePublicKey: (publicKey: PubKey) => ProjectivePointType; _isWithinCurveOrder: (num: bigint) => boolean; _isValidFieldElement: (num: bigint) => boolean; _weierstrassEquation: (x: bigint) => bigint; @@ -827,16 +750,18 @@ export function weierstrass(curveDef: CurveType): CurveFn { return _0n < num && num < Fp.ORDER; } - const { Point, ProjectivePoint, normalizePrivateKey, weierstrassEquation, isWithinCurveOrder } = + const { ProjectivePoint, normalizePrivateKey, weierstrassEquation, isWithinCurveOrder } = weierstrassPoints({ ...CURVE, toBytes(c, point, isCompressed: boolean): Uint8Array { - const x = Fp.toBytes(point.x); + const a = point.toAffine(); + const x = Fp.toBytes(a.x); const cat = ut.concatBytes; if (isCompressed) { + // TODO: hasEvenY return cat(Uint8Array.from([point.hasEvenY() ? 0x02 : 0x03]), x); } else { - return cat(Uint8Array.from([0x04]), x, Fp.toBytes(point.y)); + return cat(Uint8Array.from([0x04]), x, Fp.toBytes(a.y)); } }, fromBytes(bytes: Uint8Array) { @@ -864,7 +789,7 @@ export function weierstrass(curveDef: CurveType): CurveFn { } }, }); - type Point = typeof Point.BASE; + // type Point = typeof ProjectivePoint.BASE; // Do we need these functions at all? function numToField(num: bigint): Uint8Array { @@ -877,12 +802,12 @@ export function weierstrass(curveDef: CurveType): CurveFn { /** * Normalizes hex, bytes, Point to Point. Checks for curve equation. */ - function normalizePublicKey(publicKey: PubKey): PointType { - if (publicKey instanceof Point) { + function normalizePublicKey(publicKey: PubKey): ProjectivePointType { + if (publicKey instanceof ProjectivePoint) { publicKey.assertValidity(); return publicKey; } else if (publicKey instanceof Uint8Array || typeof publicKey === 'string') { - return Point.fromHex(publicKey); + return ProjectivePoint.fromHex(publicKey); // This can happen because PointType can be instance of different class } else throw new Error(`Unknown type of public key: ${publicKey}`); } @@ -952,7 +877,7 @@ export function weierstrass(curveDef: CurveType): CurveFn { * @param msgHash message hash * @returns Point corresponding to public key */ - recoverPublicKey(msgHash: Hex): Point { + recoverPublicKey(msgHash: Hex): typeof ProjectivePoint.BASE { const { r, s, recovery } = this; if (recovery == null) throw new Error('Cannot recover: recovery bit is not present'); if (![0, 1, 2, 3].includes(recovery)) throw new Error('Cannot recover: invalid recovery bit'); @@ -966,8 +891,8 @@ export function weierstrass(curveDef: CurveType): CurveFn { const u1 = mod.mod(-h * rinv, n); const u2 = mod.mod(s * rinv, n); const prefix = recovery & 1 ? '03' : '02'; - const R = Point.fromHex(prefix + numToFieldStr(radj)); - const Q = Point.BASE.multiplyAndAddUnsafe(R, u1, u2); // unsafe is fine: no priv data leaked + const R = ProjectivePoint.fromHex(prefix + numToFieldStr(radj)); + const Q = ProjectivePoint.BASE.multiplyAndAddUnsafe(R, u1, u2); // unsafe is fine: no priv data leaked if (!Q) throw new Error('Cannot recover: point at infinify'); Q.assertValidity(); return Q; @@ -1050,11 +975,13 @@ export function weierstrass(curveDef: CurveType): CurveFn { * @param windowSize 2, 4, 8, 16 * @returns cached point */ - precompute(windowSize = 8, point = Point.BASE): Point { - const cached = point === Point.BASE ? point : new Point(point.x, point.y); - cached._setWindowSize(windowSize); - cached.multiply(_3n); - return cached; + 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; }, }; @@ -1065,7 +992,7 @@ export function weierstrass(curveDef: CurveType): CurveFn { * @returns Public key, full when isCompressed=false; short when isCompressed=true */ function getPublicKey(privateKey: PrivKey, isCompressed = true): Uint8Array { - return Point.fromPrivateKey(privateKey).toRawBytes(isCompressed); + return ProjectivePoint.fromPrivateKey(privateKey).toRawBytes(isCompressed); } /** @@ -1077,7 +1004,7 @@ export function weierstrass(curveDef: CurveType): CurveFn { const len = (arr || str) && (item as Hex).length; if (arr) return len === compressedLen || len === uncompressedLen; if (str) return len === 2 * compressedLen || len === 2 * uncompressedLen; - if (item instanceof Point) return true; + if (item instanceof ProjectivePoint) return true; return false; } @@ -1170,7 +1097,7 @@ export function weierstrass(curveDef: CurveType): CurveFn { if (!isWithinCurveOrder(k)) return; // Important: all mod() calls in the function must be done over `n` const ik = mod.invert(k, n); - const q = Point.BASE.multiply(k); + const q = ProjectivePoint.BASE.multiply(k).toAffine(); // r = x mod n const r = mod.mod(q.x, n); if (r === _0n) return; @@ -1215,7 +1142,7 @@ export function weierstrass(curveDef: CurveType): CurveFn { } // Enable precomputes. Slows down first publicKey computation by 20ms. - Point.BASE._setWindowSize(8); + // Point.BASE._setWindowSize(8); /** * Verifies a signature against message hash and public key. @@ -1236,24 +1163,26 @@ export function weierstrass(curveDef: CurveType): CurveFn { publicKey: PubKey, opts: { lowS?: boolean } = { lowS: CURVE.lowS } ): boolean { + let _sig: Signature | undefined = undefined; try { if (signature instanceof Signature) { signature.assertValidity(); + _sig = signature; } else { // Signature can be represented in 2 ways: compact (64-byte) & DER (variable-length). // Since DER can also be 64 bytes, we check for it first. try { - signature = Signature.fromDER(signature as Hex); + _sig = Signature.fromDER(signature as Hex); } catch (derError) { if (!(derError instanceof DERError)) throw derError; - signature = Signature.fromCompact(signature as Hex); + _sig = Signature.fromCompact(signature as Hex); } } msgHash = ut.ensureBytes(msgHash); } catch (error) { return false; } - if (opts.lowS && signature.hasHighS()) return false; + if (opts.lowS && _sig.hasHighS()) return false; let P; try { P = normalizePublicKey(publicKey); @@ -1261,7 +1190,7 @@ export function weierstrass(curveDef: CurveType): CurveFn { return false; } const { n } = CURVE; - const { r, s } = signature; + const { r, s } = _sig; const h = bits2int_modN(msgHash); // Cannot use fields methods, since it is group element const sinv = mod.invert(s, n); // s^-1 @@ -1271,7 +1200,7 @@ export function weierstrass(curveDef: CurveType): CurveFn { // Some implementations compare R.x in projective, without inversion. // The speed-up is <5%, so we don't complicate the code. - const R = Point.BASE.multiplyAndAddUnsafe(P, u1, u2); + const R = ProjectivePoint.BASE.multiplyAndAddUnsafe(P, u1, u2)?.toAffine(); if (!R) return false; const v = mod.mod(R.x, n); return v === r; @@ -1283,7 +1212,7 @@ export function weierstrass(curveDef: CurveType): CurveFn { sign, signUnhashed, verify, - Point, + // Point, ProjectivePoint, Signature, utils, diff --git a/src/bls12-381.ts b/src/bls12-381.ts index c6a8181..762414d 100644 --- a/src/bls12-381.ts +++ b/src/bls12-381.ts @@ -28,7 +28,6 @@ import { } from './abstract/utils.js'; // Types import { - PointType, ProjectivePointType, ProjectiveConstructor, mapToCurveSimpleSWU, @@ -1035,7 +1034,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.Point.ZERO; + if ((bytes[0] & (1 << 6)) !== 0) return bls12_381.G1.ProjectivePoint.ZERO; const x = bytesToNumberBE(bytes.slice(0, Fp.BYTES)); const y = bytesToNumberBE(bytes.slice(Fp.BYTES)); return { x: Fp.create(x), y: Fp.create(y) }; @@ -1188,7 +1187,7 @@ export const bls12_381: CurveFn = bls({ }, Signature: { // TODO: Optimize, it's very slow because of sqrt. - decode(hex: Hex): PointType { + decode(hex: Hex): ProjectivePointType { hex = ensureBytes(hex); const P = Fp.ORDER; const half = hex.length / 2; @@ -1198,7 +1197,7 @@ export const bls12_381: CurveFn = bls({ const z2 = bytesToNumberBE(hex.slice(half)); // Indicates the infinity point const bflag1 = bitGet(z1, I_BIT_POS); - if (bflag1 === 1n) return bls12_381.G2.Point.ZERO; + if (bflag1 === 1n) return bls12_381.G2.ProjectivePoint.ZERO; const x1 = Fp.create(z1 & Fp.MASK); const x2 = Fp.create(z2); @@ -1215,14 +1214,14 @@ export const bls12_381: CurveFn = bls({ const isGreater = y1 > 0n && (y1 * 2n) / P !== aflag1; const isZero = y1 === 0n && (y0 * 2n) / P !== aflag1; if (isGreater || isZero) y = Fp2.negate(y); - const point = new bls12_381.G2.Point(x, y); + const point = bls12_381.G2.ProjectivePoint.fromAffine({ x, y }); point.assertValidity(); return point; }, - encode(point: PointType) { + encode(point: ProjectivePointType) { // NOTE: by some reasons it was missed in bls12-381, looks like bug point.assertValidity(); - if (point.equals(bls12_381.G2.Point.ZERO)) + if (point.equals(bls12_381.G2.ProjectivePoint.ZERO)) return concatBytes(COMPRESSED_ZERO, numberToBytesBE(0n, Fp.BYTES)); const { re: x0, im: x1 } = Fp2.reim(point.x); const { re: y0, im: y1 } = Fp2.reim(point.y); diff --git a/src/p256.ts b/src/p256.ts index 09e7842..0ba6ece 100644 --- a/src/p256.ts +++ b/src/p256.ts @@ -38,7 +38,7 @@ export const P256 = createCurve( export const secp256r1 = P256; const { hashToCurve, encodeToCurve } = htf.hashToCurve( - secp256r1.Point, + secp256r1.ProjectivePoint, (scalars: bigint[]) => mapSWU(scalars[0]), { DST: 'P256_XMD:SHA-256_SSWU_RO_', diff --git a/src/p384.ts b/src/p384.ts index 6286711..a28f9e0 100644 --- a/src/p384.ts +++ b/src/p384.ts @@ -42,7 +42,7 @@ export const P384 = createCurve({ export const secp384r1 = P384; const { hashToCurve, encodeToCurve } = htf.hashToCurve( - secp384r1.Point, + secp384r1.ProjectivePoint, (scalars: bigint[]) => mapSWU(scalars[0]), { DST: 'P384_XMD:SHA-384_SSWU_RO_', diff --git a/src/p521.ts b/src/p521.ts index f897a80..1525983 100644 --- a/src/p521.ts +++ b/src/p521.ts @@ -53,7 +53,7 @@ export const P521 = createCurve({ export const secp521r1 = P521; const { hashToCurve, encodeToCurve } = htf.hashToCurve( - secp521r1.Point, + secp521r1.ProjectivePoint, (scalars: bigint[]) => mapSWU(scalars[0]), { DST: 'P521_XMD:SHA-512_SSWU_RO_', diff --git a/src/secp256k1.ts b/src/secp256k1.ts index a28e153..afff496 100644 --- a/src/secp256k1.ts +++ b/src/secp256k1.ts @@ -2,7 +2,7 @@ import { sha256 } from '@noble/hashes/sha256'; import { Fp as Field, mod, pow2 } from './abstract/modular.js'; import { createCurve } from './_shortw_utils.js'; -import { PointType, mapToCurveSimpleSWU } from './abstract/weierstrass.js'; +import { ProjectivePointType as PointType, mapToCurveSimpleSWU } from './abstract/weierstrass.js'; import { ensureBytes, concatBytes, @@ -148,7 +148,7 @@ export const secp256k1 = createCurve( ); const { hashToCurve, encodeToCurve } = htf.hashToCurve( - secp256k1.Point, + secp256k1.ProjectivePoint, (scalars: bigint[]) => { const { x, y } = mapSWU(Fp.create(scalars[0])); return isoMap(x, y); @@ -173,7 +173,7 @@ const normalizePrivateKey = secp256k1.utils._normalizePrivateKey; // TODO: export? function normalizePublicKey(publicKey: Hex | PointType): PointType { - if (publicKey instanceof secp256k1.Point) { + if (publicKey instanceof secp256k1.ProjectivePoint) { publicKey.assertValidity(); return publicKey; } else { @@ -187,7 +187,7 @@ function normalizePublicKey(publicKey: Hex | PointType): PointType - toRawX(secp256k1.Point.fromPrivateKey(privateKey)), + toRawX(secp256k1.ProjectivePoint.fromPrivateKey(privateKey)), sign: schnorrSign, verify: schnorrVerify, }; diff --git a/src/stark.ts b/src/stark.ts index c8dc895..3ae9142 100644 --- a/src/stark.ts +++ b/src/stark.ts @@ -113,11 +113,10 @@ function verify0x(signature: Hex, msgHash: Hex, pubKey: Hex) { return starkCurve.verify(sig, ensureBytes0x(msgHash), ensureBytes0x(pubKey)); } -const { CURVE, Point, ProjectivePoint, Signature } = starkCurve; +const { CURVE, ProjectivePoint, Signature } = starkCurve; export const utils = starkCurve.utils; export { CURVE, - Point, Signature, ProjectivePoint, getPublicKey0x as getPublicKey, @@ -180,23 +179,23 @@ export function getAccountPath( // https://docs.starkware.co/starkex/pedersen-hash-function.html const PEDERSEN_POINTS_AFFINE = [ - new Point( + new ProjectivePoint( 2089986280348253421170679821480865132823066470938446095505822317253594081284n, 1713931329540660377023406109199410414810705867260802078187082345529207694986n ), - new Point( + new ProjectivePoint( 996781205833008774514500082376783249102396023663454813447423147977397232763n, 1668503676786377725805489344771023921079126552019160156920634619255970485781n ), - new Point( + new ProjectivePoint( 2251563274489750535117886426533222435294046428347329203627021249169616184184n, 1798716007562728905295480679789526322175868328062420237419143593021674992973n ), - new Point( + new ProjectivePoint( 2138414695194151160943305727036575959195309218611738193261179310511854807447n, 113410276730064486255102093846540133784865286929052426931474106396135072156n ), - new Point( + new ProjectivePoint( 2379962749567351885752724891227938183011949129833673362440656643086021394946n, 776496453633298175483985398648758586525933812536653089401905292063708816422n ), @@ -253,7 +252,7 @@ export function pedersen(x: PedersenArg, y: PedersenArg) { let point: ProjectivePoint = PEDERSEN_POINTS[0]; point = pedersenSingle(point, x, PEDERSEN_POINTS1); point = pedersenSingle(point, y, PEDERSEN_POINTS2); - return bytesToHexEth(point.toAffine().toRawBytes(true).slice(1)); + return bytesToHexEth(point.toRawBytes(true).slice(1)); } export function hashChain(data: PedersenArg[], fn = pedersen) { diff --git a/test/nist.test.js b/test/nist.test.js index 4aef6c1..af0237d 100644 --- a/test/nist.test.js +++ b/test/nist.test.js @@ -46,7 +46,7 @@ should('wychenproof ECDSA vectors', () => { if (group.key.curve === 'secp224r1' && group.sha !== 'SHA-224') { if (group.sha === 'SHA-256') CURVE = CURVE.create(sha256); } - const pubKey = CURVE.Point.fromHex(group.key.uncompressed); + const pubKey = CURVE.ProjectivePoint.fromHex(group.key.uncompressed); deepStrictEqual(pubKey.x, BigInt(`0x${group.key.wx}`)); deepStrictEqual(pubKey.y, BigInt(`0x${group.key.wy}`)); for (const test of group.tests) { @@ -87,7 +87,7 @@ should('wychenproof ECDH vectors', () => { for (const test of group.tests) { if (test.result === 'valid' || test.result === 'acceptable') { try { - const pub = CURVE.Point.fromHex(test.public); + const pub = CURVE.ProjectivePoint.fromHex(test.public); } catch (e) { if (e.message.includes('Point.fromHex: received invalid point.')) continue; throw e; @@ -147,7 +147,7 @@ for (const name in WYCHEPROOF_ECDH) { for (const test of group.tests) { if (test.result === 'valid' || test.result === 'acceptable') { try { - const pub = curve.Point.fromHex(test.public); + const pub = curve.ProjectivePoint.fromHex(test.public); } catch (e) { if (e.message.includes('Point.fromHex: received invalid point.')) continue; throw e; @@ -309,7 +309,7 @@ const WYCHEPROOF_ECDSA = { }; function runWycheproof(name, CURVE, group, index) { - const pubKey = CURVE.Point.fromHex(group.key.uncompressed); + const pubKey = CURVE.ProjectivePoint.fromHex(group.key.uncompressed); deepStrictEqual(pubKey.x, BigInt(`0x${group.key.wx}`)); deepStrictEqual(pubKey.y, BigInt(`0x${group.key.wy}`)); for (const test of group.tests) { @@ -367,7 +367,7 @@ should('RFC6979', () => { const curve = NIST[v.curve]; deepStrictEqual(curve.CURVE.n, hexToBigint(v.q)); const pubKey = curve.getPublicKey(v.private); - const pubPoint = curve.Point.fromHex(pubKey); + const pubPoint = curve.ProjectivePoint.fromHex(pubKey); deepStrictEqual(pubPoint.x, hexToBigint(v.Ux)); deepStrictEqual(pubPoint.y, hexToBigint(v.Uy)); for (const c of v.cases) { diff --git a/test/secp256k1.test.js b/test/secp256k1.test.js index 73c9510..9fda3fd 100644 --- a/test/secp256k1.test.js +++ b/test/secp256k1.test.js @@ -13,6 +13,7 @@ import { hexToBytes, bytesToHex } from '@noble/hashes/utils'; const hex = bytesToHex; const secp = secp256k1; +const Point = secp.ProjectivePoint; const privatesTxt = readFileSync('./test/vectors/privates-2.txt', 'utf-8'); const schCsv = readFileSync('./test/vectors/schnorr.csv', 'utf-8'); @@ -37,15 +38,15 @@ describe('secp256k1', () => { .filter((line) => line) .map((line) => line.split(':')); for (let [priv, x, y] of data) { - const point = secp.Point.fromPrivateKey(BigInt(priv)); + const point = Point.fromPrivateKey(BigInt(priv)); deepStrictEqual(toBEHex(point.x), x); deepStrictEqual(toBEHex(point.y), y); - const point2 = secp.Point.fromHex(secp.getPublicKey(toBEHex(BigInt(priv)))); + const point2 = Point.fromHex(secp.getPublicKey(toBEHex(BigInt(priv)))); deepStrictEqual(toBEHex(point2.x), x); deepStrictEqual(toBEHex(point2.y), y); - const point3 = secp.Point.fromHex(secp.getPublicKey(hexToBytes(toBEHex(BigInt(priv))))); + const point3 = Point.fromHex(secp.getPublicKey(hexToBytes(toBEHex(BigInt(priv))))); deepStrictEqual(toBEHex(point3.x), x); deepStrictEqual(toBEHex(point3.y), y); } @@ -62,15 +63,15 @@ describe('secp256k1', () => { .filter((line) => line) .map((line) => line.split(':')); for (let [priv, x, y] of data) { - const point = secp.Point.fromPrivateKey(BigInt(priv)); + const point = Point.fromPrivateKey(BigInt(priv)); deepStrictEqual(toBEHex(point.x), x); deepStrictEqual(toBEHex(point.y), y); - const point2 = secp.Point.fromHex(secp.getPublicKey(toBEHex(BigInt(priv)))); + const point2 = Point.fromHex(secp.getPublicKey(toBEHex(BigInt(priv)))); deepStrictEqual(toBEHex(point2.x), x); deepStrictEqual(toBEHex(point2.y), y); - const point3 = secp.Point.fromHex(secp.getPublicKey(hexToBytes(toBEHex(BigInt(priv))))); + const point3 = Point.fromHex(secp.getPublicKey(hexToBytes(toBEHex(BigInt(priv))))); deepStrictEqual(toBEHex(point3.x), x); deepStrictEqual(toBEHex(point3.y), y); } @@ -81,9 +82,9 @@ describe('secp256k1', () => { for (const vector of points.valid.isPoint) { const { P, expected } = vector; if (expected) { - secp.Point.fromHex(P); + Point.fromHex(P); } else { - throws(() => secp.Point.fromHex(P)); + throws(() => Point.fromHex(P)); } } }); @@ -91,7 +92,7 @@ describe('secp256k1', () => { should('.fromPrivateKey()', () => { for (const vector of points.valid.pointFromScalar) { const { d, expected } = vector; - let p = secp.Point.fromPrivateKey(d); + let p = Point.fromPrivateKey(d); deepStrictEqual(p.toHex(true), expected); } }); @@ -99,26 +100,26 @@ describe('secp256k1', () => { should('#toHex(compressed)', () => { for (const vector of points.valid.pointCompress) { const { P, compress, expected } = vector; - let p = secp.Point.fromHex(P); + let p = Point.fromHex(P); deepStrictEqual(p.toHex(compress), expected); } }); should('#toHex() roundtrip (failed case)', () => { const point1 = - secp.Point.fromPrivateKey( + Point.fromPrivateKey( 88572218780422190464634044548753414301110513745532121983949500266768436236425n ); // const hex = point1.toHex(true); - // deepStrictEqual(secp.Point.fromHex(hex).toHex(true), hex); + // deepStrictEqual(Point.fromHex(hex).toHex(true), hex); }); should('#toHex() roundtrip', () => { fc.assert( fc.property(FC_BIGINT, (x) => { - const point1 = secp.Point.fromPrivateKey(x); + const point1 = Point.fromPrivateKey(x); const hex = point1.toHex(true); - deepStrictEqual(secp.Point.fromHex(hex).toHex(true), hex); + deepStrictEqual(Point.fromHex(hex).toHex(true), hex); }) ); }); @@ -126,8 +127,8 @@ describe('secp256k1', () => { should('#add(other)', () => { for (const vector of points.valid.pointAdd) { const { P, Q, expected } = vector; - let p = secp.Point.fromHex(P); - let q = secp.Point.fromHex(Q); + let p = Point.fromHex(P); + let q = Point.fromHex(Q); if (expected) { deepStrictEqual(p.add(q).toHex(true), expected); } else { @@ -141,7 +142,7 @@ describe('secp256k1', () => { should('#multiply(privateKey)', () => { for (const vector of points.valid.pointMultiply) { const { P, d, expected } = vector; - const p = secp.Point.fromHex(P); + const p = Point.fromHex(P); if (expected) { deepStrictEqual(p.multiply(hexToNumber(d)).toHex(true), expected); } else { @@ -155,13 +156,13 @@ describe('secp256k1', () => { const { P, d } = vector; if (hexToNumber(d) < secp.CURVE.n) { throws(() => { - const p = secp.Point.fromHex(P); + const p = Point.fromHex(P); p.multiply(hexToNumber(d)).toHex(true); }); } } for (const num of [0n, 0, -1n, -1, 1.1]) { - throws(() => secp.Point.BASE.multiply(num)); + throws(() => Point.BASE.multiply(num)); } }); }); @@ -280,7 +281,7 @@ describe('secp256k1', () => { const PRIV_KEY = 0x2n; const WRONG_PRIV_KEY = 0x22n; const signature = secp.sign(MSG, PRIV_KEY); - const publicKey = secp.Point.fromPrivateKey(WRONG_PRIV_KEY).toHex(); + const publicKey = Point.fromPrivateKey(WRONG_PRIV_KEY).toHex(); deepStrictEqual(publicKey.length, 66); deepStrictEqual(secp.verify(signature, MSG, publicKey), false); }); @@ -313,7 +314,7 @@ describe('secp256k1', () => { const r = 1n; const s = 115792089237316195423570985008687907852837564279074904382605163141518162728904n; - const pub = new secp.Point(x, y); + const pub = new Point(x, y); const signature = new secp.Signature(2n, 2n); signature.r = r; signature.s = s; @@ -328,7 +329,7 @@ describe('secp256k1', () => { const y = 32670510020758816978083085130507043184471273380659243275938904335757337482424n; const r = 104546003225722045112039007203142344920046999340768276760147352389092131869133n; const s = 96900796730960181123786672629079577025401317267213807243199432755332205217369n; - const pub = new secp.Point(x, y); + const pub = new Point(x, y); const sig = new secp.Signature(r, s); deepStrictEqual(secp.verify(sig, msg, pub), false); }); @@ -338,7 +339,7 @@ describe('secp256k1', () => { const y = 17482644437196207387910659778872952193236850502325156318830589868678978890912n; const r = 432420386565659656852420866390673177323n; const s = 115792089237316195423570985008687907852837564279074904382605163141518161494334n; - const pub = new secp.Point(x, y); + const pub = new Point(x, y); const sig = new secp.Signature(r, s); deepStrictEqual(secp.verify(sig, msg, pub, { strict: false }), true); }); @@ -376,7 +377,7 @@ describe('secp256k1', () => { should('recover public key from recovery bit', () => { const message = '00000000000000000000000000000000000000000000000000000000deadbeef'; const privateKey = 123456789n; - const publicKey = secp.Point.fromHex(secp.getPublicKey(privateKey)).toHex(false); + const publicKey = Point.fromHex(secp.getPublicKey(privateKey)).toHex(false); const sig = secp.sign(message, privateKey); const recoveredPubkey = sig.recoverPublicKey(message); // const recoveredPubkey = secp.recoverPublicKey(message, signature, recovery); @@ -451,7 +452,7 @@ describe('secp256k1', () => { should('have proper curve equation in assertValidity()', () => { throws(() => { const { Fp } = secp.CURVE; - let point = new secp.Point(Fp.create(-2n), Fp.create(-1n)); + let point = new Point(Fp.create(-2n), Fp.create(-1n)); point.assertValidity(); }); }); @@ -472,15 +473,15 @@ describe('secp256k1', () => { }, pointAddScalar: (p, tweak, isCompressed) => { - const P = secp.Point.fromHex(p); + const P = Point.fromHex(p); const t = normal(tweak); - const Q = secp.Point.BASE.multiplyAndAddUnsafe(P, t, 1n); + const Q = Point.BASE.multiplyAndAddUnsafe(P, t, 1n); if (!Q) throw new Error('Tweaked point at infinity'); return Q.toRawBytes(isCompressed); }, pointMultiply: (p, tweak, isCompressed) => { - const P = secp.Point.fromHex(p); + const P = Point.fromHex(p); const h = typeof tweak === 'string' ? tweak : bytesToHex(tweak); const t = BigInt(`0x${h}`); return P.multiply(t).toRawBytes(isCompressed); @@ -528,7 +529,7 @@ describe('secp256k1', () => { should('wychenproof vectors', () => { for (let group of wp.testGroups) { - const pubKey = secp.Point.fromHex(group.key.uncompressed); + const pubKey = Point.fromHex(group.key.uncompressed); for (let test of group.tests) { const m = secp.CURVE.hash(hexToBytes(test.msg)); if (test.result === 'valid' || test.result === 'acceptable') {