Refactor BLS, change API

This commit is contained in:
Paul Miller 2023-04-02 14:38:03 +00:00
parent 37ebe6c40f
commit c15c964f77
No known key found for this signature in database
GPG Key ID: 697079DA6878B89B
7 changed files with 255 additions and 153 deletions

@ -214,6 +214,7 @@ There are following zero-dependency algorithms:
- [abstract/weierstrass: Short Weierstrass curve](#abstractweierstrass-short-weierstrass-curve)
- [abstract/edwards: Twisted Edwards curve](#abstractedwards-twisted-edwards-curve)
- [abstract/montgomery: Montgomery curve](#abstractmontgomery-montgomery-curve)
- [abstract/bls: BLS curves](#abstractbls-bls-curves)
- [abstract/hash-to-curve: Hashing strings to curve points](#abstracthash-to-curve-hashing-strings-to-curve-points)
- [abstract/poseidon: Poseidon hash](#abstractposeidon-poseidon-hash)
- [abstract/modular: Modular arithmetics utilities](#abstractmodular-modular-arithmetics-utilities)
@ -491,6 +492,74 @@ Proper Elliptic Curve Points are not implemented yet.
You must specify curve params `Fp`, `a`, `Gu` coordinate of u, `montgomeryBits` and `nByteLength`.
### abstract/bls: BLS curves
The module abstracts BLS (Barreto-Lynn-Scott) primitives.
Right now we only implement BLS12-381, but in theory defining BLS12-377, BLS24
should be straightforward.
Main methods and properties are:
- `getPublicKey(privateKey)`
- `sign(message, privateKey)`
- `verify(signature, message, publicKey)`
- `aggregatePublicKeys(publicKeys)`
- `aggregateSignatures(signatures)`
- `G1` and `G2` curves containing `CURVE` and `ProjectivePoint`
- `Signature` property with `fromHex`, `toHex` methods
- `fields` containing `Fp`, `Fp2`, `Fp6`, `Fp12`, `Fr`
Full types:
```ts
getPublicKey: (privateKey: PrivKey) => Uint8Array;
sign: {
(message: Hex, privateKey: PrivKey): Uint8Array;
(message: ProjPointType<Fp2>, privateKey: PrivKey): ProjPointType<Fp2>;
};
verify: (
signature: Hex | ProjPointType<Fp2>,
message: Hex | ProjPointType<Fp2>,
publicKey: Hex | ProjPointType<Fp>
) => boolean;
verifyBatch: (
signature: Hex | ProjPointType<Fp2>,
messages: (Hex | ProjPointType<Fp2>)[],
publicKeys: (Hex | ProjPointType<Fp>)[]
) => boolean;
aggregatePublicKeys: {
(publicKeys: Hex[]): Uint8Array;
(publicKeys: ProjPointType<Fp>[]): ProjPointType<Fp>;
};
aggregateSignatures: {
(signatures: Hex[]): Uint8Array;
(signatures: ProjPointType<Fp2>[]): ProjPointType<Fp2>;
};
millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12;
pairing: (P: ProjPointType<Fp>, Q: ProjPointType<Fp2>, withFinalExponent?: boolean) => Fp12;
G1: CurvePointsRes<Fp> & ReturnType<typeof htf.createHasher<Fp>>;
G2: CurvePointsRes<Fp2> & ReturnType<typeof htf.createHasher<Fp2>>;
Signature: SignatureCoder<Fp2>;
params: {
x: bigint;
r: bigint;
G1b: bigint;
G2b: Fp2;
};
fields: {
Fp: IField<Fp>;
Fp2: IField<Fp2>;
Fp6: IField<Fp6>;
Fp12: IField<Fp12>;
Fr: IField<bigint>;
};
utils: {
randomPrivateKey: () => Uint8Array;
calcPairingPrecomputes: (p: AffinePoint<Fp2>) => [Fp2, Fp2, Fp2][];
};
```
### abstract/hash-to-curve: Hashing strings to curve points
The module allows to hash arbitrary strings to elliptic curve points. Implements [hash-to-curve v16](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16).
@ -589,11 +658,6 @@ type PoseidonOpts = {
const instance = poseidon(opts: PoseidonOpts);
```
### abstract/bls
The module abstracts BLS (Barreto-Lynn-Scott) primitives. In theory you should be able to write BLS12-377, BLS24,
and others with it.
### abstract/modular: Modular arithmetics utilities
```ts

@ -24,13 +24,16 @@ import {
type Fp = bigint; // Can be different field?
// prettier-ignore
const _2n = BigInt(2), _3n = BigInt(3);
export type SignatureCoder<Fp2> = {
decode(hex: Hex): ProjPointType<Fp2>;
encode(point: ProjPointType<Fp2>): Uint8Array;
fromHex(hex: Hex): ProjPointType<Fp2>;
toRawBytes(point: ProjPointType<Fp2>): Uint8Array;
toHex(point: ProjPointType<Fp2>): string;
};
export type CurveType<Fp, Fp2, Fp6, Fp12> = {
r: bigint;
G1: Omit<CurvePointsType<Fp>, 'n'> & {
mapToCurve: htf.MapToCurve<Fp>;
htfDefaults: htf.Opts;
@ -40,20 +43,25 @@ export type CurveType<Fp, Fp2, Fp6, Fp12> = {
mapToCurve: htf.MapToCurve<Fp2>;
htfDefaults: htf.Opts;
};
x: bigint;
Fp: IField<Fp>;
Fr: IField<bigint>;
Fp2: IField<Fp2> & {
reim: (num: Fp2) => { re: bigint; im: bigint };
multiplyByB: (num: Fp2) => Fp2;
frobeniusMap(num: Fp2, power: number): Fp2;
fields: {
Fp: IField<Fp>;
Fr: IField<bigint>;
Fp2: IField<Fp2> & {
reim: (num: Fp2) => { re: bigint; im: bigint };
multiplyByB: (num: Fp2) => Fp2;
frobeniusMap(num: Fp2, power: number): Fp2;
};
Fp6: IField<Fp6>;
Fp12: IField<Fp12> & {
frobeniusMap(num: Fp12, power: number): Fp12;
multiplyBy014(num: Fp12, o0: Fp2, o1: Fp2, o4: Fp2): Fp12;
conjugate(num: Fp12): Fp12;
finalExponentiate(num: Fp12): Fp12;
};
};
Fp6: IField<Fp6>;
Fp12: IField<Fp12> & {
frobeniusMap(num: Fp12, power: number): Fp12;
multiplyBy014(num: Fp12, o0: Fp2, o1: Fp2, o4: Fp2): Fp12;
conjugate(num: Fp12): Fp12;
finalExponentiate(num: Fp12): Fp12;
params: {
x: bigint;
r: bigint;
};
htfDefaults: htf.Opts;
hash: CHash; // Because we need outputLen for DRBG
@ -61,18 +69,6 @@ export type CurveType<Fp, Fp2, Fp6, Fp12> = {
};
export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
CURVE: CurveType<Fp, Fp2, Fp6, Fp12>;
Fr: IField<bigint>;
Fp: IField<Fp>;
Fp2: IField<Fp2>;
Fp6: IField<Fp6>;
Fp12: IField<Fp12>;
G1: CurvePointsRes<Fp> & ReturnType<typeof htf.createHasher<Fp>>;
G2: CurvePointsRes<Fp2> & ReturnType<typeof htf.createHasher<Fp2>>;
Signature: SignatureCoder<Fp2>;
millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12;
calcPairingPrecomputes: (p: AffinePoint<Fp2>) => [Fp2, Fp2, Fp2][];
pairing: (P: ProjPointType<Fp>, Q: ProjPointType<Fp2>, withFinalExponent?: boolean) => Fp12;
getPublicKey: (privateKey: PrivKey) => Uint8Array;
sign: {
(message: Hex, privateKey: PrivKey): Uint8Array;
@ -83,6 +79,11 @@ export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
message: Hex | ProjPointType<Fp2>,
publicKey: Hex | ProjPointType<Fp>
) => boolean;
verifyBatch: (
signature: Hex | ProjPointType<Fp2>,
messages: (Hex | ProjPointType<Fp2>)[],
publicKeys: (Hex | ProjPointType<Fp>)[]
) => boolean;
aggregatePublicKeys: {
(publicKeys: Hex[]): Uint8Array;
(publicKeys: ProjPointType<Fp>[]): ProjPointType<Fp>;
@ -91,22 +92,36 @@ export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
(signatures: Hex[]): Uint8Array;
(signatures: ProjPointType<Fp2>[]): ProjPointType<Fp2>;
};
verifyBatch: (
signature: Hex | ProjPointType<Fp2>,
messages: (Hex | ProjPointType<Fp2>)[],
publicKeys: (Hex | ProjPointType<Fp>)[]
) => boolean;
millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12;
pairing: (P: ProjPointType<Fp>, Q: ProjPointType<Fp2>, withFinalExponent?: boolean) => Fp12;
G1: CurvePointsRes<Fp> & ReturnType<typeof htf.createHasher<Fp>>;
G2: CurvePointsRes<Fp2> & ReturnType<typeof htf.createHasher<Fp2>>;
Signature: SignatureCoder<Fp2>;
params: {
x: bigint;
r: bigint;
G1b: bigint;
G2b: Fp2;
};
fields: {
Fp: IField<Fp>;
Fp2: IField<Fp2>;
Fp6: IField<Fp6>;
Fp12: IField<Fp12>;
Fr: IField<bigint>;
};
utils: {
randomPrivateKey: () => Uint8Array;
calcPairingPrecomputes: (p: AffinePoint<Fp2>) => [Fp2, Fp2, Fp2][];
};
};
export function bls<Fp2, Fp6, Fp12>(
CURVE: CurveType<Fp, Fp2, Fp6, Fp12>
): CurveFn<Fp, Fp2, Fp6, Fp12> {
// Fields looks pretty specific for curve, so for now we need to pass them with opts
const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE;
const BLS_X_LEN = bitLen(CURVE.x);
// Fields are specific for curve, so for now we'll need to pass them with opts
const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE.fields;
const BLS_X_LEN = bitLen(CURVE.params.x);
const groupLen = 32; // TODO: calculate; hardcoded for now
// Pre-compute coefficients for sparse multiplication
@ -122,18 +137,18 @@ export function bls<Fp2, Fp6, Fp12>(
// Double
let t0 = Fp2.sqr(Ry); // Ry²
let t1 = Fp2.sqr(Rz); // Rz²
let t2 = Fp2.multiplyByB(Fp2.mul(t1, 3n)); // 3 * T1 * B
let t3 = Fp2.mul(t2, 3n); // 3 * T2
let t2 = Fp2.multiplyByB(Fp2.mul(t1, _3n)); // 3 * T1 * B
let t3 = Fp2.mul(t2, _3n); // 3 * T2
let t4 = Fp2.sub(Fp2.sub(Fp2.sqr(Fp2.add(Ry, Rz)), t1), t0); // (Ry + Rz)² - T1 - T0
ell_coeff.push([
Fp2.sub(t2, t0), // T2 - T0
Fp2.mul(Fp2.sqr(Rx), 3n), // 3 * Rx²
Fp2.mul(Fp2.sqr(Rx), _3n), // 3 * Rx²
Fp2.neg(t4), // -T4
]);
Rx = Fp2.div(Fp2.mul(Fp2.mul(Fp2.sub(t0, t3), Rx), Ry), 2n); // ((T0 - T3) * Rx * Ry) / 2
Ry = Fp2.sub(Fp2.sqr(Fp2.div(Fp2.add(t0, t3), 2n)), Fp2.mul(Fp2.sqr(t2), 3n)); // ((T0 + T3) / 2)² - 3 * T2²
Rx = Fp2.div(Fp2.mul(Fp2.mul(Fp2.sub(t0, t3), Rx), Ry), _2n); // ((T0 - T3) * Rx * Ry) / 2
Ry = Fp2.sub(Fp2.sqr(Fp2.div(Fp2.add(t0, t3), _2n)), Fp2.mul(Fp2.sqr(t2), _3n)); // ((T0 + T3) / 2)² - 3 * T2²
Rz = Fp2.mul(t0, t4); // T0 * T4
if (bitGet(CURVE.x, i)) {
if (bitGet(CURVE.params.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
@ -145,7 +160,7 @@ export function bls<Fp2, Fp6, Fp12>(
let t2 = Fp2.sqr(t1); // T1²
let t3 = Fp2.mul(t2, t1); // T2 * T1
let t4 = Fp2.mul(t2, Rx); // T2 * Rx
let t5 = Fp2.add(Fp2.sub(t3, Fp2.mul(t4, 2n)), Fp2.mul(Fp2.sqr(t0), Rz)); // T3 - 2 * T4 + T0² * Rz
let t5 = Fp2.add(Fp2.sub(t3, Fp2.mul(t4, _2n)), Fp2.mul(Fp2.sqr(t0), Rz)); // T3 - 2 * T4 + T0² * Rz
Rx = Fp2.mul(t1, t5); // T1 * T5
Ry = Fp2.sub(Fp2.mul(Fp2.sub(t4, t5), t0), Fp2.mul(t3, Ry)); // (T4 - T5) * T0 - T3 * Ry
Rz = Fp2.mul(Rz, t3); // Rz * T3
@ -155,7 +170,7 @@ export function bls<Fp2, Fp6, Fp12>(
}
function millerLoop(ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]): Fp12 {
const { x } = CURVE;
const { x } = CURVE.params;
const Px = g1[0];
const Py = g1[1];
let f12 = Fp12.ONE;
@ -174,8 +189,9 @@ export function bls<Fp2, Fp6, Fp12>(
const utils = {
randomPrivateKey: (): Uint8Array => {
return Fr.toBytes(hashToPrivateScalar(CURVE.randomBytes(groupLen + 8), CURVE.r));
return Fr.toBytes(hashToPrivateScalar(CURVE.randomBytes(groupLen + 8), CURVE.params.r));
},
calcPairingPrecomputes,
};
// Point on G1 curve: (x, y)
@ -236,7 +252,7 @@ export function bls<Fp2, Fp6, Fp12>(
return point instanceof G1.ProjectivePoint ? (point as G1) : G1.ProjectivePoint.fromHex(point);
}
function normP2(point: G2Hex): G2 {
return point instanceof G2.ProjectivePoint ? point : Signature.decode(point);
return point instanceof G2.ProjectivePoint ? point : Signature.fromHex(point);
}
function normP2Hash(point: G2Hex, htfOpts?: htf.htfBasicOpts): G2 {
return point instanceof G2.ProjectivePoint
@ -259,7 +275,7 @@ export function bls<Fp2, Fp6, Fp12>(
msgPoint.assertValidity();
const sigPoint = msgPoint.multiply(G1.normPrivateKeyToScalar(privateKey));
if (message instanceof G2.ProjectivePoint) return sigPoint;
return Signature.encode(sigPoint);
return Signature.toRawBytes(sigPoint);
}
// Checks if pairing of public key & hash is equal to pairing of generator & signature.
@ -309,7 +325,7 @@ export function bls<Fp2, Fp6, Fp12>(
aggAffine.assertValidity();
return aggAffine;
}
return Signature.encode(aggAffine);
return Signature.toRawBytes(aggAffine);
}
// https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407
@ -353,24 +369,30 @@ export function bls<Fp2, Fp6, Fp12>(
G1.ProjectivePoint.BASE._setWindowSize(4);
return {
CURVE,
Fr,
Fp,
Fp2,
Fp6,
Fp12,
G1,
G2,
Signature,
millerLoop,
calcPairingPrecomputes,
pairing,
getPublicKey,
sign,
verify,
verifyBatch,
aggregatePublicKeys,
aggregateSignatures,
verifyBatch,
millerLoop,
pairing,
G1,
G2,
Signature,
fields: {
Fr,
Fp,
Fp2,
Fp6,
Fp12,
},
params: {
x: CURVE.params.x,
r: CURVE.params.r,
G1b: CURVE.G1.b,
G2b: CURVE.G2.b,
},
utils,
};
}

@ -123,12 +123,12 @@ export function utf8ToBytes(str: string): Uint8Array {
// Amount of bits inside bigint (Same as n.toString(2).length)
export function bitLen(n: bigint) {
let len;
for (len = 0; n > 0n; n >>= _1n, len += 1);
for (len = 0; n > _0n; n >>= _1n, len += 1);
return len;
}
// Gets single bit at position. NOTE: first bit position is 0 (same as arrays)
// Same as !!+Array.from(n.toString(2)).reverse()[pos]
export const bitGet = (n: bigint, pos: number) => (n >> BigInt(pos)) & 1n;
export const bitGet = (n: bigint, pos: number) => (n >> BigInt(pos)) & _1n;
// Sets single bit at position
export const bitSet = (n: bigint, pos: number, value: boolean) =>
n | ((value ? _1n : _0n) << BigInt(pos));

@ -59,6 +59,7 @@ import {
bitGet,
Hex,
bitMask,
bytesToHex,
} from './abstract/utils.js';
// Types
import {
@ -72,8 +73,8 @@ import { isogenyMap } from './abstract/hash-to-curve.js';
// Be friendly to bad ECMAScript parsers by not using bigint literals
// prettier-ignore
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _4n = BigInt(4);
const _8n = BigInt(8),
_16n = BigInt(16);
// prettier-ignore
const _8n = BigInt(8), _16n = BigInt(16);
// CURVE FIELDS
// Finite field over p.
@ -950,9 +951,9 @@ const isogenyMapG1 = isogenyMap(
// SWU Map - Fp2 to G2': y² = x³ + 240i * x + 1012 + 1012i
const G2_SWU = mapToCurveSimpleSWU(Fp2, {
A: Fp2.create({ c0: Fp.create(_0n), c1: Fp.create(240n) }), // A' = 240 * I
B: Fp2.create({ c0: Fp.create(1012n), c1: Fp.create(1012n) }), // B' = 1012 * (1 + I)
Z: Fp2.create({ c0: Fp.create(-2n), c1: Fp.create(-1n) }), // Z: -(2 + I)
A: Fp2.create({ c0: Fp.create(_0n), c1: Fp.create(BigInt(240)) }), // A' = 240 * I
B: Fp2.create({ c0: Fp.create(BigInt(1012)), c1: Fp.create(BigInt(1012)) }), // B' = 1012 * (1 + I)
Z: Fp2.create({ c0: Fp.create(BigInt(-2)), c1: Fp.create(BigInt(-1)) }), // Z: -(2 + I)
});
// Optimized SWU Map - Fp to G1
const G1_SWU = mapToCurveSimpleSWU(Fp, {
@ -966,7 +967,7 @@ const G1_SWU = mapToCurveSimpleSWU(Fp, {
'0x12e2908d11688030018b12e8753eee3b2016c1f0f24f4070a0b9c14fcef35ef55a23215a316ceaa5d1cc48e98e172be0'
)
),
Z: Fp.create(11n),
Z: Fp.create(BigInt(11)),
});
// Endomorphisms (for fast cofactor clearing)
@ -1042,7 +1043,23 @@ const C_BIT_POS = Fp.BITS; // C_bit, compression bit for serialization flag
const I_BIT_POS = Fp.BITS + 1; // I_bit, point-at-infinity bit for serialization flag
const S_BIT_POS = Fp.BITS + 2; // S_bit, sign bit for serialization flag
// Compressed point of infinity
const COMPRESSED_ZERO = Fp.toBytes(bitSet(bitSet(0n, I_BIT_POS, true), S_BIT_POS, true)); // set compressed & point-at-infinity bits
const COMPRESSED_ZERO = Fp.toBytes(bitSet(bitSet(_0n, I_BIT_POS, true), S_BIT_POS, true)); // set compressed & point-at-infinity bits
function signatureG2ToRawBytes(point: ProjPointType<Fp2>) {
// NOTE: by some reasons it was missed in bls12-381, looks like bug
point.assertValidity();
const len = Fp.BYTES;
if (point.equals(bls12_381.G2.ProjectivePoint.ZERO))
return concatB(COMPRESSED_ZERO, numberToBytesBE(_0n, len));
const { x, y } = point.toAffine();
const { re: x0, im: x1 } = Fp2.reim(x);
const { re: y0, im: y1 } = Fp2.reim(y);
const tmp = y1 > _0n ? y1 * _2n : y0 * _2n;
const aflag1 = Boolean((tmp / Fp.ORDER) & _1n);
const z1 = bitSet(bitSet(x1, 381, aflag1), S_BIT_POS, true);
const z2 = x0;
return concatB(numberToBytesBE(z1, len), numberToBytesBE(z2, len));
}
// To verify curve parameters, see pairing-friendly-curves spec:
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-pairing-friendly-curves-09
@ -1056,13 +1073,13 @@ const COMPRESSED_ZERO = Fp.toBytes(bitSet(bitSet(0n, I_BIT_POS, true), S_BIT_POS
// Here goes constants && point encoding format
export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
// Fields
Fr,
Fp,
Fp2,
Fp6,
Fp12,
// order; z⁴ z² + 1
r: Fr.ORDER, // Same as N in other curves
fields: {
Fp,
Fp2,
Fp6,
Fp12,
Fr,
},
// G1 is the order-q subgroup of E1(Fp) : y² = x³ + 4, #E1(Fp) = h1q, where
// characteristic; z + (z⁴ - z² + 1)(z - 1)²/3
G1: {
@ -1095,8 +1112,8 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
const phi = new c(Fp.mul(point.px, cubicRootOfUnityModP), point.py, point.pz);
// todo: unroll
const xP = point.multiplyUnsafe(bls12_381.CURVE.x).negate(); // [x]P
const u2P = xP.multiplyUnsafe(bls12_381.CURVE.x); // [u2]P
const xP = point.multiplyUnsafe(bls12_381.params.x).negate(); // [x]P
const u2P = xP.multiplyUnsafe(bls12_381.params.x); // [u2]P
return u2P.equals(phi);
// https://eprint.iacr.org/2019/814.pdf
@ -1115,21 +1132,23 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
// https://eprint.iacr.org/2019/403
clearCofactor: (c, point) => {
// return this.multiplyUnsafe(CURVE.h);
return point.multiplyUnsafe(bls12_381.CURVE.x).add(point); // x*P + P
return point.multiplyUnsafe(bls12_381.params.x).add(point); // x*P + P
},
mapToCurve: (scalars: bigint[]) => {
const { x, y } = G1_SWU(Fp.create(scalars[0]));
return isogenyMapG1(x, y);
},
fromBytes: (bytes: Uint8Array): AffinePoint<Fp> => {
bytes = bytes.slice();
if (bytes.length === 48) {
// TODO: Fp.bytes
const P = Fp.ORDER;
const compressedValue = bytesToNumberBE(bytes);
const bflag = bitGet(compressedValue, I_BIT_POS);
// Zero
if (bflag === _1n) return { x: _0n, y: _0n };
const x = Fp.create(compressedValue & Fp.MASK);
const right = Fp.add(Fp.pow(x, _3n), Fp.create(bls12_381.CURVE.G1.b)); // y² = x³ + b
const right = Fp.add(Fp.pow(x, _3n), Fp.create(bls12_381.params.G1b)); // y² = x³ + b
let y = Fp.sqrt(right);
if (!y) throw new Error('Invalid compressed G1 point');
const aflag = bitGet(compressedValue, C_BIT_POS);
@ -1138,8 +1157,8 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
} else if (bytes.length === 96) {
// Check if the infinity flag is set
if ((bytes[0] & (1 << 6)) !== 0) return bls12_381.G1.ProjectivePoint.ZERO.toAffine();
const x = bytesToNumberBE(bytes.slice(0, Fp.BYTES));
const y = bytesToNumberBE(bytes.slice(Fp.BYTES));
const x = bytesToNumberBE(bytes.subarray(0, Fp.BYTES));
const y = bytesToNumberBE(bytes.subarray(Fp.BYTES));
return { x: Fp.create(x), y: Fp.create(y) };
} else {
throw new Error('Invalid point G1, expected 48/96 bytes');
@ -1212,7 +1231,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
// It returns false for shitty points.
// https://eprint.iacr.org/2021/1130.pdf
isTorsionFree: (c, P): boolean => {
return P.multiplyUnsafe(bls12_381.CURVE.x).negate().equals(G2psi(c, P)); // ψ(P) == [u](P)
return P.multiplyUnsafe(bls12_381.params.x).negate().equals(G2psi(c, P)); // ψ(P) == [u](P)
// Older version: https://eprint.iacr.org/2019/814.pdf
// Ψ²(P) => Ψ³(P) => [z]Ψ³(P) where z = -x => [z]Ψ³(P) - Ψ²(P) + P == O
// return P.psi2().psi().mulNegX().subtract(psi2).add(P).isZero();
@ -1222,7 +1241,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
// https://eprint.iacr.org/2017/419.pdf
// prettier-ignore
clearCofactor: (c, P) => {
const { x } = bls12_381.CURVE;
const x = bls12_381.params.x;
let t1 = P.multiplyUnsafe(x).negate(); // [-x]P
let t2 = G2psi(c, P); // Ψ(P)
let t3 = P.double(); // 2P
@ -1236,6 +1255,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
return Q; // [x²-x-1]P + [x-1]Ψ(P) + Ψ²(2P)
},
fromBytes: (bytes: Uint8Array): AffinePoint<Fp2> => {
bytes = bytes.slice();
const m_byte = bytes[0] & 0xe0;
if (m_byte === 0x20 || m_byte === 0x60 || m_byte === 0xe0) {
throw new Error('Invalid encoding flag: ' + m_byte);
@ -1246,7 +1266,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
const L = Fp.BYTES;
const slc = (b: Uint8Array, from: number, to?: number) => bytesToNumberBE(b.slice(from, to));
if (bytes.length === 96 && bitC) {
const { b } = bls12_381.CURVE.G2;
const b = bls12_381.params.G2b;
const P = Fp.ORDER;
bytes[0] = bytes[0] & 0x1f; // clear flags
@ -1280,31 +1300,31 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
}
},
toBytes: (c, point, isCompressed) => {
const { BYTES: len, ORDER: P } = Fp;
const isZero = point.equals(c.ZERO);
const { x, y } = point.toAffine();
if (isCompressed) {
const P = Fp.ORDER;
if (isZero) return concatB(COMPRESSED_ZERO, numberToBytesBE(0n, Fp.BYTES));
if (isZero) return concatB(COMPRESSED_ZERO, numberToBytesBE(_0n, len));
const flag = Boolean(y.c1 === _0n ? (y.c0 * _2n) / P : (y.c1 * _2n) / P);
// set compressed & sign bits (looks like different offsets than for G1/Fp?)
let x_1 = bitSet(x.c1, C_BIT_POS, flag);
x_1 = bitSet(x_1, S_BIT_POS, true);
return concatB(numberToBytesBE(x_1, Fp.BYTES), numberToBytesBE(x.c0, Fp.BYTES));
return concatB(numberToBytesBE(x_1, len), numberToBytesBE(x.c0, len));
} else {
if (isZero) return concatB(new Uint8Array([0x40]), new Uint8Array(4 * Fp.BYTES - 1)); // bytes[0] |= 1 << 6;
if (isZero) return concatB(new Uint8Array([0x40]), new Uint8Array(4 * len - 1)); // bytes[0] |= 1 << 6;
const { re: x0, im: x1 } = Fp2.reim(x);
const { re: y0, im: y1 } = Fp2.reim(y);
return concatB(
numberToBytesBE(x1, Fp.BYTES),
numberToBytesBE(x0, Fp.BYTES),
numberToBytesBE(y1, Fp.BYTES),
numberToBytesBE(y0, Fp.BYTES)
numberToBytesBE(x1, len),
numberToBytesBE(x0, len),
numberToBytesBE(y1, len),
numberToBytesBE(y0, len)
);
}
},
Signature: {
// TODO: Optimize, it's very slow because of sqrt.
decode(hex: Hex): ProjPointType<Fp2> {
fromHex(hex: Hex): ProjPointType<Fp2> {
hex = ensureBytes('signatureHex', hex);
const P = Fp.ORDER;
const half = hex.length / 2;
@ -1319,7 +1339,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
const x1 = Fp.create(z1 & Fp.MASK);
const x2 = Fp.create(z2);
const x = Fp2.create({ c0: x2, c1: x1 });
const y2 = Fp2.add(Fp2.pow(x, _3n), bls12_381.CURVE.G2.b); // y² = x³ + 4
const y2 = Fp2.add(Fp2.pow(x, _3n), bls12_381.params.G2b); // y² = x³ + 4
// The slow part
let y = Fp2.sqrt(y2);
if (!y) throw new Error('Failed to find a square root');
@ -1335,24 +1355,18 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
point.assertValidity();
return point;
},
encode(point: ProjPointType<Fp2>) {
// NOTE: by some reasons it was missed in bls12-381, looks like bug
point.assertValidity();
if (point.equals(bls12_381.G2.ProjectivePoint.ZERO))
return concatB(COMPRESSED_ZERO, numberToBytesBE(0n, Fp.BYTES));
const a = point.toAffine();
const { re: x0, im: x1 } = Fp2.reim(a.x);
const { re: y0, im: y1 } = Fp2.reim(a.y);
const tmp = y1 > _0n ? y1 * _2n : y0 * _2n;
const aflag1 = Boolean((tmp / Fp.ORDER) & _1n);
const z1 = bitSet(bitSet(x1, 381, aflag1), S_BIT_POS, true);
const z2 = x0;
return concatB(numberToBytesBE(z1, Fp.BYTES), numberToBytesBE(z2, Fp.BYTES));
toRawBytes(point: ProjPointType<Fp2>) {
return signatureG2ToRawBytes(point);
},
toHex(point: ProjPointType<Fp2>) {
return bytesToHex(signatureG2ToRawBytes(point));
},
},
},
// The BLS parameter x for BLS12-381
x: BLS_X,
params: {
x: BLS_X, // The BLS parameter x for BLS12-381
r: Fr.ORDER, // order; z⁴ z² + 1; CURVE.n from other curves
},
htfDefaults,
hash: sha256,
randomBytes,

@ -30,19 +30,19 @@ const FIELDS = {
pallas: { Fp: [pallas.CURVE.Fp] },
vesta: { Fp: [vesta.CURVE.Fp] },
bls12: {
Fp: [bls12_381.CURVE.Fp],
Fp: [bls12_381.fields.Fp],
Fp2: [
bls12_381.CURVE.Fp2,
fc.array(fc.bigInt(1n, bls12_381.CURVE.Fp.ORDER - 1n), {
bls12_381.fields.Fp2,
fc.array(fc.bigInt(1n, bls12_381.fields.Fp.ORDER - 1n), {
minLength: 2,
maxLength: 2,
}),
(Fp2, num) => Fp2.fromBigTuple([num[0], num[1]]),
],
// Fp6: [bls12_381.CURVE.Fp6],
// Fp6: [bls12_381.fields.Fp6],
Fp12: [
bls12_381.CURVE.Fp12,
fc.array(fc.bigInt(1n, bls12_381.CURVE.Fp.ORDER - 1n), {
bls12_381.fields.Fp12,
fc.array(fc.bigInt(1n, bls12_381.fields.Fp.ORDER - 1n), {
minLength: 12,
maxLength: 12,
}),
@ -221,7 +221,7 @@ for (const c in FIELDS) {
const isSquare = mod.FpIsSquare(Fp);
// Not implemented
if (Fp !== bls12_381.CURVE.Fp12) {
if (Fp !== bls12_381.fields.Fp12) {
should('multiply/sqrt', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {

@ -24,11 +24,10 @@ const SCALAR_VECTORS = readFileSync('./test/bls12-381/bls12-381-scalar-test-vect
const NUM_RUNS = Number(process.env.RUNS_COUNT || 10); // reduce to 1 to shorten test time
fc.configureGlobal({ numRuns: NUM_RUNS });
const { Fp2 } = bls;
const G1Point = bls.G1.ProjectivePoint;
const G2Point = bls.G2.ProjectivePoint;
const G1Aff = (x, y) => G1Point.fromAffine({ x, y });
const CURVE_ORDER = bls.CURVE.r;
const CURVE_ORDER = bls.params.r;
const FC_MSG = fc.hexaString({ minLength: 64, maxLength: 64 });
const FC_MSG_5 = fc.array(FC_MSG, { minLength: 5, maxLength: 5 });
@ -42,10 +41,10 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
function equal(a, b, comment) {
deepStrictEqual(a.equals(b), true, `eq(${comment})`);
}
const { Fp, Fp2 } = bls.fields;
// Fp
describe('bls12-381 Fp', () => {
const Fp = bls.Fp;
const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n);
should('multiply/sqrt', () => {
@ -60,8 +59,7 @@ describe('bls12-381 Fp', () => {
// Fp2
describe('bls12-381 Fp2', () => {
const Fp = bls.Fp;
const Fp2 = bls.Fp2;
const { Fp, Fp2 } = bls.fields;
const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n);
const FC_BIGINT_2 = fc.array(FC_BIGINT, { minLength: 2, maxLength: 2 });
@ -149,7 +147,7 @@ describe('bls12-381 Fp2', () => {
// Point
describe('bls12-381 Point', () => {
const Fp = bls.Fp;
const { Fp } = bls.fields;
const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n);
const PointG1 = G1Point;
const PointG2 = G2Point;
@ -557,9 +555,12 @@ describe('bls12-381 Point', () => {
];
// Use wNAF allow scalars higher than CURVE.r
const w = wNAF(G2Point, 1);
const hEff = BigInt(
'0xbc69f08f2ee75b3584c6a0ea91b352888e2a8e9145ad7689986ff031508ffe1329c2f178731db956d82bf015d1212b02ec0ec69d7477c1ae954cbc06689f6a359894c0adebbf6b4e8020005aaa95551'
);
for (let p of points) {
const ours = p.clearCofactor();
const shouldBe = w.unsafeLadder(p, bls.CURVE.G2.hEff);
const shouldBe = w.unsafeLadder(p, hEff);
deepStrictEqual(ours.equals(shouldBe), true, 'clearLast');
}
});
@ -577,12 +578,12 @@ describe('bls12-381/basic', () => {
deepStrictEqual(g1.x, G1Point.ZERO.x);
deepStrictEqual(g1.y, G1Point.ZERO.y);
// Test Non-Zero
const x = bls.Fp.create(
const x = Fp.create(
BigInt(
'0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb'
)
);
const y = bls.Fp.create(
const y = Fp.create(
BigInt(
'0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1'
)
@ -603,12 +604,12 @@ describe('bls12-381/basic', () => {
deepStrictEqual(g1.x, G1Point.ZERO.x);
deepStrictEqual(g1.y, G1Point.ZERO.y);
// Test Non-Zero
const x = bls.Fp.create(
const x = Fp.create(
BigInt(
'0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb'
)
);
const y = bls.Fp.create(
const y = Fp.create(
BigInt(
'0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1'
)
@ -689,12 +690,12 @@ describe('bls12-381/basic', () => {
// Test Zero
deepStrictEqual(G1Point.ZERO.toHex(false), B_192_40);
// Test Non-Zero
const x = bls.Fp.create(
const x = Fp.create(
BigInt(
'0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb'
)
);
const y = bls.Fp.create(
const y = Fp.create(
BigInt(
'0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1'
)
@ -710,12 +711,12 @@ describe('bls12-381/basic', () => {
// Test Zero
deepStrictEqual(G1Point.ZERO.toHex(false), B_192_40);
// Test Non-Zero
const x = bls.Fp.create(
const x = Fp.create(
BigInt(
'0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb'
)
);
const y = bls.Fp.create(
const y = Fp.create(
BigInt(
'0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1'
)
@ -801,32 +802,32 @@ describe('bls12-381/basic', () => {
throws(() => G2Point.fromPrivateKey(0n));
});
const VALID_G1 = new G1Point(
bls.Fp.create(
Fp.create(
3609742242174788176010452839163620388872641749536604986743596621604118973777515189035770461528205168143692110933639n
),
bls.Fp.create(
Fp.create(
1619277690257184054444116778047375363103842303863153349133480657158810226683757397206929105479676799650932070320089n
),
bls.Fp.create(1n)
Fp.create(1n)
);
const VALID_G1_2 = new G1Point(
bls.Fp.create(
Fp.create(
1206972466279728255044019580914616126536509750250979180256809997983196363639429409634110400978470384566664128085207n
),
bls.Fp.create(
Fp.create(
2991142246317096160788653339959532007292638191110818490939476869616372888657136539642598243964263069435065725313423n
),
bls.Fp.create(1n)
Fp.create(1n)
);
const INVALID_G1 = new G1Point(
bls.Fp.create(
Fp.create(
499001545268060011619089734015590154568173930614466321429631711131511181286230338880376679848890024401335766847607n
),
bls.Fp.create(
Fp.create(
3934582309586258715640230772291917282844636728991757779640464479794033391537662970190753981664259511166946374555673n
),
bls.Fp.create(1n)
Fp.create(1n)
);
should('aggregate pubkeys', () => {
@ -855,7 +856,7 @@ describe('bls12-381/basic', () => {
});
should(`produce correct scalars (${SCALAR_VECTORS.length} vectors)`, () => {
const options = {
p: bls.CURVE.r,
p: bls.params.r,
m: 1,
expand: undefined,
};
@ -863,7 +864,7 @@ describe('bls12-381/basic', () => {
const [okmAscii, expectedHex] = vector;
const expected = BigInt('0x' + expectedHex);
const okm = utf8ToBytes(okmAscii);
const scalars = hash_to_field(okm, 1, Object.assign({}, bls.CURVE.htfDefaults, options));
const scalars = hash_to_field(okm, 1, Object.assign({}, bls.G2.CURVE.htfDefaults, options));
deepStrictEqual(scalars[0][0], expected);
}
});
@ -871,7 +872,8 @@ describe('bls12-381/basic', () => {
// Pairing
describe('pairing', () => {
const { pairing, Fp12 } = bls;
const { pairing } = bls;
const { Fp12 } = bls.fields;
const G1 = G1Point.BASE;
const G2 = G2Point.BASE;
@ -1000,7 +1002,7 @@ describe('hash-to-curve', () => {
];
for (let i = 0; i < VECTORS_G1.length; i++) {
const t = VECTORS_G1[i];
should(`hashToCurve/G1 Killic (${i})`, () => {
should(`G1 Killic (${i})`, () => {
const p = bls.G1.hashToCurve(t.msg, {
DST: 'BLS12381G1_XMD:SHA-256_SSWU_RO_TESTGEN',
});
@ -1259,7 +1261,7 @@ describe('bls12-381 deterministic', () => {
.reverse()
.reduce((acc, i) => acc + i);
const Fp12 = bls.Fp12;
const { Fp12 } = bls.fields;
should('Killic based/Pairing', () => {
const t = bls.pairing(G1Point.BASE, G2Point.BASE);