Frame transactions
A standard Ethereum transaction is a fixed shape: one call, to one address, authorised by one ECDSA signature over the transaction hash. The signature is not a field you can swap — the sender’s identity is derived from it.
EIP-8141 — a draft proposal
co-authored by Vitalik Buterin to make Ethereum signature-agnostic — replaces
that shape with a more general one. A frame transaction, EIP-2718 type
0x06, carries:
- an ordered list of frames to execute,
- a list of signature entries that the protocol may or may not interpret,
- and the sender, named explicitly rather than recovered.
That last point is the hinge. Once the sender is a field rather than a derivation, the signature no longer has to be something the protocol understands. It can be a lattice signature, a hash-based signature, a threshold signature, or a scheme that does not exist yet — as long as the sender’s own code knows how to check it.
Frames
Section titled “Frames”A frame is a single call with its own target, value, calldata, and gas limit. Frames execute in order, and each one has a mode that determines what it is allowed to do:
| Mode | Name | Meaning |
|---|---|---|
0x00 |
DEFAULT | An ordinary call. |
0x01 |
VERIFY | Static-called — it cannot modify state. To let the transaction proceed it must explicitly APPROVE. |
0x02 |
SENDER | Runs with CALLER == tx.sender. Only executes after execution has been approved. |
The split between VERIFY and SENDER is the important one. A VERIFY frame is
where authorisation happens: it runs read-only, checks whatever it wants to
check, and calls the APPROVE opcode if satisfied. A SENDER frame is where the
actual work happens, and it only runs if a VERIFY frame approved it.
Each frame also carries flags that bound what its approval can cover — bit 0 for approving payment, bit 1 for approving execution, bit 2 for atomic batching. A frame that is not permitted to approve execution cannot authorise the transaction no matter what it does.
A typical post-quantum spend is two frames:
frames: [0] VERIFY target: (self) flags: 3 gas: 30,000 → checks the ML-DSA signature, APPROVEs payment and execution [1] SENDER target: recipient value: 1 ETH gas: 50,000 → performs the transfer, with CALLER == the accountSignature entries
Section titled “Signature entries”The second list is where the witness lives. Each entry is
[scheme, signer, msg, signature], and scheme decides who checks it:
| Scheme | Name | Who verifies |
|---|---|---|
0x00 |
ARBITRARY | Nobody, at protocol level. The bytes are simply made available to frame code. |
0x01 |
SECP256K1 | The protocol, before execution. |
0x02 |
P256 | The protocol, before execution. |
The crucial insight is that scheme is a verification mode, not an algorithm
label. There is no ML-DSA scheme number, and there does not need to be. A
post-quantum signature rides as an ARBITRARY entry: the protocol treats it as
opaque bytes, and the account’s VERIFY frame copies those bytes out with the
SIGPARAM opcode, appends its own public key, and calls the
ML-DSA verifier precompile.
This is what makes the design crypto-agile. Supporting a new signature scheme requires a new precompile and a new account type — not a new scheme number, not a change to what an address means, and not a hard fork of the transaction format.
The circularity problem, and the elision rule
Section titled “The circularity problem, and the elision rule”There is an obvious problem with putting a signature inside a transaction that the signature signs. The witness signs the transaction hash; the witness is part of the transaction; so the hash depends on the witness, which depends on the hash.
EIP-8141 breaks this with an elision rule. The signature hash is computed over the transaction with one modification:
every signature entry whose
msgis empty has itssignaturefield replaced by the empty string.
So the workflow is:
- Build the transaction with an empty witness.
- Compute the sig-hash over that.
- Sign the 32-byte sig-hash with your ML-DSA key.
- Attach the signature.
Step 4 does not change the sig-hash, because the elision rule removed those bytes from it in the first place.
This has a useful consequence: because the witness bytes are outside the hash, a relayer that corrupts them can only make the transaction invalid. It can never redirect it, change its recipient, or alter its value. The sig-hash still commits to the chain ID, the nonce, every frame including its calldata, the fee caps, the gas limit, and the shape of the signature list.
Entries with a non-empty msg work the other way round: they sign msg, not the
sig-hash, so they are hashed as-is and are never elided.
Explicit sender, explicit gas
Section titled “Explicit sender, explicit gas”Two further fields are worth calling out.
sender is a plain 20-byte address in the payload. There is no to field
at the transaction level — each frame has its own target, and an empty target
means the sender itself.
gas_limit at the transaction level is signed, which is a Frost-specific
divergence from the upstream draft. It is covered by the sig-hash, validity
requires it to be at least the transaction’s computed total gas, the payer is
charged it up front, and unused gas is refunded. Signing it bounds what a
relayer can do with the padding room that elided witnesses would otherwise
create.
Frame transactions are not free relative to ordinary ones. The intrinsic cost is 15,000 gas plus 475 per frame, plus calldata gas on everything including the signature bytes, plus each frame’s own gas. Most of the cost of a post-quantum spend is calldata gas on a multi-kilobyte witness, not the signature verification itself.
The exact accounting is in the transaction type reference.
- Accounts and keys — what the account on the other end of this looks like.
- Transaction type
0x06— the normative byte layout. - Frame opcodes —
APPROVE,SIGPARAM, and the rest of the introspection surface.