Skip to main content

sidestr_header/
v2.rs

1//! Bitcoin Knots' 164-byte v2 header and its BLAKE2b proof of work: the
2//! family of a chain beside `xbt` or `txbt4` (SPEC 3.2).
3//!
4//! Ported from the kernel's `codec/pow/knots-header-v2.js` (the pipeline) and
5//! `schema/overlays/knots-blake2b.jsonld` (the layout), which follow Knots
6//! v29.4.1 `src/primitives/block.cpp` `CBlockHeader::GetHash`.
7
8use crate::hash::{blake2b_256, tagged_hash, Digest32};
9use crate::signet::{self, BLOCK_DATA_LEN};
10use crate::stock::{arr16, arr32, u16_at, u32_at};
11use crate::{BlockHash, Error, HeaderFamily, Target, VERSION_HEADER_V2_FLAG};
12
13/// `flags` bit 2: the consensus time is `time_on_wire + time_offset`
14/// (`FLAG_USE_TIME_OFFSET` in `codec/pow/knots-header-v2.js`).
15pub const FLAG_USE_TIME_OFFSET: u8 = 4;
16
17/// `flags` bits 0–1 select the ASIC input layout of the second BLAKE2b round.
18pub const FLAG_ASIC_PROFILE_MASK: u8 = 0x03;
19
20/// `flags` bits 6–7 are reserved for future hardforks and must be zero
21/// (`knots:rule-header-flags-reserved`).
22pub const FLAG_RESERVED_MASK: u8 = 0xc0;
23
24/// The kernel's name for this proof-of-work hash (`POW_HASH_NAME`).
25pub const POW_HASH_NAME: &str = "knots:blake2b-v2";
26
27/// The 164-byte header used from the BLAKE2b fork height — on a sidestr chain
28/// beside a BLAKE2b parent, from height 0 (`siding/lib/overlay.mjs`:
29/// `blake2bHeight: 0`).
30///
31/// The layout below is the kernel's `knots:BlockHeaderV2` struct with its
32/// field comments adapted. The first 80 bytes keep the classic layout
33/// (version with bit 31 set; the wire time may be offset); proof of work is
34/// the BLAKE2b construction over a commitment tree of these fields, not
35/// SHA-256d of the bytes.
36///
37/// | offset | size | field | wire |
38/// |---|---|---|---|
39/// | 0 | 4 | `version` | u32le, bit 31 set |
40/// | 4 | 32 | `prev_block_hash` | hash256 |
41/// | 36 | 32 | `merkle_root` | hash256 |
42/// | 68 | 4 | `time_on_wire` | u32le |
43/// | 72 | 4 | `bits` | u32le |
44/// | 76 | 4 | `nonce` | u32le |
45/// | 80 | 4 | `nonce2` | u32le |
46/// | 84 | 4 | `nonce3` | u32le |
47/// | 88 | 16 | `extranonce` | bytes, wire order |
48/// | 104 | 4 | `time_offset` | u32le |
49/// | 108 | 2 | `tx_count` | u16le |
50/// | 110 | 1 | `flags` | u8 |
51/// | 111 | 1 | `xor_key_mask_clear_bits` | u8 |
52/// | 112 | 16 | `xor_key` | bytes, wire order |
53/// | 128 | 4 | `height` | i32le |
54/// | 132 | 32 | `mm_rhs` | hash256 |
55///
56/// Beside a BLAKE2b parent siding's `buildBlock` sets `version =
57/// 0xa000_0000`, every nonce, the extranonce, the XOR key and `mm_rhs` to
58/// zero, `flags = 0`, `tx_count` to the block's transaction count and
59/// `height` to the block's height; only `nonce` moves while the block is
60/// sealed against `powLimit`.
61///
62/// ```
63/// use sidestr_header::{Blake2bV2Header, fork};
64/// // txbt4 block 150,308, the fork block: the first v2 / BLAKE2b header.
65/// // Captured from a Knots 29.4.1 node (bitcoin-desktop/schema test vectors).
66/// let bytes = hex::decode(
67///     "000000a0ccb157caa788400a667f6c19858ee913c701a42c8d1cd85122ec17000000000043d2e57990429ae581621ce01aa5fbf5e4c2723996be18660a4930b91e96d6c871b4946affff001dce0ac801d123881f71b4946a00000000b10cf00d0100000000000000000000008e00000000000000000000000000000000000000244b02000000000000000000000000000000000000000000000000000000000000000000",
68/// ).unwrap();
69/// let h = Blake2bV2Header::decode(&bytes).unwrap();
70/// assert_eq!(h.height, fork::TXBT4_FORK_HEIGHT);
71/// assert_eq!(h.hash(), fork::TXBT4_FORK_HASH);
72/// assert_eq!(h.tx_count, 142);
73/// assert_eq!(h.flags, 0); // ASIC profile 0, no time offset
74/// assert_eq!(h.encode().as_slice(), bytes.as_slice());
75/// ```
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub struct Blake2bV2Header {
78    /// Raw wire version: bit 31 set marks a v2 header; the low bits carry the
79    /// usual version.
80    pub version: u32,
81    /// The previous block's hash.
82    pub prev_block_hash: BlockHash,
83    /// The transaction merkle root in **wire (internal) order**.
84    pub merkle_root: [u8; 32],
85    /// Block time as serialised. The consensus time is `time_on_wire +
86    /// time_offset` when `flags` bit 2 is set; see [`Self::time`].
87    pub time_on_wire: u32,
88    /// Compact proof-of-work target.
89    pub bits: u32,
90    /// The first nonce.
91    pub nonce: u32,
92    /// The second nonce.
93    pub nonce2: u32,
94    /// The third nonce.
95    pub nonce3: u32,
96    /// 128-bit extranonce, wire order (bitcoind's RPC shows it
97    /// byte-reversed).
98    pub extranonce: [u8; 16],
99    /// The offset added to `time_on_wire` when `flags` bit 2 is set; also a
100    /// nonce input to the second BLAKE2b round.
101    pub time_offset: u32,
102    /// Committed transaction count; must equal the block's transaction count
103    /// (`knots:rule-block-txcount`).
104    pub tx_count: u16,
105    /// Bits 0–1: ASIC layout profile; bit 2: time offset in use; bits 6–7
106    /// reserved for future hardforks (must be 0).
107    pub flags: u8,
108    /// How many leading bits of the PoW XOR mask are cleared.
109    pub xor_key_mask_clear_bits: u8,
110    /// 128-bit PoW XOR key, wire order.
111    pub xor_key: [u8; 16],
112    /// Committed height; must equal the previous header's height + 1
113    /// (`knots:rule-header-height`). Serialised `i32le`; held unsigned.
114    pub height: u32,
115    /// Merge-mining hook right-hand side, wire order (reserved; all zeros so
116    /// far).
117    pub mm_rhs: [u8; 32],
118}
119
120/// Every intermediate of the v2 pipeline, for tests and debugging — the
121/// kernel's `hashHeaderV2Detailed`. Digests are in the byte order the
122/// pipeline feeds them onward; `hash` is the display-order block hash.
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub struct V2HashStages {
125    /// `tagged("Bitcoin block hash PoW XOR key", xor_key)`.
126    pub xor_key_hash: Digest32,
127    /// Round one: the tagged hash over the committed fields.
128    pub h1: Digest32,
129    /// Round two: the merge-mining hook over `h1`.
130    pub h2: Digest32,
131    /// The first BLAKE2b-256, over `0 ‖ h2 ‖ extranonce`.
132    pub blake2b_1: Digest32,
133    /// The second BLAKE2b-256, over the profile-dependent ASIC input.
134    pub blake2b_2: Digest32,
135    /// The XOR mask applied to `blake2b_2` (zero when the key is zero).
136    pub mask: Digest32,
137    /// `flags & 3`.
138    pub asic_profile: u8,
139    /// `blake2b_2 XOR mask`: the block hash.
140    pub hash: BlockHash,
141}
142
143impl Blake2bV2Header {
144    /// Wire size in bytes.
145    pub const WIRE_SIZE: usize = 164;
146
147    /// Decodes exactly 164 bytes. Rejects any other length
148    /// ([`Error::WrongLength`]) and a version with bit 31 clear
149    /// ([`Error::VersionBit31Clear`]). Reserved flag bits are not checked
150    /// here — the kernel decodes them and its rule rejects them — see
151    /// [`Self::check_flags`].
152    pub fn decode(bytes: &[u8]) -> Result<Self, Error> {
153        if bytes.len() != Self::WIRE_SIZE {
154            return Err(Error::WrongLength {
155                family: HeaderFamily::Blake2bV2,
156                expected: Self::WIRE_SIZE,
157                actual: bytes.len(),
158            });
159        }
160        let version = u32_at(bytes, 0);
161        if version & VERSION_HEADER_V2_FLAG == 0 {
162            return Err(Error::VersionBit31Clear);
163        }
164        Ok(Blake2bV2Header {
165            version,
166            prev_block_hash: BlockHash::from_wire(arr32(bytes, 4)),
167            merkle_root: arr32(bytes, 36),
168            time_on_wire: u32_at(bytes, 68),
169            bits: u32_at(bytes, 72),
170            nonce: u32_at(bytes, 76),
171            nonce2: u32_at(bytes, 80),
172            nonce3: u32_at(bytes, 84),
173            extranonce: arr16(bytes, 88),
174            time_offset: u32_at(bytes, 104),
175            tx_count: u16_at(bytes, 108),
176            flags: bytes[110],
177            xor_key_mask_clear_bits: bytes[111],
178            xor_key: arr16(bytes, 112),
179            height: u32_at(bytes, 128),
180            mm_rhs: arr32(bytes, 132),
181        })
182    }
183
184    /// The 164 wire bytes.
185    pub fn encode(&self) -> [u8; 164] {
186        let mut out = [0u8; 164];
187        out[..72].copy_from_slice(&self.signet_preimage(self.merkle_root));
188        out[72..76].copy_from_slice(&self.bits.to_le_bytes());
189        out[76..80].copy_from_slice(&self.nonce.to_le_bytes());
190        out[80..84].copy_from_slice(&self.nonce2.to_le_bytes());
191        out[84..88].copy_from_slice(&self.nonce3.to_le_bytes());
192        out[88..104].copy_from_slice(&self.extranonce);
193        out[104..108].copy_from_slice(&self.time_offset.to_le_bytes());
194        out[108..110].copy_from_slice(&self.tx_count.to_le_bytes());
195        out[110] = self.flags;
196        out[111] = self.xor_key_mask_clear_bits;
197        out[112..128].copy_from_slice(&self.xor_key);
198        out[128..132].copy_from_slice(&self.height.to_le_bytes());
199        out[132..].copy_from_slice(&self.mm_rhs);
200        out
201    }
202
203    /// The consensus block time: `time_on_wire + time_offset` (wrapping)
204    /// when `flags` bit 2 is set, else `time_on_wire` (`headerTime` in the
205    /// kernel).
206    pub fn time(&self) -> u32 {
207        if self.uses_time_offset() {
208            self.time_on_wire.wrapping_add(self.time_offset)
209        } else {
210            self.time_on_wire
211        }
212    }
213
214    /// Whether `flags` bit 2 is set.
215    pub fn uses_time_offset(&self) -> bool {
216        self.flags & FLAG_USE_TIME_OFFSET != 0
217    }
218
219    /// `flags & 3`: which ASIC input layout the second BLAKE2b round uses.
220    pub fn asic_profile(&self) -> u8 {
221        self.flags & FLAG_ASIC_PROFILE_MASK
222    }
223
224    /// `knots:rule-header-flags-reserved`: bits 6–7 of `flags` must be zero.
225    pub fn check_flags(&self) -> Result<(), Error> {
226        if self.flags & FLAG_RESERVED_MASK != 0 {
227            Err(Error::ReservedFlags(self.flags))
228        } else {
229            Ok(())
230        }
231    }
232
233    /// The block hash, which is the proof-of-work hash (`hashHeaderV2`):
234    ///
235    /// ```text
236    /// h1   = tagged("Bitcoin block header 1",
237    ///               version ‖ prev(display) ‖ height ‖ merkle(wire) ‖ time_on_wire ‖ 0x00
238    ///               ‖ bits ‖ tx_count as u32 ‖ flags ‖ clear_bits ‖ tagged("…XOR key", xor_key))
239    /// h2   = tagged("Merge-mining hook", h1 ‖ 0¹⁶ ‖ 0¹⁶ ‖ mm_rhs(wire))
240    /// b1   = blake2b-256(0u32 ‖ h2 ‖ extranonce)
241    /// b2   = blake2b-256(profile-dependent layout of prev_hidden / h2, nonces, b1)
242    /// hash = b2 XOR mask(xor_key, clear_bits)          (display-order bytes)
243    /// ```
244    ///
245    /// Note the two byte orders inside round one: `prev_block_hash` enters
246    /// in display order (Knots' `hashPrevBlock.ReversedBytes()`) while the
247    /// merkle root enters in wire order.
248    pub fn hash(&self) -> BlockHash {
249        self.hash_stages().hash
250    }
251
252    /// The pipeline with every intermediate exposed.
253    pub fn hash_stages(&self) -> V2HashStages {
254        let prev_display = self.prev_block_hash.0;
255        let xor_key_hash = tagged_hash(b"Bitcoin block hash PoW XOR key", &[&self.xor_key]);
256
257        let mut mask = [0u8; 32];
258        if self.xor_key.iter().any(|&b| b != 0) {
259            mask = tagged_hash(b"Bitcoin block hash PoW XOR mask", &[&self.xor_key]);
260            let clear_bytes = usize::from(self.xor_key_mask_clear_bits >> 3);
261            let n = clear_bytes.min(32);
262            mask[..n].fill(0);
263            if clear_bytes < 32 {
264                mask[clear_bytes] &= 0xff >> (self.xor_key_mask_clear_bits & 7);
265            }
266        }
267
268        let prev_hidden = tagged_hash(b"Bitcoin prevblock header, hashed", &[&prev_display]);
269        let h1 = tagged_hash(
270            b"Bitcoin block header 1",
271            &[
272                &self.version.to_le_bytes(),
273                &prev_display,
274                &self.height.to_le_bytes(),
275                &self.merkle_root,
276                &self.time_on_wire.to_le_bytes(),
277                &[0u8],
278                &self.bits.to_le_bytes(),
279                &u32::from(self.tx_count).to_le_bytes(),
280                &[self.flags, self.xor_key_mask_clear_bits],
281                &xor_key_hash,
282            ],
283        );
284        let zeros16 = [0u8; 16];
285        let h2 = tagged_hash(
286            b"Merge-mining hook",
287            &[&h1, &zeros16, &zeros16, &self.mm_rhs],
288        );
289        let b1 = blake2b_256(&[&0u32.to_le_bytes(), &h2, &self.extranonce]);
290
291        let nonce = self.nonce.to_le_bytes();
292        let nonce2 = self.nonce2.to_le_bytes();
293        let nonce3 = self.nonce3.to_le_bytes();
294        let offset = self.time_offset.to_le_bytes();
295        let asic_profile = self.asic_profile();
296        let b2 = match asic_profile {
297            0 => {
298                let mut p = prev_hidden;
299                p[..6].fill(0);
300                blake2b_256(&[&p, &nonce, &nonce2, &offset, &nonce3, &b1])
301            }
302            1 => blake2b_256(&[&nonce, &nonce2, &nonce3, &offset, &b1, &h2]),
303            2 => blake2b_256(&[&[0u8; 48], &h2, &nonce, &nonce2, &offset, &nonce3, &b1]),
304            _ => blake2b_256(&[&[0u8; 80], &h2, &nonce, &nonce2, &offset, &nonce3, &b1]),
305        };
306
307        let mut hash = [0u8; 32];
308        for i in 0..32 {
309            hash[i] = b2[i] ^ mask[i];
310        }
311        V2HashStages {
312            xor_key_hash,
313            h1,
314            h2,
315            blake2b_1: b1,
316            blake2b_2: b2,
317            mask,
318            asic_profile,
319            hash: BlockHash(hash),
320        }
321    }
322
323    /// The target `bits` encodes.
324    pub fn target(&self) -> Result<Target, Error> {
325        Target::from_compact(self.bits)
326    }
327
328    /// `hash ≤ target(bits)`: the kernel's `checkProofOfWork`. False when
329    /// `bits` does not decode.
330    pub fn meets_target(&self) -> bool {
331        self.target().is_ok_and(|t| self.hash().meets(&t))
332    }
333
334    /// SPEC 4 step 1 as siding applies it: `bits` must be the compact form
335    /// of `pow_limit` and the hash must meet it.
336    pub fn check_pow(&self, pow_limit: &Target) -> Result<(), Error> {
337        crate::check_pow(self.bits, self.hash(), pow_limit)
338    }
339
340    /// The first 72 header bytes — `version ‖ prev ‖ merkle_root ‖
341    /// time_on_wire` — with `merkle_root` replaced by `stripped_merkle_root`.
342    /// Same shape as the stock family's, which is what makes the BIP-325
343    /// block data family-agnostic (`siding/lib/block.mjs` `blockData`).
344    pub fn signet_preimage(&self, stripped_merkle_root: [u8; 32]) -> [u8; BLOCK_DATA_LEN] {
345        let mut out = [0u8; BLOCK_DATA_LEN];
346        out[..4].copy_from_slice(&self.version.to_le_bytes());
347        out[4..36].copy_from_slice(&self.prev_block_hash.to_wire());
348        out[36..68].copy_from_slice(&stripped_merkle_root);
349        out[68..].copy_from_slice(&self.time_on_wire.to_le_bytes());
350        out
351    }
352
353    /// The BIP-325 block data, `SHA256(signet_preimage)` (SPEC 4 step 2).
354    pub fn block_data(&self, stripped_merkle_root: [u8; 32]) -> [u8; 32] {
355        signet::block_data(&self.signet_preimage(stripped_merkle_root))
356    }
357}