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