From 83960d445d055eb4eef6079590186c46f3524340 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Thu, 12 Jan 2023 20:18:51 +0000 Subject: [PATCH] Refactor: weierstrass assertValidity and others --- src/abstract/bls.ts | 61 ++++++++----------------------------- src/abstract/utils.ts | 17 ++++++----- src/abstract/weierstrass.ts | 8 +++-- src/jubjub.ts | 3 +- 4 files changed, 30 insertions(+), 59 deletions(-) diff --git a/src/abstract/bls.ts b/src/abstract/bls.ts index 8d52684..76fe16c 100644 --- a/src/abstract/bls.ts +++ b/src/abstract/bls.ts @@ -108,11 +108,7 @@ export function bls( CURVE: CurveType ): CurveFn { // Fields looks pretty specific for curve, so for now we need to pass them with options - const Fp = CURVE.Fp; - const Fr = CURVE.Fr; - const Fp2 = CURVE.Fp2; - const Fp6 = CURVE.Fp6; - const Fp12 = CURVE.Fp12; + const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE; const BLS_X_LEN = ut.bitLen(CURVE.x); const groupLen = 32; // TODO: calculate; hardcoded for now @@ -161,13 +157,14 @@ export function bls( } function millerLoop(ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]): Fp12 { + const { x } = CURVE; const Px = g1[0]; const Py = g1[1]; let f12 = Fp12.ONE; for (let j = 0, i = BLS_X_LEN - 2; i >= 0; i--, j++) { const E = ell[j]; f12 = Fp12.multiplyBy014(f12, E[0], Fp2.mul(E[1], Px), Fp2.mul(E[2], Py)); - if (ut.bitGet(CURVE.x, i)) { + if (ut.bitGet(x, i)) { j += 1; const F = ell[j]; f12 = Fp12.multiplyBy014(f12, F[0], Fp2.mul(F[1], Px), Fp2.mul(F[2], Py)); @@ -177,64 +174,32 @@ export function bls( return Fp12.conjugate(f12); } - const htfDefaults = { ...CURVE.htfDefaults }; - - // TODO: not needed? - function isWithinCurveOrder(num: bigint): boolean { - return 0 < num && num < CURVE.r; - } - const utils = { hexToBytes: ut.hexToBytes, bytesToHex: ut.bytesToHex, mod: mod.mod, - stringToBytes, + stringToBytes: stringToBytes, // TODO: do we need to export it here? - hashToField: (msg: Uint8Array, count: number, options: Partial = {}) => - hashToField(msg, count, { ...CURVE.htfDefaults, ...options }), + hashToField: ( + msg: Uint8Array, + count: number, + options: Partial = {} + ) => hashToField(msg, count, { ...CURVE.htfDefaults, ...options }), expandMessageXMD: (msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H = CURVE.hash) => expandMessageXMD(msg, DST, lenInBytes, H), - - /** - * Creates FIPS 186 B.4.1 compliant private keys without modulo bias. - */ - hashToPrivateKey: (hash: Hex): Uint8Array => { - hash = ut.ensureBytes(hash); - if (hash.length < 40 || hash.length > 1024) - throw new Error('Expected 40-1024 bytes of private key as per FIPS 186'); - // hashToPrivateScalar(hash, CURVE.r) - // NOTE: doesn't add +/-1 - const num = mod.mod(ut.bytesToNumberBE(hash), CURVE.r); - // This should never happen - if (num === 0n || num === 1n) throw new Error('Invalid private key'); - return ut.numberToBytesBE(num, groupLen); - }, - + hashToPrivateKey: (hash: Hex): Uint8Array => Fr.toBytes(ut.hashToPrivateScalar(hash, CURVE.r)), randomBytes: (bytesLength: number = groupLen): Uint8Array => CURVE.randomBytes(bytesLength), randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(utils.randomBytes(groupLen + 8)), - getDSTLabel: () => htfDefaults.DST, + getDSTLabel: () => CURVE.htfDefaults.DST, setDSTLabel(newLabel: string) { // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3.1 if (typeof newLabel !== 'string' || newLabel.length > 2048 || newLabel.length === 0) { throw new TypeError('Invalid DST'); } - htfDefaults.DST = newLabel; + CURVE.htfDefaults.DST = newLabel; }, }; - // TODO: reuse CURVE.G1.utils.normalizePrivateKey() ? - function normalizePrivKey(key: PrivKey): bigint { - let int: bigint; - if (key instanceof Uint8Array && key.length === groupLen) int = ut.bytesToNumberBE(key); - else if (typeof key === 'string' && key.length === 2 * groupLen) int = BigInt(`0x${key}`); - else if (ut.isPositiveInt(key)) int = BigInt(key); - else if (typeof key === 'bigint' && key > 0n) int = key; - else throw new TypeError('Expected valid private key'); - int = mod.mod(int, CURVE.r); - if (!isWithinCurveOrder(int)) throw new Error('Private key must be 0 < key < CURVE.r'); - return int; - } - // Point on G1 curve: (x, y) const G1 = weierstrassPoints({ n: Fr.ORDER, @@ -306,7 +271,7 @@ export function bls( function sign(message: G2Hex, privateKey: PrivKey): Uint8Array | G2 { const msgPoint = normP2Hash(message); msgPoint.assertValidity(); - const sigPoint = msgPoint.multiply(normalizePrivKey(privateKey)); + const sigPoint = msgPoint.multiply(G1.normalizePrivateKey(privateKey)); if (message instanceof G2.Point) return sigPoint; return Signature.encode(sigPoint); } diff --git a/src/abstract/utils.ts b/src/abstract/utils.ts index afc6135..acd4d89 100644 --- a/src/abstract/utils.ts +++ b/src/abstract/utils.ts @@ -149,21 +149,22 @@ export function nLength(n: bigint, nBitLength?: number) { } /** + * FIPS 186 B.4.1-compliant "constant-time" private key generation utility. * Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF * and convert them into private scalar, with the modulo bias being neglible. - * As per FIPS 186 B.4.1. + * Needs at least 40 bytes of input for 32-byte private key. * https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/ - * @param hash hash output from sha512, or a similar function + * @param hash hash output from SHA3 or a similar function * @returns valid private scalar */ -export function hashToPrivateScalar(hash: Hex, CURVE_ORDER: bigint, isLE = false): bigint { +export function hashToPrivateScalar(hash: Hex, groupOrder: bigint, isLE = false): bigint { hash = ensureBytes(hash); - const orderLen = nLength(CURVE_ORDER).nByteLength; - const minLen = orderLen + 8; - if (orderLen < 16 || hash.length < minLen || hash.length > 1024) - throw new Error('Expected valid bytes of private key as per FIPS 186'); + const hashLen = hash.length; + const minLen = nLength(groupOrder).nByteLength + 8; + if (minLen < 24 || hashLen < minLen || hashLen > 1024) + throw new Error(`hashToPrivateScalar: expected ${minLen}-1024 bytes of input, got ${hashLen}`); const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash); - return mod.mod(num, CURVE_ORDER - _1n) + _1n; + return mod.mod(num, groupOrder - _1n) + _1n; } export function equalBytes(b1: Uint8Array, b2: Uint8Array) { diff --git a/src/abstract/weierstrass.ts b/src/abstract/weierstrass.ts index b21ac7e..2e291b7 100644 --- a/src/abstract/weierstrass.ts +++ b/src/abstract/weierstrass.ts @@ -601,15 +601,19 @@ export function weierstrassPoints(opts: CurvePointsType) { // Zero is valid point too! if (this.equals(Point.ZERO)) { if (CURVE.allowInfinityPoint) return; - throw new Error('Point is infinity'); + 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; + // 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); - if (!Fp.equals(left, right)) throw new Error(msg); + // 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? if (!this.isTorsionFree()) throw new Error('Point must be of prime-order subgroup'); } diff --git a/src/jubjub.ts b/src/jubjub.ts index 5e7dc2a..8fb66f8 100644 --- a/src/jubjub.ts +++ b/src/jubjub.ts @@ -8,6 +8,7 @@ import { Fp } from './abstract/modular.js'; /** * jubjub Twisted Edwards curve. * https://neuromancer.sk/std/other/JubJub + * jubjub does not use EdDSA, so `hash`/sha512 params are passed because interface expects them. */ export const jubjub = twistedEdwards({ @@ -15,9 +16,9 @@ export const jubjub = twistedEdwards({ a: BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000'), d: BigInt('0x2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1'), // Finite field 𝔽p over which we'll do calculations + // Same value as bls12-381 Fr (not Fp) Fp: Fp(BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001')), // Subgroup order: how many points curve has - // 2n ** 252n + 27742317777372353535851937790883648493n; n: BigInt('0xe7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7'), // Cofactor h: BigInt(8),