From 3761b0d808a831cb3c455d6344ffa05b2625bf00 Mon Sep 17 00:00:00 2001 From: Nadav Ivgi Date: Tue, 29 Dec 2020 20:46:21 +0200 Subject: [PATCH 1/7] Implement Uint::to_be_bytes() --- src/util/endian.rs | 1 + src/util/uint.rs | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/util/endian.rs b/src/util/endian.rs index d4ae5ec4d6..e7c620f5ba 100644 --- a/src/util/endian.rs +++ b/src/util/endian.rs @@ -54,6 +54,7 @@ macro_rules! define_le_to_array { define_slice_to_be!(slice_to_u32_be, u32); define_slice_to_be!(slice_to_u64_be, u64); define_be_to_array!(u32_to_array_be, u32, 4); +define_be_to_array!(u64_to_array_be, u64, 8); define_slice_to_le!(slice_to_u16_le, u16); define_slice_to_le!(slice_to_u32_le, u32); define_slice_to_le!(slice_to_u64_le, u64); diff --git a/src/util/uint.rs b/src/util/uint.rs index 314ac6b014..16592727c4 100644 --- a/src/util/uint.rs +++ b/src/util/uint.rs @@ -102,6 +102,17 @@ macro_rules! construct_uint { $name(slice) } + /// Convert a big integer into a byte array using big-endian encoding + pub fn to_be_bytes(&self) -> [u8; $n_words * 8] { + use super::endian::u64_to_array_be; + let mut res = [0; $n_words * 8]; + for i in 0..$n_words { + let start = i * 8; + res[start..start+8].copy_from_slice(&u64_to_array_be(self.0[$n_words - (i+1)])); + } + res + } + // divmod like operation, returns (quotient, remainder) #[inline] fn div_rem(self, other: Self) -> (Self, Self) { @@ -505,6 +516,16 @@ mod tests { Uint256([0x11fed2bad1c0ffe0, 0xbaadf00ddefaceda, 0xdeafbabe2bedfeed, 0x1badcafedeadbeef])); } + #[test] + pub fn uint_to_be_bytes() { + assert_eq!(Uint128([0xdeafbabe2bedfeed, 0x1badcafedeadbeef]).to_be_bytes(), + [0x1b, 0xad, 0xca, 0xfe, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xaf, 0xba, 0xbe, 0x2b, 0xed, 0xfe, 0xed]); + + assert_eq!(Uint256([0x11fed2bad1c0ffe0, 0xbaadf00ddefaceda, 0xdeafbabe2bedfeed, 0x1badcafedeadbeef]).to_be_bytes(), + [0x1b, 0xad, 0xca, 0xfe, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xaf, 0xba, 0xbe, 0x2b, 0xed, 0xfe, 0xed, + 0xba, 0xad, 0xf0, 0x0d, 0xde, 0xfa, 0xce, 0xda, 0x11, 0xfe, 0xd2, 0xba, 0xd1, 0xc0, 0xff, 0xe0]); + } + #[test] pub fn uint256_arithmetic_test() { let init = Uint256::from_u64(0xDEADBEEFDEADBEEF).unwrap(); From 67ae602e2a502a79d2459f10622d4340152a795c Mon Sep 17 00:00:00 2001 From: Nadav Ivgi Date: Wed, 30 Dec 2020 05:26:50 +0200 Subject: [PATCH 2/7] Implement Uint::from_be_slice() Needed because Rust 1.29 does not easily allow converting from a slice into an array. --- src/util/uint.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/util/uint.rs b/src/util/uint.rs index 16592727c4..fa7ebd5880 100644 --- a/src/util/uint.rs +++ b/src/util/uint.rs @@ -90,9 +90,23 @@ macro_rules! construct_uint { } } - /// Creates big integer value from a byte slice array using + /// Creates big integer value from a byte array using /// big-endian encoding pub fn from_be_bytes(bytes: [u8; $n_words * 8]) -> $name { + Self::_from_be_slice(&bytes) + } + + /// Creates big integer value from a byte slice using + /// big-endian encoding + pub fn from_be_slice(bytes: &[u8]) -> Result<$name, Error> { + if bytes.len() != $n_words * 8 { + Err(Error::InvalidLength(bytes.len(), $n_words*8)) + } else { + Ok(Self::_from_be_slice(bytes)) + } + } + + fn _from_be_slice(bytes: &[u8]) -> $name { use super::endian::slice_to_u64_be; let mut slice = [0u64; $n_words]; slice.iter_mut() @@ -420,6 +434,13 @@ macro_rules! construct_uint { construct_uint!(Uint256, 4); construct_uint!(Uint128, 2); +/// Uint error +#[derive(Debug)] +pub enum Error { + /// Invalid slice length (actual, expected) + InvalidLength(usize, usize), +} + impl Uint256 { /// Increment by 1 #[inline] From b70361370b49680deca6354a95b1b3d5a8de4918 Mon Sep 17 00:00:00 2001 From: Nadav Ivgi Date: Tue, 29 Dec 2020 22:35:53 +0200 Subject: [PATCH 3/7] Make uint types (un)serializable --- src/util/uint.rs | 76 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/src/util/uint.rs b/src/util/uint.rs index fa7ebd5880..a4c0fedbd8 100644 --- a/src/util/uint.rs +++ b/src/util/uint.rs @@ -428,6 +428,47 @@ macro_rules! construct_uint { Ok($name(ret)) } } + + #[cfg(feature = "serde")] + impl $crate::serde::Serialize for $name { + fn serialize(&self, serializer: S) -> Result + where + S: $crate::serde::Serializer, + { + use $crate::hashes::hex::ToHex; + serializer.serialize_str(&self.to_be_bytes().to_hex()) + } + } + + #[cfg(feature = "serde")] + impl<'de> $crate::serde::Deserialize<'de> for $name { + fn deserialize>( + deserializer: D, + ) -> Result { + use ::std::fmt; + use $crate::hashes::hex::FromHex; + use $crate::serde::de; + struct Visitor; + impl<'de> de::Visitor<'de> for Visitor { + type Value = $name; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "hex string with {} characters ({} bytes", $n_words * 8 * 2, $n_words * 8) + } + + fn visit_str(self, s: &str) -> Result + where + E: de::Error, + { + let bytes = Vec::from_hex(s) + .map_err(|_| de::Error::invalid_value(de::Unexpected::Str(s), &self))?; + $name::from_be_slice(&bytes) + .map_err(|_| de::Error::invalid_length(bytes.len() * 2, &self)) + } + } + deserializer.deserialize_str(Visitor) + } + } ); } @@ -654,4 +695,39 @@ mod tests { assert_eq!(end1.ok(), Some(start1)); assert_eq!(end2.ok(), Some(start2)); } + + #[cfg(feature = "serde")] + #[test] + pub fn uint256_serde_test() { + let check = |uint, hex| { + let json = format!("\"{}\"", hex); + assert_eq!(::serde_json::to_string(&uint).unwrap(), json); + assert_eq!(::serde_json::from_str::(&json).unwrap(), uint); + }; + + check( + Uint256::from_u64(0).unwrap(), + "0000000000000000000000000000000000000000000000000000000000000000", + ); + check( + Uint256::from_u64(0xDEADBEEF).unwrap(), + "00000000000000000000000000000000000000000000000000000000deadbeef", + ); + check( + Uint256([0xaa11, 0xbb22, 0xcc33, 0xdd44]), + "000000000000dd44000000000000cc33000000000000bb22000000000000aa11", + ); + check( + Uint256([u64::max_value(), u64::max_value(), u64::max_value(), u64::max_value()]), + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + ); + check( + Uint256([ 0xA69B4555DEADBEEF, 0xA69B455CD41BB662, 0xD41BB662A69B4550, 0xDEADBEEAA69B455C ]), + "deadbeeaa69b455cd41bb662a69b4550a69b455cd41bb662a69b4555deadbeef", + ); + + assert!(::serde_json::from_str::("\"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffg\"").is_err()); // invalid char + assert!(::serde_json::from_str::("\"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"").is_err()); // invalid length + assert!(::serde_json::from_str::("\"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"").is_err()); // invalid length + } } From 4a7cf34eeb880af5841159b17ddaf007c459e145 Mon Sep 17 00:00:00 2001 From: Nadav Ivgi Date: Wed, 6 Jan 2021 00:01:21 +0200 Subject: [PATCH 4/7] Use efficient serialization for non-human-readable formats --- Cargo.toml | 1 + src/lib.rs | 1 + src/util/uint.rs | 28 +++++++++++++++++++++++++--- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 59b22722cc..9f3a584389 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,5 +34,6 @@ serde = { version = "1", features = [ "derive" ], optional = true } serde_json = "<1.0.45" serde_test = "1" secp256k1 = { version = "0.20.0", features = [ "recovery", "rand-std" ] } +bincode = "1.3.1" # We need to pin ryu (transitive dep from serde_json) to stay compatible with Rust 1.22.0 ryu = "<1.0.5" diff --git a/src/lib.rs b/src/lib.rs index 7007cf9ebe..e7387de444 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,6 +47,7 @@ pub extern crate bech32; #[cfg(feature = "serde")] #[macro_use] extern crate serde; #[cfg(all(test, feature = "serde"))] extern crate serde_json; #[cfg(all(test, feature = "serde"))] extern crate serde_test; +#[cfg(all(test, feature = "serde"))] extern crate bincode; #[cfg(all(test, feature = "unstable"))] extern crate test; #[cfg(target_pointer_width = "16")] diff --git a/src/util/uint.rs b/src/util/uint.rs index a4c0fedbd8..47968a695f 100644 --- a/src/util/uint.rs +++ b/src/util/uint.rs @@ -436,7 +436,12 @@ macro_rules! construct_uint { S: $crate::serde::Serializer, { use $crate::hashes::hex::ToHex; - serializer.serialize_str(&self.to_be_bytes().to_hex()) + let bytes = self.to_be_bytes(); + if serializer.is_human_readable() { + serializer.serialize_str(&bytes.to_hex()) + } else { + serializer.serialize_bytes(&bytes) + } } } @@ -453,7 +458,7 @@ macro_rules! construct_uint { type Value = $name; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "hex string with {} characters ({} bytes", $n_words * 8 * 2, $n_words * 8) + write!(f, "{} bytes or a hex string with {} characters", $n_words * 8, $n_words * 8 * 2) } fn visit_str(self, s: &str) -> Result @@ -465,8 +470,21 @@ macro_rules! construct_uint { $name::from_be_slice(&bytes) .map_err(|_| de::Error::invalid_length(bytes.len() * 2, &self)) } + + fn visit_bytes(self, bytes: &[u8]) -> Result + where + E: de::Error, + { + $name::from_be_slice(bytes) + .map_err(|_| de::Error::invalid_length(bytes.len(), &self)) + } + } + + if deserializer.is_human_readable() { + deserializer.deserialize_str(Visitor) + } else { + deserializer.deserialize_bytes(Visitor) } - deserializer.deserialize_str(Visitor) } } ); @@ -703,6 +721,10 @@ mod tests { let json = format!("\"{}\"", hex); assert_eq!(::serde_json::to_string(&uint).unwrap(), json); assert_eq!(::serde_json::from_str::(&json).unwrap(), uint); + + let bin_encoded = ::bincode::serialize(&uint).unwrap(); + let bin_decoded: Uint256 = ::bincode::deserialize(&bin_encoded).unwrap(); + assert_eq!(bin_decoded, uint); }; check( From 0df86b442679694c8a67bfcf40966e9c5fccea7e Mon Sep 17 00:00:00 2001 From: Nadav Ivgi Date: Thu, 14 Jan 2021 22:17:18 +0200 Subject: [PATCH 5/7] Switch to a single-variant error type, implement standard derives --- src/util/uint.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/util/uint.rs b/src/util/uint.rs index 47968a695f..91b6b71d23 100644 --- a/src/util/uint.rs +++ b/src/util/uint.rs @@ -98,9 +98,9 @@ macro_rules! construct_uint { /// Creates big integer value from a byte slice using /// big-endian encoding - pub fn from_be_slice(bytes: &[u8]) -> Result<$name, Error> { + pub fn from_be_slice(bytes: &[u8]) -> Result<$name, ParseLengthError> { if bytes.len() != $n_words * 8 { - Err(Error::InvalidLength(bytes.len(), $n_words*8)) + Err(ParseLengthError { actual: bytes.len(), expected: $n_words*8 }) } else { Ok(Self::_from_be_slice(bytes)) } @@ -493,11 +493,20 @@ macro_rules! construct_uint { construct_uint!(Uint256, 4); construct_uint!(Uint128, 2); -/// Uint error -#[derive(Debug)] -pub enum Error { - /// Invalid slice length (actual, expected) - InvalidLength(usize, usize), +/// Invalid slice length +#[derive(Debug, PartialEq, PartialOrd, Clone, Copy, Hash)] +/// Invalid slice length +pub struct ParseLengthError { + /// The length of the slice de-facto + pub actual: usize, + /// The required length of the slice + pub expected: usize, +} + +impl ::std::fmt::Display for ParseLengthError { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "Invalid length: got {}, expected {}", self.actual, self.expected) + } } impl Uint256 { From 55657cbffb353bd1e70ff2501d1141675529fd7b Mon Sep 17 00:00:00 2001 From: Nadav Ivgi Date: Sat, 16 Jan 2021 13:02:22 +0200 Subject: [PATCH 6/7] Implement Error and Eq for ParseLengthError --- src/util/uint.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/util/uint.rs b/src/util/uint.rs index 91b6b71d23..f58bdcdb40 100644 --- a/src/util/uint.rs +++ b/src/util/uint.rs @@ -494,7 +494,7 @@ construct_uint!(Uint256, 4); construct_uint!(Uint128, 2); /// Invalid slice length -#[derive(Debug, PartialEq, PartialOrd, Clone, Copy, Hash)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Copy, Hash)] /// Invalid slice length pub struct ParseLengthError { /// The length of the slice de-facto @@ -509,6 +509,8 @@ impl ::std::fmt::Display for ParseLengthError { } } +impl ::std::error::Error for ParseLengthError {} + impl Uint256 { /// Increment by 1 #[inline] From a1e98a67964be214edd3e6bf27dbd2820e0a7f19 Mon Sep 17 00:00:00 2001 From: Nadav Ivgi Date: Fri, 22 Jan 2021 00:27:28 +0200 Subject: [PATCH 7/7] Implement Ord for ParseLengthError --- src/util/uint.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/uint.rs b/src/util/uint.rs index f58bdcdb40..064ce1212c 100644 --- a/src/util/uint.rs +++ b/src/util/uint.rs @@ -494,7 +494,7 @@ construct_uint!(Uint256, 4); construct_uint!(Uint128, 2); /// Invalid slice length -#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Copy, Hash)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] /// Invalid slice length pub struct ParseLengthError { /// The length of the slice de-facto