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}