diff --git a/README.md b/README.md index b33a537..a3356dd 100644 --- a/README.md +++ b/README.md @@ -201,8 +201,6 @@ export type CurveFn = { ExtendedPoint: ExtendedPointConstructor; Signature: SignatureConstructor; utils: { - mod: (a: bigint, b?: bigint) => bigint; - invert: (number: bigint, modulo?: bigint) => bigint; randomPrivateKey: () => Uint8Array; getExtendedPublicKey: (key: PrivKey) => { head: Uint8Array; @@ -306,6 +304,7 @@ export type CurveFn = { getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array; getSharedSecret: (privateA: PrivKey, publicB: PubKey, isCompressed?: boolean) => Uint8Array; sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => SignatureType; + signUnhashed: (msg: Uint8Array, privKey: PrivKey, opts?: SignOpts) => SignatureType; verify: ( signature: Hex | SignatureType, msgHash: Hex, @@ -316,8 +315,6 @@ export type CurveFn = { ProjectivePoint: ProjectivePointConstructor; Signature: SignatureConstructor; utils: { - mod: (a: bigint) => bigint; - invert: (number: bigint) => bigint; isValidPrivateKey(privateKey: PrivKey): boolean; hashToPrivateKey: (hash: Hex) => Uint8Array; randomPrivateKey: () => Uint8Array; diff --git a/src/abstract/bls.ts b/src/abstract/bls.ts index 76fe16c..e20c0d2 100644 --- a/src/abstract/bls.ts +++ b/src/abstract/bls.ts @@ -93,12 +93,9 @@ export type CurveFn = { publicKeys: (Hex | PointType)[] ) => boolean; utils: { - bytesToHex: typeof ut.bytesToHex; - hexToBytes: typeof ut.hexToBytes; stringToBytes: typeof stringToBytes; hashToField: typeof hashToField; expandMessageXMD: typeof expandMessageXMD; - mod: typeof mod.mod; getDSTLabel: () => string; setDSTLabel(newLabel: string): void; }; @@ -177,7 +174,6 @@ export function bls( const utils = { hexToBytes: ut.hexToBytes, bytesToHex: ut.bytesToHex, - mod: mod.mod, stringToBytes: stringToBytes, // TODO: do we need to export it here? hashToField: ( diff --git a/src/abstract/edwards.ts b/src/abstract/edwards.ts index d36a3de..a135f17 100644 --- a/src/abstract/edwards.ts +++ b/src/abstract/edwards.ts @@ -130,8 +130,6 @@ export type CurveFn = { ExtendedPoint: ExtendedPointConstructor; Signature: SignatureConstructor; utils: { - mod: (a: bigint) => bigint; - invert: (number: bigint) => bigint; randomPrivateKey: () => Uint8Array; getExtendedPublicKey: (key: PrivKey) => { head: Uint8Array; @@ -146,7 +144,7 @@ export type CurveFn = { // NOTE: it is not generic twisted curve for now, but ed25519/ed448 generic implementation export function twistedEdwards(curveDef: CurveType): CurveFn { const CURVE = validateOpts(curveDef) as ReturnType; - const Fp = CURVE.Fp as mod.Field; + const Fp = CURVE.Fp; const CURVE_ORDER = CURVE.n; const maxGroupElement = _2n ** BigInt(CURVE.nByteLength * 8); @@ -662,9 +660,6 @@ export function twistedEdwards(curveDef: CurveType): CurveFn { const utils = { getExtendedPublicKey, - mod: modP, - invert: Fp.invert, - /** * Not needed for ed25519 private keys. Needed if you use scalars directly (rare). */ diff --git a/src/abstract/weierstrass.ts b/src/abstract/weierstrass.ts index 2e291b7..bdda6f1 100644 --- a/src/abstract/weierstrass.ts +++ b/src/abstract/weierstrass.ts @@ -608,13 +608,9 @@ export function weierstrassPoints(opts: CurvePointsType) { const { x, y } = this; // Check if x, y are valid field elements if (!Fp.isValid(x) || !Fp.isValid(y)) throw new Error(msg); - const left = Fp.square(y); - const right = weierstrassEquation(x); - // We subtract instead of comparing: it's safer - // (y²) - (x³ + ax + b) == 0 - if (!Fp.isZero(Fp.sub(left, right))) throw new Error(msg); - // if (!Fp.equals(left, right)) - // TODO: flag to disable this? + const left = Fp.square(y); // y² + const right = weierstrassEquation(x); // x³ + ax + b + if (!Fp.equals(left, right)) throw new Error(msg); if (!this.isTorsionFree()) throw new Error('Point must be of prime-order subgroup'); } @@ -771,8 +767,6 @@ export type CurveFn = { ProjectivePoint: ProjectiveConstructor; Signature: SignatureConstructor; utils: { - mod: (a: bigint, b?: bigint) => bigint; - invert: (number: bigint, modulo?: bigint) => bigint; _bigintToBytes: (num: bigint) => Uint8Array; _bigintToString: (num: bigint) => string; _normalizePrivateKey: (key: PrivKey) => bigint; @@ -831,9 +825,7 @@ class HmacDrbg { } return ut.concatBytes(...out); } - // There is no need in clean() method - // It's useless, there are no guarantees with JS GC - // whether bigints are removed even if you clean Uint8Arrays. + // There are no guarantees with JS GC whether bigints are removed even if you clean Uint8Arrays. } export function weierstrass(curveDef: CurveType): CurveFn { @@ -1049,8 +1041,6 @@ export function weierstrass(curveDef: CurveType): CurveFn { } const utils = { - mod: (n: bigint, modulo = Fp.ORDER) => mod.mod(n, modulo), - invert: Fp.invert, isValidPrivateKey(privateKey: PrivKey) { try { normalizePrivateKey(privateKey); diff --git a/src/bls12-381.ts b/src/bls12-381.ts index 8e51317..bdc4e09 100644 --- a/src/bls12-381.ts +++ b/src/bls12-381.ts @@ -135,6 +135,7 @@ const Fp2: mod.Field & Fp2Utils = { return { c0: Fp.mul(factor, Fp.create(a)), c1: Fp.mul(factor, Fp.create(-b)) }; }, sqrt: (num) => { + if (Fp2.equals(num, Fp2.ZERO)) return Fp2.ZERO; // Algo doesn't handles this case // TODO: Optimize this line. It's extremely slow. // Speeding this up would boost aggregateSignatures. // https://eprint.iacr.org/2012/685.pdf applicable? diff --git a/src/ed25519.ts b/src/ed25519.ts index 19df0ea..26cd6b2 100644 --- a/src/ed25519.ts +++ b/src/ed25519.ts @@ -260,7 +260,7 @@ const invertSqrt = (number: bigint) => uvRatio(_1n, number); const MAX_255B = BigInt('0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'); const bytes255ToNumberLE = (bytes: Uint8Array) => - ed25519.utils.mod(bytesToNumberLE(bytes) & MAX_255B); + ed25519.CURVE.Fp.create(bytesToNumberLE(bytes) & MAX_255B); type ExtendedPoint = ExtendedPointType; @@ -269,7 +269,7 @@ type ExtendedPoint = ExtendedPointType; function calcElligatorRistrettoMap(r0: bigint): ExtendedPoint { const { d } = ed25519.CURVE; const P = ed25519.CURVE.Fp.ORDER; - const { mod } = ed25519.utils; + const mod = ed25519.CURVE.Fp.create; const r = mod(SQRT_M1 * r0 * r0); // 1 const Ns = mod((r + _1n) * ONE_MINUS_D_SQ); // 2 let c = BigInt(-1); // 3 @@ -327,7 +327,7 @@ export class RistrettoPoint { hex = ensureBytes(hex, 32); const { a, d } = ed25519.CURVE; const P = ed25519.CURVE.Fp.ORDER; - const { mod } = ed25519.utils; + const mod = ed25519.CURVE.Fp.create; const emsg = 'RistrettoPoint.fromHex: the hex is not valid encoding of RistrettoPoint'; const s = bytes255ToNumberLE(hex); // 1. Check that s_bytes is the canonical encoding of a field element, or else abort. @@ -357,7 +357,7 @@ export class RistrettoPoint { toRawBytes(): Uint8Array { let { x, y, z, t } = this.ep; const P = ed25519.CURVE.Fp.ORDER; - const { mod } = ed25519.utils; + const mod = ed25519.CURVE.Fp.create; const u1 = mod(mod(z + y) * mod(z - y)); // 1 const u2 = mod(x * y); // 2 // Square root always exists @@ -395,7 +395,7 @@ export class RistrettoPoint { assertRstPoint(other); const a = this.ep; const b = other.ep; - const { mod } = ed25519.utils; + const mod = ed25519.CURVE.Fp.create; // (x1 * y2 == y1 * x2) | (y1 * y2 == x1 * x2) const one = mod(a.x * b.y) === mod(a.y * b.x); const two = mod(a.y * b.y) === mod(a.x * b.x); diff --git a/src/secp256k1.ts b/src/secp256k1.ts index 383ba12..7edeb8f 100644 --- a/src/secp256k1.ts +++ b/src/secp256k1.ts @@ -127,7 +127,7 @@ export const secp256k1 = createCurve( const b1 = -_1n * BigInt('0xe4437ed6010e88286f547fa90abfe4c3'); const a2 = BigInt('0x114ca50f7a8e2f3f657c1108d9d44cfd8'); const b2 = a1; - const POW_2_128 = BigInt('0x100000000000000000000000000000000'); + const POW_2_128 = BigInt('0x100000000000000000000000000000000'); // (2n**128n).toString(16) const c1 = divNearest(b2 * k, n); const c2 = divNearest(-b1 * k, n); @@ -173,20 +173,17 @@ function normalizePublicKey(publicKey: Hex | PointType): PointType { const { y } = ed25519.Point.BASE; - const u = ed25519.utils.mod((y + 1n) * ed25519.utils.invert(1n - y, ed25519.CURVE.P)); + const { Fp } = ed25519.CURVE; + const u = Fp.create((y + 1n) * Fp.invert(1n - y)); deepStrictEqual(hex(numberToBytesLE(u, 32)), x25519.Gu); }); diff --git a/test/ed448.test.js b/test/ed448.test.js index c19c823..b9c5218 100644 --- a/test/ed448.test.js +++ b/test/ed448.test.js @@ -651,9 +651,10 @@ for (let i = 0; i < VECTORS_RFC8032_PH.length; i++) { should('X448 base point', () => { const { x, y } = ed448.Point.BASE; - const { P } = ed448.CURVE; - const invX = ed448.utils.invert(x * x, P); // x² - const u = ed448.utils.mod(y * y * invX, P); // (y²/x²) + const { Fp } = ed448.CURVE; + // const invX = Fp.invert(x * x); // x² + const u = Fp.div(Fp.create(y * y), Fp.create(x * x)); // (y²/x²) + // const u = Fp.create(y * y * invX); deepStrictEqual(hex(numberToBytesLE(u, 56)), x448.Gu); }); diff --git a/test/secp256k1.test.js b/test/secp256k1.test.js index 13d38fd..7427399 100644 --- a/test/secp256k1.test.js +++ b/test/secp256k1.test.js @@ -1,5 +1,6 @@ import * as fc from 'fast-check'; import { secp256k1, schnorr } from '../lib/esm/secp256k1.js'; +import { Fp } from '../lib/esm/abstract/modular.js'; import { readFileSync } from 'fs'; import { default as ecdsa } from './vectors/ecdsa.json' assert { type: 'json' }; import { default as ecdh } from './vectors/ecdh.json' assert { type: 'json' }; @@ -16,7 +17,6 @@ const privatesTxt = readFileSync('./test/vectors/privates-2.txt', 'utf-8'); const schCsv = readFileSync('./test/vectors/schnorr.csv', 'utf-8'); const FC_BIGINT = fc.bigInt(1n + 1n, secp.CURVE.n - 1n); -const P = secp.CURVE.Fp.ORDER; // prettier-ignore const INVALID_ITEMS = ['deadbeef', Math.pow(2, 53), [1], 'xyzxyzxyxyzxyzxyxyzxyzxyxyzxyzxyxyzxyzxyxyzxyzxyxyzxyzxyxyzxyzxy', secp.CURVE.n + 2n]; @@ -50,9 +50,9 @@ should('secp256k1.getPublicKey()', () => { } }); should('secp256k1.getPublicKey() rejects invalid keys', () => { - // for (const item of INVALID_ITEMS) { - // throws(() => secp.getPublicKey(item)); - // } + for (const item of INVALID_ITEMS) { + throws(() => secp.getPublicKey(item)); + } }); should('secp256k1.precompute', () => { secp.utils.precompute(4); @@ -434,17 +434,26 @@ should('secp256k1.utils.isValidPrivateKey()', () => { deepStrictEqual(secp.utils.isValidPrivateKey(d), expected); } }); +should('have proper curve equation in assertValidity()', () => { + throws(() => { + const { Fp } = secp.CURVE; + let point = new secp.Point(Fp.create(-2n), Fp.create(-1n)); + point.assertValidity(); + }); +}); + +const Fn = Fp(secp.CURVE.n); const normal = secp.utils._normalizePrivateKey; const tweakUtils = { privateAdd: (privateKey, tweak) => { const p = normal(privateKey); const t = normal(tweak); - return secp.utils._bigintToBytes(secp.utils.mod(p + t, secp.CURVE.n)); + return secp.utils._bigintToBytes(Fn.create(p + t)); }, privateNegate: (privateKey) => { const p = normal(privateKey); - return secp.utils._bigintToBytes(secp.CURVE.n - p); + return secp.utils._bigintToBytes(Fn.negate(p)); }, pointAddScalar: (p, tweak, isCompressed) => {