mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-01-13 08:29:35 -05:00
Merge #190
190: Do not pass Monero amount to the CLI r=D4nte a=D4nte The CLI user only pass the Bitcoin amount they want to sell. The CLI then do a quote request to nectar which provides the Monero amount the taker can get. Co-authored-by: Franck Royer <franck@coblox.tech>
This commit is contained in:
commit
4d8e801c1e
@ -35,7 +35,6 @@ use swap::{
|
|||||||
protocol::{
|
protocol::{
|
||||||
bob,
|
bob,
|
||||||
bob::{cancel::CancelError, Builder},
|
bob::{cancel::CancelError, Builder},
|
||||||
SwapAmounts,
|
|
||||||
},
|
},
|
||||||
seed::Seed,
|
seed::Seed,
|
||||||
trace::init_tracing,
|
trace::init_tracing,
|
||||||
@ -89,21 +88,15 @@ async fn main() -> Result<()> {
|
|||||||
alice_peer_id,
|
alice_peer_id,
|
||||||
alice_addr,
|
alice_addr,
|
||||||
send_bitcoin,
|
send_bitcoin,
|
||||||
receive_monero,
|
|
||||||
} => {
|
} => {
|
||||||
let swap_amounts = SwapAmounts {
|
|
||||||
btc: send_bitcoin,
|
|
||||||
xmr: receive_monero,
|
|
||||||
};
|
|
||||||
|
|
||||||
let (bitcoin_wallet, monero_wallet) =
|
let (bitcoin_wallet, monero_wallet) =
|
||||||
init_wallets(config, bitcoin_network, monero_network).await?;
|
init_wallets(config, bitcoin_network, monero_network).await?;
|
||||||
|
|
||||||
let swap_id = Uuid::new_v4();
|
let swap_id = Uuid::new_v4();
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"Swap sending {} and receiving {} started with ID {}",
|
"Swap buy XMR with {} started with ID {}",
|
||||||
send_bitcoin, receive_monero, swap_id
|
send_bitcoin, swap_id
|
||||||
);
|
);
|
||||||
|
|
||||||
let bob_factory = Builder::new(
|
let bob_factory = Builder::new(
|
||||||
@ -116,7 +109,7 @@ async fn main() -> Result<()> {
|
|||||||
alice_peer_id,
|
alice_peer_id,
|
||||||
execution_params,
|
execution_params,
|
||||||
);
|
);
|
||||||
let (swap, event_loop) = bob_factory.with_init_params(swap_amounts).build().await?;
|
let (swap, event_loop) = bob_factory.with_init_params(send_bitcoin).build().await?;
|
||||||
|
|
||||||
tokio::spawn(async move { event_loop.run().await });
|
tokio::spawn(async move { event_loop.run().await });
|
||||||
bob::run(swap).await?;
|
bob::run(swap).await?;
|
||||||
|
@ -1,16 +1,25 @@
|
|||||||
pub mod timelocks;
|
|
||||||
pub mod transactions;
|
|
||||||
pub mod wallet;
|
pub mod wallet;
|
||||||
|
|
||||||
|
mod cancel;
|
||||||
|
mod lock;
|
||||||
|
mod punish;
|
||||||
|
mod redeem;
|
||||||
|
mod refund;
|
||||||
|
mod timelocks;
|
||||||
|
|
||||||
pub use crate::bitcoin::{
|
pub use crate::bitcoin::{
|
||||||
timelocks::Timelock,
|
cancel::{CancelTimelock, PunishTimelock, TxCancel},
|
||||||
transactions::{TxCancel, TxLock, TxPunish, TxRedeem, TxRefund},
|
lock::TxLock,
|
||||||
|
punish::TxPunish,
|
||||||
|
redeem::TxRedeem,
|
||||||
|
refund::TxRefund,
|
||||||
|
timelocks::{BlockHeight, ExpiredTimelocks},
|
||||||
};
|
};
|
||||||
pub use ::bitcoin::{util::amount::Amount, Address, Network, Transaction, Txid};
|
pub use ::bitcoin::{util::amount::Amount, Address, Network, Transaction, Txid};
|
||||||
pub use ecdsa_fun::{adaptor::EncryptedSignature, fun::Scalar, Signature};
|
pub use ecdsa_fun::{adaptor::EncryptedSignature, fun::Scalar, Signature};
|
||||||
pub use wallet::Wallet;
|
pub use wallet::Wallet;
|
||||||
|
|
||||||
use crate::{bitcoin::timelocks::BlockHeight, execution_params::ExecutionParams};
|
use crate::execution_params::ExecutionParams;
|
||||||
use ::bitcoin::{
|
use ::bitcoin::{
|
||||||
hashes::{hex::ToHex, Hash},
|
hashes::{hex::ToHex, Hash},
|
||||||
secp256k1,
|
secp256k1,
|
||||||
@ -25,7 +34,6 @@ use rand::{CryptoRng, RngCore};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use timelocks::ExpiredTimelocks;
|
|
||||||
|
|
||||||
// TODO: Configurable tx-fee (note: parties have to agree prior to swapping)
|
// TODO: Configurable tx-fee (note: parties have to agree prior to swapping)
|
||||||
// Current reasoning:
|
// Current reasoning:
|
||||||
@ -262,8 +270,8 @@ where
|
|||||||
|
|
||||||
pub async fn current_epoch<W>(
|
pub async fn current_epoch<W>(
|
||||||
bitcoin_wallet: &W,
|
bitcoin_wallet: &W,
|
||||||
cancel_timelock: Timelock,
|
cancel_timelock: CancelTimelock,
|
||||||
punish_timelock: Timelock,
|
punish_timelock: PunishTimelock,
|
||||||
lock_tx_id: ::bitcoin::Txid,
|
lock_tx_id: ::bitcoin::Txid,
|
||||||
) -> anyhow::Result<ExpiredTimelocks>
|
) -> anyhow::Result<ExpiredTimelocks>
|
||||||
where
|
where
|
||||||
@ -286,7 +294,7 @@ where
|
|||||||
|
|
||||||
pub async fn wait_for_cancel_timelock_to_expire<W>(
|
pub async fn wait_for_cancel_timelock_to_expire<W>(
|
||||||
bitcoin_wallet: &W,
|
bitcoin_wallet: &W,
|
||||||
cancel_timelock: Timelock,
|
cancel_timelock: CancelTimelock,
|
||||||
lock_tx_id: ::bitcoin::Txid,
|
lock_tx_id: ::bitcoin::Txid,
|
||||||
) -> Result<()>
|
) -> Result<()>
|
||||||
where
|
where
|
||||||
@ -297,3 +305,19 @@ where
|
|||||||
poll_until_block_height_is_gte(bitcoin_wallet, tx_lock_height + cancel_timelock).await;
|
poll_until_block_height_is_gte(bitcoin_wallet, tx_lock_height + cancel_timelock).await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, thiserror::Error, Debug)]
|
||||||
|
#[error("transaction does not spend anything")]
|
||||||
|
pub struct NoInputs;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, thiserror::Error, Debug)]
|
||||||
|
#[error("transaction has {0} inputs, expected 1")]
|
||||||
|
pub struct TooManyInputs(usize);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, thiserror::Error, Debug)]
|
||||||
|
#[error("empty witness stack")]
|
||||||
|
pub struct EmptyWitnessStack;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, thiserror::Error, Debug)]
|
||||||
|
#[error("input has {0} witnesses, expected 3")]
|
||||||
|
pub struct NotThreeWitnesses(usize);
|
||||||
|
180
swap/src/bitcoin/cancel.rs
Normal file
180
swap/src/bitcoin/cancel.rs
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
use crate::bitcoin::{
|
||||||
|
build_shared_output_descriptor, Address, Amount, BlockHeight, PublicKey, Transaction, TxLock,
|
||||||
|
TX_FEE,
|
||||||
|
};
|
||||||
|
use ::bitcoin::{util::bip143::SigHashCache, OutPoint, SigHash, SigHashType, TxIn, TxOut, Txid};
|
||||||
|
use anyhow::Result;
|
||||||
|
use ecdsa_fun::Signature;
|
||||||
|
use miniscript::{Descriptor, NullCtx};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{collections::HashMap, ops::Add};
|
||||||
|
|
||||||
|
/// Represent a timelock, expressed in relative block height as defined in
|
||||||
|
/// [BIP68](https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki).
|
||||||
|
/// E.g. The timelock expires 10 blocks after the reference transaction is
|
||||||
|
/// mined.
|
||||||
|
#[derive(Debug, Copy, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct CancelTimelock(u32);
|
||||||
|
|
||||||
|
impl CancelTimelock {
|
||||||
|
pub const fn new(number_of_blocks: u32) -> Self {
|
||||||
|
Self(number_of_blocks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<CancelTimelock> for BlockHeight {
|
||||||
|
type Output = BlockHeight;
|
||||||
|
|
||||||
|
fn add(self, rhs: CancelTimelock) -> Self::Output {
|
||||||
|
self + rhs.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represent a timelock, expressed in relative block height as defined in
|
||||||
|
/// [BIP68](https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki).
|
||||||
|
/// E.g. The timelock expires 10 blocks after the reference transaction is
|
||||||
|
/// mined.
|
||||||
|
#[derive(Debug, Copy, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct PunishTimelock(u32);
|
||||||
|
|
||||||
|
impl PunishTimelock {
|
||||||
|
pub const fn new(number_of_blocks: u32) -> Self {
|
||||||
|
Self(number_of_blocks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<PunishTimelock> for BlockHeight {
|
||||||
|
type Output = BlockHeight;
|
||||||
|
|
||||||
|
fn add(self, rhs: PunishTimelock) -> Self::Output {
|
||||||
|
self + rhs.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TxCancel {
|
||||||
|
inner: Transaction,
|
||||||
|
digest: SigHash,
|
||||||
|
pub(in crate::bitcoin) output_descriptor: Descriptor<::bitcoin::PublicKey>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TxCancel {
|
||||||
|
pub fn new(
|
||||||
|
tx_lock: &TxLock,
|
||||||
|
cancel_timelock: CancelTimelock,
|
||||||
|
A: PublicKey,
|
||||||
|
B: PublicKey,
|
||||||
|
) -> Self {
|
||||||
|
let cancel_output_descriptor = build_shared_output_descriptor(A.0, B.0);
|
||||||
|
|
||||||
|
let tx_in = TxIn {
|
||||||
|
previous_output: tx_lock.as_outpoint(),
|
||||||
|
script_sig: Default::default(),
|
||||||
|
sequence: cancel_timelock.0,
|
||||||
|
witness: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let tx_out = TxOut {
|
||||||
|
value: tx_lock.lock_amount().as_sat() - TX_FEE,
|
||||||
|
script_pubkey: cancel_output_descriptor.script_pubkey(NullCtx),
|
||||||
|
};
|
||||||
|
|
||||||
|
let transaction = Transaction {
|
||||||
|
version: 2,
|
||||||
|
lock_time: 0,
|
||||||
|
input: vec![tx_in],
|
||||||
|
output: vec![tx_out],
|
||||||
|
};
|
||||||
|
|
||||||
|
let digest = SigHashCache::new(&transaction).signature_hash(
|
||||||
|
0, // Only one input: lock_input (lock transaction)
|
||||||
|
&tx_lock.output_descriptor.witness_script(NullCtx),
|
||||||
|
tx_lock.lock_amount().as_sat(),
|
||||||
|
SigHashType::All,
|
||||||
|
);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
inner: transaction,
|
||||||
|
digest,
|
||||||
|
output_descriptor: cancel_output_descriptor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn txid(&self) -> Txid {
|
||||||
|
self.inner.txid()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn digest(&self) -> SigHash {
|
||||||
|
self.digest
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn amount(&self) -> Amount {
|
||||||
|
Amount::from_sat(self.inner.output[0].value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_outpoint(&self) -> OutPoint {
|
||||||
|
OutPoint::new(self.inner.txid(), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_signatures(
|
||||||
|
self,
|
||||||
|
tx_lock: &TxLock,
|
||||||
|
(A, sig_a): (PublicKey, Signature),
|
||||||
|
(B, sig_b): (PublicKey, Signature),
|
||||||
|
) -> Result<Transaction> {
|
||||||
|
let satisfier = {
|
||||||
|
let mut satisfier = HashMap::with_capacity(2);
|
||||||
|
|
||||||
|
let A = ::bitcoin::PublicKey {
|
||||||
|
compressed: true,
|
||||||
|
key: A.0.into(),
|
||||||
|
};
|
||||||
|
let B = ::bitcoin::PublicKey {
|
||||||
|
compressed: true,
|
||||||
|
key: B.0.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// The order in which these are inserted doesn't matter
|
||||||
|
satisfier.insert(A, (sig_a.into(), ::bitcoin::SigHashType::All));
|
||||||
|
satisfier.insert(B, (sig_b.into(), ::bitcoin::SigHashType::All));
|
||||||
|
|
||||||
|
satisfier
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut tx_cancel = self.inner;
|
||||||
|
tx_lock
|
||||||
|
.output_descriptor
|
||||||
|
.satisfy(&mut tx_cancel.input[0], satisfier, NullCtx)?;
|
||||||
|
|
||||||
|
Ok(tx_cancel)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_spend_transaction(
|
||||||
|
&self,
|
||||||
|
spend_address: &Address,
|
||||||
|
sequence: Option<PunishTimelock>,
|
||||||
|
) -> Transaction {
|
||||||
|
let previous_output = self.as_outpoint();
|
||||||
|
|
||||||
|
let tx_in = TxIn {
|
||||||
|
previous_output,
|
||||||
|
script_sig: Default::default(),
|
||||||
|
sequence: sequence.map(|seq| seq.0).unwrap_or(0xFFFF_FFFF),
|
||||||
|
witness: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let tx_out = TxOut {
|
||||||
|
value: self.amount().as_sat() - TX_FEE,
|
||||||
|
script_pubkey: spend_address.script_pubkey(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Transaction {
|
||||||
|
version: 2,
|
||||||
|
lock_time: 0,
|
||||||
|
input: vec![tx_in],
|
||||||
|
output: vec![tx_out],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
98
swap/src/bitcoin/lock.rs
Normal file
98
swap/src/bitcoin/lock.rs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
use crate::bitcoin::{
|
||||||
|
build_shared_output_descriptor, Address, Amount, BuildTxLockPsbt, GetNetwork, PublicKey,
|
||||||
|
Transaction, TX_FEE,
|
||||||
|
};
|
||||||
|
use ::bitcoin::{util::psbt::PartiallySignedTransaction, OutPoint, TxIn, TxOut, Txid};
|
||||||
|
use anyhow::Result;
|
||||||
|
use miniscript::{Descriptor, NullCtx};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct TxLock {
|
||||||
|
inner: Transaction,
|
||||||
|
pub(in crate::bitcoin) output_descriptor: Descriptor<::bitcoin::PublicKey>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TxLock {
|
||||||
|
pub async fn new<W>(wallet: &W, amount: Amount, A: PublicKey, B: PublicKey) -> Result<Self>
|
||||||
|
where
|
||||||
|
W: BuildTxLockPsbt + GetNetwork,
|
||||||
|
{
|
||||||
|
let lock_output_descriptor = build_shared_output_descriptor(A.0, B.0);
|
||||||
|
let address = lock_output_descriptor
|
||||||
|
.address(wallet.get_network(), NullCtx)
|
||||||
|
.expect("can derive address from descriptor");
|
||||||
|
|
||||||
|
// We construct a psbt for convenience
|
||||||
|
let psbt = wallet.build_tx_lock_psbt(address, amount).await?;
|
||||||
|
|
||||||
|
// We don't take advantage of psbt functionality yet, instead we convert to a
|
||||||
|
// raw transaction
|
||||||
|
let inner = psbt.extract_tx();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
inner,
|
||||||
|
output_descriptor: lock_output_descriptor,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lock_amount(&self) -> Amount {
|
||||||
|
Amount::from_sat(self.inner.output[self.lock_output_vout()].value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn txid(&self) -> Txid {
|
||||||
|
self.inner.txid()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_outpoint(&self) -> OutPoint {
|
||||||
|
// This is fine because a transaction that has that many outputs is not
|
||||||
|
// realistic
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
OutPoint::new(self.inner.txid(), self.lock_output_vout() as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retreive the index of the locked output in the transaction outputs
|
||||||
|
/// vector
|
||||||
|
fn lock_output_vout(&self) -> usize {
|
||||||
|
self.inner
|
||||||
|
.output
|
||||||
|
.iter()
|
||||||
|
.position(|output| {
|
||||||
|
output.script_pubkey == self.output_descriptor.script_pubkey(NullCtx)
|
||||||
|
})
|
||||||
|
.expect("transaction contains lock output")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_spend_transaction(
|
||||||
|
&self,
|
||||||
|
spend_address: &Address,
|
||||||
|
sequence: Option<u32>,
|
||||||
|
) -> Transaction {
|
||||||
|
let previous_output = self.as_outpoint();
|
||||||
|
|
||||||
|
let tx_in = TxIn {
|
||||||
|
previous_output,
|
||||||
|
script_sig: Default::default(),
|
||||||
|
sequence: sequence.unwrap_or(0xFFFF_FFFF),
|
||||||
|
witness: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let tx_out = TxOut {
|
||||||
|
value: self.inner.output[self.lock_output_vout()].value - TX_FEE,
|
||||||
|
script_pubkey: spend_address.script_pubkey(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Transaction {
|
||||||
|
version: 2,
|
||||||
|
lock_time: 0,
|
||||||
|
input: vec![tx_in],
|
||||||
|
output: vec![tx_out],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TxLock> for PartiallySignedTransaction {
|
||||||
|
fn from(from: TxLock) -> Self {
|
||||||
|
PartiallySignedTransaction::from_unsigned_tx(from.inner).expect("to be unsigned")
|
||||||
|
}
|
||||||
|
}
|
71
swap/src/bitcoin/punish.rs
Normal file
71
swap/src/bitcoin/punish.rs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
use crate::bitcoin::{Address, PublicKey, PunishTimelock, Transaction, TxCancel};
|
||||||
|
use ::bitcoin::{util::bip143::SigHashCache, SigHash, SigHashType};
|
||||||
|
use anyhow::Result;
|
||||||
|
use ecdsa_fun::Signature;
|
||||||
|
use miniscript::NullCtx;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TxPunish {
|
||||||
|
inner: Transaction,
|
||||||
|
digest: SigHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TxPunish {
|
||||||
|
pub fn new(
|
||||||
|
tx_cancel: &TxCancel,
|
||||||
|
punish_address: &Address,
|
||||||
|
punish_timelock: PunishTimelock,
|
||||||
|
) -> Self {
|
||||||
|
let tx_punish = tx_cancel.build_spend_transaction(punish_address, Some(punish_timelock));
|
||||||
|
|
||||||
|
let digest = SigHashCache::new(&tx_punish).signature_hash(
|
||||||
|
0, // Only one input: cancel transaction
|
||||||
|
&tx_cancel.output_descriptor.witness_script(NullCtx),
|
||||||
|
tx_cancel.amount().as_sat(),
|
||||||
|
SigHashType::All,
|
||||||
|
);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
inner: tx_punish,
|
||||||
|
digest,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn digest(&self) -> SigHash {
|
||||||
|
self.digest
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_signatures(
|
||||||
|
self,
|
||||||
|
tx_cancel: &TxCancel,
|
||||||
|
(A, sig_a): (PublicKey, Signature),
|
||||||
|
(B, sig_b): (PublicKey, Signature),
|
||||||
|
) -> Result<Transaction> {
|
||||||
|
let satisfier = {
|
||||||
|
let mut satisfier = HashMap::with_capacity(2);
|
||||||
|
|
||||||
|
let A = ::bitcoin::PublicKey {
|
||||||
|
compressed: true,
|
||||||
|
key: A.0.into(),
|
||||||
|
};
|
||||||
|
let B = ::bitcoin::PublicKey {
|
||||||
|
compressed: true,
|
||||||
|
key: B.0.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// The order in which these are inserted doesn't matter
|
||||||
|
satisfier.insert(A, (sig_a.into(), ::bitcoin::SigHashType::All));
|
||||||
|
satisfier.insert(B, (sig_b.into(), ::bitcoin::SigHashType::All));
|
||||||
|
|
||||||
|
satisfier
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut tx_punish = self.inner;
|
||||||
|
tx_cancel
|
||||||
|
.output_descriptor
|
||||||
|
.satisfy(&mut tx_punish.input[0], satisfier, NullCtx)?;
|
||||||
|
|
||||||
|
Ok(tx_punish)
|
||||||
|
}
|
||||||
|
}
|
113
swap/src/bitcoin/redeem.rs
Normal file
113
swap/src/bitcoin/redeem.rs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
use crate::bitcoin::{
|
||||||
|
verify_sig, Address, EmptyWitnessStack, NoInputs, NotThreeWitnesses, PublicKey, TooManyInputs,
|
||||||
|
Transaction, TxLock,
|
||||||
|
};
|
||||||
|
use ::bitcoin::{util::bip143::SigHashCache, SigHash, SigHashType, Txid};
|
||||||
|
use anyhow::{bail, Context, Result};
|
||||||
|
use ecdsa_fun::Signature;
|
||||||
|
use miniscript::NullCtx;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TxRedeem {
|
||||||
|
inner: Transaction,
|
||||||
|
digest: SigHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TxRedeem {
|
||||||
|
pub fn new(tx_lock: &TxLock, redeem_address: &Address) -> Self {
|
||||||
|
// lock_input is the shared output that is now being used as an input for the
|
||||||
|
// redeem transaction
|
||||||
|
let tx_redeem = tx_lock.build_spend_transaction(redeem_address, None);
|
||||||
|
|
||||||
|
let digest = SigHashCache::new(&tx_redeem).signature_hash(
|
||||||
|
0, // Only one input: lock_input (lock transaction)
|
||||||
|
&tx_lock.output_descriptor.witness_script(NullCtx),
|
||||||
|
tx_lock.lock_amount().as_sat(),
|
||||||
|
SigHashType::All,
|
||||||
|
);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
inner: tx_redeem,
|
||||||
|
digest,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn txid(&self) -> Txid {
|
||||||
|
self.inner.txid()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn digest(&self) -> SigHash {
|
||||||
|
self.digest
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_signatures(
|
||||||
|
self,
|
||||||
|
tx_lock: &TxLock,
|
||||||
|
(A, sig_a): (PublicKey, Signature),
|
||||||
|
(B, sig_b): (PublicKey, Signature),
|
||||||
|
) -> Result<Transaction> {
|
||||||
|
let satisfier = {
|
||||||
|
let mut satisfier = HashMap::with_capacity(2);
|
||||||
|
|
||||||
|
let A = ::bitcoin::PublicKey {
|
||||||
|
compressed: true,
|
||||||
|
key: A.0.into(),
|
||||||
|
};
|
||||||
|
let B = ::bitcoin::PublicKey {
|
||||||
|
compressed: true,
|
||||||
|
key: B.0.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// The order in which these are inserted doesn't matter
|
||||||
|
satisfier.insert(A, (sig_a.into(), ::bitcoin::SigHashType::All));
|
||||||
|
satisfier.insert(B, (sig_b.into(), ::bitcoin::SigHashType::All));
|
||||||
|
|
||||||
|
satisfier
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut tx_redeem = self.inner;
|
||||||
|
tx_lock
|
||||||
|
.output_descriptor
|
||||||
|
.satisfy(&mut tx_redeem.input[0], satisfier, NullCtx)?;
|
||||||
|
|
||||||
|
Ok(tx_redeem)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract_signature_by_key(
|
||||||
|
&self,
|
||||||
|
candidate_transaction: Transaction,
|
||||||
|
B: PublicKey,
|
||||||
|
) -> Result<Signature> {
|
||||||
|
let input = match candidate_transaction.input.as_slice() {
|
||||||
|
[input] => input,
|
||||||
|
[] => bail!(NoInputs),
|
||||||
|
[inputs @ ..] => bail!(TooManyInputs(inputs.len())),
|
||||||
|
};
|
||||||
|
|
||||||
|
let sigs = match input
|
||||||
|
.witness
|
||||||
|
.iter()
|
||||||
|
.map(|vec| vec.as_slice())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.as_slice()
|
||||||
|
{
|
||||||
|
[sig_1, sig_2, _script] => [sig_1, sig_2]
|
||||||
|
.iter()
|
||||||
|
.map(|sig| {
|
||||||
|
bitcoin::secp256k1::Signature::from_der(&sig[..sig.len() - 1])
|
||||||
|
.map(Signature::from)
|
||||||
|
})
|
||||||
|
.collect::<std::result::Result<Vec<_>, _>>(),
|
||||||
|
[] => bail!(EmptyWitnessStack),
|
||||||
|
[witnesses @ ..] => bail!(NotThreeWitnesses(witnesses.len())),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
let sig = sigs
|
||||||
|
.into_iter()
|
||||||
|
.find(|sig| verify_sig(&B, &self.digest(), &sig).is_ok())
|
||||||
|
.context("neither signature on witness stack verifies against B")?;
|
||||||
|
|
||||||
|
Ok(sig)
|
||||||
|
}
|
||||||
|
}
|
111
swap/src/bitcoin/refund.rs
Normal file
111
swap/src/bitcoin/refund.rs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
use crate::bitcoin::{
|
||||||
|
verify_sig, Address, EmptyWitnessStack, NoInputs, NotThreeWitnesses, PublicKey, TooManyInputs,
|
||||||
|
Transaction, TxCancel,
|
||||||
|
};
|
||||||
|
use ::bitcoin::{util::bip143::SigHashCache, SigHash, SigHashType, Txid};
|
||||||
|
use anyhow::{bail, Context, Result};
|
||||||
|
use ecdsa_fun::Signature;
|
||||||
|
use miniscript::NullCtx;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TxRefund {
|
||||||
|
inner: Transaction,
|
||||||
|
digest: SigHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TxRefund {
|
||||||
|
pub fn new(tx_cancel: &TxCancel, refund_address: &Address) -> Self {
|
||||||
|
let tx_punish = tx_cancel.build_spend_transaction(refund_address, None);
|
||||||
|
|
||||||
|
let digest = SigHashCache::new(&tx_punish).signature_hash(
|
||||||
|
0, // Only one input: cancel transaction
|
||||||
|
&tx_cancel.output_descriptor.witness_script(NullCtx),
|
||||||
|
tx_cancel.amount().as_sat(),
|
||||||
|
SigHashType::All,
|
||||||
|
);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
inner: tx_punish,
|
||||||
|
digest,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn txid(&self) -> Txid {
|
||||||
|
self.inner.txid()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn digest(&self) -> SigHash {
|
||||||
|
self.digest
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_signatures(
|
||||||
|
self,
|
||||||
|
tx_cancel: &TxCancel,
|
||||||
|
(A, sig_a): (PublicKey, Signature),
|
||||||
|
(B, sig_b): (PublicKey, Signature),
|
||||||
|
) -> Result<Transaction> {
|
||||||
|
let satisfier = {
|
||||||
|
let mut satisfier = HashMap::with_capacity(2);
|
||||||
|
|
||||||
|
let A = ::bitcoin::PublicKey {
|
||||||
|
compressed: true,
|
||||||
|
key: A.0.into(),
|
||||||
|
};
|
||||||
|
let B = ::bitcoin::PublicKey {
|
||||||
|
compressed: true,
|
||||||
|
key: B.0.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// The order in which these are inserted doesn't matter
|
||||||
|
satisfier.insert(A, (sig_a.into(), ::bitcoin::SigHashType::All));
|
||||||
|
satisfier.insert(B, (sig_b.into(), ::bitcoin::SigHashType::All));
|
||||||
|
|
||||||
|
satisfier
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut tx_refund = self.inner;
|
||||||
|
tx_cancel
|
||||||
|
.output_descriptor
|
||||||
|
.satisfy(&mut tx_refund.input[0], satisfier, NullCtx)?;
|
||||||
|
|
||||||
|
Ok(tx_refund)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract_signature_by_key(
|
||||||
|
&self,
|
||||||
|
candidate_transaction: Transaction,
|
||||||
|
B: PublicKey,
|
||||||
|
) -> Result<Signature> {
|
||||||
|
let input = match candidate_transaction.input.as_slice() {
|
||||||
|
[input] => input,
|
||||||
|
[] => bail!(NoInputs),
|
||||||
|
[inputs @ ..] => bail!(TooManyInputs(inputs.len())),
|
||||||
|
};
|
||||||
|
|
||||||
|
let sigs = match input
|
||||||
|
.witness
|
||||||
|
.iter()
|
||||||
|
.map(|vec| vec.as_slice())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.as_slice()
|
||||||
|
{
|
||||||
|
[sig_1, sig_2, _script] => [sig_1, sig_2]
|
||||||
|
.iter()
|
||||||
|
.map(|sig| {
|
||||||
|
bitcoin::secp256k1::Signature::from_der(&sig[..sig.len() - 1])
|
||||||
|
.map(Signature::from)
|
||||||
|
})
|
||||||
|
.collect::<std::result::Result<Vec<_>, _>>(),
|
||||||
|
[] => bail!(EmptyWitnessStack),
|
||||||
|
[witnesses @ ..] => bail!(NotThreeWitnesses(witnesses.len())),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
let sig = sigs
|
||||||
|
.into_iter()
|
||||||
|
.find(|sig| verify_sig(&B, &self.digest(), &sig).is_ok())
|
||||||
|
.context("neither signature on witness stack verifies against B")?;
|
||||||
|
|
||||||
|
Ok(sig)
|
||||||
|
}
|
||||||
|
}
|
@ -1,26 +1,5 @@
|
|||||||
use std::ops::Add;
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::ops::Add;
|
||||||
/// Represent a timelock, expressed in relative block height as defined in
|
|
||||||
/// [BIP68](https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki).
|
|
||||||
/// E.g. The timelock expires 10 blocks after the reference transaction is
|
|
||||||
/// mined.
|
|
||||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
|
||||||
#[serde(transparent)]
|
|
||||||
pub struct Timelock(u32);
|
|
||||||
|
|
||||||
impl Timelock {
|
|
||||||
pub const fn new(number_of_blocks: u32) -> Self {
|
|
||||||
Self(number_of_blocks)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Timelock> for u32 {
|
|
||||||
fn from(timelock: Timelock) -> Self {
|
|
||||||
timelock.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represent a block height, or block number, expressed in absolute block
|
/// Represent a block height, or block number, expressed in absolute block
|
||||||
/// count. E.g. The transaction was included in block #655123, 655123 block
|
/// count. E.g. The transaction was included in block #655123, 655123 block
|
||||||
@ -41,11 +20,11 @@ impl BlockHeight {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Add<Timelock> for BlockHeight {
|
impl Add<u32> for BlockHeight {
|
||||||
type Output = BlockHeight;
|
type Output = BlockHeight;
|
||||||
|
|
||||||
fn add(self, rhs: Timelock) -> Self::Output {
|
fn add(self, rhs: u32) -> Self::Output {
|
||||||
BlockHeight(self.0 + rhs.0)
|
BlockHeight(self.0 + rhs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,507 +0,0 @@
|
|||||||
use crate::bitcoin::{
|
|
||||||
build_shared_output_descriptor, timelocks::Timelock, verify_sig, Address, Amount,
|
|
||||||
BuildTxLockPsbt, GetNetwork, PublicKey, Transaction, TX_FEE,
|
|
||||||
};
|
|
||||||
use ::bitcoin::{
|
|
||||||
util::{bip143::SigHashCache, psbt::PartiallySignedTransaction},
|
|
||||||
OutPoint, SigHash, SigHashType, TxIn, TxOut, Txid,
|
|
||||||
};
|
|
||||||
use anyhow::{bail, Context, Result};
|
|
||||||
use ecdsa_fun::Signature;
|
|
||||||
use miniscript::{Descriptor, NullCtx};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
||||||
pub struct TxLock {
|
|
||||||
inner: Transaction,
|
|
||||||
output_descriptor: Descriptor<::bitcoin::PublicKey>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TxLock {
|
|
||||||
pub async fn new<W>(wallet: &W, amount: Amount, A: PublicKey, B: PublicKey) -> Result<Self>
|
|
||||||
where
|
|
||||||
W: BuildTxLockPsbt + GetNetwork,
|
|
||||||
{
|
|
||||||
let lock_output_descriptor = build_shared_output_descriptor(A.0, B.0);
|
|
||||||
let address = lock_output_descriptor
|
|
||||||
.address(wallet.get_network(), NullCtx)
|
|
||||||
.expect("can derive address from descriptor");
|
|
||||||
|
|
||||||
// We construct a psbt for convenience
|
|
||||||
let psbt = wallet.build_tx_lock_psbt(address, amount).await?;
|
|
||||||
|
|
||||||
// We don't take advantage of psbt functionality yet, instead we convert to a
|
|
||||||
// raw transaction
|
|
||||||
let inner = psbt.extract_tx();
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
inner,
|
|
||||||
output_descriptor: lock_output_descriptor,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lock_amount(&self) -> Amount {
|
|
||||||
Amount::from_sat(self.inner.output[self.lock_output_vout()].value)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn txid(&self) -> Txid {
|
|
||||||
self.inner.txid()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_outpoint(&self) -> OutPoint {
|
|
||||||
// This is fine because a transaction that has that many outputs is not
|
|
||||||
// realistic
|
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
|
||||||
OutPoint::new(self.inner.txid(), self.lock_output_vout() as u32)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retreive the index of the locked output in the transaction outputs
|
|
||||||
/// vector
|
|
||||||
fn lock_output_vout(&self) -> usize {
|
|
||||||
self.inner
|
|
||||||
.output
|
|
||||||
.iter()
|
|
||||||
.position(|output| {
|
|
||||||
output.script_pubkey == self.output_descriptor.script_pubkey(NullCtx)
|
|
||||||
})
|
|
||||||
.expect("transaction contains lock output")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_spend_transaction(
|
|
||||||
&self,
|
|
||||||
spend_address: &Address,
|
|
||||||
sequence: Option<u32>,
|
|
||||||
) -> Transaction {
|
|
||||||
let previous_output = self.as_outpoint();
|
|
||||||
|
|
||||||
let tx_in = TxIn {
|
|
||||||
previous_output,
|
|
||||||
script_sig: Default::default(),
|
|
||||||
sequence: sequence.unwrap_or(0xFFFF_FFFF),
|
|
||||||
witness: Vec::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let tx_out = TxOut {
|
|
||||||
value: self.inner.output[self.lock_output_vout()].value - TX_FEE,
|
|
||||||
script_pubkey: spend_address.script_pubkey(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Transaction {
|
|
||||||
version: 2,
|
|
||||||
lock_time: 0,
|
|
||||||
input: vec![tx_in],
|
|
||||||
output: vec![tx_out],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<TxLock> for PartiallySignedTransaction {
|
|
||||||
fn from(from: TxLock) -> Self {
|
|
||||||
PartiallySignedTransaction::from_unsigned_tx(from.inner).expect("to be unsigned")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct TxRedeem {
|
|
||||||
inner: Transaction,
|
|
||||||
digest: SigHash,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TxRedeem {
|
|
||||||
pub fn new(tx_lock: &TxLock, redeem_address: &Address) -> Self {
|
|
||||||
// lock_input is the shared output that is now being used as an input for the
|
|
||||||
// redeem transaction
|
|
||||||
let tx_redeem = tx_lock.build_spend_transaction(redeem_address, None);
|
|
||||||
|
|
||||||
let digest = SigHashCache::new(&tx_redeem).signature_hash(
|
|
||||||
0, // Only one input: lock_input (lock transaction)
|
|
||||||
&tx_lock.output_descriptor.witness_script(NullCtx),
|
|
||||||
tx_lock.lock_amount().as_sat(),
|
|
||||||
SigHashType::All,
|
|
||||||
);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
inner: tx_redeem,
|
|
||||||
digest,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn txid(&self) -> Txid {
|
|
||||||
self.inner.txid()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn digest(&self) -> SigHash {
|
|
||||||
self.digest
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_signatures(
|
|
||||||
self,
|
|
||||||
tx_lock: &TxLock,
|
|
||||||
(A, sig_a): (PublicKey, Signature),
|
|
||||||
(B, sig_b): (PublicKey, Signature),
|
|
||||||
) -> Result<Transaction> {
|
|
||||||
let satisfier = {
|
|
||||||
let mut satisfier = HashMap::with_capacity(2);
|
|
||||||
|
|
||||||
let A = ::bitcoin::PublicKey {
|
|
||||||
compressed: true,
|
|
||||||
key: A.0.into(),
|
|
||||||
};
|
|
||||||
let B = ::bitcoin::PublicKey {
|
|
||||||
compressed: true,
|
|
||||||
key: B.0.into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// The order in which these are inserted doesn't matter
|
|
||||||
satisfier.insert(A, (sig_a.into(), ::bitcoin::SigHashType::All));
|
|
||||||
satisfier.insert(B, (sig_b.into(), ::bitcoin::SigHashType::All));
|
|
||||||
|
|
||||||
satisfier
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut tx_redeem = self.inner;
|
|
||||||
tx_lock
|
|
||||||
.output_descriptor
|
|
||||||
.satisfy(&mut tx_redeem.input[0], satisfier, NullCtx)?;
|
|
||||||
|
|
||||||
Ok(tx_redeem)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_signature_by_key(
|
|
||||||
&self,
|
|
||||||
candidate_transaction: Transaction,
|
|
||||||
B: PublicKey,
|
|
||||||
) -> Result<Signature> {
|
|
||||||
let input = match candidate_transaction.input.as_slice() {
|
|
||||||
[input] => input,
|
|
||||||
[] => bail!(NoInputs),
|
|
||||||
[inputs @ ..] => bail!(TooManyInputs(inputs.len())),
|
|
||||||
};
|
|
||||||
|
|
||||||
let sigs = match input
|
|
||||||
.witness
|
|
||||||
.iter()
|
|
||||||
.map(|vec| vec.as_slice())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.as_slice()
|
|
||||||
{
|
|
||||||
[sig_1, sig_2, _script] => [sig_1, sig_2]
|
|
||||||
.iter()
|
|
||||||
.map(|sig| {
|
|
||||||
bitcoin::secp256k1::Signature::from_der(&sig[..sig.len() - 1])
|
|
||||||
.map(Signature::from)
|
|
||||||
})
|
|
||||||
.collect::<std::result::Result<Vec<_>, _>>(),
|
|
||||||
[] => bail!(EmptyWitnessStack),
|
|
||||||
[witnesses @ ..] => bail!(NotThreeWitnesses(witnesses.len())),
|
|
||||||
}?;
|
|
||||||
|
|
||||||
let sig = sigs
|
|
||||||
.into_iter()
|
|
||||||
.find(|sig| verify_sig(&B, &self.digest(), &sig).is_ok())
|
|
||||||
.context("neither signature on witness stack verifies against B")?;
|
|
||||||
|
|
||||||
Ok(sig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, thiserror::Error, Debug)]
|
|
||||||
#[error("transaction does not spend anything")]
|
|
||||||
pub struct NoInputs;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, thiserror::Error, Debug)]
|
|
||||||
#[error("transaction has {0} inputs, expected 1")]
|
|
||||||
pub struct TooManyInputs(usize);
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, thiserror::Error, Debug)]
|
|
||||||
#[error("empty witness stack")]
|
|
||||||
pub struct EmptyWitnessStack;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, thiserror::Error, Debug)]
|
|
||||||
#[error("input has {0} witnesses, expected 3")]
|
|
||||||
pub struct NotThreeWitnesses(usize);
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct TxCancel {
|
|
||||||
inner: Transaction,
|
|
||||||
digest: SigHash,
|
|
||||||
output_descriptor: Descriptor<::bitcoin::PublicKey>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TxCancel {
|
|
||||||
pub fn new(tx_lock: &TxLock, cancel_timelock: Timelock, A: PublicKey, B: PublicKey) -> Self {
|
|
||||||
let cancel_output_descriptor = build_shared_output_descriptor(A.0, B.0);
|
|
||||||
|
|
||||||
let tx_in = TxIn {
|
|
||||||
previous_output: tx_lock.as_outpoint(),
|
|
||||||
script_sig: Default::default(),
|
|
||||||
sequence: cancel_timelock.into(),
|
|
||||||
witness: Vec::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let tx_out = TxOut {
|
|
||||||
value: tx_lock.lock_amount().as_sat() - TX_FEE,
|
|
||||||
script_pubkey: cancel_output_descriptor.script_pubkey(NullCtx),
|
|
||||||
};
|
|
||||||
|
|
||||||
let transaction = Transaction {
|
|
||||||
version: 2,
|
|
||||||
lock_time: 0,
|
|
||||||
input: vec![tx_in],
|
|
||||||
output: vec![tx_out],
|
|
||||||
};
|
|
||||||
|
|
||||||
let digest = SigHashCache::new(&transaction).signature_hash(
|
|
||||||
0, // Only one input: lock_input (lock transaction)
|
|
||||||
&tx_lock.output_descriptor.witness_script(NullCtx),
|
|
||||||
tx_lock.lock_amount().as_sat(),
|
|
||||||
SigHashType::All,
|
|
||||||
);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
inner: transaction,
|
|
||||||
digest,
|
|
||||||
output_descriptor: cancel_output_descriptor,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn txid(&self) -> Txid {
|
|
||||||
self.inner.txid()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn digest(&self) -> SigHash {
|
|
||||||
self.digest
|
|
||||||
}
|
|
||||||
|
|
||||||
fn amount(&self) -> Amount {
|
|
||||||
Amount::from_sat(self.inner.output[0].value)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_outpoint(&self) -> OutPoint {
|
|
||||||
OutPoint::new(self.inner.txid(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_signatures(
|
|
||||||
self,
|
|
||||||
tx_lock: &TxLock,
|
|
||||||
(A, sig_a): (PublicKey, Signature),
|
|
||||||
(B, sig_b): (PublicKey, Signature),
|
|
||||||
) -> Result<Transaction> {
|
|
||||||
let satisfier = {
|
|
||||||
let mut satisfier = HashMap::with_capacity(2);
|
|
||||||
|
|
||||||
let A = ::bitcoin::PublicKey {
|
|
||||||
compressed: true,
|
|
||||||
key: A.0.into(),
|
|
||||||
};
|
|
||||||
let B = ::bitcoin::PublicKey {
|
|
||||||
compressed: true,
|
|
||||||
key: B.0.into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// The order in which these are inserted doesn't matter
|
|
||||||
satisfier.insert(A, (sig_a.into(), ::bitcoin::SigHashType::All));
|
|
||||||
satisfier.insert(B, (sig_b.into(), ::bitcoin::SigHashType::All));
|
|
||||||
|
|
||||||
satisfier
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut tx_cancel = self.inner;
|
|
||||||
tx_lock
|
|
||||||
.output_descriptor
|
|
||||||
.satisfy(&mut tx_cancel.input[0], satisfier, NullCtx)?;
|
|
||||||
|
|
||||||
Ok(tx_cancel)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_spend_transaction(
|
|
||||||
&self,
|
|
||||||
spend_address: &Address,
|
|
||||||
sequence: Option<Timelock>,
|
|
||||||
) -> Transaction {
|
|
||||||
let previous_output = self.as_outpoint();
|
|
||||||
|
|
||||||
let tx_in = TxIn {
|
|
||||||
previous_output,
|
|
||||||
script_sig: Default::default(),
|
|
||||||
sequence: sequence.map(Into::into).unwrap_or(0xFFFF_FFFF),
|
|
||||||
witness: Vec::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let tx_out = TxOut {
|
|
||||||
value: self.amount().as_sat() - TX_FEE,
|
|
||||||
script_pubkey: spend_address.script_pubkey(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Transaction {
|
|
||||||
version: 2,
|
|
||||||
lock_time: 0,
|
|
||||||
input: vec![tx_in],
|
|
||||||
output: vec![tx_out],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct TxRefund {
|
|
||||||
inner: Transaction,
|
|
||||||
digest: SigHash,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TxRefund {
|
|
||||||
pub fn new(tx_cancel: &TxCancel, refund_address: &Address) -> Self {
|
|
||||||
let tx_punish = tx_cancel.build_spend_transaction(refund_address, None);
|
|
||||||
|
|
||||||
let digest = SigHashCache::new(&tx_punish).signature_hash(
|
|
||||||
0, // Only one input: cancel transaction
|
|
||||||
&tx_cancel.output_descriptor.witness_script(NullCtx),
|
|
||||||
tx_cancel.amount().as_sat(),
|
|
||||||
SigHashType::All,
|
|
||||||
);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
inner: tx_punish,
|
|
||||||
digest,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn txid(&self) -> Txid {
|
|
||||||
self.inner.txid()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn digest(&self) -> SigHash {
|
|
||||||
self.digest
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_signatures(
|
|
||||||
self,
|
|
||||||
tx_cancel: &TxCancel,
|
|
||||||
(A, sig_a): (PublicKey, Signature),
|
|
||||||
(B, sig_b): (PublicKey, Signature),
|
|
||||||
) -> Result<Transaction> {
|
|
||||||
let satisfier = {
|
|
||||||
let mut satisfier = HashMap::with_capacity(2);
|
|
||||||
|
|
||||||
let A = ::bitcoin::PublicKey {
|
|
||||||
compressed: true,
|
|
||||||
key: A.0.into(),
|
|
||||||
};
|
|
||||||
let B = ::bitcoin::PublicKey {
|
|
||||||
compressed: true,
|
|
||||||
key: B.0.into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// The order in which these are inserted doesn't matter
|
|
||||||
satisfier.insert(A, (sig_a.into(), ::bitcoin::SigHashType::All));
|
|
||||||
satisfier.insert(B, (sig_b.into(), ::bitcoin::SigHashType::All));
|
|
||||||
|
|
||||||
satisfier
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut tx_refund = self.inner;
|
|
||||||
tx_cancel
|
|
||||||
.output_descriptor
|
|
||||||
.satisfy(&mut tx_refund.input[0], satisfier, NullCtx)?;
|
|
||||||
|
|
||||||
Ok(tx_refund)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_signature_by_key(
|
|
||||||
&self,
|
|
||||||
candidate_transaction: Transaction,
|
|
||||||
B: PublicKey,
|
|
||||||
) -> Result<Signature> {
|
|
||||||
let input = match candidate_transaction.input.as_slice() {
|
|
||||||
[input] => input,
|
|
||||||
[] => bail!(NoInputs),
|
|
||||||
[inputs @ ..] => bail!(TooManyInputs(inputs.len())),
|
|
||||||
};
|
|
||||||
|
|
||||||
let sigs = match input
|
|
||||||
.witness
|
|
||||||
.iter()
|
|
||||||
.map(|vec| vec.as_slice())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.as_slice()
|
|
||||||
{
|
|
||||||
[sig_1, sig_2, _script] => [sig_1, sig_2]
|
|
||||||
.iter()
|
|
||||||
.map(|sig| {
|
|
||||||
bitcoin::secp256k1::Signature::from_der(&sig[..sig.len() - 1])
|
|
||||||
.map(Signature::from)
|
|
||||||
})
|
|
||||||
.collect::<std::result::Result<Vec<_>, _>>(),
|
|
||||||
[] => bail!(EmptyWitnessStack),
|
|
||||||
[witnesses @ ..] => bail!(NotThreeWitnesses(witnesses.len())),
|
|
||||||
}?;
|
|
||||||
|
|
||||||
let sig = sigs
|
|
||||||
.into_iter()
|
|
||||||
.find(|sig| verify_sig(&B, &self.digest(), &sig).is_ok())
|
|
||||||
.context("neither signature on witness stack verifies against B")?;
|
|
||||||
|
|
||||||
Ok(sig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct TxPunish {
|
|
||||||
inner: Transaction,
|
|
||||||
digest: SigHash,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TxPunish {
|
|
||||||
pub fn new(tx_cancel: &TxCancel, punish_address: &Address, punish_timelock: Timelock) -> Self {
|
|
||||||
let tx_punish = tx_cancel.build_spend_transaction(punish_address, Some(punish_timelock));
|
|
||||||
|
|
||||||
let digest = SigHashCache::new(&tx_punish).signature_hash(
|
|
||||||
0, // Only one input: cancel transaction
|
|
||||||
&tx_cancel.output_descriptor.witness_script(NullCtx),
|
|
||||||
tx_cancel.amount().as_sat(),
|
|
||||||
SigHashType::All,
|
|
||||||
);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
inner: tx_punish,
|
|
||||||
digest,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn digest(&self) -> SigHash {
|
|
||||||
self.digest
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_signatures(
|
|
||||||
self,
|
|
||||||
tx_cancel: &TxCancel,
|
|
||||||
(A, sig_a): (PublicKey, Signature),
|
|
||||||
(B, sig_b): (PublicKey, Signature),
|
|
||||||
) -> Result<Transaction> {
|
|
||||||
let satisfier = {
|
|
||||||
let mut satisfier = HashMap::with_capacity(2);
|
|
||||||
|
|
||||||
let A = ::bitcoin::PublicKey {
|
|
||||||
compressed: true,
|
|
||||||
key: A.0.into(),
|
|
||||||
};
|
|
||||||
let B = ::bitcoin::PublicKey {
|
|
||||||
compressed: true,
|
|
||||||
key: B.0.into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// The order in which these are inserted doesn't matter
|
|
||||||
satisfier.insert(A, (sig_a.into(), ::bitcoin::SigHashType::All));
|
|
||||||
satisfier.insert(B, (sig_b.into(), ::bitcoin::SigHashType::All));
|
|
||||||
|
|
||||||
satisfier
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut tx_punish = self.inner;
|
|
||||||
tx_cancel
|
|
||||||
.output_descriptor
|
|
||||||
.satisfy(&mut tx_punish.input[0], satisfier, NullCtx)?;
|
|
||||||
|
|
||||||
Ok(tx_punish)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
use crate::{bitcoin, monero};
|
use crate::bitcoin;
|
||||||
use libp2p::{core::Multiaddr, PeerId};
|
use libp2p::{core::Multiaddr, PeerId};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@ -28,9 +28,6 @@ pub enum Command {
|
|||||||
|
|
||||||
#[structopt(long = "send-btc", help = "Bitcoin amount as floating point nr without denomination (e.g. 1.25)", parse(try_from_str = parse_btc))]
|
#[structopt(long = "send-btc", help = "Bitcoin amount as floating point nr without denomination (e.g. 1.25)", parse(try_from_str = parse_btc))]
|
||||||
send_bitcoin: bitcoin::Amount,
|
send_bitcoin: bitcoin::Amount,
|
||||||
|
|
||||||
#[structopt(long = "receive-xmr", help = "Monero amount as floating point nr without denomination (e.g. 125.1)", parse(try_from_str = parse_xmr))]
|
|
||||||
receive_monero: monero::Amount,
|
|
||||||
},
|
},
|
||||||
History,
|
History,
|
||||||
Resume(Resume),
|
Resume(Resume),
|
||||||
@ -92,8 +89,3 @@ fn parse_btc(str: &str) -> anyhow::Result<bitcoin::Amount> {
|
|||||||
let amount = bitcoin::Amount::from_str_in(str, ::bitcoin::Denomination::Bitcoin)?;
|
let amount = bitcoin::Amount::from_str_in(str, ::bitcoin::Denomination::Bitcoin)?;
|
||||||
Ok(amount)
|
Ok(amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_xmr(str: &str) -> anyhow::Result<monero::Amount> {
|
|
||||||
let amount = monero::Amount::parse_monero(str)?;
|
|
||||||
Ok(amount)
|
|
||||||
}
|
|
||||||
|
@ -2,7 +2,7 @@ use crate::{
|
|||||||
bitcoin::{EncryptedSignature, TxCancel, TxRefund},
|
bitcoin::{EncryptedSignature, TxCancel, TxRefund},
|
||||||
monero,
|
monero,
|
||||||
monero::monero_private_key,
|
monero::monero_private_key,
|
||||||
protocol::{alice, alice::AliceState, SwapAmounts},
|
protocol::{alice, alice::AliceState},
|
||||||
};
|
};
|
||||||
use ::bitcoin::hashes::core::fmt::Display;
|
use ::bitcoin::hashes::core::fmt::Display;
|
||||||
use libp2p::PeerId;
|
use libp2p::PeerId;
|
||||||
@ -101,10 +101,6 @@ impl From<Alice> for AliceState {
|
|||||||
bob_peer_id,
|
bob_peer_id,
|
||||||
} => AliceState::Started {
|
} => AliceState::Started {
|
||||||
bob_peer_id,
|
bob_peer_id,
|
||||||
amounts: SwapAmounts {
|
|
||||||
btc: state3.btc,
|
|
||||||
xmr: state3.xmr,
|
|
||||||
},
|
|
||||||
state3: Box::new(state3),
|
state3: Box::new(state3),
|
||||||
},
|
},
|
||||||
Alice::BtcLocked {
|
Alice::BtcLocked {
|
||||||
@ -112,10 +108,6 @@ impl From<Alice> for AliceState {
|
|||||||
bob_peer_id,
|
bob_peer_id,
|
||||||
} => AliceState::BtcLocked {
|
} => AliceState::BtcLocked {
|
||||||
bob_peer_id,
|
bob_peer_id,
|
||||||
amounts: SwapAmounts {
|
|
||||||
btc: state3.btc,
|
|
||||||
xmr: state3.xmr,
|
|
||||||
},
|
|
||||||
state3: Box::new(state3),
|
state3: Box::new(state3),
|
||||||
},
|
},
|
||||||
Alice::XmrLocked(state3) => AliceState::XmrLocked {
|
Alice::XmrLocked(state3) => AliceState::XmrLocked {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
monero::TransferProof,
|
monero::TransferProof,
|
||||||
protocol::{bob, bob::BobState, SwapAmounts},
|
protocol::{bob, bob::BobState},
|
||||||
};
|
};
|
||||||
use ::bitcoin::hashes::core::fmt::Display;
|
use ::bitcoin::hashes::core::fmt::Display;
|
||||||
use monero_harness::rpc::wallet::BlockHeight;
|
use monero_harness::rpc::wallet::BlockHeight;
|
||||||
@ -9,10 +9,10 @@ use serde::{Deserialize, Serialize};
|
|||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
pub enum Bob {
|
pub enum Bob {
|
||||||
Started {
|
Started {
|
||||||
state0: bob::State0,
|
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||||
amounts: SwapAmounts,
|
btc_amount: bitcoin::Amount,
|
||||||
},
|
},
|
||||||
Negotiated {
|
ExecutionSetupDone {
|
||||||
state2: bob::State2,
|
state2: bob::State2,
|
||||||
},
|
},
|
||||||
BtcLocked {
|
BtcLocked {
|
||||||
@ -46,8 +46,8 @@ pub enum BobEndState {
|
|||||||
impl From<BobState> for Bob {
|
impl From<BobState> for Bob {
|
||||||
fn from(bob_state: BobState) -> Self {
|
fn from(bob_state: BobState) -> Self {
|
||||||
match bob_state {
|
match bob_state {
|
||||||
BobState::Started { state0, amounts } => Bob::Started { state0, amounts },
|
BobState::Started { btc_amount } => Bob::Started { btc_amount },
|
||||||
BobState::Negotiated(state2) => Bob::Negotiated { state2 },
|
BobState::ExecutionSetupDone(state2) => Bob::ExecutionSetupDone { state2 },
|
||||||
BobState::BtcLocked(state3) => Bob::BtcLocked { state3 },
|
BobState::BtcLocked(state3) => Bob::BtcLocked { state3 },
|
||||||
BobState::XmrLockProofReceived {
|
BobState::XmrLockProofReceived {
|
||||||
state,
|
state,
|
||||||
@ -78,8 +78,8 @@ impl From<BobState> for Bob {
|
|||||||
impl From<Bob> for BobState {
|
impl From<Bob> for BobState {
|
||||||
fn from(db_state: Bob) -> Self {
|
fn from(db_state: Bob) -> Self {
|
||||||
match db_state {
|
match db_state {
|
||||||
Bob::Started { state0, amounts } => BobState::Started { state0, amounts },
|
Bob::Started { btc_amount } => BobState::Started { btc_amount },
|
||||||
Bob::Negotiated { state2 } => BobState::Negotiated(state2),
|
Bob::ExecutionSetupDone { state2 } => BobState::ExecutionSetupDone(state2),
|
||||||
Bob::BtcLocked { state3 } => BobState::BtcLocked(state3),
|
Bob::BtcLocked { state3 } => BobState::BtcLocked(state3),
|
||||||
Bob::XmrLockProofReceived {
|
Bob::XmrLockProofReceived {
|
||||||
state,
|
state,
|
||||||
@ -109,7 +109,7 @@ impl Display for Bob {
|
|||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Bob::Started { .. } => write!(f, "Started"),
|
Bob::Started { .. } => write!(f, "Started"),
|
||||||
Bob::Negotiated { .. } => f.write_str("Negotiated"),
|
Bob::ExecutionSetupDone { .. } => f.write_str("Execution setup done"),
|
||||||
Bob::BtcLocked { .. } => f.write_str("Bitcoin locked"),
|
Bob::BtcLocked { .. } => f.write_str("Bitcoin locked"),
|
||||||
Bob::XmrLockProofReceived { .. } => {
|
Bob::XmrLockProofReceived { .. } => {
|
||||||
f.write_str("XMR lock transaction transfer proof received")
|
f.write_str("XMR lock transaction transfer proof received")
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::bitcoin::Timelock;
|
use crate::bitcoin::{CancelTimelock, PunishTimelock};
|
||||||
use conquer_once::Lazy;
|
use conquer_once::Lazy;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@ -8,8 +8,8 @@ pub struct ExecutionParams {
|
|||||||
pub bitcoin_finality_confirmations: u32,
|
pub bitcoin_finality_confirmations: u32,
|
||||||
pub bitcoin_avg_block_time: Duration,
|
pub bitcoin_avg_block_time: Duration,
|
||||||
pub monero_finality_confirmations: u32,
|
pub monero_finality_confirmations: u32,
|
||||||
pub bitcoin_cancel_timelock: Timelock,
|
pub bitcoin_cancel_timelock: CancelTimelock,
|
||||||
pub bitcoin_punish_timelock: Timelock,
|
pub bitcoin_punish_timelock: PunishTimelock,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait GetExecutionParams {
|
pub trait GetExecutionParams {
|
||||||
@ -77,8 +77,8 @@ mod mainnet {
|
|||||||
pub static MONERO_FINALITY_CONFIRMATIONS: u32 = 15;
|
pub static MONERO_FINALITY_CONFIRMATIONS: u32 = 15;
|
||||||
|
|
||||||
// Set to 12 hours, arbitrary value to be reviewed properly
|
// Set to 12 hours, arbitrary value to be reviewed properly
|
||||||
pub static BITCOIN_CANCEL_TIMELOCK: Timelock = Timelock::new(72);
|
pub static BITCOIN_CANCEL_TIMELOCK: CancelTimelock = CancelTimelock::new(72);
|
||||||
pub static BITCOIN_PUNISH_TIMELOCK: Timelock = Timelock::new(72);
|
pub static BITCOIN_PUNISH_TIMELOCK: PunishTimelock = PunishTimelock::new(72);
|
||||||
}
|
}
|
||||||
|
|
||||||
mod testnet {
|
mod testnet {
|
||||||
@ -95,8 +95,8 @@ mod testnet {
|
|||||||
pub static MONERO_FINALITY_CONFIRMATIONS: u32 = 5;
|
pub static MONERO_FINALITY_CONFIRMATIONS: u32 = 5;
|
||||||
|
|
||||||
// This does not reflect recommended values for mainnet!
|
// This does not reflect recommended values for mainnet!
|
||||||
pub static BITCOIN_CANCEL_TIMELOCK: Timelock = Timelock::new(12);
|
pub static BITCOIN_CANCEL_TIMELOCK: CancelTimelock = CancelTimelock::new(12);
|
||||||
pub static BITCOIN_PUNISH_TIMELOCK: Timelock = Timelock::new(6);
|
pub static BITCOIN_PUNISH_TIMELOCK: PunishTimelock = PunishTimelock::new(6);
|
||||||
}
|
}
|
||||||
|
|
||||||
mod regtest {
|
mod regtest {
|
||||||
@ -111,7 +111,7 @@ mod regtest {
|
|||||||
|
|
||||||
pub static MONERO_FINALITY_CONFIRMATIONS: u32 = 1;
|
pub static MONERO_FINALITY_CONFIRMATIONS: u32 = 1;
|
||||||
|
|
||||||
pub static BITCOIN_CANCEL_TIMELOCK: Timelock = Timelock::new(100);
|
pub static BITCOIN_CANCEL_TIMELOCK: CancelTimelock = CancelTimelock::new(100);
|
||||||
|
|
||||||
pub static BITCOIN_PUNISH_TIMELOCK: Timelock = Timelock::new(50);
|
pub static BITCOIN_PUNISH_TIMELOCK: PunishTimelock = PunishTimelock::new(50);
|
||||||
}
|
}
|
||||||
|
@ -23,9 +23,9 @@ pub mod execution_params;
|
|||||||
pub mod fs;
|
pub mod fs;
|
||||||
pub mod monero;
|
pub mod monero;
|
||||||
pub mod nectar;
|
pub mod nectar;
|
||||||
pub mod network;
|
|
||||||
pub mod protocol;
|
pub mod protocol;
|
||||||
pub mod seed;
|
pub mod seed;
|
||||||
pub mod trace;
|
pub mod trace;
|
||||||
|
|
||||||
|
mod network;
|
||||||
mod serde_peer_id;
|
mod serde_peer_id;
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
use crate::monero;
|
|
||||||
use bitcoin::hashes::core::{fmt, fmt::Display};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
pub mod alice;
|
pub mod alice;
|
||||||
pub mod bob;
|
pub mod bob;
|
||||||
|
|
||||||
@ -10,26 +6,3 @@ pub struct StartingBalances {
|
|||||||
pub xmr: crate::monero::Amount,
|
pub xmr: crate::monero::Amount,
|
||||||
pub btc: bitcoin::Amount,
|
pub btc: bitcoin::Amount,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// XMR/BTC swap amounts.
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
||||||
pub struct SwapAmounts {
|
|
||||||
/// Amount of BTC to swap.
|
|
||||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
|
||||||
pub btc: bitcoin::Amount,
|
|
||||||
/// Amount of XMR to swap.
|
|
||||||
#[serde(with = "monero::monero_amount")]
|
|
||||||
pub xmr: crate::monero::Amount,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Display in XMR and BTC (not picos and sats).
|
|
||||||
impl Display for SwapAmounts {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{} sats for {} piconeros",
|
|
||||||
self.btc.as_sat(),
|
|
||||||
self.xmr.as_piconero()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
//! Run an XMR/BTC swap in the role of Alice.
|
//! Run an XMR/BTC swap in the role of Alice.
|
||||||
//! Alice holds XMR and wishes receive BTC.
|
//! Alice holds XMR and wishes receive BTC.
|
||||||
use crate::{
|
use crate::{bitcoin, database, database::Database, execution_params::ExecutionParams, monero};
|
||||||
bitcoin, database, database::Database, execution_params::ExecutionParams, monero,
|
|
||||||
protocol::SwapAmounts,
|
|
||||||
};
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use libp2p::{core::Multiaddr, PeerId};
|
use libp2p::{core::Multiaddr, PeerId};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -57,7 +54,6 @@ pub struct Builder {
|
|||||||
enum InitParams {
|
enum InitParams {
|
||||||
None,
|
None,
|
||||||
New {
|
New {
|
||||||
swap_amounts: SwapAmounts,
|
|
||||||
bob_peer_id: PeerId,
|
bob_peer_id: PeerId,
|
||||||
state3: Box<State3>,
|
state3: Box<State3>,
|
||||||
},
|
},
|
||||||
@ -88,15 +84,9 @@ impl Builder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_init_params(
|
pub fn with_init_params(self, bob_peer_id: PeerId, state3: State3) -> Self {
|
||||||
self,
|
|
||||||
swap_amounts: SwapAmounts,
|
|
||||||
bob_peer_id: PeerId,
|
|
||||||
state3: State3,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
init_params: InitParams::New {
|
init_params: InitParams::New {
|
||||||
swap_amounts,
|
|
||||||
bob_peer_id,
|
bob_peer_id,
|
||||||
state3: Box::new(state3),
|
state3: Box::new(state3),
|
||||||
},
|
},
|
||||||
@ -107,12 +97,10 @@ impl Builder {
|
|||||||
pub async fn build(self) -> Result<Swap> {
|
pub async fn build(self) -> Result<Swap> {
|
||||||
match self.init_params {
|
match self.init_params {
|
||||||
InitParams::New {
|
InitParams::New {
|
||||||
swap_amounts,
|
|
||||||
bob_peer_id,
|
bob_peer_id,
|
||||||
ref state3,
|
ref state3,
|
||||||
} => {
|
} => {
|
||||||
let initial_state = AliceState::Started {
|
let initial_state = AliceState::Started {
|
||||||
amounts: swap_amounts,
|
|
||||||
state3: state3.clone(),
|
state3: state3.clone(),
|
||||||
bob_peer_id,
|
bob_peer_id,
|
||||||
};
|
};
|
||||||
|
@ -10,23 +10,22 @@ use crate::{
|
|||||||
AliceState, Behaviour, Builder, OutEvent, QuoteResponse, State0, State3, TransferProof,
|
AliceState, Behaviour, Builder, OutEvent, QuoteResponse, State0, State3, TransferProof,
|
||||||
},
|
},
|
||||||
bob::{EncryptedSignature, QuoteRequest},
|
bob::{EncryptedSignature, QuoteRequest},
|
||||||
SwapAmounts,
|
|
||||||
},
|
},
|
||||||
seed::Seed,
|
seed::Seed,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use futures::future::RemoteHandle;
|
use futures::future::RemoteHandle;
|
||||||
use libp2p::{
|
use libp2p::{
|
||||||
core::Multiaddr, futures::FutureExt, request_response::ResponseChannel, PeerId, Swarm,
|
core::Multiaddr, futures::FutureExt, request_response::ResponseChannel, PeerId, Swarm,
|
||||||
};
|
};
|
||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::sync::Arc;
|
||||||
use tokio::sync::{broadcast, mpsc};
|
use tokio::sync::{broadcast, mpsc};
|
||||||
use tracing::{debug, error, trace, warn};
|
use tracing::{debug, error, trace, warn};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
// TODO: Use dynamic
|
// TODO: Use dynamic
|
||||||
const RATE: u32 = 100;
|
pub const RATE: u32 = 100;
|
||||||
|
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct MpscChannels<T> {
|
pub struct MpscChannels<T> {
|
||||||
@ -89,10 +88,6 @@ pub struct EventLoop {
|
|||||||
db: Arc<Database>,
|
db: Arc<Database>,
|
||||||
listen_address: Multiaddr,
|
listen_address: Multiaddr,
|
||||||
|
|
||||||
// Amounts agreed upon for swaps currently in the execution setup phase
|
|
||||||
// Note: We can do one execution setup per peer at a given time.
|
|
||||||
swap_amounts: HashMap<PeerId, SwapAmounts>,
|
|
||||||
|
|
||||||
recv_encrypted_signature: broadcast::Sender<EncryptedSignature>,
|
recv_encrypted_signature: broadcast::Sender<EncryptedSignature>,
|
||||||
send_transfer_proof: mpsc::Receiver<(PeerId, TransferProof)>,
|
send_transfer_proof: mpsc::Receiver<(PeerId, TransferProof)>,
|
||||||
|
|
||||||
@ -137,7 +132,6 @@ impl EventLoop {
|
|||||||
monero_wallet,
|
monero_wallet,
|
||||||
db,
|
db,
|
||||||
listen_address,
|
listen_address,
|
||||||
swap_amounts: Default::default(),
|
|
||||||
recv_encrypted_signature: recv_encrypted_signature.sender,
|
recv_encrypted_signature: recv_encrypted_signature.sender,
|
||||||
send_transfer_proof: send_transfer_proof.receiver,
|
send_transfer_proof: send_transfer_proof.receiver,
|
||||||
send_transfer_proof_sender: send_transfer_proof.sender,
|
send_transfer_proof_sender: send_transfer_proof.sender,
|
||||||
@ -225,12 +219,6 @@ impl EventLoop {
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// if a node restart during execution setup, the swap is aborted (safely).
|
|
||||||
self.swap_amounts.insert(bob_peer_id, SwapAmounts {
|
|
||||||
btc: btc_amount,
|
|
||||||
xmr: xmr_amount,
|
|
||||||
});
|
|
||||||
|
|
||||||
self.swarm.start_execution_setup(bob_peer_id, state0);
|
self.swarm.start_execution_setup(bob_peer_id, state0);
|
||||||
// Continues once the execution setup protocol is done
|
// Continues once the execution setup protocol is done
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -244,13 +232,6 @@ impl EventLoop {
|
|||||||
let swap_id = Uuid::new_v4();
|
let swap_id = Uuid::new_v4();
|
||||||
let handle = self.new_handle();
|
let handle = self.new_handle();
|
||||||
|
|
||||||
let swap_amounts = self.swap_amounts.remove(&bob_peer_id).ok_or_else(|| {
|
|
||||||
anyhow!(
|
|
||||||
"execution setup done for an unknown peer id: {}, node restarted in between?",
|
|
||||||
bob_peer_id
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let swap = Builder::new(
|
let swap = Builder::new(
|
||||||
self.peer_id,
|
self.peer_id,
|
||||||
self.execution_params,
|
self.execution_params,
|
||||||
@ -261,7 +242,7 @@ impl EventLoop {
|
|||||||
self.listen_address.clone(),
|
self.listen_address.clone(),
|
||||||
handle,
|
handle,
|
||||||
)
|
)
|
||||||
.with_init_params(swap_amounts, bob_peer_id, state3)
|
.with_init_params(bob_peer_id, state3)
|
||||||
.build()
|
.build()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
bitcoin,
|
bitcoin,
|
||||||
bitcoin::{
|
bitcoin::{
|
||||||
current_epoch,
|
current_epoch, wait_for_cancel_timelock_to_expire, CancelTimelock, ExpiredTimelocks,
|
||||||
timelocks::{ExpiredTimelocks, Timelock},
|
GetBlockHeight, PunishTimelock, TransactionBlockHeight, TxCancel, TxRefund,
|
||||||
wait_for_cancel_timelock_to_expire, GetBlockHeight, TransactionBlockHeight, TxCancel,
|
WatchForRawTransaction,
|
||||||
TxRefund, WatchForRawTransaction,
|
|
||||||
},
|
},
|
||||||
execution_params::ExecutionParams,
|
execution_params::ExecutionParams,
|
||||||
monero,
|
monero,
|
||||||
protocol::{
|
protocol::{
|
||||||
alice::{Message1, Message3, TransferProof},
|
alice::{Message1, Message3, TransferProof},
|
||||||
bob::{EncryptedSignature, Message0, Message2, Message4},
|
bob::{EncryptedSignature, Message0, Message2, Message4},
|
||||||
SwapAmounts,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
@ -26,12 +24,10 @@ use std::fmt;
|
|||||||
pub enum AliceState {
|
pub enum AliceState {
|
||||||
Started {
|
Started {
|
||||||
bob_peer_id: PeerId,
|
bob_peer_id: PeerId,
|
||||||
amounts: SwapAmounts,
|
|
||||||
state3: Box<State3>,
|
state3: Box<State3>,
|
||||||
},
|
},
|
||||||
BtcLocked {
|
BtcLocked {
|
||||||
bob_peer_id: PeerId,
|
bob_peer_id: PeerId,
|
||||||
amounts: SwapAmounts,
|
|
||||||
state3: Box<State3>,
|
state3: Box<State3>,
|
||||||
},
|
},
|
||||||
XmrLocked {
|
XmrLocked {
|
||||||
@ -90,8 +86,8 @@ pub struct State0 {
|
|||||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||||
pub btc: bitcoin::Amount,
|
pub btc: bitcoin::Amount,
|
||||||
pub xmr: monero::Amount,
|
pub xmr: monero::Amount,
|
||||||
pub cancel_timelock: Timelock,
|
pub cancel_timelock: CancelTimelock,
|
||||||
pub punish_timelock: Timelock,
|
pub punish_timelock: PunishTimelock,
|
||||||
pub redeem_address: bitcoin::Address,
|
pub redeem_address: bitcoin::Address,
|
||||||
pub punish_address: bitcoin::Address,
|
pub punish_address: bitcoin::Address,
|
||||||
}
|
}
|
||||||
@ -172,8 +168,8 @@ pub struct State1 {
|
|||||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||||
btc: bitcoin::Amount,
|
btc: bitcoin::Amount,
|
||||||
xmr: monero::Amount,
|
xmr: monero::Amount,
|
||||||
cancel_timelock: Timelock,
|
cancel_timelock: CancelTimelock,
|
||||||
punish_timelock: Timelock,
|
punish_timelock: PunishTimelock,
|
||||||
refund_address: bitcoin::Address,
|
refund_address: bitcoin::Address,
|
||||||
redeem_address: bitcoin::Address,
|
redeem_address: bitcoin::Address,
|
||||||
punish_address: bitcoin::Address,
|
punish_address: bitcoin::Address,
|
||||||
@ -225,8 +221,8 @@ pub struct State2 {
|
|||||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||||
btc: bitcoin::Amount,
|
btc: bitcoin::Amount,
|
||||||
xmr: monero::Amount,
|
xmr: monero::Amount,
|
||||||
cancel_timelock: Timelock,
|
cancel_timelock: CancelTimelock,
|
||||||
punish_timelock: Timelock,
|
punish_timelock: PunishTimelock,
|
||||||
refund_address: bitcoin::Address,
|
refund_address: bitcoin::Address,
|
||||||
redeem_address: bitcoin::Address,
|
redeem_address: bitcoin::Address,
|
||||||
punish_address: bitcoin::Address,
|
punish_address: bitcoin::Address,
|
||||||
@ -295,8 +291,8 @@ pub struct State3 {
|
|||||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||||
pub btc: bitcoin::Amount,
|
pub btc: bitcoin::Amount,
|
||||||
pub xmr: monero::Amount,
|
pub xmr: monero::Amount,
|
||||||
pub cancel_timelock: Timelock,
|
pub cancel_timelock: CancelTimelock,
|
||||||
pub punish_timelock: Timelock,
|
pub punish_timelock: PunishTimelock,
|
||||||
pub refund_address: bitcoin::Address,
|
pub refund_address: bitcoin::Address,
|
||||||
pub redeem_address: bitcoin::Address,
|
pub redeem_address: bitcoin::Address,
|
||||||
pub punish_address: bitcoin::Address,
|
pub punish_address: bitcoin::Address,
|
||||||
@ -341,8 +337,8 @@ pub struct State4 {
|
|||||||
S_b_bitcoin: bitcoin::PublicKey,
|
S_b_bitcoin: bitcoin::PublicKey,
|
||||||
v: monero::PrivateViewKey,
|
v: monero::PrivateViewKey,
|
||||||
xmr: monero::Amount,
|
xmr: monero::Amount,
|
||||||
cancel_timelock: Timelock,
|
cancel_timelock: CancelTimelock,
|
||||||
punish_timelock: Timelock,
|
punish_timelock: PunishTimelock,
|
||||||
refund_address: bitcoin::Address,
|
refund_address: bitcoin::Address,
|
||||||
redeem_address: bitcoin::Address,
|
redeem_address: bitcoin::Address,
|
||||||
punish_address: bitcoin::Address,
|
punish_address: bitcoin::Address,
|
||||||
@ -433,8 +429,8 @@ pub struct State5 {
|
|||||||
S_b_monero: monero::PublicKey,
|
S_b_monero: monero::PublicKey,
|
||||||
S_b_bitcoin: bitcoin::PublicKey,
|
S_b_bitcoin: bitcoin::PublicKey,
|
||||||
v: monero::PrivateViewKey,
|
v: monero::PrivateViewKey,
|
||||||
cancel_timelock: Timelock,
|
cancel_timelock: CancelTimelock,
|
||||||
punish_timelock: Timelock,
|
punish_timelock: PunishTimelock,
|
||||||
refund_address: bitcoin::Address,
|
refund_address: bitcoin::Address,
|
||||||
redeem_address: bitcoin::Address,
|
redeem_address: bitcoin::Address,
|
||||||
punish_address: bitcoin::Address,
|
punish_address: bitcoin::Address,
|
||||||
@ -483,8 +479,8 @@ pub struct State6 {
|
|||||||
S_b_monero: monero::PublicKey,
|
S_b_monero: monero::PublicKey,
|
||||||
S_b_bitcoin: bitcoin::PublicKey,
|
S_b_bitcoin: bitcoin::PublicKey,
|
||||||
v: monero::PrivateViewKey,
|
v: monero::PrivateViewKey,
|
||||||
cancel_timelock: Timelock,
|
cancel_timelock: CancelTimelock,
|
||||||
punish_timelock: Timelock,
|
punish_timelock: PunishTimelock,
|
||||||
refund_address: bitcoin::Address,
|
refund_address: bitcoin::Address,
|
||||||
redeem_address: bitcoin::Address,
|
redeem_address: bitcoin::Address,
|
||||||
punish_address: bitcoin::Address,
|
punish_address: bitcoin::Address,
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
bitcoin,
|
bitcoin,
|
||||||
bitcoin::{
|
bitcoin::{
|
||||||
poll_until_block_height_is_gte,
|
poll_until_block_height_is_gte, BlockHeight, BroadcastSignedTransaction, CancelTimelock,
|
||||||
timelocks::{BlockHeight, Timelock},
|
EncryptedSignature, GetBlockHeight, GetRawTransaction, PunishTimelock,
|
||||||
BroadcastSignedTransaction, EncryptedSignature, GetBlockHeight, GetRawTransaction,
|
|
||||||
TransactionBlockHeight, TxCancel, TxLock, TxRefund, WaitForTransactionFinality,
|
TransactionBlockHeight, TxCancel, TxLock, TxRefund, WaitForTransactionFinality,
|
||||||
WatchForRawTransaction,
|
WatchForRawTransaction,
|
||||||
},
|
},
|
||||||
@ -13,7 +12,6 @@ use crate::{
|
|||||||
protocol::{
|
protocol::{
|
||||||
alice,
|
alice,
|
||||||
alice::{event_loop::EventLoopHandle, TransferProof},
|
alice::{event_loop::EventLoopHandle, TransferProof},
|
||||||
SwapAmounts,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
@ -56,7 +54,6 @@ where
|
|||||||
|
|
||||||
pub async fn lock_xmr<W>(
|
pub async fn lock_xmr<W>(
|
||||||
bob_peer_id: PeerId,
|
bob_peer_id: PeerId,
|
||||||
amounts: SwapAmounts,
|
|
||||||
state3: alice::State3,
|
state3: alice::State3,
|
||||||
event_loop_handle: &mut EventLoopHandle,
|
event_loop_handle: &mut EventLoopHandle,
|
||||||
monero_wallet: Arc<W>,
|
monero_wallet: Arc<W>,
|
||||||
@ -72,7 +69,7 @@ where
|
|||||||
let public_view_key = state3.v.public();
|
let public_view_key = state3.v.public();
|
||||||
|
|
||||||
let (transfer_proof, _) = monero_wallet
|
let (transfer_proof, _) = monero_wallet
|
||||||
.transfer(public_spend_key, public_view_key, amounts.xmr)
|
.transfer(public_spend_key, public_view_key, state3.xmr)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// TODO(Franck): Wait for Monero to be confirmed once
|
// TODO(Franck): Wait for Monero to be confirmed once
|
||||||
@ -151,7 +148,7 @@ pub async fn publish_cancel_transaction<W>(
|
|||||||
tx_lock: TxLock,
|
tx_lock: TxLock,
|
||||||
a: bitcoin::SecretKey,
|
a: bitcoin::SecretKey,
|
||||||
B: bitcoin::PublicKey,
|
B: bitcoin::PublicKey,
|
||||||
cancel_timelock: Timelock,
|
cancel_timelock: CancelTimelock,
|
||||||
tx_cancel_sig_bob: bitcoin::Signature,
|
tx_cancel_sig_bob: bitcoin::Signature,
|
||||||
bitcoin_wallet: Arc<W>,
|
bitcoin_wallet: Arc<W>,
|
||||||
) -> Result<bitcoin::TxCancel>
|
) -> Result<bitcoin::TxCancel>
|
||||||
@ -198,7 +195,7 @@ where
|
|||||||
pub async fn wait_for_bitcoin_refund<W>(
|
pub async fn wait_for_bitcoin_refund<W>(
|
||||||
tx_cancel: &TxCancel,
|
tx_cancel: &TxCancel,
|
||||||
cancel_tx_height: BlockHeight,
|
cancel_tx_height: BlockHeight,
|
||||||
punish_timelock: Timelock,
|
punish_timelock: PunishTimelock,
|
||||||
refund_address: &bitcoin::Address,
|
refund_address: &bitcoin::Address,
|
||||||
bitcoin_wallet: Arc<W>,
|
bitcoin_wallet: Arc<W>,
|
||||||
) -> Result<(bitcoin::TxRefund, Option<bitcoin::Transaction>)>
|
) -> Result<(bitcoin::TxRefund, Option<bitcoin::Transaction>)>
|
||||||
@ -250,9 +247,9 @@ pub fn extract_monero_private_key(
|
|||||||
|
|
||||||
pub fn build_bitcoin_punish_transaction(
|
pub fn build_bitcoin_punish_transaction(
|
||||||
tx_lock: &TxLock,
|
tx_lock: &TxLock,
|
||||||
cancel_timelock: Timelock,
|
cancel_timelock: CancelTimelock,
|
||||||
punish_address: &bitcoin::Address,
|
punish_address: &bitcoin::Address,
|
||||||
punish_timelock: Timelock,
|
punish_timelock: PunishTimelock,
|
||||||
tx_punish_sig_bob: bitcoin::Signature,
|
tx_punish_sig_bob: bitcoin::Signature,
|
||||||
a: bitcoin::SecretKey,
|
a: bitcoin::SecretKey,
|
||||||
B: bitcoin::PublicKey,
|
B: bitcoin::PublicKey,
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
bitcoin,
|
bitcoin,
|
||||||
bitcoin::{
|
bitcoin::{
|
||||||
timelocks::ExpiredTimelocks, TransactionBlockHeight, WaitForTransactionFinality,
|
ExpiredTimelocks, TransactionBlockHeight, WaitForTransactionFinality,
|
||||||
WatchForRawTransaction,
|
WatchForRawTransaction,
|
||||||
},
|
},
|
||||||
database,
|
database,
|
||||||
@ -94,7 +94,6 @@ async fn run_until_internal(
|
|||||||
AliceState::Started {
|
AliceState::Started {
|
||||||
state3,
|
state3,
|
||||||
bob_peer_id,
|
bob_peer_id,
|
||||||
amounts,
|
|
||||||
} => {
|
} => {
|
||||||
let _ = wait_for_locked_bitcoin(
|
let _ = wait_for_locked_bitcoin(
|
||||||
state3.tx_lock.txid(),
|
state3.tx_lock.txid(),
|
||||||
@ -105,7 +104,6 @@ async fn run_until_internal(
|
|||||||
|
|
||||||
let state = AliceState::BtcLocked {
|
let state = AliceState::BtcLocked {
|
||||||
bob_peer_id,
|
bob_peer_id,
|
||||||
amounts,
|
|
||||||
state3,
|
state3,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -126,12 +124,10 @@ async fn run_until_internal(
|
|||||||
}
|
}
|
||||||
AliceState::BtcLocked {
|
AliceState::BtcLocked {
|
||||||
bob_peer_id,
|
bob_peer_id,
|
||||||
amounts,
|
|
||||||
state3,
|
state3,
|
||||||
} => {
|
} => {
|
||||||
lock_xmr(
|
lock_xmr(
|
||||||
bob_peer_id,
|
bob_peer_id,
|
||||||
amounts,
|
|
||||||
*state3.clone(),
|
*state3.clone(),
|
||||||
&mut event_loop_handle,
|
&mut event_loop_handle,
|
||||||
monero_wallet.clone(),
|
monero_wallet.clone(),
|
||||||
|
@ -9,12 +9,11 @@ use crate::{
|
|||||||
peer_tracker::{self, PeerTracker},
|
peer_tracker::{self, PeerTracker},
|
||||||
transport::build,
|
transport::build,
|
||||||
},
|
},
|
||||||
protocol::{alice, alice::TransferProof, bob, SwapAmounts},
|
protocol::{alice, alice::TransferProof, bob},
|
||||||
seed::Seed,
|
seed::Seed,
|
||||||
};
|
};
|
||||||
use anyhow::{bail, Error, Result};
|
use anyhow::{bail, Error, Result};
|
||||||
use libp2p::{core::Multiaddr, identity::Keypair, NetworkBehaviour, PeerId};
|
use libp2p::{core::Multiaddr, identity::Keypair, NetworkBehaviour, PeerId};
|
||||||
use rand::rngs::OsRng;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tracing::{debug, info};
|
use tracing::{debug, info};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@ -69,7 +68,7 @@ pub struct Builder {
|
|||||||
|
|
||||||
enum InitParams {
|
enum InitParams {
|
||||||
None,
|
None,
|
||||||
New { swap_amounts: SwapAmounts },
|
New { btc_amount: bitcoin::Amount },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Builder {
|
impl Builder {
|
||||||
@ -101,19 +100,17 @@ impl Builder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_init_params(self, swap_amounts: SwapAmounts) -> Self {
|
pub fn with_init_params(self, btc_amount: bitcoin::Amount) -> Self {
|
||||||
Self {
|
Self {
|
||||||
init_params: InitParams::New { swap_amounts },
|
init_params: InitParams::New { btc_amount },
|
||||||
..self
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn build(self) -> Result<(bob::Swap, bob::EventLoop)> {
|
pub async fn build(self) -> Result<(bob::Swap, bob::EventLoop)> {
|
||||||
match self.init_params {
|
match self.init_params {
|
||||||
InitParams::New { swap_amounts } => {
|
InitParams::New { btc_amount } => {
|
||||||
let initial_state = self
|
let initial_state = BobState::Started { btc_amount };
|
||||||
.make_initial_state(swap_amounts.btc, swap_amounts.xmr, self.execution_params)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let (event_loop, event_loop_handle) = self.init_event_loop()?;
|
let (event_loop, event_loop_handle) = self.init_event_loop()?;
|
||||||
|
|
||||||
@ -175,31 +172,6 @@ impl Builder {
|
|||||||
self.bitcoin_wallet.clone(),
|
self.bitcoin_wallet.clone(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn make_initial_state(
|
|
||||||
&self,
|
|
||||||
btc_to_swap: bitcoin::Amount,
|
|
||||||
xmr_to_swap: monero::Amount,
|
|
||||||
execution_params: ExecutionParams,
|
|
||||||
) -> Result<BobState> {
|
|
||||||
let amounts = SwapAmounts {
|
|
||||||
btc: btc_to_swap,
|
|
||||||
xmr: xmr_to_swap,
|
|
||||||
};
|
|
||||||
|
|
||||||
let refund_address = self.bitcoin_wallet.new_address().await?;
|
|
||||||
let state0 = bob::State0::new(
|
|
||||||
&mut OsRng,
|
|
||||||
btc_to_swap,
|
|
||||||
xmr_to_swap,
|
|
||||||
execution_params.bitcoin_cancel_timelock,
|
|
||||||
execution_params.bitcoin_punish_timelock,
|
|
||||||
refund_address,
|
|
||||||
execution_params.monero_finality_confirmations,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(BobState::Started { state0, amounts })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
bitcoin::{timelocks::ExpiredTimelocks, Txid, Wallet},
|
bitcoin::{ExpiredTimelocks, Txid, Wallet},
|
||||||
database::{Database, Swap},
|
database::{Database, Swap},
|
||||||
protocol::bob::BobState,
|
protocol::bob::BobState,
|
||||||
};
|
};
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
bitcoin::{
|
bitcoin::{
|
||||||
self, current_epoch,
|
self, current_epoch, wait_for_cancel_timelock_to_expire, BroadcastSignedTransaction,
|
||||||
timelocks::{ExpiredTimelocks, Timelock},
|
BuildTxLockPsbt, CancelTimelock, ExpiredTimelocks, GetBlockHeight, GetNetwork,
|
||||||
wait_for_cancel_timelock_to_expire, BroadcastSignedTransaction, BuildTxLockPsbt,
|
GetRawTransaction, PunishTimelock, Transaction, TransactionBlockHeight, TxCancel, Txid,
|
||||||
GetBlockHeight, GetNetwork, GetRawTransaction, Transaction, TransactionBlockHeight,
|
WatchForRawTransaction,
|
||||||
TxCancel, Txid, WatchForRawTransaction,
|
|
||||||
},
|
},
|
||||||
execution_params::ExecutionParams,
|
execution_params::ExecutionParams,
|
||||||
monero,
|
monero,
|
||||||
@ -12,7 +11,6 @@ use crate::{
|
|||||||
protocol::{
|
protocol::{
|
||||||
alice::{Message1, Message3},
|
alice::{Message1, Message3},
|
||||||
bob::{EncryptedSignature, Message0, Message2, Message4},
|
bob::{EncryptedSignature, Message0, Message2, Message4},
|
||||||
SwapAmounts,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
@ -26,10 +24,9 @@ use std::fmt;
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum BobState {
|
pub enum BobState {
|
||||||
Started {
|
Started {
|
||||||
state0: State0,
|
btc_amount: bitcoin::Amount,
|
||||||
amounts: SwapAmounts,
|
|
||||||
},
|
},
|
||||||
Negotiated(State2),
|
ExecutionSetupDone(State2),
|
||||||
BtcLocked(State3),
|
BtcLocked(State3),
|
||||||
XmrLockProofReceived {
|
XmrLockProofReceived {
|
||||||
state: State3,
|
state: State3,
|
||||||
@ -54,8 +51,8 @@ pub enum BobState {
|
|||||||
impl fmt::Display for BobState {
|
impl fmt::Display for BobState {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
BobState::Started { .. } => write!(f, "started"),
|
BobState::Started { .. } => write!(f, "quote has been requested"),
|
||||||
BobState::Negotiated(..) => write!(f, "negotiated"),
|
BobState::ExecutionSetupDone(..) => write!(f, "execution setup done"),
|
||||||
BobState::BtcLocked(..) => write!(f, "btc is locked"),
|
BobState::BtcLocked(..) => write!(f, "btc is locked"),
|
||||||
BobState::XmrLockProofReceived { .. } => {
|
BobState::XmrLockProofReceived { .. } => {
|
||||||
write!(f, "XMR lock transaction transfer proof received")
|
write!(f, "XMR lock transaction transfer proof received")
|
||||||
@ -82,8 +79,8 @@ pub struct State0 {
|
|||||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||||
btc: bitcoin::Amount,
|
btc: bitcoin::Amount,
|
||||||
xmr: monero::Amount,
|
xmr: monero::Amount,
|
||||||
cancel_timelock: Timelock,
|
cancel_timelock: CancelTimelock,
|
||||||
punish_timelock: Timelock,
|
punish_timelock: PunishTimelock,
|
||||||
refund_address: bitcoin::Address,
|
refund_address: bitcoin::Address,
|
||||||
min_monero_confirmations: u32,
|
min_monero_confirmations: u32,
|
||||||
}
|
}
|
||||||
@ -93,8 +90,8 @@ impl State0 {
|
|||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
btc: bitcoin::Amount,
|
btc: bitcoin::Amount,
|
||||||
xmr: monero::Amount,
|
xmr: monero::Amount,
|
||||||
cancel_timelock: Timelock,
|
cancel_timelock: CancelTimelock,
|
||||||
punish_timelock: Timelock,
|
punish_timelock: PunishTimelock,
|
||||||
refund_address: bitcoin::Address,
|
refund_address: bitcoin::Address,
|
||||||
min_monero_confirmations: u32,
|
min_monero_confirmations: u32,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@ -153,7 +150,6 @@ impl State0 {
|
|||||||
S_a_monero: msg.S_a_monero,
|
S_a_monero: msg.S_a_monero,
|
||||||
S_a_bitcoin: msg.S_a_bitcoin,
|
S_a_bitcoin: msg.S_a_bitcoin,
|
||||||
v,
|
v,
|
||||||
btc: self.btc,
|
|
||||||
xmr: self.xmr,
|
xmr: self.xmr,
|
||||||
cancel_timelock: self.cancel_timelock,
|
cancel_timelock: self.cancel_timelock,
|
||||||
punish_timelock: self.punish_timelock,
|
punish_timelock: self.punish_timelock,
|
||||||
@ -174,11 +170,9 @@ pub struct State1 {
|
|||||||
S_a_monero: monero::PublicKey,
|
S_a_monero: monero::PublicKey,
|
||||||
S_a_bitcoin: bitcoin::PublicKey,
|
S_a_bitcoin: bitcoin::PublicKey,
|
||||||
v: monero::PrivateViewKey,
|
v: monero::PrivateViewKey,
|
||||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
|
||||||
btc: bitcoin::Amount,
|
|
||||||
xmr: monero::Amount,
|
xmr: monero::Amount,
|
||||||
cancel_timelock: Timelock,
|
cancel_timelock: CancelTimelock,
|
||||||
punish_timelock: Timelock,
|
punish_timelock: PunishTimelock,
|
||||||
refund_address: bitcoin::Address,
|
refund_address: bitcoin::Address,
|
||||||
redeem_address: bitcoin::Address,
|
redeem_address: bitcoin::Address,
|
||||||
punish_address: bitcoin::Address,
|
punish_address: bitcoin::Address,
|
||||||
@ -212,7 +206,6 @@ impl State1 {
|
|||||||
S_a_monero: self.S_a_monero,
|
S_a_monero: self.S_a_monero,
|
||||||
S_a_bitcoin: self.S_a_bitcoin,
|
S_a_bitcoin: self.S_a_bitcoin,
|
||||||
v: self.v,
|
v: self.v,
|
||||||
btc: self.btc,
|
|
||||||
xmr: self.xmr,
|
xmr: self.xmr,
|
||||||
cancel_timelock: self.cancel_timelock,
|
cancel_timelock: self.cancel_timelock,
|
||||||
punish_timelock: self.punish_timelock,
|
punish_timelock: self.punish_timelock,
|
||||||
@ -229,24 +222,22 @@ impl State1 {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||||
pub struct State2 {
|
pub struct State2 {
|
||||||
pub A: bitcoin::PublicKey,
|
A: bitcoin::PublicKey,
|
||||||
pub b: bitcoin::SecretKey,
|
b: bitcoin::SecretKey,
|
||||||
pub s_b: cross_curve_dleq::Scalar,
|
s_b: cross_curve_dleq::Scalar,
|
||||||
pub S_a_monero: monero::PublicKey,
|
S_a_monero: monero::PublicKey,
|
||||||
pub S_a_bitcoin: bitcoin::PublicKey,
|
S_a_bitcoin: bitcoin::PublicKey,
|
||||||
pub v: monero::PrivateViewKey,
|
v: monero::PrivateViewKey,
|
||||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
xmr: monero::Amount,
|
||||||
btc: bitcoin::Amount,
|
cancel_timelock: CancelTimelock,
|
||||||
pub xmr: monero::Amount,
|
punish_timelock: PunishTimelock,
|
||||||
pub cancel_timelock: Timelock,
|
refund_address: bitcoin::Address,
|
||||||
pub punish_timelock: Timelock,
|
redeem_address: bitcoin::Address,
|
||||||
pub refund_address: bitcoin::Address,
|
punish_address: bitcoin::Address,
|
||||||
pub redeem_address: bitcoin::Address,
|
tx_lock: bitcoin::TxLock,
|
||||||
pub punish_address: bitcoin::Address,
|
tx_cancel_sig_a: Signature,
|
||||||
pub tx_lock: bitcoin::TxLock,
|
tx_refund_encsig: bitcoin::EncryptedSignature,
|
||||||
pub tx_cancel_sig_a: Signature,
|
min_monero_confirmations: u32,
|
||||||
pub tx_refund_encsig: bitcoin::EncryptedSignature,
|
|
||||||
pub min_monero_confirmations: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State2 {
|
impl State2 {
|
||||||
@ -281,13 +272,11 @@ impl State2 {
|
|||||||
S_a_monero: self.S_a_monero,
|
S_a_monero: self.S_a_monero,
|
||||||
S_a_bitcoin: self.S_a_bitcoin,
|
S_a_bitcoin: self.S_a_bitcoin,
|
||||||
v: self.v,
|
v: self.v,
|
||||||
btc: self.btc,
|
|
||||||
xmr: self.xmr,
|
xmr: self.xmr,
|
||||||
cancel_timelock: self.cancel_timelock,
|
cancel_timelock: self.cancel_timelock,
|
||||||
punish_timelock: self.punish_timelock,
|
punish_timelock: self.punish_timelock,
|
||||||
refund_address: self.refund_address,
|
refund_address: self.refund_address,
|
||||||
redeem_address: self.redeem_address,
|
redeem_address: self.redeem_address,
|
||||||
punish_address: self.punish_address,
|
|
||||||
tx_lock: self.tx_lock,
|
tx_lock: self.tx_lock,
|
||||||
tx_cancel_sig_a: self.tx_cancel_sig_a,
|
tx_cancel_sig_a: self.tx_cancel_sig_a,
|
||||||
tx_refund_encsig: self.tx_refund_encsig,
|
tx_refund_encsig: self.tx_refund_encsig,
|
||||||
@ -298,24 +287,21 @@ impl State2 {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct State3 {
|
pub struct State3 {
|
||||||
pub A: bitcoin::PublicKey,
|
A: bitcoin::PublicKey,
|
||||||
pub b: bitcoin::SecretKey,
|
b: bitcoin::SecretKey,
|
||||||
pub s_b: cross_curve_dleq::Scalar,
|
s_b: cross_curve_dleq::Scalar,
|
||||||
S_a_monero: monero::PublicKey,
|
S_a_monero: monero::PublicKey,
|
||||||
S_a_bitcoin: bitcoin::PublicKey,
|
S_a_bitcoin: bitcoin::PublicKey,
|
||||||
v: monero::PrivateViewKey,
|
v: monero::PrivateViewKey,
|
||||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
|
||||||
btc: bitcoin::Amount,
|
|
||||||
xmr: monero::Amount,
|
xmr: monero::Amount,
|
||||||
pub cancel_timelock: Timelock,
|
cancel_timelock: CancelTimelock,
|
||||||
punish_timelock: Timelock,
|
punish_timelock: PunishTimelock,
|
||||||
pub refund_address: bitcoin::Address,
|
refund_address: bitcoin::Address,
|
||||||
redeem_address: bitcoin::Address,
|
redeem_address: bitcoin::Address,
|
||||||
punish_address: bitcoin::Address,
|
tx_lock: bitcoin::TxLock,
|
||||||
pub tx_lock: bitcoin::TxLock,
|
tx_cancel_sig_a: Signature,
|
||||||
pub tx_cancel_sig_a: Signature,
|
tx_refund_encsig: bitcoin::EncryptedSignature,
|
||||||
pub tx_refund_encsig: bitcoin::EncryptedSignature,
|
min_monero_confirmations: u32,
|
||||||
pub min_monero_confirmations: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State3 {
|
impl State3 {
|
||||||
@ -347,16 +333,12 @@ impl State3 {
|
|||||||
A: self.A,
|
A: self.A,
|
||||||
b: self.b,
|
b: self.b,
|
||||||
s_b: self.s_b,
|
s_b: self.s_b,
|
||||||
S_a_monero: self.S_a_monero,
|
|
||||||
S_a_bitcoin: self.S_a_bitcoin,
|
S_a_bitcoin: self.S_a_bitcoin,
|
||||||
v: self.v,
|
v: self.v,
|
||||||
btc: self.btc,
|
|
||||||
xmr: self.xmr,
|
|
||||||
cancel_timelock: self.cancel_timelock,
|
cancel_timelock: self.cancel_timelock,
|
||||||
punish_timelock: self.punish_timelock,
|
punish_timelock: self.punish_timelock,
|
||||||
refund_address: self.refund_address,
|
refund_address: self.refund_address,
|
||||||
redeem_address: self.redeem_address,
|
redeem_address: self.redeem_address,
|
||||||
punish_address: self.punish_address,
|
|
||||||
tx_lock: self.tx_lock,
|
tx_lock: self.tx_lock,
|
||||||
tx_cancel_sig_a: self.tx_cancel_sig_a,
|
tx_cancel_sig_a: self.tx_cancel_sig_a,
|
||||||
tx_refund_encsig: self.tx_refund_encsig,
|
tx_refund_encsig: self.tx_refund_encsig,
|
||||||
@ -381,16 +363,12 @@ impl State3 {
|
|||||||
A: self.A,
|
A: self.A,
|
||||||
b: self.b.clone(),
|
b: self.b.clone(),
|
||||||
s_b: self.s_b,
|
s_b: self.s_b,
|
||||||
S_a_monero: self.S_a_monero,
|
|
||||||
S_a_bitcoin: self.S_a_bitcoin,
|
S_a_bitcoin: self.S_a_bitcoin,
|
||||||
v: self.v,
|
v: self.v,
|
||||||
btc: self.btc,
|
|
||||||
xmr: self.xmr,
|
|
||||||
cancel_timelock: self.cancel_timelock,
|
cancel_timelock: self.cancel_timelock,
|
||||||
punish_timelock: self.punish_timelock,
|
punish_timelock: self.punish_timelock,
|
||||||
refund_address: self.refund_address.clone(),
|
refund_address: self.refund_address.clone(),
|
||||||
redeem_address: self.redeem_address.clone(),
|
redeem_address: self.redeem_address.clone(),
|
||||||
punish_address: self.punish_address.clone(),
|
|
||||||
tx_lock: self.tx_lock.clone(),
|
tx_lock: self.tx_lock.clone(),
|
||||||
tx_cancel_sig_a: self.tx_cancel_sig_a.clone(),
|
tx_cancel_sig_a: self.tx_cancel_sig_a.clone(),
|
||||||
tx_refund_encsig: self.tx_refund_encsig.clone(),
|
tx_refund_encsig: self.tx_refund_encsig.clone(),
|
||||||
@ -418,24 +396,19 @@ impl State3 {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||||
pub struct State4 {
|
pub struct State4 {
|
||||||
pub A: bitcoin::PublicKey,
|
A: bitcoin::PublicKey,
|
||||||
pub b: bitcoin::SecretKey,
|
b: bitcoin::SecretKey,
|
||||||
pub s_b: cross_curve_dleq::Scalar,
|
s_b: cross_curve_dleq::Scalar,
|
||||||
S_a_monero: monero::PublicKey,
|
S_a_bitcoin: bitcoin::PublicKey,
|
||||||
pub S_a_bitcoin: bitcoin::PublicKey,
|
|
||||||
v: monero::PrivateViewKey,
|
v: monero::PrivateViewKey,
|
||||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
cancel_timelock: CancelTimelock,
|
||||||
btc: bitcoin::Amount,
|
punish_timelock: PunishTimelock,
|
||||||
xmr: monero::Amount,
|
refund_address: bitcoin::Address,
|
||||||
pub cancel_timelock: Timelock,
|
redeem_address: bitcoin::Address,
|
||||||
punish_timelock: Timelock,
|
tx_lock: bitcoin::TxLock,
|
||||||
pub refund_address: bitcoin::Address,
|
tx_cancel_sig_a: Signature,
|
||||||
pub redeem_address: bitcoin::Address,
|
tx_refund_encsig: bitcoin::EncryptedSignature,
|
||||||
punish_address: bitcoin::Address,
|
monero_wallet_restore_blockheight: u32,
|
||||||
pub tx_lock: bitcoin::TxLock,
|
|
||||||
pub tx_cancel_sig_a: Signature,
|
|
||||||
pub tx_refund_encsig: bitcoin::EncryptedSignature,
|
|
||||||
pub monero_wallet_restore_blockheight: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State4 {
|
impl State4 {
|
||||||
@ -515,23 +488,10 @@ impl State4 {
|
|||||||
let s_a = monero::private_key_from_secp256k1_scalar(s_a.into());
|
let s_a = monero::private_key_from_secp256k1_scalar(s_a.into());
|
||||||
|
|
||||||
Ok(State5 {
|
Ok(State5 {
|
||||||
A: self.A,
|
|
||||||
b: self.b.clone(),
|
|
||||||
s_a,
|
s_a,
|
||||||
s_b: self.s_b,
|
s_b: self.s_b,
|
||||||
S_a_monero: self.S_a_monero,
|
|
||||||
S_a_bitcoin: self.S_a_bitcoin,
|
|
||||||
v: self.v,
|
v: self.v,
|
||||||
btc: self.btc,
|
|
||||||
xmr: self.xmr,
|
|
||||||
cancel_timelock: self.cancel_timelock,
|
|
||||||
punish_timelock: self.punish_timelock,
|
|
||||||
refund_address: self.refund_address.clone(),
|
|
||||||
redeem_address: self.redeem_address.clone(),
|
|
||||||
punish_address: self.punish_address.clone(),
|
|
||||||
tx_lock: self.tx_lock.clone(),
|
tx_lock: self.tx_lock.clone(),
|
||||||
tx_refund_encsig: self.tx_refund_encsig.clone(),
|
|
||||||
tx_cancel_sig: self.tx_cancel_sig_a.clone(),
|
|
||||||
monero_wallet_restore_blockheight: self.monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight: self.monero_wallet_restore_blockheight,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -603,26 +563,12 @@ impl State4 {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||||
pub struct State5 {
|
pub struct State5 {
|
||||||
A: bitcoin::PublicKey,
|
|
||||||
pub b: bitcoin::SecretKey,
|
|
||||||
#[serde(with = "monero_private_key")]
|
#[serde(with = "monero_private_key")]
|
||||||
s_a: monero::PrivateKey,
|
s_a: monero::PrivateKey,
|
||||||
pub s_b: cross_curve_dleq::Scalar,
|
s_b: cross_curve_dleq::Scalar,
|
||||||
S_a_monero: monero::PublicKey,
|
v: monero::PrivateViewKey,
|
||||||
pub S_a_bitcoin: bitcoin::PublicKey,
|
tx_lock: bitcoin::TxLock,
|
||||||
pub v: monero::PrivateViewKey,
|
monero_wallet_restore_blockheight: u32,
|
||||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
|
||||||
btc: bitcoin::Amount,
|
|
||||||
xmr: monero::Amount,
|
|
||||||
cancel_timelock: Timelock,
|
|
||||||
punish_timelock: Timelock,
|
|
||||||
refund_address: bitcoin::Address,
|
|
||||||
pub redeem_address: bitcoin::Address,
|
|
||||||
punish_address: bitcoin::Address,
|
|
||||||
pub tx_lock: bitcoin::TxLock,
|
|
||||||
tx_refund_encsig: bitcoin::EncryptedSignature,
|
|
||||||
tx_cancel_sig: Signature,
|
|
||||||
pub monero_wallet_restore_blockheight: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State5 {
|
impl State5 {
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
bitcoin,
|
bitcoin,
|
||||||
bitcoin::timelocks::ExpiredTimelocks,
|
bitcoin::ExpiredTimelocks,
|
||||||
database::{Database, Swap},
|
database::{Database, Swap},
|
||||||
execution_params::ExecutionParams,
|
execution_params::ExecutionParams,
|
||||||
monero,
|
monero,
|
||||||
protocol::{
|
protocol::bob::{self, event_loop::EventLoopHandle, state::*, QuoteRequest},
|
||||||
bob::{self, event_loop::EventLoopHandle, state::*, QuoteRequest},
|
|
||||||
SwapAmounts,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use async_recursion::async_recursion;
|
use async_recursion::async_recursion;
|
||||||
|
use rand::rngs::OsRng;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::select;
|
use tokio::select;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
@ -67,12 +65,20 @@ async fn run_until_internal(
|
|||||||
Ok(state)
|
Ok(state)
|
||||||
} else {
|
} else {
|
||||||
match state {
|
match state {
|
||||||
BobState::Started { state0, amounts } => {
|
BobState::Started { btc_amount } => {
|
||||||
|
let bitcoin_refund_address = bitcoin_wallet.new_address().await?;
|
||||||
|
|
||||||
event_loop_handle.dial().await?;
|
event_loop_handle.dial().await?;
|
||||||
|
|
||||||
let state2 = negotiate(state0, amounts, &mut event_loop_handle).await?;
|
let state2 = request_quote_and_setup(
|
||||||
|
btc_amount,
|
||||||
|
&mut event_loop_handle,
|
||||||
|
execution_params,
|
||||||
|
bitcoin_refund_address,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let state = BobState::Negotiated(state2);
|
let state = BobState::ExecutionSetupDone(state2);
|
||||||
let db_state = state.clone().into();
|
let db_state = state.clone().into();
|
||||||
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
|
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
|
||||||
run_until_internal(
|
run_until_internal(
|
||||||
@ -87,7 +93,7 @@ async fn run_until_internal(
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
BobState::Negotiated(state2) => {
|
BobState::ExecutionSetupDone(state2) => {
|
||||||
// Do not lock Bitcoin if not connected to Alice.
|
// Do not lock Bitcoin if not connected to Alice.
|
||||||
event_loop_handle.dial().await?;
|
event_loop_handle.dial().await?;
|
||||||
// Alice and Bob have exchanged info
|
// Alice and Bob have exchanged info
|
||||||
@ -368,21 +374,27 @@ async fn run_until_internal(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn negotiate(
|
pub async fn request_quote_and_setup(
|
||||||
state0: crate::protocol::bob::state::State0,
|
btc_amount: bitcoin::Amount,
|
||||||
amounts: SwapAmounts,
|
|
||||||
event_loop_handle: &mut EventLoopHandle,
|
event_loop_handle: &mut EventLoopHandle,
|
||||||
|
execution_params: ExecutionParams,
|
||||||
|
bitcoin_refund_address: bitcoin::Address,
|
||||||
) -> Result<bob::state::State2> {
|
) -> Result<bob::state::State2> {
|
||||||
tracing::trace!("Starting negotiate");
|
|
||||||
event_loop_handle
|
event_loop_handle
|
||||||
.send_quote_request(QuoteRequest {
|
.send_quote_request(QuoteRequest { btc_amount })
|
||||||
btc_amount: amounts.btc,
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// TODO: Use this once Bob's CLI is modified to only pass xmr amount in
|
let quote_response = event_loop_handle.recv_quote_response().await?;
|
||||||
// argument.
|
|
||||||
let _quote_response = event_loop_handle.recv_quote_response().await?;
|
let state0 = State0::new(
|
||||||
|
&mut OsRng,
|
||||||
|
btc_amount,
|
||||||
|
quote_response.xmr_amount,
|
||||||
|
execution_params.bitcoin_cancel_timelock,
|
||||||
|
execution_params.bitcoin_punish_timelock,
|
||||||
|
bitcoin_refund_address,
|
||||||
|
execution_params.monero_finality_confirmations,
|
||||||
|
);
|
||||||
|
|
||||||
let state2 = event_loop_handle.execution_setup(state0).await?;
|
let state2 = event_loop_handle.execution_setup(state0).await?;
|
||||||
|
|
||||||
|
@ -7,12 +7,17 @@ use monero_harness::{image, Monero};
|
|||||||
use std::{path::PathBuf, sync::Arc};
|
use std::{path::PathBuf, sync::Arc};
|
||||||
use swap::{
|
use swap::{
|
||||||
bitcoin,
|
bitcoin,
|
||||||
bitcoin::Timelock,
|
bitcoin::{CancelTimelock, PunishTimelock},
|
||||||
database::Database,
|
database::Database,
|
||||||
execution_params,
|
execution_params,
|
||||||
execution_params::{ExecutionParams, GetExecutionParams},
|
execution_params::{ExecutionParams, GetExecutionParams},
|
||||||
monero,
|
monero,
|
||||||
protocol::{alice, alice::AliceState, bob, bob::BobState, SwapAmounts},
|
protocol::{
|
||||||
|
alice,
|
||||||
|
alice::{event_loop::RATE, AliceState},
|
||||||
|
bob,
|
||||||
|
bob::BobState,
|
||||||
|
},
|
||||||
seed::Seed,
|
seed::Seed,
|
||||||
};
|
};
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
@ -66,7 +71,8 @@ impl BobEventLoopJoinHandle {
|
|||||||
pub struct AliceEventLoopJoinHandle(JoinHandle<()>);
|
pub struct AliceEventLoopJoinHandle(JoinHandle<()>);
|
||||||
|
|
||||||
pub struct TestContext {
|
pub struct TestContext {
|
||||||
swap_amounts: SwapAmounts,
|
btc_amount: bitcoin::Amount,
|
||||||
|
xmr_amount: monero::Amount,
|
||||||
|
|
||||||
alice_starting_balances: StartingBalances,
|
alice_starting_balances: StartingBalances,
|
||||||
alice_bitcoin_wallet: Arc<bitcoin::Wallet>,
|
alice_bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||||
@ -84,7 +90,7 @@ impl TestContext {
|
|||||||
let (swap, event_loop) = self
|
let (swap, event_loop) = self
|
||||||
.bob_params
|
.bob_params
|
||||||
.builder()
|
.builder()
|
||||||
.with_init_params(self.swap_amounts)
|
.with_init_params(self.btc_amount)
|
||||||
.build()
|
.build()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -116,7 +122,7 @@ impl TestContext {
|
|||||||
let btc_balance_after_swap = self.alice_bitcoin_wallet.as_ref().balance().await.unwrap();
|
let btc_balance_after_swap = self.alice_bitcoin_wallet.as_ref().balance().await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
btc_balance_after_swap,
|
btc_balance_after_swap,
|
||||||
self.alice_starting_balances.btc + self.swap_amounts.btc
|
self.alice_starting_balances.btc + self.btc_amount
|
||||||
- bitcoin::Amount::from_sat(bitcoin::TX_FEE)
|
- bitcoin::Amount::from_sat(bitcoin::TX_FEE)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -126,7 +132,7 @@ impl TestContext {
|
|||||||
.get_balance()
|
.get_balance()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(xmr_balance_after_swap <= self.alice_starting_balances.xmr - self.swap_amounts.xmr);
|
assert!(xmr_balance_after_swap <= self.alice_starting_balances.xmr - self.xmr_amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn assert_alice_refunded(&mut self) {
|
pub async fn assert_alice_refunded(&mut self) {
|
||||||
@ -155,7 +161,7 @@ impl TestContext {
|
|||||||
.get_balance()
|
.get_balance()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(xmr_balance_after_swap, self.swap_amounts.xmr);
|
assert_eq!(xmr_balance_after_swap, self.xmr_amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn assert_alice_punished(&self, state: AliceState) {
|
pub async fn assert_alice_punished(&self, state: AliceState) {
|
||||||
@ -164,7 +170,7 @@ impl TestContext {
|
|||||||
let btc_balance_after_swap = self.alice_bitcoin_wallet.as_ref().balance().await.unwrap();
|
let btc_balance_after_swap = self.alice_bitcoin_wallet.as_ref().balance().await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
btc_balance_after_swap,
|
btc_balance_after_swap,
|
||||||
self.alice_starting_balances.btc + self.swap_amounts.btc
|
self.alice_starting_balances.btc + self.btc_amount
|
||||||
- bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE)
|
- bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -174,7 +180,7 @@ impl TestContext {
|
|||||||
.get_balance()
|
.get_balance()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(xmr_balance_after_swap <= self.alice_starting_balances.xmr - self.swap_amounts.xmr);
|
assert!(xmr_balance_after_swap <= self.alice_starting_balances.xmr - self.xmr_amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn assert_bob_redeemed(&self, state: BobState) {
|
pub async fn assert_bob_redeemed(&self, state: BobState) {
|
||||||
@ -193,7 +199,7 @@ impl TestContext {
|
|||||||
let btc_balance_after_swap = self.bob_bitcoin_wallet.as_ref().balance().await.unwrap();
|
let btc_balance_after_swap = self.bob_bitcoin_wallet.as_ref().balance().await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
btc_balance_after_swap,
|
btc_balance_after_swap,
|
||||||
self.bob_starting_balances.btc - self.swap_amounts.btc - lock_tx_bitcoin_fee
|
self.bob_starting_balances.btc - self.btc_amount - lock_tx_bitcoin_fee
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ensure that Bob's balance is refreshed as we use a newly created wallet
|
// Ensure that Bob's balance is refreshed as we use a newly created wallet
|
||||||
@ -206,7 +212,7 @@ impl TestContext {
|
|||||||
let xmr_balance_after_swap = self.bob_monero_wallet.as_ref().get_balance().await.unwrap();
|
let xmr_balance_after_swap = self.bob_monero_wallet.as_ref().get_balance().await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
xmr_balance_after_swap,
|
xmr_balance_after_swap,
|
||||||
self.bob_starting_balances.xmr + self.swap_amounts.xmr
|
self.bob_starting_balances.xmr + self.xmr_amount
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,7 +264,7 @@ impl TestContext {
|
|||||||
let btc_balance_after_swap = self.bob_bitcoin_wallet.as_ref().balance().await.unwrap();
|
let btc_balance_after_swap = self.bob_bitcoin_wallet.as_ref().balance().await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
btc_balance_after_swap,
|
btc_balance_after_swap,
|
||||||
self.bob_starting_balances.btc - self.swap_amounts.btc - lock_tx_bitcoin_fee
|
self.bob_starting_balances.btc - self.btc_amount - lock_tx_bitcoin_fee
|
||||||
);
|
);
|
||||||
|
|
||||||
let xmr_balance_after_swap = self.bob_monero_wallet.as_ref().get_balance().await.unwrap();
|
let xmr_balance_after_swap = self.bob_monero_wallet.as_ref().get_balance().await.unwrap();
|
||||||
@ -280,13 +286,11 @@ where
|
|||||||
|
|
||||||
let (monero, containers) = testutils::init_containers(&cli).await;
|
let (monero, containers) = testutils::init_containers(&cli).await;
|
||||||
|
|
||||||
let swap_amounts = SwapAmounts {
|
let btc_amount = bitcoin::Amount::from_sat(1_000_000);
|
||||||
btc: bitcoin::Amount::from_sat(1_000_000),
|
let xmr_amount = monero::Amount::from_monero(btc_amount.as_btc() * RATE as f64).unwrap();
|
||||||
xmr: monero::Amount::from_piconero(1_000_000_000_000),
|
|
||||||
};
|
|
||||||
|
|
||||||
let alice_starting_balances = StartingBalances {
|
let alice_starting_balances = StartingBalances {
|
||||||
xmr: swap_amounts.xmr * 10,
|
xmr: xmr_amount * 10,
|
||||||
btc: bitcoin::Amount::ZERO,
|
btc: bitcoin::Amount::ZERO,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -311,7 +315,7 @@ where
|
|||||||
|
|
||||||
let bob_starting_balances = StartingBalances {
|
let bob_starting_balances = StartingBalances {
|
||||||
xmr: monero::Amount::ZERO,
|
xmr: monero::Amount::ZERO,
|
||||||
btc: swap_amounts.btc * 10,
|
btc: btc_amount * 10,
|
||||||
};
|
};
|
||||||
|
|
||||||
let (bob_bitcoin_wallet, bob_monero_wallet) = init_test_wallets(
|
let (bob_bitcoin_wallet, bob_monero_wallet) = init_test_wallets(
|
||||||
@ -350,7 +354,8 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
let test = TestContext {
|
let test = TestContext {
|
||||||
swap_amounts,
|
btc_amount,
|
||||||
|
xmr_amount,
|
||||||
alice_starting_balances,
|
alice_starting_balances,
|
||||||
alice_bitcoin_wallet,
|
alice_bitcoin_wallet,
|
||||||
alice_monero_wallet,
|
alice_monero_wallet,
|
||||||
@ -484,7 +489,7 @@ pub struct SlowCancelConfig;
|
|||||||
impl GetExecutionParams for SlowCancelConfig {
|
impl GetExecutionParams for SlowCancelConfig {
|
||||||
fn get_execution_params() -> ExecutionParams {
|
fn get_execution_params() -> ExecutionParams {
|
||||||
ExecutionParams {
|
ExecutionParams {
|
||||||
bitcoin_cancel_timelock: Timelock::new(180),
|
bitcoin_cancel_timelock: CancelTimelock::new(180),
|
||||||
..execution_params::Regtest::get_execution_params()
|
..execution_params::Regtest::get_execution_params()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -495,7 +500,7 @@ pub struct FastCancelConfig;
|
|||||||
impl GetExecutionParams for FastCancelConfig {
|
impl GetExecutionParams for FastCancelConfig {
|
||||||
fn get_execution_params() -> ExecutionParams {
|
fn get_execution_params() -> ExecutionParams {
|
||||||
ExecutionParams {
|
ExecutionParams {
|
||||||
bitcoin_cancel_timelock: Timelock::new(1),
|
bitcoin_cancel_timelock: CancelTimelock::new(1),
|
||||||
..execution_params::Regtest::get_execution_params()
|
..execution_params::Regtest::get_execution_params()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -506,8 +511,8 @@ pub struct FastPunishConfig;
|
|||||||
impl GetExecutionParams for FastPunishConfig {
|
impl GetExecutionParams for FastPunishConfig {
|
||||||
fn get_execution_params() -> ExecutionParams {
|
fn get_execution_params() -> ExecutionParams {
|
||||||
ExecutionParams {
|
ExecutionParams {
|
||||||
bitcoin_cancel_timelock: Timelock::new(1),
|
bitcoin_cancel_timelock: CancelTimelock::new(1),
|
||||||
bitcoin_punish_timelock: Timelock::new(1),
|
bitcoin_punish_timelock: PunishTimelock::new(1),
|
||||||
..execution_params::Regtest::get_execution_params()
|
..execution_params::Regtest::get_execution_params()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user