mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-08-07 05:52:31 -04:00
WIP: onchain refund
This commit is contained in:
parent
4a99b89df4
commit
0c9caef60f
19 changed files with 770 additions and 564 deletions
40
Cargo.lock
generated
40
Cargo.lock
generated
|
@ -3725,16 +3725,16 @@ dependencies = [
|
||||||
"atty",
|
"atty",
|
||||||
"backoff",
|
"backoff",
|
||||||
"base64 0.13.0",
|
"base64 0.13.0",
|
||||||
"bdk",
|
"bdk",
|
||||||
"bdk-testutils",
|
"bdk-testutils",
|
||||||
"big-bytes",
|
"big-bytes",
|
||||||
"bitcoin",
|
"bitcoin",
|
||||||
"bitcoin-harness",
|
"bitcoin-harness",
|
||||||
"bmrng",
|
"bmrng",
|
||||||
"config",
|
"config",
|
||||||
"conquer-once",
|
"conquer-once",
|
||||||
"curve25519-dalek",
|
"curve25519-dalek",
|
||||||
"dialoguer",
|
"dialoguer",
|
||||||
"directories-next",
|
"directories-next",
|
||||||
"ecdsa_fun",
|
"ecdsa_fun",
|
||||||
"futures",
|
"futures",
|
||||||
|
@ -3756,16 +3756,16 @@ dependencies = [
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rust_decimal",
|
"rust_decimal",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_cbor",
|
"serde_cbor",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2 0.9.3",
|
"sha2 0.9.3",
|
||||||
"sigma_fun",
|
"sigma_fun",
|
||||||
"sled",
|
"sled",
|
||||||
"spectral",
|
"spectral",
|
||||||
"structopt",
|
"structopt",
|
||||||
"strum",
|
"strum",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"testcontainers 0.12.0",
|
"testcontainers 0.12.0",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"time 0.2.26",
|
"time 0.2.26",
|
||||||
"tokio 1.4.0",
|
"tokio 1.4.0",
|
||||||
|
|
|
@ -96,6 +96,7 @@ fn final_challenge(
|
||||||
Ok((h_last, h_0))
|
Ok((h_last, h_0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct AdaptorSignature {
|
pub struct AdaptorSignature {
|
||||||
s_0_a: Scalar,
|
s_0_a: Scalar,
|
||||||
s_0_b: Scalar,
|
s_0_b: Scalar,
|
||||||
|
|
|
@ -15,7 +15,7 @@ use serde::{Deserialize, Serialize};
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct TxLock {
|
pub struct TxLock {
|
||||||
inner: PartiallySignedTransaction,
|
inner: PartiallySignedTransaction,
|
||||||
pub(in crate::bitcoin) output_descriptor: Descriptor<::bitcoin::PublicKey>,
|
pub(crate) output_descriptor: Descriptor<::bitcoin::PublicKey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TxLock {
|
impl TxLock {
|
||||||
|
|
|
@ -27,6 +27,6 @@ pub mod monero;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
pub mod protocol;
|
pub mod protocol;
|
||||||
pub mod seed;
|
pub mod seed;
|
||||||
mod xmr_first_protocol;
|
pub mod xmr_first_protocol;
|
||||||
|
|
||||||
mod monero_ext;
|
mod monero_ext;
|
||||||
|
|
|
@ -40,8 +40,8 @@ impl PrivateViewKey {
|
||||||
Self(private_key)
|
Self(private_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn public(&self) -> PrivateViewKey {
|
pub fn public(&self) -> PublicViewKey {
|
||||||
PrivateViewKey(PublicKey::from_private_key(&self.0))
|
PublicViewKey(PublicKey::from_private_key(&self.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,14 +59,14 @@ impl From<PrivateViewKey> for PrivateKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PrivateViewKey> for PublicKey {
|
impl From<PublicViewKey> for PublicKey {
|
||||||
fn from(from: PrivateViewKey) -> Self {
|
fn from(from: PublicViewKey) -> Self {
|
||||||
from.0
|
from.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct PrivateViewKey(PublicKey);
|
pub struct PublicViewKey(PublicKey);
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Deserialize, Serialize, PartialEq, PartialOrd)]
|
#[derive(Debug, Copy, Clone, Deserialize, Serialize, PartialEq, PartialOrd)]
|
||||||
pub struct Amount(u64);
|
pub struct Amount(u64);
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
use crate::env::Config;
|
use crate::env::Config;
|
||||||
use crate::monero::{Amount, InsufficientFunds, PrivateViewKey, TransferProof, TxHash};
|
use crate::monero::{
|
||||||
|
Amount, InsufficientFunds, PrivateViewKey, PublicViewKey, TransferProof, TxHash,
|
||||||
|
};
|
||||||
use ::monero::{Address, Network, PrivateKey, PublicKey};
|
use ::monero::{Address, Network, PrivateKey, PublicKey};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use monero_rpc::wallet;
|
use monero_rpc::wallet;
|
||||||
|
@ -273,14 +275,14 @@ impl Wallet {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TransferRequest {
|
pub struct TransferRequest {
|
||||||
pub public_spend_key: PublicKey,
|
pub public_spend_key: PublicKey,
|
||||||
pub public_view_key: PrivateViewKey,
|
pub public_view_key: PublicViewKey,
|
||||||
pub amount: Amount,
|
pub amount: Amount,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct WatchRequest {
|
pub struct WatchRequest {
|
||||||
pub public_spend_key: PublicKey,
|
pub public_spend_key: PublicKey,
|
||||||
pub public_view_key: PrivateViewKey,
|
pub public_view_key: PublicViewKey,
|
||||||
pub transfer_proof: TransferProof,
|
pub transfer_proof: TransferProof,
|
||||||
pub conf_target: u64,
|
pub conf_target: u64,
|
||||||
pub expected: Amount,
|
pub expected: Amount,
|
||||||
|
|
|
@ -1,151 +1,14 @@
|
||||||
use crate::bitcoin::Txid;
|
use crate::bitcoin::Txid;
|
||||||
use crate::monero::wallet::WatchRequest;
|
|
||||||
use crate::monero::{PrivateViewKey, Scalar, TransferRequest};
|
|
||||||
use crate::xmr_first_protocol::transactions::xmr_lock::XmrLock;
|
|
||||||
use anyhow::Result;
|
|
||||||
use monero::PublicKey;
|
|
||||||
use monero_adaptor::alice::Alice2;
|
|
||||||
use monero_adaptor::AdaptorSignature;
|
use monero_adaptor::AdaptorSignature;
|
||||||
use rand::rngs::OsRng;
|
|
||||||
|
|
||||||
// start
|
pub mod alice;
|
||||||
pub struct Alice3 {
|
pub mod bob;
|
||||||
pub adaptor_sig: AdaptorSignature,
|
mod state_machine;
|
||||||
s_a: Scalar,
|
|
||||||
S_b_monero: monero::PublicKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
// published xmr_lock, watching for btc_lock
|
|
||||||
pub struct Alice4 {
|
|
||||||
pub adaptor_sig: AdaptorSignature,
|
|
||||||
}
|
|
||||||
|
|
||||||
// published seen btc_lock, published btc_redeem
|
|
||||||
pub struct Alice5 {}
|
|
||||||
|
|
||||||
// watching for xmr_lock
|
|
||||||
pub struct Bob3 {
|
|
||||||
xmr_swap_amount: crate::monero::Amount,
|
|
||||||
btc_swap_amount: crate::bitcoin::Amount,
|
|
||||||
xmr_lock: XmrLock,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Bob3 {
|
|
||||||
pub fn watch_for_lock_xmr(&self, wallet: &crate::monero::Wallet) {
|
|
||||||
let req = WatchRequest {
|
|
||||||
public_spend_key: self.xmr_lock.public_spend_key,
|
|
||||||
public_view_key: self.xmr_lock.public_view_key,
|
|
||||||
transfer_proof: self.xmr_lock.transfer_proof.clone(),
|
|
||||||
conf_target: 1,
|
|
||||||
expected: self.xmr_swap_amount,
|
|
||||||
};
|
|
||||||
wallet.watch_for_transfer(req);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// published btc_lock, watching for xmr_redeem
|
|
||||||
pub struct Bob4;
|
|
||||||
|
|
||||||
mod alice;
|
|
||||||
mod bob;
|
|
||||||
mod transactions;
|
mod transactions;
|
||||||
|
|
||||||
impl Alice3 {
|
|
||||||
pub fn new(alice2: Alice2, S_b_monero: PublicKey) -> Self {
|
|
||||||
Self {
|
|
||||||
adaptor_sig: alice2.adaptor_sig,
|
|
||||||
s_a: Scalar::random(&mut OsRng),
|
|
||||||
S_b_monero,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn publish_xmr_lock(&self, wallet: &crate::monero::Wallet) -> Result<Alice4> {
|
|
||||||
let S_a = monero::PublicKey::from_private_key(&monero::PrivateKey { scalar: self.s_a });
|
|
||||||
|
|
||||||
let public_spend_key = S_a + self.S_b_monero;
|
|
||||||
let public_view_key = self.v.public();
|
|
||||||
|
|
||||||
let req = TransferRequest {
|
|
||||||
public_spend_key,
|
|
||||||
public_view_key,
|
|
||||||
amount: self.xmr,
|
|
||||||
};
|
|
||||||
|
|
||||||
// we may have to send this to Bob
|
|
||||||
let _ = wallet.transfer(req)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Alice4 {
|
|
||||||
pub fn watch_for_btc_lock(&self, wallet: &crate::bitcoin::Wallet) -> Result<Alice4> {
|
|
||||||
wallet.subscribe_to(self.btc_lock());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SeenBtcLock {
|
pub struct SeenBtcLock {
|
||||||
s_0_b: monero::Scalar,
|
s_0_b: crate::monero::Scalar,
|
||||||
pub adaptor_sig: AdaptorSignature,
|
pub adaptor_sig: AdaptorSignature,
|
||||||
tx_lock_id: Txid,
|
tx_lock_id: Txid,
|
||||||
tx_lock: bitcoin::Transaction,
|
tx_lock: bitcoin::Transaction,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use crate::monero::Scalar;
|
|
||||||
use crate::xmr_first_protocol::Alice3;
|
|
||||||
use curve25519_dalek::constants::ED25519_BASEPOINT_POINT;
|
|
||||||
use curve25519_dalek::edwards::EdwardsPoint;
|
|
||||||
use monero_adaptor::alice::Alice0;
|
|
||||||
use monero_adaptor::bob::Bob0;
|
|
||||||
use rand::rngs::OsRng;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn happy_path() {
|
|
||||||
let msg_to_sign = b"hello world, monero is amazing!!";
|
|
||||||
|
|
||||||
let s_prime_a = Scalar::random(&mut OsRng);
|
|
||||||
let s_b = Scalar::random(&mut OsRng);
|
|
||||||
|
|
||||||
let pk = (s_prime_a + s_b) * ED25519_BASEPOINT_POINT;
|
|
||||||
|
|
||||||
let (r_a, R_a, R_prime_a) = {
|
|
||||||
let r_a = Scalar::random(&mut OsRng);
|
|
||||||
let R_a = r_a * ED25519_BASEPOINT_POINT;
|
|
||||||
|
|
||||||
let pk_hashed_to_point = hash_point_to_point(pk);
|
|
||||||
|
|
||||||
let R_prime_a = r_a * pk_hashed_to_point;
|
|
||||||
|
|
||||||
(r_a, R_a, R_prime_a)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut ring = [EdwardsPoint::default(); RING_SIZE];
|
|
||||||
ring[0] = pk;
|
|
||||||
|
|
||||||
ring[1..].fill_with(|| {
|
|
||||||
let x = Scalar::random(&mut OsRng);
|
|
||||||
|
|
||||||
x * ED25519_BASEPOINT_POINT
|
|
||||||
});
|
|
||||||
|
|
||||||
let alice = Alice0::new(ring, *msg_to_sign, R_a, R_prime_a, s_prime_a).unwrap();
|
|
||||||
let bob = Bob0::new(ring, *msg_to_sign, R_a, R_prime_a, s_b).unwrap();
|
|
||||||
|
|
||||||
let msg = alice.next_message();
|
|
||||||
let bob = bob.receive(msg);
|
|
||||||
|
|
||||||
let msg = bob.next_message();
|
|
||||||
let alice = alice.receive(msg).unwrap();
|
|
||||||
|
|
||||||
let msg = alice.next_message();
|
|
||||||
let bob = bob.receive(msg).unwrap();
|
|
||||||
|
|
||||||
let msg = bob.next_message();
|
|
||||||
let alice = alice.receive(msg);
|
|
||||||
|
|
||||||
let sig = alice.adaptor_sig.adapt(r_a);
|
|
||||||
|
|
||||||
assert!(sig.verify(ring, msg_to_sign).unwrap());
|
|
||||||
|
|
||||||
let alice = Alice::new(alice);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,115 +1,83 @@
|
||||||
use std::collections::VecDeque;
|
use anyhow::Result;
|
||||||
use std::task::Poll;
|
use monero::PublicKey;
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
|
||||||
pub struct StateMachine {
|
use monero_adaptor::alice::Alice2;
|
||||||
state: State,
|
use monero_adaptor::AdaptorSignature;
|
||||||
actions: VecDeque<Action>,
|
|
||||||
events: VecDeque<Event>,
|
use crate::bitcoin::TxLock;
|
||||||
|
use crate::monero::{Scalar, TransferRequest};
|
||||||
|
use curve25519_dalek::edwards::EdwardsPoint;
|
||||||
|
|
||||||
|
// start
|
||||||
|
pub struct Alice3 {
|
||||||
|
pub xmr_swap_amount: crate::monero::Amount,
|
||||||
|
pub btc_swap_amount: crate::bitcoin::Amount,
|
||||||
|
// pub adaptor_sig: AdaptorSignature,
|
||||||
|
pub a: crate::bitcoin::SecretKey,
|
||||||
|
pub B: crate::bitcoin::PublicKey,
|
||||||
|
pub s_a: Scalar,
|
||||||
|
pub S_b_monero: EdwardsPoint,
|
||||||
|
pub v_a: crate::monero::PrivateViewKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StateMachine {
|
// published xmr_lock, watching for btc_lock
|
||||||
fn inject_event(&mut self, event: Event) {
|
pub struct Alice4 {
|
||||||
match self.state {
|
a: crate::bitcoin::SecretKey,
|
||||||
State::WatchingForBtcLock => match event {
|
B: crate::bitcoin::PublicKey,
|
||||||
Event::BtcLockSeenInMempool => {
|
btc_swap_amount: crate::bitcoin::Amount,
|
||||||
self.actions.push_back(Action::SignAndBroadcastBtcRedeem);
|
// pub adaptor_sig: AdaptorSignature,
|
||||||
self.actions.push_back(Action::WatchForXmrRedeem);
|
}
|
||||||
self.state = State::WatchingForXmrRedeem;
|
|
||||||
}
|
// published seen btc_lock, published btc_redeem
|
||||||
Event::BtcLockTimeoutElapsed => {
|
pub struct Alice5;
|
||||||
self.actions.push_back(Action::BroadcastXmrRefund);
|
|
||||||
self.state = State::Aborted;
|
impl Alice3 {
|
||||||
}
|
pub fn new(
|
||||||
_ => {}
|
S_b_monero: EdwardsPoint,
|
||||||
},
|
B: crate::bitcoin::PublicKey,
|
||||||
State::WatchingForXmrRedeem => match event {
|
xmr_swap_amount: crate::monero::Amount,
|
||||||
Event::T2Elapsed => {
|
btc_swap_amount: crate::bitcoin::Amount,
|
||||||
self.actions.push_back(Action::BroadcastXmrRefund);
|
) -> Self {
|
||||||
self.actions.push_back(Action::SignAndBroadcastBtcPunish);
|
Self {
|
||||||
self.state = State::Punished;
|
xmr_swap_amount,
|
||||||
}
|
btc_swap_amount,
|
||||||
Event::XmrRedeemSeenInMempool => {
|
// adaptor_sig: alice2.adaptor_sig,
|
||||||
self.actions.push_back(Action::SignAndBroadcastBtcPunish);
|
a: crate::bitcoin::SecretKey::new_random(&mut OsRng),
|
||||||
self.state = State::Success;
|
B,
|
||||||
}
|
s_a: Scalar::random(&mut OsRng),
|
||||||
_ => {}
|
S_b_monero,
|
||||||
},
|
v_a: crate::monero::PrivateViewKey::new_random(&mut OsRng),
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub async fn publish_xmr_lock(&self, wallet: &crate::monero::Wallet) -> Result<Alice4> {
|
||||||
|
let S_a = monero::PublicKey::from_private_key(&monero::PrivateKey { scalar: self.s_a });
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<Action> {
|
let public_spend_key = S_a + self.S_b_monero;
|
||||||
if let Some(action) = self.actions.pop_front() {
|
let public_view_key = self.v_a.public();
|
||||||
Poll::Ready(action)
|
|
||||||
} else {
|
|
||||||
Poll::Pending
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
let req = TransferRequest {
|
||||||
pub enum State {
|
public_spend_key,
|
||||||
WatchingForBtcLock,
|
public_view_key,
|
||||||
WatchingForXmrRedeem,
|
amount: self.xmr_swap_amount,
|
||||||
Punished,
|
|
||||||
Success,
|
|
||||||
Aborted,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Event {
|
|
||||||
BtcLockSeenInMempool,
|
|
||||||
T2Elapsed,
|
|
||||||
BtcLockTimeoutElapsed,
|
|
||||||
XmrRedeemSeenInMempool,
|
|
||||||
}
|
|
||||||
|
|
||||||
// These actions should not fail (are retried until successful) and should be
|
|
||||||
// idempotent This allows us to greatly simplify the state machine
|
|
||||||
pub enum Action {
|
|
||||||
WatchForXmrRedeem,
|
|
||||||
SignAndBroadcastBtcPunish,
|
|
||||||
SignAndBroadcastBtcRedeem,
|
|
||||||
BroadcastXmrRefund,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn happy_path() {
|
|
||||||
let mut state_machine = StateMachine {
|
|
||||||
state: State::WatchingForBtcLock,
|
|
||||||
actions: Default::default(),
|
|
||||||
events: Default::default(),
|
|
||||||
};
|
};
|
||||||
state_machine.inject_event(Event::BtcLockSeenInMempool);
|
|
||||||
state_machine.inject_event(Event::XmrRedeemSeenInMempool);
|
|
||||||
assert_eq!(state_machine.state, State::Success);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
// we may have to send this to Bob
|
||||||
fn bob_fails_to_lock_btc() {
|
let _ = wallet.transfer(req).await?;
|
||||||
let mut state_machine = StateMachine {
|
|
||||||
state: State::WatchingForBtcLock,
|
|
||||||
actions: Default::default(),
|
|
||||||
events: Default::default(),
|
|
||||||
};
|
|
||||||
state_machine.events.push_back(Event::BtcLockTimeoutElapsed);
|
|
||||||
state_machine.run();
|
|
||||||
assert_eq!(state_machine.state, State::Aborted);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
Ok(Alice4 {
|
||||||
fn bob_fails_to_redeem_xmr_before_t2() {
|
a: self.a.clone(),
|
||||||
let mut state_machine = StateMachine {
|
B: self.B,
|
||||||
state: State::WatchingForBtcLock,
|
btc_swap_amount: Default::default(),
|
||||||
actions: Default::default(),
|
// adaptor_sig: self.adaptor_sig.clone(),
|
||||||
events: Default::default(),
|
})
|
||||||
};
|
}
|
||||||
state_machine.events.push_back(Event::BtcLockSeenInMempool);
|
}
|
||||||
state_machine.events.push_back(Event::T2Elapsed);
|
|
||||||
state_machine.run();
|
impl Alice4 {
|
||||||
assert_eq!(state_machine.state, State::Punished);
|
pub async fn watch_for_btc_lock(&self, wallet: &crate::bitcoin::Wallet) -> Result<Alice5> {
|
||||||
|
let btc_lock = TxLock::new(wallet, self.btc_swap_amount, self.a.public(), self.B).await?;
|
||||||
|
wallet.subscribe_to(btc_lock);
|
||||||
|
Ok(Alice5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,119 +1,35 @@
|
||||||
use crate::monero::TransferProof;
|
use anyhow::Result;
|
||||||
use std::collections::VecDeque;
|
use monero::PublicKey;
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
|
||||||
pub struct StateMachine {
|
use monero_adaptor::alice::Alice2;
|
||||||
state: State,
|
use monero_adaptor::AdaptorSignature;
|
||||||
actions: VecDeque<Action>,
|
|
||||||
events: VecDeque<Event>,
|
use crate::bitcoin::Txid;
|
||||||
|
use crate::monero::wallet::WatchRequest;
|
||||||
|
use crate::monero::{Scalar, TransferRequest};
|
||||||
|
use crate::xmr_first_protocol::transactions::xmr_lock::XmrLock;
|
||||||
|
|
||||||
|
// watching for xmr_lock
|
||||||
|
pub struct Bob3 {
|
||||||
|
pub xmr_swap_amount: crate::monero::Amount,
|
||||||
|
pub btc_swap_amount: crate::bitcoin::Amount,
|
||||||
|
pub xmr_lock: XmrLock,
|
||||||
|
v_b: crate::monero::PrivateViewKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StateMachine {
|
impl Bob3 {
|
||||||
fn next(&mut self, event: Event) {
|
pub fn watch_for_lock_xmr(&self, wallet: &crate::monero::Wallet) {
|
||||||
match self.state {
|
let req = WatchRequest {
|
||||||
State::WatchingForXmrLock => match event {
|
public_spend_key: self.xmr_lock.public_spend_key,
|
||||||
Event::XmrConfirmed => {
|
public_view_key: self.v_b.public(),
|
||||||
self.actions.push_back(Action::SignAndBroadcastBtcLock);
|
transfer_proof: self.xmr_lock.transfer_proof.clone(),
|
||||||
self.state = State::WaitingForBtcRedeem;
|
conf_target: 1,
|
||||||
}
|
expected: self.xmr_swap_amount,
|
||||||
Event::T1Elapsed => {
|
|
||||||
self.state = State::Aborted;
|
|
||||||
}
|
|
||||||
Event::XmrRefundSeenInMempool => {
|
|
||||||
self.state = State::Aborted;
|
|
||||||
}
|
|
||||||
_ => panic!("unhandled scenario"),
|
|
||||||
},
|
|
||||||
State::WaitingForBtcRedeem => match event {
|
|
||||||
Event::BtcRedeemSeenInMempool => {
|
|
||||||
self.actions.push_back(Action::BroadcastXmrRedeem);
|
|
||||||
self.state = State::Success;
|
|
||||||
}
|
|
||||||
Event::T1Elapsed => {
|
|
||||||
self.actions.push_back(Action::SignAndBroadcastBtcRefund);
|
|
||||||
self.state = State::Refunded;
|
|
||||||
}
|
|
||||||
Event::XmrRefundSeenInMempool => {
|
|
||||||
self.actions.push_back(Action::SignAndBroadcastBtcRefund);
|
|
||||||
self.state = State::Refunded;
|
|
||||||
}
|
|
||||||
_ => panic!("unhandled scenario"),
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(&mut self) {
|
|
||||||
while let Some(event) = self.events.pop_front() {
|
|
||||||
self.next(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
|
||||||
pub enum State {
|
|
||||||
WatchingForXmrLock,
|
|
||||||
WaitingForBtcRedeem,
|
|
||||||
Success,
|
|
||||||
Refunded,
|
|
||||||
Aborted,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Event {
|
|
||||||
XmrConfirmed,
|
|
||||||
// This will contain the s_a allowing bob to build xmr_redeem
|
|
||||||
BtcRedeemSeenInMempool,
|
|
||||||
XmrRefundSeenInMempool,
|
|
||||||
T1Elapsed,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Action {
|
|
||||||
SignAndBroadcastBtcLock,
|
|
||||||
BroadcastXmrRedeem,
|
|
||||||
SignAndBroadcastBtcRefund,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn happy_path() {
|
|
||||||
let mut state_machine = StateMachine {
|
|
||||||
state: State::WatchingForXmrLock,
|
|
||||||
actions: Default::default(),
|
|
||||||
events: Default::default(),
|
|
||||||
};
|
};
|
||||||
state_machine.events.push_back(Event::XmrConfirmed);
|
wallet.watch_for_transfer(req);
|
||||||
state_machine
|
|
||||||
.events
|
|
||||||
.push_back(Event::BtcRedeemSeenInMempool);
|
|
||||||
state_machine.run();
|
|
||||||
assert_eq!(state_machine.state, State::Success);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn alice_fails_to_redeem_btc_before_t1() {
|
|
||||||
let mut state_machine = StateMachine {
|
|
||||||
state: State::WatchingForXmrLock,
|
|
||||||
actions: Default::default(),
|
|
||||||
events: Default::default(),
|
|
||||||
};
|
|
||||||
state_machine.events.push_back(Event::XmrConfirmed);
|
|
||||||
state_machine.events.push_back(Event::T1Elapsed);
|
|
||||||
state_machine.run();
|
|
||||||
assert_eq!(state_machine.state, State::Refunded);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn alice_tries_to_refund_xmr_after_redeeming_btc() {
|
|
||||||
let mut state_machine = StateMachine {
|
|
||||||
state: State::WatchingForXmrLock,
|
|
||||||
actions: Default::default(),
|
|
||||||
events: Default::default(),
|
|
||||||
};
|
|
||||||
state_machine.events.push_back(Event::XmrConfirmed);
|
|
||||||
state_machine.events.push_back(Event::T1Elapsed);
|
|
||||||
state_machine.run();
|
|
||||||
assert_eq!(state_machine.state, State::Refunded);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// published btc_lock, watching for xmr_redeem
|
||||||
|
pub struct Bob4;
|
||||||
|
|
115
swap/src/xmr_first_protocol/state_machine/alice.rs
Normal file
115
swap/src/xmr_first_protocol/state_machine/alice.rs
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::task::Poll;
|
||||||
|
|
||||||
|
pub struct StateMachine {
|
||||||
|
state: State,
|
||||||
|
actions: VecDeque<Action>,
|
||||||
|
events: VecDeque<Event>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StateMachine {
|
||||||
|
fn inject_event(&mut self, event: Event) {
|
||||||
|
match self.state {
|
||||||
|
State::WatchingForBtcLock => match event {
|
||||||
|
Event::BtcLockSeenInMempool => {
|
||||||
|
self.actions.push_back(Action::SignAndBroadcastBtcRedeem);
|
||||||
|
self.actions.push_back(Action::WatchForXmrRedeem);
|
||||||
|
self.state = State::WatchingForXmrRedeem;
|
||||||
|
}
|
||||||
|
Event::BtcLockTimeoutElapsed => {
|
||||||
|
self.actions.push_back(Action::BroadcastXmrRefund);
|
||||||
|
self.state = State::Aborted;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
State::WatchingForXmrRedeem => match event {
|
||||||
|
Event::T2Elapsed => {
|
||||||
|
self.actions.push_back(Action::BroadcastXmrRefund);
|
||||||
|
self.actions.push_back(Action::SignAndBroadcastBtcPunish);
|
||||||
|
self.state = State::Punished;
|
||||||
|
}
|
||||||
|
Event::XmrRedeemSeenInMempool => {
|
||||||
|
self.actions.push_back(Action::SignAndBroadcastBtcPunish);
|
||||||
|
self.state = State::Success;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Action> {
|
||||||
|
if let Some(action) = self.actions.pop_front() {
|
||||||
|
Poll::Ready(action)
|
||||||
|
} else {
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
pub enum State {
|
||||||
|
WatchingForBtcLock,
|
||||||
|
WatchingForXmrRedeem,
|
||||||
|
Punished,
|
||||||
|
Success,
|
||||||
|
Aborted,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Event {
|
||||||
|
BtcLockSeenInMempool,
|
||||||
|
T2Elapsed,
|
||||||
|
BtcLockTimeoutElapsed,
|
||||||
|
XmrRedeemSeenInMempool,
|
||||||
|
}
|
||||||
|
|
||||||
|
// These actions should not fail (are retried until successful) and should be
|
||||||
|
// idempotent This allows us to greatly simplify the state machine
|
||||||
|
pub enum Action {
|
||||||
|
WatchForXmrRedeem,
|
||||||
|
SignAndBroadcastBtcPunish,
|
||||||
|
SignAndBroadcastBtcRedeem,
|
||||||
|
BroadcastXmrRefund,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn happy_path() {
|
||||||
|
let mut state_machine = StateMachine {
|
||||||
|
state: State::WatchingForBtcLock,
|
||||||
|
actions: Default::default(),
|
||||||
|
events: Default::default(),
|
||||||
|
};
|
||||||
|
state_machine.inject_event(Event::BtcLockSeenInMempool);
|
||||||
|
state_machine.inject_event(Event::XmrRedeemSeenInMempool);
|
||||||
|
assert_eq!(state_machine.state, State::Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bob_fails_to_lock_btc() {
|
||||||
|
let mut state_machine = StateMachine {
|
||||||
|
state: State::WatchingForBtcLock,
|
||||||
|
actions: Default::default(),
|
||||||
|
events: Default::default(),
|
||||||
|
};
|
||||||
|
state_machine.events.push_back(Event::BtcLockTimeoutElapsed);
|
||||||
|
state_machine.poll();
|
||||||
|
assert_eq!(state_machine.state, State::Aborted);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bob_fails_to_redeem_xmr_before_t2() {
|
||||||
|
let mut state_machine = StateMachine {
|
||||||
|
state: State::WatchingForBtcLock,
|
||||||
|
actions: Default::default(),
|
||||||
|
events: Default::default(),
|
||||||
|
};
|
||||||
|
state_machine.events.push_back(Event::BtcLockSeenInMempool);
|
||||||
|
state_machine.events.push_back(Event::T2Elapsed);
|
||||||
|
state_machine.run();
|
||||||
|
assert_eq!(state_machine.state, State::Punished);
|
||||||
|
}
|
||||||
|
}
|
118
swap/src/xmr_first_protocol/state_machine/bob.rs
Normal file
118
swap/src/xmr_first_protocol/state_machine/bob.rs
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
pub struct StateMachine {
|
||||||
|
state: State,
|
||||||
|
actions: VecDeque<Action>,
|
||||||
|
events: VecDeque<Event>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StateMachine {
|
||||||
|
fn next(&mut self, event: Event) {
|
||||||
|
match self.state {
|
||||||
|
State::WatchingForXmrLock => match event {
|
||||||
|
Event::XmrConfirmed => {
|
||||||
|
self.actions.push_back(Action::SignAndBroadcastBtcLock);
|
||||||
|
self.state = State::WaitingForBtcRedeem;
|
||||||
|
}
|
||||||
|
Event::T1Elapsed => {
|
||||||
|
self.state = State::Aborted;
|
||||||
|
}
|
||||||
|
Event::XmrRefundSeenInMempool => {
|
||||||
|
self.state = State::Aborted;
|
||||||
|
}
|
||||||
|
_ => panic!("unhandled scenario"),
|
||||||
|
},
|
||||||
|
State::WaitingForBtcRedeem => match event {
|
||||||
|
Event::BtcRedeemSeenInMempool => {
|
||||||
|
self.actions.push_back(Action::BroadcastXmrRedeem);
|
||||||
|
self.state = State::Success;
|
||||||
|
}
|
||||||
|
Event::T1Elapsed => {
|
||||||
|
self.actions.push_back(Action::SignAndBroadcastBtcRefund);
|
||||||
|
self.state = State::Refunded;
|
||||||
|
}
|
||||||
|
Event::XmrRefundSeenInMempool => {
|
||||||
|
self.actions.push_back(Action::SignAndBroadcastBtcRefund);
|
||||||
|
self.state = State::Refunded;
|
||||||
|
}
|
||||||
|
_ => panic!("unhandled scenario"),
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&mut self) {
|
||||||
|
while let Some(event) = self.events.pop_front() {
|
||||||
|
self.next(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
pub enum State {
|
||||||
|
WatchingForXmrLock,
|
||||||
|
WaitingForBtcRedeem,
|
||||||
|
Success,
|
||||||
|
Refunded,
|
||||||
|
Aborted,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Event {
|
||||||
|
XmrConfirmed,
|
||||||
|
// This will contain the s_a allowing bob to build xmr_redeem
|
||||||
|
BtcRedeemSeenInMempool,
|
||||||
|
XmrRefundSeenInMempool,
|
||||||
|
T1Elapsed,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Action {
|
||||||
|
SignAndBroadcastBtcLock,
|
||||||
|
BroadcastXmrRedeem,
|
||||||
|
SignAndBroadcastBtcRefund,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn happy_path() {
|
||||||
|
let mut state_machine = StateMachine {
|
||||||
|
state: State::WatchingForXmrLock,
|
||||||
|
actions: Default::default(),
|
||||||
|
events: Default::default(),
|
||||||
|
};
|
||||||
|
state_machine.events.push_back(Event::XmrConfirmed);
|
||||||
|
state_machine
|
||||||
|
.events
|
||||||
|
.push_back(Event::BtcRedeemSeenInMempool);
|
||||||
|
state_machine.run();
|
||||||
|
assert_eq!(state_machine.state, State::Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alice_fails_to_redeem_btc_before_t1() {
|
||||||
|
let mut state_machine = StateMachine {
|
||||||
|
state: State::WatchingForXmrLock,
|
||||||
|
actions: Default::default(),
|
||||||
|
events: Default::default(),
|
||||||
|
};
|
||||||
|
state_machine.events.push_back(Event::XmrConfirmed);
|
||||||
|
state_machine.events.push_back(Event::T1Elapsed);
|
||||||
|
state_machine.run();
|
||||||
|
assert_eq!(state_machine.state, State::Refunded);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alice_tries_to_refund_xmr_after_redeeming_btc() {
|
||||||
|
let mut state_machine = StateMachine {
|
||||||
|
state: State::WatchingForXmrLock,
|
||||||
|
actions: Default::default(),
|
||||||
|
events: Default::default(),
|
||||||
|
};
|
||||||
|
state_machine.events.push_back(Event::XmrConfirmed);
|
||||||
|
state_machine.events.push_back(Event::T1Elapsed);
|
||||||
|
state_machine.run();
|
||||||
|
assert_eq!(state_machine.state, State::Refunded);
|
||||||
|
}
|
||||||
|
}
|
2
swap/src/xmr_first_protocol/state_machine/mod.rs
Normal file
2
swap/src/xmr_first_protocol/state_machine/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// pub mod alice;
|
||||||
|
// pub mod bob;
|
|
@ -1,5 +1,5 @@
|
||||||
pub mod btc_lock;
|
pub mod btc_lock;
|
||||||
pub mod btc_redeem;
|
// pub mod btc_redeem;
|
||||||
pub mod xmr_lock;
|
pub mod xmr_lock;
|
||||||
pub mod xmr_refund;
|
pub mod xmr_refund;
|
||||||
|
|
||||||
|
@ -8,174 +8,10 @@ use crate::bitcoin::{
|
||||||
build_shared_output_descriptor, Address, Amount, PartiallySignedTransaction, PublicKey,
|
build_shared_output_descriptor, Address, Amount, PartiallySignedTransaction, PublicKey,
|
||||||
Transaction, Txid, Wallet, TX_FEE,
|
Transaction, Txid, Wallet, TX_FEE,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::{bail, Result};
|
||||||
use bdk::bitcoin::{OutPoint, Script, TxIn, TxOut};
|
use bdk::bitcoin::{OutPoint, Script, TxIn, TxOut};
|
||||||
use bdk::database::BatchDatabase;
|
use bdk::database::BatchDatabase;
|
||||||
use bdk::descriptor::Descriptor;
|
use bdk::descriptor::Descriptor;
|
||||||
use ecdsa_fun::fun::Point;
|
use ecdsa_fun::fun::Point;
|
||||||
use miniscript::DescriptorTrait;
|
use miniscript::DescriptorTrait;
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
||||||
pub struct BtcLock {
|
|
||||||
inner: PartiallySignedTransaction,
|
|
||||||
pub(crate) output_descriptor: Descriptor<::bitcoin::PublicKey>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BtcLock {
|
|
||||||
pub async fn new<B, D, C>(
|
|
||||||
wallet: &Wallet<B, D, C>,
|
|
||||||
amount: Amount,
|
|
||||||
A: PublicKey,
|
|
||||||
B: PublicKey,
|
|
||||||
) -> Result<Self>
|
|
||||||
where
|
|
||||||
D: BatchDatabase,
|
|
||||||
{
|
|
||||||
let lock_output_descriptor = build_shared_output_descriptor(A.0, B.0);
|
|
||||||
let address = lock_output_descriptor
|
|
||||||
.address(wallet.get_network())
|
|
||||||
.expect("can derive address from descriptor");
|
|
||||||
|
|
||||||
let psbt = wallet.send_to_address(address, amount).await?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
inner: psbt,
|
|
||||||
output_descriptor: lock_output_descriptor,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an instance of `TxLock` from a PSBT, the public keys of the
|
|
||||||
/// parties and the specified amount.
|
|
||||||
///
|
|
||||||
/// This function validates that the given PSBT does indeed pay that
|
|
||||||
/// specified amount to a shared output.
|
|
||||||
pub fn from_psbt(
|
|
||||||
psbt: PartiallySignedTransaction,
|
|
||||||
A: PublicKey,
|
|
||||||
B: PublicKey,
|
|
||||||
btc: Amount,
|
|
||||||
) -> Result<Self> {
|
|
||||||
let shared_output_candidate = match psbt.global.unsigned_tx.output.as_slice() {
|
|
||||||
[shared_output_candidate, _] if shared_output_candidate.value == btc.as_sat() => {
|
|
||||||
shared_output_candidate
|
|
||||||
}
|
|
||||||
[_, shared_output_candidate] if shared_output_candidate.value == btc.as_sat() => {
|
|
||||||
shared_output_candidate
|
|
||||||
}
|
|
||||||
// A single output is possible if Bob funds without any change necessary
|
|
||||||
[shared_output_candidate] if shared_output_candidate.value == btc.as_sat() => {
|
|
||||||
shared_output_candidate
|
|
||||||
}
|
|
||||||
[_, _] => {
|
|
||||||
bail!("Neither of the two provided outputs pays the right amount!");
|
|
||||||
}
|
|
||||||
[_] => {
|
|
||||||
bail!("The provided output does not pay the right amount!");
|
|
||||||
}
|
|
||||||
other => {
|
|
||||||
let num_outputs = other.len();
|
|
||||||
bail!(
|
|
||||||
"PSBT has {} outputs, expected one or two. Something is fishy!",
|
|
||||||
num_outputs
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let descriptor = build_shared_output_descriptor(A.0, B.0);
|
|
||||||
let legit_shared_output_script = descriptor.script_pubkey();
|
|
||||||
|
|
||||||
if shared_output_candidate.script_pubkey != legit_shared_output_script {
|
|
||||||
bail!("Output script is not a shared output")
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(BtcLock {
|
|
||||||
inner: psbt,
|
|
||||||
output_descriptor: descriptor,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lock_amount(&self) -> Amount {
|
|
||||||
Amount::from_sat(self.inner.clone().extract_tx().output[self.lock_output_vout()].value)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn txid(&self) -> Txid {
|
|
||||||
self.inner.clone().extract_tx().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.txid(), self.lock_output_vout() as u32)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculate the size of the script used by this transaction.
|
|
||||||
pub fn script_size() -> usize {
|
|
||||||
build_shared_output_descriptor(
|
|
||||||
Point::random(&mut thread_rng()),
|
|
||||||
Point::random(&mut thread_rng()),
|
|
||||||
)
|
|
||||||
.script_pubkey()
|
|
||||||
.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn script_pubkey(&self) -> Script {
|
|
||||||
self.output_descriptor.script_pubkey()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retreive the index of the locked output in the transaction outputs
|
|
||||||
/// vector
|
|
||||||
fn lock_output_vout(&self) -> usize {
|
|
||||||
self.inner
|
|
||||||
.clone()
|
|
||||||
.extract_tx()
|
|
||||||
.output
|
|
||||||
.iter()
|
|
||||||
.position(|output| output.script_pubkey == self.output_descriptor.script_pubkey())
|
|
||||||
.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.clone().extract_tx().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<BtcLock> for PartiallySignedTransaction {
|
|
||||||
fn from(from: BtcLock) -> Self {
|
|
||||||
from.inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Watchable for BtcLock {
|
|
||||||
fn id(&self) -> Txid {
|
|
||||||
self.txid()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn script(&self) -> Script {
|
|
||||||
self.output_descriptor.script_pubkey()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ use crate::bitcoin::{
|
||||||
build_shared_output_descriptor, Address, Amount, PartiallySignedTransaction, PublicKey,
|
build_shared_output_descriptor, Address, Amount, PartiallySignedTransaction, PublicKey,
|
||||||
Transaction, Txid, Wallet, TX_FEE,
|
Transaction, Txid, Wallet, TX_FEE,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::{bail, Result};
|
||||||
use bdk::bitcoin::{OutPoint, Script, TxIn, TxOut};
|
use bdk::bitcoin::{OutPoint, Script, TxIn, TxOut};
|
||||||
use bdk::database::BatchDatabase;
|
use bdk::database::BatchDatabase;
|
||||||
use bdk::descriptor::Descriptor;
|
use bdk::descriptor::Descriptor;
|
||||||
|
@ -11,7 +11,7 @@ use ecdsa_fun::fun::Point;
|
||||||
use miniscript::DescriptorTrait;
|
use miniscript::DescriptorTrait;
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct BtcLock {
|
pub struct BtcLock {
|
||||||
inner: PartiallySignedTransaction,
|
inner: PartiallySignedTransaction,
|
||||||
pub(crate) output_descriptor: Descriptor<::bitcoin::PublicKey>,
|
pub(crate) output_descriptor: Descriptor<::bitcoin::PublicKey>,
|
||||||
|
@ -27,7 +27,7 @@ impl BtcLock {
|
||||||
where
|
where
|
||||||
D: BatchDatabase,
|
D: BatchDatabase,
|
||||||
{
|
{
|
||||||
let lock_output_descriptor = build_shared_output_descriptor(A.0, B.0);
|
let lock_output_descriptor = build_shared_output_descriptor(A.into(), B.into());
|
||||||
let address = lock_output_descriptor
|
let address = lock_output_descriptor
|
||||||
.address(wallet.get_network())
|
.address(wallet.get_network())
|
||||||
.expect("can derive address from descriptor");
|
.expect("can derive address from descriptor");
|
||||||
|
@ -77,7 +77,7 @@ impl BtcLock {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let descriptor = build_shared_output_descriptor(A.0, B.0);
|
let descriptor = build_shared_output_descriptor(A.into(), B.into());
|
||||||
let legit_shared_output_script = descriptor.script_pubkey();
|
let legit_shared_output_script = descriptor.script_pubkey();
|
||||||
|
|
||||||
if shared_output_candidate.script_pubkey != legit_shared_output_script {
|
if shared_output_candidate.script_pubkey != legit_shared_output_script {
|
||||||
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
use crate::bitcoin::wallet::Watchable;
|
||||||
|
use crate::bitcoin::{
|
||||||
|
verify_encsig, verify_sig, Address, EmptyWitnessStack, EncryptedSignature, NoInputs,
|
||||||
|
NotThreeWitnesses, PublicKey, SecretKey, TooManyInputs, Transaction, TxLock,
|
||||||
|
};
|
||||||
|
use ::bitcoin::util::bip143::SigHashCache;
|
||||||
|
use ::bitcoin::{SigHash, SigHashType, Txid};
|
||||||
|
use anyhow::{bail, Context, Result};
|
||||||
|
use bitcoin::Script;
|
||||||
|
use ecdsa_fun::adaptor::{Adaptor, HashTranscript};
|
||||||
|
use ecdsa_fun::fun::Scalar;
|
||||||
|
use ecdsa_fun::nonce::Deterministic;
|
||||||
|
use ecdsa_fun::Signature;
|
||||||
|
use miniscript::{Descriptor, DescriptorTrait};
|
||||||
|
use sha2::Sha256;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct TxRedeem {
|
||||||
|
inner: Transaction,
|
||||||
|
digest: SigHash,
|
||||||
|
lock_output_descriptor: Descriptor<::bitcoin::PublicKey>,
|
||||||
|
watch_script: Script,
|
||||||
|
}
|
||||||
|
|
||||||
|
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.script_code(),
|
||||||
|
tx_lock.lock_amount().as_sat(),
|
||||||
|
SigHashType::All,
|
||||||
|
);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
inner: tx_redeem,
|
||||||
|
digest,
|
||||||
|
lock_output_descriptor: tx_lock.output_descriptor.clone(),
|
||||||
|
watch_script: redeem_address.script_pubkey(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn txid(&self) -> Txid {
|
||||||
|
self.inner.txid()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn digest(&self) -> SigHash {
|
||||||
|
self.digest
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn complete(
|
||||||
|
mut self,
|
||||||
|
encrypted_signature: EncryptedSignature,
|
||||||
|
a: SecretKey,
|
||||||
|
s_a: Scalar,
|
||||||
|
B: PublicKey,
|
||||||
|
) -> Result<Transaction> {
|
||||||
|
verify_encsig(
|
||||||
|
B,
|
||||||
|
PublicKey::from(s_a.clone()),
|
||||||
|
&self.digest(),
|
||||||
|
&encrypted_signature,
|
||||||
|
)
|
||||||
|
.context("Invalid encrypted signature received")?;
|
||||||
|
|
||||||
|
let sig_a = a.sign(self.digest());
|
||||||
|
let adaptor = Adaptor::<HashTranscript<Sha256>, Deterministic<Sha256>>::default();
|
||||||
|
let sig_b = adaptor.decrypt_signature(&s_a, encrypted_signature);
|
||||||
|
|
||||||
|
let satisfier = {
|
||||||
|
let mut satisfier = HashMap::with_capacity(2);
|
||||||
|
|
||||||
|
let A = ::bitcoin::PublicKey {
|
||||||
|
compressed: true,
|
||||||
|
key: a.public().into(),
|
||||||
|
};
|
||||||
|
let B = ::bitcoin::PublicKey {
|
||||||
|
compressed: true,
|
||||||
|
key: B.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
|
||||||
|
};
|
||||||
|
|
||||||
|
self.lock_output_descriptor
|
||||||
|
.satisfy(&mut self.inner.input[0], satisfier)
|
||||||
|
.context("Failed to sign Bitcoin redeem transaction")?;
|
||||||
|
|
||||||
|
Ok(self.inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
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!("too many inputs"),
|
||||||
|
};
|
||||||
|
|
||||||
|
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!("not three witnesses"),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Watchable for TxRedeem {
|
||||||
|
fn id(&self) -> Txid {
|
||||||
|
self.txid()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn script(&self) -> Script {
|
||||||
|
self.watch_script.clone()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,26 @@
|
||||||
use crate::monero::PrivateViewKey;
|
use crate::monero::TransferRequest;
|
||||||
|
use crate::xmr_first_protocol::alice::Alice4;
|
||||||
|
use anyhow::Result;
|
||||||
use monero_adaptor::AdaptorSignature;
|
use monero_adaptor::AdaptorSignature;
|
||||||
|
|
||||||
pub struct XmrRedeem {
|
pub struct XmrRefund {
|
||||||
adaptor: AdaptorSignature,
|
adaptor: AdaptorSignature,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl XmrRefund {
|
||||||
|
pub async fn publish_xmr_refund(&self, wallet: &crate::monero::Wallet) -> Result<()> {
|
||||||
|
let S_a = monero::PublicKey::from_private_key(&monero::PrivateKey { scalar: self.s_a });
|
||||||
|
|
||||||
|
let public_spend_key = S_a + self.S_b_monero;
|
||||||
|
let public_view_key = self.v_a.public();
|
||||||
|
|
||||||
|
let req = TransferRequest {
|
||||||
|
public_spend_key,
|
||||||
|
public_view_key,
|
||||||
|
amount: self.xmr_swap_amount,
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = wallet.transfer(req).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
mod bitcoind;
|
mod bitcoind;
|
||||||
mod electrs;
|
pub mod electrs;
|
||||||
|
|
||||||
use crate::harness;
|
use crate::harness;
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
|
@ -625,7 +625,7 @@ fn random_prefix() -> String {
|
||||||
chars
|
chars
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn init_containers(cli: &Cli) -> (Monero, Containers<'_>) {
|
pub async fn init_containers(cli: &Cli) -> (Monero, Containers<'_>) {
|
||||||
let prefix = random_prefix();
|
let prefix = random_prefix();
|
||||||
let bitcoind_name = format!("{}_{}", prefix, "bitcoind");
|
let bitcoind_name = format!("{}_{}", prefix, "bitcoind");
|
||||||
let (bitcoind, bitcoind_url) =
|
let (bitcoind, bitcoind_url) =
|
||||||
|
@ -761,7 +761,7 @@ async fn init_monero_container(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn init_test_wallets(
|
pub async fn init_test_wallets(
|
||||||
name: &str,
|
name: &str,
|
||||||
bitcoind_url: Url,
|
bitcoind_url: Url,
|
||||||
monero: &Monero,
|
monero: &Monero,
|
||||||
|
@ -835,10 +835,10 @@ async fn init_test_wallets(
|
||||||
// This is just to keep the containers alive
|
// This is just to keep the containers alive
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
struct Containers<'a> {
|
struct Containers<'a> {
|
||||||
bitcoind_url: Url,
|
pub bitcoind_url: Url,
|
||||||
bitcoind: Container<'a, Cli, bitcoind::Bitcoind>,
|
bitcoind: Container<'a, Cli, bitcoind::Bitcoind>,
|
||||||
monerods: Vec<Container<'a, Cli, image::Monero>>,
|
monerods: Vec<Container<'a, Cli, image::Monero>>,
|
||||||
electrs: Container<'a, Cli, electrs::Electrs>,
|
pub electrs: Container<'a, Cli, electrs::Electrs>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod alice_run_until {
|
pub mod alice_run_until {
|
||||||
|
|
110
swap/tests/xmr_first_bob_fails_to_act_alice_refunds.rs
Normal file
110
swap/tests/xmr_first_bob_fails_to_act_alice_refunds.rs
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
pub mod harness;
|
||||||
|
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
use swap::bitcoin::TxLock;
|
||||||
|
use swap::env::GetConfig;
|
||||||
|
use swap::monero;
|
||||||
|
use swap::protocol::alice::event_loop::FixedRate;
|
||||||
|
use swap::protocol::CROSS_CURVE_PROOF_SYSTEM;
|
||||||
|
use swap::seed::Seed;
|
||||||
|
use swap::xmr_first_protocol::alice::{publish_xmr_refund, Alice3};
|
||||||
|
use swap::xmr_first_protocol::bob::Bob3;
|
||||||
|
use tempfile::tempdir;
|
||||||
|
use testcontainers::clients::Cli;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn refund() {
|
||||||
|
let cli = Cli::default();
|
||||||
|
|
||||||
|
let env_config = harness::SlowCancelConfig::get_config();
|
||||||
|
|
||||||
|
let (monero, containers) = harness::init_containers(&cli).await;
|
||||||
|
|
||||||
|
let btc_swap_amount = bitcoin::Amount::from_sat(1_000_000);
|
||||||
|
let xmr_swap_amount =
|
||||||
|
monero::Amount::from_monero(btc_swap_amount.as_btc() / FixedRate::RATE).unwrap();
|
||||||
|
|
||||||
|
let alice_starting_balances = harness::StartingBalances {
|
||||||
|
xmr: xmr_swap_amount * 10,
|
||||||
|
btc: bitcoin::Amount::ZERO,
|
||||||
|
};
|
||||||
|
|
||||||
|
let electrs_rpc_port = containers
|
||||||
|
.electrs
|
||||||
|
.get_host_port(harness::electrs::RPC_PORT)
|
||||||
|
.expect("Could not map electrs rpc port");
|
||||||
|
|
||||||
|
let alice_seed = Seed::random().unwrap();
|
||||||
|
let (alice_bitcoin_wallet, alice_monero_wallet) = harness::init_test_wallets(
|
||||||
|
"Alice",
|
||||||
|
containers.bitcoind_url.clone(),
|
||||||
|
&monero,
|
||||||
|
alice_starting_balances.clone(),
|
||||||
|
tempdir().unwrap().path(),
|
||||||
|
electrs_rpc_port,
|
||||||
|
&alice_seed,
|
||||||
|
env_config.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let bob_seed = Seed::random().unwrap();
|
||||||
|
let bob_starting_balances = harness::StartingBalances {
|
||||||
|
xmr: monero::Amount::ZERO,
|
||||||
|
btc: btc_swap_amount * 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (bob_bitcoin_wallet, bob_monero_wallet) = harness::init_test_wallets(
|
||||||
|
"Bob",
|
||||||
|
containers.bitcoind_url,
|
||||||
|
&monero,
|
||||||
|
bob_starting_balances.clone(),
|
||||||
|
tempdir().unwrap().path(),
|
||||||
|
electrs_rpc_port,
|
||||||
|
&bob_seed,
|
||||||
|
env_config,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let a = swap::bitcoin::SecretKey::new_random(&mut OsRng);
|
||||||
|
let b = swap::bitcoin::SecretKey::new_random(&mut OsRng);
|
||||||
|
|
||||||
|
let s_a = monero::Scalar::random(&mut OsRng);
|
||||||
|
let S_a = monero::PublicKey::from_private_key(&monero::PrivateKey { scalar: s_a });
|
||||||
|
|
||||||
|
let s_b = monero::Scalar::random(&mut OsRng);
|
||||||
|
let S_b = monero::PublicKey::from_private_key(&monero::PrivateKey { scalar: s_b });
|
||||||
|
|
||||||
|
let (dleq_proof_s_b, (S_b_bitcoin, S_b_monero)) =
|
||||||
|
CROSS_CURVE_PROOF_SYSTEM.prove(&s_b, &mut OsRng);
|
||||||
|
|
||||||
|
let v_a = monero::PrivateViewKey::new_random(&mut OsRng);
|
||||||
|
let v_b = monero::PrivateViewKey::new_random(&mut OsRng);
|
||||||
|
|
||||||
|
let tx_lock = TxLock::new(&bob_bitcoin_wallet, btc_swap_amount, a.public(), b.public()).await?;
|
||||||
|
|
||||||
|
let alice = Alice3 {
|
||||||
|
xmr_swap_amount,
|
||||||
|
btc_swap_amount,
|
||||||
|
a,
|
||||||
|
B: b.public(),
|
||||||
|
s_a,
|
||||||
|
S_b_monero: monero::PublicKey {
|
||||||
|
point: S_b_monero.compress(),
|
||||||
|
},
|
||||||
|
v_a,
|
||||||
|
redeem_address: alice_bitcoin_wallet.new_address().await?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let bob = Bob3 {
|
||||||
|
xmr_swap_amount,
|
||||||
|
btc_swap_amount,
|
||||||
|
tx_lock,
|
||||||
|
S: S_b,
|
||||||
|
v_b,
|
||||||
|
alice_redeem_address: bob_bitcoin_wallet.new_address().await?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let alice = alice.publish_xmr_lock(&alice_monero_wallet).await.unwrap();
|
||||||
|
|
||||||
|
publish_xmr_refund(&alice_bitcoin_wallet).await.unwrap();
|
||||||
|
}
|
109
swap/tests/xmr_first_happy_path.rs
Normal file
109
swap/tests/xmr_first_happy_path.rs
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
pub mod harness;
|
||||||
|
|
||||||
|
use curve25519_dalek::constants::ED25519_BASEPOINT_POINT;
|
||||||
|
use curve25519_dalek::edwards::EdwardsPoint;
|
||||||
|
use monero_adaptor::alice::Alice0;
|
||||||
|
use monero_adaptor::bob::Bob0;
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
use swap::env::GetConfig;
|
||||||
|
use swap::monero;
|
||||||
|
use swap::monero::{PublicKey, Scalar};
|
||||||
|
use swap::protocol::alice::event_loop::FixedRate;
|
||||||
|
use swap::protocol::CROSS_CURVE_PROOF_SYSTEM;
|
||||||
|
use swap::seed::Seed;
|
||||||
|
use swap::xmr_first_protocol::alice::Alice3;
|
||||||
|
use swap::xmr_first_protocol::bob::Bob3;
|
||||||
|
use swap::xmr_first_protocol::{alice, bob};
|
||||||
|
use tempfile::tempdir;
|
||||||
|
use testcontainers::clients::Cli;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn happy_path() {
|
||||||
|
let cli = Cli::default();
|
||||||
|
|
||||||
|
let env_config = harness::SlowCancelConfig::get_config();
|
||||||
|
|
||||||
|
let (monero, containers) = harness::init_containers(&cli).await;
|
||||||
|
|
||||||
|
let btc_amount = bitcoin::Amount::from_sat(1_000_000);
|
||||||
|
let xmr_amount = monero::Amount::from_monero(btc_amount.as_btc() / FixedRate::RATE).unwrap();
|
||||||
|
|
||||||
|
let alice_starting_balances = harness::StartingBalances {
|
||||||
|
xmr: xmr_amount * 10,
|
||||||
|
btc: bitcoin::Amount::ZERO,
|
||||||
|
};
|
||||||
|
|
||||||
|
let electrs_rpc_port = containers
|
||||||
|
.electrs
|
||||||
|
.get_host_port(harness::electrs::RPC_PORT)
|
||||||
|
.expect("Could not map electrs rpc port");
|
||||||
|
|
||||||
|
let alice_seed = Seed::random().unwrap();
|
||||||
|
let (alice_bitcoin_wallet, alice_monero_wallet) = harness::init_test_wallets(
|
||||||
|
"Alice",
|
||||||
|
containers.bitcoind_url.clone(),
|
||||||
|
&monero,
|
||||||
|
alice_starting_balances.clone(),
|
||||||
|
tempdir().unwrap().path(),
|
||||||
|
electrs_rpc_port,
|
||||||
|
&alice_seed,
|
||||||
|
env_config.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let bob_seed = Seed::random().unwrap();
|
||||||
|
let bob_starting_balances = harness::StartingBalances {
|
||||||
|
xmr: monero::Amount::ZERO,
|
||||||
|
btc: btc_amount * 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (bob_bitcoin_wallet, bob_monero_wallet) = harness::init_test_wallets(
|
||||||
|
"Bob",
|
||||||
|
containers.bitcoind_url,
|
||||||
|
&monero,
|
||||||
|
bob_starting_balances.clone(),
|
||||||
|
tempdir().unwrap().path(),
|
||||||
|
electrs_rpc_port,
|
||||||
|
&bob_seed,
|
||||||
|
env_config,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let a = crate::bitcoin::SecretKey::new_random(rng);
|
||||||
|
let b = crate::bitcoin::SecretKey::new_random(rng);
|
||||||
|
|
||||||
|
let s_a = monero::Scalar::random(rng);
|
||||||
|
let s_b = monero::Scalar::random(rng);
|
||||||
|
|
||||||
|
let (dleq_proof_s_b, (S_b_bitcoin, S_b_monero)) = CROSS_CURVE_PROOF_SYSTEM.prove(&s_b, rng);
|
||||||
|
|
||||||
|
let v_a = monero::PrivateViewKey::new_random(rng);
|
||||||
|
let v_b = monero::PrivateViewKey::new_random(rng);
|
||||||
|
|
||||||
|
let alice = Alice3 {
|
||||||
|
xmr_swap_amount: xmr_amount,
|
||||||
|
btc_swap_amount: btc_amount,
|
||||||
|
a,
|
||||||
|
B: b.public(),
|
||||||
|
s_a,
|
||||||
|
S_b_monero,
|
||||||
|
v_a,
|
||||||
|
};
|
||||||
|
|
||||||
|
let bob = Bob3 {
|
||||||
|
xmr_swap_amount,
|
||||||
|
btc_swap_amount,
|
||||||
|
xmr_lock,
|
||||||
|
v_b,
|
||||||
|
};
|
||||||
|
|
||||||
|
alice.publish_xmr_lock(&alice_monero_wallet).await.unwrap();
|
||||||
|
|
||||||
|
bob.watch_for_lock_xmr(&bob_monero_wallet_wallet)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
alice.publish_btc_redeem(&alice_btc_wallet).await.unwrap();
|
||||||
|
|
||||||
|
bob.publish_xmr_redeem(&alice_monero_wallet).await.unwrap();
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue