sidestr_header/target.rs
1//! The 256-bit proof-of-work target and its compact `bits` encoding.
2//!
3//! Ported from the kernel's `expandCompact` (`codec/codec.js`) and
4//! `compactFromTarget` (`codec/headers.js`), which in turn follow Bitcoin
5//! Core's `arith_uint256::SetCompact` / `GetCompact`. The target is held as
6//! 32 big-endian bytes so that a [`BlockHash`](crate::BlockHash) (display
7//! order, also big-endian as a number) compares against it byte-wise.
8//!
9//! **Where this crate is stricter than the JS kernel.** `expandCompact`
10//! masks the sign bit away and lets an oversized exponent produce a number
11//! wider than 256 bits (which every hash would then "meet"). Core rejects
12//! both. This crate rejects both too ([`Error::CompactNegative`],
13//! [`Error::CompactOverflow`]). On a sidestr chain the difference is
14//! unreachable: SPEC 4 step 1 and siding's `chain.mjs` fix every block's
15//! `bits` to `compactFromTarget(powLimit)`, and a `powLimit` is at most
16//! 2²⁵⁶ − 1, so a valid block never carries an exponent that overflows.
17
18use crate::Error;
19use core::fmt;
20
21/// A 256-bit proof-of-work target, big-endian.
22#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
23pub struct Target([u8; 32]);
24
25impl Target {
26 /// The largest target, 2²⁵⁶ − 1: every hash meets it.
27 pub const MAX: Target = Target([0xff; 32]);
28
29 /// A target from its 32 big-endian bytes.
30 pub const fn from_be_bytes(bytes: [u8; 32]) -> Self {
31 Target(bytes)
32 }
33
34 /// The 32 big-endian bytes.
35 pub const fn to_be_bytes(self) -> [u8; 32] {
36 self.0
37 }
38
39 /// A target from 64 hex characters, most significant first — the form a
40 /// chain document's `powLimit` takes (SPEC 3).
41 ///
42 /// ```
43 /// use sidestr_header::Target;
44 /// let lim = Target::from_hex("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap();
45 /// assert_eq!(lim.to_compact(), 0x207f_ffff);
46 /// ```
47 pub fn from_hex(s: &str) -> Result<Self, Error> {
48 crate::hash::hex32(s).map(Target).ok_or(Error::InvalidHex)
49 }
50
51 /// Expands compact `bits` (Core's `SetCompact`): the low 23 bits are the
52 /// mantissa, the top byte the size in bytes of the big-endian number the
53 /// mantissa is the leading three bytes of.
54 ///
55 /// ```
56 /// use sidestr_header::Target;
57 /// // Bitcoin's genesis bits.
58 /// let t = Target::from_compact(0x1d00_ffff).unwrap();
59 /// assert_eq!(hex::encode(t.to_be_bytes()),
60 /// "00000000ffff0000000000000000000000000000000000000000000000000000");
61 /// // A well-known Core example: 0x1b0404cb.
62 /// let t = Target::from_compact(0x1b04_04cb).unwrap();
63 /// assert_eq!(hex::encode(t.to_be_bytes()),
64 /// "00000000000404cb000000000000000000000000000000000000000000000000");
65 /// ```
66 pub fn from_compact(bits: u32) -> Result<Self, Error> {
67 let exponent = (bits >> 24) as usize;
68 let mantissa = bits & 0x007f_ffff;
69 if mantissa != 0 && bits & 0x0080_0000 != 0 {
70 return Err(Error::CompactNegative(bits));
71 }
72 let mut out = [0u8; 32];
73 if exponent <= 3 {
74 // The mantissa is shifted right into fewer than three bytes.
75 let word = mantissa >> (8 * (3 - exponent));
76 out[29..].copy_from_slice(&word.to_be_bytes()[1..]);
77 return Ok(Target(out));
78 }
79 // Byte i of the mantissa (little-endian index) lands `exponent - 3 + i`
80 // bytes from the least significant end.
81 let shift = exponent - 3;
82 for i in 0..3 {
83 let byte = ((mantissa >> (8 * i)) & 0xff) as u8;
84 let pos = shift + i;
85 if pos > 31 {
86 if byte != 0 {
87 return Err(Error::CompactOverflow(bits));
88 }
89 } else {
90 out[31 - pos] = byte;
91 }
92 }
93 Ok(Target(out))
94 }
95
96 /// Compact-encodes the target (Core's `GetCompact`, the kernel's
97 /// `compactFromTarget`), including the mantissa truncation that makes the
98 /// encoding lossy: `from_compact(t.to_compact())` keeps only the three
99 /// most significant bytes of `t`.
100 ///
101 /// ```
102 /// use sidestr_header::Target;
103 /// assert_eq!(Target::from_compact(0x1d00_ffff).unwrap().to_compact(), 0x1d00_ffff);
104 /// assert_eq!(Target::MAX.to_compact(), 0x2100_ffff); // top byte 0xff would read as a sign bit
105 /// ```
106 pub fn to_compact(self) -> u32 {
107 let first = match self.0.iter().position(|&b| b != 0) {
108 Some(i) => i,
109 None => return 0,
110 };
111 let mut size = 32 - first;
112 let mut compact: u32 = if size <= 3 {
113 let mut w = 0u32;
114 for &b in &self.0[first..] {
115 w = (w << 8) | u32::from(b);
116 }
117 w << (8 * (3 - size))
118 } else {
119 (u32::from(self.0[first]) << 16)
120 | (u32::from(self.0[first + 1]) << 8)
121 | u32::from(self.0[first + 2])
122 };
123 // The sign bit of the mantissa must stay clear: shift and grow instead.
124 if compact & 0x0080_0000 != 0 {
125 compact >>= 8;
126 size += 1;
127 }
128 compact | ((size as u32) << 24)
129 }
130
131 /// Whether the target is zero, which no hash but zero can meet.
132 pub fn is_zero(self) -> bool {
133 self.0.iter().all(|&b| b == 0)
134 }
135}
136
137impl fmt::Debug for Target {
138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139 f.write_str("Target(")?;
140 crate::hash::fmt_hex(f, &self.0)?;
141 f.write_str(")")
142 }
143}
144
145impl fmt::Display for Target {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 crate::hash::fmt_hex(f, &self.0)
148 }
149}