1use 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 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#[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 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 return Err(PsbtError::KeyNotUnique);
358 }
359 return Ok(OurKey::TaprootInternal(derivation_path.into()));
360 }
361 }
362 if leaf_hashes.len() != 1 {
363 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 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 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 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
519pub 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
565pub 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
584pub 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 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 pub async fn btc_xpubs(
649 &self,
650 coin: pb::BtcCoin,
651 keypaths: &[Keypath],
652 xpub_type: pb::btc_xpubs_request::XPubType,
653 ) -> Result<Vec<String>, Error> {
654 if self.validate_version(">=9.24.0").is_err() {
655 let mut xpubs = Vec::<String>::with_capacity(keypaths.len());
657 for keypath in keypaths {
658 let converted_xpub_type = match xpub_type {
659 pb::btc_xpubs_request::XPubType::Unknown => return Err(Error::Unknown),
660 pb::btc_xpubs_request::XPubType::Tpub => pb::btc_pub_request::XPubType::Tpub,
661 pb::btc_xpubs_request::XPubType::Xpub => pb::btc_pub_request::XPubType::Xpub,
662 };
663 let xpub = self
664 .btc_xpub(coin, keypath, converted_xpub_type, false)
665 .await?;
666 xpubs.push(xpub);
667 }
668 return Ok(xpubs);
669 }
670 match self
671 .query_proto_btc(pb::btc_request::Request::Xpubs(pb::BtcXpubsRequest {
672 coin: coin as _,
673 xpub_type: xpub_type as _,
674 keypaths: keypaths.iter().map(|kp| kp.into()).collect(),
675 }))
676 .await?
677 {
678 pb::btc_response::Response::Pubs(pb::PubsResponse { pubs }) => Ok(pubs),
679 _ => Err(Error::UnexpectedResponse),
680 }
681 }
682
683 pub async fn btc_address(
688 &self,
689 coin: pb::BtcCoin,
690 keypath: &Keypath,
691 script_config: &pb::BtcScriptConfig,
692 display: bool,
693 ) -> Result<String, Error> {
694 match self
695 .query_proto(Request::BtcPub(pb::BtcPubRequest {
696 coin: coin as _,
697 keypath: keypath.to_vec(),
698 display,
699 output: Some(pb::btc_pub_request::Output::ScriptConfig(
700 script_config.clone(),
701 )),
702 }))
703 .await?
704 {
705 Response::Pub(pb::PubResponse { r#pub }) => Ok(r#pub),
706 _ => Err(Error::UnexpectedResponse),
707 }
708 }
709
710 async fn query_proto_btc(
711 &self,
712 request: pb::btc_request::Request,
713 ) -> Result<pb::btc_response::Response, Error> {
714 match self
715 .query_proto(Request::Btc(pb::BtcRequest {
716 request: Some(request),
717 }))
718 .await?
719 {
720 Response::Btc(pb::BtcResponse {
721 response: Some(response),
722 }) => Ok(response),
723 _ => Err(Error::UnexpectedResponse),
724 }
725 }
726
727 async fn get_next_response(&self, request: Request) -> Result<pb::BtcSignNextResponse, Error> {
728 match self.query_proto(request).await? {
729 Response::BtcSignNext(next_response) => Ok(next_response),
730 _ => Err(Error::UnexpectedResponse),
731 }
732 }
733
734 async fn get_next_response_nested(
735 &self,
736 request: pb::btc_request::Request,
737 ) -> Result<pb::BtcSignNextResponse, Error> {
738 match self.query_proto_btc(request).await? {
739 pb::btc_response::Response::SignNext(next_response) => Ok(next_response),
740 _ => Err(Error::UnexpectedResponse),
741 }
742 }
743
744 pub async fn btc_sign(
747 &self,
748 coin: pb::BtcCoin,
749 transaction: &Transaction,
750 format_unit: pb::btc_sign_init_request::FormatUnit,
751 ) -> Result<Vec<Vec<u8>>, Error> {
752 self.validate_version(">=9.4.0")?; if transaction.script_configs.iter().any(is_taproot_simple) {
754 self.validate_version(">=9.10.0")?; }
756
757 let mut sigs: Vec<Vec<u8>> = Vec::new();
758
759 let mut next_response = self
760 .get_next_response(Request::BtcSignInit(pb::BtcSignInitRequest {
761 coin: coin as _,
762 script_configs: transaction.script_configs.clone(),
763 output_script_configs: vec![],
764 version: transaction.version,
765 num_inputs: transaction.inputs.len() as _,
766 num_outputs: transaction.outputs.len() as _,
767 locktime: transaction.locktime,
768 format_unit: format_unit as _,
769 contains_silent_payment_outputs: false,
770 }))
771 .await?;
772
773 let mut is_inputs_pass2 = false;
774 loop {
775 match pb::btc_sign_next_response::Type::try_from(next_response.r#type)
776 .map_err(|_| Error::UnexpectedResponse)?
777 {
778 pb::btc_sign_next_response::Type::Input => {
779 let input_index: usize = next_response.index as _;
780 let tx_input: &TxInput = &transaction.inputs[input_index];
781
782 let input_is_schnorr = is_schnorr(
783 &transaction.script_configs[tx_input.script_config_index as usize],
784 );
785 let perform_antiklepto = is_inputs_pass2 && !input_is_schnorr;
786 let host_nonce = if perform_antiklepto {
787 Some(crate::antiklepto::gen_host_nonce()?)
788 } else {
789 None
790 };
791 next_response = self
792 .get_next_response(Request::BtcSignInput(pb::BtcSignInputRequest {
793 prev_out_hash: tx_input.prev_out_hash.clone(),
794 prev_out_index: tx_input.prev_out_index,
795 prev_out_value: tx_input.prev_out_value,
796 sequence: tx_input.sequence,
797 keypath: tx_input.keypath.to_vec(),
798 script_config_index: tx_input.script_config_index,
799 host_nonce_commitment: host_nonce.as_ref().map(|host_nonce| {
800 pb::AntiKleptoHostNonceCommitment {
801 commitment: crate::antiklepto::host_commit(host_nonce).to_vec(),
802 }
803 }),
804 }))
805 .await?;
806
807 if let Some(host_nonce) = host_nonce {
808 if next_response.r#type
809 != pb::btc_sign_next_response::Type::HostNonce as i32
810 {
811 return Err(Error::UnexpectedResponse);
812 }
813 if let Some(pb::AntiKleptoSignerCommitment { commitment }) =
814 next_response.anti_klepto_signer_commitment
815 {
816 next_response = self
817 .get_next_response_nested(
818 pb::btc_request::Request::AntikleptoSignature(
819 pb::AntiKleptoSignatureRequest {
820 host_nonce: host_nonce.to_vec(),
821 },
822 ),
823 )
824 .await?;
825 if !next_response.has_signature {
826 return Err(Error::UnexpectedResponse);
827 }
828 crate::antiklepto::verify_ecdsa(
829 &host_nonce,
830 &commitment,
831 &next_response.signature,
832 )?
833 } else {
834 return Err(Error::UnexpectedResponse);
835 }
836 }
837
838 if is_inputs_pass2 {
839 if !next_response.has_signature {
840 return Err(Error::UnexpectedResponse);
841 }
842 sigs.push(next_response.signature.clone());
843 }
844 if input_index == transaction.inputs.len() - 1 {
845 is_inputs_pass2 = true
846 }
847 }
848 pb::btc_sign_next_response::Type::PrevtxInit => {
849 let prevtx: &PrevTx =
850 transaction.inputs[next_response.index as usize].get_prev_tx()?;
851 next_response = self
852 .get_next_response_nested(pb::btc_request::Request::PrevtxInit(
853 pb::BtcPrevTxInitRequest {
854 version: prevtx.version,
855 num_inputs: prevtx.inputs.len() as _,
856 num_outputs: prevtx.outputs.len() as _,
857 locktime: prevtx.locktime,
858 },
859 ))
860 .await?;
861 }
862 pb::btc_sign_next_response::Type::PrevtxInput => {
863 let prevtx: &PrevTx =
864 transaction.inputs[next_response.index as usize].get_prev_tx()?;
865 let prevtx_input: &PrevTxInput =
866 &prevtx.inputs[next_response.prev_index as usize];
867 next_response = self
868 .get_next_response_nested(pb::btc_request::Request::PrevtxInput(
869 pb::BtcPrevTxInputRequest {
870 prev_out_hash: prevtx_input.prev_out_hash.clone(),
871 prev_out_index: prevtx_input.prev_out_index,
872 signature_script: prevtx_input.signature_script.clone(),
873 sequence: prevtx_input.sequence,
874 },
875 ))
876 .await?;
877 }
878 pb::btc_sign_next_response::Type::PrevtxOutput => {
879 let prevtx: &PrevTx =
880 transaction.inputs[next_response.index as usize].get_prev_tx()?;
881 let prevtx_output: &PrevTxOutput =
882 &prevtx.outputs[next_response.prev_index as usize];
883 next_response = self
884 .get_next_response_nested(pb::btc_request::Request::PrevtxOutput(
885 pb::BtcPrevTxOutputRequest {
886 value: prevtx_output.value,
887 pubkey_script: prevtx_output.pubkey_script.clone(),
888 },
889 ))
890 .await?;
891 }
892 pb::btc_sign_next_response::Type::Output => {
893 let tx_output: &TxOutput = &transaction.outputs[next_response.index as usize];
894 let request: Request = match tx_output {
895 TxOutput::Internal(output) => {
896 Request::BtcSignOutput(pb::BtcSignOutputRequest {
897 ours: true,
898 value: output.value,
899 keypath: output.keypath.to_vec(),
900 script_config_index: output.script_config_index,
901 ..Default::default()
902 })
903 }
904 TxOutput::External(output) => {
905 Request::BtcSignOutput(pb::BtcSignOutputRequest {
906 ours: false,
907 value: output.value,
908 r#type: output.payload.output_type as _,
909 payload: output.payload.data.clone(),
910 ..Default::default()
911 })
912 }
913 };
914 next_response = self.get_next_response(request).await?;
915 }
916 pb::btc_sign_next_response::Type::Done => break,
917 pb::btc_sign_next_response::Type::HostNonce => {
918 return Err(Error::UnexpectedResponse);
919 }
920 _ => return Err(Error::UnexpectedResponse),
921 }
922 }
923 Ok(sigs)
924 }
925
926 pub async fn btc_sign_psbt(
935 &self,
936 coin: pb::BtcCoin,
937 psbt: &mut bitcoin::psbt::Psbt,
938 force_script_config: Option<pb::BtcScriptConfigWithKeypath>,
939 format_unit: pb::btc_sign_init_request::FormatUnit,
940 ) -> Result<(), Error> {
941 self.validate_version(">=9.15.0")?;
945
946 let our_root_fingerprint = hex::decode(self.root_fingerprint().await?).unwrap();
947 let (transaction, our_keys) =
948 Transaction::from_psbt(&our_root_fingerprint, psbt, force_script_config)?;
949 let signatures = self.btc_sign(coin, &transaction, format_unit).await?;
950 for (psbt_input, (signature, our_key)) in
951 psbt.inputs.iter_mut().zip(signatures.iter().zip(our_keys))
952 {
953 match our_key {
954 OurKey::Segwit(pubkey, _) => {
955 psbt_input.partial_sigs.insert(
956 bitcoin::PublicKey::new(pubkey),
957 bitcoin::ecdsa::Signature {
958 signature: bitcoin::secp256k1::ecdsa::Signature::from_compact(
959 signature,
960 )
961 .map_err(|_| Error::InvalidSignature)?,
962 sighash_type: bitcoin::sighash::EcdsaSighashType::All,
963 },
964 );
965 }
966 OurKey::TaprootInternal(_) => {
967 psbt_input.tap_key_sig = Some(
968 bitcoin::taproot::Signature::from_slice(signature)
969 .map_err(|_| Error::InvalidSignature)?,
970 );
971 }
972 OurKey::TaprootScript(xonly, leaf_hash, _) => {
973 let sig = bitcoin::taproot::Signature::from_slice(signature)
974 .map_err(|_| Error::InvalidSignature)?;
975 psbt_input.tap_script_sigs.insert((xonly, leaf_hash), sig);
976 }
977 }
978 }
979 Ok(())
980 }
981
982 pub async fn btc_sign_message(
984 &self,
985 coin: pb::BtcCoin,
986 script_config: pb::BtcScriptConfigWithKeypath,
987 msg: &[u8],
988 ) -> Result<SignMessageSignature, Error> {
989 self.validate_version(">=9.5.0")?;
990
991 let host_nonce = crate::antiklepto::gen_host_nonce()?;
992 let request = pb::BtcSignMessageRequest {
993 coin: coin as _,
994 script_config: Some(script_config),
995 msg: msg.to_vec(),
996 host_nonce_commitment: Some(pb::AntiKleptoHostNonceCommitment {
997 commitment: crate::antiklepto::host_commit(&host_nonce).to_vec(),
998 }),
999 };
1000
1001 let response = self
1002 .query_proto_btc(pb::btc_request::Request::SignMessage(request))
1003 .await?;
1004 let signer_commitment = match response {
1005 pb::btc_response::Response::AntikleptoSignerCommitment(
1006 pb::AntiKleptoSignerCommitment { commitment },
1007 ) => commitment,
1008 _ => return Err(Error::UnexpectedResponse),
1009 };
1010
1011 let request = pb::AntiKleptoSignatureRequest {
1012 host_nonce: host_nonce.to_vec(),
1013 };
1014
1015 let response = self
1016 .query_proto_btc(pb::btc_request::Request::AntikleptoSignature(request))
1017 .await?;
1018 let signature = match response {
1019 pb::btc_response::Response::SignMessage(pb::BtcSignMessageResponse { signature }) => {
1020 signature
1021 }
1022 _ => return Err(Error::UnexpectedResponse),
1023 };
1024 crate::antiklepto::verify_ecdsa(&host_nonce, &signer_commitment, &signature)?;
1025
1026 let sig = signature[..64].to_vec();
1027 let recid = signature[64];
1028 let compressed: u8 = 4; let sig65: u8 = 27 + compressed + recid;
1030 let mut electrum_sig65 = vec![sig65];
1031 electrum_sig65.extend_from_slice(&sig);
1032 Ok(SignMessageSignature {
1033 sig,
1034 recid,
1035 electrum_sig65,
1036 })
1037 }
1038
1039 pub async fn btc_is_script_config_registered(
1046 &self,
1047 coin: pb::BtcCoin,
1048 script_config: &pb::BtcScriptConfig,
1049 keypath_account: Option<&Keypath>,
1050 ) -> Result<bool, Error> {
1051 match self
1052 .query_proto_btc(pb::btc_request::Request::IsScriptConfigRegistered(
1053 pb::BtcIsScriptConfigRegisteredRequest {
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 },
1060 ))
1061 .await?
1062 {
1063 pb::btc_response::Response::IsScriptConfigRegistered(response) => {
1064 Ok(response.is_registered)
1065 }
1066 _ => Err(Error::UnexpectedResponse),
1067 }
1068 }
1069
1070 pub async fn btc_register_script_config(
1081 &self,
1082 coin: pb::BtcCoin,
1083 script_config: &pb::BtcScriptConfig,
1084 keypath_account: Option<&Keypath>,
1085 xpub_type: pb::btc_register_script_config_request::XPubType,
1086 name: Option<&str>,
1087 ) -> Result<(), Error> {
1088 match self
1089 .query_proto_btc(pb::btc_request::Request::RegisterScriptConfig(
1090 pb::BtcRegisterScriptConfigRequest {
1091 registration: Some(pb::BtcScriptConfigRegistration {
1092 coin: coin as _,
1093 script_config: Some(script_config.clone()),
1094 keypath: keypath_account.map_or(vec![], |kp| kp.to_vec()),
1095 }),
1096 name: name.unwrap_or("").into(),
1097 xpub_type: xpub_type as _,
1098 },
1099 ))
1100 .await?
1101 {
1102 pb::btc_response::Response::Success(_) => Ok(()),
1103 _ => Err(Error::UnexpectedResponse),
1104 }
1105 }
1106}
1107
1108#[cfg(test)]
1109mod tests {
1110 use super::*;
1111 use crate::keypath::HARDENED;
1112
1113 #[test]
1114 fn test_payload_from_pkscript() {
1115 use std::str::FromStr;
1116 let addr = bitcoin::Address::from_str("1AMZK8xzHJWsuRErpGZTiW4jKz8fdfLUGE")
1118 .unwrap()
1119 .assume_checked();
1120 let pkscript = addr.script_pubkey().into_bytes();
1121 assert_eq!(
1122 Payload::from_pkscript(&pkscript).unwrap(),
1123 Payload {
1124 data: pkscript[3..23].to_vec(),
1125 output_type: pb::BtcOutputType::P2pkh,
1126 }
1127 );
1128
1129 let addr = bitcoin::Address::from_str("3JFL8CgtV4ZtMFYeP5LgV4JppLkHw5Gw9T")
1131 .unwrap()
1132 .assume_checked();
1133 let pkscript = addr.script_pubkey().into_bytes();
1134 assert_eq!(
1135 Payload::from_pkscript(&pkscript).unwrap(),
1136 Payload {
1137 data: pkscript[2..22].to_vec(),
1138 output_type: pb::BtcOutputType::P2sh,
1139 }
1140 );
1141
1142 let addr = bitcoin::Address::from_str("bc1qkl8ms75cq6ajxtny7e88z3u9hkpkvktt5jwh6u")
1144 .unwrap()
1145 .assume_checked();
1146 let pkscript = addr.script_pubkey().into_bytes();
1147 assert_eq!(
1148 Payload::from_pkscript(&pkscript).unwrap(),
1149 Payload {
1150 data: pkscript[2..].to_vec(),
1151 output_type: pb::BtcOutputType::P2wpkh,
1152 }
1153 );
1154
1155 let addr = bitcoin::Address::from_str(
1157 "bc1q2fhgukymf0caaqrhfxrdju4wm94wwrch2ukntl5fuc0faz8zm49q0h6ss8",
1158 )
1159 .unwrap()
1160 .assume_checked();
1161 let pkscript = addr.script_pubkey().into_bytes();
1162 assert_eq!(
1163 Payload::from_pkscript(&pkscript).unwrap(),
1164 Payload {
1165 data: pkscript[2..].to_vec(),
1166 output_type: pb::BtcOutputType::P2wsh,
1167 }
1168 );
1169
1170 let addr = bitcoin::Address::from_str(
1172 "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr",
1173 )
1174 .unwrap()
1175 .assume_checked();
1176 let pkscript = addr.script_pubkey().into_bytes();
1177 assert_eq!(
1178 Payload::from_pkscript(&pkscript).unwrap(),
1179 Payload {
1180 data: pkscript[2..].to_vec(),
1181 output_type: pb::BtcOutputType::P2tr,
1182 }
1183 );
1184 }
1185
1186 #[test]
1189 fn test_transaction_from_psbt_p2wpkh() {
1190 use std::str::FromStr;
1191
1192 let psbt_str = "cHNidP8BAHECAAAAAfbXTun4YYxDroWyzRq3jDsWFVlsZ7HUzxiORY/iR4goAAAAAAD9////AuLCAAAAAAAAFgAUg3w5W0zt3AmxRmgA5Q6wZJUDRhUowwAAAAAAABYAFJjQqUoXDcwUEqfExu9pnaSn5XBct0ElAAABAR+ghgEAAAAAABYAFHn03igII+hp819N2Zlb5LnN8atRAQDfAQAAAAABAZ9EJlMJnXF5bFVrb1eFBYrEev3pg35WpvS3RlELsMMrAQAAAAD9////AqCGAQAAAAAAFgAUefTeKAgj6GnzX03ZmVvkuc3xq1EoRs4JAAAAABYAFKG2PzjYjknaA6lmXFqPaSgHwXX9AkgwRQIhAL0v0r3LisQ9KOlGzMhM/xYqUmrv2a5sORRlkX1fqDC8AiB9XqxSNEdb4mPnp7ylF1cAlbAZ7jMhgIxHUXylTww3bwEhA0AEOM0yYEpexPoKE3vT51uxZ+8hk9sOEfBFKOeo6oDDAAAAACIGAyNQfmAT/YLmZaxxfDwClmVNt2BkFnfQu/i8Uc/hHDUiGBKiwYlUAACAAQAAgAAAAIAAAAAAAAAAAAAAIgIDnxFM7Qr9LvJwQDB9GozdTRIe3MYVuHOqT7dU2EuvHrIYEqLBiVQAAIABAACAAAAAgAEAAAAAAAAAAA==";
1195
1196 let expected_transaction = Transaction {
1197 script_configs: vec![pb::BtcScriptConfigWithKeypath {
1198 script_config: Some(pb::BtcScriptConfig {
1199 config: Some(pb::btc_script_config::Config::SimpleType(
1200 pb::btc_script_config::SimpleType::P2wpkh as _,
1201 )),
1202 }),
1203 keypath: vec![84 + HARDENED, 1 + HARDENED, HARDENED],
1204 }],
1205 version: 2,
1206 inputs: vec![TxInput {
1207 prev_out_hash: vec![
1208 246, 215, 78, 233, 248, 97, 140, 67, 174, 133, 178, 205, 26, 183, 140, 59, 22,
1209 21, 89, 108, 103, 177, 212, 207, 24, 142, 69, 143, 226, 71, 136, 40,
1210 ],
1211 prev_out_index: 0,
1212 prev_out_value: 100000,
1213 sequence: 4294967293,
1214 keypath: "m/84'/1'/0'/0/0".try_into().unwrap(),
1215 script_config_index: 0,
1216 prev_tx: Some(PrevTx {
1217 version: 1,
1218 inputs: vec![PrevTxInput {
1219 prev_out_hash: vec![
1220 159, 68, 38, 83, 9, 157, 113, 121, 108, 85, 107, 111, 87, 133, 5, 138,
1221 196, 122, 253, 233, 131, 126, 86, 166, 244, 183, 70, 81, 11, 176, 195,
1222 43,
1223 ],
1224 prev_out_index: 1,
1225 signature_script: vec![],
1226 sequence: 4294967293,
1227 }],
1228 outputs: vec![
1229 PrevTxOutput {
1230 value: 100000,
1231 pubkey_script: vec![
1232 0, 20, 121, 244, 222, 40, 8, 35, 232, 105, 243, 95, 77, 217, 153,
1233 91, 228, 185, 205, 241, 171, 81,
1234 ],
1235 },
1236 PrevTxOutput {
1237 value: 164513320,
1238 pubkey_script: vec![
1239 0, 20, 161, 182, 63, 56, 216, 142, 73, 218, 3, 169, 102, 92, 90,
1240 143, 105, 40, 7, 193, 117, 253,
1241 ],
1242 },
1243 ],
1244 locktime: 0,
1245 }),
1246 }],
1247 outputs: vec![
1248 TxOutput::External(TxExternalOutput {
1249 payload: Payload {
1250 data: vec![
1251 131, 124, 57, 91, 76, 237, 220, 9, 177, 70, 104, 0, 229, 14, 176, 100,
1252 149, 3, 70, 21,
1253 ],
1254 output_type: pb::BtcOutputType::P2wpkh,
1255 },
1256 value: 49890,
1257 }),
1258 TxOutput::Internal(TxInternalOutput {
1259 keypath: "m/84'/1'/0'/1/0".try_into().unwrap(),
1260 value: 49960,
1261 script_config_index: 0,
1262 }),
1263 ],
1264 locktime: 2441655,
1265 };
1266 let our_root_fingerprint = hex::decode("12a2c189").unwrap();
1267 let psbt = bitcoin::psbt::Psbt::from_str(psbt_str).unwrap();
1268 let (transaction, _our_keys) =
1269 Transaction::from_psbt(&our_root_fingerprint, &psbt, None).unwrap();
1270 assert_eq!(transaction, expected_transaction);
1271 }
1272}