From 45fa8b6c6fae0aa82b392e3c4a5a645f2c58aac3 Mon Sep 17 00:00:00 2001 From: archipelago Date: Thu, 6 Aug 2026 08:56:34 -0400 Subject: [PATCH] feat(neode-ui): seed & entropy explainer page at /entropy/ + link from Backup settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Standalone static guide (same pattern as /architecture/) covering how the master seed entropy is drawn (explicit OsRng, sealed KeyGenRng allowlist, degenerate-draw refusal, CSPRNG readiness ledger), how it is stored (Argon2 + ChaCha20-Poly1305 envelope), the full derivation tree (HKDF labels, NIP-06, LND aezeed one-way gate, second-order keys), what is NOT seed-derived, every failure/fallback path, and the restore flow — in paired layman/technical language. Linked from the Recovery-phrase card in Settings → Backup. CSP-safe: no inline scripts. Co-Authored-By: Claude Fable 5 --- neode-ui/public/entropy/index.html | 1019 +++++++++++++++++ neode-ui/src/views/settings/BackupSection.vue | 9 + 2 files changed, 1028 insertions(+) create mode 100644 neode-ui/public/entropy/index.html diff --git a/neode-ui/public/entropy/index.html b/neode-ui/public/entropy/index.html new file mode 100644 index 00000000..d003abf0 --- /dev/null +++ b/neode-ui/public/entropy/index.html @@ -0,0 +1,1019 @@ + + + + + +Archipelago — Seed & Entropy Guide + + + + + + +
+ +
+

Your Node's Seed & Entropy

+

How 32 random bytes become every key your node owns — where that randomness comes from, what protects it, and exactly what your 24 words can (and cannot) bring back.

+
+ 256-bit entropy + BIP-39 · 24 words + HKDF-SHA256 derivation + Kernel CSPRNG only + KEY-05 hardened +
+
+ +

Introduction

+

One master secret, many keys — by design.

+ +

+Almost everything cryptographic on this node — its identity, its Nostr keys, its mesh +transport keys, its Lightning wallet — grows from a single master seed: +32 bytes (256 bits) of randomness drawn once, shown to you once as a 24-word recovery +phrase, and never stored in raw form anywhere. +

+ +
+ The tree and the acorn + Think of the seed as an acorn. Every branch of the tree — your identity, your wallet, + your mesh radio's name — grows from it in a fixed, repeatable pattern. Plant the same + acorn on new hardware (by typing your 24 words) and the same tree grows back, + branch for branch. That's why those words are the single most valuable thing your node + ever shows you — and why anyone who copies them owns your tree. +
+ +

+This page explains, in both plain and technical language: where those 32 bytes come from, +the guardrails that make sure they're genuinely random, how they're protected on disk, +everything that is derived from them, everything that deliberately isn't, and +what happens in every failure and recovery scenario. +

+ +

The Big Picture

+

From kernel randomness to every key on the node.

+ +
+Linux kernel CSPRNG (hardware noise: interrupts, timing jitter, CPU RNG) + │ + │ getrandom(2) — via an explicitly named OsRng, nothing else allowed + ▼ +32 bytes of raw entropy ──▶ degenerate-draw check ──▶ refuse & wipe if suspicious + │ + │ BIP-39 encoding + ▼ +24-word recovery phrase ←── the only form you ever see or back up + │ + │ PBKDF2-HMAC-SHA512 × 2048 (empty passphrase) + ▼ +64-byte master seed ←── lives only in RAM, never written to disk + │ + ├── HKDF "archipelago/node/ed25519/v1" ──▶ Node identity key + DID + ├── HKDF "archipelago/nostr-node/…/v1" ──▶ Node Nostr key (npub) + ├── HKDF "archipelago/fips/secp256k1/v1" ──▶ FIPS mesh transport key + ├── HKDF "archipelago/identity/{i}/…/v1" ──▶ Personal identities (Ed25519) + ├── BIP-32 m/44'/1237'/0'/0/{i} (NIP-06) ──▶ Personal Nostr keys + ├── HKDF "archipelago/lnd/entropy/v1" ──▶ Lightning wallet entropy → aezeed + └── BIP-32 m/84'/0'/0' ──▶ Bitcoin xprv (dormant, reserved) + │ + │ and from the node identity key, second-order: + ▼ + ├── Reticulum / RNS mesh identity (HKDF, salt "archipelago-reticulum-identity-v1") + ├── Message-store encryption key (SHA-256 domain-separated) + ├── Mesh-contacts encryption key (SHA-256 domain-separated) + └── Credential-store key (SHA-256 domain-separated) +
+ +
+ One rule to remember + If it's on the diagram above, your 24 words can rebuild it from scratch, on any hardware, + forever. If it's not on the diagram (session tokens, app passwords, WireGuard keys, + the Lightning channel state…), it's independent randomness — protected by other backups, + not by the words. +
+ +

Where Randomness Comes From

+

One named source. No mixing. No silent defaults.

+ +
+ Dice you can audit + Computers can't invent randomness — they collect it. The Linux kernel constantly harvests + unpredictable physical noise (the exact nanosecond your network card interrupts, timing + jitter between CPU cores, the CPU's built-in hardware random generator) and distils it + into a cryptographic random pool. Archipelago rolls its dice by asking that pool + directly — and only that pool. There is deliberately no "mixing" of other + sources, because a single, named, well-studied source is auditable; a blend of + sources is a place for bugs to hide. +
+ +

Technically

+

+The master seed is generated by MasterSeed::generate() in +core/archipelago/src/seed.rs. It fills a 32-byte buffer using +rand::rngs::OsRng — a thin wrapper around the +getrandom(2) system call, which reads the kernel's CSPRNG +(the same source as /dev/urandom, but immune to file-descriptor +exhaustion and chroot tricks). +

+ +
    +
  • Exactly 32 bytes / 256 bits — the maximum BIP-39 strength, encoding to 24 words.
  • +
  • The RNG is named at the call site. No function anywhere in the codebase generates key material with a "default" or implicit RNG anymore (see Recent Hardening for why this is stated so emphatically).
  • +
  • The RNG type is compiler-enforced. Key-generation functions only accept RNGs on a sealed allowlist (KeyGenRng in core/archipelago/src/entropy.rs). The list has exactly one production member: OsRng. No other module — not even a future refactor — can add a weaker RNG without editing the allowlist file itself.
  • +
  • The buffer is zeroized (securely wiped from memory) on every path, success or failure.
  • +
+ +
// core/archipelago/src/seed.rs — the actual draw
+let mut entropy = [0u8; 32];
+crate::entropy::draw_key_bytes(&mut rand::rngs::OsRng, &mut entropy)?;  // guarded draw
+let mnemonic = bip39::Mnemonic::from_entropy(&entropy)?;               // → 24 words
+entropy.zeroize();                                                      // wipe the raw bytes
+ +

+The 24 words are then stretched into the 64-byte master seed using standard BIP-39: +PBKDF2-HMAC-SHA512, 2048 rounds, empty passphrase. (There is no "25th word" +passphrase — your login password protects the stored backup instead, see +The Encrypted Envelope.) That 64-byte seed exists +only in memory, is re-computed from the words whenever needed, and is +never written to disk in any form. +

+ +

When the Seed Is Born

+

At onboarding — not at first boot.

+ +
+
+

First boot: a placeholder key

+

A freshly-flashed node boots with a random temporary identity key so services can start. This key is NOT seed-derived and is about to be thrown away.

+
+
+

Onboarding: the real draw

+

When you reach the "Recovery phrase" step of setup, the seed.generate RPC performs the guarded 32-byte draw described above and shows you the 24 words — the only time they're ever displayed unprompted.

+
+
+

Derivation: keys are materialised

+

The node identity key, DID, Nostr key, FIPS mesh key, and your first personal identity are all derived from the seed and written to /var/lib/archipelago/identity/ (each file mode 0600). The placeholder key from step 1 is overwritten.

+
+
+

Password setup: the backup is sealed

+

When you set your login password, the 24 words are encrypted under it (Argon2 + ChaCha20-Poly1305) and stored as master_seed.enc — so you can re-reveal them later from Settings → Backup.

+
+
+ +
+ Refresh-proof by design + If the onboarding page is retried within 10 minutes (browser refresh, flaky connection), + the node returns the same words instead of minting a second seed. Generation is + serialised behind a lock — there is no window where two competing seeds can exist. +
+ +

The Five Guardrails

+

Defence in depth around a single random draw.

+ +
+
+

1  Sealed RNG allowlist

+

Key draws only compile against RNG types on a closed, private allowlist. Production allowlist: OsRng. Full stop. A refactor that swaps in a weak or deterministic RNG becomes a compile error, not a silent disaster.

+
+
+

2  Degenerate-draw refusal

+

Every draw is inspected for three tell-tale broken-RNG shapes: all zeros, all bytes identical, or a counting pattern. A match is refused and wiped — never retried, because retrying would mask a broken RNG instead of exposing it.

+
+
+

3  CSPRNG readiness ledger

+

Before generating the seed, the node probes whether the kernel's random pool is fully initialised, and appends the verdict to a tamper-evident, append-only log at security/csprng-readiness.jsonl (0600). You can audit, forever, the entropy conditions your seed was born under.

+
+
+

4  Build-time lint bans

+

The CI lint config bans rand::random() and rand::thread_rng() outright across the workspace — the two "convenient" RNG entry points that caused real-world wallet disasters elsewhere. Using either fails the build.

+
+
+

5  Zeroization everywhere

+

Raw entropy, mnemonics, and derived secrets are wiped from memory on every code path — including error paths — so key material doesn't linger in freed RAM or end up in crash dumps.

+
+
+ +
+ Why so paranoid about one function? + In 2026 a well-known hardware wallet shipped a bug where a code refactor quietly switched + seed generation to a predictable random source — with no error, no warning, and + seeds that looked perfectly normal. Predictable randomness is invisible: the words look + random, the wallet works, and months later someone who can predict the generator drains + it. Archipelago's answer is to make that entire class of bug impossible to compile, + and to log the health of the random pool at the exact moment your seed was created. +
+ +

What the degenerate check does — and doesn't — do

+

+The check (is_degenerate in entropy.rs) is deliberately +closed-form: it recognises exactly three catastrophic failure shapes +(all-zero, all-identical, ±1 counter). It is not a statistical entropy +estimator — those can't actually distinguish good randomness from a cleverly broken RNG, +and they introduce false positives. The real security load is carried by guardrails 1, +3 and 4; the degenerate check is a tripwire for total RNG failure (e.g. a zeroed buffer +that was never filled). +

+ +

Recent Hardening (the KEY-05 work)

+

This system was audited and rebuilt in early August 2026.

+ +

+Triggered by the COLDCARD-class of entropy defects, a full entropy & seed-generation +audit (docs/security/ENTROPY-SEED-AUDIT-2026-07-31.md) reviewed every random +draw in the codebase. The headline finding: the library used for mnemonic generation was +silently choosing its own RNG via a transitive default. It happened to be a secure one — +but nothing guaranteed that, and a dependency update could have changed it +without any diff in Archipelago's own code. +

+ + + + + + + + + + +
DateChange
Jul 30Kernel CSPRNG readiness probe added; RNG non-determinism regression test (64 consecutive mnemonics must all be unique).
Jul 31Full entropy audit published (findings F-01…F-13).
Aug 1The pivotal fix: master-seed RNG made explicit — OsRng named at the call site, injected through a testable seam, pinned by a known-answer test.
Aug 2Audit widened: 43 defaulted-RNG call sites across 15 files found and migrated to explicit OsRng — including AEAD nonces and ecash key material.
Aug 2Onboarding RPCs gated: seed.restore and friends now refuse on an already-provisioned node (previously an unauthenticated restore call could hijack a live node — fixed before any release shipped it).
Aug 2KEY-05 enforcement layer landed: sealed KeyGenRng allowlist, guarded draws, durable readiness ledger, clippy bans, supply-chain version pinning on the rand crate.
Aug 2Legacy Bitcoin Core wallet-import path deleted — the master xprv is no longer handed to any external wallet process.
+ +

What's Kept on Disk

+

The words, encrypted — and the keys, materialised. Never the raw seed.

+ + + + + + + + + + + +
FileContentsProtection
identity/master_seed.encYour 24 words (as text), encryptedArgon2(login password) + ChaCha20-Poly1305, mode 0600
identity/node_key / .pubNode Ed25519 identity key0600, seed-derived (recoverable from words)
identity/nostr_secret / nostr_pubkeyNode Nostr keypair0600, seed-derived
identity/fips_key / .pubFIPS mesh transport key (bech32 nsec)0600, seed-derived
identity/identity_indexNext unused identity derivation indexPlain integer (not secret)
identities/<uuid>.jsonPersonal identity records (keys + metadata)0600; keys seed-derived, metadata is not
identity/lnd_aezeed.encLightning wallet's own seed (see below)Encrypted under the LND wallet password
security/csprng-readiness.jsonlAppend-only entropy audit trail0600; deliberately outside identity/ so restores never touch it
+ +
+ The raw seed never touches disk + What's stored is the encrypted words and the derived keys. The 64-byte + master seed itself is recomputed in RAM from the words when needed (e.g. during restore + or identity creation) and wiped afterwards. +
+ +

The Encrypted Envelope

+

How master_seed.enc is built.

+ +
+your login password ──▶ Argon2id (memory-hard KDF) ──▶ 256-bit file key + ▲ + 16-byte random salt + +24 words ──▶ ChaCha20-Poly1305 (authenticated encryption, 12-byte random nonce) + │ + ▼ + ┌───────────┬────────────┬──────────────────────────────┐ + │ salt (16) │ nonce (12) │ ciphertext + auth tag │ = master_seed.enc + └───────────┴────────────┴──────────────────────────────┘ +
+ +
+ A safe inside a safe + Your words are locked in a digital safe whose combination is your login password — + but run through a deliberately slow, memory-hungry grinder (Argon2) so that guessing + billions of passwords per second is physically impractical, even for someone who steals + the file. The "auth tag" means the safe also notices if anyone has tampered with its + contents: a corrupted or modified file fails loudly rather than yielding wrong words. +
+ +

+Revealing the words later (Settings → Backup → Reveal) requires an authenticated +session plus re-entering your password plus your 2FA code if enabled, +is rate-limited, and the words are returned only to your browser — never written to logs. +

+ +

The Derivation Tree

+

Every key, its exact derivation, and where it lands.

+ +
+ One password, many doors — without reuse + The node never uses the master seed directly as a key. Instead it uses HKDF — think of it + as a locksmith who, given one master blank and a label ("node key", "mesh key", + "Lightning entropy"…), cuts a completely different, unrelated key for each label. Knowing + one cut key tells you nothing about the others or about the blank. The labels are fixed + strings baked into the code, which is what makes regrowing the identical tree on new + hardware possible. +
+ + + + + + + + + + + +
KeyDerivationLabel / pathMaterialised at
Node identity (Ed25519) — signs everything, forms your DIDHKDF-SHA256archipelago/node/ed25519/v1identity/node_key
Node Nostr key (secp256k1) — the node's npubHKDF-SHA256archipelago/nostr-node/secp256k1/v1identity/nostr_secret
FIPS mesh transport key — federation/mesh overlay identityHKDF-SHA256archipelago/fips/secp256k1/v1identity/fips_key
Personal identity #i (Ed25519)HKDF-SHA256archipelago/identity/{i}/ed25519/v1identities/<uuid>.json
Personal Nostr key #i — standard NIP-06, importable into other Nostr appsBIP-32m/44'/1237'/0'/0/{i}identities/<uuid>.json
Lightning (LND) wallet entropy — 16 bytesHKDF-SHA256archipelago/lnd/entropy/v1fed into LND at wallet init (see below)
Bitcoin BIP-84 account xprv — native-segwitBIP-32m/84'/0'/0'nowhere — dormant, reserved for a future on-node cold vault
Release-root signing key — fleet update signingHKDF-SHA256archipelago/release/root/ed25519/v1never on a node — derived offline by the publisher from a separate release mnemonic; nodes only pin the public key
+ +

+All HKDF derivations are HKDF-SHA256 with a distinct, versioned info-label (the +/v1 suffix means a future algorithm migration can introduce +/v2 labels without ambiguity). Personal Nostr keys intentionally use the +NIP-06 standard path instead of HKDF so that the same 24 words typed into any +NIP-06-compliant Nostr client reproduce the same npub — your social identity is +portable beyond Archipelago. +

+ +

The Lightning Special Case

+

The one branch of the tree with a one-way gate in it.

+ +
+master seed ──HKDF──▶ 16 bytes of entropy ──▶ LND generates its own "aezeed" ──▶ Lightning wallet + │ + │ ⚠ one-way: the aezeed cannot be + │ recomputed from your 24 words later + ▼ + captured ONCE at wallet init and stored as + identity/lnd_aezeed.enc (encrypted) +
+ +

+LND (the Lightning node) uses its own seed format called aezeed, which is not +BIP-39. Archipelago derives deterministic entropy from your master seed and hands it to +LND at wallet-creation time — but LND then wraps it with its own internal random salt, +so the resulting aezeed cannot be re-derived from your 24 words afterwards. +The node therefore captures the aezeed exactly once, at init, and stores it encrypted +alongside your other identity files. The Lightning seed backup screen shows it to you +with the same tap-to-reveal flow as the main phrase. +

+ +
+ Back up the Lightning seed separately + Your 24 words alone restore your node identity and on-chain derivations — but + not an already-initialised Lightning wallet, and never the off-chain channel + balances (those additionally need channel backups, which Lightning requires by design). + Treat the LND aezeed shown in the Lightning app's backup screen as a second phrase + worth writing down. Note it restores into LND-based wallets (Zeus, Blixt, another + Archipelago node) — hardware wallets can't import it. +
+ +

Second-Order Keys

+

Derived from the node key — so still fully recoverable from the words.

+ +

+Several subsystems derive their keys from the node identity key rather than the +master seed directly. Since the node key is itself seed-derived, these all regrow from +your 24 words too — the chain is words → master seed → node key → subsystem key. +

+ + + + + + + +
SubsystemDerivation from node_key
Reticulum / RNS mesh identity (LoRa long-range mesh)HKDF-SHA256, salt archipelago-reticulum-identity-v1, two labels for the X25519 + Ed25519 halves — yields a stable LXMF address that survives reinstalls
Message-store encryption (chats at rest)SHA-256("archipelago-message-store-v1" ‖ node_key)
Mesh contacts encryptionSHA-256("archipelago-mesh-contacts-v1" ‖ node_key)
Credential store (saved app credentials)Same domain-separated SHA-256 pattern
+ +
+ Domain separation, in one sentence + Each purpose prefixes a unique fixed string before hashing, so even though they start + from the same node key, every subsystem ends up with an unrelated key — compromising + one never exposes another. +
+ +

What Is NOT Derived From the Seed

+

Independent randomness — deliberately outside the tree.

+ +

+Plenty of secrets are freshly random rather than seed-derived. That's intentional: +things that should die with a session, rotate freely, or belong to a third-party app must +not be recoverable from your words (and mostly you wouldn't want them to be). +

+ +
+
+

Ephemeral by design

+

Session tokens, device/companion pairing tokens, federation invites, TOTP/2FA secrets and backup codes, all encryption nonces, X3DH ephemeral mesh keys, anonymous marketplace/discovery Nostr keys.

+
+
+

App-owned secrets

+

Every manifest-declared generated_secret (app database passwords, API keys), Bitcoin RPC credentials, the LND wallet password (distinct from its seed), Home Assistant tokens.

+
+
+

Host-level material

+

WireGuard VPN keypairs (generated by wg genkey), SSH host keys and the TLS certificate (created by the installer image at first boot), the machine-id.

+
+
+

Opt-outs from derivability

+

Extra identities created with "new random key" instead of seed derivation, and a node key after an explicit rotate-key — rotation deliberately breaks the link to the words, and says so.

+
+
+ +
+ Consequence for backups + A seed-only restore brings back everything in the derivation tree but none of the above. + App data, chat history and app secrets travel via the separate node backup/restore + feature; Lightning channels need channel backups; VPN peers re-pair. Your words are your + identity's lifeboat, not a full-system image. +
+ +

Failure Modes & Fallbacks

+

What happens when something goes wrong — at every stage.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ScenarioBehaviourOutcome
RNG returns a degenerate pattern (all-zero / repeated / counter)Draw is refused and wiped, never retried; error logged; onboarding fails loudlyNo seed created
Kernel random pool not yet initialised (exotic first-boot case)getrandom(2) blocks until the pool is seeded — an unseeded pool physically cannot produce a seed. The advisory probe logs a warning and records the verdict in the readiness ledgerWaits, then proceeds
Onboarding page refreshed / retried mid-generationSame words returned for 10 minutes (mutex-serialised); no second seed can be mintedIdempotent
master_seed.enc missing (e.g. backup write failed during setup)Node runs normally — every derived key is already materialised on disk. Only the Reveal feature and future re-derivation are unavailable; the UI says so explicitlyDegraded, functional
master_seed.enc corrupt, or wrong password at revealAuthenticated decryption fails closed with an explicit error — no fallback, no partial output, no auto-regenerationFails loudly
Restore attempted on an already-provisioned nodeThe onboarding gate refuses identity-mutating RPCs once the node is set up — a live node cannot be hijacked or accidentally re-seededRefused
Legacy / corrupt on-disk FIPS key formatSelf-heals: legacy raw-byte format is detected and migrated in place to the current bech32 formatAuto-migrated
Readiness-ledger write fails (disk full, permissions)Warns and continues — the audit trail is best-effort and can never block key generationNon-blocking
+ +
+ The one true single point of failure is you + Every software failure above fails safe. The only unrecoverable scenario is + losing the 24 words and the node's disk together. Write the words down, store + them offline, and never type them into anything except a node you're restoring. + Anyone holding them can rebuild your entire identity tree — that's precisely + what makes them a perfect backup, and a perfect target. +
+ +

The Restore Flow

+

Typing 24 words into a fresh node, step by step.

+ +
+
+

Gate check

+

Restore only proceeds on an un-onboarded node. A provisioned node refuses — this gate is load-bearing and runs before anything else.

+
+
+

Validation

+

Exactly 24 words, checked against the BIP-39 wordlist and its built-in checksum — a typo is caught here, before anything is written.

+
+
+

Identity regrowth

+

Node Ed25519 key + DID, node Nostr key, and FIPS mesh key are re-derived and written — byte-identical to the originals, because the HKDF labels are fixed.

+
+
+

Personal identity #0

+

The identity index resets to 0 and your default "Personal" identity (Ed25519 + NIP-06 Nostr key) is recreated. If you had created more seed-derived identities, their keys re-derive on demand as the index walks forward — but their names, avatars and profiles were metadata, not key material, and come from a node backup instead.

+
+
+

Mesh reactivation

+

FIPS federation auto-activation kicks off in the background; the Reticulum identity re-derives from the restored node key, so your LXMF mesh address comes back too.

+
+
+

Password & re-seal

+

When you set the (new) login password, the words are re-encrypted into a fresh master_seed.enc — the reveal feature works on the restored node just like the original.

+
+
+ +

What Comes Back — and What Doesn't

+ +
+
+

Restored by the words

+

Node identity & DID · node npub · FIPS mesh key · Reticulum/LXMF address · personal identity keys & npubs · message-store / contacts / credential encryption keys · the (dormant) Bitcoin xprv · the ability to reveal the phrase again.

+
+
+

Needs its own backup

+

Lightning wallet (aezeed — one-way gate, see above) and channel state · chat history & app data (node backup) · identity names/avatars (metadata) · app secrets (regenerate on reinstall).

+
+
+

Gone by design

+

Sessions & device pairings (re-login, re-pair) · 2FA secret (re-enrol) · WireGuard peers (re-pair) · rotated-away node keys · anonymous throwaway Nostr keys.

+
+
+ +

SeedQR

+

Your words as a scannable code — using the open SeedSigner standard.

+ +

+Wherever the phrase is displayed, a QR code tab is offered alongside the +words. For the main (BIP-39) phrase the default format is SeedQR: each +word becomes its 4-digit position in the official wordlist (24 words → 96 digits), +encoded as a compact numeric QR. Hardware wallets like Passport, SeedSigner and Keystone +import this format directly — so you can move your on-chain identity into cold storage +without ever typing the words. A plain-text QR fallback exists for wallets that read the +phrase as text. +

+ +
    +
  • The QR contains exactly the same secret as the words — treat a printout or screenshot of it with identical care.
  • +
  • The Lightning aezeed is deliberately never SeedQR-encoded — it isn't BIP-39, hardware wallets can't import it, and pretending otherwise would be dishonest. It gets a plain-text QR with an explanation instead.
  • +
  • Restore is by typed/pasted words; there's no camera-based SeedQR scanner on the restore path today.
  • +
+ +

Honest Edges

+

Known trade-offs and open hardening items, stated plainly.

+ +

+The audit that produced this system also tracked what it didn't fix. None of +these are secrets — honest security means naming the edges: +

+ +
    +
  • The words cross the RPC boundary. During onboarding the phrase travels (over HTTPS/localhost) to your browser to be displayed, sits in the browser's session storage for the wizard's duration, and is held in server memory for the 10-minute idempotence window. This is the deliberate price of a refresh-proof, display-once flow.
  • +
  • Argon2 parameters are the library defaults (≈19 MiB, 2 passes) rather than the heavier profile the design doc calls for. Still memory-hard and slow for attackers; scheduled for tightening.
  • +
  • 2FA backup codes carry a slight statistical bias from a modulo operation — cosmetically imperfect, cryptographically irrelevant at their length, and queued for cleanup.
  • +
  • The lint ban covers the main workspace, but one small helper crate outside the workspace isn't reached by it yet.
  • +
  • Best-effort backup sealing: if writing master_seed.enc fails during setup, the node continues (keys exist; only reveal is lost) — a loud failure might arguably be better, and this trade-off is under review.
  • +
+ +

Verify It Yourself

+

Don't trust — recompute.

+ +

+Because every derivation is deterministic and label-fixed, you can independently confirm +that your node's keys really do come from your words: +

+ +
    +
  • Independent re-derivation script: scripts/verify-seed-derivation.py in the Archipelago source — pure-standard-library Python, no Archipelago code. Paste your mnemonic (on a trusted, offline machine) and it recomputes node_key, nostr_secret and fips_key, byte-comparing them against /var/lib/archipelago/identity/.
  • +
  • Known-answer tests: the test suite pins the exact expected keys for a fixed test mnemonic; any change to the derivation math, however subtle, turns the build red.
  • +
  • Non-determinism regression test: 64 consecutive generated mnemonics are asserted unique — a canary against the "predictable RNG" failure class.
  • +
  • Your entropy audit trail: /var/lib/archipelago/security/csprng-readiness.jsonl records, append-only, the kernel randomness verdict at every key-generation event on this node — including the moment your seed was born.
  • +
+ +
+ The whole story in one paragraph + Your node asked the Linux kernel for 32 bytes of hardware-grade randomness through a + single, named, compiler-enforced channel; refused to proceed unless the bytes looked + alive; wrote down the health of the random pool as evidence; turned the bytes into 24 + words it showed you exactly once; locked an encrypted copy behind your password; and + then grew every identity and key it owns from those words along fixed, versioned, + independently-verifiable paths — so that the words in your drawer are, and will remain, + a complete blueprint of who your node is. +
+ +
+ +

+ Archipelago — Seed & Entropy Guide · reflects the KEY-05 hardened implementation (August 2026) · + see also the LoRa & Mesh Guide +

+ +
+ + diff --git a/neode-ui/src/views/settings/BackupSection.vue b/neode-ui/src/views/settings/BackupSection.vue index 151ad739..5fdebcce 100644 --- a/neode-ui/src/views/settings/BackupSection.vue +++ b/neode-ui/src/views/settings/BackupSection.vue @@ -289,6 +289,15 @@ defineExpose({ loadBackups }) (and 2FA code, if enabled). Only reveal it somewhere private — anyone with these words controls this node.

+ + How your seed & keys work — the full guide + +