@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
text/typescript
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
}
}
}