1use std::sync::OnceLock;
39
40use bitcoin::block::{Header, Version as HeaderVersion};
41use bitcoin::consensus::encode::{deserialize, serialize};
42use bitcoin::hashes::{sha256, sha256d, Hash, HashEngine};
43use bitcoin::key::TweakedPublicKey;
44use bitcoin::secp256k1::{
45 schnorr::Signature, All, Keypair, Message, Secp256k1, SecretKey, XOnlyPublicKey,
46};
47use bitcoin::sighash::{Annex, Prevouts, SighashCache, TapSighashType};
48use bitcoin::taproot::TapLeafHash;
49use bitcoin::transaction::Version as TxVersion;
50use bitcoin::{
51 absolute::LockTime, merkle_tree, Amount, BlockHash, CompactTarget, OutPoint, Script, ScriptBuf,
52 Sequence, Target, Transaction, TxIn, TxMerkleNode, TxOut, Txid, Witness, Wtxid,
53};
54
55use crate::error::{Error, Result};
56use crate::federation::{verify_multi_a_input, MultiA, ScriptPathError};
57use crate::parents::Family;
58use crate::rules::RuleResult;
59use crate::sighash::{verify_taproot_key_path, SighashRules};
60
61pub const SIGNET_HEADER: [u8; 4] = [0xec, 0xc7, 0xda, 0xa2];
63pub const MARKER: &str = "sidestr";
65const COMMITMENT_PREFIX: [u8; 6] = [0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed];
67const COMMITMENT_LEN: usize = 38;
70const MAX_WITNESS_ITEMS: usize = 256;
73
74pub fn secp() -> &'static Secp256k1<All> {
76 static SECP: OnceLock<Secp256k1<All>> = OnceLock::new();
77 SECP.get_or_init(Secp256k1::new)
78}
79
80pub trait HeaderFamily:
95 core::fmt::Debug + Copy + Default + PartialEq + Eq + Send + Sync + 'static
96{
97 type Header: Clone + core::fmt::Debug + PartialEq + Eq + Send + Sync + 'static;
99 type Block: SidestrBlock<Header = Self::Header>;
102 const FAMILY: Family;
104 const HEADER_LEN: usize;
106
107 fn family(&self) -> Family {
109 Self::FAMILY
110 }
111 fn header_len(&self) -> usize {
113 Self::HEADER_LEN
114 }
115 fn encode_header(&self, header: &Self::Header) -> Vec<u8>;
117 fn decode_header(&self, bytes: &[u8]) -> Result<Self::Header>;
119 fn block_hash(&self, header: &Self::Header) -> BlockHash;
121 fn signed_prefix(&self, header: &Self::Header) -> Vec<u8>;
125 fn header_height(&self, header: &Self::Header) -> Option<u32>;
127 fn new_header(
131 &self,
132 prev: BlockHash,
133 merkle_root: TxMerkleNode,
134 time: u32,
135 bits: CompactTarget,
136 height: u32,
137 tx_count: usize,
138 ) -> Self::Header;
139 fn version(&self, header: &Self::Header) -> u32;
141 fn version_number(&self, header: &Self::Header) -> i64;
150 fn prev(&self, header: &Self::Header) -> BlockHash;
152 fn merkle_root(&self, header: &Self::Header) -> TxMerkleNode;
154 fn time(&self, header: &Self::Header) -> u32;
156 fn bits(&self, header: &Self::Header) -> CompactTarget;
158 fn nonce(&self, header: &Self::Header) -> u32;
160 fn set_merkle_root(&self, header: &mut Self::Header, root: TxMerkleNode);
162 fn set_nonce(&self, header: &mut Self::Header, nonce: u32);
164 fn header_rules(&self, header: &Self::Header, height: u32) -> Vec<RuleResult> {
168 let _ = (header, height);
169 Vec::new()
170 }
171 fn block_rules(&self, header: &Self::Header, tx_count: usize) -> Vec<RuleResult> {
174 let _ = (header, tx_count);
175 Vec::new()
176 }
177 fn sighash_rules(&self, height: u32) -> SighashRules {
183 let _ = height;
184 SighashRules::Bip341
185 }
186}
187
188pub trait SidestrBlock:
194 Clone + core::fmt::Debug + PartialEq + Eq + Send + Sync + Sized + 'static
195{
196 type Header;
198 fn from_parts(header: Self::Header, txdata: Vec<Transaction>) -> Self;
200 fn header(&self) -> &Self::Header;
202 fn header_mut(&mut self) -> &mut Self::Header;
204 fn txdata(&self) -> &[Transaction];
206 fn txdata_mut(&mut self) -> &mut Vec<Transaction>;
208 fn encode(&self) -> Vec<u8>;
210 fn decode(bytes: &[u8]) -> Result<Self>;
212}
213
214pub const VERSION_HEADER_V2_FLAG: u32 = 0x8000_0000;
221
222#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
228pub struct Stock;
229
230fn stock_bit31(header: &Header) -> Result<()> {
232 if header.version.to_consensus() as u32 & VERSION_HEADER_V2_FLAG != 0 {
233 return Err(Error::Encoding(format!(
234 "stock header: version {:#010x} has bit 31 set (VERSION_HEADER_V2_FLAG): not a stock header",
235 header.version.to_consensus() as u32
236 )));
237 }
238 Ok(())
239}
240
241impl HeaderFamily for Stock {
242 type Header = Header;
243 type Block = bitcoin::Block;
244 const FAMILY: Family = Family::Stock;
245 const HEADER_LEN: usize = 80;
246
247 fn encode_header(&self, header: &Header) -> Vec<u8> {
248 serialize(header)
249 }
250 fn decode_header(&self, bytes: &[u8]) -> Result<Header> {
254 let header: Header =
255 deserialize(bytes).map_err(|e| Error::Encoding(format!("stock header: {e}")))?;
256 stock_bit31(&header)?;
257 Ok(header)
258 }
259 fn block_hash(&self, header: &Header) -> BlockHash {
260 header.block_hash()
261 }
262 fn signed_prefix(&self, header: &Header) -> Vec<u8> {
263 serialize(header)[..72].to_vec()
264 }
265 fn header_height(&self, _header: &Header) -> Option<u32> {
266 None
267 }
268 fn new_header(
269 &self,
270 prev: BlockHash,
271 merkle_root: TxMerkleNode,
272 time: u32,
273 bits: CompactTarget,
274 _height: u32,
275 _tx_count: usize,
276 ) -> Header {
277 Header {
278 version: HeaderVersion::from_consensus(0x2000_0000),
279 prev_blockhash: prev,
280 merkle_root,
281 time,
282 bits,
283 nonce: 0,
284 }
285 }
286 fn version(&self, header: &Header) -> u32 {
287 header.version.to_consensus() as u32
288 }
289 fn version_number(&self, header: &Header) -> i64 {
291 i64::from(header.version.to_consensus())
292 }
293 fn prev(&self, header: &Header) -> BlockHash {
294 header.prev_blockhash
295 }
296 fn merkle_root(&self, header: &Header) -> TxMerkleNode {
297 header.merkle_root
298 }
299 fn time(&self, header: &Header) -> u32 {
300 header.time
301 }
302 fn bits(&self, header: &Header) -> CompactTarget {
303 header.bits
304 }
305 fn nonce(&self, header: &Header) -> u32 {
306 header.nonce
307 }
308 fn set_merkle_root(&self, header: &mut Header, root: TxMerkleNode) {
309 header.merkle_root = root;
310 }
311 fn set_nonce(&self, header: &mut Header, nonce: u32) {
312 header.nonce = nonce;
313 }
314}
315
316impl SidestrBlock for bitcoin::Block {
317 type Header = Header;
318 fn from_parts(header: Header, txdata: Vec<Transaction>) -> Self {
319 bitcoin::Block { header, txdata }
320 }
321 fn header(&self) -> &Header {
322 &self.header
323 }
324 fn header_mut(&mut self) -> &mut Header {
325 &mut self.header
326 }
327 fn txdata(&self) -> &[Transaction] {
328 &self.txdata
329 }
330 fn txdata_mut(&mut self) -> &mut Vec<Transaction> {
331 &mut self.txdata
332 }
333 fn encode(&self) -> Vec<u8> {
334 serialize(self)
335 }
336 fn decode(bytes: &[u8]) -> Result<Self> {
339 let block: bitcoin::Block =
340 deserialize(bytes).map_err(|e| Error::Encoding(e.to_string()))?;
341 stock_bit31(&block.header)?;
342 Ok(block)
343 }
344}
345
346pub type Block = <Stock as HeaderFamily>::Block;
348
349#[derive(Clone, Debug, PartialEq, Eq)]
354pub struct FamilyBlock<F: HeaderFamily> {
355 pub header: F::Header,
357 pub txdata: Vec<Transaction>,
359 family: F,
361}
362
363impl<F: HeaderFamily> SidestrBlock for FamilyBlock<F> {
364 type Header = F::Header;
365 fn from_parts(header: F::Header, txdata: Vec<Transaction>) -> Self {
366 FamilyBlock {
367 header,
368 txdata,
369 family: F::default(),
370 }
371 }
372 fn header(&self) -> &F::Header {
373 &self.header
374 }
375 fn header_mut(&mut self) -> &mut F::Header {
376 &mut self.header
377 }
378 fn txdata(&self) -> &[Transaction] {
379 &self.txdata
380 }
381 fn txdata_mut(&mut self) -> &mut Vec<Transaction> {
382 &mut self.txdata
383 }
384 fn encode(&self) -> Vec<u8> {
385 let mut out = F::default().encode_header(&self.header);
386 out.extend(serialize(&self.txdata));
387 out
388 }
389 fn decode(bytes: &[u8]) -> Result<Self> {
390 if bytes.len() < F::HEADER_LEN {
391 return Err(Error::Encoding(format!(
392 "block of {} bytes is shorter than a {} header",
393 bytes.len(),
394 F::HEADER_LEN
395 )));
396 }
397 let header = F::default().decode_header(&bytes[..F::HEADER_LEN])?;
398 let txdata: Vec<Transaction> =
399 deserialize(&bytes[F::HEADER_LEN..]).map_err(|e| Error::Encoding(e.to_string()))?;
400 Ok(FamilyBlock {
401 header,
402 txdata,
403 family: F::default(),
404 })
405 }
406}
407
408pub fn block_weight<F: HeaderFamily>(family: &F, block: &F::Block) -> u64 {
413 let n = block.txdata().len();
414 let varint = match n {
415 0..=0xfc => 1,
416 0xfd..=0xffff => 3,
417 _ => 5,
418 };
419 let fixed = (family.header_len() as u64).saturating_add(varint);
420 let legacy = block
421 .txdata()
422 .iter()
423 .fold(fixed, |n, tx| n.saturating_add(tx.base_size() as u64));
424 let total = block
425 .txdata()
426 .iter()
427 .fold(fixed, |n, tx| n.saturating_add(tx.total_size() as u64));
428 legacy.saturating_mul(3).saturating_add(total)
429}
430
431pub fn merkle_root_of_txs(txdata: &[Transaction]) -> TxMerkleNode {
433 merkle_root_of(txdata.iter().map(Transaction::compute_txid))
434}
435
436pub fn witness_root_of_txs(txdata: &[Transaction]) -> [u8; 32] {
439 merkle_tree::calculate_root(
440 std::iter::once(Wtxid::all_zeros())
441 .chain(txdata.iter().skip(1).map(Transaction::compute_wtxid)),
442 )
443 .map(|h| h.to_byte_array())
444 .unwrap_or([0u8; 32])
445}
446
447fn compact_size(n: usize) -> Vec<u8> {
450 match n {
451 0..=0xfc => vec![n as u8],
452 0xfd..=0xffff => vec![0xfd, (n & 0xff) as u8, (n >> 8) as u8],
453 _ => vec![
454 0xfe,
455 (n & 0xff) as u8,
456 ((n >> 8) & 0xff) as u8,
457 ((n >> 16) & 0xff) as u8,
458 ((n >> 24) & 0xff) as u8,
459 ],
460 }
461}
462
463fn read_compact(b: &[u8], i: usize) -> Option<(usize, usize)> {
467 let first = *b.get(i)?;
468 match first {
469 0..=0xfc => Some((usize::from(first), i + 1)),
470 0xfd => {
471 let n = usize::from(*b.get(i + 1)?) | usize::from(*b.get(i + 2)?) << 8;
472 (n >= 0xfd).then_some((n, i + 3))
473 }
474 0xfe => {
475 let n = usize::from(*b.get(i + 1)?)
476 | usize::from(*b.get(i + 2)?) << 8
477 | usize::from(*b.get(i + 3)?) << 16
478 | usize::from(*b.get(i + 4)?) << 24;
479 (n >= 0x1_0000).then_some((n, i + 5))
480 }
481 _ => None,
482 }
483}
484
485pub fn encode_witness(items: &[Vec<u8>]) -> Vec<u8> {
488 let mut out = compact_size(items.len());
489 for it in items {
490 out.extend(compact_size(it.len()));
491 out.extend_from_slice(it);
492 }
493 out
494}
495
496pub fn decode_witness(bytes: &[u8]) -> Option<Vec<Vec<u8>>> {
502 let (n, mut i) = read_compact(bytes, 0)?;
503 if n > MAX_WITNESS_ITEMS {
504 return None;
505 }
506 let mut items = Vec::with_capacity(n);
507 for _ in 0..n {
508 let (len, at) = read_compact(bytes, i)?;
509 let end = at.checked_add(len)?;
510 items.push(bytes.get(at..end)?.to_vec());
511 i = end;
512 }
513 (i == bytes.len()).then_some(items)
514}
515
516pub fn commitment_output<B: SidestrBlock>(block: &B) -> Option<(usize, &Script)> {
520 commitment_output_of(block.txdata().first()?)
521}
522
523fn commitment_output_of(cb: &Transaction) -> Option<(usize, &Script)> {
524 cb.output
525 .iter()
526 .enumerate()
527 .rev()
528 .find(|(_, o)| {
529 o.script_pubkey.len() >= COMMITMENT_LEN
530 && o.script_pubkey.as_bytes().starts_with(&COMMITMENT_PREFIX)
531 })
532 .map(|(i, o)| (i, o.script_pubkey.as_script()))
533}
534
535#[derive(Debug, Clone, PartialEq, Eq)]
537pub struct Solution {
538 pub witness: Vec<Vec<u8>>,
540 pub index: usize,
542}
543
544pub fn solution_of<B: SidestrBlock>(block: &B) -> Option<Solution> {
549 let (index, spk) = commitment_output(block)?;
550 let rest = &spk.as_bytes()[COMMITMENT_LEN..];
551 if rest.is_empty() {
552 return None;
553 }
554 let (n, at) = match rest[0] {
555 1..=75 => (usize::from(rest[0]), 1),
556 0x4c if rest.len() >= 2 => (usize::from(rest[1]), 2),
557 0x4d if rest.len() >= 3 => (usize::from(rest[1]) | usize::from(rest[2]) << 8, 3),
558 _ => return None,
559 };
560 if n == 0 || rest.len() != at + n {
561 return None;
562 }
563 let push = &rest[at..];
564 let body = push.strip_prefix(&SIGNET_HEADER)?;
565 Some(Solution {
566 witness: decode_witness(body)?,
567 index,
568 })
569}
570
571pub fn with_solution<B: SidestrBlock>(block: &B, witness_items: &[Vec<u8>]) -> Result<B> {
576 let (index, spk) = commitment_output(block)
577 .ok_or_else(|| Error::Block("no witness commitment output to carry the solution".into()))?;
578 let mut push = SIGNET_HEADER.to_vec();
579 push.extend(encode_witness(witness_items));
580 let len = push.len();
581 let op: Vec<u8> = match len {
582 0..=75 => vec![len as u8],
583 76..=255 => vec![0x4c, len as u8],
584 256..=65535 => vec![0x4d, (len & 0xff) as u8, (len >> 8) as u8],
585 _ => return Err(Error::Block("solution too long for one push".into())),
586 };
587 let mut script = spk.as_bytes()[..COMMITMENT_LEN].to_vec();
588 script.extend(op);
589 script.extend(push);
590 let mut out = block.clone();
591 out.txdata_mut()[0].output[index].script_pubkey = ScriptBuf::from_bytes(script);
592 Ok(out)
593}
594
595fn stripped_coinbase(txdata: &[Transaction]) -> Transaction {
598 let mut cb = txdata[0].clone();
599 if let Some((i, spk)) = commitment_output_of(&txdata[0]) {
600 cb.output[i].script_pubkey =
601 ScriptBuf::from_bytes(spk.as_bytes()[..COMMITMENT_LEN].to_vec());
602 }
603 cb
604}
605
606fn merkle_root_of(txids: impl Iterator<Item = Txid>) -> TxMerkleNode {
607 merkle_tree::calculate_root(txids)
608 .map(TxMerkleNode::from)
609 .unwrap_or_else(TxMerkleNode::all_zeros)
610}
611
612pub fn block_data<F: HeaderFamily>(family: &F, block: &F::Block) -> [u8; 32] {
623 let txdata = block.txdata();
624 let cb = stripped_coinbase(txdata);
625 let root = merkle_root_of(
626 std::iter::once(cb.compute_txid())
627 .chain(txdata.iter().skip(1).map(Transaction::compute_txid)),
628 );
629 let mut header = block.header().clone();
630 family.set_merkle_root(&mut header, root);
631 sha256::Hash::hash(&family.signed_prefix(&header)).to_byte_array()
632}
633
634#[derive(Debug, Clone)]
638pub struct VirtualTxs {
639 pub to_spend: Transaction,
641 pub to_sign: Transaction,
643 pub prevout: TxOut,
645}
646
647pub fn virtual_txs(data: &[u8; 32], challenge: &Script, witness: &[Vec<u8>]) -> VirtualTxs {
650 let mut script_sig = vec![0x00, 0x20];
651 script_sig.extend_from_slice(data);
652 let to_spend = Transaction {
653 version: TxVersion(0),
654 lock_time: LockTime::ZERO,
655 input: vec![TxIn {
656 previous_output: OutPoint::null(),
657 script_sig: ScriptBuf::from_bytes(script_sig),
658 sequence: Sequence::ZERO,
659 witness: Witness::new(),
660 }],
661 output: vec![TxOut {
662 value: Amount::ZERO,
663 script_pubkey: challenge.to_owned(),
664 }],
665 };
666 let prevout = TxOut {
667 value: Amount::ZERO,
668 script_pubkey: challenge.to_owned(),
669 };
670 let to_sign = Transaction {
671 version: TxVersion(0),
672 lock_time: LockTime::ZERO,
673 input: vec![TxIn {
674 previous_output: OutPoint {
675 txid: to_spend.compute_txid(),
676 vout: 0,
677 },
678 script_sig: ScriptBuf::new(),
679 sequence: Sequence::ZERO,
680 witness: Witness::from_slice(witness),
681 }],
682 output: vec![TxOut {
683 value: Amount::ZERO,
684 script_pubkey: ScriptBuf::from_bytes(vec![0x6a]),
685 }],
686 };
687 VirtualTxs {
688 to_spend,
689 to_sign,
690 prevout,
691 }
692}
693
694pub fn height_push(height: u32) -> Vec<u8> {
700 let mut out = Vec::new();
701 let mut n = height;
702 while n > 0 {
703 out.push((n & 0xff) as u8);
704 n >>= 8;
705 }
706 if out.last().is_some_and(|b| b & 0x80 != 0) {
707 out.push(0);
708 }
709 if out.is_empty() {
710 return vec![0x00];
711 }
712 let mut push = vec![out.len() as u8];
713 push.extend(out);
714 push
715}
716
717pub fn coinbase_height(coinbase: &Transaction) -> Result<u32> {
724 let sig = coinbase
725 .input
726 .first()
727 .map(|i| i.script_sig.as_bytes())
728 .unwrap_or(&[]);
729 let bad = |m: &str| Err(Error::CoinbaseHeight(m.into()));
730 if sig.is_empty() {
731 return bad("coinbase scriptSig is not a hex script");
732 }
733 let n = usize::from(sig[0]);
734 if n == 0 {
735 return Ok(0);
736 }
737 if (0x51..=0x60).contains(&n) {
738 return Ok((n - 0x50) as u32);
739 }
740 if n > 75 || sig.len() < 1 + n {
741 return bad("coinbase scriptSig does not start with a height push");
742 }
743 if n > 1 && sig[n] == 0 && sig[n - 1] & 0x80 == 0 {
744 return bad("coinbase height push is not minimal");
745 }
746 if sig[n] & 0x80 != 0 {
747 return bad("coinbase height push is negative");
748 }
749 if n > 5 {
750 return bad("coinbase height push is too long for a height");
751 }
752 let h = (1..=n).rev().fold(0u64, |h, i| h * 256 + u64::from(sig[i]));
753 u32::try_from(h)
754 .map_err(|_| Error::CoinbaseHeight("coinbase height push is too long for a height".into()))
755}
756
757pub fn block_height<F: HeaderFamily>(family: &F, block: &F::Block) -> Result<u32> {
760 match family.header_height(block.header()) {
761 Some(h) => Ok(h),
762 None => coinbase_height(
763 block
764 .txdata()
765 .first()
766 .ok_or_else(|| Error::CoinbaseHeight("block has no coinbase".into()))?,
767 ),
768 }
769}
770
771#[derive(Debug, Clone)]
775pub struct BlockTemplate {
776 pub height: u32,
778 pub prev: BlockHash,
780 pub time: u32,
782 pub transactions: Vec<Transaction>,
784 pub outputs: Vec<TxOut>,
786 pub bits: CompactTarget,
788 pub marker: String,
790}
791
792pub fn witness_commitment(transactions: &[Transaction]) -> [u8; 32] {
796 let root = merkle_tree::calculate_root(
797 std::iter::once(Wtxid::all_zeros())
798 .chain(transactions.iter().map(Transaction::compute_wtxid)),
799 )
800 .map(|h| h.to_byte_array())
801 .unwrap_or([0u8; 32]);
802 let mut cat = [0u8; 64];
803 cat[..32].copy_from_slice(&root);
804 sha256d::Hash::hash(&cat).to_byte_array()
805}
806
807pub fn build_block<F: HeaderFamily>(family: &F, t: &BlockTemplate) -> F::Block {
814 let commitment = witness_commitment(&t.transactions);
815 let mut commitment_spk = COMMITMENT_PREFIX.to_vec();
816 commitment_spk.extend_from_slice(&commitment);
817 let tag = t.marker.as_bytes();
818 let mut script_sig = height_push(t.height);
819 script_sig.push(tag.len() as u8);
820 script_sig.extend_from_slice(tag);
821 let mut outputs = t.outputs.clone();
822 outputs.push(TxOut {
823 value: Amount::ZERO,
824 script_pubkey: ScriptBuf::from_bytes(commitment_spk),
825 });
826 let coinbase = Transaction {
827 version: TxVersion::TWO,
828 lock_time: LockTime::ZERO,
829 input: vec![TxIn {
830 previous_output: OutPoint::null(),
831 script_sig: ScriptBuf::from_bytes(script_sig),
832 sequence: Sequence::MAX,
833 witness: Witness::from_slice(&[[0u8; 32]]),
834 }],
835 output: outputs,
836 };
837 let mut txdata = Vec::with_capacity(t.transactions.len() + 1);
838 txdata.push(coinbase);
839 txdata.extend(t.transactions.iter().cloned());
840 let merkle_root = merkle_root_of_txs(&txdata);
841 let header = family.new_header(t.prev, merkle_root, t.time, t.bits, t.height, txdata.len());
842 F::Block::from_parts(header, txdata)
843}
844
845#[derive(Debug, Clone, Copy, PartialEq, Eq)]
852pub enum SpendPath<'a> {
853 KeyPath,
855 ScriptPath {
859 leaf_hash: TapLeafHash,
861 annex: Option<&'a [u8]>,
863 codesep_pos: u32,
865 },
866}
867
868pub fn block_sighash_for<F: HeaderFamily>(
872 family: &F,
873 block: &F::Block,
874 challenge: &Script,
875 path: &SpendPath,
876) -> Result<[u8; 32]> {
877 let data = block_data(family, block);
878 let v = virtual_txs(&data, challenge, &[]);
879 let mut cache = SighashCache::new(&v.to_sign);
880 let prevouts = [v.prevout];
881 let msg = match *path {
882 SpendPath::KeyPath => cache
883 .taproot_key_spend_signature_hash(0, &Prevouts::All(&prevouts), TapSighashType::Default)
884 .map_err(|e| Error::Block(e.to_string()))?,
885 SpendPath::ScriptPath {
886 leaf_hash,
887 annex,
888 codesep_pos,
889 } => {
890 let annex = annex
891 .map(Annex::new)
892 .transpose()
893 .map_err(|_| Error::Block("bad annex".into()))?;
894 cache
895 .taproot_signature_hash(
896 0,
897 &Prevouts::All(&prevouts),
898 annex,
899 Some((leaf_hash, codesep_pos)),
900 TapSighashType::Default,
901 )
902 .map_err(|e| Error::Block(e.to_string()))?
903 }
904 };
905 Ok(msg.to_byte_array())
906}
907
908pub fn block_sighash<F: HeaderFamily>(
910 family: &F,
911 block: &F::Block,
912 challenge: &Script,
913) -> Result<[u8; 32]> {
914 block_sighash_for(family, block, challenge, &SpendPath::KeyPath)
915}
916
917pub fn template_id<F: HeaderFamily>(
931 family: &F,
932 block: &F::Block,
933 chain_id: &str,
934 genesis_hash: Option<BlockHash>,
935) -> Result<[u8; 32]> {
936 let mut t = block.clone();
937 if let Some((i, spk)) = commitment_output(&t).map(|(i, s)| (i, s.to_owned())) {
938 t.txdata_mut()[0].output[i].script_pubkey =
939 ScriptBuf::from_bytes(spk.as_bytes()[..COMMITMENT_LEN].to_vec());
940 }
941 let root = merkle_root_of_txs(t.txdata());
942 family.set_merkle_root(t.header_mut(), root);
943 family.set_nonce(t.header_mut(), 0);
944 let height = block_height(family, block)?;
945 let tag = sha256::Hash::hash(b"sidestr/template-id").to_byte_array();
946 let mut e = sha256::Hash::engine();
947 e.input(&tag);
948 e.input(&tag);
949 e.input(&compact_size(chain_id.len()));
950 e.input(chain_id.as_bytes());
951 e.input(
952 &genesis_hash
953 .unwrap_or_else(BlockHash::all_zeros)
954 .to_byte_array(),
955 );
956 e.input(&height.to_le_bytes());
957 e.input(&family.prev(block.header()).to_byte_array());
958 e.input(&sha256::Hash::hash(&t.encode()).to_byte_array());
959 Ok(sha256::Hash::from_engine(e).to_byte_array())
960}
961
962pub fn seal_block<F: HeaderFamily>(
967 family: &F,
968 block: &F::Block,
969 witness_items: &[Vec<u8>],
970) -> Result<F::Block> {
971 let mut sealed = with_solution(block, witness_items)?;
972 let root = merkle_root_of_txs(sealed.txdata());
973 family.set_merkle_root(sealed.header_mut(), root);
974 let target = Target::from_compact(family.bits(sealed.header()));
975 for nonce in 0..=u32::MAX {
976 family.set_nonce(sealed.header_mut(), nonce);
977 if target.is_met_by(family.block_hash(sealed.header())) {
978 return Ok(sealed);
979 }
980 }
981 Err(Error::Block("no nonce meets the target".into()))
982}
983
984pub fn sign_block<F: HeaderFamily>(
991 family: &F,
992 block: &F::Block,
993 challenge: &Script,
994 key: &SecretKey,
995 aux: &[u8; 32],
996) -> Result<F::Block> {
997 let keypair = Keypair::from_secret_key(secp(), key);
998 let (xonly, _) = keypair.x_only_public_key();
999 let expected = [&[0x51, 0x20][..], &xonly.serialize()].concat();
1000 if challenge.as_bytes() != expected.as_slice() {
1001 return Err(Error::Block(
1002 "the key is not the chain's signer: the challenge names another key".into(),
1003 ));
1004 }
1005 let msg = block_sighash(family, block, challenge)?;
1006 let sig = secp().sign_schnorr_with_aux_rand(&Message::from_digest(msg), &keypair, aux);
1007 seal_block(family, block, &[sig.serialize().to_vec()])
1008}
1009
1010#[derive(Debug, Clone, PartialEq, Eq)]
1012pub enum BlockSolution {
1013 KeyPath,
1015 ScriptPath(MultiA),
1017}
1018
1019#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
1021pub enum SolutionError {
1022 #[error("no solution")]
1024 NoSolution,
1025 #[error("key path: {0}")]
1027 KeyPath(&'static str),
1028 #[error("script path: {0}")]
1030 ScriptPath(#[from] ScriptPathError),
1031}
1032
1033pub fn verify_block_solution<F: HeaderFamily>(
1043 family: &F,
1044 block: &F::Block,
1045 challenge: &Script,
1046) -> core::result::Result<BlockSolution, SolutionError> {
1047 let sol = solution_of(block).ok_or(SolutionError::NoSolution)?;
1048 let data = block_data(family, block);
1049 let v = virtual_txs(&data, challenge, &sol.witness);
1050 let prevouts = [v.prevout];
1051 let items = sol.witness.len();
1052 let has_annex = items >= 2 && sol.witness[items - 1].first() == Some(&0x50);
1053 if items - usize::from(has_annex) <= 1 {
1054 verify_key_path_input(&v.to_sign, 0, &prevouts)
1055 .map(|()| BlockSolution::KeyPath)
1056 .map_err(SolutionError::KeyPath)
1057 } else {
1058 verify_multi_a_input(&v.to_sign, 0, &prevouts)
1059 .map(BlockSolution::ScriptPath)
1060 .map_err(SolutionError::ScriptPath)
1061 }
1062}
1063
1064pub fn verify_block_signature<F: HeaderFamily>(
1066 family: &F,
1067 block: &F::Block,
1068 challenge: &Script,
1069) -> bool {
1070 verify_block_solution(family, block, challenge).is_ok()
1071}
1072
1073pub fn verify_key_path_input(
1081 tx: &Transaction,
1082 index: usize,
1083 prevouts: &[TxOut],
1084) -> core::result::Result<(), &'static str> {
1085 verify_taproot_key_path(tx, index, prevouts, SighashRules::Bip341)
1086}
1087
1088pub fn pubkey_of(key: &SecretKey) -> XOnlyPublicKey {
1091 Keypair::from_secret_key(secp(), key).x_only_public_key().0
1092}
1093
1094pub fn challenge_for(pubkey: &XOnlyPublicKey) -> ScriptBuf {
1102 ScriptBuf::from_bytes([&[0x51, 0x20][..], &pubkey.serialize()].concat())
1103}
1104
1105pub fn challenge_for_output_key(output_key: &TweakedPublicKey) -> ScriptBuf {
1109 ScriptBuf::from_bytes([&[0x51, 0x20][..], &output_key.serialize()].concat())
1110}
1111
1112pub fn key_from_hex(text: &str) -> Result<SecretKey> {
1116 let bytes = hex::decode(text.trim()).map_err(|e| Error::Encoding(e.to_string()))?;
1117 Ok(SecretKey::from_slice(&bytes)?)
1118}
1119
1120pub(crate) fn schnorr_verify(msg: &[u8; 32], sig: &[u8], pk: &[u8]) -> bool {
1121 let (Ok(sig), Ok(pk)) = (Signature::from_slice(sig), XOnlyPublicKey::from_slice(pk)) else {
1122 return false;
1123 };
1124 secp()
1125 .verify_schnorr(&sig, &Message::from_digest(*msg), &pk)
1126 .is_ok()
1127}
1128
1129pub(crate) fn annex_of<'a>(
1130 items: &mut Vec<&'a [u8]>,
1131) -> core::result::Result<Option<Annex<'a>>, &'static str> {
1132 if items.len() >= 2 && items.last().is_some_and(|a| a.first() == Some(&0x50)) {
1133 let raw = items.pop().expect("checked");
1134 return Annex::new(raw).map(Some).map_err(|_| "bad annex");
1135 }
1136 Ok(None)
1137}
1138
1139#[cfg(test)]
1140mod tests {
1141 use super::*;
1142
1143 fn cb(bytes: Vec<u8>) -> Transaction {
1144 Transaction {
1145 version: TxVersion::TWO,
1146 lock_time: LockTime::ZERO,
1147 input: vec![TxIn {
1148 previous_output: OutPoint::null(),
1149 script_sig: ScriptBuf::from_bytes(bytes),
1150 sequence: Sequence::MAX,
1151 witness: Witness::new(),
1152 }],
1153 output: vec![],
1154 }
1155 }
1156
1157 #[test]
1159 fn coinbase_height_inverts_the_push() {
1160 for h in [
1161 0u32,
1162 1,
1163 16,
1164 17,
1165 127,
1166 128,
1167 255,
1168 256,
1169 65535,
1170 70000,
1171 8_388_608,
1172 u32::MAX,
1173 ] {
1174 assert_eq!(
1175 coinbase_height(&cb([height_push(h), vec![0xff]].concat())).unwrap(),
1176 h,
1177 "height {h}"
1178 );
1179 }
1180 let err = |b: Vec<u8>| coinbase_height(&cb(b)).unwrap_err().to_string();
1181 assert!(err(vec![]).contains("not a hex script"));
1182 assert!(coinbase_height(&Transaction {
1183 input: vec![],
1184 ..cb(vec![])
1185 })
1186 .unwrap_err()
1187 .to_string()
1188 .contains("not a hex script"));
1189 assert!(err(vec![0x4c, 0x01, 0x05]).contains("height push"));
1190 assert!(err(vec![0x03, 0x01]).contains("height push"));
1191 assert!(err(vec![0x01, 0x80]).contains("negative"));
1192 assert!(err(vec![0x04, 0x00, 0x00, 0x00, 0x80]).contains("negative"));
1193 assert_eq!(
1194 coinbase_height(&cb(vec![0x03, 0xff, 0xff, 0x7f])).unwrap(),
1195 8_388_607
1196 );
1197 assert!(err(vec![0x02, 0x05, 0x00]).contains("not minimal"));
1198 assert_eq!(coinbase_height(&cb(vec![0x02, 0x80, 0x00])).unwrap(), 128);
1199 assert_eq!(coinbase_height(&cb(vec![0x51])).unwrap(), 1);
1200 assert_eq!(coinbase_height(&cb(vec![0x60])).unwrap(), 16);
1201 assert!(err(vec![0x06, 1, 1, 1, 1, 1, 1]).contains("too long"));
1202 }
1203
1204 #[test]
1205 fn witness_round_trip_and_solution_push_sizes() {
1206 let items = vec![vec![1u8; 64], vec![], vec![7u8; 300]];
1207 assert_eq!(decode_witness(&encode_witness(&items)).unwrap(), items);
1208 assert_eq!(decode_witness(&[2, 1, 9]), None);
1209 let empty = witness_commitment(&[]);
1210 assert_eq!(
1211 hex::encode(empty),
1212 "e2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf9"
1213 );
1214 let block = build_block(
1215 &Stock,
1216 &BlockTemplate {
1217 height: 3,
1218 prev: BlockHash::all_zeros(),
1219 time: 1,
1220 transactions: vec![],
1221 outputs: vec![],
1222 bits: CompactTarget::from_consensus(0x207f_ffff),
1223 marker: MARKER.into(),
1224 },
1225 );
1226 assert!(solution_of(&block).is_none());
1227 for n in [64usize, 80, 300] {
1228 let sealed = seal_block(&Stock, &block, &[vec![0xabu8; n]]).unwrap();
1229 let sol = solution_of(&sealed).unwrap();
1230 assert_eq!(sol.witness, vec![vec![0xabu8; n]]);
1231 assert!(Target::from_compact(sealed.header.bits).is_met_by(sealed.header.block_hash()));
1232 assert_eq!(coinbase_height(&sealed.txdata[0]).unwrap(), 3);
1233 assert_eq!(
1234 sealed.compute_merkle_root(),
1235 Some(sealed.header.merkle_root)
1236 );
1237 assert_eq!(block_weight(&Stock, &sealed), sealed.weight().to_wu());
1238 }
1239 assert!(with_solution(&block, &[vec![0u8; 70_000]]).is_err());
1240 }
1241
1242 #[test]
1244 fn witness_decoder_is_strict() {
1245 let one = encode_witness(&[vec![9u8; 3]]);
1246 assert!(decode_witness(&one).is_some());
1247 assert_eq!(decode_witness(&[&one[..], &[0u8][..]].concat()), None); assert_eq!(decode_witness(&one[..one.len() - 1]), None); assert_eq!(
1250 decode_witness(&[0xfd, 0x03, 0x00, 0x01, 0x09, 0x01, 0x09, 0x01, 0x09]),
1251 None
1252 ); assert_eq!(decode_witness(&[0xfd, 0xff, 0xff]), None); assert_eq!(decode_witness(&[0xff, 0, 0, 0, 0, 0, 0, 0, 0]), None); assert_eq!(decode_witness(&[]), None);
1256 assert_eq!(decode_witness(&[0]), Some(vec![]));
1257 let big = encode_witness(&[vec![0u8; 300]]);
1258 assert_eq!(decode_witness(&big).unwrap()[0].len(), 300);
1259 }
1260
1261 #[test]
1262 fn a_family_block_round_trips_and_weighs_like_bitcoins() {
1263 let b = build_block(
1265 &Stock,
1266 &BlockTemplate {
1267 height: 1,
1268 prev: BlockHash::all_zeros(),
1269 time: 7,
1270 transactions: vec![],
1271 outputs: vec![],
1272 bits: CompactTarget::from_consensus(0x207f_ffff),
1273 marker: MARKER.into(),
1274 },
1275 );
1276 let fb = FamilyBlock::<Stock>::from_parts(b.header, b.txdata.clone());
1277 assert_eq!(fb.encode(), serialize(&b));
1278 assert_eq!(FamilyBlock::<Stock>::decode(&serialize(&b)).unwrap(), fb);
1279 assert!(FamilyBlock::<Stock>::decode(&serialize(&b)[..90]).is_err());
1280 assert!(FamilyBlock::<Stock>::decode(&[serialize(&b), vec![0]].concat()).is_err());
1281 assert_eq!(hex::encode(witness_root_of_txs(&b.txdata)), "00".repeat(32));
1282 }
1283}