@tokamak-zk-evm/synthesizer
Version:
Tokamak zk-EVM Synthesizer - Processes Ethereum transactions into wire maps for Tokamak zk-SNARK proof generation
659 lines (571 loc) • 19.4 kB
text/typescript
import { TWO_POW256, bytesToBigInt, equalsBytes } from "@synthesizer-libs/util"
import { mod, fromTwos, toTwos } from "../util.js"
import {
synthesizerArith,
synthesizerEnvInf,
synthesizerBlkInf,
prepareEXTCodePt
} from "../../tokamak/core/synthesizer.js"
import { copyMemoryRegion, simulateMemoryPt } from "../../tokamak/pointers/index.js"
import { BIGINT_0, BIGINT_1 } from "@synthesizer-libs/util"
import { SynthesizerHandler } from "./types.js"
export const synthesizerHandlers: Map<number, SynthesizerHandler> = new Map([
// 0x01: ADD
[0x01, async function (runState) {
console.log('synthesizer add go')
const [a, b] = runState.stackPt.popN(2)
const r = mod(a.value + b.value, TWO_POW256)
synthesizerArith('ADD', [a.value, b.value], r, runState)
}],
// 0x02: MUL
[0x02, async function (runState) {
const [a, b] = runState.stackPt.popN(2)
const r = mod(a.value * b.value, TWO_POW256)
synthesizerArith('MUL', [a.value, b.value], r, runState)
}],
// 0x03: SUB
[0x03, async function (runState) {
const [a, b] = runState.stackPt.popN(2)
const r = mod(a.value - b.value, TWO_POW256)
synthesizerArith('SUB', [a.value, b.value], r, runState)
}],
// 0x04: DIV
[0x04, async function (runState) {
const [a, b] = runState.stackPt.popN(2)
let r
if (b.value === BIGINT_0) {
r = BIGINT_0
} else {
r = mod(a.value / b.value, TWO_POW256)
}
synthesizerArith('DIV', [a.value, b.value], r, runState)
}],
// 0x05: SDIV
[0x05, async function (runState) {
const [a, b] = runState.stackPt.popN(2)
let r
if (b.value === BIGINT_0) {
r = BIGINT_0
} else {
r = toTwos(fromTwos(a.value) / fromTwos(b.value))
}
synthesizerArith('SDIV', [a.value, b.value], r, runState)
}],
// 0x06: MOD
[0x06, async function (runState) {
const [a, b] = runState.stackPt.popN(2)
let r
if (b.value === BIGINT_0) {
r = b.value
} else {
r = mod(a.value, b.value)
}
synthesizerArith('MOD', [a.value, b.value], r, runState)
}],
// 0x07: SMOD
[0x07, async function (runState) {
const [a, b] = runState.stackPt.popN(2)
let r
if (b.value === BIGINT_0) {
r = b.value
} else {
r = fromTwos(a.value) % fromTwos(b.value)
}
// Convert SMOD operation result to two's complement representation for EVM's 256-bit unsigned integer
// Required to properly handle negative results since EVM processes all values as unsigned 256-bit integers
// Using toTwos(r) to convert negative results to two's complement ensures SMOD operation works as expected
synthesizerArith('SMOD', [a.value, b.value], toTwos(r), runState)
}],
// 0x08: ADDMOD
[0x08, async function (runState) {
const [a, b, c] = runState.stackPt.popN(3)
let r
if (c.value === BIGINT_0) {
r = BIGINT_0
} else {
r = mod(a.value + b.value, c.value)
}
synthesizerArith('ADDMOD', [a.value, b.value, c.value], r, runState)
}],
// 0x09: MULMOD
[0x09, async function (runState) {
const [a, b, c] = runState.stackPt.popN(3)
let r
if (c.value === BIGINT_0) {
r = BIGINT_0
} else {
r = mod(a.value * b.value, c.value)
}
synthesizerArith('MULMOD', [a.value, b.value, c.value], r, runState)
}],
// 0x0a: EXP
[0x0a, async function (runState) {
const [base, exponent] = runState.stackPt.popN(2)
let r
if (exponent.value === BIGINT_0) {
r = BIGINT_1
} else if (base.value === BIGINT_0) {
r = base.value
} else {
r = base.value ** exponent.value % TWO_POW256
}
synthesizerArith('EXP', [base.value, exponent.value], r, runState)
}],
// 0x0b: SIGNEXTEND
[0x0b, async function (runState) {
const [k, val] = runState.stackPt.popN(2)
synthesizerArith('SIGNEXTEND', [k.value, val.value], val.value, runState)
}],
// 0x10: LT
[0x10, async function (runState) {
const [a, b] = runState.stackPt.popN(2)
const r = a.value < b.value ? BIGINT_1 : BIGINT_0
synthesizerArith('LT', [a.value, b.value], r, runState)
}],
// 0x11: GT
[0x11, async function (runState) {
const [a, b] = runState.stackPt.popN(2)
const r = a.value > b.value ? BIGINT_1 : BIGINT_0
synthesizerArith('GT', [a.value, b.value], r, runState)
}],
// 0x12: SLT
[0x12, async function (runState) {
const [a, b] = runState.stackPt.popN(2)
const r = fromTwos(a.value) < fromTwos(b.value) ? BIGINT_1 : BIGINT_0
await synthesizerArith('SLT', [a.value, b.value], r, runState)
}],
// 0x13: SGT
[0x13, async function (runState) {
const [a, b] = runState.stackPt.popN(2)
const r = fromTwos(a.value) > fromTwos(b.value) ? BIGINT_1 : BIGINT_0
await synthesizerArith('SGT', [a.value, b.value], r, runState)
}],
// 0x14: EQ
[0x14, async function (runState) {
const [a, b] = runState.stackPt.popN(2)
const r = a.value === b.value ? BIGINT_1 : BIGINT_0
await synthesizerArith('EQ', [a.value, b.value], r, runState)
}],
// 0x15: ISZERO
[0x15, async function (runState) {
const a = runState.stackPt.pop()
const r = a.value === BIGINT_0 ? BIGINT_1 : BIGINT_0
await synthesizerArith('ISZERO', [a.value], r, runState)
}],
// 0x16: AND
[0x16, async function (runState) {
const [a, b] = runState.stackPt.popN(2)
const r = a.value & b.value
await synthesizerArith('AND', [a.value, b.value], r, runState)
}],
// 0x17: OR
[0x17, async function (runState) {
const [a, b] = runState.stackPt.popN(2)
const r = a.value | b.value
await synthesizerArith('OR', [a.value, b.value], r, runState)
}],
// 0x18: XOR
[0x18, async function (runState) {
const [a, b] = runState.stackPt.popN(2)
const r = a.value ^ b.value
await synthesizerArith('XOR', [a.value, b.value], r, runState)
}],
// 0x19: NOT
[0x19, async function (runState) {
const a = runState.stackPt.pop()
const r = BigInt.asUintN(256, ~a.value)
await synthesizerArith('NOT', [a.value], r, runState)
}],
// 0x1a: BYTE
[0x1a, async function (runState) {
const [pos, word] = runState.stackPt.popN(2)
const r = (word.value >> ((BigInt(31) - pos.value) * BigInt(8))) & BigInt(255)
await synthesizerArith('BYTE', [pos.value, word.value], r, runState)
}],
// 0x1b: SHL
[0x1b, async function (runState) {
const [a, b] = runState.stackPt.popN(2)
const r = (b.value << a.value) & ((BigInt(1) << BigInt(256)) - BigInt(1))
await synthesizerArith('SHL', [a.value, b.value], r, runState)
}],
// 0x1c: SHR
[0x1c, async function (runState) {
const [a, b] = runState.stackPt.popN(2)
const r = b.value >> a.value
await synthesizerArith('SHR', [a.value, b.value], r, runState)
}],
// 0x1d: SAR
[0x1d, async function (runState) {
const [a, b] = runState.stackPt.popN(2)
let r
const isSigned = BigInt.asIntN(256, b.value) < 0
if (a.value > 256) {
r = isSigned ? ((BigInt(1) << BigInt(256)) - BigInt(1)) : BIGINT_0
} else {
const c = b.value >> a.value
if (isSigned) {
const shiftedOutWidth = BigInt(255) - a.value
const mask = ((TWO_POW256 - BigInt(1)) >> shiftedOutWidth) << shiftedOutWidth
r = c | mask
} else {
r = c
}
}
await synthesizerArith('SAR', [a.value, b.value], r, runState)
}],
// 0x20: KECCAK256
[0x20, async function (runState) {
const [offsetPt, lengthPt] = runState.stackPt.popN(2)
const offset = offsetPt.value
const length = lengthPt.value
if (length !== BIGINT_0) {
const offsetNum = Number(offset)
const lengthNum = Number(length)
let nChunks = 1
if (lengthNum > 32) {
nChunks = Math.ceil(lengthNum / 32)
}
const chunkDataPts = []
let dataRecovered = BIGINT_0
let lengthLeft = lengthNum
for (let i = 0; i < nChunks; i++) {
const _offset = offsetNum + 32 * i
const _length = lengthLeft > 32 ? 32 : lengthLeft
lengthLeft -= _length
const dataAliasInfos = runState.memoryPt.getDataAlias(_offset, _length)
if (dataAliasInfos.length > 0) {
chunkDataPts[i] = runState.synthesizer.placeMemoryToStack(dataAliasInfos)
} else {
chunkDataPts[i] = runState.synthesizer.loadAuxin(BIGINT_0)
}
dataRecovered += chunkDataPts[i].value << BigInt((nChunks - i - 1) * 32 * 8)
}
const data = runState.memory.read(Number(offset), Number(length))
if (bytesToBigInt(data) !== dataRecovered) {
throw new Error(`Synthesizer: KECCAK256: Data loaded to be hashed mismatch`)
}
const r = runState.stack.peek(1)[0]
runState.stackPt.push(runState.synthesizer.loadKeccak(chunkDataPts, r, length))
if (runState.stack.peek(1)[0] !== runState.stackPt.peek(1)[0].value) {
throw new Error(`Synthesizer: KECCAK256: Output data mismatch`)
}
}
}],
// 0x30: ADDRESS
[0x30, async function (runState) {
await synthesizerEnvInf('ADDRESS', runState)
}],
// 0x31: BALANCE
[0x31, async function (runState) {
const addressBigInt = runState.stackPt.pop().value
await synthesizerEnvInf('BALANCE', runState, addressBigInt)
}],
// 0x32: ORIGIN
[0x32, async function (runState) {
await synthesizerEnvInf('ORIGIN', runState)
}],
// 0x33: CALLER
[0x33, async function (runState) {
await synthesizerEnvInf('CALLER', runState)
}],
// 0x34: CALLVALUE
[0x34, async function (runState) {
await synthesizerEnvInf('CALLVALUE', runState)
}],
// 0x35: CALLDATALOAD
[0x35, async function (runState) {
const pos = runState.stackPt.pop().value
await synthesizerEnvInf('CALLDATALOAD', runState, undefined, pos)
}],
// 0x36: CALLDATASIZE
[0x36, async function (runState) {
await synthesizerEnvInf('CALLDATASIZE', runState)
}],
// 0x37: CALLDATACOPY
[0x37, async function (runState) {
const [memOffset, dataOffset, dataLength] = runState.stackPt.popN(3)
if (dataLength.value !== BIGINT_0) {
const calldataMemoryPts = runState.interpreter._env.callMemoryPts
let memoryPtsToCopy = []
if (calldataMemoryPts.length > 0) {
memoryPtsToCopy = copyMemoryRegion(
runState,
dataOffset.value,
dataLength.value,
calldataMemoryPts,
memOffset.value
)
} else {
const data = runState.interpreter.getCallData().subarray(
Number(dataOffset.value),
Number(dataOffset.value + dataLength.value)
)
const entryToCopy = {
memOffset: Number(memOffset.value),
containerSize: Number(dataLength.value),
dataPt: runState.synthesizer.loadEnvInf(
runState.env.address.toString(),
'Calldata',
bytesToBigInt(data),
Number(dataOffset.value),
Number(dataLength.value)
)
}
memoryPtsToCopy.push(entryToCopy)
}
for (const entry of memoryPtsToCopy) {
runState.memoryPt.write(entry.memOffset, entry.containerSize, entry.dataPt)
}
const _outData = runState.memoryPt.viewMemory(
Number(memOffset.value),
Number(dataLength.value)
)
const outData = runState.memory.read(
Number(memOffset.value),
Number(dataLength.value)
)
if (!equalsBytes(_outData, outData)) {
throw new Error(`Synthesizer: CALLDATACOPY: Output data mismatch`)
}
}
}],
// 0x38: CODESIZE
[0x38, async function (runState) {
await synthesizerEnvInf('CODESIZE', runState)
}],
// 0x39: CODECOPY
[0x39, async function (runState) {
const [memOffset, codeOffset, dataLength] = runState.stackPt.popN(3)
if (dataLength.value !== BIGINT_0) {
const data = runState.interpreter.getCode().subarray(
Number(codeOffset.value),
Number(codeOffset.value + dataLength.value)
)
const dataBigint = bytesToBigInt(data)
const dataPt = runState.synthesizer.loadEnvInf(
runState.env.address.toString(),
'Code',
dataBigint,
Number(codeOffset.value),
Number(dataLength.value)
)
runState.memoryPt.write(
Number(memOffset.value),
Number(dataLength.value),
dataPt
)
const _outData = runState.memoryPt.viewMemory(
Number(memOffset.value),
Number(dataLength.value)
)
const outData = runState.memory.read(
Number(memOffset.value),
Number(dataLength.value)
)
if (!equalsBytes(_outData, outData)) {
throw new Error(`Synthesizer: CODECOPY: Output data mismatch`)
}
}
}],
// 0x3b: EXTCODESIZE
[0x3b, async function (runState) {
const addressBigInt = runState.stackPt.pop().value
await synthesizerEnvInf('EXTCODESIZE', runState, addressBigInt)
}],
// 0x3c: EXTCODECOPY
[0x3c, async function (runState) {
const [addressPt, memOffsetPt, codeOffsetPt, dataLengthPt] = runState.stackPt.popN(4)
if (dataLengthPt.value !== BIGINT_0) {
const dataPt = await prepareEXTCodePt(
runState,
addressPt.value,
codeOffsetPt.value,
dataLengthPt.value
)
runState.memoryPt.write(
Number(memOffsetPt.value),
Number(dataLengthPt.value),
dataPt
)
const _outData = runState.memoryPt.viewMemory(
Number(memOffsetPt.value),
Number(dataLengthPt.value)
)
const outData = runState.memory.read(
Number(memOffsetPt.value),
Number(dataLengthPt.value)
)
if (!equalsBytes(_outData, outData)) {
throw new Error(`Synthesizer: EXTCODECOPY: Output data mismatch`)
}
}
}],
// 0x3f: EXTCODEHASH
[0x3f, async function (runState) {
const addressBigInt = runState.stackPt.pop().value
await synthesizerEnvInf('EXTCODEHASH', runState, addressBigInt)
}],
// 0x3d: RETURNDATASIZE
[0x3d, async function (runState) {
await synthesizerEnvInf('RETURNDATASIZE', runState)
}],
// 0x3e: RETURNDATACOPY
[0x3e, async function (runState) {
const [memOffset, returnDataOffset, dataLength] = runState.stackPt.popN(3)
if (dataLength.value !== BIGINT_0) {
const copiedMemoryPts = copyMemoryRegion(
runState,
returnDataOffset.value,
dataLength.value,
runState.returnMemoryPts,
memOffset.value
)
for (const entry of copiedMemoryPts) {
runState.memoryPt.write(entry.memOffset, entry.containerSize, entry.dataPt)
}
const _outData = runState.memoryPt.viewMemory(
Number(memOffset.value),
Number(dataLength.value)
)
const outData = runState.memory.read(
Number(memOffset.value),
Number(dataLength.value)
)
if (!equalsBytes(_outData, outData)) {
throw new Error(`Synthesizer: RETURNDATACOPY: Output data mismatch`)
}
}
}],
// 0x3a: GASPRICE
[0x3a, async function (runState) {
await synthesizerEnvInf('GASPRICE', runState)
}],
// 0x40: BLOCKHASH
[0x40, async function (runState) {
const number = runState.stackPt.pop().value
await synthesizerBlkInf('BLOCKHASH', runState, number)
}],
// 0x41: COINBASE
[0x41, async function (runState) {
await synthesizerBlkInf('COINBASE', runState)
}],
// 0x42: TIMESTAMP
[0x42, async function (runState) {
await synthesizerBlkInf('TIMESTAMP', runState)
}],
// 0x43: NUMBER
[0x43, async function (runState) {
await synthesizerBlkInf('NUMBER', runState)
}],
// 0x44: DIFFICULTY (PREVRANDAO)
[0x44, async function (runState) {
await synthesizerBlkInf('DIFFICULTY', runState)
}],
// 0x45: GASLIMIT
[0x45, async function (runState) {
await synthesizerBlkInf('GASLIMIT', runState)
}],
// 0x46: CHAINID
[0x46, async function (runState) {
await synthesizerBlkInf('CHAINID', runState)
}],
// 0x47: SELFBALANCE
[0x47, async function (runState) {
await synthesizerBlkInf('SELFBALANCE', runState)
}],
// 0x48: BASEFEE
[0x48, async function (runState) {
await synthesizerBlkInf('BASEFEE', runState)
}],
// 0x5e: MCOPY
[0x5e, async function (runState) {
const [dst, src, length] = runState.stackPt.popN(3)
const copiedMemoryPts = copyMemoryRegion(
runState,
src.value,
length.value,
undefined,
dst.value
)
for (const entry of copiedMemoryPts) {
runState.memoryPt.write(entry.memOffset, entry.containerSize, entry.dataPt)
}
const _outData = runState.memoryPt.viewMemory(
Number(dst.value),
Number(length.value)
)
const outData = runState.memory.read(
Number(dst.value),
Number(length.value)
)
if (!equalsBytes(_outData, outData)) {
throw new Error(`Synthesizer: MCOPY: Output data mismatch`)
}
}],
// 0x5f: PUSH0
[0x5f, async function (runState) {
const dataPt = runState.synthesizer.loadPUSH(
runState.env.address.toString(),
runState.programCounterPrev,
BIGINT_0,
1
)
runState.stackPt.push(dataPt)
if (runState.stackPt.peek(1)[0].value !== runState.stack.peek(1)[0]) {
throw new Error(`Synthesizer: PUSH0: Output data mismatch`)
}
}],
// 0x60: PUSH
[0x60, async function (runState) {
const value = runState.stack.peek(1)[0]
const numToPush = runState.opCode - 0x5f
const dataPt = runState.synthesizer.loadPUSH(
runState.env.address.toString(),
runState.programCounterPrev,
value,
numToPush
)
runState.stackPt.push(dataPt)
if (runState.stackPt.peek(1)[0].value !== runState.stack.peek(1)[0]) {
throw new Error(`Synthesizer: PUSH${numToPush}: Output data mismatch`)
}
}],
// 0x80: DUP
[0x80, async function (runState) {
const stackPos = runState.opCode - 0x7f
runState.stackPt.dup(stackPos)
}],
// 0x90: SWAP
[0x90, async function (runState) {
const stackPos = runState.opCode - 0x8f
runState.stackPt.swap(stackPos)
}],
// 0xa0: LOG
[0xa0, async function (runState) {
const [memOffsetPt, memLengthPt] = runState.stackPt.popN(2)
const topicsCount = runState.opCode - 0xa0
const topicPts = runState.stackPt.popN(topicsCount)
const dataAlias = runState.memoryPt.getDataAlias(
Number(memOffsetPt.value),
Number(memLengthPt.value)
)
const dataPt = runState.synthesizer.placeMemoryToStack(dataAlias)
runState.synthesizer.storeLog(dataPt, topicPts)
}],
// 0xf3: RETURN
[0xf3, async function (runState) {
const [offset, length] = runState.stackPt.popN(2)
const returnMemoryPts = copyMemoryRegion(
runState,
offset.value,
length.value
)
runState.interpreter.finishPt(returnMemoryPts)
const simMemoryPt = simulateMemoryPt(returnMemoryPts)
const _returnData = simMemoryPt.viewMemory(0, Number(length.value))
const returnData = runState.memory.read(Number(offset.value), Number(length.value))
if (!equalsBytes(_returnData, returnData)) {
throw new Error(`Synthesizer: RETURN: Output data mismatch`)
}
}],
])