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_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 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")?; if transaction.script_configs.iter().any(is_taproot_simple) {
717 self.validate_version(">=9.10.0")?; }
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 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 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 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; 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 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 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 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 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 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 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 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]
1152 fn test_transaction_from_psbt_p2wpkh() {
1153 use std::str::FromStr;
1154
1155 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}