Skip to main content

ssz/
primitives.rs

1//! SSZ blanket impls for built-in scalar and array types.
2
3use alloc::vec::Vec;
4use core::num::NonZeroU32;
5use digest::Digest;
6use digest::typenum::U32;
7
8use crate::merkle::merkleize;
9use crate::merkle::pack_bytes;
10use crate::union::option_selector_hash;
11use crate::{BYTES_PER_LENGTH_OFFSET, Decode, DecodeError, Encode, HashTreeRoot, read_slice};
12
13// --------------------------------------------------------------------------
14// Unsigned integers
15// --------------------------------------------------------------------------
16
17macro_rules! impl_uint {
18    ($t:ty, $size:expr) => {
19        impl Encode for $t {
20            fn is_ssz_fixed_len() -> bool {
21                true
22            }
23            fn ssz_fixed_len() -> usize {
24                $size
25            }
26            fn is_basic_type() -> bool {
27                true
28            }
29            fn ssz_bytes_len(&self) -> usize {
30                $size
31            }
32            fn ssz_append(&self, buf: &mut Vec<u8>) {
33                buf.extend_from_slice(&self.to_le_bytes());
34            }
35        }
36
37        impl Decode for $t {
38            fn is_ssz_fixed_len() -> bool {
39                true
40            }
41            fn ssz_fixed_len() -> usize {
42                $size
43            }
44            fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
45                if bytes.len() != $size {
46                    return Err(DecodeError::UnexpectedEof {
47                        expected: $size,
48                        actual: bytes.len(),
49                    });
50                }
51                let mut arr = [0u8; $size];
52                arr.copy_from_slice(bytes);
53                Ok(<$t>::from_le_bytes(arr))
54            }
55        }
56
57        impl HashTreeRoot for $t {
58            fn hash_tree_root<D: Digest<OutputSize = U32>>(&self) -> [u8; 32] {
59                let mut chunk = [0u8; 32];
60                chunk[..$size].copy_from_slice(&self.to_le_bytes());
61                chunk
62            }
63        }
64    };
65}
66
67impl_uint!(u8, 1);
68impl_uint!(u16, 2);
69impl_uint!(u32, 4);
70impl_uint!(u64, 8);
71impl_uint!(u128, 16);
72
73// --------------------------------------------------------------------------
74// bool
75// --------------------------------------------------------------------------
76
77impl Encode for bool {
78    fn is_ssz_fixed_len() -> bool {
79        true
80    }
81    fn ssz_fixed_len() -> usize {
82        1
83    }
84    fn is_basic_type() -> bool {
85        true
86    }
87    fn ssz_bytes_len(&self) -> usize {
88        1
89    }
90    fn ssz_append(&self, buf: &mut Vec<u8>) {
91        buf.push(if *self { 1 } else { 0 });
92    }
93}
94
95impl Decode for bool {
96    fn is_ssz_fixed_len() -> bool {
97        true
98    }
99    fn ssz_fixed_len() -> usize {
100        1
101    }
102    fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
103        if bytes.len() != 1 {
104            return Err(DecodeError::UnexpectedEof {
105                expected: 1,
106                actual: bytes.len(),
107            });
108        }
109        match bytes[0] {
110            0 => Ok(false),
111            1 => Ok(true),
112            v => Err(DecodeError::InvalidBool(v)),
113        }
114    }
115}
116
117impl HashTreeRoot for bool {
118    fn hash_tree_root<D: Digest<OutputSize = U32>>(&self) -> [u8; 32] {
119        let mut chunk = [0u8; 32];
120        chunk[0] = if *self { 1 } else { 0 };
121        chunk
122    }
123}
124
125// --------------------------------------------------------------------------
126// NonZeroU32
127// --------------------------------------------------------------------------
128
129impl Encode for NonZeroU32 {
130    fn is_ssz_fixed_len() -> bool {
131        true
132    }
133    fn ssz_fixed_len() -> usize {
134        4
135    }
136    fn ssz_bytes_len(&self) -> usize {
137        4
138    }
139    fn ssz_append(&self, buf: &mut Vec<u8>) {
140        buf.extend_from_slice(&self.get().to_le_bytes());
141    }
142}
143
144impl Decode for NonZeroU32 {
145    fn is_ssz_fixed_len() -> bool {
146        true
147    }
148    fn ssz_fixed_len() -> usize {
149        4
150    }
151    fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
152        let raw = u32::from_ssz_bytes(bytes)?;
153        NonZeroU32::new(raw).ok_or(DecodeError::ZeroNonZero)
154    }
155}
156
157impl HashTreeRoot for NonZeroU32 {
158    fn hash_tree_root<D: Digest<OutputSize = U32>>(&self) -> [u8; 32] {
159        let mut chunk = [0u8; 32];
160        chunk[..4].copy_from_slice(&self.get().to_le_bytes());
161        chunk
162    }
163}
164
165// --------------------------------------------------------------------------
166// U256 — 32-byte little-endian unsigned integer wrapper.
167// --------------------------------------------------------------------------
168
169/// A 256-bit unsigned integer represented as 32 little-endian bytes.
170///
171/// SSZ encodes `uint256` as 32 raw LE bytes; the hash tree root is the
172/// 32-byte representation itself (single chunk).
173#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
174pub struct U256(pub [u8; 32]);
175
176impl Encode for U256 {
177    fn is_ssz_fixed_len() -> bool {
178        true
179    }
180    fn ssz_fixed_len() -> usize {
181        32
182    }
183    fn ssz_bytes_len(&self) -> usize {
184        32
185    }
186    fn ssz_append(&self, buf: &mut Vec<u8>) {
187        buf.extend_from_slice(&self.0);
188    }
189}
190
191impl Decode for U256 {
192    fn is_ssz_fixed_len() -> bool {
193        true
194    }
195    fn ssz_fixed_len() -> usize {
196        32
197    }
198    fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
199        if bytes.len() != 32 {
200            return Err(DecodeError::UnexpectedEof {
201                expected: 32,
202                actual: bytes.len(),
203            });
204        }
205        let mut arr = [0u8; 32];
206        arr.copy_from_slice(bytes);
207        Ok(U256(arr))
208    }
209}
210
211impl HashTreeRoot for U256 {
212    fn hash_tree_root<D: Digest<OutputSize = U32>>(&self) -> [u8; 32] {
213        self.0
214    }
215}
216
217// --------------------------------------------------------------------------
218// Fixed-size byte arrays [u8; N] — SSZ-spec equivalent of a `ByteVector[N]`.
219// --------------------------------------------------------------------------
220
221impl<const N: usize> Encode for [u8; N] {
222    fn is_ssz_fixed_len() -> bool {
223        true
224    }
225    fn ssz_fixed_len() -> usize {
226        N
227    }
228    fn ssz_bytes_len(&self) -> usize {
229        N
230    }
231    fn ssz_append(&self, buf: &mut Vec<u8>) {
232        buf.extend_from_slice(self);
233    }
234}
235
236impl<const N: usize> Decode for [u8; N] {
237    fn is_ssz_fixed_len() -> bool {
238        true
239    }
240    fn ssz_fixed_len() -> usize {
241        N
242    }
243    fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
244        if bytes.len() != N {
245            return Err(DecodeError::UnexpectedEof {
246                expected: N,
247                actual: bytes.len(),
248            });
249        }
250        let mut arr = [0u8; N];
251        arr.copy_from_slice(bytes);
252        Ok(arr)
253    }
254}
255
256impl<const N: usize> HashTreeRoot for [u8; N] {
257    fn hash_tree_root<D: Digest<OutputSize = U32>>(&self) -> [u8; 32] {
258        let chunks = pack_bytes(self);
259        let limit = N.div_ceil(32).max(1);
260        merkleize::<D>(&chunks, limit)
261    }
262}
263
264// Note: `[T; N]` impls for non-byte T are deferred. Use `FixedVector<T,
265// N>` for typed fixed-length arrays. The blanket `[u8; N]` impls above
266// cover byte arrays; jar's only non-byte fixed-array usages today
267// (e.g. `[Reg; 8]`) live behind newtypes that can use `FixedVector`
268// during the Stage-2+ migration.
269
270// --------------------------------------------------------------------------
271// Fixed-size arrays of u64 — `[u64; N]`.
272//
273// Encoded as `N * 8` little-endian bytes with no length prefix (SSZ's
274// equivalent of `Vector<uint64, N>`). Hash tree root packs the bytes into
275// 32-byte chunks and merkleizes with `ceil(N*8/32)` chunks. Does not
276// conflict with the `[u8; N]` blanket above — different element type.
277// --------------------------------------------------------------------------
278
279impl<const N: usize> Encode for [u64; N] {
280    fn is_ssz_fixed_len() -> bool {
281        true
282    }
283    fn ssz_fixed_len() -> usize {
284        N * 8
285    }
286    fn ssz_bytes_len(&self) -> usize {
287        N * 8
288    }
289    fn ssz_append(&self, buf: &mut Vec<u8>) {
290        for v in self {
291            buf.extend_from_slice(&v.to_le_bytes());
292        }
293    }
294}
295
296impl<const N: usize> Decode for [u64; N] {
297    fn is_ssz_fixed_len() -> bool {
298        true
299    }
300    fn ssz_fixed_len() -> usize {
301        N * 8
302    }
303    fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
304        if bytes.len() != N * 8 {
305            return Err(DecodeError::UnexpectedEof {
306                expected: N * 8,
307                actual: bytes.len(),
308            });
309        }
310        let mut out = [0u64; N];
311        for (i, v) in out.iter_mut().enumerate() {
312            let s = i * 8;
313            let arr: [u8; 8] = bytes[s..s + 8].try_into().expect("len checked");
314            *v = u64::from_le_bytes(arr);
315        }
316        Ok(out)
317    }
318}
319
320impl<const N: usize> HashTreeRoot for [u64; N] {
321    fn hash_tree_root<D: Digest<OutputSize = U32>>(&self) -> [u8; 32] {
322        // Treat as a fixed-length `Vector<uint64, N>`. Pack to bytes,
323        // merkleize with `limit = ceil(N*8/32)` chunks. No mix_in_length
324        // (fixed-size vector).
325        let mut buf: Vec<u8> = Vec::with_capacity(N * 8);
326        for v in self {
327            buf.extend_from_slice(&v.to_le_bytes());
328        }
329        let chunks = pack_bytes(&buf);
330        let limit = (N * 8).div_ceil(32).max(1);
331        merkleize::<D>(&chunks, limit)
332    }
333}
334
335// --------------------------------------------------------------------------
336// Option<T> — SSZ Union form (selector 0 = None, 1 = Some).
337// --------------------------------------------------------------------------
338
339impl<T: Encode> Encode for Option<T> {
340    fn is_ssz_fixed_len() -> bool {
341        false
342    }
343    fn ssz_fixed_len() -> usize {
344        BYTES_PER_LENGTH_OFFSET
345    }
346    fn ssz_bytes_len(&self) -> usize {
347        match self {
348            None => 1,
349            Some(t) => 1 + t.ssz_bytes_len(),
350        }
351    }
352    fn ssz_append(&self, buf: &mut Vec<u8>) {
353        match self {
354            None => buf.push(0),
355            Some(t) => {
356                buf.push(1);
357                t.ssz_append(buf);
358            }
359        }
360    }
361}
362
363impl<T: Decode> Decode for Option<T> {
364    fn is_ssz_fixed_len() -> bool {
365        false
366    }
367    fn ssz_fixed_len() -> usize {
368        BYTES_PER_LENGTH_OFFSET
369    }
370    fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
371        let selector_byte = read_slice(bytes, 0, 1)?[0];
372        match selector_byte {
373            0 => {
374                if bytes.len() != 1 {
375                    return Err(DecodeError::TrailingBytes {
376                        expected: 1,
377                        actual: bytes.len(),
378                    });
379                }
380                Ok(None)
381            }
382            1 => {
383                let inner = T::from_ssz_bytes(&bytes[1..])?;
384                Ok(Some(inner))
385            }
386            v => Err(DecodeError::InvalidSelector(v)),
387        }
388    }
389}
390
391impl<T: HashTreeRoot> HashTreeRoot for Option<T> {
392    fn hash_tree_root<D: Digest<OutputSize = U32>>(&self) -> [u8; 32] {
393        match self {
394            None => option_selector_hash::<D>([0u8; 32], 0),
395            Some(t) => option_selector_hash::<D>(t.hash_tree_root::<D>(), 1),
396        }
397    }
398}