Skip to content

Commit de14a61

Browse files
committed
feat: Note transport E2E encryption
1 parent f81162d commit de14a61

File tree

13 files changed

+460
-58
lines changed

13 files changed

+460
-58
lines changed

bin/integration-tests/src/tests/transport.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use std::sync::Arc;
22

33
use anyhow::{Context, Result};
44
use miden_client::account::AccountStorageMode;
5-
use miden_client::address::{Address, AddressInterface, RoutingParameters};
65
use miden_client::asset::FungibleAsset;
76
use miden_client::auth::RPO_FALCON_SCHEME_ID;
87
use miden_client::keystore::FilesystemKeyStore;
@@ -165,10 +164,16 @@ async fn run_flow(
165164
.await
166165
.context("failed to insert faucet in sender")?;
167166

168-
// Build recipient address
169-
let recipient_address = Address::new(recipient_account.id())
170-
.with_routing_parameters(RoutingParameters::new(AddressInterface::BasicWallet))
171-
.context("failed to build recipient address")?;
167+
// Get a recipient's address
168+
let recipient_addresses = recipient
169+
.test_store()
170+
.get_addresses_by_account_id(recipient_account.id())
171+
.await
172+
.context("failed to get recipient addresses")?;
173+
let recipient_address = recipient_addresses
174+
.first()
175+
.context("recipient should have a default address (with encryption key)")?
176+
.clone();
172177

173178
// Ensure recipient has no input notes
174179
recipient.sync_state().await.context("recipient initial sync")?;

bin/miden-cli/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,15 @@ impl Cli {
188188
let keystore = CliKeyStore::new(cli_config.secret_keys_directory.clone())
189189
.map_err(CliError::KeyStore)?;
190190

191+
// Create encryption keystore (shares directory with auth keystore)
192+
let encryption_keystore = CliKeyStore::new(cli_config.secret_keys_directory.clone())
193+
.map_err(CliError::KeyStore)?;
194+
191195
let mut builder = ClientBuilder::new()
192196
.sqlite_store(cli_config.store_filepath.clone())
193197
.grpc_client(&cli_config.rpc.endpoint.clone().into(), Some(cli_config.rpc.timeout_ms))
194198
.authenticator(Arc::new(keystore.clone()))
199+
.encryption_keystore(Arc::new(encryption_keystore))
195200
.in_debug_mode(in_debug_mode)
196201
.tx_graceful_blocks(Some(TX_GRACEFUL_BLOCK_DELTA));
197202

bin/miden-cli/tests/cli.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,12 +1074,14 @@ async fn create_rust_client_with_store_path(
10741074

10751075
let keystore = CliKeyStore::new(temp_dir())?;
10761076

1077+
let encryption_keystore = Arc::new(keystore.clone());
10771078
Ok((
10781079
TestClient::new(
10791080
Arc::new(GrpcClient::new(&endpoint, 10_000)),
10801081
rng,
10811082
store,
10821083
Some(std::sync::Arc::new(keystore.clone())),
1084+
Some(encryption_keystore),
10831085
ExecutionOptions::new(
10841086
Some(MAX_TX_EXECUTION_CYCLES),
10851087
MIN_TX_EXECUTION_CYCLES,

crates/idxdb-store/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,9 @@ impl Store for WebStore {
324324
async fn list_setting_keys(&self) -> Result<Vec<String>, StoreError> {
325325
self.list_setting_keys().await
326326
}
327+
328+
// ENCRYPTION KEYS
329+
// --------------------------------------------------------------------------------------------
327330
}
328331

329332
// UTILS

crates/rust-client/src/account/mod.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ use alloc::vec::Vec;
3939
use miden_lib::account::auth::{AuthEcdsaK256Keccak, AuthRpoFalcon512};
4040
use miden_lib::account::wallets::BasicWallet;
4141
use miden_objects::account::auth::PublicKey;
42+
use miden_objects::address::RoutingParameters;
43+
use miden_objects::crypto::dsa::eddsa_25519::SecretKey;
44+
use miden_objects::crypto::ies::{SealingKey, UnsealingKey};
4245
use miden_objects::note::NoteTag;
4346
// RE-EXPORTS
4447
// ================================================================================================
@@ -165,7 +168,40 @@ impl<AUTH> Client<AUTH> {
165168

166169
match tracked_account {
167170
None => {
168-
let default_address = Address::new(account.id());
171+
// Generate encryption key pair and create default address with public key
172+
let default_address = if let Some(ref keystore) = self.encryption_keystore {
173+
// Generate encryption X25519 key pair
174+
let mut rng = rand::rng();
175+
let secret_key = SecretKey::with_rng(&mut rng);
176+
let public_key = secret_key.public_key();
177+
178+
let sealing_key = SealingKey::X25519XChaCha20Poly1305(public_key);
179+
let unsealing_key = UnsealingKey::X25519XChaCha20Poly1305(secret_key);
180+
181+
// Create address with encryption key
182+
let address = Address::new(account.id())
183+
.with_routing_parameters(
184+
RoutingParameters::new(AddressInterface::BasicWallet)
185+
.with_encryption_key(sealing_key),
186+
)
187+
.map_err(|e| {
188+
ClientError::ClientInitializationError(format!(
189+
"Failed to create address with encryption key: {e}"
190+
))
191+
})?;
192+
193+
// Store key in keystore by address
194+
keystore.add_encryption_key(&address, &unsealing_key).map_err(|e| {
195+
ClientError::ClientInitializationError(format!(
196+
"Failed to store encryption key: {e}"
197+
))
198+
})?;
199+
200+
address
201+
} else {
202+
// No keystore - use plain address
203+
Address::new(account.id())
204+
};
169205

170206
// If the account is not being tracked, insert it into the store regardless of the
171207
// `overwrite` flag

crates/rust-client/src/builder.rs

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use miden_tx::ExecutionOptions;
88
use miden_tx::auth::TransactionAuthenticator;
99
use rand::Rng;
1010

11-
use crate::keystore::FilesystemKeyStore;
11+
use crate::keystore::{EncryptionKeyStore, FilesystemKeyStore};
1212
use crate::note_transport::NoteTransportClient;
1313
use crate::rpc::NodeRpcClient;
1414
use crate::store::{Store, StoreError};
@@ -36,6 +36,16 @@ enum AuthenticatorConfig<AUTH> {
3636
Instance(Arc<AUTH>),
3737
}
3838

39+
/// Represents the configuration for an encryption keystore.
40+
///
41+
/// This enum defers encryption keystore instantiation until the build phase.
42+
enum EncryptionKeystoreConfig {
43+
/// Use a filesystem keystore at the given path.
44+
Path(String),
45+
/// Use a custom encryption keystore instance.
46+
Instance(Arc<dyn EncryptionKeyStore + Send + Sync>),
47+
}
48+
3949
// STORE BUILDER
4050
// ================================================================================================
4151

@@ -59,8 +69,7 @@ pub trait StoreFactory {
5969
/// A builder for constructing a Miden client.
6070
///
6171
/// This builder allows you to configure the various components required by the client, such as the
62-
/// RPC endpoint, store, RNG, and keystore. It is generic over the keystore type. By default, it
63-
/// uses `FilesystemKeyStore<rand::rngs::StdRng>`.
72+
/// RPC endpoint, store, RNG, and keystore. It is generic over the authenticator type.
6473
pub struct ClientBuilder<AUTH> {
6574
/// An optional custom RPC client. If provided, this takes precedence over `rpc_endpoint`.
6675
rpc_api: Option<Arc<dyn NodeRpcClient>>,
@@ -70,6 +79,8 @@ pub struct ClientBuilder<AUTH> {
7079
rng: Option<Box<dyn FeltRng>>,
7180
/// The keystore configuration provided by the user.
7281
keystore: Option<AuthenticatorConfig<AUTH>>,
82+
/// The encryption keystore configuration.
83+
encryption_keystore: Option<EncryptionKeystoreConfig>,
7384
/// A flag to enable debug mode.
7485
in_debug_mode: DebugMode,
7586
/// The number of blocks that are considered old enough to discard pending transactions. If
@@ -91,6 +102,7 @@ impl<AUTH> Default for ClientBuilder<AUTH> {
91102
store: None,
92103
rng: None,
93104
keystore: None,
105+
encryption_keystore: None,
94106
in_debug_mode: DebugMode::Disabled,
95107
tx_graceful_blocks: Some(TX_GRACEFUL_BLOCKS),
96108
max_block_number_delta: None,
@@ -154,6 +166,16 @@ where
154166
self
155167
}
156168

169+
/// Optionally provide a custom encryption keystore instance.
170+
#[must_use]
171+
pub fn encryption_keystore<ENC>(mut self, keystore: Arc<ENC>) -> Self
172+
where
173+
ENC: EncryptionKeyStore + Send + Sync + 'static,
174+
{
175+
self.encryption_keystore = Some(EncryptionKeystoreConfig::Instance(keystore));
176+
self
177+
}
178+
157179
/// Optionally set a maximum number of blocks that the client can be behind the network.
158180
/// By default, there's no maximum.
159181
#[must_use]
@@ -175,9 +197,11 @@ where
175197
///
176198
/// This stores the keystore path as a configuration option so that actual keystore
177199
/// initialization is deferred until `build()`. This avoids panicking during builder chaining.
200+
/// The same directory will be used for both authentication and encryption keys.
178201
#[must_use]
179202
pub fn filesystem_keystore(mut self, keystore_path: &str) -> Self {
180203
self.keystore = Some(AuthenticatorConfig::Path(keystore_path.to_string()));
204+
self.encryption_keystore = Some(EncryptionKeystoreConfig::Path(keystore_path.to_string()));
181205
self
182206
}
183207

@@ -241,16 +265,29 @@ where
241265
Some(AuthenticatorConfig::Path(ref path)) => {
242266
let keystore = FilesystemKeyStore::new(path.into())
243267
.map_err(|err| ClientError::ClientInitializationError(err.to_string()))?;
244-
Some(Arc::new(AUTH::from(keystore)))
268+
Some(Arc::new(AUTH::from_keystore(keystore)))
245269
},
246270
None => None,
247271
};
248272

273+
// Initialize the encryption keystore.
274+
let encryption_keystore: Option<Arc<dyn EncryptionKeyStore + Send + Sync>> =
275+
match self.encryption_keystore {
276+
Some(EncryptionKeystoreConfig::Instance(ks)) => Some(ks),
277+
Some(EncryptionKeystoreConfig::Path(ref path)) => {
278+
let keystore = FilesystemKeyStore::new(path.into())
279+
.map_err(|err| ClientError::ClientInitializationError(err.to_string()))?;
280+
Some(Arc::new(keystore))
281+
},
282+
None => None,
283+
};
284+
249285
Client::new(
250286
rpc_api,
251287
rng,
252288
store,
253289
authenticator,
290+
encryption_keystore,
254291
ExecutionOptions::new(
255292
Some(MAX_TX_EXECUTION_CYCLES),
256293
MIN_TX_EXECUTION_CYCLES,
@@ -271,12 +308,14 @@ where
271308
// ================================================================================================
272309

273310
/// Marker trait to capture the bounds the builder requires for the authenticator type
274-
/// parameter
275-
pub trait BuilderAuthenticator:
276-
TransactionAuthenticator + From<FilesystemKeyStore<rand::rngs::StdRng>> + 'static
277-
{
311+
/// parameter.
312+
pub trait BuilderAuthenticator: TransactionAuthenticator + 'static {
313+
/// Creates an authenticator from a `FilesystemKeyStore`.
314+
fn from_keystore(keystore: FilesystemKeyStore<rand::rngs::StdRng>) -> Self;
278315
}
279-
impl<T> BuilderAuthenticator for T where
280-
T: TransactionAuthenticator + From<FilesystemKeyStore<rand::rngs::StdRng>> + 'static
281-
{
316+
317+
impl BuilderAuthenticator for FilesystemKeyStore<rand::rngs::StdRng> {
318+
fn from_keystore(keystore: FilesystemKeyStore<rand::rngs::StdRng>) -> Self {
319+
keystore
320+
}
282321
}

0 commit comments

Comments
 (0)