cat_envelope/
lib.rs

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
22/// Protocol ID for the envelope
23pub const PROTOCOL_ID: [u8; 3] = *b"cat";
24
25/// Hash of G per BIP-341: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#constructing-and-spending-taproot-outputs
26pub 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
225/// Checks if the input has a taproot annex in its witness.
226pub fn has_annex(input: &TxIn) -> bool {
227    input.witness.last().is_some_and(|last_elem|
228        // From BIP341:
229        // If there are at least two witness elements, and the first byte of
230        // the last element is 0x50, this last element is called annex a
231        // and is removed from the witness stack.
232        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    /// Hex encoded script
239    pub script: ScriptBuf,
240    /// Number of witness elements expected
241    pub num_witness_elements: usize,
242}
243
244/// Takes every script and wraps it, then builds a taptree out of it
245pub 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
274/// Wraps the script in an envelope with the protocol id "cat".
275/// Adds [`num_witness_elements`] OP_DROPs so the witness can be provided
276/// with the transaction still being valid.
277/// The script is then locked to a OP_CHECKSIG with the [`signer_pk`].
278pub fn wrap_script(
279    script: ScriptBuf,
280    num_witness_elements: usize,
281    signer_pk: &XOnlyPublicKey,
282) -> ScriptBuf {
283    // start with envelope
284    let builder = Builder::new()
285        .push_slice([])
286        .push_opcode(opcodes::all::OP_IF)
287        .push_slice(PROTOCOL_ID);
288
289    // split script into 520 byte chunks, the max stack element size
290    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    // envelope ends with an OP_ENDIF
299    let enveloped = with_script.push_opcode(opcodes::all::OP_ENDIF);
300
301    // add OP_DROPs to handle the witness elements to satisfy the
302    // enveloped script
303    let with_witness_drops = if num_witness_elements == 0 {
304        // no witness elements, no OP_DROPs needed
305        enveloped
306    } else if num_witness_elements == 1 {
307        // only a single witness element, can just use a OP_DROP
308        enveloped.push_opcode(opcodes::all::OP_DROP)
309    } else if num_witness_elements % 2 == 0 {
310        // odd even of witness elements, divide by 2 and just use OP_2DROPs
311        (0..(num_witness_elements / 2)).fold(enveloped, |builder, _| {
312            builder.push_opcode(opcodes::all::OP_2DROP)
313        })
314    } else {
315        // odd number of witness elements, divide by 2 and use OP_2DROPs
316        // then add a final OP_DROP
317        (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    // Add final checksig and finalize
325    with_witness_drops
326        .push_x_only_key(signer_pk)
327        .push_opcode(opcodes::all::OP_CHECKSIG)
328        .into_script()
329}
330
331/// Changes the script to the wrapped script and modifies the control block accordingly
332/// Returns the newly created wrapped script
333pub fn wrap_tx_input(input: &mut TxIn, signer_pk: &XOnlyPublicKey) -> Result<Option<ScriptBuf>> {
334    match input.witness.tapscript() {
335        None => Ok(None), // if it isn't a taproot input, ignore this input
336        Some(script) => {
337            let buf = ScriptBuf::from(script);
338
339            // annex(optional) + control block + script
340            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            // change out the script, to wrapped version
349            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
360/// Signs the transaction input and adds the signature as the first element of the witness.
361/// This function assumes the input is a taproot script spend and uses sig hash default.
362pub 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    // add signature to witness
381    // no easy way to add to front of witness, need to rebuild :/
382    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
395/// Changes the script to the unwrapped script
396/// Returns the previous wrapped script of the input
397pub 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        // annex(optional) + control block + script
404        let num_taptree_elements: usize = if has_annex(input) { 3 } else { 2 };
405        let script_index = input.witness.len() - num_taptree_elements;
406
407        // must be a taproot tx to have an envelope
408        let script = input.witness.tapscript()?;
409        let script_buf = ScriptBuf::from(script);
410
411        let mut wit = input.witness.to_vec();
412
413        // change out the script,
414        // script is first taptree witness element
415        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        // validate original tx
522        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        // validate
545        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        // modify tx to wrapped version
552        let wrapped_script = wrap_tx_input(&mut tx.input[0], &signer_pk)
553            .unwrap()
554            .unwrap();
555        assert_ne!(original, tx); // make sure it changed
556
557        // create new prevout
558        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
572        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        // validate
590        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        // basic addition
601        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        // create spending tx
618        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        // validate original tx
658        let result = verify_tx(&tx, &prevouts, VerifyFlags::OpCatEnabled);
659        assert_eq!(result, Ok(()));
660
661        let original = tx.clone();
662
663        // modify tx to wrapped version
664        let wrapped_script = wrap_tx_input(&mut tx.input[0], &signer_pk)
665            .unwrap()
666            .unwrap();
667        assert_ne!(original, tx); // make sure it changed
668
669        // create new prevout
670        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
684        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        // validate
702        let result = verify_tx_input_tapscript(&tx, &prevouts, 0);
703        assert_eq!(result, Ok(()))
704    }
705}