From 06cd71072a809691c45ad2fe9bdbf3c16557aced Mon Sep 17 00:00:00 2001 From: benthecarman Date: Fri, 8 Dec 2023 01:14:10 -0600 Subject: [PATCH] WIP: stateless keys --- dlc-manager/src/channel_updater.rs | 42 +++++++++++++--- dlc-manager/src/contract/offered_contract.rs | 3 +- dlc-manager/src/contract/signed_contract.rs | 23 +++++++++ dlc-manager/src/contract_updater.rs | 50 +++++++++++--------- dlc-manager/src/manager.rs | 28 +++++++++-- dlc-manager/src/utils.rs | 44 ++++++++++++++--- dlc-manager/tests/channel_execution_tests.rs | 7 ++- dlc-manager/tests/manager_execution_tests.rs | 5 ++ sample/src/main.rs | 15 +++--- 9 files changed, 169 insertions(+), 48 deletions(-) diff --git a/dlc-manager/src/channel_updater.rs b/dlc-manager/src/channel_updater.rs index dfd9cf74..a3edb9de 100644 --- a/dlc-manager/src/channel_updater.rs +++ b/dlc-manager/src/channel_updater.rs @@ -62,6 +62,7 @@ macro_rules! get_signed_channel_state { } }}; } +use crate::utils::SerialIds; pub(crate) use get_signed_channel_state; /// Creates an [`OfferedChannel`] and an associated [`OfferedContract`] using @@ -74,6 +75,7 @@ pub fn offer_channel( cet_nsequence: u32, refund_delay: u32, wallet: &W, + seed: [u8; 32], blockchain: &B, time: &T, ) -> Result<(OfferedChannel, OfferedContract), Error> @@ -82,8 +84,13 @@ where B::Target: Blockchain, T::Target: Time, { - let (offer_params, _, funding_inputs_info) = crate::utils::get_party_params( + let temp_id = get_new_temporary_id(); + let serial_ids = SerialIds::generate(); + let funding_privkey = crate::utils::compute_secret_key(seed, temp_id, serial_ids); + let (offer_params, funding_inputs_info) = crate::utils::get_party_params( secp, + funding_privkey, + serial_ids, contract.offer_collateral, contract.fee_rate, wallet, @@ -92,6 +99,7 @@ where let party_points = crate::utils::get_party_base_points(secp, wallet)?; let offered_contract = OfferedContract::new( + temp_id, contract, oracle_announcements.to_vec(), &offer_params, @@ -136,6 +144,7 @@ pub fn accept_channel_offer( offered_channel: &OfferedChannel, offered_contract: &OfferedContract, wallet: &W, + seed: [u8; 32], blockchain: &B, ) -> Result<(AcceptedChannel, AcceptedContract, AcceptChannel), Error> where @@ -146,8 +155,12 @@ where let total_collateral = offered_contract.total_collateral; - let (accept_params, _, funding_inputs) = crate::utils::get_party_params( + let serial_ids = SerialIds::generate(); + let fund_secret_key = crate::utils::compute_secret_key(seed, offered_contract.id, serial_ids); + let (accept_params, funding_inputs) = crate::utils::get_party_params( secp, + fund_secret_key, + serial_ids, total_collateral - offered_contract.offer_params.collateral, offered_contract.fee_rate_per_vb, wallet, @@ -209,7 +222,11 @@ where &offered_channel.temporary_channel_id, ); - let own_fund_sk = wallet.get_secret_key_for_pubkey(&accept_params.fund_pubkey)?; + let serial_ids = SerialIds { + payout_serial_id: accept_params.payout_serial_id, + change_serial_id: accept_params.change_serial_id, + }; + let own_fund_sk = crate::utils::compute_secret_key(seed, offered_contract.id, serial_ids); let buffer_adaptor_signature = get_tx_adaptor_signature( secp, @@ -265,6 +282,7 @@ pub fn verify_and_sign_accepted_channel( offered_contract: &OfferedContract, accept_channel: &AcceptChannel, cet_nsequence: u32, + seed: [u8; 32], signer: &S, ) -> Result<(SignedChannel, SignedContract, SignChannel), Error> where @@ -299,8 +317,11 @@ where &offer_own_base_secret, ); - let offer_fund_sk = - signer.get_secret_key_for_pubkey(&offered_contract.offer_params.fund_pubkey)?; + let serial_ids = SerialIds { + payout_serial_id: accept_params.payout_serial_id, + change_serial_id: accept_params.change_serial_id, + }; + let offer_fund_sk = crate::utils::compute_secret_key(seed, offered_contract.id, serial_ids); let offer_revoke_params = offered_channel.party_points.get_revokable_params( secp, @@ -596,6 +617,7 @@ pub fn settle_channel_accept( lock_time: u32, peer_timeout: u64, signer: &S, + seed: [u8; 32], time: &T, ) -> Result where @@ -634,7 +656,13 @@ where let fund_vout = channel.fund_output_index; let funding_script_pubkey = &channel.fund_script_pubkey; - let own_fund_sk = signer.get_secret_key_for_pubkey(&channel.own_params.fund_pubkey)?; + let serial_ids = SerialIds { + payout_serial_id: channel.own_params.payout_serial_id, + change_serial_id: channel.own_params.change_serial_id, + }; + // todo i think wrong id + let own_fund_sk = + crate::utils::compute_secret_key(seed, channel.temporary_channel_id, serial_ids); let (settle_tx, settle_adaptor_signature) = get_settle_tx_and_adaptor_sig( secp, @@ -967,7 +995,9 @@ where S::Target: Signer, T::Target: Time, { + let temp_id = get_new_temporary_id(); let mut offered_contract = OfferedContract::new( + temp_id, contract_input, oracle_announcements, &signed_channel.own_params, diff --git a/dlc-manager/src/contract/offered_contract.rs b/dlc-manager/src/contract/offered_contract.rs index aa793b65..593d2592 100644 --- a/dlc-manager/src/contract/offered_contract.rs +++ b/dlc-manager/src/contract/offered_contract.rs @@ -75,6 +75,7 @@ impl OfferedContract { /// Creates a new [`OfferedContract`] from the given parameters. pub fn new( + temp_id: [u8; 32], contract: &ContractInput, oracle_announcements: Vec>, offer_params: &PartyParams, @@ -102,7 +103,7 @@ impl OfferedContract { }) .collect::>(); OfferedContract { - id: crate::utils::get_new_temporary_id(), + id: temp_id, is_offer_party: true, contract_info, offer_params: offer_params.clone(), diff --git a/dlc-manager/src/contract/signed_contract.rs b/dlc-manager/src/contract/signed_contract.rs index f392a892..18288cc9 100644 --- a/dlc-manager/src/contract/signed_contract.rs +++ b/dlc-manager/src/contract/signed_contract.rs @@ -4,6 +4,7 @@ use crate::conversion_utils::PROTOCOL_VERSION; use crate::ChannelId; use super::accepted_contract::AcceptedContract; +use crate::utils::SerialIds; use dlc_messages::CetAdaptorSignature; use dlc_messages::CetAdaptorSignatures; use dlc_messages::FundingSignatures; @@ -46,4 +47,26 @@ impl SignedContract { funding_signatures: self.funding_signatures.clone(), } } + + pub(crate) fn get_serial_ids(&self) -> SerialIds { + if self.accepted_contract.offered_contract.is_offer_party { + SerialIds { + payout_serial_id: self + .accepted_contract + .offered_contract + .offer_params + .payout_serial_id, + change_serial_id: self + .accepted_contract + .offered_contract + .offer_params + .change_serial_id, + } + } else { + SerialIds { + payout_serial_id: self.accepted_contract.accept_params.payout_serial_id, + change_serial_id: self.accepted_contract.accept_params.change_serial_id, + } + } + } } diff --git a/dlc-manager/src/contract_updater.rs b/dlc-manager/src/contract_updater.rs index 4fefb8c2..b9d09327 100644 --- a/dlc-manager/src/contract_updater.rs +++ b/dlc-manager/src/contract_updater.rs @@ -12,6 +12,7 @@ use secp256k1_zkp::{ ecdsa::Signature, All, EcdsaAdaptorSignature, PublicKey, Secp256k1, SecretKey, Signing, }; +use crate::utils::{get_new_temporary_id, SerialIds}; use crate::{ contract::{ accepted_contract::AcceptedContract, contract_info::ContractInfo, @@ -32,6 +33,7 @@ pub fn offer_contract( refund_delay: u32, counter_party: &PublicKey, wallet: &W, + seed: [u8; 32], blockchain: &B, time: &T, ) -> Result<(OfferedContract, OfferDlc), Error> @@ -42,8 +44,13 @@ where { contract_input.validate()?; - let (party_params, _, funding_inputs_info) = crate::utils::get_party_params( + let temp_id = get_new_temporary_id(); + let serial_ids = SerialIds::generate(); + let funding_privkey = crate::utils::compute_secret_key(seed, temp_id, serial_ids); + let (party_params, funding_inputs_info) = crate::utils::get_party_params( secp, + funding_privkey, + serial_ids, contract_input.offer_collateral, contract_input.fee_rate, wallet, @@ -51,6 +58,7 @@ where )?; let offered_contract = OfferedContract::new( + temp_id, contract_input, oracle_announcements, &party_params, @@ -71,6 +79,7 @@ pub fn accept_contract( secp: &Secp256k1, offered_contract: &OfferedContract, wallet: &W, + seed: [u8; 32], blockchain: &B, ) -> Result<(AcceptedContract, AcceptDlc), crate::Error> where @@ -79,8 +88,12 @@ where { let total_collateral = offered_contract.total_collateral; - let (accept_params, fund_secret_key, funding_inputs) = crate::utils::get_party_params( + let serial_ids = SerialIds::generate(); + let fund_secret_key = crate::utils::compute_secret_key(seed, offered_contract.id, serial_ids); + let (accept_params, funding_inputs) = crate::utils::get_party_params( secp, + fund_secret_key, + serial_ids, total_collateral - offered_contract.offer_params.collateral, offered_contract.fee_rate_per_vb, wallet, @@ -636,41 +649,37 @@ where } /// Signs and return the CET that can be used to close the given contract. -pub fn get_signed_cet( +pub fn get_signed_cet( secp: &Secp256k1, contract: &SignedContract, contract_info: &ContractInfo, adaptor_info: &AdaptorInfo, attestations: &[(usize, OracleAttestation)], - signer: &S, -) -> Result -where - S::Target: Signer, -{ + seed: [u8; 32], +) -> Result { let (range_info, sigs) = crate::utils::get_range_info_and_oracle_sigs(contract_info, adaptor_info, attestations)?; let mut cet = contract.accepted_contract.dlc_transactions.cets[range_info.cet_index].clone(); let offered_contract = &contract.accepted_contract.offered_contract; - let (adaptor_sigs, fund_pubkey, other_pubkey) = if offered_contract.is_offer_party { + let (adaptor_sigs, other_pubkey) = if offered_contract.is_offer_party { ( contract .accepted_contract .adaptor_signatures .as_ref() .unwrap(), - &offered_contract.offer_params.fund_pubkey, &contract.accepted_contract.accept_params.fund_pubkey, ) } else { ( contract.adaptor_signatures.as_ref().unwrap(), - &contract.accepted_contract.accept_params.fund_pubkey, &offered_contract.offer_params.fund_pubkey, ) }; - let funding_sk = signer.get_secret_key_for_pubkey(fund_pubkey)?; + let funding_sk = + crate::utils::compute_secret_key(seed, offered_contract.id, contract.get_serial_ids()); dlc::sign_cet( secp, @@ -694,33 +703,29 @@ where } /// Signs and return the refund transaction to refund the contract. -pub fn get_signed_refund( +pub fn get_signed_refund( secp: &Secp256k1, contract: &SignedContract, - signer: &S, -) -> Result -where - S::Target: Signer, -{ + seed: [u8; 32], +) -> Result { let accepted_contract = &contract.accepted_contract; let offered_contract = &accepted_contract.offered_contract; let funding_script_pubkey = &accepted_contract.dlc_transactions.funding_script_pubkey; let fund_output_value = accepted_contract.dlc_transactions.get_fund_output().value; - let (fund_pubkey, other_fund_pubkey, other_sig) = if offered_contract.is_offer_party { + let (other_fund_pubkey, other_sig) = if offered_contract.is_offer_party { ( - &offered_contract.offer_params.fund_pubkey, &accepted_contract.accept_params.fund_pubkey, &accepted_contract.accept_refund_signature, ) } else { ( - &accepted_contract.accept_params.fund_pubkey, &offered_contract.offer_params.fund_pubkey, &contract.offer_refund_signature, ) }; - let fund_priv_key = signer.get_secret_key_for_pubkey(fund_pubkey)?; + let fund_priv_key = + crate::utils::compute_secret_key(seed, offered_contract.id, contract.get_serial_ids()); let mut refund = accepted_contract.dlc_transactions.refund.clone(); dlc::util::sign_multi_sig_input( secp, @@ -766,6 +771,7 @@ mod tests { secp256k1_zkp::SECP256K1, &offered_contract, &wallet, + [0; 32], &blockchain, ) .expect("Not to fail"); diff --git a/dlc-manager/src/manager.rs b/dlc-manager/src/manager.rs index c21c3c95..dfa4cd43 100644 --- a/dlc-manager/src/manager.rs +++ b/dlc-manager/src/manager.rs @@ -70,6 +70,7 @@ where store: S, secp: Secp256k1, chain_monitor: ChainMonitor, + seed: [u8; 32], time: T, fee_estimator: F, } @@ -174,6 +175,7 @@ where blockchain: B, store: S, oracles: HashMap, + seed: [u8; 32], time: T, fee_estimator: F, ) -> Result { @@ -191,6 +193,7 @@ where time, fee_estimator, chain_monitor, + seed, }) } @@ -306,6 +309,7 @@ where REFUND_DELAY, &counter_party, &self.wallet, + self.seed, &self.blockchain, &self.time, )?; @@ -331,6 +335,7 @@ where &self.secp, &offered_contract, &self.wallet, + self.seed, &self.blockchain, )?; @@ -580,7 +585,7 @@ where contract_info, adaptor_info, &attestations, - &self.wallet, + self.seed, )?; match self.close_contract( contract, @@ -636,7 +641,7 @@ where contract_info, adaptor_info, &attestations, - &self.wallet, + self.seed, )?; // Check that the lock time has passed @@ -785,7 +790,7 @@ where .get_transaction_confirmations(&refund.txid())?; if confirmations == 0 { let refund = - crate::contract_updater::get_signed_refund(&self.secp, contract, &self.wallet)?; + crate::contract_updater::get_signed_refund(&self.secp, contract, self.seed)?; self.blockchain.send_transaction(&refund)?; } @@ -877,6 +882,7 @@ where CET_NSEQUENCE, REFUND_DELAY, &self.wallet, + self.seed, &self.blockchain, &self.time, )?; @@ -920,6 +926,7 @@ where &offered_channel, &offered_contract, &self.wallet, + self.seed, &self.blockchain, )?; @@ -991,6 +998,7 @@ where 0, PEER_TIMEOUT, &self.wallet, + self.seed, &self.time, )?; @@ -1324,6 +1332,7 @@ where accept_channel, //TODO(tibo): this should be parameterizable. CET_NSEQUENCE, + self.seed, &self.wallet, ); @@ -2366,7 +2375,18 @@ mod test { mocks::mock_time::set_time(0); - Manager::new(wallet, blockchain.clone(), store, oracles, time, blockchain).unwrap() + let seed = [0; 32]; + + Manager::new( + wallet, + blockchain.clone(), + store, + oracles, + seed, + time, + blockchain, + ) + .unwrap() } fn pubkey() -> PublicKey { diff --git a/dlc-manager/src/utils.rs b/dlc-manager/src/utils.rs index fe2c4f81..8eaaa368 100644 --- a/dlc-manager/src/utils.rs +++ b/dlc-manager/src/utils.rs @@ -1,6 +1,8 @@ //! #Utils use std::ops::Deref; +use bitcoin::hashes::sha256::Hash as Sha256; +use bitcoin::hashes::{Hash, HashEngine, Hmac, HmacEngine}; use bitcoin::{consensus::Encodable, Txid}; use dlc::{PartyParams, TxInputInfo}; use dlc_messages::{ @@ -59,26 +61,54 @@ pub(crate) fn compute_id( res } +/// Computes the secret key from the temporary id and the seed. +pub(crate) fn compute_secret_key( + seed: [u8; 32], + temporary_id: [u8; 32], + serial_ids: SerialIds, +) -> SecretKey { + let mut hmac = HmacEngine::::new(&seed); + hmac.input(&temporary_id); + hmac.input(&serial_ids.payout_serial_id.to_be_bytes()); + hmac.input(&serial_ids.change_serial_id.to_be_bytes()); + let secret_bytes = Hmac::from_engine(hmac).into_inner(); + SecretKey::from_slice(&secret_bytes).expect("Secret key is valid") +} + +#[derive(Debug, Clone, Copy)] +pub(crate) struct SerialIds { + pub payout_serial_id: u64, + pub change_serial_id: u64, +} + +impl SerialIds { + pub(crate) fn generate() -> Self { + SerialIds { + payout_serial_id: get_new_serial_id(), + change_serial_id: get_new_serial_id(), + } + } +} + pub(crate) fn get_party_params( secp: &Secp256k1, + funding_privkey: SecretKey, + serial_ids: SerialIds, own_collateral: u64, fee_rate: u64, wallet: &W, blockchain: &B, -) -> Result<(PartyParams, SecretKey, Vec), Error> +) -> Result<(PartyParams, Vec), Error> where W::Target: Wallet, B::Target: Blockchain, { - let funding_privkey = wallet.get_new_secret_key()?; let funding_pubkey = PublicKey::from_secret_key(secp, &funding_privkey); let payout_addr = wallet.get_new_address()?; let payout_spk = payout_addr.script_pubkey(); - let payout_serial_id = get_new_serial_id(); let change_addr = wallet.get_new_change_address()?; let change_spk = change_addr.script_pubkey(); - let change_serial_id = get_new_serial_id(); // Add base cost of fund tx + CET / 2 and a CET output to the collateral. let appr_required_amount = @@ -116,15 +146,15 @@ where let party_params = PartyParams { fund_pubkey: funding_pubkey, change_script_pubkey: change_spk, - change_serial_id, + change_serial_id: serial_ids.payout_serial_id, payout_script_pubkey: payout_spk, - payout_serial_id, + payout_serial_id: serial_ids.payout_serial_id, inputs: funding_tx_info, collateral: own_collateral, input_amount: total_input, }; - Ok((party_params, funding_privkey, funding_inputs_info)) + Ok((party_params, funding_inputs_info)) } pub(crate) fn get_party_base_points( diff --git a/dlc-manager/tests/channel_execution_tests.rs b/dlc-manager/tests/channel_execution_tests.rs index 0fe286c2..885ac780 100644 --- a/dlc-manager/tests/channel_execution_tests.rs +++ b/dlc-manager/tests/channel_execution_tests.rs @@ -26,6 +26,7 @@ use test_utils::{get_enum_test_params, TestParams}; use std::sync::mpsc::{Receiver, Sender}; use std::thread; +use bitcoin::secp256k1::rand::random; use std::{ collections::HashMap, sync::{ @@ -250,7 +251,7 @@ fn channel_renew_race_test() { } fn channel_execution_test(test_params: TestParams, path: TestPath) { - env_logger::init(); + env_logger::try_init().ok(); let (alice_send, bob_receive) = channel::>(); let (bob_send, alice_receive) = channel::>(); let (sync_send, sync_receive) = channel::<()>(); @@ -339,12 +340,14 @@ fn channel_execution_test(test_params: TestParams, path: TestPath) { refresh_wallet(&alice_wallet, 200000000); refresh_wallet(&bob_wallet, 200000000); + let alice_seed = random::<[u8; 32]>(); let alice_manager = Arc::new(Mutex::new( Manager::new( Arc::clone(&alice_wallet), Arc::clone(&electrs), alice_store, alice_oracles, + alice_seed, Arc::clone(&mock_time), Arc::clone(&electrs), ) @@ -354,12 +357,14 @@ fn channel_execution_test(test_params: TestParams, path: TestPath) { let alice_manager_loop = Arc::clone(&alice_manager); let alice_manager_send = Arc::clone(&alice_manager); + let bob_seed = random::<[u8; 32]>(); let bob_manager = Arc::new(Mutex::new( Manager::new( Arc::clone(&bob_wallet), Arc::clone(&electrs), Arc::clone(&bob_store), bob_oracles, + bob_seed, Arc::clone(&mock_time), Arc::clone(&electrs), ) diff --git a/dlc-manager/tests/manager_execution_tests.rs b/dlc-manager/tests/manager_execution_tests.rs index 44febde3..d6268634 100644 --- a/dlc-manager/tests/manager_execution_tests.rs +++ b/dlc-manager/tests/manager_execution_tests.rs @@ -14,6 +14,7 @@ use electrs_blockchain_provider::ElectrsBlockchainProvider; use simple_wallet::SimpleWallet; use test_utils::*; +use bitcoin::secp256k1::rand::random; use bitcoin_test_utils::rpc_helpers::init_clients; use bitcoincore_rpc::RpcApi; use dlc_manager::contract::{numerical_descriptor::DifferenceParams, Contract}; @@ -646,12 +647,14 @@ fn manager_execution_test(test_params: TestParams, path: TestPath, manual_close: refresh_wallet(&alice_wallet, 200000000); refresh_wallet(&bob_wallet, 200000000); + let alice_seed = random::<[u8; 32]>(); let alice_manager = Arc::new(Mutex::new( Manager::new( Arc::clone(&alice_wallet), Arc::clone(&electrs), alice_store, alice_oracles, + alice_seed, Arc::clone(&mock_time), Arc::clone(&electrs), ) @@ -661,12 +664,14 @@ fn manager_execution_test(test_params: TestParams, path: TestPath, manual_close: let alice_manager_loop = Arc::clone(&alice_manager); let alice_manager_send = Arc::clone(&alice_manager); + let bob_seed = random::<[u8; 32]>(); let bob_manager = Arc::new(Mutex::new( Manager::new( Arc::clone(&bob_wallet), Arc::clone(&electrs), bob_store, bob_oracles, + bob_seed, Arc::clone(&mock_time), Arc::clone(&electrs), ) diff --git a/sample/src/main.rs b/sample/src/main.rs index 3ada0c94..b8d18a15 100644 --- a/sample/src/main.rs +++ b/sample/src/main.rs @@ -78,6 +78,13 @@ async fn main() { let mut oracles = HashMap::new(); oracles.insert(oracle.get_public_key(), Box::new(oracle)); + let dlc_data_dir = format!("{}/.dlc", config.storage_dir_path); + let logger = Arc::new(FilesystemLogger::new(dlc_data_dir.clone())); + + let mut ephemeral_bytes = [0; 32]; + thread_rng().fill_bytes(&mut ephemeral_bytes); + let sk_path = format!("{}/secret_key", dlc_data_dir); + // Instantiate a DlcManager. let dlc_manager = Arc::new(Mutex::new( dlc_manager::manager::Manager::new( @@ -88,19 +95,13 @@ async fn main() { .expect("Error creating storage."), ), oracles, + ephemeral_bytes, Arc::new(dlc_manager::SystemTimeProvider {}), bitcoind_provider.clone(), ) .expect("Could not create manager."), )); - let dlc_data_dir = format!("{}/.dlc", config.storage_dir_path); - let logger = Arc::new(FilesystemLogger::new(dlc_data_dir.clone())); - - let mut ephemeral_bytes = [0; 32]; - thread_rng().fill_bytes(&mut ephemeral_bytes); - let sk_path = format!("{}/secret_key", dlc_data_dir); - // We store the private key in plaintext as this is an example, should be // avoided in a real application. let sk = if fs::metadata(&sk_path).is_ok() {