Skip to content
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
7 changes: 3 additions & 4 deletions Cargo.lock

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

5 changes: 4 additions & 1 deletion crates/hashi-guardian-init/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"] }
22 changes: 18 additions & 4 deletions crates/hashi-guardian-init/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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
Expand All @@ -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 <node-config.toml> ...
cargo run -p hashi-guardian-init -- tools fetch-info --endpoint <guardian-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.
4 changes: 2 additions & 2 deletions crates/hashi-guardian-init/provisioner-init.sample.yaml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ fn required_env(name: &str) -> Result<String> {
fn decode_mpc_master_g(mpc_public_key: &[u8]) -> Result<HashiMasterG> {
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}"))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
70 changes: 67 additions & 3 deletions crates/hashi-guardian-init/src/main.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<Config> {
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]
Expand All @@ -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(())
}
7 changes: 0 additions & 7 deletions crates/internal-tools/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
35 changes: 0 additions & 35 deletions crates/internal-tools/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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),
}
}
Loading