Skip to main content

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}