SSZ source decoding¶
Scalar readers and variable-list navigation over the private-input byte source. Concrete container layouts belong in their decoder modules.
let SSZ_OFF_BYTES¶
The width of one entry in an SSZ variable-field offset table
(uint32, little-endian).
let SSZ_OFF_BYTES : int(4) = 4let SSZ_UINT_BYTES¶
let SSZ_UINT_BYTES : int(8) = 8function ssz_field_offset¶
function ssz_field_offset(base, delta) = base + deltafunction ssz_field_offset(base, delta) = base + deltafunction scratch_field_offset¶
function scratch_field_offset(base, delta) = base + deltafunction scratch_field_offset(base, delta) = base + deltafunction ssz_u32_at¶
function ssz_u32_at(input, offset) = {
let byte0 = slice_byte(input, offset);
let offset1 = ssz_field_offset(offset, 1);
let byte1 = slice_byte(input, offset1);
let offset2 = ssz_field_offset(offset, 2);
let byte2 = slice_byte(input, offset2);
let offset3 = ssz_field_offset(offset, 3);
let byte3 = slice_byte(input, offset3);
let b0 = sail_zero_extend(byte0, 32);
let b1 = sail_zero_extend(byte1, 32);
let b2 = sail_zero_extend(byte2, 32);
let b3 = sail_zero_extend(byte3, 32);
let shifted1 = sail_shiftleft(b1, 8);
let shifted2 = sail_shiftleft(b2, 16);
let shifted3 = sail_shiftleft(b3, 24);
let high = or_vec(shifted2, shifted3);
let nonzero = or_vec(shifted1, high);
let result = or_vec(b0, nonzero);
unsigned(result)
}val or_vec = pure {lem: "or_vec", coq: "or_vec", ocaml: "or_vec", interpreter: "or_vec", lean: "_lean_bvor", _: "or_bits"}: forall ('n : Int).
(bits('n), bits('n)) -> bits('n)val sail_shiftleft = pure {lean: "_lean_shiftl", _: "shiftl"}: forall ('n : Int) ('amount : Int).
(bitvector('n), int('amount)) -> bitvector('n)val sail_zero_extend = pure {lean: "Sail.BitVec.zeroExtend", _: "zero_extend"}: forall ('n : Int) ('m : Int), 'm >= 'n.
(bits('n), int('m)) -> bits('m)function ssz_field_offset(base, delta) = base + deltafunction ssz_u32_at(input, offset) = {
let byte0 = slice_byte(input, offset);
let offset1 = ssz_field_offset(offset, 1);
let byte1 = slice_byte(input, offset1);
let offset2 = ssz_field_offset(offset, 2);
let byte2 = slice_byte(input, offset2);
let offset3 = ssz_field_offset(offset, 3);
let byte3 = slice_byte(input, offset3);
let b0 = sail_zero_extend(byte0, 32);
let b1 = sail_zero_extend(byte1, 32);
let b2 = sail_zero_extend(byte2, 32);
let b3 = sail_zero_extend(byte3, 32);
let shifted1 = sail_shiftleft(b1, 8);
let shifted2 = sail_shiftleft(b2, 16);
let shifted3 = sail_shiftleft(b3, 24);
let high = or_vec(shifted2, shifted3);
let nonzero = or_vec(shifted1, high);
let result = or_vec(b0, nonzero);
unsigned(result)
}converts a bit vector of length $n$ to an integer in the range $0$ to $2^n - 1$.
val unsigned = pure {ocaml: "uint", lem: "uint", interpreter: "uint", coq: "uint", lean: "BitVec.toNatInt", _: "sail_unsigned"}: forall ('n : Int).
bits('n) -> range(0, 2 ^ 'n - 1)function ssz_u32¶
function ssz_u32(input, offset) = ssz_u32_at(input, offset)function ssz_u32(input, offset) = ssz_u32_at(input, offset)function ssz_u32_at(input, offset) = {
let byte0 = slice_byte(input, offset);
let offset1 = ssz_field_offset(offset, 1);
let byte1 = slice_byte(input, offset1);
let offset2 = ssz_field_offset(offset, 2);
let byte2 = slice_byte(input, offset2);
let offset3 = ssz_field_offset(offset, 3);
let byte3 = slice_byte(input, offset3);
let b0 = sail_zero_extend(byte0, 32);
let b1 = sail_zero_extend(byte1, 32);
let b2 = sail_zero_extend(byte2, 32);
let b3 = sail_zero_extend(byte3, 32);
let shifted1 = sail_shiftleft(b1, 8);
let shifted2 = sail_shiftleft(b2, 16);
let shifted3 = sail_shiftleft(b3, 24);
let high = or_vec(shifted2, shifted3);
let nonzero = or_vec(shifted1, high);
let result = or_vec(b0, nonzero);
unsigned(result)
}function ssz_u32_in_slice¶
Reads an offset-table entry after establishing that the dynamic table position is contained by its enclosing input slice.
function ssz_u32_in_slice(input : StatelessInputSlice, offset : ssz_offset) -> ssz_offset =
if offset <= input.len & 4 <= input.len - offset then {
ssz_u32_at(input, offset)
} else {
fatal_error(InvalidConfig)
}function fatal_error(_reason) = exit(())function ssz_u32_at(input, offset) = {
let byte0 = slice_byte(input, offset);
let offset1 = ssz_field_offset(offset, 1);
let byte1 = slice_byte(input, offset1);
let offset2 = ssz_field_offset(offset, 2);
let byte2 = slice_byte(input, offset2);
let offset3 = ssz_field_offset(offset, 3);
let byte3 = slice_byte(input, offset3);
let b0 = sail_zero_extend(byte0, 32);
let b1 = sail_zero_extend(byte1, 32);
let b2 = sail_zero_extend(byte2, 32);
let b3 = sail_zero_extend(byte3, 32);
let shifted1 = sail_shiftleft(b1, 8);
let shifted2 = sail_shiftleft(b2, 16);
let shifted3 = sail_shiftleft(b3, 24);
let high = or_vec(shifted2, shifted3);
let nonzero = or_vec(shifted1, high);
let result = or_vec(b0, nonzero);
unsigned(result)
}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,
}A stateless-input range with its coordinate and length packed existentially.
type StatelessInputSlice = {
'off 'len,
stateless_input_valid_range('off, 'len).
StatelessInputSliceFields('off, 'len)
}A container-relative offset carried by an SSZ uint32.
type ssz_offset = range(0, 2 ^ 32 - 1)function ssz_offset_to_source_pointer¶
Narrows a wire-bounded SSZ offset at the host byte-position boundary.
function ssz_offset_to_source_pointer(value : ssz_offset) -> stateless_input_pointer =
valueA container-relative offset carried by an SSZ uint32.
type ssz_offset = range(0, 2 ^ 32 - 1)A coordinate in the immutable stateless-input envelope.
type stateless_input_pointer = range(0, stateless_input_region_bound)function decode_ssz_uint¶
function decode_ssz_uint(input, offset) = {
let byte0 = slice_byte(input, offset);
let offset1 = ssz_field_offset(offset, 1);
let byte1 = slice_byte(input, offset1);
let offset2 = ssz_field_offset(offset, 2);
let byte2 = slice_byte(input, offset2);
let offset3 = ssz_field_offset(offset, 3);
let byte3 = slice_byte(input, offset3);
let offset4 = ssz_field_offset(offset, 4);
let byte4 = slice_byte(input, offset4);
let offset5 = ssz_field_offset(offset, 5);
let byte5 = slice_byte(input, offset5);
let offset6 = ssz_field_offset(offset, 6);
let byte6 = slice_byte(input, offset6);
let offset7 = ssz_field_offset(offset, 7);
let byte7 = slice_byte(input, offset7);
unsigned(byte0)
+ unsigned(byte1)
* 2 ^ 8
+ unsigned(byte2)
* 2 ^ 16
+ unsigned(byte3)
* 2 ^ 24
+ unsigned(byte4)
* 2 ^ 32
+ unsigned(byte5)
* 2 ^ 40
+ unsigned(byte6)
* 2 ^ 48
+ unsigned(byte7)
* 2 ^ 56
}function decode_ssz_uint(input, offset) = {
let byte0 = slice_byte(input, offset);
let offset1 = ssz_field_offset(offset, 1);
let byte1 = slice_byte(input, offset1);
let offset2 = ssz_field_offset(offset, 2);
let byte2 = slice_byte(input, offset2);
let offset3 = ssz_field_offset(offset, 3);
let byte3 = slice_byte(input, offset3);
let offset4 = ssz_field_offset(offset, 4);
let byte4 = slice_byte(input, offset4);
let offset5 = ssz_field_offset(offset, 5);
let byte5 = slice_byte(input, offset5);
let offset6 = ssz_field_offset(offset, 6);
let byte6 = slice_byte(input, offset6);
let offset7 = ssz_field_offset(offset, 7);
let byte7 = slice_byte(input, offset7);
unsigned(byte0)
+ unsigned(byte1)
* 2 ^ 8
+ unsigned(byte2)
* 2 ^ 16
+ unsigned(byte3)
* 2 ^ 24
+ unsigned(byte4)
* 2 ^ 32
+ unsigned(byte5)
* 2 ^ 40
+ unsigned(byte6)
* 2 ^ 48
+ unsigned(byte7)
* 2 ^ 56
}We have special support for raising values to the power of two. Any Sail expression 2 ^ x will be compiled to this builtin.
val pow2 = pure {lean: "_lean_pow2i", _: "pow2"}: forall ('n : Int). int('n) -> int(2 ^ 'n)function ssz_field_offset(base, delta) = base + deltaconverts a bit vector of length $n$ to an integer in the range $0$ to $2^n - 1$.
val unsigned = pure {ocaml: "uint", lem: "uint", interpreter: "uint", coq: "uint", lean: "BitVec.toNatInt", _: "sail_unsigned"}: forall ('n : Int).
bits('n) -> range(0, 2 ^ 'n - 1)function decode_scratch_uint¶
function decode_scratch_uint(input, offset) = {
let byte0 = slice_byte(input, offset);
let offset1 = scratch_field_offset(offset, 1);
let byte1 = slice_byte(input, offset1);
let offset2 = scratch_field_offset(offset, 2);
let byte2 = slice_byte(input, offset2);
let offset3 = scratch_field_offset(offset, 3);
let byte3 = slice_byte(input, offset3);
let offset4 = scratch_field_offset(offset, 4);
let byte4 = slice_byte(input, offset4);
let offset5 = scratch_field_offset(offset, 5);
let byte5 = slice_byte(input, offset5);
let offset6 = scratch_field_offset(offset, 6);
let byte6 = slice_byte(input, offset6);
let offset7 = scratch_field_offset(offset, 7);
let byte7 = slice_byte(input, offset7);
unsigned(byte0)
+ unsigned(byte1)
* 2 ^ 8
+ unsigned(byte2)
* 2 ^ 16
+ unsigned(byte3)
* 2 ^ 24
+ unsigned(byte4)
* 2 ^ 32
+ unsigned(byte5)
* 2 ^ 40
+ unsigned(byte6)
* 2 ^ 48
+ unsigned(byte7)
* 2 ^ 56
}function decode_scratch_uint(input, offset) = {
let byte0 = slice_byte(input, offset);
let offset1 = scratch_field_offset(offset, 1);
let byte1 = slice_byte(input, offset1);
let offset2 = scratch_field_offset(offset, 2);
let byte2 = slice_byte(input, offset2);
let offset3 = scratch_field_offset(offset, 3);
let byte3 = slice_byte(input, offset3);
let offset4 = scratch_field_offset(offset, 4);
let byte4 = slice_byte(input, offset4);
let offset5 = scratch_field_offset(offset, 5);
let byte5 = slice_byte(input, offset5);
let offset6 = scratch_field_offset(offset, 6);
let byte6 = slice_byte(input, offset6);
let offset7 = scratch_field_offset(offset, 7);
let byte7 = slice_byte(input, offset7);
unsigned(byte0)
+ unsigned(byte1)
* 2 ^ 8
+ unsigned(byte2)
* 2 ^ 16
+ unsigned(byte3)
* 2 ^ 24
+ unsigned(byte4)
* 2 ^ 32
+ unsigned(byte5)
* 2 ^ 40
+ unsigned(byte6)
* 2 ^ 48
+ unsigned(byte7)
* 2 ^ 56
}We have special support for raising values to the power of two. Any Sail expression 2 ^ x will be compiled to this builtin.
val pow2 = pure {lean: "_lean_pow2i", _: "pow2"}: forall ('n : Int). int('n) -> int(2 ^ 'n)function scratch_field_offset(base, delta) = base + deltaconverts a bit vector of length $n$ to an integer in the range $0$ to $2^n - 1$.
val unsigned = pure {ocaml: "uint", lem: "uint", interpreter: "uint", coq: "uint", lean: "BitVec.toNatInt", _: "sail_unsigned"}: forall ('n : Int).
bits('n) -> range(0, 2 ^ 'n - 1)function ssz_addr¶
function ssz_addr(input, offset) = {
let value = slice_load_n(input, offset, ADDRESS_BYTE_LENGTH);
word_to_address(value)
}function ssz_addr(input, offset) = {
let value = slice_load_n(input, offset, ADDRESS_BYTE_LENGTH);
word_to_address(value)
}Converts a word to its low 160-bit address in canonical byte order.
function word_to_address(value : word) -> address = {
let zero_bytes = vector_init(20, 0x00);
var result : address = Address(zero_bytes);
result[0] = get_slice_int(8, value, 152);
result[1] = get_slice_int(8, value, 144);
result[2] = get_slice_int(8, value, 136);
result[3] = get_slice_int(8, value, 128);
result[4] = get_slice_int(8, value, 120);
result[5] = get_slice_int(8, value, 112);
result[6] = get_slice_int(8, value, 104);
result[7] = get_slice_int(8, value, 96);
result[8] = get_slice_int(8, value, 88);
result[9] = get_slice_int(8, value, 80);
result[10] = get_slice_int(8, value, 72);
result[11] = get_slice_int(8, value, 64);
result[12] = get_slice_int(8, value, 56);
result[13] = get_slice_int(8, value, 48);
result[14] = get_slice_int(8, value, 40);
result[15] = get_slice_int(8, value, 32);
result[16] = get_slice_int(8, value, 24);
result[17] = get_slice_int(8, value, 16);
result[18] = get_slice_int(8, value, 8);
result[19] = get_slice_int(8, value, 0);
result
}let ADDRESS_BYTE_LENGTH : int(20) = 20function ssz_bytes32¶
function ssz_bytes32(input, offset) = {
let value = slice_load(input, offset);
word_to_hash(value)
}function ssz_bytes32(input, offset) = {
let value = slice_load(input, offset);
word_to_hash(value)
}Serializes an EVM word as a 32-byte big-endian digest.
function word_to_hash(value : word) -> hash = {
let zero_bytes = vector_init(32, 0x00);
var result : hash = B256(zero_bytes);
result[0] = get_slice_int(8, value, 248);
result[1] = get_slice_int(8, value, 240);
result[2] = get_slice_int(8, value, 232);
result[3] = get_slice_int(8, value, 224);
result[4] = get_slice_int(8, value, 216);
result[5] = get_slice_int(8, value, 208);
result[6] = get_slice_int(8, value, 200);
result[7] = get_slice_int(8, value, 192);
result[8] = get_slice_int(8, value, 184);
result[9] = get_slice_int(8, value, 176);
result[10] = get_slice_int(8, value, 168);
result[11] = get_slice_int(8, value, 160);
result[12] = get_slice_int(8, value, 152);
result[13] = get_slice_int(8, value, 144);
result[14] = get_slice_int(8, value, 136);
result[15] = get_slice_int(8, value, 128);
result[16] = get_slice_int(8, value, 120);
result[17] = get_slice_int(8, value, 112);
result[18] = get_slice_int(8, value, 104);
result[19] = get_slice_int(8, value, 96);
result[20] = get_slice_int(8, value, 88);
result[21] = get_slice_int(8, value, 80);
result[22] = get_slice_int(8, value, 72);
result[23] = get_slice_int(8, value, 64);
result[24] = get_slice_int(8, value, 56);
result[25] = get_slice_int(8, value, 48);
result[26] = get_slice_int(8, value, 40);
result[27] = get_slice_int(8, value, 32);
result[28] = get_slice_int(8, value, 24);
result[29] = get_slice_int(8, value, 16);
result[30] = get_slice_int(8, value, 8);
result[31] = get_slice_int(8, value, 0);
result
}function ssz_logs_bloom_index¶
function ssz_logs_bloom_index(index) = 255 - indexfunction ssz_logs_bloom_index(index) = 255 - indexfunction ssz_logs_bloom¶
Decodes a fixed 256-byte logs bloom from SSZ wire order.
function ssz_logs_bloom(input, offset) = {
var out : LogsBloom = vector_init(256, 0x00);
foreach (k from 0 to 255) {
let output_index = ssz_logs_bloom_index(k);
let source_offset = ssz_field_offset(offset, k);
out[output_index] = slice_byte(input, source_offset)
};
out
}function ssz_field_offset(base, delta) = base + deltaDecodes a fixed 256-byte logs bloom from SSZ wire order.
function ssz_logs_bloom(input, offset) = {
var out : LogsBloom = vector_init(256, 0x00);
foreach (k from 0 to 255) {
let output_index = ssz_logs_bloom_index(k);
let source_offset = ssz_field_offset(offset, k);
out[output_index] = slice_byte(input, source_offset)
};
out
}function ssz_logs_bloom_index(index) = 255 - indexval vector_init = pure {lean: "vectorInit", _: "vector_init"}: forall ('n : Int) ('a : Type), 'n >= 0.
(implicit('n), 'a) -> vector('n, 'a)The 2048-bit logs bloom filter (YP §4.4.1), as 256 bytes.
type LogsBloom = vector(256, dec, byte)function ssz_u256_index¶
function ssz_u256_index(index) = 31 - indexfunction ssz_u256_index(index) = 31 - indexfunction ssz_u256¶
Decodes a 32-byte little-endian SSZ integer into an EVM word.
function ssz_u256(input, offset) = {
var result : word = WORD_ZERO;
foreach (k from 0 to 31) {
let source_index = ssz_u256_index(k);
let source_offset = ssz_field_offset(offset, source_index);
let source_byte = slice_byte(input, source_offset);
let byte_value = unsigned(source_byte);
let shifted_result = word_mul(result, 256);
result = word_add(shifted_result, byte_value)
};
result
}function ssz_field_offset(base, delta) = base + deltaDecodes a 32-byte little-endian SSZ integer into an EVM word.
function ssz_u256(input, offset) = {
var result : word = WORD_ZERO;
foreach (k from 0 to 31) {
let source_index = ssz_u256_index(k);
let source_offset = ssz_field_offset(offset, source_index);
let source_byte = slice_byte(input, source_offset);
let byte_value = unsigned(source_byte);
let shifted_result = word_mul(result, 256);
result = word_add(shifted_result, byte_value)
};
result
}function ssz_u256_index(index) = 31 - indexconverts a bit vector of length $n$ to an integer in the range $0$ to $2^n - 1$.
val unsigned = pure {ocaml: "uint", lem: "uint", interpreter: "uint", coq: "uint", lean: "BitVec.toNatInt", _: "sail_unsigned"}: forall ('n : Int).
bits('n) -> range(0, 2 ^ 'n - 1)let WORD_ZERO : word = word_from_bits(0x0000000000000000000000000000000000000000000000000000000000000000)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)