|
| 1 | +//! A Cairo-like u256 type. |
| 2 | +//! |
| 3 | +//! This `U256` type purpose is not to be used to perfomr arithmetic operations, |
| 4 | +//! but rather to offer a handy interface to convert from and to Cairo's u256 values. |
| 5 | +//! Indeed, the Cairo language represent u256 values as a two felts struct, |
| 6 | +//! representing the `low` and `high` 128 bits of the value. |
| 7 | +//! We mirror this representation, allowing for efficient serialization/deserializatin. |
| 8 | +//! |
| 9 | +//! We recommand you create From/Into implementation to bridge the gap between your favourite u256 type, |
| 10 | +//! and the one provided by this crate. |
| 11 | +
|
| 12 | +#[cfg(feature = "num-traits")] |
| 13 | +mod num_traits_impl; |
| 14 | +mod primitive_conversions; |
| 15 | +#[cfg(test)] |
| 16 | +mod tests; |
| 17 | + |
| 18 | +use core::{fmt::Debug, str::FromStr}; |
| 19 | + |
| 20 | +use crate::felt::{Felt, PrimitiveFromFeltError}; |
| 21 | + |
| 22 | +/// Error types that can occur when parsing a string into a U256. |
| 23 | +#[derive(Debug)] |
| 24 | +pub enum FromStrError { |
| 25 | + /// The string contain too many characters to be the representation of a valid u256 value. |
| 26 | + StringTooLong, |
| 27 | + /// The parsed value exceeds the maximum representable value for U256. |
| 28 | + ValueTooBig, |
| 29 | + /// The string contains invalid characters for the expected format. |
| 30 | + Invalid, |
| 31 | + /// Underlying u128 parsing failed. |
| 32 | + Parse(<u128 as FromStr>::Err), |
| 33 | +} |
| 34 | + |
| 35 | +impl core::fmt::Display for FromStrError { |
| 36 | + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
| 37 | + match self { |
| 38 | + FromStrError::ValueTooBig => core::fmt::Display::fmt("value too big for u256", f), |
| 39 | + FromStrError::Invalid => core::fmt::Display::fmt("invalid characters", f), |
| 40 | + FromStrError::Parse(e) => { |
| 41 | + // Avoid using format as it requires `alloc` |
| 42 | + core::fmt::Display::fmt("invalid string: ", f)?; |
| 43 | + core::fmt::Display::fmt(e, f) |
| 44 | + } |
| 45 | + FromStrError::StringTooLong => { |
| 46 | + core::fmt::Display::fmt("too many characters to be a valid u256 represenation", f) |
| 47 | + } |
| 48 | + } |
| 49 | + } |
| 50 | +} |
| 51 | + |
| 52 | +#[cfg(feature = "std")] |
| 53 | +impl std::error::Error for FromStrError {} |
| 54 | + |
| 55 | +/// A 256-bit unsigned integer represented as two 128-bit components. |
| 56 | +/// |
| 57 | +/// The internal representation uses big-endian ordering where `high` contains |
| 58 | +/// the most significant 128 bits and `low` contains the least significant 128 bits. |
| 59 | +/// This reflects the way u256 are represented in the Cairo language. |
| 60 | +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] |
| 61 | +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] |
| 62 | +pub struct U256 { |
| 63 | + high: u128, |
| 64 | + low: u128, |
| 65 | +} |
| 66 | + |
| 67 | +impl U256 { |
| 68 | + /// Returns the high 128 bits of the U256 value. |
| 69 | + pub fn high(&self) -> u128 { |
| 70 | + self.high |
| 71 | + } |
| 72 | + |
| 73 | + /// Returns the low 128 bits of the U256 value. |
| 74 | + pub fn low(&self) -> u128 { |
| 75 | + self.low |
| 76 | + } |
| 77 | + |
| 78 | + /// Constructs a U256 from explicit high and low 128-bit components. |
| 79 | + /// |
| 80 | + /// This is the most direct way to create a U256 when you already have |
| 81 | + /// the component values separated. |
| 82 | + pub fn from_parts(high: u128, low: u128) -> Self { |
| 83 | + Self { low, high } |
| 84 | + } |
| 85 | + |
| 86 | + /// Attempts to construct a U256 from two Felt values representing high and low parts. |
| 87 | + /// |
| 88 | + /// This conversion can fail if either Felt value cannot be represented as a u128, |
| 89 | + /// which would indicate the Felt contains a value outside the valid range. |
| 90 | + pub fn try_from_felt_parts(high: Felt, low: Felt) -> Result<Self, PrimitiveFromFeltError> { |
| 91 | + Ok(Self { |
| 92 | + high: high.try_into()?, |
| 93 | + low: low.try_into()?, |
| 94 | + }) |
| 95 | + } |
| 96 | + |
| 97 | + /// Attempts to construct a U256 from decimal string representations of high and low parts. |
| 98 | + /// |
| 99 | + /// Both strings must be valid decimal representations that fit within u128 range. |
| 100 | + pub fn try_from_dec_str_parts(high: &str, low: &str) -> Result<Self, <u128 as FromStr>::Err> { |
| 101 | + Ok(Self { |
| 102 | + high: high.parse()?, |
| 103 | + low: low.parse()?, |
| 104 | + }) |
| 105 | + } |
| 106 | + |
| 107 | + /// Attempts to construct a U256 from hexadecimal string representations of high and low parts. |
| 108 | + /// |
| 109 | + /// Both strings must be valid hexadecimal (prefixed or not) representations that fit within u128 range. |
| 110 | + pub fn try_from_hex_str_parts(high: &str, low: &str) -> Result<Self, <u128 as FromStr>::Err> { |
| 111 | + let high = if high.starts_with("0x") || high.starts_with("0X") { |
| 112 | + &high[2..] |
| 113 | + } else { |
| 114 | + high |
| 115 | + }; |
| 116 | + let low = if low.starts_with("0x") || low.starts_with("0X") { |
| 117 | + &low[2..] |
| 118 | + } else { |
| 119 | + low |
| 120 | + }; |
| 121 | + |
| 122 | + Ok(Self { |
| 123 | + high: u128::from_str_radix(high, 16)?, |
| 124 | + low: u128::from_str_radix(low, 16)?, |
| 125 | + }) |
| 126 | + } |
| 127 | + |
| 128 | + /// Parses a hexadecimal string into a U256 value. |
| 129 | + /// |
| 130 | + /// Accepts strings with or without "0x"/"0X" prefixes and handles leading zero removal. |
| 131 | + /// The implementation automatically determines the split between high and low components |
| 132 | + /// based on string length, with values over 32 hex digits requiring high component usage. |
| 133 | + pub fn from_hex_str(hex_str: &str) -> Result<Self, FromStrError> { |
| 134 | + // Remove prefix |
| 135 | + let string_without_prefix = if hex_str.starts_with("0x") || hex_str.starts_with("0X") { |
| 136 | + &hex_str[2..] |
| 137 | + } else { |
| 138 | + hex_str |
| 139 | + }; |
| 140 | + |
| 141 | + if string_without_prefix.is_empty() { |
| 142 | + return Err(FromStrError::Invalid); |
| 143 | + } |
| 144 | + |
| 145 | + // Remove leading zero |
| 146 | + let string_without_zero_padding = string_without_prefix.trim_start_matches('0'); |
| 147 | + |
| 148 | + let (high, low) = if string_without_zero_padding.is_empty() { |
| 149 | + // The string was uniquely made out of of `0` |
| 150 | + (0, 0) |
| 151 | + } else if string_without_zero_padding.len() > 64 { |
| 152 | + return Err(FromStrError::StringTooLong); |
| 153 | + } else if string_without_zero_padding.len() > 32 { |
| 154 | + // The 32 last characters are the `low` u128 bytes, |
| 155 | + // all the other ones are the `high` u128 bytes. |
| 156 | + let delimiter_index = string_without_zero_padding.len() - 32; |
| 157 | + ( |
| 158 | + u128::from_str_radix(&string_without_zero_padding[0..delimiter_index], 16) |
| 159 | + .map_err(FromStrError::Parse)?, |
| 160 | + u128::from_str_radix(&string_without_zero_padding[delimiter_index..], 16) |
| 161 | + .map_err(FromStrError::Parse)?, |
| 162 | + ) |
| 163 | + } else { |
| 164 | + // There is no `high` bytes. |
| 165 | + ( |
| 166 | + 0, |
| 167 | + u128::from_str_radix(string_without_zero_padding, 16) |
| 168 | + .map_err(FromStrError::Parse)?, |
| 169 | + ) |
| 170 | + }; |
| 171 | + |
| 172 | + Ok(U256 { high, low }) |
| 173 | + } |
| 174 | + |
| 175 | + /// Parses a decimal string into a `u256`. |
| 176 | + /// |
| 177 | + /// Custom arithmetic is executed in order to efficiently parse the input as two `u128` values. |
| 178 | + /// |
| 179 | + /// This implementation performs digit-by-digit multiplication to handle values |
| 180 | + /// that exceed u128 range. The algorithm uses overflow detection to prevent |
| 181 | + /// silent wraparound and ensures accurate representation of large decimal numbers. |
| 182 | + /// Values with more than 78 decimal digits are rejected as they exceed U256 capacity. |
| 183 | + pub fn from_dec_str(dec_str: &str) -> Result<Self, FromStrError> { |
| 184 | + if dec_str.is_empty() { |
| 185 | + return Err(FromStrError::Invalid); |
| 186 | + } |
| 187 | + |
| 188 | + // Ignore leading zeros |
| 189 | + let string_without_zero_padding = dec_str.trim_start_matches('0'); |
| 190 | + |
| 191 | + let (high, low) = if string_without_zero_padding.is_empty() { |
| 192 | + // The string was uniquely made out of of `0` |
| 193 | + (0, 0) |
| 194 | + } else if string_without_zero_padding.len() > 78 { |
| 195 | + return Err(FromStrError::StringTooLong); |
| 196 | + } else { |
| 197 | + let mut low = 0u128; |
| 198 | + let mut high = 0u128; |
| 199 | + |
| 200 | + // b is ascii value of the char less the ascii value of the char '0' |
| 201 | + // which happen to be equal to the number represented by the char. |
| 202 | + // b = ascii(char) - ascii('0') |
| 203 | + for b in string_without_zero_padding |
| 204 | + .bytes() |
| 205 | + .map(|b| b.wrapping_sub(b'0')) |
| 206 | + { |
| 207 | + // Using `wrapping_sub` all non 0-9 characters will yield a value greater than 9. |
| 208 | + if b > 9 { |
| 209 | + return Err(FromStrError::Invalid); |
| 210 | + } |
| 211 | + |
| 212 | + // We use a [long multiplication](https://en.wikipedia.org/wiki/Multiplication_algorithm#Long_multiplication) |
| 213 | + // algorithm to perform the computation. |
| 214 | + // The idea is that if |
| 215 | + // `v = (high << 128) + low` |
| 216 | + // then |
| 217 | + // `v * 10 = ((high * 10) << 128) + low * 10` |
| 218 | + |
| 219 | + // Compute `high * 10`, return error on overflow. |
| 220 | + let (new_high, did_overflow) = high.overflowing_mul(10); |
| 221 | + if did_overflow { |
| 222 | + return Err(FromStrError::ValueTooBig); |
| 223 | + } |
| 224 | + // Now we want to compute `low * 10`, but in case it overflows, we want to carry rather than error. |
| 225 | + // To do so, we perform another long multiplication to get both the result and carry values, |
| 226 | + // this time breaking the u128 (low) value into two u64 (low_low and low_high), |
| 227 | + // perform multiplication on each part individually, extracting an eventual carry, and finally |
| 228 | + // combining them back. |
| 229 | + // |
| 230 | + // Any overflow on the high part will result in an error. |
| 231 | + // Any overflow on the low part should be handled by carrying the extra amount to the high part. |
| 232 | + let (new_low, carry) = { |
| 233 | + let low_low = low as u64; |
| 234 | + let low_high = (low >> 64) as u64; |
| 235 | + |
| 236 | + // Both of those values cannot overflow, as they are u64 stored into a u128. |
| 237 | + // Intead they will just start using the highest half part of their bytes. |
| 238 | + let low_low = (low_low as u128) * 10; |
| 239 | + let low_high = (low_high as u128) * 10; |
| 240 | + |
| 241 | + // The carry of the multiplication per 10 is in the highest 64 bytes of the `low_high` part. |
| 242 | + let carry_mul_10 = low_high >> 64; |
| 243 | + // We shift back the bytes, erasing any carry we may have. |
| 244 | + let low_high_without_carry = low_high << 64; |
| 245 | + |
| 246 | + // By adding back the two low parts together we get its new value. |
| 247 | + let (new_low, did_overflow) = low_low.overflowing_add(low_high_without_carry); |
| 248 | + // I couldn't come up with a value where `did_overflow` is true, |
| 249 | + // but better safe than sorry |
| 250 | + (new_low, carry_mul_10 + if did_overflow { 1 } else { 0 }) |
| 251 | + }; |
| 252 | + |
| 253 | + // Add carry to high if it exists. |
| 254 | + let new_high = if carry != 0 { |
| 255 | + let (new_high, did_overflow) = new_high.overflowing_add(carry); |
| 256 | + // Error if it overflows. |
| 257 | + if did_overflow { |
| 258 | + return Err(FromStrError::ValueTooBig); |
| 259 | + } |
| 260 | + new_high |
| 261 | + } else { |
| 262 | + new_high |
| 263 | + }; |
| 264 | + |
| 265 | + // Add the new digit to low. |
| 266 | + let (new_low, did_overflow) = new_low.overflowing_add(b.into()); |
| 267 | + |
| 268 | + // Add one to high if the previous operation overflowed. |
| 269 | + if did_overflow { |
| 270 | + let (new_high, did_overflow) = new_high.overflowing_add(1); |
| 271 | + // Error if it overflows. |
| 272 | + if did_overflow { |
| 273 | + return Err(FromStrError::ValueTooBig); |
| 274 | + } |
| 275 | + high = new_high; |
| 276 | + } else { |
| 277 | + high = new_high; |
| 278 | + } |
| 279 | + |
| 280 | + low = new_low |
| 281 | + } |
| 282 | + |
| 283 | + (high, low) |
| 284 | + }; |
| 285 | + |
| 286 | + Ok(U256 { high, low }) |
| 287 | + } |
| 288 | +} |
| 289 | + |
| 290 | +impl FromStr for U256 { |
| 291 | + type Err = FromStrError; |
| 292 | + |
| 293 | + /// Parses a string into a U256 by detecting the format automatically. |
| 294 | + /// |
| 295 | + /// Strings beginning with "0x" or "0X" are treated as hexadecimal, |
| 296 | + /// while all other strings are interpreted as decimal. |
| 297 | + fn from_str(s: &str) -> Result<Self, Self::Err> { |
| 298 | + if s.starts_with("0x") || s.starts_with("0X") { |
| 299 | + Self::from_hex_str(s) |
| 300 | + } else { |
| 301 | + Self::from_dec_str(s) |
| 302 | + } |
| 303 | + } |
| 304 | +} |
0 commit comments