@@ -22,11 +22,12 @@ use bitcoin::hashes::{sha256, Hash, HashEngine, Hmac, HmacEngine};
2222use bitcoin:: key:: Secp256k1 ;
2323use lightning:: impl_writeable_tlv_based_enum;
2424use lightning:: io:: { self , Error , ErrorKind } ;
25+ use lightning:: sign:: { EntropySource as LdkEntropySource , RandomBytes } ;
2526use lightning:: util:: persist:: { KVStore , KVStoreSync } ;
2627use lightning:: util:: ser:: { Readable , Writeable } ;
27- use rand:: RngCore ;
2828use vss_client:: client:: VssClient ;
2929use vss_client:: error:: VssError ;
30+ use vss_client:: headers:: sigs_auth:: SigsAuthProvider ;
3031use vss_client:: headers:: { FixedHeaders , LnurlAuthToJwtProvider , VssHeaderProvider } ;
3132use vss_client:: prost:: Message ;
3233use vss_client:: types:: {
@@ -66,6 +67,7 @@ impl_writeable_tlv_based_enum!(VssSchemaVersion,
6667) ;
6768
6869const VSS_LNURL_AUTH_HARDENED_CHILD_INDEX : u32 = 138 ;
70+ const VSS_SIGS_AUTH_HARDENED_CHILD_INDEX : u32 = 139 ;
6971const 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
699709async 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 {}
817833pub 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 {
933969mod 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)
0 commit comments