Browse Source

Allow more than one attestation event per CFD

We can now pass more than one set of `nonce_pks` when creating a CFD.
Each set corresponds to (and is identified by) a different oracle
event, allowing us to construct CFDs which can be settled via oracle
attestation at more than one point in time.

We also remember the CET-event relationship in `CfdTransactions`. This
is needed so that we can select the correct CET when the oracle
attests to the outcome of an event. Additionally, we save the
`nonce_pks` to more easily verify the adaptor signatures.
upload-correct-windows-binary
Philipp Hoenisch 3 years ago
committed by Lucas Soriano del Pino
parent
commit
20648e821b
No known key found for this signature in database GPG Key ID: EE611E973A1530E7
  1. 6
      cfd_protocol/src/lib.rs
  2. 92
      cfd_protocol/src/protocol.rs
  3. 385
      cfd_protocol/tests/cfds.rs
  4. 3
      daemon/src/model/cfd.rs
  5. 18
      daemon/src/monitor.rs
  6. 83
      daemon/src/setup_contract.rs
  7. 23
      daemon/src/wire.rs

6
cfd_protocol/src/lib.rs

@ -5,8 +5,8 @@ pub mod interval;
pub use protocol::{ pub use protocol::{
close_transaction, commit_descriptor, compute_adaptor_pk, create_cfd_transactions, close_transaction, commit_descriptor, compute_adaptor_pk, create_cfd_transactions,
finalize_spend_transaction, lock_descriptor, punish_transaction, renew_cfd_transactions, finalize_spend_transaction, generate_payouts, lock_descriptor, punish_transaction,
spending_tx_sighash, CfdTransactions, PartyParams, Payout, PunishParams, TransactionExt, renew_cfd_transactions, spending_tx_sighash, Announcement, Cets, CfdTransactions, PartyParams,
WalletExt, Payout, PunishParams, TransactionExt, WalletExt,
}; };
pub use secp256k1_zkp; pub use secp256k1_zkp;

92
cfd_protocol/src/protocol.rs

@ -21,6 +21,7 @@ use bdk::FeeRate;
use itertools::Itertools; use itertools::Itertools;
use secp256k1_zkp::{self, schnorrsig, EcdsaAdaptorSignature, SecretKey, Signature, SECP256K1}; use secp256k1_zkp::{self, schnorrsig, EcdsaAdaptorSignature, SecretKey, Signature, SECP256K1};
use std::collections::HashMap; use std::collections::HashMap;
use std::hash::Hasher;
use std::iter::FromIterator; use std::iter::FromIterator;
use std::num::NonZeroU8; use std::num::NonZeroU8;
use std::ops::RangeInclusive; use std::ops::RangeInclusive;
@ -72,19 +73,19 @@ where
/// * `taker` - The initial parameters of the taker. /// * `taker` - The initial parameters of the taker.
/// * `taker_punish_params` - The punish parameters of the taker. /// * `taker_punish_params` - The punish parameters of the taker.
/// * `oracle_pk` - The public key of the oracle. /// * `oracle_pk` - The public key of the oracle.
/// * `nonce_pks` - One R-value per price digit signed by the oracle. Their order matches a big /// * `cet_timelock` - Relative timelock of the CET transaction with respect to the commit
/// endian encoding of the price. /// transaction.
/// * `refund_timelock` - Relative timelock of the refund transaction with respect to the commit /// * `refund_timelock` - Relative timelock of the refund transaction with respect to the commit
/// transaction. /// transaction.
/// * `payouts` - All the possible ways in which the contract can be settled, according to the /// * `payouts_per_event` - All the possible ways in which the contract can be settled, according to
/// conditions of the bet. /// the conditions of the bet. The key is the event at which the oracle will attest the price.
/// * `identity_sk` - The secret key of the caller, used to sign and encsign different transactions. /// * `identity_sk` - The secret key of the caller, used to sign and encsign different transactions.
pub fn create_cfd_transactions( pub fn create_cfd_transactions(
(maker, maker_punish_params): (PartyParams, PunishParams), (maker, maker_punish_params): (PartyParams, PunishParams),
(taker, taker_punish_params): (PartyParams, PunishParams), (taker, taker_punish_params): (PartyParams, PunishParams),
(oracle_pk, nonce_pks): (schnorrsig::PublicKey, &[schnorrsig::PublicKey]), oracle_pk: schnorrsig::PublicKey,
(cet_timelock, refund_timelock): (u32, u32), (cet_timelock, refund_timelock): (u32, u32),
payouts: Vec<Payout>, payouts_per_event: HashMap<Announcement, Vec<Payout>>,
identity_sk: SecretKey, identity_sk: SecretKey,
) -> Result<CfdTransactions> { ) -> Result<CfdTransactions> {
let lock_tx = lock_transaction( let lock_tx = lock_transaction(
@ -109,9 +110,9 @@ pub fn create_cfd_transactions(
taker.address, taker.address,
taker_punish_params, taker_punish_params,
), ),
(oracle_pk, nonce_pks), oracle_pk,
(cet_timelock, refund_timelock), (cet_timelock, refund_timelock),
payouts, payouts_per_event,
identity_sk, identity_sk,
) )
} }
@ -130,9 +131,9 @@ pub fn renew_cfd_transactions(
Address, Address,
PunishParams, PunishParams,
), ),
(oracle_pk, nonce_pks): (schnorrsig::PublicKey, &[schnorrsig::PublicKey]), oracle_pk: schnorrsig::PublicKey,
(cet_timelock, refund_timelock): (u32, u32), (cet_timelock, refund_timelock): (u32, u32),
payouts: Vec<Payout>, payouts_per_event: HashMap<Announcement, Vec<Payout>>,
identity_sk: SecretKey, identity_sk: SecretKey,
) -> Result<CfdTransactions> { ) -> Result<CfdTransactions> {
build_cfds( build_cfds(
@ -149,9 +150,9 @@ pub fn renew_cfd_transactions(
taker_address, taker_address,
taker_punish_params, taker_punish_params,
), ),
(oracle_pk, nonce_pks), oracle_pk,
(cet_timelock, refund_timelock), (cet_timelock, refund_timelock),
payouts, payouts_per_event,
identity_sk, identity_sk,
) )
} }
@ -170,9 +171,9 @@ fn build_cfds(
Address, Address,
PunishParams, PunishParams,
), ),
(oracle_pk, nonce_pks): (schnorrsig::PublicKey, &[schnorrsig::PublicKey]), oracle_pk: schnorrsig::PublicKey,
(cet_timelock, refund_timelock): (u32, u32), (cet_timelock, refund_timelock): (u32, u32),
payouts: Vec<Payout>, payouts_per_event: HashMap<Announcement, Vec<Payout>>,
identity_sk: SecretKey, identity_sk: SecretKey,
) -> Result<CfdTransactions> { ) -> Result<CfdTransactions> {
let commit_tx = CommitTransaction::new( let commit_tx = CommitTransaction::new(
@ -215,24 +216,31 @@ fn build_cfds(
(tx.into_inner(), sig) (tx.into_inner(), sig)
}; };
let cets = payouts let mut cets = vec![];
.into_iter() for (event, payouts) in payouts_per_event.iter() {
let cets_tmp = payouts
.iter()
.map(|payout| { .map(|payout| {
let cet = ContractExecutionTx::new( let cet = ContractExecutionTx::new(
&commit_tx, &commit_tx,
payout.clone(), payout.clone(),
&maker_address, &maker_address,
&taker_address, &taker_address,
nonce_pks, event.nonce_pks.as_slice(),
cet_timelock, cet_timelock,
)?; )?;
let encsig = cet.encsign(identity_sk, &oracle_pk)?; let encsig = cet.encsign(identity_sk, &oracle_pk)?;
Ok((cet.into_inner(), encsig, payout.digits)) Ok((cet.into_inner(), encsig, payout.digits.clone()))
}) })
.collect::<Result<Vec<_>>>() .collect::<Result<Vec<_>>>()
.context("cannot build and sign all cets")?; .context("cannot build and sign all cets")?;
cets.push(Cets {
event: event.clone(),
cets: cets_tmp,
});
}
Ok(CfdTransactions { Ok(CfdTransactions {
lock: lock_tx, lock: lock_tx,
@ -341,10 +349,42 @@ pub struct PunishParams {
pub struct CfdTransactions { pub struct CfdTransactions {
pub lock: PartiallySignedTransaction, pub lock: PartiallySignedTransaction,
pub commit: (Transaction, EcdsaAdaptorSignature), pub commit: (Transaction, EcdsaAdaptorSignature),
pub cets: Vec<(Transaction, EcdsaAdaptorSignature, interval::Digits)>, pub cets: Vec<Cets>,
pub refund: (Transaction, Signature), pub refund: (Transaction, Signature),
} }
/// Group of CETs associated with a particular oracle announcement.
///
/// All of the adaptor signatures included will be _possibly_ unlocked
/// by the attestation corresponding to the announcement. In practice,
/// only one of the adaptor signatures should be unlocked if the
/// payout intervals are constructed correctly. To check if an adaptor
/// signature can be unlocked by a price attestation, verify whether
/// the price attested to lies within its interval.
#[derive(Debug, Clone)]
pub struct Cets {
pub event: Announcement,
pub cets: Vec<(Transaction, EcdsaAdaptorSignature, interval::Digits)>,
}
#[derive(Debug, Clone, Eq)]
pub struct Announcement {
pub id: String,
pub nonce_pks: Vec<schnorrsig::PublicKey>,
}
impl std::hash::Hash for Announcement {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state)
}
}
impl PartialEq for Announcement {
fn eq(&self, other: &Self) -> bool {
self.id.eq(&other.id)
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Payout { pub struct Payout {
digits: interval::Digits, digits: interval::Digits,
@ -352,23 +392,23 @@ pub struct Payout {
taker_amount: Amount, taker_amount: Amount,
} }
impl Payout { pub fn generate_payouts(
pub fn new(
range: RangeInclusive<u64>, range: RangeInclusive<u64>,
maker_amount: Amount, maker_amount: Amount,
taker_amount: Amount, taker_amount: Amount,
) -> Result<Vec<Self>> { ) -> Result<Vec<Payout>> {
let digits = interval::Digits::new(range).context("invalid interval")?; let digits = interval::Digits::new(range).context("invalid interval")?;
Ok(digits Ok(digits
.into_iter() .into_iter()
.map(|digits| Self { .map(|digits| Payout {
digits, digits,
maker_amount, maker_amount,
taker_amount, taker_amount,
}) })
.collect()) .collect())
} }
impl Payout {
fn into_txouts(self, maker_address: &Address, taker_address: &Address) -> Vec<TxOut> { fn into_txouts(self, maker_address: &Address, taker_address: &Address) -> Vec<TxOut> {
let txouts = [ let txouts = [
(self.maker_amount, maker_address), (self.maker_amount, maker_address),
@ -464,7 +504,7 @@ mod tests {
let orig_maker_amount = 1000; let orig_maker_amount = 1000;
let orig_taker_amount = 1000; let orig_taker_amount = 1000;
let payouts = Payout::new( let payouts = generate_payouts(
0..=10_000, 0..=10_000,
Amount::from_sat(orig_maker_amount), Amount::from_sat(orig_maker_amount),
Amount::from_sat(orig_taker_amount), Amount::from_sat(orig_taker_amount),
@ -498,7 +538,7 @@ mod tests {
let orig_maker_amount = dummy_dust_limit.as_sat() - 1; let orig_maker_amount = dummy_dust_limit.as_sat() - 1;
let orig_taker_amount = 1000; let orig_taker_amount = 1000;
let payouts = Payout::new( let payouts = generate_payouts(
0..=10_000, 0..=10_000,
Amount::from_sat(orig_maker_amount), Amount::from_sat(orig_maker_amount),
Amount::from_sat(orig_taker_amount), Amount::from_sat(orig_taker_amount),

385
cfd_protocol/tests/cfds.rs

@ -8,12 +8,14 @@ use bdk::SignOptions;
use bitcoin::util::psbt::PartiallySignedTransaction; use bitcoin::util::psbt::PartiallySignedTransaction;
use cfd_protocol::{ use cfd_protocol::{
close_transaction, commit_descriptor, compute_adaptor_pk, create_cfd_transactions, close_transaction, commit_descriptor, compute_adaptor_pk, create_cfd_transactions,
finalize_spend_transaction, interval, lock_descriptor, punish_transaction, finalize_spend_transaction, generate_payouts, interval, lock_descriptor, punish_transaction,
renew_cfd_transactions, spending_tx_sighash, CfdTransactions, Payout, PunishParams, renew_cfd_transactions, spending_tx_sighash, Announcement, Cets, CfdTransactions, Payout,
TransactionExt, WalletExt, PunishParams, TransactionExt, WalletExt,
}; };
use rand::{thread_rng, CryptoRng, RngCore}; use rand::{thread_rng, CryptoRng, RngCore};
use secp256k1_zkp::{schnorrsig, EcdsaAdaptorSignature, SecretKey, Signature, SECP256K1}; use secp256k1_zkp::{schnorrsig, EcdsaAdaptorSignature, SecretKey, Signature, SECP256K1};
use std::collections::HashMap;
use std::iter::FromIterator;
use std::str::FromStr; use std::str::FromStr;
#[test] #[test]
@ -26,25 +28,38 @@ fn create_cfd() {
let maker_wallet = build_wallet(&mut rng, Amount::from_btc(0.4).unwrap(), 5).unwrap(); let maker_wallet = build_wallet(&mut rng, Amount::from_btc(0.4).unwrap(), 5).unwrap();
let taker_wallet = build_wallet(&mut rng, Amount::from_btc(0.4).unwrap(), 5).unwrap(); let taker_wallet = build_wallet(&mut rng, Amount::from_btc(0.4).unwrap(), 5).unwrap();
let oracle_data = OliviaData::example(); let oracle_data_0 = OliviaData::example_0();
let oracle_pk = oracle_data.pk; let oracle_data_1 = OliviaData::example_1();
let nonce_pks = oracle_data.nonce_pks.clone();
let payouts = vec![ let oracle_pk = oracle_data_0.pk;
Payout::new(
0..=40_000, let event_0 = oracle_data_0.announcement();
Amount::from_btc(1.5).unwrap(), let event_1 = oracle_data_1.announcement();
let payouts_per_event = HashMap::from_iter([
(
event_0.clone(),
generate_payouts(0..=50_000, Amount::ZERO, Amount::from_btc(2.0).unwrap()).unwrap(),
),
(
event_1.clone(),
[
generate_payouts(
40_001..=70_000,
Amount::from_btc(0.5).unwrap(), Amount::from_btc(0.5).unwrap(),
Amount::from_btc(1.5).unwrap(),
) )
.unwrap(), .unwrap(),
Payout::new( generate_payouts(
40_001..=70_000, 70_001..=100_000,
Amount::ZERO, Amount::from_btc(1.5).unwrap(),
Amount::from_btc(2.0).unwrap(), Amount::from_btc(0.5).unwrap(),
) )
.unwrap(), .unwrap(),
] ]
.concat(); .concat(),
),
]);
let cet_timelock = 0; let cet_timelock = 0;
let refund_timelock = 0; let refund_timelock = 0;
@ -53,11 +68,16 @@ fn create_cfd() {
&mut rng, &mut rng,
(&maker_wallet, maker_lock_amount), (&maker_wallet, maker_lock_amount),
(&taker_wallet, taker_lock_amount), (&taker_wallet, taker_lock_amount),
(oracle_pk, &nonce_pks), oracle_pk,
payouts, payouts_per_event,
(cet_timelock, refund_timelock), (cet_timelock, refund_timelock),
); );
assert_contains_cets_for_event(&maker_cfd_txs.cets, &event_0);
assert_contains_cets_for_event(&maker_cfd_txs.cets, &event_1);
assert_contains_cets_for_event(&taker_cfd_txs.cets, &event_0);
assert_contains_cets_for_event(&taker_cfd_txs.cets, &event_1);
let lock_desc = lock_descriptor(maker.pk, taker.pk); let lock_desc = lock_descriptor(maker.pk, taker.pk);
let lock_amount = maker_lock_amount + taker_lock_amount; let lock_amount = maker_lock_amount + taker_lock_amount;
@ -70,7 +90,7 @@ fn create_cfd() {
verify_cfd_sigs( verify_cfd_sigs(
(&maker_cfd_txs, maker.pk, maker.pub_pk), (&maker_cfd_txs, maker.pk, maker.pub_pk),
(&taker_cfd_txs, taker.pk, taker.pub_pk), (&taker_cfd_txs, taker.pk, taker.pub_pk),
(oracle_pk, &nonce_pks), (oracle_pk, vec![event_0, event_1]),
(&lock_desc, lock_amount), (&lock_desc, lock_amount),
(&commit_desc, commit_amount), (&commit_desc, commit_amount),
); );
@ -96,7 +116,7 @@ fn create_cfd() {
taker.rev_sk, taker.rev_sk,
taker_addr, taker_addr,
), ),
oracle_data, &[oracle_data_0, oracle_data_1],
(lock_desc, lock_amount), (lock_desc, lock_amount),
(commit_desc, commit_amount), (commit_desc, commit_amount),
); );
@ -112,20 +132,23 @@ fn renew_cfd() {
let maker_wallet = build_wallet(&mut rng, Amount::from_btc(0.4).unwrap(), 5).unwrap(); let maker_wallet = build_wallet(&mut rng, Amount::from_btc(0.4).unwrap(), 5).unwrap();
let taker_wallet = build_wallet(&mut rng, Amount::from_btc(0.4).unwrap(), 5).unwrap(); let taker_wallet = build_wallet(&mut rng, Amount::from_btc(0.4).unwrap(), 5).unwrap();
let oracle_data = OliviaData::example(); let oracle_data = OliviaData::example_0();
let oracle_pk = oracle_data.pk; let oracle_pk = oracle_data.pk;
let nonce_pks = oracle_data.nonce_pks.clone(); let event = oracle_data.announcement();
let payouts = vec![ let payouts_per_event = HashMap::from_iter([(
Payout::new(0..=10_000, Amount::from_btc(2.0).unwrap(), Amount::ZERO).unwrap(), event,
Payout::new( vec![
generate_payouts(0..=10_000, Amount::from_btc(2.0).unwrap(), Amount::ZERO).unwrap(),
generate_payouts(
10_001..=50_000, 10_001..=50_000,
Amount::ZERO, Amount::ZERO,
Amount::from_btc(2.0).unwrap(), Amount::from_btc(2.0).unwrap(),
) )
.unwrap(), .unwrap(),
] ]
.concat(); .concat(),
)]);
let cet_timelock = 0; let cet_timelock = 0;
let refund_timelock = 0; let refund_timelock = 0;
@ -134,8 +157,8 @@ fn renew_cfd() {
&mut rng, &mut rng,
(&maker_wallet, maker_lock_amount), (&maker_wallet, maker_lock_amount),
(&taker_wallet, taker_lock_amount), (&taker_wallet, taker_lock_amount),
(oracle_pk, &nonce_pks), oracle_pk,
payouts, payouts_per_event,
(cet_timelock, refund_timelock), (cet_timelock, refund_timelock),
); );
@ -147,21 +170,28 @@ fn renew_cfd() {
let (taker_rev_sk, taker_rev_pk) = make_keypair(&mut rng); let (taker_rev_sk, taker_rev_pk) = make_keypair(&mut rng);
let (taker_pub_sk, taker_pub_pk) = make_keypair(&mut rng); let (taker_pub_sk, taker_pub_pk) = make_keypair(&mut rng);
let payouts = vec![ let oracle_data = OliviaData::example_1();
Payout::new( let oracle_pk = oracle_data.pk;
let event = oracle_data.announcement();
let payouts_per_event = HashMap::from_iter([(
event.clone(),
vec![
generate_payouts(
0..=50_000, 0..=50_000,
Amount::from_btc(1.5).unwrap(), Amount::from_btc(1.5).unwrap(),
Amount::from_btc(0.5).unwrap(), Amount::from_btc(0.5).unwrap(),
) )
.unwrap(), .unwrap(),
Payout::new( generate_payouts(
50_001..=70_000, 50_001..=70_000,
Amount::from_btc(0.5).unwrap(), Amount::from_btc(0.5).unwrap(),
Amount::from_btc(1.5).unwrap(), Amount::from_btc(1.5).unwrap(),
) )
.unwrap(), .unwrap(),
] ]
.concat(); .concat(),
)]);
let maker_cfd_txs = renew_cfd_transactions( let maker_cfd_txs = renew_cfd_transactions(
maker_cfd_txs.lock, maker_cfd_txs.lock,
@ -183,9 +213,9 @@ fn renew_cfd() {
publish_pk: taker_pub_pk, publish_pk: taker_pub_pk,
}, },
), ),
(oracle_pk, &nonce_pks), oracle_pk,
(cet_timelock, refund_timelock), (cet_timelock, refund_timelock),
payouts.clone(), payouts_per_event.clone(),
maker.sk, maker.sk,
) )
.unwrap(); .unwrap();
@ -210,13 +240,16 @@ fn renew_cfd() {
publish_pk: taker_pub_pk, publish_pk: taker_pub_pk,
}, },
), ),
(oracle_pk, &nonce_pks), oracle_pk,
(cet_timelock, refund_timelock), (cet_timelock, refund_timelock),
payouts, payouts_per_event,
taker.sk, taker.sk,
) )
.unwrap(); .unwrap();
assert_contains_cets_for_event(&maker_cfd_txs.cets, &event);
assert_contains_cets_for_event(&taker_cfd_txs.cets, &event);
let lock_desc = lock_descriptor(maker.pk, taker.pk); let lock_desc = lock_descriptor(maker.pk, taker.pk);
let lock_amount = maker_lock_amount + taker_lock_amount; let lock_amount = maker_lock_amount + taker_lock_amount;
@ -229,7 +262,7 @@ fn renew_cfd() {
verify_cfd_sigs( verify_cfd_sigs(
(&maker_cfd_txs, maker.pk, maker_pub_pk), (&maker_cfd_txs, maker.pk, maker_pub_pk),
(&taker_cfd_txs, taker.pk, taker_pub_pk), (&taker_cfd_txs, taker.pk, taker_pub_pk),
(oracle_pk, &nonce_pks), (oracle_pk, vec![event]),
(&lock_desc, lock_amount), (&lock_desc, lock_amount),
(&commit_desc, commit_amount), (&commit_desc, commit_amount),
); );
@ -255,7 +288,7 @@ fn renew_cfd() {
taker_rev_sk, taker_rev_sk,
taker_addr, taker_addr,
), ),
oracle_data, &[oracle_data],
(lock_desc, lock_amount), (lock_desc, lock_amount),
(commit_desc, commit_amount), (commit_desc, commit_amount),
) )
@ -271,17 +304,19 @@ fn collaboratively_close_cfd() {
let maker_wallet = build_wallet(&mut rng, Amount::from_btc(0.4).unwrap(), 5).unwrap(); let maker_wallet = build_wallet(&mut rng, Amount::from_btc(0.4).unwrap(), 5).unwrap();
let taker_wallet = build_wallet(&mut rng, Amount::from_btc(0.4).unwrap(), 5).unwrap(); let taker_wallet = build_wallet(&mut rng, Amount::from_btc(0.4).unwrap(), 5).unwrap();
let oracle_data = OliviaData::example(); let oracle_data = OliviaData::example_0();
let oracle_pk = oracle_data.pk; let oracle_pk = oracle_data.pk;
let nonce_pks = oracle_data.nonce_pks; let event = oracle_data.announcement();
let payouts = vec![Payout::new( let payouts_per_event = HashMap::from_iter([(
event,
generate_payouts(
0..=100_000, 0..=100_000,
Amount::from_btc(1.5).unwrap(), Amount::from_btc(1.5).unwrap(),
Amount::from_btc(0.5).unwrap(), Amount::from_btc(0.5).unwrap(),
) )
.unwrap()] .unwrap(),
.concat(); )]);
let cet_timelock = 0; let cet_timelock = 0;
let refund_timelock = 0; let refund_timelock = 0;
@ -290,8 +325,8 @@ fn collaboratively_close_cfd() {
&mut rng, &mut rng,
(&maker_wallet, maker_lock_amount), (&maker_wallet, maker_lock_amount),
(&taker_wallet, taker_lock_amount), (&taker_wallet, taker_lock_amount),
(oracle_pk, &nonce_pks), oracle_pk,
payouts, payouts_per_event,
(cet_timelock, refund_timelock), (cet_timelock, refund_timelock),
); );
@ -335,8 +370,8 @@ fn create_cfd_txs(
rng: &mut (impl RngCore + CryptoRng), rng: &mut (impl RngCore + CryptoRng),
(maker_wallet, maker_lock_amount): (&bdk::Wallet<(), bdk::database::MemoryDatabase>, Amount), (maker_wallet, maker_lock_amount): (&bdk::Wallet<(), bdk::database::MemoryDatabase>, Amount),
(taker_wallet, taker_lock_amount): (&bdk::Wallet<(), bdk::database::MemoryDatabase>, Amount), (taker_wallet, taker_lock_amount): (&bdk::Wallet<(), bdk::database::MemoryDatabase>, Amount),
(oracle_pk, nonce_pks): (schnorrsig::PublicKey, &[schnorrsig::PublicKey]), oracle_pk: schnorrsig::PublicKey,
payouts: Vec<Payout>, payouts_per_event: HashMap<Announcement, Vec<Payout>>,
(cet_timelock, refund_timelock): (u32, u32), (cet_timelock, refund_timelock): (u32, u32),
) -> ( ) -> (
CfdTransactions, CfdTransactions,
@ -362,6 +397,7 @@ fn create_cfd_txs(
let taker_params = taker_wallet let taker_params = taker_wallet
.build_party_params(taker_lock_amount, taker_pk) .build_party_params(taker_lock_amount, taker_pk)
.unwrap(); .unwrap();
let maker_cfd_txs = create_cfd_transactions( let maker_cfd_txs = create_cfd_transactions(
( (
maker_params.clone(), maker_params.clone(),
@ -377,9 +413,9 @@ fn create_cfd_txs(
publish_pk: taker_pub_pk, publish_pk: taker_pub_pk,
}, },
), ),
(oracle_pk, nonce_pks), oracle_pk,
(cet_timelock, refund_timelock), (cet_timelock, refund_timelock),
payouts.clone(), payouts_per_event.clone(),
maker_sk, maker_sk,
) )
.unwrap(); .unwrap();
@ -398,9 +434,9 @@ fn create_cfd_txs(
publish_pk: taker_pub_pk, publish_pk: taker_pub_pk,
}, },
), ),
(oracle_pk, nonce_pks), oracle_pk,
(cet_timelock, refund_timelock), (cet_timelock, refund_timelock),
payouts, payouts_per_event,
taker_sk, taker_sk,
) )
.unwrap(); .unwrap();
@ -440,7 +476,7 @@ struct CfdKeys {
fn verify_cfd_sigs( fn verify_cfd_sigs(
(maker_cfd_txs, maker_pk, maker_publish_pk): (&CfdTransactions, PublicKey, PublicKey), (maker_cfd_txs, maker_pk, maker_publish_pk): (&CfdTransactions, PublicKey, PublicKey),
(taker_cfd_txs, taker_pk, taker_publish_pk): (&CfdTransactions, PublicKey, PublicKey), (taker_cfd_txs, taker_pk, taker_publish_pk): (&CfdTransactions, PublicKey, PublicKey),
(oracle_pk, nonce_pks): (schnorrsig::PublicKey, &[schnorrsig::PublicKey]), (oracle_pk, events): (schnorrsig::PublicKey, Vec<Announcement>),
(lock_desc, lock_amount): (&Descriptor<PublicKey>, Amount), (lock_desc, lock_amount): (&Descriptor<PublicKey>, Amount),
(commit_desc, commit_amount): (&Descriptor<PublicKey>, Amount), (commit_desc, commit_amount): (&Descriptor<PublicKey>, Amount),
) { ) {
@ -460,8 +496,19 @@ fn verify_cfd_sigs(
&taker_pk.key, &taker_pk.key,
) )
.expect("valid taker refund sig"); .expect("valid taker refund sig");
for (tx, _, digits) in taker_cfd_txs.cets.iter() {
maker_cfd_txs for grouped_taker_cets in taker_cfd_txs.cets.iter() {
let grouped_maker_cets = maker_cfd_txs
.cets
.iter()
.find(|grouped_maker_cets| grouped_maker_cets.event == grouped_taker_cets.event)
.expect("both parties to have the same set of payouts");
let event = events
.iter()
.find(|event| event.id == grouped_maker_cets.event.id)
.expect("event to exist");
for (tx, _, digits) in grouped_taker_cets.cets.iter() {
grouped_maker_cets
.cets .cets
.iter() .iter()
.find(|(maker_tx, maker_encsig, _)| { .find(|(maker_tx, maker_encsig, _)| {
@ -471,7 +518,7 @@ fn verify_cfd_sigs(
maker_encsig, maker_encsig,
digits, digits,
&maker_pk.key, &maker_pk.key,
(oracle_pk, nonce_pks), (oracle_pk, event.nonce_pks.as_slice()),
commit_desc, commit_desc,
commit_amount, commit_amount,
) )
@ -479,8 +526,20 @@ fn verify_cfd_sigs(
}) })
.expect("one valid maker cet encsig per cet"); .expect("one valid maker cet encsig per cet");
} }
for (tx, _, msg_nonce_pairs) in maker_cfd_txs.cets.iter() { }
taker_cfd_txs
for grouped_maker_cets in maker_cfd_txs.cets.iter() {
let grouped_taker_cets = taker_cfd_txs
.cets
.iter()
.find(|grouped_taker_cets| grouped_taker_cets.event == grouped_maker_cets.event)
.expect("both parties to have the same set of payouts");
let event = events
.iter()
.find(|event| event.id == grouped_maker_cets.event.id)
.expect("event to exist");
for (tx, _, digits) in grouped_maker_cets.cets.iter() {
grouped_taker_cets
.cets .cets
.iter() .iter()
.find(|(taker_tx, taker_encsig, _)| { .find(|(taker_tx, taker_encsig, _)| {
@ -488,9 +547,9 @@ fn verify_cfd_sigs(
&& verify_cet_encsig( && verify_cet_encsig(
tx, tx,
taker_encsig, taker_encsig,
msg_nonce_pairs, digits,
&taker_pk.key, &taker_pk.key,
(oracle_pk, nonce_pks), (oracle_pk, event.nonce_pks.as_slice()),
commit_desc, commit_desc,
commit_amount, commit_amount,
) )
@ -498,6 +557,8 @@ fn verify_cfd_sigs(
}) })
.expect("one valid taker cet encsig per cet"); .expect("one valid taker cet encsig per cet");
} }
}
encverify_spend( encverify_spend(
&taker_cfd_txs.commit.0, &taker_cfd_txs.commit.0,
&maker_cfd_txs.commit.1, &maker_cfd_txs.commit.1,
@ -557,7 +618,7 @@ fn check_cfd_txs(
SecretKey, SecretKey,
Address, Address,
), ),
oracle_data: OliviaData, oracle_data_list: &[OliviaData],
(lock_desc, lock_amount): (Descriptor<PublicKey>, Amount), (lock_desc, lock_amount): (Descriptor<PublicKey>, Amount),
(commit_desc, commit_amount): (Descriptor<PublicKey>, Amount), (commit_desc, commit_amount): (Descriptor<PublicKey>, Amount),
) { ) {
@ -604,51 +665,77 @@ fn check_cfd_txs(
// CETs: // CETs:
let unlocked_cets = maker_cfd_txs.cets.clone().into_iter().filter_map({ for Cets { event, cets } in maker_cfd_txs.cets.clone().into_iter() {
|(tx, _, digits)| { let oracle_data = oracle_data_list
.iter()
.find(|data| data.id == event.id)
.expect("every cet to correspond to an existing event");
let price = oracle_data.price; let price = oracle_data.price;
let oracle_attestations = oracle_data.attestations.clone();
let taker_cets = taker_cfd_txs
.cets
.iter()
.find_map(
|Cets {
event: other_event,
cets,
}| (other_event.id == event.id).then(|| cets),
)
.expect("same events for taker and maker");
cets.into_iter().for_each(|(tx, _, digits)| {
if !digits.range().contains(&price) { if !digits.range().contains(&price) {
return None; return;
} }
let oracle_attestations = oracle_data.attestations.clone();
build_and_check_cet( build_and_check_cet(
tx, tx,
&taker_cfd_txs.cets, taker_cets,
(&maker_sk, &maker_pk), (&maker_sk, &maker_pk),
&taker_pk, &taker_pk,
(price, &oracle_attestations), (price, &oracle_attestations),
(&signed_commit_tx_maker, &commit_desc, commit_amount), (&signed_commit_tx_maker, &commit_desc, commit_amount),
) )
.expect("valid maker cet"); .expect("valid unlocked maker cet");
Some(())
}
}); });
assert_eq!(unlocked_cets.count(), 1, "Expected to unlock only 1 CET"); }
let unlocked_cets = taker_cfd_txs for Cets { event, cets } in taker_cfd_txs.cets.clone().into_iter() {
.cets let oracle_data = oracle_data_list
.into_iter() .iter()
.filter_map(|(tx, _, digits)| { .find(|data| data.id == event.id)
.expect("every cet to correspond to an existing event");
let price = oracle_data.price; let price = oracle_data.price;
let oracle_attestations = oracle_data.attestations.clone();
let maker_cets = maker_cfd_txs
.cets
.iter()
.find_map(
|Cets {
event: other_event,
cets,
}| (other_event.id == event.id).then(|| cets),
)
.expect("same events for taker and maker");
cets.into_iter().for_each(|(tx, _, digits)| {
if !digits.range().contains(&price) { if !digits.range().contains(&price) {
return None; return;
} }
build_and_check_cet( build_and_check_cet(
tx, tx,
&maker_cfd_txs.cets, maker_cets,
(&taker_sk, &taker_pk), (&taker_sk, &taker_pk),
&maker_pk, &maker_pk,
(price, &oracle_data.attestations), (price, &oracle_attestations),
(&signed_commit_tx_maker, &commit_desc, commit_amount), (&signed_commit_tx_taker, &commit_desc, commit_amount),
) )
.expect("valid taker cet"); .expect("valid unlocked taker cet");
Some(())
}); });
assert_eq!(unlocked_cets.count(), 1, "Expected to unlock only 1 CET"); }
// Punish transactions: // Punish transactions:
@ -930,6 +1017,7 @@ fn make_keypair(rng: &mut (impl RngCore + CryptoRng)) -> (SecretKey, PublicKey)
} }
struct OliviaData { struct OliviaData {
id: String,
pk: schnorrsig::PublicKey, pk: schnorrsig::PublicKey,
nonce_pks: Vec<schnorrsig::PublicKey>, nonce_pks: Vec<schnorrsig::PublicKey>,
price: u64, price: u64,
@ -937,18 +1025,65 @@ struct OliviaData {
} }
impl OliviaData { impl OliviaData {
fn example_0() -> Self {
Self::example(
Self::EVENT_ID_0,
Self::PRICE_0,
&Self::NONCE_PKS_0,
&Self::ATTESTATIONS_0,
)
}
fn example_1() -> Self {
Self::example(
Self::EVENT_ID_1,
Self::PRICE_1,
&Self::NONCE_PKS_1,
&Self::ATTESTATIONS_1,
)
}
/// An example of all the data necessary from `olivia` to test the /// An example of all the data necessary from `olivia` to test the
/// CFD protocol. /// CFD protocol.
/// ///
/// Data comes from this event: /// Data comes from this event:
/// https://outcome.observer/h00.ooo/x/BitMEX/BXBT/2021-10-05T02:00:00.price[n:20]. /// https://outcome.observer/h00.ooo/x/BitMEX/BXBT/2021-10-05T02:00:00.price[n:20].
pub fn example() -> Self { fn example(id: &str, price: u64, nonce_pks: &[&str], attestations: &[&str]) -> Self {
let pk = schnorrsig::PublicKey::from_str( let oracle_pk = schnorrsig::PublicKey::from_str(Self::OLIVIA_PK).unwrap();
"ddd4636845a90185991826be5a494cde9f4a6947b1727217afedc6292fa4caf7",
) let id = id.to_string();
.unwrap();
let nonce_pks = nonce_pks
.iter()
.map(|pk| schnorrsig::PublicKey::from_str(pk).unwrap())
.collect();
let attestations = attestations
.iter()
.map(|pk| SecretKey::from_str(pk).unwrap())
.collect();
Self {
id,
pk: oracle_pk,
nonce_pks,
attestations,
price,
}
}
fn announcement(&self) -> Announcement {
Announcement {
id: self.id.clone(),
nonce_pks: self.nonce_pks.clone(),
}
}
let nonce_pks = [ const OLIVIA_PK: &'static str =
"ddd4636845a90185991826be5a494cde9f4a6947b1727217afedc6292fa4caf7";
const EVENT_ID_0: &'static str = "/x/BitMEX/BXBT/2021-10-05T02:00:00.price[n:20]";
const NONCE_PKS_0: [&'static str; 20] = [
"d02d163cf9623f567c4e3faf851a9266ac1ede13da4ca4141f3a7717fba9a739", "d02d163cf9623f567c4e3faf851a9266ac1ede13da4ca4141f3a7717fba9a739",
"bc310f26aa5addbc382f653d8530aaead7c25e3546abc24639f490e36d4bdb88", "bc310f26aa5addbc382f653d8530aaead7c25e3546abc24639f490e36d4bdb88",
"2661375f570dcc32300d442e85b6d72dfa3232dccda45e8fb4a2d1e758d1d374", "2661375f570dcc32300d442e85b6d72dfa3232dccda45e8fb4a2d1e758d1d374",
@ -969,12 +1104,9 @@ impl OliviaData {
"763009afb0ffd99c7b835488cb3b0302f3b78f59bbfd5292bedab8ef9da8c1b7", "763009afb0ffd99c7b835488cb3b0302f3b78f59bbfd5292bedab8ef9da8c1b7",
"3867af9048309a05004a164bdea09899f23ff1d83b6491b2b53a1b7b92e0eb2e", "3867af9048309a05004a164bdea09899f23ff1d83b6491b2b53a1b7b92e0eb2e",
"688118e6b59e27944c277513db2711a520f4283c7c53a11f58d9f6a46d82c964", "688118e6b59e27944c277513db2711a520f4283c7c53a11f58d9f6a46d82c964",
] ];
.iter() const PRICE_0: u64 = 49262;
.map(|pk| schnorrsig::PublicKey::from_str(pk).unwrap()) const ATTESTATIONS_0: [&'static str; 20] = [
.collect();
let attestations = [
"5bc7663195971daaa1e3e6a81b4bca65882791644bc446fc060cbc118a3ace0f", "5bc7663195971daaa1e3e6a81b4bca65882791644bc446fc060cbc118a3ace0f",
"721d0cb56a0778a1ca7907f81a0787f34385b13f854c845c4c5539f7f6267958", "721d0cb56a0778a1ca7907f81a0787f34385b13f854c845c4c5539f7f6267958",
"044aeef0d525c8ff48758c80939e95807bc640990cc03f53ab6fc0b262045221", "044aeef0d525c8ff48758c80939e95807bc640990cc03f53ab6fc0b262045221",
@ -995,16 +1127,61 @@ impl OliviaData {
"0818c9c245d7d2162cd393c562a121f80405a27d22ae465e95030c31ebb4bd24", "0818c9c245d7d2162cd393c562a121f80405a27d22ae465e95030c31ebb4bd24",
"b7c03f0bd6d63bd78ad4ea0f3452ff9717ba65ca42038e6e90a1aa558b7942dc", "b7c03f0bd6d63bd78ad4ea0f3452ff9717ba65ca42038e6e90a1aa558b7942dc",
"90c4d8ec9f408ccb62a62daa993c20f2f86799e1fdea520c6d060418e55fd216", "90c4d8ec9f408ccb62a62daa993c20f2f86799e1fdea520c6d060418e55fd216",
] ];
.iter()
.map(|pk| SecretKey::from_str(pk).unwrap()) const EVENT_ID_1: &'static str = "/x/BitMEX/BXBT/2021-10-05T08:00:00.price[n:20]";
.collect(); const NONCE_PKS_1: [&'static str; 20] = [
"150df2e64f39706e726eaa1fe081af3edf376d9644723e135a99328fd194caca",
"b90629cedc7cb8430b4d15c84bbe1fe173e70e626d40c465e64de29d4879e20f",
"ae14ffb8701d3e224b6632a1bb7b099c8aa90979c3fb788422daa08bca25fa68",
"3717940a7e8c35b48b3596498ed93e4d54ba01a2bcbb645d30dae2fc98f087a8",
"91beb5da91cc8b4ee6ae603e7ae41cc041d5ea2c13bae9f0e630c69f6c0adfad",
"c51cafb450b01f30ec8bd2b4b5fed6f7e179f49945959f0d7609b4b9c5ab3781",
"75f2d9332aa1b2d84446a4b2aa276b4c2853659ab0ba74f0881289d3ab700f0c",
"5367de73acb53e69b0a4f777e564f87055fede5d4492ddafae876a815fa6166c",
"2087a513adb1aa2cc8506ca58306723ed13ba82e054f5bf29fcbeef1ab915c5a",
"71c980fb6adae9c121405628c91daffcc5ab52a8a0b6f53c953e8a0236b05782",
"d370d22f06751fc649f6ee930ac7f8f3b00389fdad02883a8038a81c46c33b19",
"fa6f7d37dc88b510c250dcae1023cce5009d5beb85a75f5b8b10c973b62348aa",
"a658077f9c963d1f41cf63b7ebf6e08331f5d201554b3af7814673108abe1bf3",
"8a816bf4caa2d6114b2e4d3ab9bff0d470ee0b90163c78c9b67f90238ead9319",
"c2519a4e764a65204c469062e260d8565f7730847c507b92c987e478ca91abe1",
"59cb6b5beac6511a671076530cc6cc9f1926f54c640828f38c363b110dd8a0cd",
"4625b1f3ab9ee01455fa1a98d15fc8d73a7cf41becb4ca5c6eab88db0ba7c114",
"82a4de403c604fe40aa3804c5ada6af54c425c0576980b50f259d32dc1a0fcff",
"5c4fb87b3812982759ed7264676e713e4e477a41759261515b04797db393ef62",
"f3f6b9134c0fdd670767fbf478fd0dd3430f195ce9c21cabb84f3c1dd4848a11",
];
const PRICE_1: u64 = 49493;
const ATTESTATIONS_1: [&'static str; 20] = [
"605f458e9a7bd216ff522e45f6cd14378c03ccfd4d35a69b9b6ce5c4ebfc89fa",
"edc7215277d2c24a7a4659ff8831352db609fcc467fead5e27fdada172cdfd86",
"1c2d76fcbe724b1fabd2622b991e90bbb2ea9244489de960747134c9fd695dcb",
"26b4f078c9ca2233b18b0e42c4bb9867e5de8ee35b500e30b28d9b1742322e49",
"2b59aeaacb80056b45dc12d6525d5c75343ef75730623c8d9893e2b681bf4b85",
"782e38e777d527e7cb0028a6d03e8f760c6202dbc5ac605f67f995919dee6182",
"a902f37f71a78e4bcf431a778024bd775db6d7ade0626a9e7bc4cdf0b1e52dfd",
"3927eb5ef3b56817c08709e0af1bb643ad4d95dbf5a92a49e1e9c8c811e929c4",
"9ff44fa9d8377a3531792cd6362e4a5b24b86d85602749d301f8449859065b77",
"6a2156ff0aaef174b36d5f8adc597fdcb26f306f7ef6e9a485faabc8eb29da2e",
"53445b507c0de312959fe4566b82db93987dd0b854f1a33bbad7768512bcaf69",
"793c40e0ec3a830c46658bfaed7df74e3fc6781e421e00db5b5f46b26ce4d092",
"db7f800da2f22878c8fc8368047308146e1ebd6316c389303c07ebeed7488fc9",
"73921d09e0d567a03f3a411c0f3455f9f652bbede808a694cca0fa94619f5ba9",
"3d4bd70d93f20aa6b1621ccd077c90bcdee47ce2bae15155434a77a3153a3235",
"90fc10577ab737e311b43288a266490f222a6ecb9f9667e01d7a54c0437d145f",
"51d350616c6fdf90254240b757184fc0dd226328adb42be214ec25832854950e",
"bab3a6269e172ac590fd36683724f087b4add293bb0ee4ef3d21fb5929985c75",
"d65a4c71062fc0b0210bb3e239f60d826a37d28caadfc52edd7afde6e91ff818",
"ea5dfd972784808a15543f850c7bc86bff2b51cff81ec68fc4c3977d5e7d38de",
];
}
Self { fn assert_contains_cets_for_event(cets: &[Cets], event: &Announcement) {
pk, assert!(!cets
nonce_pks, .iter()
attestations, .find(|cet| cet.event.id == event.id)
price: 49262, .expect("cet to correspond to existing event")
} .cets
} .is_empty());
} }

3
daemon/src/model/cfd.rs

@ -11,6 +11,7 @@ use rust_decimal::prelude::{FromPrimitive, ToPrimitive};
use rust_decimal::Decimal; use rust_decimal::Decimal;
use rust_decimal_macros::dec; use rust_decimal_macros::dec;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt; use std::fmt;
use std::ops::{Neg, RangeInclusive}; use std::ops::{Neg, RangeInclusive};
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
@ -1060,6 +1061,6 @@ pub struct Dlc {
/// The fully signed lock transaction ready to be published on chain /// The fully signed lock transaction ready to be published on chain
pub lock: (Transaction, Descriptor<PublicKey>), pub lock: (Transaction, Descriptor<PublicKey>),
pub commit: (Transaction, EcdsaAdaptorSignature, Descriptor<PublicKey>), pub commit: (Transaction, EcdsaAdaptorSignature, Descriptor<PublicKey>),
pub cets: Vec<(Transaction, EcdsaAdaptorSignature, RangeInclusive<u64>)>, pub cets: HashMap<String, Vec<(Transaction, EcdsaAdaptorSignature, RangeInclusive<u64>)>>,
pub refund: (Transaction, Signature), pub refund: (Transaction, Signature),
} }

18
daemon/src/monitor.rs

@ -25,7 +25,7 @@ pub struct StartMonitoring {
pub struct MonitorParams { pub struct MonitorParams {
lock: (Txid, Descriptor<PublicKey>), lock: (Txid, Descriptor<PublicKey>),
commit: (Txid, Descriptor<PublicKey>), commit: (Txid, Descriptor<PublicKey>),
cets: Vec<(Txid, Script, RangeInclusive<u64>)>, cets: HashMap<String, Vec<(Txid, Script, RangeInclusive<u64>)>>,
refund: (Txid, Script, u32), refund: (Txid, Script, u32),
} }
@ -221,6 +221,9 @@ where
async fn handle_oracle_attestation(&mut self, attestation: oracle::Attestation) -> Result<()> { async fn handle_oracle_attestation(&mut self, attestation: oracle::Attestation) -> Result<()> {
for (order_id, MonitorParams { cets, .. }) in self.cfds.clone().into_iter() { for (order_id, MonitorParams { cets, .. }) in self.cfds.clone().into_iter() {
let cets = cets
.get(&attestation.id)
.context("No CET for oracle event found")?;
let (txid, script_pubkey) = let (txid, script_pubkey) =
match cets.iter().find_map(|(txid, script_pubkey, range)| { match cets.iter().find_map(|(txid, script_pubkey, range)| {
range range
@ -468,9 +471,16 @@ impl MonitorParams {
commit: (dlc.commit.0.txid(), dlc.commit.2), commit: (dlc.commit.0.txid(), dlc.commit.2),
cets: dlc cets: dlc
.cets .cets
.into_iter() .iter()
.map(|(tx, _, range)| (tx.txid(), script_pubkey.clone(), range)) .map(|(event_id, cets)| {
.collect(), (
event_id.clone(),
cets.iter()
.map(|(tx, _, range)| (tx.txid(), script_pubkey.clone(), range.clone()))
.collect::<Vec<_>>(),
)
})
.collect::<HashMap<_, _>>(),
refund: ( refund: (
dlc.refund.0.txid(), dlc.refund.0.txid(),
script_pubkey, script_pubkey,

83
daemon/src/setup_contract.rs

@ -9,14 +9,20 @@ use bdk::descriptor::Descriptor;
use cfd_protocol::secp256k1_zkp::EcdsaAdaptorSignature; use cfd_protocol::secp256k1_zkp::EcdsaAdaptorSignature;
use cfd_protocol::{ use cfd_protocol::{
commit_descriptor, compute_adaptor_pk, create_cfd_transactions, interval, lock_descriptor, commit_descriptor, compute_adaptor_pk, create_cfd_transactions, interval, lock_descriptor,
spending_tx_sighash, PartyParams, PunishParams, spending_tx_sighash, Announcement, PartyParams, PunishParams,
}; };
use futures::stream::FusedStream; use futures::stream::FusedStream;
use futures::{Sink, SinkExt, StreamExt}; use futures::{Sink, SinkExt, StreamExt};
use std::collections::HashMap; use std::collections::HashMap;
use std::iter::FromIterator;
use std::ops::RangeInclusive; use std::ops::RangeInclusive;
/// Given an initial set of parameters, sets up the CFD contract with the other party. /// 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( pub async fn new(
mut sink: impl Sink<SetupMsg, Error = anyhow::Error> + Unpin, mut sink: impl Sink<SetupMsg, Error = anyhow::Error> + Unpin,
mut stream: impl FusedStream<Item = SetupMsg> + Unpin, mut stream: impl FusedStream<Item = SetupMsg> + Unpin,
@ -61,17 +67,23 @@ pub async fn new(
) )
} }
let payouts = payout_curve::calculate( 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.order.price,
cfd.quantity_usd, cfd.quantity_usd,
params.maker().lock_amount, params.maker().lock_amount,
(params.taker().lock_amount, cfd.order.leverage), (params.taker().lock_amount, cfd.order.leverage),
)?; )?,
)]);
let own_cfd_txs = create_cfd_transactions( let own_cfd_txs = create_cfd_transactions(
(params.maker().clone(), *params.maker_punish()), (params.maker().clone(), *params.maker_punish()),
(params.taker().clone(), *params.taker_punish()), (params.taker().clone(), *params.taker_punish()),
(oracle_pk, &nonce_pks), oracle_pk,
( (
model::cfd::Cfd::CET_TIMELOCK, model::cfd::Cfd::CET_TIMELOCK,
cfd.refund_timelock_in_blocks(), cfd.refund_timelock_in_blocks(),
@ -84,6 +96,7 @@ pub async fn new(
sink.send(SetupMsg::Msg1(Msg1::from(own_cfd_txs.clone()))) sink.send(SetupMsg::Msg1(Msg1::from(own_cfd_txs.clone())))
.await .await
.context("Failed to send Msg1")?; .context("Failed to send Msg1")?;
let msg1 = stream let msg1 = stream
.select_next_some() .select_next_some()
.await .await
@ -122,15 +135,22 @@ pub async fn new(
) )
.context("Punish adaptor signature does not verify")?; .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( verify_cets(
(&oracle_pk, &[]), (&oracle_pk, &nonce_pks),
&params.other, &params.other,
&own_cets, own_grouped_cets.cets.as_slice(),
&msg1.cets, other_cets.as_slice(),
&commit_desc, &commit_desc,
commit_amount, commit_amount,
) )
.context("CET signatures don't verify")?; .context("CET signatures don't verify")?;
}
let lock_tx = own_cfd_txs.lock; let lock_tx = own_cfd_txs.lock;
let refund_tx = own_cfd_txs.refund.0; let refund_tx = own_cfd_txs.refund.0;
@ -144,11 +164,6 @@ pub async fn new(
) )
.context("Refund signature does not verify")?; .context("Refund signature does not verify")?;
let mut cet_by_digits = own_cets
.into_iter()
.map(|(tx, _, digits)| (digits.range(), (tx, digits)))
.collect::<HashMap<_, _>>();
let mut signed_lock_tx = wallet.sign(lock_tx).await?; let mut signed_lock_tx = wallet.sign(lock_tx).await?;
sink.send(SetupMsg::Msg2(Msg2 { sink.send(SetupMsg::Msg2(Msg2 {
signed_lock: signed_lock_tx.clone(), signed_lock: signed_lock_tx.clone(),
@ -168,6 +183,37 @@ pub async fn new(
// need some fallback handling (after x time) to spend the outputs in a different way so the // need some fallback handling (after x time) to spend the outputs in a different way so the
// other party cannot hold us hostage // 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 { Ok(Dlc {
identity: sk, identity: sk,
identity_counterparty: params.other.identity_pk, identity_counterparty: params.other.identity_pk,
@ -176,16 +222,7 @@ pub async fn new(
address: params.own.address, address: params.own.address,
lock: (signed_lock_tx.extract_tx(), lock_desc), lock: (signed_lock_tx.extract_tx(), lock_desc),
commit: (commit_tx, msg1.commit, commit_desc), commit: (commit_tx, msg1.commit, commit_desc),
cets: msg1 cets,
.cets
.into_iter()
.map(|(range, sig)| {
let (cet, digits) = cet_by_digits.remove(&range).context("unknown CET")?;
Ok((cet, sig, digits.range()))
})
.collect::<Result<Vec<_>>>()
.context("Failed to re-map CETs")?,
refund: (refund_tx, msg1.refund), refund: (refund_tx, msg1.refund),
}) })
} }

23
daemon/src/wire.rs

@ -10,6 +10,7 @@ use cfd_protocol::secp256k1_zkp::EcdsaAdaptorSignature;
use cfd_protocol::{CfdTransactions, PartyParams, PunishParams}; use cfd_protocol::{CfdTransactions, PartyParams, PunishParams};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt; use std::fmt;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ops::RangeInclusive; use std::ops::RangeInclusive;
@ -228,19 +229,29 @@ impl From<Msg0> for (PartyParams, PunishParams) {
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct Msg1 { pub struct Msg1 {
pub commit: EcdsaAdaptorSignature, pub commit: EcdsaAdaptorSignature,
pub cets: Vec<(RangeInclusive<u64>, EcdsaAdaptorSignature)>, pub cets: HashMap<String, Vec<(RangeInclusive<u64>, EcdsaAdaptorSignature)>>,
pub refund: Signature, pub refund: Signature,
} }
impl From<CfdTransactions> for Msg1 { impl From<CfdTransactions> for Msg1 {
fn from(txs: CfdTransactions) -> Self { fn from(txs: CfdTransactions) -> Self {
Self { let cets = txs
commit: txs.commit.1, .cets
cets: txs .into_iter()
.map(|grouped_cets| {
(
grouped_cets.event.id,
grouped_cets
.cets .cets
.into_iter() .into_iter()
.map(|(_, sig, digits)| (digits.range(), sig)) .map(|(_, encsig, digits)| (digits.range(), encsig))
.collect(), .collect::<Vec<_>>(),
)
})
.collect::<HashMap<_, _>>();
Self {
commit: txs.commit.1,
cets,
refund: txs.refund.1, refund: txs.refund.1,
} }
} }

Loading…
Cancel
Save