1use anyhow::{anyhow, Result};
2use bitcoin::blockdata::{
3 opcodes,
4 script::{
5 Instruction::{self, Op, PushBytes},
6 Instructions,
7 },
8};
9use bitcoin::key::Secp256k1;
10use bitcoin::script::Builder;
11use bitcoin::secp256k1::{All, Keypair};
12use bitcoin::sighash::{Prevouts, SighashCache};
13use bitcoin::taproot::{LeafVersion, Signature, TaprootBuilder, TAPROOT_ANNEX_PREFIX};
14use bitcoin::{
15 script, Script, ScriptBuf, TapLeafHash, TapSighashType, Transaction, TxIn, TxOut, Witness,
16 XOnlyPublicKey,
17};
18use std::iter::Peekable;
19
20pub use bitcoin;
21
22pub const PROTOCOL_ID: [u8; 3] = *b"cat";
24
25pub const NUMS: [u8; 32] = [
27 0x50, 0x92, 0x9b, 0x74, 0xc1, 0xa0, 0x49, 0x54, 0xb7, 0x8b, 0x4b, 0x60, 0x35, 0xe9, 0x7a, 0x5e,
28 0x07, 0x8a, 0x5a, 0x0f, 0x28, 0xec, 0x96, 0xd5, 0x47, 0xbf, 0xee, 0x9a, 0xce, 0x80, 0x3a, 0xc0,
29];
30
31type RawEnvelope = Envelope<Vec<Vec<u8>>>;
32pub(crate) type ParsedEnvelope = Envelope<ScriptBuf>;
33
34#[derive(Default, PartialEq, Clone, Debug, Eq)]
35pub struct Envelope<T> {
36 pub input: u32,
37 pub offset: u32,
38 pub payload: T,
39 pub pushnum: bool,
40 pub stutter: bool,
41}
42
43impl From<RawEnvelope> for ParsedEnvelope {
44 fn from(envelope: RawEnvelope) -> Self {
45 let bytes = envelope.payload.into_iter().flatten().collect::<Vec<_>>();
46
47 let script = ScriptBuf::from(bytes);
48
49 Self {
50 payload: script,
51 input: envelope.input,
52 offset: envelope.offset,
53 pushnum: envelope.pushnum,
54 stutter: envelope.stutter,
55 }
56 }
57}
58
59impl ParsedEnvelope {
60 pub(crate) fn from_transaction(transaction: &Transaction) -> Vec<Self> {
61 RawEnvelope::from_transaction(transaction)
62 .into_iter()
63 .map(|envelope| envelope.into())
64 .collect()
65 }
66}
67
68impl RawEnvelope {
69 pub(crate) fn from_transaction(transaction: &Transaction) -> Vec<Self> {
70 let mut envelopes = Vec::new();
71
72 for (i, input) in transaction.input.iter().enumerate() {
73 if let Some(tapscript) = input.witness.tapscript() {
74 if let Ok(input_envelopes) = Self::from_tapscript(tapscript, i) {
75 envelopes.extend(input_envelopes);
76 }
77 }
78 }
79
80 envelopes
81 }
82
83 pub(crate) fn from_tapscript(tapscript: &Script, input: usize) -> Result<Vec<Self>> {
84 let mut envelopes = Vec::new();
85
86 let mut instructions = tapscript.instructions().peekable();
87
88 let mut stuttered = false;
89 while let Some(instruction) = instructions.next().transpose()? {
90 if instruction == PushBytes((&[]).into()) {
91 let (stutter, envelope) =
92 Self::from_instructions(&mut instructions, input, envelopes.len(), stuttered)?;
93 if let Some(envelope) = envelope {
94 envelopes.push(envelope);
95 } else {
96 stuttered = stutter;
97 }
98 }
99 }
100
101 Ok(envelopes)
102 }
103
104 fn accept(instructions: &mut Peekable<Instructions>, instruction: Instruction) -> Result<bool> {
105 if instructions.peek() == Some(&Ok(instruction)) {
106 instructions.next().transpose()?;
107 Ok(true)
108 } else {
109 Ok(false)
110 }
111 }
112
113 fn from_instructions(
114 instructions: &mut Peekable<Instructions>,
115 input: usize,
116 offset: usize,
117 stutter: bool,
118 ) -> Result<(bool, Option<Self>)> {
119 if !Self::accept(instructions, Op(opcodes::all::OP_IF))? {
120 let stutter = instructions.peek() == Some(&Ok(PushBytes((&[]).into())));
121 return Ok((stutter, None));
122 }
123
124 if !Self::accept(instructions, PushBytes((&PROTOCOL_ID).into()))? {
125 let stutter = instructions.peek() == Some(&Ok(PushBytes((&[]).into())));
126 return Ok((stutter, None));
127 }
128
129 let mut pushnum = false;
130
131 let mut payload = Vec::new();
132
133 loop {
134 match instructions.next().transpose()? {
135 None => return Ok((false, None)),
136 Some(Op(opcodes::all::OP_ENDIF)) => {
137 return Ok((
138 false,
139 Some(Envelope {
140 input: input.try_into().expect("invalid input"),
141 offset: offset.try_into().expect("invalid offset"),
142 payload,
143 pushnum,
144 stutter,
145 }),
146 ));
147 }
148 Some(Op(opcodes::all::OP_PUSHNUM_NEG1)) => {
149 pushnum = true;
150 payload.push(vec![0x81]);
151 }
152 Some(Op(opcodes::all::OP_PUSHNUM_1)) => {
153 pushnum = true;
154 payload.push(vec![1]);
155 }
156 Some(Op(opcodes::all::OP_PUSHNUM_2)) => {
157 pushnum = true;
158 payload.push(vec![2]);
159 }
160 Some(Op(opcodes::all::OP_PUSHNUM_3)) => {
161 pushnum = true;
162 payload.push(vec![3]);
163 }
164 Some(Op(opcodes::all::OP_PUSHNUM_4)) => {
165 pushnum = true;
166 payload.push(vec![4]);
167 }
168 Some(Op(opcodes::all::OP_PUSHNUM_5)) => {
169 pushnum = true;
170 payload.push(vec![5]);
171 }
172 Some(Op(opcodes::all::OP_PUSHNUM_6)) => {
173 pushnum = true;
174 payload.push(vec![6]);
175 }
176 Some(Op(opcodes::all::OP_PUSHNUM_7)) => {
177 pushnum = true;
178 payload.push(vec![7]);
179 }
180 Some(Op(opcodes::all::OP_PUSHNUM_8)) => {
181 pushnum = true;
182 payload.push(vec![8]);
183 }
184 Some(Op(opcodes::all::OP_PUSHNUM_9)) => {
185 pushnum = true;
186 payload.push(vec![9]);
187 }
188 Some(Op(opcodes::all::OP_PUSHNUM_10)) => {
189 pushnum = true;
190 payload.push(vec![10]);
191 }
192 Some(Op(opcodes::all::OP_PUSHNUM_11)) => {
193 pushnum = true;
194 payload.push(vec![11]);
195 }
196 Some(Op(opcodes::all::OP_PUSHNUM_12)) => {
197 pushnum = true;
198 payload.push(vec![12]);
199 }
200 Some(Op(opcodes::all::OP_PUSHNUM_13)) => {
201 pushnum = true;
202 payload.push(vec![13]);
203 }
204 Some(Op(opcodes::all::OP_PUSHNUM_14)) => {
205 pushnum = true;
206 payload.push(vec![14]);
207 }
208 Some(Op(opcodes::all::OP_PUSHNUM_15)) => {
209 pushnum = true;
210 payload.push(vec![15]);
211 }
212 Some(Op(opcodes::all::OP_PUSHNUM_16)) => {
213 pushnum = true;
214 payload.push(vec![16]);
215 }
216 Some(PushBytes(push)) => {
217 payload.push(push.as_bytes().to_vec());
218 }
219 Some(_) => return Ok((false, None)),
220 }
221 }
222 }
223}
224
225pub fn has_annex(input: &TxIn) -> bool {
227 input.witness.last().is_some_and(|last_elem|
228 input.witness.len() >= 2 && last_elem.first() == Some(&TAPROOT_ANNEX_PREFIX))
233}
234
235#[derive(Debug, Clone)]
236#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
237pub struct TaptreeElement {
238 pub script: ScriptBuf,
240 pub num_witness_elements: usize,
242}
243
244pub fn wrap_taptree(
246 secp: &Secp256k1<All>,
247 scripts: Vec<TaptreeElement>,
248 internal_key: XOnlyPublicKey,
249 signer_pk: &XOnlyPublicKey,
250) -> Result<ScriptBuf> {
251 if scripts.is_empty() {
252 return Err(anyhow::anyhow!("empty scripts"));
253 }
254
255 let len = scripts.len() as u32;
256 let wrapped_scripts = scripts
257 .into_iter()
258 .enumerate()
259 .map(|(i, e)| {
260 (
261 len - i as u32,
262 wrap_script(e.script, e.num_witness_elements, signer_pk),
263 )
264 })
265 .collect::<Vec<_>>();
266
267 let spend_info = TaprootBuilder::with_huffman_tree(wrapped_scripts)?
268 .finalize(secp, internal_key)
269 .map_err(|_| anyhow::anyhow!("failed to build spend info"))?;
270
271 Ok(ScriptBuf::new_p2tr_tweaked(spend_info.output_key()))
272}
273
274pub fn wrap_script(
279 script: ScriptBuf,
280 num_witness_elements: usize,
281 signer_pk: &XOnlyPublicKey,
282) -> ScriptBuf {
283 let builder = Builder::new()
285 .push_slice([])
286 .push_opcode(opcodes::all::OP_IF)
287 .push_slice(PROTOCOL_ID);
288
289 let with_script = script
291 .as_bytes()
292 .chunks(520)
293 .fold(builder, |builder, chunk| {
294 let bytes: &script::PushBytes = chunk.try_into().expect("520 bytes in valid");
295 builder.push_slice(bytes)
296 });
297
298 let enveloped = with_script.push_opcode(opcodes::all::OP_ENDIF);
300
301 let with_witness_drops = if num_witness_elements == 0 {
304 enveloped
306 } else if num_witness_elements == 1 {
307 enveloped.push_opcode(opcodes::all::OP_DROP)
309 } else if num_witness_elements % 2 == 0 {
310 (0..(num_witness_elements / 2)).fold(enveloped, |builder, _| {
312 builder.push_opcode(opcodes::all::OP_2DROP)
313 })
314 } else {
315 (0..(num_witness_elements / 2))
318 .fold(enveloped, |builder, _| {
319 builder.push_opcode(opcodes::all::OP_2DROP)
320 })
321 .push_opcode(opcodes::all::OP_DROP)
322 };
323
324 with_witness_drops
326 .push_x_only_key(signer_pk)
327 .push_opcode(opcodes::all::OP_CHECKSIG)
328 .into_script()
329}
330
331pub fn wrap_tx_input(input: &mut TxIn, signer_pk: &XOnlyPublicKey) -> Result<Option<ScriptBuf>> {
334 match input.witness.tapscript() {
335 None => Ok(None), Some(script) => {
337 let buf = ScriptBuf::from(script);
338
339 let num_taptree_elements: usize = if has_annex(input) { 3 } else { 2 };
341 let script_index = input.witness.len() - num_taptree_elements;
342
343 let num_witness_elements = input.witness.len() - num_taptree_elements;
344 let wrapped = wrap_script(buf, num_witness_elements, signer_pk);
345
346 let mut wit = input.witness.to_vec();
347
348 if let Some(element) = wit.get_mut(script_index) {
350 *element = wrapped.as_bytes().to_vec();
351 }
352
353 input.witness = Witness::from(wit);
354
355 Ok(Some(wrapped))
356 }
357 }
358}
359
360pub fn sign_tx_input(
363 secp: &Secp256k1<All>,
364 tx: &mut Transaction,
365 signer: &Keypair,
366 input_idx: usize,
367 prevouts: &Prevouts<TxOut>,
368 script: &Script,
369) -> Result<Signature> {
370 let mut cache = SighashCache::new(tx.clone());
371
372 let leaf_hash = TapLeafHash::from_script(script, LeafVersion::TapScript);
373 let sighash_type = TapSighashType::Default;
374
375 let hash = cache
376 .taproot_script_spend_signature_hash(input_idx, prevouts, leaf_hash, sighash_type)
377 .map_err(|e| anyhow!("Taproot spend signature hash failed {e}"))?;
378 let signature = secp.sign_schnorr_no_aux_rand(&hash.into(), signer);
379
380 let mut wit = tx.input[input_idx].witness.to_vec();
383
384 let sig = Signature {
385 signature,
386 sighash_type,
387 };
388
389 wit.insert(0, sig.to_vec());
390 tx.input[input_idx].witness = Witness::from(wit);
391
392 Ok(sig)
393}
394
395pub fn unwrap_tx_input(tx: &mut Transaction, idx: usize) -> Option<ScriptBuf> {
398 let envelopes = ParsedEnvelope::from_transaction(tx);
399
400 if let Some(envelope) = envelopes.into_iter().find(|e| e.input == idx as u32) {
401 let input = &mut tx.input[envelope.input as usize];
402
403 let num_taptree_elements: usize = if has_annex(input) { 3 } else { 2 };
405 let script_index = input.witness.len() - num_taptree_elements;
406
407 let script = input.witness.tapscript()?;
409 let script_buf = ScriptBuf::from(script);
410
411 let mut wit = input.witness.to_vec();
412
413 if let Some(element) = wit.get_mut(script_index) {
416 if element == script.as_bytes() {
417 *element = envelope.payload.as_bytes().to_vec();
418 }
419 }
420
421 input.witness = Witness::from(wit);
422
423 Some(script_buf)
424 } else {
425 None
426 }
427}
428
429pub fn get_unwrapped_script(tx: &Transaction, idx: usize) -> Option<ScriptBuf> {
430 let envelopes = ParsedEnvelope::from_transaction(tx);
431
432 envelopes
433 .into_iter()
434 .find(|e| e.input == idx as u32)
435 .map(|e| e.payload)
436}
437
438#[cfg(test)]
439mod tests {
440 use super::*;
441 use bitcoin::consensus::Decodable;
442 use bitcoin::hashes::Hash;
443 use bitcoin::hex::FromHex;
444 use bitcoin::io::Cursor;
445 use bitcoin::key::UntweakedPublicKey;
446 use bitcoin::transaction::Version;
447 use bitcoin::{absolute, Amount, OutPoint, Sequence, Txid, WPubkeyHash};
448 use bitcoin_tx_verify::{verify_tx, verify_tx_input_tapscript, VerifyFlags};
449 use rand::rngs::OsRng;
450 use std::collections::HashMap;
451 use std::str::FromStr;
452
453 #[test]
454 fn test_wrap_script() {
455 let script = ScriptBuf::new_p2wpkh(
456 &WPubkeyHash::from_str("0eb77d4815ed327d4d724de8b89a9592a5f74e69").unwrap(),
457 );
458
459 let signer_key = XOnlyPublicKey::from_str(
460 "e1ff3bfdd4e40315959b08b4fcc8245eaa514637e1d4ec2ae166b743341be1af",
461 )
462 .unwrap();
463
464 let wrapped = wrap_script(script.clone(), 1, &signer_key);
465
466 let envelopes = RawEnvelope::from_tapscript(wrapped.as_script(), 0).unwrap();
467 assert_eq!(envelopes.len(), 1);
468 let parsed = ParsedEnvelope::from(envelopes[0].clone());
469 assert_eq!(parsed.payload, script);
470 }
471
472 #[test]
473 fn test_wrap_tx_input() {
474 let tx_bytes: Vec<u8> = FromHex::from_hex("02000000000101b125029710f2ba7fe064292418d2e833bae2897f7bf6e2f1a2242c87635dfc2e0100000000ffffffff01905f010000000000160014cea9d080198881e00baead0521d5be4e660693771001000100040200000004bb00000020f92434758cd2d2eaa58881b31cdfd3c3515448f80e1b51ac32d77c6ec65f1dce20184c0ede118ec8cd31f699524bae48a81ed01ded5f8d08f2f4ff4286b33b027020d0c8f23f944956475f4ef6823c171a46f2f39123fb8e62c3255087e4d68e366c20ad95131bc0b799c0b1af477fb14fcf26a6a9f76079e48bf090acb7e8367bfd0e01020400000000209d9f03916f15de9baac8de5a785e8f3c401d85a49f476a14de38ad0dcea4d3db010004ffffffff3f79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ef3d745bcf6458f86768ae8adba97ac0a1702cd9cebb786f8665c5a84d87d8ae6b7e7e7e7e201f8f848c1c8015fba58504000a171722182cb30ac3716afa9ecb8d398ab039847c7e7e7e7e7e7e7e7e7e0a54617053696768617368a8767b7e7ea811424950303334302f6368616c6c656e6765a8767b2079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179876766b7b7e7e7e7ea86c7c7e6c7601007e7b88517e2079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ac21c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0bb000000").unwrap();
475 let mut tx_cursor = Cursor::new(tx_bytes);
476 let mut tx: Transaction = Transaction::consensus_decode(&mut tx_cursor).unwrap();
477 let original = tx.clone();
478
479 let output_key = XOnlyPublicKey::from_str(
480 "c6ee2efbb6a663bd2d9996e2e7cf5d2a27cb4375879fe3b6beb669dcce6505cd",
481 )
482 .unwrap();
483
484 wrap_tx_input(&mut tx.input[0], &output_key).unwrap();
485
486 assert_ne!(original, tx)
487 }
488
489 #[test]
490 fn test_unwrap_tx_inputs() {
491 let tx_bytes: Vec<u8> = FromHex::from_hex("02000000000101b125029710f2ba7fe064292418d2e833bae2897f7bf6e2f1a2242c87635dfc2e0100000000ffffffff01905f010000000000160014cea9d080198881e00baead0521d5be4e660693771001000100040200000004bb00000020f92434758cd2d2eaa58881b31cdfd3c3515448f80e1b51ac32d77c6ec65f1dce20184c0ede118ec8cd31f699524bae48a81ed01ded5f8d08f2f4ff4286b33b027020d0c8f23f944956475f4ef6823c171a46f2f39123fb8e62c3255087e4d68e366c20ad95131bc0b799c0b1af477fb14fcf26a6a9f76079e48bf090acb7e8367bfd0e01020400000000209d9f03916f15de9baac8de5a785e8f3c401d85a49f476a14de38ad0dcea4d3db010004ffffffff3f79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ef3d745bcf6458f86768ae8adba97ac0a1702cd9cebb786f8665c5a84d87d8ae6b7e7e7e7e201f8f848c1c8015fba58504000a171722182cb30ac3716afa9ecb8d398ab039847c7e7e7e7e7e7e7e7e7e0a54617053696768617368a8767b7e7ea811424950303334302f6368616c6c656e6765a8767b2079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179876766b7b7e7e7e7ea86c7c7e6c7601007e7b88517e2079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ac21c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0bb000000").unwrap();
492 let mut tx_cursor = Cursor::new(tx_bytes);
493 let mut tx: Transaction = Transaction::consensus_decode(&mut tx_cursor).unwrap();
494 let original = tx.clone();
495
496 let output_key = XOnlyPublicKey::from_str(
497 "c6ee2efbb6a663bd2d9996e2e7cf5d2a27cb4375879fe3b6beb669dcce6505cd",
498 )
499 .unwrap();
500
501 wrap_tx_input(&mut tx.input[0], &output_key).unwrap();
502
503 assert_ne!(original, tx);
504
505 let script = unwrap_tx_input(&mut tx, 0);
506
507 assert!(script.is_some());
508 }
509
510 #[test]
511 fn validate_wrapping_tx_inputs() {
512 let tx_bytes: Vec<u8> = FromHex::from_hex("02000000000101b125029710f2ba7fe064292418d2e833bae2897f7bf6e2f1a2242c87635dfc2e0100000000ffffffff01905f010000000000160014cea9d080198881e00baead0521d5be4e660693771001000100040200000004bb00000020f92434758cd2d2eaa58881b31cdfd3c3515448f80e1b51ac32d77c6ec65f1dce20184c0ede118ec8cd31f699524bae48a81ed01ded5f8d08f2f4ff4286b33b027020d0c8f23f944956475f4ef6823c171a46f2f39123fb8e62c3255087e4d68e366c20ad95131bc0b799c0b1af477fb14fcf26a6a9f76079e48bf090acb7e8367bfd0e01020400000000209d9f03916f15de9baac8de5a785e8f3c401d85a49f476a14de38ad0dcea4d3db010004ffffffff3f79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ef3d745bcf6458f86768ae8adba97ac0a1702cd9cebb786f8665c5a84d87d8ae6b7e7e7e7e201f8f848c1c8015fba58504000a171722182cb30ac3716afa9ecb8d398ab039847c7e7e7e7e7e7e7e7e7e0a54617053696768617368a8767b7e7ea811424950303334302f6368616c6c656e6765a8767b2079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179876766b7b7e7e7e7ea86c7c7e6c7601007e7b88517e2079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ac21c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0bb000000").unwrap();
513 let mut tx_cursor = Cursor::new(tx_bytes);
514 let mut tx: Transaction = Transaction::consensus_decode(&mut tx_cursor).unwrap();
515 let original = tx.clone();
516
517 let script = ScriptBuf::from_hex("6b7e7e7e7e201f8f848c1c8015fba58504000a171722182cb30ac3716afa9ecb8d398ab039847c7e7e7e7e7e7e7e7e7e0a54617053696768617368a8767b7e7ea811424950303334302f6368616c6c656e6765a8767b2079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179876766b7b7e7e7e7ea86c7c7e6c7601007e7b88517e2079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ac").unwrap();
518
519 assert_eq!(tx.input[0].witness.tapscript().unwrap(), script.as_script());
520
521 let secp = Secp256k1::new();
523 let value = Amount::from_sat(100000);
524 let ikey = UntweakedPublicKey::from_slice(&NUMS).expect("NUMS point from BIP 341");
525
526 let spend_info = TaprootBuilder::new()
527 .add_leaf(0, script.clone())
528 .unwrap()
529 .finalize(&secp, ikey)
530 .unwrap();
531 let script_pubkey = ScriptBuf::new_p2tr_tweaked(spend_info.output_key());
532 let prevout: TxOut = TxOut {
533 value,
534 script_pubkey,
535 };
536
537 let prevouts = tx
538 .input
539 .iter()
540 .map(|i| i.previous_output)
541 .zip(vec![prevout])
542 .collect();
543
544 let result = verify_tx(&tx, &prevouts, VerifyFlags::OpCatEnabled);
546 assert!(result.is_ok());
547
548 let signer = Keypair::new(&secp, &mut OsRng);
549 let (signer_pk, _) = signer.x_only_public_key();
550
551 let wrapped_script = wrap_tx_input(&mut tx.input[0], &signer_pk)
553 .unwrap()
554 .unwrap();
555 assert_ne!(original, tx); let wrapped = wrap_script(script, tx.input[0].witness.len() - 2, &signer_pk);
559 assert_eq!(wrapped_script, wrapped);
560 let spend_info = TaprootBuilder::new()
561 .add_leaf(0, wrapped.clone())
562 .unwrap()
563 .finalize(&secp, ikey)
564 .unwrap();
565 let script_pubkey = ScriptBuf::new_p2tr_tweaked(spend_info.output_key());
566 let prevout: TxOut = TxOut {
567 value,
568 script_pubkey,
569 };
570
571 sign_tx_input(
573 &secp,
574 &mut tx,
575 &signer,
576 0,
577 &Prevouts::All(&[prevout.clone()]),
578 wrapped.as_script(),
579 )
580 .unwrap();
581
582 let prevouts = tx
583 .input
584 .iter()
585 .map(|i| i.previous_output)
586 .zip(vec![prevout.clone()])
587 .collect();
588
589 let result = verify_tx_input_tapscript(&tx, &prevouts, 0);
591 assert_eq!(result, Ok(()))
592 }
593
594 #[test]
595 fn end_to_end_test() {
596 let secp = Secp256k1::new();
597 let signer = Keypair::new(&secp, &mut OsRng);
598 let (signer_pk, _) = signer.x_only_public_key();
599
600 let script = Builder::new()
602 .push_opcode(opcodes::all::OP_1ADD)
603 .push_opcode(opcodes::all::OP_PUSHNUM_2)
604 .push_opcode(opcodes::all::OP_EQUAL)
605 .into_script();
606
607 let ikey = UntweakedPublicKey::from_slice(&NUMS).expect("NUMS point from BIP 341");
608
609 let spend_info = TaprootBuilder::new()
610 .add_leaf(0, script.clone())
611 .unwrap()
612 .finalize(&secp, ikey)
613 .unwrap();
614
615 let addr_script = ScriptBuf::new_p2tr_tweaked(spend_info.output_key());
616
617 let witness = {
619 let control = spend_info
620 .control_block(&(script.clone(), LeafVersion::TapScript))
621 .unwrap();
622 Witness::from(vec![
623 vec![0x01],
624 script.as_bytes().to_vec(),
625 control.serialize(),
626 ])
627 };
628 let mut tx = Transaction {
629 version: Version::TWO,
630 lock_time: absolute::LockTime::ZERO,
631 input: vec![TxIn {
632 previous_output: OutPoint::new(Txid::all_zeros(), 0),
633 script_sig: Default::default(),
634 sequence: Sequence::MAX,
635 witness,
636 }],
637 output: vec![TxOut {
638 value: Amount::from_sat(100_000),
639 script_pubkey: Default::default(),
640 }],
641 };
642 assert_eq!(tx.input[0].witness.tapscript().unwrap(), script.as_script());
643
644 let value = Amount::from_sat(100_100);
645 let dummy_prevout = TxOut {
646 value,
647 script_pubkey: addr_script,
648 };
649
650 let prevouts = tx
651 .input
652 .iter()
653 .map(|i| i.previous_output)
654 .zip(vec![dummy_prevout])
655 .collect();
656
657 let result = verify_tx(&tx, &prevouts, VerifyFlags::OpCatEnabled);
659 assert_eq!(result, Ok(()));
660
661 let original = tx.clone();
662
663 let wrapped_script = wrap_tx_input(&mut tx.input[0], &signer_pk)
665 .unwrap()
666 .unwrap();
667 assert_ne!(original, tx); let wrapped = wrap_script(script, tx.input[0].witness.len() - 2, &signer_pk);
671 assert_eq!(wrapped_script, wrapped);
672 let spend_info = TaprootBuilder::new()
673 .add_leaf(0, wrapped.clone())
674 .unwrap()
675 .finalize(&secp, ikey)
676 .unwrap();
677 let script_pubkey = ScriptBuf::new_p2tr_tweaked(spend_info.output_key());
678 let prevout: TxOut = TxOut {
679 value,
680 script_pubkey,
681 };
682
683 sign_tx_input(
685 &secp,
686 &mut tx,
687 &signer,
688 0,
689 &Prevouts::All(&[prevout.clone()]),
690 wrapped.as_script(),
691 )
692 .unwrap();
693
694 let prevouts = tx
695 .input
696 .iter()
697 .map(|i| i.previous_output)
698 .zip(vec![prevout])
699 .collect::<HashMap<_, _>>();
700
701 let result = verify_tx_input_tapscript(&tx, &prevouts, 0);
703 assert_eq!(result, Ok(()))
704 }
705}