WIP: onchain happy path

This commit is contained in:
rishflab 2021-05-10 14:58:07 +10:00
parent acb243a0a2
commit 4a99b89df4
14 changed files with 551 additions and 63 deletions

41
Cargo.lock generated
View File

@ -3735,26 +3735,27 @@ dependencies = [
"conquer-once",
"curve25519-dalek",
"dialoguer",
"directories-next",
"ecdsa_fun",
"futures",
"get-port",
"hyper 0.14.5",
"itertools 0.10.0",
"libp2p",
"libp2p-async-await",
"miniscript",
"monero",
"monero-harness",
"monero-rpc",
"pem",
"port_check",
"prettytable-rs",
"rand 0.7.3",
"rand_chacha 0.2.2",
"reqwest",
"rust_decimal",
"serde",
"directories-next",
"ecdsa_fun",
"futures",
"get-port",
"hyper 0.14.5",
"itertools 0.10.0",
"libp2p",
"libp2p-async-await",
"miniscript",
"monero",
"monero-adaptor",
"monero-harness",
"monero-rpc",
"pem",
"port_check",
"prettytable-rs",
"rand 0.7.3",
"rand_chacha 0.2.2",
"reqwest",
"rust_decimal",
"serde",
"serde_cbor",
"serde_json",
"sha2 0.9.3",

View File

@ -11,8 +11,8 @@ use rand::rngs::OsRng;
use std::convert::TryInto;
use tiny_keccak::{Hasher, Keccak};
mod alice;
mod bob;
pub mod alice;
pub mod bob;
const RING_SIZE: usize = 11;
const DOMAIN_TAG: &str = "CSLAG_c";
@ -135,7 +135,7 @@ pub struct Signature {
impl Signature {
#[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
.iter()
.flat_map(|pk| pk.compress().as_bytes().to_vec())

View File

@ -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" }
miniscript = { version = "5", features = ["serde"] }
monero = { version = "0.11", features = ["serde_support"] }
monero-adaptor = { path = "../monero-adaptor" }
monero-rpc = { path = "../monero-rpc" }
pem = "0.8"
prettytable-rs = "0.8"

View File

@ -21,7 +21,6 @@ pub fn init(level: LevelFilter) -> Result<()> {
builder.init();
}
tracing
tracing::info!("Initialized tracing with level: {}", level);
Ok(())

View File

@ -3,7 +3,7 @@ mod wallet_rpc;
pub use ::monero::{Address, Network, PrivateKey, PublicKey};
pub use curve25519_dalek::scalar::Scalar;
pub use wallet::Wallet;
pub use wallet::{TransferRequest, Wallet};
pub use wallet_rpc::{WalletRpc, WalletRpcProcess};
use crate::bitcoin;
@ -40,8 +40,8 @@ impl PrivateViewKey {
Self(private_key)
}
pub fn public(&self) -> PublicViewKey {
PublicViewKey(PublicKey::from_private_key(&self.0))
pub fn public(&self) -> PrivateViewKey {
PrivateViewKey(PublicKey::from_private_key(&self.0))
}
}
@ -59,14 +59,14 @@ impl From<PrivateViewKey> for PrivateKey {
}
}
impl From<PublicViewKey> for PublicKey {
fn from(from: PublicViewKey) -> Self {
impl From<PrivateViewKey> for PublicKey {
fn from(from: PrivateViewKey) -> Self {
from.0
}
}
#[derive(Clone, Copy, Debug)]
pub struct PublicViewKey(PublicKey);
pub struct PrivateViewKey(PublicKey);
#[derive(Debug, Copy, Clone, Deserialize, Serialize, PartialEq, PartialOrd)]
pub struct Amount(u64);

View File

@ -1,7 +1,5 @@
use crate::env::Config;
use crate::monero::{
Amount, InsufficientFunds, PrivateViewKey, PublicViewKey, TransferProof, TxHash,
};
use crate::monero::{Amount, InsufficientFunds, PrivateViewKey, TransferProof, TxHash};
use ::monero::{Address, Network, PrivateKey, PublicKey};
use anyhow::{Context, Result};
use monero_rpc::wallet;
@ -275,14 +273,14 @@ impl Wallet {
#[derive(Debug)]
pub struct TransferRequest {
pub public_spend_key: PublicKey,
pub public_view_key: PublicViewKey,
pub public_view_key: PrivateViewKey,
pub amount: Amount,
}
#[derive(Debug)]
pub struct WatchRequest {
pub public_spend_key: PublicKey,
pub public_view_key: PublicViewKey,
pub public_view_key: PrivateViewKey,
pub transfer_proof: TransferProof,
pub conf_target: u64,
pub expected: Amount,

View File

@ -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 bob;
mod transactions;
pub trait Persist {
fn persist(&self);
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 {
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);
}
}

View File

@ -1,4 +1,3 @@
use crate::xmr_first_protocol::Persist;
use std::collections::VecDeque;
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)]
pub enum State {
WatchingForBtcLock,
WatchingForXmrRedeem,
// WaitingForT2,
Punished,
Success,
Aborted,
@ -100,12 +84,10 @@ mod tests {
events: Default::default(),
};
state_machine.inject_event(Event::BtcLockSeenInMempool);
assert_eq!(state_machine.poll())
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 {

View File

@ -1,5 +1,4 @@
use crate::monero::TransferProof;
use crate::xmr_first_protocol::Persist;
use std::collections::VecDeque;
pub struct StateMachine {
@ -50,12 +49,6 @@ impl StateMachine {
}
}
impl Persist for Event {
fn persist(&self) {
todo!()
}
}
#[derive(PartialEq, Debug)]
pub enum State {
WatchingForXmrLock,
@ -66,7 +59,6 @@ pub enum State {
}
pub enum Event {
// todo: do we simply wait for confirmations are is mempool sufficient?
XmrConfirmed,
// This will contain the s_a allowing bob to build xmr_redeem
BtcRedeemSeenInMempool,

View 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()
}
}

View 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()
}
}

View 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,
}

View File

@ -0,0 +1,6 @@
use crate::monero::PrivateViewKey;
use monero_adaptor::AdaptorSignature;
pub struct XmrRedeem {
adaptor: AdaptorSignature,
}