Transaction signature rules¶
Signature validity, recovery parity, and sender authentication after the transaction codec has established the envelope's canonical wire shape.
The sender authentication¶
public_keys[i] is a witness, not trusted input. The sender address is
derived from the key, then authenticated by recovering the signature's
address with its envelope y_parity and requiring the two addresses to
match. A forged key, wrong parity, or bad signature therefore rejects the
block.
function tx_signature_parity¶
Validates the selected signature scheme's v domain and returns its
recovery parity. Legacy accepts 27/28 or an EIP-155 value binding the
chain id; typed envelopes accept only an explicit zero-or-one parity.
An invalid v rejects the block here, so an unvalidated parity is
impossible to pass to sender recovery.
function tx_signature_parity(chain_id : chain_identifier, scheme : TxSignatureScheme, v : word) -> y_parity =
match scheme {
LegacySignature => {
let eip155_v = word_ule(35, v);
let signature_chain_id = legacy_sig_chain_id(v);
let expected_chain_id = word_of_chain_identifier(chain_id);
if (v == 27) | (v == 28) | (eip155_v & (signature_chain_id == expected_chain_id)) then {
let parity_bit = word_and(v, WORD_ONE);
if parity_bit == WORD_ONE then {
0
} else {
1
}
} else {
fatal_error(InvalidSignature)
}
},
TypedSignature => {
if v == WORD_ZERO then {
0
} else if v == WORD_ONE then {
1
} else {
fatal_error(InvalidSignature)
}
},
}function fatal_error(_reason) = exit(())The chain id encoded in a legacy signature with v >= 35
(EIP-155).
function legacy_sig_chain_id(v : word) -> word = {
let adjusted_v = word_sub(v, 35);
word_div(adjusted_v, 2)
}Bitwise conjunction of two words.
function word_and(left : word, right : word) -> word = {
let left_bits = get_slice_int(256, left, 0);
let right_bits = get_slice_int(256, right, 0);
let result_bits = and_vec(left_bits, right_bits);
let result = unsigned(result_bits);
u256(result)
}Converts a chain identifier to the value exposed by CHAINID.
function word_of_chain_identifier(value : chain_identifier) -> chain_identifier = valuefunction word_ule(a, b) = {
let greater = word_ult(b, a);
not_bool(greater)
}let WORD_ONE : word = word_from_bits(0x0000000000000000000000000000000000000000000000000000000000000001)let WORD_ZERO : word = word_from_bits(0x0000000000000000000000000000000000000000000000000000000000000000)The reason a block fails validation; one variant per violated block-validity rule.
enum FatalError = {
/* chain config: wrong fork / inactive activation */
InvalidConfig,
/* witness ancestor headers not contiguous */
HeaderChainBroken,
/* a transaction failed to RLP-decode */
RlpDecode,
/* a tx signature did not authenticate its sender */
InvalidSignature,
/* header.gas_limit is outside the consensus domain */
InvalidGasLimit,
/* EIP-7778: a tx exceeds the block's remaining gas */
GasUsedExceedsLimit,
/* a tx exceeds the block's remaining blob gas */
BlobGasLimitExceeded,
/* an invalid tx or a failed block-end system call */
ExecutionInvalid,
/* recomputed cumulative gas != header.gas_used */
InvalidGasUsed,
/* recomputed blob gas != header.blob_gas_used */
InvalidBlobGasUsed,
/* header.excess_blob_gas != expected */
InvalidExcessBlobGas,
/* recomputed post-state root != header.state_root */
InvalidStateRoot,
/* recomputed receipts root != header.receipts_root */
InvalidReceiptsRoot,
/* recomputed logs bloom != header.logs_bloom */
InvalidLogsBloom,
/* recomputed block hash != payload expected hash */
InvalidBlockHash,
/* header.parent_hash != authenticated parent */
InvalidParentHash,
/* EIP-7928: BAL item count > gas_limit / 2000 */
BlockAccessListTooLarge,
/* reconstructed EIP-7928 BAL bytes mismatch */
InvalidBlockAccessList,
/* reconstructed EIP-7685 request bytes mismatch */
InvalidExecutionRequests,
/* a missing/inconsistent proof node (thrown at use) */
WitnessDeficient,
/* an exact protocol integer exceeds its bounded execution representation */
NumericOverflow,
}The two transaction-signature encodings. Legacy transactions use the
original/EIP-155 v domain; every EIP-2718 typed envelope carries an
explicit zero-or-one parity.
enum TxSignatureScheme = { LegacySignature, TypedSignature }A chain identifier. Typed-transaction chain identifiers and the stateless chain configuration are decoded as unsigned 64-bit integers.
type chain_identifier = range(0, chain_identifier_bound)The EVM 256-bit machine word (YP §9.1). A transparent range keeps the mathematical subtype relation visible: narrower non-negative ranges can be passed as words without a model-level conversion.
type word = range(0, 2 ^ 256 - 1)The parity bit used by transaction signatures.
type y_parity = range(0, 1)function tx_auth_valid¶
Authenticates a transaction: enforce the EIP-2 low-s bound, recover the
signer selected by y_parity, and bind it to the address derived from the
witnessed 65-byte public key.
function tx_auth_valid(sender : address, h : hash, parity : y_parity, r : word, s : word) -> bool =
let high_s = word_ult(SECP_N_HALF, s) in
if high_s then {
false
} else {
let recovered = ecrecover_addr(h, parity, r, s);
recovered.success & (recovered.address == sender)
}Recovers the signer address from (h, y_parity, r, s), returning
recovery success and the recovered address (used by EIP-7702).
function ecrecover_addr(h : hash, yparity : y_parity, r : word, s : word) -> AddressResult = {
host_ecrecover(h, yparity, r, s)
}function word_ult(a, b) = a < bn/2 of the secp256k1 group order — the EIP-2 low-s malleability
bound.
let SECP_N_HALF : word = word_from_bits(0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0)A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)The common digest type used by trie, code, and block hashes.
type hash = b256The EVM 256-bit machine word (YP §9.1). A transparent range keeps the mathematical subtype relation visible: narrower non-negative ranges can be passed as words without a model-level conversion.
type word = range(0, 2 ^ 256 - 1)The parity bit used by transaction signatures.
type y_parity = range(0, 1)