Skip to main content

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        let _api_call = self.begin_api_call().await;
690        self.btc_xpub_inner(coin, keypath, xpub_type, display).await
691    }
692
693    async fn btc_xpub_inner(
694        &self,
695        coin: pb::BtcCoin,
696        keypath: &Keypath,
697        xpub_type: pb::btc_pub_request::XPubType,
698        display: bool,
699    ) -> Result<String, Error> {
700        match self
701            .query_proto(Request::BtcPub(pb::BtcPubRequest {
702                coin: coin as _,
703                keypath: keypath.to_vec(),
704                display,
705                output: Some(pb::btc_pub_request::Output::XpubType(xpub_type as _)),
706            }))
707            .await?
708        {
709            Response::Pub(pb::PubResponse { r#pub }) => Ok(r#pub),
710            _ => Err(Error::UnexpectedResponse),
711        }
712    }
713
714    /// Retrieves multiple xpubs at once. Only standard keypaths are allowed.
715    /// On firmware <v9.24.0, this falls back to fetching each xpub one-by-one.
716    pub async fn btc_xpubs(
717        &self,
718        coin: pb::BtcCoin,
719        keypaths: &[Keypath],
720        xpub_type: pb::btc_xpubs_request::XPubType,
721    ) -> Result<Vec<String>, Error> {
722        let _api_call = self.begin_api_call().await;
723        if self.validate_version(">=9.24.0").is_err() {
724            // Fallback to fetching them one-by-one on older firmware.
725            let mut xpubs = Vec::<String>::with_capacity(keypaths.len());
726            for keypath in keypaths {
727                let converted_xpub_type = match xpub_type {
728                    pb::btc_xpubs_request::XPubType::Unknown => return Err(Error::Unknown),
729                    pb::btc_xpubs_request::XPubType::Tpub => pb::btc_pub_request::XPubType::Tpub,
730                    pb::btc_xpubs_request::XPubType::Xpub => pb::btc_pub_request::XPubType::Xpub,
731                };
732                let xpub = self
733                    .btc_xpub_inner(coin, keypath, converted_xpub_type, false)
734                    .await?;
735                xpubs.push(xpub);
736            }
737            return Ok(xpubs);
738        }
739        match self
740            .query_proto_btc(pb::btc_request::Request::Xpubs(pb::BtcXpubsRequest {
741                coin: coin as _,
742                xpub_type: xpub_type as _,
743                keypaths: keypaths.iter().map(|kp| kp.into()).collect(),
744            }))
745            .await?
746        {
747            pb::btc_response::Response::Pubs(pb::PubsResponse { pubs }) => Ok(pubs),
748            _ => Err(Error::UnexpectedResponse),
749        }
750    }
751
752    /// Retrieves a Bitcoin address at the provided keypath.
753    ///
754    /// For the simple script configs (single-sig), the keypath must follow the
755    /// BIP44/BIP49/BIP84/BIP86 conventions.
756    pub async fn btc_address(
757        &self,
758        coin: pb::BtcCoin,
759        keypath: &Keypath,
760        script_config: &pb::BtcScriptConfig,
761        display: bool,
762    ) -> Result<String, Error> {
763        let _api_call = self.begin_api_call().await;
764        match self
765            .query_proto(Request::BtcPub(pb::BtcPubRequest {
766                coin: coin as _,
767                keypath: keypath.to_vec(),
768                display,
769                output: Some(pb::btc_pub_request::Output::ScriptConfig(
770                    script_config.clone(),
771                )),
772            }))
773            .await?
774        {
775            Response::Pub(pb::PubResponse { r#pub }) => Ok(r#pub),
776            _ => Err(Error::UnexpectedResponse),
777        }
778    }
779
780    async fn query_proto_btc(
781        &self,
782        request: pb::btc_request::Request,
783    ) -> Result<pb::btc_response::Response, Error> {
784        match self
785            .query_proto(Request::Btc(pb::BtcRequest {
786                request: Some(request),
787            }))
788            .await?
789        {
790            Response::Btc(pb::BtcResponse {
791                response: Some(response),
792            }) => Ok(response),
793            _ => Err(Error::UnexpectedResponse),
794        }
795    }
796
797    async fn get_next_response(&self, request: Request) -> Result<pb::BtcSignNextResponse, Error> {
798        match self.query_proto(request).await? {
799            Response::BtcSignNext(next_response) => Ok(next_response),
800            _ => Err(Error::UnexpectedResponse),
801        }
802    }
803
804    async fn get_next_response_nested(
805        &self,
806        request: pb::btc_request::Request,
807    ) -> Result<pb::BtcSignNextResponse, Error> {
808        match self.query_proto_btc(request).await? {
809            pb::btc_response::Response::SignNext(next_response) => Ok(next_response),
810            _ => Err(Error::UnexpectedResponse),
811        }
812    }
813
814    /// Sign a Bitcoin transaction. Returns one 64 byte signature (compact serlization of the R and
815    /// S values) per input.
816    pub async fn btc_sign(
817        &self,
818        coin: pb::BtcCoin,
819        transaction: &Transaction,
820        format_unit: pb::btc_sign_init_request::FormatUnit,
821    ) -> Result<Vec<Vec<u8>>, Error> {
822        let _api_call = self.begin_api_call().await;
823        self.btc_sign_inner(coin, transaction, format_unit).await
824    }
825
826    async fn btc_sign_inner(
827        &self,
828        coin: pb::BtcCoin,
829        transaction: &Transaction,
830        format_unit: pb::btc_sign_init_request::FormatUnit,
831    ) -> Result<Vec<Vec<u8>>, Error> {
832        self.validate_version(">=9.4.0")?; // anti-klepto since 9.4.0
833        if transaction.script_configs.iter().any(is_taproot_simple) {
834            self.validate_version(">=9.10.0")?; // taproot since 9.10.0
835        }
836        if transaction.outputs.iter().any(|output| {
837            matches!(
838                output,
839                TxOutput::External(tx_output)
840                    if tx_output.payload.output_type == pb::BtcOutputType::OpReturn
841            )
842        }) {
843            self.validate_version(">=9.24.0")?;
844        }
845
846        let mut sigs: Vec<Vec<u8>> = Vec::new();
847
848        let mut next_response = self
849            .get_next_response(Request::BtcSignInit(pb::BtcSignInitRequest {
850                coin: coin as _,
851                script_configs: transaction.script_configs.clone(),
852                output_script_configs: vec![],
853                version: transaction.version,
854                num_inputs: transaction.inputs.len() as _,
855                num_outputs: transaction.outputs.len() as _,
856                locktime: transaction.locktime,
857                format_unit: format_unit as _,
858                contains_silent_payment_outputs: false,
859            }))
860            .await?;
861
862        let mut is_inputs_pass2 = false;
863        loop {
864            match pb::btc_sign_next_response::Type::try_from(next_response.r#type)
865                .map_err(|_| Error::UnexpectedResponse)?
866            {
867                pb::btc_sign_next_response::Type::Input => {
868                    let input_index: usize = next_response.index as _;
869                    let tx_input: &TxInput = &transaction.inputs[input_index];
870
871                    let input_is_schnorr = is_schnorr(
872                        &transaction.script_configs[tx_input.script_config_index as usize],
873                    );
874                    let perform_antiklepto = is_inputs_pass2 && !input_is_schnorr;
875                    let host_nonce = if perform_antiklepto {
876                        Some(crate::antiklepto::gen_host_nonce()?)
877                    } else {
878                        None
879                    };
880                    next_response = self
881                        .get_next_response(Request::BtcSignInput(pb::BtcSignInputRequest {
882                            prev_out_hash: tx_input.prev_out_hash.clone(),
883                            prev_out_index: tx_input.prev_out_index,
884                            prev_out_value: tx_input.prev_out_value,
885                            sequence: tx_input.sequence,
886                            keypath: tx_input.keypath.to_vec(),
887                            script_config_index: tx_input.script_config_index,
888                            host_nonce_commitment: host_nonce.as_ref().map(|host_nonce| {
889                                pb::AntiKleptoHostNonceCommitment {
890                                    commitment: crate::antiklepto::host_commit(host_nonce).to_vec(),
891                                }
892                            }),
893                        }))
894                        .await?;
895
896                    if let Some(host_nonce) = host_nonce {
897                        if next_response.r#type
898                            != pb::btc_sign_next_response::Type::HostNonce as i32
899                        {
900                            return Err(Error::UnexpectedResponse);
901                        }
902                        if let Some(pb::AntiKleptoSignerCommitment { commitment }) =
903                            next_response.anti_klepto_signer_commitment
904                        {
905                            next_response = self
906                                .get_next_response_nested(
907                                    pb::btc_request::Request::AntikleptoSignature(
908                                        pb::AntiKleptoSignatureRequest {
909                                            host_nonce: host_nonce.to_vec(),
910                                        },
911                                    ),
912                                )
913                                .await?;
914                            if !next_response.has_signature {
915                                return Err(Error::UnexpectedResponse);
916                            }
917                            crate::antiklepto::verify_ecdsa(
918                                &host_nonce,
919                                &commitment,
920                                &next_response.signature,
921                            )?
922                        } else {
923                            return Err(Error::UnexpectedResponse);
924                        }
925                    }
926
927                    if is_inputs_pass2 {
928                        if !next_response.has_signature {
929                            return Err(Error::UnexpectedResponse);
930                        }
931                        sigs.push(next_response.signature.clone());
932                    }
933                    if input_index == transaction.inputs.len() - 1 {
934                        is_inputs_pass2 = true
935                    }
936                }
937                pb::btc_sign_next_response::Type::PrevtxInit => {
938                    let prevtx: &PrevTx =
939                        transaction.inputs[next_response.index as usize].get_prev_tx()?;
940                    next_response = self
941                        .get_next_response_nested(pb::btc_request::Request::PrevtxInit(
942                            pb::BtcPrevTxInitRequest {
943                                version: prevtx.version,
944                                num_inputs: prevtx.inputs.len() as _,
945                                num_outputs: prevtx.outputs.len() as _,
946                                locktime: prevtx.locktime,
947                            },
948                        ))
949                        .await?;
950                }
951                pb::btc_sign_next_response::Type::PrevtxInput => {
952                    let prevtx: &PrevTx =
953                        transaction.inputs[next_response.index as usize].get_prev_tx()?;
954                    let prevtx_input: &PrevTxInput =
955                        &prevtx.inputs[next_response.prev_index as usize];
956                    next_response = self
957                        .get_next_response_nested(pb::btc_request::Request::PrevtxInput(
958                            pb::BtcPrevTxInputRequest {
959                                prev_out_hash: prevtx_input.prev_out_hash.clone(),
960                                prev_out_index: prevtx_input.prev_out_index,
961                                signature_script: prevtx_input.signature_script.clone(),
962                                sequence: prevtx_input.sequence,
963                            },
964                        ))
965                        .await?;
966                }
967                pb::btc_sign_next_response::Type::PrevtxOutput => {
968                    let prevtx: &PrevTx =
969                        transaction.inputs[next_response.index as usize].get_prev_tx()?;
970                    let prevtx_output: &PrevTxOutput =
971                        &prevtx.outputs[next_response.prev_index as usize];
972                    next_response = self
973                        .get_next_response_nested(pb::btc_request::Request::PrevtxOutput(
974                            pb::BtcPrevTxOutputRequest {
975                                value: prevtx_output.value,
976                                pubkey_script: prevtx_output.pubkey_script.clone(),
977                            },
978                        ))
979                        .await?;
980                }
981                pb::btc_sign_next_response::Type::Output => {
982                    let tx_output: &TxOutput = &transaction.outputs[next_response.index as usize];
983                    let request: Request = match tx_output {
984                        TxOutput::Internal(output) => {
985                            Request::BtcSignOutput(pb::BtcSignOutputRequest {
986                                ours: true,
987                                value: output.value,
988                                keypath: output.keypath.to_vec(),
989                                script_config_index: output.script_config_index,
990                                ..Default::default()
991                            })
992                        }
993                        TxOutput::External(output) => {
994                            Request::BtcSignOutput(pb::BtcSignOutputRequest {
995                                ours: false,
996                                value: output.value,
997                                r#type: output.payload.output_type as _,
998                                payload: output.payload.data.clone(),
999                                ..Default::default()
1000                            })
1001                        }
1002                    };
1003                    next_response = self.get_next_response(request).await?;
1004                }
1005                pb::btc_sign_next_response::Type::Done => break,
1006                pb::btc_sign_next_response::Type::HostNonce => {
1007                    return Err(Error::UnexpectedResponse);
1008                }
1009                _ => return Err(Error::UnexpectedResponse),
1010            }
1011        }
1012        Ok(sigs)
1013    }
1014
1015    /// Sign a PSBT.
1016    ///
1017    /// If `force_script_config` is None, we attempt to infer the involved script configs. For the
1018    /// simple script config (single sig), we infer the script config from the involved redeem
1019    /// scripts and provided derviation paths.
1020    ///
1021    /// Multisig and policy configs are currently not inferred and must be provided using
1022    /// `force_script_config`.
1023    pub async fn btc_sign_psbt(
1024        &self,
1025        coin: pb::BtcCoin,
1026        psbt: &mut bitcoin::psbt::Psbt,
1027        force_script_config: Option<pb::BtcScriptConfigWithKeypath>,
1028        format_unit: pb::btc_sign_init_request::FormatUnit,
1029    ) -> Result<(), Error> {
1030        let _api_call = self.begin_api_call().await;
1031        // since v9.15.0, the BitBox02 accepts "internal" outputs (ones sent to the BitBox02 with
1032        // the keypath) even if the keypath is not a change keypath. PSBTs often contain the key
1033        // origin info in outputs even in regular send-to-self outputs.
1034        self.validate_version(">=9.15.0")?;
1035
1036        let our_root_fingerprint = self.root_fingerprint_inner().await?;
1037        let (transaction, our_keys) =
1038            Transaction::from_psbt(&our_root_fingerprint, psbt, force_script_config)?;
1039        let signatures = self.btc_sign_inner(coin, &transaction, format_unit).await?;
1040        for (psbt_input, (signature, our_key)) in
1041            psbt.inputs.iter_mut().zip(signatures.iter().zip(our_keys))
1042        {
1043            match our_key {
1044                OurKey::Segwit(pubkey, _) => {
1045                    psbt_input.partial_sigs.insert(
1046                        bitcoin::PublicKey::new(pubkey),
1047                        bitcoin::ecdsa::Signature {
1048                            signature: bitcoin::secp256k1::ecdsa::Signature::from_compact(
1049                                signature,
1050                            )
1051                            .map_err(|_| Error::InvalidSignature)?,
1052                            sighash_type: bitcoin::sighash::EcdsaSighashType::All,
1053                        },
1054                    );
1055                }
1056                OurKey::TaprootInternal(_) => {
1057                    psbt_input.tap_key_sig = Some(
1058                        bitcoin::taproot::Signature::from_slice(signature)
1059                            .map_err(|_| Error::InvalidSignature)?,
1060                    );
1061                }
1062                OurKey::TaprootScript(xonly, leaf_hash, _) => {
1063                    let sig = bitcoin::taproot::Signature::from_slice(signature)
1064                        .map_err(|_| Error::InvalidSignature)?;
1065                    psbt_input.tap_script_sigs.insert((xonly, leaf_hash), sig);
1066                }
1067            }
1068        }
1069        Ok(())
1070    }
1071
1072    /// Sign a message.
1073    pub async fn btc_sign_message(
1074        &self,
1075        coin: pb::BtcCoin,
1076        script_config: pb::BtcScriptConfigWithKeypath,
1077        msg: &[u8],
1078    ) -> Result<SignMessageSignature, Error> {
1079        let _api_call = self.begin_api_call().await;
1080        self.validate_version(">=9.5.0")?;
1081
1082        let host_nonce = crate::antiklepto::gen_host_nonce()?;
1083        let request = pb::BtcSignMessageRequest {
1084            coin: coin as _,
1085            script_config: Some(script_config),
1086            msg: msg.to_vec(),
1087            host_nonce_commitment: Some(pb::AntiKleptoHostNonceCommitment {
1088                commitment: crate::antiklepto::host_commit(&host_nonce).to_vec(),
1089            }),
1090        };
1091
1092        let response = self
1093            .query_proto_btc(pb::btc_request::Request::SignMessage(request))
1094            .await?;
1095        let signer_commitment = match response {
1096            pb::btc_response::Response::AntikleptoSignerCommitment(
1097                pb::AntiKleptoSignerCommitment { commitment },
1098            ) => commitment,
1099            _ => return Err(Error::UnexpectedResponse),
1100        };
1101
1102        let request = pb::AntiKleptoSignatureRequest {
1103            host_nonce: host_nonce.to_vec(),
1104        };
1105
1106        let response = self
1107            .query_proto_btc(pb::btc_request::Request::AntikleptoSignature(request))
1108            .await?;
1109        let signature = match response {
1110            pb::btc_response::Response::SignMessage(pb::BtcSignMessageResponse { signature }) => {
1111                signature
1112            }
1113            _ => return Err(Error::UnexpectedResponse),
1114        };
1115        crate::antiklepto::verify_ecdsa(&host_nonce, &signer_commitment, &signature)?;
1116
1117        let sig = signature[..64].to_vec();
1118        let recid = signature[64];
1119        let compressed: u8 = 4; // BitBox02 uses only compressed pubkeys
1120        let sig65: u8 = 27 + compressed + recid;
1121        let mut electrum_sig65 = vec![sig65];
1122        electrum_sig65.extend_from_slice(&sig);
1123        Ok(SignMessageSignature {
1124            sig,
1125            recid,
1126            electrum_sig65,
1127        })
1128    }
1129
1130    /// Before a multisig or policy script config can be used to display receive addresses or sign
1131    /// transactions, it must be registered on the device. This function checks if the script config
1132    /// was already registered.
1133    ///
1134    /// `keypath_account` must be set if the script config is multisig, and can be `None` if it is a
1135    /// policy.
1136    pub async fn btc_is_script_config_registered(
1137        &self,
1138        coin: pb::BtcCoin,
1139        script_config: &pb::BtcScriptConfig,
1140        keypath_account: Option<&Keypath>,
1141    ) -> Result<bool, Error> {
1142        let _api_call = self.begin_api_call().await;
1143        match self
1144            .query_proto_btc(pb::btc_request::Request::IsScriptConfigRegistered(
1145                pb::BtcIsScriptConfigRegisteredRequest {
1146                    registration: Some(pb::BtcScriptConfigRegistration {
1147                        coin: coin as _,
1148                        script_config: Some(script_config.clone()),
1149                        keypath: keypath_account.map_or(vec![], |kp| kp.to_vec()),
1150                    }),
1151                },
1152            ))
1153            .await?
1154        {
1155            pb::btc_response::Response::IsScriptConfigRegistered(response) => {
1156                Ok(response.is_registered)
1157            }
1158            _ => Err(Error::UnexpectedResponse),
1159        }
1160    }
1161
1162    /// Before a multisig or policy script config can be used to display receive addresses or sign
1163    /// transcations, it must be registered on the device.
1164    ///
1165    /// If no name is provided, the user will be asked to enter it on the device instead.  If
1166    /// provided, it must be non-empty, smaller or equal to 30 chars, consist only of printable
1167    /// ASCII characters, and contain no whitespace other than spaces.
1168    ///
1169    ///
1170    /// `keypath_account` must be set if the script config is multisig, and can be `None` if it is a
1171    /// policy.
1172    pub async fn btc_register_script_config(
1173        &self,
1174        coin: pb::BtcCoin,
1175        script_config: &pb::BtcScriptConfig,
1176        keypath_account: Option<&Keypath>,
1177        xpub_type: pb::btc_register_script_config_request::XPubType,
1178        name: Option<&str>,
1179    ) -> Result<(), Error> {
1180        let _api_call = self.begin_api_call().await;
1181        match self
1182            .query_proto_btc(pb::btc_request::Request::RegisterScriptConfig(
1183                pb::BtcRegisterScriptConfigRequest {
1184                    registration: Some(pb::BtcScriptConfigRegistration {
1185                        coin: coin as _,
1186                        script_config: Some(script_config.clone()),
1187                        keypath: keypath_account.map_or(vec![], |kp| kp.to_vec()),
1188                    }),
1189                    name: name.unwrap_or("").into(),
1190                    xpub_type: xpub_type as _,
1191                },
1192            ))
1193            .await?
1194        {
1195            pb::btc_response::Response::Success(_) => Ok(()),
1196            _ => Err(Error::UnexpectedResponse),
1197        }
1198    }
1199}
1200
1201#[cfg(test)]
1202mod tests {
1203    use super::*;
1204    use crate::keypath::HARDENED;
1205
1206    #[test]
1207    fn test_payload_from_pkscript() {
1208        use std::str::FromStr;
1209        // P2PKH
1210        let addr = bitcoin::Address::from_str("1AMZK8xzHJWsuRErpGZTiW4jKz8fdfLUGE")
1211            .unwrap()
1212            .assume_checked();
1213        let pkscript = addr.script_pubkey().into_bytes();
1214        assert_eq!(
1215            Payload::from_pkscript(&pkscript).unwrap(),
1216            Payload {
1217                data: pkscript[3..23].to_vec(),
1218                output_type: pb::BtcOutputType::P2pkh,
1219            }
1220        );
1221
1222        // P2SH
1223        let addr = bitcoin::Address::from_str("3JFL8CgtV4ZtMFYeP5LgV4JppLkHw5Gw9T")
1224            .unwrap()
1225            .assume_checked();
1226        let pkscript = addr.script_pubkey().into_bytes();
1227        assert_eq!(
1228            Payload::from_pkscript(&pkscript).unwrap(),
1229            Payload {
1230                data: pkscript[2..22].to_vec(),
1231                output_type: pb::BtcOutputType::P2sh,
1232            }
1233        );
1234
1235        // P2WPKH
1236        let addr = bitcoin::Address::from_str("bc1qkl8ms75cq6ajxtny7e88z3u9hkpkvktt5jwh6u")
1237            .unwrap()
1238            .assume_checked();
1239        let pkscript = addr.script_pubkey().into_bytes();
1240        assert_eq!(
1241            Payload::from_pkscript(&pkscript).unwrap(),
1242            Payload {
1243                data: pkscript[2..].to_vec(),
1244                output_type: pb::BtcOutputType::P2wpkh,
1245            }
1246        );
1247
1248        // P2WSH
1249        let addr = bitcoin::Address::from_str(
1250            "bc1q2fhgukymf0caaqrhfxrdju4wm94wwrch2ukntl5fuc0faz8zm49q0h6ss8",
1251        )
1252        .unwrap()
1253        .assume_checked();
1254        let pkscript = addr.script_pubkey().into_bytes();
1255        assert_eq!(
1256            Payload::from_pkscript(&pkscript).unwrap(),
1257            Payload {
1258                data: pkscript[2..].to_vec(),
1259                output_type: pb::BtcOutputType::P2wsh,
1260            }
1261        );
1262
1263        // P2TR
1264        let addr = bitcoin::Address::from_str(
1265            "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr",
1266        )
1267        .unwrap()
1268        .assume_checked();
1269        let pkscript = addr.script_pubkey().into_bytes();
1270        assert_eq!(
1271            Payload::from_pkscript(&pkscript).unwrap(),
1272            Payload {
1273                data: pkscript[2..].to_vec(),
1274                output_type: pb::BtcOutputType::P2tr,
1275            }
1276        );
1277
1278        // OP_RETURN empty (OP_0)
1279        let pkscript = hex::decode("6a00").unwrap();
1280        assert_eq!(
1281            Payload::from_pkscript(&pkscript).unwrap(),
1282            Payload {
1283                data: Vec::new(),
1284                output_type: pb::BtcOutputType::OpReturn,
1285            }
1286        );
1287
1288        // OP_RETURN with data push
1289        let pkscript = hex::decode("6a03aabbcc").unwrap();
1290        assert_eq!(
1291            Payload::from_pkscript(&pkscript).unwrap(),
1292            Payload {
1293                data: vec![0xaa, 0xbb, 0xcc],
1294                output_type: pb::BtcOutputType::OpReturn,
1295            }
1296        );
1297
1298        // OP_RETURN with 80-byte payload (PUSHDATA1)
1299        let mut pkscript = vec![opcodes::all::OP_RETURN.to_u8(), 0x4c, 0x50];
1300        pkscript.extend(std::iter::repeat_n(0xaa, 80));
1301        assert_eq!(
1302            Payload::from_pkscript(&pkscript).unwrap(),
1303            Payload {
1304                data: vec![0xaa; 80],
1305                output_type: pb::BtcOutputType::OpReturn,
1306            }
1307        );
1308
1309        // Invalid OP_RETURN scripts
1310        let pkscript = hex::decode("6a").unwrap();
1311        assert!(matches!(
1312            Payload::from_pkscript(&pkscript),
1313            Err(PayloadError::InvalidOpReturn(
1314                "naked OP_RETURN is not supported"
1315            ))
1316        ));
1317
1318        let pkscript = hex::decode("6a6a").unwrap();
1319        assert!(matches!(
1320            Payload::from_pkscript(&pkscript),
1321            Err(PayloadError::InvalidOpReturn(
1322                "no data push found after OP_RETURN"
1323            ))
1324        ));
1325
1326        let pkscript = hex::decode("6a0000").unwrap();
1327        assert!(matches!(
1328            Payload::from_pkscript(&pkscript),
1329            Err(PayloadError::InvalidOpReturn(
1330                "only one data push supported after OP_RETURN"
1331            ))
1332        ));
1333    }
1334
1335    // Test that a PSBT containing only p2wpkh inputs is converted correctly to a transaction to be
1336    // signed by the BitBox.
1337    #[test]
1338    fn test_transaction_from_psbt_p2wpkh() {
1339        use std::str::FromStr;
1340
1341        // Based on mnemonic:
1342        // route glue else try obey local kidney future teach unaware pulse exclude.
1343        let psbt_str = "cHNidP8BAHECAAAAAfbXTun4YYxDroWyzRq3jDsWFVlsZ7HUzxiORY/iR4goAAAAAAD9////AuLCAAAAAAAAFgAUg3w5W0zt3AmxRmgA5Q6wZJUDRhUowwAAAAAAABYAFJjQqUoXDcwUEqfExu9pnaSn5XBct0ElAAABAR+ghgEAAAAAABYAFHn03igII+hp819N2Zlb5LnN8atRAQDfAQAAAAABAZ9EJlMJnXF5bFVrb1eFBYrEev3pg35WpvS3RlELsMMrAQAAAAD9////AqCGAQAAAAAAFgAUefTeKAgj6GnzX03ZmVvkuc3xq1EoRs4JAAAAABYAFKG2PzjYjknaA6lmXFqPaSgHwXX9AkgwRQIhAL0v0r3LisQ9KOlGzMhM/xYqUmrv2a5sORRlkX1fqDC8AiB9XqxSNEdb4mPnp7ylF1cAlbAZ7jMhgIxHUXylTww3bwEhA0AEOM0yYEpexPoKE3vT51uxZ+8hk9sOEfBFKOeo6oDDAAAAACIGAyNQfmAT/YLmZaxxfDwClmVNt2BkFnfQu/i8Uc/hHDUiGBKiwYlUAACAAQAAgAAAAIAAAAAAAAAAAAAAIgIDnxFM7Qr9LvJwQDB9GozdTRIe3MYVuHOqT7dU2EuvHrIYEqLBiVQAAIABAACAAAAAgAEAAAAAAAAAAA==";
1344
1345        let expected_transaction = Transaction {
1346            script_configs: vec![pb::BtcScriptConfigWithKeypath {
1347                script_config: Some(pb::BtcScriptConfig {
1348                    config: Some(pb::btc_script_config::Config::SimpleType(
1349                        pb::btc_script_config::SimpleType::P2wpkh as _,
1350                    )),
1351                }),
1352                keypath: vec![84 + HARDENED, 1 + HARDENED, HARDENED],
1353            }],
1354            version: 2,
1355            inputs: vec![TxInput {
1356                prev_out_hash: vec![
1357                    246, 215, 78, 233, 248, 97, 140, 67, 174, 133, 178, 205, 26, 183, 140, 59, 22,
1358                    21, 89, 108, 103, 177, 212, 207, 24, 142, 69, 143, 226, 71, 136, 40,
1359                ],
1360                prev_out_index: 0,
1361                prev_out_value: 100000,
1362                sequence: 4294967293,
1363                keypath: "m/84'/1'/0'/0/0".try_into().unwrap(),
1364                script_config_index: 0,
1365                prev_tx: Some(PrevTx {
1366                    version: 1,
1367                    inputs: vec![PrevTxInput {
1368                        prev_out_hash: vec![
1369                            159, 68, 38, 83, 9, 157, 113, 121, 108, 85, 107, 111, 87, 133, 5, 138,
1370                            196, 122, 253, 233, 131, 126, 86, 166, 244, 183, 70, 81, 11, 176, 195,
1371                            43,
1372                        ],
1373                        prev_out_index: 1,
1374                        signature_script: vec![],
1375                        sequence: 4294967293,
1376                    }],
1377                    outputs: vec![
1378                        PrevTxOutput {
1379                            value: 100000,
1380                            pubkey_script: vec![
1381                                0, 20, 121, 244, 222, 40, 8, 35, 232, 105, 243, 95, 77, 217, 153,
1382                                91, 228, 185, 205, 241, 171, 81,
1383                            ],
1384                        },
1385                        PrevTxOutput {
1386                            value: 164513320,
1387                            pubkey_script: vec![
1388                                0, 20, 161, 182, 63, 56, 216, 142, 73, 218, 3, 169, 102, 92, 90,
1389                                143, 105, 40, 7, 193, 117, 253,
1390                            ],
1391                        },
1392                    ],
1393                    locktime: 0,
1394                }),
1395            }],
1396            outputs: vec![
1397                TxOutput::External(TxExternalOutput {
1398                    payload: Payload {
1399                        data: vec![
1400                            131, 124, 57, 91, 76, 237, 220, 9, 177, 70, 104, 0, 229, 14, 176, 100,
1401                            149, 3, 70, 21,
1402                        ],
1403                        output_type: pb::BtcOutputType::P2wpkh,
1404                    },
1405                    value: 49890,
1406                }),
1407                TxOutput::Internal(TxInternalOutput {
1408                    keypath: "m/84'/1'/0'/1/0".try_into().unwrap(),
1409                    value: 49960,
1410                    script_config_index: 0,
1411                }),
1412            ],
1413            locktime: 2441655,
1414        };
1415        let our_root_fingerprint = hex::decode("12a2c189").unwrap();
1416        let psbt = bitcoin::psbt::Psbt::from_str(psbt_str).unwrap();
1417        let (transaction, _our_keys) =
1418            Transaction::from_psbt(&our_root_fingerprint, &psbt, None).unwrap();
1419        assert_eq!(transaction, expected_transaction);
1420    }
1421}