From 31aa473801ffce215082ef607d5d76ec96918da8 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Mon, 16 Mar 2026 12:50:15 +0000 Subject: [PATCH 1/4] feat: add miden-genesis tool for canonical genesis state (#1788) New binary crate that generates canonical AggLayer genesis accounts (bridge, bridge admin, GER manager) and a genesis.toml config file. Only the bridge account is included in the genesis block; bridge admin and GER manager are generated as local accounts to be deployed later. Co-Authored-By: Claude Opus 4.6 (1M context) --- Cargo.lock | 15 +++ Cargo.toml | 1 + bin/genesis/Cargo.toml | 26 ++++++ bin/genesis/src/main.rs | 199 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 241 insertions(+) create mode 100644 bin/genesis/Cargo.toml create mode 100644 bin/genesis/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 16ea17404..934e65a2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2871,6 +2871,21 @@ dependencies = [ "unicode-width 0.1.14", ] +[[package]] +name = "miden-genesis" +version = "0.14.0-alpha.5" +dependencies = [ + "anyhow", + "clap", + "fs-err", + "hex", + "miden-agglayer", + "miden-protocol", + "miden-standards", + "rand", + "rand_chacha", +] + [[package]] name = "miden-large-smt-backend-rocksdb" version = "0.14.0-alpha.5" diff --git a/Cargo.toml b/Cargo.toml index 2156ddf8f..e9afffca1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "bin/genesis", "bin/network-monitor", "bin/node", "bin/remote-prover", diff --git a/bin/genesis/Cargo.toml b/bin/genesis/Cargo.toml new file mode 100644 index 000000000..85de079eb --- /dev/null +++ b/bin/genesis/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "miden-genesis" +publish = false + +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +readme.workspace = true +repository.workspace = true +rust-version.workspace = true +version.workspace = true + +[lints] +workspace = true + +[dependencies] +anyhow = { workspace = true } +clap = { workspace = true } +fs-err = { workspace = true } +hex = { workspace = true } +miden-agglayer = { version = "=0.14.0-alpha.1" } +miden-protocol = { features = ["std"], workspace = true } +miden-standards = { workspace = true } +rand = { workspace = true } +rand_chacha = { workspace = true } diff --git a/bin/genesis/src/main.rs b/bin/genesis/src/main.rs new file mode 100644 index 000000000..872ac04c2 --- /dev/null +++ b/bin/genesis/src/main.rs @@ -0,0 +1,199 @@ +use std::path::PathBuf; +use std::sync::Arc; +use std::time::{SystemTime, UNIX_EPOCH}; + +use anyhow::Context; +use clap::Parser; +use miden_agglayer::create_bridge_account; +use miden_protocol::account::auth::{AuthScheme, AuthSecretKey}; +use miden_protocol::account::delta::{AccountStorageDelta, AccountVaultDelta}; +use miden_protocol::account::{ + Account, + AccountCode, + AccountDelta, + AccountFile, + AccountStorageMode, + AccountType, +}; +use miden_protocol::crypto::dsa::falcon512_rpo::{self, SecretKey as RpoSecretKey}; +use miden_protocol::crypto::rand::RpoRandomCoin; +use miden_protocol::utils::Deserializable; +use miden_protocol::{Felt, ONE, Word}; +use miden_standards::AuthMethod; +use miden_standards::account::wallets::create_basic_wallet; +use rand::Rng; +use rand_chacha::ChaCha20Rng; +use rand_chacha::rand_core::SeedableRng; + +/// Generate canonical Miden genesis accounts (bridge, bridge admin, GER manager) +/// and a genesis.toml configuration file. +#[derive(Parser)] +#[command(name = "miden-genesis")] +struct Cli { + /// Output directory for generated files. + #[arg(long, default_value = "./genesis")] + output_dir: PathBuf, + + /// Hex-encoded Falcon512 public key for the bridge admin account. + /// If omitted, a new keypair is generated and the secret key is included in the .mac file. + #[arg(long, value_name = "HEX")] + bridge_admin_public_key: Option, + + /// Hex-encoded Falcon512 public key for the GER manager account. + /// If omitted, a new keypair is generated and the secret key is included in the .mac file. + #[arg(long, value_name = "HEX")] + ger_manager_public_key: Option, +} + +fn main() -> anyhow::Result<()> { + let cli = Cli::parse(); + + fs_err::create_dir_all(&cli.output_dir).context("failed to create output directory")?; + + // Generate or parse bridge admin key. + let (bridge_admin_pub, bridge_admin_secret) = + resolve_falcon_key(cli.bridge_admin_public_key.as_deref(), "bridge admin")?; + + // Generate or parse GER manager key. + let (ger_manager_pub, ger_manager_secret) = + resolve_falcon_key(cli.ger_manager_public_key.as_deref(), "GER manager")?; + + // Create bridge admin wallet (nonce=0, local account to be deployed later). + let bridge_admin = create_basic_wallet( + rand::random(), + AuthMethod::SingleSig { + approver: (bridge_admin_pub.into(), AuthScheme::Falcon512Rpo), + }, + AccountType::RegularAccountImmutableCode, + AccountStorageMode::Public, + ) + .context("failed to create bridge admin account")?; + let bridge_admin = strip_code_decorators(bridge_admin); + let bridge_admin_id = bridge_admin.id(); + + // Create GER manager wallet (nonce=0, local account to be deployed later). + let ger_manager = create_basic_wallet( + rand::random(), + AuthMethod::SingleSig { + approver: (ger_manager_pub.into(), AuthScheme::Falcon512Rpo), + }, + AccountType::RegularAccountImmutableCode, + AccountStorageMode::Public, + ) + .context("failed to create GER manager account")?; + let ger_manager = strip_code_decorators(ger_manager); + let ger_manager_id = ger_manager.id(); + + // Create bridge account (NoAuth, nonce=0), then bump nonce to 1 for genesis. + let mut rng = ChaCha20Rng::from_seed(rand::random()); + let bridge_seed: [u64; 4] = rng.random(); + let bridge_seed = Word::from(bridge_seed.map(Felt::new)); + let bridge = create_bridge_account(bridge_seed, bridge_admin_id, ger_manager_id); + let bridge = strip_code_decorators(bridge); + + // Bump bridge nonce to 1 (required for genesis accounts). + // File-loaded accounts via [[account]] in genesis.toml are included as-is, + // so we must set nonce=1 before writing the .mac file. + let bridge = bump_nonce_to_one(bridge).context("failed to bump bridge account nonce")?; + + // Write .mac files. + let bridge_admin_secrets = bridge_admin_secret + .map(|sk| vec![AuthSecretKey::Falcon512Rpo(sk)]) + .unwrap_or_default(); + AccountFile::new(bridge_admin, bridge_admin_secrets) + .write(cli.output_dir.join("bridge_admin.mac")) + .context("failed to write bridge_admin.mac")?; + + let ger_manager_secrets = ger_manager_secret + .map(|sk| vec![AuthSecretKey::Falcon512Rpo(sk)]) + .unwrap_or_default(); + AccountFile::new(ger_manager, ger_manager_secrets) + .write(cli.output_dir.join("ger_manager.mac")) + .context("failed to write ger_manager.mac")?; + + let bridge_id = bridge.id(); + AccountFile::new(bridge, vec![]) + .write(cli.output_dir.join("bridge.mac")) + .context("failed to write bridge.mac")?; + + // Write genesis.toml. + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system time before UNIX epoch") + .as_secs(); + + let genesis_toml = format!( + r#"version = 1 +timestamp = {timestamp} + +[fee_parameters] +verification_base_fee = 0 + +[[account]] +path = "bridge.mac" +"#, + ); + + fs_err::write(cli.output_dir.join("genesis.toml"), genesis_toml) + .context("failed to write genesis.toml")?; + + println!("Genesis files written to {}", cli.output_dir.display()); + println!(" bridge_admin.mac (id: {})", bridge_admin_id.to_hex()); + println!(" ger_manager.mac (id: {})", ger_manager_id.to_hex()); + println!(" bridge.mac (id: {})", bridge_id.to_hex()); + println!(" genesis.toml"); + + Ok(()) +} + +/// Resolves a Falcon512 key pair: either parses the provided hex public key or generates a new +/// keypair. +fn resolve_falcon_key( + hex_pubkey: Option<&str>, + label: &str, +) -> anyhow::Result<(falcon512_rpo::PublicKey, Option)> { + if let Some(hex_str) = hex_pubkey { + let bytes = + hex::decode(hex_str).with_context(|| format!("invalid hex for {label} public key"))?; + let pubkey = falcon512_rpo::PublicKey::read_from_bytes(&bytes) + .with_context(|| format!("failed to deserialize {label} public key"))?; + Ok((pubkey, None)) + } else { + let mut rng = ChaCha20Rng::from_seed(rand::random()); + let auth_seed: [u64; 4] = rng.random(); + let mut coin = RpoRandomCoin::new(Word::from(auth_seed.map(Felt::new))); + let secret_key = RpoSecretKey::with_rng(&mut coin); + let public_key = secret_key.public_key(); + Ok((public_key, Some(secret_key))) + } +} + +/// Bumps an account's nonce from 0 to 1 using an `AccountDelta`. +/// +/// Genesis accounts loaded via `[[account]]` in genesis.toml are included as-is (no automatic +/// nonce bump). By convention, nonce=0 means "not yet deployed" and genesis accounts must have +/// nonce>=1. +fn bump_nonce_to_one(mut account: Account) -> anyhow::Result { + let delta = AccountDelta::new( + account.id(), + AccountStorageDelta::default(), + AccountVaultDelta::default(), + ONE, + )?; + account.apply_delta(&delta)?; + debug_assert_eq!(account.nonce(), ONE); + Ok(account) +} + +/// Strips source location decorators from an account's code MAST forest. +/// +/// This ensures serialized .mac files are deterministic regardless of build path. +fn strip_code_decorators(account: Account) -> Account { + let (id, vault, storage, code, nonce, seed) = account.into_parts(); + + let mut mast = code.mast(); + Arc::make_mut(&mut mast).strip_decorators(); + let code = AccountCode::from_parts(mast, code.procedures().to_vec()); + + Account::new_unchecked(id, vault, storage, code, nonce, seed) +} From f470b6b22adfa6aa190872138cde716dbe52e1e0 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Mon, 16 Mar 2026 13:08:04 +0000 Subject: [PATCH 2/4] chore(genesis): add missing Cargo.toml metadata fields Add description, keywords, and exclude to match other bin/ crate conventions. Co-Authored-By: Claude Opus 4.6 (1M context) --- bin/genesis/Cargo.toml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bin/genesis/Cargo.toml b/bin/genesis/Cargo.toml index 85de079eb..6821fb700 100644 --- a/bin/genesis/Cargo.toml +++ b/bin/genesis/Cargo.toml @@ -1,11 +1,13 @@ [package] -name = "miden-genesis" -publish = false - authors.workspace = true +description = "A tool for generating canonical Miden genesis accounts and configuration" edition.workspace = true +exclude.workspace = true homepage.workspace = true +keywords = ["miden", "genesis"] license.workspace = true +name = "miden-genesis" +publish = false readme.workspace = true repository.workspace = true rust-version.workspace = true From a429be7773fb932df5cf326524c84ba26d9cc4cb Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Mon, 16 Mar 2026 13:14:25 +0000 Subject: [PATCH 3/4] chore(genesis): fix toml formatting Co-Authored-By: Claude Opus 4.6 (1M context) --- bin/genesis/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/genesis/Cargo.toml b/bin/genesis/Cargo.toml index 6821fb700..a9ef0a387 100644 --- a/bin/genesis/Cargo.toml +++ b/bin/genesis/Cargo.toml @@ -4,7 +4,7 @@ description = "A tool for generating canonical Miden genesis accounts edition.workspace = true exclude.workspace = true homepage.workspace = true -keywords = ["miden", "genesis"] +keywords = ["genesis", "miden"] license.workspace = true name = "miden-genesis" publish = false From 4c21a29f4164bad8129954dc095865b5498bef7a Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Mon, 16 Mar 2026 13:14:37 +0000 Subject: [PATCH 4/4] chore: add changelog entry for miden-genesis tool Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 496312b0c..b03f3de59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Enhancements +- Added `miden-genesis` tool for generating canonical AggLayer genesis accounts and configuration ([#1797](https://github.com/0xMiden/node/pull/1797)). - Expose per-tree RocksDB tuning options ([#1782](https://github.com/0xMiden/node/pull/1782)). - Added verbose `info!`-level logging to the network transaction builder for transaction execution, note filtering failures, and transaction outcomes ([#1770](https://github.com/0xMiden/node/pull/1770)). - [BREAKING] Move block proving from Blocker Producer to the Store ([#1579](https://github.com/0xMiden/node/pull/1579)).