@tokamak-zk-evm/synthesizer
Version:
Tokamak zk-EVM Synthesizer - Processes Ethereum transactions into wire maps for Tokamak zk-SNARK proof generation
393 lines (312 loc) • 11.6 kB
text/typescript
import {
bytesToBigInt,
bytesToUnprefixedHex,
concatBytes,
equalsBytes,
setLengthLeft,
unprefixedHexToBytes,
} from "@synthesizer-libs/util"
import { ERROR, EvmError } from '../../exceptions.js'
import {
BLS_FIELD_MODULUS,
BLS_G1_INFINITY_POINT_BYTES,
BLS_G1_POINT_BYTE_LENGTH,
BLS_G2_INFINITY_POINT_BYTES,
BLS_G2_POINT_BYTE_LENGTH,
BLS_ONE_BUFFER,
BLS_ZERO_BUFFER,
} from './constants.js'
import type { EVMBLSInterface } from '../../types.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: Uint8Array, mcl: any, verifyOrder = true): any {
if (equalsBytes(input, BLS_G1_INFINITY_POINT_BYTES)) {
return new mcl.G1()
}
const p_x = bytesToUnprefixedHex(input.subarray(16, BLS_G1_POINT_BYTE_LENGTH / 2))
const p_y = bytesToUnprefixedHex(input.subarray(80, 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 EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE)
}
// Check if these coordinates are actually on the curve.
if (G1.isValid() === false) {
throw new EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE)
}
return G1
}
// input: a mcl G1 point
// output: a 128-byte Uint8Array
function BLS12_381_FromG1Point(input: any): Uint8Array {
// 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(BLS_G1_POINT_BYTE_LENGTH)
}
const xBytes = setLengthLeft(unprefixedHexToBytes(decoded[1]), 64)
const yBytes = setLengthLeft(unprefixedHexToBytes(decoded[2]), 64)
return concatBytes(xBytes, yBytes)
}
/**
* 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: Uint8Array, mcl: any, verifyOrder = true): any {
if (equalsBytes(input, BLS_G2_INFINITY_POINT_BYTES)) {
return new mcl.G2()
}
const p_x_1 = input.subarray(0, 64)
const p_x_2 = input.subarray(64, BLS_G2_POINT_BYTE_LENGTH / 2)
const p_y_1 = input.subarray(128, 192)
const p_y_2 = input.subarray(192, 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 EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE)
}
if (p.isValid() === false) {
throw new EvmError(ERROR.BLS_12_381_POINT_NOT_ON_CURVE)
}
return p
}
// input: a mcl G2 point
// output: a 256-byte Uint8Array
function BLS12_381_FromG2Point(input: any): Uint8Array {
// 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(BLS_G2_POINT_BYTE_LENGTH)
}
const decoded = decodeStr.match(/"?[0-9a-f]+"?/g) // match above pattern.
const xBytes1 = setLengthLeft(unprefixedHexToBytes(decoded[1]), 64)
const xBytes2 = setLengthLeft(unprefixedHexToBytes(decoded[2]), 64)
const yBytes1 = setLengthLeft(unprefixedHexToBytes(decoded[3]), 64)
const yBytes2 = setLengthLeft(unprefixedHexToBytes(decoded[4]), 64)
return concatBytes(xBytes1, xBytes2, yBytes1, yBytes2)
}
// input: a 32-byte hex scalar Uint8Array
// output: a mcl Fr point
function BLS12_381_ToFrPoint(input: Uint8Array, mcl: any): any {
const mclHex = mcl.fromHexStr(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: Uint8Array, mcl: any): any {
// check if point is in field
if (bytesToBigInt(fpCoordinate) >= BLS_FIELD_MODULUS) {
throw new EvmError(ERROR.BLS_12_381_FP_NOT_IN_FIELD)
}
const fp = new mcl.Fp()
fp.setBigEndianMod(mcl.fromHexStr(bytesToUnprefixedHex(fpCoordinate)))
return fp
}
// input: two 64-byte buffers
// output: a mcl Fp2 point
function BLS12_381_ToFp2Point(fpXCoordinate: Uint8Array, fpYCoordinate: Uint8Array, mcl: any): any {
// check if the coordinates are in the field
if (bytesToBigInt(fpXCoordinate) >= BLS_FIELD_MODULUS) {
throw new EvmError(ERROR.BLS_12_381_FP_NOT_IN_FIELD)
}
if (bytesToBigInt(fpYCoordinate) >= BLS_FIELD_MODULUS) {
throw new EvmError(ERROR.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(bytesToUnprefixedHex(fpXCoordinate.subarray(16)), 16)
fp_y.setStr(bytesToUnprefixedHex(fpYCoordinate.subarray(16)), 16)
fp2.set_a(fp_x)
fp2.set_b(fp_y)
return fp2
}
/**
* 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.
*/
export class MCLBLS implements EVMBLSInterface {
protected readonly _mcl: any
constructor(mcl: any) {
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: Uint8Array): Uint8Array {
// convert input to G1 points, add them, and convert the output to a Uint8Array.
const p1 = BLS12_381_ToG1Point(input.subarray(0, BLS_G1_POINT_BYTE_LENGTH), this._mcl, false)
const p2 = BLS12_381_ToG1Point(
input.subarray(BLS_G1_POINT_BYTE_LENGTH, BLS_G1_POINT_BYTE_LENGTH * 2),
this._mcl,
false,
)
const result = this._mcl.add(p1, p2)
return BLS12_381_FromG1Point(result)
}
mulG1(input: Uint8Array): Uint8Array {
// convert input to G1 points, add them, and convert the output to a Uint8Array.
const p = BLS12_381_ToG1Point(input.subarray(0, BLS_G1_POINT_BYTE_LENGTH), this._mcl)
const frPoint = BLS12_381_ToFrPoint(input.subarray(BLS_G1_POINT_BYTE_LENGTH, 160), this._mcl)
const result = this._mcl.mul(p, frPoint)
return BLS12_381_FromG1Point(result)
}
addG2(input: Uint8Array): Uint8Array {
// convert input to G1 points, add them, and convert the output to a Uint8Array.
const p1 = BLS12_381_ToG2Point(input.subarray(0, BLS_G2_POINT_BYTE_LENGTH), this._mcl, false)
const p2 = BLS12_381_ToG2Point(
input.subarray(BLS_G2_POINT_BYTE_LENGTH, BLS_G2_POINT_BYTE_LENGTH * 2),
this._mcl,
false,
)
const result = this._mcl.add(p1, p2)
return BLS12_381_FromG2Point(result)
}
mulG2(input: Uint8Array): Uint8Array {
// convert input to G2 point/Fr point, add them, and convert the output to a Uint8Array.
const p = BLS12_381_ToG2Point(input.subarray(0, BLS_G2_POINT_BYTE_LENGTH), this._mcl)
const frPoint = BLS12_381_ToFrPoint(input.subarray(BLS_G2_POINT_BYTE_LENGTH, 288), this._mcl)
const result = this._mcl.mul(p, frPoint)
return BLS12_381_FromG2Point(result)
}
mapFPtoG1(input: Uint8Array): Uint8Array {
// 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: Uint8Array): Uint8Array {
// 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: Uint8Array): Uint8Array {
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 + BLS_G1_POINT_BYTE_LENGTH),
this._mcl,
)
const Fr = BLS12_381_ToFrPoint(
input.subarray(pairStart + 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: Uint8Array): Uint8Array {
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 + BLS_G2_POINT_BYTE_LENGTH),
this._mcl,
)
const Fr = BLS12_381_ToFrPoint(
input.subarray(pairStart + 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: Uint8Array): Uint8Array {
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 + BLS_G1_POINT_BYTE_LENGTH),
this._mcl,
)
const g2start = pairStart + BLS_G1_POINT_BYTE_LENGTH
const G2 = BLS12_381_ToG2Point(
input.subarray(g2start, g2start + 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 BLS_ONE_BUFFER
} else {
return BLS_ZERO_BUFFER
}
}
}
export {
BLS12_381_FromG1Point,
BLS12_381_FromG2Point,
BLS12_381_ToFp2Point,
BLS12_381_ToFpPoint,
BLS12_381_ToFrPoint,
BLS12_381_ToG1Point,
BLS12_381_ToG2Point,
}