Skip to main content

sidestr_header/
stock.rs

1//! The stock 80-byte header, SHA-256d: the family of a chain beside `btc` or
2//! `tbtc4` (SPEC 3.2).
3
4use crate::hash::sha256d;
5use crate::signet::{self, BLOCK_DATA_LEN};
6use crate::{BlockHash, Error, HeaderFamily, Target, VERSION_HEADER_V2_FLAG};
7
8/// The 80-byte Bitcoin block header (`btc:BlockHeader` in the kernel's
9/// `schema/core.jsonld`).
10///
11/// Layout, all little-endian: `version` (4) ‖ `prev_block_hash` (32) ‖
12/// `merkle_root` (32) ‖ `time` (4) ‖ `bits` (4) ‖ `nonce` (4). The block
13/// hash is SHA-256d of these bytes, printed byte-reversed.
14///
15/// Beside a stock parent siding builds every header with
16/// `version = 0x2000_0000` and bit 31 clear (`siding/lib/block.mjs`
17/// `buildBlock`); the height lives only in the coinbase's BIP 34 push, so
18/// this struct has no height field. `version` is held as `u32` rather than
19/// Bitcoin's `i32`: the two agree for every header this crate accepts,
20/// because a set bit 31 is rejected by [`StockHeader::decode`].
21///
22/// ```
23/// use sidestr_header::{StockHeader, Target};
24/// // sidestr:dreamlab block 0 (agentbox ADR-2103), beside tbtc4.
25/// let bytes = hex::decode(
26///     "0000002000000000000000000000000000000000000000000000000000000000000000003a87d59ecf60ab58ee75948cc39d1bb44ac4285747e64b5a1e7a960d37764cb40e67b26affff7f2002000000",
27/// ).unwrap();
28/// let h = StockHeader::decode(&bytes).unwrap();
29/// assert_eq!(h.version, 0x2000_0000);
30/// assert_eq!(h.time, 1_790_076_686);
31/// assert_eq!(h.nonce, 2);
32/// assert_eq!(h.hash().to_string(),
33///            "4db37517728bd509c0cb96ee5a2e3e2a77f9e965a092e9f67948b413d453dbc0");
34/// let pow_limit = Target::from_hex("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap();
35/// assert!(h.check_pow(&pow_limit).is_ok());
36/// assert_eq!(h.encode().as_slice(), bytes.as_slice());
37/// ```
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub struct StockHeader {
40    /// Block version. Bit 31 must be clear (see the struct docs).
41    pub version: u32,
42    /// The previous block's hash; all zeros for a genesis.
43    pub prev_block_hash: BlockHash,
44    /// The transaction merkle root in **wire (internal) order** — the bytes
45    /// as serialised, not as an explorer prints them.
46    pub merkle_root: [u8; 32],
47    /// Block time, Unix seconds.
48    pub time: u32,
49    /// Compact proof-of-work target; on a sidestr chain always the compact
50    /// form of `powLimit`.
51    pub bits: u32,
52    /// The proof-of-work nonce.
53    pub nonce: u32,
54}
55
56impl StockHeader {
57    /// Wire size in bytes.
58    pub const WIRE_SIZE: usize = 80;
59
60    /// Decodes exactly 80 bytes. Rejects any other length
61    /// ([`Error::WrongLength`]) and a version with bit 31 set
62    /// ([`Error::VersionBit31Set`]).
63    pub fn decode(bytes: &[u8]) -> Result<Self, Error> {
64        if bytes.len() != Self::WIRE_SIZE {
65            return Err(Error::WrongLength {
66                family: HeaderFamily::Stock,
67                expected: Self::WIRE_SIZE,
68                actual: bytes.len(),
69            });
70        }
71        let version = u32_at(bytes, 0);
72        if version & VERSION_HEADER_V2_FLAG != 0 {
73            return Err(Error::VersionBit31Set);
74        }
75        Ok(StockHeader {
76            version,
77            prev_block_hash: BlockHash::from_wire(arr32(bytes, 4)),
78            merkle_root: arr32(bytes, 36),
79            time: u32_at(bytes, 68),
80            bits: u32_at(bytes, 72),
81            nonce: u32_at(bytes, 76),
82        })
83    }
84
85    /// The 80 wire bytes.
86    pub fn encode(&self) -> [u8; 80] {
87        let mut out = [0u8; 80];
88        out[..72].copy_from_slice(&self.signet_preimage(self.merkle_root));
89        out[72..76].copy_from_slice(&self.bits.to_le_bytes());
90        out[76..].copy_from_slice(&self.nonce.to_le_bytes());
91        out
92    }
93
94    /// The block hash: SHA-256d of the 80 bytes, in display order. It is also
95    /// the proof-of-work hash (the kernel's `sha256d` `powHash`).
96    pub fn hash(&self) -> BlockHash {
97        BlockHash::from_wire(sha256d(&self.encode()))
98    }
99
100    /// The target `bits` encodes.
101    pub fn target(&self) -> Result<Target, Error> {
102        Target::from_compact(self.bits)
103    }
104
105    /// `hash ≤ target(bits)`: the kernel's `checkProofOfWork`. False when
106    /// `bits` does not decode.
107    pub fn meets_target(&self) -> bool {
108        self.target().is_ok_and(|t| self.hash().meets(&t))
109    }
110
111    /// SPEC 4 step 1 as siding applies it: `bits` must be the compact form
112    /// of `pow_limit` and the hash must meet it.
113    pub fn check_pow(&self, pow_limit: &Target) -> Result<(), Error> {
114        crate::check_pow(self.bits, self.hash(), pow_limit)
115    }
116
117    /// The first 72 header bytes — `version ‖ prev ‖ merkle_root ‖ time` —
118    /// with `merkle_root` replaced by `stripped_merkle_root`, the root over
119    /// the coinbase stripped of its solution. This is what BIP-325's block
120    /// data hashes (`siding/lib/block.mjs` `blockData`).
121    pub fn signet_preimage(&self, stripped_merkle_root: [u8; 32]) -> [u8; BLOCK_DATA_LEN] {
122        let mut out = [0u8; BLOCK_DATA_LEN];
123        out[..4].copy_from_slice(&self.version.to_le_bytes());
124        out[4..36].copy_from_slice(&self.prev_block_hash.to_wire());
125        out[36..68].copy_from_slice(&stripped_merkle_root);
126        out[68..].copy_from_slice(&self.time.to_le_bytes());
127        out
128    }
129
130    /// The BIP-325 block data, `SHA256(signet_preimage)`, which the block's
131    /// signature commits to (SPEC 4 step 2).
132    pub fn block_data(&self, stripped_merkle_root: [u8; 32]) -> [u8; 32] {
133        signet::block_data(&self.signet_preimage(stripped_merkle_root))
134    }
135}
136
137pub(crate) fn u32_at(b: &[u8], at: usize) -> u32 {
138    u32::from_le_bytes([b[at], b[at + 1], b[at + 2], b[at + 3]])
139}
140
141pub(crate) fn u16_at(b: &[u8], at: usize) -> u16 {
142    u16::from_le_bytes([b[at], b[at + 1]])
143}
144
145pub(crate) fn arr32(b: &[u8], at: usize) -> [u8; 32] {
146    let mut out = [0u8; 32];
147    out.copy_from_slice(&b[at..at + 32]);
148    out
149}
150
151pub(crate) fn arr16(b: &[u8], at: usize) -> [u8; 16] {
152    let mut out = [0u8; 16];
153    out.copy_from_slice(&b[at..at + 16]);
154    out
155}