1use bitcoin::consensus::encode::serialize;
24use bitcoin::hashes::{sha256, Hash, HashEngine};
25use bitcoin::sighash::{Annex, Prevouts, SighashCache, TapSighashType};
26use bitcoin::{Transaction, TxOut};
27
28use crate::block::{annex_of, schnorr_verify};
29use crate::parents::Family;
30
31pub const SIGHASH_UNIFIED: u8 = 0x20;
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
37pub enum SighashRules {
38 #[default]
40 Bip341,
41 KnotsUnified,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum UnifiedTaproot<'a> {
51 KeyPath,
53 ScriptPath {
55 leaf_hash: &'a [u8; 32],
57 codesep_pos: u32,
59 },
60}
61
62fn sha(parts: &[&[u8]]) -> [u8; 32] {
63 let mut e = sha256::Hash::engine();
64 for p in parts {
65 e.input(p);
66 }
67 sha256::Hash::from_engine(e).to_byte_array()
68}
69
70fn compact_size(n: usize) -> Vec<u8> {
71 match n {
72 0..=0xfc => vec![n as u8],
73 0xfd..=0xffff => vec![0xfd, (n & 0xff) as u8, (n >> 8) as u8],
74 _ => {
75 let mut v = vec![0xfe];
76 v.extend((n as u32).to_le_bytes());
77 v
78 }
79 }
80}
81
82pub fn unified_taproot_sighash(
101 tx: &Transaction,
102 index: usize,
103 prevouts: &[TxOut],
104 hash_type: u8,
105 annex: Option<&[u8]>,
106 path: UnifiedTaproot,
107) -> Result<[u8; 32], &'static str> {
108 if hash_type & SIGHASH_UNIFIED == 0 {
109 return Err("unified sighash without SIGHASH_UNIFIED");
110 }
111 if hash_type & !(0x1f | 0x80 | SIGHASH_UNIFIED) != 0 {
112 return Err("invalid taproot sighash type");
113 }
114 let output_type = hash_type & 0x1f;
115 if !(1..=3).contains(&output_type) {
116 return Err("invalid taproot sighash type");
117 }
118 if prevouts.len() != tx.input.len() {
119 return Err("unified sighash needs every input prevout");
120 }
121 let input = tx.input.get(index).ok_or("no such input")?;
122 let anyone = hash_type & 0x80 != 0;
123 let outpoint = |i: &bitcoin::TxIn| serialize(&i.previous_output);
124 let mut msg: Vec<u8> = vec![0x00, hash_type];
125 msg.extend((tx.version.0 as u32).to_le_bytes());
126 msg.extend(tx.lock_time.to_consensus_u32().to_le_bytes());
127 msg.push(0x00);
128 if !anyone {
129 let prev: Vec<u8> = tx.input.iter().flat_map(outpoint).collect();
130 let amounts: Vec<u8> = prevouts
131 .iter()
132 .flat_map(|p| p.value.to_sat().to_le_bytes())
133 .collect();
134 let spks: Vec<u8> = prevouts
135 .iter()
136 .flat_map(|p| serialize(&p.script_pubkey))
137 .collect();
138 let seqs: Vec<u8> = tx
139 .input
140 .iter()
141 .flat_map(|i| i.sequence.0.to_le_bytes())
142 .collect();
143 msg.extend(sha(&[&prev]));
144 msg.extend(sha(&[&amounts]));
145 msg.extend(sha(&[&spks]));
146 msg.extend(sha(&[&seqs]));
147 }
148 if output_type != 2 && output_type != 3 {
149 let outs: Vec<u8> = tx.output.iter().flat_map(serialize).collect();
150 msg.extend(sha(&[&outs]));
151 }
152 msg.push(match path {
153 UnifiedTaproot::KeyPath => 2,
154 UnifiedTaproot::ScriptPath { .. } => 3,
155 });
156 if anyone {
157 msg.extend(outpoint(input));
158 msg.extend(serialize(&prevouts[index]));
159 msg.extend(input.sequence.0.to_le_bytes());
160 } else {
161 msg.extend((index as u32).to_le_bytes());
162 }
163 match annex {
164 Some(a) => {
165 msg.push(1);
166 msg.extend(sha(&[&compact_size(a.len()), a]));
167 }
168 None => msg.push(0),
169 }
170 if output_type == 3 {
171 let out = tx
172 .output
173 .get(index)
174 .ok_or("sighash single without matching output")?;
175 msg.extend(sha(&[&serialize(out)]));
176 }
177 if let UnifiedTaproot::ScriptPath {
178 leaf_hash,
179 codesep_pos,
180 } = path
181 {
182 msg.extend(leaf_hash);
183 msg.push(0x00);
184 msg.extend(codesep_pos.to_le_bytes());
185 }
186 let tag = sha256::Hash::hash(b"UnifiedSighash").to_byte_array();
187 Ok(sha(&[&tag, &tag, &msg]))
188}
189
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
194pub enum UnifiedLegacy {
195 Base,
197 WitnessV0,
199}
200
201pub fn unified_legacy_sighash(
226 tx: &Transaction,
227 index: usize,
228 prevouts: &[TxOut],
229 hash_type: u8,
230 script_type: UnifiedLegacy,
231 script_code: &[u8],
232) -> Result<[u8; 32], &'static str> {
233 if hash_type & SIGHASH_UNIFIED == 0 {
234 return Err("unified sighash without SIGHASH_UNIFIED");
235 }
236 if prevouts.len() != tx.input.len() {
237 return Err("unified sighash needs every input prevout");
238 }
239 let input = tx.input.get(index).ok_or("no such input")?;
240 let output_type = hash_type & 0x1f;
241 let anyone = hash_type & 0x80 != 0;
242 let outpoint = |i: &bitcoin::TxIn| serialize(&i.previous_output);
243 let mut msg: Vec<u8> = vec![0x00, hash_type];
244 msg.extend((tx.version.0 as u32).to_le_bytes());
245 msg.extend(tx.lock_time.to_consensus_u32().to_le_bytes());
246 msg.push(0x00);
247 if !anyone {
248 let prev: Vec<u8> = tx.input.iter().flat_map(outpoint).collect();
249 let amounts: Vec<u8> = prevouts
250 .iter()
251 .flat_map(|p| p.value.to_sat().to_le_bytes())
252 .collect();
253 let spks: Vec<u8> = prevouts
254 .iter()
255 .flat_map(|p| serialize(&p.script_pubkey))
256 .collect();
257 let seqs: Vec<u8> = tx
258 .input
259 .iter()
260 .flat_map(|i| i.sequence.0.to_le_bytes())
261 .collect();
262 msg.extend(sha(&[&prev]));
263 msg.extend(sha(&[&amounts]));
264 msg.extend(sha(&[&spks]));
265 msg.extend(sha(&[&seqs]));
266 }
267 if output_type != 2 && output_type != 3 {
268 let outs: Vec<u8> = tx.output.iter().flat_map(serialize).collect();
269 msg.extend(sha(&[&outs]));
270 }
271 msg.push(match script_type {
272 UnifiedLegacy::Base => 0,
273 UnifiedLegacy::WitnessV0 => 1,
274 });
275 if anyone {
276 msg.extend(outpoint(input));
277 msg.extend(serialize(&prevouts[index]));
278 msg.extend(input.sequence.0.to_le_bytes());
279 } else {
280 msg.extend((index as u32).to_le_bytes());
281 }
282 msg.extend(compact_size(script_code.len()));
283 msg.extend(script_code);
284 if output_type == 3 {
285 let out = tx
286 .output
287 .get(index)
288 .ok_or("sighash single without matching output")?;
289 msg.extend(sha(&[&serialize(out)]));
290 }
291 let tag = sha256::Hash::hash(b"UnifiedSighash").to_byte_array();
292 Ok(sha(&[&tag, &tag, &msg]))
293}
294
295pub fn verify_taproot_key_path(
302 tx: &Transaction,
303 index: usize,
304 prevouts: &[TxOut],
305 rules: SighashRules,
306) -> Result<(), &'static str> {
307 let prevout = prevouts.get(index).ok_or("no prevout for the input")?;
308 let input = tx.input.get(index).ok_or("no such input")?;
309 if !prevout.script_pubkey.is_p2tr() {
310 return Err("unsupported script type: sidestr-core verifies taproot spends only");
311 }
312 if !input.script_sig.is_empty() {
313 return Err("WITNESS_MALLEATED");
314 }
315 let mut items: Vec<&[u8]> = input.witness.iter().collect();
316 if items.is_empty() {
317 return Err("empty taproot witness");
318 }
319 let annex = annex_of(&mut items)?;
320 if items.len() != 1 {
321 return Err("taproot script path: not a key-path spend");
322 }
323 let raw = items[0];
324 let (sig, hash_type) = match raw.len() {
325 64 => (raw, 0u8),
326 65 => {
327 if raw[64] == 0 {
328 return Err("explicit SIGHASH_DEFAULT in 65-byte signature");
329 }
330 (&raw[..64], raw[64])
331 }
332 _ => return Err("bad key-path signature size"),
333 };
334 let msg = if rules == SighashRules::KnotsUnified && hash_type & SIGHASH_UNIFIED != 0 {
335 unified_taproot_sighash(
336 tx,
337 index,
338 prevouts,
339 hash_type,
340 annex.as_ref().map(Annex::as_bytes),
341 UnifiedTaproot::KeyPath,
342 )?
343 } else {
344 let ty = TapSighashType::from_consensus_u8(hash_type)
345 .map_err(|_| "invalid taproot sighash type")?;
346 if prevouts.len() != tx.input.len() {
347 return Err("taproot sighash needs every input prevout");
348 }
349 SighashCache::new(tx)
350 .taproot_signature_hash(index, &Prevouts::All(prevouts), annex, None, ty)
351 .map_err(|_| "sighash failed")?
352 .to_byte_array()
353 };
354 if schnorr_verify(&msg, sig, &prevout.script_pubkey.as_bytes()[2..34]) {
355 Ok(())
356 } else {
357 Err("invalid key-path schnorr signature")
358 }
359}
360
361pub fn verify_supported_input(
376 tx: &Transaction,
377 index: usize,
378 prevouts: &[TxOut],
379 rules: SighashRules,
380) -> Result<(), String> {
381 let prevout = prevouts.get(index).ok_or("no prevout for the input")?;
382 if prevout.script_pubkey.as_bytes() == [0x51] {
383 let input = tx.input.get(index).ok_or("no input at that index")?;
384 return if input.script_sig.is_empty() && input.witness.is_empty() {
385 Ok(())
386 } else {
387 Err("an OP_TRUE collateral spend has empty scriptSig and witness".into())
388 };
389 }
390 if is_script_path(tx, index, prevout) {
391 return crate::channel::verify_channel_input(tx, index, prevouts, rules)
392 .map(|_| ())
393 .map_err(|e| format!("taproot script path: {e}"));
394 }
395 verify_taproot_key_path(tx, index, prevouts, rules).map_err(str::to_owned)
396}
397
398fn is_script_path(tx: &Transaction, index: usize, prevout: &TxOut) -> bool {
402 let Some(input) = tx.input.get(index) else {
403 return false;
404 };
405 if !prevout.script_pubkey.is_p2tr() {
406 return false;
407 }
408 let n = input.witness.len();
409 let annex = n >= 2
410 && input
411 .witness
412 .last()
413 .is_some_and(|a| a.first() == Some(&0x50));
414 n - usize::from(annex) >= 2
415}
416
417pub fn rules_for(family: Family) -> SighashRules {
434 match family {
435 Family::Stock => SighashRules::Bip341,
436 Family::Blake2b => SighashRules::KnotsUnified,
437 }
438}
439
440pub fn key_path_hash_type(rules: SighashRules) -> u8 {
445 match rules {
446 SighashRules::KnotsUnified => 0x01 | SIGHASH_UNIFIED,
447 SighashRules::Bip341 => 0x01,
448 }
449}
450
451pub fn key_path_sighash(
483 tx: &Transaction,
484 index: usize,
485 prevouts: &[TxOut],
486 rules: SighashRules,
487) -> Result<([u8; 32], u8), &'static str> {
488 let hash_type = key_path_hash_type(rules);
489 let msg = match rules {
490 SighashRules::KnotsUnified => unified_taproot_sighash(
491 tx,
492 index,
493 prevouts,
494 hash_type,
495 None,
496 UnifiedTaproot::KeyPath,
497 )?,
498 SighashRules::Bip341 => {
499 if prevouts.len() != tx.input.len() {
500 return Err("taproot sighash needs every input prevout");
501 }
502 if index >= tx.input.len() {
503 return Err("no such input");
504 }
505 SighashCache::new(tx)
506 .taproot_key_spend_signature_hash(
507 index,
508 &Prevouts::All(prevouts),
509 TapSighashType::All,
510 )
511 .map_err(|_| "sighash failed")?
512 .to_byte_array()
513 }
514 };
515 Ok((msg, hash_type))
516}
517
518#[cfg(test)]
519mod tests {
520 use super::*;
521 use bitcoin::secp256k1::{Keypair, Message, SecretKey};
522 use bitcoin::transaction::Version;
523 use bitcoin::{absolute::LockTime, Amount, OutPoint, ScriptBuf, Sequence, TxIn, Witness};
524
525 fn spend(hash_type: u8, rules: SighashRules) -> (Transaction, Vec<TxOut>) {
526 let key = SecretKey::from_slice(&[3u8; 32]).unwrap();
527 let kp = Keypair::from_secret_key(crate::block::secp(), &key);
528 let me = crate::block::challenge_for(&kp.x_only_public_key().0);
529 let prevouts = vec![TxOut {
530 value: Amount::from_sat(10_000),
531 script_pubkey: me.clone(),
532 }];
533 let mut tx = Transaction {
534 version: Version::TWO,
535 lock_time: LockTime::ZERO,
536 input: vec![TxIn {
537 previous_output: OutPoint {
538 txid: bitcoin::Txid::all_zeros(),
539 vout: 1,
540 },
541 script_sig: ScriptBuf::new(),
542 sequence: Sequence(0xffff_fffd),
543 witness: Witness::new(),
544 }],
545 output: vec![TxOut {
546 value: Amount::from_sat(9_000),
547 script_pubkey: me,
548 }],
549 };
550 let msg = match rules {
551 SighashRules::KnotsUnified if hash_type & SIGHASH_UNIFIED != 0 => {
552 unified_taproot_sighash(&tx, 0, &prevouts, hash_type, None, UnifiedTaproot::KeyPath)
553 .unwrap()
554 }
555 _ => SighashCache::new(&tx)
556 .taproot_key_spend_signature_hash(
557 0,
558 &Prevouts::All(&prevouts),
559 TapSighashType::from_consensus_u8(hash_type & !SIGHASH_UNIFIED).unwrap(),
560 )
561 .unwrap()
562 .to_byte_array(),
563 };
564 let sig = crate::block::secp()
565 .sign_schnorr_with_aux_rand(&Message::from_digest(msg), &kp, &[0u8; 32])
566 .serialize()
567 .to_vec();
568 let item = if hash_type == 0 {
569 sig
570 } else {
571 [sig, vec![hash_type]].concat()
572 };
573 tx.input[0].witness = Witness::from_slice(&[item]);
574 (tx, prevouts)
575 }
576
577 #[test]
578 fn unified_bit_is_read_only_where_the_rules_say() {
579 let (tx, p) = spend(0x21, SighashRules::KnotsUnified);
580 assert!(verify_taproot_key_path(&tx, 0, &p, SighashRules::KnotsUnified).is_ok());
581 assert_eq!(
582 verify_taproot_key_path(&tx, 0, &p, SighashRules::Bip341),
583 Err("invalid taproot sighash type")
584 );
585 let (tx, p) = spend(0x00, SighashRules::KnotsUnified);
586 assert!(verify_taproot_key_path(&tx, 0, &p, SighashRules::KnotsUnified).is_ok());
587 assert!(verify_taproot_key_path(&tx, 0, &p, SighashRules::Bip341).is_ok());
588 let (tx, p) = spend(0x01, SighashRules::Bip341);
589 assert!(verify_taproot_key_path(&tx, 0, &p, SighashRules::KnotsUnified).is_ok());
590 let (tx, p) = spend(0x21, SighashRules::Bip341);
592 assert!(verify_taproot_key_path(&tx, 0, &p, SighashRules::KnotsUnified).is_err());
593 }
594
595 #[test]
596 fn unified_message_refuses_undefined_types() {
597 let (tx, p) = spend(0x00, SighashRules::Bip341);
598 for bad in [0x20u8, 0x24, 0x60, 0xa0] {
599 assert!(
600 unified_taproot_sighash(&tx, 0, &p, bad, None, UnifiedTaproot::KeyPath).is_err(),
601 "{bad:#x}"
602 );
603 }
604 assert!(unified_taproot_sighash(&tx, 0, &p, 0x01, None, UnifiedTaproot::KeyPath).is_err());
605 assert!(unified_taproot_sighash(&tx, 0, &[], 0x21, None, UnifiedTaproot::KeyPath).is_err());
606 let a = unified_taproot_sighash(&tx, 0, &p, 0x21, None, UnifiedTaproot::KeyPath).unwrap();
607 let b = unified_taproot_sighash(&tx, 0, &p, 0x21, Some(&[0x50]), UnifiedTaproot::KeyPath)
608 .unwrap();
609 let c = unified_taproot_sighash(
610 &tx,
611 0,
612 &p,
613 0x21,
614 None,
615 UnifiedTaproot::ScriptPath {
616 leaf_hash: &[9u8; 32],
617 codesep_pos: 0xffff_ffff,
618 },
619 )
620 .unwrap();
621 let d = unified_taproot_sighash(&tx, 0, &p, 0xa1, None, UnifiedTaproot::KeyPath).unwrap();
622 assert!(a != b && a != c && a != d && b != c);
623 }
624}