Skip to content

Commit cbaced5

Browse files
feat: improve non-human-readable felt serialization space efficiency (#155)
Co-authored-by: Gabriel Bosio <[email protected]>
1 parent 2ede7b7 commit cbaced5

File tree

2 files changed

+55
-18
lines changed

2 files changed

+55
-18
lines changed

crates/starknet-types-core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ rand_chacha = "0.9"
6666
rand = "0.9.2"
6767
rstest = "0.24"
6868
lazy_static = { version = "1.5", default-features = false }
69+
bincode = "1"
6970

7071
[[bench]]
7172
name = "criterion_hashes"

crates/starknet-types-core/src/felt/serde.rs

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ use core::fmt;
66
use lambdaworks_math::field::{
77
element::FieldElement, fields::fft_friendly::stark_252_prime_field::Stark252PrimeField,
88
};
9-
use serde::{de, Deserialize, Serialize};
9+
use serde::{
10+
de::{self},
11+
Deserialize, Serialize,
12+
};
1013

1114
use super::Felt;
1215

@@ -18,7 +21,9 @@ impl Serialize for Felt {
1821
if serializer.is_human_readable() {
1922
serializer.serialize_str(&format!("{:#x}", self))
2023
} else {
21-
serializer.serialize_bytes(&self.to_bytes_be())
24+
let be_bytes = self.to_bytes_be();
25+
let first_significant_byte_index = be_bytes.iter().position(|&b| b != 0).unwrap_or(31);
26+
serializer.serialize_bytes(&be_bytes[first_significant_byte_index..])
2227
}
2328
}
2429
}
@@ -66,21 +71,25 @@ impl de::Visitor<'_> for FeltVisitor {
6671
where
6772
E: de::Error,
6873
{
69-
match value.try_into() {
70-
Ok(v) => Ok(Felt::from_bytes_be(&v)),
71-
_ => Err(de::Error::invalid_length(value.len(), &self)),
74+
if value.len() > 32 {
75+
return Err(de::Error::invalid_length(value.len(), &self));
7276
}
77+
78+
let mut buffer = [0u8; 32];
79+
buffer[32 - value.len()..].copy_from_slice(value);
80+
Ok(Felt::from_bytes_be(&buffer))
7381
}
7482
}
7583

7684
#[cfg(test)]
7785
mod tests {
7886
use super::*;
87+
use bincode::Options;
88+
use proptest::prelude::*;
89+
use serde_test::{assert_tokens, Configure, Token};
7990

8091
#[test]
8192
fn serde() {
82-
use serde_test::{assert_tokens, Configure, Token};
83-
8493
assert_tokens(&Felt::ZERO.readable(), &[Token::String("0x0")]);
8594
assert_tokens(&Felt::TWO.readable(), &[Token::String("0x2")]);
8695
assert_tokens(&Felt::THREE.readable(), &[Token::String("0x3")]);
@@ -91,21 +100,48 @@ mod tests {
91100
)],
92101
);
93102

94-
assert_tokens(&Felt::ZERO.compact(), &[Token::Bytes(&[0; 32])]);
95-
static TWO: [u8; 32] = [
96-
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
97-
0, 0, 2,
98-
];
99-
assert_tokens(&Felt::TWO.compact(), &[Token::Bytes(&TWO)]);
100-
static THREE: [u8; 32] = [
101-
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
102-
0, 0, 3,
103-
];
104-
assert_tokens(&Felt::THREE.compact(), &[Token::Bytes(&THREE)]);
103+
assert_tokens(&Felt::ZERO.compact(), &[Token::Bytes(&[0; 1])]);
104+
assert_tokens(&Felt::TWO.compact(), &[Token::Bytes(&[2])]);
105+
assert_tokens(&Felt::THREE.compact(), &[Token::Bytes(&[3])]);
105106
static MAX: [u8; 32] = [
106107
8, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
107108
0, 0, 0,
108109
];
109110
assert_tokens(&Felt::MAX.compact(), &[Token::Bytes(&MAX)]);
111+
assert_tokens(
112+
&Felt::from_hex_unchecked("0xbabe").compact(),
113+
&[Token::Bytes(&[0xba, 0xbe])],
114+
);
115+
assert_tokens(
116+
&Felt::from_hex_unchecked("0xba000000be").compact(),
117+
&[Token::Bytes(&[0xba, 0, 0, 0, 0xbe])],
118+
);
119+
assert_tokens(
120+
&Felt::from_hex_unchecked("0xbabe0000").compact(),
121+
&[Token::Bytes(&[0xba, 0xbe, 0, 0])],
122+
);
123+
}
124+
125+
#[test]
126+
fn backward_compatible_deserialization() {
127+
static TWO_SERIALIZED_USING_PREVIOUS_IMPLEMENTATION: [u8; 33] = [
128+
32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
129+
0, 0, 0, 2,
130+
];
131+
132+
let options = bincode::DefaultOptions::new();
133+
let deserialized = options
134+
.deserialize(&TWO_SERIALIZED_USING_PREVIOUS_IMPLEMENTATION)
135+
.unwrap();
136+
assert_eq!(Felt::TWO, deserialized);
137+
}
138+
139+
proptest! {
140+
#[test]
141+
fn compact_round_trip(ref x in any::<Felt>()) {
142+
let serialized = bincode::serialize(&x).unwrap();
143+
let deserialized: Felt = bincode::deserialize(&serialized).unwrap();
144+
prop_assert_eq!(x, &deserialized);
145+
}
110146
}
111147
}

0 commit comments

Comments
 (0)