Skip to content

Commit 0a82707

Browse files
committed
f Upstream VssStoreBuilder and VssStore to lightning-persister
Bump `vss-client-ng` 0.4 -> 0.5, add `build_with_sigs_auth()` method, rename `build()` to `build_with_lnurl()`, replace `RandEntropySource` with `VssEntropySource` backed by LDK's `RandomBytes`, drop `rand` from the `vss` feature in favor of `getrandom`, re-export `vss_client` from the crate root, remove `AuthProviderSetupFailed` error variant, and update tests to use `VssStoreBuilder`. Co-Authored-By: HAL 9000
1 parent d827772 commit 0a82707

File tree

8 files changed

+110
-38
lines changed

8 files changed

+110
-38
lines changed

lightning-persister/Cargo.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@ rustdoc-args = ["--cfg", "docsrs"]
1717

1818
[features]
1919
tokio = ["dep:tokio"]
20-
vss = ["dep:vss-client", "dep:tokio", "dep:rand", "dep:lightning-macros"]
20+
vss = ["dep:vss-client", "dep:tokio", "dep:getrandom", "dep:lightning-macros"]
2121

2222
[dependencies]
2323
bitcoin = "0.32.2"
2424
lightning = { version = "0.3.0", path = "../lightning" }
2525
lightning-macros = { version = "0.2.0", path = "../lightning-macros", optional = true }
2626
tokio = { version = "1.35", optional = true, default-features = false, features = ["rt-multi-thread"] }
27-
vss-client = { package = "vss-client-ng", version = "0.4", optional = true }
28-
rand = { version = "0.9.2", default-features = false, features = ["thread_rng"], optional = true }
27+
vss-client = { package = "vss-client-ng", version = "0.5", optional = true }
28+
getrandom = { version = "0.3", optional = true }
2929

3030
[target.'cfg(windows)'.dependencies]
3131
windows-sys = { version = "0.48.0", default-features = false, features = ["Win32_Storage_FileSystem", "Win32_Foundation"] }
@@ -36,6 +36,7 @@ criterion = { version = "0.4", optional = true, default-features = false }
3636
[dev-dependencies]
3737
lightning = { version = "0.3.0", path = "../lightning", features = ["_test_utils"] }
3838
bitcoin = { version = "0.32.2", default-features = false }
39+
rand = { version = "0.9.2", default-features = false, features = ["thread_rng"] }
3940
tokio = { version = "1.35", default-features = false, features = ["macros"] }
4041

4142
[lints]

lightning-persister/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ pub mod fs_store;
1313
#[cfg(feature = "vss")]
1414
pub mod vss_store;
1515

16+
#[cfg(feature = "vss")]
17+
pub use vss_client;
18+
1619
mod utils;
1720

1821
#[cfg(test)]

lightning-persister/src/vss_store.rs

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@ use bitcoin::hashes::{sha256, Hash, HashEngine, Hmac, HmacEngine};
2222
use bitcoin::key::Secp256k1;
2323
use lightning::impl_writeable_tlv_based_enum;
2424
use lightning::io::{self, Error, ErrorKind};
25+
use lightning::sign::{EntropySource as LdkEntropySource, RandomBytes};
2526
use lightning::util::persist::{KVStore, KVStoreSync};
2627
use lightning::util::ser::{Readable, Writeable};
27-
use rand::RngCore;
2828
use vss_client::client::VssClient;
2929
use vss_client::error::VssError;
30+
use vss_client::headers::sigs_auth::SigsAuthProvider;
3031
use vss_client::headers::{FixedHeaders, LnurlAuthToJwtProvider, VssHeaderProvider};
3132
use vss_client::prost::Message;
3233
use vss_client::types::{
@@ -66,6 +67,7 @@ impl_writeable_tlv_based_enum!(VssSchemaVersion,
6667
);
6768

6869
const VSS_LNURL_AUTH_HARDENED_CHILD_INDEX: u32 = 138;
70+
const VSS_SIGS_AUTH_HARDENED_CHILD_INDEX: u32 = 139;
6971
const VSS_SCHEMA_VERSION_KEY: &str = "vss_schema_version";
7072

7173
// We set this to a small number of threads that would still allow to make some progress if one
@@ -111,6 +113,10 @@ impl VssStore {
111113
derive_data_encryption_and_obfuscation_keys(&vss_seed);
112114
let key_obfuscator = KeyObfuscator::new(obfuscation_master_key);
113115

116+
let mut entropy_seed = [0u8; 32];
117+
getrandom::fill(&mut entropy_seed).expect("Failed to generate random bytes");
118+
let entropy_source = RandomBytes::new(entropy_seed);
119+
114120
let sync_retry_policy = retry_policy();
115121
let blocking_client = VssClient::new_with_headers(
116122
base_url.clone(),
@@ -126,6 +132,7 @@ impl VssStore {
126132
&store_id,
127133
data_encryption_key,
128134
&key_obfuscator,
135+
&entropy_source,
129136
)
130137
.await
131138
})
@@ -142,6 +149,7 @@ impl VssStore {
142149
store_id,
143150
data_encryption_key,
144151
key_obfuscator,
152+
entropy_source,
145153
));
146154

147155
Ok(Self { inner, next_version, internal_runtime: Some(internal_runtime) })
@@ -380,6 +388,7 @@ struct VssStoreInner {
380388
store_id: String,
381389
data_encryption_key: [u8; 32],
382390
key_obfuscator: KeyObfuscator,
391+
entropy_source: RandomBytes,
383392
// Per-key locks that ensures that we don't have concurrent writes to the same namespace/key.
384393
// The lock also encapsulates the latest written version per key.
385394
locks: Mutex<HashMap<String, Arc<tokio::sync::Mutex<u64>>>>,
@@ -389,7 +398,7 @@ impl VssStoreInner {
389398
pub(crate) fn new(
390399
schema_version: VssSchemaVersion, blocking_client: VssClient<CustomRetryPolicy>,
391400
async_client: VssClient<CustomRetryPolicy>, store_id: String,
392-
data_encryption_key: [u8; 32], key_obfuscator: KeyObfuscator,
401+
data_encryption_key: [u8; 32], key_obfuscator: KeyObfuscator, entropy_source: RandomBytes,
393402
) -> Self {
394403
let locks = Mutex::new(HashMap::new());
395404
Self {
@@ -399,6 +408,7 @@ impl VssStoreInner {
399408
store_id,
400409
data_encryption_key,
401410
key_obfuscator,
411+
entropy_source,
402412
locks,
403413
}
404414
}
@@ -519,7 +529,7 @@ impl VssStoreInner {
519529
Error::new(ErrorKind::Other, msg)
520530
})?;
521531

522-
let storable_builder = StorableBuilder::new(RandEntropySource);
532+
let storable_builder = StorableBuilder::new(VssEntropySource(&self.entropy_source));
523533
let aad =
524534
if self.schema_version == VssSchemaVersion::V1 { store_key.as_bytes() } else { &[] };
525535
let decrypted = storable_builder.deconstruct(storable, &self.data_encryption_key, aad)?.0;
@@ -540,7 +550,7 @@ impl VssStoreInner {
540550

541551
let store_key = self.build_obfuscated_key(&primary_namespace, &secondary_namespace, &key);
542552
let vss_version = -1;
543-
let storable_builder = StorableBuilder::new(RandEntropySource);
553+
let storable_builder = StorableBuilder::new(VssEntropySource(&self.entropy_source));
544554
let aad =
545555
if self.schema_version == VssSchemaVersion::V1 { store_key.as_bytes() } else { &[] };
546556
let storable =
@@ -698,7 +708,7 @@ fn retry_policy() -> CustomRetryPolicy {
698708

699709
async fn determine_and_write_schema_version(
700710
client: &VssClient<CustomRetryPolicy>, store_id: &String, data_encryption_key: [u8; 32],
701-
key_obfuscator: &KeyObfuscator,
711+
key_obfuscator: &KeyObfuscator, entropy_source: &RandomBytes,
702712
) -> io::Result<VssSchemaVersion> {
703713
// Build the obfuscated `vss_schema_version` key.
704714
let obfuscated_prefix = key_obfuscator.obfuscate(&format! {"{}#{}", "", ""});
@@ -729,7 +739,7 @@ async fn determine_and_write_schema_version(
729739
Error::new(ErrorKind::Other, msg)
730740
})?;
731741

732-
let storable_builder = StorableBuilder::new(RandEntropySource);
742+
let storable_builder = StorableBuilder::new(VssEntropySource(entropy_source));
733743
// Schema version was added starting with V1, so if set at all, we use the key as `aad`
734744
let aad = store_key.as_bytes();
735745
let decrypted = storable_builder
@@ -773,7 +783,7 @@ async fn determine_and_write_schema_version(
773783
let schema_version = VssSchemaVersion::V1;
774784
let encoded_version = schema_version.encode();
775785

776-
let storable_builder = StorableBuilder::new(RandEntropySource);
786+
let storable_builder = StorableBuilder::new(VssEntropySource(entropy_source));
777787
let vss_version = -1;
778788
let aad = store_key.as_bytes();
779789
let storable =
@@ -800,12 +810,18 @@ async fn determine_and_write_schema_version(
800810
}
801811
}
802812

803-
/// A source for generating entropy/randomness using [`rand`].
804-
pub(crate) struct RandEntropySource;
813+
/// A thin wrapper bridging LDK's [`RandomBytes`] to the vss-client [`EntropySource`] trait.
814+
struct VssEntropySource<'a>(&'a RandomBytes);
805815

806-
impl EntropySource for RandEntropySource {
816+
impl EntropySource for VssEntropySource<'_> {
807817
fn fill_bytes(&self, buffer: &mut [u8]) {
808-
rand::rng().fill_bytes(buffer);
818+
let mut offset = 0;
819+
while offset < buffer.len() {
820+
let random = self.0.get_secure_random_bytes();
821+
let to_copy = (buffer.len() - offset).min(32);
822+
buffer[offset..offset + to_copy].copy_from_slice(&random[..to_copy]);
823+
offset += to_copy;
824+
}
809825
}
810826
}
811827

@@ -817,8 +833,6 @@ impl RefUnwindSafe for VssStore {}
817833
pub enum VssStoreBuildError {
818834
/// Key derivation failed
819835
KeyDerivationFailed,
820-
/// Authentication provider setup failed
821-
AuthProviderSetupFailed,
822836
/// Store setup failed
823837
StoreSetupFailed,
824838
}
@@ -827,7 +841,6 @@ impl fmt::Display for VssStoreBuildError {
827841
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
828842
match *self {
829843
Self::KeyDerivationFailed => write!(f, "Key derivation failed"),
830-
Self::AuthProviderSetupFailed => write!(f, "Authentication provider setup failed"),
831844
Self::StoreSetupFailed => write!(f, "Store setup failed"),
832845
}
833846
}
@@ -848,6 +861,30 @@ impl VssStoreBuilder {
848861
Self { vss_xprv, vss_url, store_id }
849862
}
850863

864+
/// Builds a [`VssStore`] with the simple signature-based authentication scheme.
865+
///
866+
/// `fixed_headers` are included as it is in all the requests made to VSS.
867+
///
868+
/// **Caution**: VSS support is in **alpha** and is considered experimental. Using VSS (or any
869+
/// remote persistence) may cause LDK to panic if persistence failures are unrecoverable, i.e.,
870+
/// if they remain unresolved after internal retries are exhausted.
871+
///
872+
/// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md
873+
pub fn build_with_sigs_auth(
874+
&self, fixed_headers: HashMap<String, String>,
875+
) -> Result<VssStore, VssStoreBuildError> {
876+
let secp_ctx = Secp256k1::new();
877+
let sigs_auth_xprv = self
878+
.vss_xprv
879+
.derive_priv(
880+
&secp_ctx,
881+
&[ChildNumber::Hardened { index: VSS_SIGS_AUTH_HARDENED_CHILD_INDEX }],
882+
)
883+
.map_err(|_| VssStoreBuildError::KeyDerivationFailed)?;
884+
let auth_provider = SigsAuthProvider::new(sigs_auth_xprv.private_key, fixed_headers);
885+
self.build_with_header_provider(Arc::new(auth_provider))
886+
}
887+
851888
/// Builds a [`VssStore`] with [LNURL-auth] based authentication scheme as default method for
852889
/// authentication/authorization.
853890
///
@@ -864,7 +901,7 @@ impl VssStoreBuilder {
864901
///
865902
/// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md
866903
/// [LNURL-auth]: https://github.com/lnurl/luds/blob/luds/04.md
867-
pub fn build(
904+
pub fn build_with_lnurl(
868905
&self, lnurl_auth_server_url: String, fixed_headers: HashMap<String, String>,
869906
) -> Result<VssStore, VssStoreBuildError> {
870907
let secp_ctx = Secp256k1::new();
@@ -877,8 +914,7 @@ impl VssStoreBuilder {
877914
.map_err(|_| VssStoreBuildError::KeyDerivationFailed)?;
878915

879916
let lnurl_auth_jwt_provider =
880-
LnurlAuthToJwtProvider::new(lnurl_auth_xprv, lnurl_auth_server_url, fixed_headers)
881-
.map_err(|_| VssStoreBuildError::AuthProviderSetupFailed)?;
917+
LnurlAuthToJwtProvider::new(lnurl_auth_xprv, lnurl_auth_server_url, fixed_headers);
882918

883919
let header_provider = Arc::new(lnurl_auth_jwt_provider);
884920

@@ -933,9 +969,9 @@ impl VssStoreBuilder {
933969
mod tests {
934970
use std::collections::HashMap;
935971

972+
use bitcoin::bip32::Xpriv;
936973
use rand::distr::Alphanumeric;
937974
use rand::{rng, Rng, RngCore};
938-
use vss_client::headers::FixedHeaders;
939975

940976
use super::*;
941977
use crate::io::test_utils::do_read_write_remove_list_persist;
@@ -947,9 +983,10 @@ mod tests {
947983
let rand_store_id: String = (0..7).map(|_| rng.sample(Alphanumeric) as char).collect();
948984
let mut vss_seed = [0u8; 32];
949985
rng.fill_bytes(&mut vss_seed);
950-
let header_provider = Arc::new(FixedHeaders::new(HashMap::new()));
951-
let vss_store =
952-
VssStore::new(vss_base_url, rand_store_id, vss_seed, header_provider).unwrap();
986+
let vss_xprv = Xpriv::new_master(bitcoin::Network::Testnet, &vss_seed).unwrap();
987+
let vss_store = VssStoreBuilder::new(vss_xprv, vss_base_url, rand_store_id)
988+
.build_with_fixed_headers(HashMap::new())
989+
.unwrap();
953990
do_read_write_remove_list_persist(&vss_store);
954991
}
955992

@@ -960,9 +997,10 @@ mod tests {
960997
let rand_store_id: String = (0..7).map(|_| rng.sample(Alphanumeric) as char).collect();
961998
let mut vss_seed = [0u8; 32];
962999
rng.fill_bytes(&mut vss_seed);
963-
let header_provider = Arc::new(FixedHeaders::new(HashMap::new()));
964-
let vss_store =
965-
VssStore::new(vss_base_url, rand_store_id, vss_seed, header_provider).unwrap();
1000+
let vss_xprv = Xpriv::new_master(bitcoin::Network::Testnet, &vss_seed).unwrap();
1001+
let vss_store = VssStoreBuilder::new(vss_xprv, vss_base_url, rand_store_id)
1002+
.build_with_fixed_headers(HashMap::new())
1003+
.unwrap();
9661004

9671005
do_read_write_remove_list_persist(&vss_store);
9681006
drop(vss_store)

lightning/src/ln/channelmanager.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9268,7 +9268,8 @@ impl<
92689268
ComplFunc: FnOnce(
92699269
Option<u64>,
92709270
bool,
9271-
) -> (Option<MonitorUpdateCompletionAction>, Option<RAAMonitorUpdateBlockingAction>),
9271+
)
9272+
-> (Option<MonitorUpdateCompletionAction>, Option<RAAMonitorUpdateBlockingAction>),
92729273
>(
92739274
&self, prev_hop: HTLCPreviousHopData, payment_preimage: PaymentPreimage,
92749275
payment_info: Option<PaymentClaimDetails>, attribution_data: Option<AttributionData>,
@@ -9306,7 +9307,8 @@ impl<
93069307
ComplFunc: FnOnce(
93079308
Option<u64>,
93089309
bool,
9309-
) -> (Option<MonitorUpdateCompletionAction>, Option<RAAMonitorUpdateBlockingAction>),
9310+
)
9311+
-> (Option<MonitorUpdateCompletionAction>, Option<RAAMonitorUpdateBlockingAction>),
93109312
>(
93119313
&self, prev_hop: HTLCClaimSource, payment_preimage: PaymentPreimage,
93129314
payment_info: Option<PaymentClaimDetails>, attribution_data: Option<AttributionData>,

lightning/src/ln/functional_tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -856,7 +856,7 @@ pub fn test_justice_tx_htlc_timeout() {
856856
revoked_local_txn[1].input[0].witness.last().unwrap().len(),
857857
OFFERED_HTLC_SCRIPT_WEIGHT
858858
); // HTLC-Timeout
859-
// Revoke the old state
859+
// Revoke the old state
860860
claim_payment(&nodes[0], &[&nodes[1]], payment_preimage_3);
861861

862862
{
@@ -6153,7 +6153,7 @@ pub fn test_announce_disable_channels() {
61536153
match e {
61546154
MessageSendEvent::BroadcastChannelUpdate { ref msg, .. } => {
61556155
assert_eq!(msg.contents.channel_flags & (1 << 1), 1 << 1); // The "channel disabled" bit should be set
6156-
// Check that each channel gets updated exactly once
6156+
// Check that each channel gets updated exactly once
61576157
if chans_disabled
61586158
.insert(msg.contents.short_channel_id, msg.contents.timestamp)
61596159
.is_some()

lightning/src/ln/funding.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,15 @@ impl FundingTemplate {
220220
return Err(());
221221
}
222222
let FundingTemplate { shared_input, min_feerate, max_feerate } = self;
223-
build_funding_contribution!(value_added, vec![], shared_input, min_feerate, max_feerate, wallet, await)
223+
build_funding_contribution!(
224+
value_added,
225+
vec![],
226+
shared_input,
227+
min_feerate,
228+
max_feerate,
229+
wallet,
230+
await
231+
)
224232
}
225233

226234
/// Creates a [`FundingContribution`] for adding funds to a channel using `wallet` to perform
@@ -251,7 +259,15 @@ impl FundingTemplate {
251259
return Err(());
252260
}
253261
let FundingTemplate { shared_input, min_feerate, max_feerate } = self;
254-
build_funding_contribution!(Amount::ZERO, outputs, shared_input, min_feerate, max_feerate, wallet, await)
262+
build_funding_contribution!(
263+
Amount::ZERO,
264+
outputs,
265+
shared_input,
266+
min_feerate,
267+
max_feerate,
268+
wallet,
269+
await
270+
)
255271
}
256272

257273
/// Creates a [`FundingContribution`] for removing funds from a channel using `wallet` to
@@ -282,7 +298,15 @@ impl FundingTemplate {
282298
return Err(());
283299
}
284300
let FundingTemplate { shared_input, min_feerate, max_feerate } = self;
285-
build_funding_contribution!(value_added, outputs, shared_input, min_feerate, max_feerate, wallet, await)
301+
build_funding_contribution!(
302+
value_added,
303+
outputs,
304+
shared_input,
305+
min_feerate,
306+
max_feerate,
307+
wallet,
308+
await
309+
)
286310
}
287311

288312
/// Creates a [`FundingContribution`] for both adding and removing funds from a channel using

lightning/src/ln/htlc_reserve_unit_tests.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,8 @@ pub fn test_channel_reserve_holding_cell_htlcs() {
288288
stat.value_to_self_msat
289289
- (stat.pending_outbound_htlcs_amount_msat
290290
+ recv_value_21 + recv_value_22
291-
+ total_fee_msat + total_fee_msat
291+
+ total_fee_msat
292+
+ total_fee_msat
292293
+ commit_tx_fee_3_htlcs),
293294
stat.channel_reserve_msat
294295
);

0 commit comments

Comments
 (0)