From 069452dbe70b1a5cb0f2fb584fae479d58762ffa Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Thu, 12 Jan 2023 19:38:10 +0000 Subject: [PATCH] BLS, jubjub refactoring --- src/abstract/bls.ts | 93 ++++++++++++++++++++++----------------------- src/bls12-381.ts | 20 ++++++---- src/jubjub.ts | 6 +-- 3 files changed, 60 insertions(+), 59 deletions(-) diff --git a/src/abstract/bls.ts b/src/abstract/bls.ts index 13107ac..8d52684 100644 --- a/src/abstract/bls.ts +++ b/src/abstract/bls.ts @@ -1,13 +1,26 @@ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ -// Barreto-Lynn-Scott Curves. A family of pairing friendly curves, with embedding degree = 12 or 24 -// NOTE: only 12 supported for now -// Constructed from pair of weierstrass curves, based pairing logic +/** + * BLS (Barreto-Lynn-Scott) family of pairing-friendly curves. + * Implements BLS (Boneh-Lynn-Shacham) signatures. + * Consists of two curves: G1 and G2: + * - G1 is a subgroup of (x, y) E(Fq) over y² = x³ + 4. + * - G2 is a subgroup of ((x₁, x₂+i), (y₁, y₂+i)) E(Fq²) over y² = x³ + 4(1 + i) where i is √-1 + * - Gt, created by bilinear (ate) pairing e(G1, G2), consists of p-th roots of unity in + * Fq^k where k is embedding degree. Only degree 12 is currently supported, 24 is not. + * Pairing is used to aggregate and verify signatures. + * We are using Fp for private keys (shorter) and Fp₂ for signatures (longer). + * Some projects may prefer to swap this relation, it is not supported for now. + */ import * as mod from './modular.js'; -import { ensureBytes, numberToBytesBE, bytesToNumberBE, bitLen, bitGet } from './utils.js'; -import * as utils from './utils.js'; -// Types -import { hexToBytes, bytesToHex, Hex, PrivKey } from './utils.js'; -import { htfOpts, stringToBytes, hash_to_field, expand_message_xmd } from './hash-to-curve.js'; +import * as ut from './utils.js'; +// Types require separate import +import { Hex, PrivKey } from './utils.js'; +import { + htfOpts, + stringToBytes, + hash_to_field as hashToField, + expand_message_xmd as expandMessageXMD, +} from './hash-to-curve.js'; import { CurvePointsType, PointType, CurvePointsRes, weierstrassPoints } from './weierstrass.js'; type Fp = bigint; // Can be different field? @@ -39,7 +52,7 @@ export type CurveType = { finalExponentiate(num: Fp12): Fp12; }; htfDefaults: htfOpts; - hash: utils.CHash; // Because we need outputLen for DRBG + hash: ut.CHash; // Because we need outputLen for DRBG randomBytes: (bytesLength?: number) => Uint8Array; }; @@ -80,11 +93,11 @@ export type CurveFn = { publicKeys: (Hex | PointType)[] ) => boolean; utils: { - bytesToHex: typeof utils.bytesToHex; - hexToBytes: typeof utils.hexToBytes; + bytesToHex: typeof ut.bytesToHex; + hexToBytes: typeof ut.hexToBytes; stringToBytes: typeof stringToBytes; - hashToField: typeof hash_to_field; - expandMessageXMD: typeof expand_message_xmd; + hashToField: typeof hashToField; + expandMessageXMD: typeof expandMessageXMD; mod: typeof mod.mod; getDSTLabel: () => string; setDSTLabel(newLabel: string): void; @@ -100,7 +113,8 @@ export function bls( const Fp2 = CURVE.Fp2; const Fp6 = CURVE.Fp6; const Fp12 = CURVE.Fp12; - const BLS_X_LEN = bitLen(CURVE.x); + const BLS_X_LEN = ut.bitLen(CURVE.x); + const groupLen = 32; // TODO: calculate; hardcoded for now // Pre-compute coefficients for sparse multiplication // Point addition and point double calculations is reused for coefficients @@ -125,7 +139,7 @@ export function bls( Rx = Fp2.div(Fp2.mul(Fp2.mul(Fp2.sub(t0, t3), Rx), Ry), 2n); // ((T0 - T3) * Rx * Ry) / 2 Ry = Fp2.sub(Fp2.square(Fp2.div(Fp2.add(t0, t3), 2n)), Fp2.mul(Fp2.square(t2), 3n)); // ((T0 + T3) / 2)² - 3 * T2² Rz = Fp2.mul(t0, t4); // T0 * T4 - if (bitGet(CURVE.x, i)) { + if (ut.bitGet(CURVE.x, i)) { // Addition let t0 = Fp2.sub(Ry, Fp2.mul(Qy, Rz)); // Ry - Qy * Rz let t1 = Fp2.sub(Rx, Fp2.mul(Qx, Rz)); // Rx - Qx * Rz @@ -153,7 +167,7 @@ export function bls( 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 (bitGet(CURVE.x, i)) { + if (ut.bitGet(CURVE.x, i)) { j += 1; const F = ell[j]; f12 = Fp12.multiplyBy014(f12, F[0], Fp2.mul(F[1], Px), Fp2.mul(F[2], Py)); @@ -163,59 +177,41 @@ export function bls( return Fp12.conjugate(f12); } - // bls12-381 is a construction of two curves: - // 1. Fp: (x, y) - // 2. Fp₂: ((x₁, x₂+i), (y₁, y₂+i)) - (complex numbers) - // - // Bilinear Pairing (ate pairing) is used to combine both elements into a paired one: - // Fp₁₂ = e(Fp, Fp2) - // where Fp₁₂ = 12-degree polynomial - // Pairing is used to verify signatures. - // - // We are using Fp for private keys (shorter) and Fp2 for signatures (longer). - // Some projects may prefer to swap this relation, it is not supported for now. - const htfDefaults = { ...CURVE.htfDefaults }; + // TODO: not needed? function isWithinCurveOrder(num: bigint): boolean { return 0 < num && num < CURVE.r; } const utils = { - hexToBytes: hexToBytes, - bytesToHex: bytesToHex, + hexToBytes: ut.hexToBytes, + bytesToHex: ut.bytesToHex, mod: mod.mod, stringToBytes, // TODO: do we need to export it here? hashToField: (msg: Uint8Array, count: number, options: Partial = {}) => - hash_to_field(msg, count, { ...CURVE.htfDefaults, ...options }), + hashToField(msg, count, { ...CURVE.htfDefaults, ...options }), expandMessageXMD: (msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H = CURVE.hash) => - expand_message_xmd(msg, DST, lenInBytes, H), + expandMessageXMD(msg, DST, lenInBytes, H), /** - * Can take 40 or more bytes of uniform input e.g. from CSPRNG or KDF - * and convert them into private key, with the modulo bias being negligible. - * As per FIPS 186 B.1.1. - * 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 - * @returns valid private key + * Creates FIPS 186 B.4.1 compliant private keys without modulo bias. */ hashToPrivateKey: (hash: Hex): Uint8Array => { - hash = ensureBytes(hash); + 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(bytesToNumberBE(hash), CURVE.r); + 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 numberToBytesBE(num, 32); + return ut.numberToBytesBE(num, groupLen); }, - randomBytes: (bytesLength: number = 32): Uint8Array => CURVE.randomBytes(bytesLength), - // NIST SP 800-56A rev 3, section 5.6.1.2.2 - // https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/ - randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(utils.randomBytes(40)), + randomBytes: (bytesLength: number = groupLen): Uint8Array => CURVE.randomBytes(bytesLength), + randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(utils.randomBytes(groupLen + 8)), getDSTLabel: () => htfDefaults.DST, setDSTLabel(newLabel: string) { // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3.1 @@ -226,11 +222,12 @@ export function bls( }, }; + // TODO: reuse CURVE.G1.utils.normalizePrivateKey() ? function normalizePrivKey(key: PrivKey): bigint { let int: bigint; - if (key instanceof Uint8Array && key.length === 32) int = bytesToNumberBE(key); - else if (typeof key === 'string' && key.length === 64) int = BigInt(`0x${key}`); - else if (typeof key === 'number' && key > 0 && Number.isSafeInteger(key)) int = BigInt(key); + 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); diff --git a/src/bls12-381.ts b/src/bls12-381.ts index cd34853..8e51317 100644 --- a/src/bls12-381.ts +++ b/src/bls12-381.ts @@ -1,4 +1,16 @@ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ + +// The pairing-friendly Barreto-Lynn-Scott elliptic curve construction allows to: +// - Construct zk-SNARKs at the 128-bit security +// - Use threshold signatures, which allows a user to sign lots of messages with one signature and verify them swiftly in a batch, using Boneh-Lynn-Shacham signature scheme. +// Differences from @noble/bls12-381 1.4: +// - PointG1 -> G1.Point +// - PointG2 -> G2.Point +// - PointG2.fromSignature -> Signature.decode +// - PointG2.toSignature -> Signature.encode +// - Fixed Fp2 ORDER +// - Points now have only two coordinates + import { sha256 } from '@noble/hashes/sha256'; import { randomBytes } from '@noble/hashes/utils'; import { bls, CurveFn } from './abstract/bls.js'; @@ -23,14 +35,6 @@ import { } from './abstract/weierstrass.js'; import { isogenyMap } from './abstract/hash-to-curve.js'; -// Differences from bls12-381: -// - PointG1 -> G1.Point -// - PointG2 -> G2.Point -// - PointG2.fromSignature -> Signature.decode -// - PointG2.toSignature -> Signature.encode -// - Fixed Fp2 ORDER -// Points now have only two coordinates - // CURVE FIELDS // Finite field over p. const Fp = diff --git a/src/jubjub.ts b/src/jubjub.ts index f98dac6..5e7dc2a 100644 --- a/src/jubjub.ts +++ b/src/jubjub.ts @@ -1,5 +1,5 @@ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ -import { sha256 } from '@noble/hashes/sha256'; +import { sha512 } from '@noble/hashes/sha512'; import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils'; import { twistedEdwards } from './abstract/edwards.js'; import { blake2s } from '@noble/hashes/blake2s'; @@ -16,7 +16,7 @@ export const jubjub = twistedEdwards({ d: BigInt('0x2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1'), // Finite field 𝔽p over which we'll do calculations Fp: Fp(BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001')), - // Subgroup order: how many points ed25519 has + // Subgroup order: how many points curve has // 2n ** 252n + 27742317777372353535851937790883648493n; n: BigInt('0xe7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7'), // Cofactor @@ -24,7 +24,7 @@ export const jubjub = twistedEdwards({ // Base point (x, y) aka generator point Gx: BigInt('0x11dafe5d23e1218086a365b99fbf3d3be72f6afd7d1f72623e6b071492d1122b'), Gy: BigInt('0x1d523cf1ddab1a1793132e78c866c0c33e26ba5cc220fed7cc3f870e59d292aa'), - hash: sha256, + hash: sha512, randomBytes, } as const);