Skip to main content

ssz/
lib.rs

1//! SimpleSerialize (SSZ) codec with `hash_tree_root`.
2//!
3//! Implements the Ethereum consensus SSZ wire format plus two jar-specific
4//! extensions ([`MissingOr`], [`SparseList`]) that allow precomputed subtree
5//! roots to substitute transparently for materialized leaves.
6//!
7//! The default hash function is SHA-256 (via the optional `sha2` feature);
8//! the [`HashTreeRoot`] trait is generic over any `digest::Digest` with a
9//! 32-byte output, so callers can plug in alternative hashes at compile time.
10//!
11//! # Wire format
12//!
13//! See [`encoding`](https://github.com/ethereum/consensus-specs/blob/dev/ssz/simple-serialize.md)
14//! for the spec we implement. Notable deviations:
15//!
16//! - [`Option`] / SSZ Union: byte 0 = None (no payload), byte 1 = Some(T) + T's bytes.
17//! - [`MissingOr`]: byte 0 = Materialized + T's bytes, byte 1 = Missing + 32 raw bytes.
18//! - [`SparseList`]: same wire format as a `List<T, N>` plus a length prefix.
19
20#![cfg_attr(not(feature = "std"), no_std)]
21extern crate alloc;
22
23use alloc::vec::Vec;
24use digest::Digest;
25use digest::typenum::U32;
26
27pub mod bits;
28pub mod collections;
29mod error;
30pub mod list;
31pub mod merkle;
32pub mod missing;
33pub mod primitives;
34pub mod radix;
35mod smallvec_impl;
36pub mod sparse;
37pub mod union;
38pub mod vector;
39
40pub use bits::{Bitlist, Bitvector};
41// Re-exports so consumers of the derive macros can name the crates the
42// generated code references without taking direct dependencies.
43pub use digest;
44
45/// Hidden re-exports used by the derive macros. Not part of the public
46/// API; do not depend on this directly.
47#[doc(hidden)]
48pub mod __private {
49    pub use alloc::vec::Vec;
50}
51pub use error::DecodeError;
52pub use list::List;
53pub use merkle::{merkleize, mix_in_length, mix_in_selector, pack_bytes, zero_hash};
54pub use missing::MissingOr;
55pub use primitives::U256;
56pub use radix::RadixMap;
57pub use sparse::SparseList;
58pub use vector::FixedVector;
59
60#[cfg(feature = "derive")]
61pub use ssz_derive::{Decode, Encode, HashTreeRoot};
62
63/// The number of bytes used to encode a variable-length list offset.
64///
65/// SSZ fixes this at 4 (a little-endian `u32`).
66pub const BYTES_PER_LENGTH_OFFSET: usize = 4;
67
68/// Chunk size in bytes for SSZ merkleization.
69pub const BYTES_PER_CHUNK: usize = 32;
70
71/// SSZ encoding trait.
72///
73/// `ssz_append` is the primary primitive: every other method delegates to it.
74pub trait Encode {
75    /// `true` iff this type is fixed-length (no variable-length fields).
76    fn is_ssz_fixed_len() -> bool;
77
78    /// Number of bytes this type occupies in the fixed-length portion of a
79    /// container encoding. For variable-length types this returns
80    /// [`BYTES_PER_LENGTH_OFFSET`] (i.e. the size of the offset slot).
81    fn ssz_fixed_len() -> usize {
82        BYTES_PER_LENGTH_OFFSET
83    }
84
85    /// `true` for "basic" SSZ types (uintN, bool), which pack adjacent
86    /// elements into shared 32-byte chunks for merkleization. Composite
87    /// types (containers, lists, structs) return `false` (the default).
88    fn is_basic_type() -> bool {
89        false
90    }
91
92    /// Total size of `self` when serialized.
93    fn ssz_bytes_len(&self) -> usize;
94
95    /// Append the encoding of `self` to `buf`.
96    fn ssz_append(&self, buf: &mut Vec<u8>);
97
98    /// Serialize into a fresh `Vec<u8>` allocated through the global allocator.
99    fn as_ssz_bytes(&self) -> Vec<u8> {
100        let mut v = Vec::with_capacity(self.ssz_bytes_len());
101        self.ssz_append(&mut v);
102        v
103    }
104}
105
106/// SSZ decoding trait.
107pub trait Decode: Sized {
108    /// `true` iff this type is fixed-length.
109    fn is_ssz_fixed_len() -> bool;
110
111    /// Number of bytes this type occupies in the fixed-length portion of a
112    /// container encoding. Variable-length types return
113    /// [`BYTES_PER_LENGTH_OFFSET`].
114    fn ssz_fixed_len() -> usize {
115        BYTES_PER_LENGTH_OFFSET
116    }
117
118    /// Decode a full instance from `bytes`, rejecting trailing input.
119    fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError>;
120}
121
122/// Computes a 32-byte hash tree root for SSZ types.
123///
124/// Generic over the hash function so callers can plug in SHA-256, Blake2b,
125/// etc. Requires `OutputSize = U32`, i.e. a 32-byte digest.
126pub trait HashTreeRoot {
127    /// Compute the hash tree root using `D` as the underlying hash.
128    fn hash_tree_root<D: Digest<OutputSize = U32>>(&self) -> [u8; 32];
129}
130
131/// Convenience SHA-256 entry point.
132///
133/// Rust forbids default type parameters on free functions, so this is the
134/// SHA-256-specialised companion to [`HashTreeRoot::hash_tree_root`].
135#[cfg(feature = "sha2")]
136pub fn hash_tree_root<T: HashTreeRoot + ?Sized>(value: &T) -> [u8; 32] {
137    value.hash_tree_root::<sha2::Sha256>()
138}
139
140// --------------------------------------------------------------------------
141// Internal helpers
142// --------------------------------------------------------------------------
143
144/// Wraps a slice index check that returns [`DecodeError::UnexpectedEof`] on
145/// out-of-bounds.
146#[inline]
147pub(crate) fn read_slice(bytes: &[u8], offset: usize, len: usize) -> Result<&[u8], DecodeError> {
148    let end = offset.checked_add(len).ok_or(DecodeError::UnexpectedEof {
149        expected: len,
150        actual: bytes.len().saturating_sub(offset),
151    })?;
152    if end > bytes.len() {
153        return Err(DecodeError::UnexpectedEof {
154            expected: len,
155            actual: bytes.len().saturating_sub(offset),
156        });
157    }
158    Ok(&bytes[offset..end])
159}
160
161/// Reads a little-endian u32 length offset from `bytes[off..off+4]`.
162#[inline]
163pub(crate) fn read_offset(bytes: &[u8], off: usize) -> Result<usize, DecodeError> {
164    let slice = read_slice(bytes, off, BYTES_PER_LENGTH_OFFSET)?;
165    let arr: [u8; 4] = slice.try_into().expect("len checked");
166    Ok(u32::from_le_bytes(arr) as usize)
167}