@tokamak-zk-evm/synthesizer
Version:
Tokamak zk-EVM Synthesizer - Processes Ethereum transactions into wire maps for Tokamak zk-SNARK proof generation
219 lines • 10.2 kB
JavaScript
import { VerkleAccessedStateType } from '@synthesizer-libs/common';
import { BIGINT_0, VERKLE_BASIC_DATA_LEAF_KEY, VERKLE_CODE_HASH_LEAF_KEY, VERKLE_CODE_OFFSET, VERKLE_HEADER_STORAGE_OFFSET, VERKLE_MAIN_STORAGE_OFFSET, VERKLE_NODE_WIDTH, bytesToHex, getVerkleKey, getVerkleStem, getVerkleTreeIndicesForCodeChunk, intToBytes, } from "@synthesizer-libs/util";
import debugDefault from 'debug';
const debug = debugDefault('statemanager:verkle:aw');
/**
* Tree key constants.
*/
const WitnessBranchReadCost = BigInt(1900);
const WitnessChunkReadCost = BigInt(200);
const WitnessBranchWriteCost = BigInt(3000);
const WitnessChunkWriteCost = BigInt(500);
const WitnessChunkFillCost = BigInt(6200);
export class VerkleAccessWitness {
constructor(opts) {
if (opts.verkleCrypto === undefined) {
throw new Error('verkle crypto required');
}
this.verkleCrypto = opts.verkleCrypto;
this.stems = opts.stems ?? new Map();
this.chunks = opts.chunks ?? new Map();
}
touchAndChargeProofOfAbsence(address) {
let gas = BIGINT_0;
gas += this.touchAddressOnReadAndComputeGas(address, 0, VERKLE_BASIC_DATA_LEAF_KEY);
gas += this.touchAddressOnReadAndComputeGas(address, 0, VERKLE_CODE_HASH_LEAF_KEY);
return gas;
}
touchAndChargeMessageCall(address) {
let gas = BIGINT_0;
gas += this.touchAddressOnReadAndComputeGas(address, 0, VERKLE_BASIC_DATA_LEAF_KEY);
return gas;
}
touchAndChargeValueTransfer(target) {
let gas = BIGINT_0;
gas += this.touchAddressOnWriteAndComputeGas(target, 0, VERKLE_BASIC_DATA_LEAF_KEY);
return gas;
}
touchAndChargeContractCreateInit(address) {
let gas = BIGINT_0;
gas += this.touchAddressOnWriteAndComputeGas(address, 0, VERKLE_BASIC_DATA_LEAF_KEY);
return gas;
}
touchAndChargeContractCreateCompleted(address) {
let gas = BIGINT_0;
gas += this.touchAddressOnWriteAndComputeGas(address, 0, VERKLE_BASIC_DATA_LEAF_KEY);
gas += this.touchAddressOnWriteAndComputeGas(address, 0, VERKLE_CODE_HASH_LEAF_KEY);
return gas;
}
touchTxOriginAndComputeGas(origin) {
let gas = BIGINT_0;
gas += this.touchAddressOnReadAndComputeGas(origin, 0, VERKLE_BASIC_DATA_LEAF_KEY);
gas += this.touchAddressOnReadAndComputeGas(origin, 0, VERKLE_CODE_HASH_LEAF_KEY);
return gas;
}
touchTxTargetAndComputeGas(target, { sendsValue } = {}) {
let gas = BIGINT_0;
gas += this.touchAddressOnReadAndComputeGas(target, 0, VERKLE_CODE_HASH_LEAF_KEY);
if (sendsValue === true) {
gas += this.touchAddressOnWriteAndComputeGas(target, 0, VERKLE_BASIC_DATA_LEAF_KEY);
}
else {
gas += this.touchAddressOnReadAndComputeGas(target, 0, VERKLE_BASIC_DATA_LEAF_KEY);
}
return gas;
}
touchCodeChunksRangeOnReadAndChargeGas(contact, startPc, endPc) {
let gas = BIGINT_0;
for (let chunkNum = Math.floor(startPc / 31); chunkNum <= Math.floor(endPc / 31); chunkNum++) {
const { treeIndex, subIndex } = getVerkleTreeIndicesForCodeChunk(chunkNum);
gas += this.touchAddressOnReadAndComputeGas(contact, treeIndex, subIndex);
}
return gas;
}
touchCodeChunksRangeOnWriteAndChargeGas(contact, startPc, endPc) {
let gas = BIGINT_0;
for (let chunkNum = Math.floor(startPc / 31); chunkNum <= Math.floor(endPc / 31); chunkNum++) {
const { treeIndex, subIndex } = getVerkleTreeIndicesForCodeChunk(chunkNum);
gas += this.touchAddressOnWriteAndComputeGas(contact, treeIndex, subIndex);
}
return gas;
}
touchAddressOnWriteAndComputeGas(address, treeIndex, subIndex) {
return this.touchAddressAndChargeGas(address, treeIndex, subIndex, { isWrite: true });
}
touchAddressOnReadAndComputeGas(address, treeIndex, subIndex) {
return this.touchAddressAndChargeGas(address, treeIndex, subIndex, { isWrite: false });
}
touchAddressAndChargeGas(address, treeIndex, subIndex, { isWrite }) {
let gas = BIGINT_0;
const { stemRead, stemWrite, chunkRead, chunkWrite, chunkFill } = this.touchAddress(address, treeIndex, subIndex, { isWrite });
if (stemRead === true) {
gas += WitnessBranchReadCost;
}
if (stemWrite === true) {
gas += WitnessBranchWriteCost;
}
if (chunkRead === true) {
gas += WitnessChunkReadCost;
}
if (chunkWrite === true) {
gas += WitnessChunkWriteCost;
}
if (chunkFill === true) {
gas += WitnessChunkFillCost;
}
debug(`touchAddressAndChargeGas=${gas} address=${address} treeIndex=${treeIndex} subIndex=${subIndex}`);
return gas;
}
touchAddress(address, treeIndex, subIndex, { isWrite } = {}) {
let stemRead = false, stemWrite = false, chunkRead = false, chunkWrite = false;
// currently there are no gas charges for setting the chunk for the first time
// i.e. no fill cost is charged right now
const chunkFill = false;
const accessedStemKey = getVerkleStem(this.verkleCrypto, address, treeIndex);
const accessedStemHex = bytesToHex(accessedStemKey);
let accessedStem = this.stems.get(accessedStemHex);
if (accessedStem === undefined) {
stemRead = true;
accessedStem = { address, treeIndex };
this.stems.set(accessedStemHex, accessedStem);
}
const accessedChunkKey = getVerkleKey(accessedStemKey, typeof subIndex === 'number' ? intToBytes(subIndex) : subIndex);
const accessedChunkKeyHex = bytesToHex(accessedChunkKey);
let accessedChunk = this.chunks.get(accessedChunkKeyHex);
if (accessedChunk === undefined) {
chunkRead = true;
accessedChunk = {};
this.chunks.set(accessedChunkKeyHex, accessedChunk);
}
if (isWrite === true) {
if (accessedStem.write !== true) {
stemWrite = true;
// this would also directly modify in the map
accessedStem.write = true;
}
if (accessedChunk.write !== true) {
chunkWrite = true;
// this would also directly modify in the map
accessedChunk.write = true;
}
}
debug(`${accessedChunkKeyHex}: isWrite=${isWrite} for steamRead=${stemRead} stemWrite=${stemWrite} chunkRead=${chunkRead} chunkWrite=${chunkWrite} chunkFill=${chunkFill}`);
return { stemRead, stemWrite, chunkRead, chunkWrite, chunkFill };
}
merge(accessWitness) {
for (const [chunkKey, chunkValue] of accessWitness.chunks.entries()) {
const stemKey = chunkKey.slice(0, chunkKey.length - 2);
const stem = accessWitness.stems.get(stemKey);
if (stem === undefined) {
throw Error(`Internal error: missing stem for the chunkKey=${chunkKey}`);
}
const thisStem = this.stems.get(stemKey);
if (thisStem === undefined) {
this.stems.set(stemKey, stem);
}
else {
thisStem.write = thisStem.write !== true ? stem.write : true;
}
const thisChunk = this.chunks.get(chunkKey);
if (thisChunk === undefined) {
this.chunks.set(chunkKey, chunkValue);
}
else {
thisChunk.write = thisChunk.write !== true ? chunkValue.write : true;
thisChunk.fill = thisChunk.fill !== true ? thisChunk.fill : true;
}
}
}
*rawAccesses() {
for (const chunkKey of this.chunks.keys()) {
// drop the last byte
const stemKey = chunkKey.slice(0, chunkKey.length - 2);
const stem = this.stems.get(stemKey);
if (stem === undefined) {
throw Error(`Internal error: missing stem for the chunkKey=${chunkKey}`);
}
const { address, treeIndex } = stem;
const chunkIndex = Number(`0x${chunkKey.slice(chunkKey.length - 2)}`);
const accessedState = { address, treeIndex, chunkIndex, chunkKey };
yield accessedState;
}
}
*accesses() {
for (const rawAccess of this.rawAccesses()) {
const { address, treeIndex, chunkIndex, chunkKey } = rawAccess;
const accessedState = decodeAccessedState(treeIndex, chunkIndex);
yield { ...accessedState, address, chunkKey };
}
}
}
export function decodeAccessedState(treeIndex, chunkIndex) {
const position = BigInt(treeIndex) * BigInt(VERKLE_NODE_WIDTH) + BigInt(chunkIndex);
switch (position) {
case BigInt(0):
return { type: VerkleAccessedStateType.BasicData };
case BigInt(1):
return { type: VerkleAccessedStateType.CodeHash };
default:
if (position < VERKLE_HEADER_STORAGE_OFFSET) {
throw Error(`No attribute yet stored >=2 and <${VERKLE_HEADER_STORAGE_OFFSET}`);
}
if (position >= VERKLE_HEADER_STORAGE_OFFSET && position < VERKLE_CODE_OFFSET) {
const slot = position - BigInt(VERKLE_HEADER_STORAGE_OFFSET);
return { type: VerkleAccessedStateType.Storage, slot };
}
else if (position >= VERKLE_CODE_OFFSET && position < VERKLE_MAIN_STORAGE_OFFSET) {
const codeChunkIdx = Number(position) - VERKLE_CODE_OFFSET;
return { type: VerkleAccessedStateType.Code, codeOffset: codeChunkIdx * 31 };
}
else if (position >= VERKLE_MAIN_STORAGE_OFFSET) {
const slot = BigInt(position - VERKLE_MAIN_STORAGE_OFFSET);
return { type: VerkleAccessedStateType.Storage, slot };
}
else {
throw Error(`Invalid treeIndex=${treeIndex} chunkIndex=${chunkIndex} for verkle tree access`);
}
}
}
//# sourceMappingURL=verkleAccessWitness.js.map