Refactor: weierstrass assertValidity and others

This commit is contained in:
Paul Miller 2023-01-12 20:18:51 +00:00
parent 23cc2aa5d1
commit 83960d445d
No known key found for this signature in database
GPG Key ID: 697079DA6878B89B
4 changed files with 30 additions and 59 deletions

@ -108,11 +108,7 @@ export function bls<Fp2, Fp6, Fp12>(
CURVE: CurveType<Fp, Fp2, Fp6, Fp12> CURVE: CurveType<Fp, Fp2, Fp6, Fp12>
): CurveFn<Fp, Fp2, Fp6, Fp12> { ): CurveFn<Fp, Fp2, Fp6, Fp12> {
// Fields looks pretty specific for curve, so for now we need to pass them with options // Fields looks pretty specific for curve, so for now we need to pass them with options
const Fp = CURVE.Fp; const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE;
const Fr = CURVE.Fr;
const Fp2 = CURVE.Fp2;
const Fp6 = CURVE.Fp6;
const Fp12 = CURVE.Fp12;
const BLS_X_LEN = ut.bitLen(CURVE.x); const BLS_X_LEN = ut.bitLen(CURVE.x);
const groupLen = 32; // TODO: calculate; hardcoded for now const groupLen = 32; // TODO: calculate; hardcoded for now
@ -161,13 +157,14 @@ export function bls<Fp2, Fp6, Fp12>(
} }
function millerLoop(ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]): Fp12 { function millerLoop(ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]): Fp12 {
const { x } = CURVE;
const Px = g1[0]; const Px = g1[0];
const Py = g1[1]; const Py = g1[1];
let f12 = Fp12.ONE; let f12 = Fp12.ONE;
for (let j = 0, i = BLS_X_LEN - 2; i >= 0; i--, j++) { for (let j = 0, i = BLS_X_LEN - 2; i >= 0; i--, j++) {
const E = ell[j]; const E = ell[j];
f12 = Fp12.multiplyBy014(f12, E[0], Fp2.mul(E[1], Px), Fp2.mul(E[2], Py)); 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; j += 1;
const F = ell[j]; const F = ell[j];
f12 = Fp12.multiplyBy014(f12, F[0], Fp2.mul(F[1], Px), Fp2.mul(F[2], Py)); f12 = Fp12.multiplyBy014(f12, F[0], Fp2.mul(F[1], Px), Fp2.mul(F[2], Py));
@ -177,64 +174,32 @@ export function bls<Fp2, Fp6, Fp12>(
return Fp12.conjugate(f12); return Fp12.conjugate(f12);
} }
const htfDefaults = { ...CURVE.htfDefaults };
// TODO: not needed?
function isWithinCurveOrder(num: bigint): boolean {
return 0 < num && num < CURVE.r;
}
const utils = { const utils = {
hexToBytes: ut.hexToBytes, hexToBytes: ut.hexToBytes,
bytesToHex: ut.bytesToHex, bytesToHex: ut.bytesToHex,
mod: mod.mod, mod: mod.mod,
stringToBytes, stringToBytes: stringToBytes,
// TODO: do we need to export it here? // TODO: do we need to export it here?
hashToField: (msg: Uint8Array, count: number, options: Partial<typeof htfDefaults> = {}) => hashToField: (
hashToField(msg, count, { ...CURVE.htfDefaults, ...options }), msg: Uint8Array,
count: number,
options: Partial<typeof CURVE.htfDefaults> = {}
) => hashToField(msg, count, { ...CURVE.htfDefaults, ...options }),
expandMessageXMD: (msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H = CURVE.hash) => expandMessageXMD: (msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H = CURVE.hash) =>
expandMessageXMD(msg, DST, lenInBytes, H), expandMessageXMD(msg, DST, lenInBytes, H),
hashToPrivateKey: (hash: Hex): Uint8Array => Fr.toBytes(ut.hashToPrivateScalar(hash, CURVE.r)),
/**
* 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);
},
randomBytes: (bytesLength: number = groupLen): Uint8Array => CURVE.randomBytes(bytesLength), randomBytes: (bytesLength: number = groupLen): Uint8Array => CURVE.randomBytes(bytesLength),
randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(utils.randomBytes(groupLen + 8)), randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(utils.randomBytes(groupLen + 8)),
getDSTLabel: () => htfDefaults.DST, getDSTLabel: () => CURVE.htfDefaults.DST,
setDSTLabel(newLabel: string) { setDSTLabel(newLabel: string) {
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3.1 // 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) { if (typeof newLabel !== 'string' || newLabel.length > 2048 || newLabel.length === 0) {
throw new TypeError('Invalid DST'); 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) // Point on G1 curve: (x, y)
const G1 = weierstrassPoints({ const G1 = weierstrassPoints({
n: Fr.ORDER, n: Fr.ORDER,
@ -306,7 +271,7 @@ export function bls<Fp2, Fp6, Fp12>(
function sign(message: G2Hex, privateKey: PrivKey): Uint8Array | G2 { function sign(message: G2Hex, privateKey: PrivKey): Uint8Array | G2 {
const msgPoint = normP2Hash(message); const msgPoint = normP2Hash(message);
msgPoint.assertValidity(); msgPoint.assertValidity();
const sigPoint = msgPoint.multiply(normalizePrivKey(privateKey)); const sigPoint = msgPoint.multiply(G1.normalizePrivateKey(privateKey));
if (message instanceof G2.Point) return sigPoint; if (message instanceof G2.Point) return sigPoint;
return Signature.encode(sigPoint); return Signature.encode(sigPoint);
} }

@ -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 * 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. * 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/ * 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 * @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); hash = ensureBytes(hash);
const orderLen = nLength(CURVE_ORDER).nByteLength; const hashLen = hash.length;
const minLen = orderLen + 8; const minLen = nLength(groupOrder).nByteLength + 8;
if (orderLen < 16 || hash.length < minLen || hash.length > 1024) if (minLen < 24 || hashLen < minLen || hashLen > 1024)
throw new Error('Expected valid bytes of private key as per FIPS 186'); throw new Error(`hashToPrivateScalar: expected ${minLen}-1024 bytes of input, got ${hashLen}`);
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash); 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) { export function equalBytes(b1: Uint8Array, b2: Uint8Array) {

@ -601,15 +601,19 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
// Zero is valid point too! // Zero is valid point too!
if (this.equals(Point.ZERO)) { if (this.equals(Point.ZERO)) {
if (CURVE.allowInfinityPoint) return; 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` // Some 3rd-party test vectors require different wording between here & `fromCompressedHex`
const msg = 'Point is not on elliptic curve'; const msg = 'Point is not on elliptic curve';
const { x, y } = this; const { x, y } = this;
// Check if x, y are valid field elements
if (!Fp.isValid(x) || !Fp.isValid(y)) throw new Error(msg); if (!Fp.isValid(x) || !Fp.isValid(y)) throw new Error(msg);
const left = Fp.square(y); const left = Fp.square(y);
const right = weierstrassEquation(x); 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? // TODO: flag to disable this?
if (!this.isTorsionFree()) throw new Error('Point must be of prime-order subgroup'); if (!this.isTorsionFree()) throw new Error('Point must be of prime-order subgroup');
} }

@ -8,6 +8,7 @@ import { Fp } from './abstract/modular.js';
/** /**
* jubjub Twisted Edwards curve. * jubjub Twisted Edwards curve.
* https://neuromancer.sk/std/other/JubJub * 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({ export const jubjub = twistedEdwards({
@ -15,9 +16,9 @@ export const jubjub = twistedEdwards({
a: BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000'), a: BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000'),
d: BigInt('0x2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1'), d: BigInt('0x2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1'),
// Finite field 𝔽p over which we'll do calculations // Finite field 𝔽p over which we'll do calculations
// Same value as bls12-381 Fr (not Fp)
Fp: Fp(BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001')), Fp: Fp(BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001')),
// Subgroup order: how many points curve has // Subgroup order: how many points curve has
// 2n ** 252n + 27742317777372353535851937790883648493n;
n: BigInt('0xe7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7'), n: BigInt('0xe7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7'),
// Cofactor // Cofactor
h: BigInt(8), h: BigInt(8),