From f37eedca7f286187c71587797e08da189c4eee70 Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Tue, 18 Feb 2025 21:53:43 +0200 Subject: [PATCH] feat(acir_field): Add little-endian byte serialization for FieldElement (#7258) Co-authored-by: Akosh Farkash --- acvm-repo/acir_field/src/field_element.rs | 61 +++++++++++++++++++++-- acvm-repo/acir_field/src/generic_ark.rs | 6 +++ 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/acvm-repo/acir_field/src/field_element.rs b/acvm-repo/acir_field/src/field_element.rs index 0249b410aa7..8afc76da9d8 100644 --- a/acvm-repo/acir_field/src/field_element.rs +++ b/acvm-repo/acir_field/src/field_element.rs @@ -274,12 +274,15 @@ impl AcirField for FieldElement { } fn to_be_bytes(self) -> Vec { - // to_be_bytes! uses little endian which is why we reverse the output - // TODO: Add a little endian equivalent, so the caller can use whichever one - // TODO they desire + let mut bytes = self.to_le_bytes(); + bytes.reverse(); + bytes + } + + /// Converts the field element to a vector of bytes in little-endian order + fn to_le_bytes(self) -> Vec { let mut bytes = Vec::new(); self.0.serialize_uncompressed(&mut bytes).unwrap(); - bytes.reverse(); bytes } @@ -289,6 +292,12 @@ impl AcirField for FieldElement { FieldElement(F::from_be_bytes_mod_order(bytes)) } + /// Converts bytes in little-endian order into a FieldElement and applies a + /// reduction if needed. + fn from_le_bytes_reduce(bytes: &[u8]) -> FieldElement { + FieldElement(F::from_le_bytes_mod_order(bytes)) + } + /// Returns the closest number of bytes to the bits specified /// This method truncates fn fetch_nearest_bytes(&self, num_bits: usize) -> Vec { @@ -405,6 +414,50 @@ mod tests { assert_eq!(max_num_bits_bn254, 254); } + proptest! { + #[test] + fn test_endianness_prop(value in any::()) { + let field = FieldElement::::from(value); + // Test serialization consistency + let le_bytes = field.to_le_bytes(); + let be_bytes = field.to_be_bytes(); + + let mut reversed_le = le_bytes.clone(); + reversed_le.reverse(); + prop_assert_eq!(&be_bytes, &reversed_le, "BE bytes should be reverse of LE bytes"); + + // Test deserialization consistency + let from_le = FieldElement::from_le_bytes_reduce(&le_bytes); + let from_be = FieldElement::from_be_bytes_reduce(&be_bytes); + prop_assert_eq!(from_le, from_be, "Deserialization should be consistent between LE and BE"); + prop_assert_eq!(from_le, field, "Deserialized value should match original"); + } + } + + #[test] + fn test_endianness() { + let field = FieldElement::::from(0x1234_5678_u32); + let le_bytes = field.to_le_bytes(); + let be_bytes = field.to_be_bytes(); + + // Check that the bytes are reversed between BE and LE + let mut reversed_le = le_bytes.clone(); + reversed_le.reverse(); + assert_eq!(&be_bytes, &reversed_le); + + // Verify we can reconstruct the same field element from either byte order + let from_le = FieldElement::from_le_bytes_reduce(&le_bytes); + let from_be = FieldElement::from_be_bytes_reduce(&be_bytes); + assert_eq!(from_le, from_be); + assert_eq!(from_le, field); + + // Additional test with a larger number to ensure proper byte handling + let large_field = FieldElement::::from(0x0123_4567_89AB_CDEF_u64); + let large_le = large_field.to_le_bytes(); + let reconstructed = FieldElement::from_le_bytes_reduce(&large_le); + assert_eq!(reconstructed, large_field); + } + proptest! { // This currently panics due to the fact that we allow inputs which are greater than the field modulus, // automatically reducing them to fit within the canonical range. diff --git a/acvm-repo/acir_field/src/generic_ark.rs b/acvm-repo/acir_field/src/generic_ark.rs index 74927c07a36..04761dd1ed0 100644 --- a/acvm-repo/acir_field/src/generic_ark.rs +++ b/acvm-repo/acir_field/src/generic_ark.rs @@ -75,6 +75,12 @@ pub trait AcirField: /// Converts bytes into a FieldElement and applies a reduction if needed. fn from_be_bytes_reduce(bytes: &[u8]) -> Self; + /// Converts bytes in little-endian order into a FieldElement and applies a reduction if needed. + fn from_le_bytes_reduce(bytes: &[u8]) -> Self; + + /// Converts the field element to a vector of bytes in little-endian order + fn to_le_bytes(self) -> Vec; + /// Returns the closest number of bytes to the bits specified /// This method truncates fn fetch_nearest_bytes(&self, num_bits: usize) -> Vec;