From 3bf208a62a13b690be7482b596435f09bb0ece07 Mon Sep 17 00:00:00 2001 From: GeneFerneau Date: Fri, 30 Jul 2021 06:03:58 +0000 Subject: [PATCH] WalletSwapCoin: Split into two classes + add trait Convert WalletSwapCoin into Incoming/OutgoingSwapCoin, and add a WalletSwapCoin trait for common functionality --- src/contracts.rs | 167 ++++++++++++-------- src/main.rs | 11 +- src/maker_protocol.rs | 48 ++++-- src/taker_protocol.rs | 20 +-- src/wallet_sync.rs | 355 ++++++++++++++++++++++++++++++------------ 5 files changed, 406 insertions(+), 195 deletions(-) diff --git a/src/contracts.rs b/src/contracts.rs index 9ef7ae62..1d6a084f 100644 --- a/src/contracts.rs +++ b/src/contracts.rs @@ -22,7 +22,9 @@ use rand::RngCore; use crate::error::Error; use crate::messages::ConfirmedCoinSwapTxInfo; -use crate::wallet_sync::{create_multisig_redeemscript, Wallet, WalletSwapCoin, NETWORK}; +use crate::wallet_sync::{ + create_multisig_redeemscript, IncomingSwapCoin, OutgoingSwapCoin, Wallet, NETWORK, +}; //TODO should be configurable somehow //relatively low value for now so that its easier to test on regtest @@ -44,6 +46,9 @@ pub trait SwapCoin { fn get_multisig_redeemscript(&self) -> Script; fn get_contract_tx(&self) -> Transaction; fn get_contract_redeemscript(&self) -> Script; + fn get_other_privkey(&self) -> Option<&SecretKey>; + fn get_contract_privkey(&self) -> Option<&SecretKey>; + fn get_hash_preimage(&self) -> Option<&[u8; 32]>; fn get_funding_amount(&self) -> u64; fn verify_contract_tx_receiver_sig(&self, sig: &Signature) -> bool; fn verify_contract_tx_sender_sig(&self, sig: &Signature) -> bool; @@ -454,79 +459,105 @@ fn verify_contract_tx_sig( secp.verify(&sighash, sig, &pubkey.key).is_ok() } -impl SwapCoin for WalletSwapCoin { - fn get_multisig_redeemscript(&self) -> Script { - let secp = Secp256k1::new(); - create_multisig_redeemscript( - &self.other_pubkey, - &PublicKey { - compressed: true, - key: secp256k1::PublicKey::from_secret_key(&secp, &self.my_privkey), - }, - ) - } +macro_rules! impl_swapcoin { + ($coin:ident) => { + impl SwapCoin for $coin { + fn get_multisig_redeemscript(&self) -> Script { + let secp = Secp256k1::new(); + create_multisig_redeemscript( + &self.other_pubkey, + &PublicKey { + compressed: true, + key: secp256k1::PublicKey::from_secret_key(&secp, &self.my_privkey), + }, + ) + } - fn get_contract_tx(&self) -> Transaction { - self.contract_tx.clone() - } + fn get_contract_tx(&self) -> Transaction { + self.contract_tx.clone() + } - fn get_contract_redeemscript(&self) -> Script { - self.contract_redeemscript.clone() - } + fn get_contract_redeemscript(&self) -> Script { + self.contract_redeemscript.clone() + } - fn get_funding_amount(&self) -> u64 { - self.funding_amount - } + fn get_other_privkey(&self) -> Option<&SecretKey> { + self.other_privkey.as_ref() + } - fn verify_contract_tx_receiver_sig(&self, sig: &Signature) -> bool { - self.verify_contract_tx_sig(sig) - } + fn get_contract_privkey(&self) -> Option<&SecretKey> { + Some(self.get_lock_privkey()) + } - fn verify_contract_tx_sender_sig(&self, sig: &Signature) -> bool { - self.verify_contract_tx_sig(sig) - } + fn get_hash_preimage(&self) -> Option<&[u8; 32]> { + self.hash_preimage.as_ref() + } - fn apply_privkey(&mut self, privkey: SecretKey) -> Result<(), Error> { - let secp = Secp256k1::new(); - let pubkey = PublicKey { - compressed: true, - key: secp256k1::PublicKey::from_secret_key(&secp, &privkey), - }; - if pubkey != self.other_pubkey { - return Err(Error::Protocol("not correct privkey")); + fn get_funding_amount(&self) -> u64 { + self.funding_amount + } + + fn verify_contract_tx_receiver_sig(&self, sig: &Signature) -> bool { + self.verify_contract_tx_sig(sig) + } + + fn verify_contract_tx_sender_sig(&self, sig: &Signature) -> bool { + self.verify_contract_tx_sig(sig) + } + + fn apply_privkey(&mut self, privkey: SecretKey) -> Result<(), Error> { + let secp = Secp256k1::new(); + let pubkey = PublicKey { + compressed: true, + key: secp256k1::PublicKey::from_secret_key(&secp, &privkey), + }; + if pubkey != self.other_pubkey { + return Err(Error::Protocol("not correct privkey")); + } + self.other_privkey = Some(privkey); + Ok(()) + } } - self.other_privkey = Some(privkey); - Ok(()) - } + }; } -impl WalletSwapCoin { - //"_with_my_privkey" as opposed to with other_privkey - pub fn sign_contract_tx_with_my_privkey( - &self, - contract_tx: &Transaction, - ) -> Result { - let multisig_redeemscript = self.get_multisig_redeemscript(); - Ok(sign_contract_tx( - contract_tx, - &multisig_redeemscript, - self.funding_amount, - &self.my_privkey, - ) - .map_err(|_| Error::Protocol("error with signing contract tx"))?) - } +impl_swapcoin!(IncomingSwapCoin); +impl_swapcoin!(OutgoingSwapCoin); + +macro_rules! sign_and_verify_contract { + ($coin:ident) => { + impl $coin { + //"_with_my_privkey" as opposed to with other_privkey + pub fn sign_contract_tx_with_my_privkey( + &self, + contract_tx: &Transaction, + ) -> Result { + let multisig_redeemscript = self.get_multisig_redeemscript(); + Ok(sign_contract_tx( + contract_tx, + &multisig_redeemscript, + self.funding_amount, + &self.my_privkey, + ) + .map_err(|_| Error::Protocol("error with signing contract tx"))?) + } - pub fn verify_contract_tx_sig(&self, sig: &Signature) -> bool { - verify_contract_tx_sig( - &self.contract_tx, - &self.get_multisig_redeemscript(), - self.funding_amount, - &self.other_pubkey, - sig, - ) - } + pub fn verify_contract_tx_sig(&self, sig: &Signature) -> bool { + verify_contract_tx_sig( + &self.contract_tx, + &self.get_multisig_redeemscript(), + self.funding_amount, + &self.other_pubkey, + sig, + ) + } + } + }; } +sign_and_verify_contract!(IncomingSwapCoin); +sign_and_verify_contract!(OutgoingSwapCoin); + impl SwapCoin for WatchOnlySwapCoin { fn get_multisig_redeemscript(&self) -> Script { create_multisig_redeemscript(&self.sender_pubkey, &self.receiver_pubkey) @@ -540,6 +571,18 @@ impl SwapCoin for WatchOnlySwapCoin { self.contract_redeemscript.clone() } + fn get_other_privkey(&self) -> Option<&SecretKey> { + None + } + + fn get_contract_privkey(&self) -> Option<&SecretKey> { + None + } + + fn get_hash_preimage(&self) -> Option<&[u8; 32]> { + None + } + fn get_funding_amount(&self) -> u64 { self.funding_amount } diff --git a/src/main.rs b/src/main.rs index 9833056e..714ff87a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -191,17 +191,18 @@ fn display_wallet_balance(wallet_file_name: &PathBuf, long_form: Option) { compressed: true, key: bitcoin::secp256k1::PublicKey::from_secret_key( &secp, - &swapcoin.contract_privkey, + swapcoin.get_contract_privkey().unwrap(), ), }; let type_string = if contract_pubkey - == read_hashlock_pubkey_from_contract(&swapcoin.contract_redeemscript).unwrap() + == read_hashlock_pubkey_from_contract(&swapcoin.get_contract_redeemscript()) + .unwrap() { "hashlock" } else { assert_eq!( contract_pubkey, - read_timelock_pubkey_from_contract(&swapcoin.contract_redeemscript) + read_timelock_pubkey_from_contract(&swapcoin.get_contract_redeemscript()) .unwrap() ); "timelock" @@ -214,8 +215,8 @@ fn display_wallet_balance(wallet_file_name: &PathBuf, long_form: Option) { if long_form { &"" } else { &txid[58..64] }, utxo.vout, type_string, - if swapcoin.hash_preimage.is_some() { "known" } else { "unknown" }, - read_locktime_from_contract(&swapcoin.contract_redeemscript) + if swapcoin.get_hash_preimage().is_some() { "known" } else { "unknown" }, + read_locktime_from_contract(&swapcoin.get_contract_redeemscript()) .expect("unable to read locktime from contract"), utxo.confirmations, utxo.amount diff --git a/src/maker_protocol.rs b/src/maker_protocol.rs index f0d26b83..91629d9f 100644 --- a/src/maker_protocol.rs +++ b/src/maker_protocol.rs @@ -29,7 +29,7 @@ use crate::messages::{ SendersContractSig, SignReceiversContractTx, SignSendersAndReceiversContractTxes, SignSendersContractTx, SwapCoinPrivateKey, TakerToMakerMessage, }; -use crate::wallet_sync::{CoreAddressLabelType, Wallet, WalletSwapCoin}; +use crate::wallet_sync::{CoreAddressLabelType, IncomingSwapCoin, OutgoingSwapCoin, Wallet}; //TODO //this using of strings to indicate allowed methods doesnt fit aesthetically @@ -58,8 +58,8 @@ struct ConnectionState { //None means that the taker connection is open to doing a number of things //listed in the NEWLY_CONNECTED_TAKER_... const allowed_method: Option<&'static str>, - incoming_swapcoins: Option>, - outgoing_swapcoins: Option>, + incoming_swapcoins: Option>, + outgoing_swapcoins: Option>, pending_funding_txes: Option>, } @@ -366,7 +366,7 @@ fn handle_proof_of_funding( log::trace!(target: "maker", "proof of funding valid, creating own funding txes"); - connection_state.incoming_swapcoins = Some(Vec::::new()); + connection_state.incoming_swapcoins = Some(Vec::::new()); for (funding_info, &funding_output_index, &funding_output, &incoming_swapcoin_keys) in izip!( proof.confirmed_funding_txes.iter(), funding_output_indexes.iter(), @@ -401,7 +401,7 @@ fn handle_proof_of_funding( .incoming_swapcoins .as_mut() .unwrap() - .push(WalletSwapCoin::new( + .push(IncomingSwapCoin::new( coin_privkey, coin_other_pubkey, my_receivers_contract_tx.clone(), @@ -520,10 +520,10 @@ fn handle_senders_and_receivers_contract_sigs( let mut w = wallet.write().unwrap(); incoming_swapcoins .iter() - .for_each(|incoming_swapcoin| w.add_swapcoin(incoming_swapcoin.clone())); + .for_each(|incoming_swapcoin| w.add_incoming_swapcoin(incoming_swapcoin.clone())); outgoing_swapcoins .iter() - .for_each(|outgoing_swapcoin| w.add_swapcoin(outgoing_swapcoin.clone())); + .for_each(|outgoing_swapcoin| w.add_outgoing_swapcoin(outgoing_swapcoin.clone())); w.update_swap_coins_list()?; //TODO add coin to watchtowers @@ -553,12 +553,20 @@ fn handle_sign_receivers_contract_tx( let mut sigs = Vec::::new(); for receivers_contract_tx_info in message.txes { sigs.push( - wallet + if let Some(c) = wallet .read() .unwrap() - .find_swapcoin(&receivers_contract_tx_info.multisig_redeemscript) - .ok_or(Error::Protocol("multisig_redeemscript not found"))? - .sign_contract_tx_with_my_privkey(&receivers_contract_tx_info.contract_tx)?, + .find_incoming_swapcoin(&receivers_contract_tx_info.multisig_redeemscript) + { + c.sign_contract_tx_with_my_privkey(&receivers_contract_tx_info.contract_tx)? + } else { + wallet + .read() + .unwrap() + .find_outgoing_swapcoin(&receivers_contract_tx_info.multisig_redeemscript) + .ok_or(Error::Protocol("multisig_redeemscript not found"))? + .sign_contract_tx_with_my_privkey(&receivers_contract_tx_info.contract_tx)? + }, ); } Ok(Some(MakerToTakerMessage::ReceiversContractSig( @@ -575,7 +583,7 @@ fn handle_hash_preimage( let mut wallet_mref = wallet.write().unwrap(); for multisig_redeemscript in message.senders_multisig_redeemscripts { let mut incoming_swapcoin = wallet_mref - .find_swapcoin_mut(&multisig_redeemscript) + .find_incoming_swapcoin_mut(&multisig_redeemscript) .ok_or(Error::Protocol("senders multisig_redeemscript not found"))?; if read_hashvalue_from_contract(&incoming_swapcoin.contract_redeemscript) .map_err(|_| Error::Protocol("unable to read hashvalue from contract"))? @@ -591,7 +599,7 @@ fn handle_hash_preimage( let mut swapcoin_private_keys = Vec::::new(); for multisig_redeemscript in message.receivers_multisig_redeemscripts { let outgoing_swapcoin = wallet_ref - .find_swapcoin(&multisig_redeemscript) + .find_outgoing_swapcoin(&multisig_redeemscript) .ok_or(Error::Protocol("receivers multisig_redeemscript not found"))?; if read_hashvalue_from_contract(&outgoing_swapcoin.contract_redeemscript) .map_err(|_| Error::Protocol("unable to read hashvalue from contract"))? @@ -619,10 +627,16 @@ fn handle_private_key_handover( ) -> Result, Error> { let mut wallet_ref = wallet.write().unwrap(); for swapcoin_private_key in message.swapcoin_private_keys { - wallet_ref - .find_swapcoin_mut(&swapcoin_private_key.multisig_redeemscript) - .ok_or(Error::Protocol("multisig_redeemscript not found"))? - .apply_privkey(swapcoin_private_key.key)? + if let Some(c) = + wallet_ref.find_incoming_swapcoin_mut(&swapcoin_private_key.multisig_redeemscript) + { + c.apply_privkey(swapcoin_private_key.key)? + } else { + wallet_ref + .find_outgoing_swapcoin_mut(&swapcoin_private_key.multisig_redeemscript) + .ok_or(Error::Protocol("multisig_redeemscript not found"))? + .apply_privkey(swapcoin_private_key.key)? + } } wallet_ref.update_swap_coins_list()?; println!("successfully completed coinswap"); diff --git a/src/taker_protocol.rs b/src/taker_protocol.rs index a133888f..13dfa720 100644 --- a/src/taker_protocol.rs +++ b/src/taker_protocol.rs @@ -40,7 +40,9 @@ use crate::messages::{ use crate::get_bitcoin_rpc; use crate::offerbook_sync::{sync_offerbook, OfferAddress}; -use crate::wallet_sync::{generate_keypair, CoreAddressLabelType, Wallet, WalletSwapCoin}; +use crate::wallet_sync::{ + generate_keypair, CoreAddressLabelType, IncomingSwapCoin, OutgoingSwapCoin, Wallet, +}; #[tokio::main] pub async fn start_taker(rpc: &Client, wallet: &mut Wallet) { @@ -112,7 +114,7 @@ async fn send_coinswap( .for_each(|(sig, outgoing_swapcoin)| outgoing_swapcoin.others_contract_sig = Some(*sig)); for outgoing_swapcoin in &outgoing_swapcoins { - wallet.add_swapcoin(outgoing_swapcoin.clone()); + wallet.add_outgoing_swapcoin(outgoing_swapcoin.clone()); } wallet.update_swap_coins_list().unwrap(); @@ -132,7 +134,7 @@ async fn send_coinswap( let mut active_maker_addresses = Vec::::new(); let mut previous_maker: Option = None; let mut watchonly_swapcoins = Vec::>::new(); - let mut incoming_swapcoins = Vec::::new(); + let mut incoming_swapcoins = Vec::::new(); for maker_index in 0..maker_count { let maker = maker_offers_addresses.pop().unwrap(); @@ -289,7 +291,7 @@ async fn send_coinswap( incoming_swapcoin.others_contract_sig = Some(receiver_contract_sig); } for incoming_swapcoin in &incoming_swapcoins { - wallet.add_swapcoin(incoming_swapcoin.clone()); + wallet.add_incoming_swapcoin(incoming_swapcoin.clone()); } wallet.update_swap_coins_list().unwrap(); @@ -387,7 +389,7 @@ async fn send_coinswap( //update incoming_swapcoins with privkey on disk here for incoming_swapcoin in &incoming_swapcoins { wallet - .find_swapcoin_mut(&incoming_swapcoin.get_multisig_redeemscript()) + .find_incoming_swapcoin_mut(&incoming_swapcoin.get_multisig_redeemscript()) .unwrap() .other_privkey = incoming_swapcoin.other_privkey; } @@ -825,7 +827,7 @@ async fn send_proof_of_funding_and_get_contract_txes( fn sign_receivers_contract_txes( receivers_contract_txes: &[Transaction], - outgoing_swapcoins: &[WalletSwapCoin], + outgoing_swapcoins: &[OutgoingSwapCoin], ) -> Result, Error> { receivers_contract_txes .iter() @@ -907,7 +909,7 @@ fn create_incoming_swapcoins( next_peer_multisig_pubkeys: &[PublicKey], next_peer_multisig_keys_or_nonces: &[SecretKey], preimage: [u8; 32], -) -> Result, Error> { +) -> Result, Error> { let next_swap_multisig_redeemscripts = maker_sign_sender_and_receiver_contracts .senders_contract_txes_info .iter() @@ -948,7 +950,7 @@ fn create_incoming_swapcoins( ) .collect::>(); - let mut incoming_swapcoins = Vec::::new(); + let mut incoming_swapcoins = Vec::::new(); for ( multisig_redeemscript, &maker_funded_multisig_pubkey, @@ -978,7 +980,7 @@ fn create_incoming_swapcoins( o_ms_pubkey1 }; - let mut incoming_swapcoin = WalletSwapCoin::new( + let mut incoming_swapcoin = IncomingSwapCoin::new( maker_funded_multisig_privkey, maker_funded_other_multisig_pubkey, my_receivers_contract_tx.clone(), diff --git a/src/wallet_sync.rs b/src/wallet_sync.rs index 99318fb7..aec55a84 100644 --- a/src/wallet_sync.rs +++ b/src/wallet_sync.rs @@ -71,7 +71,8 @@ struct WalletFileData { seedphrase: String, extension: String, external_index: u32, - swap_coins: Vec, + incoming_swap_coins: Vec, + outgoing_swap_coins: Vec, prevout_to_contract_map: HashMap, } @@ -79,7 +80,8 @@ pub struct Wallet { master_key: ExtendedPrivKey, wallet_file_name: String, external_index: u32, - swap_coins: HashMap, + incoming_swap_coins: HashMap, + outgoing_swap_coins: HashMap, } pub enum CoreAddressLabelType { @@ -91,127 +93,212 @@ const WATCH_ONLY_SWAPCOIN_LABEL: &str = "watchonly_swapcoin_label"; //swapcoins are UTXOs + metadata which are not from the deterministic wallet //they are made in the process of a coinswap #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] -pub struct WalletSwapCoin { +pub struct IncomingSwapCoin { pub my_privkey: SecretKey, pub other_pubkey: PublicKey, pub other_privkey: Option, pub contract_tx: Transaction, pub contract_redeemscript: Script, - //either timelock_privkey for outgoing swapcoins or hashlock_privkey for incoming swapcoins - pub contract_privkey: SecretKey, + pub hashlock_privkey: SecretKey, pub funding_amount: u64, pub others_contract_sig: Option, pub hash_preimage: Option<[u8; 32]>, } -//TODO split WalletSwapCoin into two structs, IncomingSwapCoin and OutgoingSwapCoin -//where Incoming has hashlock_privkey and Outgoing has timelock_privkey -//that is a much more rustic way of doing things, which uses the compiler to check for some bugs -impl WalletSwapCoin { +//swapcoins are UTXOs + metadata which are not from the deterministic wallet +//they are made in the process of a coinswap +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +pub struct OutgoingSwapCoin { + pub my_privkey: SecretKey, + pub other_pubkey: PublicKey, + pub other_privkey: Option, + pub contract_tx: Transaction, + pub contract_redeemscript: Script, + pub timelock_privkey: SecretKey, + pub funding_amount: u64, + pub others_contract_sig: Option, + pub hash_preimage: Option<[u8; 32]>, +} + +impl IncomingSwapCoin { pub fn new( my_privkey: SecretKey, other_pubkey: PublicKey, contract_tx: Transaction, contract_redeemscript: Script, - contract_privkey: SecretKey, + hashlock_privkey: SecretKey, funding_amount: u64, - ) -> WalletSwapCoin { + ) -> Self { let secp = Secp256k1::new(); - let contract_pubkey = PublicKey { + let hashlock_pubkey = PublicKey { compressed: true, - key: secp256k1::PublicKey::from_secret_key(&secp, &contract_privkey), + key: secp256k1::PublicKey::from_secret_key(&secp, &hashlock_privkey), }; assert!( - contract_pubkey + hashlock_pubkey == contracts::read_hashlock_pubkey_from_contract(&contract_redeemscript).unwrap() - || contract_pubkey - == contracts::read_timelock_pubkey_from_contract(&contract_redeemscript) - .unwrap() ); - WalletSwapCoin { + Self { my_privkey, other_pubkey, other_privkey: None, contract_tx, contract_redeemscript, - contract_privkey, + hashlock_privkey, funding_amount, others_contract_sig: None, hash_preimage: None, } } - fn get_my_pubkey(&self) -> PublicKey { + pub fn get_lock_privkey(&self) -> &SecretKey { + &self.hashlock_privkey + } +} + +impl OutgoingSwapCoin { + pub fn new( + my_privkey: SecretKey, + other_pubkey: PublicKey, + contract_tx: Transaction, + contract_redeemscript: Script, + timelock_privkey: SecretKey, + funding_amount: u64, + ) -> Self { let secp = Secp256k1::new(); - PublicKey { + let timelock_pubkey = PublicKey { compressed: true, - key: secp256k1::PublicKey::from_secret_key(&secp, &self.my_privkey), + key: secp256k1::PublicKey::from_secret_key(&secp, &timelock_privkey), + }; + assert!( + timelock_pubkey + == contracts::read_timelock_pubkey_from_contract(&contract_redeemscript).unwrap() + ); + Self { + my_privkey, + other_pubkey, + other_privkey: None, + contract_tx, + contract_redeemscript, + timelock_privkey, + funding_amount, + others_contract_sig: None, + hash_preimage: None, } } + pub fn get_lock_privkey(&self) -> &SecretKey { + &self.timelock_privkey + } +} + +pub trait WalletSwapCoin { + fn get_my_pubkey(&self) -> PublicKey; + fn get_other_pubkey(&self) -> &PublicKey; fn sign_transaction_input( &self, index: usize, tx: &Transaction, input: &mut TxIn, redeemscript: &Script, - ) -> Result<(), &'static str> { - if self.other_privkey.is_none() { - return Err("unable to sign: incomplete coinswap for this input"); - } - let secp = Secp256k1::new(); - let my_pubkey = self.get_my_pubkey(); - - let sighash = secp256k1::Message::from_slice( - &SigHashCache::new(tx).signature_hash( - index, - redeemscript, - self.funding_amount, - SigHashType::All, - )[..], - ) - .unwrap(); + ) -> Result<(), &'static str>; +} - let sig_mine = secp.sign(&sighash, &self.my_privkey); - let sig_other = secp.sign(&sighash, &self.other_privkey.unwrap()); +macro_rules! impl_wallet_swapcoin { + ($coin:ident) => { + impl WalletSwapCoin for $coin { + fn get_my_pubkey(&self) -> PublicKey { + let secp = Secp256k1::new(); + PublicKey { + compressed: true, + key: secp256k1::PublicKey::from_secret_key(&secp, &self.my_privkey), + } + } - let (sig_first, sig_second) = - if my_pubkey.serialize()[..] < self.other_pubkey.serialize()[..] { - (sig_mine, sig_other) - } else { - (sig_other, sig_mine) - }; + fn get_other_pubkey(&self) -> &PublicKey { + &self.other_pubkey + } - input.witness.push(Vec::new()); //first is multisig dummy - input.witness.push(sig_first.serialize_der().to_vec()); - input.witness.push(sig_second.serialize_der().to_vec()); - input.witness[1].push(SigHashType::All as u8); - input.witness[2].push(SigHashType::All as u8); - input.witness.push(redeemscript.to_bytes()); - Ok(()) - } + fn sign_transaction_input( + &self, + index: usize, + tx: &Transaction, + input: &mut TxIn, + redeemscript: &Script, + ) -> Result<(), &'static str> { + if self.other_privkey.is_none() { + return Err("unable to sign: incomplete coinswap for this input"); + } + let secp = Secp256k1::new(); + let my_pubkey = self.get_my_pubkey(); + + let sighash = secp256k1::Message::from_slice( + &SigHashCache::new(tx).signature_hash( + index, + redeemscript, + self.funding_amount, + SigHashType::All, + )[..], + ) + .unwrap(); + + let sig_mine = secp.sign(&sighash, &self.my_privkey); + let sig_other = secp.sign(&sighash, &self.other_privkey.unwrap()); + + let (sig_first, sig_second) = + if my_pubkey.serialize()[..] < self.other_pubkey.serialize()[..] { + (sig_mine, sig_other) + } else { + (sig_other, sig_mine) + }; + + input.witness.push(Vec::new()); //first is multisig dummy + input.witness.push(sig_first.serialize_der().to_vec()); + input.witness.push(sig_second.serialize_der().to_vec()); + input.witness[1].push(SigHashType::All as u8); + input.witness[2].push(SigHashType::All as u8); + input.witness.push(redeemscript.to_bytes()); + Ok(()) + } + } + }; } +impl_wallet_swapcoin!(IncomingSwapCoin); +impl_wallet_swapcoin!(OutgoingSwapCoin); + impl Wallet { pub fn print_wallet_key_data(&self) { println!( "master key = {}, external_index = {}", self.master_key, self.external_index ); - for (multisig_redeemscript, swapcoin) in &self.swap_coins { - println!( - "{} {}:{} {}", - Address::p2wsh(multisig_redeemscript, NETWORK), - swapcoin.contract_tx.input[0].previous_output.txid, - swapcoin.contract_tx.input[0].previous_output.vout, - if swapcoin.other_privkey.is_some() { - " known" - } else { - "unknown" - } - ) + + for (multisig_redeemscript, swapcoin) in &self.incoming_swap_coins { + Self::print_script_and_coin(multisig_redeemscript, swapcoin); + } + for (multisig_redeemscript, swapcoin) in &self.outgoing_swap_coins { + Self::print_script_and_coin(multisig_redeemscript, swapcoin); } - println!("swapcoin count = {}", self.swap_coins.len()); + println!( + "swapcoin count = {}", + self.incoming_swap_coins.len() + self.outgoing_swap_coins.len() + ); + } + + fn print_script_and_coin(script: &Script, coin: &dyn SwapCoin) { + let contract_tx = coin.get_contract_tx(); + println!( + "{} {}:{} {}", + Address::p2wsh(script, NETWORK), + contract_tx.input[0].previous_output.txid, + contract_tx.input[0].previous_output.vout, + if coin.get_other_privkey().is_some() { + " known" + } else { + "unknown" + } + ) } pub fn save_new_wallet_file>( @@ -224,7 +311,8 @@ impl Wallet { seedphrase, extension, external_index: 0, - swap_coins: Vec::new(), + incoming_swap_coins: Vec::new(), + outgoing_swap_coins: Vec::new(), prevout_to_contract_map: HashMap::::new(), }; let wallet_file = File::create(wallet_file_name)?; @@ -264,11 +352,16 @@ impl Wallet { master_key: xprv, wallet_file_name, external_index: wallet_file_data.external_index, - swap_coins: wallet_file_data - .swap_coins + incoming_swap_coins: wallet_file_data + .incoming_swap_coins + .iter() + .map(|sc| (sc.get_multisig_redeemscript(), sc.clone())) + .collect::>(), + outgoing_swap_coins: wallet_file_data + .outgoing_swap_coins .iter() .map(|sc| (sc.get_multisig_redeemscript(), sc.clone())) - .collect::>(), + .collect::>(), }; Ok(wallet) } @@ -289,29 +382,56 @@ impl Wallet { pub fn update_swap_coins_list(&self) -> Result<(), Error> { let mut wallet_file_data = Wallet::load_wallet_file_data(&self.wallet_file_name)?; - wallet_file_data.swap_coins = self - .swap_coins + wallet_file_data.incoming_swap_coins = self + .incoming_swap_coins + .values() + .cloned() + .collect::>(); + wallet_file_data.outgoing_swap_coins = self + .outgoing_swap_coins .values() .cloned() - .collect::>(); + .collect::>(); let wallet_file = File::create(&self.wallet_file_name[..])?; serde_json::to_writer(wallet_file, &wallet_file_data).map_err(|e| io::Error::from(e))?; Ok(()) } - pub fn find_swapcoin(&self, multisig_redeemscript: &Script) -> Option<&WalletSwapCoin> { - self.swap_coins.get(multisig_redeemscript) + pub fn find_incoming_swapcoin( + &self, + multisig_redeemscript: &Script, + ) -> Option<&IncomingSwapCoin> { + self.incoming_swap_coins.get(multisig_redeemscript) + } + + pub fn find_outgoing_swapcoin( + &self, + multisig_redeemscript: &Script, + ) -> Option<&OutgoingSwapCoin> { + self.outgoing_swap_coins.get(multisig_redeemscript) } - pub fn find_swapcoin_mut( + pub fn find_incoming_swapcoin_mut( &mut self, multisig_redeemscript: &Script, - ) -> Option<&mut WalletSwapCoin> { - self.swap_coins.get_mut(multisig_redeemscript) + ) -> Option<&mut IncomingSwapCoin> { + self.incoming_swap_coins.get_mut(multisig_redeemscript) } - pub fn add_swapcoin(&mut self, coin: WalletSwapCoin) { - self.swap_coins + pub fn find_outgoing_swapcoin_mut( + &mut self, + multisig_redeemscript: &Script, + ) -> Option<&mut OutgoingSwapCoin> { + self.outgoing_swap_coins.get_mut(multisig_redeemscript) + } + + pub fn add_incoming_swapcoin(&mut self, coin: IncomingSwapCoin) { + self.incoming_swap_coins + .insert(coin.get_multisig_redeemscript(), coin); + } + + pub fn add_outgoing_swapcoin(&mut self, coin: OutgoingSwapCoin) { + self.outgoing_swap_coins .insert(coin.get_multisig_redeemscript(), coin); } @@ -481,13 +601,13 @@ impl Wallet { .filter(|d| !self.is_xpub_descriptor_imported(rpc, &d).unwrap()) .collect::>(); - let swapcoin_descriptors_to_import = self - .swap_coins + let mut swapcoin_descriptors_to_import = self + .incoming_swap_coins .values() .map(|sc| { format!( "wsh(sortedmulti(2,{},{}))", - sc.other_pubkey, + sc.get_other_pubkey(), sc.get_my_pubkey() ) }) @@ -495,6 +615,20 @@ impl Wallet { .filter(|d| !self.is_swapcoin_descriptor_imported(rpc, &d)) .collect::>(); + swapcoin_descriptors_to_import.extend( + self.outgoing_swap_coins + .values() + .map(|sc| { + format!( + "wsh(sortedmulti(2,{},{}))", + sc.get_other_pubkey(), + sc.get_my_pubkey() + ) + }) + .map(|d| rpc.get_descriptor_info(&d).unwrap().descriptor) + .filter(|d| !self.is_swapcoin_descriptor_imported(rpc, &d)), + ); + if hd_descriptors_to_import.is_empty() && swapcoin_descriptors_to_import.is_empty() { return Ok(()); } @@ -560,12 +694,19 @@ impl Wallet { fingerprint == master_private_key.fingerprint(&secp).to_string() } else { //utxo might be one of our swapcoins - self.find_swapcoin( + self.find_incoming_swapcoin( u.witness_script .as_ref() .unwrap_or(&Script::from(Vec::from_hex("").unwrap())), ) .map_or(false, |sc| sc.other_privkey.is_some()) + || self + .find_outgoing_swapcoin( + u.witness_script + .as_ref() + .unwrap_or(&Script::from(Vec::from_hex("").unwrap())), + ) + .map_or(false, |sc| sc.other_privkey.is_some()) } } @@ -604,20 +745,26 @@ impl Wallet { pub fn find_incomplete_coinswaps( &self, rpc: &Client, - ) -> Result>, Error> { + ) -> Result>, Error> { rpc.call::("lockunspent", &[Value::Bool(true)]) .map_err(|e| Error::Rpc(e))?; - let completed_coinswap_hashvalues = self - .swap_coins + let mut completed_coinswap_hashvalues = self + .incoming_swap_coins .values() .filter(|sc| sc.other_privkey.is_some()) .map(|sc| read_hashvalue_from_contract(&sc.contract_redeemscript).unwrap()) .collect::>(); + completed_coinswap_hashvalues.extend( + self.outgoing_swap_coins + .values() + .filter(|sc| sc.other_privkey.is_some()) + .map(|sc| read_hashvalue_from_contract(&sc.contract_redeemscript).unwrap()), + ); //TODO make this read_hashvalue_from_contract() a struct function of WalletCoinSwap let mut incomplete_swapcoin_groups = - HashMap::<[u8; 20], Vec<(ListUnspentResultEntry, &WalletSwapCoin)>>::new(); + HashMap::<[u8; 20], Vec<(ListUnspentResultEntry, &dyn SwapCoin)>>::new(); for utxo in rpc.list_unspent(None, None, None, None, None)? { if utxo.descriptor.is_none() { continue; @@ -627,22 +774,26 @@ impl Wallet { } else { continue; }; - let swapcoin = if let Some(s) = self.find_swapcoin(multisig_redeemscript) { - s - } else { - continue; - }; - if swapcoin.other_privkey.is_some() { + let swapcoin: &dyn SwapCoin = + if let Some(s) = self.find_incoming_swapcoin(multisig_redeemscript) { + s + } else if let Some(s) = self.find_outgoing_swapcoin(multisig_redeemscript) { + s + } else { + continue; + }; + if swapcoin.get_other_privkey().is_some() { continue; } - let swapcoin_hashvalue = read_hashvalue_from_contract(&swapcoin.contract_redeemscript) - .expect("unable to read hashvalue from contract_redeemscript"); + let swapcoin_hashvalue = + read_hashvalue_from_contract(&swapcoin.get_contract_redeemscript()) + .expect("unable to read hashvalue from contract_redeemscript"); if completed_coinswap_hashvalues.contains(&swapcoin_hashvalue) { continue; } incomplete_swapcoin_groups .entry(swapcoin_hashvalue) - .or_insert(Vec::<(ListUnspentResultEntry, &WalletSwapCoin)>::new()) + .or_insert(Vec::<(ListUnspentResultEntry, &dyn SwapCoin)>::new()) .push((utxo, swapcoin)); } Ok(incomplete_swapcoin_groups) @@ -749,7 +900,7 @@ impl Wallet { ) .into_script(); - self.find_swapcoin(&redeemscript) + self.find_outgoing_swapcoin(&redeemscript) .unwrap() .sign_transaction_input(ix, &tx_clone, &mut input, &redeemscript) .unwrap(); @@ -1056,7 +1207,7 @@ impl Wallet { hashlock_pubkeys: &[PublicKey], hashvalue: [u8; 20], locktime: u16, //returns: funding_txes, swapcoins, timelock_pubkeys - ) -> Result<(Vec, Vec, Vec), Error> { + ) -> Result<(Vec, Vec, Vec), Error> { let (coinswap_addresses, my_multisig_privkeys): (Vec<_>, Vec<_>) = other_multisig_pubkeys .iter() .map(|other_key| self.create_and_import_coinswap_address(rpc, other_key)) @@ -1070,7 +1221,7 @@ impl Wallet { // an integer but also can be Sweep let mut timelock_pubkeys = Vec::::new(); - let mut outgoing_swapcoins = Vec::::new(); + let mut outgoing_swapcoins = Vec::::new(); for ( my_funding_tx, @@ -1104,7 +1255,7 @@ impl Wallet { ); timelock_pubkeys.push(timelock_pubkey); - outgoing_swapcoins.push(WalletSwapCoin::new( + outgoing_swapcoins.push(OutgoingSwapCoin::new( my_multisig_privkey, other_multisig_pubkey, my_senders_contract_tx,