Skip to content

feat: fix node setup UX [skip-line-limit]#1338

Merged
ryardley merged 45 commits into
mainfrom
ry/1337-autogenerate-net-keypair
Feb 20, 2026
Merged

feat: fix node setup UX [skip-line-limit]#1338
ryardley merged 45 commits into
mainfrom
ry/1337-autogenerate-net-keypair

Conversation

@ryardley

@ryardley ryardley commented Feb 17, 2026

Copy link
Copy Markdown
Contributor

Closes: #1337

This fixes up some UX issues I came across setting up the ciphernode on a box.

This PR:

  • New ciphernode setup command: Consolidated old config-set into enclave ciphernode setup refactored to collect all inputs first before executing
  • Derive libp2p PeerId: Derive libp2p PeerId from ETH wallet.
  • Interactive config directory: Prompts for config directory with smart default so we can configure in . (current working directory)
  • Address-key validation: Validates private key matches configured address at startup (fails if mismatched)
  • Secure memory handling: Passwords and private keys now use Zeroizing wrappers to securely zero memory
  • Address derived from key: Ethereum address is derived from private key (no separate --eth-address flag)
  • Removed skip_eth bypass: Eliminated ability to skip Ethereum address configuration
  • write_sync now flushes db writes: Running write sync in a command was sometimes exiting before writes have completed flushing to disk. Now it explicitly flushes all writes.
  • Updated docs: References to config-set now point to ciphernode setup
  • Fix testing issues: Tests now work on local virtual environments by moving /tmp stuff that requires bb to ./target/tmp

Example output:

image

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced enclave ciphernode setup command as the unified initialization flow, replacing the legacy enclave config-set command.
    • Enhanced security: sensitive credentials (passwords, private keys) now use memory-wiping protection to ensure secure handling.
    • Automatic Ethereum address derivation from private key during setup.
    • Added optional address verification validation control for test environments.
  • Changes

    • Setup workflow now accepts private key directly; Ethereum address is automatically derived.
    • Consolidated configuration initialization into the ciphernode setup flow.

@vercel

vercel Bot commented Feb 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
crisp Ready Ready Preview, Comment Feb 20, 2026 7:32am
enclave-docs Ready Ready Preview, Comment Feb 20, 2026 7:32am

Request Review

@coderabbitai

coderabbitai Bot commented Feb 17, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces a new interactive enclave ciphernode setup command replacing the legacy config-set flow. It refactors password and wallet handling to use secure Zeroizing types, adds address-validation control to the ciphernode builder, reorganizes config modules in the entrypoint, consolidates test utilities, and updates CLI command routing.

Changes

Cohort / File(s) Summary
CLI Setup Command
crates/cli/src/ciphernode/mod.rs, crates/cli/src/ciphernode/setup.rs, crates/cli/src/ciphernode/context.rs
New Setup subcommand with interactive orchestration (RPC URL, password, private key, net keypair). Includes user prompts, validation, address derivation, and configuration building.
CLI Restructuring
crates/cli/src/cli.rs, crates/cli/src/main.rs
Removed ConfigSet variant and config_set module; routed setup flow through new Ciphernode::Setup path. Updated error messages to reference enclave ciphernode setup.
Legacy Config-Set Removal
crates/cli/src/config_set.rs
Deleted entire module; functionality replaced by new setup orchestration.
Password Security
crates/cli/src/password.rs, crates/cli/src/password_set.rs, crates/entrypoint/src/password/set.rs
Refactored password handling to use Zeroizing<String> with value parser. Added ask_for_password helper and execute_bytes variant in entrypoint.
Wallet Security
crates/cli/src/wallet.rs, crates/cli/src/wallet_set.rs, crates/entrypoint/src/wallet/set.rs
Updated private_key fields to Zeroizing<String>; replaced ensure_hex with ensure_hex_zeroizing. Added ask_for_private_key helper with address derivation.
Config Module Reorganization
crates/entrypoint/src/lib.rs, crates/entrypoint/src/config/mod.rs, crates/entrypoint/src/config/setup.rs, crates/entrypoint/src/config/set_address.rs
Renamed config_set to config; split into setup (sync, takes config_dir and Address) and set_address (writes address to YAML). Added address field to AppConfig.
Ciphernode Builder
crates/ciphernode-builder/src/ciphernode_builder.rs, crates/ciphernode-builder/src/event_system.rs
Replaced start_buffer with ignore_address_check flag; added conditional address verification logic in build method.
Helper Utilities
crates/cli/src/helpers/mod.rs, crates/utils/src/alloy.rs
Added parse_zeroizing, ensure_hex_zeroizing in CLI helpers; added eth_address_from_private_key utility function.
Test Utility Consolidation
crates/zk-prover/src/test_utils.rs, crates/zk-prover/src/lib.rs, crates/zk-prover/src/backend/tests.rs, crates/zk-prover/src/config.rs, crates/zk-prover/src/prover.rs, crates/zk-prover/tests/*, crates/tests/tests/integration.rs
Introduced centralized get_tempdir() utility; replaced scattered tempfile::tempdir() calls across test files.
Crypto Security
crates/evm/src/helpers.rs
Wrapped decrypted private key in Zeroizing<String> for secure memory handling.
Dependencies
crates/cli/Cargo.toml, crates/entrypoint/Cargo.toml
Added dirs and e3-utils to CLI; added serde_yaml to entrypoint.
Documentation
docs/pages/ciphernode-operators/running.mdx
Updated setup instructions to use enclave ciphernode setup instead of enclave config-set.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant CLI as CLI Setup
    participant Config as Config Manager
    participant Password as Password Setup
    participant Wallet as Wallet Setup
    participant Net as Net Keypair Setup
    
    User->>CLI: enclave ciphernode setup [options]
    CLI->>CLI: Validate/prompt for RPC URL
    CLI->>CLI: Resolve password (prompt if needed)
    CLI->>CLI: Resolve private key (prompt if needed)
    CLI->>Wallet: Derive address from key
    CLI->>CLI: Prompt for config directory
    CLI->>Config: execute(rpc_url, config_dir, address)
    Config-->>CLI: AppConfig created
    CLI->>Password: execute(config, password)
    Password-->>CLI: Password stored & encrypted
    CLI->>Wallet: execute(config, private_key)
    Wallet-->>CLI: Wallet key stored & encrypted
    alt Generate net keypair
        CLI->>Net: generate_net_keypair()
    else Use provided net keypair
        CLI->>Net: set_net_keypair(keypair)
    else Auto-generate if none provided
        CLI->>Net: auto_generate()
    end
    Net-->>CLI: Net keypair configured
    CLI-->>User: ✓ Enclave configuration successfully created!
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly Related PRs

Suggested Labels

ciphernode

Suggested Reviewers

  • ctrlc03

Poem

🐰 Hops through the setup flow so bright,
With zeroizing types held tight,
No more config-set in sight,
Just ciphernode setup—done right! 🔐✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The code changes comprehensively implement all objectives from issue #1337: auto-generates network keypair by default, provides guided interactive setup command 'enclave ciphernode setup', collects all required information before execution, derives Ethereum address from private key, and validates address match at startup.
Out of Scope Changes check ✅ Passed Additional changes include: securing sensitive data with Zeroizing wrappers for passwords/keys, refactoring temporary directory handling in tests to use a centralized utility, updating ciphernode builder to verify address-key match, and adding helper functions for zeroized string parsing. These are all reasonable supporting changes that enable or complement the core setup UX improvements.
Title check ✅ Passed The title 'feat: fix node setup UX' directly and clearly describes the primary change—improving the ciphernode setup user experience through a new interactive command.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ry/1337-autogenerate-net-keypair

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ryardley

Copy link
Copy Markdown
Contributor Author

yeah I need to fix the tests to use real addresses not going to finish this tonight but will do so tomorrow

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
crates/entrypoint/src/config_set/mod.rs (1)

41-41: Prefer &Path over &PathBuf in function signatures.

Idiomatic Rust prefers &Path since PathBuf auto-derefs to Path, and accepting &Path is more flexible for callers.

♻️ Suggested change
+use std::path::Path;
-pub fn execute(rpc_url: String, config_dir: &PathBuf, address: &Address) -> Result<AppConfig> {
+pub fn execute(rpc_url: String, config_dir: &Path, address: &Address) -> Result<AppConfig> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/entrypoint/src/config_set/mod.rs` at line 41, Change the function
signature of execute to accept a reference to Path instead of PathBuf: replace
the parameter type config_dir: &PathBuf with config_dir: &Path in the execute
function so callers can pass any Path-like type; update any internal uses (e.g.,
methods expecting &PathBuf) to work with &Path (use .to_path_buf() only when an
owned PathBuf is required) and update all callers to pass a &Path (or let the
compiler auto-deref other path types). Ensure necessary use statements
(std::path::Path) are present and adjust any type annotations that assumed
PathBuf.
crates/cli/src/password_set.rs (1)

13-18: Inconsistent trimming between provided and prompted password paths.

When a password is provided via argument (line 18), it's returned as-is (untrimmed). When entered interactively (line 40), it's trimmed. This means a CLI-passed password " foo " would be stored differently than if the same string were typed at the prompt.

Suggested fix
     if let Some(pw_str) = input {
         if pw_str.trim().is_empty() {
             bail!("Password must not be blank")
         }
-        return Ok(pw_str);
+        let trimmed = Zeroizing::new(pw_str.trim().to_owned());
+        return Ok(trimmed);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cli/src/password_set.rs` around lines 13 - 18, The ask_for_password
function treats argument-provided passwords and prompted passwords
inconsistently: trim the provided Zeroizing<String> the same way as the
interactive path to ensure identical handling; specifically, in ask_for_password
when handling the Some(pw_str) branch, apply the same trimming/check (e.g., call
trim() on the string and bail if empty) and return the trimmed Zeroizing<String>
so the return value matches what prompt_password returns.
crates/cli/src/cli.rs (1)

121-131: enclave start with no config runs setup but doesn't start the node or inform the user.

When a user runs enclave start without an existing config, setup runs and then the function returns (line 140). The node never actually starts, and unlike the explicit setup path (line 119), there's no message telling the user to run enclave start again.

Consider adding a hint after setup completes:

                     ciphernode::setup::execute(
                         None,
                         None,
                         None,
                         None,
                         false,
                     )
                     .await?;
+                    println!("Setup complete. Please run `enclave start` again to start your node.");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cli/src/cli.rs` around lines 121 - 131, The Commands::Start match arm
currently runs ciphernode::setup::execute when no config exists but then
returns, leaving the node stopped and the user uninformed; update that branch so
that after awaiting ciphernode::setup::execute you either (a) invoke the same
start logic used for the normal start path (reuse or call the function that
actually launches the node) so the node begins immediately, or (b) print a clear
hint like "Setup complete — run `enclave start` again to start the node" (or
both). Locate the Commands::Start match arm and the ciphernode::setup::execute
call and add the follow-up action (start invocation or user message) to ensure
the user knows the next step or the node starts automatically.
crates/cli/src/wallet.rs (1)

20-21: Consider updating ensure_hex to return Zeroizing<String> to avoid plaintext key exposure in intermediate String.

The parser currently returns Result<String>, which means the plaintext key briefly exists in a non-zeroizing String before clap converts it to Zeroizing<String>. For sensitive data like private keys, it's better to wrap the result immediately:

Suggested improvement
-fn ensure_hex(s: &str) -> Result<String> {
+fn ensure_hex(s: &str) -> Result<Zeroizing<String>> {
     if !s.starts_with("0x") {
         bail!("hex value must start with '0x'")
     }
     hex::decode(&s[2..])?;
-    Ok(s.to_string())
+    Ok(Zeroizing::new(s.to_string()))
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cli/src/wallet.rs` around lines 20 - 21, The parser ensure_hex
currently returns Result<String>, briefly creating a non-zeroizing String for
sensitive private-key input; change ensure_hex to return
Result<Zeroizing<String>> so the sensitive bytes are immediately wrapped and
never exist in a plain String, update its signature and any call sites (e.g.,
the clap arg value_parser used with private_key) to accept
Result<Zeroizing<String>>, and ensure any internal string construction uses
Zeroizing::new(...) or maps the existing String into Zeroizing before returning
to avoid exposing plaintext in memory.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/ciphernode-builder/src/ciphernode_builder.rs`:
- Around line 416-426: The comparison between signer.address().to_string() and
addr is case-sensitive; instead parse the config addr string into the same
Address type and compare the Address values (or at minimum use a
case-insensitive comparison) before bailing. In the block guarded by
self.ignore_address_check where you call provider_cache.ensure_signer().await?
and use signer.address(), parse addr into an ethers::types::Address (handle
parse errors) and compare it to signer.address() for equality (or call
.eq_ignore_ascii_case if you prefer string comparison), then bail only if they
differ.

In `@crates/cli/src/wallet_set.rs`:
- Line 36: The success message printed in wallet_set.rs currently reads
"WalletKey key has been successfully stored and encrypted." — update the println
in the wallet_set module (the println! call) to correct the typo to a clearer
message such as "Wallet key has been successfully stored and encrypted." or
"Private key has been successfully stored and encrypted." so it reads naturally.

In `@docs/pages/ciphernode-operators/running.mdx`:
- Line 62: The "Initialize Configuration" step currently shows the wrong command
string "enclave ciphernode start"; update that text to "enclave ciphernode
setup" so it matches the intended initialization command (leave the "Start Your
Node" section using "enclave start -v" as-is). Search for the literal "enclave
ciphernode start" in the document and replace it with "enclave ciphernode setup"
to correct the documentation.

---

Nitpick comments:
In `@crates/cli/src/cli.rs`:
- Around line 121-131: The Commands::Start match arm currently runs
ciphernode::setup::execute when no config exists but then returns, leaving the
node stopped and the user uninformed; update that branch so that after awaiting
ciphernode::setup::execute you either (a) invoke the same start logic used for
the normal start path (reuse or call the function that actually launches the
node) so the node begins immediately, or (b) print a clear hint like "Setup
complete — run `enclave start` again to start the node" (or both). Locate the
Commands::Start match arm and the ciphernode::setup::execute call and add the
follow-up action (start invocation or user message) to ensure the user knows the
next step or the node starts automatically.

In `@crates/cli/src/password_set.rs`:
- Around line 13-18: The ask_for_password function treats argument-provided
passwords and prompted passwords inconsistently: trim the provided
Zeroizing<String> the same way as the interactive path to ensure identical
handling; specifically, in ask_for_password when handling the Some(pw_str)
branch, apply the same trimming/check (e.g., call trim() on the string and bail
if empty) and return the trimmed Zeroizing<String> so the return value matches
what prompt_password returns.

In `@crates/cli/src/wallet.rs`:
- Around line 20-21: The parser ensure_hex currently returns Result<String>,
briefly creating a non-zeroizing String for sensitive private-key input; change
ensure_hex to return Result<Zeroizing<String>> so the sensitive bytes are
immediately wrapped and never exist in a plain String, update its signature and
any call sites (e.g., the clap arg value_parser used with private_key) to accept
Result<Zeroizing<String>>, and ensure any internal string construction uses
Zeroizing::new(...) or maps the existing String into Zeroizing before returning
to avoid exposing plaintext in memory.

In `@crates/entrypoint/src/config_set/mod.rs`:
- Line 41: Change the function signature of execute to accept a reference to
Path instead of PathBuf: replace the parameter type config_dir: &PathBuf with
config_dir: &Path in the execute function so callers can pass any Path-like
type; update any internal uses (e.g., methods expecting &PathBuf) to work with
&Path (use .to_path_buf() only when an owned PathBuf is required) and update all
callers to pass a &Path (or let the compiler auto-deref other path types).
Ensure necessary use statements (std::path::Path) are present and adjust any
type annotations that assumed PathBuf.

Comment thread crates/ciphernode-builder/src/ciphernode_builder.rs Outdated
Comment thread crates/cli/src/wallet_set.rs Outdated
Comment thread docs/pages/ciphernode-operators/running.mdx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
crates/cli/src/helpers/mod.rs (1)

24-33: Error message in generic helper is domain-specific.

ensure_hex is a general-purpose hex validator, but Line 30's error message says "private key must only contain hex characters." If this function is reused for non-private-key hex values, the message will be confusing.

Suggested fix
     if !s[2..].chars().all(|c| c.is_ascii_hexdigit()) {
-        bail!("private key must only contain hex characters [0-9a-fA-F]");
+        bail!("hex value must only contain hex characters [0-9a-fA-F]");
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cli/src/helpers/mod.rs` around lines 24 - 33, The helper function
ensure_hex currently emits a domain-specific error ("private key must only
contain hex characters"), so update its error text to be generic: change the
second bail! message to something like "hex value must only contain hex
characters [0-9a-fA-F]" (and optionally make the first bail! message consistent,
e.g., "hex value must start with '0x'") in the ensure_hex function to avoid
referencing "private key" for non-key usage.
crates/cli/src/ciphernode/mod.rs (2)

44-50: Sensitive CLI args will appear in /proc/<pid>/cmdline and shell history.

Both --password and --private-key accept secrets as direct CLI arguments. While Zeroizing protects in-process memory, the values remain visible in the process list and shell history. Since the PR already implements interactive prompts as the primary UX (prompting when not supplied), consider documenting that users should prefer the interactive prompt over passing these flags directly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cli/src/ciphernode/mod.rs` around lines 44 - 50, The CLI currently
accepts sensitive secrets via the password and private_key args (fields password
and private_key using parse_zeroizing and ensure_hex_zeroizing) which can leak
to /proc and shell history; update the arg help text and user docs to clearly
warn users to prefer the interactive prompt (i.e., when these Options are None)
rather than passing secrets on the command line, and add an explicit warning in
the CLI --help and project README near the ciphernode usage; optionally mark
these args as hidden or document mitigation (e.g., use env/interactive) in the
same place so users see the security guidance.

52-54: Consider wrapping net_keypair in Zeroizing<String> for consistency with other sensitive fields.

The password and private_key fields use Zeroizing<String> to scrub sensitive data from memory, but net_keypair (an ed25519 private key) is a plain Option<String>. For consistency and defense-in-depth, applying the same zeroization would be prudent.

Note: The net keypair is encrypted immediately after being passed to the downstream handler, so the window for exposure is small. If applied, ensure the downstream net::keypair::set::execute can accept Zeroizing<String>.

Suggested approach
         /// The network private key (ed25519)
-        #[arg(long = "net-keypair", short = 'n')]
-        net_keypair: Option<String>,
+        #[arg(long = "net-keypair", short = 'n', value_parser = parse_zeroizing)]
+        net_keypair: Option<Zeroizing<String>>,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cli/src/ciphernode/mod.rs` around lines 52 - 54, Wrap the net_keypair
field's type in Zeroizing<String> (i.e., change net_keypair: Option<String> to
net_keypair: Option<Zeroizing<String>>) so the ed25519 private key is zeroed
from memory like password and private_key; update any constructors/deserializers
that build the struct and convert incoming strings into Zeroizing<String> before
storing, and adjust the downstream call site net::keypair::set::execute to
accept or be given a &str/owned String by unwrapping or cloning the Zeroizing
value as needed (or update that function signature to accept Zeroizing<String>)
to ensure compatibility.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@crates/ciphernode-builder/src/ciphernode_builder.rs`:
- Around line 416-426: The address comparison in the block guarded by
self.ignore_address_check is already correct: it normalizes both the config
address (addr) and the signer's address (signer.address()) to lowercase before
comparing; no code changes are required—retain the ensure_signer() call and the
current lowercase normalization of signer.address().to_string() and addr to
preserve EIP-55 casing tolerance.

---

Nitpick comments:
In `@crates/cli/src/ciphernode/mod.rs`:
- Around line 44-50: The CLI currently accepts sensitive secrets via the
password and private_key args (fields password and private_key using
parse_zeroizing and ensure_hex_zeroizing) which can leak to /proc and shell
history; update the arg help text and user docs to clearly warn users to prefer
the interactive prompt (i.e., when these Options are None) rather than passing
secrets on the command line, and add an explicit warning in the CLI --help and
project README near the ciphernode usage; optionally mark these args as hidden
or document mitigation (e.g., use env/interactive) in the same place so users
see the security guidance.
- Around line 52-54: Wrap the net_keypair field's type in Zeroizing<String>
(i.e., change net_keypair: Option<String> to net_keypair:
Option<Zeroizing<String>>) so the ed25519 private key is zeroed from memory like
password and private_key; update any constructors/deserializers that build the
struct and convert incoming strings into Zeroizing<String> before storing, and
adjust the downstream call site net::keypair::set::execute to accept or be given
a &str/owned String by unwrapping or cloning the Zeroizing value as needed (or
update that function signature to accept Zeroizing<String>) to ensure
compatibility.

In `@crates/cli/src/helpers/mod.rs`:
- Around line 24-33: The helper function ensure_hex currently emits a
domain-specific error ("private key must only contain hex characters"), so
update its error text to be generic: change the second bail! message to
something like "hex value must only contain hex characters [0-9a-fA-F]" (and
optionally make the first bail! message consistent, e.g., "hex value must start
with '0x'") in the ensure_hex function to avoid referencing "private key" for
non-key usage.

hmzakhalid
hmzakhalid previously approved these changes Feb 19, 2026
@ryardley

Copy link
Copy Markdown
Contributor Author

as soon as I can build and run this locally will approve or make changess.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Node setup UX

2 participants