mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-08-10 07:20:07 -04:00
WIP: onchain happy path
This commit is contained in:
parent
acb243a0a2
commit
4a99b89df4
14 changed files with 551 additions and 63 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3745,6 +3745,7 @@ dependencies = [
|
||||||
"libp2p-async-await",
|
"libp2p-async-await",
|
||||||
"miniscript",
|
"miniscript",
|
||||||
"monero",
|
"monero",
|
||||||
|
"monero-adaptor",
|
||||||
"monero-harness",
|
"monero-harness",
|
||||||
"monero-rpc",
|
"monero-rpc",
|
||||||
"pem",
|
"pem",
|
||||||
|
|
|
@ -11,8 +11,8 @@ use rand::rngs::OsRng;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use tiny_keccak::{Hasher, Keccak};
|
use tiny_keccak::{Hasher, Keccak};
|
||||||
|
|
||||||
mod alice;
|
pub mod alice;
|
||||||
mod bob;
|
pub mod bob;
|
||||||
|
|
||||||
const RING_SIZE: usize = 11;
|
const RING_SIZE: usize = 11;
|
||||||
const DOMAIN_TAG: &str = "CSLAG_c";
|
const DOMAIN_TAG: &str = "CSLAG_c";
|
||||||
|
@ -135,7 +135,7 @@ pub struct Signature {
|
||||||
|
|
||||||
impl Signature {
|
impl Signature {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn verify(&self, ring: [EdwardsPoint; RING_SIZE], msg: &[u8; 32]) -> Result<bool> {
|
pub fn verify(&self, ring: [EdwardsPoint; RING_SIZE], msg: &[u8; 32]) -> Result<bool> {
|
||||||
let ring_concat = ring
|
let ring_concat = ring
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|pk| pk.compress().as_bytes().to_vec())
|
.flat_map(|pk| pk.compress().as_bytes().to_vec())
|
||||||
|
|
|
@ -31,6 +31,7 @@ libp2p = { version = "0.36", default-features = false, features = ["tcp-tokio",
|
||||||
libp2p-async-await = { git = "https://github.com/comit-network/rust-libp2p-async-await" }
|
libp2p-async-await = { git = "https://github.com/comit-network/rust-libp2p-async-await" }
|
||||||
miniscript = { version = "5", features = ["serde"] }
|
miniscript = { version = "5", features = ["serde"] }
|
||||||
monero = { version = "0.11", features = ["serde_support"] }
|
monero = { version = "0.11", features = ["serde_support"] }
|
||||||
|
monero-adaptor = { path = "../monero-adaptor" }
|
||||||
monero-rpc = { path = "../monero-rpc" }
|
monero-rpc = { path = "../monero-rpc" }
|
||||||
pem = "0.8"
|
pem = "0.8"
|
||||||
prettytable-rs = "0.8"
|
prettytable-rs = "0.8"
|
||||||
|
|
|
@ -21,7 +21,6 @@ pub fn init(level: LevelFilter) -> Result<()> {
|
||||||
builder.init();
|
builder.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing
|
|
||||||
tracing::info!("Initialized tracing with level: {}", level);
|
tracing::info!("Initialized tracing with level: {}", level);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -3,7 +3,7 @@ mod wallet_rpc;
|
||||||
|
|
||||||
pub use ::monero::{Address, Network, PrivateKey, PublicKey};
|
pub use ::monero::{Address, Network, PrivateKey, PublicKey};
|
||||||
pub use curve25519_dalek::scalar::Scalar;
|
pub use curve25519_dalek::scalar::Scalar;
|
||||||
pub use wallet::Wallet;
|
pub use wallet::{TransferRequest, Wallet};
|
||||||
pub use wallet_rpc::{WalletRpc, WalletRpcProcess};
|
pub use wallet_rpc::{WalletRpc, WalletRpcProcess};
|
||||||
|
|
||||||
use crate::bitcoin;
|
use crate::bitcoin;
|
||||||
|
@ -40,8 +40,8 @@ impl PrivateViewKey {
|
||||||
Self(private_key)
|
Self(private_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn public(&self) -> PublicViewKey {
|
pub fn public(&self) -> PrivateViewKey {
|
||||||
PublicViewKey(PublicKey::from_private_key(&self.0))
|
PrivateViewKey(PublicKey::from_private_key(&self.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,14 +59,14 @@ impl From<PrivateViewKey> for PrivateKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PublicViewKey> for PublicKey {
|
impl From<PrivateViewKey> for PublicKey {
|
||||||
fn from(from: PublicViewKey) -> Self {
|
fn from(from: PrivateViewKey) -> Self {
|
||||||
from.0
|
from.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct PublicViewKey(PublicKey);
|
pub struct PrivateViewKey(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,7 +1,5 @@
|
||||||
use crate::env::Config;
|
use crate::env::Config;
|
||||||
use crate::monero::{
|
use crate::monero::{Amount, InsufficientFunds, PrivateViewKey, TransferProof, TxHash};
|
||||||
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;
|
||||||
|
@ -275,14 +273,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: PublicViewKey,
|
pub public_view_key: PrivateViewKey,
|
||||||
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: PublicViewKey,
|
pub public_view_key: PrivateViewKey,
|
||||||
pub transfer_proof: TransferProof,
|
pub transfer_proof: TransferProof,
|
||||||
pub conf_target: u64,
|
pub conf_target: u64,
|
||||||
pub expected: Amount,
|
pub expected: Amount,
|
||||||
|
|
|
@ -1,6 +1,151 @@
|
||||||
|
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 rand::rngs::OsRng;
|
||||||
|
|
||||||
|
// start
|
||||||
|
pub struct Alice3 {
|
||||||
|
pub adaptor_sig: AdaptorSignature,
|
||||||
|
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 alice;
|
||||||
mod bob;
|
mod bob;
|
||||||
|
mod transactions;
|
||||||
|
|
||||||
pub trait Persist {
|
impl Alice3 {
|
||||||
fn persist(&self);
|
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 {
|
||||||
|
s_0_b: monero::Scalar,
|
||||||
|
pub adaptor_sig: AdaptorSignature,
|
||||||
|
tx_lock_id: Txid,
|
||||||
|
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,4 +1,3 @@
|
||||||
use crate::xmr_first_protocol::Persist;
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::task::Poll;
|
use std::task::Poll;
|
||||||
|
|
||||||
|
@ -35,14 +34,6 @@ impl StateMachine {
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
// State::WaitingForT2 => match event {
|
|
||||||
// Event::T2Elapsed => {
|
|
||||||
// self.actions.push_back(Action::SignAndBroadcastBtcPunish);
|
|
||||||
// self.actions.push_back(Action::BroadcastXmrRefund);
|
|
||||||
// self.state = State::Success;
|
|
||||||
// }
|
|
||||||
// _ => {}
|
|
||||||
// },
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,17 +47,10 @@ impl StateMachine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Persist for Event {
|
|
||||||
fn persist(&self) {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub enum State {
|
pub enum State {
|
||||||
WatchingForBtcLock,
|
WatchingForBtcLock,
|
||||||
WatchingForXmrRedeem,
|
WatchingForXmrRedeem,
|
||||||
// WaitingForT2,
|
|
||||||
Punished,
|
Punished,
|
||||||
Success,
|
Success,
|
||||||
Aborted,
|
Aborted,
|
||||||
|
@ -100,12 +84,10 @@ mod tests {
|
||||||
events: Default::default(),
|
events: Default::default(),
|
||||||
};
|
};
|
||||||
state_machine.inject_event(Event::BtcLockSeenInMempool);
|
state_machine.inject_event(Event::BtcLockSeenInMempool);
|
||||||
assert_eq!(state_machine.poll())
|
|
||||||
state_machine.inject_event(Event::XmrRedeemSeenInMempool);
|
state_machine.inject_event(Event::XmrRedeemSeenInMempool);
|
||||||
assert_eq!(state_machine.state, State::Success);
|
assert_eq!(state_machine.state, State::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bob_fails_to_lock_btc() {
|
fn bob_fails_to_lock_btc() {
|
||||||
let mut state_machine = StateMachine {
|
let mut state_machine = StateMachine {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::monero::TransferProof;
|
use crate::monero::TransferProof;
|
||||||
use crate::xmr_first_protocol::Persist;
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
pub struct StateMachine {
|
pub struct StateMachine {
|
||||||
|
@ -50,12 +49,6 @@ impl StateMachine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Persist for Event {
|
|
||||||
fn persist(&self) {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub enum State {
|
pub enum State {
|
||||||
WatchingForXmrLock,
|
WatchingForXmrLock,
|
||||||
|
@ -66,7 +59,6 @@ pub enum State {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
// todo: do we simply wait for confirmations are is mempool sufficient?
|
|
||||||
XmrConfirmed,
|
XmrConfirmed,
|
||||||
// This will contain the s_a allowing bob to build xmr_redeem
|
// This will contain the s_a allowing bob to build xmr_redeem
|
||||||
BtcRedeemSeenInMempool,
|
BtcRedeemSeenInMempool,
|
||||||
|
|
181
swap/src/xmr_first_protocol/transactions.rs
Normal file
181
swap/src/xmr_first_protocol/transactions.rs
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
pub mod btc_lock;
|
||||||
|
pub mod btc_redeem;
|
||||||
|
pub mod xmr_lock;
|
||||||
|
pub mod xmr_refund;
|
||||||
|
|
||||||
|
use crate::bitcoin::wallet::Watchable;
|
||||||
|
use crate::bitcoin::{
|
||||||
|
build_shared_output_descriptor, Address, Amount, PartiallySignedTransaction, PublicKey,
|
||||||
|
Transaction, Txid, Wallet, TX_FEE,
|
||||||
|
};
|
||||||
|
use anyhow::Result;
|
||||||
|
use bdk::bitcoin::{OutPoint, Script, TxIn, TxOut};
|
||||||
|
use bdk::database::BatchDatabase;
|
||||||
|
use bdk::descriptor::Descriptor;
|
||||||
|
use ecdsa_fun::fun::Point;
|
||||||
|
use miniscript::DescriptorTrait;
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
176
swap/src/xmr_first_protocol/transactions/btc_lock.rs
Normal file
176
swap/src/xmr_first_protocol/transactions/btc_lock.rs
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
use crate::bitcoin::wallet::Watchable;
|
||||||
|
use crate::bitcoin::{
|
||||||
|
build_shared_output_descriptor, Address, Amount, PartiallySignedTransaction, PublicKey,
|
||||||
|
Transaction, Txid, Wallet, TX_FEE,
|
||||||
|
};
|
||||||
|
use anyhow::Result;
|
||||||
|
use bdk::bitcoin::{OutPoint, Script, TxIn, TxOut};
|
||||||
|
use bdk::database::BatchDatabase;
|
||||||
|
use bdk::descriptor::Descriptor;
|
||||||
|
use ecdsa_fun::fun::Point;
|
||||||
|
use miniscript::DescriptorTrait;
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
0
swap/src/xmr_first_protocol/transactions/btc_redeem.rs
Normal file
0
swap/src/xmr_first_protocol/transactions/btc_redeem.rs
Normal file
7
swap/src/xmr_first_protocol/transactions/xmr_lock.rs
Normal file
7
swap/src/xmr_first_protocol/transactions/xmr_lock.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
use crate::monero::{PrivateViewKey, PublicKey, TransferProof};
|
||||||
|
|
||||||
|
pub struct XmrLock {
|
||||||
|
pub public_spend_key: PublicKey,
|
||||||
|
pub public_view_key: PrivateViewKey,
|
||||||
|
pub transfer_proof: TransferProof,
|
||||||
|
}
|
6
swap/src/xmr_first_protocol/transactions/xmr_refund.rs
Normal file
6
swap/src/xmr_first_protocol/transactions/xmr_refund.rs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
use crate::monero::PrivateViewKey;
|
||||||
|
use monero_adaptor::AdaptorSignature;
|
||||||
|
|
||||||
|
pub struct XmrRedeem {
|
||||||
|
adaptor: AdaptorSignature,
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue