diff --git a/daemon/src/maker.rs b/daemon/src/maker.rs index eb7b3c6..b3a14db 100644 --- a/daemon/src/maker.rs +++ b/daemon/src/maker.rs @@ -19,6 +19,7 @@ mod model; mod routes_maker; mod seed; mod send_wire_message_actor; +mod setup_contract_actor; mod to_sse_event; mod wire; diff --git a/daemon/src/maker_cfd_actor.rs b/daemon/src/maker_cfd_actor.rs index 24cb1ba..622f0ba 100644 --- a/daemon/src/maker_cfd_actor.rs +++ b/daemon/src/maker_cfd_actor.rs @@ -1,18 +1,14 @@ -use std::collections::HashMap; use std::time::SystemTime; use crate::db::{insert_cfd, insert_order, load_all_cfds, load_order_by_id, Origin}; use crate::model::cfd::{Cfd, CfdState, CfdStateCommon, FinalizedCfd, Order, OrderId}; use crate::model::{TakerId, Usd}; -use crate::wire::{Msg0, Msg1, SetupMsg}; -use crate::{maker_cfd_actor, maker_inc_connections_actor}; -use bdk::bitcoin::secp256k1::{schnorrsig, SecretKey}; -use bdk::bitcoin::{self, Amount}; +use crate::wire::SetupMsg; +use crate::{maker_cfd_actor, maker_inc_connections_actor, setup_contract_actor}; +use bdk::bitcoin::secp256k1::schnorrsig; +use bdk::bitcoin::{self}; use bdk::database::BatchDatabase; -use cfd_protocol::{ - commit_descriptor, create_cfd_transactions, lock_descriptor, PartyParams, PunishParams, - WalletExt, -}; +use cfd_protocol::WalletExt; use futures::Future; use tokio::sync::{mpsc, watch}; @@ -161,10 +157,7 @@ where }) .unwrap(); } - maker_cfd_actor::Command::StartContractSetup { - taker_id, - order_id: _order_id, - } => { + maker_cfd_actor::Command::StartContractSetup { taker_id, order_id } => { // Kick-off the CFD protocol let (sk, pk) = crate::keypair::new(&mut rand::thread_rng()); @@ -173,7 +166,9 @@ where .build_party_params(bitcoin::Amount::ZERO, pk) .unwrap(); - let (actor, inbox) = setup_contract( + let cfd = load_order_by_id(order_id, &mut conn).await.unwrap(); + + let (actor, inbox) = setup_contract_actor::new( { let inbox = takers.clone(); move |msg| { @@ -187,9 +182,10 @@ where .unwrap() } }, - maker_params, + setup_contract_actor::OwnParams::Maker(maker_params), sk, oracle_pk, + cfd, ); tokio::spawn({ @@ -221,91 +217,3 @@ where (actor, sender) } - -/// Given an initial set of parameters, sets up the CFD contract with the taker. -/// -/// Returns the [`FinalizedCfd`] which contains the lock transaction, ready to be signed and sent to -/// the taker. Signing of the lock transaction is not included in this function because we want the -/// actor above to own the wallet. -fn setup_contract( - send_to_taker: impl Fn(SetupMsg), - maker: PartyParams, - sk: SecretKey, - oracle_pk: schnorrsig::PublicKey, -) -> ( - impl Future, - mpsc::UnboundedSender, -) { - let (sender, mut receiver) = mpsc::unbounded_channel::(); - - let actor = async move { - let (rev_sk, rev_pk) = crate::keypair::new(&mut rand::thread_rng()); - let (publish_sk, publish_pk) = crate::keypair::new(&mut rand::thread_rng()); - - let maker_punish = PunishParams { - revocation_pk: rev_pk, - publish_pk, - }; - send_to_taker(SetupMsg::Msg0(Msg0::from((maker.clone(), maker_punish)))); - - let msg0 = receiver.recv().await.unwrap().try_into_msg0().unwrap(); - let (taker, taker_punish) = msg0.into(); - - let maker_cfd_txs = create_cfd_transactions( - (maker.clone(), maker_punish), - (taker.clone(), taker_punish), - oracle_pk, - 0, // TODO: Calculate refund timelock based on CFD term - vec![], - sk, - ) - .unwrap(); - - send_to_taker(SetupMsg::Msg1(Msg1::from(maker_cfd_txs.clone()))); - let msg1 = receiver.recv().await.unwrap().try_into_msg1().unwrap(); - - let _lock_desc = lock_descriptor(taker.identity_pk, taker.identity_pk); - // let lock_amount = maker_lock_amount + taker_lock_amount; - let _commit_desc = commit_descriptor( - ( - taker.identity_pk, - taker_punish.revocation_pk, - taker_punish.publish_pk, - ), - (taker.identity_pk, rev_pk, publish_pk), - ); - let commit_tx = maker_cfd_txs.commit.0; - let _commit_amount = Amount::from_sat(commit_tx.output[0].value); - - // TODO: Verify all signatures from the taker here - - let lock_tx = maker_cfd_txs.lock; - let refund_tx = maker_cfd_txs.refund.0; - - let mut cet_by_id = maker_cfd_txs - .cets - .into_iter() - .map(|(tx, _, msg, _)| (tx.txid(), (tx, msg))) - .collect::>(); - - FinalizedCfd { - identity: sk, - revocation: rev_sk, - publish: publish_sk, - lock: lock_tx, - commit: (commit_tx, *msg1.commit), - cets: msg1 - .cets - .into_iter() - .map(|(txid, sig)| { - let (cet, msg) = cet_by_id.remove(&txid).expect("unknown CET"); - - (cet, *sig, msg) - }) - .collect::>(), - refund: (refund_tx, msg1.refund), - } - }; - - (actor, sender) -} diff --git a/daemon/src/setup_contract_actor.rs b/daemon/src/setup_contract_actor.rs new file mode 100644 index 0000000..c1dd6bf --- /dev/null +++ b/daemon/src/setup_contract_actor.rs @@ -0,0 +1,310 @@ +use crate::model::cfd::{AsBlocks, FinalizedCfd, Order}; +use crate::wire::{AdaptorSignature, Msg0, Msg1, SetupMsg}; +use anyhow::{Context, Result}; +use bdk::bitcoin::secp256k1::{schnorrsig, SecretKey, Signature, SECP256K1}; +use bdk::bitcoin::{Amount, PublicKey, Transaction, Txid}; +use bdk::descriptor::Descriptor; +use cfd_protocol::{ + commit_descriptor, compute_signature_point, create_cfd_transactions, lock_descriptor, + spending_tx_sighash, EcdsaAdaptorSignature, PartyParams, PunishParams, +}; +use futures::Future; +use std::collections::HashMap; +use tokio::sync::mpsc; + +/// A factor to be added to the CFD order term for calculating the refund timelock. +/// +/// The refund timelock is important in case the oracle disappears or never publishes a signature. +/// Ideally, both users collaboratively settle in the refund scenario. This factor is important if +/// the users do not settle collaboratively. +/// `1.5` times the term as defined in CFD order should be safe in the extreme case where a user +/// publishes the commit transaction right after the contract was initialized. In this case, the +/// oracle still has `1.0 * cfdorder.term` time to attest and no one can publish the refund +/// transaction. +/// The downside is that if the oracle disappears: the users would only notice at the end +/// of the cfd term. In this case the users has to wait for another `1.5` times of the +/// term to get his funds back. +pub const REFUND_THRESHOLD: f32 = 1.5; + +/// Given an initial set of parameters, sets up the CFD contract with the other party. +/// Passing OwnParams identifies whether caller is the maker or the taker. +/// +/// Returns the [`FinalizedCfd`] which contains the lock transaction, ready to be signed and sent to +/// the other party. Signing of the lock transaction is not included in this function because we +/// want the Cfd actor to own the wallet. +pub fn new( + send_to_other: impl Fn(SetupMsg), + own_params: OwnParams, + sk: SecretKey, + oracle_pk: schnorrsig::PublicKey, + order: Order, +) -> ( + impl Future, + mpsc::UnboundedSender, +) { + let (sender, mut receiver) = mpsc::unbounded_channel::(); + + let actor = async move { + let (rev_sk, rev_pk) = crate::keypair::new(&mut rand::thread_rng()); + let (publish_sk, publish_pk) = crate::keypair::new(&mut rand::thread_rng()); + + let params = { + let (own, own_role) = match own_params.clone() { + OwnParams::Maker(maker) => (maker, Role::Maker), + OwnParams::Taker(taker) => (taker, Role::Taker), + }; + + let own_punish = PunishParams { + revocation_pk: rev_pk, + publish_pk, + }; + send_to_other(SetupMsg::Msg0(Msg0::from((own.clone(), own_punish)))); + + let msg0 = receiver.recv().await.unwrap().try_into_msg0().unwrap(); + let (other, other_punish) = msg0.into(); + + AllParams::new(own, own_punish, other, other_punish, own_role) + }; + + let own_cfd_txs = create_cfd_transactions( + (params.maker().clone(), *params.maker_punish()), + (params.taker().clone(), *params.taker_punish()), + oracle_pk, + order.term.mul_f32(REFUND_THRESHOLD).as_blocks().ceil() as u32, + vec![], + sk, + ) + .unwrap(); + + send_to_other(SetupMsg::Msg1(Msg1::from(own_cfd_txs.clone()))); + let msg1 = receiver.recv().await.unwrap().try_into_msg1().unwrap(); + + let lock_desc = lock_descriptor(params.maker().identity_pk, params.taker().identity_pk); + + let lock_amount = params.maker().lock_amount + params.taker().lock_amount; + + let commit_desc = commit_descriptor( + ( + params.maker().identity_pk, + params.maker_punish().revocation_pk, + params.maker_punish().publish_pk, + ), + ( + params.taker().identity_pk, + params.taker_punish().revocation_pk, + params.taker_punish().publish_pk, + ), + ); + + let own_cets = own_cfd_txs.cets; + let commit_tx = own_cfd_txs.commit.0.clone(); + + let commit_amount = Amount::from_sat(commit_tx.output[0].value); + + verify_adaptor_signature( + &commit_tx, + &lock_desc, + lock_amount, + &msg1.commit, + ¶ms.own_punish.publish_pk, + ¶ms.other.identity_pk, + ) + .unwrap(); + + verify_cets( + &oracle_pk, + ¶ms.other, + &own_cets, + &msg1.cets, + &commit_desc, + commit_amount, + ) + .unwrap(); + + let lock_tx = own_cfd_txs.lock; + let refund_tx = own_cfd_txs.refund.0; + + verify_signature( + &refund_tx, + &commit_desc, + commit_amount, + &msg1.refund, + ¶ms.other.identity_pk, + ) + .unwrap(); + + let mut cet_by_id = own_cets + .into_iter() + .map(|(tx, _, msg, _)| (tx.txid(), (tx, msg))) + .collect::>(); + + FinalizedCfd { + identity: sk, + revocation: rev_sk, + publish: publish_sk, + lock: lock_tx, + commit: (commit_tx, *msg1.commit), + cets: msg1 + .cets + .into_iter() + .map(|(txid, sig)| { + let (cet, msg) = cet_by_id.remove(&txid).expect("unknown CET"); + + (cet, *sig, msg) + }) + .collect::>(), + refund: (refund_tx, msg1.refund), + } + }; + + (actor, sender) +} + +#[derive(Clone)] +#[allow(dead_code)] +pub enum OwnParams { + Maker(PartyParams), + Taker(PartyParams), +} + +/// Role of the actor's owner in the upcoming contract +enum Role { + Maker, + Taker, +} + +/// A convenience struct for storing PartyParams and PunishParams of both +/// parties and the role of the caller. +struct AllParams { + pub own: PartyParams, + pub own_punish: PunishParams, + pub other: PartyParams, + pub other_punish: PunishParams, + pub own_role: Role, +} + +impl AllParams { + fn new( + own: PartyParams, + own_punish: PunishParams, + other: PartyParams, + other_punish: PunishParams, + own_role: Role, + ) -> Self { + Self { + own, + own_punish, + other, + other_punish, + own_role, + } + } + + fn maker(&self) -> &PartyParams { + match self.own_role { + Role::Maker => &self.own, + Role::Taker => &self.other, + } + } + + fn taker(&self) -> &PartyParams { + match self.own_role { + Role::Maker => &self.other, + Role::Taker => &self.own, + } + } + + fn maker_punish(&self) -> &PunishParams { + match self.own_role { + Role::Maker => &self.own_punish, + Role::Taker => &self.other_punish, + } + } + fn taker_punish(&self) -> &PunishParams { + match self.own_role { + Role::Maker => &self.other_punish, + Role::Taker => &self.own_punish, + } + } +} + +fn verify_cets( + oracle_pk: &schnorrsig::PublicKey, + other: &PartyParams, + own_cets: &[( + Transaction, + EcdsaAdaptorSignature, + Vec, + schnorrsig::PublicKey, + )], + cets: &[(Txid, AdaptorSignature)], + commit_desc: &Descriptor, + commit_amount: Amount, +) -> Result<()> { + for (tx, _, msg, nonce_pk) in own_cets.iter() { + let other_encsig = cets + .iter() + .find_map(|(txid, encsig)| (txid == &tx.txid()).then(|| encsig)) + .expect("one encsig per cet, per party"); + + verify_cet_encsig( + tx, + other_encsig, + msg, + &other.identity_pk, + (oracle_pk, nonce_pk), + commit_desc, + commit_amount, + ) + .expect("valid maker cet encsig") + } + Ok(()) +} + +fn verify_adaptor_signature( + tx: &Transaction, + spent_descriptor: &Descriptor, + spent_amount: Amount, + encsig: &AdaptorSignature, + encryption_point: &PublicKey, + pk: &PublicKey, +) -> Result<()> { + let sighash = spending_tx_sighash(tx, spent_descriptor, spent_amount); + + encsig + .verify(SECP256K1, &sighash, &pk.key, &encryption_point.key) + .context("failed to verify encsig spend tx") +} + +fn verify_signature( + tx: &Transaction, + spent_descriptor: &Descriptor, + spent_amount: Amount, + sig: &Signature, + pk: &PublicKey, +) -> Result<()> { + let sighash = spending_tx_sighash(tx, spent_descriptor, spent_amount); + SECP256K1.verify(&sighash, sig, &pk.key)?; + Ok(()) +} + +fn verify_cet_encsig( + tx: &Transaction, + encsig: &AdaptorSignature, + msg: &[u8], + pk: &PublicKey, + (oracle_pk, nonce_pk): (&schnorrsig::PublicKey, &schnorrsig::PublicKey), + spent_descriptor: &Descriptor, + spent_amount: Amount, +) -> Result<()> { + let sig_point = compute_signature_point(oracle_pk, nonce_pk, msg) + .context("could not calculate signature point")?; + verify_adaptor_signature( + tx, + spent_descriptor, + spent_amount, + encsig, + &PublicKey::new(sig_point), + pk, + ) +} diff --git a/daemon/src/taker.rs b/daemon/src/taker.rs index 43295c2..9b4b083 100644 --- a/daemon/src/taker.rs +++ b/daemon/src/taker.rs @@ -20,6 +20,7 @@ mod model; mod routes_taker; mod seed; mod send_wire_message_actor; +mod setup_contract_actor; mod taker_cfd_actor; mod taker_inc_message_actor; mod to_sse_event; diff --git a/daemon/src/taker_cfd_actor.rs b/daemon/src/taker_cfd_actor.rs index 0714bb9..b47315a 100644 --- a/daemon/src/taker_cfd_actor.rs +++ b/daemon/src/taker_cfd_actor.rs @@ -2,39 +2,19 @@ use crate::db::{ insert_cfd, insert_new_cfd_state_by_order_id, insert_order, load_all_cfds, load_order_by_id, Origin, }; -use crate::model::cfd::{AsBlocks, Cfd, CfdState, CfdStateCommon, FinalizedCfd, Order, OrderId}; +use crate::model::cfd::{Cfd, CfdState, CfdStateCommon, FinalizedCfd, Order, OrderId}; use crate::model::Usd; -use crate::wire; -use crate::wire::{AdaptorSignature, Msg0, Msg1, SetupMsg}; -use anyhow::{Context, Result}; -use bdk::bitcoin::secp256k1::{schnorrsig, SecretKey, Signature, SECP256K1}; -use bdk::bitcoin::{self, Amount, PublicKey, Transaction, Txid}; +use crate::wire::SetupMsg; +use crate::{setup_contract_actor, wire}; +use bdk::bitcoin::secp256k1::schnorrsig; +use bdk::bitcoin::{self}; use bdk::database::BatchDatabase; -use bdk::descriptor::Descriptor; -use cfd_protocol::{ - commit_descriptor, compute_signature_point, create_cfd_transactions, lock_descriptor, - spending_tx_sighash, EcdsaAdaptorSignature, PartyParams, PunishParams, WalletExt, -}; +use cfd_protocol::WalletExt; use core::panic; use futures::Future; -use std::collections::HashMap; use std::time::SystemTime; use tokio::sync::{mpsc, watch}; -/// A factor to be added to the CFD order term for calculating the refund timelock. -/// -/// The refund timelock is important in case the oracle disappears or never publishes a signature. -/// Ideally, both users collaboratively settle in the refund scenario. This factor is important if -/// the users do not settle collaboratively. -/// `1.5` times the term as defined in CFD order should be safe in the extreme case where a user -/// publishes the commit transaction right after the contract was initialized. In this case, the -/// oracle still has `1.0 * cfdorder.term` time to attest and no one can publish the refund -/// transaction. -/// The downside is that if the oracle disappears: the users would only notice at the end -/// of the cfd term. In this case the users has to wait for another `1.5` times of the -/// term to get his funds back. -pub const REFUND_THRESHOLD: f32 = 1.5; - #[derive(Debug)] #[allow(clippy::large_enum_variant)] pub enum Command { @@ -135,12 +115,12 @@ where let cfd = load_order_by_id(order_id, &mut conn).await.unwrap(); - let (actor, inbox) = setup_contract( + let (actor, inbox) = setup_contract_actor::new( { let inbox = out_msg_maker_inbox.clone(); move |msg| inbox.send(wire::TakerToMaker::Protocol(msg)).unwrap() }, - taker_params, + setup_contract_actor::OwnParams::Taker(taker_params), sk, oracle_pk, cfd, @@ -175,204 +155,3 @@ where (actor, sender) } - -/// Given an initial set of parameters, sets up the CFD contract with the maker. -/// -/// Returns the [`FinalizedCfd`] which contains the lock transaction, ready to be signed and sent to -/// the maker. Signing of the lock transaction is not included in this function because we want the -/// actor above to own the wallet. -fn setup_contract( - send_to_maker: impl Fn(SetupMsg), - taker: PartyParams, - sk: SecretKey, - oracle_pk: schnorrsig::PublicKey, - order: Order, -) -> ( - impl Future, - mpsc::UnboundedSender, -) { - let (sender, mut receiver) = mpsc::unbounded_channel::(); - - let actor = async move { - let (rev_sk, rev_pk) = crate::keypair::new(&mut rand::thread_rng()); - let (publish_sk, publish_pk) = crate::keypair::new(&mut rand::thread_rng()); - - let taker_punish = PunishParams { - revocation_pk: rev_pk, - publish_pk, - }; - send_to_maker(SetupMsg::Msg0(Msg0::from((taker.clone(), taker_punish)))); - - let msg0 = receiver.recv().await.unwrap().try_into_msg0().unwrap(); - let (maker, maker_punish) = msg0.into(); - - let taker_cfd_txs = create_cfd_transactions( - (maker.clone(), maker_punish), - (taker.clone(), taker_punish), - oracle_pk, - order.term.mul_f32(REFUND_THRESHOLD).as_blocks().ceil() as u32, - vec![], - sk, - ) - .unwrap(); - - send_to_maker(SetupMsg::Msg1(Msg1::from(taker_cfd_txs.clone()))); - let msg1 = receiver.recv().await.unwrap().try_into_msg1().unwrap(); - - let lock_desc = lock_descriptor(maker.identity_pk, taker.identity_pk); - - let lock_amount = maker.lock_amount + taker.lock_amount; - - let commit_desc = commit_descriptor( - ( - maker.identity_pk, - maker_punish.revocation_pk, - maker_punish.publish_pk, - ), - (taker.identity_pk, rev_pk, publish_pk), - ); - - let taker_cets = taker_cfd_txs.cets; - let commit_tx = taker_cfd_txs.commit.0.clone(); - - let commit_amount = Amount::from_sat(commit_tx.output[0].value); - - verify_adaptor_signature( - &commit_tx, - &lock_desc, - lock_amount, - &msg1.commit, - &taker_punish.publish_pk, - &maker.identity_pk, - ) - .unwrap(); - - verify_cets( - &oracle_pk, - &maker, - &taker_cets, - &msg1.cets, - &commit_desc, - commit_amount, - ) - .unwrap(); - - let lock_tx = taker_cfd_txs.lock; - let refund_tx = taker_cfd_txs.refund.0; - - verify_signature( - &refund_tx, - &commit_desc, - commit_amount, - &msg1.refund, - &maker.identity_pk, - ) - .unwrap(); - - let mut cet_by_id = taker_cets - .into_iter() - .map(|(tx, _, msg, _)| (tx.txid(), (tx, msg))) - .collect::>(); - - FinalizedCfd { - identity: sk, - revocation: rev_sk, - publish: publish_sk, - lock: lock_tx, - commit: (commit_tx, *msg1.commit), - cets: msg1 - .cets - .into_iter() - .map(|(txid, sig)| { - let (cet, msg) = cet_by_id.remove(&txid).expect("unknown CET"); - - (cet, *sig, msg) - }) - .collect::>(), - refund: (refund_tx, msg1.refund), - } - }; - - (actor, sender) -} - -fn verify_cets( - oracle_pk: &schnorrsig::PublicKey, - maker: &PartyParams, - taker_cets: &[( - Transaction, - EcdsaAdaptorSignature, - Vec, - schnorrsig::PublicKey, - )], - cets: &[(Txid, AdaptorSignature)], - commit_desc: &Descriptor, - commit_amount: Amount, -) -> Result<()> { - for (tx, _, msg, nonce_pk) in taker_cets.iter() { - let maker_encsig = cets - .iter() - .find_map(|(txid, encsig)| (txid == &tx.txid()).then(|| encsig)) - .expect("one encsig per cet, per party"); - - verify_cet_encsig( - tx, - maker_encsig, - msg, - &maker.identity_pk, - (oracle_pk, nonce_pk), - commit_desc, - commit_amount, - ) - .expect("valid maker cet encsig") - } - Ok(()) -} - -fn verify_adaptor_signature( - tx: &Transaction, - spent_descriptor: &Descriptor, - spent_amount: Amount, - encsig: &AdaptorSignature, - encryption_point: &PublicKey, - pk: &PublicKey, -) -> Result<()> { - let sighash = spending_tx_sighash(tx, spent_descriptor, spent_amount); - - encsig - .verify(SECP256K1, &sighash, &pk.key, &encryption_point.key) - .context("failed to verify encsig spend tx") -} - -fn verify_signature( - tx: &Transaction, - spent_descriptor: &Descriptor, - spent_amount: Amount, - sig: &Signature, - pk: &PublicKey, -) -> Result<()> { - let sighash = spending_tx_sighash(tx, spent_descriptor, spent_amount); - SECP256K1.verify(&sighash, sig, &pk.key)?; - Ok(()) -} - -fn verify_cet_encsig( - tx: &Transaction, - encsig: &AdaptorSignature, - msg: &[u8], - pk: &PublicKey, - (oracle_pk, nonce_pk): (&schnorrsig::PublicKey, &schnorrsig::PublicKey), - spent_descriptor: &Descriptor, - spent_amount: Amount, -) -> Result<()> { - let sig_point = compute_signature_point(oracle_pk, nonce_pk, msg) - .context("could not calculate signature point")?; - verify_adaptor_signature( - tx, - spent_descriptor, - spent_amount, - encsig, - &PublicKey::new(sig_point), - pk, - ) -}