Get Block Witness
mega_getBlockWitness — fetch the SALT + MPT witness needed to stateless-verify a MegaETH block.
MegaETH defines a mega_getBlockWitness RPC method to return the cryptographic witness for a single MegaETH block. The witness contains the subset of state the block reads or writes, packaged with proofs against the previous block's state root, so that a stateless verifier can re-execute the block without holding any chain state locally.
The RPC method is served at the public MegaETH RPC endpoint:
https://mainnet.megaeth.com/rpcThe witness JSON-RPC method is the same logical service that powers the stateless validator's --witness-endpoint. Any client — an operator running stateless-validator or a custom verifier — can call it directly.
Request
Method
mega_getBlockWitness
Params
[<keys>] — single-element array
<keys> is a JSON object that identifies the block. blockNumber is always required; pair it with blockHash to pin the witness to a specific block.
blockNumber
Quantity (hex)
Yes
Block number, 0x-prefixed lowercase hex (e.g. "0x7fd").
blockHash
Data (32 bytes)
No
0x-prefixed lowercase hash of the block to fetch the witness for. Pins the result, so it is reorg-safe.
Lookup modes
The combination of fields chosen determines the lookup mode. Always pass blockHash when one is available. The blockNumber-only mode does not pin the result to a specific block and can return a witness for the wrong fork.
blockNumber + blockHash
Preferred
The caller already knows the canonical block hash (e.g. fetched from eth_getBlockByNumber first). The witness is pinned to that exact block, so the result is reorg-safe.
blockNumber
Avoid
Last-resort convenience. The backend returns the first stored witness it finds at that height — there is no guarantee the returned witness is for the block you expect. Only use when you cannot obtain a hash and can independently verify the response.
Calling mega_getBlockWitness with blockNumber only is unsafe for any caller that needs a specific block. The server returns the first witness it finds at that height, which is non-deterministic, may correspond to a non-canonical fork, and may change between calls. Always pair blockNumber with blockHash unless you are willing to validate the response yourself (e.g. by re-deriving the block hash from the returned witness against an independently-trusted header).
Examples
Response
The response result is a single string of the form <version>:<base64-payload>.
version
Encoding version. Currently v0. Bumped if the witness payload format ever changes — clients must check the prefix.
payload
Base64-encoded, Zstd-compressed bincode tuple (SaltWitness, MptWitness).
Errors
-32602
Invalid params — malformed JSON, missing blockNumber, or unparseable hex.
-32000
Witness not found — no witness stored for the requested keys.
-32001
Decompression failed — stored payload is corrupted (server-side issue).
The server returns -32000 (a 404 equivalent) when no witness exists for the requested keys. The RPC layer in front of the witness service may also return standard JSON-RPC transport codes: -32700 (parse error), -32600 (invalid request), -32603 (internal error).
Decoding pipeline
To turn the response string back into a witness, apply these steps in order:
Verify the string starts with the literal prefix
v0:and strip it.Base64-decode the payload (standard alphabet, padded).
Zstd-decompress the result.
Bincode-deserialize using the legacy config (fixed-int encoding, little-endian) into
(SaltWitness, MptWitness).
A reference Rust implementation lives in the upstream stateless validator at fetch_witness_raw:
SaltWitness is defined in the salt crate; MptWitness is defined in the stateless-core crate of the stateless-validator repository. Add both as Cargo dependencies (via a git or path source) before compiling this snippet.
Witness data structure
The decoded payload is a 2-tuple — one component per state surface the validator must check. Every type below is given as the upstream Rust definition with its source location, so a third-party decoder in another language can reproduce the byte layout exactly.
SaltWitness — main state trie
SaltWitness — main state trieCarries the subset of SALT key-value pairs the block touches, plus a single multi-point IPA proof binding them to the previous block's state root.
Defined at salt/src/proof/salt_witness.rs:46:
kvs
Witnessed slots. Some(v) means the slot is occupied with value v; None means the slot is proven empty; an absent key is unknown (verifier must error).
proof
IPA multi-point proof + the path commitments needed to authenticate every entry in kvs against the state root.
A verifier uses the witness as a StateReader / TrieReader: every state read during block re-execution falls through to kvs (existence-proven) or errors (unknown). The three-state distinction (existing / proven-empty / unknown) is what blocks a malicious witness server from hiding state by omission — see the security model in the upstream SaltWitness doc-comment. For the trie design this witness proves against, see the SALT README.
SaltKey
SaltKeyDefined at salt/src/types.rs:198:
The u64 packs the SALT trie address into two fields:
63..=40
bucket_id
24 bits (~16M)
Index into the static main trie. The first NUM_META_BUCKETS = 65_536 buckets hold metadata.
39..=0
slot_id
40 bits (~1T)
Slot offset inside the bucket's SHI hash table.
Use bucket_id = key >> 40 and slot_id = key & ((1<<40) - 1) to unpack. Bincode-legacy serializes SaltKey as a fixed 8-byte little-endian u64.
SaltValue
SaltValueDefined at salt/src/types.rs:274:
data holds a length-prefixed key-value blob:
0
1 byte
key_len
1
1 byte
value_len
2..2+key_len
key_len
key
2+key_len..2+key_len+value_len
value_len
value
2+key_len+value_len..94
remainder
zero-padded
Three SaltValue flavors share this encoding:
Kind
key_len
value_len
Used bytes
Notes
Account
20
40 (EOA) / 72 (contract)
62 / 94
Key is the 20-byte address; value is the encoded account body.
Storage
52
32
86
Key is address(20) ++ storage_slot(32); value is the 32-byte slot value.
BucketMeta
12
0
14
Reserved for the metadata buckets — BucketMeta is fully encoded into the 12-byte key, value is empty.
SaltProof
SaltProofDefined at salt/src/proof/prover.rs:103:
parents_commitments
BTreeMap<NodeId, SerdeCommitment>
Commitment for every trie node on the path from a witnessed bucket up to the root. Lookups by NodeId = u64 (a flat trie-node index) — the verifier walks these to the root.
proof
SerdeMultiPointProof
The IPA multi-point opening proof over the Banderwagon scalar field. Serialized via MultiPointProof::to_bytes; deserialize with MultiPointProof::from_bytes(&buf, DOMAIN_SIZE) where DOMAIN_SIZE = 256 (the IPA polynomial degree, matching SALT's 256-ary trie fan-out).
levels
FxHashMap<BucketId, u8>
Number of subtree levels for each bucket present in the proof. Required because the verifier doesn't always know a bucket's capacity from the witness alone.
SerdeCommitment wraps a Banderwagon group Element; SerdeMultiPointProof wraps an ipa_multipoint::MultiPointProof. Both serialize to opaque byte vectors via the IPA crate's encoding.
Map encoding order. kvs and parents_commitments are BTreeMap, so bincode emits their entries in canonical (sorted) key order — wire output is byte-stable for re-implementors that want to round-trip-compare. levels is an FxHashMap, whose iteration order depends on the hasher; do not assume a fixed order when re-encoding.
MptWitness — withdrawals storage trie
MptWitness — withdrawals storage trieA small Merkle Patricia Trie witness covering the storage trie of the L2-to-L1 message-passer contract (0x4200000000000000000000000000000000000016), so the validator can recompute withdrawals_root after applying the block's withdrawal-message writes.
Defined at stateless-core/src/withdrawals.rs:49:
storage_root
B256
Pre-state storage root of the L2ToL1MessagePasser contract. Serialized as 32 raw bytes.
state
Vec<Bytes>
RLP-encoded MPT trie nodes that authenticate the storage slots the block's withdrawal writes will touch. Each Bytes is a length-prefixed byte string.
This is intentionally an MPT (not SALT) witness: withdrawals are committed to the standard Ethereum withdrawals MPT root for L1 compatibility, so the slice of state needed to maintain it is proved separately from the SALT-backed account/storage state.
Example
Fetch the witness for a known block and pipe it through the decode pipeline. Replace <BLOCK_NUMBER> (0x-prefixed lowercase hex) and <BLOCK_HASH> with values from eth_getBlockByNumber. The pipeline below assumes jq and zstd are on PATH — install them via brew install jq zstd on macOS or apt install jq zstd on Debian/Ubuntu.
witness.bincode is a Zstd-decompressed bincode tuple — feed it into a Rust deserializer (using the snippet under Decoding pipeline) to obtain (SaltWitness, MptWitness).
Related pages
Stateless Validation — the operator guide for the reference client that consumes this RPC.
stateless-validator source — Rust implementation of the witness fetcher and verifier.
SALT — the authenticated key-value store that produces
SaltWitness.
Last updated