feat: BIP-39 master seed for unified key derivation
Some checks failed
Build Archipelago ISO (dev) / build-iso (push) Failing after 17m51s
Container Orchestration Tests / smoke-tests (push) Has been cancelled
Container Orchestration Tests / unit-tests (push) Has been cancelled

Replace fragmented random key generation with a single 24-word BIP-39
mnemonic that deterministically derives all node keys: Ed25519 (DID),
secp256k1 (Nostr/Bitcoin), BIP-84 xprv (Bitcoin Core), and LND aezeed
entropy. New onboarding flow: seed generate → word verification → identity
naming. Restore path enabled via 24-word entry. Includes seed RPC handlers,
mock backend support, LND/Bitcoin Core wallet-from-seed integration, and
UI polish across settings and discover views.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-31 01:41:24 +01:00
parent 3d50fb9888
commit a8292ab622
50 changed files with 2200 additions and 258 deletions

View File

@@ -64,6 +64,45 @@ impl NodeIdentity {
})
}
/// Create node identity from a BIP-39 master seed (deterministic derivation).
/// Writes derived key to disk in the same format as load_or_create.
pub async fn from_seed(identity_dir: &Path, seed: &crate::seed::MasterSeed) -> Result<Self> {
fs::create_dir_all(identity_dir)
.await
.context("Failed to create identity directory")?;
let signing_key = crate::seed::derive_node_ed25519(seed)?;
let key_path = identity_dir.join(NODE_KEY_FILE);
let pub_path = identity_dir.join(NODE_KEY_PUB_FILE);
fs::write(&key_path, signing_key.to_bytes())
.await
.context("Failed to write node key")?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(&key_path, std::fs::Permissions::from_mode(0o600))
.await
.context("Failed to set key permissions")?;
}
fs::write(&pub_path, signing_key.verifying_key().as_bytes())
.await
.context("Failed to write node public key")?;
let pubkey_hex = hex::encode(signing_key.verifying_key().as_bytes());
tracing::info!("Derived node identity from seed (pubkey: {}...)", &pubkey_hex[..16]);
Ok(Self {
signing_key,
_identity_dir: identity_dir.to_path_buf(),
})
}
/// Check if a node key already exists on disk.
pub fn key_exists(identity_dir: &Path) -> bool {
identity_dir.join(NODE_KEY_FILE).exists()
}
/// Access the signing key (for key derivation, e.g. mesh encryption).
pub fn signing_key(&self) -> &SigningKey {
&self.signing_key
@@ -115,6 +154,11 @@ impl NodeIdentity {
.map_err(|e| anyhow::anyhow!("Invalid pubkey hex: {}", e))
}
/// Generate a W3C DID Document for this identity.
#[allow(dead_code)]
pub fn did_document(&self) -> Result<serde_json::Value> {
did_document_from_pubkey_hex(&self.pubkey_hex())
}
}
/// Convert Ed25519 pubkey (hex) to did:key format.