Refactor BLS, change API
This commit is contained in:
parent
37ebe6c40f
commit
c15c964f77
74
README.md
74
README.md
@ -214,6 +214,7 @@ There are following zero-dependency algorithms:
|
|||||||
- [abstract/weierstrass: Short Weierstrass curve](#abstractweierstrass-short-weierstrass-curve)
|
- [abstract/weierstrass: Short Weierstrass curve](#abstractweierstrass-short-weierstrass-curve)
|
||||||
- [abstract/edwards: Twisted Edwards curve](#abstractedwards-twisted-edwards-curve)
|
- [abstract/edwards: Twisted Edwards curve](#abstractedwards-twisted-edwards-curve)
|
||||||
- [abstract/montgomery: Montgomery curve](#abstractmontgomery-montgomery-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/hash-to-curve: Hashing strings to curve points](#abstracthash-to-curve-hashing-strings-to-curve-points)
|
||||||
- [abstract/poseidon: Poseidon hash](#abstractposeidon-poseidon-hash)
|
- [abstract/poseidon: Poseidon hash](#abstractposeidon-poseidon-hash)
|
||||||
- [abstract/modular: Modular arithmetics utilities](#abstractmodular-modular-arithmetics-utilities)
|
- [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`.
|
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
|
### 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).
|
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);
|
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
|
### abstract/modular: Modular arithmetics utilities
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
@ -24,13 +24,16 @@ import {
|
|||||||
|
|
||||||
type Fp = bigint; // Can be different field?
|
type Fp = bigint; // Can be different field?
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
const _2n = BigInt(2), _3n = BigInt(3);
|
||||||
|
|
||||||
export type SignatureCoder<Fp2> = {
|
export type SignatureCoder<Fp2> = {
|
||||||
decode(hex: Hex): ProjPointType<Fp2>;
|
fromHex(hex: Hex): ProjPointType<Fp2>;
|
||||||
encode(point: ProjPointType<Fp2>): Uint8Array;
|
toRawBytes(point: ProjPointType<Fp2>): Uint8Array;
|
||||||
|
toHex(point: ProjPointType<Fp2>): string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CurveType<Fp, Fp2, Fp6, Fp12> = {
|
export type CurveType<Fp, Fp2, Fp6, Fp12> = {
|
||||||
r: bigint;
|
|
||||||
G1: Omit<CurvePointsType<Fp>, 'n'> & {
|
G1: Omit<CurvePointsType<Fp>, 'n'> & {
|
||||||
mapToCurve: htf.MapToCurve<Fp>;
|
mapToCurve: htf.MapToCurve<Fp>;
|
||||||
htfDefaults: htf.Opts;
|
htfDefaults: htf.Opts;
|
||||||
@ -40,7 +43,7 @@ export type CurveType<Fp, Fp2, Fp6, Fp12> = {
|
|||||||
mapToCurve: htf.MapToCurve<Fp2>;
|
mapToCurve: htf.MapToCurve<Fp2>;
|
||||||
htfDefaults: htf.Opts;
|
htfDefaults: htf.Opts;
|
||||||
};
|
};
|
||||||
x: bigint;
|
fields: {
|
||||||
Fp: IField<Fp>;
|
Fp: IField<Fp>;
|
||||||
Fr: IField<bigint>;
|
Fr: IField<bigint>;
|
||||||
Fp2: IField<Fp2> & {
|
Fp2: IField<Fp2> & {
|
||||||
@ -55,24 +58,17 @@ export type CurveType<Fp, Fp2, Fp6, Fp12> = {
|
|||||||
conjugate(num: Fp12): Fp12;
|
conjugate(num: Fp12): Fp12;
|
||||||
finalExponentiate(num: Fp12): Fp12;
|
finalExponentiate(num: Fp12): Fp12;
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
params: {
|
||||||
|
x: bigint;
|
||||||
|
r: bigint;
|
||||||
|
};
|
||||||
htfDefaults: htf.Opts;
|
htfDefaults: htf.Opts;
|
||||||
hash: CHash; // Because we need outputLen for DRBG
|
hash: CHash; // Because we need outputLen for DRBG
|
||||||
randomBytes: (bytesLength?: number) => Uint8Array;
|
randomBytes: (bytesLength?: number) => Uint8Array;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CurveFn<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;
|
getPublicKey: (privateKey: PrivKey) => Uint8Array;
|
||||||
sign: {
|
sign: {
|
||||||
(message: Hex, privateKey: PrivKey): Uint8Array;
|
(message: Hex, privateKey: PrivKey): Uint8Array;
|
||||||
@ -83,6 +79,11 @@ export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
|
|||||||
message: Hex | ProjPointType<Fp2>,
|
message: Hex | ProjPointType<Fp2>,
|
||||||
publicKey: Hex | ProjPointType<Fp>
|
publicKey: Hex | ProjPointType<Fp>
|
||||||
) => boolean;
|
) => boolean;
|
||||||
|
verifyBatch: (
|
||||||
|
signature: Hex | ProjPointType<Fp2>,
|
||||||
|
messages: (Hex | ProjPointType<Fp2>)[],
|
||||||
|
publicKeys: (Hex | ProjPointType<Fp>)[]
|
||||||
|
) => boolean;
|
||||||
aggregatePublicKeys: {
|
aggregatePublicKeys: {
|
||||||
(publicKeys: Hex[]): Uint8Array;
|
(publicKeys: Hex[]): Uint8Array;
|
||||||
(publicKeys: ProjPointType<Fp>[]): ProjPointType<Fp>;
|
(publicKeys: ProjPointType<Fp>[]): ProjPointType<Fp>;
|
||||||
@ -91,22 +92,36 @@ export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
|
|||||||
(signatures: Hex[]): Uint8Array;
|
(signatures: Hex[]): Uint8Array;
|
||||||
(signatures: ProjPointType<Fp2>[]): ProjPointType<Fp2>;
|
(signatures: ProjPointType<Fp2>[]): ProjPointType<Fp2>;
|
||||||
};
|
};
|
||||||
verifyBatch: (
|
millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12;
|
||||||
signature: Hex | ProjPointType<Fp2>,
|
pairing: (P: ProjPointType<Fp>, Q: ProjPointType<Fp2>, withFinalExponent?: boolean) => Fp12;
|
||||||
messages: (Hex | ProjPointType<Fp2>)[],
|
G1: CurvePointsRes<Fp> & ReturnType<typeof htf.createHasher<Fp>>;
|
||||||
publicKeys: (Hex | ProjPointType<Fp>)[]
|
G2: CurvePointsRes<Fp2> & ReturnType<typeof htf.createHasher<Fp2>>;
|
||||||
) => boolean;
|
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: {
|
utils: {
|
||||||
randomPrivateKey: () => Uint8Array;
|
randomPrivateKey: () => Uint8Array;
|
||||||
|
calcPairingPrecomputes: (p: AffinePoint<Fp2>) => [Fp2, Fp2, Fp2][];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function bls<Fp2, Fp6, Fp12>(
|
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 opts
|
// Fields are specific for curve, so for now we'll need to pass them with opts
|
||||||
const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE;
|
const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE.fields;
|
||||||
const BLS_X_LEN = bitLen(CURVE.x);
|
const BLS_X_LEN = bitLen(CURVE.params.x);
|
||||||
const groupLen = 32; // TODO: calculate; hardcoded for now
|
const groupLen = 32; // TODO: calculate; hardcoded for now
|
||||||
|
|
||||||
// Pre-compute coefficients for sparse multiplication
|
// Pre-compute coefficients for sparse multiplication
|
||||||
@ -122,18 +137,18 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
// Double
|
// Double
|
||||||
let t0 = Fp2.sqr(Ry); // Ry²
|
let t0 = Fp2.sqr(Ry); // Ry²
|
||||||
let t1 = Fp2.sqr(Rz); // Rz²
|
let t1 = Fp2.sqr(Rz); // Rz²
|
||||||
let t2 = Fp2.multiplyByB(Fp2.mul(t1, 3n)); // 3 * T1 * B
|
let t2 = Fp2.multiplyByB(Fp2.mul(t1, _3n)); // 3 * T1 * B
|
||||||
let t3 = Fp2.mul(t2, 3n); // 3 * T2
|
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
|
let t4 = Fp2.sub(Fp2.sub(Fp2.sqr(Fp2.add(Ry, Rz)), t1), t0); // (Ry + Rz)² - T1 - T0
|
||||||
ell_coeff.push([
|
ell_coeff.push([
|
||||||
Fp2.sub(t2, t0), // T2 - T0
|
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
|
Fp2.neg(t4), // -T4
|
||||||
]);
|
]);
|
||||||
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.sqr(Fp2.div(Fp2.add(t0, t3), 2n)), Fp2.mul(Fp2.sqr(t2), 3n)); // ((T0 + T3) / 2)² - 3 * T2²
|
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
|
Rz = Fp2.mul(t0, t4); // T0 * T4
|
||||||
if (bitGet(CURVE.x, i)) {
|
if (bitGet(CURVE.params.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
|
||||||
@ -145,7 +160,7 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
let t2 = Fp2.sqr(t1); // T1²
|
let t2 = Fp2.sqr(t1); // T1²
|
||||||
let t3 = Fp2.mul(t2, t1); // T2 * T1
|
let t3 = Fp2.mul(t2, t1); // T2 * T1
|
||||||
let t4 = Fp2.mul(t2, Rx); // T2 * Rx
|
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
|
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
|
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
|
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 {
|
function millerLoop(ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]): Fp12 {
|
||||||
const { x } = CURVE;
|
const { x } = CURVE.params;
|
||||||
const Px = g1[0];
|
const Px = g1[0];
|
||||||
const Py = g1[1];
|
const Py = g1[1];
|
||||||
let f12 = Fp12.ONE;
|
let f12 = Fp12.ONE;
|
||||||
@ -174,8 +189,9 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
|
|
||||||
const utils = {
|
const utils = {
|
||||||
randomPrivateKey: (): Uint8Array => {
|
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)
|
// 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);
|
return point instanceof G1.ProjectivePoint ? (point as G1) : G1.ProjectivePoint.fromHex(point);
|
||||||
}
|
}
|
||||||
function normP2(point: G2Hex): G2 {
|
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 {
|
function normP2Hash(point: G2Hex, htfOpts?: htf.htfBasicOpts): G2 {
|
||||||
return point instanceof G2.ProjectivePoint
|
return point instanceof G2.ProjectivePoint
|
||||||
@ -259,7 +275,7 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
msgPoint.assertValidity();
|
msgPoint.assertValidity();
|
||||||
const sigPoint = msgPoint.multiply(G1.normPrivateKeyToScalar(privateKey));
|
const sigPoint = msgPoint.multiply(G1.normPrivateKeyToScalar(privateKey));
|
||||||
if (message instanceof G2.ProjectivePoint) return sigPoint;
|
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.
|
// 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();
|
aggAffine.assertValidity();
|
||||||
return aggAffine;
|
return aggAffine;
|
||||||
}
|
}
|
||||||
return Signature.encode(aggAffine);
|
return Signature.toRawBytes(aggAffine);
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407
|
// 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);
|
G1.ProjectivePoint.BASE._setWindowSize(4);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
CURVE,
|
getPublicKey,
|
||||||
|
sign,
|
||||||
|
verify,
|
||||||
|
verifyBatch,
|
||||||
|
aggregatePublicKeys,
|
||||||
|
aggregateSignatures,
|
||||||
|
millerLoop,
|
||||||
|
pairing,
|
||||||
|
G1,
|
||||||
|
G2,
|
||||||
|
Signature,
|
||||||
|
fields: {
|
||||||
Fr,
|
Fr,
|
||||||
Fp,
|
Fp,
|
||||||
Fp2,
|
Fp2,
|
||||||
Fp6,
|
Fp6,
|
||||||
Fp12,
|
Fp12,
|
||||||
G1,
|
},
|
||||||
G2,
|
params: {
|
||||||
Signature,
|
x: CURVE.params.x,
|
||||||
millerLoop,
|
r: CURVE.params.r,
|
||||||
calcPairingPrecomputes,
|
G1b: CURVE.G1.b,
|
||||||
pairing,
|
G2b: CURVE.G2.b,
|
||||||
getPublicKey,
|
},
|
||||||
sign,
|
|
||||||
verify,
|
|
||||||
aggregatePublicKeys,
|
|
||||||
aggregateSignatures,
|
|
||||||
verifyBatch,
|
|
||||||
utils,
|
utils,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -123,12 +123,12 @@ export function utf8ToBytes(str: string): Uint8Array {
|
|||||||
// Amount of bits inside bigint (Same as n.toString(2).length)
|
// Amount of bits inside bigint (Same as n.toString(2).length)
|
||||||
export function bitLen(n: bigint) {
|
export function bitLen(n: bigint) {
|
||||||
let len;
|
let len;
|
||||||
for (len = 0; n > 0n; n >>= _1n, len += 1);
|
for (len = 0; n > _0n; n >>= _1n, len += 1);
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
// Gets single bit at position. NOTE: first bit position is 0 (same as arrays)
|
// Gets single bit at position. NOTE: first bit position is 0 (same as arrays)
|
||||||
// Same as !!+Array.from(n.toString(2)).reverse()[pos]
|
// 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
|
// Sets single bit at position
|
||||||
export const bitSet = (n: bigint, pos: number, value: boolean) =>
|
export const bitSet = (n: bigint, pos: number, value: boolean) =>
|
||||||
n | ((value ? _1n : _0n) << BigInt(pos));
|
n | ((value ? _1n : _0n) << BigInt(pos));
|
||||||
|
102
src/bls12-381.ts
102
src/bls12-381.ts
@ -59,6 +59,7 @@ import {
|
|||||||
bitGet,
|
bitGet,
|
||||||
Hex,
|
Hex,
|
||||||
bitMask,
|
bitMask,
|
||||||
|
bytesToHex,
|
||||||
} from './abstract/utils.js';
|
} from './abstract/utils.js';
|
||||||
// Types
|
// Types
|
||||||
import {
|
import {
|
||||||
@ -72,8 +73,8 @@ import { isogenyMap } from './abstract/hash-to-curve.js';
|
|||||||
// Be friendly to bad ECMAScript parsers by not using bigint literals
|
// Be friendly to bad ECMAScript parsers by not using bigint literals
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _4n = BigInt(4);
|
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _4n = BigInt(4);
|
||||||
const _8n = BigInt(8),
|
// prettier-ignore
|
||||||
_16n = BigInt(16);
|
const _8n = BigInt(8), _16n = BigInt(16);
|
||||||
|
|
||||||
// CURVE FIELDS
|
// CURVE FIELDS
|
||||||
// Finite field over p.
|
// Finite field over p.
|
||||||
@ -950,9 +951,9 @@ const isogenyMapG1 = isogenyMap(
|
|||||||
|
|
||||||
// SWU Map - Fp2 to G2': y² = x³ + 240i * x + 1012 + 1012i
|
// SWU Map - Fp2 to G2': y² = x³ + 240i * x + 1012 + 1012i
|
||||||
const G2_SWU = mapToCurveSimpleSWU(Fp2, {
|
const G2_SWU = mapToCurveSimpleSWU(Fp2, {
|
||||||
A: Fp2.create({ c0: Fp.create(_0n), c1: Fp.create(240n) }), // A' = 240 * I
|
A: Fp2.create({ c0: Fp.create(_0n), c1: Fp.create(BigInt(240)) }), // A' = 240 * I
|
||||||
B: Fp2.create({ c0: Fp.create(1012n), c1: Fp.create(1012n) }), // B' = 1012 * (1 + I)
|
B: Fp2.create({ c0: Fp.create(BigInt(1012)), c1: Fp.create(BigInt(1012)) }), // B' = 1012 * (1 + I)
|
||||||
Z: Fp2.create({ c0: Fp.create(-2n), c1: Fp.create(-1n) }), // Z: -(2 + I)
|
Z: Fp2.create({ c0: Fp.create(BigInt(-2)), c1: Fp.create(BigInt(-1)) }), // Z: -(2 + I)
|
||||||
});
|
});
|
||||||
// Optimized SWU Map - Fp to G1
|
// Optimized SWU Map - Fp to G1
|
||||||
const G1_SWU = mapToCurveSimpleSWU(Fp, {
|
const G1_SWU = mapToCurveSimpleSWU(Fp, {
|
||||||
@ -966,7 +967,7 @@ const G1_SWU = mapToCurveSimpleSWU(Fp, {
|
|||||||
'0x12e2908d11688030018b12e8753eee3b2016c1f0f24f4070a0b9c14fcef35ef55a23215a316ceaa5d1cc48e98e172be0'
|
'0x12e2908d11688030018b12e8753eee3b2016c1f0f24f4070a0b9c14fcef35ef55a23215a316ceaa5d1cc48e98e172be0'
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
Z: Fp.create(11n),
|
Z: Fp.create(BigInt(11)),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Endomorphisms (for fast cofactor clearing)
|
// 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 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
|
const S_BIT_POS = Fp.BITS + 2; // S_bit, sign bit for serialization flag
|
||||||
// Compressed point of infinity
|
// 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:
|
// To verify curve parameters, see pairing-friendly-curves spec:
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-pairing-friendly-curves-09
|
// 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
|
// Here goes constants && point encoding format
|
||||||
export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
|
export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
|
||||||
// Fields
|
// Fields
|
||||||
Fr,
|
fields: {
|
||||||
Fp,
|
Fp,
|
||||||
Fp2,
|
Fp2,
|
||||||
Fp6,
|
Fp6,
|
||||||
Fp12,
|
Fp12,
|
||||||
// order; z⁴ − z² + 1
|
Fr,
|
||||||
r: Fr.ORDER, // Same as N in other curves
|
},
|
||||||
// G1 is the order-q subgroup of E1(Fp) : y² = x³ + 4, #E1(Fp) = h1q, where
|
// G1 is the order-q subgroup of E1(Fp) : y² = x³ + 4, #E1(Fp) = h1q, where
|
||||||
// characteristic; z + (z⁴ - z² + 1)(z - 1)²/3
|
// characteristic; z + (z⁴ - z² + 1)(z - 1)²/3
|
||||||
G1: {
|
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);
|
const phi = new c(Fp.mul(point.px, cubicRootOfUnityModP), point.py, point.pz);
|
||||||
|
|
||||||
// todo: unroll
|
// todo: unroll
|
||||||
const xP = point.multiplyUnsafe(bls12_381.CURVE.x).negate(); // [x]P
|
const xP = point.multiplyUnsafe(bls12_381.params.x).negate(); // [x]P
|
||||||
const u2P = xP.multiplyUnsafe(bls12_381.CURVE.x); // [u2]P
|
const u2P = xP.multiplyUnsafe(bls12_381.params.x); // [u2]P
|
||||||
return u2P.equals(phi);
|
return u2P.equals(phi);
|
||||||
|
|
||||||
// https://eprint.iacr.org/2019/814.pdf
|
// 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
|
// https://eprint.iacr.org/2019/403
|
||||||
clearCofactor: (c, point) => {
|
clearCofactor: (c, point) => {
|
||||||
// return this.multiplyUnsafe(CURVE.h);
|
// 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[]) => {
|
mapToCurve: (scalars: bigint[]) => {
|
||||||
const { x, y } = G1_SWU(Fp.create(scalars[0]));
|
const { x, y } = G1_SWU(Fp.create(scalars[0]));
|
||||||
return isogenyMapG1(x, y);
|
return isogenyMapG1(x, y);
|
||||||
},
|
},
|
||||||
fromBytes: (bytes: Uint8Array): AffinePoint<Fp> => {
|
fromBytes: (bytes: Uint8Array): AffinePoint<Fp> => {
|
||||||
|
bytes = bytes.slice();
|
||||||
if (bytes.length === 48) {
|
if (bytes.length === 48) {
|
||||||
|
// TODO: Fp.bytes
|
||||||
const P = Fp.ORDER;
|
const P = Fp.ORDER;
|
||||||
const compressedValue = bytesToNumberBE(bytes);
|
const compressedValue = bytesToNumberBE(bytes);
|
||||||
const bflag = bitGet(compressedValue, I_BIT_POS);
|
const bflag = bitGet(compressedValue, I_BIT_POS);
|
||||||
// Zero
|
// Zero
|
||||||
if (bflag === _1n) return { x: _0n, y: _0n };
|
if (bflag === _1n) return { x: _0n, y: _0n };
|
||||||
const x = Fp.create(compressedValue & Fp.MASK);
|
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);
|
let y = Fp.sqrt(right);
|
||||||
if (!y) throw new Error('Invalid compressed G1 point');
|
if (!y) throw new Error('Invalid compressed G1 point');
|
||||||
const aflag = bitGet(compressedValue, C_BIT_POS);
|
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) {
|
} else if (bytes.length === 96) {
|
||||||
// Check if the infinity flag is set
|
// Check if the infinity flag is set
|
||||||
if ((bytes[0] & (1 << 6)) !== 0) return bls12_381.G1.ProjectivePoint.ZERO.toAffine();
|
if ((bytes[0] & (1 << 6)) !== 0) return bls12_381.G1.ProjectivePoint.ZERO.toAffine();
|
||||||
const x = bytesToNumberBE(bytes.slice(0, Fp.BYTES));
|
const x = bytesToNumberBE(bytes.subarray(0, Fp.BYTES));
|
||||||
const y = bytesToNumberBE(bytes.slice(Fp.BYTES));
|
const y = bytesToNumberBE(bytes.subarray(Fp.BYTES));
|
||||||
return { x: Fp.create(x), y: Fp.create(y) };
|
return { x: Fp.create(x), y: Fp.create(y) };
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid point G1, expected 48/96 bytes');
|
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.
|
// It returns false for shitty points.
|
||||||
// https://eprint.iacr.org/2021/1130.pdf
|
// https://eprint.iacr.org/2021/1130.pdf
|
||||||
isTorsionFree: (c, P): boolean => {
|
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
|
// Older version: https://eprint.iacr.org/2019/814.pdf
|
||||||
// Ψ²(P) => Ψ³(P) => [z]Ψ³(P) where z = -x => [z]Ψ³(P) - Ψ²(P) + P == O
|
// Ψ²(P) => Ψ³(P) => [z]Ψ³(P) where z = -x => [z]Ψ³(P) - Ψ²(P) + P == O
|
||||||
// return P.psi2().psi().mulNegX().subtract(psi2).add(P).isZero();
|
// 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
|
// https://eprint.iacr.org/2017/419.pdf
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
clearCofactor: (c, P) => {
|
clearCofactor: (c, P) => {
|
||||||
const { x } = bls12_381.CURVE;
|
const x = bls12_381.params.x;
|
||||||
let t1 = P.multiplyUnsafe(x).negate(); // [-x]P
|
let t1 = P.multiplyUnsafe(x).negate(); // [-x]P
|
||||||
let t2 = G2psi(c, P); // Ψ(P)
|
let t2 = G2psi(c, P); // Ψ(P)
|
||||||
let t3 = P.double(); // 2P
|
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)
|
return Q; // [x²-x-1]P + [x-1]Ψ(P) + Ψ²(2P)
|
||||||
},
|
},
|
||||||
fromBytes: (bytes: Uint8Array): AffinePoint<Fp2> => {
|
fromBytes: (bytes: Uint8Array): AffinePoint<Fp2> => {
|
||||||
|
bytes = bytes.slice();
|
||||||
const m_byte = bytes[0] & 0xe0;
|
const m_byte = bytes[0] & 0xe0;
|
||||||
if (m_byte === 0x20 || m_byte === 0x60 || m_byte === 0xe0) {
|
if (m_byte === 0x20 || m_byte === 0x60 || m_byte === 0xe0) {
|
||||||
throw new Error('Invalid encoding flag: ' + m_byte);
|
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 L = Fp.BYTES;
|
||||||
const slc = (b: Uint8Array, from: number, to?: number) => bytesToNumberBE(b.slice(from, to));
|
const slc = (b: Uint8Array, from: number, to?: number) => bytesToNumberBE(b.slice(from, to));
|
||||||
if (bytes.length === 96 && bitC) {
|
if (bytes.length === 96 && bitC) {
|
||||||
const { b } = bls12_381.CURVE.G2;
|
const b = bls12_381.params.G2b;
|
||||||
const P = Fp.ORDER;
|
const P = Fp.ORDER;
|
||||||
|
|
||||||
bytes[0] = bytes[0] & 0x1f; // clear flags
|
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) => {
|
toBytes: (c, point, isCompressed) => {
|
||||||
|
const { BYTES: len, ORDER: P } = Fp;
|
||||||
const isZero = point.equals(c.ZERO);
|
const isZero = point.equals(c.ZERO);
|
||||||
const { x, y } = point.toAffine();
|
const { x, y } = point.toAffine();
|
||||||
if (isCompressed) {
|
if (isCompressed) {
|
||||||
const P = Fp.ORDER;
|
if (isZero) return concatB(COMPRESSED_ZERO, numberToBytesBE(_0n, len));
|
||||||
if (isZero) return concatB(COMPRESSED_ZERO, numberToBytesBE(0n, Fp.BYTES));
|
|
||||||
const flag = Boolean(y.c1 === _0n ? (y.c0 * _2n) / P : (y.c1 * _2n) / P);
|
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?)
|
// set compressed & sign bits (looks like different offsets than for G1/Fp?)
|
||||||
let x_1 = bitSet(x.c1, C_BIT_POS, flag);
|
let x_1 = bitSet(x.c1, C_BIT_POS, flag);
|
||||||
x_1 = bitSet(x_1, S_BIT_POS, true);
|
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 {
|
} 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: x0, im: x1 } = Fp2.reim(x);
|
||||||
const { re: y0, im: y1 } = Fp2.reim(y);
|
const { re: y0, im: y1 } = Fp2.reim(y);
|
||||||
return concatB(
|
return concatB(
|
||||||
numberToBytesBE(x1, Fp.BYTES),
|
numberToBytesBE(x1, len),
|
||||||
numberToBytesBE(x0, Fp.BYTES),
|
numberToBytesBE(x0, len),
|
||||||
numberToBytesBE(y1, Fp.BYTES),
|
numberToBytesBE(y1, len),
|
||||||
numberToBytesBE(y0, Fp.BYTES)
|
numberToBytesBE(y0, len)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Signature: {
|
Signature: {
|
||||||
// TODO: Optimize, it's very slow because of sqrt.
|
// TODO: Optimize, it's very slow because of sqrt.
|
||||||
decode(hex: Hex): ProjPointType<Fp2> {
|
fromHex(hex: Hex): ProjPointType<Fp2> {
|
||||||
hex = ensureBytes('signatureHex', hex);
|
hex = ensureBytes('signatureHex', hex);
|
||||||
const P = Fp.ORDER;
|
const P = Fp.ORDER;
|
||||||
const half = hex.length / 2;
|
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 x1 = Fp.create(z1 & Fp.MASK);
|
||||||
const x2 = Fp.create(z2);
|
const x2 = Fp.create(z2);
|
||||||
const x = Fp2.create({ c0: x2, c1: x1 });
|
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
|
// The slow part
|
||||||
let y = Fp2.sqrt(y2);
|
let y = Fp2.sqrt(y2);
|
||||||
if (!y) throw new Error('Failed to find a square root');
|
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();
|
point.assertValidity();
|
||||||
return point;
|
return point;
|
||||||
},
|
},
|
||||||
encode(point: ProjPointType<Fp2>) {
|
toRawBytes(point: ProjPointType<Fp2>) {
|
||||||
// NOTE: by some reasons it was missed in bls12-381, looks like bug
|
return signatureG2ToRawBytes(point);
|
||||||
point.assertValidity();
|
},
|
||||||
if (point.equals(bls12_381.G2.ProjectivePoint.ZERO))
|
toHex(point: ProjPointType<Fp2>) {
|
||||||
return concatB(COMPRESSED_ZERO, numberToBytesBE(0n, Fp.BYTES));
|
return bytesToHex(signatureG2ToRawBytes(point));
|
||||||
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));
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// The BLS parameter x for BLS12-381
|
params: {
|
||||||
x: BLS_X,
|
x: BLS_X, // The BLS parameter x for BLS12-381
|
||||||
|
r: Fr.ORDER, // order; z⁴ − z² + 1; CURVE.n from other curves
|
||||||
|
},
|
||||||
htfDefaults,
|
htfDefaults,
|
||||||
hash: sha256,
|
hash: sha256,
|
||||||
randomBytes,
|
randomBytes,
|
||||||
|
@ -30,19 +30,19 @@ const FIELDS = {
|
|||||||
pallas: { Fp: [pallas.CURVE.Fp] },
|
pallas: { Fp: [pallas.CURVE.Fp] },
|
||||||
vesta: { Fp: [vesta.CURVE.Fp] },
|
vesta: { Fp: [vesta.CURVE.Fp] },
|
||||||
bls12: {
|
bls12: {
|
||||||
Fp: [bls12_381.CURVE.Fp],
|
Fp: [bls12_381.fields.Fp],
|
||||||
Fp2: [
|
Fp2: [
|
||||||
bls12_381.CURVE.Fp2,
|
bls12_381.fields.Fp2,
|
||||||
fc.array(fc.bigInt(1n, bls12_381.CURVE.Fp.ORDER - 1n), {
|
fc.array(fc.bigInt(1n, bls12_381.fields.Fp.ORDER - 1n), {
|
||||||
minLength: 2,
|
minLength: 2,
|
||||||
maxLength: 2,
|
maxLength: 2,
|
||||||
}),
|
}),
|
||||||
(Fp2, num) => Fp2.fromBigTuple([num[0], num[1]]),
|
(Fp2, num) => Fp2.fromBigTuple([num[0], num[1]]),
|
||||||
],
|
],
|
||||||
// Fp6: [bls12_381.CURVE.Fp6],
|
// Fp6: [bls12_381.fields.Fp6],
|
||||||
Fp12: [
|
Fp12: [
|
||||||
bls12_381.CURVE.Fp12,
|
bls12_381.fields.Fp12,
|
||||||
fc.array(fc.bigInt(1n, bls12_381.CURVE.Fp.ORDER - 1n), {
|
fc.array(fc.bigInt(1n, bls12_381.fields.Fp.ORDER - 1n), {
|
||||||
minLength: 12,
|
minLength: 12,
|
||||||
maxLength: 12,
|
maxLength: 12,
|
||||||
}),
|
}),
|
||||||
@ -221,7 +221,7 @@ for (const c in FIELDS) {
|
|||||||
|
|
||||||
const isSquare = mod.FpIsSquare(Fp);
|
const isSquare = mod.FpIsSquare(Fp);
|
||||||
// Not implemented
|
// Not implemented
|
||||||
if (Fp !== bls12_381.CURVE.Fp12) {
|
if (Fp !== bls12_381.fields.Fp12) {
|
||||||
should('multiply/sqrt', () => {
|
should('multiply/sqrt', () => {
|
||||||
fc.assert(
|
fc.assert(
|
||||||
fc.property(FC_BIGINT, (num) => {
|
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
|
const NUM_RUNS = Number(process.env.RUNS_COUNT || 10); // reduce to 1 to shorten test time
|
||||||
fc.configureGlobal({ numRuns: NUM_RUNS });
|
fc.configureGlobal({ numRuns: NUM_RUNS });
|
||||||
|
|
||||||
const { Fp2 } = bls;
|
|
||||||
const G1Point = bls.G1.ProjectivePoint;
|
const G1Point = bls.G1.ProjectivePoint;
|
||||||
const G2Point = bls.G2.ProjectivePoint;
|
const G2Point = bls.G2.ProjectivePoint;
|
||||||
const G1Aff = (x, y) => G1Point.fromAffine({ x, y });
|
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 = fc.hexaString({ minLength: 64, maxLength: 64 });
|
||||||
const FC_MSG_5 = fc.array(FC_MSG, { minLength: 5, maxLength: 5 });
|
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) {
|
function equal(a, b, comment) {
|
||||||
deepStrictEqual(a.equals(b), true, `eq(${comment})`);
|
deepStrictEqual(a.equals(b), true, `eq(${comment})`);
|
||||||
}
|
}
|
||||||
|
const { Fp, Fp2 } = bls.fields;
|
||||||
|
|
||||||
// Fp
|
// Fp
|
||||||
describe('bls12-381 Fp', () => {
|
describe('bls12-381 Fp', () => {
|
||||||
const Fp = bls.Fp;
|
|
||||||
const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n);
|
const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n);
|
||||||
|
|
||||||
should('multiply/sqrt', () => {
|
should('multiply/sqrt', () => {
|
||||||
@ -60,8 +59,7 @@ describe('bls12-381 Fp', () => {
|
|||||||
|
|
||||||
// Fp2
|
// Fp2
|
||||||
describe('bls12-381 Fp2', () => {
|
describe('bls12-381 Fp2', () => {
|
||||||
const Fp = bls.Fp;
|
const { Fp, Fp2 } = bls.fields;
|
||||||
const Fp2 = bls.Fp2;
|
|
||||||
const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n);
|
const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n);
|
||||||
const FC_BIGINT_2 = fc.array(FC_BIGINT, { minLength: 2, maxLength: 2 });
|
const FC_BIGINT_2 = fc.array(FC_BIGINT, { minLength: 2, maxLength: 2 });
|
||||||
|
|
||||||
@ -149,7 +147,7 @@ describe('bls12-381 Fp2', () => {
|
|||||||
|
|
||||||
// Point
|
// Point
|
||||||
describe('bls12-381 Point', () => {
|
describe('bls12-381 Point', () => {
|
||||||
const Fp = bls.Fp;
|
const { Fp } = bls.fields;
|
||||||
const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n);
|
const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n);
|
||||||
const PointG1 = G1Point;
|
const PointG1 = G1Point;
|
||||||
const PointG2 = G2Point;
|
const PointG2 = G2Point;
|
||||||
@ -557,9 +555,12 @@ describe('bls12-381 Point', () => {
|
|||||||
];
|
];
|
||||||
// Use wNAF allow scalars higher than CURVE.r
|
// Use wNAF allow scalars higher than CURVE.r
|
||||||
const w = wNAF(G2Point, 1);
|
const w = wNAF(G2Point, 1);
|
||||||
|
const hEff = BigInt(
|
||||||
|
'0xbc69f08f2ee75b3584c6a0ea91b352888e2a8e9145ad7689986ff031508ffe1329c2f178731db956d82bf015d1212b02ec0ec69d7477c1ae954cbc06689f6a359894c0adebbf6b4e8020005aaa95551'
|
||||||
|
);
|
||||||
for (let p of points) {
|
for (let p of points) {
|
||||||
const ours = p.clearCofactor();
|
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');
|
deepStrictEqual(ours.equals(shouldBe), true, 'clearLast');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -577,12 +578,12 @@ describe('bls12-381/basic', () => {
|
|||||||
deepStrictEqual(g1.x, G1Point.ZERO.x);
|
deepStrictEqual(g1.x, G1Point.ZERO.x);
|
||||||
deepStrictEqual(g1.y, G1Point.ZERO.y);
|
deepStrictEqual(g1.y, G1Point.ZERO.y);
|
||||||
// Test Non-Zero
|
// Test Non-Zero
|
||||||
const x = bls.Fp.create(
|
const x = Fp.create(
|
||||||
BigInt(
|
BigInt(
|
||||||
'0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb'
|
'0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const y = bls.Fp.create(
|
const y = Fp.create(
|
||||||
BigInt(
|
BigInt(
|
||||||
'0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1'
|
'0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1'
|
||||||
)
|
)
|
||||||
@ -603,12 +604,12 @@ describe('bls12-381/basic', () => {
|
|||||||
deepStrictEqual(g1.x, G1Point.ZERO.x);
|
deepStrictEqual(g1.x, G1Point.ZERO.x);
|
||||||
deepStrictEqual(g1.y, G1Point.ZERO.y);
|
deepStrictEqual(g1.y, G1Point.ZERO.y);
|
||||||
// Test Non-Zero
|
// Test Non-Zero
|
||||||
const x = bls.Fp.create(
|
const x = Fp.create(
|
||||||
BigInt(
|
BigInt(
|
||||||
'0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb'
|
'0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const y = bls.Fp.create(
|
const y = Fp.create(
|
||||||
BigInt(
|
BigInt(
|
||||||
'0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1'
|
'0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1'
|
||||||
)
|
)
|
||||||
@ -689,12 +690,12 @@ describe('bls12-381/basic', () => {
|
|||||||
// Test Zero
|
// Test Zero
|
||||||
deepStrictEqual(G1Point.ZERO.toHex(false), B_192_40);
|
deepStrictEqual(G1Point.ZERO.toHex(false), B_192_40);
|
||||||
// Test Non-Zero
|
// Test Non-Zero
|
||||||
const x = bls.Fp.create(
|
const x = Fp.create(
|
||||||
BigInt(
|
BigInt(
|
||||||
'0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb'
|
'0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const y = bls.Fp.create(
|
const y = Fp.create(
|
||||||
BigInt(
|
BigInt(
|
||||||
'0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1'
|
'0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1'
|
||||||
)
|
)
|
||||||
@ -710,12 +711,12 @@ describe('bls12-381/basic', () => {
|
|||||||
// Test Zero
|
// Test Zero
|
||||||
deepStrictEqual(G1Point.ZERO.toHex(false), B_192_40);
|
deepStrictEqual(G1Point.ZERO.toHex(false), B_192_40);
|
||||||
// Test Non-Zero
|
// Test Non-Zero
|
||||||
const x = bls.Fp.create(
|
const x = Fp.create(
|
||||||
BigInt(
|
BigInt(
|
||||||
'0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb'
|
'0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const y = bls.Fp.create(
|
const y = Fp.create(
|
||||||
BigInt(
|
BigInt(
|
||||||
'0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1'
|
'0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1'
|
||||||
)
|
)
|
||||||
@ -801,32 +802,32 @@ describe('bls12-381/basic', () => {
|
|||||||
throws(() => G2Point.fromPrivateKey(0n));
|
throws(() => G2Point.fromPrivateKey(0n));
|
||||||
});
|
});
|
||||||
const VALID_G1 = new G1Point(
|
const VALID_G1 = new G1Point(
|
||||||
bls.Fp.create(
|
Fp.create(
|
||||||
3609742242174788176010452839163620388872641749536604986743596621604118973777515189035770461528205168143692110933639n
|
3609742242174788176010452839163620388872641749536604986743596621604118973777515189035770461528205168143692110933639n
|
||||||
),
|
),
|
||||||
bls.Fp.create(
|
Fp.create(
|
||||||
1619277690257184054444116778047375363103842303863153349133480657158810226683757397206929105479676799650932070320089n
|
1619277690257184054444116778047375363103842303863153349133480657158810226683757397206929105479676799650932070320089n
|
||||||
),
|
),
|
||||||
bls.Fp.create(1n)
|
Fp.create(1n)
|
||||||
);
|
);
|
||||||
const VALID_G1_2 = new G1Point(
|
const VALID_G1_2 = new G1Point(
|
||||||
bls.Fp.create(
|
Fp.create(
|
||||||
1206972466279728255044019580914616126536509750250979180256809997983196363639429409634110400978470384566664128085207n
|
1206972466279728255044019580914616126536509750250979180256809997983196363639429409634110400978470384566664128085207n
|
||||||
),
|
),
|
||||||
bls.Fp.create(
|
Fp.create(
|
||||||
2991142246317096160788653339959532007292638191110818490939476869616372888657136539642598243964263069435065725313423n
|
2991142246317096160788653339959532007292638191110818490939476869616372888657136539642598243964263069435065725313423n
|
||||||
),
|
),
|
||||||
bls.Fp.create(1n)
|
Fp.create(1n)
|
||||||
);
|
);
|
||||||
|
|
||||||
const INVALID_G1 = new G1Point(
|
const INVALID_G1 = new G1Point(
|
||||||
bls.Fp.create(
|
Fp.create(
|
||||||
499001545268060011619089734015590154568173930614466321429631711131511181286230338880376679848890024401335766847607n
|
499001545268060011619089734015590154568173930614466321429631711131511181286230338880376679848890024401335766847607n
|
||||||
),
|
),
|
||||||
bls.Fp.create(
|
Fp.create(
|
||||||
3934582309586258715640230772291917282844636728991757779640464479794033391537662970190753981664259511166946374555673n
|
3934582309586258715640230772291917282844636728991757779640464479794033391537662970190753981664259511166946374555673n
|
||||||
),
|
),
|
||||||
bls.Fp.create(1n)
|
Fp.create(1n)
|
||||||
);
|
);
|
||||||
|
|
||||||
should('aggregate pubkeys', () => {
|
should('aggregate pubkeys', () => {
|
||||||
@ -855,7 +856,7 @@ describe('bls12-381/basic', () => {
|
|||||||
});
|
});
|
||||||
should(`produce correct scalars (${SCALAR_VECTORS.length} vectors)`, () => {
|
should(`produce correct scalars (${SCALAR_VECTORS.length} vectors)`, () => {
|
||||||
const options = {
|
const options = {
|
||||||
p: bls.CURVE.r,
|
p: bls.params.r,
|
||||||
m: 1,
|
m: 1,
|
||||||
expand: undefined,
|
expand: undefined,
|
||||||
};
|
};
|
||||||
@ -863,7 +864,7 @@ describe('bls12-381/basic', () => {
|
|||||||
const [okmAscii, expectedHex] = vector;
|
const [okmAscii, expectedHex] = vector;
|
||||||
const expected = BigInt('0x' + expectedHex);
|
const expected = BigInt('0x' + expectedHex);
|
||||||
const okm = utf8ToBytes(okmAscii);
|
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);
|
deepStrictEqual(scalars[0][0], expected);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -871,7 +872,8 @@ describe('bls12-381/basic', () => {
|
|||||||
|
|
||||||
// Pairing
|
// Pairing
|
||||||
describe('pairing', () => {
|
describe('pairing', () => {
|
||||||
const { pairing, Fp12 } = bls;
|
const { pairing } = bls;
|
||||||
|
const { Fp12 } = bls.fields;
|
||||||
const G1 = G1Point.BASE;
|
const G1 = G1Point.BASE;
|
||||||
const G2 = G2Point.BASE;
|
const G2 = G2Point.BASE;
|
||||||
|
|
||||||
@ -1000,7 +1002,7 @@ describe('hash-to-curve', () => {
|
|||||||
];
|
];
|
||||||
for (let i = 0; i < VECTORS_G1.length; i++) {
|
for (let i = 0; i < VECTORS_G1.length; i++) {
|
||||||
const t = VECTORS_G1[i];
|
const t = VECTORS_G1[i];
|
||||||
should(`hashToCurve/G1 Killic (${i})`, () => {
|
should(`G1 Killic (${i})`, () => {
|
||||||
const p = bls.G1.hashToCurve(t.msg, {
|
const p = bls.G1.hashToCurve(t.msg, {
|
||||||
DST: 'BLS12381G1_XMD:SHA-256_SSWU_RO_TESTGEN',
|
DST: 'BLS12381G1_XMD:SHA-256_SSWU_RO_TESTGEN',
|
||||||
});
|
});
|
||||||
@ -1259,7 +1261,7 @@ describe('bls12-381 deterministic', () => {
|
|||||||
.reverse()
|
.reverse()
|
||||||
.reduce((acc, i) => acc + i);
|
.reduce((acc, i) => acc + i);
|
||||||
|
|
||||||
const Fp12 = bls.Fp12;
|
const { Fp12 } = bls.fields;
|
||||||
|
|
||||||
should('Killic based/Pairing', () => {
|
should('Killic based/Pairing', () => {
|
||||||
const t = bls.pairing(G1Point.BASE, G2Point.BASE);
|
const t = bls.pairing(G1Point.BASE, G2Point.BASE);
|
||||||
|
Loading…
Reference in New Issue
Block a user