rawtx_rs/
output.rs

1//! Information about Bitcoin transaction outputs.
2
3use crate::script::{Multisig, PubKeyInfo};
4use bitcoin::{blockdata::opcodes::all as opcodes, script, Amount, TxOut};
5use std::{error, fmt};
6
7#[derive(Debug, Clone)]
8pub enum OutputError {
9    PubkeyInfo(script::Error),
10}
11
12impl fmt::Display for OutputError {
13    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
14        match self {
15            OutputError::PubkeyInfo(e) => {
16                write!(f, "Could not extract pubkey infos from input: {}", e)
17            }
18        }
19    }
20}
21
22impl error::Error for OutputError {
23    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
24        match *self {
25            OutputError::PubkeyInfo(ref e) => Some(e),
26        }
27    }
28}
29
30#[derive(PartialEq, Eq, Debug, Clone)]
31pub struct OutputInfo {
32    pub out_type: OutputType,
33    pub value: Amount,
34    pub pubkey_stats: Vec<PubKeyInfo>,
35}
36
37impl OutputInfo {
38    pub fn new(output: &TxOut) -> Result<OutputInfo, OutputError> {
39        Ok(OutputInfo {
40            out_type: output.get_type(),
41            value: Amount::from_sat(output.value.to_sat()),
42            pubkey_stats: PubKeyInfo::from_output(output)?,
43        })
44    }
45
46    /// Returns true if the output is an OP_RETURN output (of any [OpReturnFlavor]).
47    pub fn is_opreturn(&self) -> bool {
48        matches!(self.out_type, OutputType::OpReturn(_))
49    }
50}
51
52#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)]
53pub enum OutputType {
54    P2pk,
55    P2pkh,
56    P2wpkhV0,
57    P2ms,
58    P2sh,
59    P2wshV0,
60    OpReturn(OpReturnFlavor),
61    P2tr,
62    P2a,
63    Unknown,
64}
65
66#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)]
67pub enum OpReturnFlavor {
68    Unspecified,
69    WitnessCommitment,
70    Omni,
71    /// Stacks version 2 blockcommit. OP_RETURN start with `X2[`.
72    /// https://forum.stacks.org/t/op-return-outputs/12000
73    StacksBlockCommit,
74    Len1Byte,
75    Len20Byte,
76    Len80Byte,
77    Bip47PaymentCode,
78    /// A Rootstock (https://rootstock.io/) coinbase OP_RETURN marker.
79    /// Documented on https://dev.rootstock.io/node-operators/merged-mining/getting-started/
80    RSKBlock,
81    /// A CoreDao (https://coredao.org/) coinbase OP_RETURN marker.
82    /// Documented on https://github.com/coredao-org/docs/blob/main/docs/become-a-delegator/delegators/delegating-hash.md#implementation
83    CoreDao,
84    /// A ExSat (https://exsat.network/) coinbase OP_RETURN marker.
85    /// Documented on https://docs.exsat.network/guides-of-data-consensus/others/operation-references/synchronizer-operations/synchronizer-registration#register-on-chain-via-op_return
86    ExSat,
87    /// A HathorNetwork (https://hathor.network/) coinbase OP_RETURN marker.
88    /// Documented on https://github.com/HathorNetwork/rfcs/blob/master/text/0006-merged-mining-with-bitcoin.md
89    HathorNetwork,
90    /// A Ordinals Runestone
91    /// Documented on https://docs.ordinals.com/runes.html
92    Runestone,
93}
94
95impl fmt::Display for OpReturnFlavor {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        match self {
98            OpReturnFlavor::Unspecified => write!(f, "OP_RETURN"),
99            OpReturnFlavor::WitnessCommitment => write!(f, "Witness Commitment"),
100            OpReturnFlavor::Omni => write!(f, "OP_RETURN (OmniLayer)"),
101            OpReturnFlavor::StacksBlockCommit => write!(f, "OP_RETURN (Stacks v2 blockcommit)"),
102            OpReturnFlavor::Len1Byte => write!(f, "OP_RETURN (0 byte)"),
103            OpReturnFlavor::Len20Byte => write!(f, "OP_RETURN (20 byte)"),
104            OpReturnFlavor::Len80Byte => write!(f, "OP_RETURN (80 byte)"),
105            OpReturnFlavor::Bip47PaymentCode => write!(f, "OP_RETURN (BIP 47 Payment Code)"),
106            OpReturnFlavor::RSKBlock => write!(f, "OP_RETURN (Rootstock merge mining info)"),
107            OpReturnFlavor::CoreDao => write!(f, "OP_RETURN (CoreDao delegation info)"),
108            OpReturnFlavor::ExSat => write!(f, "OP_RETURN (ExSat info)"),
109            OpReturnFlavor::HathorNetwork => write!(f, "OP_RETURN (HatorNetwork aux_block_hash)"),
110            OpReturnFlavor::Runestone => write!(f, "OP_RETURN (Runestone)"),
111        }
112    }
113}
114
115impl fmt::Display for OutputType {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        match self {
118            OutputType::P2pk => write!(f, "P2PK"),
119            OutputType::P2pkh => write!(f, "P2PKH"),
120            OutputType::P2wpkhV0 => write!(f, "P2WPKH v0"),
121            OutputType::P2ms => write!(f, "P2MS"),
122            OutputType::P2sh => write!(f, "P2SH"),
123            OutputType::P2wshV0 => write!(f, "P2WSH v0"),
124            OutputType::OpReturn(flavor) => write!(f, "{}", flavor),
125            OutputType::P2tr => write!(f, "P2TR"),
126            OutputType::P2a => write!(f, "P2A"),
127            OutputType::Unknown => write!(f, "UNKNOWN"),
128        }
129    }
130}
131
132pub trait OutputTypeDetection {
133    fn get_type(&self) -> OutputType;
134
135    fn is_p2ms(&self) -> bool;
136    fn is_p2tr(&self) -> bool;
137    fn is_p2a(&self) -> bool;
138
139    // OP_RETURN flavor detection
140    fn is_witness_commitment(&self) -> bool;
141    fn is_opreturn_omni(&self) -> bool;
142    fn is_opreturn_stacks_blockcommit(&self) -> bool;
143    fn is_opreturn_with_len(&self, length: usize) -> bool;
144    fn is_opreturn_bip47_payment_code(&self) -> bool;
145    fn is_opreturn_rsk_block(&self) -> bool;
146    fn is_opreturn_coredao(&self) -> bool;
147    fn is_opreturn_exsat(&self) -> bool;
148    fn is_opreturn_hathor(&self) -> bool;
149    fn is_opreturn_runestone(&self) -> bool;
150}
151
152impl OutputTypeDetection for TxOut {
153    fn get_type(&self) -> OutputType {
154        if self.script_pubkey.is_p2pkh() {
155            OutputType::P2pkh
156        } else if self.script_pubkey.is_p2sh() {
157            OutputType::P2sh
158        } else if self.script_pubkey.is_p2wpkh() {
159            OutputType::P2wpkhV0
160        } else if self.script_pubkey.is_p2wsh() {
161            OutputType::P2wshV0
162        } else if self.is_p2tr() {
163            OutputType::P2tr
164        } else if self.is_p2a() {
165            OutputType::P2a
166        } else if self.script_pubkey.is_op_return() {
167            if self.is_witness_commitment() {
168                return OutputType::OpReturn(OpReturnFlavor::WitnessCommitment);
169            } else if self.is_opreturn_omni() {
170                return OutputType::OpReturn(OpReturnFlavor::Omni);
171            } else if self.is_opreturn_stacks_blockcommit() {
172                return OutputType::OpReturn(OpReturnFlavor::StacksBlockCommit);
173            } else if self.is_opreturn_bip47_payment_code() {
174                return OutputType::OpReturn(OpReturnFlavor::Bip47PaymentCode);
175            } else if self.is_opreturn_rsk_block() {
176                return OutputType::OpReturn(OpReturnFlavor::RSKBlock);
177            } else if self.is_opreturn_coredao() {
178                return OutputType::OpReturn(OpReturnFlavor::CoreDao);
179            } else if self.is_opreturn_exsat() {
180                return OutputType::OpReturn(OpReturnFlavor::ExSat);
181            } else if self.is_opreturn_hathor() {
182                return OutputType::OpReturn(OpReturnFlavor::HathorNetwork);
183            } else if self.is_opreturn_runestone() {
184                return OutputType::OpReturn(OpReturnFlavor::Runestone);
185            } else if self.is_opreturn_with_len(1) {
186                return OutputType::OpReturn(OpReturnFlavor::Len1Byte);
187            } else if self.is_opreturn_with_len(20) {
188                return OutputType::OpReturn(OpReturnFlavor::Len20Byte);
189            // catch-all for 80 byte OP_RETURNs. Inlcude known flavors before this one
190            } else if self.is_opreturn_with_len(80) {
191                return OutputType::OpReturn(OpReturnFlavor::Len80Byte);
192            }
193            OutputType::OpReturn(OpReturnFlavor::Unspecified)
194        } else if self.script_pubkey.is_p2pk() {
195            OutputType::P2pk
196        } else if self.is_p2ms() {
197            OutputType::P2ms
198        } else {
199            OutputType::Unknown
200        }
201    }
202
203    /// Checks if an output pays to a P2MS script.
204    ///
205    /// A P2MS output has a standard OP_CHECKMULTSIG template as usually seen in
206    /// e.g. P2SH redeemscripts as script_pubkey. N and M (n-of-m) can't be
207    /// bigger than 3 and m must be bigger than or equal to n;
208    /// `script_pubkey: [ <OP_PUSHNUM_N>   M * <pubkey>   <OP_PUSHNUM_M> <OP_CHECKMULTISIG> ]`
209    fn is_p2ms(&self) -> bool {
210        if let Ok(Some(n_of_m)) = self.script_pubkey.get_opcheckmultisig_n_m() {
211            let n = n_of_m.0;
212            let m = n_of_m.1;
213            if n <= 3 && m <= 3 && m >= n {
214                return true;
215            }
216        }
217        false
218    }
219
220    /// Checks if an output pays to a P2TR script.
221    ///
222    /// A P2TR output pushes the witness version 1 followed by a 32-byte schnorr-pubkey
223    /// `script_pubkey: [ OP_PUSHNUM_1  <32-byte pubkey> ]`
224    fn is_p2tr(&self) -> bool {
225        let script_pubkey_bytes = self.script_pubkey.as_bytes();
226        if script_pubkey_bytes.len() == 34
227            && script_pubkey_bytes[0] == opcodes::OP_PUSHNUM_1.to_u8()
228            && script_pubkey_bytes[1] == opcodes::OP_PUSHBYTES_32.to_u8()
229        {
230            return true;
231        }
232        false
233    }
234
235    /// Checks if an output pays to a P2A script.
236    ///
237    /// A P2A output pushes the witness version 1 followed by the 2 bytes hex-encoded as 4e73
238    /// `script_pubkey: [ OP_PUSHNUM_1  <4e73> ]`
239    fn is_p2a(&self) -> bool {
240        let script_pubkey_bytes = self.script_pubkey.as_bytes();
241        script_pubkey_bytes.len() == 4
242            && script_pubkey_bytes[0] == opcodes::OP_PUSHNUM_1.to_u8()
243            && script_pubkey_bytes[1] == opcodes::OP_PUSHBYTES_2.to_u8()
244            && script_pubkey_bytes[2] == 0x4eu8
245            && script_pubkey_bytes[3] == 0x73u8
246    }
247
248    /// Checks if an output is a OP_RETURN output meeting the requirements for an wittness commitment
249    /// as found in Coinbase transactions.
250    ///
251    /// A witness commitment is atleast 38 bytes long and starts with `6a24aa21a9ed`. More details
252    /// can be found in [BIP-141](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#commitment-structure).
253    fn is_witness_commitment(&self) -> bool {
254        let script_pubkey_bytes = self.script_pubkey.as_bytes();
255        if script_pubkey_bytes.len() >= 38
256            && script_pubkey_bytes[0] == 0x6A
257            && script_pubkey_bytes[1] == 0x24
258            && script_pubkey_bytes[2] == 0xAA
259            && script_pubkey_bytes[3] == 0x21
260            && script_pubkey_bytes[4] == 0xA9
261            && script_pubkey_bytes[5] == 0xED
262        {
263            return true;
264        }
265        false
266    }
267
268    /// Checks if an output is a OP_RETURN output meeting the requirements for a OmniLayer transaction.
269    ///
270    /// The data in OmniLayer transactions starts with the String 'omni' which is 6f 6d 6e 69 in hex.
271    fn is_opreturn_omni(&self) -> bool {
272        let script_pubkey_bytes = self.script_pubkey.as_bytes();
273        if script_pubkey_bytes.len() > 6 && script_pubkey_bytes[0] == 0x6A &&
274                // -- leaving this out as its not clear if all omni op_returns have the same length
275                // script_pubkey_bytes[1] == 0x14 &&
276                script_pubkey_bytes[2] == 0x6f &&
277                script_pubkey_bytes[3] == 0x6d &&
278                script_pubkey_bytes[4] == 0x6e &&
279                script_pubkey_bytes[5] == 0x69
280        {
281            return true;
282        }
283        false
284    }
285
286    /// Checks if an output is a OP_RETURN output meeting the requirements
287    /// for a Stacks blockcommit.
288    ///
289    /// The script_pubkey of a Stacks OP_RETURN block_commit pushes 80 bytes
290    /// with 'OP_PUSHDATA1 80'. These 80 bytes start with the string 'X2'
291    /// which is 0x58 0x32 in hex followed a '[' (0x5b).
292    /// https://forum.stacks.org/t/op-return-outputs/12000
293    fn is_opreturn_stacks_blockcommit(&self) -> bool {
294        let script_pubkey_bytes = self.script_pubkey.as_bytes();
295        if script_pubkey_bytes.len() == 83
296            && script_pubkey_bytes[0] == 0x6A
297            && script_pubkey_bytes[1] == 0x4C
298            && script_pubkey_bytes[2] == 0x50
299            && script_pubkey_bytes[3] == 0x58
300            && script_pubkey_bytes[4] == 0x32
301            && script_pubkey_bytes[5] == 0x5b
302        {
303            return true;
304        }
305        false
306    }
307
308    /// Checks if an output is a OP_RETURN output meeting the requirements
309    /// for a resuable payment code
310    ///
311    /// A payment code notification transaction contains an OP_RETURN output
312    /// with 80-byte payload. The script pubkey is of the structure
313    /// OP_RETURN(0x6a) OP_PUSHDATA1(0x4c) 80-bytes(0x50)
314    ///
315    fn is_opreturn_bip47_payment_code(&self) -> bool {
316        let script_pubkey_bytes = self.script_pubkey.as_bytes();
317        if script_pubkey_bytes.len() != 83 {
318            return false;
319        }
320
321        if !(script_pubkey_bytes[0] == opcodes::OP_RETURN.to_u8()
322            && script_pubkey_bytes[1] == opcodes::OP_PUSHDATA1.to_u8()
323            && script_pubkey_bytes[2] == 80)
324        {
325            return false;
326        }
327
328        // Examine the payload
329        let payload = &script_pubkey_bytes[3..];
330        // Byte 0 - version should be 0x01 or 0x02
331        if payload[0] != 0x01 && payload[0] != 0x02 {
332            return false;
333        }
334        // Byte 2 - Sign should be 0x02 or 0x03
335        let sign_byte = payload[2];
336        if sign_byte != 0x02 && sign_byte != 0x03 {
337            return false;
338        }
339        // Bytes 3-34 - x value, must be a member of the secp256k1 group
340        // However, we can't test this as the x value is blinded / masked. Since
341        // we aren't the receiver of the notifaction, we can't unblind/unmask the notification.
342        // However, it shouldn't be all zeros.
343        if payload[3..35].iter().all(|&b| b == 0) {
344            return false;
345        }
346
347        // Bytes 35-66 - chain-code, must not be all zeros
348        let chain_code = &payload[35..67];
349        if chain_code.iter().all(|&b| b == 0) {
350            return false;
351        }
352        // Bytes 67-79 - reserved for future expansion, zero-filled
353        let reserved_bytes = &payload[67..80];
354        if !reserved_bytes.iter().all(|&b| b == 0) {
355            return false;
356        }
357        true
358    }
359
360    /// Checks if an output is an OP_RETURN output meeting the requirements
361    /// for a RSK merge mining information output in a coinbase transaction.
362    ///
363    /// Format: OP_RETURN [length 0x29] [RSKBLOCK: (0x52534b424c4f434b3a)] [RskBlockInfo]
364    fn is_opreturn_rsk_block(&self) -> bool {
365        let script_pubkey_bytes = self.script_pubkey.as_bytes();
366        script_pubkey_bytes.len() == 43
367            && script_pubkey_bytes[0] == 0x6A
368            && script_pubkey_bytes[1] == 0x29 // length (OP_PUSHBYTES_41)
369            && script_pubkey_bytes[2] == b'R'
370            && script_pubkey_bytes[3] == b'S'
371            && script_pubkey_bytes[4] == b'K'
372            && script_pubkey_bytes[5] == b'B'
373            && script_pubkey_bytes[6] == b'L'
374            && script_pubkey_bytes[7] == b'O'
375            && script_pubkey_bytes[8] == b'C'
376            && script_pubkey_bytes[9] == b'K'
377            && script_pubkey_bytes[10] == b':'
378            // F2Pool is using OP_PUSHDATA1 instead of the OP_PUSHBYTES_41
379            || script_pubkey_bytes.len() == 44
380            && script_pubkey_bytes[0] == 0x6A
381            && script_pubkey_bytes[1] == 0x4c // OP_PUSHDATA1 (F2Pool is doing this..)
382            && script_pubkey_bytes[2] == 0x29 // length
383            && script_pubkey_bytes[3] == b'R'
384            && script_pubkey_bytes[4] == b'S'
385            && script_pubkey_bytes[5] == b'K'
386            && script_pubkey_bytes[6] == b'B'
387            && script_pubkey_bytes[7] == b'L'
388            && script_pubkey_bytes[8] == b'O'
389            && script_pubkey_bytes[9] == b'C'
390            && script_pubkey_bytes[10] == b'K'
391            && script_pubkey_bytes[11] == b':'
392    }
393
394    /// Checks if an output is an OP_RETURN output meeting the requirements
395    /// for a CORE dao output in a coinbase transaction.
396    ///
397    /// Format: OP_RETURN [length 0x2d] [CORE (0x434f5245)] [Version 0x01] [Delegate Information]
398    fn is_opreturn_coredao(&self) -> bool {
399        let script_pubkey_bytes = self.script_pubkey.as_bytes();
400        script_pubkey_bytes.len() == 47
401            && script_pubkey_bytes[0] == 0x6A
402            && script_pubkey_bytes[1] == 0x2d // length
403            && script_pubkey_bytes[2] == b'C'
404            && script_pubkey_bytes[3] == b'O'
405            && script_pubkey_bytes[4] == b'R'
406            && script_pubkey_bytes[5] == b'E'
407            && script_pubkey_bytes[6] == 0x01 // version
408    }
409
410    /// Checks if an output is an OP_RETURN output meeting the requirements
411    /// for a CORE dao output in a coinbase transaction.
412    ///
413    /// Format: OP_RETURN [length 0x12] [EXSAT (0x4558534154)] [Version 0x01] [synchronizer account]
414    fn is_opreturn_exsat(&self) -> bool {
415        let script_pubkey_bytes = self.script_pubkey.as_bytes();
416        script_pubkey_bytes.len() > 8
417            && script_pubkey_bytes[0] == 0x6A
418            // script_pubkey_bytes[1] is the length, but this might be different for each pool
419            && script_pubkey_bytes[2] == b'E'
420            && script_pubkey_bytes[3] == b'X'
421            && script_pubkey_bytes[4] == b'S'
422            && script_pubkey_bytes[5] == b'A'
423            && script_pubkey_bytes[6] == b'T'
424            && script_pubkey_bytes[7] == 0x01 // version
425    }
426
427    /// Checks if an output is an OP_RETURN output meeting the requirements
428    /// for a HathorNetwork output in a coinbase transaction.
429    ///
430    /// Format: OP_RETURN [length 0x12] [Hath (48 61 74 68)] [aux_block_hash]
431    fn is_opreturn_hathor(&self) -> bool {
432        let script_pubkey_bytes = self.script_pubkey.as_bytes();
433        script_pubkey_bytes.len() == 38
434            && script_pubkey_bytes[0] == 0x6A
435            && script_pubkey_bytes[1] == 0x24 // length
436            && script_pubkey_bytes[2] == b'H'
437            && script_pubkey_bytes[3] == b'a'
438            && script_pubkey_bytes[4] == b't'
439            && script_pubkey_bytes[5] == b'h'
440    }
441
442    /// Checks if an output is an OP_RETURN output meeting the requirements
443    /// for a Runestone OP_RETURN output.
444    ///
445    /// Format: OP_RETURN OP_PUSHNUM_13 [OP_PUSHBYTES_X]
446    fn is_opreturn_runestone(&self) -> bool {
447        let script_pubkey_bytes = self.script_pubkey.as_bytes();
448        if script_pubkey_bytes.len() > 2
449            && script_pubkey_bytes[0] == opcodes::OP_RETURN.to_u8()
450            && script_pubkey_bytes[1] == opcodes::OP_PUSHNUM_13.to_u8()
451        {
452            for (index, inst_result) in self.script_pubkey.instructions().enumerate() {
453                if let Ok(inst) = inst_result {
454                    match index {
455                        0 => (), // we already checked that this is an OP_RETURN
456                        1 => (), // we already checked that this is an OP_PUSHNUM_13
457                        _ => {
458                            // all others need to be data pushes
459                            match inst {
460                                script::Instruction::Op(_) => {
461                                    return false;
462                                }
463                                script::Instruction::PushBytes(_) => (),
464                            }
465                        }
466                    }
467                } else {
468                    return false;
469                }
470            }
471            return true;
472        }
473        false
474    }
475
476    /// Compares the data length of an OP_RETURN output with the given `data_length`. Returns
477    /// true if equal.
478    ///
479    /// This assumes OP_RETURN use the minimal data push. That means for data shorter than
480    /// or equal to (<=) 75 bytes a OP_PUSHBYTES_X is used. For longer data a OP_PUSHDATA1
481    /// is used.
482    fn is_opreturn_with_len(&self, data_length: usize) -> bool {
483        const MIN_OPRETURN_LEN: usize = 1 + 1; // OP_RETURN OP_0
484        const MAX_OPRETURN_LEN: usize = 1 + 1 + 1 + 80; // OP_RETURN OP_PUSHDATA1 data-length [80 btyes]
485        const MAX_OPPUSHBYTES_LEN: usize = 1 + 1 + 75; // OP_RETURN OP_PUSHBYTES_75 [75 bytes]
486
487        if self.script_pubkey.len() < MIN_OPRETURN_LEN
488            || self.script_pubkey.len() > MAX_OPRETURN_LEN
489        {
490            return false;
491        }
492
493        if !self.script_pubkey.as_bytes()[0] == 0x6A {
494            return false;
495        }
496
497        if self.script_pubkey.len() <= MAX_OPPUSHBYTES_LEN {
498            return self.script_pubkey.len() - 1 - 1 == data_length;
499        }
500
501        if self.script_pubkey.len() > MAX_OPPUSHBYTES_LEN {
502            return self.script_pubkey.len() - 1 - 1 - 1 == data_length;
503        }
504
505        false
506    }
507}
508
509pub trait OutputSigops {
510    fn sigops(&self) -> usize;
511}
512
513impl OutputSigops for TxOut {
514    fn sigops(&self) -> usize {
515        const SIGOPS_SCALE_FACTOR: usize = 4;
516
517        // in P2TR scripts, no sigops are counted
518        if self.is_p2tr() {
519            return 0;
520        }
521
522        // for example, for P2MS script_pubkeys (OP_CHECKMUTLISIG)
523        SIGOPS_SCALE_FACTOR * self.script_pubkey.count_sigops_legacy()
524    }
525}
526
527#[cfg(test)]
528mod tests {
529    use super::{OpReturnFlavor, OutputType, OutputTypeDetection};
530    use bitcoin::Transaction;
531
532    #[test]
533    fn output_type_detection_p2ms() {
534        // mainnet ac1d9ed701af32ea52fabd0834acfb1ba4e3584cf0553551f1b61b3d7fb05ee7
535        let raw_tx = hex::decode("0100000001ffc0d6d6b592cd2b4160300a278ea5e250b5055b5536dcfb2da5dcc46022765a00000000694630430220575ddd235a989befbf98f43b008666e56af07be89e47e09d18690c75846fb587021f00830605aa09febc51132001e0dbcad860e54d4657b55aaf961b527a935b8a01210281feb90c058c3436f8bc361930ae99fcfb530a699cdad141d7244bfcad521a1fffffffff03204e0000000000002551210281feb90c058c3436f8bc361930ae99fcfb530a699cdad141d7244bfcad521a1f51ae204e0000000000001976a914a988f8039a203cf86136e0d32b9d77eafa5a6bef88ac46f4d501000000001976a914161d7a3d0ee15c793ab300433192f949d8f3566588ac00000000").unwrap();
536        let tx: Transaction = bitcoin::consensus::deserialize(&raw_tx).unwrap();
537        let out0 = &tx.output[0];
538        assert!(out0.is_p2ms());
539        assert_eq!(out0.get_type(), OutputType::P2ms);
540    }
541
542    #[test]
543    fn output_type_detection_p2ms2() {
544        // mainnet d5a02fd4d7e3cf5ca02d2a4c02c8124ba00907eb85801dddfe984428714e3946
545        let raw_tx = hex::decode("010000000150db0324e3733b7d4915a42acf51d4cd95629fb5a659da68d01292e3152abf7d010000006b4830450221008c24014a99a87736aa47a773d738cbbcd60dadfbb2aa294d4f00cda1e4dae66f022076fa9be5d50eecd2e8e1dbe364b158f2d5df049cbcd8cc759970dd23fab41423012102dc6546ba58b9bc26365357a428516d48c9bbc230dd6fc72912654aaad460ef19ffffffff02781e00000000000069512102d7f69a1fc373a72468ae84634d9949fdeab4d1c903c6f23a3465f79c889342a421028836687b0c942c94801ce11b2601cbb1e900e6544ef28369e69977195794d47b2102dc6546ba58b9bc26365357a428516d48c9bbc230dd6fc72912654aaad460ef1953ae3c660d00000000001976a914e4e9d188d9806fef75904225f370009aa4103a9d88ac00000000").unwrap();
546        let tx: Transaction = bitcoin::consensus::deserialize(&raw_tx).unwrap();
547        let out0 = &tx.output[0];
548        assert!(out0.is_p2ms());
549        assert_eq!(out0.get_type(), OutputType::P2ms);
550    }
551
552    #[test]
553    fn output_type_detection_p2tr() {
554        // signet 9f3d438ab92e86bd86c64749416df8d3a48bcef97b7c32ccefc2ec4f02caac74
555        let raw_tx = hex::decode("020000000001029dac93ef467e6035bf641f4076b2a8ac6a4368e93d6c7dc8dcfb38b9bed7da840100000000feffffffbe415b1058e5294f30ccc12332d00636aa8874448141a0446737a1ffc7e6f5060100000000feffffff0410270000000000002251207a61c588fd357d8ed58f624fa7f97a651d1ac00b53b055e9b852507dd319a3d41027000000000000225120acd385f4c428f2ce97644de474a579a77435f40b6161d1c1875f48f2626fccde1e0e1e00000000001600147f611a8cfa64617c05c1b44341b4e469631371c3102700000000000022512070271d98a521d0e4102ebdbc40f3e553666fb5b85c8c3d2709138568c6c90b230247304402202945170a29517bf8773f6a741e587d87b3f4ec6e7348fae8443d45bc5a30f82402200207fcdb3369e55060725bdc2343236271e2dddb62a3077577a85e6f79d22404012103f682085f03c8a27288258933370b4cef8badb4c8a0e8bbfa31d78a450dffd543024730440220711d103aaed2122a8ddef8fd5523ccc7e3748382804dddccdf46e4755c2d1e9f022060e0564f3bf307d5c2128a4bcfd521c33a2bf1c3590cfc0d4fa7c8e02af26ab4012103f682085f03c8a27288258933370b4cef8badb4c8a0e8bbfa31d78a450dffd54300000000").unwrap();
556        let tx: Transaction = bitcoin::consensus::deserialize(&raw_tx).unwrap();
557        let out0 = &tx.output[0];
558        let out1 = &tx.output[1];
559        let out3 = &tx.output[3];
560        assert!(out0.is_p2tr());
561        assert!(out1.is_p2tr());
562        assert!(out3.is_p2tr());
563        assert_eq!(out0.get_type(), OutputType::P2tr);
564        assert_eq!(out1.get_type(), OutputType::P2tr);
565        assert_eq!(out3.get_type(), OutputType::P2tr);
566    }
567
568    #[test]
569    fn output_type_detection_witness_commitment() {
570        // mainnet 2a352a3473385dc9f7b79967aba7aeaafa5f7994d5031ac5b43d168b7566c092
571        // coinbase of 00000000000000000009a77c962fabb1b12c54dc1e978080df0155381f97fb5f (674485)
572        let raw_tx = hex::decode("010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff5403b54a0a41d8134fa906ec3741d8134fa83cdae32f3154486173682ffabe6d6d022644aec8e65c41869919439905bd5a6546045825016e8ab843121b089ee79f8000000000000000bf00d52a508f000000000000ffffffff02114cf82c000000001976a9142220867b1e79c403fafe339a809a65ed01cb697988ac0000000000000000266a24aa21a9ed0a8154218fc45bc35f274fafd2490849f8b88f75b3cd63b95096b2a861018f300120000000000000000000000000000000000000000000000000000000000000000000000000").unwrap();
573        let tx: Transaction = bitcoin::consensus::deserialize(&raw_tx).unwrap();
574        let out1 = &tx.output[1];
575        assert!(out1.is_witness_commitment());
576        assert_eq!(
577            out1.get_type(),
578            OutputType::OpReturn(OpReturnFlavor::WitnessCommitment)
579        );
580    }
581
582    #[test]
583    fn output_type_detection_opreturn_omni() {
584        // mainnet 60d37517d7f6140a560c1aa3961f8c00ac663c73620825e889fb9e19b62d3ad7
585        let raw_tx = hex::decode("0100000001305fe5e034c625b571638cbce7837970ca1e84830e794c77de6582ea419bcfb5000000006a47304402206d3f22eff2a26e7f6e2c6e1ccfafa0ae174b5c265353b19d8ee3510316de40ed02201a49d55eaad2f78e7a0358e6ec0f9230ace39451c883845ad107af8822e1ccef0121030651e1d15ae9a284ffd712885529d3344db3700be756e6c22c56a6c1b57d359dffffffff03f64e0600000000001976a914b64513c1f1b889a556463243cca9c26ee626b9a088ac22020000000000001976a914c958135faa72449c106564acba252cfbc3a35ca688ac0000000000000000166a146f6d6e69000000000000001f000002115728ef0000000000").unwrap();
586        let tx: Transaction = bitcoin::consensus::deserialize(&raw_tx).unwrap();
587        let out2 = &tx.output[2];
588        assert!(out2.is_opreturn_omni());
589        assert_eq!(out2.get_type(), OutputType::OpReturn(OpReturnFlavor::Omni));
590    }
591
592    #[test]
593    fn output_type_detection_opreturn_stacks_blockcommmit() {
594        // mainnet 04496abaffb19abf1390b8fc94a8e487eba640c8adc385cfc951637b963fc86a
595        let raw_tx = hex::decode("01000000018d9da5a2bc0789aa38bd9e2c1248a5ddaea8f00e097748ca80695f6333adcc04030000006a47304402200704d5f943227080b0c1b8668a37d819804a5c99a0bdda593b3dc167c32a39d402207d2bbd4384e369d671f0c46a54fc4fbfeb14724ab99ea695d1184d4f1074ad18012103fb3bc5bae4c088ca38a8c68bfe741f3b1cb62a067b69917908089a2082af31aefdffffff040000000000000000536a4c5058325b1352fba61836c82246b240fb64043b3e705f8975aa2062d886e247aaeee76ad26f14f91d22c38c8c2fd41ff85e4b22b0cf97b412c53f9aa0182bd6deb51c9567000a3596004b000a2f80012b03989f0400000000001976a914000000000000000000000000000000000000000088ac989f0400000000001976a914000000000000000000000000000000000000000088ac1132920b000000001976a9142c16c83270b688fa3ac46dc69cc01f6321bce41088ac00000000").unwrap();
596        let tx: Transaction = bitcoin::consensus::deserialize(&raw_tx).unwrap();
597        let out2 = &tx.output[0];
598        assert!(out2.is_opreturn_stacks_blockcommit());
599        assert_eq!(
600            out2.get_type(),
601            OutputType::OpReturn(OpReturnFlavor::StacksBlockCommit)
602        );
603    }
604
605    #[test]
606    fn output_type_detection_p2a() {
607        // mainnet 4752bdfc1041b46fb49cb551d35d06233bcb71ee3b6e7df9cb765db881f8104f
608        let rawtx = hex::decode("020000000001016352fb25843f7e3e20ac70119a9e46447d24257e0a250215402da5449764610f0100000000fdffffff02d0070000000000000451024e73581b00000000000022512044b35747ac9a995294839fdbefa823ae2d0cfbed950b72755499d9039ae739b501400cb9cb49f7790080d9601d1c24bebfd0668ef170145888f303f487ab5a9c4acdb511274f7b305faef35718af80c108df5ac51538f71f450ecda63c8fa952365200000000").unwrap();
609        let tx: Transaction = bitcoin::consensus::deserialize(&rawtx).unwrap();
610        let out0 = &tx.output[0];
611        let out1 = &tx.output[1];
612        assert!(out0.is_p2a());
613        assert!(out1.is_p2tr());
614        assert_eq!(out0.get_type(), OutputType::P2a);
615        assert_eq!(out1.get_type(), OutputType::P2tr);
616    }
617
618    #[test]
619    fn output_type_detection_bip47_failure() {
620        // mainnet e6a5226770651406459b65f31b46ba9f164f273e9a0b4d4dd58c4cd8dbc56d78
621        // This tx (and others) caused a problem with the inital implemenation of bip47 detection.
622        // See https://github.com/0xB10C/rawtx-rs/pull/30/files#r2029106483
623        // The transaction has a short OP_RETURN as output 0 we'd previously access an invalid
624        // index because the length wasn't correctly checked.
625        let txhex = "020000000001015792fbb130d7429483d7b30a90408f917883434c825b1a1ff06f416ac909f40d0300000000ffffffff030000000000000000036a01003c2d5000000000001976a914b7b5c111d77baf2881a1076e3224ff318b620fb888ac7bd80c0000000000220020eca0c4e30e7eafea1c3388ba7760de0484be8e548e6520bc69a7635e0f640fab0500483045022100b50e9df79b938f4dbc1af2b75635c5192a5b1819671b0f84c3707829ec138c7402206fd3413a5475602e1414ddb2dd1eed67360cf845312801ab145e8072079058b5014730440220774d3856a6d05fe0877ef117462cfe5e4812c2e3b53402b60b11743dd26ed90602206d1d212b8f3f2dd2e454d5c35a2a8017aa8674426f24097c569cb0049658850701473044022017af04b20a119352d06def462cef26942e92f0f2ef2f0216d5993930f83f0c34022035d2ccd624b63a0531cea9259be703c0aa9ff1c2cf3d576c4878310cfa3ae9ff01ad532102272aefe25b54a7f5fe991a68e21146c34ac9bcafd81682dc599d4a337ce2535c2102c33eb3a569e78f8cd53480128383e602ba06314700e83dfcb5da149132a6b3e52102f1ec97fcf92d60baa4baa9b35d6e61ea06337db698919125e590af67cc30ae192102f2416ebb7b55a691ef094056d989b30d97f4600db03e6a6f9339e3034977b54f21039be0c58b27b79011b50a0ca2c73dd029ec909711345a61c3984630d1010dbe5355ae00000000";
626        let rawtx = hex::decode(txhex).unwrap();
627        let tx: Transaction = bitcoin::consensus::deserialize(&rawtx).unwrap();
628        let out = &tx.output[0];
629        assert!(!out.is_opreturn_bip47_payment_code());
630    }
631
632    #[test]
633    fn output_type_detection_bip47_payment_code() {
634        let testcases = vec![
635            // output, raw tx hex
636            // ---
637            // mainnet aa7bb5c839e6d513fdde4ef994995363f98a6d6cbe795663f432a39a38d71025 with BIP47
638            // output in output 0
639            (0, "02000000000101cdd51d9048b22420cd2af3538aa7ea71951b81b4dee3894cc20f5c13fe463f783500000000fdffffff040000000000000000536a4c50010003a4b1880f11b6de85617c0aa9a21a3073dbe5a2fa277aa8f626ba9cb95b3c9c025e36a6ec791cedb876a079a264a40fa42531aa60825ca1d243998f54dd8977450000000000000000000000000022020000000000001976a914f522819122c73c04068577724e2ee7d05a0965d888ac983a00000000000016001437808929e894e2c691bd705f802c203716ea11fc9bdb00000000000016001427f5f2387d9efc49fade31556e81c30f8fa666730247304402206661b9bba8f4bf116f9a53c36de4b4ba35501f0d46a1c36a76c0e792b1c422ef02206dc75e32ec9a47068a749d0e1fee455ee483568a78387b86c8b179d293230f90012103c6c004d651ae8428b33a7ba4222bc92e4a7631f5791cb286e49fb38e89a3e662806b0b00"),
640            // testnet cccd8cdb344df8e93e6e6c783688605c500e81f4cd130427c6f9f042446176af with BIP47
641            // output in output 0
642            // from https://medium.com/@ottosch/how-bip47-works-ee641cc14bf3
643            (0, "02000000000101ba7f679c0adb514945c9a4a00f73ecbb73f2e6116208e4ae314b9910c9f221020000000000ffffffff040000000000000000536a4c50010002b13b2911719409d704ecc69f74fa315a6cb20fdd6ee39bc9874667703d67b164927b0e88f89f3f8b963549eab2533b5d7ed481a3bea7e953b546b4e91b6f50d80000000000000000000000000022020000000000001976a914e335b83658f7565d19405117cbd2f85a743653ba88ac983a000000000000160014b1c8c8ba853483ac9faaa9445e5d2b64f6e4e5fed291200000000000160014b9623df59eda43987c3102cf8fc3935c0c651b2e0247304402201de79d8959e8d0420b9ca54932497aad6d068c0d7222c0a7ac5badab335169ff02206f67d38356d7238eb0a5a53cfe57bf7c0553a6bbe270199b969eba008fa1a35f01210281ae97b31eccbbfd83b3cbef1c0c6dc82cd13f34668fae266cf9dc87352144ef00000000"),
644            // testnet 0e2e4695a3c49272ef631426a9fd2dae6ec3a469e3a39a3db51aa476cd09de2e with BIP47
645            // output in output 0
646            // from https://medium.com/@ottosch/how-bip47-works-ee641cc14bf3
647            (0, "01000000000101a390284b399704a12c1d5c9b43181f0b984f2c17f619522675de80962546e5af0000000000ffffffff030000000000000000536a4c500100036c11d62dc5d47a03edf9027dc045406f81da1bf89e701c3ac691468ecb154661d7e65b9aa400701c44f396b627e8592bbb081764d92df98ebce9a8a3b9360dfc0000000000000000000000000022020000000000001976a9143431c401040f906b9eaea2d0fe2511688b4033c988ac1c470000000000001600147701a0aa820832428ef0133c7ba1b44b627ecd0f0247304402202ebad56e95f7dfa91c68c0e7a7e4e70e4b0eff674e903f1909ae3b03649ca25602206b6b31a17a231e90a035e10127e4cde84a36270d1e5e21ef11140078f65fac91012102a7a6edc3de4e35ab54f98b69bedace05cc649e0dc51c7dfdeabe0db42be32d7c00000000"),
648            // signet ee001f84787f48a53dc460d1aeba78da663e10b9aeee152270ff93404918b229 with BIP47
649            // output in output 0
650            (0, "020000000001012364df1e5c5b2a357e63183f7985bfd14e0be165c76bfb34d9067c63e6bfd19be400000000fdffffff030000000000000000536a4c500100029a516f23dcc89a26430b72baac2e6b3abded6cdf98e1a6b377f519e77b9facf5c2affa8263329f80d922378fbf63a62cf07cda0b67942bd00db44823c91f72070000000000000000000000000056fa05000000000016001418387b71aa4c8d241f5ae8848e79299a40bb5e6922020000000000001976a91465724c591ae40f62c0815a51522d60d2e319b45f88ac0247304402202a10fff3b0fc8cf275f54bd4e71fdc1fad6580fc442842c3a6da81e63dcfe75102203b96541ceef9e33cc9c459f028446e36300a83f958598b750b64d608478dffe0012102010012e1eb74242bdd7c9c43be974cdd1d8e301f3cd52823d28f100bc53b40becaad0300"),
651            // signet 1e40201df09c10cd42340756aebfe9e8614791ae144f4caf75f95ebd699f365a with BIP47
652            // output in output 0
653            (0, "02000000000101dca9b1082c0ebb7d3c56368c5edca4feb254c55bae4fca2bdf2a92f52a9ce5320200000000fdffffff030000000000000000536a4c50010002fc575207a99903a525022be54fb6c9872ab418ff766795a6437c80393a57861d078e55fe8dbc995d772703bbe6076143b05c5ee8e007e2bffedf87d618e3d76a0000000000000000000000000002e80500000000001600145f13323a3d6271d8ea28370cdeb2d3c07d172fdf22020000000000001976a9142cedadc4dc423f698cbdcac8ee9571b91c56f13f88ac0247304402201168bb51d7031ddeaf6fd95abe14b6641b2cdfcc38e3983ecc0ff19f249063d002200a2afd7afed9cae6888761808c1440370f48eaada67d008f1143e0dd96ec884f01210288263dfde13ab364938a44fa983c3f51f7c38cad01fdde7d0c94ade12deeb860caad0300"),
654            // signet 4d8dc61d66203a8ed76a9b70e4198d6678abc2f9e63dfa828a7a1a35be618582 with BIP47
655            // output in output 1
656            (1, "02000000000101866718b8560b53bdb878865e6c8c5ca76415d00a0d00cd9cf4b7b7154a81fdd90100000000fdffffff032cf10500000000001600149e1c68bf6d006ac3ffa59cd75efab7888ee862390000000000000000536a4c50010002c1addde6d69e908195bc0e43c4fd16ba0f7c1e4181a03f7c04d5a21dd8c378af6c0c378f068498915452cf7005b0cd5f33f228971c345038271bf974cec731730000000000000000000000000022020000000000001976a91464fa781a0d4389518af519efe591dd8f1ca63e2488ac0247304402204458c82463809b368cf705f1ade063bd46174d2f679e2e9ebc4e54fe7f37e6ac02204c1c7f6e1b7541c8a6eb3954b4eca694a9a4fd8da9712c1348a34b951c9506e1012103ce30da189fed4a7885ec579feacc008a2df738d5592ecbd3ac840fd973675063caad0300"),
657            // signet d9fd814a15b7b7f49ccd000d0ad01564a75c8c6c5e8678b8bd530b56b8186786 with BIP47
658            // output in output 0
659            (0, "020000000001018d456318a27c4b421fbbcc8f9714dec7f99c21fa19677c9e7478e01ee041f89a0200000000fdffffff030000000000000000536a4c5001000202d0f2d07130dace07e45b57e5f6fb6a5f1c86e5aa7d6dd4d8aa4f30337f1fe3827dc9098a6650fc8b8e81a48c4c9c5b04dca2f77e8a98241d319842ab2517f1000000000000000000000000003af405000000000016001431afd3b96454958646eaf28726ab8f9916267b4622020000000000001976a914d86b062ac7d8d682149d71ab884dae54b4b816f188ac0247304402200a7fa0ee9048147bcdf043e93c6913e7b3620edc4eaa570d7eaf764c98c775a502203664108b6f06b237fb41911390b94267f2177b90319c3ecafdf2fcb87d8833810121026d02c3435f4e13f348c8ca852995b756ee34539cdeb3da39c8d87ec163ef2bcccaad0300"),
660            // signet 9af841e01ee078749e7c6719fa219cf9c7de14978fccbb1f424b7ca21863458d with BIP47
661            // output in output 0
662            (0, "0200000000010129b218494093ff702215eeaeb9103e66da78baaed160c43da5487f78841f00ee0100000000fdffffff030000000000000000536a4c50010002cdd82e5a14af597148a04cc15b0209c67b27ade0f763de4af9c8277a80725b6f22f46db3d2815e080fac413c758cd26ad54196e42744ebde36a159b0179c55b30000000000000000000000000022020000000000001976a914f85cc4a7022a3f2b91eccd5b426d68ba40be9b8988ac48f7050000000000160014043f2fd2079a9d7a2e0fec2e25a9667cd0f79c380247304402206fb17a155cfce2a1c0c55bdafdc7935f538bea5ff74cfacd4ee998dbb51ffae20220571dce283d7288fd759aed0bcd44aeeee897a588434c62f0952e0f18cf9c6a46012103c961e88949b6ed625a9bacdd104d190dd2b0fbcc94f97afdfe8ddfad25dca9c0caad0300"),
663            // signet fb5b842cae4ceec77dd1a54d901a53c1d27d3ae9311112af3b6affba4634aabb with BIP47
664            // output in output 1
665            (1, "02000000000101828561be351a7a8a82fa3de6f9c2ab78668d19e4709b6ad78e3a20661dc68d4d0000000000fdffffff031eee05000000000016001444267dab545a75dfe6af8178e20b9cad6cd8e2ff0000000000000000536a4c500100020ddf6d3bb46d48e5211db2760ef7438dc419654dbe695d58c948b8b1f5ea7353cfdced7eab6f6a80fa14bc74d91e058b0c1d120a605c36ad5f3246ffa4d89b580000000000000000000000000022020000000000001976a914e705081188cdd43527fbea2150db9b0189970e0c88ac0247304402204261a04f3b8651612bb94cb204a5f90139f196a87aa166ebf92a26c75d3c18a402204422255c8222527ad445e2f063175dfc274b98b14a97b065b33e5c4cd6c74de9012103b1a3d3e1277b64dfe9e05f3a2421c1a53a3bd41aa2f8e69fd24985bf105a355ecaad0300"),
666            // signet 32e59c2af5922adf2bca4fae5bc554b2fea4dc5e8c36563c7dbb0e2c08b1a9dc with BIP47
667            // output in output 1
668            (1, "02000000000101bbaa3446baff6a3baf121131e93a7dd2c1531a904da5d17dc7ee4cae2c845bfb0000000000fdffffff0322020000000000001976a914ffba4d90d0be82b01c490e08697b9ffc673310e388ac0000000000000000536a4c50010002d0e5ecefdcad186cee3d4bf2a5c68a0bd8cd463a4ee644e2f21023cdf0e4a68dd79604907773547b8e0f7acf57c55fdcf7da42b1ce3761ea333498a4904824a00000000000000000000000000010eb05000000000016001411732aa696294ca454a633e909d5544367a6f9250247304402202ae0ceb7a1f898e99bfb79c5d60143ea4505dae0bb225c0fdb3481189c0c4d050220521b0a2e98ad682d151155b16d7f97ba1458e72741359d61e093756c02d96bde012102f1f400b3976309cc5bf1c81b09fd8cb59e4c49d5118c2d8ad5e3036d25fe1314caad0300"),
669            // mainnet 9414f1681fb1255bd168a806254321a837008dd4480c02226063183deb100204 with BIP47
670            // output in output 1
671            // from test vectors https://gist.github.com/SamouraiDev/6aad669604c5930864bd
672            (1, "010000000186f411ab1c8e70ae8a0795ab7a6757aea6e4d5ae1826fc7b8f00c597d500609c010000006b483045022100ac8c6dbc482c79e86c18928a8b364923c774bfdbd852059f6b3778f2319b59a7022029d7cc5724e2f41ab1fcfc0ba5a0d4f57ca76f72f19530ba97c860c70a6bf0a801210272d83d8a1fa323feab1c085157a0791b46eba34afb8bfbfaeb3a3fcc3f2c9ad8ffffffff0210270000000000001976a9148066a8e7ee82e5c5b9b7dc1765038340dc5420a988ac1027000000000000536a4c50010002063e4eb95e62791b06c50e1a3a942e1ecaaa9afbbeb324d16ae6821e091611fa96c0cf048f607fe51a0327f5e2528979311c78cb2de0d682c61e1180fc3d543b0000000000000000000000000000000000")
673        ];
674        for (i, (output_index, txhex)) in testcases.iter().enumerate() {
675            println!("Testing case {}", i);
676            let rawtx = hex::decode(txhex).unwrap();
677            let tx: Transaction = bitcoin::consensus::deserialize(&rawtx).unwrap();
678            let out = &tx.output[*output_index];
679            assert!(out.is_opreturn_bip47_payment_code());
680            assert_eq!(
681                out.get_type(),
682                OutputType::OpReturn(OpReturnFlavor::Bip47PaymentCode)
683            );
684        }
685    }
686
687    #[test]
688    fn output_type_detection_opreturn_coinbase() {
689        const NA: usize = usize::MAX;
690        let testcases = vec![
691            // rsk output, coredao output, exsat output, raw tx hex
692            // ---
693            // mainnet coinbase of block 890680 da7bc085ce387c50c8b280934a93d0b05ec987fa06345fa0a82c195f4c030916
694            (5, 3, 4, NA, "010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff580338970d1b4d696e656420627920416e74506f6f6c3937304d0043010b007fe4fabe6d6d0b528b660c38cd611ff89d34e03ae3d92d7f9ac6bec6599422e8fcb18978507f08000000000000000000936100aeb83100000000ffffffff06220200000000000017a91442402a28dd61f2718a4b27ae72a4791d5bbdade7874b31eb120000000017a9145249bdf2c131d43995cff42e8feee293f79297a8870000000000000000266a24aa21a9ed2b33a26f7157d656c9fd7aab093e4a63a3c463ecf9294156002e6a134ac22f7200000000000000002f6a2d434f52450142fdeae88682a965939fee9b7b2bd5b99694ff644e3ecda72cb7961caa4b541b1e322bcfe0b5a0300000000000000000146a12455853415401000d130f0e0e0b041f12001300000000000000002b6a2952534b424c4f434b3a359c5f6d8523559163efd9f7884d3ef37b5953b334cd79efab0af412007111430120000000000000000000000000000000000000000000000000000000000000000000000000"),
695            // mainnet coinbase of block 890688 f5adbbcf21bb598e260e5ea9ce40eba0c39747fe1347e1f3dd3072f19f0232d4
696            (5, 3, 4, NA, "010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff590340970d1c4d696e656420627920416e74506f6f6c3935394d006b001d88b8f801fabe6d6d332a2c23490221c9658da4e117b5d8a3d6aea298b15e8eca1e6ef8b5060d19bc08000000000000000000f8608d6d020000000000ffffffff06220200000000000017a91442402a28dd61f2718a4b27ae72a4791d5bbdade7878f3dc4120000000017a9145249bdf2c131d43995cff42e8feee293f79297a8870000000000000000266a24aa21a9ed3bca42ac84fe2476aa283585a57912919dc0989a4ff0c2ed69006ec4f0aedcdb00000000000000002f6a2d434f5245012953559db5cc88ab20b1960faa9793803d0703374e3ecda72cb7961caa4b541b1e322bcfe0b5a0300000000000000000146a12455853415401000d130f0e0e0b041f12001300000000000000002b6a2952534b424c4f434b3ac217de142117834272725babb0a7ffc355805877cc34cd79efab0a0a007111bf0120000000000000000000000000000000000000000000000000000000000000000000000000"),
697            // mainnet coinbase of block 799999 c61bfc223ec25581bde44aa229deccb2ac855b99bff7098ecb6edb5dd5fca816
698            (3, 2, NA, NA,"010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff5803ff340c1b4d696e656420627920416e74506f6f6c3930374a00b3004c23a345fabe6d6d4bd088d7ad6d953c6204534adf6e781f1b9dc83b631c445e22010a064e1e080b02000000000000002fd40000510e160000000000ffffffff04485d0f260000000017a9144b09d828dfc8baaba5d04ee77397e04b1050cc73870000000000000000266a24aa21a9ed8d16907a7020cedcdeb04966ea3550de7240d647e24ebbdd9dad990f324339c300000000000000002f6a2d434f52450164db24a662e20bbdf72d1cc6e973dbb2d12897d55997be5a09d05bb9bac27ec60419d0b373f32b2000000000000000002b6a2952534b424c4f434b3a4a327f77c940503af6f3b98b88419e0bfefff343ffe2a212327ed32a0053dfbf0120000000000000000000000000000000000000000000000000000000000000000000000000"),
699            // mainnet coinbase of block 840000 a0db149ace545beabbd87a8d6b20ffd6aa3b5a50e58add49a3d435f898c272cf
700            (1, NA, NA, NA, "010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff600340d10c192f5669614254432f4d696e65642062792062757a7a3132302f2cfabe6d6d144b553283a6e1a150c9989428c0695e3a1bef7d482ed1f829bbe25897fd37dc10000000000000001058a4c9000cc3a31889b38ae08249000000000000ffffffff03fb80e4f2000000001976a914536ffa992491508dca0354e52f32a3a7a679a53a88ac00000000000000002b6a2952534b424c4f434b3a52e15efafb3e2cf6dc2fc0e6bde5cb1d7d2143f1e089bd874e6b7913005fb2a00000000000000000266a24aa21a9ed88601d3d03ccce017fe2131c4c95a7292e4372983148e62996bb5e2de0e4d1d80120000000000000000000000000000000000000000000000000000000000000000000000000"),
701            // mainnet coinbase of block 840011 feecf78a27927207dc21540efeda88cc2a32d7c59a7c1a72329b04918ffc031c
702            (5, 3, NA, NA, "010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff64034bd10c2cfabe6d6d1f0fa69d2963d0c315a112f5f919c4d0225e21d109a8a40151a16f09460d4a6110000000f09f909f092f4632506f6f6c2f6600000000000000000000000000000000000000000000000000000000000000000000000500423d0100000000000622020000000000001976a914c6740a12d0a7d556f89782bf5faf0e12cf25a63988acf47c9083000000001976a914c85526a428126c00ad071b56341a5a553a5e96a388ac0000000000000000266a24aa21a9ed74a2c74f1251642cdeb0c0ac9222465f604afe78bfe6db8fc89d0c22924f8da300000000000000002f6a2d434f52450164db24a662e20bbdf72d1cc6e973dbb2d12897d5e7ec323813c943336c579e238228a8ebd096a7e50000000000000000266a24486174681f48b44796265b5f7229ddd13df801436533bfafb4ceb84c58c77483a9bbf3a200000000000000002c6a4c2952534b424c4f434b3a84396648c7e1be1123bfc316ffd41792323f833bb794b7e089bd871b005fb368012000000000000000000000000000000000000000000000000000000000000000000fdbe040"),
703            // mainnet coinbase of block 877777 ddeb60b3d20864be2a029338e24a54c838a29727238c7ad7a95db696e01da3b3
704            (6, 3, 4, 5, "010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff6403d1640d2cfabe6d6d2b1fb3a4a828afdd7608d1f8c1b4dccc39d8a75f797a133a2f8a16d66f54893f10000000f09f909f092f4632506f6f6c2f7300000000000000000000000000000000000000000000000000000000000000000000000500673f084b000000000722020000000000001976a914c6740a12d0a7d556f89782bf5faf0e12cf25a63988acaefec312000000001976a914c85526a428126c00ad071b56341a5a553a5e96a388ac0000000000000000266a24aa21a9ed93a39b1567cf5ca8b9c4a80e4567e2ad642ed52c3b087f19b3f652148ed5ebb700000000000000002f6a2d434f524501ebbaf365b0d5fa072e2b2429db23696291f2c038e7ec323813c943336c579e238228a8ebd096a7e50000000000000000126a10455853415401051b0f0e0e0b1f1200130000000000000000266a24486174684878f3caa0965ac6a8c27596f07bb0968e14c2f68372c3903d970ed4e107e8f000000000000000002c6a4c2952534b424c4f434b3a16cf32df3db0d8dcd29513c17408ecdaffb11af833681fd54be8aa0b006c3ecb012000000000000000000000000000000000000000000000000000000000000000009a24f33e"),
705            // mainnet coinbase of block 877780 1c1cd0ebe9cbdf1ca5e9debbd5007321019c50046e29a17ca38895a309a432dd
706            (4, 3, NA, NA, "010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff5a03d4640d1d506f7765726564206279204c75786f7220546563682400320238d28ebcfabe6d6d84e0cf7e6b67e03bd3ff229bb662e6878c4f4ad115d4c9a1284fb8c6bd9012ec10000000000000000000c437001a360200000000ffffffff05220200000000000017a914bf73ad4cf3a107812bad3deb310611bee49a3c7987f53900130000000017a914056adde53ebc396a1b3b678bb0d3a5c116ff430c870000000000000000266a24aa21a9ed2159e3043910b8a942205da1e208a55841697cbc5fdc30add503374a23622d0000000000000000002f6a2d434f524501a21cbd3caa4fe89bccd1d716c92ce4533e4d4733f459cc4ca322d298304ff163b2a360d756c5db8400000000000000002b6a2952534b424c4f434b3ae5156a9b29201650c69df60be76c488512831869a47d33681fd54b18006c3f560120000000000000000000000000000000000000000000000000000000000000000000000000"),
707            // mainnet coinbase of block 877792 0d22b51487d7b06b76fe894898b0cccf598f037b0f42701f37875e531b6e48e9
708            (NA, 3, NA, NA, "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff3103e0640d04843979672f466f756e6472792055534120506f6f6c202364726f70676f6c642f234456df3d616f0000000000ffffffff0422020000000000002251203daaca9b82a51aca960c1491588246029d7e0fc49e0abdbcc8fd17574be5c74b7efcc512000000002200207086320071974eef5e72eaa01dd9096e10c0383483855ea6b344259c244f73c20000000000000000266a24aa21a9ed9674ac27a1d6a81ee2087cc127ef242ccfa4d7f8245e41df9d2007c337dfb72d00000000000000002f6a2d434f5245012e50087fb834747606ed01ad67ad0f32129ab431e6d18fda214e5b9f350ffc7b6cf3058b9026e7650120000000000000000000000000000000000000000000000000000000000000000000000000"),
709            // mainnet coinbase of block 890732 2e5905e5b35119ffe905dcf7aec528b5199e6cc01e8363466a51e554f63bfd35
710            (6, 3, 4, 5, "010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff640363970d2cfabe6d6db1815c3a23ca2384f392a766b033f6f67bc0e52231e93d39eb34dba2dd84fd4610000000f09f909f092f4632506f6f6c2f6b000000000000000000000000000000000000000000000000000000000000000000000005000477be32000000000722020000000000001976a914c6740a12d0a7d556f89782bf5faf0e12cf25a63988ac5559c412000000001976a91469f2a01f4ff9e6ac24df9062e9828753474b348088ac0000000000000000266a24aa21a9ed825c6312e536c2ccf9a406c746b30eab23ddcf59a0313906f7a9e69a712b569900000000000000002f6a2d434f524501f6fdbc19a25dc91454cec19ef7714e8b67c4e0e6e7ec323813c943336c579e238228a8ebd096a7e50000000000000000126a10455853415401051b0f0e0e0b1f1200130000000000000000266a24486174686db90bbcde2697d1bbf9fc11cd57ae4c085349ea646f25bdae43bff94b5f6aeb00000000000000002c6a4c2952534b424c4f434b3a87f76f090e7e9934cebcaa5ba23f3a48e3edfc79522277f27a6160150071149801200000000000000000000000000000000000000000000000000000000000000000faa04d41"),
711        ];
712        for (i, (rsk_out_i, coredao_out_i, exsat_out_i, hathor_out_i, txhex)) in
713            testcases.iter().enumerate()
714        {
715            println!("Testing case {}", i);
716            let rawtx = hex::decode(txhex).unwrap();
717            let tx: Transaction = bitcoin::consensus::deserialize(&rawtx).unwrap();
718
719            // not all test cases have rsk outputs..
720            if *rsk_out_i != NA {
721                let rsk_out = &tx.output[*rsk_out_i];
722                assert!(rsk_out.is_opreturn_rsk_block());
723                assert_eq!(
724                    rsk_out.get_type(),
725                    OutputType::OpReturn(OpReturnFlavor::RSKBlock)
726                );
727            }
728
729            // not all test cases have coredao outputs..
730            if *coredao_out_i != NA {
731                let coredao_out = &tx.output[*coredao_out_i];
732                assert!(coredao_out.is_opreturn_coredao());
733                assert_eq!(
734                    coredao_out.get_type(),
735                    OutputType::OpReturn(OpReturnFlavor::CoreDao)
736                );
737            }
738
739            // not all test cases have exsat outputs..
740            if *exsat_out_i != NA {
741                let exsat_out = &tx.output[*exsat_out_i];
742                assert!(exsat_out.is_opreturn_exsat());
743                assert_eq!(
744                    exsat_out.get_type(),
745                    OutputType::OpReturn(OpReturnFlavor::ExSat)
746                );
747            }
748
749            // not all test cases have hathor outputs..
750            if *hathor_out_i != NA {
751                let hathor_out = &tx.output[*hathor_out_i];
752                assert!(hathor_out.is_opreturn_hathor());
753                assert_eq!(
754                    hathor_out.get_type(),
755                    OutputType::OpReturn(OpReturnFlavor::HathorNetwork)
756                );
757            }
758        }
759    }
760
761    #[test]
762    fn output_type_detection_opreturn_runestone() {
763        let testcases = vec![
764            // output, raw tx hex
765            // ---
766            // mainnet dc83243928d731a8d1f4de922fcbd27f9c5fac76533c053c8f41d0d363e1e891 with
767            // Runestone output as output 0
768            (0, "02000000000102b3d7ed7f7749a7b1ee032f1f8e40df38f97ce4b4cb0ffae1b3d3d31a96adf9090100000000ffffffff7eff527136c8eb2c37a1c0cea9076ca34411e491759ad8685046d97aa9e3e15d0200000000ffffffff0300000000000000000e6a5d0b00c0a23303d89af1dc12012202000000000000160014979bdd94f2d5972c062a7839ef114c193eca970ae1a4160000000000160014d58d2895ae676864b28af1d980c797d83f4d781f02473044022005309bf4bb5df2e5757deffedf171f3b93c19431ac1706debc6c0419ac3ab31e022002600e160dbeede6ab9473746c24cd9a382a2e05c67db203886497a8f713099c012102b9f2ec0c7c12cf5d1781518cfeadbe70d912084c07659f38a537ed1c2aa0e7a502473044022050ee4c0c3ba75d83a40d6304e4285e368b626e669d602f28c4ae7f6c32ac8b3e0220651b3299be01d93bdf91def09a1430e36a5fd25e43218caa5402e94c3b2a6918012103497a874c3412319689cc435756a820c3cf4cbb22d88ccafa29a8dc5e74eaa53800000000"),
769            // mainnet 2d9dfbe8e8acd34fefa32b5640b1f8bf3b620879055b9e3e0a48df8bfd9123b1 with
770            // Runestone output as output 1
771            (1, "02000000000101c68b2c3b546fb597e8cc4d10c9149a422b3dabef222bb28b83090b106da1282d0000000000ffffffff042202000000000000225120ffdd573c4ffac896f32c87c55923c5259736c4eda62b69e2317da1f41d8760120000000000000000116a5d0eff7f818cec82d08bc0a88281d215d62900000000000016001471a0ad6195dfc360f87452dd43e7a185f2667f28e867040000000000160014becb2a1cb327c74b96b1b5e4944f3155fa38793902473044022077e76fd6d6507fc7ac123c0a8c54b22be3566261ea28aab05479e9f86434ba1b02205c67a890799723a4174f48fef696e5d53a2ee30ae6481ef1cfe2fc10d8cb7c1c012102e951e84704fca4c56e7687e5d66b70501408860b1eb38f96930115c2c1af0b4700000000"),
772            // mainnet 2bb85f4b004be6da54f766c17c1e855187327112c231ef2ff35ebad0ea67c69e with
773            // Runestone output as output 1
774            (1, "020000000001017fb9cc941aa0ca3aaf339783564d2d29ec3254a9128f5d49ad3eeb002aeb40ec0000000000000000000242342a6b000000002251203b8b3ab1453eb47e2d4903b963776680e30863df3625d3e74292338ae7928da10000000000000000246a5d21020704b5e1d8e1c8eeb788a30705a02d039f3e01020680dc9afd2808c7e8430a640340924b2624416402a52ed7cf4eba6b2c535d2def8e649a74ed97aaca5ec54881ef3b34da68bb13d76d6b420e60297a9247cb081d1e59cb2c260b1509cff25d4b3158204c04e894d5357840e324b24c959ca6a5082035f6ffae12f331202bc84bf4612eac0063036f7264010b2047f22ed15d3082f5e9a005864528e4f991ade841a9c5846e2c118425878b6be1010d09b530368c74df10a3036821c04c04e894d5357840e324b24c959ca6a5082035f6ffae12f331202bc84bf4612e00000000"),
775            // mainnet 95bfa45afda983f3d6b9893a7e9200c12c4015bcdaffc3b933d508f9e47b7483 with
776            // Runestone output as output 1
777            (1, "02000000000101ebbdf6c9c8802683f7002220f4598f4fb09e9312246e84295618d8a236cb647a0000000000fdffffff024404000000000000225120bdd80b1763ec6cc124c578c9cd3f9321f65186135123b921e2062cea7b029f3700000000000000001e6a5d1b0205048b93fbc8f4c580850a010203940505e84b06acd1d5bec304034085e21f3bd22fa1c17c974891fb274ce5daf19cdc7a8e4dc246c044b52748559ec1c55237753edbd8ebe7e9148336a459043517eb252fd80db0bd3b3d44c5eb537f203706667a27041bec19c7e0ff94aaa11457860e960b34cc2aa3b7bc4c8866d326ac0063036f72640102022202010320ef287cdfbf0d302346654d1e5c75c4ddf708ae4abc81caa6a89eb9880abf1d93010b209198aa67989b85f5e3c404f3465a0d0e7f8b137e963100b18bd298e3b03166b6010d088bc91e492f020a0a6821c13706667a27041bec19c7e0ff94aaa11457860e960b34cc2aa3b7bc4c8866d32640d10c00"),
778        ];
779        for (i, (output_index, txhex)) in testcases.iter().enumerate() {
780            println!("Testing case {}", i);
781            let rawtx = hex::decode(txhex).unwrap();
782            let tx: Transaction = bitcoin::consensus::deserialize(&rawtx).unwrap();
783            let out = &tx.output[*output_index];
784            assert!(out.is_opreturn_runestone());
785            assert_eq!(
786                out.get_type(),
787                OutputType::OpReturn(OpReturnFlavor::Runestone)
788            );
789        }
790    }
791}