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. 88
      cfd_protocol/src/protocol.rs
  3. 385
      cfd_protocol/tests/cfds.rs
  4. 2
      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::{
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;

88
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<Payout>,
payouts_per_event: HashMap<Announcement, Vec<Payout>>,
identity_sk: SecretKey,
) -> Result<CfdTransactions> {
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<Payout>,
payouts_per_event: HashMap<Announcement, Vec<Payout>>,
identity_sk: SecretKey,
) -> Result<CfdTransactions> {
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<Payout>,
payouts_per_event: HashMap<Announcement, Vec<Payout>>,
identity_sk: SecretKey,
) -> Result<CfdTransactions> {
let commit_tx = CommitTransaction::new(
@ -215,25 +216,32 @@ fn build_cfds(
(tx.into_inner(), sig)
};
let cets = payouts
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,
nonce_pks,
event.nonce_pks.as_slice(),
cet_timelock,
)?;
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<_>>>()
.context("cannot build and sign all cets")?;
Ok(Cets { event, cets })
})
.collect::<Result<_>>()?;
Ok(CfdTransactions {
lock: lock_tx,
commit: (commit_tx.into_inner(), commit_encsig),
@ -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<Cets>,
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)]
pub struct Payout {
digits: interval::Digits,
@ -352,16 +392,15 @@ pub struct Payout {
taker_amount: Amount,
}
impl Payout {
pub fn new(
pub fn generate_payouts(
range: RangeInclusive<u64>,
maker_amount: Amount,
taker_amount: Amount,
) -> Result<Vec<Self>> {
) -> Result<Vec<Payout>> {
let digits = interval::Digits::new(range).context("invalid interval")?;
Ok(digits
.into_iter()
.map(|digits| Self {
.map(|digits| Payout {
digits,
maker_amount,
taker_amount,
@ -369,6 +408,7 @@ impl Payout {
.collect())
}
impl Payout {
fn into_txouts(self, maker_address: &Address, taker_address: &Address) -> Vec<TxOut> {
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),

385
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(),
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(),
Payout::new(
40_001..=70_000,
Amount::ZERO,
Amount::from_btc(2.0).unwrap(),
generate_payouts(
70_001..=100_000,
Amount::from_btc(1.5).unwrap(),
Amount::from_btc(0.5).unwrap(),
)
.unwrap(),
]
.concat();
.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 event = oracle_data.announcement();
let payouts = vec![
Payout::new(0..=10_000, Amount::from_btc(2.0).unwrap(), Amount::ZERO).unwrap(),
Payout::new(
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();
.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(
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(),
Payout::new(
generate_payouts(
50_001..=70_000,
Amount::from_btc(0.5).unwrap(),
Amount::from_btc(1.5).unwrap(),
)
.unwrap(),
]
.concat();
.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(
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()]
.concat();
.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<Payout>,
oracle_pk: schnorrsig::PublicKey,
payouts_per_event: HashMap<Announcement, Vec<Payout>>,
(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<Announcement>),
(lock_desc, lock_amount): (&Descriptor<PublicKey>, Amount),
(commit_desc, commit_amount): (&Descriptor<PublicKey>, Amount),
) {
@ -460,8 +496,19 @@ 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(|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, _)| {
@ -471,7 +518,7 @@ fn verify_cfd_sigs(
maker_encsig,
digits,
&maker_pk.key,
(oracle_pk, nonce_pks),
(oracle_pk, event.nonce_pks.as_slice()),
commit_desc,
commit_amount,
)
@ -479,8 +526,20 @@ fn verify_cfd_sigs(
})
.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
.iter()
.find(|(taker_tx, taker_encsig, _)| {
@ -488,9 +547,9 @@ fn verify_cfd_sigs(
&& verify_cet_encsig(
tx,
taker_encsig,
msg_nonce_pairs,
digits,
&taker_pk.key,
(oracle_pk, nonce_pks),
(oracle_pk, event.nonce_pks.as_slice()),
commit_desc,
commit_amount,
)
@ -498,6 +557,8 @@ fn verify_cfd_sigs(
})
.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<PublicKey>, Amount),
(commit_desc, commit_amount): (Descriptor<PublicKey>, Amount),
) {
@ -604,51 +665,77 @@ fn check_cfd_txs(
// CETs:
let unlocked_cets = maker_cfd_txs.cets.clone().into_iter().filter_map({
|(tx, _, digits)| {
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");
Some(())
}
.expect("valid unlocked maker cet");
});
assert_eq!(unlocked_cets.count(), 1, "Expected to unlock only 1 CET");
}
let unlocked_cets = taker_cfd_txs
.cets
.into_iter()
.filter_map(|(tx, _, digits)| {
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 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<schnorrsig::PublicKey>,
price: u64,
@ -937,18 +1025,65 @@ 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 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 {
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",
"bc310f26aa5addbc382f653d8530aaead7c25e3546abc24639f490e36d4bdb88",
"2661375f570dcc32300d442e85b6d72dfa3232dccda45e8fb4a2d1e758d1d374",
@ -969,12 +1104,9 @@ impl OliviaData {
"763009afb0ffd99c7b835488cb3b0302f3b78f59bbfd5292bedab8ef9da8c1b7",
"3867af9048309a05004a164bdea09899f23ff1d83b6491b2b53a1b7b92e0eb2e",
"688118e6b59e27944c277513db2711a520f4283c7c53a11f58d9f6a46d82c964",
]
.iter()
.map(|pk| schnorrsig::PublicKey::from_str(pk).unwrap())
.collect();
let attestations = [
];
const PRICE_0: u64 = 49262;
const ATTESTATIONS_0: [&'static str; 20] = [
"5bc7663195971daaa1e3e6a81b4bca65882791644bc446fc060cbc118a3ace0f",
"721d0cb56a0778a1ca7907f81a0787f34385b13f854c845c4c5539f7f6267958",
"044aeef0d525c8ff48758c80939e95807bc640990cc03f53ab6fc0b262045221",
@ -995,16 +1127,61 @@ impl OliviaData {
"0818c9c245d7d2162cd393c562a121f80405a27d22ae465e95030c31ebb4bd24",
"b7c03f0bd6d63bd78ad4ea0f3452ff9717ba65ca42038e6e90a1aa558b7942dc",
"90c4d8ec9f408ccb62a62daa993c20f2f86799e1fdea520c6d060418e55fd216",
]
.iter()
.map(|pk| SecretKey::from_str(pk).unwrap())
.collect();
Self {
pk,
nonce_pks,
attestations,
price: 49262,
}
];
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
pub lock: (Transaction, 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),
}

18
daemon/src/monitor.rs

@ -25,7 +25,7 @@ pub struct StartMonitoring {
pub struct MonitorParams {
lock: (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),
}
@ -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::<Vec<_>>(),
)
})
.collect::<HashMap<_, _>>(),
refund: (
dlc.refund.0.txid(),
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::{
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<SetupMsg, Error = anyhow::Error> + 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.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")?;
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, &[]),
(&oracle_pk, &nonce_pks),
&params.other,
&own_cets,
&msg1.cets,
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::<HashMap<_, _>>();
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::<Result<Vec<_>>>()?;
Ok((event_id, cets))
})
.collect::<Result<HashMap<_, _>>>()?;
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::<Result<Vec<_>>>()
.context("Failed to re-map CETs")?,
cets,
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 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<Msg0> for (PartyParams, PunishParams) {
#[derive(Debug, Serialize, Deserialize)]
pub struct Msg1 {
pub commit: EcdsaAdaptorSignature,
pub cets: Vec<(RangeInclusive<u64>, EcdsaAdaptorSignature)>,
pub cets: HashMap<String, Vec<(RangeInclusive<u64>, EcdsaAdaptorSignature)>>,
pub refund: Signature,
}
impl From<CfdTransactions> for Msg1 {
fn from(txs: CfdTransactions) -> Self {
Self {
commit: txs.commit.1,
cets: txs
let cets = txs
.cets
.into_iter()
.map(|grouped_cets| {
(
grouped_cets.event.id,
grouped_cets
.cets
.into_iter()
.map(|(_, sig, digits)| (digits.range(), sig))
.collect(),
.map(|(_, encsig, digits)| (digits.range(), encsig))
.collect::<Vec<_>>(),
)
})
.collect::<HashMap<_, _>>();
Self {
commit: txs.commit.1,
cets,
refund: txs.refund.1,
}
}

Loading…
Cancel
Save