diff --git a/Cargo.lock b/Cargo.lock index d0014632a6..7418c8856c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2973,6 +2973,8 @@ dependencies = [ "bcs", "bitcoin", "clap", + "fastcrypto", + "hashi", "hashi-guardian", "hashi-types", "hex", @@ -2982,6 +2984,7 @@ dependencies = [ "serde", "serde_yaml", "tokio", + "toml", "tonic", "tracing", ] @@ -3522,16 +3525,12 @@ name = "internal-tools" version = "0.0.0" dependencies = [ "anyhow", - "bcs", - "bitcoin", "clap", "fastcrypto", "fastcrypto-tbls", "hashi", "hashi-types", "hex", - "hpke", - "k256", "rand 0.8.5", "sui-sdk-types", "tokio", diff --git a/crates/hashi-guardian-init/Cargo.toml b/crates/hashi-guardian-init/Cargo.toml index 495a01c7fc..6ee10b3a0e 100644 --- a/crates/hashi-guardian-init/Cargo.toml +++ b/crates/hashi-guardian-init/Cargo.toml @@ -6,7 +6,9 @@ edition = "2024" [dependencies] anyhow.workspace = true bcs.workspace = true +bitcoin.workspace = true clap.workspace = true +hashi = { path = "../hashi" } hashi-types = { path = "../hashi-types" } hashi-guardian = { path = "../hashi-guardian" } hex.workspace = true @@ -16,9 +18,10 @@ rand.workspace = true serde.workspace = true serde_yaml.workspace = true tokio.workspace = true +toml.workspace = true tonic.workspace = true tracing.workspace = true [dev-dependencies] -bitcoin.workspace = true +fastcrypto.workspace = true hashi-guardian = { path = "../hashi-guardian", features = ["test-utils"] } diff --git a/crates/hashi-guardian-init/README.md b/crates/hashi-guardian-init/README.md index 067fa0a2cb..3ecb667de6 100644 --- a/crates/hashi-guardian-init/README.md +++ b/crates/hashi-guardian-init/README.md @@ -2,10 +2,10 @@ Off-enclave tooling that initializes a guardian. It reads the guardian's S3 logs via `hashi_guardian::s3_reader`, verifies the attested enclave, and emits the -artifacts that drive initialization. Today it houses the key-provisioner flow; -the operator init flow will move here too. +artifacts that drive initialization. It also houses guardian helper tooling and +dev-only shortcuts. -## provisioner-init +## provision A one-shot flow run by a key provisioner (IOP-225 checks A–E). It: @@ -27,7 +27,7 @@ A one-shot flow run by a key provisioner (IOP-225 checks A–E). It: ### Usage ```bash -cargo run -p hashi-guardian-init -- provisioner --config provisioner-init.sample.yaml +cargo run -p hashi-guardian-init -- provision --config provisioner-init.sample.yaml ``` ### Config @@ -39,3 +39,17 @@ secret-sharing instance and (post-genesis) the committee are scraped from S3, not configured. `hashi_committee_genesis` is needed only at genesis; omit it once a `committee-update/` log exists. Omit `relay_endpoint` for a dry run. + +## tools + +Guardian helper tooling lives under `tools`: + +```bash +cargo run -p hashi-guardian-init -- tools dev-bootstrap --config ... +cargo run -p hashi-guardian-init -- tools fetch-info --endpoint +cargo run -p hashi-guardian-init -- tools generate-master-key +``` + +`dev-bootstrap` is a centralized dev shortcut for driving a guardian through +bootstrap. `fetch-info` prints deployed guardian public keys. `generate-master-key` +creates the BTC master keypair used by the dev bootstrap flow. diff --git a/crates/hashi-guardian-init/provisioner-init.sample.yaml b/crates/hashi-guardian-init/provisioner-init.sample.yaml index 44b38972e3..6bae64585f 100644 --- a/crates/hashi-guardian-init/provisioner-init.sample.yaml +++ b/crates/hashi-guardian-init/provisioner-init.sample.yaml @@ -1,7 +1,7 @@ -# Sample configuration for the `provisioner-init` tool. +# Sample configuration for the `provision` command. # # Run with: -# cargo run -p hashi-guardian-init -- provisioner --config provisioner-init.sample.yaml +# cargo run -p hashi-guardian-init -- provision --config provisioner-init.sample.yaml # Key Provisioner's secret share (32-byte k256 scalar, hex-encoded). share: diff --git a/crates/internal-tools/src/bootstrap_guardian.rs b/crates/hashi-guardian-init/src/dev_bootstrap.rs similarity index 99% rename from crates/internal-tools/src/bootstrap_guardian.rs rename to crates/hashi-guardian-init/src/dev_bootstrap.rs index 3f43a0a2c3..5ae45aa8a5 100644 --- a/crates/internal-tools/src/bootstrap_guardian.rs +++ b/crates/hashi-guardian-init/src/dev_bootstrap.rs @@ -345,7 +345,7 @@ fn required_env(name: &str) -> Result { fn decode_mpc_master_g(mpc_public_key: &[u8]) -> Result { anyhow::ensure!( !mpc_public_key.is_empty(), - "on-chain mpc_public_key is empty (is DKG / end_reconfig complete?)" + "on-chain mpc_public_key is empty (DKG / end_reconfig may not have completed yet)" ); bcs::from_bytes(mpc_public_key).map_err(|e| anyhow!("decode on-chain MPC verifying key G: {e}")) } diff --git a/crates/internal-tools/src/fetch_guardian_info.rs b/crates/hashi-guardian-init/src/fetch_info.rs similarity index 100% rename from crates/internal-tools/src/fetch_guardian_info.rs rename to crates/hashi-guardian-init/src/fetch_info.rs diff --git a/crates/internal-tools/src/generate_master_key.rs b/crates/hashi-guardian-init/src/generate_master_key.rs similarity index 96% rename from crates/internal-tools/src/generate_master_key.rs rename to crates/hashi-guardian-init/src/generate_master_key.rs index da3d594383..49bc1a95bf 100644 --- a/crates/internal-tools/src/generate_master_key.rs +++ b/crates/hashi-guardian-init/src/generate_master_key.rs @@ -4,7 +4,7 @@ //! Generate a fresh BTC master keypair for the guardian. The pubkey is //! pinned on-chain at publish time via `hashi publish //! --guardian-btc-public-key`; the secret is fed back to -//! `bootstrap-guardian --master-secret-hex` after DKG so the shares it +//! `dev-bootstrap --master-secret-hex` after DKG so the shares it //! splits cover the *same* key already on chain. use anyhow::Result; diff --git a/crates/hashi-guardian-init/src/main.rs b/crates/hashi-guardian-init/src/main.rs index 8facace823..69d834dfcb 100644 --- a/crates/hashi-guardian-init/src/main.rs +++ b/crates/hashi-guardian-init/src/main.rs @@ -1,11 +1,17 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use anyhow::Context; use clap::Parser; use clap::Subcommand; +use hashi::config::Config; +use hashi::onchain::OnchainState; use std::path::PathBuf; mod config; +mod dev_bootstrap; +mod fetch_info; +mod generate_master_key; mod heartbeat_checks; mod limiter_recovery; mod provisioner; @@ -21,12 +27,52 @@ struct Cli { #[derive(Subcommand)] enum Command { /// Run a key provisioner's init checks against guardian S3 logs and emit its share. - Provisioner { + Provision { /// Path to provisioner-init YAML config file. #[arg(long)] config: PathBuf, }, - // Operator init lands here as a sibling subcommand. + /// Guardian helper tooling and dev-only shortcuts. + Tools { + #[command(subcommand)] + command: ToolsCommand, + }, +} + +#[derive(Subcommand)] +enum ToolsCommand { + /// Drive the current centralized dev guardian bootstrap shortcut. + DevBootstrap { + #[command(flatten)] + config: ConfigArgs, + #[command(flatten)] + args: dev_bootstrap::Args, + }, + /// Fetch deployed guardian public keys. + FetchInfo { + #[command(flatten)] + args: fetch_info::Args, + }, + /// Generate a fresh BTC master keypair for the dev bootstrap shortcut. + GenerateMasterKey { + #[command(flatten)] + args: generate_master_key::Args, + }, +} + +#[derive(Parser)] +struct ConfigArgs { + /// Path to a node config TOML file (provides sui-rpc and hashi-ids). + #[arg(long)] + config: PathBuf, +} + +impl ConfigArgs { + fn load(&self) -> anyhow::Result { + let s = std::fs::read_to_string(&self.config) + .with_context(|| format!("failed to read config: {}", self.config.display()))?; + toml::from_str(&s).context("failed to parse config TOML") + } } #[tokio::main] @@ -35,12 +81,30 @@ async fn main() -> anyhow::Result<()> { .with_target(false) .with_env() .init(); + hashi::init_crypto_provider(); match Cli::parse().command { - Command::Provisioner { config } => { + Command::Provision { config } => { let cfg = provisioner::ProvisionerConfig::load_yaml(&config)?; provisioner::run(cfg).await?; } + Command::Tools { command } => match command { + ToolsCommand::DevBootstrap { config, args } => { + let cfg = config.load()?; + let sui_rpc = cfg + .sui_rpc + .as_deref() + .ok_or_else(|| anyhow::anyhow!("config missing sui-rpc"))?; + println!("Connecting to Sui RPC: {sui_rpc}"); + let (onchain_state, _watcher) = + OnchainState::new(sui_rpc, cfg.hashi_ids(), None, None, None) + .await + .context("failed to connect to Sui RPC")?; + dev_bootstrap::run(args, &onchain_state).await?; + } + ToolsCommand::FetchInfo { args } => fetch_info::run(args).await?, + ToolsCommand::GenerateMasterKey { args } => generate_master_key::run(args)?, + }, } Ok(()) } diff --git a/crates/internal-tools/Cargo.toml b/crates/internal-tools/Cargo.toml index cb5cbdf0e7..feaed5c13d 100644 --- a/crates/internal-tools/Cargo.toml +++ b/crates/internal-tools/Cargo.toml @@ -12,8 +12,6 @@ fastcrypto.workspace = true fastcrypto-tbls.workspace = true sui-sdk-types.workspace = true -bitcoin.workspace = true - clap.workspace = true tokio.workspace = true anyhow.workspace = true @@ -22,8 +20,3 @@ tracing.workspace = true tracing-subscriber.workspace = true toml.workspace = true rand.workspace = true -bcs.workspace = true - -# bootstrap-guardian only -hpke.workspace = true -k256.workspace = true diff --git a/crates/internal-tools/src/main.rs b/crates/internal-tools/src/main.rs index c7ecb1fbff..4b40a3721e 100644 --- a/crates/internal-tools/src/main.rs +++ b/crates/internal-tools/src/main.rs @@ -12,9 +12,6 @@ use clap::Subcommand; use hashi::config::Config; use hashi::onchain::OnchainState; -mod bootstrap_guardian; -mod fetch_guardian_info; -mod generate_master_key; mod key_recovery; #[derive(Parser)] @@ -48,23 +45,6 @@ enum Commands { #[command(flatten)] args: key_recovery::Args, }, - BootstrapGuardian { - #[command(flatten)] - config: ConfigArgs, - #[command(flatten)] - args: bootstrap_guardian::Args, - }, - FetchGuardianInfo { - #[command(flatten)] - args: fetch_guardian_info::Args, - }, - /// Generate a fresh BTC master keypair for the guardian. Used by the - /// deploy workflow: pin the pubkey on-chain via `hashi publish`, then - /// hand the secret to `bootstrap-guardian --master-secret-hex`. - GenerateMasterKey { - #[command(flatten)] - args: generate_master_key::Args, - }, } #[tokio::main] @@ -92,20 +72,5 @@ async fn main() -> anyhow::Result<()> { .context("failed to connect to Sui RPC")?; key_recovery::run(args, &onchain_state, chain_id).await } - Commands::BootstrapGuardian { config, args } => { - let cfg = config.load()?; - let sui_rpc = cfg - .sui_rpc - .as_deref() - .ok_or_else(|| anyhow!("config missing sui-rpc"))?; - println!("Connecting to Sui RPC: {sui_rpc}"); - let (onchain_state, _watcher) = - OnchainState::new(sui_rpc, cfg.hashi_ids(), None, None, None) - .await - .context("failed to connect to Sui RPC")?; - bootstrap_guardian::run(args, &onchain_state).await - } - Commands::FetchGuardianInfo { args } => fetch_guardian_info::run(args).await, - Commands::GenerateMasterKey { args } => generate_master_key::run(args), } }