fix(ecash): give every node a backup phrase, and prove restore works
Demo images / Build & push demo images (push) Failing after 2m11s
Demo images / Build & push demo images (push) Failing after 2m11s
Running the route suite on this box surfaced that the backup was unreachable here: `identity/master_seed.enc` is written during onboarding, and any node onboarded before that step existed simply does not have one. Reveal bailed with "this node has no encrypted seed backup", and restore followed it down. But the choice on such a node was never "derived phrase or independent phrase" — it was "independent phrase or no backup at all", and a wallet whose coins can be restored from words the operator holds beats one whose coins die with a single file. So it now generates one, recorded as `independent`, and every surface that shows it says plainly that restoring the node will not bring the ecash back — only these words will. `derivable_from_node_seed` lets the card say which kind you are about to get *before* you write anything down. Also: a mint that never implemented NUT-09 answered restore with a bare 404, which surfaced as "mint returned 404 with no further detail" — true, and useless to someone trying to get their coins back. It now names the limitation. The route suite was reading `result.amount_sats` from mint-claim, which answers with `minted_sats`. A working claim had been reporting as a failure; that was one of the two reds carried over from yesterday. The real gap, though, was that "recovered 0 sats" passes on a wallet with nothing to find — exactly the shape of a backup that looks fine until the day you need it. test-ecash-restore.sh does the test that settles it: mint, **delete the wallet file**, restore, check the coins came back. On this box: 87 sats before the wipe, 0 after, 61 recovered from the phrase alone — every coin minted since the phrase existed, and none of the 26 sats minted before it, which used random secrets and never could come back. Testnet only, and it refuses to run otherwise. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
cbbd20e22e
commit
e30516316b
@@ -260,13 +260,16 @@ impl RpcHandler {
|
||||
Ok(Some(seed)) => Some(seed.source()),
|
||||
_ => None,
|
||||
};
|
||||
// Whether the node has an encrypted master seed decides whether the
|
||||
// "set up" path can derive from it, which is what the operator is
|
||||
// promised: your node's 24 words already cover your ecash.
|
||||
// A phrase can always be established. Whether the node has an
|
||||
// encrypted master seed decides only *which kind*: derived from it
|
||||
// (the node's 24 words already cover the ecash), or independent (the
|
||||
// phrase is the only copy). The UI needs both facts to set the right
|
||||
// expectation before the operator commits to writing something down.
|
||||
Ok(serde_json::json!({
|
||||
"active": active,
|
||||
"source": source,
|
||||
"can_activate": crate::seed::seed_exists(data_dir),
|
||||
"can_activate": true,
|
||||
"derivable_from_node_seed": crate::seed::seed_exists(data_dir),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -306,12 +309,22 @@ impl RpcHandler {
|
||||
}));
|
||||
}
|
||||
|
||||
// No encrypted master seed to derive from — common on nodes onboarded
|
||||
// before that step existed. The choice here is not "derived or
|
||||
// independent", it is "independent or no backup at all", so we make
|
||||
// one and label it honestly. Every surface that shows an
|
||||
// `independent` phrase says the node's own recovery phrase does not
|
||||
// cover it.
|
||||
if !crate::seed::seed_exists(data_dir) {
|
||||
password.zeroize();
|
||||
anyhow::bail!(
|
||||
"This node has no encrypted seed backup, so an ecash recovery \
|
||||
phrase cannot be derived from it."
|
||||
);
|
||||
let seed = crate::wallet::nut13::establish_independent(data_dir).await?;
|
||||
let words = seed.words();
|
||||
return Ok(serde_json::json!({
|
||||
"words": words,
|
||||
"word_count": words.len(),
|
||||
"source": seed.source(),
|
||||
"newly_activated": true,
|
||||
}));
|
||||
}
|
||||
|
||||
// The backup passphrase may differ from the login password — same
|
||||
|
||||
Reference in New Issue
Block a user