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