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(
368 tx: &Transaction,
369 index: usize,
370 prevouts: &[TxOut],
371 rules: SighashRules,
372) -> Result<(), String> {
373 let prevout = prevouts.get(index).ok_or("no prevout for the input")?;
374 if prevout.script_pubkey.as_bytes() == [0x51] {
375 let input = tx.input.get(index).ok_or("no input at that index")?;
376 return if input.script_sig.is_empty() && input.witness.is_empty() {
377 Ok(())
378 } else {
379 Err("an OP_TRUE collateral spend has empty scriptSig and witness".into())
380 };
381 }
382 verify_taproot_key_path(tx, index, prevouts, rules).map_err(str::to_owned)
383}
384
385pub fn rules_for(family: Family) -> SighashRules {
402 match family {
403 Family::Stock => SighashRules::Bip341,
404 Family::Blake2b => SighashRules::KnotsUnified,
405 }
406}
407
408pub fn key_path_hash_type(rules: SighashRules) -> u8 {
413 match rules {
414 SighashRules::KnotsUnified => 0x01 | SIGHASH_UNIFIED,
415 SighashRules::Bip341 => 0x01,
416 }
417}
418
419pub fn key_path_sighash(
451 tx: &Transaction,
452 index: usize,
453 prevouts: &[TxOut],
454 rules: SighashRules,
455) -> Result<([u8; 32], u8), &'static str> {
456 let hash_type = key_path_hash_type(rules);
457 let msg = match rules {
458 SighashRules::KnotsUnified => unified_taproot_sighash(
459 tx,
460 index,
461 prevouts,
462 hash_type,
463 None,
464 UnifiedTaproot::KeyPath,
465 )?,
466 SighashRules::Bip341 => {
467 if prevouts.len() != tx.input.len() {
468 return Err("taproot sighash needs every input prevout");
469 }
470 if index >= tx.input.len() {
471 return Err("no such input");
472 }
473 SighashCache::new(tx)
474 .taproot_key_spend_signature_hash(
475 index,
476 &Prevouts::All(prevouts),
477 TapSighashType::All,
478 )
479 .map_err(|_| "sighash failed")?
480 .to_byte_array()
481 }
482 };
483 Ok((msg, hash_type))
484}
485
486#[cfg(test)]
487mod tests {
488 use super::*;
489 use bitcoin::secp256k1::{Keypair, Message, SecretKey};
490 use bitcoin::transaction::Version;
491 use bitcoin::{absolute::LockTime, Amount, OutPoint, ScriptBuf, Sequence, TxIn, Witness};
492
493 fn spend(hash_type: u8, rules: SighashRules) -> (Transaction, Vec<TxOut>) {
494 let key = SecretKey::from_slice(&[3u8; 32]).unwrap();
495 let kp = Keypair::from_secret_key(crate::block::secp(), &key);
496 let me = crate::block::challenge_for(&kp.x_only_public_key().0);
497 let prevouts = vec![TxOut {
498 value: Amount::from_sat(10_000),
499 script_pubkey: me.clone(),
500 }];
501 let mut tx = Transaction {
502 version: Version::TWO,
503 lock_time: LockTime::ZERO,
504 input: vec![TxIn {
505 previous_output: OutPoint {
506 txid: bitcoin::Txid::all_zeros(),
507 vout: 1,
508 },
509 script_sig: ScriptBuf::new(),
510 sequence: Sequence(0xffff_fffd),
511 witness: Witness::new(),
512 }],
513 output: vec![TxOut {
514 value: Amount::from_sat(9_000),
515 script_pubkey: me,
516 }],
517 };
518 let msg = match rules {
519 SighashRules::KnotsUnified if hash_type & SIGHASH_UNIFIED != 0 => {
520 unified_taproot_sighash(&tx, 0, &prevouts, hash_type, None, UnifiedTaproot::KeyPath)
521 .unwrap()
522 }
523 _ => SighashCache::new(&tx)
524 .taproot_key_spend_signature_hash(
525 0,
526 &Prevouts::All(&prevouts),
527 TapSighashType::from_consensus_u8(hash_type & !SIGHASH_UNIFIED).unwrap(),
528 )
529 .unwrap()
530 .to_byte_array(),
531 };
532 let sig = crate::block::secp()
533 .sign_schnorr_with_aux_rand(&Message::from_digest(msg), &kp, &[0u8; 32])
534 .serialize()
535 .to_vec();
536 let item = if hash_type == 0 {
537 sig
538 } else {
539 [sig, vec![hash_type]].concat()
540 };
541 tx.input[0].witness = Witness::from_slice(&[item]);
542 (tx, prevouts)
543 }
544
545 #[test]
546 fn unified_bit_is_read_only_where_the_rules_say() {
547 let (tx, p) = spend(0x21, SighashRules::KnotsUnified);
548 assert!(verify_taproot_key_path(&tx, 0, &p, SighashRules::KnotsUnified).is_ok());
549 assert_eq!(
550 verify_taproot_key_path(&tx, 0, &p, SighashRules::Bip341),
551 Err("invalid taproot sighash type")
552 );
553 let (tx, p) = spend(0x00, SighashRules::KnotsUnified);
554 assert!(verify_taproot_key_path(&tx, 0, &p, SighashRules::KnotsUnified).is_ok());
555 assert!(verify_taproot_key_path(&tx, 0, &p, SighashRules::Bip341).is_ok());
556 let (tx, p) = spend(0x01, SighashRules::Bip341);
557 assert!(verify_taproot_key_path(&tx, 0, &p, SighashRules::KnotsUnified).is_ok());
558 let (tx, p) = spend(0x21, SighashRules::Bip341);
560 assert!(verify_taproot_key_path(&tx, 0, &p, SighashRules::KnotsUnified).is_err());
561 }
562
563 #[test]
564 fn unified_message_refuses_undefined_types() {
565 let (tx, p) = spend(0x00, SighashRules::Bip341);
566 for bad in [0x20u8, 0x24, 0x60, 0xa0] {
567 assert!(
568 unified_taproot_sighash(&tx, 0, &p, bad, None, UnifiedTaproot::KeyPath).is_err(),
569 "{bad:#x}"
570 );
571 }
572 assert!(unified_taproot_sighash(&tx, 0, &p, 0x01, None, UnifiedTaproot::KeyPath).is_err());
573 assert!(unified_taproot_sighash(&tx, 0, &[], 0x21, None, UnifiedTaproot::KeyPath).is_err());
574 let a = unified_taproot_sighash(&tx, 0, &p, 0x21, None, UnifiedTaproot::KeyPath).unwrap();
575 let b = unified_taproot_sighash(&tx, 0, &p, 0x21, Some(&[0x50]), UnifiedTaproot::KeyPath)
576 .unwrap();
577 let c = unified_taproot_sighash(
578 &tx,
579 0,
580 &p,
581 0x21,
582 None,
583 UnifiedTaproot::ScriptPath {
584 leaf_hash: &[9u8; 32],
585 codesep_pos: 0xffff_ffff,
586 },
587 )
588 .unwrap();
589 let d = unified_taproot_sighash(&tx, 0, &p, 0xa1, None, UnifiedTaproot::KeyPath).unwrap();
590 assert!(a != b && a != c && a != d && b != c);
591 }
592}