Skip to content

Commit f10dcbe

Browse files
authored
field array: fix serialization (#18)
* field array: fix serialization * clippy
1 parent 397e7cc commit f10dcbe

File tree

1 file changed

+99
-8
lines changed

1 file changed

+99
-8
lines changed

src/array.rs

Lines changed: 99 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use serde::{Deserialize, Deserializer, Serialize, de::Visitor, ser::SerializeTuple};
1+
use serde::de::{SeqAccess, Visitor};
2+
use serde::{Deserialize, Serialize};
23
use ssz::{Decode, DecodeError, Encode};
34
use std::ops::{Deref, DerefMut};
45

@@ -94,9 +95,10 @@ impl<const N: usize> Serialize for FieldArray<N> {
9495
where
9596
S: serde::Serializer,
9697
{
98+
use serde::ser::SerializeTuple;
9799
let mut seq = serializer.serialize_tuple(N)?;
98-
for element in self.0.iter().map(PrimeField32::as_canonical_u32) {
99-
seq.serialize_element(&element)?;
100+
for element in &self.0 {
101+
seq.serialize_element(element)?;
100102
}
101103
seq.end()
102104
}
@@ -105,7 +107,7 @@ impl<const N: usize> Serialize for FieldArray<N> {
105107
impl<'de, const N: usize> Deserialize<'de> for FieldArray<N> {
106108
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
107109
where
108-
D: Deserializer<'de>,
110+
D: serde::Deserializer<'de>,
109111
{
110112
struct FieldArrayVisitor<const N: usize>;
111113

@@ -118,14 +120,13 @@ impl<'de, const N: usize> Deserialize<'de> for FieldArray<N> {
118120

119121
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
120122
where
121-
A: serde::de::SeqAccess<'de>,
123+
A: SeqAccess<'de>,
122124
{
123125
let mut arr = [F::ZERO; N];
124-
for (i, p) in arr.iter_mut().enumerate() {
125-
let val: u32 = seq
126+
for (i, elem) in arr.iter_mut().enumerate() {
127+
*elem = seq
126128
.next_element()?
127129
.ok_or_else(|| serde::de::Error::invalid_length(i, &self))?;
128-
*p = F::new(val);
129130
}
130131
Ok(FieldArray(arr))
131132
}
@@ -347,6 +348,41 @@ mod tests {
347348
prop_assert_eq!(encoded.len(), expected_size);
348349
prop_assert_eq!(field_array.ssz_bytes_len(), expected_size);
349350
}
351+
352+
#[test]
353+
fn proptest_serde_roundtrip(
354+
values in prop::collection::vec(0u32..F::ORDER_U32, LARGE_SIZE)
355+
) {
356+
let arr: [F; LARGE_SIZE] = std::array::from_fn(|i| F::new(values[i]));
357+
let original = FieldArray(arr);
358+
359+
let config = bincode::config::standard().with_fixed_int_encoding();
360+
let encoded = bincode::serde::encode_to_vec(original, config)
361+
.expect("Failed to serialize");
362+
let decoded: FieldArray<LARGE_SIZE> = bincode::serde::decode_from_slice(&encoded, config)
363+
.expect("Failed to deserialize")
364+
.0;
365+
366+
prop_assert_eq!(original, decoded);
367+
}
368+
369+
#[test]
370+
fn proptest_serde_deterministic(
371+
values in prop::array::uniform5(0u32..F::ORDER_U32)
372+
) {
373+
let arr = values.map(F::new);
374+
let field_array = FieldArray(arr);
375+
376+
let config = bincode::config::standard().with_fixed_int_encoding();
377+
378+
// Encode twice and verify both encodings are identical
379+
let encoding1 = bincode::serde::encode_to_vec(field_array, config)
380+
.expect("Failed to serialize");
381+
let encoding2 = bincode::serde::encode_to_vec(field_array, config)
382+
.expect("Failed to serialize");
383+
384+
prop_assert_eq!(encoding1, encoding2);
385+
}
350386
}
351387

352388
#[test]
@@ -370,4 +406,59 @@ mod tests {
370406
let encoded = bincode::serde::encode_to_vec(arr, config).unwrap();
371407
assert_eq!(encoded.len(), arr.len() * F::NUM_BYTES);
372408
}
409+
410+
#[test]
411+
fn test_serde_uses_montgomery_form() {
412+
// Create a field array with known values
413+
let arr = FieldArray([F::new(1), F::new(2), F::new(3)]);
414+
415+
// Serialize using bincode
416+
let config = bincode::config::standard().with_fixed_int_encoding();
417+
let encoded = bincode::serde::encode_to_vec(arr, config).unwrap();
418+
419+
// Extract the raw u32 values from the encoded bytes
420+
let mut raw_values = Vec::new();
421+
for i in 0..arr.len() {
422+
let start = i * F::NUM_BYTES;
423+
let chunk = &encoded[start..start + F::NUM_BYTES];
424+
let value = u32::from_le_bytes(chunk.try_into().unwrap());
425+
raw_values.push(value);
426+
}
427+
428+
// Verify that the serialized values are in Montgomery form, not canonical form.
429+
//
430+
// - If they were in canonical form, we would see [1, 2, 3]
431+
// - In Montgomery form, they should be different values
432+
//
433+
// We check this to confirm the serialization is using Montgomery form as in Plonky3.
434+
//
435+
// This is for consistency with other serializations including field elements over the codebase.
436+
assert_ne!(
437+
raw_values,
438+
vec![1, 2, 3],
439+
"Values should be in Montgomery form, not canonical form"
440+
);
441+
442+
// Verify that when we access the internal value directly, it matches what was serialized
443+
// This confirms we're serializing the Montgomery representation
444+
for (i, &expected_monty) in raw_values.iter().enumerate() {
445+
// Access the internal Montgomery value through unsafe (for testing only)
446+
let actual_monty = unsafe {
447+
// SAFETY: MontyField31 is repr(transparent) with a u32 value field
448+
std::ptr::read((&raw const arr[i]).cast::<u32>())
449+
};
450+
451+
assert_eq!(
452+
actual_monty, expected_monty,
453+
"Element {} should serialize its internal Montgomery form",
454+
i
455+
);
456+
}
457+
458+
// Verify roundtrip works correctly
459+
let decoded: FieldArray<3> = bincode::serde::decode_from_slice(&encoded, config)
460+
.expect("Failed to deserialize")
461+
.0;
462+
assert_eq!(arr, decoded, "Roundtrip should preserve values");
463+
}
373464
}

0 commit comments

Comments
 (0)