forked from tornado-packages/noble-curves
BLS, jubjub refactoring
This commit is contained in:
parent
2e81f31d2e
commit
069452dbe7
@ -1,13 +1,26 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
/*! 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
|
* BLS (Barreto-Lynn-Scott) family of pairing-friendly curves.
|
||||||
// Constructed from pair of weierstrass curves, based pairing logic
|
* 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 * as mod from './modular.js';
|
||||||
import { ensureBytes, numberToBytesBE, bytesToNumberBE, bitLen, bitGet } from './utils.js';
|
import * as ut from './utils.js';
|
||||||
import * as utils from './utils.js';
|
// Types require separate import
|
||||||
// Types
|
import { Hex, PrivKey } from './utils.js';
|
||||||
import { hexToBytes, bytesToHex, Hex, PrivKey } from './utils.js';
|
import {
|
||||||
import { htfOpts, stringToBytes, hash_to_field, expand_message_xmd } from './hash-to-curve.js';
|
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';
|
import { CurvePointsType, PointType, CurvePointsRes, weierstrassPoints } from './weierstrass.js';
|
||||||
|
|
||||||
type Fp = bigint; // Can be different field?
|
type Fp = bigint; // Can be different field?
|
||||||
@ -39,7 +52,7 @@ export type CurveType<Fp, Fp2, Fp6, Fp12> = {
|
|||||||
finalExponentiate(num: Fp12): Fp12;
|
finalExponentiate(num: Fp12): Fp12;
|
||||||
};
|
};
|
||||||
htfDefaults: htfOpts;
|
htfDefaults: htfOpts;
|
||||||
hash: utils.CHash; // Because we need outputLen for DRBG
|
hash: ut.CHash; // Because we need outputLen for DRBG
|
||||||
randomBytes: (bytesLength?: number) => Uint8Array;
|
randomBytes: (bytesLength?: number) => Uint8Array;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -80,11 +93,11 @@ export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
|
|||||||
publicKeys: (Hex | PointType<Fp>)[]
|
publicKeys: (Hex | PointType<Fp>)[]
|
||||||
) => boolean;
|
) => boolean;
|
||||||
utils: {
|
utils: {
|
||||||
bytesToHex: typeof utils.bytesToHex;
|
bytesToHex: typeof ut.bytesToHex;
|
||||||
hexToBytes: typeof utils.hexToBytes;
|
hexToBytes: typeof ut.hexToBytes;
|
||||||
stringToBytes: typeof stringToBytes;
|
stringToBytes: typeof stringToBytes;
|
||||||
hashToField: typeof hash_to_field;
|
hashToField: typeof hashToField;
|
||||||
expandMessageXMD: typeof expand_message_xmd;
|
expandMessageXMD: typeof expandMessageXMD;
|
||||||
mod: typeof mod.mod;
|
mod: typeof mod.mod;
|
||||||
getDSTLabel: () => string;
|
getDSTLabel: () => string;
|
||||||
setDSTLabel(newLabel: string): void;
|
setDSTLabel(newLabel: string): void;
|
||||||
@ -100,7 +113,8 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
const Fp2 = CURVE.Fp2;
|
const Fp2 = CURVE.Fp2;
|
||||||
const Fp6 = CURVE.Fp6;
|
const Fp6 = CURVE.Fp6;
|
||||||
const Fp12 = CURVE.Fp12;
|
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
|
// Pre-compute coefficients for sparse multiplication
|
||||||
// Point addition and point double calculations is reused for coefficients
|
// 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
|
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²
|
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
|
Rz = Fp2.mul(t0, t4); // T0 * T4
|
||||||
if (bitGet(CURVE.x, i)) {
|
if (ut.bitGet(CURVE.x, i)) {
|
||||||
// Addition
|
// Addition
|
||||||
let t0 = Fp2.sub(Ry, Fp2.mul(Qy, Rz)); // Ry - Qy * Rz
|
let t0 = Fp2.sub(Ry, Fp2.mul(Qy, Rz)); // Ry - Qy * Rz
|
||||||
let t1 = Fp2.sub(Rx, Fp2.mul(Qx, Rz)); // Rx - Qx * 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++) {
|
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 (bitGet(CURVE.x, i)) {
|
if (ut.bitGet(CURVE.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));
|
||||||
@ -163,59 +177,41 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
return Fp12.conjugate(f12);
|
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 };
|
const htfDefaults = { ...CURVE.htfDefaults };
|
||||||
|
|
||||||
|
// TODO: not needed?
|
||||||
function isWithinCurveOrder(num: bigint): boolean {
|
function isWithinCurveOrder(num: bigint): boolean {
|
||||||
return 0 < num && num < CURVE.r;
|
return 0 < num && num < CURVE.r;
|
||||||
}
|
}
|
||||||
|
|
||||||
const utils = {
|
const utils = {
|
||||||
hexToBytes: hexToBytes,
|
hexToBytes: ut.hexToBytes,
|
||||||
bytesToHex: bytesToHex,
|
bytesToHex: ut.bytesToHex,
|
||||||
mod: mod.mod,
|
mod: mod.mod,
|
||||||
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: (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) =>
|
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
|
* Creates FIPS 186 B.4.1 compliant private keys without modulo bias.
|
||||||
* 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
|
|
||||||
*/
|
*/
|
||||||
hashToPrivateKey: (hash: Hex): Uint8Array => {
|
hashToPrivateKey: (hash: Hex): Uint8Array => {
|
||||||
hash = ensureBytes(hash);
|
hash = ut.ensureBytes(hash);
|
||||||
if (hash.length < 40 || hash.length > 1024)
|
if (hash.length < 40 || hash.length > 1024)
|
||||||
throw new Error('Expected 40-1024 bytes of private key as per FIPS 186');
|
throw new Error('Expected 40-1024 bytes of private key as per FIPS 186');
|
||||||
// hashToPrivateScalar(hash, CURVE.r)
|
// hashToPrivateScalar(hash, CURVE.r)
|
||||||
// NOTE: doesn't add +/-1
|
// 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
|
// This should never happen
|
||||||
if (num === 0n || num === 1n) throw new Error('Invalid private key');
|
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),
|
randomBytes: (bytesLength: number = groupLen): Uint8Array => CURVE.randomBytes(bytesLength),
|
||||||
// NIST SP 800-56A rev 3, section 5.6.1.2.2
|
randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(utils.randomBytes(groupLen + 8)),
|
||||||
// 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)),
|
|
||||||
getDSTLabel: () => htfDefaults.DST,
|
getDSTLabel: () => 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
|
||||||
@ -226,11 +222,12 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: reuse CURVE.G1.utils.normalizePrivateKey() ?
|
||||||
function normalizePrivKey(key: PrivKey): bigint {
|
function normalizePrivKey(key: PrivKey): bigint {
|
||||||
let int: bigint;
|
let int: bigint;
|
||||||
if (key instanceof Uint8Array && key.length === 32) int = bytesToNumberBE(key);
|
if (key instanceof Uint8Array && key.length === groupLen) int = ut.bytesToNumberBE(key);
|
||||||
else if (typeof key === 'string' && key.length === 64) int = BigInt(`0x${key}`);
|
else if (typeof key === 'string' && key.length === 2 * groupLen) int = BigInt(`0x${key}`);
|
||||||
else if (typeof key === 'number' && key > 0 && Number.isSafeInteger(key)) int = BigInt(key);
|
else if (ut.isPositiveInt(key)) int = BigInt(key);
|
||||||
else if (typeof key === 'bigint' && key > 0n) int = key;
|
else if (typeof key === 'bigint' && key > 0n) int = key;
|
||||||
else throw new TypeError('Expected valid private key');
|
else throw new TypeError('Expected valid private key');
|
||||||
int = mod.mod(int, CURVE.r);
|
int = mod.mod(int, CURVE.r);
|
||||||
|
@ -1,4 +1,16 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
/*! 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 { sha256 } from '@noble/hashes/sha256';
|
||||||
import { randomBytes } from '@noble/hashes/utils';
|
import { randomBytes } from '@noble/hashes/utils';
|
||||||
import { bls, CurveFn } from './abstract/bls.js';
|
import { bls, CurveFn } from './abstract/bls.js';
|
||||||
@ -23,14 +35,6 @@ import {
|
|||||||
} from './abstract/weierstrass.js';
|
} from './abstract/weierstrass.js';
|
||||||
import { isogenyMap } from './abstract/hash-to-curve.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
|
// CURVE FIELDS
|
||||||
// Finite field over p.
|
// Finite field over p.
|
||||||
const Fp =
|
const Fp =
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
/*! 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 { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils';
|
||||||
import { twistedEdwards } from './abstract/edwards.js';
|
import { twistedEdwards } from './abstract/edwards.js';
|
||||||
import { blake2s } from '@noble/hashes/blake2s';
|
import { blake2s } from '@noble/hashes/blake2s';
|
||||||
@ -16,7 +16,7 @@ export const jubjub = twistedEdwards({
|
|||||||
d: BigInt('0x2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1'),
|
d: BigInt('0x2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1'),
|
||||||
// Finite field 𝔽p over which we'll do calculations
|
// Finite field 𝔽p over which we'll do calculations
|
||||||
Fp: Fp(BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001')),
|
Fp: Fp(BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001')),
|
||||||
// Subgroup order: how many points ed25519 has
|
// Subgroup order: how many points curve has
|
||||||
// 2n ** 252n + 27742317777372353535851937790883648493n;
|
// 2n ** 252n + 27742317777372353535851937790883648493n;
|
||||||
n: BigInt('0xe7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7'),
|
n: BigInt('0xe7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7'),
|
||||||
// Cofactor
|
// Cofactor
|
||||||
@ -24,7 +24,7 @@ export const jubjub = twistedEdwards({
|
|||||||
// Base point (x, y) aka generator point
|
// Base point (x, y) aka generator point
|
||||||
Gx: BigInt('0x11dafe5d23e1218086a365b99fbf3d3be72f6afd7d1f72623e6b071492d1122b'),
|
Gx: BigInt('0x11dafe5d23e1218086a365b99fbf3d3be72f6afd7d1f72623e6b071492d1122b'),
|
||||||
Gy: BigInt('0x1d523cf1ddab1a1793132e78c866c0c33e26ba5cc220fed7cc3f870e59d292aa'),
|
Gy: BigInt('0x1d523cf1ddab1a1793132e78c866c0c33e26ba5cc220fed7cc3f870e59d292aa'),
|
||||||
hash: sha256,
|
hash: sha512,
|
||||||
randomBytes,
|
randomBytes,
|
||||||
} as const);
|
} as const);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user