1#![no_std]
2#![forbid(unsafe_code)]
3#[cfg(feature = "std")]
6extern crate std;
7
8use core::fmt;
9use core::hash::{Hash, Hasher};
10use eth_valkyoth_codec::{
11 DecodeError, rlp_integer_payload_to_u64, rlp_integer_payload_to_u256_bytes,
12};
13pub use subtle::Choice;
14use subtle::ConstantTimeEq as _;
15
16mod rlp;
17#[cfg(test)]
18mod tests;
19
20pub use rlp::PrimitiveRlpError;
21
22fn parse_canonical_u64(bytes: &[u8]) -> Result<u64, PrimitiveError> {
23 rlp_integer_payload_to_u64(bytes).map_err(map_rlp_integer_error)
26}
27
28fn canonical_u256_bytes(bytes: &[u8]) -> Result<[u8; 32], PrimitiveError> {
29 rlp_integer_payload_to_u256_bytes(bytes).map_err(map_rlp_integer_error)
32}
33
34fn map_rlp_integer_error(error: DecodeError) -> PrimitiveError {
35 match error {
36 DecodeError::Malformed => PrimitiveError::NonCanonicalInteger,
37 DecodeError::LengthOverflow => PrimitiveError::IntegerTooLarge,
38 DecodeError::OffsetOutOfBounds => PrimitiveError::IntegerTooLarge,
39 DecodeError::InputTooLarge
40 | DecodeError::TrailingBytes
41 | DecodeError::DecoderOverread
42 | DecodeError::UnexpectedList
43 | DecodeError::UnexpectedScalar
44 | DecodeError::ListTooLong
45 | DecodeError::NestingTooDeep
46 | DecodeError::AllocationExceeded
47 | DecodeError::ProofTooLarge
48 | DecodeError::ItemCountExceeded
49 | DecodeError::UnreviewedDeploymentPolicy => PrimitiveError::UnexpectedCodecError,
50 _ => PrimitiveError::UnexpectedCodecError,
51 }
52}
53
54macro_rules! id_type {
55 ($name:ident, $inner:ty, $doc:literal) => {
56 #[doc = $doc]
57 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
58 pub struct $name($inner);
59
60 impl $name {
61 #[must_use]
63 pub const fn new(value: $inner) -> Self {
64 Self(value)
65 }
66
67 #[must_use]
69 pub const fn get(self) -> $inner {
70 self.0
71 }
72
73 pub fn try_from_canonical_be_slice(bytes: &[u8]) -> Result<Self, PrimitiveError> {
78 parse_canonical_u64(bytes).map(Self)
79 }
80 }
81
82 impl From<$inner> for $name {
83 fn from(value: $inner) -> Self {
84 Self::new(value)
85 }
86 }
87
88 impl From<$name> for $inner {
89 fn from(value: $name) -> Self {
90 value.get()
91 }
92 }
93 };
94}
95
96id_type!(
97 ChainId,
98 u64,
99 "Ethereum chain identifier.\n\nThis type enforces canonical integer encoding only. EIP-155 assigns chain ID 0 to unsigned legacy transactions; signed transaction validation must reject chain ID 0 independently."
100);
101
102impl ChainId {
103 pub fn try_from_signed_canonical_be_slice(bytes: &[u8]) -> Result<Self, PrimitiveError> {
108 let chain_id = Self::try_from_canonical_be_slice(bytes)?;
109 if chain_id.get() == 0 {
110 return Err(PrimitiveError::ReservedLegacyType);
111 }
112 Ok(chain_id)
113 }
114}
115
116id_type!(BlockNumber, u64, "Ethereum execution-layer block number.");
117id_type!(Gas, u64, "Gas quantity.");
118id_type!(Nonce, u64, "Account transaction nonce.");
119id_type!(UnixTimestamp, u64, "Block timestamp as Unix seconds.");
120
121#[non_exhaustive]
123#[derive(Clone, Copy, Debug, Eq, PartialEq)]
124pub enum PrimitiveError {
125 NonCanonicalInteger,
127 IntegerTooLarge,
129 TransactionTypeTooLarge,
131 ReservedLegacyType,
133 UnexpectedCodecError,
135}
136
137impl fmt::Display for PrimitiveError {
138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139 match self {
140 Self::NonCanonicalInteger => f.write_str("integer is not canonical shortest-form"),
141 Self::IntegerTooLarge => f.write_str("integer exceeds primitive width"),
142 Self::TransactionTypeTooLarge => {
143 f.write_str("transaction type exceeds EIP-2718 typed envelope range")
144 }
145 Self::ReservedLegacyType => {
146 f.write_str("zero is reserved for the legacy transaction domain")
147 }
148 Self::UnexpectedCodecError => {
149 f.write_str("codec returned an unexpected error for this primitive")
150 }
151 }
152 }
153}
154
155#[cfg(feature = "std")]
156impl std::error::Error for PrimitiveError {}
157
158#[derive(Clone, Copy, Debug)]
167pub struct Address([u8; 20]);
168
169impl Address {
170 #[must_use]
172 pub const fn from_bytes(bytes: [u8; 20]) -> Self {
173 Self(bytes)
174 }
175
176 #[must_use]
178 pub const fn to_bytes(self) -> [u8; 20] {
179 self.0
180 }
181
182 #[must_use]
187 pub fn ct_eq(&self, other: &Self) -> Choice {
188 self.0.ct_eq(&other.0)
189 }
190}
191
192impl PartialEq for Address {
193 fn eq(&self, other: &Self) -> bool {
194 bool::from(self.ct_eq(other))
195 }
196}
197
198impl Eq for Address {}
199
200impl Hash for Address {
201 fn hash<H: Hasher>(&self, state: &mut H) {
202 self.0.hash(state);
203 }
204}
205
206impl From<[u8; 20]> for Address {
207 fn from(bytes: [u8; 20]) -> Self {
208 Self::from_bytes(bytes)
209 }
210}
211
212impl From<Address> for [u8; 20] {
213 fn from(value: Address) -> Self {
214 value.to_bytes()
215 }
216}
217
218#[derive(Clone, Copy, Debug)]
227pub struct B256([u8; 32]);
228
229impl B256 {
230 #[must_use]
232 pub const fn from_bytes(bytes: [u8; 32]) -> Self {
233 Self(bytes)
234 }
235
236 #[must_use]
238 pub const fn to_bytes(self) -> [u8; 32] {
239 self.0
240 }
241
242 #[must_use]
247 pub fn ct_eq(&self, other: &Self) -> Choice {
248 self.0.ct_eq(&other.0)
249 }
250}
251
252impl PartialEq for B256 {
253 fn eq(&self, other: &Self) -> bool {
254 bool::from(self.ct_eq(other))
255 }
256}
257
258impl Eq for B256 {}
259
260impl Hash for B256 {
261 fn hash<H: Hasher>(&self, state: &mut H) {
262 self.0.hash(state);
263 }
264}
265
266impl From<[u8; 32]> for B256 {
267 fn from(bytes: [u8; 32]) -> Self {
268 Self::from_bytes(bytes)
269 }
270}
271
272impl From<B256> for [u8; 32] {
273 fn from(value: B256) -> Self {
274 value.to_bytes()
275 }
276}
277
278#[derive(Clone, Copy, Debug)]
280pub struct Wei([u8; 32]);
281
282impl Wei {
283 pub const ZERO: Self = Self([0_u8; 32]);
285
286 #[must_use]
288 pub const fn from_be_bytes(bytes: [u8; 32]) -> Self {
289 Self(bytes)
290 }
291
292 #[must_use]
294 pub const fn from_u128(value: u128) -> Self {
295 let [
296 b0,
297 b1,
298 b2,
299 b3,
300 b4,
301 b5,
302 b6,
303 b7,
304 b8,
305 b9,
306 b10,
307 b11,
308 b12,
309 b13,
310 b14,
311 b15,
312 ] = value.to_be_bytes();
313 Self([
314 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8,
315 0_u8, 0_u8, b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15,
316 ])
317 }
318
319 #[must_use]
321 pub const fn to_be_bytes(self) -> [u8; 32] {
322 self.0
323 }
324
325 pub fn try_from_canonical_be_slice(bytes: &[u8]) -> Result<Self, PrimitiveError> {
330 canonical_u256_bytes(bytes).map(Self)
331 }
332
333 #[must_use]
338 pub fn ct_eq(&self, other: &Self) -> Choice {
339 self.0.ct_eq(&other.0)
340 }
341}
342
343impl PartialEq for Wei {
344 fn eq(&self, other: &Self) -> bool {
345 bool::from(self.ct_eq(other))
346 }
347}
348
349impl Eq for Wei {}
350
351impl Hash for Wei {
352 fn hash<H: Hasher>(&self, state: &mut H) {
353 self.0.hash(state);
354 }
355}
356
357impl From<[u8; 32]> for Wei {
358 fn from(bytes: [u8; 32]) -> Self {
359 Self::from_be_bytes(bytes)
360 }
361}
362
363impl From<Wei> for [u8; 32] {
364 fn from(value: Wei) -> Self {
365 value.to_be_bytes()
366 }
367}
368
369#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
371pub struct TransactionType(u8);
372
373impl TransactionType {
374 pub const LEGACY: Self = Self(0);
376 pub const MAX_TYPED: u8 = 0x7f;
378
379 pub const fn try_new_typed(value: u8) -> Result<Self, PrimitiveError> {
384 match value {
385 0 => Err(PrimitiveError::ReservedLegacyType),
386 1..=Self::MAX_TYPED => Ok(Self(value)),
387 _ => Err(PrimitiveError::TransactionTypeTooLarge),
388 }
389 }
390
391 pub const fn try_new_with_legacy(value: u8) -> Result<Self, PrimitiveError> {
397 if value > Self::MAX_TYPED {
398 return Err(PrimitiveError::TransactionTypeTooLarge);
399 }
400 Ok(Self(value))
401 }
402
403 #[must_use]
405 pub const fn get(self) -> u8 {
406 self.0
407 }
408}
409
410impl TryFrom<u8> for TransactionType {
411 type Error = PrimitiveError;
412
413 fn try_from(value: u8) -> Result<Self, Self::Error> {
419 Self::try_new_typed(value)
420 }
421}
422
423impl From<TransactionType> for u8 {
424 fn from(value: TransactionType) -> Self {
425 value.get()
426 }
427}