UNPKG

@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
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`) } }], ])