feature: Make ExpandedDifficulty use U256 internally

Adds a dependency on the primitive-types crate.

Also adds custom hex debug formatting for compact and expanded
difficulties.
This commit is contained in:
teor 2020-08-03 17:13:01 +10:00
parent c04d1b7b8f
commit b7fac7b3bc
3 changed files with 220 additions and 51 deletions

103
Cargo.lock generated
View File

@ -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",

View File

@ -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"] }

View File

@ -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<ExpandedDifficulty> {
// 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<Self>;
}
#[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