bitbox_api/
btc.rs

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