Browse Source

Merge pull request #161 from comit-network/multiple-payout-points

Allow more than one attestation event per CFD
upload-correct-windows-binary
Lucas Soriano 3 years ago
committed by GitHub
parent
commit
1d90402399
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      cfd_protocol/src/lib.rs
  2. 138
      cfd_protocol/src/protocol.rs
  3. 565
      cfd_protocol/tests/cfds.rs
  4. 2
      daemon/src/model/cfd.rs
  5. 18
      daemon/src/monitor.rs
  6. 103
      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;

138
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 cets = payouts_per_event
.into_iter() .into_iter()
.map(|payout| { .map(|(event, payouts)| {
let cet = ContractExecutionTx::new( let cets = payouts
&commit_tx, .iter()
payout.clone(), .map(|payout| {
&maker_address, let cet = ContractExecutionTx::new(
&taker_address, &commit_tx,
nonce_pks, payout.clone(),
cet_timelock, &maker_address,
)?; &taker_address,
event.nonce_pks.as_slice(),
let encsig = cet.encsign(identity_sk, &oracle_pk)?; cet_timelock,
)?;
Ok((cet.into_inner(), encsig, payout.digits))
let encsig = cet.encsign(identity_sk, &oracle_pk)?;
Ok((cet.into_inner(), encsig, payout.digits.clone()))
})
.collect::<Result<Vec<_>>>()
.context("cannot build and sign all cets")?;
Ok(Cets { event, cets })
}) })
.collect::<Result<Vec<_>>>() .collect::<Result<_>>()?;
.context("cannot build and sign all cets")?;
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<Payout>> {
) -> Result<Vec<Self>> { 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| Payout {
.map(|digits| Self { 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),

565
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();
Amount::from_btc(0.5).unwrap(),
) let payouts_per_event = HashMap::from_iter([
.unwrap(), (
Payout::new( event_0.clone(),
40_001..=70_000, generate_payouts(0..=50_000, Amount::ZERO, Amount::from_btc(2.0).unwrap()).unwrap(),
Amount::ZERO, ),
Amount::from_btc(2.0).unwrap(), (
) event_1.clone(),
.unwrap(), [
] generate_payouts(
.concat(); 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 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![
10_001..=50_000, generate_payouts(0..=10_000, Amount::from_btc(2.0).unwrap(), Amount::ZERO).unwrap(),
Amount::ZERO, generate_payouts(
Amount::from_btc(2.0).unwrap(), 10_001..=50_000,
) Amount::ZERO,
.unwrap(), Amount::from_btc(2.0).unwrap(),
] )
.concat(); .unwrap(),
]
.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;
0..=50_000, let event = oracle_data.announcement();
Amount::from_btc(1.5).unwrap(),
Amount::from_btc(0.5).unwrap(), let payouts_per_event = HashMap::from_iter([(
) event.clone(),
.unwrap(), vec![
Payout::new( generate_payouts(
50_001..=70_000, 0..=50_000,
Amount::from_btc(0.5).unwrap(), Amount::from_btc(1.5).unwrap(),
Amount::from_btc(1.5).unwrap(), Amount::from_btc(0.5).unwrap(),
) )
.unwrap(), .unwrap(),
] generate_payouts(
.concat(); 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( 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([(
0..=100_000, event,
Amount::from_btc(1.5).unwrap(), generate_payouts(
Amount::from_btc(0.5).unwrap(), 0..=100_000,
) Amount::from_btc(1.5).unwrap(),
.unwrap()] Amount::from_btc(0.5).unwrap(),
.concat(); )
.unwrap(),
)]);
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,44 +496,69 @@ 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 .cets
.iter() .iter()
.find(|(maker_tx, maker_encsig, _)| { .find(|grouped_maker_cets| grouped_maker_cets.event == grouped_taker_cets.event)
maker_tx.txid() == tx.txid() .expect("both parties to have the same set of payouts");
&& verify_cet_encsig( let event = events
tx, .iter()
maker_encsig, .find(|event| event.id == grouped_maker_cets.event.id)
digits, .expect("event to exist");
&maker_pk.key, for (tx, _, digits) in grouped_taker_cets.cets.iter() {
(oracle_pk, nonce_pks), grouped_maker_cets
commit_desc, .cets
commit_amount, .iter()
) .find(|(maker_tx, maker_encsig, _)| {
.is_ok() maker_tx.txid() == tx.txid()
}) && verify_cet_encsig(
.expect("one valid maker cet encsig per cet"); 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 .cets
.iter() .iter()
.find(|(taker_tx, taker_encsig, _)| { .find(|grouped_taker_cets| grouped_taker_cets.event == grouped_maker_cets.event)
taker_tx.txid() == tx.txid() .expect("both parties to have the same set of payouts");
&& verify_cet_encsig( let event = events
tx, .iter()
taker_encsig, .find(|event| event.id == grouped_maker_cets.event.id)
msg_nonce_pairs, .expect("event to exist");
&taker_pk.key, for (tx, _, digits) in grouped_maker_cets.cets.iter() {
(oracle_pk, nonce_pks), grouped_taker_cets
commit_desc, .cets
commit_amount, .iter()
) .find(|(taker_tx, taker_encsig, _)| {
.is_ok() taker_tx.txid() == tx.txid()
}) && verify_cet_encsig(
.expect("one valid taker cet encsig per cet"); 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( 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
let price = oracle_data.price; .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) { 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(()) for Cets { event, cets } in taker_cfd_txs.cets.clone().into_iter() {
} let oracle_data = oracle_data_list
}); .iter()
assert_eq!(unlocked_cets.count(), 1, "Expected to unlock only 1 CET"); .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 let maker_cets = maker_cfd_txs
.cets .cets
.into_iter() .iter()
.filter_map(|(tx, _, digits)| { .find_map(
let price = oracle_data.price; |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,74 +1025,163 @@ 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",
)
.unwrap();
let nonce_pks = [ let id = id.to_string();
"d02d163cf9623f567c4e3faf851a9266ac1ede13da4ca4141f3a7717fba9a739",
"bc310f26aa5addbc382f653d8530aaead7c25e3546abc24639f490e36d4bdb88", let nonce_pks = nonce_pks
"2661375f570dcc32300d442e85b6d72dfa3232dccda45e8fb4a2d1e758d1d374", .iter()
"fcc68fbf071d391b14c0867cb4defb5a8abc12418dff3dfc2f84fd4025cb2716", .map(|pk| schnorrsig::PublicKey::from_str(pk).unwrap())
"cf5c2b7fe3851c64a7ff9635a9bfc50cdd301401d002f2da049f4c6a20e8457b", .collect();
"14f1005d8c2832a2c4666dd732dd9bb3af9c8f70ebcdaec96869b1ca0c8e0de6",
"299ee1c9c20fab8b067adf452a7d7661b5e7f5dd6bc707562805002e7cb8443e", let attestations = attestations
"bcb4e5a594346de298993a7a31762f598b5224b977e23182369e9ed3e5127f78", .iter()
"25e09a16ee5d469069abfb62cd5e1f20af50cf15241f571e64fa28b127304574", .map(|pk| SecretKey::from_str(pk).unwrap())
"3ed5a1422f43299caf281123aba88bd4bc61ec863f4afb79c7ce7663ad44db5d", .collect();
"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();
Self { Self {
pk, id,
pk: oracle_pk,
nonce_pks, nonce_pks,
attestations, 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());
} }

2
daemon/src/model/cfd.rs

@ -1068,6 +1068,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,

103
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([(
cfd.order.price, Announcement {
cfd.quantity_usd, id: "dummy_id_to_be_replaced".to_string(),
params.maker().lock_amount, nonce_pks: nonce_pks.clone(),
(params.taker().lock_amount, cfd.order.leverage), },
)?; 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( 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")?;
verify_cets( for own_grouped_cets in &own_cets {
(&oracle_pk, &[]), let other_cets = msg1
&params.other, .cets
&own_cets, .get(&own_grouped_cets.event.id)
&msg1.cets, .context("Expect event to exist in msg")?;
&commit_desc,
commit_amount, verify_cets(
) (&oracle_pk, &nonce_pks),
.context("CET signatures don't verify")?; &params.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 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 {
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::<Vec<_>>(),
)
})
.collect::<HashMap<_, _>>();
Self { Self {
commit: txs.commit.1, commit: txs.commit.1,
cets: txs cets,
.cets
.into_iter()
.map(|(_, sig, digits)| (digits.range(), sig))
.collect(),
refund: txs.refund.1, refund: txs.refund.1,
} }
} }

Loading…
Cancel
Save