UNPKG

@tokamak-zk-evm/synthesizer

Version:

Tokamak zk-EVM Synthesizer - Processes Ethereum transactions into wire maps for Tokamak zk-SNARK proof generation

173 lines (141 loc) 5.09 kB
import { BIGINT_0, bigIntToBytes, bytesToBigInt, concatBytes, equalsBytes, hexToBytes, setLengthLeft, } from "@synthesizer-libs/util" import { bn254 } from 'ethereum-cryptography/bn.js' import { ERROR, EvmError } from '../../exceptions.js' import type { EVMBN254Interface } from '../../types.js' const G1_INFINITY_POINT_BYTES = new Uint8Array(64) const G2_INFINITY_POINT_BYTES = new Uint8Array(128) const G1_POINT_BYTE_LENGTH = 64 const G1_ELEMENT_BYTE_LENGTH = 32 const G2_POINT_BYTE_LENGTH = 128 const ZERO_BUFFER = new Uint8Array(32) const ONE_BUFFER = concatBytes(new Uint8Array(31), hexToBytes('0x01')) // Copied from @noble/curves/abstract/curve.ts (not exported in ethereum-cryptography) export type AffinePoint<T> = { x: T y: T } & { z?: never; t?: never } /** * Converts an Uint8Array to a Noble G1 point. * @param input Input Uint8Array. Should be 64 bytes * @returns Noble G1 point */ function toG1Point(input: Uint8Array) { if (equalsBytes(input, G1_INFINITY_POINT_BYTES)) { return bn254.G1.ProjectivePoint.ZERO } const x = bytesToBigInt(input.subarray(0, G1_ELEMENT_BYTE_LENGTH)) const y = bytesToBigInt(input.subarray(G1_ELEMENT_BYTE_LENGTH, G1_POINT_BYTE_LENGTH)) const G1 = bn254.G1.ProjectivePoint.fromAffine({ x, y, }) G1.assertValidity() return G1 } function fromG1Point(input: AffinePoint<bigint>): Uint8Array { const xBytes = setLengthLeft(bigIntToBytes(input.x), G1_ELEMENT_BYTE_LENGTH) const yBytes = setLengthLeft(bigIntToBytes(input.y), G1_ELEMENT_BYTE_LENGTH) return concatBytes(xBytes, yBytes) } // input: a 32-byte hex scalar Uint8Array // output: a Noble Fr point function toFrPoint(input: Uint8Array): bigint { const Fr = bn254.fields.Fr.fromBytes(input) if (Fr >= bn254.fields.Fr.ORDER) { return Fr % bn254.fields.Fr.ORDER } return Fr } /** * Converts an Uint8Array to a Noble 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 * @returns Noble G2 point */ function toG2Point(input: Uint8Array) { if (equalsBytes(input, G2_INFINITY_POINT_BYTES)) { return bn254.G2.ProjectivePoint.ZERO } const p_x_2 = input.subarray(0, G1_ELEMENT_BYTE_LENGTH) const p_x_1 = input.subarray(G1_ELEMENT_BYTE_LENGTH, G1_ELEMENT_BYTE_LENGTH * 2) const start2 = G1_ELEMENT_BYTE_LENGTH * 2 const p_y_2 = input.subarray(start2, start2 + G1_ELEMENT_BYTE_LENGTH) const p_y_1 = input.subarray(start2 + G1_ELEMENT_BYTE_LENGTH, start2 + G1_ELEMENT_BYTE_LENGTH * 2) for (const p of [p_x_1, p_x_2, p_y_1, p_y_2]) { const pB = bytesToBigInt(p) if (bn254.fields.Fp.create(pB) !== pB) { throw new EvmError(ERROR.BN254_FP_NOT_IN_FIELD) } } const Fp2X = toFp2Point(p_x_1, p_x_2) const Fp2Y = toFp2Point(p_y_1, p_y_2) const pG2 = bn254.G2.ProjectivePoint.fromAffine({ x: Fp2X, y: Fp2Y, }) pG2.assertValidity() return pG2 } function toFp2Point(fpXCoordinate: Uint8Array, fpYCoordinate: Uint8Array) { if (bytesToBigInt(fpXCoordinate) >= bn254.fields.Fp2.ORDER) { throw new EvmError(ERROR.BN254_FP_NOT_IN_FIELD) } if (bytesToBigInt(fpYCoordinate) >= bn254.fields.Fp2.ORDER) { throw new EvmError(ERROR.BN254_FP_NOT_IN_FIELD) } const fpBytes = concatBytes(fpXCoordinate, fpYCoordinate) const FP = bn254.fields.Fp2.fromBytes(fpBytes) return FP } /** * Implementation of the `EVMBN254Interface` using the `ethereum-cryptography (`@noble/curves`) * JS library, see https://github.com/ethereum/js-ethereum-cryptography. * * This is the EVM default implementation. */ export class NobleBN254 implements EVMBN254Interface { add(input: Uint8Array): Uint8Array { const p1 = toG1Point(input.slice(0, G1_POINT_BYTE_LENGTH)) const p2 = toG1Point(input.slice(G1_POINT_BYTE_LENGTH, G1_POINT_BYTE_LENGTH * 2)) const result = fromG1Point(p1.add(p2)) return result } mul(input: Uint8Array): Uint8Array { const p1 = toG1Point(input.slice(0, G1_POINT_BYTE_LENGTH)) const scalar = toFrPoint(input.slice(G1_POINT_BYTE_LENGTH, 96)) if (scalar === BIGINT_0) { return G1_INFINITY_POINT_BYTES } const result = fromG1Point(p1.multiply(scalar)) return result } pairing(input: Uint8Array): Uint8Array { // Extract the pairs from the input const pairLength = 192 const pairs = [] for (let k = 0; k < input.length / pairLength; k++) { const pairStart = pairLength * k const G1 = toG1Point(input.subarray(pairStart, pairStart + G1_POINT_BYTE_LENGTH)) const g2start = pairStart + G1_POINT_BYTE_LENGTH const G2 = toG2Point(input.subarray(g2start, g2start + G2_POINT_BYTE_LENGTH)) if (G1 === bn254.G1.ProjectivePoint.ZERO || G2 === bn254.G2.ProjectivePoint.ZERO) { continue } pairs.push({ g1: G1, g2: G2 }) } const res = bn254.pairingBatch(pairs) if (bn254.fields.Fp12.eql(res, bn254.fields.Fp12.ONE) === true) { return ONE_BUFFER } else { return ZERO_BUFFER } } }