diff --git a/ark-bdk-wallet/src/lib.rs b/ark-bdk-wallet/src/lib.rs index 1ba37e5e..9d81b685 100644 --- a/ark-bdk-wallet/src/lib.rs +++ b/ark-bdk-wallet/src/lib.rs @@ -2,7 +2,6 @@ use anyhow::Result; use ark_client::error::Error; use ark_client::error::ErrorContext; use ark_client::wallet::Balance; -use ark_client::wallet::BoardingWallet; use ark_client::wallet::OnchainWallet; use ark_client::wallet::Persistence; use ark_core::BoardingOutput; @@ -16,9 +15,7 @@ use bdk_wallet::Wallet as BdkWallet; use bitcoin::bip32::Xpriv; use bitcoin::key::Keypair; use bitcoin::key::Secp256k1; -use bitcoin::secp256k1::schnorr::Signature; use bitcoin::secp256k1::All; -use bitcoin::secp256k1::Message; use bitcoin::Address; use bitcoin::Amount; use bitcoin::FeeRate; @@ -38,8 +35,6 @@ pub struct Wallet where DB: Persistence, { - kp: Keypair, - secp: Secp256k1, inner: Arc>, #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] client: esplora_client::AsyncClient, @@ -54,7 +49,7 @@ where { pub fn new( kp: Keypair, - secp: Secp256k1, + _secp: Secp256k1, network: Network, esplora_url: &str, db: DB, @@ -75,8 +70,6 @@ where esplora_client::Builder::new(esplora_url).build_async_with_sleeper::()?; Ok(Self { - kp, - secp, inner: Arc::new(RwLock::new(wallet)), client, db, @@ -237,44 +230,24 @@ where } } -impl BoardingWallet for Wallet +impl Persistence for Wallet where DB: Persistence, { - fn new_boarding_output( + fn save_boarding_output( &self, - server_pk: XOnlyPublicKey, - exit_delay: bitcoin::Sequence, - network: Network, - ) -> Result { - let sk = self.kp.secret_key(); - let (owner_pk, _) = sk.public_key(&self.secp).x_only_public_key(); - - let boarding_output = - BoardingOutput::new(&self.secp, server_pk, owner_pk, exit_delay, network)?; - - self.db - .save_boarding_output(sk, boarding_output.clone()) - .context("Failed saving boarding output")?; - - Ok(boarding_output) + sk: bitcoin::secp256k1::SecretKey, + boarding_output: BoardingOutput, + ) -> Result<(), Error> { + self.db.save_boarding_output(sk, boarding_output) } - fn get_boarding_outputs(&self) -> Result, Error> { + fn load_boarding_outputs(&self) -> Result, Error> { self.db.load_boarding_outputs() } - fn sign_for_pk(&self, pk: &XOnlyPublicKey, msg: &Message) -> Result { - let key = self - .db - .sk_for_pk(pk) - .with_context(|| format!("Failed retrieving SK for PK {pk}"))?; - - let sig = self - .secp - .sign_schnorr_no_aux_rand(msg, &key.keypair(&self.secp)); - - Ok(sig) + fn sk_for_pk(&self, pk: &XOnlyPublicKey) -> Result { + self.db.sk_for_pk(pk) } } diff --git a/ark-client/src/batch.rs b/ark-client/src/batch.rs index bbc7e2ac..c42c116f 100644 --- a/ark-client/src/batch.rs +++ b/ark-client/src/batch.rs @@ -2,8 +2,8 @@ use crate::error::ErrorContext as _; use crate::swap_storage::SwapStorage; use crate::utils::sleep; use crate::utils::timeout_op; -use crate::wallet::BoardingWallet; use crate::wallet::OnchainWallet; +use crate::wallet::Persistence; use crate::Blockchain; use crate::Client; use crate::Error; @@ -52,7 +52,7 @@ use std::collections::HashMap; impl Client where B: Blockchain, - W: BoardingWallet + OnchainWallet, + W: OnchainWallet + Persistence, S: SwapStorage + 'static, K: crate::KeyProvider, { @@ -927,7 +927,7 @@ where &self, ) -> Result<(Vec, Vec, Amount), Error> { // Get all known boarding outputs. - let boarding_outputs = self.inner.wallet.get_boarding_outputs()?; + let boarding_outputs = self.inner.wallet.load_boarding_outputs()?; let mut boarding_inputs: Vec = Vec::new(); let mut total_amount = Amount::ZERO; @@ -1162,11 +1162,12 @@ where })?; let owner_pk = onchain_input.boarding_output().owner_pk(); - let sig = self + let sk = self .inner .wallet - .sign_for_pk(&owner_pk, &msg) + .sk_for_pk(&owner_pk) .map_err(|e| ark_core::Error::ad_hoc(e.to_string()))?; + let sig = secp.sign_schnorr_no_aux_rand(&msg, &sk.keypair(&secp)); Ok((sig, owner_pk)) }; @@ -1635,10 +1636,13 @@ where schnorr::Signature, ark_core::Error, > { - self.inner + let sk = self + .inner .wallet - .sign_for_pk(pk, msg) - .map_err(|e| ark_core::Error::ad_hoc(e.to_string())) + .sk_for_pk(pk) + .map_err(|e| ark_core::Error::ad_hoc(e.to_string()))?; + let secp = self.secp(); + Ok(secp.sign_schnorr_no_aux_rand(msg, &sk.keypair(secp))) }; sign_commitment_psbt( diff --git a/ark-client/src/boltz.rs b/ark-client/src/boltz.rs index 39d0af2d..57edc3c4 100644 --- a/ark-client/src/boltz.rs +++ b/ark-client/src/boltz.rs @@ -2,8 +2,8 @@ use crate::batch::BatchOutputType; use crate::error::ErrorContext as _; use crate::swap_storage::SwapStorage; use crate::timeout_op; -use crate::wallet::BoardingWallet; use crate::wallet::OnchainWallet; +use crate::wallet::Persistence; use crate::Blockchain; use crate::Client; use crate::Error; @@ -73,7 +73,7 @@ pub struct ClaimVhtlcResult { impl Client where B: Blockchain, - W: BoardingWallet + OnchainWallet, + W: OnchainWallet + Persistence, S: SwapStorage + 'static, K: crate::KeyProvider, { diff --git a/ark-client/src/coin_select.rs b/ark-client/src/coin_select.rs index 034aebbd..e186dd69 100644 --- a/ark-client/src/coin_select.rs +++ b/ark-client/src/coin_select.rs @@ -1,6 +1,6 @@ use crate::swap_storage::SwapStorage; -use crate::wallet::BoardingWallet; use crate::wallet::OnchainWallet; +use crate::wallet::Persistence; use crate::Blockchain; use crate::Client; use crate::Error; @@ -34,11 +34,11 @@ pub async fn coin_select_for_onchain( > where B: Blockchain, - W: BoardingWallet + OnchainWallet, + W: OnchainWallet + Persistence, S: SwapStorage + 'static, K: crate::KeyProvider, { - let boarding_outputs = client.inner.wallet.get_boarding_outputs()?; + let boarding_outputs = client.inner.wallet.load_boarding_outputs()?; let now = Timestamp::now(); diff --git a/ark-client/src/fee_estimation.rs b/ark-client/src/fee_estimation.rs index 3be9f646..cc0eee9e 100644 --- a/ark-client/src/fee_estimation.rs +++ b/ark-client/src/fee_estimation.rs @@ -1,8 +1,8 @@ use crate::batch; use crate::batch::BatchOutputType; use crate::error::ErrorContext; -use crate::wallet::BoardingWallet; use crate::wallet::OnchainWallet; +use crate::wallet::Persistence; use crate::Client; use crate::Error; use crate::KeyProvider; @@ -20,7 +20,7 @@ use rand::Rng; impl Client where B: crate::Blockchain, - W: BoardingWallet + OnchainWallet, + W: OnchainWallet + Persistence, S: SwapStorage + 'static, K: KeyProvider, { diff --git a/ark-client/src/lib.rs b/ark-client/src/lib.rs index 3b6a5cd2..f60950c0 100644 --- a/ark-client/src/lib.rs +++ b/ark-client/src/lib.rs @@ -2,8 +2,8 @@ use crate::error::ErrorContext; use crate::key_provider::KeypairIndex; use crate::utils::sleep; use crate::utils::timeout_op; -use crate::wallet::BoardingWallet; use crate::wallet::OnchainWallet; +use crate::wallet::Persistence; use ark_core::build_anchor_tx; use ark_core::history; use ark_core::history::generate_incoming_vtxo_transaction_history; @@ -85,11 +85,10 @@ pub const DEFAULT_GAP_LIMIT: u32 = 20; /// # use ark_client::{Blockchain, Client, Error, SpendStatus, TxStatus}; /// # use ark_client::OfflineClient; /// # use bitcoin::key::Keypair; -/// # use bitcoin::secp256k1::{Message, SecretKey}; +/// # use bitcoin::secp256k1::SecretKey; /// # use std::sync::Arc; -/// # use bitcoin::{Address, Amount, FeeRate, Network, Psbt, Transaction, Txid, XOnlyPublicKey}; -/// # use bitcoin::secp256k1::schnorr::Signature; -/// # use ark_client::wallet::{Balance, BoardingWallet, OnchainWallet, Persistence}; +/// # use bitcoin::{Address, Amount, FeeRate, Psbt, Transaction, Txid, XOnlyPublicKey}; +/// # use ark_client::wallet::{Balance, OnchainWallet, Persistence}; /// # use ark_client::InMemorySwapStorage; /// # use ark_core::{BoardingOutput, UtxoCoinSelection, ExplorerUtxo}; /// # use ark_client::StaticKeyProvider; @@ -135,7 +134,7 @@ pub const DEFAULT_GAP_LIMIT: u32 = 20; /// # } /// /// struct MyWallet {} -/// # impl OnchainWallet for MyWallet where { +/// # impl OnchainWallet for MyWallet { /// # /// # fn get_onchain_address(&self) -> Result { /// # unimplemented!("You can implement this function using your preferred client library such as bdk") @@ -162,9 +161,7 @@ pub const DEFAULT_GAP_LIMIT: u32 = 20; /// # } /// # } /// # -/// -/// struct InMemoryDb {} -/// # impl Persistence for InMemoryDb { +/// # impl Persistence for MyWallet { /// # /// # fn save_boarding_output( /// # &self, @@ -183,28 +180,6 @@ pub const DEFAULT_GAP_LIMIT: u32 = 20; /// # } /// # } /// # -/// # -/// # impl BoardingWallet for MyWallet -/// # where -/// # { -/// # fn new_boarding_output( -/// # &self, -/// # server_pk: XOnlyPublicKey, -/// # exit_delay: bitcoin::Sequence, -/// # network: Network, -/// # ) -> Result { -/// # unimplemented!() -/// # } -/// # -/// # fn get_boarding_outputs(&self) -> Result, Error> { -/// # unimplemented!() -/// # } -/// # -/// # fn sign_for_pk(&self, pk: &XOnlyPublicKey, msg: &Message) -> Result { -/// # unimplemented!() -/// # } -/// # } -/// # /// // Initialize the client with a static keypair /// async fn init_client_with_keypair() -> Result, ark_client::Error> { /// // Create a keypair for signing transactions @@ -371,7 +346,7 @@ pub trait Blockchain { impl OfflineClient where B: Blockchain, - W: BoardingWallet + OnchainWallet, + W: OnchainWallet + Persistence, S: SwapStorage + 'static, K: KeyProvider, { @@ -578,7 +553,7 @@ where impl Client where B: Blockchain, - W: BoardingWallet + OnchainWallet, + W: OnchainWallet + Persistence, S: SwapStorage + 'static, K: KeyProvider, { @@ -721,16 +696,28 @@ where Ok(discovered_count) } - // At the moment we are always generating the same address. + /// Get a boarding address for receiving funds on-chain that can later be settled into VTXOs. + /// + /// Creates a new boarding output using the current keypair and saves it to persistence. pub fn get_boarding_address(&self) -> Result { let server_info = &self.server_info; - let boarding_output = self.inner.wallet.new_boarding_output( + let keypair = self.next_keypair(KeypairIndex::LastUnused)?; + let owner_pk = keypair.x_only_public_key().0; + + let boarding_output = ark_core::BoardingOutput::new( + self.secp(), server_info.signer_pk.into(), + owner_pk, server_info.boarding_exit_delay, server_info.network, )?; + // Save the boarding output and its secret key for later signing + self.inner + .wallet + .save_boarding_output(keypair.secret_key(), boarding_output.clone())?; + Ok(boarding_output.address().clone()) } diff --git a/ark-client/src/send_vtxo.rs b/ark-client/src/send_vtxo.rs index e51dd6fa..fabb5ae0 100644 --- a/ark-client/src/send_vtxo.rs +++ b/ark-client/src/send_vtxo.rs @@ -1,8 +1,8 @@ use crate::error::ErrorContext; use crate::swap_storage::SwapStorage; use crate::utils::timeout_op; -use crate::wallet::BoardingWallet; use crate::wallet::OnchainWallet; +use crate::wallet::Persistence; use crate::Blockchain; use crate::Client; use crate::Error; @@ -27,7 +27,7 @@ use bitcoin::XOnlyPublicKey; impl Client where B: Blockchain, - W: BoardingWallet + OnchainWallet, + W: OnchainWallet + Persistence, S: SwapStorage + 'static, K: crate::KeyProvider, { diff --git a/ark-client/src/unilateral_exit.rs b/ark-client/src/unilateral_exit.rs index 2f607af6..6851efa0 100644 --- a/ark-client/src/unilateral_exit.rs +++ b/ark-client/src/unilateral_exit.rs @@ -4,8 +4,8 @@ use crate::error::ErrorContext; use crate::swap_storage::SwapStorage; use crate::utils::sleep; use crate::utils::timeout_op; -use crate::wallet::BoardingWallet; use crate::wallet::OnchainWallet; +use crate::wallet::Persistence; use crate::Blockchain; use crate::Client; use ark_core::build_unilateral_exit_tree_txids; @@ -30,7 +30,7 @@ use std::collections::HashSet; impl Client where B: Blockchain, - W: BoardingWallet + OnchainWallet, + W: OnchainWallet + Persistence, S: SwapStorage + 'static, K: crate::KeyProvider, { @@ -261,15 +261,17 @@ where Some(script) => { let mut res = vec![]; let pks = extract_checksig_pubkeys(script); + let secp = self.secp(); for pk in pks { if let Ok(keypair) = self.keypair_by_pk(&pk) { - let sig = Secp256k1::new().sign_schnorr_no_aux_rand(&msg, &keypair); + let sig = secp.sign_schnorr_no_aux_rand(&msg, &keypair); let pk = keypair.x_only_public_key().0; res.push((sig, pk)) } - if let Ok(sig) = self.inner.wallet.sign_for_pk(&pk, &msg) { + if let Ok(sk) = self.inner.wallet.sk_for_pk(&pk) { + let sig = secp.sign_schnorr_no_aux_rand(&msg, &sk.keypair(&secp)); res.push((sig, pk)) } } diff --git a/ark-client/src/wallet.rs b/ark-client/src/wallet.rs index c030b432..e1d136c8 100644 --- a/ark-client/src/wallet.rs +++ b/ark-client/src/wallet.rs @@ -1,34 +1,14 @@ use crate::error::Error; use ark_core::BoardingOutput; use ark_core::UtxoCoinSelection; -use bitcoin::secp256k1::schnorr::Signature; -use bitcoin::secp256k1::Message; use bitcoin::secp256k1::SecretKey; use bitcoin::Address; use bitcoin::Amount; use bitcoin::FeeRate; -use bitcoin::Network; use bitcoin::Psbt; use bitcoin::XOnlyPublicKey; use std::future::Future; -// TODO: I think we should get rid of `BoardingWallet` and `OnchainWallet` and just use -// `KeyProvider` for everything! Also, IMO this client doesn't benefit from having an "on-chain -// wallet" within it. - -pub trait BoardingWallet { - fn new_boarding_output( - &self, - server_pubkey: XOnlyPublicKey, - exit_delay: bitcoin::Sequence, - network: Network, - ) -> Result; - - fn get_boarding_outputs(&self) -> Result, Error>; - - fn sign_for_pk(&self, pk: &XOnlyPublicKey, msg: &Message) -> Result; -} - pub trait OnchainWallet { fn get_onchain_address(&self) -> Result;