@ethereumjs/evm
Version:
JavaScript Ethereum Virtual Machine (EVM) implementation
274 lines • 12.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MCLBLS = void 0;
exports.BLS12_381_FromG1Point = BLS12_381_FromG1Point;
exports.BLS12_381_FromG2Point = BLS12_381_FromG2Point;
exports.BLS12_381_ToFp2Point = BLS12_381_ToFp2Point;
exports.BLS12_381_ToFpPoint = BLS12_381_ToFpPoint;
exports.BLS12_381_ToFrPoint = BLS12_381_ToFrPoint;
exports.BLS12_381_ToG1Point = BLS12_381_ToG1Point;
exports.BLS12_381_ToG2Point = BLS12_381_ToG2Point;
const util_1 = require("@ethereumjs/util");
const errors_ts_1 = require("../../errors.js");
const constants_ts_1 = require("./constants.js");
/**
* Converts an Uint8Array to a MCL G1 point. Raises errors if the point is not on the curve
* and (if activated) if the point is in the subgroup / order check.
* @param input Input Uint8Array. Should be 128 bytes
* @param mcl MCL instance
* @param verifyOrder Perform the subgroup check (defaults to true)
* @returns MCL G1 point
*/
function BLS12_381_ToG1Point(input, mcl, verifyOrder = true) {
if ((0, util_1.equalsBytes)(input, constants_ts_1.BLS_G1_INFINITY_POINT_BYTES)) {
return new mcl.G1();
}
const p_x = (0, util_1.bytesToUnprefixedHex)(input.subarray(16, constants_ts_1.BLS_G1_POINT_BYTE_LENGTH / 2));
const p_y = (0, util_1.bytesToUnprefixedHex)(input.subarray(80, constants_ts_1.BLS_G1_POINT_BYTE_LENGTH));
const G1 = new mcl.G1();
const Fp_X = new mcl.Fp();
const Fp_Y = new mcl.Fp();
const One = new mcl.Fp();
Fp_X.setStr(p_x, 16);
Fp_Y.setStr(p_y, 16);
One.setStr('1', 16);
G1.setX(Fp_X);
G1.setY(Fp_Y);
G1.setZ(One);
mcl.verifyOrderG1(verifyOrder);
if (verifyOrder && G1.isValidOrder() === false) {
throw new errors_ts_1.EVMError(errors_ts_1.EVMError.errorMessages.BLS_12_381_POINT_NOT_ON_CURVE);
}
// Check if these coordinates are actually on the curve.
if (G1.isValid() === false) {
throw new errors_ts_1.EVMError(errors_ts_1.EVMError.errorMessages.BLS_12_381_POINT_NOT_ON_CURVE);
}
return G1;
}
// input: a mcl G1 point
// output: a 128-byte Uint8Array
function BLS12_381_FromG1Point(input) {
// TODO: figure out if there is a better way to decode these values.
const decodeStr = input.getStr(16); //return a string of pattern "1 <x_coord> <y_coord>"
const decoded = decodeStr.match(/"?[0-9a-f]+"?/g); // match above pattern.
if (decodeStr === '0') {
return new Uint8Array(constants_ts_1.BLS_G1_POINT_BYTE_LENGTH);
}
const xBytes = (0, util_1.setLengthLeft)((0, util_1.unprefixedHexToBytes)(decoded[1]), 64);
const yBytes = (0, util_1.setLengthLeft)((0, util_1.unprefixedHexToBytes)(decoded[2]), 64);
return (0, util_1.concatBytes)(xBytes, yBytes);
}
// input: two 64-byte buffers
// output: a mcl Fp2 point
function BLS12_381_ToFp2Point(fpXCoordinate, fpYCoordinate, mcl) {
// check if the coordinates are in the field
if ((0, util_1.bytesToBigInt)(fpXCoordinate) >= constants_ts_1.BLS_FIELD_MODULUS) {
throw new errors_ts_1.EVMError(errors_ts_1.EVMError.errorMessages.BLS_12_381_FP_NOT_IN_FIELD);
}
if ((0, util_1.bytesToBigInt)(fpYCoordinate) >= constants_ts_1.BLS_FIELD_MODULUS) {
throw new errors_ts_1.EVMError(errors_ts_1.EVMError.errorMessages.BLS_12_381_FP_NOT_IN_FIELD);
}
const fp_x = new mcl.Fp();
const fp_y = new mcl.Fp();
const fp2 = new mcl.Fp2();
fp_x.setStr((0, util_1.bytesToUnprefixedHex)(fpXCoordinate.subarray(16)), 16);
fp_y.setStr((0, util_1.bytesToUnprefixedHex)(fpYCoordinate.subarray(16)), 16);
fp2.set_a(fp_x);
fp2.set_b(fp_y);
return fp2;
}
/**
* Converts an Uint8Array to a MCL G2 point. Raises errors if the point is not on the curve
* and (if activated) if the point is in the subgroup / order check.
* @param input Input Uint8Array. Should be 256 bytes
* @param mcl MCL instance
* @param verifyOrder Perform the subgroup check (defaults to true)
* @returns MCL G2 point
*/
function BLS12_381_ToG2Point(input, mcl, verifyOrder = true) {
if ((0, util_1.equalsBytes)(input, constants_ts_1.BLS_G2_INFINITY_POINT_BYTES)) {
return new mcl.G2();
}
const p_x_1 = input.subarray(0, 64);
const p_x_2 = input.subarray(64, constants_ts_1.BLS_G2_POINT_BYTE_LENGTH / 2);
const p_y_1 = input.subarray(128, 192);
const p_y_2 = input.subarray(192, constants_ts_1.BLS_G2_POINT_BYTE_LENGTH);
const Fp2X = BLS12_381_ToFp2Point(p_x_1, p_x_2, mcl);
const Fp2Y = BLS12_381_ToFp2Point(p_y_1, p_y_2, mcl);
const FpOne = new mcl.Fp();
FpOne.setStr('1', 16);
const FpZero = new mcl.Fp();
FpZero.setStr('0', 16);
const Fp2One = new mcl.Fp2();
Fp2One.set_a(FpOne);
Fp2One.set_b(FpZero);
const p = new mcl.G2();
p.setX(Fp2X);
p.setY(Fp2Y);
p.setZ(Fp2One);
mcl.verifyOrderG2(verifyOrder);
if (verifyOrder && p.isValidOrder() === false) {
throw new errors_ts_1.EVMError(errors_ts_1.EVMError.errorMessages.BLS_12_381_POINT_NOT_ON_CURVE);
}
if (p.isValid() === false) {
throw new errors_ts_1.EVMError(errors_ts_1.EVMError.errorMessages.BLS_12_381_POINT_NOT_ON_CURVE);
}
return p;
}
// input: a mcl G2 point
// output: a 256-byte Uint8Array
function BLS12_381_FromG2Point(input) {
// TODO: figure out if there is a better way to decode these values.
const decodeStr = input.getStr(16); //return a string of pattern "1 <x_coord_1> <x_coord_2> <y_coord_1> <y_coord_2>"
if (decodeStr === '0') {
return new Uint8Array(constants_ts_1.BLS_G2_POINT_BYTE_LENGTH);
}
const decoded = decodeStr.match(/"?[0-9a-f]+"?/g); // match above pattern.
const xBytes1 = (0, util_1.setLengthLeft)((0, util_1.unprefixedHexToBytes)(decoded[1]), 64);
const xBytes2 = (0, util_1.setLengthLeft)((0, util_1.unprefixedHexToBytes)(decoded[2]), 64);
const yBytes1 = (0, util_1.setLengthLeft)((0, util_1.unprefixedHexToBytes)(decoded[3]), 64);
const yBytes2 = (0, util_1.setLengthLeft)((0, util_1.unprefixedHexToBytes)(decoded[4]), 64);
return (0, util_1.concatBytes)(xBytes1, xBytes2, yBytes1, yBytes2);
}
// input: a 32-byte hex scalar Uint8Array
// output: a mcl Fr point
function BLS12_381_ToFrPoint(input, mcl) {
const mclHex = mcl.fromHexStr((0, util_1.bytesToUnprefixedHex)(input));
const Fr = new mcl.Fr();
Fr.setBigEndianMod(mclHex);
return Fr;
}
// input: a 64-byte buffer
// output: a mcl Fp point
function BLS12_381_ToFpPoint(fpCoordinate, mcl) {
// check if point is in field
if ((0, util_1.bytesToBigInt)(fpCoordinate) >= constants_ts_1.BLS_FIELD_MODULUS) {
throw new errors_ts_1.EVMError(errors_ts_1.EVMError.errorMessages.BLS_12_381_FP_NOT_IN_FIELD);
}
const fp = new mcl.Fp();
fp.setBigEndianMod(mcl.fromHexStr((0, util_1.bytesToUnprefixedHex)(fpCoordinate)));
return fp;
}
/**
* Implementation of the `EVMBLSInterface` using the `mcl-wasm` WASM `mcl` wrapper library,
* see https://github.com/herumi/mcl-wasm.
*
* This can be optionally used to replace the build-in Noble implementation (`NobleBLS`) with
* a more performant WASM variant. See EVM `bls` constructor option on how to use.
*/
class MCLBLS {
constructor(mcl) {
this._mcl = mcl;
}
init() {
this._mcl.setMapToMode(this._mcl.IRTF); // set the right map mode; otherwise mapToG2 will return wrong values.
this._mcl.verifyOrderG1(true); // subgroup checks for G1
this._mcl.verifyOrderG2(true); // subgroup checks for G2
}
addG1(input) {
// convert input to G1 points, add them, and convert the output to a Uint8Array.
const p1 = BLS12_381_ToG1Point(input.subarray(0, constants_ts_1.BLS_G1_POINT_BYTE_LENGTH), this._mcl, false);
const p2 = BLS12_381_ToG1Point(input.subarray(constants_ts_1.BLS_G1_POINT_BYTE_LENGTH, constants_ts_1.BLS_G1_POINT_BYTE_LENGTH * 2), this._mcl, false);
const result = this._mcl.add(p1, p2);
return BLS12_381_FromG1Point(result);
}
mulG1(input) {
// convert input to G1 points, add them, and convert the output to a Uint8Array.
const p = BLS12_381_ToG1Point(input.subarray(0, constants_ts_1.BLS_G1_POINT_BYTE_LENGTH), this._mcl);
const frPoint = BLS12_381_ToFrPoint(input.subarray(constants_ts_1.BLS_G1_POINT_BYTE_LENGTH, 160), this._mcl);
const result = this._mcl.mul(p, frPoint);
return BLS12_381_FromG1Point(result);
}
addG2(input) {
// convert input to G1 points, add them, and convert the output to a Uint8Array.
const p1 = BLS12_381_ToG2Point(input.subarray(0, constants_ts_1.BLS_G2_POINT_BYTE_LENGTH), this._mcl, false);
const p2 = BLS12_381_ToG2Point(input.subarray(constants_ts_1.BLS_G2_POINT_BYTE_LENGTH, constants_ts_1.BLS_G2_POINT_BYTE_LENGTH * 2), this._mcl, false);
const result = this._mcl.add(p1, p2);
return BLS12_381_FromG2Point(result);
}
mulG2(input) {
// convert input to G2 point/Fr point, add them, and convert the output to a Uint8Array.
const p = BLS12_381_ToG2Point(input.subarray(0, constants_ts_1.BLS_G2_POINT_BYTE_LENGTH), this._mcl);
const frPoint = BLS12_381_ToFrPoint(input.subarray(constants_ts_1.BLS_G2_POINT_BYTE_LENGTH, 288), this._mcl);
const result = this._mcl.mul(p, frPoint);
return BLS12_381_FromG2Point(result);
}
mapFPtoG1(input) {
// convert input to Fp1 point
const Fp1Point = BLS12_381_ToFpPoint(input.subarray(0, 64), this._mcl);
// map it to G1
const result = Fp1Point.mapToG1();
return BLS12_381_FromG1Point(result);
}
mapFP2toG2(input) {
// convert input to Fp2 point
const Fp2Point = BLS12_381_ToFp2Point(input.subarray(0, 64), input.subarray(64, 128), this._mcl);
// map it to G2
const result = Fp2Point.mapToG2();
return BLS12_381_FromG2Point(result);
}
msmG1(input) {
const pairLength = 160;
const numPairs = input.length / pairLength;
const G1Array = [];
const FrArray = [];
for (let k = 0; k < numPairs; k++) {
const pairStart = pairLength * k;
const G1 = BLS12_381_ToG1Point(input.subarray(pairStart, pairStart + constants_ts_1.BLS_G1_POINT_BYTE_LENGTH), this._mcl);
const Fr = BLS12_381_ToFrPoint(input.subarray(pairStart + constants_ts_1.BLS_G1_POINT_BYTE_LENGTH, pairStart + pairLength), this._mcl);
G1Array.push(G1);
FrArray.push(Fr);
}
const result = this._mcl.mulVec(G1Array, FrArray);
return BLS12_381_FromG1Point(result);
}
msmG2(input) {
const pairLength = 288;
const numPairs = input.length / pairLength;
const G2Array = [];
const FrArray = [];
for (let k = 0; k < numPairs; k++) {
const pairStart = pairLength * k;
const G2 = BLS12_381_ToG2Point(input.subarray(pairStart, pairStart + constants_ts_1.BLS_G2_POINT_BYTE_LENGTH), this._mcl);
const Fr = BLS12_381_ToFrPoint(input.subarray(pairStart + constants_ts_1.BLS_G2_POINT_BYTE_LENGTH, pairStart + pairLength), this._mcl);
G2Array.push(G2);
FrArray.push(Fr);
}
const result = this._mcl.mulVec(G2Array, FrArray);
return BLS12_381_FromG2Point(result);
}
pairingCheck(input) {
const pairLength = 384;
const pairs = [];
for (let k = 0; k < input.length / pairLength; k++) {
const pairStart = pairLength * k;
const G1 = BLS12_381_ToG1Point(input.subarray(pairStart, pairStart + constants_ts_1.BLS_G1_POINT_BYTE_LENGTH), this._mcl);
const g2start = pairStart + constants_ts_1.BLS_G1_POINT_BYTE_LENGTH;
const G2 = BLS12_381_ToG2Point(input.subarray(g2start, g2start + constants_ts_1.BLS_G2_POINT_BYTE_LENGTH), this._mcl);
pairs.push([G1, G2]);
}
// run the pairing check
// reference (Nethermind): https://github.com/NethermindEth/nethermind/blob/374b036414722b9c8ad27e93d64840b8f63931b9/src/Nethermind/Nethermind.Evm/Precompiles/Bls/Mcl/PairingPrecompile.cs#L93
let GT;
for (let index = 0; index < pairs.length; index++) {
const pair = pairs[index];
const G1 = pair[0];
const G2 = pair[1];
if (index === 0) {
GT = this._mcl.millerLoop(G1, G2);
}
else {
GT = this._mcl.mul(GT, this._mcl.millerLoop(G1, G2));
}
}
GT = this._mcl.finalExp(GT);
if (GT.isOne() === true) {
return constants_ts_1.BLS_ONE_BUFFER;
}
else {
return constants_ts_1.BLS_ZERO_BUFFER;
}
}
}
exports.MCLBLS = MCLBLS;
//# sourceMappingURL=mcl.js.map