Skip to content

Offline seed-export CLI so a custodied wallet survives custody removal (step 3 of dig_ecosystem#1701) #329

Description

@MichaelTaylor3d

Task

Build the offline seed-export CLI — step 3 of https://github.com/DIG-Network/dig_ecosystem/issues/1701.

Node-side USER custody is being retired. For a user who migrated their seed into this node and kept
no independent copy, the node's seed file is the only surviving copy of their spend key, so the
custody code cannot be deleted until that key has been handed back. This command is that hand-back,
and it is deleted along with the custody surface.

Step 3 was previously skipped on a population count of zero. That count was scoped to the wrong
store. The real population is one, and it is a user's wallet.

Context

The measured blob:

property value
size 248 bytes
first byte 0x01 — the legacy EncryptedSeed layout
sealed under a user password (no device key for that base)
funded state unknown (wallet.meta.json absent)
status already orphaned — current builds resolve a different base directory

Five other blobs exist and are not population: they are operator identity (DIGOP1, device key,
never funded). They are not touched, not exported, not counted.

seed_path() resolves {LOCALAPPDATA || HOME || "."}/DigWallet/seed.bin
(crates/dig-wallet/src/lib.rs:200).

The two traps

  1. It must decrypt the LEGACY format. An export built on dig_keystore::opaque::open fails on
    100% of the real population. It must go through seed_store::decrypt_seed, which dispatches on
    the leading magic.
  2. It must accept an explicit path. The blob is not where seed_path() resolves on that machine,
    so a resolver-only command cannot see the one thing it exists to rescue.

Scope — do not widen it

dig-node wallet export-seed:

  • requires local filesystem access plus the wallet password — the same two things an attacker
    would already need
  • prints the mnemonic to the console
  • no network surface, no RPC method, no loopback endpoint. A served wallet.export_seed was
    considered and REJECTED on the parent ticket: it would permanently add a seed-exfiltration
    capability to the loopback surface, reachable by anything holding a paired token or an mTLS client
    certificate, to solve a one-time migration. That ruling stands.
  • is deleted in step 4 with the custody code

Read-only: no blob is deleted, moved, zeroized or overwritten. POST /api/export stays — it is
currently the sole recovery path in the workspace. seed_path(), seed_store and autoseed are
not modified; five live operator identities depend on them.

Evidence

Legacy 0x01 decrypts, proven by mutation (building on opaque::open must fail the legacy test by
name); an explicit path reaches a non-default location; a wrong password fails cleanly; the mnemonic
reaches no log and no error string.

Limit

The parent's acceptance is export → import into dig-app → derived address matches → only then the
node blob is zeroized. That is a human-observed round trip, not a unit test, and it remains
outstanding after this ticket.

Parent

https://github.com/DIG-Network/dig_ecosystem/issues/1701

Siblings

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions