diff --git a/src/abstract/bls.ts b/src/abstract/bls.ts index 90a8089..70f4e3a 100644 --- a/src/abstract/bls.ts +++ b/src/abstract/bls.ts @@ -18,7 +18,7 @@ import { Hex, PrivKey } from './utils.js'; import * as htf from './hash-to-curve.js'; import { CurvePointsType, - ProjectivePointType as PPointType, + ProjPointType as ProjPointType, CurvePointsRes, weierstrassPoints, AffinePoint, @@ -27,8 +27,8 @@ import { type Fp = bigint; // Can be different field? export type SignatureCoder = { - decode(hex: Hex): PPointType; - encode(point: PPointType): Uint8Array; + decode(hex: Hex): ProjPointType; + encode(point: ProjPointType): Uint8Array; }; export type CurveType = { @@ -79,29 +79,29 @@ export type CurveFn = { G1: ReturnType<(typeof htf.hashToCurve)>, G2: ReturnType<(typeof htf.hashToCurve)>, }, - pairing: (P: PPointType, Q: PPointType, withFinalExponent?: boolean) => Fp12; + pairing: (P: ProjPointType, Q: ProjPointType, withFinalExponent?: boolean) => Fp12; getPublicKey: (privateKey: PrivKey) => Uint8Array; sign: { (message: Hex, privateKey: PrivKey): Uint8Array; - (message: PPointType, privateKey: PrivKey): PPointType; + (message: ProjPointType, privateKey: PrivKey): ProjPointType; }; verify: ( - signature: Hex | PPointType, - message: Hex | PPointType, - publicKey: Hex | PPointType + signature: Hex | ProjPointType, + message: Hex | ProjPointType, + publicKey: Hex | ProjPointType ) => boolean; aggregatePublicKeys: { (publicKeys: Hex[]): Uint8Array; - (publicKeys: PPointType[]): PPointType; + (publicKeys: ProjPointType[]): ProjPointType; }; aggregateSignatures: { (signatures: Hex[]): Uint8Array; - (signatures: PPointType[]): PPointType; + (signatures: ProjPointType[]): ProjPointType; }; verifyBatch: ( - signature: Hex | PPointType, - messages: (Hex | PPointType)[], - publicKeys: (Hex | PPointType)[] + signature: Hex | ProjPointType, + messages: (Hex | ProjPointType)[], + publicKeys: (Hex | ProjPointType)[] ) => boolean; utils: { stringToBytes: typeof htf.stringToBytes; diff --git a/src/abstract/edwards.ts b/src/abstract/edwards.ts index 5f24799..e1496a2 100644 --- a/src/abstract/edwards.ts +++ b/src/abstract/edwards.ts @@ -67,39 +67,39 @@ export type AffinePoint = { } & { z?: never; t?: never }; // Instance of Extended Point with coordinates in X, Y, Z, T -export interface ExtendedPointType extends Group { +export interface ExtPointType extends Group { readonly ex: bigint; readonly ey: bigint; readonly ez: bigint; readonly et: bigint; - multiply(scalar: bigint): ExtendedPointType; - multiplyUnsafe(scalar: bigint): ExtendedPointType; + multiply(scalar: bigint): ExtPointType; + multiplyUnsafe(scalar: bigint): ExtPointType; isSmallOrder(): boolean; isTorsionFree(): boolean; toAffine(iz?: bigint): AffinePoint; - clearCofactor(): ExtendedPointType; + clearCofactor(): ExtPointType; } // Static methods of Extended Point with coordinates in X, Y, Z, T -export interface ExtendedPointConstructor extends GroupConstructor { - new (x: bigint, y: bigint, z: bigint, t: bigint): ExtendedPointType; - fromAffine(p: AffinePoint): ExtendedPointType; - fromHex(hex: Hex): ExtendedPointType; - fromPrivateKey(privateKey: PrivKey): ExtendedPointType; // TODO: remove +export interface ExtPointConstructor extends GroupConstructor { + new (x: bigint, y: bigint, z: bigint, t: bigint): ExtPointType; + fromAffine(p: AffinePoint): ExtPointType; + fromHex(hex: Hex): ExtPointType; + fromPrivateKey(privateKey: PrivKey): ExtPointType; // TODO: remove } export type CurveFn = { CURVE: ReturnType; - getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array; + getPublicKey: (privateKey: Hex) => Uint8Array; sign: (message: Hex, privateKey: Hex) => Uint8Array; verify: (sig: Hex, message: Hex, publicKey: Hex) => boolean; - ExtendedPoint: ExtendedPointConstructor; + ExtendedPoint: ExtPointConstructor; utils: { randomPrivateKey: () => Uint8Array; - getExtendedPublicKey: (key: PrivKey) => { + getExtendedPublicKey: (key: Hex) => { head: Uint8Array; prefix: Uint8Array; scalar: bigint; - point: ExtendedPointType; + point: ExtPointType; pointBytes: Uint8Array; }; }; @@ -153,20 +153,18 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { // GE = subgroup element, not full group return n === _0n ? n : assertGE(n); } - function badc(a: any) { - return a == null || !ut.big(a); - } + const coord = (n: bigint) => _0n <= n && n < MASK; // not < P because of ZIP215 - const pointPrecomputes = new Map(); + const pointPrecomputes = new Map(); /** * Extended Point works in extended coordinates: (x, y, z, t) ∋ (x=x/z, y=y/z, t=xy). * Default Point works in affine coordinates: (x, y) * https://en.wikipedia.org/wiki/Twisted_Edwards_curve#Extended_coordinates */ - class ExtendedPoint implements ExtendedPointType { - 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 + class Point implements ExtPointType { + static readonly BASE = new Point(CURVE.Gx, CURVE.Gy, _1n, modP(CURVE.Gx * CURVE.Gy)); + static readonly ZERO = new Point(_0n, _1n, _1n, _0n); // 0, 1, 1, 0 constructor( readonly ex: bigint, @@ -174,9 +172,10 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { readonly ez: bigint, readonly et: bigint ) { - if (badc(ey)) throw new Error('y required'); - if (badc(ez)) throw new Error('z required'); - if (badc(et)) throw new Error('t required'); + if (!coord(ex)) throw new Error('x required'); + if (!coord(ey)) throw new Error('y required'); + if (!coord(ez)) throw new Error('z required'); + if (!coord(et)) throw new Error('t required'); } get x(): bigint { @@ -186,15 +185,15 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { return this.toAffine().y; } - static fromAffine(p: AffinePoint): ExtendedPoint { + static fromAffine(p: AffinePoint): Point { const { x, y } = p || {}; - if (p instanceof ExtendedPoint) throw new Error('fromAffine: extended point not allowed'); + if (p instanceof Point) throw new Error('fromAffine: extended point not allowed'); if (!ut.big(x) || !ut.big(y)) throw new Error('fromAffine: invalid affine point'); - return new ExtendedPoint(x, y, _1n, modP(x * y)); + return new Point(x, y, _1n, modP(x * y)); } - static normalizeZ(points: ExtendedPoint[]): ExtendedPoint[] { + static normalizeZ(points: Point[]): Point[] { const toInv = Fp.invertBatch(points.map((p) => p.ez)); - return points.map((p, i) => p.toAffine(toInv[i])).map(ExtendedPoint.fromAffine); + return points.map((p, i) => p.toAffine(toInv[i])).map(Point.fromAffine); } // We calculate precomputes for elliptic curve point multiplication @@ -209,7 +208,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { } // Compare one point to another. - equals(other: ExtendedPoint): boolean { + equals(other: Point): boolean { assertExtPoint(other); const { ex: X1, ey: Y1, ez: Z1 } = this; const { ex: X2, ey: Y2, ez: Z2 } = other; @@ -221,18 +220,18 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { } protected is0(): boolean { - return this.equals(ExtendedPoint.ZERO); + return this.equals(Point.ZERO); } // Inverses point to one corresponding to (x, -y) in Affine coordinates. - negate(): ExtendedPoint { - return new ExtendedPoint(modP(-this.ex), this.ey, this.ez, modP(-this.et)); + negate(): Point { + return new Point(modP(-this.ex), this.ey, this.ez, modP(-this.et)); } // Fast algo for doubling Extended Point. // https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#doubling-dbl-2008-hwcd // Cost: 4M + 4S + 1*a + 6add + 1*2. - double(): ExtendedPoint { + double(): Point { const { a } = CURVE; const { ex: X1, ey: Y1, ez: Z1 } = this; const A = modP(X1 * X1); // A = X12 @@ -248,13 +247,13 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { const Y3 = modP(G * H); // Y3 = G*H const T3 = modP(E * H); // T3 = E*H const Z3 = modP(F * G); // Z3 = F*G - return new ExtendedPoint(X3, Y3, Z3, T3); + return new Point(X3, Y3, Z3, T3); } // Fast algo for adding 2 Extended Points. // https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#addition-add-2008-hwcd // Cost: 9M + 1*a + 1*d + 7add. - add(other: ExtendedPoint) { + add(other: Point) { assertExtPoint(other); const { a, d } = CURVE; const { ex: X1, ey: Y1, ez: Z1, et: T1 } = this; @@ -277,7 +276,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { const Y3 = modP(G * H); const T3 = modP(E * H); const Z3 = modP(F * G); - return new ExtendedPoint(X3, Y3, Z3, T3); + return new Point(X3, Y3, Z3, T3); } const A = modP(X1 * X2); // A = X1*X2 const B = modP(Y1 * Y2); // B = Y1*Y2 @@ -292,29 +291,29 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { const T3 = modP(E * H); // T3 = E*H const Z3 = modP(F * G); // Z3 = F*G - return new ExtendedPoint(X3, Y3, Z3, T3); + return new Point(X3, Y3, Z3, T3); } - subtract(other: ExtendedPoint): ExtendedPoint { + subtract(other: Point): Point { return this.add(other.negate()); } - private wNAF(n: bigint): { p: ExtendedPoint; f: ExtendedPoint } { - return wnaf.wNAFCached(this, pointPrecomputes, n, ExtendedPoint.normalizeZ); + private wNAF(n: bigint): { p: Point; f: Point } { + return wnaf.wNAFCached(this, pointPrecomputes, n, Point.normalizeZ); } // Constant time multiplication. // Uses wNAF method. Windowed method may be 10% faster, // but takes 2x longer to generate and consumes 2x memory. - multiply(scalar: bigint): ExtendedPoint { + multiply(scalar: bigint): Point { const { p, f } = this.wNAF(assertGE(scalar)); - return ExtendedPoint.normalizeZ([p, f])[0]; + return Point.normalizeZ([p, f])[0]; } // Non-constant-time multiplication. Uses double-and-add algorithm. // It's faster, but should only be used when you don't care about // an exposed private key e.g. sig verification. - multiplyUnsafe(scalar: bigint): ExtendedPoint { + multiplyUnsafe(scalar: bigint): Point { let n = assertGE0(scalar); if (n === _0n) return I; if (this.equals(I) || n === _1n) return this; @@ -349,7 +348,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { if (zz !== _1n) throw new Error('invZ was invalid'); return { x: ax, y: ay }; } - clearCofactor(): ExtendedPoint { + clearCofactor(): Point { const { h: cofactor } = CURVE; if (cofactor === _1n) return this; return this.multiplyUnsafe(cofactor); @@ -399,7 +398,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { const isXOdd = (x & _1n) === _1n; const isLastByteOdd = (lastByte & 0x80) !== 0; if (isLastByteOdd !== isXOdd) x = modP(-x); - return ExtendedPoint.fromAffine({ x, y }); + return Point.fromAffine({ x, y }); } static fromPrivateKey(privateKey: PrivKey) { return getExtendedPublicKey(privateKey).point; @@ -415,11 +414,11 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { return ut.bytesToHex(this.toRawBytes()); // Same as toRawBytes, but returns string. } } - const { BASE: G, ZERO: I } = ExtendedPoint; - const wnaf = wNAF(ExtendedPoint, CURVE.nByteLength * 8); + const { BASE: G, ZERO: I } = Point; + const wnaf = wNAF(Point, CURVE.nByteLength * 8); function assertExtPoint(other: unknown) { - if (!(other instanceof ExtendedPoint)) throw new Error('ExtendedPoint expected'); + if (!(other instanceof Point)) throw new Error('ExtendedPoint expected'); } // Little-endian SHA512 with modulo n function modnLE(hash: Uint8Array): bigint { @@ -490,15 +489,15 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { sig = ensureBytes(sig, 2 * len); message = ensureBytes(message); if (CURVE.preHash) message = CURVE.preHash(message); - const R = ExtendedPoint.fromHex(sig.slice(0, len), false); // non-strict; allows 0..MASK + const R = Point.fromHex(sig.slice(0, len), false); // non-strict; allows 0..MASK const s = ut.bytesToNumberLE(sig.slice(len, 2 * len)); - const A = ExtendedPoint.fromHex(publicKey, false); // Check for s bounds, hex validity + const A = Point.fromHex(publicKey, false); // Check for s bounds, hex validity const SB = G.multiplyUnsafe(s); const k = hashDomainToScalar(ut.concatBytes(R.toRawBytes(), A.toRawBytes(), message), context); const kA = A.multiplyUnsafe(k); const RkA = R.add(kA); // [8][S]B = [8]R + [8][k]A' - return RkA.subtract(SB).clearCofactor().equals(ExtendedPoint.ZERO); + return RkA.subtract(SB).clearCofactor().equals(Point.ZERO); } // Enable precomputes. Slows down first publicKey computation by 20ms. @@ -523,7 +522,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { * but allows to speed-up subsequent getPublicKey() calls up to 20x. * @param windowSize 2, 4, 8, 16 */ - precompute(windowSize = 8, point = ExtendedPoint.BASE): typeof ExtendedPoint.BASE { + precompute(windowSize = 8, point = Point.BASE): typeof Point.BASE { point._setWindowSize(windowSize); point.multiply(BigInt(3)); return point; @@ -535,7 +534,7 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { getPublicKey, sign, verify, - ExtendedPoint, + ExtendedPoint: Point, utils, }; } diff --git a/src/abstract/weierstrass.ts b/src/abstract/weierstrass.ts index 7c03b8f..2daabf4 100644 --- a/src/abstract/weierstrass.ts +++ b/src/abstract/weierstrass.ts @@ -32,12 +32,9 @@ export type BasicCurve = ut.BasicCurve & { endo?: EndomorphismOpts; // When a cofactor != 1, there can be an effective methods to: // 1. Determine whether a point is torsion-free - isTorsionFree?: (c: ProjectiveConstructor, point: ProjectivePointType) => boolean; + isTorsionFree?: (c: ProjConstructor, point: ProjPointType) => boolean; // 2. Clear torsion component - clearCofactor?: ( - c: ProjectiveConstructor, - point: ProjectivePointType - ) => ProjectivePointType; + clearCofactor?: (c: ProjConstructor, point: ProjPointType) => ProjPointType; }; // ASN.1 DER encoding utilities @@ -115,43 +112,35 @@ export type AffinePoint = { y: T; } & { z?: never }; // Instance for 3d XYZ points -export interface ProjectivePointType extends Group> { +export interface ProjPointType extends Group> { readonly px: T; readonly py: T; readonly pz: T; - multiply(scalar: bigint): ProjectivePointType; - multiplyUnsafe(scalar: bigint): ProjectivePointType; - multiplyAndAddUnsafe( - Q: ProjectivePointType, - a: bigint, - b: bigint - ): ProjectivePointType | undefined; + multiply(scalar: bigint): ProjPointType; + multiplyUnsafe(scalar: bigint): ProjPointType; + multiplyAndAddUnsafe(Q: ProjPointType, a: bigint, b: bigint): ProjPointType | undefined; _setWindowSize(windowSize: number): void; toAffine(iz?: T): AffinePoint; isTorsionFree(): boolean; - clearCofactor(): ProjectivePointType; + clearCofactor(): ProjPointType; assertValidity(): void; hasEvenY(): boolean; toRawBytes(isCompressed?: boolean): Uint8Array; toHex(isCompressed?: boolean): string; } // Static methods for 3d XYZ points -export interface ProjectiveConstructor extends GroupConstructor> { - new (x: T, y: T, z: T): ProjectivePointType; - fromAffine(p: AffinePoint): ProjectivePointType; - fromHex(hex: Hex): ProjectivePointType; - fromPrivateKey(privateKey: PrivKey): ProjectivePointType; - normalizeZ(points: ProjectivePointType[]): ProjectivePointType[]; +export interface ProjConstructor extends GroupConstructor> { + new (x: T, y: T, z: T): ProjPointType; + fromAffine(p: AffinePoint): ProjPointType; + fromHex(hex: Hex): ProjPointType; + fromPrivateKey(privateKey: PrivKey): ProjPointType; + normalizeZ(points: ProjPointType[]): ProjPointType[]; } export type CurvePointsType = BasicCurve & { // Bytes fromBytes: (bytes: Uint8Array) => AffinePoint; - toBytes: ( - c: ProjectiveConstructor, - point: ProjectivePointType, - compressed: boolean - ) => Uint8Array; + toBytes: (c: ProjConstructor, point: ProjPointType, compressed: boolean) => Uint8Array; }; function validatePointOpts(curve: CurvePointsType) { @@ -185,7 +174,7 @@ function validatePointOpts(curve: CurvePointsType) { } export type CurvePointsRes = { - ProjectivePoint: ProjectiveConstructor; + ProjectivePoint: ProjConstructor; normalizePrivateKey: (key: PrivKey) => bigint; weierstrassEquation: (x: T) => T; isWithinCurveOrder: (num: bigint) => boolean; @@ -230,17 +219,15 @@ export function weierstrassPoints(opts: CurvePointsType) { if (typeof key === 'bigint') { // Curve order check is done below num = key; - } else if (ut.isPositiveInt(key)) { - num = BigInt(key); } else if (typeof key === 'string') { - if (key.length !== 2 * groupLen) throw new Error(`Private key must be ${groupLen} bytes`); + if (key.length !== 2 * groupLen) throw new Error(`must be ${groupLen} bytes`); // Validates individual octets num = ut.hexToNumber(key); } else if (key instanceof Uint8Array) { - if (key.length !== groupLen) throw new Error(`Private key must be ${groupLen} bytes`); + if (key.length !== groupLen) throw new Error(`must be ${groupLen} bytes`); num = ut.bytesToNumberBE(key); } else { - throw new Error('Private key was invalid'); + throw new Error('private key must be bytes, hex or bigint, not ' + typeof key); } // Useful for curves with cofactor != 1 if (wrapPrivateKey) num = mod.mod(num, order); @@ -249,25 +236,28 @@ export function weierstrassPoints(opts: CurvePointsType) { } const pointPrecomputes = new Map(); + function assertPrjPoint(other: unknown) { + if (!(other instanceof ProjectivePoint)) throw new Error('ProjectivePoint expected'); + } /** * Projective Point works in 3d / projective (homogeneous) coordinates: (x, y, z) ∋ (x=x/z, y=y/z) * Default Point works in 2d / affine coordinates: (x, y) * We're doing calculations in projective, because its operations don't require costly inversion. */ - class ProjectivePoint implements ProjectivePointType { + class ProjectivePoint implements ProjPointType { static readonly BASE = new ProjectivePoint(CURVE.Gx, CURVE.Gy, Fp.ONE); static readonly ZERO = new ProjectivePoint(Fp.ZERO, Fp.ONE, Fp.ZERO); constructor(readonly px: T, readonly py: T, readonly pz: T) { - if (py == null || !Fp.isValid(py)) throw new Error('ProjectivePoint: y required'); - if (pz == null || !Fp.isValid(pz)) throw new Error('ProjectivePoint: z required'); + if (px == null || !Fp.isValid(px)) throw new Error('x required'); + if (py == null || !Fp.isValid(py)) throw new Error('y required'); + if (pz == null || !Fp.isValid(pz)) throw new Error('z required'); } static fromAffine(p: AffinePoint): ProjectivePoint { const { x, y } = p || {}; - if (!p || !Fp.isValid(x) || !Fp.isValid(y)) - throw new Error('fromAffine: invalid affine point'); - if (p instanceof ProjectivePoint) throw new Error('fromAffine: projective point not allowed'); + if (!p || !Fp.isValid(x) || !Fp.isValid(y)) throw new Error('invalid affine point'); + if (p instanceof ProjectivePoint) throw new Error('projective point not allowed'); const is0 = (i: T) => Fp.equals(i, Fp.ZERO); // fromAffine(x:0, y:0) would produce (x:0, y:0, z:1), but we need (x:0, y:1, z:0) if (is0(x) && is0(y)) return ProjectivePoint.ZERO; @@ -587,11 +577,8 @@ export function weierstrassPoints(opts: CurvePointsType) { 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 Error('ProjectivePoint expected'); - } return { - ProjectivePoint: ProjectivePoint as ProjectiveConstructor, + ProjectivePoint: ProjectivePoint as ProjConstructor, normalizePrivateKey, weierstrassEquation, isWithinCurveOrder, @@ -607,7 +594,7 @@ export interface SignatureType { addRecoveryBit(recovery: number): SignatureType; hasHighS(): boolean; normalizeS(): SignatureType; - recoverPublicKey(msgHash: Hex): ProjectivePointType; + recoverPublicKey(msgHash: Hex): ProjPointType; // DER-encoded toDERRawBytes(isCompressed?: boolean): Uint8Array; toDERHex(isCompressed?: boolean): string; @@ -621,7 +608,7 @@ export type SignatureConstructor = { fromDER(hex: Hex): SignatureType; }; -export type PubKey = Hex | ProjectivePointType; +export type PubKey = Hex | ProjPointType; export type CurveType = BasicCurve & { // Default options @@ -651,7 +638,7 @@ export type CurveFn = { getSharedSecret: (privateA: PrivKey, publicB: Hex, isCompressed?: boolean) => Uint8Array; sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => SignatureType; verify: (signature: Hex | SignatureType, msgHash: Hex, publicKey: Hex, opts?: VerOpts) => boolean; - ProjectivePoint: ProjectiveConstructor; + ProjectivePoint: ProjConstructor; Signature: SignatureConstructor; utils: { _bigintToBytes: (num: bigint) => Uint8Array; @@ -1104,7 +1091,7 @@ export function weierstrass(curveDef: CurveType): CurveFn { publicKey: Hex, opts = defaultVerOpts ): boolean { - let P: ProjectivePointType; + let P: ProjPointType; let _sig: Signature | undefined = undefined; if (publicKey instanceof Point) throw new Error('publicKey must be hex'); try { diff --git a/src/bls12-381.ts b/src/bls12-381.ts index d3d6374..464bd82 100644 --- a/src/bls12-381.ts +++ b/src/bls12-381.ts @@ -28,8 +28,8 @@ import { } from './abstract/utils.js'; // Types import { - ProjectivePointType, - ProjectiveConstructor, + ProjPointType, + ProjConstructor, mapToCurveSimpleSWU, AffinePoint, } from './abstract/weierstrass.js'; @@ -886,7 +886,7 @@ function psi(x: Fp2, y: Fp2): [Fp2, Fp2] { return [x2, y2]; } // Ψ endomorphism -function G2psi(c: ProjectiveConstructor, P: ProjectivePointType) { +function G2psi(c: ProjConstructor, P: ProjPointType) { const affine = P.toAffine(); const p = psi(affine.x, affine.y); return new c(p[0], p[1], Fp2.ONE); @@ -899,7 +899,7 @@ const PSI2_C1 = function psi2(x: Fp2, y: Fp2): [Fp2, Fp2] { return [Fp2.mul(x, PSI2_C1), Fp2.negate(y)]; } -function G2psi2(c: ProjectiveConstructor, P: ProjectivePointType) { +function G2psi2(c: ProjConstructor, P: ProjPointType) { const affine = P.toAffine(); const p = psi2(affine.x, affine.y); return new c(p[0], p[1], Fp2.ONE); @@ -1190,7 +1190,7 @@ export const bls12_381: CurveFn = bls({ }, Signature: { // TODO: Optimize, it's very slow because of sqrt. - decode(hex: Hex): ProjectivePointType { + decode(hex: Hex): ProjPointType { hex = ensureBytes(hex); const P = Fp.ORDER; const half = hex.length / 2; @@ -1222,7 +1222,7 @@ export const bls12_381: CurveFn = bls({ point.assertValidity(); return point; }, - encode(point: ProjectivePointType) { + encode(point: ProjPointType) { // NOTE: by some reasons it was missed in bls12-381, looks like bug point.assertValidity(); if (point.equals(bls12_381.G2.ProjectivePoint.ZERO)) diff --git a/src/ed25519.ts b/src/ed25519.ts index 377d44a..7600b17 100644 --- a/src/ed25519.ts +++ b/src/ed25519.ts @@ -1,7 +1,7 @@ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ import { sha512 } from '@noble/hashes/sha512'; import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils'; -import { twistedEdwards, ExtendedPointType } from './abstract/edwards.js'; +import { twistedEdwards, ExtPointType } from './abstract/edwards.js'; import { montgomery } from './abstract/montgomery.js'; import { mod, pow2, isNegativeLE, Fp as Field, FpSqrtEven } from './abstract/modular.js'; import { @@ -269,7 +269,7 @@ const MAX_255B = BigInt('0x7ffffffffffffffffffffffffffffffffffffffffffffffffffff const bytes255ToNumberLE = (bytes: Uint8Array) => ed25519.CURVE.Fp.create(bytesToNumberLE(bytes) & MAX_255B); -type ExtendedPoint = ExtendedPointType; +type ExtendedPoint = ExtPointType; // Computes Elligator map for Ristretto // https://ristretto.group/formulas/elligator.html diff --git a/src/secp256k1.ts b/src/secp256k1.ts index 6f5976e..fcedb58 100644 --- a/src/secp256k1.ts +++ b/src/secp256k1.ts @@ -2,15 +2,8 @@ import { sha256 } from '@noble/hashes/sha256'; import { Fp as Field, mod, pow2 } from './abstract/modular.js'; import { createCurve } from './_shortw_utils.js'; -import { ProjectivePointType as PointType, mapToCurveSimpleSWU } from './abstract/weierstrass.js'; -import { - ensureBytes, - concatBytes, - Hex, - hexToBytes, - bytesToNumberBE, - PrivKey, -} from './abstract/utils.js'; +import { ProjPointType as PointType, mapToCurveSimpleSWU } from './abstract/weierstrass.js'; +import { ensureBytes, concatBytes, Hex, bytesToNumberBE, PrivKey } from './abstract/utils.js'; import { randomBytes } from '@noble/hashes/utils'; import * as htf from './abstract/hash-to-curve.js'; @@ -62,45 +55,6 @@ function sqrtMod(y: bigint): bigint { const Fp = Field(secp256k1P, undefined, undefined, { sqrt: sqrtMod }); type Fp = bigint; -const isoMap = htf.isogenyMap( - Fp, - [ - // xNum - [ - '0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7', - '0x7d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581', - '0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262', - '0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c', - ], - // xDen - [ - '0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b', - '0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14', - '0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1 - ], - // yNum - [ - '0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c', - '0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3', - '0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931', - '0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84', - ], - // yDen - [ - '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b', - '0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573', - '0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f', - '0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1 - ], - ].map((i) => i.map((j) => BigInt(j))) as [Fp[], Fp[], Fp[], Fp[]] -); - -const mapSWU = mapToCurveSimpleSWU(Fp, { - A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'), - B: BigInt('1771'), - Z: Fp.create(BigInt('-11')), -}); - export const secp256k1 = createCurve( { // Params: a, b @@ -147,54 +101,11 @@ export const secp256k1 = createCurve( sha256 ); -const { hashToCurve, encodeToCurve } = htf.hashToCurve( - secp256k1.ProjectivePoint, - (scalars: bigint[]) => { - const { x, y } = mapSWU(Fp.create(scalars[0])); - return isoMap(x, y); - }, - { - DST: 'secp256k1_XMD:SHA-256_SSWU_RO_', - encodeDST: 'secp256k1_XMD:SHA-256_SSWU_NU_', - p: Fp.ORDER, - m: 1, - k: 128, - expand: 'xmd', - hash: sha256, - } -); -export { hashToCurve, encodeToCurve }; - // Schnorr const _0n = BigInt(0); const numTo32b = secp256k1.utils._bigintToBytes; -const numTo32bStr = secp256k1.utils._bigintToString; -const normalizePrivateKey = secp256k1.utils._normalizePrivateKey; - -// TODO: export? -function normalizePublicKey(publicKey: Hex | PointType): PointType { - if (publicKey instanceof secp256k1.ProjectivePoint) { - publicKey.assertValidity(); - return publicKey; - } else { - const bytes = ensureBytes(publicKey); - // Schnorr is 32 bytes - if (bytes.length !== 32) throw new Error('Schnorr pubkeys must be 32 bytes'); - const x = bytesToNumberBE(bytes); - if (!isValidFieldElement(x)) throw new Error('Point is not on curve'); - const y2 = secp256k1.utils._weierstrassEquation(x); // y² = x³ + ax + b - let y = sqrtMod(y2); // y = y² ^ (p+1)/4 - const isYOdd = (y & _1n) === _1n; - // Schnorr - if (isYOdd) y = secp256k1.CURVE.Fp.negate(y); - const point = secp256k1.ProjectivePoint.fromAffine({ x, y }); - point.assertValidity(); - return point; - } -} - -const isWithinCurveOrder = secp256k1.utils._isWithinCurveOrder; -const isValidFieldElement = secp256k1.utils._isValidFieldElement; +const fe = (x: bigint) => typeof x === 'bigint' && _0n < x && x < secp256k1P; +const ge = (x: bigint) => typeof x === 'bigint' && _0n < x && x < secp256k1N; const TAGS = { challenge: 'BIP0340/challenge', @@ -213,45 +124,44 @@ export function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array { } return sha256(concatBytes(tagP, ...messages)); } - -const toRawX = (point: PointType) => point.toRawBytes(true).slice(1); - // Schnorr signatures are superior to ECDSA from above. // Below is Schnorr-specific code as per BIP0340. -function schnorrChallengeFinalize(ch: Uint8Array): bigint { - return mod(bytesToNumberBE(ch), secp256k1.CURVE.n); -} -// Do we need this at all for Schnorr? -class SchnorrSignature { - constructor(readonly r: bigint, readonly s: bigint) { - this.assertValidity(); - } - static fromHex(hex: Hex) { - const bytes = ensureBytes(hex); - const len = 32; // group length - if (bytes.length !== 2 * len) - throw new Error(`SchnorrSignature.fromHex: expected ${2 * len} bytes, not ${bytes.length}`); - const r = bytesToNumberBE(bytes.subarray(0, len)); - const s = bytesToNumberBE(bytes.subarray(len, 2 * len)); - return new SchnorrSignature(r, s); - } - assertValidity() { - const { r, s } = this; - if (!isValidFieldElement(r) || !isWithinCurveOrder(s)) throw new Error('Invalid signature'); - } - toHex(): string { - return numTo32bStr(this.r) + numTo32bStr(this.s); - } - toRawBytes(): Uint8Array { - return hexToBytes(this.toHex()); - } -} +const tag = taggedHash; +const toRawX = (point: PointType) => point.toRawBytes(true).slice(1); +const b2num = bytesToNumberBE; +const modN = (x: bigint) => mod(x, secp256k1N); +function validateRS(r: bigint, s: bigint) { + if (!fe(r) || !ge(s)) throw new Error('Invalid signature'); +} +const PPoint = secp256k1.ProjectivePoint; function schnorrGetScalar(priv: bigint) { - const point = secp256k1.ProjectivePoint.fromPrivateKey(priv); - const scalar = point.hasEvenY() ? priv : secp256k1.CURVE.n - priv; + const point = PPoint.fromPrivateKey(priv); + const scalar = point.hasEvenY() ? priv : modN(-priv); return { point, scalar, x: toRawX(point) }; } +function lift_x(x: bigint) { + if (!fe(x)) throw new Error('not fe'); // Fail if x ≥ p. + const c = mod(x * x * x + BigInt(7), secp256k1P); // Let c = x3 + 7 mod p. + let y = sqrtMod(c); // Let y = c^(p+1)/4 mod p. + if (y % 2n !== 0n) y = mod(-y, secp256k1P); // Return the unique point P such that x(P) = x and + const p = new PPoint(x, y, _1n); // y(P) = y if y mod 2 = 0 or y(P) = p-y otherwise. + p.assertValidity(); + return p; +} +function packSig(r: bigint, s: bigint): Uint8Array { + validateRS(r, s); + const sig = new Uint8Array(64); + sig.set(numTo32b(r), 0); + sig.set(numTo32b(s), 32); + return sig; +} +function unpackSig(sig: Uint8Array): { r: bigint; s: bigint } { + const r = b2num(sig.subarray(0, 32)); // Let r = int(sig[0:32]); fail if r ≥ p. + const s = b2num(sig.subarray(32, 64)); // Let s = int(sig[32:64]); fail if s ≥ n. + validateRS(r, s); + return { r, s }; +} /** * Synchronously creates Schnorr signature. Improved security: verifies itself before * producing an output. @@ -259,26 +169,20 @@ function schnorrGetScalar(priv: bigint) { * @param privateKey private key * @param auxRand random bytes that would be added to k. Bad RNG won't break it. */ -function schnorrSign( - message: Hex, - privateKey: PrivKey, - auxRand: Hex = randomBytes(32) -): Uint8Array { +function schnorrSign(message: Hex, privateKey: Hex, auxRand: Hex = randomBytes(32)): Uint8Array { if (message == null) throw new Error(`sign: Expected valid message, not "${message}"`); const m = ensureBytes(message); // checks for isWithinCurveOrder - const { x: px, scalar: d } = schnorrGetScalar(normalizePrivateKey(privateKey)); - const rand = ensureBytes(auxRand); - if (rand.length !== 32) throw new Error('sign: Expected 32 bytes of aux randomness'); - const tag = taggedHash; - const t0h = tag(TAGS.aux, rand); - const t = numTo32b(d ^ bytesToNumberBE(t0h)); - const k0h = tag(TAGS.nonce, t, px, m); - const k0 = mod(bytesToNumberBE(k0h), secp256k1.CURVE.n); - if (k0 === _0n) throw new Error('sign: Creation of signature failed. k is zero'); - const { point: R, x: rx, scalar: k } = schnorrGetScalar(k0); - const e = schnorrChallengeFinalize(tag(TAGS.challenge, rx, px, m)); - const sig = new SchnorrSignature(R.px, mod(k + e * d, secp256k1.CURVE.n)).toRawBytes(); + const { x: px, scalar: d } = schnorrGetScalar(b2num(ensureBytes(privateKey, 32))); + const a = ensureBytes(auxRand, 32); // Auxiliary random data a: a 32-byte array + // TODO: replace with proper xor? + const t = numTo32b(d ^ bytesToNumberBE(tag(TAGS.aux, a))); // Let t be the byte-wise xor of bytes(d) and hashBIP0340/aux(a) + const rand = tag(TAGS.nonce, t, px, m); // Let rand = hashBIP0340/nonce(t || bytes(P) || m) + const k_ = modN(bytesToNumberBE(rand)); // Let k' = int(rand) mod n + if (k_ === _0n) throw new Error('sign failed: k is zero'); // Fail if k' = 0. + const { point: R, x: rx, scalar: k } = schnorrGetScalar(k_); + const e = modN(b2num(tag(TAGS.challenge, rx, px, m))); + const sig = packSig(R.px, modN(k + e * d)); if (!schnorrVerify(sig, m, px)) throw new Error('sign: Invalid signature produced'); return sig; } @@ -288,23 +192,12 @@ function schnorrSign( */ function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean { try { - const raw = signature instanceof SchnorrSignature; - const sig: SchnorrSignature = raw ? signature : SchnorrSignature.fromHex(signature); - if (raw) sig.assertValidity(); // just in case - - const { r, s } = sig; + const P = lift_x(b2num(ensureBytes(publicKey, 32))); // P = lift_x(int(pk)); fail if that fails + const { r, s } = unpackSig(ensureBytes(signature, 64)); const m = ensureBytes(message); - const P = normalizePublicKey(publicKey); - const e = schnorrChallengeFinalize(taggedHash(TAGS.challenge, numTo32b(r), toRawX(P), m)); - // Finalize - // R = s⋅G - e⋅P - // -eP == (n-e)P - const R = secp256k1.ProjectivePoint.BASE.multiplyAndAddUnsafe( - P, - normalizePrivateKey(s), - mod(-e, secp256k1.CURVE.n) - ); - if (!R || !R.hasEvenY() || R.toAffine().x !== r) return false; + const e = modN(b2num(tag(TAGS.challenge, numTo32b(r), toRawX(P), m))); + const R = PPoint.BASE.multiplyAndAddUnsafe(P, s, modN(-e)); // R = s⋅G - e⋅P + if (!R || !R.hasEvenY() || R.toAffine().x !== r) return false; // -eP == (n-e)P return true; } catch (error) { return false; @@ -312,10 +205,63 @@ function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean { } export const schnorr = { - Signature: SchnorrSignature, // Schnorr's pubkey is just `x` of Point (BIP340) - getPublicKey: (privateKey: PrivKey): Uint8Array => - toRawX(secp256k1.ProjectivePoint.fromPrivateKey(privateKey)), + getPublicKey: (privateKey: PrivKey): Uint8Array => toRawX(PPoint.fromPrivateKey(privateKey)), sign: schnorrSign, verify: schnorrVerify, }; + +const isoMap = htf.isogenyMap( + Fp, + [ + // xNum + [ + '0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7', + '0x7d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581', + '0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262', + '0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c', + ], + // xDen + [ + '0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b', + '0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14', + '0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1 + ], + // yNum + [ + '0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c', + '0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3', + '0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931', + '0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84', + ], + // yDen + [ + '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b', + '0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573', + '0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f', + '0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1 + ], + ].map((i) => i.map((j) => BigInt(j))) as [Fp[], Fp[], Fp[], Fp[]] +); +const mapSWU = mapToCurveSimpleSWU(Fp, { + A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'), + B: BigInt('1771'), + Z: Fp.create(BigInt('-11')), +}); +const { hashToCurve, encodeToCurve } = htf.hashToCurve( + secp256k1.ProjectivePoint, + (scalars: bigint[]) => { + const { x, y } = mapSWU(Fp.create(scalars[0])); + return isoMap(x, y); + }, + { + DST: 'secp256k1_XMD:SHA-256_SSWU_RO_', + encodeDST: 'secp256k1_XMD:SHA-256_SSWU_NU_', + p: Fp.ORDER, + m: 1, + k: 128, + expand: 'xmd', + hash: sha256, + } +); +export { hashToCurve, encodeToCurve }; diff --git a/src/stark.ts b/src/stark.ts index e42a603..271362e 100644 --- a/src/stark.ts +++ b/src/stark.ts @@ -1,14 +1,14 @@ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ import { keccak_256 } from '@noble/hashes/sha3'; import { sha256 } from '@noble/hashes/sha256'; -import { weierstrass, ProjectivePointType } from './abstract/weierstrass.js'; +import { weierstrass, ProjPointType } from './abstract/weierstrass.js'; import * as cutils from './abstract/utils.js'; import { Fp, mod, Field, validateField } from './abstract/modular.js'; import { getHash } from './_shortw_utils.js'; import * as poseidon from './abstract/poseidon.js'; import { utf8ToBytes } from '@noble/hashes/utils'; -type ProjectivePoint = ProjectivePointType; +type ProjectivePoint = ProjPointType; // Stark-friendly elliptic curve // https://docs.starkware.co/starkex/stark-curve.html