Skip to main content

hns_header_consensus/
lib.rs

1#![doc = "Canonical 236-byte Handshake headers and proof-of-work consensus."]
2
3use blake2::digest::{Update, VariableOutput};
4use blake2::{Blake2b512, Blake2bVar, Digest as BlakeDigest};
5use hns_encoding::{Decoder, Encoder};
6use hns_primitives::{
7    BlockHash, BlockTime, Chainwork, CompactTarget, Height, MerkleRoot, PowHash, PowMask,
8    ReservedRoot, ShareHash, TreeRoot, WitnessRoot,
9};
10use sha3::{Digest as ShaDigest, Sha3_256};
11use thiserror::Error;
12
13pub const HEADER_SIZE: usize = 236;
14pub const EXTRA_NONCE_SIZE: usize = 24;
15
16#[derive(Clone, Debug, Eq, PartialEq)]
17pub struct Header {
18    pub nonce: u32,
19    pub time: BlockTime,
20    pub previous_block: BlockHash,
21    pub tree_root: TreeRoot,
22    pub extra_nonce: [u8; EXTRA_NONCE_SIZE],
23    pub reserved_root: ReservedRoot,
24    pub witness_root: WitnessRoot,
25    pub merkle_root: MerkleRoot,
26    pub version: u32,
27    pub bits: CompactTarget,
28    pub mask: PowMask,
29}
30
31impl Default for Header {
32    fn default() -> Self {
33        Self {
34            nonce: 0,
35            time: BlockTime::new(0),
36            previous_block: BlockHash::default(),
37            tree_root: TreeRoot::default(),
38            extra_nonce: [0; EXTRA_NONCE_SIZE],
39            reserved_root: ReservedRoot::default(),
40            witness_root: WitnessRoot::default(),
41            merkle_root: MerkleRoot::default(),
42            version: 0,
43            bits: CompactTarget::new(0),
44            mask: PowMask::default(),
45        }
46    }
47}
48
49impl Header {
50    pub fn encode(&self) -> [u8; HEADER_SIZE] {
51        let mut encoder = Encoder::with_capacity(HEADER_SIZE);
52        encoder.put_u32_le(self.nonce);
53        encoder.put_u64_le(self.time.get());
54        encoder.put_bytes(self.previous_block.as_bytes());
55        encoder.put_bytes(self.tree_root.as_bytes());
56        encoder.put_bytes(&self.extra_nonce);
57        encoder.put_bytes(self.reserved_root.as_bytes());
58        encoder.put_bytes(self.witness_root.as_bytes());
59        encoder.put_bytes(self.merkle_root.as_bytes());
60        encoder.put_u32_le(self.version);
61        encoder.put_u32_le(self.bits.get());
62        encoder.put_bytes(self.mask.as_bytes());
63        encoder
64            .into_bytes()
65            .try_into()
66            .expect("header encoding is always 236 bytes")
67    }
68
69    pub fn decode(input: &[u8]) -> Result<Self, HeaderError> {
70        if input.len() != HEADER_SIZE {
71            return Err(HeaderError::InvalidLength {
72                actual: input.len(),
73            });
74        }
75        let mut decoder = Decoder::new(input);
76        let header = Self {
77            nonce: decoder.read_u32_le()?,
78            time: BlockTime::new(decoder.read_u64_le()?),
79            previous_block: BlockHash::new(decoder.read_array()?),
80            tree_root: TreeRoot::new(decoder.read_array()?),
81            extra_nonce: decoder.read_array()?,
82            reserved_root: ReservedRoot::new(decoder.read_array()?),
83            witness_root: WitnessRoot::new(decoder.read_array()?),
84            merkle_root: MerkleRoot::new(decoder.read_array()?),
85            version: decoder.read_u32_le()?,
86            bits: CompactTarget::new(decoder.read_u32_le()?),
87            mask: PowMask::new(decoder.read_array()?),
88        };
89        decoder.finish()?;
90        Ok(header)
91    }
92
93    pub fn block_hash(&self) -> BlockHash {
94        BlockHash::new(self.pow_hash().into_bytes())
95    }
96
97    pub fn subheader(&self) -> [u8; 128] {
98        let mut encoder = Encoder::with_capacity(128);
99        encoder.put_bytes(&self.extra_nonce);
100        encoder.put_bytes(self.reserved_root.as_bytes());
101        encoder.put_bytes(self.witness_root.as_bytes());
102        encoder.put_bytes(self.merkle_root.as_bytes());
103        encoder.put_u32_le(self.version);
104        encoder.put_u32_le(self.bits.get());
105        encoder
106            .into_bytes()
107            .try_into()
108            .expect("subheader is always 128 bytes")
109    }
110
111    pub fn sub_hash(&self) -> [u8; 32] {
112        blake2b_256(&[&self.subheader()])
113    }
114
115    pub fn mask_hash(&self) -> [u8; 32] {
116        blake2b_256(&[self.previous_block.as_bytes(), self.mask.as_bytes()])
117    }
118
119    pub fn commit_hash(&self) -> [u8; 32] {
120        blake2b_256(&[&self.sub_hash(), &self.mask_hash()])
121    }
122
123    pub fn preheader(&self) -> [u8; 128] {
124        let mut encoder = Encoder::with_capacity(128);
125        encoder.put_u32_le(self.nonce);
126        encoder.put_u64_le(self.time.get());
127        encoder.put_bytes(&self.padding::<20>());
128        encoder.put_bytes(self.previous_block.as_bytes());
129        encoder.put_bytes(self.tree_root.as_bytes());
130        encoder.put_bytes(&self.commit_hash());
131        encoder
132            .into_bytes()
133            .try_into()
134            .expect("preheader is always 128 bytes")
135    }
136
137    pub fn share_hash(&self) -> ShareHash {
138        let preheader = self.preheader();
139        let left = blake2b_512(&preheader);
140        let right = sha3_256(&[&preheader, &self.padding::<8>()]);
141        ShareHash::new(blake2b_256(&[&left, &self.padding::<32>(), &right]))
142    }
143
144    pub fn pow_hash(&self) -> PowHash {
145        let mut hash = self.share_hash().into_bytes();
146        for (byte, mask) in hash.iter_mut().zip(self.mask.as_bytes()) {
147            *byte ^= mask;
148        }
149        PowHash::new(hash)
150    }
151
152    pub fn verify_pow(&self) -> bool {
153        DecodedTarget::from_compact(self.bits).is_met_by(self.pow_hash().as_bytes())
154    }
155
156    fn padding<const LENGTH: usize>(&self) -> [u8; LENGTH] {
157        let mut output = [0_u8; LENGTH];
158        for (index, byte) in output.iter_mut().enumerate() {
159            *byte =
160                self.previous_block.as_bytes()[index % 32] ^ self.tree_root.as_bytes()[index % 32];
161        }
162        output
163    }
164}
165
166#[derive(Clone, Copy, Debug, Eq, PartialEq)]
167pub struct DecodedTarget {
168    bytes: [u8; 32],
169    negative: bool,
170    overflow: bool,
171}
172
173impl DecodedTarget {
174    pub fn from_compact(bits: CompactTarget) -> Self {
175        let bits = bits.get();
176        if bits == 0 {
177            return Self {
178                bytes: [0; 32],
179                negative: false,
180                overflow: false,
181            };
182        }
183        let exponent = (bits >> 24) as usize;
184        let negative = bits & 0x0080_0000 != 0;
185        let mantissa = bits & 0x007f_ffff;
186        let mut bytes = [0_u8; 32];
187        let mut overflow = false;
188        if exponent <= 3 {
189            let value = mantissa >> (8 * (3 - exponent));
190            bytes[29..32].copy_from_slice(&value.to_be_bytes()[1..4]);
191        } else {
192            let mantissa_bytes = [
193                ((mantissa >> 16) & 0xff) as u8,
194                ((mantissa >> 8) & 0xff) as u8,
195                (mantissa & 0xff) as u8,
196            ];
197            for (offset, byte) in mantissa_bytes.into_iter().enumerate() {
198                let position = 32_isize - exponent as isize + offset as isize;
199                if !(0..32).contains(&position) {
200                    overflow |= byte != 0;
201                } else {
202                    bytes[position as usize] = byte;
203                }
204            }
205        }
206        Self {
207            bytes,
208            negative,
209            overflow,
210        }
211    }
212
213    pub const fn bytes(&self) -> &[u8; 32] {
214        &self.bytes
215    }
216
217    pub fn is_valid(&self) -> bool {
218        !self.negative && !self.overflow && self.bytes.iter().any(|byte| *byte != 0)
219    }
220
221    pub fn is_met_by(&self, hash: &[u8; 32]) -> bool {
222        self.is_valid() && hash <= &self.bytes
223    }
224
225    pub fn proof(&self) -> Option<Chainwork> {
226        if !self.is_valid() {
227            return None;
228        }
229        U256::from_be_bytes(self.bytes)
230            .work_for_target()
231            .map(|work| Chainwork::from_be_bytes(work.to_be_bytes()))
232    }
233
234    pub fn to_compact(self) -> CompactTarget {
235        let Some(first) = self.bytes.iter().position(|byte| *byte != 0) else {
236            return CompactTarget::new(0);
237        };
238        let mut exponent = 32 - first;
239        let mut mantissa = if exponent <= 3 {
240            let mut value = 0_u32;
241            for byte in &self.bytes[first..] {
242                value = (value << 8) | u32::from(*byte);
243            }
244            value << (8 * (3 - exponent))
245        } else {
246            (u32::from(self.bytes[first]) << 16)
247                | (u32::from(self.bytes[first + 1]) << 8)
248                | u32::from(self.bytes[first + 2])
249        };
250        if mantissa & 0x0080_0000 != 0 {
251            mantissa >>= 8;
252            exponent += 1;
253        }
254        CompactTarget::new(((exponent as u32) << 24) | mantissa)
255    }
256}
257
258#[derive(Clone, Copy, Debug, Eq, PartialEq)]
259pub enum Network {
260    Mainnet,
261    Testnet,
262    Regtest,
263    Simnet,
264}
265
266impl Network {
267    pub const fn id(self) -> u8 {
268        match self {
269            Self::Mainnet => 0,
270            Self::Testnet => 1,
271            Self::Regtest => 2,
272            Self::Simnet => 3,
273        }
274    }
275
276    pub const fn parameters(self) -> NetworkParameters {
277        match self {
278            Self::Mainnet => NetworkParameters {
279                network: self,
280                packet_magic: 0x5b6e_f2d3,
281                port: 12_038,
282                brontide_port: 44_806,
283                pow: PowParameters {
284                    limit: hex32(
285                        "0000000000ffff00000000000000000000000000000000000000000000000000",
286                    ),
287                    bits: CompactTarget::new(0x1c00_ffff),
288                    target_window: 144,
289                    target_spacing: 600,
290                    target_timespan: 86_400,
291                    minimum_actual_timespan: 21_600,
292                    maximum_actual_timespan: 345_600,
293                    target_reset: false,
294                    no_retargeting: false,
295                },
296                genesis_hash: BlockHash::new(hex32(
297                    "5b6ef2d3c1f3cdcadfd9a030ba1811efdd17740f14e166489760741d075992e0",
298                )),
299                genesis_time: BlockTime::new(1_580_745_078),
300            },
301            Self::Testnet => NetworkParameters {
302                network: self,
303                packet_magic: 0xb152_0dd2,
304                port: 13_038,
305                brontide_port: 45_806,
306                pow: PowParameters {
307                    limit: hex32(
308                        "00000000ffff0000000000000000000000000000000000000000000000000000",
309                    ),
310                    bits: CompactTarget::new(0x1d00_ffff),
311                    target_window: 144,
312                    target_spacing: 600,
313                    target_timespan: 86_400,
314                    minimum_actual_timespan: 21_600,
315                    maximum_actual_timespan: 345_600,
316                    target_reset: true,
317                    no_retargeting: false,
318                },
319                genesis_hash: BlockHash::new(hex32(
320                    "b1520dd24372f82ec94ebf8cf9d9b037d419c4aa3575d05dec70aedd1b427901",
321                )),
322                genesis_time: BlockTime::new(1_580_745_079),
323            },
324            Self::Regtest => NetworkParameters {
325                network: self,
326                packet_magic: 0xae38_95cf,
327                port: 14_038,
328                brontide_port: 46_806,
329                pow: PowParameters {
330                    limit: hex32(
331                        "7fffff0000000000000000000000000000000000000000000000000000000000",
332                    ),
333                    bits: CompactTarget::new(0x207f_ffff),
334                    target_window: 144,
335                    target_spacing: 600,
336                    target_timespan: 86_400,
337                    minimum_actual_timespan: 21_600,
338                    maximum_actual_timespan: 345_600,
339                    target_reset: true,
340                    no_retargeting: true,
341                },
342                genesis_hash: BlockHash::new(hex32(
343                    "ae3895cf597eff05b19e02a70ceeeecb9dc72dbfe6504a50e9343a72f06a87c5",
344                )),
345                genesis_time: BlockTime::new(1_580_745_080),
346            },
347            Self::Simnet => NetworkParameters {
348                network: self,
349                packet_magic: 0x0e64_8edc,
350                port: 15_038,
351                brontide_port: 47_806,
352                pow: PowParameters {
353                    limit: hex32(
354                        "7fffff0000000000000000000000000000000000000000000000000000000000",
355                    ),
356                    bits: CompactTarget::new(0x207f_ffff),
357                    target_window: 144,
358                    target_spacing: 600,
359                    target_timespan: 86_400,
360                    minimum_actual_timespan: 21_600,
361                    maximum_actual_timespan: 345_600,
362                    target_reset: false,
363                    no_retargeting: false,
364                },
365                genesis_hash: BlockHash::new(hex32(
366                    "0e648edc9cddb179014658061ea3f666a45cf44881877ae506e6babefbef6992",
367                )),
368                genesis_time: BlockTime::new(1_580_745_081),
369            },
370        }
371    }
372}
373
374#[derive(Clone, Copy, Debug, Eq, PartialEq)]
375pub struct PowParameters {
376    pub limit: [u8; 32],
377    pub bits: CompactTarget,
378    pub target_window: u32,
379    pub target_spacing: u32,
380    pub target_timespan: u32,
381    pub minimum_actual_timespan: u32,
382    pub maximum_actual_timespan: u32,
383    pub target_reset: bool,
384    pub no_retargeting: bool,
385}
386
387#[derive(Clone, Copy, Debug, Eq, PartialEq)]
388pub struct NetworkParameters {
389    pub network: Network,
390    pub packet_magic: u32,
391    pub port: u16,
392    pub brontide_port: u16,
393    pub pow: PowParameters,
394    pub genesis_hash: BlockHash,
395    pub genesis_time: BlockTime,
396}
397
398impl NetworkParameters {
399    pub const fn genesis_header(self) -> Header {
400        Header {
401            nonce: 0,
402            time: self.genesis_time,
403            previous_block: BlockHash::new([0; 32]),
404            tree_root: TreeRoot::new([0; 32]),
405            extra_nonce: [0; EXTRA_NONCE_SIZE],
406            reserved_root: ReservedRoot::new([0; 32]),
407            witness_root: WitnessRoot::new(hex32(
408                "1a2c60b9439206938f8d7823782abdb8b211a57431e9c9b6a6365d8d42893351",
409            )),
410            merkle_root: MerkleRoot::new(hex32(
411                "8e4c9756fef2ad10375f360e0560fcc7587eb5223ddf8cd7c7e06e60a1140b15",
412            )),
413            version: 0,
414            bits: self.pow.bits,
415            mask: PowMask::new([0; 32]),
416        }
417    }
418}
419
420#[derive(Clone, Copy, Debug, Eq, PartialEq)]
421pub struct DifficultyPoint {
422    pub height: Height,
423    pub time: BlockTime,
424    pub bits: CompactTarget,
425    pub chainwork: Chainwork,
426}
427
428pub fn expected_next_bits(
429    parameters: PowParameters,
430    next_time: BlockTime,
431    previous: DifficultyPoint,
432    first_suitable: Option<DifficultyPoint>,
433    last_suitable: Option<DifficultyPoint>,
434) -> Result<CompactTarget, HeaderError> {
435    if parameters.no_retargeting {
436        return Ok(parameters.bits);
437    }
438    if parameters.target_reset
439        && next_time.get()
440            > previous
441                .time
442                .get()
443                .saturating_add(u64::from(parameters.target_spacing) * 2)
444    {
445        return Ok(parameters.bits);
446    }
447    if previous.height.get() < parameters.target_window.saturating_add(2) {
448        if previous.bits != parameters.bits {
449            return Err(HeaderError::InvalidDifficulty);
450        }
451        return Ok(parameters.bits);
452    }
453    retarget_bits(
454        parameters,
455        first_suitable.ok_or(HeaderError::MissingDifficultyPoint)?,
456        last_suitable.ok_or(HeaderError::MissingDifficultyPoint)?,
457    )
458}
459
460pub fn retarget_bits(
461    parameters: PowParameters,
462    first: DifficultyPoint,
463    last: DifficultyPoint,
464) -> Result<CompactTarget, HeaderError> {
465    if last.height <= first.height {
466        return Err(HeaderError::InvalidDifficulty);
467    }
468    let work_delta = last
469        .chainwork
470        .checked_sub(first.chainwork)
471        .map_err(|_| HeaderError::InvalidDifficulty)?;
472    let scaled_work = work_delta
473        .checked_mul_u64(u64::from(parameters.target_spacing))
474        .map_err(|_| HeaderError::InvalidDifficulty)?;
475    let actual_timespan = last.time.get().saturating_sub(first.time.get()).clamp(
476        u64::from(parameters.minimum_actual_timespan),
477        u64::from(parameters.maximum_actual_timespan),
478    );
479    let work = scaled_work
480        .checked_div_u64(actual_timespan)
481        .map_err(|_| HeaderError::InvalidDifficulty)?;
482    if work == Chainwork::ZERO {
483        return Ok(parameters.bits);
484    }
485    let target = U256::from_be_bytes(work.to_be_bytes())
486        .target_for_work()
487        .ok_or(HeaderError::InvalidDifficulty)?;
488    if target > U256::from_be_bytes(parameters.limit) {
489        return Ok(parameters.bits);
490    }
491    Ok(DecodedTarget {
492        bytes: target.to_be_bytes(),
493        negative: false,
494        overflow: false,
495    }
496    .to_compact())
497}
498
499#[derive(Clone, Copy, Debug, Eq, PartialEq)]
500pub struct HeaderValidationContext {
501    pub height: Height,
502    pub previous_block: BlockHash,
503    pub median_time: BlockTime,
504    pub now: BlockTime,
505    pub expected_bits: CompactTarget,
506}
507
508pub fn validate_header(
509    parameters: NetworkParameters,
510    header: &Header,
511    context: HeaderValidationContext,
512) -> Result<Chainwork, HeaderError> {
513    let is_genesis = context.height.get() == 0;
514    if is_genesis {
515        if header != &parameters.genesis_header() || header.block_hash() != parameters.genesis_hash
516        {
517            return Err(HeaderError::WrongGenesis);
518        }
519    } else if header.previous_block != context.previous_block {
520        return Err(HeaderError::WrongPreviousBlock);
521    }
522    if header.time <= context.median_time
523        || header.time.get() > context.now.get().saturating_add(7200)
524    {
525        return Err(HeaderError::InvalidTime);
526    }
527    if header.bits != context.expected_bits {
528        return Err(HeaderError::InvalidDifficulty);
529    }
530    let target = DecodedTarget::from_compact(header.bits);
531    if !is_genesis && !target.is_met_by(header.pow_hash().as_bytes()) {
532        return Err(HeaderError::InvalidProofOfWork);
533    }
534    target.proof().ok_or(HeaderError::InvalidProofOfWork)
535}
536
537#[derive(Debug, Error)]
538pub enum HeaderError {
539    #[error(transparent)]
540    Decode(#[from] hns_encoding::DecodeError),
541    #[error("Handshake header must be exactly 236 bytes, got {actual}")]
542    InvalidLength { actual: usize },
543    #[error("header does not match the selected network genesis")]
544    WrongGenesis,
545    #[error("header does not connect to the expected previous block")]
546    WrongPreviousBlock,
547    #[error("invalid header time")]
548    InvalidTime,
549    #[error("invalid header difficulty")]
550    InvalidDifficulty,
551    #[error("missing suitable difficulty point")]
552    MissingDifficultyPoint,
553    #[error("header proof of work does not meet target")]
554    InvalidProofOfWork,
555}
556
557#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
558struct U256([u8; 32]);
559
560impl U256 {
561    const ZERO: Self = Self([0; 32]);
562    const ONE: Self = Self::from_u64(1);
563
564    const fn from_be_bytes(bytes: [u8; 32]) -> Self {
565        Self(bytes)
566    }
567
568    const fn to_be_bytes(self) -> [u8; 32] {
569        self.0
570    }
571
572    const fn from_u64(value: u64) -> Self {
573        let mut bytes = [0_u8; 32];
574        let value = value.to_be_bytes();
575        let mut index = 0;
576        while index < 8 {
577            bytes[24 + index] = value[index];
578            index += 1;
579        }
580        Self(bytes)
581    }
582
583    fn checked_add(self, other: Self) -> Option<Self> {
584        let mut output = [0_u8; 32];
585        let mut carry = 0_u16;
586        for index in (0..32).rev() {
587            let sum = u16::from(self.0[index]) + u16::from(other.0[index]) + carry;
588            output[index] = sum as u8;
589            carry = sum >> 8;
590        }
591        (carry == 0).then_some(Self(output))
592    }
593
594    fn work_for_target(self) -> Option<Self> {
595        if self == Self::ZERO {
596            return None;
597        }
598        let Some(divisor) = self.checked_add(Self::ONE) else {
599            return Some(Self::ONE);
600        };
601        Self::divide_two_to_256(divisor)
602    }
603
604    fn target_for_work(self) -> Option<Self> {
605        if self == Self::ZERO {
606            return None;
607        }
608        if self == Self::ONE {
609            return Some(Self([0xff; 32]));
610        }
611        Self::divide_two_to_256(self)?.checked_sub(Self::ONE)
612    }
613
614    fn checked_sub(self, other: Self) -> Option<Self> {
615        (self >= other).then(|| self.wrapping_sub(other))
616    }
617
618    fn shift_left_one(&mut self) -> bool {
619        let high = self.0[0] & 0x80 != 0;
620        let mut carry = 0_u8;
621        for byte in self.0.iter_mut().rev() {
622            let next_carry = *byte >> 7;
623            *byte = (*byte << 1) | carry;
624            carry = next_carry;
625        }
626        high
627    }
628
629    fn wrapping_sub(self, other: Self) -> Self {
630        let mut output = [0_u8; 32];
631        let mut borrow = 0_i16;
632        for index in (0..32).rev() {
633            let difference = i16::from(self.0[index]) - i16::from(other.0[index]) - borrow;
634            if difference < 0 {
635                output[index] = (difference + 256) as u8;
636                borrow = 1;
637            } else {
638                output[index] = difference as u8;
639                borrow = 0;
640            }
641        }
642        Self(output)
643    }
644
645    fn set_bit(&mut self, bit: usize) {
646        self.0[31 - bit / 8] |= 1 << (bit % 8);
647    }
648
649    fn divide_two_to_256(divisor: Self) -> Option<Self> {
650        if divisor <= Self::ONE {
651            return None;
652        }
653        let mut remainder = Self::ZERO;
654        let mut quotient = Self::ZERO;
655        for bit in (0..=256).rev() {
656            let high = remainder.shift_left_one();
657            if bit == 256 {
658                remainder.0[31] |= 1;
659            }
660            if high || remainder >= divisor {
661                remainder = remainder.wrapping_sub(divisor);
662                if bit < 256 {
663                    quotient.set_bit(bit);
664                }
665            }
666        }
667        Some(quotient)
668    }
669}
670
671fn blake2b_256(parts: &[&[u8]]) -> [u8; 32] {
672    let mut hasher = Blake2bVar::new(32).expect("valid BLAKE2b output length");
673    for part in parts {
674        Update::update(&mut hasher, part);
675    }
676    let mut output = [0_u8; 32];
677    hasher
678        .finalize_variable(&mut output)
679        .expect("valid BLAKE2b output buffer");
680    output
681}
682
683fn blake2b_512(input: &[u8]) -> [u8; 64] {
684    let mut hasher = Blake2b512::new();
685    BlakeDigest::update(&mut hasher, input);
686    hasher.finalize().into()
687}
688
689fn sha3_256(parts: &[&[u8]]) -> [u8; 32] {
690    let mut hasher = Sha3_256::new();
691    for part in parts {
692        ShaDigest::update(&mut hasher, part);
693    }
694    hasher.finalize().into()
695}
696
697const fn hex32(value: &str) -> [u8; 32] {
698    let bytes = value.as_bytes();
699    assert!(bytes.len() == 64);
700    let mut output = [0_u8; 32];
701    let mut index = 0;
702    while index < 32 {
703        output[index] = (hex_nibble(bytes[index * 2]) << 4) | hex_nibble(bytes[index * 2 + 1]);
704        index += 1;
705    }
706    output
707}
708
709const fn hex_nibble(value: u8) -> u8 {
710    match value {
711        b'0'..=b'9' => value - b'0',
712        b'a'..=b'f' => value - b'a' + 10,
713        b'A'..=b'F' => value - b'A' + 10,
714        _ => panic!("invalid hexadecimal constant"),
715    }
716}
717
718#[cfg(test)]
719mod tests {
720    use super::*;
721
722    #[test]
723    fn header_round_trips_in_exact_hsd_order() {
724        let raw = (0..HEADER_SIZE)
725            .map(|index| index as u8)
726            .collect::<Vec<_>>();
727        let header = Header::decode(&raw).expect("valid");
728        assert_eq!(header.nonce, 0x0302_0100);
729        assert_eq!(header.time.get(), 0x0b0a_0908_0706_0504);
730        assert_eq!(header.encode().as_slice(), raw);
731    }
732
733    #[test]
734    fn network_genesis_hashes_match_hsd() {
735        for network in [
736            Network::Mainnet,
737            Network::Testnet,
738            Network::Regtest,
739            Network::Simnet,
740        ] {
741            let parameters = network.parameters();
742            let genesis = parameters.genesis_header();
743            assert_eq!(genesis.block_hash(), parameters.genesis_hash);
744            assert_eq!(
745                validate_header(
746                    parameters,
747                    &genesis,
748                    HeaderValidationContext {
749                        height: Height::new(0),
750                        previous_block: BlockHash::default(),
751                        median_time: BlockTime::new(0),
752                        now: genesis.time,
753                        expected_bits: genesis.bits,
754                    },
755                )
756                .expect("valid genesis"),
757                DecodedTarget::from_compact(genesis.bits)
758                    .proof()
759                    .expect("work")
760            );
761        }
762    }
763
764    #[test]
765    fn compact_targets_and_chainwork_match_hsd_boundaries() {
766        for bits in [
767            0x0112_0000,
768            0x0201_2300,
769            0x0312_3456,
770            0x0412_3456,
771            0x1d00_ffff,
772            0x207f_ffff,
773        ] {
774            let compact = CompactTarget::new(bits);
775            let target = DecodedTarget::from_compact(compact);
776            assert!(target.is_valid());
777            assert_eq!(target.to_compact(), compact);
778        }
779        assert_eq!(
780            DecodedTarget::from_compact(CompactTarget::new(0x207f_ffff))
781                .proof()
782                .expect("proof")
783                .to_be_bytes(),
784            Chainwork::from_limbs_le([2, 0, 0, 0]).to_be_bytes()
785        );
786    }
787
788    #[test]
789    fn retarget_matches_pinned_hsd_half_timespan_vector() {
790        let pow = Network::Mainnet.parameters().pow;
791        let first = DifficultyPoint {
792            height: Height::new(1000),
793            time: BlockTime::new(1_000_000),
794            bits: pow.bits,
795            chainwork: Chainwork::from_be_bytes(hex32(
796                "0000000000000000000000000000000000000000000000000123456789abcdef",
797            )),
798        };
799        let last = DifficultyPoint {
800            height: Height::new(first.height.get() + pow.target_window),
801            time: BlockTime::new(first.time.get() + u64::from(pow.target_timespan / 2)),
802            bits: pow.bits,
803            chainwork: Chainwork::from_be_bytes(hex32(
804                "0000000000000000000000000000000000000000000000000123d56819ac5def",
805            )),
806        };
807        assert_eq!(
808            retarget_bits(pow, first, last).expect("retarget"),
809            CompactTarget::new(0x1b7f_ff80)
810        );
811    }
812
813    #[test]
814    fn validation_rejects_wrong_network_genesis() {
815        let mainnet = Network::Mainnet.parameters();
816        let testnet = Network::Testnet.parameters();
817        let header = testnet.genesis_header();
818        let context = HeaderValidationContext {
819            height: Height::new(0),
820            previous_block: BlockHash::default(),
821            median_time: BlockTime::new(0),
822            now: BlockTime::new(header.time.get()),
823            expected_bits: header.bits,
824        };
825        assert!(matches!(
826            validate_header(mainnet, &header, context),
827            Err(HeaderError::WrongGenesis)
828        ));
829    }
830}