Skip to content

Commit

Permalink
Make watchtower broadcast pre-signed spending txes
Browse files Browse the repository at this point in the history
Now the watchtower will monitor broadcasted contract transactions and
will spend from them either via the timelock branch when that timelock
matures, or via the hashlock branch if the other side has spent their
side of the coinswap on-chain with the hash preimage, which reveals it
to us.

Also added is code where the maker generates presigned spending
transactions and sends them to the watchtower.
  • Loading branch information
chris-belcher committed Jan 28, 2022
1 parent 0a87ebb commit ba2731a
Show file tree
Hide file tree
Showing 9 changed files with 679 additions and 191 deletions.
29 changes: 20 additions & 9 deletions src/contracts.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::sync::Arc;

use std::array::TryFromSliceError;
use std::convert::TryInto;

use bitcoin::{
Expand Down Expand Up @@ -29,8 +28,8 @@ use crate::wallet_sync::{

//TODO should be configurable somehow
//relatively low value for now so that its easier to test on regtest
pub const REFUND_LOCKTIME: u16 = 30; //in blocks
pub const REFUND_LOCKTIME_STEP: u16 = 5; //in blocks
pub const REFUND_LOCKTIME: u16 = 6; //in blocks
pub const REFUND_LOCKTIME_STEP: u16 = 3; //in blocks

//like the Incoming/OutgoingSwapCoin structs but no privkey or signature information
//used by the taker to monitor coinswaps between two makers
Expand Down Expand Up @@ -113,9 +112,15 @@ pub fn create_contract_redeemscript(
}

//TODO put all these magic numbers in a const or something
pub fn read_hashvalue_from_contract(redeemscript: &Script) -> Result<Hash160, TryFromSliceError> {
//a better way is to use redeemscript.instructions() like read_locktime_from_contract()
pub fn read_hashvalue_from_contract(redeemscript: &Script) -> Result<Hash160, &'static str> {
if redeemscript.to_bytes().len() < 25 {
return Err("script too short");
}
Ok(Hash160::from_inner(
redeemscript.to_bytes()[4..24].try_into()?,
redeemscript.to_bytes()[4..24]
.try_into()
.map_err(|_| "tryinto error")?,
))
}

Expand All @@ -141,14 +146,20 @@ pub fn read_locktime_from_contract(redeemscript: &Script) -> Option<u16> {

pub fn read_hashlock_pubkey_from_contract(
redeemscript: &Script,
) -> Result<PublicKey, bitcoin::util::key::Error> {
PublicKey::from_slice(&redeemscript.to_bytes()[27..60])
) -> Result<PublicKey, &'static str> {
if redeemscript.to_bytes().len() < 61 {
return Err("script too short");
}
PublicKey::from_slice(&redeemscript.to_bytes()[27..60]).map_err(|_| "pubkey error")
}

pub fn read_timelock_pubkey_from_contract(
redeemscript: &Script,
) -> Result<PublicKey, bitcoin::util::key::Error> {
PublicKey::from_slice(&redeemscript.to_bytes()[65..98])
) -> Result<PublicKey, &'static str> {
if redeemscript.to_bytes().len() < 99 {
return Err("script too short");
}
PublicKey::from_slice(&redeemscript.to_bytes()[65..98]).map_err(|_| "pubkey error")
}

pub fn read_pubkeys_from_multisig_redeemscript(
Expand Down
13 changes: 7 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,17 +454,18 @@ pub fn recover_from_incomplete_coinswap(
.enumerate()
{
wallet
.import_redeemscript(
&rpc,
&swapcoin.1.get_contract_redeemscript(),
wallet_sync::CoreAddressLabelType::Wallet,
)
.import_wallet_redeemscript(&rpc, &swapcoin.1.get_contract_redeemscript())
.unwrap();

let signed_contract_tx = swapcoin.1.get_fully_signed_contract_tx();
if dont_broadcast {
let txhex = bitcoin::consensus::encode::serialize_hex(&signed_contract_tx);
println!("contract_tx_{} = \n{}", ii, txhex);
println!(
"contract_tx_{} (txid = {}) = \n{}",
ii,
signed_contract_tx.txid(),
txhex
);
let accepted = rpc
.test_mempool_accept(&[txhex.clone()])
.unwrap()
Expand Down
27 changes: 14 additions & 13 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use bitcoin::consensus::encode::deserialize;
use bitcoin::hashes::{hash160::Hash as Hash160, hex::FromHex};
use bitcoin::Transaction;
use bitcoin::{Script, Transaction};

use std::path::PathBuf;
use structopt::StructOpt;
Expand All @@ -9,7 +9,7 @@ use teleport;
use teleport::maker_protocol::MakerBehavior;
use teleport::wallet_direct_send::{CoinToSpend, Destination, SendAmount};
use teleport::wallet_sync::WalletSyncAddressAmount;
use teleport::watchtower_client::ContractInfo;
use teleport::watchtower_protocol::{ContractTransaction, ContractsInfo};

#[derive(Debug, StructOpt)]
#[structopt(name = "teleport", about = "A tool for CoinSwap")]
Expand Down Expand Up @@ -164,20 +164,21 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
let contract_txes = contract_transactions_hex
.iter()
.map(|cth| {
deserialize::<Transaction>(
.map(|cth| ContractTransaction {
tx: deserialize::<Transaction>(
&Vec::from_hex(&cth).expect("Invalid transaction hex string"),
)
.expect("Unable to deserialize transaction hex")
.expect("Unable to deserialize transaction hex"),
redeemscript: Script::new(),
hashlock_spend_without_preimage: None,
timelock_spend: None,
timelock_spend_broadcasted: false,
})
.collect::<Vec<Transaction>>();
let contracts_to_watch = contract_txes
.iter()
.map(|contract_tx| ContractInfo {
contract_tx: contract_tx.clone(),
})
.collect::<Vec<ContractInfo>>();
teleport::watchtower_client::test_watchtower_client(contracts_to_watch);
.collect::<Vec<ContractTransaction>>();
teleport::watchtower_client::test_watchtower_client(ContractsInfo {
contract_txes,
wallet_label: String::new(),
});
}
}

Expand Down
56 changes: 38 additions & 18 deletions src/maker_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,9 @@ use crate::messages::{
SendersContractSig, SignReceiversContractTx, SignSendersAndReceiversContractTxes,
SignSendersContractTx, SwapCoinPrivateKey, TakerToMakerMessage,
};
use crate::wallet_sync::{CoreAddressLabelType, IncomingSwapCoin, OutgoingSwapCoin, Wallet};
use crate::watchtower_client::{
ping_watchtowers, register_coinswap_with_watchtowers, ContractInfo,
};
use crate::wallet_sync::{IncomingSwapCoin, OutgoingSwapCoin, Wallet, WalletSwapCoin};
use crate::watchtower_client::{ping_watchtowers, register_coinswap_with_watchtowers};
use crate::watchtower_protocol::{ContractTransaction, ContractsInfo};

//used to configure the maker do weird things for testing
#[derive(Debug, Clone, Copy)]
Expand Down Expand Up @@ -517,11 +516,10 @@ fn handle_proof_of_funding(
funding_outputs.iter(),
incoming_swapcoin_keys.iter()
) {
wallet.read().unwrap().import_redeemscript(
&rpc,
&funding_info.multisig_redeemscript,
CoreAddressLabelType::Wallet,
)?;
wallet
.read()
.unwrap()
.import_wallet_redeemscript(&rpc, &funding_info.multisig_redeemscript)?;
wallet.read().unwrap().import_tx_with_merkleproof(
&rpc,
&funding_info.funding_tx,
Expand Down Expand Up @@ -677,17 +675,39 @@ async fn handle_senders_and_receivers_contract_sigs(
outgoing_swapcoin.others_contract_sig = Some(senders_sig)
});

register_coinswap_with_watchtowers(
incoming_swapcoins
let wallet_label = wallet.read().unwrap().get_core_wallet_label();
let internal_addresses = wallet
.read()
.unwrap()
.get_next_internal_addresses(&rpc, incoming_swapcoins.len() as u32)?;
register_coinswap_with_watchtowers(ContractsInfo {
contract_txes: incoming_swapcoins
.iter()
.map(|isc| ContractInfo {
contract_tx: isc.contract_tx.clone(),
.zip(internal_addresses.iter())
.map(|(isc, addr)| ContractTransaction {
tx: isc.get_fully_signed_contract_tx(),
redeemscript: isc.contract_redeemscript.clone(),
hashlock_spend_without_preimage: Some(
isc.create_hashlock_spend_without_preimage(addr),
),
timelock_spend: None,
timelock_spend_broadcasted: false,
})
.chain(outgoing_swapcoins.iter().map(|osc| ContractInfo {
contract_tx: osc.contract_tx.clone(),
}))
.collect::<Vec<ContractInfo>>(),
)
.chain(
outgoing_swapcoins
.iter()
.zip(internal_addresses.iter())
.map(|(osc, addr)| ContractTransaction {
tx: osc.get_fully_signed_contract_tx(),
redeemscript: osc.contract_redeemscript.clone(),
hashlock_spend_without_preimage: None,
timelock_spend: Some(osc.create_timelock_spend(addr)),
timelock_spend_broadcasted: false,
}),
)
.collect::<Vec<ContractTransaction>>(),
wallet_label,
})
.await?;

let mut w = wallet.write().unwrap();
Expand Down
49 changes: 24 additions & 25 deletions src/taker_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,12 @@ use crate::messages::{

use crate::offerbook_sync::{sync_offerbook, OfferAddress};
use crate::wallet_sync::{
generate_keypair, CoreAddressLabelType, IncomingSwapCoin, OutgoingSwapCoin, Wallet,
generate_keypair, import_watchonly_redeemscript, IncomingSwapCoin, OutgoingSwapCoin, Wallet,
};

use crate::watchtower_client::ContractInfo;
use crate::watchtower_protocol::check_for_broadcasted_contract_txes;
use crate::watchtower_protocol::{
check_for_broadcasted_contract_txes, ContractTransaction, ContractsInfo,
};

#[tokio::main]
pub async fn start_taker(rpc: &Client, wallet: &mut Wallet) {
Expand Down Expand Up @@ -267,7 +268,6 @@ async fn send_coinswap(
} else {
let next_swapcoins = create_watch_only_swap_coins(
rpc,
wallet,
&maker_sign_sender_and_receiver_contracts,
&next_peer_multisig_pubkeys,
&next_swap_contract_redeemscripts,
Expand Down Expand Up @@ -753,20 +753,9 @@ async fn request_receivers_contract_tx_signatures<S: SwapCoin>(
async fn wait_for_funding_tx_confirmation(
rpc: &Client,
funding_txids: &[Txid],
contract_txes_to_watch: &[Vec<Transaction>],
contract_to_watch: &[Vec<Transaction>],
last_checked_block_height: &mut Option<u64>,
) -> Result<Option<(Vec<Transaction>, Vec<String>)>, Error> {
let contract_infos_to_watch = contract_txes_to_watch
.iter()
.map(|contract_txes| {
contract_txes
.iter()
.map(|contract_tx| ContractInfo {
contract_tx: contract_tx.clone(),
})
.collect::<Vec<ContractInfo>>()
})
.collect::<Vec<Vec<ContractInfo>>>();
let mut txid_tx_map = HashMap::<Txid, Transaction>::new();
let mut txid_blockhash_map = HashMap::<Txid, BlockHash>::new();
loop {
Expand Down Expand Up @@ -801,13 +790,28 @@ async fn wait_for_funding_tx_confirmation(
.collect::<Result<Vec<String>, bitcoincore_rpc::Error>>()?;
return Ok(Some((txes, merkleproofs)));
}
if !contract_infos_to_watch.is_empty() {
if !contract_to_watch.is_empty() {
let contracts_broadcasted = check_for_broadcasted_contract_txes(
rpc,
&contract_infos_to_watch,
&contract_to_watch
.iter()
.map(|txes| ContractsInfo {
contract_txes: txes
.iter()
.map(|tx| ContractTransaction {
tx: tx.clone(),
redeemscript: Script::new(),
hashlock_spend_without_preimage: None,
timelock_spend: None,
timelock_spend_broadcasted: false,
})
.collect::<Vec<ContractTransaction>>(),
wallet_label: String::new(),
})
.collect::<Vec<ContractsInfo>>(),
last_checked_block_height,
)?;
if contracts_broadcasted {
if !contracts_broadcasted.is_empty() {
log::info!("Contract transactions were broadcasted! Aborting");
return Ok(None);
}
Expand Down Expand Up @@ -1009,7 +1013,6 @@ fn sign_senders_contract_txes(

fn create_watch_only_swap_coins(
rpc: &Client,
wallet: &mut Wallet,
maker_sign_sender_and_receiver_contracts: &SignSendersAndReceiversContractTxes,
next_peer_multisig_pubkeys: &[PublicKey],
next_swap_contract_redeemscripts: &[Script],
Expand All @@ -1036,11 +1039,7 @@ fn create_watch_only_swap_coins(
//TODO error handle here the case where next_swapcoin.contract_tx script pubkey
// is not equal to p2wsh(next_swap_contract_redeemscripts)
for swapcoin in &next_swapcoins {
wallet.import_redeemscript(
rpc,
&swapcoin.get_multisig_redeemscript(),
CoreAddressLabelType::WatchOnlySwapCoin,
)?
import_watchonly_redeemscript(rpc, &swapcoin.get_multisig_redeemscript())?
}
Ok(next_swapcoins)
}
Expand Down
2 changes: 1 addition & 1 deletion src/wallet_direct_send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ impl Wallet {
if dest_addr.network != NETWORK {
panic!("wrong address network type (e.g. testnet, regtest)");
}
let miner_fee = 500; //TODO do this calculation properly
let miner_fee = 2000; //TODO do this calculation properly

let mut output = Vec::<TxOut>::new();
let total_input_value = unspent_inputs
Expand Down
Loading

0 comments on commit ba2731a

Please sign in to comment.