From b352304b715ba87c427c72b3c86602557979fc38 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Tue, 3 Feb 2026 16:50:41 +0100 Subject: [PATCH] gl-cli: add signer generate-secret command With the command "glcli signer generate-secret" the user can generate a hsm_secret file from a mnemonic sentence and passphrase using BIP39. Signed-off-by: Lagrang3 --- docs/src/getting-started/register.md | 6 ++++ libs/gl-cli/Cargo.toml | 1 + libs/gl-cli/src/signer.rs | 49 ++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/docs/src/getting-started/register.md b/docs/src/getting-started/register.md index b05bfbba8..347cca50d 100644 --- a/docs/src/getting-started/register.md +++ b/docs/src/getting-started/register.md @@ -59,6 +59,12 @@ phrase and then convert it into a seed secret we can use: --8<-- "getting_started.py:create_seed" ``` +=== "Bash" + ```bash + # For example gl-cli can be used to create an hsm_secret file + gl-cli signer generate-secret + ``` + !!! important Remember to store the seed somewhere (file on disk, registry, etc) because without it, you will not have access to the node, and any diff --git a/libs/gl-cli/Cargo.toml b/libs/gl-cli/Cargo.toml index 493ec2333..4090f2749 100644 --- a/libs/gl-cli/Cargo.toml +++ b/libs/gl-cli/Cargo.toml @@ -18,6 +18,7 @@ test = true doc = true [dependencies] +bip39 = { version = "2.2", features = ["rand"] } clap = { version = "4.5", features = ["derive"] } dirs = "6.0" env_logger = "0.11" diff --git a/libs/gl-cli/src/signer.rs b/libs/gl-cli/src/signer.rs index d103ec33f..345d5ecdd 100644 --- a/libs/gl-cli/src/signer.rs +++ b/libs/gl-cli/src/signer.rs @@ -1,9 +1,12 @@ use crate::error::{Error, Result}; use crate::util; +use bip39::Mnemonic; use clap::Subcommand; use core::fmt::Debug; use gl_client::signer::Signer; use lightning_signer::bitcoin::Network; +use std::fs::File; +use std::io::Write; use std::path::Path; use tokio::{join, signal}; use util::{CREDENTIALS_FILE_NAME, SEED_FILE_NAME}; @@ -19,15 +22,61 @@ pub enum Command { Run, /// Prints the version of the signer used Version, + /// Generate a new hsm_secret file from a 12 word seed phrase + GenerateSecret { + #[arg(long)] + mnemonic: Option, + #[arg(long)] + passphrase: Option, + }, } pub async fn command_handler>(cmd: Command, config: Config

) -> Result<()> { match cmd { Command::Run => run_handler(config).await, Command::Version => version(config).await, + Command::GenerateSecret { + mnemonic, + passphrase, + } => generate_secret(config, mnemonic, passphrase).await, } } +async fn generate_secret>( + config: Config

, + mnemonic: Option, + passphrase: Option, +) -> Result<()> { + // Check if we can find a seed file, refuse to generate a new one if exists + let seed_path = config.data_dir.as_ref().join(SEED_FILE_NAME); + let seed = util::read_seed(&seed_path); + if seed.is_some() { + return Err(Error::custom(format!( + "Seed already exists: {}", + seed_path.display() + ))); + } + let mnemonic = match mnemonic { + Some(sentence) => { + Mnemonic::parse(sentence).map_err(|e| Error::custom(format!("Bad mnemonic: {e}")))? + } + None => Mnemonic::generate(12) + .map_err(|e| Error::custom(format!("Failed to generate mnemonic: {e}")))?, + }; + let passphrase = passphrase.unwrap_or(String::new()); + println!( + "Mnemonic sentence: \"{}\"", + mnemonic.words().collect::>().join(" ") + ); + println!("Passphrase: \"{passphrase}\"",); + let seed = &mnemonic.to_seed(passphrase)[0..32]; + let mut file = File::create(seed_path) + .map_err(|e| Error::custom(format!("Failed to create seed: {e}")))?; + file.write_all(seed) + .map_err(|e| Error::custom(format!("Failed to write seed to file: {e}")))?; + Ok(()) +} + async fn run_handler>(config: Config

) -> Result<()> { // Check if we can find a seed file, if we can not find one, we need to register first. let seed_path = config.data_dir.as_ref().join(SEED_FILE_NAME);