diff --git a/Cargo.lock b/Cargo.lock index ac3f921c..895061e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -192,6 +192,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "bitvec" +version = "0.17.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41262f11d771fd4a61aa3ce019fca363b4b6c282fca9da2a31186d3965a47a5c" +dependencies = [ + "either", + "radium", +] + [[package]] name = "blake2b_simd" version = "0.5.10" @@ -253,6 +263,12 @@ dependencies = [ "sha2", ] +[[package]] +name = "byte-slice-cast" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0a5e3906bcbf133e33c1d4d95afc664ad37fbdb9f6568d8043e7ea8c27d93d3" + [[package]] name = "byte-tools" version = "0.3.1" @@ -458,6 +474,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "ctor" version = "0.1.15" @@ -600,6 +622,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "either" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" + [[package]] name = "equihash" version = "0.1.0" @@ -626,6 +654,18 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +[[package]] +name = "fixed-hash" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11498d382790b7a8f2fd211780bec78619bba81cdad3a283997c0c41f836759c" +dependencies = [ + "byteorder", + "rand 0.7.3", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fnv" version = "1.0.7" @@ -966,6 +1006,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "impl-codec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1be51a921b067b0eaca2fad532d9400041561aa922221cc65f95a85641c6bf53" +dependencies = [ + "parity-scale-codec", +] + [[package]] name = "indenter" version = "0.3.0" @@ -1333,6 +1382,18 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +[[package]] +name = "parity-scale-codec" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34d38aeaffc032ec69faa476b3caaca8d4dd7f3f798137ff30359e5c7869ceb6" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "serde", +] + [[package]] name = "parking_lot" version = "0.10.2" @@ -1421,6 +1482,17 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" +[[package]] +name = "primitive-types" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c55c21c64d0eaa4d7ed885d959ef2d62d9e488c27c0e02d9aa5ce6c877b7d5f8" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint", +] + [[package]] name = "proc-macro-error" version = "1.0.3" @@ -1544,6 +1616,12 @@ dependencies = [ "proc-macro2 1.0.19", ] +[[package]] +name = "radium" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac" + [[package]] name = "rand" version = "0.4.6" @@ -1738,6 +1816,12 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rusty-fork" version = "0.3.0" @@ -1973,6 +2057,12 @@ dependencies = [ "syn 1.0.35", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.8.0" @@ -2481,6 +2571,18 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" +[[package]] +name = "uint" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173cd16430c206dc1a430af8a89a0e9c076cf15cb42b4aedb10e8cc8fee73681" +dependencies = [ + "byteorder", + "crunchy", + "rustc-hex", + "static_assertions", +] + [[package]] name = "unicode-segmentation" version = "1.6.0" @@ -2635,6 +2737,7 @@ dependencies = [ "hex", "jubjub", "lazy_static", + "primitive-types", "proptest", "proptest-derive", "rand_core 0.5.1", diff --git a/zebra-chain/Cargo.toml b/zebra-chain/Cargo.toml index e7f2abf5..3532c676 100644 --- a/zebra-chain/Cargo.toml +++ b/zebra-chain/Cargo.toml @@ -18,6 +18,7 @@ futures = "0.3" hex = "0.4" jubjub = "0.3.0" lazy_static = "1.4.0" +primitive-types = "0.7.2" rand_core = "0.5.1" ripemd160 = "0.8.0" secp256k1 = { version = "0.17.2", features = ["serde"] } diff --git a/zebra-chain/src/block/difficulty.rs b/zebra-chain/src/block/difficulty.rs index 6edd39d9..1dd39db5 100644 --- a/zebra-chain/src/block/difficulty.rs +++ b/zebra-chain/src/block/difficulty.rs @@ -10,6 +10,10 @@ //! block's work value depends on the fixed threshold in the block header, not //! the actual work represented by the block header hash. +use primitive_types::U256; + +#[cfg(test)] +use proptest::prelude::*; #[cfg(test)] use proptest_derive::Arbitrary; @@ -41,10 +45,19 @@ use proptest_derive::Arbitrary; /// Without these consensus rules, some `ExpandedDifficulty` values would have /// multiple equivalent `CompactDifficulty` values, due to redundancy in the /// floating-point format. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] #[cfg_attr(test, derive(Arbitrary))] pub struct CompactDifficulty(pub u32); +impl fmt::Debug for CompactDifficulty { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("CompactDifficulty") + // Use hex, because it's a float + .field(&format_args!("{:#010x}", self.0)) + .finish() + } +} + /// A 256-bit unsigned "expanded difficulty" value. /// /// Used as a target threshold for the difficulty of a `BlockHeaderHash`. @@ -61,11 +74,27 @@ pub struct CompactDifficulty(pub u32); /// Therefore, consensus-critical code must perform the specified /// conversions to `CompactDifficulty`, even if the original /// `ExpandedDifficulty` values are known. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] -#[cfg_attr(test, derive(Arbitrary))] -pub struct ExpandedDifficulty([u8; 32]); +#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] +pub struct ExpandedDifficulty(U256); + +impl fmt::Debug for ExpandedDifficulty { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut buf = [0; 32]; + // Use the same byte order as BlockHeaderHash + self.0.to_little_endian(&mut buf); + f.debug_tuple("ExpandedDifficulty") + .field(&hex::encode(&buf)) + .finish() + } +} impl CompactDifficulty { + /// CompactDifficulty exponent base. + const BASE: u32 = 256; + + /// CompactDifficulty exponent offset. + const OFFSET: i32 = 3; + /// CompactDifficulty floating-point precision. const PRECISION: u32 = 24; @@ -75,10 +104,7 @@ impl CompactDifficulty { /// CompactDifficulty unsigned mantissa mask. /// /// Also the maximum unsigned mantissa value. - const U_MANT_MASK: u32 = CompactDifficulty::SIGN_BIT - 1; - - /// CompactDifficulty exponent offset. - const OFFSET: i32 = 3; + const UNSIGNED_MANTISSA_MASK: u32 = CompactDifficulty::SIGN_BIT - 1; /// Calculate the ExpandedDifficulty for a compact representation. /// @@ -90,10 +116,11 @@ impl CompactDifficulty { pub fn to_expanded(&self) -> Option { // The constants for this floating-point representation. // Alias the struct constants here, so the code is easier to read. + const BASE: u32 = CompactDifficulty::BASE; + const OFFSET: i32 = CompactDifficulty::OFFSET; const PRECISION: u32 = CompactDifficulty::PRECISION; const SIGN_BIT: u32 = CompactDifficulty::SIGN_BIT; - const U_MANT_MASK: u32 = CompactDifficulty::U_MANT_MASK; - const OFFSET: i32 = CompactDifficulty::OFFSET; + const UNSIGNED_MANTISSA_MASK: u32 = CompactDifficulty::UNSIGNED_MANTISSA_MASK; // Negative values in this floating-point representation. // 0 if (x & 2^23 == 2^23) @@ -103,35 +130,46 @@ impl CompactDifficulty { } // The components of the result - // The fractional part of the number + // The fractional part of the floating-point number // x & (2^23 - 1) - let mantissa = self.0 & U_MANT_MASK; + let mantissa = self.0 & UNSIGNED_MANTISSA_MASK; - // The position of the number in the result, in bytes (rather than bits) + // The exponent for the multiplier in the floating-point number // 256^(floor(x/(2^24)) - 3) // The i32 conversion is safe, because we've just divided self by 2^24. let exponent = ((self.0 >> PRECISION) as i32) - OFFSET; - // Now put the mantissa in the right place in the result, based on - // the exponent. - let mut result = [0; 32]; - for (i, b) in mantissa.to_le_bytes().iter().enumerate() { - // These conversions are safe, due to the size of the array, and the - // range checks before array access. - let position = exponent + i as i32; - if position >= 32 { - if *b != 0 { - // zcashd rejects overflow values, without comparing the - // hash - return None; - } - } else if position >= 0 { - // zcashd truncates fractional values - result[position as usize] = *b; - } - } + // Normalise the mantissa and exponent before multiplying. + // + // zcashd rejects non-zero overflow values, but accepts overflows where + // all the overflowing bits are zero. It also allows underflows. + let (mantissa, exponent) = match (mantissa, exponent) { + // Overflow: check for non-zero overflow bits + // + // If m is non-zero, overflow. If m is zero, invalid. + (_, e) if (e >= 32) => return None, + // If m is larger than the remaining bytes, overflow. + // Otherwise, avoid overflows in base^exponent. + (m, e) if (e == 31 && m > u8::MAX.into()) => return None, + (m, e) if (e == 31 && m <= u8::MAX.into()) => (m << 16, e - 2), + (m, e) if (e == 30 && m > u16::MAX.into()) => return None, + (m, e) if (e == 30 && m <= u16::MAX.into()) => (m << 8, e - 1), - if result == [0; 32] { + // Underflow: perform the right shift. + // The abs is safe, because we've just divided by 2^24, and offset + // is small. + (m, e) if (e < 0) => (m >> ((e.abs() * 8) as u32), 0), + (m, e) => (m, e), + }; + + // Now calculate the result: mantissa*base^exponent + // Earlier code should make sure all these values are in range. + let mantissa: U256 = mantissa.into(); + let base: U256 = BASE.into(); + let exponent: U256 = exponent.into(); + let result = mantissa * base.pow(exponent); + + if result == U256::zero() { // zcashd rejects zero values, without comparing the hash None } else { @@ -140,6 +178,19 @@ impl CompactDifficulty { } } +#[cfg(test)] +impl Arbitrary for ExpandedDifficulty { + type Parameters = (); + + fn arbitrary_with(_args: ()) -> Self::Strategy { + (any::<[u8; 32]>()) + .prop_map(|v| ExpandedDifficulty(U256::from_little_endian(&v))) + .boxed() + } + + type Strategy = BoxedStrategy; +} + #[cfg(test)] mod tests { use super::*; @@ -153,9 +204,32 @@ mod tests { // Alias the struct constants here, so the code is easier to read. const PRECISION: u32 = CompactDifficulty::PRECISION; const SIGN_BIT: u32 = CompactDifficulty::SIGN_BIT; - const U_MANT_MASK: u32 = CompactDifficulty::U_MANT_MASK; + const UNSIGNED_MANTISSA_MASK: u32 = CompactDifficulty::UNSIGNED_MANTISSA_MASK; const OFFSET: i32 = CompactDifficulty::OFFSET; + /// Test debug formatting. + #[test] + fn debug_format() { + zebra_test::init(); + + assert_eq!( + format!("{:?}", CompactDifficulty(0)), + "CompactDifficulty(0x00000000)" + ); + assert_eq!( + format!("{:?}", CompactDifficulty(1)), + "CompactDifficulty(0x00000001)" + ); + assert_eq!( + format!("{:?}", CompactDifficulty(u32::MAX)), + "CompactDifficulty(0xffffffff)" + ); + + assert_eq!(format!("{:?}", ExpandedDifficulty(U256::zero())), "ExpandedDifficulty(\"0000000000000000000000000000000000000000000000000000000000000000\")"); + assert_eq!(format!("{:?}", ExpandedDifficulty(U256::one())), "ExpandedDifficulty(\"0100000000000000000000000000000000000000000000000000000000000000\")"); + assert_eq!(format!("{:?}", ExpandedDifficulty(U256::MAX)), "ExpandedDifficulty(\"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\")"); + } + /// Test zero values for CompactDifficulty. #[test] fn compact_zero() { @@ -167,7 +241,7 @@ mod tests { // Small value zeroes let small_zero_1 = CompactDifficulty(1); assert_eq!(small_zero_1.to_expanded(), None); - let small_zero_max = CompactDifficulty(U_MANT_MASK); + let small_zero_max = CompactDifficulty(UNSIGNED_MANTISSA_MASK); assert_eq!(small_zero_max.to_expanded(), None); // Special-cased zeroes, negative in the floating-point representation @@ -187,9 +261,7 @@ mod tests { zebra_test::init(); // Values equal to one - let mut expanded_one = [0; 32]; - expanded_one[0] = 1; - let expanded_one = Some(ExpandedDifficulty(expanded_one)); + let expanded_one = Some(ExpandedDifficulty(U256::one())); let one = CompactDifficulty(OFFSET as u32 * (1 << PRECISION) + 1); assert_eq!(one.to_expanded(), expanded_one); @@ -197,31 +269,24 @@ mod tests { assert_eq!(another_one.to_expanded(), expanded_one); // Maximum mantissa - let mut expanded_mant = [0; 32]; - expanded_mant[0] = 0xff; - expanded_mant[1] = 0xff; - expanded_mant[2] = 0x7f; - let expanded_mant = Some(ExpandedDifficulty(expanded_mant)); + let expanded_mant = Some(ExpandedDifficulty(UNSIGNED_MANTISSA_MASK.into())); - let mant = CompactDifficulty(OFFSET as u32 * (1 << PRECISION) + U_MANT_MASK); + let mant = CompactDifficulty(OFFSET as u32 * (1 << PRECISION) + UNSIGNED_MANTISSA_MASK); assert_eq!(mant.to_expanded(), expanded_mant); // Maximum valid exponent - let mut expanded_exp = [0; 32]; - expanded_exp[31] = 1; - let expanded_exp = Some(ExpandedDifficulty(expanded_exp)); + let exponent: U256 = (31 * 8).into(); + let expanded_exp = Some(ExpandedDifficulty(U256::from(2).pow(exponent))); let exp = CompactDifficulty((31 + OFFSET as u32) * (1 << PRECISION) + 1); assert_eq!(exp.to_expanded(), expanded_exp); // Maximum valid mantissa and exponent - let mut expanded_me = [0; 32]; - expanded_me[29] = 0xff; - expanded_me[30] = 0xff; - expanded_me[31] = 0x7f; + let exponent: U256 = (29 * 8).into(); + let expanded_me = U256::from(UNSIGNED_MANTISSA_MASK) * U256::from(2).pow(exponent); let expanded_me = Some(ExpandedDifficulty(expanded_me)); - let me = CompactDifficulty((31 + 1) * (1 << PRECISION) + U_MANT_MASK); + let me = CompactDifficulty((31 + 1) * (1 << PRECISION) + UNSIGNED_MANTISSA_MASK); assert_eq!(me.to_expanded(), expanded_me); // Maximum value, at least according to the spec