Add BIP39 seed phrases (SilentDragonXLite-compatible) and HD transparent keys

Derive transparent (t-addr) keys from the HD seed and add BIP39 mnemonic seed
phrases that are byte-for-byte compatible with SilentDragonXLite, so the same
24 words recover the same shielded and transparent addresses in either wallet.

HD transparent keys:
- Derive t-keys from the seed at m/44'/coin'/0'/0/i (were random CKeys).
- CHDChain gains a version-gated transparent counter; existing wallets load
  unchanged. GenerateNewKey routes through DeriveNewChildKey when enabled
  (-hdtransparent, default on).
- Restore from a seed hex via -hdseed with gap-limit pre-derivation; birthday
  pinned to genesis so the rescan is not clipped.

BIP39 seed phrases:
- Wire the vendored trezor BIP39 lib (src/crypto/bip39) into the build, fix its
  BIP39_WORDS guard, and disable the insecure mnemonic cache.
- Match SDXLite exactly: English wordlist, empty passphrase, PBKDF2 64-byte
  seed, coin type 141, ZIP-32 m/32'/141'/i' and BIP44 m/44'/141'/0'/0/i. Store
  the 32-byte entropy and expand to the 64-byte seed on demand.
- Restore via -mnemonic, create via -usemnemonic, reveal via z_exportmnemonic.

Verified by gtests including a known-answer BIP39 seed vector and z/t address
derivation checks (src/gtest/test_hdtransparent.cpp, test_mnemonic_compat.cpp).
Docs in doc/hd-transparent-keys.md and doc/seed-phrase.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-06 01:57:18 -05:00
parent 84aefb5475
commit 4caf2fc68f
23 changed files with 1044 additions and 22 deletions

View File

@@ -0,0 +1,87 @@
# HD transparent keys
DragonX derives **transparent** (t-address) keys deterministically from the
wallet's HD seed, so they can be recovered from the seed alone — the same way
Sapling (shielded) keys already are.
## Derivation
Transparent keys are derived over secp256k1 using BIP32/BIP44:
```
m / 44' / coin_type' / 0' / 0 / i
```
* `coin_type` is `Params().BIP44CoinType()`**141** on mainnet, **1** on
test/regtest.
* Account is fixed at `0'` and the chain at `0` (external). The internal/change
chain (`1`) is **not** used: on this `ac_private=1` chain a non-coinbase
transparent output is consensus-invalid, so transparent change can never carry
value.
* `i` is `CHDChain.transparentChildCounter`, a monotonic index persisted in the
wallet so the same addresses regenerate after a seed-only restore.
Each derived key records its `hdKeypath` and the seed fingerprint (`seedFp`) in
its `CKeyMetadata`, matching the Sapling scheme.
## Why this matters on a private chain
On DragonX (`ac_private=1` from genesis) a normal user can never *receive* to a
transparent address — inbound t-payments are rejected by consensus. The only
thing that legitimately lands spendable value on a t-address is a **mining
coinbase** (plus notary/burn special cases). There is no "coinbase must be
shielded" rule, so mature coinbase is directly spendable.
So HD transparent keys exist to let a **miner recover coinbase rewards** that
were paid to wallet-derived t-addresses, using only the seed.
## Enabling / disabling
Controlled by `-hdtransparent` (default **on**). When on and the wallet has an
HD seed, every newly generated transparent key (receive address, change,
coinbase payout drawn from the keypool) is HD-derived.
```
-hdtransparent=0 # keep the legacy behaviour (random transparent keys)
```
## Backing up and restoring
* **Back up the seed.** `z_exportwallet <file>` writes the 32-byte HD seed as a
`# HDSeed=<hex>` line. Guard this value like a private key.
* **Restore into a fresh/empty wallet** by starting the node with:
```
-hdseed=<64-hex-character seed>
-hdtransparentgaplimit=<n> # HD transparent keys to pre-derive (default 1000)
```
On restore the node injects the seed, pre-derives `n` transparent keys with a
genesis birthday, and the normal startup rescan finds any coinbase paid to
them. Raise `-hdtransparentgaplimit` if the wallet minted more than `n`
distinct coinbase addresses.
> **Warning:** passing `-hdseed` on the command line exposes the seed to your
> shell history and the process list. Prefer putting it in `DRAGONX.conf` with
> tight file permissions, and remove it after the restore completes.
## Limitations (read before relying on recovery)
* **Legacy random keys are not recoverable.** Any transparent key created before
this feature (or with `-hdtransparent=0`) came from the CSPRNG, not the seed,
and the phrase/seed will **not** regenerate it. Keep `wallet.dat` /
`dumpwallet` backups for those. A wallet that predates the feature and then
enables it becomes a *mix* of random (old) and HD (new) keys.
* **Gap limit.** A rescan only discovers keys already present in the wallet.
Restore pre-derives `-hdtransparentgaplimit` keys; coinbase paid to an index
beyond that window is not found until you derive further and rescan again.
* **Scope.** Recovers transparent **coinbase** value only, per the consensus
rules above. Shielded funds are recovered separately via the Sapling HD keys.
## On-disk compatibility
The transparent counter is stored in `CHDChain` under a new serialization
version (`VERSION_HD_TRANSPARENT = 2`). Existing v1 `wallet.dat` records load
unchanged (the counter defaults to 0); the record is rewritten as v2 the first
time an HD transparent key is derived. Downgrading a v2 wallet to an older
binary is not supported.

90
doc/seed-phrase.md Normal file
View File

@@ -0,0 +1,90 @@
# BIP39 seed phrases (SilentDragonXLite-compatible)
DragonX full-node wallets can be created from and restored to a **BIP39 24-word
seed phrase** that is **byte-for-byte compatible with SilentDragonXLite**: the
same words produce the same transparent (t-) and shielded (z-) addresses in
either wallet, so funds move between the light wallet and the full node with one
backup.
## What makes them compatible
Compatibility requires the mnemonic, the seed derivation, and every HD path to
match exactly. They do:
| Detail | Value (both wallets) |
|---|---|
| Word list | BIP39 English, 2048 words |
| Passphrase | empty (no "25th word") |
| Mnemonic → seed | PBKDF2-HMAC-SHA512, 2048 rounds, salt `"mnemonic"`, 64-byte output |
| Coin type | 141 (KMD SLIP-0044) |
| Shielded path | `m/32'/141'/i'` (ZIP-32) |
| Transparent path | `m/44'/141'/0'/0/i` (BIP44) |
The node stores the 32-byte BIP39 **entropy** (SilentDragonXLite's on-disk
convention) and expands it to the 64-byte seed on demand for derivation. The
node's vendored BIP39 library (`src/crypto/bip39`) is byte-identical to
SilentDragonXLite's `tiny-bip39` 0.6.2, and the derivation is anchored by a
known-answer test (`src/gtest/test_mnemonic_compat.cpp`).
## Restore from a phrase
Start the node once, on a **fresh/empty datadir**, with the phrase:
```
dragonxd -mnemonic="word1 word2 ... word24"
```
or, preferably (keeps the phrase out of your shell history and process list),
put it in `DRAGONX.conf` with tight permissions:
```
mnemonic=word1 word2 ... word24
```
On restore the node pre-derives keys and rescans from genesis to recover funds:
* `-hdtransparentgaplimit=<n>` — HD transparent keys to pre-derive (default 1000)
* `-mnemonicsaplinggap=<n>` — shielded addresses to pre-derive (default 100)
Raise these if the wallet used many addresses. Restore only works on a wallet
with no seed yet (a brand-new datadir); it refuses to overwrite an existing seed.
## Create a new phrase on the node
By default new node wallets use a random (non-mnemonic) seed. To create a new
wallet from a fresh 24-word phrase instead — so you can export it and use it in
SilentDragonXLite — start with:
```
dragonxd -usemnemonic
```
## Show / back up the phrase
For a mnemonic wallet (created with `-usemnemonic` or restored with `-mnemonic`):
```
dragonx-cli z_exportmnemonic
```
returns the 24 words and the seed fingerprint. The wallet must be unlocked.
Guard the phrase like a private key.
## Limitations
* **English + empty passphrase only.** Any other word list or a BIP39 passphrase
would break compatibility, so they are not accepted.
* **Legacy / random-seed wallets have no phrase.** A wallet created before this
feature (or without `-usemnemonic`) has a random seed; `z_exportmnemonic`
returns an error for it — use `z_exportwallet` to back up the raw seed. Such
wallets are not SilentDragonXLite-compatible.
* **Scope.** Recovers HD-derived shielded funds and transparent coinbase (see
[hd-transparent-keys.md](hd-transparent-keys.md) for why only coinbase lands on
t-addresses on this `ac_private=1` chain). Keys imported with `z_importkey` are
not seed-derived and are not recovered by the phrase.
## On-disk compatibility
Mnemonic wallets set `CHDChain` version 3 (`VERSION_HD_MNEMONIC`). Older wallet
records load unchanged. Downgrading a mnemonic wallet to an older binary is not
supported.