From 20648e821b0a6f502a3a05fc09c5d66f22c71573 Mon Sep 17 00:00:00 2001 From: Philipp Hoenisch Date: Tue, 28 Sep 2021 16:15:10 +1000 Subject: [PATCH 1/2] 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. --- cfd_protocol/src/lib.rs | 6 +- cfd_protocol/src/protocol.rs | 142 +++++---- cfd_protocol/tests/cfds.rs | 565 +++++++++++++++++++++++------------ daemon/src/model/cfd.rs | 3 +- daemon/src/monitor.rs | 18 +- daemon/src/setup_contract.rs | 103 +++++-- daemon/src/wire.rs | 23 +- 7 files changed, 568 insertions(+), 292 deletions(-) diff --git a/cfd_protocol/src/lib.rs b/cfd_protocol/src/lib.rs index c57dcae..597e7a5 100644 --- a/cfd_protocol/src/lib.rs +++ b/cfd_protocol/src/lib.rs @@ -5,8 +5,8 @@ pub mod interval; pub use protocol::{ close_transaction, commit_descriptor, compute_adaptor_pk, create_cfd_transactions, - finalize_spend_transaction, lock_descriptor, punish_transaction, renew_cfd_transactions, - spending_tx_sighash, CfdTransactions, PartyParams, Payout, PunishParams, TransactionExt, - WalletExt, + finalize_spend_transaction, generate_payouts, lock_descriptor, punish_transaction, + renew_cfd_transactions, spending_tx_sighash, Announcement, Cets, CfdTransactions, PartyParams, + Payout, PunishParams, TransactionExt, WalletExt, }; pub use secp256k1_zkp; diff --git a/cfd_protocol/src/protocol.rs b/cfd_protocol/src/protocol.rs index 3abd790..d1bae1c 100644 --- a/cfd_protocol/src/protocol.rs +++ b/cfd_protocol/src/protocol.rs @@ -21,6 +21,7 @@ use bdk::FeeRate; use itertools::Itertools; use secp256k1_zkp::{self, schnorrsig, EcdsaAdaptorSignature, SecretKey, Signature, SECP256K1}; use std::collections::HashMap; +use std::hash::Hasher; use std::iter::FromIterator; use std::num::NonZeroU8; use std::ops::RangeInclusive; @@ -72,19 +73,19 @@ where /// * `taker` - The initial parameters of the taker. /// * `taker_punish_params` - The punish parameters of the taker. /// * `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 -/// endian encoding of the price. +/// * `cet_timelock` - Relative timelock of the CET transaction with respect to the commit +/// transaction. /// * `refund_timelock` - Relative timelock of the refund transaction with respect to the commit /// transaction. -/// * `payouts` - All the possible ways in which the contract can be settled, according to the -/// conditions of the bet. +/// * `payouts_per_event` - All the possible ways in which the contract can be settled, according to +/// 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. pub fn create_cfd_transactions( (maker, maker_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), - payouts: Vec, + payouts_per_event: HashMap>, identity_sk: SecretKey, ) -> Result { let lock_tx = lock_transaction( @@ -109,9 +110,9 @@ pub fn create_cfd_transactions( taker.address, taker_punish_params, ), - (oracle_pk, nonce_pks), + oracle_pk, (cet_timelock, refund_timelock), - payouts, + payouts_per_event, identity_sk, ) } @@ -130,9 +131,9 @@ pub fn renew_cfd_transactions( Address, PunishParams, ), - (oracle_pk, nonce_pks): (schnorrsig::PublicKey, &[schnorrsig::PublicKey]), + oracle_pk: schnorrsig::PublicKey, (cet_timelock, refund_timelock): (u32, u32), - payouts: Vec, + payouts_per_event: HashMap>, identity_sk: SecretKey, ) -> Result { build_cfds( @@ -149,9 +150,9 @@ pub fn renew_cfd_transactions( taker_address, taker_punish_params, ), - (oracle_pk, nonce_pks), + oracle_pk, (cet_timelock, refund_timelock), - payouts, + payouts_per_event, identity_sk, ) } @@ -170,9 +171,9 @@ fn build_cfds( Address, PunishParams, ), - (oracle_pk, nonce_pks): (schnorrsig::PublicKey, &[schnorrsig::PublicKey]), + oracle_pk: schnorrsig::PublicKey, (cet_timelock, refund_timelock): (u32, u32), - payouts: Vec, + payouts_per_event: HashMap>, identity_sk: SecretKey, ) -> Result { let commit_tx = CommitTransaction::new( @@ -215,24 +216,31 @@ fn build_cfds( (tx.into_inner(), sig) }; - let cets = payouts - .into_iter() - .map(|payout| { - let cet = ContractExecutionTx::new( - &commit_tx, - payout.clone(), - &maker_address, - &taker_address, - nonce_pks, - cet_timelock, - )?; - - let encsig = cet.encsign(identity_sk, &oracle_pk)?; - - Ok((cet.into_inner(), encsig, payout.digits)) - }) - .collect::>>() - .context("cannot build and sign all cets")?; + let mut cets = vec![]; + for (event, payouts) in payouts_per_event.iter() { + let cets_tmp = payouts + .iter() + .map(|payout| { + let cet = ContractExecutionTx::new( + &commit_tx, + payout.clone(), + &maker_address, + &taker_address, + event.nonce_pks.as_slice(), + cet_timelock, + )?; + + let encsig = cet.encsign(identity_sk, &oracle_pk)?; + + Ok((cet.into_inner(), encsig, payout.digits.clone())) + }) + .collect::>>() + .context("cannot build and sign all cets")?; + cets.push(Cets { + event: event.clone(), + cets: cets_tmp, + }); + } Ok(CfdTransactions { lock: lock_tx, @@ -341,10 +349,42 @@ pub struct PunishParams { pub struct CfdTransactions { pub lock: PartiallySignedTransaction, pub commit: (Transaction, EcdsaAdaptorSignature), - pub cets: Vec<(Transaction, EcdsaAdaptorSignature, interval::Digits)>, + pub cets: Vec, 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, +} + +impl std::hash::Hash for Announcement { + fn hash(&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)] pub struct Payout { digits: interval::Digits, @@ -352,23 +392,23 @@ pub struct Payout { taker_amount: Amount, } -impl Payout { - pub fn new( - range: RangeInclusive, - maker_amount: Amount, - taker_amount: Amount, - ) -> Result> { - let digits = interval::Digits::new(range).context("invalid interval")?; - Ok(digits - .into_iter() - .map(|digits| Self { - digits, - maker_amount, - taker_amount, - }) - .collect()) - } +pub fn generate_payouts( + range: RangeInclusive, + maker_amount: Amount, + taker_amount: Amount, +) -> Result> { + let digits = interval::Digits::new(range).context("invalid interval")?; + Ok(digits + .into_iter() + .map(|digits| Payout { + digits, + maker_amount, + taker_amount, + }) + .collect()) +} +impl Payout { fn into_txouts(self, maker_address: &Address, taker_address: &Address) -> Vec { let txouts = [ (self.maker_amount, maker_address), @@ -464,7 +504,7 @@ mod tests { let orig_maker_amount = 1000; let orig_taker_amount = 1000; - let payouts = Payout::new( + let payouts = generate_payouts( 0..=10_000, Amount::from_sat(orig_maker_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_taker_amount = 1000; - let payouts = Payout::new( + let payouts = generate_payouts( 0..=10_000, Amount::from_sat(orig_maker_amount), Amount::from_sat(orig_taker_amount), diff --git a/cfd_protocol/tests/cfds.rs b/cfd_protocol/tests/cfds.rs index 5a75127..64860bf 100644 --- a/cfd_protocol/tests/cfds.rs +++ b/cfd_protocol/tests/cfds.rs @@ -8,12 +8,14 @@ use bdk::SignOptions; use bitcoin::util::psbt::PartiallySignedTransaction; use cfd_protocol::{ close_transaction, commit_descriptor, compute_adaptor_pk, create_cfd_transactions, - finalize_spend_transaction, interval, lock_descriptor, punish_transaction, - renew_cfd_transactions, spending_tx_sighash, CfdTransactions, Payout, PunishParams, - TransactionExt, WalletExt, + finalize_spend_transaction, generate_payouts, interval, lock_descriptor, punish_transaction, + renew_cfd_transactions, spending_tx_sighash, Announcement, Cets, CfdTransactions, Payout, + PunishParams, TransactionExt, WalletExt, }; use rand::{thread_rng, CryptoRng, RngCore}; use secp256k1_zkp::{schnorrsig, EcdsaAdaptorSignature, SecretKey, Signature, SECP256K1}; +use std::collections::HashMap; +use std::iter::FromIterator; use std::str::FromStr; #[test] @@ -26,25 +28,38 @@ fn create_cfd() { 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 oracle_data = OliviaData::example(); - let oracle_pk = oracle_data.pk; - let nonce_pks = oracle_data.nonce_pks.clone(); + let oracle_data_0 = OliviaData::example_0(); + let oracle_data_1 = OliviaData::example_1(); - let payouts = vec![ - Payout::new( - 0..=40_000, - Amount::from_btc(1.5).unwrap(), - Amount::from_btc(0.5).unwrap(), - ) - .unwrap(), - Payout::new( - 40_001..=70_000, - Amount::ZERO, - Amount::from_btc(2.0).unwrap(), - ) - .unwrap(), - ] - .concat(); + let oracle_pk = oracle_data_0.pk; + + let event_0 = oracle_data_0.announcement(); + 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(1.5).unwrap(), + ) + .unwrap(), + generate_payouts( + 70_001..=100_000, + Amount::from_btc(1.5).unwrap(), + Amount::from_btc(0.5).unwrap(), + ) + .unwrap(), + ] + .concat(), + ), + ]); let cet_timelock = 0; let refund_timelock = 0; @@ -53,11 +68,16 @@ fn create_cfd() { &mut rng, (&maker_wallet, maker_lock_amount), (&taker_wallet, taker_lock_amount), - (oracle_pk, &nonce_pks), - payouts, + oracle_pk, + payouts_per_event, (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_amount = maker_lock_amount + taker_lock_amount; @@ -70,7 +90,7 @@ fn create_cfd() { verify_cfd_sigs( (&maker_cfd_txs, maker.pk, maker.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), (&commit_desc, commit_amount), ); @@ -96,7 +116,7 @@ fn create_cfd() { taker.rev_sk, taker_addr, ), - oracle_data, + &[oracle_data_0, oracle_data_1], (lock_desc, lock_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 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 nonce_pks = oracle_data.nonce_pks.clone(); - - let payouts = vec![ - Payout::new(0..=10_000, Amount::from_btc(2.0).unwrap(), Amount::ZERO).unwrap(), - Payout::new( - 10_001..=50_000, - Amount::ZERO, - Amount::from_btc(2.0).unwrap(), - ) - .unwrap(), - ] - .concat(); + let event = oracle_data.announcement(); + + let payouts_per_event = HashMap::from_iter([( + event, + vec![ + generate_payouts(0..=10_000, Amount::from_btc(2.0).unwrap(), Amount::ZERO).unwrap(), + generate_payouts( + 10_001..=50_000, + Amount::ZERO, + Amount::from_btc(2.0).unwrap(), + ) + .unwrap(), + ] + .concat(), + )]); let cet_timelock = 0; let refund_timelock = 0; @@ -134,8 +157,8 @@ fn renew_cfd() { &mut rng, (&maker_wallet, maker_lock_amount), (&taker_wallet, taker_lock_amount), - (oracle_pk, &nonce_pks), - payouts, + oracle_pk, + payouts_per_event, (cet_timelock, refund_timelock), ); @@ -147,21 +170,28 @@ fn renew_cfd() { let (taker_rev_sk, taker_rev_pk) = make_keypair(&mut rng); let (taker_pub_sk, taker_pub_pk) = make_keypair(&mut rng); - let payouts = vec![ - Payout::new( - 0..=50_000, - Amount::from_btc(1.5).unwrap(), - Amount::from_btc(0.5).unwrap(), - ) - .unwrap(), - Payout::new( - 50_001..=70_000, - Amount::from_btc(0.5).unwrap(), - Amount::from_btc(1.5).unwrap(), - ) - .unwrap(), - ] - .concat(); + let oracle_data = OliviaData::example_1(); + 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, + Amount::from_btc(1.5).unwrap(), + Amount::from_btc(0.5).unwrap(), + ) + .unwrap(), + generate_payouts( + 50_001..=70_000, + Amount::from_btc(0.5).unwrap(), + Amount::from_btc(1.5).unwrap(), + ) + .unwrap(), + ] + .concat(), + )]); let maker_cfd_txs = renew_cfd_transactions( maker_cfd_txs.lock, @@ -183,9 +213,9 @@ fn renew_cfd() { publish_pk: taker_pub_pk, }, ), - (oracle_pk, &nonce_pks), + oracle_pk, (cet_timelock, refund_timelock), - payouts.clone(), + payouts_per_event.clone(), maker.sk, ) .unwrap(); @@ -210,13 +240,16 @@ fn renew_cfd() { publish_pk: taker_pub_pk, }, ), - (oracle_pk, &nonce_pks), + oracle_pk, (cet_timelock, refund_timelock), - payouts, + payouts_per_event, taker.sk, ) .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_amount = maker_lock_amount + taker_lock_amount; @@ -229,7 +262,7 @@ fn renew_cfd() { verify_cfd_sigs( (&maker_cfd_txs, maker.pk, maker_pub_pk), (&taker_cfd_txs, taker.pk, taker_pub_pk), - (oracle_pk, &nonce_pks), + (oracle_pk, vec![event]), (&lock_desc, lock_amount), (&commit_desc, commit_amount), ); @@ -255,7 +288,7 @@ fn renew_cfd() { taker_rev_sk, taker_addr, ), - oracle_data, + &[oracle_data], (lock_desc, lock_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 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 nonce_pks = oracle_data.nonce_pks; + let event = oracle_data.announcement(); - let payouts = vec![Payout::new( - 0..=100_000, - Amount::from_btc(1.5).unwrap(), - Amount::from_btc(0.5).unwrap(), - ) - .unwrap()] - .concat(); + let payouts_per_event = HashMap::from_iter([( + event, + generate_payouts( + 0..=100_000, + Amount::from_btc(1.5).unwrap(), + Amount::from_btc(0.5).unwrap(), + ) + .unwrap(), + )]); let cet_timelock = 0; let refund_timelock = 0; @@ -290,8 +325,8 @@ fn collaboratively_close_cfd() { &mut rng, (&maker_wallet, maker_lock_amount), (&taker_wallet, taker_lock_amount), - (oracle_pk, &nonce_pks), - payouts, + oracle_pk, + payouts_per_event, (cet_timelock, refund_timelock), ); @@ -335,8 +370,8 @@ fn create_cfd_txs( rng: &mut (impl RngCore + CryptoRng), (maker_wallet, maker_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]), - payouts: Vec, + oracle_pk: schnorrsig::PublicKey, + payouts_per_event: HashMap>, (cet_timelock, refund_timelock): (u32, u32), ) -> ( CfdTransactions, @@ -362,6 +397,7 @@ fn create_cfd_txs( let taker_params = taker_wallet .build_party_params(taker_lock_amount, taker_pk) .unwrap(); + let maker_cfd_txs = create_cfd_transactions( ( maker_params.clone(), @@ -377,9 +413,9 @@ fn create_cfd_txs( publish_pk: taker_pub_pk, }, ), - (oracle_pk, nonce_pks), + oracle_pk, (cet_timelock, refund_timelock), - payouts.clone(), + payouts_per_event.clone(), maker_sk, ) .unwrap(); @@ -398,9 +434,9 @@ fn create_cfd_txs( publish_pk: taker_pub_pk, }, ), - (oracle_pk, nonce_pks), + oracle_pk, (cet_timelock, refund_timelock), - payouts, + payouts_per_event, taker_sk, ) .unwrap(); @@ -440,7 +476,7 @@ struct CfdKeys { fn verify_cfd_sigs( (maker_cfd_txs, maker_pk, maker_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), (lock_desc, lock_amount): (&Descriptor, Amount), (commit_desc, commit_amount): (&Descriptor, Amount), ) { @@ -460,44 +496,69 @@ fn verify_cfd_sigs( &taker_pk.key, ) .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(|(maker_tx, maker_encsig, _)| { - maker_tx.txid() == tx.txid() - && verify_cet_encsig( - tx, - maker_encsig, - digits, - &maker_pk.key, - (oracle_pk, nonce_pks), - commit_desc, - commit_amount, - ) - .is_ok() - }) - .expect("one valid maker cet encsig per cet"); + .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 + .iter() + .find(|(maker_tx, maker_encsig, _)| { + maker_tx.txid() == tx.txid() + && verify_cet_encsig( + tx, + maker_encsig, + digits, + &maker_pk.key, + (oracle_pk, event.nonce_pks.as_slice()), + commit_desc, + commit_amount, + ) + .is_ok() + }) + .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(|(taker_tx, taker_encsig, _)| { - taker_tx.txid() == tx.txid() - && verify_cet_encsig( - tx, - taker_encsig, - msg_nonce_pairs, - &taker_pk.key, - (oracle_pk, nonce_pks), - commit_desc, - commit_amount, - ) - .is_ok() - }) - .expect("one valid taker cet encsig per cet"); + .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 + .iter() + .find(|(taker_tx, taker_encsig, _)| { + taker_tx.txid() == tx.txid() + && verify_cet_encsig( + tx, + taker_encsig, + digits, + &taker_pk.key, + (oracle_pk, event.nonce_pks.as_slice()), + commit_desc, + commit_amount, + ) + .is_ok() + }) + .expect("one valid taker cet encsig per cet"); + } } + encverify_spend( &taker_cfd_txs.commit.0, &maker_cfd_txs.commit.1, @@ -557,7 +618,7 @@ fn check_cfd_txs( SecretKey, Address, ), - oracle_data: OliviaData, + oracle_data_list: &[OliviaData], (lock_desc, lock_amount): (Descriptor, Amount), (commit_desc, commit_amount): (Descriptor, Amount), ) { @@ -604,51 +665,77 @@ fn check_cfd_txs( // CETs: - let unlocked_cets = maker_cfd_txs.cets.clone().into_iter().filter_map({ - |(tx, _, digits)| { - let price = oracle_data.price; + for Cets { event, cets } in maker_cfd_txs.cets.clone().into_iter() { + 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 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) { - return None; + return; } - let oracle_attestations = oracle_data.attestations.clone(); build_and_check_cet( tx, - &taker_cfd_txs.cets, + taker_cets, (&maker_sk, &maker_pk), &taker_pk, (price, &oracle_attestations), (&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"); + for Cets { event, cets } in taker_cfd_txs.cets.clone().into_iter() { + 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 oracle_attestations = oracle_data.attestations.clone(); - let unlocked_cets = taker_cfd_txs - .cets - .into_iter() - .filter_map(|(tx, _, digits)| { - let price = oracle_data.price; + 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) { - return None; + return; } build_and_check_cet( tx, - &maker_cfd_txs.cets, + maker_cets, (&taker_sk, &taker_pk), &maker_pk, - (price, &oracle_data.attestations), - (&signed_commit_tx_maker, &commit_desc, commit_amount), + (price, &oracle_attestations), + (&signed_commit_tx_taker, &commit_desc, commit_amount), ) - .expect("valid taker cet"); - - Some(()) + .expect("valid unlocked taker cet"); }); - assert_eq!(unlocked_cets.count(), 1, "Expected to unlock only 1 CET"); + } // Punish transactions: @@ -930,6 +1017,7 @@ fn make_keypair(rng: &mut (impl RngCore + CryptoRng)) -> (SecretKey, PublicKey) } struct OliviaData { + id: String, pk: schnorrsig::PublicKey, nonce_pks: Vec, price: u64, @@ -937,74 +1025,163 @@ struct 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 /// CFD protocol. /// /// Data comes from this event: /// https://outcome.observer/h00.ooo/x/BitMEX/BXBT/2021-10-05T02:00:00.price[n:20]. - pub fn example() -> Self { - let pk = schnorrsig::PublicKey::from_str( - "ddd4636845a90185991826be5a494cde9f4a6947b1727217afedc6292fa4caf7", - ) - .unwrap(); + fn example(id: &str, price: u64, nonce_pks: &[&str], attestations: &[&str]) -> Self { + let oracle_pk = schnorrsig::PublicKey::from_str(Self::OLIVIA_PK).unwrap(); - let nonce_pks = [ - "d02d163cf9623f567c4e3faf851a9266ac1ede13da4ca4141f3a7717fba9a739", - "bc310f26aa5addbc382f653d8530aaead7c25e3546abc24639f490e36d4bdb88", - "2661375f570dcc32300d442e85b6d72dfa3232dccda45e8fb4a2d1e758d1d374", - "fcc68fbf071d391b14c0867cb4defb5a8abc12418dff3dfc2f84fd4025cb2716", - "cf5c2b7fe3851c64a7ff9635a9bfc50cdd301401d002f2da049f4c6a20e8457b", - "14f1005d8c2832a2c4666dd732dd9bb3af9c8f70ebcdaec96869b1ca0c8e0de6", - "299ee1c9c20fab8b067adf452a7d7661b5e7f5dd6bc707562805002e7cb8443e", - "bcb4e5a594346de298993a7a31762f598b5224b977e23182369e9ed3e5127f78", - "25e09a16ee5d469069abfb62cd5e1f20af50cf15241f571e64fa28b127304574", - "3ed5a1422f43299caf281123aba88bd4bc61ec863f4afb79c7ce7663ad44db5d", - "a7e0f61212735c192c4bf16e0a3e925e65f9f3feb6f1e5e8d6f5c18cf2dbb5a8", - "a36a631015d9036d0c321fea7cf12f589aa196e7279b4a290de5112c2940e540", - "b5bdd931f81970139e7301ac654b378077c3ed993ca7893ed93fee5fc6f7a782", - "00090816e256b41e042dce38bde99ab3cf9482f9b066836988d3ed54833638e8", - "3530408e93c251f5f488d3b1c608157177c459d6fab1966abebf765bcc9338d2", - "603269ce88d112ff7fcfcaab82f228be97deca37f8190084d509c71b51a30432", - "f0587414fcc6c56aef11d4a1d287ad6b55b237c5b8a5d5d93eb9ca06f6466ccf", - "763009afb0ffd99c7b835488cb3b0302f3b78f59bbfd5292bedab8ef9da8c1b7", - "3867af9048309a05004a164bdea09899f23ff1d83b6491b2b53a1b7b92e0eb2e", - "688118e6b59e27944c277513db2711a520f4283c7c53a11f58d9f6a46d82c964", - ] - .iter() - .map(|pk| schnorrsig::PublicKey::from_str(pk).unwrap()) - .collect(); - - let attestations = [ - "5bc7663195971daaa1e3e6a81b4bca65882791644bc446fc060cbc118a3ace0f", - "721d0cb56a0778a1ca7907f81a0787f34385b13f854c845c4c5539f7f6267958", - "044aeef0d525c8ff48758c80939e95807bc640990cc03f53ab6fc0b262045221", - "79f5175423ec6ee69c8d0e55251db85f3015c2edfa5a03095443fbbf35eb2282", - "233b9ec549e9cc7c702109d29636db85a3ec63a66f3b53444bcc7586d36ca439", - "2961a00320b7c9a70220060019a6ca88e18c205fadd2f873c174e5ccbbed527e", - "bdb76e8f81c39ade4205ead9b68118757fc49ec22769605f26ef904b235283d6", - "6e75dafedf4ed685513ec1f5c93508de4fad2be05b46001ac00c03474f4690e1", - "cfcfc27eb9273b343b3042f0386e77efe329066be079788bb00ab47d72f26780", - "2d931ffd2963e74566365674583abc427bdb6ae571c4887d81f1920f0850665d", - "33b6f1112fa046cbc04be44c615e70519702662c1f72d8d49b3c4613614a8a46", - "19e569b15410fa9a758c1a6c211eae8c1547efbe0ac6a7709902be93415f2f09", - "d859dd5c9a58e1836d1eea3ebe7f48198a681d29e5a5cd6922532d2e94a53a1d", - "3387eb2ad5e64cd102167766bb72b447f4a2e5129d161e422f9d41cd7d1cc281", - "db35a9778a1e3abc8d8ab2f4a79346ae2154c9e0b4932d859d1f3e244f67ae76", - "c3be969e8b889cfb2ece71123e6be5538a2d3a1229637b18bccc179073c38059", - "6f73263f430e10b82d0fd06c4ddd3b8a6b58c3e756745bd0d9e71a399e517921", - "0818c9c245d7d2162cd393c562a121f80405a27d22ae465e95030c31ebb4bd24", - "b7c03f0bd6d63bd78ad4ea0f3452ff9717ba65ca42038e6e90a1aa558b7942dc", - "90c4d8ec9f408ccb62a62daa993c20f2f86799e1fdea520c6d060418e55fd216", - ] - .iter() - .map(|pk| SecretKey::from_str(pk).unwrap()) - .collect(); + let id = id.to_string(); + + 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 { - pk, + id, + pk: oracle_pk, nonce_pks, attestations, - price: 49262, + price, + } + } + + fn announcement(&self) -> Announcement { + Announcement { + id: self.id.clone(), + nonce_pks: self.nonce_pks.clone(), } } + + 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", + "bc310f26aa5addbc382f653d8530aaead7c25e3546abc24639f490e36d4bdb88", + "2661375f570dcc32300d442e85b6d72dfa3232dccda45e8fb4a2d1e758d1d374", + "fcc68fbf071d391b14c0867cb4defb5a8abc12418dff3dfc2f84fd4025cb2716", + "cf5c2b7fe3851c64a7ff9635a9bfc50cdd301401d002f2da049f4c6a20e8457b", + "14f1005d8c2832a2c4666dd732dd9bb3af9c8f70ebcdaec96869b1ca0c8e0de6", + "299ee1c9c20fab8b067adf452a7d7661b5e7f5dd6bc707562805002e7cb8443e", + "bcb4e5a594346de298993a7a31762f598b5224b977e23182369e9ed3e5127f78", + "25e09a16ee5d469069abfb62cd5e1f20af50cf15241f571e64fa28b127304574", + "3ed5a1422f43299caf281123aba88bd4bc61ec863f4afb79c7ce7663ad44db5d", + "a7e0f61212735c192c4bf16e0a3e925e65f9f3feb6f1e5e8d6f5c18cf2dbb5a8", + "a36a631015d9036d0c321fea7cf12f589aa196e7279b4a290de5112c2940e540", + "b5bdd931f81970139e7301ac654b378077c3ed993ca7893ed93fee5fc6f7a782", + "00090816e256b41e042dce38bde99ab3cf9482f9b066836988d3ed54833638e8", + "3530408e93c251f5f488d3b1c608157177c459d6fab1966abebf765bcc9338d2", + "603269ce88d112ff7fcfcaab82f228be97deca37f8190084d509c71b51a30432", + "f0587414fcc6c56aef11d4a1d287ad6b55b237c5b8a5d5d93eb9ca06f6466ccf", + "763009afb0ffd99c7b835488cb3b0302f3b78f59bbfd5292bedab8ef9da8c1b7", + "3867af9048309a05004a164bdea09899f23ff1d83b6491b2b53a1b7b92e0eb2e", + "688118e6b59e27944c277513db2711a520f4283c7c53a11f58d9f6a46d82c964", + ]; + const PRICE_0: u64 = 49262; + const ATTESTATIONS_0: [&'static str; 20] = [ + "5bc7663195971daaa1e3e6a81b4bca65882791644bc446fc060cbc118a3ace0f", + "721d0cb56a0778a1ca7907f81a0787f34385b13f854c845c4c5539f7f6267958", + "044aeef0d525c8ff48758c80939e95807bc640990cc03f53ab6fc0b262045221", + "79f5175423ec6ee69c8d0e55251db85f3015c2edfa5a03095443fbbf35eb2282", + "233b9ec549e9cc7c702109d29636db85a3ec63a66f3b53444bcc7586d36ca439", + "2961a00320b7c9a70220060019a6ca88e18c205fadd2f873c174e5ccbbed527e", + "bdb76e8f81c39ade4205ead9b68118757fc49ec22769605f26ef904b235283d6", + "6e75dafedf4ed685513ec1f5c93508de4fad2be05b46001ac00c03474f4690e1", + "cfcfc27eb9273b343b3042f0386e77efe329066be079788bb00ab47d72f26780", + "2d931ffd2963e74566365674583abc427bdb6ae571c4887d81f1920f0850665d", + "33b6f1112fa046cbc04be44c615e70519702662c1f72d8d49b3c4613614a8a46", + "19e569b15410fa9a758c1a6c211eae8c1547efbe0ac6a7709902be93415f2f09", + "d859dd5c9a58e1836d1eea3ebe7f48198a681d29e5a5cd6922532d2e94a53a1d", + "3387eb2ad5e64cd102167766bb72b447f4a2e5129d161e422f9d41cd7d1cc281", + "db35a9778a1e3abc8d8ab2f4a79346ae2154c9e0b4932d859d1f3e244f67ae76", + "c3be969e8b889cfb2ece71123e6be5538a2d3a1229637b18bccc179073c38059", + "6f73263f430e10b82d0fd06c4ddd3b8a6b58c3e756745bd0d9e71a399e517921", + "0818c9c245d7d2162cd393c562a121f80405a27d22ae465e95030c31ebb4bd24", + "b7c03f0bd6d63bd78ad4ea0f3452ff9717ba65ca42038e6e90a1aa558b7942dc", + "90c4d8ec9f408ccb62a62daa993c20f2f86799e1fdea520c6d060418e55fd216", + ]; + + const EVENT_ID_1: &'static str = "/x/BitMEX/BXBT/2021-10-05T08:00:00.price[n:20]"; + 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", + ]; +} + +fn assert_contains_cets_for_event(cets: &[Cets], event: &Announcement) { + assert!(!cets + .iter() + .find(|cet| cet.event.id == event.id) + .expect("cet to correspond to existing event") + .cets + .is_empty()); } diff --git a/daemon/src/model/cfd.rs b/daemon/src/model/cfd.rs index 2ab4b32..9763635 100644 --- a/daemon/src/model/cfd.rs +++ b/daemon/src/model/cfd.rs @@ -11,6 +11,7 @@ use rust_decimal::prelude::{FromPrimitive, ToPrimitive}; use rust_decimal::Decimal; use rust_decimal_macros::dec; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use std::fmt; use std::ops::{Neg, RangeInclusive}; use std::time::{Duration, SystemTime}; @@ -1060,6 +1061,6 @@ pub struct Dlc { /// The fully signed lock transaction ready to be published on chain pub lock: (Transaction, Descriptor), pub commit: (Transaction, EcdsaAdaptorSignature, Descriptor), - pub cets: Vec<(Transaction, EcdsaAdaptorSignature, RangeInclusive)>, + pub cets: HashMap)>>, pub refund: (Transaction, Signature), } diff --git a/daemon/src/monitor.rs b/daemon/src/monitor.rs index 49ec987..416f0e6 100644 --- a/daemon/src/monitor.rs +++ b/daemon/src/monitor.rs @@ -25,7 +25,7 @@ pub struct StartMonitoring { pub struct MonitorParams { lock: (Txid, Descriptor), commit: (Txid, Descriptor), - cets: Vec<(Txid, Script, RangeInclusive)>, + cets: HashMap)>>, refund: (Txid, Script, u32), } @@ -221,6 +221,9 @@ where async fn handle_oracle_attestation(&mut self, attestation: oracle::Attestation) -> Result<()> { 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) = match cets.iter().find_map(|(txid, script_pubkey, range)| { range @@ -468,9 +471,16 @@ impl MonitorParams { commit: (dlc.commit.0.txid(), dlc.commit.2), cets: dlc .cets - .into_iter() - .map(|(tx, _, range)| (tx.txid(), script_pubkey.clone(), range)) - .collect(), + .iter() + .map(|(event_id, cets)| { + ( + event_id.clone(), + cets.iter() + .map(|(tx, _, range)| (tx.txid(), script_pubkey.clone(), range.clone())) + .collect::>(), + ) + }) + .collect::>(), refund: ( dlc.refund.0.txid(), script_pubkey, diff --git a/daemon/src/setup_contract.rs b/daemon/src/setup_contract.rs index 05dbad2..299bfe6 100644 --- a/daemon/src/setup_contract.rs +++ b/daemon/src/setup_contract.rs @@ -9,14 +9,20 @@ 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, PartyParams, PunishParams, + 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. +/// 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 + Unpin, mut stream: impl FusedStream + Unpin, @@ -61,17 +67,23 @@ pub async fn new( ) } - let payouts = payout_curve::calculate( - cfd.order.price, - cfd.quantity_usd, - params.maker().lock_amount, - (params.taker().lock_amount, cfd.order.leverage), - )?; + 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, &nonce_pks), + oracle_pk, ( model::cfd::Cfd::CET_TIMELOCK, cfd.refund_timelock_in_blocks(), @@ -84,6 +96,7 @@ pub async fn new( sink.send(SetupMsg::Msg1(Msg1::from(own_cfd_txs.clone()))) .await .context("Failed to send Msg1")?; + let msg1 = stream .select_next_some() .await @@ -122,15 +135,22 @@ pub async fn new( ) .context("Punish adaptor signature does not verify")?; - verify_cets( - (&oracle_pk, &[]), - ¶ms.other, - &own_cets, - &msg1.cets, - &commit_desc, - commit_amount, - ) - .context("CET signatures don't 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; @@ -144,11 +164,6 @@ pub async fn new( ) .context("Refund signature does not verify")?; - let mut cet_by_digits = own_cets - .into_iter() - .map(|(tx, _, digits)| (digits.range(), (tx, digits))) - .collect::>(); - let mut signed_lock_tx = wallet.sign(lock_tx).await?; sink.send(SetupMsg::Msg2(Msg2 { 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 // 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::>>()?; + Ok((event_id, cets)) + }) + .collect::>>()?; + Ok(Dlc { identity: sk, identity_counterparty: params.other.identity_pk, @@ -176,16 +222,7 @@ pub async fn new( address: params.own.address, lock: (signed_lock_tx.extract_tx(), lock_desc), commit: (commit_tx, msg1.commit, commit_desc), - cets: msg1 - .cets - .into_iter() - .map(|(range, sig)| { - let (cet, digits) = cet_by_digits.remove(&range).context("unknown CET")?; - - Ok((cet, sig, digits.range())) - }) - .collect::>>() - .context("Failed to re-map CETs")?, + cets, refund: (refund_tx, msg1.refund), }) } diff --git a/daemon/src/wire.rs b/daemon/src/wire.rs index 2bba4fe..1c11789 100644 --- a/daemon/src/wire.rs +++ b/daemon/src/wire.rs @@ -10,6 +10,7 @@ use cfd_protocol::secp256k1_zkp::EcdsaAdaptorSignature; use cfd_protocol::{CfdTransactions, PartyParams, PunishParams}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use std::fmt; use std::marker::PhantomData; use std::ops::RangeInclusive; @@ -228,19 +229,29 @@ impl From for (PartyParams, PunishParams) { #[derive(Debug, Serialize, Deserialize)] pub struct Msg1 { pub commit: EcdsaAdaptorSignature, - pub cets: Vec<(RangeInclusive, EcdsaAdaptorSignature)>, + pub cets: HashMap, EcdsaAdaptorSignature)>>, pub refund: Signature, } impl From for Msg1 { fn from(txs: CfdTransactions) -> Self { + let cets = txs + .cets + .into_iter() + .map(|grouped_cets| { + ( + grouped_cets.event.id, + grouped_cets + .cets + .into_iter() + .map(|(_, encsig, digits)| (digits.range(), encsig)) + .collect::>(), + ) + }) + .collect::>(); Self { commit: txs.commit.1, - cets: txs - .cets - .into_iter() - .map(|(_, sig, digits)| (digits.range(), sig)) - .collect(), + cets, refund: txs.refund.1, } } From e881a1afcd18137effa5ca388a2b1d521a6619c7 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 6 Oct 2021 08:59:13 +1100 Subject: [PATCH 2/2] Build CfdTransactions without mutability --- cfd_protocol/src/protocol.rs | 50 ++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/cfd_protocol/src/protocol.rs b/cfd_protocol/src/protocol.rs index d1bae1c..16a643d 100644 --- a/cfd_protocol/src/protocol.rs +++ b/cfd_protocol/src/protocol.rs @@ -216,31 +216,31 @@ fn build_cfds( (tx.into_inner(), sig) }; - let mut cets = vec![]; - for (event, payouts) in payouts_per_event.iter() { - let cets_tmp = payouts - .iter() - .map(|payout| { - let cet = ContractExecutionTx::new( - &commit_tx, - payout.clone(), - &maker_address, - &taker_address, - event.nonce_pks.as_slice(), - cet_timelock, - )?; - - let encsig = cet.encsign(identity_sk, &oracle_pk)?; - - Ok((cet.into_inner(), encsig, payout.digits.clone())) - }) - .collect::>>() - .context("cannot build and sign all cets")?; - cets.push(Cets { - event: event.clone(), - cets: cets_tmp, - }); - } + let cets = payouts_per_event + .into_iter() + .map(|(event, payouts)| { + let cets = payouts + .iter() + .map(|payout| { + let cet = ContractExecutionTx::new( + &commit_tx, + payout.clone(), + &maker_address, + &taker_address, + event.nonce_pks.as_slice(), + cet_timelock, + )?; + + let encsig = cet.encsign(identity_sk, &oracle_pk)?; + + Ok((cet.into_inner(), encsig, payout.digits.clone())) + }) + .collect::>>() + .context("cannot build and sign all cets")?; + + Ok(Cets { event, cets }) + }) + .collect::>()?; Ok(CfdTransactions { lock: lock_tx,