You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
364 lines
11 KiB
364 lines
11 KiB
use crate::model::cfd::{Cfd, Dlc, Role};
|
|
use crate::wallet::Wallet;
|
|
use crate::wire::{Msg0, Msg1, Msg2, SetupMsg};
|
|
use crate::{model, payout_curve};
|
|
use anyhow::{Context, Result};
|
|
use bdk::bitcoin::secp256k1::{schnorrsig, Signature, SECP256K1};
|
|
use bdk::bitcoin::{Amount, PublicKey, Transaction};
|
|
use bdk::descriptor::Descriptor;
|
|
use cfd_protocol::secp256k1_zkp::EcdsaAdaptorSignature;
|
|
use cfd_protocol::{
|
|
commit_descriptor, compute_adaptor_pk, create_cfd_transactions, interval, lock_descriptor,
|
|
spending_tx_sighash, Announcement, PartyParams, PunishParams,
|
|
};
|
|
use futures::stream::FusedStream;
|
|
use futures::{Sink, SinkExt, StreamExt};
|
|
use std::collections::HashMap;
|
|
use std::iter::FromIterator;
|
|
use std::ops::RangeInclusive;
|
|
|
|
/// Given an initial set of parameters, sets up the CFD contract with
|
|
/// the other party.
|
|
///
|
|
/// TODO: Replace `nonce_pks` argument with set of
|
|
/// `daemon::oracle::Announcement`, which can be mapped into
|
|
/// `cfd_protocol::Announcement`.
|
|
pub async fn new(
|
|
mut sink: impl Sink<SetupMsg, Error = anyhow::Error> + Unpin,
|
|
mut stream: impl FusedStream<Item = SetupMsg> + Unpin,
|
|
(oracle_pk, nonce_pks): (schnorrsig::PublicKey, Vec<schnorrsig::PublicKey>),
|
|
cfd: Cfd,
|
|
wallet: Wallet,
|
|
role: Role,
|
|
) -> Result<Dlc> {
|
|
let (sk, pk) = crate::keypair::new(&mut rand::thread_rng());
|
|
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 margin = cfd.margin().context("Failed to calculate margin")?;
|
|
let own_params = wallet
|
|
.build_party_params(margin, pk)
|
|
.await
|
|
.context("Failed to build party params")?;
|
|
|
|
let own_punish = PunishParams {
|
|
revocation_pk: rev_pk,
|
|
publish_pk,
|
|
};
|
|
|
|
sink.send(SetupMsg::Msg0(Msg0::from((own_params.clone(), own_punish))))
|
|
.await
|
|
.context("Failed to send Msg0")?;
|
|
let msg0 = stream
|
|
.select_next_some()
|
|
.await
|
|
.try_into_msg0()
|
|
.context("Failed to read Msg0")?;
|
|
|
|
let (other, other_punish) = msg0.into();
|
|
|
|
let params = AllParams::new(own_params, own_punish, other, other_punish, role);
|
|
|
|
if params.other.lock_amount != cfd.counterparty_margin()? {
|
|
anyhow::bail!(
|
|
"Amounts sent by counterparty don't add up, expected margin {} but got {}",
|
|
cfd.counterparty_margin()?,
|
|
params.other.lock_amount
|
|
)
|
|
}
|
|
|
|
let payouts = HashMap::from_iter([(
|
|
Announcement {
|
|
id: "dummy_id_to_be_replaced".to_string(),
|
|
nonce_pks: nonce_pks.clone(),
|
|
},
|
|
payout_curve::calculate(
|
|
cfd.order.price,
|
|
cfd.quantity_usd,
|
|
params.maker().lock_amount,
|
|
(params.taker().lock_amount, cfd.order.leverage),
|
|
)?,
|
|
)]);
|
|
|
|
let own_cfd_txs = create_cfd_transactions(
|
|
(params.maker().clone(), *params.maker_punish()),
|
|
(params.taker().clone(), *params.taker_punish()),
|
|
oracle_pk,
|
|
(
|
|
model::cfd::Cfd::CET_TIMELOCK,
|
|
cfd.refund_timelock_in_blocks(),
|
|
),
|
|
payouts,
|
|
sk,
|
|
)
|
|
.context("Failed to create CFD transactions")?;
|
|
|
|
sink.send(SetupMsg::Msg1(Msg1::from(own_cfd_txs.clone())))
|
|
.await
|
|
.context("Failed to send Msg1")?;
|
|
|
|
let msg1 = stream
|
|
.select_next_some()
|
|
.await
|
|
.try_into_msg1()
|
|
.context("Failed to read Msg1")?;
|
|
|
|
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,
|
|
)
|
|
.context("Punish adaptor signature does not verify")?;
|
|
|
|
for own_grouped_cets in &own_cets {
|
|
let other_cets = msg1
|
|
.cets
|
|
.get(&own_grouped_cets.event.id)
|
|
.context("Expect event to exist in msg")?;
|
|
|
|
verify_cets(
|
|
(&oracle_pk, &nonce_pks),
|
|
¶ms.other,
|
|
own_grouped_cets.cets.as_slice(),
|
|
other_cets.as_slice(),
|
|
&commit_desc,
|
|
commit_amount,
|
|
)
|
|
.context("CET signatures don't verify")?;
|
|
}
|
|
|
|
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,
|
|
)
|
|
.context("Refund signature does not verify")?;
|
|
|
|
let mut signed_lock_tx = wallet.sign(lock_tx).await?;
|
|
sink.send(SetupMsg::Msg2(Msg2 {
|
|
signed_lock: signed_lock_tx.clone(),
|
|
}))
|
|
.await
|
|
.context("Failed to send Msg2")?;
|
|
let msg2 = stream
|
|
.select_next_some()
|
|
.await
|
|
.try_into_msg2()
|
|
.context("Failed to read Msg2")?;
|
|
signed_lock_tx
|
|
.merge(msg2.signed_lock)
|
|
.context("Failed to merge lock PSBTs")?;
|
|
|
|
// TODO: In case we sign+send but never receive (the signed lock_tx from the other party) we
|
|
// need some fallback handling (after x time) to spend the outputs in a different way so the
|
|
// other party cannot hold us hostage
|
|
|
|
let cets = own_cets
|
|
.into_iter()
|
|
.map(|grouped_cets| {
|
|
let event_id = grouped_cets.event.id;
|
|
let other_cets = msg1
|
|
.cets
|
|
.get(&event_id)
|
|
.with_context(|| format!("Counterparty CETs for event {} missing", event_id))?;
|
|
let cets = grouped_cets
|
|
.cets
|
|
.into_iter()
|
|
.map(|(tx, _, digits)| {
|
|
let other_encsig = other_cets
|
|
.iter()
|
|
.find_map(|(other_range, other_encsig)| {
|
|
(other_range == &digits.range()).then(|| other_encsig)
|
|
})
|
|
.with_context(|| {
|
|
format!(
|
|
"Missing counterparty adaptor signature for CET corresponding to
|
|
price range {:?}",
|
|
digits.range()
|
|
)
|
|
})?;
|
|
Ok((tx, *other_encsig, digits.range()))
|
|
})
|
|
.collect::<Result<Vec<_>>>()?;
|
|
Ok((event_id, cets))
|
|
})
|
|
.collect::<Result<HashMap<_, _>>>()?;
|
|
|
|
Ok(Dlc {
|
|
identity: sk,
|
|
identity_counterparty: params.other.identity_pk,
|
|
revocation: rev_sk,
|
|
publish: publish_sk,
|
|
address: params.own.address,
|
|
lock: (signed_lock_tx.extract_tx(), lock_desc),
|
|
commit: (commit_tx, msg1.commit, commit_desc),
|
|
cets,
|
|
refund: (refund_tx, msg1.refund),
|
|
})
|
|
}
|
|
|
|
/// 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_params: (&schnorrsig::PublicKey, &[schnorrsig::PublicKey]),
|
|
other: &PartyParams,
|
|
own_cets: &[(Transaction, EcdsaAdaptorSignature, interval::Digits)],
|
|
cets: &[(RangeInclusive<u64>, EcdsaAdaptorSignature)],
|
|
commit_desc: &Descriptor<PublicKey>,
|
|
commit_amount: Amount,
|
|
) -> Result<()> {
|
|
for (tx, _, digits) in own_cets.iter() {
|
|
let other_encsig = cets
|
|
.iter()
|
|
.find_map(|(range, encsig)| (range == &digits.range()).then(|| encsig))
|
|
.expect("one encsig per cet, per party");
|
|
|
|
verify_cet_encsig(
|
|
tx,
|
|
other_encsig,
|
|
digits,
|
|
&other.identity_pk,
|
|
oracle_params,
|
|
commit_desc,
|
|
commit_amount,
|
|
)
|
|
.expect("valid maker cet encsig")
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn verify_adaptor_signature(
|
|
tx: &Transaction,
|
|
spent_descriptor: &Descriptor<PublicKey>,
|
|
spent_amount: Amount,
|
|
encsig: &EcdsaAdaptorSignature,
|
|
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: &EcdsaAdaptorSignature,
|
|
digits: &interval::Digits,
|
|
pk: &PublicKey,
|
|
(oracle_pk, nonce_pks): (&schnorrsig::PublicKey, &[schnorrsig::PublicKey]),
|
|
spent_descriptor: &Descriptor<PublicKey>,
|
|
spent_amount: Amount,
|
|
) -> Result<()> {
|
|
let index_nonce_pairs = &digits
|
|
.to_indices()
|
|
.into_iter()
|
|
.zip(nonce_pks.iter().cloned())
|
|
.collect::<Vec<_>>();
|
|
let adaptor_point = compute_adaptor_pk(oracle_pk, index_nonce_pairs)
|
|
.context("could not calculate adaptor point")?;
|
|
verify_adaptor_signature(
|
|
tx,
|
|
spent_descriptor,
|
|
spent_amount,
|
|
encsig,
|
|
&PublicKey::new(adaptor_point),
|
|
pk,
|
|
)
|
|
}
|
|
|