diff --git a/src/builder.rs b/src/builder.rs index 43171db1f..4fa8f53f5 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -65,7 +65,9 @@ use bitcoin::secp256k1::PublicKey; use bitcoin::{BlockHash, Network}; #[cfg(any(vss, vss_test))] -use bitcoin::bip32::ChildNumber; +use bitcoin::bip32::{ChildNumber, Xpriv}; +#[cfg(any(vss, vss_test))] +use std::collections::HashMap; use std::convert::TryInto; use std::default::Default; use std::fmt; @@ -74,6 +76,8 @@ use std::path::PathBuf; use std::sync::atomic::AtomicBool; use std::sync::{Arc, Mutex, RwLock}; use std::time::SystemTime; +#[cfg(any(vss, vss_test))] +use vss_client::headers::{FixedHeaders, LnurlAuthToJwtProvider, VssHeaderProvider}; #[derive(Debug, Clone)] enum ChainDataSourceConfig { @@ -357,10 +361,28 @@ impl NodeBuilder { self.build_with_store(kv_store) } - /// Builds a [`Node`] instance with a [`VssStore`] backend and according to the options + /// Builds a [`Node`] instance with a [VSS] backend and according to the options /// previously configured. + /// + /// Uses [LNURL-auth] based authentication scheme as default method for authentication/authorization. + /// + /// The LNURL challenge will be retrieved by making a request to the given `lnurl_auth_server_url`. + /// The returned JWT token in response to the signed LNURL request, will be used for + /// authentication/authorization of all the requests made to VSS. + /// + /// `fixed_headers` are included as it is in all the requests made to VSS and LNURL auth server. + /// + /// **Caution**: VSS support is in **alpha** and is considered experimental. + /// Using VSS (or any remote persistence) may cause LDK to panic if persistence failures are + /// unrecoverable, i.e., if they remain unresolved after internal retries are exhausted. + /// + /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md + /// [LNURL-auth]: https://github.com/lnurl/luds/blob/luds/04.md #[cfg(any(vss, vss_test))] - pub fn build_with_vss_store(&self, url: String, store_id: String) -> Result { + pub fn build_with_vss_store( + &self, vss_url: String, store_id: String, lnurl_auth_server_url: String, + fixed_headers: HashMap, + ) -> Result { use bitcoin::key::Secp256k1; let logger = setup_logger(&self.config)?; @@ -370,23 +392,81 @@ impl NodeBuilder { self.entropy_source_config.as_ref(), Arc::clone(&logger), )?; + let config = Arc::new(self.config.clone()); - 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 - })?; + let vss_xprv = derive_vss_xprv(config, &seed_bytes, Arc::clone(&logger))?; - let vss_xprv = xprv - .derive_priv(&Secp256k1::new(), &[ChildNumber::Hardened { index: 877 }]) + let lnurl_auth_xprv = vss_xprv + .derive_priv(&Secp256k1::new(), &[ChildNumber::Hardened { index: 138 }]) .map_err(|e| { log_error!(logger, "Failed to derive VSS secret: {}", e); BuildError::KVStoreSetupFailed })?; + let lnurl_auth_jwt_provider = + LnurlAuthToJwtProvider::new(lnurl_auth_xprv, lnurl_auth_server_url, fixed_headers) + .map_err(|e| { + log_error!(logger, "Failed to create LnurlAuthToJwtProvider: {}", e); + BuildError::KVStoreSetupFailed + })?; + + let header_provider = Arc::new(lnurl_auth_jwt_provider); + + self.build_with_vss_store_and_header_provider(vss_url, store_id, header_provider) + } + + /// Builds a [`Node`] instance with a [VSS] backend and according to the options + /// previously configured. + /// + /// Uses [`FixedHeaders`] as default method for authentication/authorization. + /// + /// Given `fixed_headers` are included as it is in all the requests made to VSS. + /// + /// **Caution**: VSS support is in **alpha** and is considered experimental. + /// Using VSS (or any remote persistence) may cause LDK to panic if persistence failures are + /// unrecoverable, i.e., if they remain unresolved after internal retries are exhausted. + /// + /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md + #[cfg(any(vss, vss_test))] + pub fn build_with_vss_store_and_fixed_headers( + &self, 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) + } + + /// Builds a [`Node`] instance with a [VSS] backend and according to the options + /// previously configured. + /// + /// Given `header_provider` is used to attach headers to every request made + /// to VSS. + /// + /// **Caution**: VSS support is in **alpha** and is considered experimental. + /// Using VSS (or any remote persistence) may cause LDK to panic if persistence failures are + /// unrecoverable, i.e., if they remain unresolved after internal retries are exhausted. + /// + /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md + #[cfg(any(vss, vss_test))] + pub fn build_with_vss_store_and_header_provider( + &self, vss_url: String, store_id: String, header_provider: Arc, + ) -> Result { + let logger = setup_logger(&self.config)?; + + let seed_bytes = seed_bytes_from_config( + &self.config, + self.entropy_source_config.as_ref(), + Arc::clone(&logger), + )?; + + let config = Arc::new(self.config.clone()); + + let vss_xprv = derive_vss_xprv(config.clone(), &seed_bytes, Arc::clone(&logger))?; + let vss_seed_bytes: [u8; 32] = vss_xprv.private_key.secret_bytes(); - let vss_store = Arc::new(VssStore::new(url, store_id, vss_seed_bytes)); + let vss_store = Arc::new(VssStore::new(vss_url, store_id, vss_seed_bytes, header_provider)); build_with_store_internal( config, self.chain_data_source_config.as_ref(), @@ -551,6 +631,80 @@ impl ArcedNodeBuilder { self.inner.read().unwrap().build_with_fs_store().map(Arc::new) } + /// Builds a [`Node`] instance with a [VSS] backend and according to the options + /// previously configured. + /// + /// Uses [LNURL-auth] based authentication scheme as default method for authentication/authorization. + /// + /// The LNURL challenge will be retrieved by making a request to the given `lnurl_auth_server_url`. + /// The returned JWT token in response to the signed LNURL request, will be used for + /// authentication/authorization of all the requests made to VSS. + /// + /// `fixed_headers` are included as it is in all the requests made to VSS and LNURL auth server. + /// + /// **Caution**: VSS support is in **alpha** and is considered experimental. + /// Using VSS (or any remote persistence) may cause LDK to panic if persistence failures are + /// unrecoverable, i.e., if they remain unresolved after internal retries are exhausted. + /// + /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md + /// [LNURL-auth]: https://github.com/lnurl/luds/blob/luds/04.md + #[cfg(any(vss, vss_test))] + pub fn build_with_vss_store( + &self, 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) + .map(Arc::new) + } + + /// Builds a [`Node`] instance with a [VSS] backend and according to the options + /// previously configured. + /// + /// Uses [`FixedHeaders`] as default method for authentication/authorization. + /// + /// Given `fixed_headers` are included as it is in all the requests made to VSS. + /// + /// **Caution**: VSS support is in **alpha** and is considered experimental. + /// Using VSS (or any remote persistence) may cause LDK to panic if persistence failures are + /// unrecoverable, i.e., if they remain unresolved after internal retries are exhausted. + /// + /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md + #[cfg(any(vss, vss_test))] + pub fn build_with_vss_store_and_fixed_headers( + &self, 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) + .map(Arc::new) + } + + /// Builds a [`Node`] instance with a [VSS] backend and according to the options + /// previously configured. + /// + /// Given `header_provider` is used to attach headers to every request made + /// to VSS. + /// + /// **Caution**: VSS support is in **alpha** and is considered experimental. + /// Using VSS (or any remote persistence) may cause LDK to panic if persistence failures are + /// unrecoverable, i.e., if they remain unresolved after internal retries are exhausted. + /// + /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md + #[cfg(any(vss, vss_test))] + pub fn build_with_vss_store_and_header_provider( + &self, 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) + .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) @@ -1079,6 +1233,23 @@ fn seed_bytes_from_config( } } +#[cfg(any(vss, vss_test))] +fn derive_vss_xprv( + config: Arc, seed_bytes: &[u8; 64], logger: Arc, +) -> Result { + use bitcoin::key::Secp256k1; + + let xprv = Xpriv::new_master(config.network, seed_bytes).map_err(|e| { + log_error!(logger, "Failed to derive master secret: {}", e); + BuildError::InvalidSeedBytes + })?; + + xprv.derive_priv(&Secp256k1::new(), &[ChildNumber::Hardened { index: 877 }]).map_err(|e| { + log_error!(logger, "Failed to derive VSS secret: {}", e); + BuildError::KVStoreSetupFailed + }) +} + /// Sanitize the user-provided node alias to ensure that it is a valid protocol-specified UTF-8 string. pub(crate) fn sanitize_alias(alias_str: &str) -> Result { let alias = alias_str.trim(); diff --git a/src/io/vss_store.rs b/src/io/vss_store.rs index 474f7dbc7..fbed7f7cf 100644 --- a/src/io/vss_store.rs +++ b/src/io/vss_store.rs @@ -8,6 +8,7 @@ use lightning::io::{self, Error, ErrorKind}; #[cfg(test)] use std::panic::RefUnwindSafe; +use std::sync::Arc; use std::time::Duration; use crate::io::utils::check_namespace_key_validity; @@ -17,6 +18,7 @@ use rand::RngCore; use tokio::runtime::Runtime; use vss_client::client::VssClient; use vss_client::error::VssError; +use vss_client::headers::VssHeaderProvider; use vss_client::types::{ DeleteObjectRequest, GetObjectRequest, KeyValue, ListKeyVersionsRequest, PutObjectRequest, Storable, @@ -43,7 +45,10 @@ pub struct VssStore { } impl VssStore { - pub(crate) fn new(base_url: String, store_id: String, data_encryption_key: [u8; 32]) -> Self { + pub(crate) fn new( + base_url: String, store_id: String, data_encryption_key: [u8; 32], + header_provider: Arc, + ) -> Self { let runtime = tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap(); let storable_builder = StorableBuilder::new(data_encryption_key, RandEntropySource); let retry_policy = ExponentialBackoffRetryPolicy::new(Duration::from_millis(100)) @@ -59,7 +64,7 @@ impl VssStore { ) }) as _); - let client = VssClient::new(base_url, retry_policy); + let client = VssClient::new_with_headers(base_url, retry_policy, header_provider); Self { client, store_id, runtime, storable_builder } } @@ -238,6 +243,8 @@ mod tests { use crate::io::test_utils::do_read_write_remove_list_persist; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng, RngCore}; + use std::collections::HashMap; + use vss_client::headers::FixedHeaders; #[test] fn read_write_remove_list_persist() { @@ -246,7 +253,9 @@ mod tests { let rand_store_id: String = (0..7).map(|_| rng.sample(Alphanumeric) as char).collect(); let mut data_encryption_key = [0u8; 32]; rng.fill_bytes(&mut data_encryption_key); - let vss_store = VssStore::new(vss_base_url, rand_store_id, data_encryption_key); + let header_provider = Arc::new(FixedHeaders::new(HashMap::new())); + let vss_store = + VssStore::new(vss_base_url, rand_store_id, data_encryption_key, header_provider); do_read_write_remove_list_persist(&vss_store); } diff --git a/src/lib.rs b/src/lib.rs index 42b99406a..8213712b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,6 +100,8 @@ pub use bip39; pub use bitcoin; pub use lightning; pub use lightning_invoice; +#[cfg(any(vss, vss_test))] +pub use vss_client; pub use balance::{BalanceDetails, LightningBalance, PendingSweepBalance}; pub use error::Error as NodeError; diff --git a/tests/integration_tests_vss.rs b/tests/integration_tests_vss.rs index 483902375..525c1f1f1 100644 --- a/tests/integration_tests_vss.rs +++ b/tests/integration_tests_vss.rs @@ -10,6 +10,7 @@ mod common; use ldk_node::Builder; +use std::collections::HashMap; #[test] fn channel_full_cycle_with_vss_store() { @@ -20,15 +21,26 @@ fn channel_full_cycle_with_vss_store() { let mut builder_a = Builder::from_config(config_a); builder_a.set_chain_source_esplora(esplora_url.clone(), None); let vss_base_url = std::env::var("TEST_VSS_BASE_URL").unwrap(); - let node_a = - builder_a.build_with_vss_store(vss_base_url.clone(), "node_1_store".to_string()).unwrap(); + let node_a = builder_a + .build_with_vss_store_and_fixed_headers( + vss_base_url.clone(), + "node_1_store".to_string(), + HashMap::new(), + ) + .unwrap(); node_a.start().unwrap(); println!("\n== Node B =="); let config_b = common::random_config(true); let mut builder_b = Builder::from_config(config_b); builder_b.set_chain_source_esplora(esplora_url.clone(), None); - let node_b = builder_b.build_with_vss_store(vss_base_url, "node_2_store".to_string()).unwrap(); + let node_b = builder_b + .build_with_vss_store_and_fixed_headers( + vss_base_url, + "node_2_store".to_string(), + HashMap::new(), + ) + .unwrap(); node_b.start().unwrap(); common::do_channel_full_cycle(