Browse Source

Merge pull request #57 from comit-network/refactor-contract-setup

Add contract setup and signature verification on the maker side
no-contract-setup-message
Mariusz 3 years ago
committed by GitHub
parent
commit
904d90c0ad
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      daemon/src/maker.rs
  2. 114
      daemon/src/maker_cfd_actor.rs
  3. 310
      daemon/src/setup_contract_actor.rs
  4. 1
      daemon/src/taker.rs
  5. 237
      daemon/src/taker_cfd_actor.rs

1
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;

114
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<Output = FinalizedCfd>,
mpsc::UnboundedSender<SetupMsg>,
) {
let (sender, mut receiver) = mpsc::unbounded_channel::<SetupMsg>();
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::<HashMap<_, _>>();
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::<Vec<_>>(),
refund: (refund_tx, msg1.refund),
}
};
(actor, sender)
}

310
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<Output = FinalizedCfd>,
mpsc::UnboundedSender<SetupMsg>,
) {
let (sender, mut receiver) = mpsc::unbounded_channel::<SetupMsg>();
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,
&params.own_punish.publish_pk,
&params.other.identity_pk,
)
.unwrap();
verify_cets(
&oracle_pk,
&params.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,
&params.other.identity_pk,
)
.unwrap();
let mut cet_by_id = own_cets
.into_iter()
.map(|(tx, _, msg, _)| (tx.txid(), (tx, msg)))
.collect::<HashMap<_, _>>();
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::<Vec<_>>(),
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<u8>,
schnorrsig::PublicKey,
)],
cets: &[(Txid, AdaptorSignature)],
commit_desc: &Descriptor<PublicKey>,
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<PublicKey>,
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<PublicKey>,
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<PublicKey>,
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,
)
}

1
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;

237
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<Output = FinalizedCfd>,
mpsc::UnboundedSender<SetupMsg>,
) {
let (sender, mut receiver) = mpsc::unbounded_channel::<SetupMsg>();
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::<HashMap<_, _>>();
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::<Vec<_>>(),
refund: (refund_tx, msg1.refund),
}
};
(actor, sender)
}
fn verify_cets(
oracle_pk: &schnorrsig::PublicKey,
maker: &PartyParams,
taker_cets: &[(
Transaction,
EcdsaAdaptorSignature,
Vec<u8>,
schnorrsig::PublicKey,
)],
cets: &[(Txid, AdaptorSignature)],
commit_desc: &Descriptor<PublicKey>,
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<PublicKey>,
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<PublicKey>,
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<PublicKey>,
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,
)
}

Loading…
Cancel
Save