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 != ¶meters.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}