thruput_u256/lib.rs
1//! 256-bit integers stored as `[u32; 8]`, native to a 32-bit register file,
2//! with the math utilities an Ethereum port reaches for.
3//!
4//! [`U256`] is the unsigned type and [`I256`] its two's complement signed
5//! counterpart. Both carry the utility surface an Ethereum port expects:
6//! OpenZeppelin's `Math.sol` on the unsigned side ([`U256::mul_div`] on an
7//! exact 512-bit intermediate, [`U256::mul_shr`], modular arithmetic,
8//! [`U256::integer_sqrt`] and the `log2`/`log10`/`log256` family with a
9//! [`Rounding`] direction) and `SignedMath.sol` on the signed one
10//! ([`I256::average`], [`I256::min`], [`I256::max`],
11//! [`I256::unsigned_abs`]).
12//!
13//! Parity's `uint` stores four `u64` limbs and multiplies through `u128`
14//! intermediates, which on riscv32 is a `compiler_builtins` call rather than an
15//! instruction. Measured on the swap suite: its `Div`/`Rem` lowered to 11,300
16//! bytes of rv32 text and its `integer_sqrt` to 12,732 -- together over a third
17//! of the 64 KiB artifact code cap.
18//!
19//! Both types behave like the primitives on overflow: the operators wrap when
20//! `overflow-checks` is off (as every contract profile sets it) and panic when
21//! it is on. Use [`U256::checked_add`] and friends where it matters.
22//!
23//! [`I256`] costs far less than a second implementation. Addition,
24//! subtraction, multiplication and the bitwise operations produce the same
25//! bits on both types, so only comparison, division, the arithmetic right
26//! shift and formatting are genuinely separate code -- see `signed`.
27//!
28//! Every `Display`, `FromStr`, `LowerHex` and `UpperHex` impl in this crate --
29//! on the integers and on their error types alike -- sits behind the `fmt`
30//! feature, so a guest build carries neither the decimal-division loop nor the
31//! string literals those impls need. `thruput-sdk` enables `fmt` only under
32//! `cfg(not(target_arch = "riscv32"))`, so a contract does not build them at
33//! all rather than building them and relying on the linker to notice they are
34//! dead: a `Display` bound on [`U256`] does not resolve for
35//! `riscv32im-unknown-none-elf`. Cargo features are additive, so a dependency
36//! that names this crate with `fmt` itself would still turn it back on --
37//! nothing here can prevent that. `Debug` is the deliberate exception -- see
38//! `fmt`.
39//!
40//! The keying is exact under the `nightly` feature (guest builds get it
41//! through `thruput-sdk`), which reads rustc's real `overflow-checks` flag.
42//! Without it — on a stable compiler, where that cfg does not exist — overflow
43//! checks are always on: the operators panic on overflow in every profile.
44#![cfg_attr(not(any(feature = "std", test)), no_std)]
45// Keys the operators off the same flag rustc uses for the primitives. Behind a
46// cargo feature so stable consumers (the CLI's spec-tool build) can compile
47// this crate: without it the operators check overflow unconditionally --
48// stable consumers are host tools, never gas-metered contracts, so the guard
49// is cheap and never silently wraps. Guest builds always run nightly and get
50// `nightly` through `thruput-sdk`'s target-gated dependency, keeping the exact
51// keying.
52#![cfg_attr(feature = "nightly", feature(cfg_overflow_checks))]
53
54mod arith;
55mod div;
56mod fmt;
57mod integration;
58mod math;
59mod signed;
60mod signed_arith;
61mod signed_fmt;
62mod signed_math;
63mod sqrt;
64
65pub use div::{checked_mul_div_u128, mul_div_u128};
66pub use math::Rounding;
67pub use signed::I256;
68
69#[cfg(feature = "fmt")]
70pub use fmt::ParseU256Error;
71#[cfg(feature = "fmt")]
72pub use signed_fmt::ParseI256Error;
73
74/// The number of 32-bit limbs in a [`U256`].
75pub(crate) const LIMBS: usize = 8;
76
77/// A 256-bit unsigned integer.
78///
79/// Limbs are least-significant-first in memory; the wire encoding is
80/// most-significant-first (see `integration`), matching xdrs' `u128`.
81///
82#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
83pub struct U256 {
84 pub(crate) limbs: [u32; LIMBS],
85}
86
87impl U256 {
88 /// The additive identity.
89 pub const ZERO: Self = Self::from_limbs([0; LIMBS]);
90
91 /// The multiplicative identity.
92 pub const ONE: Self = Self::from_limbs([1, 0, 0, 0, 0, 0, 0, 0]);
93
94 /// The largest representable value.
95 pub const MAX: Self = Self::from_limbs([u32::MAX; LIMBS]);
96
97 /// The size of this integer in bits.
98 pub const BITS: u32 = 256;
99
100 /// Constructs a value from limbs, least-significant-first.
101 #[inline]
102 pub const fn from_limbs(limbs: [u32; LIMBS]) -> Self {
103 Self { limbs }
104 }
105
106 /// Constructs a value from a `u128`.
107 ///
108 /// Inherent, and deliberately so. `From` is implemented for every unsigned
109 /// width, which leaves `U256::from(1000)` ambiguous -- an unsuffixed
110 /// literal matches all of them -- and an inherent function shadows the
111 /// trait for the path form, pinning the literal to `u128`. It is also
112 /// usable in const position, which `From::from` is not.
113 ///
114 /// The trait impls are untouched, so `.into()` and generic `T: Into<U256>`
115 /// code still convert from any width. What this shadowing costs is
116 /// `U256::from(x)` for an `x` narrower than `u128`; use `x.into()` or
117 /// [`from_u32`](Self::from_u32) / [`from_u64`](Self::from_u64) there.
118 #[inline]
119 pub const fn from(n: u128) -> Self {
120 Self::from_u128(n)
121 }
122
123 /// Constructs a value from a `u128`, under a name no trait shadows.
124 ///
125 /// [`from`](Self::from) is this function behind the literal-pinning
126 /// shadow, and the `From<u128>` impl calls it too, so neither body
127 /// depends on the shadowing to terminate.
128 #[inline]
129 pub const fn from_u128(n: u128) -> Self {
130 Self::from_limbs([
131 n as u32,
132 (n >> 32) as u32,
133 (n >> 64) as u32,
134 (n >> 96) as u32,
135 0,
136 0,
137 0,
138 0,
139 ])
140 }
141
142 /// Constructs a value from a `u32`, without widening at the call site.
143 #[inline]
144 pub const fn from_u32(n: u32) -> Self {
145 Self::from_limbs([n, 0, 0, 0, 0, 0, 0, 0])
146 }
147
148 /// Constructs a value from a `u64`, without widening at the call site.
149 #[inline]
150 pub const fn from_u64(n: u64) -> Self {
151 Self::from_limbs([n as u32, (n >> 32) as u32, 0, 0, 0, 0, 0, 0])
152 }
153
154 /// Returns the limbs, least-significant-first.
155 #[inline]
156 pub const fn to_limbs(self) -> [u32; LIMBS] {
157 self.limbs
158 }
159
160 /// Returns `true` if this is zero.
161 #[inline]
162 pub const fn is_zero(self) -> bool {
163 let mut i = 0;
164 while i < LIMBS {
165 if self.limbs[i] != 0 {
166 return false;
167 }
168 i += 1;
169 }
170 true
171 }
172
173 /// The number of significant bits: `0` for zero, otherwise one more than
174 /// the index of the highest set bit.
175 #[inline]
176 pub const fn bit_len(self) -> u32 {
177 let mut i = LIMBS;
178 while i > 0 {
179 i -= 1;
180 if self.limbs[i] != 0 {
181 return (i as u32) * 32 + (32 - self.limbs[i].leading_zeros());
182 }
183 }
184 0
185 }
186
187 /// The number of leading zero bits.
188 #[inline]
189 pub const fn leading_zeros(self) -> u32 {
190 Self::BITS - self.bit_len()
191 }
192
193 /// The number of significant limbs: `0` for zero, otherwise one more than
194 /// the index of the highest nonzero limb.
195 #[inline]
196 pub(crate) const fn limb_len(self) -> usize {
197 let mut i = LIMBS;
198 while i > 0 {
199 i -= 1;
200 if self.limbs[i] != 0 {
201 return i + 1;
202 }
203 }
204 0
205 }
206
207 /// Returns the bit at `i`, counting from the least significant.
208 ///
209 /// Bits at or above [`BITS`](Self::BITS) are zero, rather than a panic:
210 /// callers index by a computed position (an exponent bit, a mask width),
211 /// and the out-of-range answer is unambiguous.
212 #[inline]
213 pub const fn bit(self, i: u32) -> bool {
214 if i >= Self::BITS {
215 return false;
216 }
217 (self.limbs[(i / 32) as usize] >> (i % 32)) & 1 == 1
218 }
219
220 /// Comparison usable from a `const fn`.
221 ///
222 /// The [`Ord`] impl is the one callers want; it just is not `const`, and
223 /// the arithmetic in this crate is. Same ordering, same answer.
224 #[inline]
225 pub const fn const_cmp(self, other: Self) -> core::cmp::Ordering {
226 let mut i = LIMBS;
227 while i > 0 {
228 i -= 1;
229 if self.limbs[i] < other.limbs[i] {
230 return core::cmp::Ordering::Less;
231 }
232 if self.limbs[i] > other.limbs[i] {
233 return core::cmp::Ordering::Greater;
234 }
235 }
236 core::cmp::Ordering::Equal
237 }
238
239 /// `self == other`, from a `const fn`.
240 #[inline]
241 pub(crate) const fn const_eq(self, other: Self) -> bool {
242 matches!(self.const_cmp(other), core::cmp::Ordering::Equal)
243 }
244
245 /// `self < other`, from a `const fn`.
246 #[inline]
247 pub(crate) const fn const_lt(self, other: Self) -> bool {
248 matches!(self.const_cmp(other), core::cmp::Ordering::Less)
249 }
250
251 /// `self > other`, from a `const fn`.
252 #[inline]
253 pub(crate) const fn const_gt(self, other: Self) -> bool {
254 matches!(self.const_cmp(other), core::cmp::Ordering::Greater)
255 }
256
257 /// `self >= other`, from a `const fn`.
258 #[inline]
259 pub(crate) const fn const_ge(self, other: Self) -> bool {
260 !self.const_lt(other)
261 }
262
263 /// Returns the big-endian byte representation.
264 ///
265 /// Outlined on rv32 -- every contract that stores a `U256` pays for this
266 /// one. See the inlining note in `arith`.
267 #[cfg_attr(target_arch = "riscv32", inline(never))]
268 pub const fn to_be_bytes(self) -> [u8; 32] {
269 let mut out = [0u8; 32];
270 let mut i = 0;
271 while i < LIMBS {
272 // Limb `i` is the `i`th least significant, so it occupies the
273 // `i`th group of four bytes counting back from the end.
274 let bytes = self.limbs[LIMBS - 1 - i].to_be_bytes();
275 out[i * 4] = bytes[0];
276 out[i * 4 + 1] = bytes[1];
277 out[i * 4 + 2] = bytes[2];
278 out[i * 4 + 3] = bytes[3];
279 i += 1;
280 }
281 out
282 }
283
284 /// Constructs a value from its big-endian byte representation.
285 ///
286 /// Outlined on rv32; see the inlining note in `arith`.
287 #[cfg_attr(target_arch = "riscv32", inline(never))]
288 pub const fn from_be_bytes(bytes: [u8; 32]) -> Self {
289 let mut limbs = [0u32; LIMBS];
290 let mut i = 0;
291 while i < LIMBS {
292 limbs[LIMBS - 1 - i] = u32::from_be_bytes([
293 bytes[i * 4],
294 bytes[i * 4 + 1],
295 bytes[i * 4 + 2],
296 bytes[i * 4 + 3],
297 ]);
298 i += 1;
299 }
300 Self::from_limbs(limbs)
301 }
302}
303
304// ---------------------------------------------------------------------------
305// Conversions
306// ---------------------------------------------------------------------------
307
308// The bodies below all call inherent-only names (`from_u32`, `from_u64`,
309// `from_u128`), never `Self::from`. That spelling would compile today -- the
310// inherent `from` shadows the trait in path form -- but its termination would
311// hang on the shadowing: rename the inherent fn and `Self::from(n)` inside
312// `impl From<u128>` re-resolves to the trait impl and recurses, with only a
313// warn-level `unconditional_recursion` lint standing in the way.
314macro_rules! impl_from_small {
315 ($($t:ty),* $(,)?) => {
316 $(
317 impl From<$t> for U256 {
318 #[inline]
319 fn from(n: $t) -> Self {
320 Self::from_u32(n as u32)
321 }
322 }
323 )*
324 };
325}
326
327impl_from_small!(u8, u16, u32);
328
329impl From<u64> for U256 {
330 #[inline]
331 fn from(n: u64) -> Self {
332 Self::from_u64(n)
333 }
334}
335
336impl From<u128> for U256 {
337 #[inline]
338 fn from(n: u128) -> Self {
339 Self::from_u128(n)
340 }
341}
342
343impl From<bool> for U256 {
344 #[inline]
345 fn from(b: bool) -> Self {
346 if b { Self::ONE } else { Self::ZERO }
347 }
348}
349
350/// Returned when an integer conversion in this crate does not fit.
351///
352/// One type covers every direction -- [`U256`] or [`I256`] down to a narrower
353/// primitive, and between the two 256-bit types -- and every reason, whether
354/// the value is out of the target's range or, converting to an unsigned type,
355/// negative. That is what `core::num::TryFromIntError` does:
356/// `u8::try_from(300u32)` and `u8::try_from(-1i32)` both yield it, with no
357/// split by the source's signedness and no payload to inspect. Splitting it
358/// here would add a type without adding information.
359///
360/// It is a distinct type from `core::num::TryFromIntError` only because that
361/// one cannot be constructed outside `core`.
362#[derive(Debug, Clone, Copy, PartialEq, Eq)]
363pub struct TryFromIntError(pub(crate) ());
364
365/// Gated on `fmt` like every other `Display` in this crate: the message is a
366/// string literal, and a guest build that never formats the error should not
367/// carry it. `std` implies `fmt`, so the `Error` impl below always has its
368/// `Display` bound.
369#[cfg(feature = "fmt")]
370impl core::fmt::Display for TryFromIntError {
371 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
372 f.write_str("value does not fit in the target type")
373 }
374}
375
376#[cfg(feature = "std")]
377impl std::error::Error for TryFromIntError {}
378
379macro_rules! impl_try_into {
380 ($($t:ty => $limbs:expr),* $(,)?) => {
381 $(
382 impl TryFrom<U256> for $t {
383 type Error = TryFromIntError;
384
385 #[inline]
386 fn try_from(n: U256) -> Result<Self, Self::Error> {
387 if n.limb_len() > $limbs {
388 return Err(TryFromIntError(()));
389 }
390 let mut out: $t = 0;
391 let mut i = $limbs;
392 while i > 0 {
393 i -= 1;
394 out = (out << 16 << 16) | (n.limbs[i] as $t);
395 }
396 Ok(out)
397 }
398 }
399 )*
400 };
401}
402
403// `<< 16 << 16` rather than `<< 32`: for `u32` a 32-bit shift is UB-adjacent
404// (it panics/wraps depending on profile), and this form stays correct for
405// every width without a special case.
406impl_try_into!(u32 => 1, u64 => 2, u128 => 4);
407
408/// Targets narrower than a limb, where the limb count is not the bound.
409///
410/// `impl_try_into!` keys its check on `limb_len`, which cannot express "fits
411/// in eight bits", and its `<< 16 << 16` accumulator would overflow the target
412/// outright. Narrowing to `u32` first and letting that type's own conversion
413/// finish the job is both correct and smaller than a bespoke bound.
414///
415/// These exist for parity with OpenZeppelin's `SafeCast`, whose `toUint8`
416/// through `toUint128` a port will reach for by name.
417macro_rules! impl_try_into_narrow {
418 ($($t:ty),* $(,)?) => {
419 $(
420 impl TryFrom<U256> for $t {
421 type Error = TryFromIntError;
422
423 #[inline]
424 fn try_from(n: U256) -> Result<Self, Self::Error> {
425 let narrowed = u32::try_from(n)?;
426 <$t>::try_from(narrowed)
427 .map_err(|_| TryFromIntError(()))
428 }
429 }
430 )*
431 };
432}
433
434impl_try_into_narrow!(u8, u16);
435
436impl PartialOrd for U256 {
437 #[inline]
438 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
439 Some(self.cmp(other))
440 }
441}
442
443impl Ord for U256 {
444 #[inline]
445 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
446 let mut i = LIMBS;
447 while i > 0 {
448 i -= 1;
449 if self.limbs[i] != other.limbs[i] {
450 return self.limbs[i].cmp(&other.limbs[i]);
451 }
452 }
453 core::cmp::Ordering::Equal
454 }
455}