diff --git a/README.md b/README.md index 1235dbb..b1526a5 100644 --- a/README.md +++ b/README.md @@ -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, privateKey: PrivKey): ProjPointType; +}; +verify: ( + signature: Hex | ProjPointType, + message: Hex | ProjPointType, + publicKey: Hex | ProjPointType +) => boolean; +verifyBatch: ( + signature: Hex | ProjPointType, + messages: (Hex | ProjPointType)[], + publicKeys: (Hex | ProjPointType)[] +) => boolean; +aggregatePublicKeys: { + (publicKeys: Hex[]): Uint8Array; + (publicKeys: ProjPointType[]): ProjPointType; +}; +aggregateSignatures: { + (signatures: Hex[]): Uint8Array; + (signatures: ProjPointType[]): ProjPointType; +}; +millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12; +pairing: (P: ProjPointType, Q: ProjPointType, withFinalExponent?: boolean) => Fp12; +G1: CurvePointsRes & ReturnType>; +G2: CurvePointsRes & ReturnType>; +Signature: SignatureCoder; +params: { + x: bigint; + r: bigint; + G1b: bigint; + G2b: Fp2; +}; +fields: { + Fp: IField; + Fp2: IField; + Fp6: IField; + Fp12: IField; + Fr: IField; +}; +utils: { + randomPrivateKey: () => Uint8Array; + calcPairingPrecomputes: (p: AffinePoint) => [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 diff --git a/src/abstract/bls.ts b/src/abstract/bls.ts index 72584b8..7e965b2 100644 --- a/src/abstract/bls.ts +++ b/src/abstract/bls.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 = { - decode(hex: Hex): ProjPointType; - encode(point: ProjPointType): Uint8Array; + fromHex(hex: Hex): ProjPointType; + toRawBytes(point: ProjPointType): Uint8Array; + toHex(point: ProjPointType): string; }; export type CurveType = { - r: bigint; G1: Omit, 'n'> & { mapToCurve: htf.MapToCurve; htfDefaults: htf.Opts; @@ -40,20 +43,25 @@ export type CurveType = { mapToCurve: htf.MapToCurve; htfDefaults: htf.Opts; }; - x: bigint; - Fp: IField; - Fr: IField; - Fp2: IField & { - reim: (num: Fp2) => { re: bigint; im: bigint }; - multiplyByB: (num: Fp2) => Fp2; - frobeniusMap(num: Fp2, power: number): Fp2; + fields: { + Fp: IField; + Fr: IField; + Fp2: IField & { + reim: (num: Fp2) => { re: bigint; im: bigint }; + multiplyByB: (num: Fp2) => Fp2; + frobeniusMap(num: Fp2, power: number): Fp2; + }; + Fp6: IField; + Fp12: IField & { + 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; - Fp12: IField & { - 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 = { }; export type CurveFn = { - CURVE: CurveType; - Fr: IField; - Fp: IField; - Fp2: IField; - Fp6: IField; - Fp12: IField; - G1: CurvePointsRes & ReturnType>; - G2: CurvePointsRes & ReturnType>; - Signature: SignatureCoder; - millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12; - calcPairingPrecomputes: (p: AffinePoint) => [Fp2, Fp2, Fp2][]; - pairing: (P: ProjPointType, Q: ProjPointType, withFinalExponent?: boolean) => Fp12; getPublicKey: (privateKey: PrivKey) => Uint8Array; sign: { (message: Hex, privateKey: PrivKey): Uint8Array; @@ -83,6 +79,11 @@ export type CurveFn = { message: Hex | ProjPointType, publicKey: Hex | ProjPointType ) => boolean; + verifyBatch: ( + signature: Hex | ProjPointType, + messages: (Hex | ProjPointType)[], + publicKeys: (Hex | ProjPointType)[] + ) => boolean; aggregatePublicKeys: { (publicKeys: Hex[]): Uint8Array; (publicKeys: ProjPointType[]): ProjPointType; @@ -91,22 +92,36 @@ export type CurveFn = { (signatures: Hex[]): Uint8Array; (signatures: ProjPointType[]): ProjPointType; }; - verifyBatch: ( - signature: Hex | ProjPointType, - messages: (Hex | ProjPointType)[], - publicKeys: (Hex | ProjPointType)[] - ) => boolean; + millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12; + pairing: (P: ProjPointType, Q: ProjPointType, withFinalExponent?: boolean) => Fp12; + G1: CurvePointsRes & ReturnType>; + G2: CurvePointsRes & ReturnType>; + Signature: SignatureCoder; + params: { + x: bigint; + r: bigint; + G1b: bigint; + G2b: Fp2; + }; + fields: { + Fp: IField; + Fp2: IField; + Fp6: IField; + Fp12: IField; + Fr: IField; + }; utils: { randomPrivateKey: () => Uint8Array; + calcPairingPrecomputes: (p: AffinePoint) => [Fp2, Fp2, Fp2][]; }; }; export function bls( CURVE: CurveType ): CurveFn { - // 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( // 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( 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( } 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( 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( 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( 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( 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( 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, }; } diff --git a/src/abstract/utils.ts b/src/abstract/utils.ts index 20e6a3b..722e3a1 100644 --- a/src/abstract/utils.ts +++ b/src/abstract/utils.ts @@ -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)); diff --git a/src/bls12-381.ts b/src/bls12-381.ts index 99d52b3..6f4d733 100644 --- a/src/bls12-381.ts +++ b/src/bls12-381.ts @@ -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) { + // 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 = 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 = 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 = 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 => { + 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 = 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 = 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 = 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 = bls({ return Q; // [x²-x-1]P + [x-1]Ψ(P) + Ψ²(2P) }, fromBytes: (bytes: Uint8Array): AffinePoint => { + 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 = 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 = 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 { + fromHex(hex: Hex): ProjPointType { hex = ensureBytes('signatureHex', hex); const P = Fp.ORDER; const half = hex.length / 2; @@ -1319,7 +1339,7 @@ export const bls12_381: CurveFn = 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 = bls({ point.assertValidity(); return point; }, - encode(point: ProjPointType) { - // 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) { + return signatureG2ToRawBytes(point); + }, + toHex(point: ProjPointType) { + 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, diff --git a/test/basic.test.js b/test/basic.test.js index aa23467..f075766 100644 --- a/test/basic.test.js +++ b/test/basic.test.js @@ -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) => { diff --git a/test/bls12-381.test.js b/test/bls12-381.test.js index 18d1efa..adfc374 100644 --- a/test/bls12-381.test.js +++ b/test/bls12-381.test.js @@ -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); diff --git a/test/ed25519.test.js b/test/ed25519.test.js index f086349..25a5ed3 100644 --- a/test/ed25519.test.js +++ b/test/ed25519.test.js @@ -407,7 +407,7 @@ describe('ed25519', () => { ed25519.verify(sig_invalid, message, publicKey); }); }); - + should('not accept point without z, t', () => { const t = 81718630521762619991978402609047527194981150691135404693881672112315521837062n; const point = ed25519.ExtendedPoint.fromAffine({ x: t, y: t });