-
Notifications
You must be signed in to change notification settings - Fork 43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Non-Human-Readable Deserialization for Felt Using Serde #76
base: main
Are you sure you want to change the base?
Changes from all commits
fd0e062
850ee6e
d74fc67
6723646
d25da84
7b98204
fd2299f
5302ed4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -924,10 +924,15 @@ mod arithmetic { | |
mod serde_impl { | ||
use alloc::{format, string::String}; | ||
use core::fmt; | ||
use serde::{de, ser::SerializeSeq, Deserialize, Serialize}; | ||
use serde::{ | ||
de, de::DeserializeSeed, ser::SerializeTuple, Deserialize, Deserializer, Serialize, | ||
Serializer, | ||
}; | ||
|
||
use super::*; | ||
|
||
const COMPRESSED: u8 = 0b1000_0000; | ||
|
||
impl Serialize for Felt { | ||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||
where | ||
|
@@ -936,11 +941,33 @@ mod serde_impl { | |
if serializer.is_human_readable() { | ||
serializer.serialize_str(&format!("{:#x}", self)) | ||
} else { | ||
let mut seq = serializer.serialize_seq(Some(32))?; | ||
for b in self.to_bytes_be() { | ||
seq.serialize_element(&b)?; | ||
let bytes = self.to_bytes_be(); | ||
let first_significant_byte = bytes.iter().position(|&b| b != 0).unwrap_or(31); | ||
|
||
struct RestSerialize<'a>(&'a [u8]); | ||
impl<'a> Serialize for RestSerialize<'a> { | ||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { | ||
let mut tup = serializer.serialize_tuple(self.0.len())?; | ||
for el in self.0 { | ||
tup.serialize_element(el)?; | ||
} | ||
tup.end() | ||
} | ||
} | ||
|
||
if first_significant_byte > 1 { | ||
let len = 32 - first_significant_byte; | ||
|
||
let mut tup = serializer.serialize_tuple(2)?; | ||
tup.serialize_element(&(len as u8 | COMPRESSED))?; | ||
tup.serialize_element(&RestSerialize(&bytes[first_significant_byte..]))?; | ||
tup.end() | ||
} else { | ||
let mut tup = serializer.serialize_tuple(2)?; | ||
tup.serialize_element(&bytes[0])?; | ||
tup.serialize_element(&RestSerialize(&bytes[1..]))?; | ||
tup.end() | ||
} | ||
seq.end() | ||
} | ||
} | ||
} | ||
|
@@ -950,7 +977,11 @@ mod serde_impl { | |
where | ||
D: ::serde::Deserializer<'de>, | ||
{ | ||
deserializer.deserialize_str(FeltVisitor) | ||
if deserializer.is_human_readable() { | ||
deserializer.deserialize_str(FeltVisitor) | ||
} else { | ||
deserializer.deserialize_tuple(2, FeltVisitor) | ||
} | ||
} | ||
} | ||
|
||
|
@@ -975,6 +1006,51 @@ mod serde_impl { | |
.ok_or(String::from("Expected hex string to be prefixed by '0x'")) | ||
.map_err(de::Error::custom) | ||
} | ||
|
||
fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> { | ||
struct RestDeserialize<'a>(&'a mut [u8]); | ||
impl<'a, 'de> DeserializeSeed<'de> for RestDeserialize<'a> { | ||
type Value = (); | ||
fn deserialize<D: Deserializer<'de>>( | ||
self, | ||
deserializer: D, | ||
) -> Result<Self::Value, D::Error> { | ||
struct RestDeserializeVisitor<'a>(&'a mut [u8]); | ||
impl<'a, 'de> de::Visitor<'de> for RestDeserializeVisitor<'a> { | ||
type Value = (); | ||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
formatter.write_str("Failed to deserialize felt") | ||
} | ||
fn visit_seq<A: de::SeqAccess<'de>>( | ||
self, | ||
mut seq: A, | ||
) -> Result<Self::Value, A::Error> { | ||
for (i, el) in self.0.iter_mut().enumerate() { | ||
*el = seq | ||
.next_element::<u8>()? | ||
.ok_or(de::Error::invalid_length(i + 1, &"more bytes"))?; | ||
} | ||
Ok(()) | ||
} | ||
} | ||
deserializer.deserialize_tuple(self.0.len(), RestDeserializeVisitor(self.0)) | ||
} | ||
} | ||
|
||
let mut bytes = [0u8; 32]; | ||
let first = seq | ||
.next_element::<u8>()? | ||
.ok_or(de::Error::invalid_length(1, &"tagging byte"))?; | ||
|
||
if first & COMPRESSED != 0 { | ||
let len = first & !COMPRESSED; | ||
seq.next_element_seed(RestDeserialize(&mut bytes[32 - len as usize..]))?; | ||
} else { | ||
bytes[0] = first; | ||
seq.next_element_seed(RestDeserialize(&mut bytes[1..]))?; | ||
} | ||
Ok(Felt::from_bytes_be(&bytes)) | ||
} | ||
} | ||
} | ||
|
||
|
@@ -1637,41 +1713,62 @@ mod test { | |
#[test] | ||
#[cfg(feature = "serde")] | ||
fn deserialize() { | ||
assert_de_tokens(&Felt::ZERO, &[Token::String("0x0")]); | ||
assert_de_tokens(&Felt::TWO, &[Token::String("0x2")]); | ||
assert_de_tokens(&Felt::THREE, &[Token::String("0x3")]); | ||
assert_de_tokens(&Felt::ZERO.readable(), &[Token::String("0x0")]); | ||
assert_de_tokens(&Felt::TWO.readable(), &[Token::String("0x2")]); | ||
assert_de_tokens(&Felt::THREE.readable(), &[Token::String("0x3")]); | ||
assert_de_tokens( | ||
&Felt::MAX, | ||
&[Token::String( | ||
"0x800000000000011000000000000000000000000000000000000000000000000", | ||
)], | ||
); | ||
} | ||
|
||
#[test] | ||
#[cfg(feature = "serde")] | ||
fn serialize() { | ||
assert_ser_tokens(&Felt::ZERO.readable(), &[Token::String("0x0")]); | ||
assert_ser_tokens(&Felt::TWO.readable(), &[Token::String("0x2")]); | ||
assert_ser_tokens(&Felt::THREE.readable(), &[Token::String("0x3")]); | ||
assert_ser_tokens( | ||
&Felt::MAX.readable(), | ||
&[Token::String( | ||
"0x800000000000011000000000000000000000000000000000000000000000000", | ||
)], | ||
); | ||
|
||
assert_ser_tokens( | ||
assert_de_tokens( | ||
&Felt::ZERO.compact(), | ||
&[ | ||
Token::Seq { len: Some(32) }, | ||
Token::Tuple { len: 2 }, | ||
Token::U8(1 | 0x80), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we really need this first element containing len and the compressed flag?
And know that because we are using big endian repr, missing bytes are There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Serialization can be: let be_bytes = self.to_bytes_be();
let mut i = match be_bytes.iter().find(|b| b != 0) {
Some(i) => i,
None => { // handle Felt::ZERO and return }
}
let mut seq = serializer.serialize_seq(Some(32 - i))?;
while i < 32 {
seq.serialize_element(be_bytes[i])?;
i += 1;
}
seq.end(); And deserialization: let mut buffer = [0; 32];
let mut i = 32;
while i > 0 {
if let Some(b) = seq.next_element()? {
buffer[i-1] = b;
} else {
break;
}
i -= 1;
}
if i == 0 && seq.next_element()?.is_some() {
// Return error invalid len
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why wouldn't this way simpler impl work? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But isn't what we are already doing with And how exactly could it fail? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea here we had with @jbcaron was to make a binary format that never exceeds 32 bytes in the worse case (abitrary hash), and compresses it down to a few bytes in the best cases (small felt) To do that, we think of the input as a byte stream and we use the felt unused bits in the first byte to encode a flag telling us if the felt is compressed or not When the felt is compressed we treat that first byte as the length, so we read However, we found out that there is an issue: using serde Taking inspiration from fixed-sized arrays implementation in serde, they are serialized using That gave me an idea though - which I tried here https://gist.github.com/cchudant/9bbb025c60a9fe23ca56b895fdcff13c @tdelabro what do you think of that? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, I understand better. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still, why do you need the flag for? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You need the byte that contain flag+len only when you serialize [Felt]. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So my recomandation would be:
This way values that are a single felt will always be between 1 and 32 bytes.
This makes you spare one byte in the case of felts that are 32 bytes long and nothing for all the others. Not gonna lie, it's not a huge optimization but may still be worth for some people. |
||
Token::Tuple { len: 1 }, | ||
Token::U8(0), | ||
Token::TupleEnd, | ||
Token::TupleEnd, | ||
], | ||
); | ||
assert_de_tokens( | ||
&Felt::TWO.compact(), | ||
&[ | ||
Token::Tuple { len: 2 }, | ||
Token::U8(1 | 0x80), | ||
Token::Tuple { len: 1 }, | ||
Token::U8(2), | ||
Token::TupleEnd, | ||
Token::TupleEnd, | ||
], | ||
); | ||
assert_de_tokens( | ||
&Felt::THREE.compact(), | ||
&[ | ||
Token::Tuple { len: 2 }, | ||
Token::U8(1 | 0x80), | ||
Token::Tuple { len: 1 }, | ||
Token::U8(3), | ||
Token::TupleEnd, | ||
Token::TupleEnd, | ||
], | ||
); | ||
assert_de_tokens( | ||
&Felt::MAX.compact(), | ||
&[ | ||
Token::Tuple { len: 2 }, | ||
Token::U8(8), | ||
Token::Tuple { len: 31 }, | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(17), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
|
@@ -1696,93 +1793,64 @@ mod test { | |
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::TupleEnd, | ||
Token::TupleEnd, | ||
], | ||
); | ||
} | ||
|
||
#[test] | ||
#[cfg(feature = "serde")] | ||
fn serialize() { | ||
assert_ser_tokens(&Felt::ZERO.readable(), &[Token::String("0x0")]); | ||
assert_ser_tokens(&Felt::TWO.readable(), &[Token::String("0x2")]); | ||
assert_ser_tokens(&Felt::THREE.readable(), &[Token::String("0x3")]); | ||
assert_ser_tokens( | ||
&Felt::MAX.readable(), | ||
&[Token::String( | ||
"0x800000000000011000000000000000000000000000000000000000000000000", | ||
)], | ||
); | ||
|
||
assert_ser_tokens( | ||
&Felt::ZERO.compact(), | ||
&[ | ||
Token::Tuple { len: 2 }, | ||
Token::U8(1 | 0x80), | ||
Token::Tuple { len: 1 }, | ||
Token::U8(0), | ||
Token::SeqEnd, | ||
Token::TupleEnd, | ||
Token::TupleEnd, | ||
], | ||
); | ||
assert_ser_tokens( | ||
&Felt::TWO.compact(), | ||
&[ | ||
Token::Seq { len: Some(32) }, | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::Tuple { len: 2 }, | ||
Token::U8(1 | 0x80), | ||
Token::Tuple { len: 1 }, | ||
Token::U8(2), | ||
Token::SeqEnd, | ||
Token::TupleEnd, | ||
Token::TupleEnd, | ||
], | ||
); | ||
assert_ser_tokens( | ||
&Felt::THREE.compact(), | ||
&[ | ||
Token::Seq { len: Some(32) }, | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::Tuple { len: 2 }, | ||
Token::U8(1 | 0x80), | ||
Token::Tuple { len: 1 }, | ||
Token::U8(3), | ||
Token::SeqEnd, | ||
Token::TupleEnd, | ||
Token::TupleEnd, | ||
], | ||
); | ||
assert_ser_tokens( | ||
&Felt::MAX.compact(), | ||
&[ | ||
Token::Seq { len: Some(32) }, | ||
Token::Tuple { len: 2 }, | ||
Token::U8(8), | ||
Token::Tuple { len: 31 }, | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
|
@@ -1814,7 +1882,8 @@ mod test { | |
Token::U8(0), | ||
Token::U8(0), | ||
Token::U8(0), | ||
Token::SeqEnd, | ||
Token::TupleEnd, | ||
Token::TupleEnd, | ||
], | ||
); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can avoid the if else nesting of
if first_significant_byte > 1
doing the following: