Skip to content

Commit

Permalink
feat: felt creation from bytes is infallible (#16)
Browse files Browse the repository at this point in the history
Co-authored-by: Pedro Fontana <[email protected]>
  • Loading branch information
Oppen and pefontana authored Nov 22, 2023
1 parent 8476f50 commit 3d46777
Showing 1 changed file with 136 additions and 17 deletions.
153 changes: 136 additions & 17 deletions crates/starknet-types-core/src/felt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,20 +63,108 @@ impl Felt {
UnsignedInteger::from_limbs([544, 0, 0, 32]),
));

/// Creates a new [Felt] from its big-endian representation in a [u8] slice.
/// This is as performant as [from_bytes_le](Felt::from_bytes_le)
pub fn from_bytes_be(bytes: &[u8]) -> Result<Self, FromBytesError> {
/// Creates a new [Felt] from its big-endian representation in a [u8; 32] array.
/// This is as performant as [from_bytes_le](Felt::from_bytes_le).
pub fn from_bytes_be(bytes: &[u8; 32]) -> Self {
FieldElement::from_bytes_be(bytes)
.map(Self)
.map_err(|_| FromBytesError)
.expect("from_bytes_be shouldn't fail for these many bytes")
}

/// Creates a new [Felt] from its little-endian representation in a [u8] slice.
/// This is as performant as [from_bytes_be](Felt::from_bytes_be)
pub fn from_bytes_le(bytes: &[u8]) -> Result<Self, FromBytesError> {
/// Creates a new [Felt] from its little-endian representation in a [u8; 32] array.
/// This is as performant as [from_bytes_le](Felt::from_bytes_be).
pub fn from_bytes_le(bytes: &[u8; 32]) -> Self {
FieldElement::from_bytes_le(bytes)
.map(Self)
.map_err(|_| FromBytesError)
.expect("from_bytes_le shouldn't fail for these many bytes")
}

/// Creates a new [Felt] from its big-endian representation in a [u8] slice.
/// This is as performant as [from_bytes_le](Felt::from_bytes_le_slice).
/// All bytes in the slice are consumed, as if first creating a big integer
/// from them, but the conversion is performed in constant space on the stack.
pub fn from_bytes_be_slice(bytes: &[u8]) -> Self {
// NB: lambdaworks ignores the remaining bytes when len > 32, so we loop
// multiplying by BASE, effectively decomposing in base 2^256 to build
// digits with a length of 32 bytes. This is analogous to splitting the
// number `xyz` as `x * 10^2 + y * 10^1 + z * 10^0`.
const BASE: Felt = Self(FieldElement::<Stark252PrimeField>::const_from_raw(
UnsignedInteger::from_limbs([
576413109808302096,
18446744073700081664,
5151653887,
18446741271209837569,
]),
));
// Sanity check; gets removed in release builds.
debug_assert_eq!(BASE, Felt::TWO.pow(256u32));

let mut factor = Self::ONE;
let mut res = Self::ZERO;
let chunks = bytes.rchunks_exact(32);
let remainder = chunks.remainder();

for chunk in chunks {
let digit =
Self::from_bytes_be(&chunk.try_into().expect("conversion to same-sized array"));
res += digit * factor;
factor *= BASE;
}

if remainder.is_empty() {
return res;
}

let mut remainder = remainder.iter().rev().cloned();
let buf: [u8; 32] = core::array::from_fn(move |_| remainder.next().unwrap_or_default());
let digit = Self::from_bytes_le(&buf);
res += digit * factor;

res
}

/// Creates a new [Felt] from its little-endian representation in a [u8] slice.
/// This is as performant as [from_bytes_be](Felt::from_bytes_be_slice).
/// All bytes in the slice are consumed, as if first creating a big integer
/// from them, but the conversion is performed in constant space on the stack.
pub fn from_bytes_le_slice(bytes: &[u8]) -> Self {
// NB: lambdaworks ignores the remaining bytes when len > 32, so we loop
// multiplying by BASE, effectively decomposing in base 2^256 to build
// digits with a length of 32 bytes. This is analogous to splitting the
// number `xyz` as `x * 10^2 + y * 10^1 + z * 10^0`.
const BASE: Felt = Self(FieldElement::<Stark252PrimeField>::const_from_raw(
UnsignedInteger::from_limbs([
576413109808302096,
18446744073700081664,
5151653887,
18446741271209837569,
]),
));
// Sanity check; gets removed in release builds.
debug_assert_eq!(BASE, Felt::TWO.pow(256u32));

let mut factor = Self::ONE;
let mut res = Self::ZERO;
let chunks = bytes.chunks_exact(32);
let remainder = chunks.remainder();

for chunk in chunks {
let digit =
Self::from_bytes_le(&chunk.try_into().expect("conversion to same-sized array"));
res += digit * factor;
factor *= BASE;
}

if remainder.is_empty() {
return res;
}

let mut remainder = remainder.iter().cloned();
let buf: [u8; 32] = core::array::from_fn(move |_| remainder.next().unwrap_or_default());
let digit = Self::from_bytes_le(&buf);
res += digit * factor;

res
}

/// Converts to big-endian byte representation in a [u8] array.
Expand Down Expand Up @@ -835,24 +923,24 @@ mod test {

proptest! {
#[test]
fn new_in_range(ref x in any::<[u8; 40]>()) {
let x_be = Felt::from_bytes_be(x).unwrap();
fn new_in_range(ref x in any::<[u8; 32]>()) {
let x_be = Felt::from_bytes_be(x);
prop_assert!(x_be < Felt::MAX);
let x_le = Felt::from_bytes_le(x).unwrap();
let x_le = Felt::from_bytes_le(x);
prop_assert!(x_le < Felt::MAX);
}

#[test]
fn to_be_bytes(ref x in any::<Felt>()) {
let bytes = x.to_bytes_be();
let y = &Felt::from_bytes_be(&bytes).unwrap();
let y = &Felt::from_bytes_be(&bytes);
prop_assert_eq!(x, y);
}

#[test]
fn to_le_bytes(ref x in any::<Felt>()) {
let bytes = x.to_bytes_le();
let y = &Felt::from_bytes_le(&bytes).unwrap();
let y = &Felt::from_bytes_le(&bytes);
prop_assert_eq!(x, y);
}

Expand All @@ -869,7 +957,7 @@ mod test {
res[i] = acc;
acc = 0;
}
let y = &Felt::from_bytes_be(&res).unwrap();
let y = &Felt::from_bytes_be(&res);
prop_assert_eq!(x, y);
}

Expand All @@ -893,13 +981,44 @@ mod test {
bytes[(3 - i) * 8 + j] = limb_bytes[j]
}
}
let y = &Felt::from_bytes_le(&bytes).unwrap();
let y = &Felt::from_bytes_le(&bytes);
prop_assert_eq!(x, y);
}

#[test]
fn from_bytes_be_in_range(ref x in any::<[u8; 40]>()) {
let x = Felt::from_bytes_be(x).unwrap();
fn from_bytes_le_slice_works_for_all_lengths(x in 0..1000usize) {
let bytes: [u8; 1000] = core::array::from_fn(|i| i as u8);
let expected = bytes.iter()
.enumerate()
.map(|(i, x)| Felt::from(*x) * Felt::from(256).pow(i as u64))
.take(x)
.sum::<Felt>();
let x = Felt::from_bytes_le_slice(&bytes[0..x]);
prop_assert_eq!(x, expected, "x={:x} expected={:x}", x, expected);
}

#[test]
fn from_bytes_be_slice_works_for_all_lengths(x in 0..1000usize) {
let bytes: [u8; 1000] = core::array::from_fn(|i| i as u8);
let expected = bytes.iter()
.rev()
.enumerate()
.map(|(i, x)| Felt::from(*x) * Felt::from(256).pow(i as u64))
.take(x)
.sum::<Felt>();
let x = Felt::from_bytes_be_slice(&bytes[1000-x..1000]);
prop_assert_eq!(x, expected, "x={:x} expected={:x}", x, expected);
}

#[test]
fn from_bytes_le_in_range(ref x in any::<[u8; 32]>()) {
let x = Felt::from_bytes_le(x);
prop_assert!(x <= Felt::MAX);
}

#[test]
fn from_bytes_be_in_range(ref x in any::<[u8; 32]>()) {
let x = Felt::from_bytes_be(x);
prop_assert!(x <= Felt::MAX);
}

Expand Down

0 comments on commit 3d46777

Please sign in to comment.