diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index c5e53381e1..73a0533d7e 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -918,7 +918,7 @@ pub enum P2SHSigner { } impl P2SHSigner { - fn try_from_coin(coin: &Coin, swap_unique_data: &[u8]) -> Result + pub fn try_from_coin(coin: &Coin, swap_unique_data: &[u8]) -> Result where Coin: UtxoCommonOps + SwapOps, { @@ -1055,6 +1055,7 @@ pub async fn p2sh_spending_tx(coin: &T, input: P2SHSpendingTxI input.prev_transaction, input.redeem_script, input.script_data.into(), + SIGHASH_ALL, ) .await .map_err(|e| format!("WalletConnect P2SH signing error: {e}")), @@ -1122,7 +1123,7 @@ async fn gen_taker_funding_spend_preimage( pub async fn gen_and_sign_taker_funding_spend_preimage( coin: &T, args: &GenTakerFundingSpendArgs<'_, T>, - htlc_keypair: &KeyPair, + p2sh_signer: &P2SHSigner, ) -> GenPreimageResult { let funding_time_lock = args .funding_time_lock @@ -1138,16 +1139,31 @@ pub async fn gen_and_sign_taker_funding_spend_preimage( args.taker_pub, args.maker_pub, ); - let signature = calc_and_sign_sighash( - &preimage, - DEFAULT_SWAP_VOUT, - &redeem_script, - htlc_keypair, - coin.as_ref().conf.signature_version, - SIGHASH_ALL, - coin.as_ref().conf.fork_id, - ) - .map_mm_err()?; + + let signature = match p2sh_signer { + P2SHSigner::KeyPair(htlc_keypair) => calc_and_sign_sighash( + &preimage, + DEFAULT_SWAP_VOUT, + &redeem_script, + htlc_keypair, + coin.as_ref().conf.signature_version, + SIGHASH_ALL, + coin.as_ref().conf.fork_id, + ) + .map_mm_err()?, + #[cfg(feature = "utxo-walletconnect")] + P2SHSigner::WalletConnect(session_topic) => wallet_connect::sign_p2sh_get_sig_only( + coin, + session_topic, + &preimage, + args.funding_tx.clone(), + redeem_script.into(), + SIGHASH_ALL, + ) + .await + .mm_err(|e| TxGenError::Signing(format!("WalletConnect P2SH signing error: {e}")))?, + }; + Ok(TxPreimageWithSig { preimage: preimage.into(), signature: signature.take().into(), @@ -1244,7 +1260,7 @@ pub async fn sign_and_send_taker_funding_spend( coin: &T, preimage: &TxPreimageWithSig, gen_args: &GenTakerFundingSpendArgs<'_, T>, - htlc_keypair: &KeyPair, + p2sh_signer: &P2SHSigner, ) -> Result { let redeem_script = swap_proto_v2_scripts::taker_funding_script( try_tx_s!(gen_args.funding_time_lock.try_into()), @@ -1259,15 +1275,30 @@ pub async fn sign_and_send_taker_funding_spend( payment_input.amount = funding_output.value; signer.consensus_branch_id = coin.as_ref().conf.consensus_branch_id; - let taker_signature = try_tx_s!(calc_and_sign_sighash( - &signer, - DEFAULT_SWAP_VOUT, - &redeem_script, - htlc_keypair, - coin.as_ref().conf.signature_version, - SIGHASH_ALL, - coin.as_ref().conf.fork_id - )); + let taker_signature = match p2sh_signer { + P2SHSigner::KeyPair(htlc_keypair) => try_tx_s!(calc_and_sign_sighash( + &signer, + DEFAULT_SWAP_VOUT, + &redeem_script, + htlc_keypair, + coin.as_ref().conf.signature_version, + SIGHASH_ALL, + coin.as_ref().conf.fork_id + )), + #[cfg(feature = "utxo-walletconnect")] + P2SHSigner::WalletConnect(session_topic) => try_tx_s!( + wallet_connect::sign_p2sh_get_sig_only( + coin, + session_topic, + &signer, + gen_args.funding_tx.clone(), + redeem_script.clone().into(), + SIGHASH_ALL, + ) + .await + ), + }; + let sig_hash_all_fork_id = (SIGHASH_ALL | coin.as_ref().conf.fork_id) as u8; let mut maker_signature_with_sighash = preimage.signature.to_vec(); @@ -1406,7 +1437,7 @@ async fn gen_taker_payment_spend_preimage( pub async fn gen_and_sign_taker_payment_spend_preimage( coin: &T, args: &GenTakerPaymentSpendArgs<'_, T>, - htlc_keypair: &KeyPair, + p2sh_signer: &P2SHSigner, ) -> GenPreimageResult { let time_lock = args .time_lock @@ -1423,16 +1454,30 @@ pub async fn gen_and_sign_taker_payment_spend_preimage SIGHASH_ALL, }; - let signature = calc_and_sign_sighash( - &preimage, - DEFAULT_SWAP_VOUT, - &redeem_script, - htlc_keypair, - coin.as_ref().conf.signature_version, - sig_hash_type, - coin.as_ref().conf.fork_id, - ) - .map_mm_err()?; + let signature = match p2sh_signer { + P2SHSigner::KeyPair(htlc_keypair) => calc_and_sign_sighash( + &preimage, + DEFAULT_SWAP_VOUT, + &redeem_script, + htlc_keypair, + coin.as_ref().conf.signature_version, + sig_hash_type, + coin.as_ref().conf.fork_id, + ) + .map_mm_err()?, + #[cfg(feature = "utxo-walletconnect")] + P2SHSigner::WalletConnect(session_topic) => wallet_connect::sign_p2sh_get_sig_only( + coin, + session_topic, + &preimage, + args.taker_tx.clone(), + redeem_script.into(), + sig_hash_type, + ) + .await + .mm_err(|e| TxGenError::Signing(format!("WalletConnect P2SH signing error: {e}")))?, + }; + Ok(TxPreimageWithSig { preimage: preimage.into(), signature: signature.take().into(), @@ -1502,14 +1547,14 @@ pub async fn sign_and_broadcast_taker_payment_spend( preimage: &TxPreimageWithSig, gen_args: &GenTakerPaymentSpendArgs<'_, T>, secret: &[u8], - htlc_keypair: &KeyPair, + p2sh_signer: &P2SHSigner, ) -> Result { let secret_hash = dhash160(secret); let redeem_script = swap_proto_v2_scripts::taker_payment_script( try_tx_s!(gen_args.time_lock.try_into()), secret_hash.as_slice(), gen_args.taker_pub, - htlc_keypair.public(), + gen_args.maker_pub, ); let mut signer: TransactionInputSigner = preimage.preimage.clone().into(); @@ -1541,15 +1586,30 @@ pub async fn sign_and_broadcast_taker_payment_spend( } drop_mutability!(signer); - let maker_signature = try_tx_s!(calc_and_sign_sighash( - &signer, - DEFAULT_SWAP_VOUT, - &redeem_script, - htlc_keypair, - coin.as_ref().conf.signature_version, - SIGHASH_ALL, - coin.as_ref().conf.fork_id - )); + let maker_signature = match p2sh_signer { + P2SHSigner::KeyPair(htlc_keypair) => try_tx_s!(calc_and_sign_sighash( + &signer, + DEFAULT_SWAP_VOUT, + &redeem_script, + htlc_keypair, + coin.as_ref().conf.signature_version, + SIGHASH_ALL, + coin.as_ref().conf.fork_id + )), + #[cfg(feature = "utxo-walletconnect")] + P2SHSigner::WalletConnect(session_topic) => try_tx_s!( + wallet_connect::sign_p2sh_get_sig_only( + coin, + session_topic, + &signer, + gen_args.taker_tx.clone(), + redeem_script.clone().into(), + SIGHASH_ALL, + ) + .await + ), + }; + let mut taker_signature_with_sighash = preimage.signature.to_vec(); let taker_sig_hash = match gen_args.dex_fee { DexFee::Standard(_) => (SIGHASH_SINGLE | coin.as_ref().conf.fork_id) as u8, @@ -1774,8 +1834,7 @@ pub async fn send_maker_spends_taker_payment( script_pubkey, }; - let signer = P2SHSigner::try_from_coin(&coin, args.swap_unique_data) - .map_err(|e| TransactionErr::Plain(ERRL!("Failed to create P2SHSigner: {}", e)))?; + let signer = try_tx_s!(P2SHSigner::try_from_coin(&coin, args.swap_unique_data)); let input = P2SHSpendingTxInput { prev_transaction, @@ -1860,17 +1919,19 @@ pub fn create_maker_payment_spend_preimage( drop_mutability!(prev_transaction); let payment_value = try_tx_fus!(prev_transaction.first_output()).value; - let key_pair = coin.derive_htlc_key_pair(swap_unique_data); + let taker_pub = coin.derive_htlc_pubkey(swap_unique_data); let script_data = Builder::default().into_script(); let redeem_script = payment_script( time_lock, secret_hash, &try_tx_fus!(Public::from_slice(maker_pub)), - key_pair.public(), + &try_tx_fus!(Public::from_slice(&taker_pub)), ) .into(); + let coin = coin.clone(); + let swap_unique_data = swap_unique_data.to_vec(); let fut = async move { let my_address = try_tx_s!(coin.as_ref().derivation_method.single_addr_or_err().await); let fee = try_tx_s!( @@ -1898,7 +1959,7 @@ pub fn create_maker_payment_spend_preimage( script_data, sequence: SEQUENCE_FINAL, lock_time: time_lock, - signer: P2SHSigner::KeyPair(key_pair), + signer: try_tx_s!(P2SHSigner::try_from_coin(&coin, &swap_unique_data)), }; let transaction = try_tx_s!(coin.p2sh_spending_tx(input).await); @@ -1915,7 +1976,6 @@ pub fn create_taker_payment_refund_preimage( secret_hash: &[u8], swap_unique_data: &[u8], ) -> TransactionFut { - let coin = coin.clone(); let mut prev_transaction: UtxoTx = try_tx_fus!(deserialize(taker_payment_tx).map_err(|e| TransactionErr::Plain(format!( "Failed to deserialize transaction (coin={}, hex={}) : {}", @@ -1927,15 +1987,18 @@ pub fn create_taker_payment_refund_preimage( drop_mutability!(prev_transaction); let payment_value = try_tx_fus!(prev_transaction.first_output()).value; - let key_pair = coin.derive_htlc_key_pair(swap_unique_data); + let taker_pub = coin.derive_htlc_pubkey(swap_unique_data); let script_data = Builder::default().push_opcode(Opcode::OP_1).into_script(); let redeem_script = payment_script( time_lock, secret_hash, - key_pair.public(), + &try_tx_fus!(Public::from_slice(&taker_pub)), &try_tx_fus!(Public::from_slice(maker_pub)), ) .into(); + + let coin = coin.clone(); + let swap_unique_data = swap_unique_data.to_vec(); let fut = async move { let my_address = try_tx_s!(coin.as_ref().derivation_method.single_addr_or_err().await); let fee = try_tx_s!( @@ -1962,7 +2025,7 @@ pub fn create_taker_payment_refund_preimage( script_data, sequence: SEQUENCE_FINAL - 1, lock_time: time_lock, - signer: P2SHSigner::KeyPair(key_pair), + signer: try_tx_s!(P2SHSigner::try_from_coin(&coin, &swap_unique_data)), }; let transaction = try_tx_s!(coin.p2sh_spending_tx(input).await); @@ -2019,8 +2082,7 @@ pub async fn send_taker_spends_maker_payment( script_pubkey, }; - let signer = P2SHSigner::try_from_coin(&coin, args.swap_unique_data) - .map_err(|e| TransactionErr::Plain(ERRL!("Failed to create P2SHSigner: {}", e)))?; + let signer = try_tx_s!(P2SHSigner::try_from_coin(&coin, args.swap_unique_data)); let input = P2SHSpendingTxInput { prev_transaction, @@ -2081,8 +2143,7 @@ pub async fn refund_htlc_payment( script_pubkey, }; - let signer = P2SHSigner::try_from_coin(&coin, args.swap_unique_data) - .map_err(|e| TransactionErr::Plain(ERRL!("Failed to create P2SHSigner: {}", e)))?; + let signer = try_tx_s!(P2SHSigner::try_from_coin(&coin, args.swap_unique_data)); let input = P2SHSpendingTxInput { prev_transaction, @@ -5390,7 +5451,7 @@ where let my_address = try_tx_s!(coin.as_ref().derivation_method.single_addr_or_err().await).clone(); let payment_value = try_tx_s!(args.funding_tx.first_output()).value; - let key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); + let taker_pubkey = coin.derive_htlc_pubkey(args.swap_unique_data); let script_data = Builder::default() .push_data(args.taker_secret) .push_opcode(Opcode::OP_0) @@ -5401,7 +5462,7 @@ where let redeem_script = swap_proto_v2_scripts::taker_funding_script( time_lock, args.taker_secret_hash, - key_pair.public(), + &try_tx_s!(Public::from_slice(&taker_pubkey)), args.maker_pubkey, ) .into(); @@ -5429,7 +5490,7 @@ where script_data, sequence: SEQUENCE_FINAL, lock_time: time_lock, - signer: P2SHSigner::KeyPair(key_pair), + signer: try_tx_s!(P2SHSigner::try_from_coin(&coin, args.swap_unique_data)), }; let transaction = try_tx_s!(coin.p2sh_spending_tx(input).await); @@ -5559,7 +5620,7 @@ pub async fn spend_maker_payment_v2( let my_address = try_tx_s!(coin.as_ref().derivation_method.single_addr_or_err().await).clone(); let payment_value = try_tx_s!(args.maker_payment_tx.first_output()).value; - let key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); + let taker_pub = coin.derive_htlc_pubkey(args.swap_unique_data); let script_data = Builder::default() .push_data(&args.maker_secret) .push_opcode(Opcode::OP_1) @@ -5572,7 +5633,7 @@ pub async fn spend_maker_payment_v2( args.maker_secret_hash, args.taker_secret_hash, args.maker_pub, - key_pair.public(), + &try_tx_s!(Public::from_slice(&taker_pub)), ) .into(); @@ -5600,7 +5661,7 @@ pub async fn spend_maker_payment_v2( script_data, sequence: SEQUENCE_FINAL, lock_time: time_lock, - signer: P2SHSigner::KeyPair(key_pair), + signer: try_tx_s!(P2SHSigner::try_from_coin(coin, args.swap_unique_data)), }; let transaction = try_tx_s!(coin.p2sh_spending_tx(input).await); @@ -5621,7 +5682,7 @@ where let my_address = try_tx_s!(coin.as_ref().derivation_method.single_addr_or_err().await).clone(); let payment_value = try_tx_s!(args.maker_payment_tx.first_output()).value; - let key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); + let maker_pub = coin.derive_htlc_pubkey(args.swap_unique_data); let script_data = Builder::default() .push_data(args.taker_secret) .push_opcode(Opcode::OP_0) @@ -5633,7 +5694,7 @@ where time_lock, args.maker_secret_hash, args.taker_secret_hash, - key_pair.public(), + &try_tx_s!(Public::from_slice(&maker_pub)), args.taker_pub, ) .into(); @@ -5661,7 +5722,7 @@ where script_data, sequence: SEQUENCE_FINAL, lock_time: time_lock, - signer: P2SHSigner::KeyPair(key_pair), + signer: try_tx_s!(P2SHSigner::try_from_coin(&coin, args.swap_unique_data)), }; let transaction = try_tx_s!(coin.p2sh_spending_tx(input).await); diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index bc7d2bfd60..90b7bb9c4b 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -26,6 +26,7 @@ use crate::rpc_command::init_withdraw::{InitWithdrawCoin, WithdrawTaskHandleShar use crate::tx_history_storage::{GetTxHistoryFilters, WalletId}; use crate::utxo::rpc_clients::BlockHashOrHeight; use crate::utxo::utxo_builder::{UtxoArcBuilder, UtxoCoinBuilder}; +use crate::utxo::utxo_common::P2SHSigner; use crate::utxo::utxo_hd_wallet::{UtxoHDAccount, UtxoHDAddress}; use crate::utxo::utxo_tx_history_v2::{ UtxoMyAddressesHistoryError, UtxoTxDetailsError, UtxoTxDetailsParams, UtxoTxHistoryOps, @@ -40,7 +41,7 @@ use crate::{ SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignRawTransactionRequest, SignatureResult, SpendMakerPaymentArgs, SpendPaymentArgs, SwapOps, SwapTxTypeWithSecretHash, TakerCoinSwapOpsV2, ToBytes, TradePreimageValue, TransactionFut, TransactionResult, - TxMarshalingErr, TxPreimageWithSig, ValidateAddressResult, ValidateFeeArgs, ValidateMakerPaymentArgs, + TxGenError, TxMarshalingErr, TxPreimageWithSig, ValidateAddressResult, ValidateFeeArgs, ValidateMakerPaymentArgs, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateSwapV2TxResult, ValidateTakerFundingArgs, ValidateTakerFundingSpendPreimageResult, ValidateTakerPaymentSpendPreimageResult, ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, @@ -757,8 +758,8 @@ impl TakerCoinSwapOpsV2 for UtxoStandardCoin { args: &GenTakerFundingSpendArgs<'_, Self>, swap_unique_data: &[u8], ) -> GenPreimageResult { - let htlc_keypair = self.derive_htlc_key_pair(swap_unique_data); - utxo_common::gen_and_sign_taker_funding_spend_preimage(self, args, &htlc_keypair).await + let signer = P2SHSigner::try_from_coin(self, swap_unique_data).map_err(TxGenError::Signing)?; + utxo_common::gen_and_sign_taker_funding_spend_preimage(self, args, &signer).await } async fn validate_taker_funding_spend_preimage( @@ -775,8 +776,8 @@ impl TakerCoinSwapOpsV2 for UtxoStandardCoin { args: &GenTakerFundingSpendArgs<'_, Self>, swap_unique_data: &[u8], ) -> Result { - let htlc_keypair = self.derive_htlc_key_pair(swap_unique_data); - utxo_common::sign_and_send_taker_funding_spend(self, preimage, args, &htlc_keypair).await + let signer = try_tx_s!(P2SHSigner::try_from_coin(self, swap_unique_data)); + utxo_common::sign_and_send_taker_funding_spend(self, preimage, args, &signer).await } async fn refund_combined_taker_payment( @@ -800,8 +801,8 @@ impl TakerCoinSwapOpsV2 for UtxoStandardCoin { args: &GenTakerPaymentSpendArgs<'_, Self>, swap_unique_data: &[u8], ) -> GenPreimageResult { - let key_pair = self.derive_htlc_key_pair(swap_unique_data); - utxo_common::gen_and_sign_taker_payment_spend_preimage(self, args, &key_pair).await + let signer = P2SHSigner::try_from_coin(self, swap_unique_data).map_err(TxGenError::Signing)?; + utxo_common::gen_and_sign_taker_payment_spend_preimage(self, args, &signer).await } async fn validate_taker_payment_spend_preimage( @@ -821,8 +822,8 @@ impl TakerCoinSwapOpsV2 for UtxoStandardCoin { ) -> Result { let preimage = preimage .ok_or_else(|| TransactionErr::Plain(ERRL!("taker_payment_spend_preimage must be Some for UTXO coin")))?; - let htlc_keypair = self.derive_htlc_key_pair(swap_unique_data); - utxo_common::sign_and_broadcast_taker_payment_spend(self, preimage, gen_args, secret, &htlc_keypair).await + let signer = try_tx_s!(P2SHSigner::try_from_coin(self, swap_unique_data)); + utxo_common::sign_and_broadcast_taker_payment_spend(self, preimage, gen_args, secret, &signer).await } async fn find_taker_payment_spend_tx( @@ -851,7 +852,8 @@ impl TakerCoinSwapOpsV2 for UtxoStandardCoin { impl CommonSwapOpsV2 for UtxoStandardCoin { fn derive_htlc_pubkey_v2(&self, swap_unique_data: &[u8]) -> Self::Pubkey { - *self.derive_htlc_key_pair(swap_unique_data).public() + let pubkey_bytes = self.derive_htlc_pubkey(swap_unique_data); + Public::Compressed(pubkey_bytes.into()) } #[inline(always)] diff --git a/mm2src/coins/utxo/wallet_connect.rs b/mm2src/coins/utxo/wallet_connect.rs index 217d494962..cf9ecfb4f7 100644 --- a/mm2src/coins/utxo/wallet_connect.rs +++ b/mm2src/coins/utxo/wallet_connect.rs @@ -162,12 +162,13 @@ struct SignedPsbt { /// /// An **array** of this struct is sent to WalletConnect in `SignPsbt` request. #[derive(Serialize)] +#[serde(rename_all = "camelCase")] struct InputSigningParams { /// The index of the input to sign. index: u32, /// The address to sign the input with. address: String, - /// The sighash types to use for signing. + /// An array of whitelisted sighash types that the wallet is allowed to use when signing this input. sighash_types: Vec, } @@ -218,8 +219,10 @@ async fn sign_psbt( /// `prev_tx` is the previous transaction that contains the P2SH output being spent. /// `redeem_script` is the redeem script that is used to spend the P2SH output. /// `unlocking_script` is the unlocking script that picks the appropriate spending path (normal spend (with secret hash) vs refund) +/// +/// Returns the signed transaction and the bare P2SH signature returned by WalletConnect. #[expect(clippy::too_many_arguments)] -pub async fn sign_p2sh_with_walletconnect( +async fn sign_p2sh_with_walletconnect( wc: &WalletConnectCtx, session_topic: &WcTopic, chain_id: &WcChainId, @@ -228,7 +231,8 @@ pub async fn sign_p2sh_with_walletconnect( prev_tx: UtxoTx, redeem_script: Bytes, unlocking_script: Bytes, -) -> MmResult { + sighash_type: EcdsaSighashType, +) -> MmResult<(UtxoTx, Bytes), WalletConnectError> { let signing_address = signing_address.display_address().map_to_mm(|e| { WalletConnectError::InternalError(format!("Failed to convert the signing address to a string: {e}")) })?; @@ -249,13 +253,13 @@ pub async fn sign_p2sh_with_walletconnect( // We need to provide the redeem script as it's used in the signing process. psbt.inputs[DEFAULT_SWAP_VIN].redeem_script = Some(redeem_script.take().into()); // TODO: Check whether we should put `fork_id` here or not. When we support a `fork_id`-based chain in WalletConnect. - psbt.inputs[DEFAULT_SWAP_VIN].sighash_type = Some(EcdsaSighashType::All.into()); + psbt.inputs[DEFAULT_SWAP_VIN].sighash_type = Some(sighash_type.into()); // Ask WalletConnect to sign the PSBT for us. let inputs = vec![InputSigningParams { index: DEFAULT_SWAP_VIN as u32, address: signing_address.clone(), - sighash_types: vec![EcdsaSighashType::All as u8], + sighash_types: vec![sighash_type as u8], }]; let signed_psbt = sign_psbt(wc, session_topic, chain_id, psbt, inputs, false).await?; @@ -284,21 +288,22 @@ pub async fn sign_p2sh_with_walletconnect( tx_to_sign.inputs[DEFAULT_SWAP_VIN].script_sig = final_script_sig; tx_to_sign.inputs[DEFAULT_SWAP_VIN].script_witness = vec![]; - Ok(tx_to_sign) + Ok((tx_to_sign, Bytes::from(walletconnect_sig.to_vec()))) } /// Signs a P2SH transaction that has a single input using WalletConnect. /// /// This is just another wrapper around `sign_p2sh_with_walletconnect` to avoid some boilerplate given /// that there is an accessible `coin`. -pub async fn sign_p2sh( +async fn sign_p2sh_get_tx_and_sig( coin: &impl AsRef, session_topic: &WcTopic, tx_input_signer: &TransactionInputSigner, prev_tx: UtxoTx, redeem_script: Bytes, unlocking_script: Bytes, -) -> MmResult { + sighash_type: u32, +) -> MmResult<(UtxoTx, Bytes), WalletConnectError> { let ctx = MmArc::from_weak(&coin.as_ref().ctx) .ok_or_else(|| WalletConnectError::InternalError("Couldn't get access to MmArc".to_string()))?; let wc_ctx = WalletConnectCtx::from_ctx(&ctx)?; @@ -316,6 +321,9 @@ pub async fn sign_p2sh( .as_ref() .ok_or_else(|| WalletConnectError::InternalError("Chain ID is not set".to_string()))?; + let sighash_type = EcdsaSighashType::from_standard(sighash_type) + .map_err(|e| WalletConnectError::InternalError(format!("Bad sighash type: {e}")))?; + sign_p2sh_with_walletconnect( &wc_ctx, session_topic, @@ -325,15 +333,67 @@ pub async fn sign_p2sh( prev_tx, redeem_script, unlocking_script, + sighash_type, + ) + .await +} + +/// Signs a P2SH transaction that has a single input using WalletConnect and returns the signed transaction. +pub async fn sign_p2sh( + coin: &impl AsRef, + session_topic: &WcTopic, + tx_input_signer: &TransactionInputSigner, + prev_tx: UtxoTx, + redeem_script: Bytes, + unlocking_script: Bytes, + sighash_type: u32, +) -> MmResult { + sign_p2sh_get_tx_and_sig( + coin, + session_topic, + tx_input_signer, + prev_tx, + redeem_script, + unlocking_script, + sighash_type, + ) + .await + .map(|(tx, _p2sh_sig)| tx) +} + +/// Signs a P2SH transaction that has a single input using WalletConnect and returns only the bare P2SH signature. +pub async fn sign_p2sh_get_sig_only( + coin: &impl AsRef, + session_topic: &WcTopic, + tx_input_signer: &TransactionInputSigner, + prev_tx: UtxoTx, + redeem_script: Bytes, + sighash_type: u32, +) -> MmResult { + sign_p2sh_get_tx_and_sig( + coin, + session_topic, + tx_input_signer, + prev_tx, + redeem_script, + // Since we will not use the resulting tx, we can put any dummy unlocking script here. + Bytes::new(), + sighash_type, ) .await + .map(|(_tx, p2sh_sig)| { + let mut p2sh_sig = p2sh_sig.take(); + // Remove the sighash byte at the end so to align with the output of `calc_and_sign_sighash`. + p2sh_sig.pop(); + Bytes::from(p2sh_sig) + }) } /// Signs a P2PKH/P2WPKH spending transaction using WalletConnect. /// /// Contrary to what the function name might suggest, this function can sign both P2PKH and **P2WPKH** inputs. /// `prev_txs` is a map of previous transactions that contain the P2PKH inputs being spent. P2WPKH inputs don't need their previous transactions. -pub async fn sign_p2pkh_with_walletconnect( +async fn sign_p2pkh_with_walletconnect( wc: &WalletConnectCtx, session_topic: &WcTopic, chain_id: &WcChainId, diff --git a/mm2src/mm2_main/tests/mm2_tests/wallet_connect_tests.rs b/mm2src/mm2_main/tests/mm2_tests/wallet_connect_tests.rs index bafea5a608..c518f72e08 100644 --- a/mm2src/mm2_main/tests/mm2_tests/wallet_connect_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/wallet_connect_tests.rs @@ -10,7 +10,7 @@ use serde_json::json; #[cfg(not(target_arch = "wasm32"))] /// Perform a swap using WalletConnect protocol with two tBTC (testnet4) coins. -async fn perform_walletconnect_swap() { +async fn perform_walletconnect_swap(use_swaps_proto_v2: bool) { let walletconnect_namespaces = json!({ "required_namespaces": { "bip122": { @@ -66,7 +66,10 @@ async fn perform_walletconnect_swap() { let trading_pair = (coins[0]["coin"].as_str().unwrap(), coins[1]["coin"].as_str().unwrap()); let coins = json!(coins); - let bob_conf = Mm2TestConfForSwap::bob_conf_with_policy(&Mm2InitPrivKeyPolicy::GlobalHDAccount, &coins); + let mut bob_conf = Mm2TestConfForSwap::bob_conf_with_policy(&Mm2InitPrivKeyPolicy::GlobalHDAccount, &coins); + if use_swaps_proto_v2 { + bob_conf.conf["use_trading_proto_v2"] = true.into(); + } // Uncomment to test the refund case. The quickest way to test both refunds is to reject signing TakerPaymentSpend (the 4th signing prompt). // This will force the taker to refund himself and after sometime the maker will also refund himself because he can't spend the TakerPayment anymore (as it's already refunded). // Note that you need to run the test with `--features custom-swap-locktime` to enable the custom `payment_locktime` feature. @@ -79,11 +82,14 @@ async fn perform_walletconnect_swap() { log!("Bob log path: {}", mm_bob.log_path.display()); Timer::sleep(2.).await; - let alice_conf = Mm2TestConfForSwap::alice_conf_with_policy( + let mut alice_conf = Mm2TestConfForSwap::alice_conf_with_policy( &Mm2InitPrivKeyPolicy::GlobalHDAccount, &coins, &mm_bob.my_seed_addr(), ); + if use_swaps_proto_v2 { + alice_conf.conf["use_trading_proto_v2"] = true.into(); + } // Uncomment to test the refund case // alice_conf.conf["payment_locktime"] = (1 * 60).into(); let mut mm_alice = MarketMakerIt::start_async(alice_conf.conf, alice_conf.rpc_password, None) @@ -138,6 +144,12 @@ async fn perform_walletconnect_swap() { #[test] #[ignore] -fn test_walletconnect_swap() { - block_on(perform_walletconnect_swap()); +fn test_walletconnect_swap_v1() { + block_on(perform_walletconnect_swap(false)); +} + +#[test] +#[ignore] +fn test_walletconnect_swap_v2() { + block_on(perform_walletconnect_swap(true)); }