From b3abfd027badbf9b0ea4c17c59ae268327bc7ff7 Mon Sep 17 00:00:00 2001 From: Mariusz Klochowicz Date: Tue, 9 Nov 2021 11:06:27 +1030 Subject: [PATCH] Depend on cfd protocol from `maia` repository Cfd protocol got moved into a separate repository. All references of `cfd_protocol` were renamed to `maia`. Patch cargo.toml with a fixed git revision until it gets a public release. --- Cargo.lock | 76 +- Cargo.toml | 3 +- cfd_protocol/Cargo.toml | 17 - cfd_protocol/src/interval.rs | 157 --- .../src/interval/digit_decomposition.rs | 459 ------- cfd_protocol/src/lib.rs | 12 - cfd_protocol/src/oracle.rs | 40 - cfd_protocol/src/protocol.rs | 573 -------- cfd_protocol/src/protocol/sighash_ext.rs | 15 - cfd_protocol/src/protocol/transaction_ext.rs | 26 - cfd_protocol/src/protocol/transactions.rs | 596 --------- cfd_protocol/src/protocol/txin_ext.rs | 23 - cfd_protocol/tests/cfds.rs | 1184 ----------------- daemon/Cargo.toml | 2 +- daemon/src/lib.rs | 2 +- daemon/src/maker_cfd.rs | 2 +- daemon/src/model/cfd.rs | 8 +- daemon/src/oracle.rs | 8 +- daemon/src/payout_curve.rs | 2 +- daemon/src/setup_contract.rs | 8 +- daemon/src/wallet.rs | 3 +- daemon/src/wire.rs | 4 +- daemon/tests/happy_path.rs | 2 +- .../harness/{cfd_protocol.rs => maia.rs} | 6 +- daemon/tests/harness/mocks/oracle.rs | 2 +- daemon/tests/harness/mocks/wallet.rs | 6 +- daemon/tests/harness/mod.rs | 2 +- 27 files changed, 47 insertions(+), 3191 deletions(-) delete mode 100644 cfd_protocol/Cargo.toml delete mode 100644 cfd_protocol/src/interval.rs delete mode 100644 cfd_protocol/src/interval/digit_decomposition.rs delete mode 100644 cfd_protocol/src/lib.rs delete mode 100644 cfd_protocol/src/oracle.rs delete mode 100644 cfd_protocol/src/protocol.rs delete mode 100644 cfd_protocol/src/protocol/sighash_ext.rs delete mode 100644 cfd_protocol/src/protocol/transaction_ext.rs delete mode 100644 cfd_protocol/src/protocol/transactions.rs delete mode 100644 cfd_protocol/src/protocol/txin_ext.rs delete mode 100644 cfd_protocol/tests/cfds.rs rename daemon/tests/harness/{cfd_protocol.rs => maia.rs} (97%) diff --git a/Cargo.lock b/Cargo.lock index c77426d..67b02e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -264,7 +264,6 @@ dependencies = [ "base64-compat", "bech32", "bitcoin_hashes 0.10.0", - "bitcoinconsensus", "secp256k1", "serde", ] @@ -284,16 +283,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bitcoinconsensus" -version = "0.19.0-3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a8aa43b5cd02f856cb126a9af819e77b8910fdd74dd1407be649f2f5fe3a1b5" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -368,21 +357,6 @@ dependencies = [ "jobserver", ] -[[package]] -name = "cfd_protocol" -version = "0.1.0" -dependencies = [ - "anyhow", - "bdk", - "bit-vec", - "bitcoin", - "itertools", - "proptest", - "rand 0.6.5", - "secp256k1-zkp", - "thiserror", -] - [[package]] name = "cfg-if" version = "0.1.10" @@ -634,7 +608,6 @@ dependencies = [ "atty", "bdk", "bytes", - "cfd_protocol", "chrono", "clap", "derive_more", @@ -643,6 +616,7 @@ dependencies = [ "hkdf", "http-api-problem", "itertools", + "maia", "mockall", "mockall_derive", "nalgebra", @@ -1470,6 +1444,20 @@ dependencies = [ "serde_json", ] +[[package]] +name = "maia" +version = "0.1.0" +source = "git+https://github.com/comit-network/maia?rev=70fc548da0fe4f34478fb34ec437fa9a434c7ee3#70fc548da0fe4f34478fb34ec437fa9a434c7ee3" +dependencies = [ + "anyhow", + "bdk", + "bit-vec", + "itertools", + "rand 0.6.5", + "secp256k1-zkp", + "thiserror", +] + [[package]] name = "matchers" version = "0.0.1" @@ -2021,29 +2009,6 @@ dependencies = [ "yansi", ] -[[package]] -name = "proptest" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0d9cc07f18492d879586c92b485def06bc850da3118075cd45d50e9c95b0e5" -dependencies = [ - "bitflags", - "byteorder", - "lazy_static", - "num-traits", - "quick-error", - "rand 0.8.4", - "rand_chacha 0.3.1", - "rand_xorshift 0.3.0", - "regex-syntax", -] - -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - [[package]] name = "quote" version = "1.0.10" @@ -2068,7 +2033,7 @@ dependencies = [ "rand_jitter", "rand_os", "rand_pcg", - "rand_xorshift 0.1.1", + "rand_xorshift", "winapi 0.3.9", ] @@ -2240,15 +2205,6 @@ dependencies = [ "rand_core 0.3.1", ] -[[package]] -name = "rand_xorshift" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" -dependencies = [ - "rand_core 0.6.3", -] - [[package]] name = "rawpointer" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index 1ba6d1b..6e37a98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,9 @@ [workspace] -members = ["cfd_protocol", "daemon", "xtra_productivity"] +members = ["daemon", "xtra_productivity"] resolver = "2" [patch.crates-io] rocket = { git = "https://github.com/SergioBenitez/Rocket" } # Need to patch rocket dependency of `rocket_basicauth` until there is an official release. xtra = { git = "https://github.com/Restioson/xtra" } # Latest master has crucial patches. secp256k1-zkp = { git = "https://github.com/ElementsProject/rust-secp256k1-zkp" } # Latest master has crucial patches. +maia = { git = "https://github.com/comit-network/maia", rev = "70fc548da0fe4f34478fb34ec437fa9a434c7ee3" } diff --git a/cfd_protocol/Cargo.toml b/cfd_protocol/Cargo.toml deleted file mode 100644 index cc26484..0000000 --- a/cfd_protocol/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "cfd_protocol" -version = "0.1.0" -edition = "2018" - -[dependencies] -anyhow = "1" -bdk = { version = "0.13", default-features = false } -bit-vec = "0.6" -itertools = "0.10" -rand = "0.6" -secp256k1-zkp = { version = "0.4", features = ["bitcoin_hashes", "global-context", "serde"] } -thiserror = "1" - -[dev-dependencies] -bitcoin = { version = "0.27", features = ["rand", "bitcoinconsensus"] } -proptest = { version = "1", default-features = false, features = ["std"] } diff --git a/cfd_protocol/src/interval.rs b/cfd_protocol/src/interval.rs deleted file mode 100644 index 30bdab5..0000000 --- a/cfd_protocol/src/interval.rs +++ /dev/null @@ -1,157 +0,0 @@ -use bit_vec::BitVec; -use std::fmt::Display; -use std::num::NonZeroU8; -use std::ops::RangeInclusive; - -mod digit_decomposition; - -/// Maximum supported BTC price in whole USD. -pub const MAX_PRICE_DEC: u64 = (BASE as u64).pow(MAX_DIGITS as u32) - 1; - -/// Maximum number of binary digits for BTC price in whole USD. -const MAX_DIGITS: usize = 20; - -const BASE: usize = 2; - -/// Binary representation of a price interval. -#[derive(Clone, Debug, PartialEq)] -pub struct Digits(BitVec); - -impl Digits { - pub fn new(range: RangeInclusive) -> Result, Error> { - let (start, end) = range.into_inner(); - if start > MAX_PRICE_DEC || end > MAX_PRICE_DEC { - return Err(Error::RangeOverMax); - } - - if start > end { - return Err(Error::DecreasingRange); - } - - let digits = digit_decomposition::group_by_ignoring_digits( - start as usize, - end as usize, - BASE, - MAX_DIGITS, - ) - .iter() - .map(|digits| { - let digits = digits.iter().map(|n| *n != 0).collect::(); - Digits(digits) - }) - .collect(); - - Ok(digits) - } - - /// Calculate the range of prices expressed by these digits. - /// - /// With the resulting range one can assess wether a particular - /// price corresponds to the described interval. - pub fn range(&self) -> RangeInclusive { - let missing_bits = MAX_DIGITS - self.0.len(); - - let mut bits = self.0.clone(); - bits.append(&mut BitVec::from_elem(missing_bits, false)); - let start = bits.as_u64(); - - let mut bits = self.0.clone(); - bits.append(&mut BitVec::from_elem(missing_bits, true)); - let end = bits.as_u64(); - - start..=end - } - - /// Map each bit to its index in the set {0, 1}, starting at 1. - pub fn to_indices(&self) -> Vec { - self.0 - .iter() - .map(|bit| NonZeroU8::new(if bit { 2u8 } else { 1u8 }).expect("1 and 2 are non-zero")) - .collect() - } - - pub fn len(&self) -> usize { - self.0.len() - } - - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("Interval would generate values over maximum price of {MAX_PRICE_DEC}.")] - RangeOverMax, - #[error("Invalid decreasing interval.")] - DecreasingRange, -} - -impl Display for Digits { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0 - .iter() - .try_for_each(|digit| write!(f, "{}", digit as u8))?; - - Ok(()) - } -} - -trait BitVecExt { - fn as_u64(&self) -> u64; -} - -impl BitVecExt for BitVec { - fn as_u64(&self) -> u64 { - let len = self.len(); - - self.iter().enumerate().fold(0, |acc, (i, x)| { - acc + ((x as u64) * (BASE.pow((len - i - 1) as u32) as u64)) - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use anyhow::Result; - use proptest::prelude::*; - - #[derive(Debug, Clone)] - struct Interval(RangeInclusive); - - impl Interval { - fn new(range: RangeInclusive) -> Self { - Self(range) - } - - fn to_digits(&self) -> Result> { - let digits = Digits::new(self.0.clone())?; - - Ok(digits) - } - } - - impl PartialEq> for Interval { - fn eq(&self, other: &Vec) -> bool { - let sub_intervals = other.iter().flat_map(|i| i.range()); - sub_intervals.eq(self.0.clone()) - } - } - - prop_compose! { - fn interval()(x in 0u64..=MAX_PRICE_DEC, y in 0u64..=MAX_PRICE_DEC) -> Interval { - let (start, end) = if x < y { (x, y) } else { (y, x) }; - - Interval::new(start..=end) - } - } - - proptest! { - #[test] - fn interval_equal_to_sum_of_sub_intervals_described_by_digits(interval in interval()) { - prop_assert!(interval == interval.to_digits().unwrap()) - } - } -} diff --git a/cfd_protocol/src/interval/digit_decomposition.rs b/cfd_protocol/src/interval/digit_decomposition.rs deleted file mode 100644 index 246982a..0000000 --- a/cfd_protocol/src/interval/digit_decomposition.rs +++ /dev/null @@ -1,459 +0,0 @@ -//! Utility functions to decompose numeric outcome values -//! -//! This code has been lifted from: -//! - -/// Describes an interval that starts at `prefix || start` and terminates at `prefix || end`. -struct PrefixInterval { - /// The prefix common to all numbers within the interval. - prefix: Vec, - /// The suffix of the first number in the interval. - start: Vec, - /// The suffix of the last number in the interval. - end: Vec, -} - -/// Decompose a numeric value into digits in the specified base. If the decomposed -/// value contains less than `nb_digits`, zeroes will be prepended to reach `nb_digits` -/// size. -fn decompose_value(mut value: usize, base: usize, nb_digits: usize) -> Vec { - let mut res = Vec::new(); - - while value > 0 { - res.push(value % base); - value = ((value as f64) / (base as f64)).floor() as usize; - } - - while res.len() < nb_digits { - res.push(0); - } - - assert_eq!(nb_digits, res.len()); - - res.into_iter().rev().collect() -} - -/// Returns the interval [start, end] as a `PrefixInterval`, which will contain -/// the common prefix to all numbers in the interval as well as the start and end -/// suffixes decomposed in the specified base, and zero padded to `nb_digits` if -/// necessary. -fn separate_prefix(start: usize, end: usize, base: usize, nb_digits: usize) -> PrefixInterval { - let start_digits = decompose_value(start, base, nb_digits); - let end_digits = decompose_value(end, base, nb_digits); - let mut prefix = Vec::new(); - - let mut i = 0; - while i < nb_digits && start_digits[i] == end_digits[i] { - prefix.push(start_digits[i]); - i += 1; - } - let start = start_digits.into_iter().skip(prefix.len()).collect(); - - let end = end_digits.into_iter().skip(prefix.len()).collect(); - - PrefixInterval { prefix, start, end } -} - -/// Removes the trailing digits from `digits` that are equal to `num`. -fn remove_tail_if_equal(mut digits: Vec, num: usize) -> Vec { - let mut i = digits.len(); - while i > 1 && digits[i - 1] == num { - i -= 1; - } - digits.truncate(i); - digits -} - -/// Compute the groupings for the end of the interval. -fn back_groupings(digits: Vec, base: usize) -> Vec> { - let digits = remove_tail_if_equal(digits, base - 1); - if digits.is_empty() { - return vec![vec![base - 1]]; - } - let mut prefix = vec![digits[0]]; - let mut res: Vec> = Vec::new(); - for digit in digits.iter().skip(1) { - let mut last = 0; - let digit = *digit; - while last < digit { - let mut new_res = prefix.clone(); - new_res.push(last); - res.push(new_res); - last += 1; - } - prefix.push(digit); - } - res.push(digits); - res -} - -/// Compute the groupings for the beginning of the interval. -fn front_groupings(digits: Vec, base: usize) -> Vec> { - let digits = remove_tail_if_equal(digits, 0); - if digits.is_empty() { - return vec![vec![0]]; - } - let mut prefix = digits.clone(); - let mut res: Vec> = vec![digits.clone()]; - for digit in digits.into_iter().skip(1).rev() { - prefix.pop(); - let mut last = digit + 1; - while last < base { - let mut new_res = prefix.clone(); - new_res.push(last); - res.push(new_res); - last += 1; - } - } - - res -} - -/// Compute the groupings for the middle of the interval. -fn middle_grouping(first_digit_start: usize, first_digit_end: usize) -> Vec> { - let mut res: Vec> = Vec::new(); - let mut first_digit_start = first_digit_start + 1; - while first_digit_start < first_digit_end { - res.push(vec![first_digit_start]); - first_digit_start += 1; - } - - res -} - -/// Returns the set of decomposed prefixes that cover the range [start, end]. -pub(crate) fn group_by_ignoring_digits( - start: usize, - end: usize, - base: usize, - num_digits: usize, -) -> Vec> { - let prefix_range = separate_prefix(start, end, base, num_digits); - let start_is_all_zeros = prefix_range.start.iter().all(|x| *x == 0); - let end_is_all_max = prefix_range.end.iter().all(|x| *x == base - 1); - - if start == end || start_is_all_zeros && end_is_all_max && !prefix_range.prefix.is_empty() { - return vec![prefix_range.prefix]; - } - let mut res: Vec> = Vec::new(); - if prefix_range.prefix.len() == num_digits - 1 { - for i in prefix_range.start[prefix_range.start.len() - 1] - ..prefix_range.end[prefix_range.end.len() - 1] + 1 - { - let mut new_res = prefix_range.prefix.clone(); - new_res.push(i); - res.push(new_res) - } - } else { - let mut front = front_groupings(prefix_range.start.clone(), base); - let mut middle = middle_grouping(prefix_range.start[0], prefix_range.end[0]); - let mut back = back_groupings(prefix_range.end.clone(), base); - res.append(&mut front); - res.append(&mut middle); - res.append(&mut back); - res = res - .into_iter() - .map(|x| { - prefix_range - .prefix - .iter() - .cloned() - .chain(x.into_iter()) - .collect() - }) - .collect(); - } - - res -} - -#[cfg(test)] -mod tests { - struct DecompositionTestCase { - composed: usize, - decomposed: Vec, - base: usize, - nb_digits: usize, - } - - struct GroupingTestCase { - start_index: usize, - end_index: usize, - base: usize, - nb_digits: usize, - expected: Vec>, - } - fn decomposition_test_cases() -> Vec { - vec![ - DecompositionTestCase { - composed: 123456789, - decomposed: vec![1, 2, 3, 4, 5, 6, 7, 8, 9], - base: 10, - nb_digits: 9, - }, - DecompositionTestCase { - composed: 4321, - decomposed: vec![1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1], - base: 2, - nb_digits: 13, - }, - DecompositionTestCase { - composed: 0, - decomposed: vec![0, 0, 0, 0], - base: 8, - nb_digits: 4, - }, - DecompositionTestCase { - composed: 2, - decomposed: vec![0, 2], - base: 10, - nb_digits: 2, - }, - DecompositionTestCase { - composed: 1, - decomposed: vec![1], - base: 2, - nb_digits: 1, - }, - ] - } - - fn grouping_test_cases() -> Vec { - vec![ - GroupingTestCase { - start_index: 123, - end_index: 123, - base: 10, - nb_digits: 3, - expected: vec![vec![1, 2, 3]], - }, - GroupingTestCase { - start_index: 171, - end_index: 210, - base: 16, - nb_digits: 2, - expected: vec![ - vec![10, 11], - vec![10, 12], - vec![10, 13], - vec![10, 14], - vec![10, 15], - vec![11], - vec![12], - vec![13, 0], - vec![13, 1], - vec![13, 2], - ], - }, - GroupingTestCase { - start_index: 73899, - end_index: 73938, - base: 16, - nb_digits: 6, - expected: vec![ - vec![0, 1, 2, 0, 10, 11], - vec![0, 1, 2, 0, 10, 12], - vec![0, 1, 2, 0, 10, 13], - vec![0, 1, 2, 0, 10, 14], - vec![0, 1, 2, 0, 10, 15], - vec![0, 1, 2, 0, 11], - vec![0, 1, 2, 0, 12], - vec![0, 1, 2, 0, 13, 0], - vec![0, 1, 2, 0, 13, 1], - vec![0, 1, 2, 0, 13, 2], - ], - }, - GroupingTestCase { - start_index: 1234, - end_index: 4321, - base: 10, - nb_digits: 4, - expected: vec![ - vec![1, 2, 3, 4], - vec![1, 2, 3, 5], - vec![1, 2, 3, 6], - vec![1, 2, 3, 7], - vec![1, 2, 3, 8], - vec![1, 2, 3, 9], - vec![1, 2, 4], - vec![1, 2, 5], - vec![1, 2, 6], - vec![1, 2, 7], - vec![1, 2, 8], - vec![1, 2, 9], - vec![1, 3], - vec![1, 4], - vec![1, 5], - vec![1, 6], - vec![1, 7], - vec![1, 8], - vec![1, 9], - vec![2], - vec![3], - vec![4, 0], - vec![4, 1], - vec![4, 2], - vec![4, 3, 0], - vec![4, 3, 1], - vec![4, 3, 2, 0], - vec![4, 3, 2, 1], - ], - }, - GroupingTestCase { - start_index: 1201234, - end_index: 1204321, - base: 10, - nb_digits: 8, - expected: vec![ - vec![0, 1, 2, 0, 1, 2, 3, 4], - vec![0, 1, 2, 0, 1, 2, 3, 5], - vec![0, 1, 2, 0, 1, 2, 3, 6], - vec![0, 1, 2, 0, 1, 2, 3, 7], - vec![0, 1, 2, 0, 1, 2, 3, 8], - vec![0, 1, 2, 0, 1, 2, 3, 9], - vec![0, 1, 2, 0, 1, 2, 4], - vec![0, 1, 2, 0, 1, 2, 5], - vec![0, 1, 2, 0, 1, 2, 6], - vec![0, 1, 2, 0, 1, 2, 7], - vec![0, 1, 2, 0, 1, 2, 8], - vec![0, 1, 2, 0, 1, 2, 9], - vec![0, 1, 2, 0, 1, 3], - vec![0, 1, 2, 0, 1, 4], - vec![0, 1, 2, 0, 1, 5], - vec![0, 1, 2, 0, 1, 6], - vec![0, 1, 2, 0, 1, 7], - vec![0, 1, 2, 0, 1, 8], - vec![0, 1, 2, 0, 1, 9], - vec![0, 1, 2, 0, 2], - vec![0, 1, 2, 0, 3], - vec![0, 1, 2, 0, 4, 0], - vec![0, 1, 2, 0, 4, 1], - vec![0, 1, 2, 0, 4, 2], - vec![0, 1, 2, 0, 4, 3, 0], - vec![0, 1, 2, 0, 4, 3, 1], - vec![0, 1, 2, 0, 4, 3, 2, 0], - vec![0, 1, 2, 0, 4, 3, 2, 1], - ], - }, - GroupingTestCase { - start_index: 2200, - end_index: 4999, - base: 10, - nb_digits: 4, - expected: vec![ - vec![2, 2], - vec![2, 3], - vec![2, 4], - vec![2, 5], - vec![2, 6], - vec![2, 7], - vec![2, 8], - vec![2, 9], - vec![3], - vec![4], - ], - }, - GroupingTestCase { - start_index: 0, - end_index: 99, - base: 10, - nb_digits: 2, - expected: vec![ - vec![0], - vec![1], - vec![2], - vec![3], - vec![4], - vec![5], - vec![6], - vec![7], - vec![8], - vec![9], - ], - }, - GroupingTestCase { - start_index: 100, - end_index: 199, - base: 10, - nb_digits: 3, - expected: vec![vec![1]], - }, - GroupingTestCase { - start_index: 100, - end_index: 200, - base: 10, - nb_digits: 3, - expected: vec![vec![1], vec![2, 0, 0]], - }, - GroupingTestCase { - start_index: 11, - end_index: 18, - base: 10, - nb_digits: 2, - expected: vec![ - vec![1, 1], - vec![1, 2], - vec![1, 3], - vec![1, 4], - vec![1, 5], - vec![1, 6], - vec![1, 7], - vec![1, 8], - ], - }, - GroupingTestCase { - start_index: 11, - end_index: 23, - base: 2, - nb_digits: 5, - expected: vec![vec![0, 1, 0, 1, 1], vec![0, 1, 1], vec![1, 0]], - }, - GroupingTestCase { - start_index: 5677, - end_index: 8621, - base: 2, - nb_digits: 14, - expected: vec![ - vec![0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1], - vec![0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1], - vec![0, 1, 0, 1, 1, 0, 0, 0, 1, 1], - vec![0, 1, 0, 1, 1, 0, 0, 1], - vec![0, 1, 0, 1, 1, 0, 1], - vec![0, 1, 0, 1, 1, 1], - vec![0, 1, 1], - vec![1, 0, 0, 0, 0, 0], - vec![1, 0, 0, 0, 0, 1, 0], - vec![1, 0, 0, 0, 0, 1, 1, 0, 0], - vec![1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0], - vec![1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0], - vec![1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0], - ], - }, - ] - } - - #[test] - fn decompose_value_test() { - for test_case in decomposition_test_cases() { - assert_eq!( - test_case.decomposed, - super::decompose_value(test_case.composed, test_case.base, test_case.nb_digits) - ); - } - } - - #[test] - fn group_by_ignoring_digits_test() { - for test_case in grouping_test_cases() { - assert_eq!( - test_case.expected, - super::group_by_ignoring_digits( - test_case.start_index, - test_case.end_index, - test_case.base, - test_case.nb_digits - ) - ); - } - } -} diff --git a/cfd_protocol/src/lib.rs b/cfd_protocol/src/lib.rs deleted file mode 100644 index 597e7a5..0000000 --- a/cfd_protocol/src/lib.rs +++ /dev/null @@ -1,12 +0,0 @@ -mod oracle; -mod protocol; - -pub mod interval; - -pub use protocol::{ - close_transaction, commit_descriptor, compute_adaptor_pk, create_cfd_transactions, - 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/oracle.rs b/cfd_protocol/src/oracle.rs deleted file mode 100644 index 4233e43..0000000 --- a/cfd_protocol/src/oracle.rs +++ /dev/null @@ -1,40 +0,0 @@ -pub use secp256k1_zkp::*; - -use anyhow::Result; -use secp256k1_zkp::schnorrsig; -use std::num::NonZeroU8; - -/// Compute an attestation public key for the given oracle public key, -/// announcement nonce public key and outcome index. -pub fn attestation_pk( - oracle_pk: &schnorrsig::PublicKey, - nonce_pk: &schnorrsig::PublicKey, - index: NonZeroU8, -) -> Result { - let nonce_pk = schnorr_pubkey_to_pubkey(nonce_pk); - - let mut nonce_pk_sum = nonce_pk; - nonce_pk_sum.mul_assign(SECP256K1, &index_to_bytes(index))?; - - let oracle_pk = schnorr_pubkey_to_pubkey(oracle_pk); - let attestation_pk = oracle_pk.combine(&nonce_pk_sum)?; - - Ok(attestation_pk) -} - -fn schnorr_pubkey_to_pubkey(pk: &schnorrsig::PublicKey) -> secp256k1_zkp::PublicKey { - let mut buf = Vec::::with_capacity(33); - - buf.push(0x02); // append even byte - buf.extend(&pk.serialize()); - - secp256k1_zkp::PublicKey::from_slice(&buf).expect("valid key") -} - -fn index_to_bytes(index: NonZeroU8) -> [u8; 32] { - let mut bytes = [0u8; 32]; - - bytes[31] = index.get(); - - bytes -} diff --git a/cfd_protocol/src/protocol.rs b/cfd_protocol/src/protocol.rs deleted file mode 100644 index 09293fa..0000000 --- a/cfd_protocol/src/protocol.rs +++ /dev/null @@ -1,573 +0,0 @@ -pub use transaction_ext::TransactionExt; -pub use transactions::{close_transaction, punish_transaction}; - -use crate::protocol::sighash_ext::SigHashExt; -use crate::protocol::transactions::{ - lock_transaction, CommitTransaction, ContractExecutionTransaction as ContractExecutionTx, - RefundTransaction, -}; -use crate::{interval, oracle}; -use anyhow::{bail, Context, Result}; -use bdk::bitcoin::hashes::hex::ToHex; -use bdk::bitcoin::util::bip143::SigHashCache; -use bdk::bitcoin::util::psbt::PartiallySignedTransaction; -use bdk::bitcoin::{Address, Amount, PublicKey, SigHashType, Transaction, TxOut}; -use bdk::database::BatchDatabase; -use bdk::descriptor::Descriptor; -use bdk::miniscript::descriptor::Wsh; -use bdk::miniscript::DescriptorTrait; -use bdk::wallet::AddressIndex; -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; - -mod sighash_ext; -mod transaction_ext; -mod transactions; -mod txin_ext; - -/// Static script to be used to create lock tx -const DUMMY_2OF2_MULTISIG: &str = - "0020b5aa99ed7e0fa92483eb045ab8b7a59146d4d9f6653f21ba729b4331895a5b46"; - -pub trait WalletExt { - fn build_party_params(&self, amount: Amount, identity_pk: PublicKey) -> Result; -} - -impl WalletExt for bdk::Wallet -where - D: BatchDatabase, -{ - fn build_party_params(&self, amount: Amount, identity_pk: PublicKey) -> Result { - let mut builder = self.build_tx(); - builder - .ordering(bdk::wallet::tx_builder::TxOrdering::Bip69Lexicographic) - .fee_rate(FeeRate::from_sat_per_vb(1.0)) - .add_recipient( - DUMMY_2OF2_MULTISIG.parse().expect("Should be valid script"), - amount.as_sat(), - ); - let (lock_psbt, _) = builder.finish()?; - let address = self.get_address(AddressIndex::New)?.address; - Ok(PartyParams { - lock_psbt, - identity_pk, - lock_amount: amount, - address, - }) - } -} - -/// Build all the transactions and some of the signatures and -/// encrypted signatures needed to perform the CFD protocol. -/// -/// # Arguments -/// -/// * `maker` - The initial parameters of the maker. -/// * `maker_punish_params` - The punish parameters of the maker. -/// * `taker` - The initial parameters of the taker. -/// * `taker_punish_params` - The punish parameters of the taker. -/// * `oracle_pk` - The public key of the oracle. -/// * `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_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: schnorrsig::PublicKey, - (cet_timelock, refund_timelock): (u32, u32), - payouts_per_event: HashMap>, - identity_sk: SecretKey, -) -> Result { - let lock_tx = lock_transaction( - maker.lock_psbt.clone(), - taker.lock_psbt.clone(), - maker.identity_pk, - taker.identity_pk, - maker.lock_amount + taker.lock_amount, - ); - - build_cfds( - lock_tx, - ( - maker.identity_pk, - maker.lock_amount, - maker.address, - maker_punish_params, - ), - ( - taker.identity_pk, - taker.lock_amount, - taker.address, - taker_punish_params, - ), - oracle_pk, - (cet_timelock, refund_timelock), - payouts_per_event, - identity_sk, - ) -} - -pub fn renew_cfd_transactions( - lock_tx: PartiallySignedTransaction, - (maker_pk, maker_lock_amount, maker_address, maker_punish_params): ( - PublicKey, - Amount, - Address, - PunishParams, - ), - (taker_pk, taker_lock_amount, taker_address, taker_punish_params): ( - PublicKey, - Amount, - Address, - PunishParams, - ), - oracle_pk: schnorrsig::PublicKey, - (cet_timelock, refund_timelock): (u32, u32), - payouts_per_event: HashMap>, - identity_sk: SecretKey, -) -> Result { - build_cfds( - lock_tx, - ( - maker_pk, - maker_lock_amount, - maker_address, - maker_punish_params, - ), - ( - taker_pk, - taker_lock_amount, - taker_address, - taker_punish_params, - ), - oracle_pk, - (cet_timelock, refund_timelock), - payouts_per_event, - identity_sk, - ) -} - -fn build_cfds( - lock_tx: PartiallySignedTransaction, - (maker_pk, maker_lock_amount, maker_address, maker_punish_params): ( - PublicKey, - Amount, - Address, - PunishParams, - ), - (taker_pk, taker_lock_amount, taker_address, taker_punish_params): ( - PublicKey, - Amount, - Address, - PunishParams, - ), - oracle_pk: schnorrsig::PublicKey, - (cet_timelock, refund_timelock): (u32, u32), - payouts_per_event: HashMap>, - identity_sk: SecretKey, -) -> Result { - let commit_tx = CommitTransaction::new( - &lock_tx.global.unsigned_tx, - ( - maker_pk, - maker_punish_params.revocation_pk, - maker_punish_params.publish_pk, - ), - ( - taker_pk, - taker_punish_params.revocation_pk, - taker_punish_params.publish_pk, - ), - ) - .context("cannot build commit tx")?; - - let identity_pk = secp256k1_zkp::PublicKey::from_secret_key(SECP256K1, &identity_sk); - let commit_encsig = if identity_pk == maker_pk.key { - commit_tx.encsign(identity_sk, &taker_punish_params.publish_pk) - } else if identity_pk == taker_pk.key { - commit_tx.encsign(identity_sk, &maker_punish_params.publish_pk) - } else { - bail!("identity sk does not belong to taker or maker") - }; - - let refund = { - let tx = RefundTransaction::new( - &commit_tx, - refund_timelock, - &maker_address, - &taker_address, - maker_lock_amount, - taker_lock_amount, - ); - - let sighash = tx.sighash().to_message(); - let sig = SECP256K1.sign(&sighash, &identity_sk); - - (tx.into_inner(), sig) - }; - - 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, - commit: (commit_tx.into_inner(), commit_encsig), - cets, - refund, - }) -} - -pub fn lock_descriptor(maker_pk: PublicKey, taker_pk: PublicKey) -> Descriptor { - const MINISCRIPT_TEMPLATE: &str = "c:and_v(v:pk(A),pk_k(B))"; - - let maker_pk = ToHex::to_hex(&maker_pk.key); - let taker_pk = ToHex::to_hex(&taker_pk.key); - - let miniscript = MINISCRIPT_TEMPLATE - .replace("A", &maker_pk) - .replace("B", &taker_pk); - - let miniscript = miniscript.parse().expect("a valid miniscript"); - - Descriptor::Wsh(Wsh::new(miniscript).expect("a valid descriptor")) -} - -pub fn commit_descriptor( - (maker_own_pk, maker_rev_pk, maker_publish_pk): (PublicKey, PublicKey, PublicKey), - (taker_own_pk, taker_rev_pk, taker_publish_pk): (PublicKey, PublicKey, PublicKey), -) -> Descriptor { - let maker_own_pk_hash = maker_own_pk.pubkey_hash().as_hash(); - let maker_own_pk = (&maker_own_pk.key.serialize().to_vec()).to_hex(); - let maker_publish_pk_hash = maker_publish_pk.pubkey_hash().as_hash(); - let maker_rev_pk_hash = maker_rev_pk.pubkey_hash().as_hash(); - - let taker_own_pk_hash = taker_own_pk.pubkey_hash().as_hash(); - let taker_own_pk = (&taker_own_pk.key.serialize().to_vec()).to_hex(); - let taker_publish_pk_hash = taker_publish_pk.pubkey_hash().as_hash(); - let taker_rev_pk_hash = taker_rev_pk.pubkey_hash().as_hash(); - - // raw script: - // or(and(pk(maker_own_pk),pk(taker_own_pk)),or(and(pk(maker_own_pk),and(pk(taker_publish_pk), - // pk(taker_rev_pk))),and(pk(taker_own_pk),and(pk(maker_publish_pk),pk(maker_rev_pk))))) - let full_script = format!("wsh(c:andor(pk({maker_own_pk}),pk_k({taker_own_pk}),or_i(and_v(v:pkh({maker_own_pk_hash}),and_v(v:pkh({taker_publish_pk_hash}),pk_h({taker_rev_pk_hash}))),and_v(v:pkh({taker_own_pk_hash}),and_v(v:pkh({maker_publish_pk_hash}),pk_h({maker_rev_pk_hash}))))))", - maker_own_pk = maker_own_pk, - taker_own_pk = taker_own_pk, - maker_own_pk_hash = maker_own_pk_hash, - taker_own_pk_hash = taker_own_pk_hash, - taker_publish_pk_hash = taker_publish_pk_hash, - taker_rev_pk_hash = taker_rev_pk_hash, - maker_publish_pk_hash = maker_publish_pk_hash, - maker_rev_pk_hash = maker_rev_pk_hash - ); - - full_script.parse().expect("a valid miniscript") -} - -pub fn spending_tx_sighash( - spending_tx: &Transaction, - spent_descriptor: &Descriptor, - spent_amount: Amount, -) -> secp256k1_zkp::Message { - let sighash = SigHashCache::new(spending_tx).signature_hash( - 0, - &spent_descriptor.script_code(), - spent_amount.as_sat(), - SigHashType::All, - ); - sighash.to_message() -} - -pub fn finalize_spend_transaction( - mut tx: Transaction, - spent_descriptor: &Descriptor, - (pk_0, sig_0): (PublicKey, Signature), - (pk_1, sig_1): (PublicKey, Signature), -) -> Result { - let satisfier = HashMap::from_iter(vec![ - (pk_0, (sig_0, SigHashType::All)), - (pk_1, (sig_1, SigHashType::All)), - ]); - - let input = tx - .input - .iter_mut() - .exactly_one() - .expect("all spend transactions to have one input"); - spent_descriptor.satisfy(input, satisfier)?; - - Ok(tx) -} - -#[derive(Clone)] -pub struct PartyParams { - pub lock_psbt: PartiallySignedTransaction, - pub identity_pk: PublicKey, - pub lock_amount: Amount, - pub address: Address, -} - -#[derive(Debug, Copy, Clone)] -pub struct PunishParams { - pub revocation_pk: PublicKey, - pub publish_pk: PublicKey, -} - -#[derive(Debug, Clone)] -pub struct CfdTransactions { - pub lock: PartiallySignedTransaction, - pub commit: (Transaction, EcdsaAdaptorSignature), - 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, PartialEq)] -pub struct Payout { - digits: interval::Digits, - maker_amount: Amount, - taker_amount: Amount, -} - -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 { - pub fn digits(&self) -> &interval::Digits { - &self.digits - } - - pub fn maker_amount(&self) -> &Amount { - &self.maker_amount - } - - pub fn taker_amount(&self) -> &Amount { - &self.taker_amount - } - - fn into_txouts(self, maker_address: &Address, taker_address: &Address) -> Vec { - let txouts = [ - (self.maker_amount, maker_address), - (self.taker_amount, taker_address), - ] - .iter() - .filter_map(|(amount, address)| { - let script_pubkey = address.script_pubkey(); - let dust_limit = script_pubkey.dust_value(); - (amount >= &dust_limit).then(|| TxOut { - value: amount.as_sat(), - script_pubkey, - }) - }) - .collect::>(); - - txouts - } - - /// Subtracts fee fairly from both outputs - /// - /// We need to consider a few cases: - /// - If both amounts are >= DUST, they share the fee equally - /// - If one amount is < DUST, it set to 0 and the other output needs to cover for the fee. - fn with_updated_fee( - self, - fee: Amount, - dust_limit_maker: Amount, - dust_limit_taker: Amount, - ) -> Result { - let maker_amount = self.maker_amount; - let taker_amount = self.taker_amount; - - let mut updated = self; - match ( - maker_amount - .checked_sub(fee / 2) - .map(|a| a > dust_limit_maker) - .unwrap_or(false), - taker_amount - .checked_sub(fee / 2) - .map(|a| a > dust_limit_taker) - .unwrap_or(false), - ) { - (true, true) => { - updated.maker_amount -= fee / 2; - updated.taker_amount -= fee / 2; - } - (false, true) => { - updated.maker_amount = Amount::ZERO; - updated.taker_amount = taker_amount - (fee + maker_amount); - } - (true, false) => { - updated.maker_amount = maker_amount - (fee + taker_amount); - updated.taker_amount = Amount::ZERO; - } - (false, false) => bail!("Amounts are too small, could not subtract fee."), - } - Ok(updated) - } -} - -pub fn compute_adaptor_pk( - oracle_pk: &schnorrsig::PublicKey, - index_nonce_pairs: &[(NonZeroU8, schnorrsig::PublicKey)], -) -> Result { - let attestation_pks = index_nonce_pairs - .iter() - .map(|(index, nonce_pk)| oracle::attestation_pk(oracle_pk, nonce_pk, *index)) - .collect::>>()?; - let adaptor_pk = secp256k1_zkp::PublicKey::combine_keys( - attestation_pks.iter().collect::>().as_slice(), - )?; - - Ok(adaptor_pk) -} - -#[cfg(test)] -mod tests { - use super::*; - - use bdk::bitcoin::Network; - - // TODO add proptest for this - - #[test] - fn test_fee_subtraction_bigger_than_dust() { - let key = "032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af" - .parse() - .unwrap(); - let dummy_address = Address::p2wpkh(&key, Network::Regtest).unwrap(); - let dummy_dust_limit = dummy_address.script_pubkey().dust_value(); - - let orig_maker_amount = 1000; - let orig_taker_amount = 1000; - let payouts = generate_payouts( - 0..=10_000, - Amount::from_sat(orig_maker_amount), - Amount::from_sat(orig_taker_amount), - ) - .unwrap(); - let fee = 100; - - for payout in payouts { - let updated_payout = payout - .with_updated_fee(Amount::from_sat(fee), dummy_dust_limit, dummy_dust_limit) - .unwrap(); - - assert_eq!( - updated_payout.maker_amount, - Amount::from_sat(orig_maker_amount - fee / 2) - ); - assert_eq!( - updated_payout.taker_amount, - Amount::from_sat(orig_taker_amount - fee / 2) - ); - } - } - - #[test] - fn test_fee_subtraction_smaller_than_dust() { - let key = "032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af" - .parse() - .unwrap(); - let dummy_address = Address::p2wpkh(&key, Network::Regtest).unwrap(); - let dummy_dust_limit = dummy_address.script_pubkey().dust_value(); - - let orig_maker_amount = dummy_dust_limit.as_sat() - 1; - let orig_taker_amount = 1000; - let payouts = generate_payouts( - 0..=10_000, - Amount::from_sat(orig_maker_amount), - Amount::from_sat(orig_taker_amount), - ) - .unwrap(); - let fee = 100; - - for payout in payouts { - let updated_payout = payout - .with_updated_fee(Amount::from_sat(fee), dummy_dust_limit, dummy_dust_limit) - .unwrap(); - - assert_eq!(updated_payout.maker_amount, Amount::from_sat(0)); - assert_eq!( - updated_payout.taker_amount, - Amount::from_sat(orig_taker_amount - (fee + orig_maker_amount)) - ); - } - } -} diff --git a/cfd_protocol/src/protocol/sighash_ext.rs b/cfd_protocol/src/protocol/sighash_ext.rs deleted file mode 100644 index 3674fb1..0000000 --- a/cfd_protocol/src/protocol/sighash_ext.rs +++ /dev/null @@ -1,15 +0,0 @@ -use bdk::bitcoin::hashes::Hash; -use bdk::bitcoin::SigHash; - -pub(crate) trait SigHashExt { - fn to_message(self) -> secp256k1_zkp::Message; -} - -impl SigHashExt for SigHash { - fn to_message(self) -> secp256k1_zkp::Message { - use secp256k1_zkp::bitcoin_hashes::Hash; - let hash = secp256k1_zkp::bitcoin_hashes::sha256d::Hash::from_inner(*self.as_inner()); - - hash.into() - } -} diff --git a/cfd_protocol/src/protocol/transaction_ext.rs b/cfd_protocol/src/protocol/transaction_ext.rs deleted file mode 100644 index effab95..0000000 --- a/cfd_protocol/src/protocol/transaction_ext.rs +++ /dev/null @@ -1,26 +0,0 @@ -use anyhow::{Context, Result}; -use bdk::bitcoin::{OutPoint, Script, Transaction}; - -pub trait TransactionExt { - fn get_virtual_size(&self) -> f64; - fn outpoint(&self, script_pubkey: &Script) -> Result; -} - -impl TransactionExt for Transaction { - fn get_virtual_size(&self) -> f64 { - self.get_weight() as f64 / 4.0 - } - - fn outpoint(&self, script_pubkey: &Script) -> Result { - let vout = self - .output - .iter() - .position(|out| &out.script_pubkey == script_pubkey) - .context("script pubkey not found in tx")?; - - Ok(OutPoint { - txid: self.txid(), - vout: vout as u32, - }) - } -} diff --git a/cfd_protocol/src/protocol/transactions.rs b/cfd_protocol/src/protocol/transactions.rs deleted file mode 100644 index 3cf065d..0000000 --- a/cfd_protocol/src/protocol/transactions.rs +++ /dev/null @@ -1,596 +0,0 @@ -use crate::protocol::sighash_ext::SigHashExt; -use crate::protocol::transaction_ext::TransactionExt; -use crate::protocol::txin_ext::TxInExt; -use crate::protocol::{ - commit_descriptor, compute_adaptor_pk, lock_descriptor, Payout, DUMMY_2OF2_MULTISIG, -}; -use anyhow::{Context, Result}; -use bdk::bitcoin::util::bip143::SigHashCache; -use bdk::bitcoin::util::psbt::{Global, PartiallySignedTransaction}; -use bdk::bitcoin::{ - Address, Amount, OutPoint, PublicKey, SigHash, SigHashType, Transaction, TxIn, TxOut, -}; -use bdk::descriptor::Descriptor; -use bdk::miniscript::DescriptorTrait; -use itertools::Itertools; -use secp256k1_zkp::{self, schnorrsig, EcdsaAdaptorSignature, SecretKey, SECP256K1}; -use std::collections::HashMap; -use std::iter::FromIterator; -use std::num::NonZeroU8; - -/// In satoshi per vbyte. -const SATS_PER_VBYTE: f64 = 1.0; - -pub(crate) fn lock_transaction( - maker_psbt: PartiallySignedTransaction, - taker_psbt: PartiallySignedTransaction, - maker_pk: PublicKey, - taker_pk: PublicKey, - amount: Amount, -) -> PartiallySignedTransaction { - let lock_descriptor = lock_descriptor(maker_pk, taker_pk); - - let maker_change = maker_psbt - .global - .unsigned_tx - .output - .into_iter() - .filter(|out| { - out.script_pubkey != DUMMY_2OF2_MULTISIG.parse().expect("To be a valid script") - }) - .collect::>(); - - let taker_change = taker_psbt - .global - .unsigned_tx - .output - .into_iter() - .filter(|out| { - out.script_pubkey != DUMMY_2OF2_MULTISIG.parse().expect("To be a valid script") - }) - .collect::>(); - - let lock_output = TxOut { - value: amount.as_sat(), - script_pubkey: lock_descriptor.script_pubkey(), - }; - - let input = vec![ - maker_psbt.global.unsigned_tx.input, - taker_psbt.global.unsigned_tx.input, - ] - .concat(); - - let output = std::iter::once(lock_output) - .chain(maker_change) - .chain(taker_change) - .collect(); - - let lock_tx = Transaction { - version: 2, - lock_time: 0, - input, - output, - }; - - PartiallySignedTransaction { - global: Global::from_unsigned_tx(lock_tx).expect("to be unsigned"), - inputs: vec![maker_psbt.inputs, taker_psbt.inputs].concat(), - outputs: vec![maker_psbt.outputs, taker_psbt.outputs].concat(), - } -} - -#[derive(Debug, Clone)] -pub(crate) struct CommitTransaction { - inner: Transaction, - descriptor: Descriptor, - amount: Amount, - sighash: SigHash, - lock_descriptor: Descriptor, - fee: Fee, -} - -impl CommitTransaction { - /// Expected size of signed transaction in virtual bytes, plus a - /// buffer to account for different signature lengths. - const SIGNED_VBYTES: f64 = 148.5 + (3.0 * 2.0) / 4.0; - - pub(crate) fn new( - lock_tx: &Transaction, - (maker_pk, maker_rev_pk, maker_publish_pk): (PublicKey, PublicKey, PublicKey), - (taker_pk, taker_rev_pk, taker_publish_pk): (PublicKey, PublicKey, PublicKey), - ) -> Result { - let lock_descriptor = lock_descriptor(maker_pk, taker_pk); - let (lock_outpoint, lock_amount) = { - let outpoint = lock_tx - .outpoint(&lock_descriptor.script_pubkey()) - .context("lock script not found in lock tx")?; - let amount = lock_tx.output[outpoint.vout as usize].value; - - (outpoint, amount) - }; - - let lock_input = TxIn { - previous_output: lock_outpoint, - ..Default::default() - }; - - let descriptor = commit_descriptor( - (maker_pk, maker_rev_pk, maker_publish_pk), - (taker_pk, taker_rev_pk, taker_publish_pk), - ); - - let output = TxOut { - value: lock_amount, - script_pubkey: descriptor.script_pubkey(), - }; - - let mut inner = Transaction { - version: 2, - lock_time: 0, - input: vec![lock_input], - output: vec![output], - }; - let fee = Fee::new(Self::SIGNED_VBYTES); - - let commit_tx_amount = lock_amount - fee.as_u64(); - inner.output[0].value = commit_tx_amount; - - let sighash = SigHashCache::new(&inner).signature_hash( - 0, - &lock_descriptor.script_code(), - lock_amount, - SigHashType::All, - ); - - Ok(Self { - inner, - descriptor, - lock_descriptor, - amount: Amount::from_sat(commit_tx_amount), - sighash, - fee, - }) - } - - pub(crate) fn encsign( - &self, - sk: SecretKey, - publish_them_pk: &PublicKey, - ) -> EcdsaAdaptorSignature { - EcdsaAdaptorSignature::encrypt( - SECP256K1, - &self.sighash.to_message(), - &sk, - &publish_them_pk.key, - ) - } - - pub(crate) fn into_inner(self) -> Transaction { - self.inner - } - - fn outpoint(&self) -> OutPoint { - self.inner - .outpoint(&self.descriptor.script_pubkey()) - .expect("to find commit output in commit tx") - } - - fn amount(&self) -> Amount { - self.amount - } - - fn descriptor(&self) -> Descriptor { - self.descriptor.clone() - } - - fn fee(&self) -> u64 { - self.fee.as_u64() - } -} - -#[derive(Debug, Clone)] -pub(crate) struct ContractExecutionTransaction { - inner: Transaction, - index_nonce_pairs: Vec<(NonZeroU8, schnorrsig::PublicKey)>, - sighash: SigHash, - commit_descriptor: Descriptor, -} - -impl ContractExecutionTransaction { - /// Expected size of signed transaction in virtual bytes, plus a - /// buffer to account for different signature lengths. - const SIGNED_VBYTES: f64 = 206.5 + (3.0 * 2.0) / 4.0; - - pub(crate) fn new( - commit_tx: &CommitTransaction, - payout: Payout, - maker_address: &Address, - taker_address: &Address, - nonce_pks: &[schnorrsig::PublicKey], - relative_timelock_in_blocks: u32, - ) -> Result { - let index_nonce_pairs: Vec<_> = payout - .digits - .to_indices() - .into_iter() - .zip(nonce_pks.iter().cloned()) - .collect(); - - let commit_input = TxIn { - previous_output: commit_tx.outpoint(), - sequence: relative_timelock_in_blocks, - ..Default::default() - }; - - let fee = Fee::new(Self::SIGNED_VBYTES).add(commit_tx.fee() as f64); - let output = payout - .with_updated_fee( - Amount::from_sat(fee.as_u64()), - maker_address.script_pubkey().dust_value(), - taker_address.script_pubkey().dust_value(), - )? - .into_txouts(maker_address, taker_address); - - let tx = Transaction { - version: 2, - lock_time: 0, - input: vec![commit_input], - output, - }; - - let sighash = SigHashCache::new(&tx).signature_hash( - 0, - &commit_tx.descriptor.script_code(), - commit_tx.amount.as_sat(), - SigHashType::All, - ); - - Ok(Self { - inner: tx, - index_nonce_pairs, - sighash, - commit_descriptor: commit_tx.descriptor(), - }) - } - - pub(crate) fn encsign( - &self, - sk: SecretKey, - oracle_pk: &schnorrsig::PublicKey, - ) -> Result { - let adaptor_point = compute_adaptor_pk(oracle_pk, &self.index_nonce_pairs)?; - - Ok(EcdsaAdaptorSignature::encrypt( - SECP256K1, - &self.sighash.to_message(), - &sk, - &adaptor_point, - )) - } - - pub(crate) fn into_inner(self) -> Transaction { - self.inner - } -} - -#[derive(Debug, Clone)] -pub(crate) struct RefundTransaction { - inner: Transaction, - sighash: SigHash, - commit_output_descriptor: Descriptor, -} - -impl RefundTransaction { - /// Expected size of signed transaction in virtual bytes, plus a - /// buffer to account for different signature lengths. - const SIGNED_VBYTES: f64 = 206.5 + (3.0 * 2.0) / 4.0; - - pub(crate) fn new( - commit_tx: &CommitTransaction, - relative_locktime_in_blocks: u32, - maker_address: &Address, - taker_address: &Address, - maker_amount: Amount, - taker_amount: Amount, - ) -> Self { - let commit_input = TxIn { - previous_output: commit_tx.outpoint(), - sequence: relative_locktime_in_blocks, - ..Default::default() - }; - - let maker_output = TxOut { - value: maker_amount.as_sat(), - script_pubkey: maker_address.script_pubkey(), - }; - - let taker_output = TxOut { - value: taker_amount.as_sat(), - script_pubkey: taker_address.script_pubkey(), - }; - - let mut tx = Transaction { - version: 2, - lock_time: 0, - input: vec![commit_input], - output: vec![maker_output, taker_output], - }; - - let mut fee = Self::SIGNED_VBYTES * SATS_PER_VBYTE; - fee += commit_tx.fee() as f64; - tx.output[0].value -= (fee / 2.0) as u64; - tx.output[1].value -= (fee / 2.0) as u64; - - let commit_output_descriptor = commit_tx.descriptor(); - - let sighash = SigHashCache::new(&tx).signature_hash( - 0, - &commit_tx.descriptor().script_code(), - commit_tx.amount().as_sat(), - SigHashType::All, - ); - - Self { - inner: tx, - sighash, - commit_output_descriptor, - } - } - - pub(crate) fn sighash(&self) -> SigHash { - self.sighash - } - - pub(crate) fn into_inner(self) -> Transaction { - self.inner - } -} - -/// Build a transaction which closes the CFD contract. -/// -/// This transaction spends directly from the lock transaction. Both -/// parties must agree on the split of coins between `maker_amount` -/// and `taker_amount`. -pub fn close_transaction( - lock_descriptor: &Descriptor, - lock_outpoint: OutPoint, - lock_amount: Amount, - (maker_address, maker_amount): (&Address, Amount), - (taker_address, taker_amount): (&Address, Amount), -) -> Result<(Transaction, secp256k1_zkp::Message)> { - /// Expected size of signed transaction in virtual bytes, plus a - /// buffer to account for different signature lengths. - const SIGNED_VBYTES: f64 = 167.5 + (3.0 * 2.0) / 4.0; - - let lock_input = TxIn { - previous_output: lock_outpoint, - ..Default::default() - }; - - // TODO: The fee could take into account the network state in this - // case, since this transaction is to be broadcast immediately - // after building and signing it - let (maker_fee, taker_fee) = Fee::new(SIGNED_VBYTES).split(); - - let maker_output = TxOut { - value: maker_amount.as_sat() - maker_fee, - script_pubkey: maker_address.script_pubkey(), - }; - let taker_output = TxOut { - value: taker_amount.as_sat() - taker_fee, - script_pubkey: taker_address.script_pubkey(), - }; - - let tx = Transaction { - version: 2, - lock_time: 0, - input: vec![lock_input], - output: vec![maker_output, taker_output], - }; - - let sighash = SigHashCache::new(&tx) - .signature_hash( - 0, - &lock_descriptor.script_code(), - lock_amount.as_sat(), - SigHashType::All, - ) - .to_message(); - - Ok((tx, sighash)) -} - -pub fn punish_transaction( - commit_descriptor: &Descriptor, - address: &Address, - encsig: EcdsaAdaptorSignature, - sk: SecretKey, - revocation_them_sk: SecretKey, - pub_them_pk: PublicKey, - revoked_commit_tx: &Transaction, -) -> Result { - /// Expected size of signed transaction in virtual bytes, plus a - /// buffer to account for different signature lengths. - const SIGNED_VBYTES: f64 = 219.5 + (3.0 * 3.0) / 4.0; - - let input = revoked_commit_tx - .input - .clone() - .into_iter() - .exactly_one() - .context("commit transaction inputs != 1")?; - - let publish_them_sk = input - .find_map_signature(|sig| encsig.recover(SECP256K1, &sig, &pub_them_pk.key).ok()) - .context("could not recover publish sk from commit tx")?; - - let commit_outpoint = revoked_commit_tx - .outpoint(&commit_descriptor.script_pubkey()) - .expect("to find commit output in commit tx"); - let commit_amount = revoked_commit_tx.output[commit_outpoint.vout as usize].value; - - let mut punish_tx = { - let output = TxOut { - value: commit_amount, - script_pubkey: address.script_pubkey(), - }; - let mut tx = Transaction { - version: 2, - lock_time: 0, - input: vec![TxIn { - previous_output: commit_outpoint, - ..Default::default() - }], - output: vec![output], - }; - - let fee = SIGNED_VBYTES * SATS_PER_VBYTE; - tx.output[0].value = commit_amount - fee as u64; - - tx - }; - - let sighash = SigHashCache::new(&punish_tx).signature_hash( - 0, - &commit_descriptor.script_code(), - commit_amount, - SigHashType::All, - ); - - let satisfier = { - let pk = { - let key = secp256k1_zkp::PublicKey::from_secret_key(SECP256K1, &sk); - PublicKey { - compressed: true, - key, - } - }; - let pk_hash = pk.pubkey_hash().as_hash(); - let sig_sk = SECP256K1.sign(&sighash.to_message(), &sk); - - let pub_them_pk_hash = pub_them_pk.pubkey_hash().as_hash(); - let sig_pub_them = SECP256K1.sign(&sighash.to_message(), &publish_them_sk); - - let rev_them_pk = { - let key = secp256k1_zkp::PublicKey::from_secret_key(SECP256K1, &revocation_them_sk); - PublicKey { - compressed: true, - key, - } - }; - let rev_them_pk_hash = rev_them_pk.pubkey_hash().as_hash(); - let sig_rev_them = SECP256K1.sign(&sighash.to_message(), &revocation_them_sk); - - let sighash_all = SigHashType::All; - HashMap::from_iter(vec![ - (pk_hash, (pk, (sig_sk, sighash_all))), - (pub_them_pk_hash, (pub_them_pk, (sig_pub_them, sighash_all))), - (rev_them_pk_hash, (rev_them_pk, (sig_rev_them, sighash_all))), - ]) - }; - - commit_descriptor.satisfy(&mut punish_tx.input[0], satisfier)?; - - Ok(punish_tx) -} - -#[derive(Clone, Debug)] -struct Fee { - fee: f64, -} - -impl Fee { - fn new(signed_vbytes: f64) -> Self { - let fee = signed_vbytes * SATS_PER_VBYTE; - Self { fee } - } - - #[must_use] - fn add(self, number: f64) -> Fee { - Fee { - fee: self.fee + number, - } - } - - fn as_u64(&self) -> u64 { - // Ceil to prevent going lower than the min relay fee - self.fee.ceil() as u64 - } - - fn split(&self) -> (u64, u64) { - let half = self.as_u64() / 2; - (half as u64, self.as_u64() - half) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use proptest::prelude::*; - - proptest! { - #[test] - fn test_fee_always_above_min_relay_fee(signed_vbytes in 1.0f64..100_000_000.0f64) { - let fee = Fee::new(signed_vbytes); - let (maker_fee, taker_fee) = fee.split(); - - prop_assert!(signed_vbytes <= fee.as_u64() as f64); - prop_assert!(signed_vbytes <= (maker_fee + taker_fee) as f64); - } - } - - // A bunch of tests illustrating how fees are split - - #[test] - fn test_splitting_fee_1_0() { - const SIGNED_VBYTES_TEST: f64 = 1.0; - - let fee = Fee::new(SIGNED_VBYTES_TEST); - let (maker_fee, taker_fee) = fee.split(); - - assert_eq!(fee.as_u64(), 1); - assert_eq!(maker_fee, 0); - assert_eq!(taker_fee, 1); - assert!((maker_fee + taker_fee) as f64 >= SIGNED_VBYTES_TEST); - } - - #[test] - fn test_splitting_fee_2_0() { - const SIGNED_VBYTES_TEST: f64 = 2.0; - - let fee = Fee::new(SIGNED_VBYTES_TEST); - let (maker_fee, taker_fee) = fee.split(); - - assert_eq!(fee.as_u64(), 2); - assert_eq!(maker_fee, 1); - assert_eq!(taker_fee, 1); - assert!((maker_fee + taker_fee) as f64 >= SIGNED_VBYTES_TEST); - } - - #[test] - fn test_splitting_fee_2_1() { - const SIGNED_VBYTES_TEST: f64 = 2.1; - - let fee = Fee::new(SIGNED_VBYTES_TEST); - let (maker_fee, taker_fee) = fee.split(); - - assert_eq!(fee.as_u64(), 3); - assert_eq!(maker_fee, 1); - assert_eq!(taker_fee, 2); - assert!((maker_fee + taker_fee) as f64 >= SIGNED_VBYTES_TEST); - } - - #[test] - fn test_splitting_fee_2_6() { - const SIGNED_VBYTES_TEST: f64 = 2.6; - - let fee = Fee::new(SIGNED_VBYTES_TEST); - let (maker_fee, taker_fee) = fee.split(); - - assert_eq!(fee.as_u64(), 3); - assert_eq!(maker_fee, 1); - assert_eq!(taker_fee, 2); - assert!((maker_fee + taker_fee) as f64 >= SIGNED_VBYTES_TEST); - } -} diff --git a/cfd_protocol/src/protocol/txin_ext.rs b/cfd_protocol/src/protocol/txin_ext.rs deleted file mode 100644 index cc73136..0000000 --- a/cfd_protocol/src/protocol/txin_ext.rs +++ /dev/null @@ -1,23 +0,0 @@ -use bdk::bitcoin::secp256k1::Signature; -use bdk::bitcoin::TxIn; - -pub(crate) trait TxInExt { - fn find_map_signature(&self, f: F) -> Option - where - F: Fn(Signature) -> Option; -} - -impl TxInExt for TxIn { - fn find_map_signature(&self, f: F) -> Option - where - F: Fn(Signature) -> Option, - { - self.witness - .iter() - .filter_map(|elem| { - let elem = elem.as_slice(); - Signature::from_der(&elem[..elem.len() - 1]).ok() - }) - .find_map(f) - } -} diff --git a/cfd_protocol/tests/cfds.rs b/cfd_protocol/tests/cfds.rs deleted file mode 100644 index 0b29267..0000000 --- a/cfd_protocol/tests/cfds.rs +++ /dev/null @@ -1,1184 +0,0 @@ -use anyhow::{bail, Context, Result}; -use bdk::bitcoin::util::bip32::ExtendedPrivKey; -use bdk::bitcoin::{Address, Amount, Network, PrivateKey, PublicKey, Transaction}; -use bdk::descriptor::Descriptor; -use bdk::miniscript::DescriptorTrait; -use bdk::wallet::AddressIndex; -use bdk::SignOptions; -use bitcoin::util::psbt::PartiallySignedTransaction; -use cfd_protocol::{ - close_transaction, commit_descriptor, compute_adaptor_pk, create_cfd_transactions, - 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] -fn create_cfd() { - let mut rng = thread_rng(); - - let maker_lock_amount = Amount::ONE_BTC; - let taker_lock_amount = Amount::ONE_BTC; - - 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_0 = OliviaData::example_0(); - let oracle_data_1 = OliviaData::example_1(); - - 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; - - let (maker_cfd_txs, taker_cfd_txs, maker, taker, maker_addr, taker_addr) = create_cfd_txs( - &mut rng, - (&maker_wallet, maker_lock_amount), - (&taker_wallet, taker_lock_amount), - 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; - - let commit_desc = commit_descriptor( - (maker.pk, maker.rev_pk, maker.pub_pk), - (taker.pk, taker.rev_pk, taker.pub_pk), - ); - let commit_amount = Amount::from_sat(maker_cfd_txs.commit.0.output[0].value); - - verify_cfd_sigs( - (&maker_cfd_txs, maker.pk, maker.pub_pk), - (&taker_cfd_txs, taker.pk, taker.pub_pk), - (oracle_pk, vec![event_0, event_1]), - (&lock_desc, lock_amount), - (&commit_desc, commit_amount), - ); - - check_cfd_txs( - ( - maker_wallet, - maker_cfd_txs, - maker.sk, - maker.pk, - maker.pub_sk, - maker.pub_pk, - maker.rev_sk, - maker_addr, - ), - ( - taker_wallet, - taker_cfd_txs, - taker.sk, - taker.pk, - taker.pub_sk, - taker.pub_pk, - taker.rev_sk, - taker_addr, - ), - &[oracle_data_0, oracle_data_1], - (lock_desc, lock_amount), - (commit_desc, commit_amount), - ); -} - -#[test] -fn renew_cfd() { - let mut rng = thread_rng(); - - let maker_lock_amount = Amount::ONE_BTC; - let taker_lock_amount = Amount::ONE_BTC; - - 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_0(); - let oracle_pk = oracle_data.pk; - 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; - - let (maker_cfd_txs, taker_cfd_txs, maker, taker, maker_addr, taker_addr) = create_cfd_txs( - &mut rng, - (&maker_wallet, maker_lock_amount), - (&taker_wallet, taker_lock_amount), - oracle_pk, - payouts_per_event, - (cet_timelock, refund_timelock), - ); - - // renew cfd transactions - - let (maker_rev_sk, maker_rev_pk) = make_keypair(&mut rng); - let (maker_pub_sk, maker_pub_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 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, - ( - maker.pk, - maker_lock_amount, - maker_addr.clone(), - PunishParams { - revocation_pk: maker_rev_pk, - publish_pk: maker_pub_pk, - }, - ), - ( - taker.pk, - taker_lock_amount, - taker_addr.clone(), - PunishParams { - revocation_pk: taker_rev_pk, - publish_pk: taker_pub_pk, - }, - ), - oracle_pk, - (cet_timelock, refund_timelock), - payouts_per_event.clone(), - maker.sk, - ) - .unwrap(); - - let taker_cfd_txs = renew_cfd_transactions( - taker_cfd_txs.lock, - ( - maker.pk, - maker_lock_amount, - maker_addr.clone(), - PunishParams { - revocation_pk: maker_rev_pk, - publish_pk: maker_pub_pk, - }, - ), - ( - taker.pk, - taker_lock_amount, - taker_addr.clone(), - PunishParams { - revocation_pk: taker_rev_pk, - publish_pk: taker_pub_pk, - }, - ), - oracle_pk, - (cet_timelock, refund_timelock), - 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; - - let commit_desc = commit_descriptor( - (maker.pk, maker_rev_pk, maker_pub_pk), - (taker.pk, taker_rev_pk, taker_pub_pk), - ); - let commit_amount = Amount::from_sat(maker_cfd_txs.commit.0.output[0].value); - - verify_cfd_sigs( - (&maker_cfd_txs, maker.pk, maker_pub_pk), - (&taker_cfd_txs, taker.pk, taker_pub_pk), - (oracle_pk, vec![event]), - (&lock_desc, lock_amount), - (&commit_desc, commit_amount), - ); - - check_cfd_txs( - ( - maker_wallet, - maker_cfd_txs, - maker.sk, - maker.pk, - maker_pub_sk, - maker_pub_pk, - maker_rev_sk, - maker_addr, - ), - ( - taker_wallet, - taker_cfd_txs, - taker.sk, - taker.pk, - taker_pub_sk, - taker_pub_pk, - taker_rev_sk, - taker_addr, - ), - &[oracle_data], - (lock_desc, lock_amount), - (commit_desc, commit_amount), - ) -} - -#[test] -fn collaboratively_close_cfd() { - let mut rng = thread_rng(); - - let maker_lock_amount = Amount::ONE_BTC; - let taker_lock_amount = Amount::ONE_BTC; - - 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_0(); - let oracle_pk = oracle_data.pk; - let event = oracle_data.announcement(); - - 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; - - let (maker_cfd_txs, _, maker, taker, maker_addr, taker_addr) = create_cfd_txs( - &mut rng, - (&maker_wallet, maker_lock_amount), - (&taker_wallet, taker_lock_amount), - oracle_pk, - payouts_per_event, - (cet_timelock, refund_timelock), - ); - - let lock_tx = maker_cfd_txs.lock.extract_tx(); - let lock_desc = lock_descriptor(maker.pk, taker.pk); - let (lock_outpoint, lock_amount) = { - let outpoint = lock_tx - .outpoint(&lock_desc.script_pubkey()) - .expect("lock script to be in lock tx"); - let amount = Amount::from_sat(lock_tx.output[outpoint.vout as usize].value); - - (outpoint, amount) - }; - - let maker_amount = Amount::ONE_BTC; - let taker_amount = Amount::ONE_BTC; - - let (close_tx, close_sighash) = close_transaction( - &lock_desc, - lock_outpoint, - lock_amount, - (&maker_addr, maker_amount), - (&taker_addr, taker_amount), - ) - .expect("to build close tx"); - - let sig_maker = SECP256K1.sign(&close_sighash, &maker.sk); - let sig_taker = SECP256K1.sign(&close_sighash, &taker.sk); - let signed_close_tx = finalize_spend_transaction( - close_tx, - &lock_desc, - (maker.pk, sig_maker), - (taker.pk, sig_taker), - ) - .expect("to sign close tx"); - - check_tx(&lock_tx, &signed_close_tx, &lock_desc).expect("valid close tx"); -} - -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: schnorrsig::PublicKey, - payouts_per_event: HashMap>, - (cet_timelock, refund_timelock): (u32, u32), -) -> ( - CfdTransactions, - CfdTransactions, - CfdKeys, - CfdKeys, - Address, - Address, -) { - let (maker_sk, maker_pk) = make_keypair(rng); - let (taker_sk, taker_pk) = make_keypair(rng); - - let maker_addr = maker_wallet.get_address(AddressIndex::New).unwrap(); - let taker_addr = taker_wallet.get_address(AddressIndex::New).unwrap(); - - let (maker_rev_sk, maker_rev_pk) = make_keypair(rng); - let (maker_pub_sk, maker_pub_pk) = make_keypair(rng); - let (taker_rev_sk, taker_rev_pk) = make_keypair(rng); - let (taker_pub_sk, taker_pub_pk) = make_keypair(rng); - let maker_params = maker_wallet - .build_party_params(maker_lock_amount, maker_pk) - .unwrap(); - let taker_params = taker_wallet - .build_party_params(taker_lock_amount, taker_pk) - .unwrap(); - - let maker_cfd_txs = create_cfd_transactions( - ( - maker_params.clone(), - PunishParams { - revocation_pk: maker_rev_pk, - publish_pk: maker_pub_pk, - }, - ), - ( - taker_params.clone(), - PunishParams { - revocation_pk: taker_rev_pk, - publish_pk: taker_pub_pk, - }, - ), - oracle_pk, - (cet_timelock, refund_timelock), - payouts_per_event.clone(), - maker_sk, - ) - .unwrap(); - let taker_cfd_txs = create_cfd_transactions( - ( - maker_params, - PunishParams { - revocation_pk: maker_rev_pk, - publish_pk: maker_pub_pk, - }, - ), - ( - taker_params, - PunishParams { - revocation_pk: taker_rev_pk, - publish_pk: taker_pub_pk, - }, - ), - oracle_pk, - (cet_timelock, refund_timelock), - payouts_per_event, - taker_sk, - ) - .unwrap(); - ( - maker_cfd_txs, - taker_cfd_txs, - CfdKeys { - sk: maker_sk, - pk: maker_pk, - rev_sk: maker_rev_sk, - rev_pk: maker_rev_pk, - pub_sk: maker_pub_sk, - pub_pk: maker_pub_pk, - }, - CfdKeys { - sk: taker_sk, - pk: taker_pk, - rev_sk: taker_rev_sk, - rev_pk: taker_rev_pk, - pub_sk: taker_pub_sk, - pub_pk: taker_pub_pk, - }, - maker_addr.address, - taker_addr.address, - ) -} - -struct CfdKeys { - sk: SecretKey, - pk: PublicKey, - rev_sk: SecretKey, - rev_pk: PublicKey, - pub_sk: SecretKey, - pub_pk: PublicKey, -} - -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, events): (schnorrsig::PublicKey, Vec), - (lock_desc, lock_amount): (&Descriptor, Amount), - (commit_desc, commit_amount): (&Descriptor, Amount), -) { - verify_spend( - &taker_cfd_txs.refund.0, - &maker_cfd_txs.refund.1, - commit_desc, - commit_amount, - &maker_pk.key, - ) - .expect("valid maker refund sig"); - verify_spend( - &maker_cfd_txs.refund.0, - &taker_cfd_txs.refund.1, - commit_desc, - commit_amount, - &taker_pk.key, - ) - .expect("valid taker refund sig"); - - 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, _)| { - 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 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, _)| { - 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, - lock_desc, - lock_amount, - &taker_publish_pk.key, - &maker_pk.key, - ) - .expect("valid maker commit encsig"); - encverify_spend( - &maker_cfd_txs.commit.0, - &taker_cfd_txs.commit.1, - lock_desc, - lock_amount, - &maker_publish_pk.key, - &taker_pk.key, - ) - .expect("valid taker commit encsig"); -} - -fn check_cfd_txs( - ( - maker_wallet, - maker_cfd_txs, - maker_sk, - maker_pk, - maker_pub_sk, - maker_pub_pk, - maker_rev_sk, - maker_addr, - ): ( - bdk::Wallet<(), bdk::database::MemoryDatabase>, - CfdTransactions, - SecretKey, - PublicKey, - SecretKey, - PublicKey, - SecretKey, - Address, - ), - ( - taker_wallet, - taker_cfd_txs, - taker_sk, - taker_pk, - taker_pub_sk, - taker_pub_pk, - taker_rev_sk, - taker_addr, - ): ( - bdk::Wallet<(), bdk::database::MemoryDatabase>, - CfdTransactions, - SecretKey, - PublicKey, - SecretKey, - PublicKey, - SecretKey, - Address, - ), - oracle_data_list: &[OliviaData], - (lock_desc, lock_amount): (Descriptor, Amount), - (commit_desc, commit_amount): (Descriptor, Amount), -) { - // Lock transaction (either party can do this): - - let signed_lock_tx = sign_lock_tx(maker_cfd_txs.lock.clone(), maker_wallet, taker_wallet) - .expect("to build signed lock tx"); - - // Commit transactions: - - let signed_commit_tx_maker = decrypt_and_sign( - maker_cfd_txs.commit.0.clone(), - (&maker_sk, &maker_pk), - &maker_pub_sk, - &taker_pk, - &taker_cfd_txs.commit.1, - &lock_desc, - lock_amount, - ) - .expect("maker to build signed commit tx"); - check_tx(&signed_lock_tx, &signed_commit_tx_maker, &lock_desc).expect("valid maker commit tx"); - let signed_commit_tx_taker = decrypt_and_sign( - taker_cfd_txs.commit.0.clone(), - (&taker_sk, &taker_pk), - &taker_pub_sk, - &maker_pk, - &maker_cfd_txs.commit.1, - &lock_desc, - lock_amount, - ) - .expect("taker to build signed commit tx"); - check_tx(&signed_lock_tx, &signed_commit_tx_taker, &lock_desc).expect("valid taker commit tx"); - - // Refund transaction (both parties would produce the same one): - - let signed_refund_tx = finalize_spend_transaction( - maker_cfd_txs.refund.0.clone(), - &commit_desc, - (maker_pk, maker_cfd_txs.refund.1), - (taker_pk, taker_cfd_txs.refund.1), - ) - .expect("to build signed refund tx"); - check_tx(&signed_commit_tx_maker, &signed_refund_tx, &commit_desc).expect("valid refund tx"); - - // CETs: - - 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; - } - - build_and_check_cet( - tx, - taker_cets, - (&maker_sk, &maker_pk), - &taker_pk, - (price, &oracle_attestations), - (&signed_commit_tx_maker, &commit_desc, commit_amount), - ) - .expect("valid unlocked maker 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 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; - } - - build_and_check_cet( - tx, - maker_cets, - (&taker_sk, &taker_pk), - &maker_pk, - (price, &oracle_attestations), - (&signed_commit_tx_taker, &commit_desc, commit_amount), - ) - .expect("valid unlocked taker cet"); - }); - } - - // Punish transactions: - - let punish_tx_maker = punish_transaction( - &commit_desc, - &maker_addr, - maker_cfd_txs.commit.1, - maker_sk, - taker_rev_sk, - taker_pub_pk, - &signed_commit_tx_taker, - ) - .expect("maker to build punish tx"); - check_tx(&signed_commit_tx_taker, &punish_tx_maker, &commit_desc) - .expect("valid maker punish tx"); - let punish_tx_taker = punish_transaction( - &commit_desc, - &taker_addr, - taker_cfd_txs.commit.1, - taker_sk, - maker_rev_sk, - maker_pub_pk, - &signed_commit_tx_maker, - ) - .expect("taker to build punish tx"); - check_tx(&signed_commit_tx_maker, &punish_tx_taker, &commit_desc) - .expect("valid taker punish tx"); -} - -fn build_and_check_cet( - cet: Transaction, - cets_other: &[(Transaction, EcdsaAdaptorSignature, interval::Digits)], - (sk, pk): (&SecretKey, &PublicKey), - pk_other: &PublicKey, - (price, oracle_attestations): (u64, &[SecretKey]), - (commit_tx, commit_desc, commit_amount): (&Transaction, &Descriptor, Amount), -) -> Result<()> { - let (encsig_other, n_bits) = cets_other - .iter() - .find_map(|(_, encsig, digits)| { - (digits.range().contains(&price)).then(|| (encsig, digits.len())) - }) - .expect("one encsig per cet, per party"); - - let (oracle_attestations, _) = oracle_attestations.split_at(n_bits); - - let mut decryption_sk = oracle_attestations[0]; - for oracle_attestation in oracle_attestations[1..].iter() { - decryption_sk.add_assign(oracle_attestation.as_ref())?; - } - - let signed_cet = decrypt_and_sign( - cet, - (sk, pk), - &decryption_sk, - pk_other, - encsig_other, - commit_desc, - commit_amount, - ) - .context("failed to build signed cet")?; - check_tx(commit_tx, &signed_cet, commit_desc).context("invalid cet")?; - - Ok(()) -} - -fn check_tx( - spent_tx: &Transaction, - spend_tx: &Transaction, - spent_descriptor: &Descriptor, -) -> Result<()> { - let spent_script_pubkey = spent_descriptor.script_pubkey(); - let spent_outpoint = spent_tx - .outpoint(&spent_script_pubkey) - .context("spend tx doesn't spend from spent tx")?; - let spent_amount = spent_tx.output[spent_outpoint.vout as usize].value; - - check_tx_fee(&[spent_tx], spend_tx)?; - spent_descriptor.script_pubkey().verify( - 0, - spent_amount, - bitcoin::consensus::serialize(spend_tx).as_slice(), - )?; - - Ok(()) -} - -fn decrypt_and_sign( - spend_tx: Transaction, - (sk, pk): (&SecretKey, &PublicKey), - decryption_sk: &SecretKey, - pk_other: &PublicKey, - encsig_other: &EcdsaAdaptorSignature, - spent_descriptor: &Descriptor, - spent_amount: Amount, -) -> Result { - let sighash = spending_tx_sighash(&spend_tx, spent_descriptor, spent_amount); - - let sig_self = SECP256K1.sign(&sighash, sk); - - encsig_other - .verify( - SECP256K1, - &sighash, - &pk_other.key, - &secp256k1_zkp::PublicKey::from_secret_key(SECP256K1, decryption_sk), - ) - .expect("wrong decryption key"); - let sig_other = encsig_other.decrypt(decryption_sk)?; - - let signed_commit_tx = finalize_spend_transaction( - spend_tx, - spent_descriptor, - (*pk, sig_self), - (*pk_other, sig_other), - )?; - - Ok(signed_commit_tx) -} - -fn sign_lock_tx( - mut lock_tx: PartiallySignedTransaction, - maker_wallet: bdk::Wallet<(), bdk::database::MemoryDatabase>, - taker_wallet: bdk::Wallet<(), bdk::database::MemoryDatabase>, -) -> Result { - maker_wallet - .sign( - &mut lock_tx, - SignOptions { - trust_witness_utxo: true, - ..Default::default() - }, - ) - .context("maker could not sign lock tx")?; - taker_wallet - .sign( - &mut lock_tx, - SignOptions { - trust_witness_utxo: true, - ..Default::default() - }, - ) - .context("taker could not sign lock tx")?; - - Ok(lock_tx.extract_tx()) -} - -fn verify_spend( - tx: &Transaction, - sig: &Signature, - spent_descriptor: &Descriptor, - spent_amount: Amount, - pk: &secp256k1_zkp::PublicKey, -) -> Result<()> { - let sighash = spending_tx_sighash(tx, spent_descriptor, spent_amount); - SECP256K1 - .verify(&sighash, sig, pk) - .context("failed to verify sig on spend tx") -} - -fn verify_cet_encsig( - tx: &Transaction, - encsig: &EcdsaAdaptorSignature, - digits: &interval::Digits, - pk: &secp256k1_zkp::PublicKey, - (oracle_pk, nonce_pks): (schnorrsig::PublicKey, &[schnorrsig::PublicKey]), - spent_descriptor: &Descriptor, - spent_amount: Amount, -) -> Result<()> { - let index_nonce_pairs = &digits - .to_indices() - .into_iter() - .zip(nonce_pks.iter().cloned()) - .collect::>(); - - let adaptor_point = compute_adaptor_pk(&oracle_pk, index_nonce_pairs) - .context("could not calculate adaptor point")?; - encverify_spend( - tx, - encsig, - spent_descriptor, - spent_amount, - &adaptor_point, - pk, - ) -} - -fn encverify_spend( - tx: &Transaction, - encsig: &EcdsaAdaptorSignature, - spent_descriptor: &Descriptor, - spent_amount: Amount, - encryption_point: &secp256k1_zkp::PublicKey, - pk: &secp256k1_zkp::PublicKey, -) -> Result<()> { - let sighash = spending_tx_sighash(tx, spent_descriptor, spent_amount); - encsig - .verify(SECP256K1, &sighash, pk, encryption_point) - .context("failed to verify encsig spend tx") -} - -fn check_tx_fee(input_txs: &[&Transaction], spend_tx: &Transaction) -> Result<()> { - let input_amount = spend_tx - .input - .iter() - .try_fold::<_, _, Result<_>>(0, |acc, input| { - let value = input_txs - .iter() - .find_map(|tx| { - (tx.txid() == input.previous_output.txid) - .then(|| tx.output[input.previous_output.vout as usize].value) - }) - .with_context(|| { - format!( - "spend tx input {} not found in input_txs", - input.previous_output - ) - })?; - - Ok(acc + value) - })?; - - let output_amount = spend_tx - .output - .iter() - .fold(0, |acc, output| acc + output.value); - let fee = input_amount - output_amount; - - let min_relay_fee = spend_tx.get_virtual_size(); - if (fee as f64) < min_relay_fee { - bail!("min relay fee not met, {} < {}", fee, min_relay_fee) - } - - Ok(()) -} - -fn build_wallet( - rng: &mut (impl RngCore + CryptoRng), - utxo_amount: Amount, - num_utxos: u8, -) -> Result> { - use bdk::{populate_test_db, testutils}; - - let mut seed = [0u8; 32]; - rng.fill_bytes(&mut seed); - - let key = ExtendedPrivKey::new_master(Network::Regtest, &seed)?; - let descriptors = testutils!(@descriptors (&format!("wpkh({}/*)", key))); - - let mut database = bdk::database::MemoryDatabase::new(); - - for index in 0..num_utxos { - populate_test_db!( - &mut database, - testutils! { - @tx ( (@external descriptors, index as u32) => utxo_amount.as_sat() ) (@confirmations 1) - }, - Some(100) - ); - } - - let wallet = bdk::Wallet::new_offline(&descriptors.0, None, Network::Regtest, database)?; - - Ok(wallet) -} - -fn make_keypair(rng: &mut (impl RngCore + CryptoRng)) -> (SecretKey, PublicKey) { - let sk = SecretKey::new(rng); - let pk = PublicKey::from_private_key( - SECP256K1, - &PrivateKey { - compressed: true, - network: Network::Regtest, - key: sk, - }, - ); - - (sk, pk) -} - -struct OliviaData { - id: String, - pk: schnorrsig::PublicKey, - nonce_pks: Vec, - price: u64, - attestations: Vec, -} - -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, - ) - } - - /// Generate an example of all the data from `olivia` needed to test the - /// CFD protocol end-to-end. - 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(), - } - } - - 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/Cargo.toml b/daemon/Cargo.toml index ff3be62..a47a04a 100644 --- a/daemon/Cargo.toml +++ b/daemon/Cargo.toml @@ -9,7 +9,6 @@ async-trait = "0.1.51" atty = "0.2" bdk = { version = "0.13", default-features = false, features = ["sqlite", "electrum"] } bytes = "1" -cfd_protocol = { path = "../cfd_protocol" } chrono = { version = "0.4", features = ["serde"] } clap = "3.0.0-beta.5" derive_more = { version = "0.99.16", default-features = false, features = ["display"] } @@ -18,6 +17,7 @@ hex = "0.4" hkdf = "0.11" http-api-problem = { version = "0.51.0", features = ["rocket"] } itertools = "0.10" +maia = "0.1.0" nalgebra = { version = "0.29", default-features = false, features = ["std"] } ndarray = "0.15.3" ndarray_einsum_beta = "0.7.0" diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs index 154dc3e..3237272 100644 --- a/daemon/src/lib.rs +++ b/daemon/src/lib.rs @@ -4,8 +4,8 @@ use crate::maker_cfd::{FromTaker, NewTakerOnline}; use crate::model::cfd::{Cfd, Order, UpdateCfdProposals}; use crate::oracle::Attestation; use anyhow::Result; -use cfd_protocol::secp256k1_zkp::schnorrsig; use futures::Stream; +use maia::secp256k1_zkp::schnorrsig; use sqlx::SqlitePool; use std::collections::HashMap; use std::future::Future; diff --git a/daemon/src/maker_cfd.rs b/daemon/src/maker_cfd.rs index ee8d7e0..cf463c6 100644 --- a/daemon/src/maker_cfd.rs +++ b/daemon/src/maker_cfd.rs @@ -12,9 +12,9 @@ use crate::{log_error, maker_inc_connections, monitor, oracle, setup_contract, w use anyhow::{Context as _, Result}; use async_trait::async_trait; use bdk::bitcoin::secp256k1::schnorrsig; -use cfd_protocol::secp256k1_zkp::Signature; use futures::channel::mpsc; use futures::{future, SinkExt}; +use maia::secp256k1_zkp::Signature; use sqlx::pool::PoolConnection; use sqlx::Sqlite; use std::collections::HashMap; diff --git a/daemon/src/model/cfd.rs b/daemon/src/model/cfd.rs index e6c3624..3fb1e36 100644 --- a/daemon/src/model/cfd.rs +++ b/daemon/src/model/cfd.rs @@ -8,8 +8,8 @@ use bdk::bitcoin::secp256k1::{SecretKey, Signature}; use bdk::bitcoin::{Address, Amount, PublicKey, Script, SignedAmount, Transaction, Txid}; use bdk::descriptor::Descriptor; use bdk::miniscript::DescriptorTrait; -use cfd_protocol::secp256k1_zkp::{self, EcdsaAdaptorSignature, SECP256K1}; -use cfd_protocol::{finalize_spend_transaction, spending_tx_sighash, TransactionExt}; +use maia::secp256k1_zkp::{self, EcdsaAdaptorSignature, SECP256K1}; +use maia::{finalize_spend_transaction, spending_tx_sighash, TransactionExt}; use rocket::request::FromParam; use rust_decimal::prelude::FromPrimitive; use rust_decimal::Decimal; @@ -1715,7 +1715,7 @@ impl Dlc { (outpoint, amount) }; - let (tx, sighash) = cfd_protocol::close_transaction( + let (tx, sighash) = maia::close_transaction( lock_desc, lock_outpoint, lock_amount, @@ -1740,7 +1740,7 @@ impl Dlc { )); let (_, lock_desc) = &self.lock; - let spend_tx = cfd_protocol::finalize_spend_transaction( + let spend_tx = maia::finalize_spend_transaction( close_tx, lock_desc, (own_pk, own_sig), diff --git a/daemon/src/oracle.rs b/daemon/src/oracle.rs index d4d73df..6474a4a 100644 --- a/daemon/src/oracle.rs +++ b/daemon/src/oracle.rs @@ -3,7 +3,7 @@ use crate::model::BitMexPriceEventId; use crate::{log_error, tokio_ext, try_continue}; use anyhow::{Context, Result}; use async_trait::async_trait; -use cfd_protocol::secp256k1_zkp::{schnorrsig, SecretKey}; +use maia::secp256k1_zkp::{schnorrsig, SecretKey}; use rocket::time::{OffsetDateTime, Time}; use serde::Deserialize; use std::collections::{HashMap, HashSet}; @@ -278,9 +278,9 @@ pub struct Announcement { pub nonce_pks: Vec, } -impl From for cfd_protocol::Announcement { +impl From for maia::Announcement { fn from(announcement: Announcement) -> Self { - cfd_protocol::Announcement { + maia::Announcement { id: announcement.id.to_string(), nonce_pks: announcement.nonce_pks, } @@ -300,7 +300,7 @@ impl xtra::Message for NewAttestationFetched { mod olivia_api { use crate::model::BitMexPriceEventId; use anyhow::Context; - use cfd_protocol::secp256k1_zkp::{schnorrsig, SecretKey}; + use maia::secp256k1_zkp::{schnorrsig, SecretKey}; use std::convert::TryFrom; use time::OffsetDateTime; diff --git a/daemon/src/payout_curve.rs b/daemon/src/payout_curve.rs index e729b0e..7b11008 100644 --- a/daemon/src/payout_curve.rs +++ b/daemon/src/payout_curve.rs @@ -4,8 +4,8 @@ use crate::model::{Leverage, Price, Usd}; use crate::payout_curve::curve::Curve; use anyhow::{Context, Result}; use bdk::bitcoin; -use cfd_protocol::{generate_payouts, Payout}; use itertools::Itertools; +use maia::{generate_payouts, Payout}; use ndarray::prelude::*; use num::{FromPrimitive, ToPrimitive}; use rust_decimal::Decimal; diff --git a/daemon/src/setup_contract.rs b/daemon/src/setup_contract.rs index dbeab5d..3de3ce7 100644 --- a/daemon/src/setup_contract.rs +++ b/daemon/src/setup_contract.rs @@ -10,14 +10,14 @@ use bdk::bitcoin::util::psbt::PartiallySignedTransaction; use bdk::bitcoin::{Amount, PublicKey, Transaction}; use bdk::descriptor::Descriptor; use bdk::miniscript::DescriptorTrait; -use cfd_protocol::secp256k1_zkp::EcdsaAdaptorSignature; -use cfd_protocol::{ +use futures::stream::FusedStream; +use futures::{Sink, SinkExt, StreamExt}; +use maia::secp256k1_zkp::EcdsaAdaptorSignature; +use maia::{ commit_descriptor, compute_adaptor_pk, create_cfd_transactions, interval, lock_descriptor, renew_cfd_transactions, secp256k1_zkp, 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; diff --git a/daemon/src/wallet.rs b/daemon/src/wallet.rs index ecfc8d1..8951c50 100644 --- a/daemon/src/wallet.rs +++ b/daemon/src/wallet.rs @@ -7,8 +7,9 @@ use bdk::bitcoin::{Address, Amount, PublicKey, Script, Transaction, Txid}; use bdk::blockchain::{ElectrumBlockchain, NoopProgress}; use bdk::wallet::AddressIndex; use bdk::{electrum_client, FeeRate, KeychainKind, SignOptions}; -use cfd_protocol::{PartyParams, WalletExt}; +use maia::{PartyParams, WalletExt}; use rocket::serde::json::Value; + use std::path::Path; use std::sync::Arc; use tokio::sync::Mutex; diff --git a/daemon/src/wire.rs b/daemon/src/wire.rs index fecb998..399a046 100644 --- a/daemon/src/wire.rs +++ b/daemon/src/wire.rs @@ -6,8 +6,8 @@ use bdk::bitcoin::secp256k1::Signature; use bdk::bitcoin::util::psbt::PartiallySignedTransaction; use bdk::bitcoin::{Address, Amount, PublicKey}; use bytes::BytesMut; -use cfd_protocol::secp256k1_zkp::{EcdsaAdaptorSignature, SecretKey}; -use cfd_protocol::{CfdTransactions, PartyParams, PunishParams}; +use maia::secp256k1_zkp::{EcdsaAdaptorSignature, SecretKey}; +use maia::{CfdTransactions, PartyParams, PunishParams}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use snow::TransportState; diff --git a/daemon/tests/happy_path.rs b/daemon/tests/happy_path.rs index 7295bd4..8b20459 100644 --- a/daemon/tests/happy_path.rs +++ b/daemon/tests/happy_path.rs @@ -3,12 +3,12 @@ use crate::harness::mocks::oracle::dummy_announcement; use crate::harness::mocks::wallet::build_party_params; use crate::harness::start_both; use anyhow::Context; -use cfd_protocol::secp256k1_zkp::schnorrsig; use daemon::maker_cfd; use daemon::model::cfd::{Cfd, CfdState, Order, Origin}; use daemon::model::{Price, Usd}; use daemon::tokio_ext::FutureExt; use harness::bdk::dummy_tx_id; +use maia::secp256k1_zkp::schnorrsig; use rust_decimal_macros::dec; use std::time::Duration; use tokio::sync::{watch, MutexGuard}; diff --git a/daemon/tests/harness/cfd_protocol.rs b/daemon/tests/harness/maia.rs similarity index 97% rename from daemon/tests/harness/cfd_protocol.rs rename to daemon/tests/harness/maia.rs index 9e7822a..05502c2 100644 --- a/daemon/tests/harness/cfd_protocol.rs +++ b/daemon/tests/harness/maia.rs @@ -2,9 +2,9 @@ use anyhow::Result; use bdk::bitcoin; use bdk::bitcoin::util::bip32::ExtendedPrivKey; use bdk::bitcoin::{Amount, Network}; -use cfd_protocol::secp256k1_zkp::rand::{CryptoRng, RngCore}; -use cfd_protocol::secp256k1_zkp::{schnorrsig, SecretKey}; -use cfd_protocol::Announcement; +use maia::secp256k1_zkp::rand::{CryptoRng, RngCore}; +use maia::secp256k1_zkp::{schnorrsig, SecretKey}; +use maia::Announcement; use std::str::FromStr; pub fn dummy_wallet( diff --git a/daemon/tests/harness/mocks/oracle.rs b/daemon/tests/harness/mocks/oracle.rs index fe2fac4..4aca9d4 100644 --- a/daemon/tests/harness/mocks/oracle.rs +++ b/daemon/tests/harness/mocks/oracle.rs @@ -1,4 +1,4 @@ -use crate::harness::cfd_protocol::OliviaData; +use crate::harness::maia::OliviaData; use daemon::model::BitMexPriceEventId; use daemon::oracle; use mockall::*; diff --git a/daemon/tests/harness/mocks/wallet.rs b/daemon/tests/harness/mocks/wallet.rs index 04a6d75..19712c4 100644 --- a/daemon/tests/harness/mocks/wallet.rs +++ b/daemon/tests/harness/mocks/wallet.rs @@ -1,11 +1,11 @@ -use crate::harness::cfd_protocol::dummy_wallet; +use crate::harness::maia::dummy_wallet; use anyhow::Result; use bdk::bitcoin::util::psbt::PartiallySignedTransaction; use bdk::bitcoin::{ecdsa, Amount, Txid}; -use cfd_protocol::secp256k1_zkp::Secp256k1; -use cfd_protocol::{PartyParams, WalletExt}; use daemon::model::{Timestamp, WalletInfo}; use daemon::wallet::{self}; +use maia::secp256k1_zkp::Secp256k1; +use maia::{PartyParams, WalletExt}; use mockall::*; use rand::thread_rng; use std::sync::Arc; diff --git a/daemon/tests/harness/mod.rs b/daemon/tests/harness/mod.rs index 3f81d09..41d79f2 100644 --- a/daemon/tests/harness/mod.rs +++ b/daemon/tests/harness/mod.rs @@ -16,7 +16,7 @@ use xtra::spawn::TokioGlobalSpawnExt; use xtra::Actor; pub mod bdk; -pub mod cfd_protocol; +pub mod maia; pub mod mocks; pub async fn start_both() -> (Maker, Taker) {