BLS, jubjub refactoring

This commit is contained in:
Paul Miller 2023-01-12 19:38:10 +00:00
parent 2e81f31d2e
commit 069452dbe7
No known key found for this signature in database
GPG Key ID: 697079DA6878B89B
3 changed files with 60 additions and 59 deletions

@ -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<Fp, Fp2, Fp6, Fp12> = {
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<Fp, Fp2, Fp6, Fp12> = {
publicKeys: (Hex | PointType<Fp>)[]
) => 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<Fp2, Fp6, Fp12>(
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<Fp2, Fp6, Fp12>(
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<Fp2, Fp6, Fp12>(
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<Fp2, Fp6, Fp12>(
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<typeof htfDefaults> = {}) =>
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<Fp2, Fp6, Fp12>(
},
};
// 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);

@ -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 =

@ -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);