From 6c168dfcfc413d703e284615ea8add5c0588145b Mon Sep 17 00:00:00 2001 From: Gursharan Singh <3442979+G8XSU@users.noreply.github.com> Date: Thu, 4 Jan 2024 14:29:38 -0800 Subject: [PATCH] Add Vss client side encryption using StorableBuilder --- Cargo.toml | 1 + src/builder.rs | 39 +++++++++++++++++++++++++++++++--- src/io/vss_store.rs | 38 ++++++++++++++++++++++++++------- tests/integration_tests_vss.rs | 5 +++-- 4 files changed, 70 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b831c1d75..90a41cdf8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,6 +69,7 @@ uniffi = { version = "0.25.1", features = ["build"], optional = true } [target.'cfg(vss)'.dependencies] vss-client = "0.1" +prost = { version = "0.11.6", default-features = false} [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", features = ["winbase"] } diff --git a/src/builder.rs b/src/builder.rs index b60123844..0eca05b3e 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -51,6 +51,8 @@ use bip39::Mnemonic; use bitcoin::BlockHash; +#[cfg(any(vss, vss_test))] +use bitcoin::bip32::ChildNumber; use std::convert::TryInto; use std::default::Default; use std::fmt; @@ -285,10 +287,41 @@ impl NodeBuilder { /// previously configured. #[cfg(any(vss, vss_test))] pub fn build_with_vss_store( - &self, url: &str, store_id: String, + &self, url: String, store_id: String, ) -> Result, BuildError> { - let vss = Arc::new(VssStore::new(url, store_id)); - self.build_with_store(vss) + 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 xprv = bitcoin::bip32::ExtendedPrivKey::new_master(config.network.into(), &seed_bytes) + .map_err(|e| { + log_error!(logger, "Failed to derive master secret: {}", e); + BuildError::InvalidSeedBytes + })?; + + let vss_xprv = xprv + .ckd_priv(&Secp256k1::new(), ChildNumber::Hardened { index: 877 }) + .map_err(|e| { + log_error!(logger, "Failed to derive VSS secret: {}", e); + BuildError::KVStoreSetupFailed + })?; + + 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)); + build_with_store_internal( + config, + self.chain_data_source_config.as_ref(), + self.gossip_source_config.as_ref(), + seed_bytes, + logger, + vss_store, + ) } /// Builds a [`Node`] instance according to the options previously configured. diff --git a/src/io/vss_store.rs b/src/io/vss_store.rs index c48e44c86..0ec901e6e 100644 --- a/src/io/vss_store.rs +++ b/src/io/vss_store.rs @@ -6,25 +6,31 @@ use std::panic::RefUnwindSafe; use crate::io::utils::check_namespace_key_validity; use lightning::util::persist::KVStore; +use prost::Message; +use rand::RngCore; use tokio::runtime::Runtime; use vss_client::client::VssClient; use vss_client::error::VssError; use vss_client::types::{ DeleteObjectRequest, GetObjectRequest, KeyValue, ListKeyVersionsRequest, PutObjectRequest, + Storable, }; +use vss_client::util::storable_builder::{EntropySource, StorableBuilder}; /// A [`KVStore`] implementation that writes to and reads from a [VSS](https://github.com/lightningdevkit/vss-server/blob/main/README.md) backend. pub struct VssStore { client: VssClient, store_id: String, runtime: Runtime, + storable_builder: StorableBuilder, } impl VssStore { - pub(crate) fn new(base_url: &str, store_id: String) -> Self { - let client = VssClient::new(base_url); + pub(crate) fn new(base_url: String, store_id: String, data_encryption_key: [u8; 32]) -> Self { + let client = VssClient::new(base_url.as_str()); let runtime = tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap(); - Self { client, store_id, runtime } + let storable_builder = StorableBuilder::new(data_encryption_key, RandEntropySource); + Self { client, store_id, runtime, storable_builder } } fn build_key( @@ -99,20 +105,25 @@ impl KVStore for VssStore { _ => Error::new(ErrorKind::Other, msg), } })?; - Ok(resp.value.unwrap().value) + // unwrap safety: resp.value must be always present for a non-erroneous VSS response, otherwise + // it is an API-violation which is converted to [`VssError::InternalServerError`] in [`VssClient`] + let storable = Storable::decode(&resp.value.unwrap().value[..])?; + Ok(self.storable_builder.deconstruct(storable)?.0) } fn write( &self, primary_namespace: &str, secondary_namespace: &str, key: &str, buf: &[u8], ) -> io::Result<()> { check_namespace_key_validity(primary_namespace, secondary_namespace, Some(key), "write")?; + let version = -1; + let storable = self.storable_builder.build(buf.to_vec(), version); let request = PutObjectRequest { store_id: self.store_id.clone(), global_version: None, transaction_items: vec![KeyValue { key: self.build_key(primary_namespace, secondary_namespace, key)?, - version: -1, - value: buf.to_vec(), + version, + value: storable.encode_to_vec(), }], delete_items: vec![], }; @@ -171,6 +182,15 @@ impl KVStore for VssStore { } } +/// A source for generating entropy/randomness using [`rand`]. +pub(crate) struct RandEntropySource; + +impl EntropySource for RandEntropySource { + fn fill_bytes(&self, buffer: &mut [u8]) { + rand::thread_rng().fill_bytes(buffer); + } +} + #[cfg(test)] impl RefUnwindSafe for VssStore {} @@ -180,14 +200,16 @@ mod tests { use super::*; use crate::io::test_utils::do_read_write_remove_list_persist; use rand::distributions::Alphanumeric; - use rand::{thread_rng, Rng}; + use rand::{thread_rng, Rng, RngCore}; #[test] fn read_write_remove_list_persist() { let vss_base_url = std::env::var("TEST_VSS_BASE_URL").unwrap(); let mut rng = thread_rng(); let rand_store_id: String = (0..7).map(|_| rng.sample(Alphanumeric) as char).collect(); - let vss_store = VssStore::new(&vss_base_url, rand_store_id); + 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); do_read_write_remove_list_persist(&vss_store); } diff --git a/tests/integration_tests_vss.rs b/tests/integration_tests_vss.rs index 165f6a386..26d0456d4 100644 --- a/tests/integration_tests_vss.rs +++ b/tests/integration_tests_vss.rs @@ -13,14 +13,15 @@ fn channel_full_cycle_with_vss_store() { let mut builder_a = Builder::from_config(config_a); builder_a.set_esplora_server(esplora_url.clone()); let vss_base_url = std::env::var("TEST_VSS_BASE_URL").unwrap(); - let node_a = builder_a.build_with_vss_store(&vss_base_url, "node_1_store".to_string()).unwrap(); + let node_a = + builder_a.build_with_vss_store(vss_base_url.clone(), "node_1_store".to_string()).unwrap(); node_a.start().unwrap(); println!("\n== Node B =="); let config_b = common::random_config(); let mut builder_b = Builder::from_config(config_b); builder_b.set_esplora_server(esplora_url); - 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(vss_base_url, "node_2_store".to_string()).unwrap(); node_b.start().unwrap(); common::do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, false);