Skip to main content

eth_valkyoth_codec/rlp/
integer.rs

1use core::convert::TryFrom;
2
3use crate::{DecodeAccumulator, DecodeError, DecodeLimits, require_exact_consumption};
4
5use super::{RlpScalar, RlpScalarForm, decode_rlp_scalar_partial};
6
7/// Ethereum U256 maximum byte width in bytes.
8///
9/// Mirrors the primitive crate's internal `MAX_U256_BYTES`; changes to
10/// Ethereum integer canonicality rules must be applied to both crates.
11pub const MAX_RLP_U256_BYTES: usize = 32;
12
13const MAX_U64_BYTES: usize = 8;
14const MAX_U128_BYTES: usize = 16;
15// Mirrors the primitive crate's internal integer radix. Keep both in sync.
16const INTEGER_RADIX_U64: u64 = 256;
17const INTEGER_RADIX_U128: u128 = 256;
18
19/// Borrowed canonical RLP integer.
20///
21/// Ethereum integers are encoded as shortest-form unsigned big-endian bytes.
22/// The empty payload represents zero. A non-empty payload whose first byte is
23/// zero is rejected.
24#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25pub struct RlpInteger<'a> {
26    scalar: RlpScalar<'a>,
27}
28
29impl<'a> RlpInteger<'a> {
30    /// Validates a decoded RLP scalar as a canonical integer.
31    pub fn try_from_scalar(scalar: RlpScalar<'a>) -> Result<Self, DecodeError> {
32        validate_integer_payload(scalar.payload())?;
33        Ok(Self { scalar })
34    }
35
36    /// Returns the underlying scalar byte string.
37    #[must_use]
38    pub const fn scalar(self) -> RlpScalar<'a> {
39        self.scalar
40    }
41
42    /// Returns the canonical integer payload bytes.
43    #[must_use]
44    pub const fn payload(self) -> &'a [u8] {
45        self.scalar.payload()
46    }
47
48    /// Returns the total encoded item length in bytes.
49    #[must_use]
50    pub const fn encoded_len(self) -> usize {
51        self.scalar.encoded_len()
52    }
53
54    /// Returns the RLP header length in bytes.
55    #[must_use]
56    pub const fn header_len(self) -> usize {
57        self.scalar.header_len()
58    }
59
60    /// Returns the canonical scalar form used by the integer encoding.
61    #[must_use]
62    pub const fn form(self) -> RlpScalarForm {
63        self.scalar.form()
64    }
65
66    /// Returns true when the integer value is zero.
67    #[must_use]
68    pub const fn is_zero(self) -> bool {
69        self.scalar.payload().is_empty()
70    }
71
72    /// Converts this integer to `u64` after checking the byte width.
73    pub fn to_u64(self) -> Result<u64, DecodeError> {
74        fold_u64(self.payload())
75    }
76
77    /// Converts this integer to `u128` after checking the byte width.
78    pub fn to_u128(self) -> Result<u128, DecodeError> {
79        fold_u128(self.payload())
80    }
81
82    /// Converts this integer to right-aligned unsigned 256-bit bytes.
83    pub fn to_be_bytes32(self) -> Result<[u8; 32], DecodeError> {
84        to_be_bytes32(self.payload())
85    }
86}
87
88impl<'a> TryFrom<RlpScalar<'a>> for RlpInteger<'a> {
89    type Error = DecodeError;
90
91    fn try_from(value: RlpScalar<'a>) -> Result<Self, Self::Error> {
92        Self::try_from_scalar(value)
93    }
94}
95
96/// Decodes exactly one canonical RLP integer.
97pub fn decode_rlp_integer<'a>(
98    input: &'a [u8],
99    limits: DecodeLimits,
100) -> Result<RlpInteger<'a>, DecodeError> {
101    let mut accumulator = limits.accumulator();
102    let integer = decode_rlp_integer_partial(input, &mut accumulator)?;
103    require_exact_consumption(integer.encoded_len(), input.len())?;
104    Ok(integer)
105}
106
107/// Decodes one canonical RLP integer from the start of `input`.
108///
109/// Warning: this intentionally accepts trailing bytes. Use
110/// [`decode_rlp_integer`] when the full input must be consumed.
111///
112/// The input-length budget check applies to the full `input` slice, not only
113/// the consumed integer bytes. Callers that decode from a larger outer buffer
114/// must pre-slice before calling this helper.
115pub fn decode_rlp_integer_partial<'a>(
116    input: &'a [u8],
117    accumulator: &mut DecodeAccumulator,
118) -> Result<RlpInteger<'a>, DecodeError> {
119    let scalar = decode_rlp_scalar_partial(input, accumulator)?;
120    RlpInteger::try_from_scalar(scalar)
121}
122
123/// Decodes exactly one canonical RLP integer and converts it to `u64`.
124pub fn decode_rlp_u64(input: &[u8], limits: DecodeLimits) -> Result<u64, DecodeError> {
125    decode_rlp_integer(input, limits)?.to_u64()
126}
127
128/// Decodes exactly one canonical RLP integer and converts it to `u128`.
129pub fn decode_rlp_u128(input: &[u8], limits: DecodeLimits) -> Result<u128, DecodeError> {
130    decode_rlp_integer(input, limits)?.to_u128()
131}
132
133/// Decodes exactly one canonical RLP integer as unsigned 256-bit bytes.
134pub fn decode_rlp_u256_bytes(input: &[u8], limits: DecodeLimits) -> Result<[u8; 32], DecodeError> {
135    decode_rlp_integer(input, limits)?.to_be_bytes32()
136}
137
138fn validate_integer_payload(payload: &[u8]) -> Result<(), DecodeError> {
139    if payload.first().is_some_and(|byte| *byte == 0) {
140        return Err(DecodeError::Malformed);
141    }
142    Ok(())
143}
144
145fn fold_u64(payload: &[u8]) -> Result<u64, DecodeError> {
146    // Pre-condition: validate_integer_payload already rejected leading zeros.
147    // Length is the only integer-canonicality guard needed here.
148    if payload.len() > MAX_U64_BYTES {
149        return Err(DecodeError::LengthOverflow);
150    }
151
152    let mut value = 0_u64;
153    for byte in payload {
154        value = value
155            .checked_mul(INTEGER_RADIX_U64)
156            .ok_or(DecodeError::LengthOverflow)?;
157        value = value
158            .checked_add(u64::from(*byte))
159            .ok_or(DecodeError::LengthOverflow)?;
160    }
161    Ok(value)
162}
163
164fn fold_u128(payload: &[u8]) -> Result<u128, DecodeError> {
165    // Pre-condition: validate_integer_payload already rejected leading zeros.
166    // Length is the only integer-canonicality guard needed here.
167    if payload.len() > MAX_U128_BYTES {
168        return Err(DecodeError::LengthOverflow);
169    }
170
171    let mut value = 0_u128;
172    for byte in payload {
173        value = value
174            .checked_mul(INTEGER_RADIX_U128)
175            .ok_or(DecodeError::LengthOverflow)?;
176        value = value
177            .checked_add(u128::from(*byte))
178            .ok_or(DecodeError::LengthOverflow)?;
179    }
180    Ok(value)
181}
182
183fn to_be_bytes32(payload: &[u8]) -> Result<[u8; 32], DecodeError> {
184    // Pre-condition: validate_integer_payload already rejected leading zeros.
185    // Length is the only integer-canonicality guard needed here.
186    if payload.len() > MAX_RLP_U256_BYTES {
187        return Err(DecodeError::LengthOverflow);
188    }
189
190    let mut output = [0_u8; 32];
191    let start = MAX_RLP_U256_BYTES
192        .checked_sub(payload.len())
193        .ok_or(DecodeError::LengthOverflow)?;
194    // start is 32 - payload.len(), with payload.len() <= 32, so the range is
195    // always inside output. Keep the Result form to satisfy indexing policy.
196    let target = output
197        .get_mut(start..)
198        .ok_or(DecodeError::OffsetOutOfBounds)?;
199    target.copy_from_slice(payload);
200    Ok(output)
201}