Skip to content

Commit 7ba04ce

Browse files
committed
Expose a VssStoreBuilder allowing to build VssStore independently
Previously, `VssStore` was a private object that could only be constructed internally via the `Builder` object. However, some users might want to use `VssStore` independently of/before the `Node` is constructed. To this end, we here expose a new `VssStoreBuilder` that allows to independently construct a `VssStore` instance that then can be handed to `Builder::build_with_store`. For now we keep the previous VSS-related methods on `Builder` around, but have them reuse the `VssStoreBuilder` internally. This also allows us to not change anything related to bindings. For bindings, we still have to explore if/how we can expose `VssStore` in the future.
1 parent 198ff30 commit 7ba04ce

File tree

3 files changed

+185
-67
lines changed

3 files changed

+185
-67
lines changed

src/builder.rs

Lines changed: 22 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use std::{fmt, fs};
1616
use bdk_wallet::template::Bip84;
1717
use bdk_wallet::{KeychainKind, Wallet as BdkWallet};
1818
use bitcoin::bip32::{ChildNumber, Xpriv};
19+
use bitcoin::key::Secp256k1;
1920
use bitcoin::secp256k1::PublicKey;
2021
use bitcoin::{BlockHash, Network};
2122
use lightning::chain::{chainmonitor, BestBlock, Watch};
@@ -38,7 +39,7 @@ use lightning::util::persist::{
3839
use lightning::util::ser::ReadableArgs;
3940
use lightning::util::sweep::OutputSweeper;
4041
use lightning_persister::fs_store::FilesystemStore;
41-
use vss_client::headers::{FixedHeaders, LnurlAuthToJwtProvider, VssHeaderProvider};
42+
use vss_client::headers::VssHeaderProvider;
4243

4344
use crate::chain::ChainSource;
4445
use crate::config::{
@@ -55,7 +56,7 @@ use crate::io::sqlite_store::SqliteStore;
5556
use crate::io::utils::{
5657
read_external_pathfinding_scores_from_cache, read_node_metrics, write_node_metrics,
5758
};
58-
use crate::io::vss_store::VssStore;
59+
use crate::io::vss_store::VssStoreBuilder;
5960
use crate::io::{
6061
self, PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE, PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
6162
};
@@ -76,8 +77,6 @@ use crate::wallet::persist::KVStoreWalletPersister;
7677
use crate::wallet::Wallet;
7778
use crate::{Node, NodeMetrics};
7879

79-
const VSS_HARDENED_CHILD_INDEX: u32 = 877;
80-
const VSS_LNURL_AUTH_HARDENED_CHILD_INDEX: u32 = 138;
8180
const LSPS_HARDENED_CHILD_INDEX: u32 = 577;
8281
const PERSISTER_MAX_PENDING_UPDATES: u64 = 100;
8382

@@ -589,41 +588,14 @@ impl NodeBuilder {
589588
&self, node_entropy: NodeEntropy, vss_url: String, store_id: String,
590589
lnurl_auth_server_url: String, fixed_headers: HashMap<String, String>,
591590
) -> Result<Node, BuildError> {
592-
use bitcoin::key::Secp256k1;
593-
594591
let logger = setup_logger(&self.log_writer_config, &self.config)?;
592+
let builder = VssStoreBuilder::new(node_entropy, vss_url, store_id, self.config.network);
593+
let vss_store = builder.build(lnurl_auth_server_url, fixed_headers).map_err(|e| {
594+
log_error!(logger, "Failed to setup VSS store: {}", e);
595+
BuildError::KVStoreSetupFailed
596+
})?;
595597

596-
let seed_bytes = node_entropy.to_seed_bytes();
597-
let config = Arc::new(self.config.clone());
598-
599-
let vss_xprv =
600-
derive_xprv(config, &seed_bytes, VSS_HARDENED_CHILD_INDEX, Arc::clone(&logger))?;
601-
602-
let lnurl_auth_xprv = vss_xprv
603-
.derive_priv(
604-
&Secp256k1::new(),
605-
&[ChildNumber::Hardened { index: VSS_LNURL_AUTH_HARDENED_CHILD_INDEX }],
606-
)
607-
.map_err(|e| {
608-
log_error!(logger, "Failed to derive VSS secret: {}", e);
609-
BuildError::KVStoreSetupFailed
610-
})?;
611-
612-
let lnurl_auth_jwt_provider =
613-
LnurlAuthToJwtProvider::new(lnurl_auth_xprv, lnurl_auth_server_url, fixed_headers)
614-
.map_err(|e| {
615-
log_error!(logger, "Failed to create LnurlAuthToJwtProvider: {}", e);
616-
BuildError::KVStoreSetupFailed
617-
})?;
618-
619-
let header_provider = Arc::new(lnurl_auth_jwt_provider);
620-
621-
self.build_with_vss_store_and_header_provider(
622-
node_entropy,
623-
vss_url,
624-
store_id,
625-
header_provider,
626-
)
598+
self.build_with_store(node_entropy, Arc::new(vss_store))
627599
}
628600

629601
/// Builds a [`Node`] instance with a [VSS] backend and according to the options
@@ -638,18 +610,19 @@ impl NodeBuilder {
638610
/// unrecoverable, i.e., if they remain unresolved after internal retries are exhausted.
639611
///
640612
/// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md
613+
/// [`FixedHeaders`]: vss_client::headers::FixedHeaders
641614
pub fn build_with_vss_store_and_fixed_headers(
642615
&self, node_entropy: NodeEntropy, vss_url: String, store_id: String,
643616
fixed_headers: HashMap<String, String>,
644617
) -> Result<Node, BuildError> {
645-
let header_provider = Arc::new(FixedHeaders::new(fixed_headers));
618+
let logger = setup_logger(&self.log_writer_config, &self.config)?;
619+
let builder = VssStoreBuilder::new(node_entropy, vss_url, store_id, self.config.network);
620+
let vss_store = builder.build_with_fixed_headers(fixed_headers).map_err(|e| {
621+
log_error!(logger, "Failed to setup VSS store: {}", e);
622+
BuildError::KVStoreSetupFailed
623+
})?;
646624

647-
self.build_with_vss_store_and_header_provider(
648-
node_entropy,
649-
vss_url,
650-
store_id,
651-
header_provider,
652-
)
625+
self.build_with_store(node_entropy, Arc::new(vss_store))
653626
}
654627

655628
/// Builds a [`Node`] instance with a [VSS] backend and according to the options
@@ -668,24 +641,11 @@ impl NodeBuilder {
668641
header_provider: Arc<dyn VssHeaderProvider>,
669642
) -> Result<Node, BuildError> {
670643
let logger = setup_logger(&self.log_writer_config, &self.config)?;
671-
672-
let seed_bytes = node_entropy.to_seed_bytes();
673-
let config = Arc::new(self.config.clone());
674-
675-
let vss_xprv = derive_xprv(
676-
config.clone(),
677-
&seed_bytes,
678-
VSS_HARDENED_CHILD_INDEX,
679-
Arc::clone(&logger),
680-
)?;
681-
682-
let vss_seed_bytes: [u8; 32] = vss_xprv.private_key.secret_bytes();
683-
684-
let vss_store =
685-
VssStore::new(vss_url, store_id, vss_seed_bytes, header_provider).map_err(|e| {
686-
log_error!(logger, "Failed to setup VSS store: {}", e);
687-
BuildError::KVStoreSetupFailed
688-
})?;
644+
let builder = VssStoreBuilder::new(node_entropy, vss_url, store_id, self.config.network);
645+
let vss_store = builder.build_with_header_provider(header_provider).map_err(|e| {
646+
log_error!(logger, "Failed to setup VSS store: {}", e);
647+
BuildError::KVStoreSetupFailed
648+
})?;
689649

690650
self.build_with_store(node_entropy, Arc::new(vss_store))
691651
}
@@ -1797,8 +1757,6 @@ fn setup_logger(
17971757
fn derive_xprv(
17981758
config: Arc<Config>, seed_bytes: &[u8; 64], hardened_child_index: u32, logger: Arc<Logger>,
17991759
) -> Result<Xpriv, BuildError> {
1800-
use bitcoin::key::Secp256k1;
1801-
18021760
let xprv = Xpriv::new_master(config.network, seed_bytes).map_err(|e| {
18031761
log_error!(logger, "Failed to derive master secret: {}", e);
18041762
BuildError::WalletSetupFailed

src/io/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub mod sqlite_store;
1111
#[cfg(test)]
1212
pub(crate) mod test_utils;
1313
pub(crate) mod utils;
14-
pub(crate) mod vss_store;
14+
pub mod vss_store;
1515

1616
/// The event queue will be persisted under this key.
1717
pub(crate) const EVENT_QUEUE_PERSISTENCE_PRIMARY_NAMESPACE: &str = "";

src/io/vss_store.rs

Lines changed: 162 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55
// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
66
// accordance with one or both of these licenses.
77

8+
//! Objects related to [`VssStore`] live here.
9+
810
use std::boxed::Box;
911
use std::collections::HashMap;
12+
use std::fmt;
1013
use std::future::Future;
1114
#[cfg(test)]
1215
use std::panic::RefUnwindSafe;
@@ -15,7 +18,10 @@ use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
1518
use std::sync::{Arc, Mutex};
1619
use std::time::Duration;
1720

21+
use bitcoin::bip32::{ChildNumber, Xpriv};
1822
use bitcoin::hashes::{sha256, Hash, HashEngine, Hmac, HmacEngine};
23+
use bitcoin::key::Secp256k1;
24+
use bitcoin::Network;
1925
use lightning::impl_writeable_tlv_based_enum;
2026
use lightning::io::{self, Error, ErrorKind};
2127
use lightning::util::persist::{KVStore, KVStoreSync};
@@ -24,7 +30,7 @@ use prost::Message;
2430
use rand::RngCore;
2531
use vss_client::client::VssClient;
2632
use vss_client::error::VssError;
27-
use vss_client::headers::VssHeaderProvider;
33+
use vss_client::headers::{FixedHeaders, LnurlAuthToJwtProvider, VssHeaderProvider};
2834
use vss_client::types::{
2935
DeleteObjectRequest, GetObjectRequest, KeyValue, ListKeyVersionsRequest, PutObjectRequest,
3036
Storable,
@@ -36,6 +42,7 @@ use vss_client::util::retry::{
3642
};
3743
use vss_client::util::storable_builder::{EntropySource, StorableBuilder};
3844

45+
use crate::entropy::NodeEntropy;
3946
use crate::io::utils::check_namespace_key_validity;
4047

4148
type CustomRetryPolicy = FilteredRetryPolicy<
@@ -61,13 +68,17 @@ impl_writeable_tlv_based_enum!(VssSchemaVersion,
6168
(1, V1) => {},
6269
);
6370

71+
const VSS_HARDENED_CHILD_INDEX: u32 = 877;
72+
const VSS_LNURL_AUTH_HARDENED_CHILD_INDEX: u32 = 138;
6473
const VSS_SCHEMA_VERSION_KEY: &str = "vss_schema_version";
6574

6675
// We set this to a small number of threads that would still allow to make some progress if one
6776
// would hit a blocking case
6877
const INTERNAL_RUNTIME_WORKERS: usize = 2;
6978

70-
/// A [`KVStoreSync`] implementation that writes to and reads from a [VSS](https://github.com/lightningdevkit/vss-server/blob/main/README.md) backend.
79+
/// A [`KVStore`]/[`KVStoreSync`] implementation that writes to and reads from a [VSS] backend.
80+
///
81+
/// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md
7182
pub struct VssStore {
7283
inner: Arc<VssStoreInner>,
7384
// Version counter to ensure that writes are applied in the correct order. It is assumed that read and list
@@ -139,6 +150,12 @@ impl VssStore {
139150

140151
Ok(Self { inner, next_version, internal_runtime: Some(internal_runtime) })
141152
}
153+
/// Returns a [`VssStoreBuilder`] allowing to build a [`VssStore`].
154+
pub fn builder(
155+
node_entropy: NodeEntropy, vss_url: String, store_id: String, network: Network,
156+
) -> VssStoreBuilder {
157+
VssStoreBuilder::new(node_entropy, vss_url, store_id, network)
158+
}
142159

143160
// Same logic as for the obfuscated keys below, but just for locking, using the plaintext keys
144161
fn build_locking_key(
@@ -800,6 +817,149 @@ impl EntropySource for RandEntropySource {
800817
#[cfg(test)]
801818
impl RefUnwindSafe for VssStore {}
802819

820+
/// An error that could arise during [`VssStore`] building.
821+
#[derive(Debug, Clone, PartialEq)]
822+
pub enum VssStoreBuildError {
823+
/// Key derivation failed
824+
KeyDerivationFailed,
825+
/// Authentication provider setup failed
826+
AuthProviderSetupFailed,
827+
/// Store setup failed
828+
StoreSetupFailed,
829+
}
830+
831+
impl fmt::Display for VssStoreBuildError {
832+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
833+
match *self {
834+
Self::KeyDerivationFailed => write!(f, "Key derivation failed"),
835+
Self::AuthProviderSetupFailed => write!(f, "Authentication provider setup failed"),
836+
Self::StoreSetupFailed => write!(f, "Store setup failed"),
837+
}
838+
}
839+
}
840+
841+
impl std::error::Error for VssStoreBuildError {}
842+
843+
/// A builder for a [`VssStore`] instance.
844+
pub struct VssStoreBuilder {
845+
node_entropy: NodeEntropy,
846+
vss_url: String,
847+
store_id: String,
848+
network: Network,
849+
}
850+
851+
impl VssStoreBuilder {
852+
/// Create a new [`VssStoreBuilder`].
853+
pub fn new(
854+
node_entropy: NodeEntropy, vss_url: String, store_id: String, network: Network,
855+
) -> Self {
856+
Self { node_entropy, vss_url, store_id, network }
857+
}
858+
859+
/// Builds a [`VssStore`] with [LNURL-auth] based authentication scheme as default method for
860+
/// authentication/authorization.
861+
///
862+
/// The LNURL challenge will be retrieved by making a request to the given
863+
/// `lnurl_auth_server_url`. The returned JWT token in response to the signed LNURL request,
864+
/// will be used for authentication/authorization of all the requests made to VSS.
865+
///
866+
/// `fixed_headers` are included as it is in all the requests made to VSS and LNURL auth
867+
/// server.
868+
///
869+
/// **Caution**: VSS support is in **alpha** and is considered experimental. Using VSS (or any
870+
/// remote persistence) may cause LDK to panic if persistence failures are unrecoverable, i.e.,
871+
/// if they remain unresolved after internal retries are exhausted.
872+
///
873+
/// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md
874+
/// [LNURL-auth]: https://github.com/lnurl/luds/blob/luds/04.md
875+
pub fn build(
876+
&self, lnurl_auth_server_url: String, fixed_headers: HashMap<String, String>,
877+
) -> Result<VssStore, VssStoreBuildError> {
878+
use bitcoin::key::Secp256k1;
879+
880+
let seed_bytes = self.node_entropy.to_seed_bytes();
881+
let vss_xprv = Xpriv::new_master(self.network, &seed_bytes)
882+
.map_err(|_| VssStoreBuildError::KeyDerivationFailed)
883+
.and_then(|master| {
884+
master
885+
.derive_priv(
886+
&Secp256k1::new(),
887+
&[ChildNumber::Hardened { index: VSS_HARDENED_CHILD_INDEX }],
888+
)
889+
.map_err(|_| VssStoreBuildError::KeyDerivationFailed)
890+
})?;
891+
892+
let lnurl_auth_xprv = vss_xprv
893+
.derive_priv(
894+
&Secp256k1::new(),
895+
&[ChildNumber::Hardened { index: VSS_LNURL_AUTH_HARDENED_CHILD_INDEX }],
896+
)
897+
.map_err(|_| VssStoreBuildError::KeyDerivationFailed)?;
898+
899+
let lnurl_auth_jwt_provider =
900+
LnurlAuthToJwtProvider::new(lnurl_auth_xprv, lnurl_auth_server_url, fixed_headers)
901+
.map_err(|_| VssStoreBuildError::AuthProviderSetupFailed)?;
902+
903+
let header_provider = Arc::new(lnurl_auth_jwt_provider);
904+
905+
self.build_with_header_provider(header_provider)
906+
}
907+
908+
/// Builds a [`VssStore`] with [`FixedHeaders`] as default method for
909+
/// authentication/authorization.
910+
///
911+
/// Given `fixed_headers` are included as it is in all the requests made to VSS.
912+
///
913+
/// **Caution**: VSS support is in **alpha** and is considered experimental. Using VSS (or any
914+
/// remote persistence) may cause LDK to panic if persistence failures are unrecoverable, i.e.,
915+
/// if they remain unresolved after internal retries are exhausted.
916+
///
917+
/// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md
918+
pub fn build_with_fixed_headers(
919+
&self, fixed_headers: HashMap<String, String>,
920+
) -> Result<VssStore, VssStoreBuildError> {
921+
let header_provider = Arc::new(FixedHeaders::new(fixed_headers));
922+
self.build_with_header_provider(header_provider)
923+
}
924+
925+
/// Builds a [`VssStore`] with [`VssHeaderProvider`].
926+
///
927+
/// Any headers provided by `header_provider` will be attached to every request made to VSS.
928+
///
929+
/// **Caution**: VSS support is in **alpha** and is considered experimental.
930+
/// Using VSS (or any remote persistence) may cause LDK to panic if persistence failures are
931+
/// unrecoverable, i.e., if they remain unresolved after internal retries are exhausted.
932+
///
933+
/// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md
934+
pub fn build_with_header_provider(
935+
&self, header_provider: Arc<dyn VssHeaderProvider>,
936+
) -> Result<VssStore, VssStoreBuildError> {
937+
let seed_bytes = self.node_entropy.to_seed_bytes();
938+
let vss_xprv = Xpriv::new_master(self.network, &seed_bytes)
939+
.map_err(|_| VssStoreBuildError::KeyDerivationFailed)
940+
.and_then(|master| {
941+
master
942+
.derive_priv(
943+
&Secp256k1::new(),
944+
&[ChildNumber::Hardened { index: VSS_HARDENED_CHILD_INDEX }],
945+
)
946+
.map_err(|_| VssStoreBuildError::KeyDerivationFailed)
947+
})?;
948+
949+
let vss_seed_bytes: [u8; 32] = vss_xprv.private_key.secret_bytes();
950+
951+
let vss_store = VssStore::new(
952+
self.vss_url.clone(),
953+
self.store_id.clone(),
954+
vss_seed_bytes,
955+
header_provider,
956+
)
957+
.map_err(|_| VssStoreBuildError::StoreSetupFailed)?;
958+
959+
Ok(vss_store)
960+
}
961+
}
962+
803963
#[cfg(test)]
804964
#[cfg(vss_test)]
805965
mod tests {

0 commit comments

Comments
 (0)