diff --git a/README.md b/README.md index d11c5fc8e..4e60d3602 100644 --- a/README.md +++ b/README.md @@ -14,20 +14,26 @@ LDK Node is a self-custodial Lightning node in library form. Its central goal is The primary abstraction of the library is the [`Node`][api_docs_node], which can be retrieved by setting up and configuring a [`Builder`][api_docs_builder] to your liking and calling one of the `build` methods. `Node` can then be controlled via commands such as `start`, `stop`, `open_channel`, `send`, etc. ```rust -use ldk_node::Builder; -use ldk_node::lightning_invoice::Bolt11Invoice; -use ldk_node::lightning::ln::msgs::SocketAddress; use ldk_node::bitcoin::secp256k1::PublicKey; use ldk_node::bitcoin::Network; +use ldk_node::entropy::{generate_entropy_mnemonic, NodeEntropy}; +use ldk_node::lightning::ln::msgs::SocketAddress; +use ldk_node::lightning_invoice::Bolt11Invoice; +use ldk_node::Builder; use std::str::FromStr; fn main() { let mut builder = Builder::new(); builder.set_network(Network::Testnet); builder.set_chain_source_esplora("https://blockstream.info/testnet/api".to_string(), None); - builder.set_gossip_source_rgs("https://rapidsync.lightningdevkit.org/testnet/snapshot".to_string()); + builder.set_gossip_source_rgs( + "https://rapidsync.lightningdevkit.org/testnet/snapshot".to_string(), + ); + - let node = builder.build().unwrap(); + let mnemonic = generate_entropy_mnemonic(None); + let node_entropy = NodeEntropy::from_bip39_mnemonic(mnemonic, None); + let node = builder.build(node_entropy).unwrap(); node.start().unwrap(); diff --git a/bindings/kotlin/ldk-node-android/lib/src/androidTest/kotlin/org/lightningdevkit/ldknode/AndroidLibTest.kt b/bindings/kotlin/ldk-node-android/lib/src/androidTest/kotlin/org/lightningdevkit/ldknode/AndroidLibTest.kt index fb29d3219..dd550f71a 100644 --- a/bindings/kotlin/ldk-node-android/lib/src/androidTest/kotlin/org/lightningdevkit/ldknode/AndroidLibTest.kt +++ b/bindings/kotlin/ldk-node-android/lib/src/androidTest/kotlin/org/lightningdevkit/ldknode/AndroidLibTest.kt @@ -34,8 +34,13 @@ class AndroidLibTest { val builder1 = Builder.fromConfig(config1) val builder2 = Builder.fromConfig(config2) - val node1 = builder1.build() - val node2 = builder2.build() + val mnemonic1 = generateEntropyMnemonic(null) + val nodeEntropy1 = NodeEntropy.fromBip39Mnemonic(mnemonic1, null) + val node1 = builder1.build(nodeEntropy1) + + val mnemonic2 = generateEntropyMnemonic(null) + val nodeEntropy2 = NodeEntropy.fromBip39Mnemonic(mnemonic2, null) + val node2 = builder2.build(nodeEntropy2) node1.start() node2.start() diff --git a/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt b/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt index c8c43c49c..006878a4c 100644 --- a/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt +++ b/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt @@ -193,8 +193,13 @@ class LibraryTest { builder2.setChainSourceEsplora(esploraEndpoint, null) builder2.setCustomLogger(logWriter2) - val node1 = builder1.build() - val node2 = builder2.build() + val mnemonic1 = generateEntropyMnemonic(null) + val nodeEntropy1 = NodeEntropy.fromBip39Mnemonic(mnemonic1, null) + val node1 = builder1.build(nodeEntropy1) + + val mnemonic2 = generateEntropyMnemonic(null) + val nodeEntropy2 = NodeEntropy.fromBip39Mnemonic(mnemonic2, null) + val node2 = builder2.build(nodeEntropy2) node1.start() node2.start() diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index ff2469c7e..c4ebf56a6 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -47,6 +47,20 @@ dictionary LSPS2ServiceConfig { boolean client_trusts_lsp; }; +interface NodeEntropy { + [Name=from_bip39_mnemonic] + constructor(Mnemonic mnemonic, string? passphrase); + [Throws=EntropyError, Name=from_seed_bytes] + constructor(bytes seed_bytes); + [Throws=EntropyError, Name=from_seed_path] + constructor(string seed_path); +}; + +enum EntropyError { + "InvalidSeedBytes", + "InvalidSeedFile", +}; + enum WordCount { "Words12", "Words15", @@ -80,10 +94,6 @@ interface Builder { constructor(); [Name=from_config] constructor(Config config); - void set_entropy_seed_path(string seed_path); - [Throws=BuildError] - void set_entropy_seed_bytes(sequence seed_bytes); - void set_entropy_bip39_mnemonic(Mnemonic mnemonic, string? passphrase); void set_chain_source_esplora(string server_url, EsploraSyncConfig? config); void set_chain_source_electrum(string server_url, ElectrumSyncConfig? config); void set_chain_source_bitcoind_rpc(string rpc_host, u16 rpc_port, string rpc_user, string rpc_password); @@ -107,15 +117,15 @@ interface Builder { [Throws=BuildError] void set_async_payments_role(AsyncPaymentsRole? role); [Throws=BuildError] - Node build(); + Node build(NodeEntropy node_entropy); [Throws=BuildError] - Node build_with_fs_store(); + Node build_with_fs_store(NodeEntropy node_entropy); [Throws=BuildError] - Node build_with_vss_store(string vss_url, string store_id, string lnurl_auth_server_url, record fixed_headers); + Node build_with_vss_store(NodeEntropy node_entropy, string vss_url, string store_id, string lnurl_auth_server_url, record fixed_headers); [Throws=BuildError] - Node build_with_vss_store_and_fixed_headers(string vss_url, string store_id, record fixed_headers); + Node build_with_vss_store_and_fixed_headers(NodeEntropy node_entropy, string vss_url, string store_id, record fixed_headers); [Throws=BuildError] - Node build_with_vss_store_and_header_provider(string vss_url, string store_id, VssHeaderProvider header_provider); + Node build_with_vss_store_and_header_provider(NodeEntropy node_entropy, string vss_url, string store_id, VssHeaderProvider header_provider); }; interface Node { @@ -357,8 +367,6 @@ dictionary BestBlock { [Error] enum BuildError { - "InvalidSeedBytes", - "InvalidSeedFile", "InvalidSystemTime", "InvalidChannelMonitor", "InvalidListeningAddresses", diff --git a/bindings/python/src/ldk_node/test_ldk_node.py b/bindings/python/src/ldk_node/test_ldk_node.py index f71e89df8..0b73e6a47 100644 --- a/bindings/python/src/ldk_node/test_ldk_node.py +++ b/bindings/python/src/ldk_node/test_ldk_node.py @@ -97,13 +97,15 @@ def send_to_address(address, amount_sats): def setup_node(tmp_dir, esplora_endpoint, listening_addresses): + mnemonic = generate_entropy_mnemonic(None) + node_entropy = NodeEntropy.from_bip39_mnemonic(mnemonic, None) config = default_config() builder = Builder.from_config(config) builder.set_storage_dir_path(tmp_dir) builder.set_chain_source_esplora(esplora_endpoint, None) builder.set_network(DEFAULT_TEST_NETWORK) builder.set_listening_addresses(listening_addresses) - return builder.build() + return builder.build(node_entropy) def get_esplora_endpoint(): if os.environ.get('ESPLORA_ENDPOINT'): diff --git a/src/builder.rs b/src/builder.rs index 63e84db37..13a7567b7 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -15,7 +15,6 @@ use std::{fmt, fs}; use bdk_wallet::template::Bip84; use bdk_wallet::{KeychainKind, Wallet as BdkWallet}; -use bip39::Mnemonic; use bitcoin::bip32::{ChildNumber, Xpriv}; use bitcoin::secp256k1::PublicKey; use bitcoin::{BlockHash, Network}; @@ -45,9 +44,10 @@ use crate::chain::ChainSource; use crate::config::{ default_user_config, may_announce_channel, AnnounceError, AsyncPaymentsRole, BitcoindRestClientConfig, Config, ElectrumSyncConfig, EsploraSyncConfig, - DEFAULT_ESPLORA_SERVER_URL, DEFAULT_LOG_FILENAME, DEFAULT_LOG_LEVEL, WALLET_KEYS_SEED_LEN, + DEFAULT_ESPLORA_SERVER_URL, DEFAULT_LOG_FILENAME, DEFAULT_LOG_LEVEL, }; use crate::connection::ConnectionManager; +use crate::entropy::NodeEntropy; use crate::event::EventQueue; use crate::fee_estimator::OnchainFeeEstimator; use crate::gossip::GossipSource; @@ -101,13 +101,6 @@ enum ChainDataSourceConfig { }, } -#[derive(Debug, Clone)] -enum EntropySourceConfig { - SeedFile(String), - SeedBytes([u8; WALLET_KEYS_SEED_LEN]), - Bip39Mnemonic { mnemonic: Mnemonic, passphrase: Option }, -} - #[derive(Debug, Clone)] enum GossipSourceConfig { P2PNetwork, @@ -157,10 +150,6 @@ impl std::fmt::Debug for LogWriterConfig { /// [`Node`]: crate::Node #[derive(Debug, Clone, PartialEq)] pub enum BuildError { - /// The given seed bytes are invalid, e.g., have invalid length. - InvalidSeedBytes, - /// The given seed file is invalid, e.g., has invalid length, or could not be read. - InvalidSeedFile, /// The current system time is invalid, clocks might have gone backwards. InvalidSystemTime, /// The a read channel monitor is invalid. @@ -200,8 +189,6 @@ pub enum BuildError { impl fmt::Display for BuildError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - Self::InvalidSeedBytes => write!(f, "Given seed bytes are invalid."), - Self::InvalidSeedFile => write!(f, "Given seed file is invalid or could not be read."), Self::InvalidSystemTime => { write!(f, "System time is invalid. Clocks might have gone back in time.") }, @@ -245,7 +232,6 @@ impl std::error::Error for BuildError {} #[derive(Debug)] pub struct NodeBuilder { config: Config, - entropy_source_config: Option, chain_data_source_config: Option, gossip_source_config: Option, liquidity_source_config: Option, @@ -264,7 +250,6 @@ impl NodeBuilder { /// Creates a new builder instance from an [`Config`]. pub fn from_config(config: Config) -> Self { - let entropy_source_config = None; let chain_data_source_config = None; let gossip_source_config = None; let liquidity_source_config = None; @@ -273,7 +258,6 @@ impl NodeBuilder { let pathfinding_scores_sync_config = None; Self { config, - entropy_source_config, chain_data_source_config, gossip_source_config, liquidity_source_config, @@ -294,33 +278,6 @@ impl NodeBuilder { self } - /// Configures the [`Node`] instance to source its wallet entropy from a seed file on disk. - /// - /// If the given file does not exist a new random seed file will be generated and - /// stored at the given location. - pub fn set_entropy_seed_path(&mut self, seed_path: String) -> &mut Self { - self.entropy_source_config = Some(EntropySourceConfig::SeedFile(seed_path)); - self - } - - /// Configures the [`Node`] instance to source its wallet entropy from the given - /// [`WALLET_KEYS_SEED_LEN`] seed bytes. - pub fn set_entropy_seed_bytes(&mut self, seed_bytes: [u8; WALLET_KEYS_SEED_LEN]) -> &mut Self { - self.entropy_source_config = Some(EntropySourceConfig::SeedBytes(seed_bytes)); - self - } - - /// Configures the [`Node`] instance to source its wallet entropy from a [BIP 39] mnemonic. - /// - /// [BIP 39]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki - pub fn set_entropy_bip39_mnemonic( - &mut self, mnemonic: Mnemonic, passphrase: Option, - ) -> &mut Self { - self.entropy_source_config = - Some(EntropySourceConfig::Bip39Mnemonic { mnemonic, passphrase }); - self - } - /// Configures the [`Node`] instance to source its chain data from the given Esplora server. /// /// If no `sync_config` is given, default values are used. See [`EsploraSyncConfig`] for more @@ -584,7 +541,7 @@ impl NodeBuilder { /// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options /// previously configured. - pub fn build(&self) -> Result { + pub fn build(&self, node_entropy: NodeEntropy) -> Result { let storage_dir_path = self.config.storage_dir_path.clone(); fs::create_dir_all(storage_dir_path.clone()) .map_err(|_| BuildError::StoragePathAccessFailed)?; @@ -596,19 +553,19 @@ impl NodeBuilder { ) .map_err(|_| BuildError::KVStoreSetupFailed)?, ); - self.build_with_store(kv_store) + self.build_with_store(node_entropy, kv_store) } /// Builds a [`Node`] instance with a [`FilesystemStore`] backend and according to the options /// previously configured. - pub fn build_with_fs_store(&self) -> Result { + pub fn build_with_fs_store(&self, node_entropy: NodeEntropy) -> Result { let mut storage_dir_path: PathBuf = self.config.storage_dir_path.clone().into(); storage_dir_path.push("fs_store"); fs::create_dir_all(storage_dir_path.clone()) .map_err(|_| BuildError::StoragePathAccessFailed)?; let kv_store = Arc::new(FilesystemStore::new(storage_dir_path)); - self.build_with_store(kv_store) + self.build_with_store(node_entropy, kv_store) } /// Builds a [`Node`] instance with a [VSS] backend and according to the options @@ -629,19 +586,14 @@ impl NodeBuilder { /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md /// [LNURL-auth]: https://github.com/lnurl/luds/blob/luds/04.md pub fn build_with_vss_store( - &self, vss_url: String, store_id: String, lnurl_auth_server_url: String, - fixed_headers: HashMap, + &self, node_entropy: NodeEntropy, vss_url: String, store_id: String, + lnurl_auth_server_url: String, fixed_headers: HashMap, ) -> Result { use bitcoin::key::Secp256k1; let logger = setup_logger(&self.log_writer_config, &self.config)?; - let seed_bytes = seed_bytes_from_config( - &self.config, - self.entropy_source_config.as_ref(), - Arc::clone(&logger), - )?; - + let seed_bytes = node_entropy.to_seed_bytes(); let config = Arc::new(self.config.clone()); let vss_xprv = @@ -666,7 +618,12 @@ impl NodeBuilder { let header_provider = Arc::new(lnurl_auth_jwt_provider); - self.build_with_vss_store_and_header_provider(vss_url, store_id, header_provider) + self.build_with_vss_store_and_header_provider( + node_entropy, + vss_url, + store_id, + header_provider, + ) } /// Builds a [`Node`] instance with a [VSS] backend and according to the options @@ -682,11 +639,17 @@ impl NodeBuilder { /// /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md pub fn build_with_vss_store_and_fixed_headers( - &self, vss_url: String, store_id: String, fixed_headers: HashMap, + &self, node_entropy: NodeEntropy, vss_url: String, store_id: String, + fixed_headers: HashMap, ) -> Result { let header_provider = Arc::new(FixedHeaders::new(fixed_headers)); - self.build_with_vss_store_and_header_provider(vss_url, store_id, header_provider) + self.build_with_vss_store_and_header_provider( + node_entropy, + vss_url, + store_id, + header_provider, + ) } /// Builds a [`Node`] instance with a [VSS] backend and according to the options @@ -701,25 +664,12 @@ impl NodeBuilder { /// /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md pub fn build_with_vss_store_and_header_provider( - &self, vss_url: String, store_id: String, header_provider: Arc, + &self, node_entropy: NodeEntropy, vss_url: String, store_id: String, + header_provider: Arc, ) -> Result { let logger = setup_logger(&self.log_writer_config, &self.config)?; - let runtime = if let Some(handle) = self.runtime_handle.as_ref() { - Arc::new(Runtime::with_handle(handle.clone(), Arc::clone(&logger))) - } else { - Arc::new(Runtime::new(Arc::clone(&logger)).map_err(|e| { - log_error!(logger, "Failed to setup tokio runtime: {}", e); - BuildError::RuntimeSetupFailed - })?) - }; - - let seed_bytes = seed_bytes_from_config( - &self.config, - self.entropy_source_config.as_ref(), - Arc::clone(&logger), - )?; - + let seed_bytes = node_entropy.to_seed_bytes(); let config = Arc::new(self.config.clone()); let vss_xprv = derive_xprv( @@ -737,22 +687,13 @@ impl NodeBuilder { BuildError::KVStoreSetupFailed })?; - build_with_store_internal( - config, - self.chain_data_source_config.as_ref(), - self.gossip_source_config.as_ref(), - self.liquidity_source_config.as_ref(), - self.pathfinding_scores_sync_config.as_ref(), - self.async_payments_role, - seed_bytes, - runtime, - logger, - Arc::new(vss_store), - ) + self.build_with_store(node_entropy, Arc::new(vss_store)) } /// Builds a [`Node`] instance according to the options previously configured. - pub fn build_with_store(&self, kv_store: Arc) -> Result { + pub fn build_with_store( + &self, node_entropy: NodeEntropy, kv_store: Arc, + ) -> Result { let logger = setup_logger(&self.log_writer_config, &self.config)?; let runtime = if let Some(handle) = self.runtime_handle.as_ref() { @@ -764,11 +705,7 @@ impl NodeBuilder { })?) }; - let seed_bytes = seed_bytes_from_config( - &self.config, - self.entropy_source_config.as_ref(), - Arc::clone(&logger), - )?; + let seed_bytes = node_entropy.to_seed_bytes(); let config = Arc::new(self.config.clone()); build_with_store_internal( @@ -813,37 +750,6 @@ impl ArcedNodeBuilder { Self { inner } } - /// Configures the [`Node`] instance to source its wallet entropy from a seed file on disk. - /// - /// If the given file does not exist a new random seed file will be generated and - /// stored at the given location. - pub fn set_entropy_seed_path(&self, seed_path: String) { - self.inner.write().unwrap().set_entropy_seed_path(seed_path); - } - - /// Configures the [`Node`] instance to source its wallet entropy from the given - /// [`WALLET_KEYS_SEED_LEN`] seed bytes. - /// - /// **Note:** Will return an error if the length of the given `seed_bytes` differs from - /// [`WALLET_KEYS_SEED_LEN`]. - pub fn set_entropy_seed_bytes(&self, seed_bytes: Vec) -> Result<(), BuildError> { - if seed_bytes.len() != WALLET_KEYS_SEED_LEN { - return Err(BuildError::InvalidSeedBytes); - } - let mut bytes = [0u8; WALLET_KEYS_SEED_LEN]; - bytes.copy_from_slice(&seed_bytes); - - self.inner.write().unwrap().set_entropy_seed_bytes(bytes); - Ok(()) - } - - /// Configures the [`Node`] instance to source its wallet entropy from a [BIP 39] mnemonic. - /// - /// [BIP 39]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki - pub fn set_entropy_bip39_mnemonic(&self, mnemonic: Mnemonic, passphrase: Option) { - self.inner.write().unwrap().set_entropy_bip39_mnemonic(mnemonic, passphrase); - } - /// Configures the [`Node`] instance to source its chain data from the given Esplora server. /// /// If no `sync_config` is given, default values are used. See [`EsploraSyncConfig`] for more @@ -1051,14 +957,16 @@ impl ArcedNodeBuilder { /// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options /// previously configured. - pub fn build(&self) -> Result, BuildError> { - self.inner.read().unwrap().build().map(Arc::new) + pub fn build(&self, node_entropy: Arc) -> Result, BuildError> { + self.inner.read().unwrap().build(*node_entropy).map(Arc::new) } /// Builds a [`Node`] instance with a [`FilesystemStore`] backend and according to the options /// previously configured. - pub fn build_with_fs_store(&self) -> Result, BuildError> { - self.inner.read().unwrap().build_with_fs_store().map(Arc::new) + pub fn build_with_fs_store( + &self, node_entropy: Arc, + ) -> Result, BuildError> { + self.inner.read().unwrap().build_with_fs_store(*node_entropy).map(Arc::new) } /// Builds a [`Node`] instance with a [VSS] backend and according to the options @@ -1079,13 +987,19 @@ impl ArcedNodeBuilder { /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md /// [LNURL-auth]: https://github.com/lnurl/luds/blob/luds/04.md pub fn build_with_vss_store( - &self, vss_url: String, store_id: String, lnurl_auth_server_url: String, - fixed_headers: HashMap, + &self, node_entropy: Arc, vss_url: String, store_id: String, + lnurl_auth_server_url: String, fixed_headers: HashMap, ) -> Result, BuildError> { self.inner .read() .unwrap() - .build_with_vss_store(vss_url, store_id, lnurl_auth_server_url, fixed_headers) + .build_with_vss_store( + *node_entropy, + vss_url, + store_id, + lnurl_auth_server_url, + fixed_headers, + ) .map(Arc::new) } @@ -1102,12 +1016,13 @@ impl ArcedNodeBuilder { /// /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md pub fn build_with_vss_store_and_fixed_headers( - &self, vss_url: String, store_id: String, fixed_headers: HashMap, + &self, node_entropy: Arc, vss_url: String, store_id: String, + fixed_headers: HashMap, ) -> Result, BuildError> { self.inner .read() .unwrap() - .build_with_vss_store_and_fixed_headers(vss_url, store_id, fixed_headers) + .build_with_vss_store_and_fixed_headers(*node_entropy, vss_url, store_id, fixed_headers) .map(Arc::new) } @@ -1123,18 +1038,26 @@ impl ArcedNodeBuilder { /// /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md pub fn build_with_vss_store_and_header_provider( - &self, vss_url: String, store_id: String, header_provider: Arc, + &self, node_entropy: Arc, vss_url: String, store_id: String, + header_provider: Arc, ) -> Result, BuildError> { self.inner .read() .unwrap() - .build_with_vss_store_and_header_provider(vss_url, store_id, header_provider) + .build_with_vss_store_and_header_provider( + *node_entropy, + vss_url, + store_id, + header_provider, + ) .map(Arc::new) } /// Builds a [`Node`] instance according to the options previously configured. - pub fn build_with_store(&self, kv_store: Arc) -> Result, BuildError> { - self.inner.read().unwrap().build_with_store(kv_store).map(Arc::new) + pub fn build_with_store( + &self, node_entropy: Arc, kv_store: Arc, + ) -> Result, BuildError> { + self.inner.read().unwrap().build_with_store(*node_entropy, kv_store).map(Arc::new) } } @@ -1285,7 +1208,7 @@ fn build_with_store_internal( // Initialize the on-chain wallet and chain access let xprv = bitcoin::bip32::Xpriv::new_master(config.network, &seed_bytes).map_err(|e| { log_error!(logger, "Failed to derive master secret: {}", e); - BuildError::InvalidSeedBytes + BuildError::WalletSetupFailed })?; let descriptor = Bip84(xprv, KeychainKind::External); @@ -1871,28 +1794,6 @@ fn setup_logger( Ok(Arc::new(logger)) } -fn seed_bytes_from_config( - config: &Config, entropy_source_config: Option<&EntropySourceConfig>, logger: Arc, -) -> Result<[u8; 64], BuildError> { - match entropy_source_config { - Some(EntropySourceConfig::SeedBytes(bytes)) => Ok(bytes.clone()), - Some(EntropySourceConfig::SeedFile(seed_path)) => { - Ok(io::utils::read_or_generate_seed_file(seed_path, Arc::clone(&logger)) - .map_err(|_| BuildError::InvalidSeedFile)?) - }, - Some(EntropySourceConfig::Bip39Mnemonic { mnemonic, passphrase }) => match passphrase { - Some(passphrase) => Ok(mnemonic.to_seed(passphrase)), - None => Ok(mnemonic.to_seed("")), - }, - None => { - // Default to read or generate from the default location generate a seed file. - let seed_path = format!("{}/keys_seed", config.storage_dir_path); - Ok(io::utils::read_or_generate_seed_file(&seed_path, Arc::clone(&logger)) - .map_err(|_| BuildError::InvalidSeedFile)?) - }, - } -} - fn derive_xprv( config: Arc, seed_bytes: &[u8; 64], hardened_child_index: u32, logger: Arc, ) -> Result { @@ -1900,13 +1801,13 @@ fn derive_xprv( let xprv = Xpriv::new_master(config.network, seed_bytes).map_err(|e| { log_error!(logger, "Failed to derive master secret: {}", e); - BuildError::InvalidSeedBytes + BuildError::WalletSetupFailed })?; xprv.derive_priv(&Secp256k1::new(), &[ChildNumber::Hardened { index: hardened_child_index }]) .map_err(|e| { log_error!(logger, "Failed to derive hardened child secret: {}", e); - BuildError::InvalidSeedBytes + BuildError::WalletSetupFailed }) } diff --git a/src/entropy.rs b/src/entropy.rs new file mode 100644 index 000000000..8bd338622 --- /dev/null +++ b/src/entropy.rs @@ -0,0 +1,191 @@ +// This file is Copyright its original authors, visible in version control history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license , at your option. You may not use this file except in +// accordance with one or both of these licenses. + +//! Contains utilities for configuring and generating entropy. + +use std::fmt; + +use bip39::Mnemonic; + +use crate::config::WALLET_KEYS_SEED_LEN; +use crate::io; + +/// An error that could arise during [`NodeEntropy`] construction. +#[derive(Debug, Clone, PartialEq)] +pub enum EntropyError { + /// The given seed bytes are invalid, e.g., have invalid length. + InvalidSeedBytes, + /// The given seed file is invalid, e.g., has invalid length, or could not be read. + InvalidSeedFile, +} + +impl fmt::Display for EntropyError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::InvalidSeedBytes => write!(f, "Given seed bytes are invalid."), + Self::InvalidSeedFile => write!(f, "Given seed file is invalid or could not be read."), + } + } +} + +impl std::error::Error for EntropyError {} + +/// The node entropy, i.e., the main secret from which all other secrets of the [`Node`] are +/// derived. +/// +/// [`Node`]: crate::Node +#[derive(Copy, Clone)] +pub struct NodeEntropy([u8; WALLET_KEYS_SEED_LEN]); + +impl NodeEntropy { + /// Configures the [`Node`] instance to source its wallet entropy from a [BIP 39] mnemonic. + /// + /// [BIP 39]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki + /// [`Node`]: crate::Node + pub fn from_bip39_mnemonic(mnemonic: Mnemonic, passphrase: Option) -> Self { + match passphrase { + Some(passphrase) => Self(mnemonic.to_seed(passphrase)), + None => Self(mnemonic.to_seed("")), + } + } + + /// Configures the [`Node`] instance to source its wallet entropy from the given + /// [`WALLET_KEYS_SEED_LEN`] seed bytes. + /// + /// [`Node`]: crate::Node + #[cfg(not(feature = "uniffi"))] + pub fn from_seed_bytes(seed_bytes: [u8; WALLET_KEYS_SEED_LEN]) -> Self { + Self(seed_bytes) + } + + /// Configures the [`Node`] instance to source its wallet entropy from the given + /// [`WALLET_KEYS_SEED_LEN`] seed bytes. + /// + /// Will return an error if the length of the given `Vec` is not exactly + /// [`WALLET_KEYS_SEED_LEN`]. + /// + /// [`Node`]: crate::Node + #[cfg(feature = "uniffi")] + pub fn from_seed_bytes(seed_bytes: Vec) -> Result { + if seed_bytes.len() != WALLET_KEYS_SEED_LEN { + return Err(EntropyError::InvalidSeedBytes); + } + let mut seed_bytes_inner = [0u8; WALLET_KEYS_SEED_LEN]; + seed_bytes_inner.copy_from_slice(&seed_bytes); + Ok(Self(seed_bytes_inner)) + } + + /// Configures the [`Node`] instance to source its wallet entropy from a seed file on disk. + /// + /// If the given file does not exist a new random seed file will be generated and + /// stored at the given location. + /// + /// [`Node`]: crate::Node + pub fn from_seed_path(seed_path: String) -> Result { + Ok(Self( + io::utils::read_or_generate_seed_file(&seed_path) + .map_err(|_| EntropyError::InvalidSeedFile)?, + )) + } + + pub(crate) fn to_seed_bytes(&self) -> [u8; WALLET_KEYS_SEED_LEN] { + self.0 + } +} + +impl fmt::Display for NodeEntropy { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "NODE ENTROPY") + } +} + +impl fmt::Debug for NodeEntropy { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "NODE ENTROPY") + } +} + +/// Generates a random [BIP 39] mnemonic with the specified word count. +/// +/// If no word count is specified, defaults to 24 words (256-bit entropy). +/// +/// The result may be used to initialize the [`NodeEntropy`], i.e., can be given to +/// [`NodeEntropy::from_bip39_mnemonic`]. +/// +/// [BIP 39]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki +/// [`Node`]: crate::Node +pub fn generate_entropy_mnemonic(word_count: Option) -> Mnemonic { + let word_count = word_count.unwrap_or(WordCount::Words24).word_count(); + Mnemonic::generate(word_count).expect("Failed to generate mnemonic") +} + +/// Supported BIP39 mnemonic word counts for entropy generation. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum WordCount { + /// 12-word mnemonic (128-bit entropy) + Words12, + /// 15-word mnemonic (160-bit entropy) + Words15, + /// 18-word mnemonic (192-bit entropy) + Words18, + /// 21-word mnemonic (224-bit entropy) + Words21, + /// 24-word mnemonic (256-bit entropy) + Words24, +} + +impl WordCount { + /// Returns the word count as a usize value. + pub fn word_count(&self) -> usize { + match self { + WordCount::Words12 => 12, + WordCount::Words15 => 15, + WordCount::Words18 => 18, + WordCount::Words21 => 21, + WordCount::Words24 => 24, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn mnemonic_to_entropy_to_mnemonic() { + // Test default (24 words) + let mnemonic = generate_entropy_mnemonic(None); + let entropy = mnemonic.to_entropy(); + assert_eq!(mnemonic, Mnemonic::from_entropy(&entropy).unwrap()); + assert_eq!(mnemonic.word_count(), 24); + + // Test with different word counts + let word_counts = [ + WordCount::Words12, + WordCount::Words15, + WordCount::Words18, + WordCount::Words21, + WordCount::Words24, + ]; + + for word_count in word_counts { + let mnemonic = generate_entropy_mnemonic(Some(word_count)); + let entropy = mnemonic.to_entropy(); + assert_eq!(mnemonic, Mnemonic::from_entropy(&entropy).unwrap()); + + // Verify expected word count + let expected_words = match word_count { + WordCount::Words12 => 12, + WordCount::Words15 => 15, + WordCount::Words18 => 18, + WordCount::Words21 => 21, + WordCount::Words24 => 24, + }; + assert_eq!(mnemonic.word_count(), expected_words); + } + } +} diff --git a/src/ffi/types.rs b/src/ffi/types.rs index 3c88a665f..c69987c96 100644 --- a/src/ffi/types.rs +++ b/src/ffi/types.rs @@ -47,6 +47,7 @@ pub use crate::config::{ default_config, AnchorChannelsConfig, BackgroundSyncConfig, ElectrumSyncConfig, EsploraSyncConfig, MaxDustHTLCExposure, }; +pub use crate::entropy::{generate_entropy_mnemonic, EntropyError, NodeEntropy, WordCount}; use crate::error::Error; pub use crate::graph::{ChannelInfo, ChannelUpdateInfo, NodeAnnouncementInfo, NodeInfo}; pub use crate::liquidity::{LSPS1OrderStatus, LSPS2ServiceConfig}; diff --git a/src/io/utils.rs b/src/io/utils.rs index 1b4b02a82..928d4031b 100644 --- a/src/io/utils.rs +++ b/src/io/utils.rs @@ -17,7 +17,6 @@ use bdk_chain::miniscript::{Descriptor, DescriptorPublicKey}; use bdk_chain::tx_graph::ChangeSet as BdkTxGraphChangeSet; use bdk_chain::ConfirmationBlockTime; use bdk_wallet::ChangeSet as BdkWalletChangeSet; -use bip39::Mnemonic; use bitcoin::Network; use lightning::io::Cursor; use lightning::ln::msgs::DecodeError; @@ -47,45 +46,19 @@ use crate::io::{ }; use crate::logger::{log_error, LdkLogger, Logger}; use crate::peer_store::PeerStore; -use crate::types::{Broadcaster, DynStore, KeysManager, Sweeper, WordCount}; +use crate::types::{Broadcaster, DynStore, KeysManager, Sweeper}; use crate::wallet::ser::{ChangeSetDeserWrapper, ChangeSetSerWrapper}; use crate::{Error, EventQueue, NodeMetrics, PaymentDetails}; pub const EXTERNAL_PATHFINDING_SCORES_CACHE_KEY: &str = "external_pathfinding_scores_cache"; -/// Generates a random [BIP 39] mnemonic with the specified word count. -/// -/// If no word count is specified, defaults to 24 words (256-bit entropy). -/// -/// The result may be used to initialize the [`Node`] entropy, i.e., can be given to -/// [`Builder::set_entropy_bip39_mnemonic`]. -/// -/// [BIP 39]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki -/// [`Node`]: crate::Node -/// [`Builder::set_entropy_bip39_mnemonic`]: crate::Builder::set_entropy_bip39_mnemonic -pub fn generate_entropy_mnemonic(word_count: Option) -> Mnemonic { - let word_count = word_count.unwrap_or(WordCount::Words24).word_count(); - Mnemonic::generate(word_count).expect("Failed to generate mnemonic") -} - -pub(crate) fn read_or_generate_seed_file( - keys_seed_path: &str, logger: L, -) -> std::io::Result<[u8; WALLET_KEYS_SEED_LEN]> -where - L::Target: LdkLogger, -{ +pub(crate) fn read_or_generate_seed_file( + keys_seed_path: &str, +) -> std::io::Result<[u8; WALLET_KEYS_SEED_LEN]> { if Path::new(&keys_seed_path).exists() { - let seed = fs::read(keys_seed_path).map_err(|e| { - log_error!(logger, "Failed to read keys seed file: {}", keys_seed_path); - e - })?; + let seed = fs::read(keys_seed_path)?; if seed.len() != WALLET_KEYS_SEED_LEN { - log_error!( - logger, - "Failed to read keys seed file due to invalid length: {}", - keys_seed_path - ); return Err(std::io::Error::new( std::io::ErrorKind::InvalidData, "Failed to read keys seed file due to invalid length", @@ -97,37 +70,19 @@ where Ok(key) } else { let mut key = [0; WALLET_KEYS_SEED_LEN]; - OsRng.try_fill_bytes(&mut key).map_err(|e| { - log_error!(logger, "Failed to generate entropy: {}", e); + OsRng.try_fill_bytes(&mut key).map_err(|_| { std::io::Error::new(std::io::ErrorKind::Other, "Failed to generate seed bytes") })?; if let Some(parent_dir) = Path::new(&keys_seed_path).parent() { - fs::create_dir_all(parent_dir).map_err(|e| { - log_error!( - logger, - "Failed to create parent directory for key seed file: {}.", - keys_seed_path - ); - e - })?; + fs::create_dir_all(parent_dir)?; } - let mut f = fs::File::create(keys_seed_path).map_err(|e| { - log_error!(logger, "Failed to create keys seed file: {}", keys_seed_path); - e - })?; - - f.write_all(&key).map_err(|e| { - log_error!(logger, "Failed to write node keys seed to disk: {}", keys_seed_path); - e - })?; + let mut f = fs::File::create(keys_seed_path)?; - f.sync_all().map_err(|e| { - log_error!(logger, "Failed to sync node keys seed to disk: {}", keys_seed_path); - e - })?; + f.write_all(&key)?; + f.sync_all()?; Ok(key) } } @@ -623,39 +578,15 @@ pub(crate) fn read_bdk_wallet_change_set( #[cfg(test)] mod tests { - use super::*; + use super::read_or_generate_seed_file; + use super::test_utils::random_storage_path; #[test] - fn mnemonic_to_entropy_to_mnemonic() { - // Test default (24 words) - let mnemonic = generate_entropy_mnemonic(None); - let entropy = mnemonic.to_entropy(); - assert_eq!(mnemonic, Mnemonic::from_entropy(&entropy).unwrap()); - assert_eq!(mnemonic.word_count(), 24); - - // Test with different word counts - let word_counts = [ - WordCount::Words12, - WordCount::Words15, - WordCount::Words18, - WordCount::Words21, - WordCount::Words24, - ]; - - for word_count in word_counts { - let mnemonic = generate_entropy_mnemonic(Some(word_count)); - let entropy = mnemonic.to_entropy(); - assert_eq!(mnemonic, Mnemonic::from_entropy(&entropy).unwrap()); - - // Verify expected word count - let expected_words = match word_count { - WordCount::Words12 => 12, - WordCount::Words15 => 15, - WordCount::Words18 => 18, - WordCount::Words21 => 21, - WordCount::Words24 => 24, - }; - assert_eq!(mnemonic.word_count(), expected_words); - } + fn generated_seed_is_readable() { + let mut rand_path = random_storage_path(); + rand_path.push("test_keys_seed"); + let expected_seed_bytes = read_or_generate_seed_file(&rand_path.to_str().unwrap()).unwrap(); + let read_seed_bytes = read_or_generate_seed_file(&rand_path.to_str().unwrap()).unwrap(); + assert_eq!(expected_seed_bytes, read_seed_bytes); } } diff --git a/src/lib.rs b/src/lib.rs index c0b02ae2f..bbae8ac72 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,7 @@ //! //! use ldk_node::bitcoin::secp256k1::PublicKey; //! use ldk_node::bitcoin::Network; +//! use ldk_node::entropy::{generate_entropy_mnemonic, NodeEntropy}; //! use ldk_node::lightning::ln::msgs::SocketAddress; //! use ldk_node::lightning_invoice::Bolt11Invoice; //! use ldk_node::Builder; @@ -41,7 +42,9 @@ //! "https://rapidsync.lightningdevkit.org/testnet/snapshot".to_string(), //! ); //! -//! let node = builder.build().unwrap(); +//! let mnemonic = generate_entropy_mnemonic(None); +//! let node_entropy = NodeEntropy::from_bip39_mnemonic(mnemonic, None); +//! let node = builder.build(node_entropy).unwrap(); //! //! node.start().unwrap(); //! @@ -83,6 +86,7 @@ mod chain; pub mod config; mod connection; mod data_store; +pub mod entropy; mod error; mod event; mod fee_estimator; @@ -130,7 +134,6 @@ use fee_estimator::{ConfirmationTarget, FeeEstimator, OnchainFeeEstimator}; use ffi::*; use gossip::GossipSource; use graph::NetworkGraph; -pub use io::utils::generate_entropy_mnemonic; use io::utils::write_node_metrics; use lightning::chain::BestBlock; use lightning::events::bump_transaction::{Input, Wallet as LdkWallet}; @@ -160,7 +163,6 @@ use types::{ }; pub use types::{ ChannelDetails, CustomTlvRecord, DynStore, PeerDetails, SyncAndAsyncKVStore, UserChannelId, - WordCount, }; pub use { bip39, bitcoin, lightning, lightning_invoice, lightning_liquidity, lightning_types, tokio, @@ -1626,11 +1628,19 @@ impl Node { /// # use ldk_node::config::Config; /// # use ldk_node::payment::PaymentDirection; /// # use ldk_node::bitcoin::Network; + /// # use ldk_node::entropy::{generate_entropy_mnemonic, NodeEntropy}; + /// # use rand::distr::Alphanumeric; + /// # use rand::{rng, Rng}; /// # let mut config = Config::default(); /// # config.network = Network::Regtest; - /// # config.storage_dir_path = "/tmp/ldk_node_test/".to_string(); + /// # let mut temp_path = std::env::temp_dir(); + /// # let rand_dir: String = (0..7).map(|_| rng().sample(Alphanumeric) as char).collect(); + /// # temp_path.push(rand_dir); + /// # config.storage_dir_path = temp_path.display().to_string(); /// # let builder = Builder::from_config(config); - /// # let node = builder.build().unwrap(); + /// # let mnemonic = generate_entropy_mnemonic(None); + /// # let node_entropy = NodeEntropy::from_bip39_mnemonic(mnemonic, None); + /// # let node = builder.build(node_entropy.into()).unwrap(); /// node.list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound); /// ``` pub fn list_payments_with_filter bool>( diff --git a/src/types.rs b/src/types.rs index 6d6bdcd20..b8dc10b18 100644 --- a/src/types.rs +++ b/src/types.rs @@ -36,34 +36,6 @@ use crate::logger::Logger; use crate::message_handler::NodeCustomMessageHandler; use crate::payment::PaymentDetails; -/// Supported BIP39 mnemonic word counts for entropy generation. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum WordCount { - /// 12-word mnemonic (128-bit entropy) - Words12, - /// 15-word mnemonic (160-bit entropy) - Words15, - /// 18-word mnemonic (192-bit entropy) - Words18, - /// 21-word mnemonic (224-bit entropy) - Words21, - /// 24-word mnemonic (256-bit entropy) - Words24, -} - -impl WordCount { - /// Returns the word count as a usize value. - pub fn word_count(&self) -> usize { - match self { - WordCount::Words12 => 12, - WordCount::Words15 => 15, - WordCount::Words18 => 18, - WordCount::Words21 => 21, - WordCount::Words24 => 24, - } - } -} - /// A supertrait that requires that a type implements both [`KVStore`] and [`KVStoreSync`] at the /// same time. pub trait SyncAndAsyncKVStore: KVStore + KVStoreSync {} diff --git a/tests/common/mod.rs b/tests/common/mod.rs index b70d2d675..38ecb1fd3 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -29,6 +29,7 @@ use electrsd::corepc_node::{Client as BitcoindClient, Node as BitcoinD}; use electrsd::{corepc_node, ElectrsD}; use electrum_client::ElectrumApi; use ldk_node::config::{AsyncPaymentsRole, Config, ElectrumSyncConfig, EsploraSyncConfig}; +use ldk_node::entropy::{generate_entropy_mnemonic, NodeEntropy}; use ldk_node::io::sqlite_store::SqliteStore; use ldk_node::payment::{PaymentDirection, PaymentKind, PaymentStatus}; use ldk_node::{ @@ -292,11 +293,24 @@ impl Default for TestStoreType { } } -#[derive(Clone, Default)] +#[derive(Clone)] pub(crate) struct TestConfig { pub node_config: Config, pub log_writer: TestLogWriter, pub store_type: TestStoreType, + pub node_entropy: NodeEntropy, +} + +impl Default for TestConfig { + fn default() -> Self { + let node_config = Default::default(); + let log_writer = Default::default(); + let store_type = Default::default(); + + let mnemonic = generate_entropy_mnemonic(None); + let node_entropy = NodeEntropy::from_bip39_mnemonic(mnemonic, None); + TestConfig { node_config, log_writer, store_type, node_entropy } + } } macro_rules! setup_builder { @@ -330,7 +344,7 @@ pub(crate) fn setup_two_nodes_with_store( println!("== Node A =="); let mut config_a = random_config(anchor_channels); config_a.store_type = store_type; - let node_a = setup_node(chain_source, config_a, None); + let node_a = setup_node(chain_source, config_a); println!("\n== Node B =="); let mut config_b = random_config(anchor_channels); @@ -347,18 +361,16 @@ pub(crate) fn setup_two_nodes_with_store( .trusted_peers_no_reserve .push(node_a.node_id()); } - let node_b = setup_node(chain_source, config_b, None); + let node_b = setup_node(chain_source, config_b); (node_a, node_b) } -pub(crate) fn setup_node( - chain_source: &TestChainSource, config: TestConfig, seed_bytes: Option>, -) -> TestNode { - setup_node_for_async_payments(chain_source, config, seed_bytes, None) +pub(crate) fn setup_node(chain_source: &TestChainSource, config: TestConfig) -> TestNode { + setup_node_for_async_payments(chain_source, config, None) } pub(crate) fn setup_node_for_async_payments( - chain_source: &TestChainSource, config: TestConfig, seed_bytes: Option>, + chain_source: &TestChainSource, config: TestConfig, async_payments_role: Option, ) -> TestNode { setup_builder!(builder, config.node_config); @@ -412,27 +424,14 @@ pub(crate) fn setup_node_for_async_payments( }, } - if let Some(seed) = seed_bytes { - #[cfg(feature = "uniffi")] - { - builder.set_entropy_seed_bytes(seed).unwrap(); - } - #[cfg(not(feature = "uniffi"))] - { - let mut bytes = [0u8; 64]; - bytes.copy_from_slice(&seed); - builder.set_entropy_seed_bytes(bytes); - } - } - builder.set_async_payments_role(async_payments_role).unwrap(); let node = match config.store_type { TestStoreType::TestSyncStore => { let kv_store = Arc::new(TestSyncStore::new(config.node_config.storage_dir_path.into())); - builder.build_with_store(kv_store).unwrap() + builder.build_with_store(config.node_entropy.into(), kv_store).unwrap() }, - TestStoreType::Sqlite => builder.build().unwrap(), + TestStoreType::Sqlite => builder.build(config.node_entropy.into()).unwrap(), }; node.start().unwrap(); diff --git a/tests/integration_tests_cln.rs b/tests/integration_tests_cln.rs index e8eb72a1d..0245f1fdf 100644 --- a/tests/integration_tests_cln.rs +++ b/tests/integration_tests_cln.rs @@ -43,7 +43,7 @@ async fn test_cln() { let mut builder = Builder::from_config(config.node_config); builder.set_chain_source_esplora("http://127.0.0.1:3002".to_string(), None); - let node = builder.build().unwrap(); + let node = builder.build(config.node_entropy).unwrap(); node.start().unwrap(); // Premine some funds and distribute diff --git a/tests/integration_tests_lnd.rs b/tests/integration_tests_lnd.rs index 311a11c3c..8f1d4c868 100755 --- a/tests/integration_tests_lnd.rs +++ b/tests/integration_tests_lnd.rs @@ -41,7 +41,7 @@ async fn test_lnd() { let mut builder = Builder::from_config(config.node_config); builder.set_chain_source_esplora("http://127.0.0.1:3002".to_string(), None); - let node = builder.build().unwrap(); + let node = builder.build(config.node_entropy).unwrap(); node.start().unwrap(); // Premine some funds and distribute diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index d6c7c9447..7c1ed8344 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -159,7 +159,7 @@ async fn multi_hop_sending() { let sync_config = EsploraSyncConfig { background_sync_config: None }; setup_builder!(builder, config.node_config); builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); - let node = builder.build().unwrap(); + let node = builder.build(config.node_entropy.into()).unwrap(); node.start().unwrap(); nodes.push(node); } @@ -259,7 +259,8 @@ async fn start_stop_reinit() { setup_builder!(builder, config.node_config); builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); - let node = builder.build_with_store(Arc::clone(&test_sync_store)).unwrap(); + let node = + builder.build_with_store(config.node_entropy.into(), Arc::clone(&test_sync_store)).unwrap(); node.start().unwrap(); let expected_node_id = node.node_id(); @@ -297,7 +298,8 @@ async fn start_stop_reinit() { setup_builder!(builder, config.node_config); builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); - let reinitialized_node = builder.build_with_store(Arc::clone(&test_sync_store)).unwrap(); + let reinitialized_node = + builder.build_with_store(config.node_entropy.into(), Arc::clone(&test_sync_store)).unwrap(); reinitialized_node.start().unwrap(); assert_eq!(reinitialized_node.node_id(), expected_node_id); @@ -606,10 +608,9 @@ async fn onchain_wallet_recovery() { let chain_source = TestChainSource::Esplora(&electrsd); - let seed_bytes = vec![42u8; 64]; - let original_config = random_config(true); - let original_node = setup_node(&chain_source, original_config, Some(seed_bytes.clone())); + let original_node_entropy = original_config.node_entropy; + let original_node = setup_node(&chain_source, original_config); let premine_amount_sat = 100_000; @@ -648,8 +649,9 @@ async fn onchain_wallet_recovery() { drop(original_node); // Now we start from scratch, only the seed remains the same. - let recovered_config = random_config(true); - let recovered_node = setup_node(&chain_source, recovered_config, Some(seed_bytes)); + let mut recovered_config = random_config(true); + recovered_config.node_entropy = original_node_entropy; + let recovered_node = setup_node(&chain_source, recovered_config); recovered_node.sync_wallets().unwrap(); assert_eq!( @@ -703,7 +705,7 @@ async fn run_rbf_test(is_insert_block: bool) { macro_rules! config_node { ($chain_source:expr, $anchor_channels:expr) => {{ let config_a = random_config($anchor_channels); - let node = setup_node(&$chain_source, config_a, None); + let node = setup_node(&$chain_source, config_a); node }}; } @@ -822,7 +824,7 @@ async fn sign_verify_msg() { let (_bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let config = random_config(true); let chain_source = TestChainSource::Esplora(&electrsd); - let node = setup_node(&chain_source, config, None); + let node = setup_node(&chain_source, config); // Tests arbitrary message signing and later verification let msg = "OK computer".as_bytes(); @@ -1296,7 +1298,6 @@ async fn async_payment() { let node_sender = setup_node_for_async_payments( &chain_source, config_sender, - None, Some(AsyncPaymentsRole::Client), ); @@ -1306,7 +1307,6 @@ async fn async_payment() { let node_sender_lsp = setup_node_for_async_payments( &chain_source, config_sender_lsp, - None, Some(AsyncPaymentsRole::Server), ); @@ -1317,7 +1317,6 @@ async fn async_payment() { let node_receiver_lsp = setup_node_for_async_payments( &chain_source, config_receiver_lsp, - None, Some(AsyncPaymentsRole::Server), ); @@ -1326,7 +1325,7 @@ async fn async_payment() { config_receiver.node_config.node_alias = None; config_receiver.log_writer = TestLogWriter::Custom(Arc::new(MultiNodeLogger::new("receiver ".to_string()))); - let node_receiver = setup_node(&chain_source, config_receiver, None); + let node_receiver = setup_node(&chain_source, config_receiver); let address_sender = node_sender.onchain_payment().new_address().unwrap(); let address_sender_lsp = node_sender_lsp.onchain_payment().new_address().unwrap(); @@ -1450,8 +1449,8 @@ async fn test_node_announcement_propagation() { config_b.node_config.listening_addresses = Some(node_b_listening_addresses.clone()); config_b.node_config.announcement_addresses = None; - let node_a = setup_node(&chain_source, config_a, None); - let node_b = setup_node(&chain_source, config_b, None); + let node_a = setup_node(&chain_source, config_a); + let node_b = setup_node(&chain_source, config_b); let address_a = node_a.onchain_payment().new_address().unwrap(); let premine_amount_sat = 5_000_000; @@ -1711,7 +1710,7 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) { setup_builder!(service_builder, service_config.node_config); service_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); service_builder.set_liquidity_provider_lsps2(lsps2_service_config); - let service_node = service_builder.build().unwrap(); + let service_node = service_builder.build(service_config.node_entropy.into()).unwrap(); service_node.start().unwrap(); let service_node_id = service_node.node_id(); @@ -1721,13 +1720,13 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) { setup_builder!(client_builder, client_config.node_config); client_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); client_builder.set_liquidity_source_lsps2(service_node_id, service_addr, None); - let client_node = client_builder.build().unwrap(); + let client_node = client_builder.build(client_config.node_entropy.into()).unwrap(); client_node.start().unwrap(); let payer_config = random_config(true); setup_builder!(payer_builder, payer_config.node_config); payer_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); - let payer_node = payer_builder.build().unwrap(); + let payer_node = payer_builder.build(payer_config.node_entropy.into()).unwrap(); payer_node.start().unwrap(); let service_addr = service_node.onchain_payment().new_address().unwrap(); @@ -1916,7 +1915,7 @@ async fn facade_logging() { config.log_writer = TestLogWriter::LogFacade; println!("== Facade logging starts =="); - let _node = setup_node(&chain_source, config, None); + let _node = setup_node(&chain_source, config); assert!(!logger.retrieve_logs().is_empty()); for (_, entry) in logger.retrieve_logs().iter().enumerate() { @@ -1995,10 +1994,8 @@ async fn spontaneous_send_with_custom_preimage() { async fn drop_in_async_context() { let (_bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = TestChainSource::Esplora(&electrsd); - let seed_bytes = vec![42u8; 64]; - let config = random_config(true); - let node = setup_node(&chain_source, config, Some(seed_bytes)); + let node = setup_node(&chain_source, config); node.stop().unwrap(); } @@ -2030,7 +2027,7 @@ async fn lsps2_client_trusts_lsp() { setup_builder!(service_builder, service_config.node_config); service_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); service_builder.set_liquidity_provider_lsps2(lsps2_service_config); - let service_node = service_builder.build().unwrap(); + let service_node = service_builder.build(service_config.node_entropy.into()).unwrap(); service_node.start().unwrap(); let service_node_id = service_node.node_id(); let service_addr = service_node.listening_addresses().unwrap().first().unwrap().clone(); @@ -2039,14 +2036,14 @@ async fn lsps2_client_trusts_lsp() { setup_builder!(client_builder, client_config.node_config); client_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); client_builder.set_liquidity_source_lsps2(service_node_id, service_addr.clone(), None); - let client_node = client_builder.build().unwrap(); + let client_node = client_builder.build(client_config.node_entropy.into()).unwrap(); client_node.start().unwrap(); let client_node_id = client_node.node_id(); let payer_config = random_config(true); setup_builder!(payer_builder, payer_config.node_config); payer_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); - let payer_node = payer_builder.build().unwrap(); + let payer_node = payer_builder.build(payer_config.node_entropy.into()).unwrap(); payer_node.start().unwrap(); let service_addr_onchain = service_node.onchain_payment().new_address().unwrap(); @@ -2203,7 +2200,7 @@ async fn lsps2_lsp_trusts_client_but_client_does_not_claim() { setup_builder!(service_builder, service_config.node_config); service_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); service_builder.set_liquidity_provider_lsps2(lsps2_service_config); - let service_node = service_builder.build().unwrap(); + let service_node = service_builder.build(service_config.node_entropy.into()).unwrap(); service_node.start().unwrap(); let service_node_id = service_node.node_id(); @@ -2213,7 +2210,7 @@ async fn lsps2_lsp_trusts_client_but_client_does_not_claim() { setup_builder!(client_builder, client_config.node_config); client_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); client_builder.set_liquidity_source_lsps2(service_node_id, service_addr.clone(), None); - let client_node = client_builder.build().unwrap(); + let client_node = client_builder.build(client_config.node_entropy.into()).unwrap(); client_node.start().unwrap(); let client_node_id = client_node.node_id(); @@ -2221,7 +2218,7 @@ async fn lsps2_lsp_trusts_client_but_client_does_not_claim() { let payer_config = random_config(true); setup_builder!(payer_builder, payer_config.node_config); payer_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); - let payer_node = payer_builder.build().unwrap(); + let payer_node = payer_builder.build(payer_config.node_entropy.into()).unwrap(); payer_node.start().unwrap(); let service_addr_onchain = service_node.onchain_payment().new_address().unwrap(); diff --git a/tests/integration_tests_vss.rs b/tests/integration_tests_vss.rs index 3b384ec45..54912b358 100644 --- a/tests/integration_tests_vss.rs +++ b/tests/integration_tests_vss.rs @@ -11,6 +11,7 @@ mod common; use std::collections::HashMap; +use ldk_node::entropy::NodeEntropy; use ldk_node::Builder; use rand::{rng, Rng}; @@ -25,6 +26,7 @@ async fn channel_full_cycle_with_vss_store() { let vss_base_url = std::env::var("TEST_VSS_BASE_URL").unwrap(); let node_a = builder_a .build_with_vss_store_and_fixed_headers( + config_a.node_entropy, vss_base_url.clone(), "node_1_store".to_string(), HashMap::new(), @@ -38,6 +40,7 @@ async fn channel_full_cycle_with_vss_store() { builder_b.set_chain_source_esplora(esplora_url.clone(), None); let node_b = builder_b .build_with_vss_store_and_fixed_headers( + config_b.node_entropy, vss_base_url, "node_2_store".to_string(), HashMap::new(), @@ -68,6 +71,7 @@ async fn vss_v0_schema_backwards_compatibility() { let store_id = format!("v0_compat_test_{}", rand_suffix); let storage_path = common::random_storage_path().to_str().unwrap().to_owned(); let seed_bytes = [42u8; 64]; + let node_entropy = NodeEntropy::from_seed_bytes(seed_bytes); // Setup a v0.6.2 `Node` persisted with the v0 scheme. let (old_balance, old_node_id) = { @@ -112,11 +116,15 @@ async fn vss_v0_schema_backwards_compatibility() { let mut builder_new = Builder::new(); builder_new.set_network(bitcoin::Network::Regtest); builder_new.set_storage_dir_path(storage_path); - builder_new.set_entropy_seed_bytes(seed_bytes); builder_new.set_chain_source_esplora(esplora_url, None); let node_new = builder_new - .build_with_vss_store_and_fixed_headers(vss_base_url, store_id, HashMap::new()) + .build_with_vss_store_and_fixed_headers( + node_entropy, + vss_base_url, + store_id, + HashMap::new(), + ) .unwrap(); node_new.start().unwrap(); @@ -142,16 +150,17 @@ async fn vss_node_restart() { let store_id = format!("restart_test_{}", rand_suffix); let storage_path = common::random_storage_path().to_str().unwrap().to_owned(); let seed_bytes = [42u8; 64]; + let node_entropy = NodeEntropy::from_seed_bytes(seed_bytes); // Setup initial node and fund it. let (expected_balance_sats, expected_node_id) = { let mut builder = Builder::new(); builder.set_network(bitcoin::Network::Regtest); builder.set_storage_dir_path(storage_path.clone()); - builder.set_entropy_seed_bytes(seed_bytes); builder.set_chain_source_esplora(esplora_url.clone(), None); let node = builder .build_with_vss_store_and_fixed_headers( + node_entropy, vss_base_url.clone(), store_id.clone(), HashMap::new(), @@ -181,11 +190,15 @@ async fn vss_node_restart() { let mut builder = Builder::new(); builder.set_network(bitcoin::Network::Regtest); builder.set_storage_dir_path(storage_path); - builder.set_entropy_seed_bytes(seed_bytes); builder.set_chain_source_esplora(esplora_url, None); let node = builder - .build_with_vss_store_and_fixed_headers(vss_base_url, store_id, HashMap::new()) + .build_with_vss_store_and_fixed_headers( + node_entropy, + vss_base_url, + store_id, + HashMap::new(), + ) .unwrap(); node.start().unwrap(); diff --git a/tests/reorg_test.rs b/tests/reorg_test.rs index 491a37fd4..89660a407 100644 --- a/tests/reorg_test.rs +++ b/tests/reorg_test.rs @@ -31,7 +31,7 @@ proptest! { macro_rules! config_node { ($chain_source: expr, $anchor_channels: expr) => {{ let config_a = random_config($anchor_channels); - let node = setup_node(&$chain_source, config_a, None); + let node = setup_node(&$chain_source, config_a); node }}; }