Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Network keypair to enclave init. #209

Merged
merged 9 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/ciphernode/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/ciphernode/enclave/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ dialoguer = "0.11.0"
enclave-core = { path = "../core" }
enclave_node = { path = "../enclave_node" }
hex = { workspace = true }
libp2p = { workspace = true }
once_cell = "1.20.2"
router = { path = "../router" }
tokio = { workspace = true }
Expand Down
43 changes: 24 additions & 19 deletions packages/ciphernode/enclave/src/commands/init.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::commands::password::{self, PasswordCommands};
use anyhow::anyhow;
use anyhow::bail;
use anyhow::Result;
use crate::commands::{
net,
password::{self, PasswordCommands},
};
use alloy::primitives::Address;
use anyhow::{anyhow, bail, Result};
use config::load_config;
use config::RPC;
use dialoguer::{theme::ColorfulTheme, Input};
Expand All @@ -27,21 +29,10 @@ fn validate_rpc_url(url: &String) -> Result<()> {
}

fn validate_eth_address(address: &String) -> Result<()> {
if address.is_empty() {
return Ok(());
match Address::parse_checksummed(address, None) {
Ok(_) => Ok(()),
Err(e) => bail!("Invalid Ethereum address: {}", e),
}
if !address.starts_with("0x") {
bail!("Address must start with '0x'")
}
if address.len() != 42 {
bail!("Address must be 42 characters long (including '0x')")
}
for c in address[2..].chars() {
if !c.is_ascii_hexdigit() {
bail!("Address must contain only hexadecimal characters")
}
}
Ok(())
}

#[instrument(name = "app", skip_all, fields(id = get_tag()))]
Expand All @@ -50,6 +41,8 @@ pub async fn execute(
eth_address: Option<String>,
password: Option<String>,
skip_eth: bool,
net_keypair: Option<String>,
generate_net_keypair: bool,
) -> Result<()> {
let rpc_url = match rpc_url {
Some(url) => {
Expand Down Expand Up @@ -133,10 +126,22 @@ chains:
password,
overwrite: true,
},
config,
&config,
)
.await?;

if generate_net_keypair {
net::execute(net::NetCommands::GenerateKey, &config).await?;
} else {
net::execute(
net::NetCommands::SetKey {
net_keypair: net_keypair,
},
&config,
)
.await?;
}

println!("Enclave configuration successfully created!");
println!("You can start your node using `enclave start`");

Expand Down
8 changes: 8 additions & 0 deletions packages/ciphernode/enclave/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,13 @@ pub enum Commands {
/// Skip asking for eth
#[arg(long = "skip-eth")]
skip_eth: bool,

/// The network private key (ed25519)
#[arg(long = "net-keypair")]
net_keypair: Option<String>,

/// Generate a new network keypair
#[arg(long = "generate-net-keypair")]
generate_net_keypair: bool,
},
}
37 changes: 37 additions & 0 deletions packages/ciphernode/enclave/src/commands/net/generate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use actix::Actor;
use anyhow::{bail, Result};
use cipher::Cipher;
use config::AppConfig;
use enclave_core::{EventBus, GetErrors};
use enclave_node::get_repositories;
use libp2p::identity::Keypair;
use zeroize::Zeroize;

pub async fn execute(config: &AppConfig) -> Result<()> {
let kp = Keypair::generate_ed25519();
println!(
"Generated new keypair with peer ID: {}",
kp.public().to_peer_id()
);
let mut bytes = kp.try_into_ed25519()?.to_bytes().to_vec();
let cipher = Cipher::from_config(config).await?;
let encrypted = cipher.encrypt_data(&mut bytes.clone())?;
let bus = EventBus::new(true).start();
let repositories = get_repositories(&config, &bus)?;
hmzakhalid marked this conversation as resolved.
Show resolved Hide resolved
bytes.zeroize();

// NOTE: We are writing an encrypted string here
repositories.libp2p_keypair().write(&encrypted);

let errors = bus.send(GetErrors).await?;
if errors.len() > 0 {
for error in errors.iter() {
println!("{error}");
}
bail!("There were errors generating the network keypair")
}

println!("Network keypair has been successfully generated and encrypted.");

Ok(())
}
15 changes: 14 additions & 1 deletion packages/ciphernode/enclave/src/commands/net/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
mod generate;
mod purge;
mod set;
use anyhow::*;
use clap::Subcommand;
use config::AppConfig;
Expand All @@ -7,11 +9,22 @@ use config::AppConfig;
pub enum NetCommands {
/// Purge the current peer ID from the database.
PurgeId,

/// Generate a new network keypair
GenerateKey,

/// Set the network private key
SetKey {
#[arg(long = "net-keypair")]
net_keypair: Option<String>,
},
}

pub async fn execute(command: NetCommands, config: AppConfig) -> Result<()> {
pub async fn execute(command: NetCommands, config: &AppConfig) -> Result<()> {
match command {
NetCommands::PurgeId => purge::execute(&config).await?,
NetCommands::GenerateKey => generate::execute(&config).await?,
NetCommands::SetKey { net_keypair } => set::execute(&config, net_keypair).await?,
};

Ok(())
Expand Down
2 changes: 1 addition & 1 deletion packages/ciphernode/enclave/src/commands/net/purge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use enclave_node::get_repositories;
pub async fn execute(config: &AppConfig) -> Result<()> {
let bus = EventBus::new(true).start();
let repositories = get_repositories(&config, &bus)?;
repositories.libp2pid().clear();
repositories.libp2p_keypair().clear();
println!("Peer ID has been purged. A new Peer ID will be generated upon restart.");
Ok(())
}
59 changes: 59 additions & 0 deletions packages/ciphernode/enclave/src/commands/net/set.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use actix::Actor;
use alloy::primitives::hex;
use anyhow::{bail, Result};
use cipher::Cipher;
use config::AppConfig;
use dialoguer::{theme::ColorfulTheme, Password};
use enclave_core::{EventBus, GetErrors};
use enclave_node::get_repositories;
use libp2p::identity::Keypair;

pub fn create_keypair(input: &String) -> Result<Keypair> {
match hex::check(input) {
Ok(()) => match Keypair::ed25519_from_bytes(hex::decode(input)?) {
Ok(kp) => Ok(kp),
Err(e) => bail!("Invalid network keypair: {}", e),
},
Err(e) => bail!("Error decoding network keypair: {}", e),
}
}

fn validate_keypair_input(input: &String) -> Result<()> {
create_keypair(input).map(|_| ())
}

pub async fn execute(config: &AppConfig, net_keypair: Option<String>) -> Result<()> {
let input = if let Some(net_keypair) = net_keypair {
let kp = create_keypair(&net_keypair)?;
kp.try_into_ed25519()?.to_bytes().to_vec()
} else {
let kp = Password::with_theme(&ColorfulTheme::default())
.with_prompt("Enter your network private key")
.validate_with(validate_keypair_input)
.interact()?
.trim()
.to_string();
let kp = create_keypair(&kp)?;
kp.try_into_ed25519()?.to_bytes().to_vec()
};

let cipher = Cipher::from_config(config).await?;
let encrypted = cipher.encrypt_data(&mut input.clone())?;
let bus = EventBus::new(true).start();
let repositories = get_repositories(&config, &bus)?;

// NOTE: We are writing an encrypted string here
repositories.libp2p_keypair().write(&encrypted);
hmzakhalid marked this conversation as resolved.
Show resolved Hide resolved

let errors = bus.send(GetErrors).await?;
if errors.len() > 0 {
for error in errors.iter() {
println!("{error}");
}
bail!("There were errors setting the network keypair")
}

println!("Network keypair has been successfully encrypted.");

Ok(())
}
2 changes: 1 addition & 1 deletion packages/ciphernode/enclave/src/commands/password/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub enum PasswordCommands {
},
}

pub async fn execute(command: PasswordCommands, config: AppConfig) -> Result<()> {
pub async fn execute(command: PasswordCommands, config: &AppConfig) -> Result<()> {
match command {
PasswordCommands::Create {
password,
Expand Down
31 changes: 6 additions & 25 deletions packages/ciphernode/enclave/src/commands/wallet/set.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use actix::Actor;
use alloy::{hex::FromHex, primitives::FixedBytes, signers::local::PrivateKeySigner};
use anyhow::{anyhow, bail, Result};
use cipher::Cipher;
use config::AppConfig;
Expand All @@ -7,33 +8,13 @@ use enclave_core::{EventBus, GetErrors};
use enclave_node::get_repositories;

pub fn validate_private_key(input: &String) -> Result<()> {
// Require 0x prefix
if !input.starts_with("0x") {
return Err(anyhow!(
"Invalid private key format: must start with '0x' prefix"
));
}

// Remove 0x prefix
let key = &input[2..];

// Check length
if key.len() != 64 {
return Err(anyhow!(
"Invalid private key length: {}. Expected 64 characters after '0x' prefix",
key.len()
));
}

// Validate hex characters and convert to bytes
let _ = (0..key.len())
.step_by(2)
.map(|i| u8::from_str_radix(&key[i..i + 2], 16))
.collect::<Result<Vec<u8>, _>>()
.map_err(|e| anyhow!("Invalid hex character: {}", e))?;

let bytes =
FixedBytes::<32>::from_hex(input).map_err(|e| anyhow!("Invalid private key: {}", e))?;
let _ =
PrivateKeySigner::from_bytes(&bytes).map_err(|e| anyhow!("Invalid private key: {}", e))?;
Ok(())
}

pub async fn execute(config: &AppConfig, private_key: Option<String>) -> Result<()> {
let input = if let Some(private_key) = private_key {
validate_private_key(&private_key)?;
Expand Down
18 changes: 15 additions & 3 deletions packages/ciphernode/enclave/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,23 @@ impl Cli {
eth_address,
password,
skip_eth,
} => init::execute(rpc_url, eth_address, password, skip_eth).await?,
Commands::Password { command } => password::execute(command, config).await?,
net_keypair,
generate_net_keypair,
} => {
init::execute(
rpc_url,
eth_address,
password,
skip_eth,
net_keypair,
generate_net_keypair,
)
.await?
}
Commands::Password { command } => password::execute(command, &config).await?,
Commands::Aggregator { command } => aggregator::execute(command, config).await?,
Commands::Wallet { command } => wallet::execute(command, config).await?,
Commands::Net { command } => net::execute(command, config).await?,
Commands::Net { command } => net::execute(command, &config).await?,
}

Ok(())
Expand Down
2 changes: 1 addition & 1 deletion packages/ciphernode/enclave_node/src/aggregator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ pub async fn setup_aggregator(
&cipher,
config.quic_port(),
config.enable_mdns(),
repositories.libp2pid(),
repositories.libp2p_keypair(),
)
.await?;

Expand Down
2 changes: 1 addition & 1 deletion packages/ciphernode/enclave_node/src/ciphernode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ pub async fn setup_ciphernode(
&cipher,
config.quic_port(),
config.enable_mdns(),
repositories.libp2pid(),
repositories.libp2p_keypair(),
)
.await?;

Expand Down
33 changes: 14 additions & 19 deletions packages/ciphernode/net/src/network_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ use crate::NetworkPeer;
/// Actor for connecting to an libp2p client via it's mpsc channel interface
/// This Actor should be responsible for
use actix::prelude::*;
use anyhow::anyhow;
use anyhow::Result;
use anyhow::{anyhow, bail, Result};
use cipher::Cipher;
use data::Repository;
use enclave_core::{EnclaveEvent, EventBus, EventId, Subscribe};
Expand Down Expand Up @@ -75,35 +74,31 @@ impl NetworkManager {
enable_mdns: bool,
repository: Repository<Vec<u8>>,
) -> Result<(Addr<Self>, tokio::task::JoinHandle<Result<()>>, String)> {
info!("Reading from repository");
let mut bytes = if let Some(bytes) = repository.read().await? {
let decrypted = cipher.decrypt_data(&bytes)?;
info!("Found keypair in repository");
decrypted
} else {
let kp = libp2p::identity::Keypair::generate_ed25519();
info!("Generated new keypair {}", kp.public().to_peer_id());
let innerkp = kp.try_into_ed25519()?;
let bytes = innerkp.to_bytes().to_vec();

// We need to clone here so that returned bytes are not zeroized
repository.write(&cipher.encrypt_data(&mut bytes.clone())?);
info!("Saved new keypair to repository");
bytes
// Get existing keypair or generate a new one
let mut bytes = match repository.read().await? {
Some(bytes) => {
info!("Found keypair in repository");
cipher.decrypt_data(&bytes)?
}
None => bail!("No network keypair found in repository, please generate a new one using `enclave net generate-key`"),
};

let ed25519_keypair = ed25519::Keypair::try_from_bytes(&mut bytes)?;
let keypair: libp2p::identity::Keypair = ed25519_keypair.try_into()?;
// Create peer from keypair
let keypair: libp2p::identity::Keypair =
ed25519::Keypair::try_from_bytes(&mut bytes)?.try_into()?;
let mut peer = NetworkPeer::new(
&keypair,
peers,
Some(quic_port),
"tmp-enclave-gossip-topic",
enable_mdns,
)?;

// Setup and start network manager
let rx = peer.rx().ok_or(anyhow!("Peer rx already taken"))?;
let p2p_addr = NetworkManager::setup(bus, peer.tx(), rx);
let handle = tokio::spawn(async move { Ok(peer.start().await?) });

Ok((p2p_addr, handle, keypair.public().to_peer_id().to_string()))
}
}
Expand Down
Loading
Loading