bitbox_api/
btc.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Functions and methods related to Bitcoin.
4
5use crate::runtime::Runtime;
6
7use crate::error::Error;
8use crate::pb::{self, request::Request, response::Response};
9use crate::Keypath;
10use crate::PairedBitBox;
11
12pub use bitcoin::{
13    bip32::{Fingerprint, Xpub},
14    blockdata::script::witness_version::WitnessVersion,
15    Script,
16};
17
18use bitcoin::blockdata::{opcodes, script::Instruction};
19
20#[cfg(feature = "wasm")]
21use enum_assoc::Assoc;
22
23#[cfg(feature = "wasm")]
24pub(crate) fn serde_deserialize_simple_type<'de, D>(deserializer: D) -> Result<i32, D::Error>
25where
26    D: serde::Deserializer<'de>,
27{
28    use serde::Deserialize;
29    Ok(pb::btc_script_config::SimpleType::deserialize(deserializer)?.into())
30}
31
32#[cfg(feature = "wasm")]
33pub(crate) fn serde_deserialize_multisig<'de, D>(
34    deserializer: D,
35) -> Result<pb::btc_script_config::Multisig, D::Error>
36where
37    D: serde::Deserializer<'de>,
38{
39    use serde::Deserialize;
40    use std::str::FromStr;
41
42    #[derive(serde::Deserialize)]
43    #[serde(rename_all = "camelCase")]
44    struct Multisig {
45        threshold: u32,
46        xpubs: Vec<String>,
47        our_xpub_index: u32,
48        script_type: pb::btc_script_config::multisig::ScriptType,
49    }
50    let ms = Multisig::deserialize(deserializer)?;
51    let xpubs = ms
52        .xpubs
53        .iter()
54        .map(|s| Xpub::from_str(s.as_str()))
55        .collect::<Result<Vec<Xpub>, _>>()
56        .map_err(serde::de::Error::custom)?;
57    Ok(pb::btc_script_config::Multisig {
58        threshold: ms.threshold,
59        xpubs: xpubs.iter().map(convert_xpub).collect(),
60        our_xpub_index: ms.our_xpub_index,
61        script_type: ms.script_type.into(),
62    })
63}
64
65#[cfg(feature = "wasm")]
66#[derive(serde::Deserialize)]
67pub(crate) struct SerdeScriptConfig(pb::btc_script_config::Config);
68
69#[cfg(feature = "wasm")]
70impl From<SerdeScriptConfig> for pb::BtcScriptConfig {
71    fn from(value: SerdeScriptConfig) -> Self {
72        pb::BtcScriptConfig {
73            config: Some(value.0),
74        }
75    }
76}
77
78#[derive(Clone, Debug, PartialEq)]
79pub struct PrevTxInput {
80    pub prev_out_hash: Vec<u8>,
81    pub prev_out_index: u32,
82    pub signature_script: Vec<u8>,
83    pub sequence: u32,
84}
85
86impl From<&bitcoin::TxIn> for PrevTxInput {
87    fn from(value: &bitcoin::TxIn) -> Self {
88        PrevTxInput {
89            prev_out_hash: (value.previous_output.txid.as_ref() as &[u8]).to_vec(),
90            prev_out_index: value.previous_output.vout,
91            signature_script: value.script_sig.as_bytes().to_vec(),
92            sequence: value.sequence.to_consensus_u32(),
93        }
94    }
95}
96#[derive(Clone, Debug, PartialEq)]
97pub struct PrevTxOutput {
98    pub value: u64,
99    pub pubkey_script: Vec<u8>,
100}
101
102impl From<&bitcoin::TxOut> for PrevTxOutput {
103    fn from(value: &bitcoin::TxOut) -> Self {
104        PrevTxOutput {
105            value: value.value.to_sat(),
106            pubkey_script: value.script_pubkey.as_bytes().to_vec(),
107        }
108    }
109}
110
111#[derive(Clone, Debug, PartialEq)]
112pub struct PrevTx {
113    pub version: u32,
114    pub inputs: Vec<PrevTxInput>,
115    pub outputs: Vec<PrevTxOutput>,
116    pub locktime: u32,
117}
118
119impl From<&bitcoin::Transaction> for PrevTx {
120    fn from(value: &bitcoin::Transaction) -> Self {
121        PrevTx {
122            version: value.version.0 as _,
123            inputs: value.input.iter().map(PrevTxInput::from).collect(),
124            outputs: value.output.iter().map(PrevTxOutput::from).collect(),
125            locktime: value.lock_time.to_consensus_u32(),
126        }
127    }
128}
129
130#[derive(Debug, PartialEq)]
131pub struct TxInput {
132    pub prev_out_hash: Vec<u8>,
133    pub prev_out_index: u32,
134    pub prev_out_value: u64,
135    pub sequence: u32,
136    pub keypath: Keypath,
137    pub script_config_index: u32,
138    // Can be None if all transaction inputs are Taproot.
139    pub prev_tx: Option<PrevTx>,
140}
141
142impl TxInput {
143    fn get_prev_tx(&self) -> Result<&PrevTx, Error> {
144        self.prev_tx.as_ref().ok_or(Error::BtcSign(
145            "input's previous transaction required but missing".into(),
146        ))
147    }
148}
149
150#[derive(Debug, PartialEq)]
151pub struct TxInternalOutput {
152    pub keypath: Keypath,
153    pub value: u64,
154    pub script_config_index: u32,
155}
156
157#[derive(Debug, PartialEq)]
158pub struct Payload {
159    pub data: Vec<u8>,
160    pub output_type: pb::BtcOutputType,
161}
162
163#[derive(thiserror::Error, Debug)]
164pub enum PayloadError {
165    #[error("unrecognized pubkey script")]
166    Unrecognized,
167    #[error("{0}")]
168    InvalidOpReturn(&'static str),
169}
170
171impl Payload {
172    pub fn from_pkscript(pkscript: &[u8]) -> Result<Payload, PayloadError> {
173        let script = Script::from_bytes(pkscript);
174        if script.is_p2pkh() {
175            Ok(Payload {
176                data: pkscript[3..23].to_vec(),
177                output_type: pb::BtcOutputType::P2pkh,
178            })
179        } else if script.is_p2sh() {
180            Ok(Payload {
181                data: pkscript[2..22].to_vec(),
182                output_type: pb::BtcOutputType::P2sh,
183            })
184        } else if script.is_p2wpkh() {
185            Ok(Payload {
186                data: pkscript[2..].to_vec(),
187                output_type: pb::BtcOutputType::P2wpkh,
188            })
189        } else if script.is_p2wsh() {
190            Ok(Payload {
191                data: pkscript[2..].to_vec(),
192                output_type: pb::BtcOutputType::P2wsh,
193            })
194        } else if script.is_p2tr() {
195            Ok(Payload {
196                data: pkscript[2..].to_vec(),
197                output_type: pb::BtcOutputType::P2tr,
198            })
199        } else if matches!(script.as_bytes().first(), Some(byte) if *byte == opcodes::all::OP_RETURN.to_u8())
200        {
201            let mut instructions = script.instructions_minimal();
202            match instructions.next() {
203                Some(Ok(Instruction::Op(op))) if op == opcodes::all::OP_RETURN => {}
204                _ => return Err(PayloadError::Unrecognized),
205            }
206
207            let payload = match instructions.next() {
208                None => {
209                    return Err(PayloadError::InvalidOpReturn(
210                        "naked OP_RETURN is not supported",
211                    ))
212                }
213                Some(Ok(Instruction::Op(op))) if op == opcodes::all::OP_PUSHBYTES_0 => Vec::new(),
214                Some(Ok(Instruction::PushBytes(push))) => push.as_bytes().to_vec(),
215                Some(Ok(_)) => {
216                    return Err(PayloadError::InvalidOpReturn(
217                        "no data push found after OP_RETURN",
218                    ))
219                }
220                Some(Err(_)) => {
221                    return Err(PayloadError::InvalidOpReturn(
222                        "failed to parse OP_RETURN payload",
223                    ))
224                }
225            };
226
227            match instructions.next() {
228                None => Ok(Payload {
229                    data: payload,
230                    output_type: pb::BtcOutputType::OpReturn,
231                }),
232                Some(Ok(_)) => Err(PayloadError::InvalidOpReturn(
233                    "only one data push supported after OP_RETURN",
234                )),
235                Some(Err(_)) => Err(PayloadError::InvalidOpReturn(
236                    "failed to parse OP_RETURN payload",
237                )),
238            }
239        } else {
240            Err(PayloadError::Unrecognized)
241        }
242    }
243}
244
245#[derive(Debug, PartialEq)]
246pub struct TxExternalOutput {
247    pub payload: Payload,
248    pub value: u64,
249}
250
251impl TryFrom<&bitcoin::TxOut> for TxExternalOutput {
252    type Error = PsbtError;
253    fn try_from(value: &bitcoin::TxOut) -> Result<Self, Self::Error> {
254        Ok(TxExternalOutput {
255            payload: Payload::from_pkscript(value.script_pubkey.as_bytes())?,
256            value: value.value.to_sat(),
257        })
258    }
259}
260
261#[derive(Debug, PartialEq)]
262pub enum TxOutput {
263    Internal(TxInternalOutput),
264    External(TxExternalOutput),
265}
266
267#[derive(Debug, PartialEq)]
268pub struct Transaction {
269    pub script_configs: Vec<pb::BtcScriptConfigWithKeypath>,
270    pub version: u32,
271    pub inputs: Vec<TxInput>,
272    pub outputs: Vec<TxOutput>,
273    pub locktime: u32,
274}
275// See https://github.com/spesmilo/electrum/blob/84dc181b6e7bb20e88ef6b98fb8925c5f645a765/electrum/ecc.py#L521-L523
276#[derive(Debug, PartialEq, serde::Serialize)]
277#[serde(rename_all = "camelCase")]
278pub struct SignMessageSignature {
279    pub sig: Vec<u8>,
280    pub recid: u8,
281    pub electrum_sig65: Vec<u8>,
282}
283
284#[derive(thiserror::Error, Debug)]
285#[cfg_attr(feature = "wasm", derive(Assoc), func(pub const fn js_code(&self) -> &'static str))]
286pub enum PsbtError {
287    #[error("{0}")]
288    #[cfg_attr(feature = "wasm", assoc(js_code = "sign-error"))]
289    SignError(#[from] bitcoin::psbt::SignError),
290    #[error("Taproot pubkeys must be unique across the internal key and all leaf scripts.")]
291    #[cfg_attr(feature = "wasm", assoc(js_code = "key-not-unique"))]
292    KeyNotUnique,
293    #[error("Could not find our key in an input.")]
294    #[cfg_attr(feature = "wasm", assoc(js_code = "key-not-found"))]
295    KeyNotFound,
296    #[error("Unrecognized/unsupported output type.")]
297    #[cfg_attr(feature = "wasm", assoc(js_code = "unknown-output-type"))]
298    UnknownOutputType,
299    #[error("Invalid OP_RETURN script: {0}")]
300    #[cfg_attr(feature = "wasm", assoc(js_code = "invalid-op-return"))]
301    InvalidOpReturn(&'static str),
302}
303
304impl From<PayloadError> for PsbtError {
305    fn from(value: PayloadError) -> Self {
306        match value {
307            PayloadError::Unrecognized => PsbtError::UnknownOutputType,
308            PayloadError::InvalidOpReturn(message) => PsbtError::InvalidOpReturn(message),
309        }
310    }
311}
312
313enum OurKey {
314    Segwit(bitcoin::secp256k1::PublicKey, Keypath),
315    TaprootInternal(Keypath),
316    TaprootScript(
317        bitcoin::secp256k1::XOnlyPublicKey,
318        bitcoin::taproot::TapLeafHash,
319        Keypath,
320    ),
321}
322
323impl OurKey {
324    fn keypath(&self) -> Keypath {
325        match self {
326            OurKey::Segwit(_, kp) => kp.clone(),
327            OurKey::TaprootInternal(kp) => kp.clone(),
328            OurKey::TaprootScript(_, _, kp) => kp.clone(),
329        }
330    }
331}
332
333trait PsbtOutputInfo {
334    fn get_bip32_derivation(
335        &self,
336    ) -> &std::collections::BTreeMap<bitcoin::secp256k1::PublicKey, bitcoin::bip32::KeySource>;
337
338    fn get_tap_internal_key(&self) -> Option<&bitcoin::secp256k1::XOnlyPublicKey>;
339    fn get_tap_key_origins(
340        &self,
341    ) -> &std::collections::BTreeMap<
342        bitcoin::secp256k1::XOnlyPublicKey,
343        (
344            Vec<bitcoin::taproot::TapLeafHash>,
345            bitcoin::bip32::KeySource,
346        ),
347    >;
348}
349
350impl PsbtOutputInfo for &bitcoin::psbt::Input {
351    fn get_bip32_derivation(
352        &self,
353    ) -> &std::collections::BTreeMap<bitcoin::secp256k1::PublicKey, bitcoin::bip32::KeySource> {
354        &self.bip32_derivation
355    }
356
357    fn get_tap_internal_key(&self) -> Option<&bitcoin::secp256k1::XOnlyPublicKey> {
358        self.tap_internal_key.as_ref()
359    }
360
361    fn get_tap_key_origins(
362        &self,
363    ) -> &std::collections::BTreeMap<
364        bitcoin::secp256k1::XOnlyPublicKey,
365        (
366            Vec<bitcoin::taproot::TapLeafHash>,
367            bitcoin::bip32::KeySource,
368        ),
369    > {
370        &self.tap_key_origins
371    }
372}
373
374impl PsbtOutputInfo for &bitcoin::psbt::Output {
375    fn get_bip32_derivation(
376        &self,
377    ) -> &std::collections::BTreeMap<bitcoin::secp256k1::PublicKey, bitcoin::bip32::KeySource> {
378        &self.bip32_derivation
379    }
380
381    fn get_tap_internal_key(&self) -> Option<&bitcoin::secp256k1::XOnlyPublicKey> {
382        self.tap_internal_key.as_ref()
383    }
384
385    fn get_tap_key_origins(
386        &self,
387    ) -> &std::collections::BTreeMap<
388        bitcoin::secp256k1::XOnlyPublicKey,
389        (
390            Vec<bitcoin::taproot::TapLeafHash>,
391            bitcoin::bip32::KeySource,
392        ),
393    > {
394        &self.tap_key_origins
395    }
396}
397
398fn find_our_key<T: PsbtOutputInfo>(
399    our_root_fingerprint: &[u8],
400    output_info: T,
401) -> Result<OurKey, PsbtError> {
402    for (xonly, (leaf_hashes, (fingerprint, derivation_path))) in
403        output_info.get_tap_key_origins().iter()
404    {
405        if &fingerprint[..] == our_root_fingerprint {
406            // TODO: check for fingerprint collision
407
408            if let Some(tap_internal_key) = output_info.get_tap_internal_key() {
409                if tap_internal_key == xonly {
410                    if !leaf_hashes.is_empty() {
411                        // TODO change err msg, we don't support the
412                        // same key as internal key and also in a leaf
413                        // script.
414                        return Err(PsbtError::KeyNotUnique);
415                    }
416                    return Ok(OurKey::TaprootInternal(derivation_path.into()));
417                }
418            }
419            if leaf_hashes.len() != 1 {
420                // TODO change err msg, per BIP-388 all pubkeys are
421                // unique, so it can't be in multiple leafs.
422                return Err(PsbtError::KeyNotUnique);
423            }
424            return Ok(OurKey::TaprootScript(
425                *xonly,
426                leaf_hashes[0],
427                derivation_path.into(),
428            ));
429        }
430    }
431    for (pubkey, (fingerprint, derivation_path)) in output_info.get_bip32_derivation().iter() {
432        if &fingerprint[..] == our_root_fingerprint {
433            // TODO: check for fingerprint collision
434            return Ok(OurKey::Segwit(*pubkey, derivation_path.into()));
435        }
436    }
437    Err(PsbtError::KeyNotFound)
438}
439
440fn script_config_from_utxo(
441    output: &bitcoin::TxOut,
442    keypath: Keypath,
443    redeem_script: Option<&bitcoin::ScriptBuf>,
444    _witness_script: Option<&bitcoin::ScriptBuf>,
445) -> Result<pb::BtcScriptConfigWithKeypath, PsbtError> {
446    let keypath = keypath.hardened_prefix();
447    if output.script_pubkey.is_p2wpkh() {
448        return Ok(pb::BtcScriptConfigWithKeypath {
449            script_config: Some(make_script_config_simple(
450                pb::btc_script_config::SimpleType::P2wpkh,
451            )),
452            keypath: keypath.to_vec(),
453        });
454    }
455    let redeem_script_is_p2wpkh = redeem_script.map(|s| s.is_p2wpkh()).unwrap_or(false);
456    if output.script_pubkey.is_p2sh() && redeem_script_is_p2wpkh {
457        return Ok(pb::BtcScriptConfigWithKeypath {
458            script_config: Some(make_script_config_simple(
459                pb::btc_script_config::SimpleType::P2wpkhP2sh,
460            )),
461            keypath: keypath.to_vec(),
462        });
463    }
464    if output.script_pubkey.is_p2tr() {
465        return Ok(pb::BtcScriptConfigWithKeypath {
466            script_config: Some(make_script_config_simple(
467                pb::btc_script_config::SimpleType::P2tr,
468            )),
469            keypath: keypath.to_vec(),
470        });
471    }
472    // Check for segwit multisig (p2wsh or p2wsh-p2sh).
473    let redeem_script_is_p2wsh = redeem_script.map(|s| s.is_p2wsh()).unwrap_or(false);
474    let is_p2wsh_p2sh = output.script_pubkey.is_p2sh() && redeem_script_is_p2wsh;
475    if output.script_pubkey.is_p2wsh() || is_p2wsh_p2sh {
476        todo!();
477    }
478    Err(PsbtError::UnknownOutputType)
479}
480
481impl Transaction {
482    fn from_psbt(
483        our_root_fingerprint: &[u8],
484        psbt: &bitcoin::psbt::Psbt,
485        force_script_config: Option<pb::BtcScriptConfigWithKeypath>,
486    ) -> Result<(Self, Vec<OurKey>), PsbtError> {
487        let mut script_configs: Vec<pb::BtcScriptConfigWithKeypath> = Vec::new();
488        let mut is_script_config_forced = false;
489        if let Some(cfg) = force_script_config {
490            script_configs.push(cfg);
491            is_script_config_forced = true;
492        }
493
494        let mut our_keys: Vec<OurKey> = Vec::new();
495        let mut inputs: Vec<TxInput> = Vec::new();
496
497        let mut add_script_config = |script_config: pb::BtcScriptConfigWithKeypath| -> usize {
498            match script_configs.iter().position(|el| el == &script_config) {
499                Some(pos) => pos,
500                None => {
501                    script_configs.push(script_config);
502                    script_configs.len() - 1
503                }
504            }
505        };
506
507        for (input_index, (tx_input, psbt_input)) in
508            psbt.unsigned_tx.input.iter().zip(&psbt.inputs).enumerate()
509        {
510            let utxo = psbt.spend_utxo(input_index)?;
511            let our_key = find_our_key(our_root_fingerprint, psbt_input)?;
512            let script_config_index = if is_script_config_forced {
513                0
514            } else {
515                add_script_config(script_config_from_utxo(
516                    utxo,
517                    our_key.keypath(),
518                    psbt_input.redeem_script.as_ref(),
519                    psbt_input.witness_script.as_ref(),
520                )?)
521            };
522
523            inputs.push(TxInput {
524                prev_out_hash: (tx_input.previous_output.txid.as_ref() as &[u8]).to_vec(),
525                prev_out_index: tx_input.previous_output.vout,
526                prev_out_value: utxo.value.to_sat(),
527                sequence: tx_input.sequence.to_consensus_u32(),
528                keypath: our_key.keypath(),
529                script_config_index: script_config_index as _,
530                prev_tx: psbt_input.non_witness_utxo.as_ref().map(PrevTx::from),
531            });
532            our_keys.push(our_key);
533        }
534
535        let mut outputs: Vec<TxOutput> = Vec::new();
536        for (tx_output, psbt_output) in psbt.unsigned_tx.output.iter().zip(&psbt.outputs) {
537            let our_key = find_our_key(our_root_fingerprint, psbt_output);
538            // Either change output or a non-change output owned by the BitBox.
539            match our_key {
540                Ok(our_key) => {
541                    let script_config_index = if is_script_config_forced {
542                        0
543                    } else {
544                        add_script_config(script_config_from_utxo(
545                            tx_output,
546                            our_key.keypath(),
547                            psbt_output.redeem_script.as_ref(),
548                            psbt_output.witness_script.as_ref(),
549                        )?)
550                    };
551                    outputs.push(TxOutput::Internal(TxInternalOutput {
552                        keypath: our_key.keypath(),
553                        value: tx_output.value.to_sat(),
554                        script_config_index: script_config_index as _,
555                    }));
556                }
557                Err(_) => {
558                    outputs.push(TxOutput::External(tx_output.try_into()?));
559                }
560            }
561        }
562
563        Ok((
564            Transaction {
565                script_configs,
566                version: psbt.unsigned_tx.version.0 as _,
567                inputs,
568                outputs,
569                locktime: psbt.unsigned_tx.lock_time.to_consensus_u32(),
570            },
571            our_keys,
572        ))
573    }
574}
575
576/// Create a single-sig script config.
577pub fn make_script_config_simple(
578    simple_type: pb::btc_script_config::SimpleType,
579) -> pb::BtcScriptConfig {
580    pb::BtcScriptConfig {
581        config: Some(pb::btc_script_config::Config::SimpleType(
582            simple_type.into(),
583        )),
584    }
585}
586
587#[derive(Clone)]
588#[cfg_attr(
589    feature = "wasm",
590    derive(serde::Deserialize),
591    serde(rename_all = "camelCase")
592)]
593#[derive(PartialEq)]
594pub struct KeyOriginInfo {
595    pub root_fingerprint: Option<bitcoin::bip32::Fingerprint>,
596    pub keypath: Option<Keypath>,
597    pub xpub: bitcoin::bip32::Xpub,
598}
599
600fn convert_xpub(xpub: &bitcoin::bip32::Xpub) -> pb::XPub {
601    pb::XPub {
602        depth: vec![xpub.depth],
603        parent_fingerprint: xpub.parent_fingerprint[..].to_vec(),
604        child_num: xpub.child_number.into(),
605        chain_code: xpub.chain_code[..].to_vec(),
606        public_key: xpub.public_key.serialize().to_vec(),
607    }
608}
609
610impl From<KeyOriginInfo> for pb::KeyOriginInfo {
611    fn from(value: KeyOriginInfo) -> Self {
612        pb::KeyOriginInfo {
613            root_fingerprint: value
614                .root_fingerprint
615                .map_or(vec![], |fp| fp.as_bytes().to_vec()),
616            keypath: value.keypath.map_or(vec![], |kp| kp.to_vec()),
617            xpub: Some(convert_xpub(&value.xpub)),
618        }
619    }
620}
621
622/// Create a multi-sig script config.
623pub fn make_script_config_multisig(
624    threshold: u32,
625    xpubs: &[bitcoin::bip32::Xpub],
626    our_xpub_index: u32,
627    script_type: pb::btc_script_config::multisig::ScriptType,
628) -> pb::BtcScriptConfig {
629    pb::BtcScriptConfig {
630        config: Some(pb::btc_script_config::Config::Multisig(
631            pb::btc_script_config::Multisig {
632                threshold,
633                xpubs: xpubs.iter().map(convert_xpub).collect(),
634                our_xpub_index,
635                script_type: script_type as _,
636            },
637        )),
638    }
639}
640
641/// Create a wallet policy script config according to the wallet policies BIP:
642/// <https://github.com/bitcoin/bips/pull/1389>
643///
644/// At least one of the keys must be ours, i.e. contain our root fingerprint and a keypath to one of
645/// our xpubs.
646pub fn make_script_config_policy(policy: &str, keys: &[KeyOriginInfo]) -> pb::BtcScriptConfig {
647    pb::BtcScriptConfig {
648        config: Some(pb::btc_script_config::Config::Policy(
649            pb::btc_script_config::Policy {
650                policy: policy.into(),
651                keys: keys.iter().cloned().map(pb::KeyOriginInfo::from).collect(),
652            },
653        )),
654    }
655}
656
657fn is_taproot_simple(script_config: &pb::BtcScriptConfigWithKeypath) -> bool {
658    matches!(
659        script_config.script_config.as_ref(),
660        Some(pb::BtcScriptConfig {
661            config: Some(pb::btc_script_config::Config::SimpleType(simple_type)),
662        }) if *simple_type == pb::btc_script_config::SimpleType::P2tr as i32
663    )
664}
665
666fn is_taproot_policy(script_config: &pb::BtcScriptConfigWithKeypath) -> bool {
667    matches!(
668        script_config.script_config.as_ref(),
669        Some(pb::BtcScriptConfig {
670            config: Some(pb::btc_script_config::Config::Policy(policy)),
671        })  if policy.policy.as_str().starts_with("tr("),
672    )
673}
674
675fn is_schnorr(script_config: &pb::BtcScriptConfigWithKeypath) -> bool {
676    is_taproot_simple(script_config) | is_taproot_policy(script_config)
677}
678
679impl<R: Runtime> PairedBitBox<R> {
680    /// Retrieves an xpub. For non-standard keypaths, a warning is displayed on the BitBox even if
681    /// `display` is false.
682    pub async fn btc_xpub(
683        &self,
684        coin: pb::BtcCoin,
685        keypath: &Keypath,
686        xpub_type: pb::btc_pub_request::XPubType,
687        display: bool,
688    ) -> Result<String, Error> {
689        match self
690            .query_proto(Request::BtcPub(pb::BtcPubRequest {
691                coin: coin as _,
692                keypath: keypath.to_vec(),
693                display,
694                output: Some(pb::btc_pub_request::Output::XpubType(xpub_type as _)),
695            }))
696            .await?
697        {
698            Response::Pub(pb::PubResponse { r#pub }) => Ok(r#pub),
699            _ => Err(Error::UnexpectedResponse),
700        }
701    }
702
703    /// Retrieves multiple xpubs at once. Only standard keypaths are allowed.
704    /// On firmware <v9.24.0, this falls back to calling `btc_xpub()` for each keypath.
705    pub async fn btc_xpubs(
706        &self,
707        coin: pb::BtcCoin,
708        keypaths: &[Keypath],
709        xpub_type: pb::btc_xpubs_request::XPubType,
710    ) -> Result<Vec<String>, Error> {
711        if self.validate_version(">=9.24.0").is_err() {
712            // Fallback to fetching them one-by-one on older firmware.
713            let mut xpubs = Vec::<String>::with_capacity(keypaths.len());
714            for keypath in keypaths {
715                let converted_xpub_type = match xpub_type {
716                    pb::btc_xpubs_request::XPubType::Unknown => return Err(Error::Unknown),
717                    pb::btc_xpubs_request::XPubType::Tpub => pb::btc_pub_request::XPubType::Tpub,
718                    pb::btc_xpubs_request::XPubType::Xpub => pb::btc_pub_request::XPubType::Xpub,
719                };
720                let xpub = self
721                    .btc_xpub(coin, keypath, converted_xpub_type, false)
722                    .await?;
723                xpubs.push(xpub);
724            }
725            return Ok(xpubs);
726        }
727        match self
728            .query_proto_btc(pb::btc_request::Request::Xpubs(pb::BtcXpubsRequest {
729                coin: coin as _,
730                xpub_type: xpub_type as _,
731                keypaths: keypaths.iter().map(|kp| kp.into()).collect(),
732            }))
733            .await?
734        {
735            pb::btc_response::Response::Pubs(pb::PubsResponse { pubs }) => Ok(pubs),
736            _ => Err(Error::UnexpectedResponse),
737        }
738    }
739
740    /// Retrieves a Bitcoin address at the provided keypath.
741    ///
742    /// For the simple script configs (single-sig), the keypath must follow the
743    /// BIP44/BIP49/BIP84/BIP86 conventions.
744    pub async fn btc_address(
745        &self,
746        coin: pb::BtcCoin,
747        keypath: &Keypath,
748        script_config: &pb::BtcScriptConfig,
749        display: bool,
750    ) -> Result<String, Error> {
751        match self
752            .query_proto(Request::BtcPub(pb::BtcPubRequest {
753                coin: coin as _,
754                keypath: keypath.to_vec(),
755                display,
756                output: Some(pb::btc_pub_request::Output::ScriptConfig(
757                    script_config.clone(),
758                )),
759            }))
760            .await?
761        {
762            Response::Pub(pb::PubResponse { r#pub }) => Ok(r#pub),
763            _ => Err(Error::UnexpectedResponse),
764        }
765    }
766
767    async fn query_proto_btc(
768        &self,
769        request: pb::btc_request::Request,
770    ) -> Result<pb::btc_response::Response, Error> {
771        match self
772            .query_proto(Request::Btc(pb::BtcRequest {
773                request: Some(request),
774            }))
775            .await?
776        {
777            Response::Btc(pb::BtcResponse {
778                response: Some(response),
779            }) => Ok(response),
780            _ => Err(Error::UnexpectedResponse),
781        }
782    }
783
784    async fn get_next_response(&self, request: Request) -> Result<pb::BtcSignNextResponse, Error> {
785        match self.query_proto(request).await? {
786            Response::BtcSignNext(next_response) => Ok(next_response),
787            _ => Err(Error::UnexpectedResponse),
788        }
789    }
790
791    async fn get_next_response_nested(
792        &self,
793        request: pb::btc_request::Request,
794    ) -> Result<pb::BtcSignNextResponse, Error> {
795        match self.query_proto_btc(request).await? {
796            pb::btc_response::Response::SignNext(next_response) => Ok(next_response),
797            _ => Err(Error::UnexpectedResponse),
798        }
799    }
800
801    /// Sign a Bitcoin transaction. Returns one 64 byte signature (compact serlization of the R and
802    /// S values) per input.
803    pub async fn btc_sign(
804        &self,
805        coin: pb::BtcCoin,
806        transaction: &Transaction,
807        format_unit: pb::btc_sign_init_request::FormatUnit,
808    ) -> Result<Vec<Vec<u8>>, Error> {
809        self.validate_version(">=9.4.0")?; // anti-klepto since 9.4.0
810        if transaction.script_configs.iter().any(is_taproot_simple) {
811            self.validate_version(">=9.10.0")?; // taproot since 9.10.0
812        }
813        if transaction.outputs.iter().any(|output| {
814            matches!(
815                output,
816                TxOutput::External(tx_output)
817                    if tx_output.payload.output_type == pb::BtcOutputType::OpReturn
818            )
819        }) {
820            self.validate_version(">=9.24.0")?;
821        }
822
823        let mut sigs: Vec<Vec<u8>> = Vec::new();
824
825        let mut next_response = self
826            .get_next_response(Request::BtcSignInit(pb::BtcSignInitRequest {
827                coin: coin as _,
828                script_configs: transaction.script_configs.clone(),
829                output_script_configs: vec![],
830                version: transaction.version,
831                num_inputs: transaction.inputs.len() as _,
832                num_outputs: transaction.outputs.len() as _,
833                locktime: transaction.locktime,
834                format_unit: format_unit as _,
835                contains_silent_payment_outputs: false,
836            }))
837            .await?;
838
839        let mut is_inputs_pass2 = false;
840        loop {
841            match pb::btc_sign_next_response::Type::try_from(next_response.r#type)
842                .map_err(|_| Error::UnexpectedResponse)?
843            {
844                pb::btc_sign_next_response::Type::Input => {
845                    let input_index: usize = next_response.index as _;
846                    let tx_input: &TxInput = &transaction.inputs[input_index];
847
848                    let input_is_schnorr = is_schnorr(
849                        &transaction.script_configs[tx_input.script_config_index as usize],
850                    );
851                    let perform_antiklepto = is_inputs_pass2 && !input_is_schnorr;
852                    let host_nonce = if perform_antiklepto {
853                        Some(crate::antiklepto::gen_host_nonce()?)
854                    } else {
855                        None
856                    };
857                    next_response = self
858                        .get_next_response(Request::BtcSignInput(pb::BtcSignInputRequest {
859                            prev_out_hash: tx_input.prev_out_hash.clone(),
860                            prev_out_index: tx_input.prev_out_index,
861                            prev_out_value: tx_input.prev_out_value,
862                            sequence: tx_input.sequence,
863                            keypath: tx_input.keypath.to_vec(),
864                            script_config_index: tx_input.script_config_index,
865                            host_nonce_commitment: host_nonce.as_ref().map(|host_nonce| {
866                                pb::AntiKleptoHostNonceCommitment {
867                                    commitment: crate::antiklepto::host_commit(host_nonce).to_vec(),
868                                }
869                            }),
870                        }))
871                        .await?;
872
873                    if let Some(host_nonce) = host_nonce {
874                        if next_response.r#type
875                            != pb::btc_sign_next_response::Type::HostNonce as i32
876                        {
877                            return Err(Error::UnexpectedResponse);
878                        }
879                        if let Some(pb::AntiKleptoSignerCommitment { commitment }) =
880                            next_response.anti_klepto_signer_commitment
881                        {
882                            next_response = self
883                                .get_next_response_nested(
884                                    pb::btc_request::Request::AntikleptoSignature(
885                                        pb::AntiKleptoSignatureRequest {
886                                            host_nonce: host_nonce.to_vec(),
887                                        },
888                                    ),
889                                )
890                                .await?;
891                            if !next_response.has_signature {
892                                return Err(Error::UnexpectedResponse);
893                            }
894                            crate::antiklepto::verify_ecdsa(
895                                &host_nonce,
896                                &commitment,
897                                &next_response.signature,
898                            )?
899                        } else {
900                            return Err(Error::UnexpectedResponse);
901                        }
902                    }
903
904                    if is_inputs_pass2 {
905                        if !next_response.has_signature {
906                            return Err(Error::UnexpectedResponse);
907                        }
908                        sigs.push(next_response.signature.clone());
909                    }
910                    if input_index == transaction.inputs.len() - 1 {
911                        is_inputs_pass2 = true
912                    }
913                }
914                pb::btc_sign_next_response::Type::PrevtxInit => {
915                    let prevtx: &PrevTx =
916                        transaction.inputs[next_response.index as usize].get_prev_tx()?;
917                    next_response = self
918                        .get_next_response_nested(pb::btc_request::Request::PrevtxInit(
919                            pb::BtcPrevTxInitRequest {
920                                version: prevtx.version,
921                                num_inputs: prevtx.inputs.len() as _,
922                                num_outputs: prevtx.outputs.len() as _,
923                                locktime: prevtx.locktime,
924                            },
925                        ))
926                        .await?;
927                }
928                pb::btc_sign_next_response::Type::PrevtxInput => {
929                    let prevtx: &PrevTx =
930                        transaction.inputs[next_response.index as usize].get_prev_tx()?;
931                    let prevtx_input: &PrevTxInput =
932                        &prevtx.inputs[next_response.prev_index as usize];
933                    next_response = self
934                        .get_next_response_nested(pb::btc_request::Request::PrevtxInput(
935                            pb::BtcPrevTxInputRequest {
936                                prev_out_hash: prevtx_input.prev_out_hash.clone(),
937                                prev_out_index: prevtx_input.prev_out_index,
938                                signature_script: prevtx_input.signature_script.clone(),
939                                sequence: prevtx_input.sequence,
940                            },
941                        ))
942                        .await?;
943                }
944                pb::btc_sign_next_response::Type::PrevtxOutput => {
945                    let prevtx: &PrevTx =
946                        transaction.inputs[next_response.index as usize].get_prev_tx()?;
947                    let prevtx_output: &PrevTxOutput =
948                        &prevtx.outputs[next_response.prev_index as usize];
949                    next_response = self
950                        .get_next_response_nested(pb::btc_request::Request::PrevtxOutput(
951                            pb::BtcPrevTxOutputRequest {
952                                value: prevtx_output.value,
953                                pubkey_script: prevtx_output.pubkey_script.clone(),
954                            },
955                        ))
956                        .await?;
957                }
958                pb::btc_sign_next_response::Type::Output => {
959                    let tx_output: &TxOutput = &transaction.outputs[next_response.index as usize];
960                    let request: Request = match tx_output {
961                        TxOutput::Internal(output) => {
962                            Request::BtcSignOutput(pb::BtcSignOutputRequest {
963                                ours: true,
964                                value: output.value,
965                                keypath: output.keypath.to_vec(),
966                                script_config_index: output.script_config_index,
967                                ..Default::default()
968                            })
969                        }
970                        TxOutput::External(output) => {
971                            Request::BtcSignOutput(pb::BtcSignOutputRequest {
972                                ours: false,
973                                value: output.value,
974                                r#type: output.payload.output_type as _,
975                                payload: output.payload.data.clone(),
976                                ..Default::default()
977                            })
978                        }
979                    };
980                    next_response = self.get_next_response(request).await?;
981                }
982                pb::btc_sign_next_response::Type::Done => break,
983                pb::btc_sign_next_response::Type::HostNonce => {
984                    return Err(Error::UnexpectedResponse);
985                }
986                _ => return Err(Error::UnexpectedResponse),
987            }
988        }
989        Ok(sigs)
990    }
991
992    /// Sign a PSBT.
993    ///
994    /// If `force_script_config` is None, we attempt to infer the involved script configs. For the
995    /// simple script config (single sig), we infer the script config from the involved redeem
996    /// scripts and provided derviation paths.
997    ///
998    /// Multisig and policy configs are currently not inferred and must be provided using
999    /// `force_script_config`.
1000    pub async fn btc_sign_psbt(
1001        &self,
1002        coin: pb::BtcCoin,
1003        psbt: &mut bitcoin::psbt::Psbt,
1004        force_script_config: Option<pb::BtcScriptConfigWithKeypath>,
1005        format_unit: pb::btc_sign_init_request::FormatUnit,
1006    ) -> Result<(), Error> {
1007        // since v9.15.0, the BitBox02 accepts "internal" outputs (ones sent to the BitBox02 with
1008        // the keypath) even if the keypath is not a change keypath. PSBTs often contain the key
1009        // origin info in outputs even in regular send-to-self outputs.
1010        self.validate_version(">=9.15.0")?;
1011
1012        let our_root_fingerprint = hex::decode(self.root_fingerprint().await?).unwrap();
1013        let (transaction, our_keys) =
1014            Transaction::from_psbt(&our_root_fingerprint, psbt, force_script_config)?;
1015        let signatures = self.btc_sign(coin, &transaction, format_unit).await?;
1016        for (psbt_input, (signature, our_key)) in
1017            psbt.inputs.iter_mut().zip(signatures.iter().zip(our_keys))
1018        {
1019            match our_key {
1020                OurKey::Segwit(pubkey, _) => {
1021                    psbt_input.partial_sigs.insert(
1022                        bitcoin::PublicKey::new(pubkey),
1023                        bitcoin::ecdsa::Signature {
1024                            signature: bitcoin::secp256k1::ecdsa::Signature::from_compact(
1025                                signature,
1026                            )
1027                            .map_err(|_| Error::InvalidSignature)?,
1028                            sighash_type: bitcoin::sighash::EcdsaSighashType::All,
1029                        },
1030                    );
1031                }
1032                OurKey::TaprootInternal(_) => {
1033                    psbt_input.tap_key_sig = Some(
1034                        bitcoin::taproot::Signature::from_slice(signature)
1035                            .map_err(|_| Error::InvalidSignature)?,
1036                    );
1037                }
1038                OurKey::TaprootScript(xonly, leaf_hash, _) => {
1039                    let sig = bitcoin::taproot::Signature::from_slice(signature)
1040                        .map_err(|_| Error::InvalidSignature)?;
1041                    psbt_input.tap_script_sigs.insert((xonly, leaf_hash), sig);
1042                }
1043            }
1044        }
1045        Ok(())
1046    }
1047
1048    /// Sign a message.
1049    pub async fn btc_sign_message(
1050        &self,
1051        coin: pb::BtcCoin,
1052        script_config: pb::BtcScriptConfigWithKeypath,
1053        msg: &[u8],
1054    ) -> Result<SignMessageSignature, Error> {
1055        self.validate_version(">=9.5.0")?;
1056
1057        let host_nonce = crate::antiklepto::gen_host_nonce()?;
1058        let request = pb::BtcSignMessageRequest {
1059            coin: coin as _,
1060            script_config: Some(script_config),
1061            msg: msg.to_vec(),
1062            host_nonce_commitment: Some(pb::AntiKleptoHostNonceCommitment {
1063                commitment: crate::antiklepto::host_commit(&host_nonce).to_vec(),
1064            }),
1065        };
1066
1067        let response = self
1068            .query_proto_btc(pb::btc_request::Request::SignMessage(request))
1069            .await?;
1070        let signer_commitment = match response {
1071            pb::btc_response::Response::AntikleptoSignerCommitment(
1072                pb::AntiKleptoSignerCommitment { commitment },
1073            ) => commitment,
1074            _ => return Err(Error::UnexpectedResponse),
1075        };
1076
1077        let request = pb::AntiKleptoSignatureRequest {
1078            host_nonce: host_nonce.to_vec(),
1079        };
1080
1081        let response = self
1082            .query_proto_btc(pb::btc_request::Request::AntikleptoSignature(request))
1083            .await?;
1084        let signature = match response {
1085            pb::btc_response::Response::SignMessage(pb::BtcSignMessageResponse { signature }) => {
1086                signature
1087            }
1088            _ => return Err(Error::UnexpectedResponse),
1089        };
1090        crate::antiklepto::verify_ecdsa(&host_nonce, &signer_commitment, &signature)?;
1091
1092        let sig = signature[..64].to_vec();
1093        let recid = signature[64];
1094        let compressed: u8 = 4; // BitBox02 uses only compressed pubkeys
1095        let sig65: u8 = 27 + compressed + recid;
1096        let mut electrum_sig65 = vec![sig65];
1097        electrum_sig65.extend_from_slice(&sig);
1098        Ok(SignMessageSignature {
1099            sig,
1100            recid,
1101            electrum_sig65,
1102        })
1103    }
1104
1105    /// Before a multisig or policy script config can be used to display receive addresses or sign
1106    /// transactions, it must be registered on the device. This function checks if the script config
1107    /// was already registered.
1108    ///
1109    /// `keypath_account` must be set if the script config is multisig, and can be `None` if it is a
1110    /// policy.
1111    pub async fn btc_is_script_config_registered(
1112        &self,
1113        coin: pb::BtcCoin,
1114        script_config: &pb::BtcScriptConfig,
1115        keypath_account: Option<&Keypath>,
1116    ) -> Result<bool, Error> {
1117        match self
1118            .query_proto_btc(pb::btc_request::Request::IsScriptConfigRegistered(
1119                pb::BtcIsScriptConfigRegisteredRequest {
1120                    registration: Some(pb::BtcScriptConfigRegistration {
1121                        coin: coin as _,
1122                        script_config: Some(script_config.clone()),
1123                        keypath: keypath_account.map_or(vec![], |kp| kp.to_vec()),
1124                    }),
1125                },
1126            ))
1127            .await?
1128        {
1129            pb::btc_response::Response::IsScriptConfigRegistered(response) => {
1130                Ok(response.is_registered)
1131            }
1132            _ => Err(Error::UnexpectedResponse),
1133        }
1134    }
1135
1136    /// Before a multisig or policy script config can be used to display receive addresses or sign
1137    /// transcations, it must be registered on the device.
1138    ///
1139    /// If no name is provided, the user will be asked to enter it on the device instead.  If
1140    /// provided, it must be non-empty, smaller or equal to 30 chars, consist only of printable
1141    /// ASCII characters, and contain no whitespace other than spaces.
1142    ///
1143    ///
1144    /// `keypath_account` must be set if the script config is multisig, and can be `None` if it is a
1145    /// policy.
1146    pub async fn btc_register_script_config(
1147        &self,
1148        coin: pb::BtcCoin,
1149        script_config: &pb::BtcScriptConfig,
1150        keypath_account: Option<&Keypath>,
1151        xpub_type: pb::btc_register_script_config_request::XPubType,
1152        name: Option<&str>,
1153    ) -> Result<(), Error> {
1154        match self
1155            .query_proto_btc(pb::btc_request::Request::RegisterScriptConfig(
1156                pb::BtcRegisterScriptConfigRequest {
1157                    registration: Some(pb::BtcScriptConfigRegistration {
1158                        coin: coin as _,
1159                        script_config: Some(script_config.clone()),
1160                        keypath: keypath_account.map_or(vec![], |kp| kp.to_vec()),
1161                    }),
1162                    name: name.unwrap_or("").into(),
1163                    xpub_type: xpub_type as _,
1164                },
1165            ))
1166            .await?
1167        {
1168            pb::btc_response::Response::Success(_) => Ok(()),
1169            _ => Err(Error::UnexpectedResponse),
1170        }
1171    }
1172}
1173
1174#[cfg(test)]
1175mod tests {
1176    use super::*;
1177    use crate::keypath::HARDENED;
1178
1179    #[test]
1180    fn test_payload_from_pkscript() {
1181        use std::str::FromStr;
1182        // P2PKH
1183        let addr = bitcoin::Address::from_str("1AMZK8xzHJWsuRErpGZTiW4jKz8fdfLUGE")
1184            .unwrap()
1185            .assume_checked();
1186        let pkscript = addr.script_pubkey().into_bytes();
1187        assert_eq!(
1188            Payload::from_pkscript(&pkscript).unwrap(),
1189            Payload {
1190                data: pkscript[3..23].to_vec(),
1191                output_type: pb::BtcOutputType::P2pkh,
1192            }
1193        );
1194
1195        // P2SH
1196        let addr = bitcoin::Address::from_str("3JFL8CgtV4ZtMFYeP5LgV4JppLkHw5Gw9T")
1197            .unwrap()
1198            .assume_checked();
1199        let pkscript = addr.script_pubkey().into_bytes();
1200        assert_eq!(
1201            Payload::from_pkscript(&pkscript).unwrap(),
1202            Payload {
1203                data: pkscript[2..22].to_vec(),
1204                output_type: pb::BtcOutputType::P2sh,
1205            }
1206        );
1207
1208        // P2WPKH
1209        let addr = bitcoin::Address::from_str("bc1qkl8ms75cq6ajxtny7e88z3u9hkpkvktt5jwh6u")
1210            .unwrap()
1211            .assume_checked();
1212        let pkscript = addr.script_pubkey().into_bytes();
1213        assert_eq!(
1214            Payload::from_pkscript(&pkscript).unwrap(),
1215            Payload {
1216                data: pkscript[2..].to_vec(),
1217                output_type: pb::BtcOutputType::P2wpkh,
1218            }
1219        );
1220
1221        // P2WSH
1222        let addr = bitcoin::Address::from_str(
1223            "bc1q2fhgukymf0caaqrhfxrdju4wm94wwrch2ukntl5fuc0faz8zm49q0h6ss8",
1224        )
1225        .unwrap()
1226        .assume_checked();
1227        let pkscript = addr.script_pubkey().into_bytes();
1228        assert_eq!(
1229            Payload::from_pkscript(&pkscript).unwrap(),
1230            Payload {
1231                data: pkscript[2..].to_vec(),
1232                output_type: pb::BtcOutputType::P2wsh,
1233            }
1234        );
1235
1236        // P2TR
1237        let addr = bitcoin::Address::from_str(
1238            "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr",
1239        )
1240        .unwrap()
1241        .assume_checked();
1242        let pkscript = addr.script_pubkey().into_bytes();
1243        assert_eq!(
1244            Payload::from_pkscript(&pkscript).unwrap(),
1245            Payload {
1246                data: pkscript[2..].to_vec(),
1247                output_type: pb::BtcOutputType::P2tr,
1248            }
1249        );
1250
1251        // OP_RETURN empty (OP_0)
1252        let pkscript = hex::decode("6a00").unwrap();
1253        assert_eq!(
1254            Payload::from_pkscript(&pkscript).unwrap(),
1255            Payload {
1256                data: Vec::new(),
1257                output_type: pb::BtcOutputType::OpReturn,
1258            }
1259        );
1260
1261        // OP_RETURN with data push
1262        let pkscript = hex::decode("6a03aabbcc").unwrap();
1263        assert_eq!(
1264            Payload::from_pkscript(&pkscript).unwrap(),
1265            Payload {
1266                data: vec![0xaa, 0xbb, 0xcc],
1267                output_type: pb::BtcOutputType::OpReturn,
1268            }
1269        );
1270
1271        // OP_RETURN with 80-byte payload (PUSHDATA1)
1272        let mut pkscript = vec![opcodes::all::OP_RETURN.to_u8(), 0x4c, 0x50];
1273        pkscript.extend(std::iter::repeat_n(0xaa, 80));
1274        assert_eq!(
1275            Payload::from_pkscript(&pkscript).unwrap(),
1276            Payload {
1277                data: vec![0xaa; 80],
1278                output_type: pb::BtcOutputType::OpReturn,
1279            }
1280        );
1281
1282        // Invalid OP_RETURN scripts
1283        let pkscript = hex::decode("6a").unwrap();
1284        assert!(matches!(
1285            Payload::from_pkscript(&pkscript),
1286            Err(PayloadError::InvalidOpReturn(
1287                "naked OP_RETURN is not supported"
1288            ))
1289        ));
1290
1291        let pkscript = hex::decode("6a6a").unwrap();
1292        assert!(matches!(
1293            Payload::from_pkscript(&pkscript),
1294            Err(PayloadError::InvalidOpReturn(
1295                "no data push found after OP_RETURN"
1296            ))
1297        ));
1298
1299        let pkscript = hex::decode("6a0000").unwrap();
1300        assert!(matches!(
1301            Payload::from_pkscript(&pkscript),
1302            Err(PayloadError::InvalidOpReturn(
1303                "only one data push supported after OP_RETURN"
1304            ))
1305        ));
1306    }
1307
1308    // Test that a PSBT containing only p2wpkh inputs is converted correctly to a transaction to be
1309    // signed by the BitBox.
1310    #[test]
1311    fn test_transaction_from_psbt_p2wpkh() {
1312        use std::str::FromStr;
1313
1314        // Based on mnemonic:
1315        // route glue else try obey local kidney future teach unaware pulse exclude.
1316        let psbt_str = "cHNidP8BAHECAAAAAfbXTun4YYxDroWyzRq3jDsWFVlsZ7HUzxiORY/iR4goAAAAAAD9////AuLCAAAAAAAAFgAUg3w5W0zt3AmxRmgA5Q6wZJUDRhUowwAAAAAAABYAFJjQqUoXDcwUEqfExu9pnaSn5XBct0ElAAABAR+ghgEAAAAAABYAFHn03igII+hp819N2Zlb5LnN8atRAQDfAQAAAAABAZ9EJlMJnXF5bFVrb1eFBYrEev3pg35WpvS3RlELsMMrAQAAAAD9////AqCGAQAAAAAAFgAUefTeKAgj6GnzX03ZmVvkuc3xq1EoRs4JAAAAABYAFKG2PzjYjknaA6lmXFqPaSgHwXX9AkgwRQIhAL0v0r3LisQ9KOlGzMhM/xYqUmrv2a5sORRlkX1fqDC8AiB9XqxSNEdb4mPnp7ylF1cAlbAZ7jMhgIxHUXylTww3bwEhA0AEOM0yYEpexPoKE3vT51uxZ+8hk9sOEfBFKOeo6oDDAAAAACIGAyNQfmAT/YLmZaxxfDwClmVNt2BkFnfQu/i8Uc/hHDUiGBKiwYlUAACAAQAAgAAAAIAAAAAAAAAAAAAAIgIDnxFM7Qr9LvJwQDB9GozdTRIe3MYVuHOqT7dU2EuvHrIYEqLBiVQAAIABAACAAAAAgAEAAAAAAAAAAA==";
1317
1318        let expected_transaction = Transaction {
1319            script_configs: vec![pb::BtcScriptConfigWithKeypath {
1320                script_config: Some(pb::BtcScriptConfig {
1321                    config: Some(pb::btc_script_config::Config::SimpleType(
1322                        pb::btc_script_config::SimpleType::P2wpkh as _,
1323                    )),
1324                }),
1325                keypath: vec![84 + HARDENED, 1 + HARDENED, HARDENED],
1326            }],
1327            version: 2,
1328            inputs: vec![TxInput {
1329                prev_out_hash: vec![
1330                    246, 215, 78, 233, 248, 97, 140, 67, 174, 133, 178, 205, 26, 183, 140, 59, 22,
1331                    21, 89, 108, 103, 177, 212, 207, 24, 142, 69, 143, 226, 71, 136, 40,
1332                ],
1333                prev_out_index: 0,
1334                prev_out_value: 100000,
1335                sequence: 4294967293,
1336                keypath: "m/84'/1'/0'/0/0".try_into().unwrap(),
1337                script_config_index: 0,
1338                prev_tx: Some(PrevTx {
1339                    version: 1,
1340                    inputs: vec![PrevTxInput {
1341                        prev_out_hash: vec![
1342                            159, 68, 38, 83, 9, 157, 113, 121, 108, 85, 107, 111, 87, 133, 5, 138,
1343                            196, 122, 253, 233, 131, 126, 86, 166, 244, 183, 70, 81, 11, 176, 195,
1344                            43,
1345                        ],
1346                        prev_out_index: 1,
1347                        signature_script: vec![],
1348                        sequence: 4294967293,
1349                    }],
1350                    outputs: vec![
1351                        PrevTxOutput {
1352                            value: 100000,
1353                            pubkey_script: vec![
1354                                0, 20, 121, 244, 222, 40, 8, 35, 232, 105, 243, 95, 77, 217, 153,
1355                                91, 228, 185, 205, 241, 171, 81,
1356                            ],
1357                        },
1358                        PrevTxOutput {
1359                            value: 164513320,
1360                            pubkey_script: vec![
1361                                0, 20, 161, 182, 63, 56, 216, 142, 73, 218, 3, 169, 102, 92, 90,
1362                                143, 105, 40, 7, 193, 117, 253,
1363                            ],
1364                        },
1365                    ],
1366                    locktime: 0,
1367                }),
1368            }],
1369            outputs: vec![
1370                TxOutput::External(TxExternalOutput {
1371                    payload: Payload {
1372                        data: vec![
1373                            131, 124, 57, 91, 76, 237, 220, 9, 177, 70, 104, 0, 229, 14, 176, 100,
1374                            149, 3, 70, 21,
1375                        ],
1376                        output_type: pb::BtcOutputType::P2wpkh,
1377                    },
1378                    value: 49890,
1379                }),
1380                TxOutput::Internal(TxInternalOutput {
1381                    keypath: "m/84'/1'/0'/1/0".try_into().unwrap(),
1382                    value: 49960,
1383                    script_config_index: 0,
1384                }),
1385            ],
1386            locktime: 2441655,
1387        };
1388        let our_root_fingerprint = hex::decode("12a2c189").unwrap();
1389        let psbt = bitcoin::psbt::Psbt::from_str(psbt_str).unwrap();
1390        let (transaction, _our_keys) =
1391            Transaction::from_psbt(&our_root_fingerprint, &psbt, None).unwrap();
1392        assert_eq!(transaction, expected_transaction);
1393    }
1394}