16 Commits

Author SHA1 Message Date
92e6c7008d wallet: define CHDChain's static constants out of line
hush-gtest failed to link with "undefined reference to
CHDChain::VERSION_HD_MNEMONIC". The version constants are static const int with
in-class initialisers and no definition anywhere, so any ODR use needs one --
and gtest's EXPECT_*/ASSERT_* macros take their arguments by const reference,
which is exactly that. test_mnemonic_compat.cpp:161 passes VERSION_HD_MNEMONIC
to EXPECT_LT.

dragonxd links either way, because nothing in the daemon binds these to a
reference; only the test target exposed it, and the test target was never built
on the branch that introduced the test.

Define all four rather than only the one that failed: VERSION_HD_BASE,
VERSION_HD_TRANSPARENT and CURRENT_VERSION carry the identical latent fault, and
the next EXPECT_EQ against any of them would hit the same wall. Fixing the test
instead would have hidden the problem rather than removed it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 18:58:24 +02:00
733df964ec Merge autoshield-safety into dev: auto-shield coinbase + BIP39 by default
Brings the auto-shield-coinbase feature and the wallet seed work onto the release
line so both are exercised from dev rather than a side branch.

From upstream: auto-shield of matured coinbase into a wallet z-address, plus
fixes to two pre-existing scheduler wedges (consolidation dispatching every block
instead of once per interval, and a failing sweep stranding its running flag).

On top of that:
  * the auto-shield destination is chosen by re-deriving m/32'/coin'/i' from the
    seed and taking the lowest in-gap account the wallet holds a key for, rather
    than the first entry in std::set order. Key metadata is NOT evidence of
    provenance -- z_importwallet copies hdKeypath and seedFp verbatim from the
    import file -- so a crafted import could otherwise claim account 0 and
    capture every shielded reward;
  * HD seed/chain persistence is hardened: the chain record is written before the
    seed, a corrupt hdchain is loud rather than silently reverting derivation to
    the raw entropy, hdchain survives -salvagewallet, and the silent BIP39 ->
    random seed fallback is gone;
  * seed provenance is recorded, and -autoshield defaults ON only where that
    provenance says the seed is recoverable;
  * mnemonic wallets now store the EXPANDED 64-byte BIP39 seed with the 32-byte
    entropy in a separate display-only record. Derivation reads stored bytes
    directly on every binary, so key trees are identical and no CHDChain version
    bump or minversion fence is needed -- the format stays readable by earlier
    releases;
  * new wallets are created from a BIP39 phrase by default;
  * the plaintext hdseed record is erased when a wallet is encrypted. It was
    previously left behind, and CDB::Rewrite copies surviving records verbatim,
    so the unencrypted seed persisted on disk forever.

TWO DEFAULTS CHANGE for new wallets: auto-shielding (where the seed provenance is
known) and BIP39 seed generation. Existing wallets are untouched -- seed
generation is reachable only when a wallet has none, and all three key stores
refuse to replace an existing seed.

Testing state, stated plainly: every commit built clean and the branch was
verified on an isolated chain -- destination selection provably ignores an
imported foreign key, both provenance branches behave, a new-format wallet
reopened by a pre-change binary lists identical addresses, and restoring only the
24 words recovers the wallet. NOT yet tested: any of this against a funded wallet
on mainnet, or a soak of these defaults on a real node.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 11:53:32 -05:00
d12e7dc99d Merge origin/master into dev: restore the mingw -Wa,-mbig-obj flag
dev branched from the release line before 1c3523aac and so lost -Wa,-mbig-obj
from util/build-win.sh. Without it the Windows cross-compile fails at link: large
template/boost-heavy translation units exceed the PE/COFF ~32k-section limit, and
GNU ld emits "dangerous relocation" on .pdata and crashes. v1.1.0 therefore could
not produce a win64 binary at all.

The merge brings only that one file back. util/build-win.sh is the only file both
sides touched, and the two edits are ~20 lines apart, so it auto-merges keeping
both fixes: -Wa,-mbig-obj on the configure line and -DARCH=default on the cmake
line. Verified after merging that dev's own work is intact -- the extended
checkpoint table, the guarded RandomX dedup, the 1.1.0 version bump, and
ARCH=default in the other two build scripts.

No C++ changed; the only delta from the pre-merge dev is the shell script.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 10:42:29 -05:00
9734402d7b wallet: create new wallets from a BIP39 seed phrase by default
New wallets now get an exportable 24-word phrase instead of a random seed that
no phrase can ever reproduce. Only wallets with no seed yet are affected;
GenerateNewSeed is reachable from one place, under !HaveHDSeed(), and all three
key stores refuse to replace an existing seed.

This is safe to default on now that the storage form is backwards compatible: the
expanded 64-byte BIP39 seed is what gets stored, so a binary predating any of
this reads it and derives the same keys.

Verified on an isolated chain before flipping:
  - a new-format wallet reopened with the tagged v1.1.0 binary, which has no
    knowledge of the entropy record, listed identical addresses;
  - restoring only the 24 words into a fresh datadir recovered every address.

Note this changes what a new wallet is, not what an existing one is: the same
entropy yields a different key tree depending on which side of this commit
created the wallet. Nothing migrates, and nothing needs to.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 07:01:22 +02:00
20b2cbe830 wallet: store the expanded BIP39 seed for mnemonic wallets
Mnemonic wallets stored the 32-byte BIP39 entropy as the HD seed and relied on
CHDChain.fMnemonicSeed to tell the deriver to expand it first. That flag is
version-gated in the CHDChain serialisation, so a binary that predates it reads
the record, never consumes the trailing byte, and derives from the raw entropy --
a different key tree, silently, with no error.

Store the expanded 64-byte BIP39 seed instead, with fMnemonic = false, and keep
the entropy in its own display-only record. Derivation then reads the stored
bytes directly on every binary, old or new, so key trees are identical and no
CHDChain version bump or minversion fence is needed. The wallet format stays
readable by earlier releases rather than becoming one-way.

Addresses are unchanged: the previous format expanded the entropy on every read
and fed the same 64 bytes to Master(). This is also the form the tree already
round-trips through -- z_exportwallet dumps the expanded seed, and restoring that
hex via -hdseed installs it with fMnemonic = false.

Write order is load-bearing: seed first, entropy second. A crash between them
leaves a wallet with a seed and no phrase, which is merely inconvenient. The
reverse would leave an entropy record with no seed, and the next start would mint
a different seed while the wallet still held a phrase for the old one.

-usemnemonic still defaults to false; only explicit opt-in and -mnemonic restores
take this path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 06:39:38 +02:00
2d7dd90c55 wallet: plumb the mnemonic entropy through CWallet
Wires the key store secret to the database records: load and store paths on
CWallet, the two ReadKeyValue arms, and the export path.

GetMnemonicPhrase now prefers the entropy record and verifies it before printing:
a phrase is only returned if expanding it reproduces the seed derivation actually
uses. It falls back to the existing fMnemonicSeed path, so wallets that store the
entropy AS the seed keep working unchanged.

IsMnemonicSeed() now means "a phrase is available" rather than "the seed is the
entropy", which is what every caller actually wants.

Still a no-op on every existing wallet: nothing creates an entropy record yet, so
GetMnemonicEntropy returns false and the old code path is taken.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 06:37:43 +02:00
3caec548ae wallet: persist the mnemonic entropy in wallet.dat
Adds the record pair for the entropy, mirroring "hdseed"/"chdseed": a plaintext
form, an encrypted form that erases its plaintext counterpart the way
WriteCryptedKey does, and an erase.

Both new types are registered in IsKeyType so -salvagewallet preserves them.
Without that, salvage would silently drop the phrase while keeping the wallet
otherwise intact.

The records are defined but nothing writes them yet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 06:30:18 +02:00
2a7fdcc1db wallet: add an optional mnemonic-entropy secret to the key stores
Additive plumbing for storing a BIP39 entropy alongside the HD seed, mirroring
how the seed itself is handled through both key store layers: a plaintext member
on CBasicKeyStore, an encrypted pair on CCryptoKeyStore, encryption during the
unencrypted-to-encrypted conversion in EncryptKeys with the plaintext cleared,
and decryption on unlock.

RawHDSeed and CKeyingMaterial are the same secure_allocator vector type, so the
entropy passes through EncryptSecret/DecryptSecret with no adaptation.

Nothing calls this yet; there is no behaviour change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 06:23:09 +02:00
143c33de48 wallet: erase the plaintext HD seed record when the wallet is encrypted
CWalletDB::WriteCryptedHDSeed wrote the "chdseed" record and left "hdseed" in
place, unlike WriteCryptedKey which erases "key"/"wkey" after writing "ckey".
No erase of "hdseed" existed anywhere in src/wallet/.

CDB::Rewrite does not save us: EncryptWallet calls it with pszSkip defaulted, so
it copies every surviving record verbatim into the new file. The result is that a
wallet created unencrypted and later encrypted keeps its raw HD seed in cleartext
on disk permanently, and reloads it into memory on every start.

Add CWalletDB::EraseHDSeed and call it from CWallet::SetCryptedHDSeed after the
encrypted record is written, through the same CWalletDB so it shares
EncryptWallet's transaction. Erase returns true on DB_NOTFOUND, so a wallet that
was never written in plaintext is unaffected.

The erase is deliberately best-effort and only logs on failure. A hard failure
here propagates into CCryptoKeyStore::EncryptKeys, which EncryptWallet turns into
assert(false) with half the keys encrypted in memory; a warning is strictly
better than that.

Note this path is only reachable with -developerencryptwallet, which is
experimental and off by default on this chain, so this is a latent fix rather
than a live one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 06:14:31 +02:00
e2f88175ab wallet: default -autoshield ON only when the HD seed is known-recoverable
Autoshield moves mined coinbase into a z-address that only this wallet's HD seed
can re-derive. Defaulting that ON is only defensible where the user can actually
restore that seed.

Two cases fail that test. A seedless legacy wallet has a seed minted onto it
silently at first start, so no backup the user already holds contains it. And a
wallet seeded by an earlier build predates provenance recording, so we cannot
tell which case it was. Both are now classified as not-known-recoverable and
autoshield stays off there until the operator backs the seed up and passes
-autoshield=1. An explicit -autoshield=0/1 still wins in either direction.

Wallets this software created on an empty datadir, or restored from a
user-supplied -mnemonic/-hdseed, keep the ON default: in both cases the user
either has the phrase or supplied the seed themselves.

Verified on real wallets: a wallet carrying no origin record is classified
unknown and logs "autoshield left OFF by default: HD seed origin 4"; a wallet
created by the previous commit logs "autoshield enabled" with no
re-classification, confirming the record persists rather than being recomputed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 03:22:55 +02:00
a494eabdce wallet: harden HD seed/chain persistence and record seed provenance
Autoshield sends mined coinbase to a seed-derived z-address, so the records that
decide how the seed derives keys become fund-safety critical. Three of them were
not treated that way.

InstallHDSeed wrote the seed record before the chain record, non-transactionally.
A crash between the two left a wallet holding a seed with no hdchain: on the next
load hdChain silently reverts to defaults, clearing fMnemonicSeed -- which
switches the derivation input from the expanded BIP39 seed to the raw entropy --
and resetting saplingAccountCounter. Write the chain first; the opposite torn
state is harmless and self-heals, because HaveHDSeed() is then false and init
installs again.

The hdchain record was read as a bare deserialise inside a catch-all with strErr
never set, and it is not a key type, so a corrupt record was downgraded to a
non-critical error and the node booted into the wrong key tree. Report it, track
whether it was read, and refuse to load a wallet that holds a seed but no
readable hdchain. Also preserve hdchain through a keys-only salvage, which would
otherwise drop it and produce exactly the state we now refuse.

GenerateNewSeed silently fell back to a random seed when BIP39 generation failed,
producing a wallet that looks mnemonic-capable but whose words can never be
exported and which no seed phrase can restore. A user who asked for -usemnemonic
now gets that or a hard failure.

Finally, record how the seed came to exist -- created on an empty wallet,
restored from -mnemonic/-hdseed, retrofitted onto a pre-existing seedless wallet,
or predating this record. A retrofitted seed is in no backup the user already
holds, so a feature that moves funds into addresses only that seed can re-derive
must not enable itself there by default. Nothing consumes this yet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 03:18:35 +02:00
dad162a89a wallet: pick the autoshield destination by seed re-derivation, in-gap only
resolveDestination() took the first spendable address in std::set order, which
orders on the raw Sapling diversifier (zcash/Address.hpp:95-98) -- uncorrelated
with anything the operator can see, and unstable across restarts as addresses
are added.

Worse, GetSaplingPaymentAddresses() also returns z_importkey/z_importwallet
addresses, and CKeyMetadata is NOT evidence of provenance: both hdKeypath and
seedFp are copied verbatim out of the import source (rpcdump.cpp:511-516 ->
wallet.cpp:5440-5441) with no verification. A crafted import can therefore claim
this wallet's seedFp and keypath m/32'/coin'/0' and capture every shielded
mining reward into a key no seed restore can reproduce. Filtering on metadata
would not have caught that.

Derive instead. A bare -mnemonic/-hdseed restore pre-derives exactly
-mnemonicsaplinggap sapling accounts from index 0 with saplingAccountCounter
reset (init.cpp:2349-2355), so the only self-recoverable destinations are the
default addresses of m/32'/<coin>'/i' for i below the gap. Walk that window from
the seed and take the lowest index the wallet holds a spending key for. Deriving
is the only authoritative test and cannot be spoofed.

When nothing in the window is held yet, derive the next account -- but only if it
will land inside the window. GenerateNewSaplingZKey does not derive at
saplingAccountCounter: its do/while skips indices already held
(wallet.cpp:150-157), so a bare counter-below-gap test is unsound. Predict the
lowest free index at or above the counter and post-verify the returned address.
If no free account remains below the gap, refuse the round and leave the coinbase
transparent -- transparent funds are still recoverable through the 1000-key
transparent gap, an unfindable note is not.

Refusing is safe: main_impl turns a false return into a clean skip, and main()
always advances nextAutoShield and clears fAutoShieldRunning, so a refused round
cannot latch the feature off.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 02:52:34 +02:00
bb292a89cc test: merge feature/autoshield-coinbase into v1.1.0 for isolated testing
Not for release. Built solely to exercise auto-shield-coinbase on an isolated
mining chain, since the feature is a no-op on a non-mining node (the canary
seed has no balance, no z-address, no coinbase, and does not mine).

Merge is clean: util/build-win.sh auto-merged, keeping both our ARCH=default
and their -Wa,-mbig-obj. Checkpoints, verify-once guard and the version bump
all survive.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 19:24:20 -05:00
660678f9bb build: bump version to 1.1.0
CLIENT_VERSION 1000350 -> 1010050. Goes to 1.1.0 rather than 1.0.4 because
1.0.3 is already burned and ambiguous: origin/dragonx's debian changelog
already claims 1.0.3 and the daemon bundled in the ObsidianDragon 2.0.1
installer is labelled v1.0.3-dc45e7d90, so a 1.0.4 would sort above builds
that contain less.

Bumped in configure.ac (authoritative) and in the src/clientversion.h fallback
used when HAVE_CONFIG_H is unset, which the header itself asks to be kept in
sync.

DELIBERATELY NOT BUMPED: SPROUT_VALUE_VERSION, SAPLING_VALUE_VERSION and
SAPLING_VALUE_OPTIONAL_VERSION in chain.h. Those are thresholds marking the
CLIENT_VERSION that INTRODUCED each block-index format, not "the current
version". Raising SAPLING_VALUE_OPTIONAL_VERSION to 1010050 would push every
record written by a v1.0.3 node (nVersion 1000350) into the legacy
raw-CAmount branch of the deserializer and misparse it. The stale comment
naming 1000350 as the current CLIENT_VERSION is updated; the constants stand.

1010050 >= 1000350, so this build still writes and reads the optional format.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 19:15:56 -05:00
fbcf160478 fix(pow): only dedup RandomX when CheckBlockHeader verified it; drop fake git id
Brings onto dev the two fixes that until now existed only on release/1.0.4, so
nothing is stranded on a branch we are not shipping from.

1. GUARDED VERIFY-ONCE (main.cpp)

4e67e687d arms the RandomX dedup unconditionally whenever fCheckPOW is set. But
CheckBlockHeader returns early -- BEFORE reaching its RandomX check -- for a
block whose timestamp is >60s in the future (*futureblockp==1), and CheckBlock
deliberately continues on that path. There, hush_checkPOW is the ONLY RandomX
verification the block gets, so suppressing it leaves the block unverified.

Not a chain-acceptance hole: ConnectBlock rejects futureblock != 0, so such a
block never joins the chain. But it silently weakens DoS banning -- an invalid
future block gets rejected for its timestamp instead of for bad PoW, which is a
regression against the un-deduped behaviour it replaced.

ScopedRandomXSkip now takes an `arm` flag and CheckBlock passes fHeaderChecked,
so the dedup applies only where the header check actually completed and did the
verification. Strictly a tightening: it can only cause MORE verification than
before, never less.

2. NO FAKE GIT IDENTITY (clientversion.cpp)

A hardcoded `#define GIT_ARCHIVE 1` with GIT_COMMIT_ID "a86845f3dc", dated Feb
2018, is reached whenever build.h supplies no BUILD_DESC -- i.e. any build
without git metadata, which is exactly the tarball/CI release case. Such
binaries reported themselves as that Komodo commit regardless of content; a
build here did precisely that before this was found. The archive substitution
placeholders are kept, so a real git-archive export still works; a git-less
build now reports "-unk", which is honest and greppable.

Both syntax-clean. Rebuild and re-validation on EPYC follows; the earlier
validated binary (md5 fe83d70fec5b50c38bf65ea6c733ffa9) predates these.

release/1.0.4 is parked, not deleted -- its commit records why the v1.0.2
lineage cannot ship (block-index format incompatibility with dev-written
chainstate).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 19:15:56 -05:00
7dc904c96f perf(sync): extend DRAGONX checkpoints to 3,226,000; build portable RandomX
The RandomX skip in RandomXValidationRequired() has never fired in production.
It skips verification below the last in-index checkpoint, but the DRAGONX
checkpoint table ended at 2,838,000 while ASSETCHAINS_RANDOMX_VALIDATION is
2,838,976 -- the window was empty by 976 blocks. Every block since the RandomX
activation has been fully verified, at ~65ms per hash on the fastest x86 core
available and ~180ms on a typical user machine.

Extends the table by 388 entries at stride 1000, from 2,839,000 to 3,226,000
(tip - ~5,600, far beyond any reorg this chain has produced; max observed depth
is 3-4). Blocks requiring a RandomX verify drop from 391,447 to ~5,500. The
same extension carries the existing script/zk-proof skip (fScriptChecks,
fExpensiveChecks) over the same range.

Checkpoint data verification, before it went anywhere near source:
  - generated on a continuously-synced node
  - all 388 hashes identical on 4 other full nodes (388/388 on each)
  - reverse-verified hash -> height, all on the active chain
  - re-extracted from the patched file and diffed against the verified set
  - 2,838 pre-existing entries unchanged; all 3,226 ascending and unique
Trailer fields computed from RPC, not util/checkpoints.pl, which greps a
rotating debug.log and assumes 1440 blk/day (DragonX is 2400).

Also switches all three build scripts from -DARCH=native to -DARCH=default.
RandomX's CMakeLists maps ARCH=native to -march=native, tuning the binary to
the build host: a Zen4 build emitted 746 AVX-512 instructions into
librandomx.a, and every seed reports avx512f=no, so that binary would SIGILL
inside RandomX fleet-wide -- and on any user CPU older than the build machine.
build-win.sh had the same flag, so shipped Windows binaries inherited it.
ARCH=default keeps -maes and per-file -mssse3/-mavx2, so the portable baseline
costs essentially nothing. Note build.sh skips cmake entirely when
src/RandomX/build/ exists, so a stale dir silently preserves the old ARCH.

Validated on an isolated datadir on an EPYC seed, bootstrap -> tip:
  - below 3,226,000 (RandomX skipped):  91.3 blk/s (22,823 blocks / 250s)
  - at/above 3,226,000 (verified):       4.7 blk/s
  ~19x at the boundary. The 4.7 blk/s baseline matches a same-day restore on
  the old binary, corroborating it independently.
  - gettxoutsetinfo at height 3,231,951 BYTE-IDENTICAL to a live node
    (hash_serialized 4885c2374ef8b84c648b97d560a57cfcc99bb979142dc89c2a5ccbc90a1f1692,
     222,635 txs/txouts, 15,826,352 bytes, total 667909.93689180)
  - synced through all 388 new checkpoints with zero rejections
  - verifychain 4 (500) and 3 (2000) both true
Binary: v1.0.3-bf3c33c53-dirty, stripped md5 fe83d70fec5b50c38bf65ea6c733ffa9

Checkpoints decay at 2,400 blocks/day; regenerating them belongs on the
release checklist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 19:15:56 -05:00
22 changed files with 1338 additions and 78 deletions

View File

@@ -2,8 +2,8 @@ dnl require autoconf 2.60 (AS_ECHO/AS_ECHO_N)
AC_PREREQ([2.60])
define(_CLIENT_VERSION_MAJOR, 1)
dnl Must be kept in sync with src/clientversion.h , ugh!
define(_CLIENT_VERSION_MINOR, 0)
define(_CLIENT_VERSION_REVISION, 3)
define(_CLIENT_VERSION_MINOR, 1)
define(_CLIENT_VERSION_REVISION, 0)
define(_CLIENT_VERSION_BUILD, 50)
define(_ZC_BUILD_VAL, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, m4_incr(_CLIENT_VERSION_BUILD), m4_eval(_CLIENT_VERSION_BUILD < 50), 1, m4_eval(_CLIENT_VERSION_BUILD - 24), m4_eval(_CLIENT_VERSION_BUILD == 50), 1, , m4_eval(_CLIENT_VERSION_BUILD - 50)))
define(_CLIENT_VERSION_SUFFIX, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, _CLIENT_VERSION_REVISION-beta$1, m4_eval(_CLIENT_VERSION_BUILD < 50), 1, _CLIENT_VERSION_REVISION-rc$1, m4_eval(_CLIENT_VERSION_BUILD == 50), 1, _CLIENT_VERSION_REVISION, _CLIENT_VERSION_REVISION-$1)))

View File

@@ -1,3 +1,24 @@
dragonx (1.1.0) stable; urgency=medium
* Extend DRAGONX checkpoints to height 3,226,000, enabling the existing
RandomX skip below the last in-index checkpoint. Blocks requiring a
RandomX verify drop from ~391,000 to ~5,500; measured 91.3 blk/s below
the checkpoint vs 4.7 above on identical hardware.
* Verify each block's RandomX solution once per connect instead of twice,
and only when CheckBlockHeader actually performed the verification
(future-timestamped blocks keep their check).
* Build RandomX with ARCH=default rather than ARCH=native, so binaries are
not silently tuned to the build machine's CPU. A Zen4 build previously
emitted AVX-512 into librandomx.a, which cannot run on the seed fleet or
on older user CPUs.
* Remove a hardcoded git commit id that made builds without git metadata
report themselves as an unrelated 2018 commit.
* Includes the accumulated dev-branch work since 1.0.2: parallel RandomX
pre-verification, adaptive -dbcache, Sapling witness desync fix, BIP39
seed phrases, chain-level Sapling turnstile, and the audit fixes.
-- DragonX Developers <dev@dragonx.is> Thu, 21 Aug 2026 22:00:00 +0000
dragonx (1.0.3) stable; urgency=medium
* IBD/sync speedups: parallel RandomX pre-verification, adaptive -dbcache, P2P download fixes

View File

@@ -35,7 +35,7 @@ extern bool fZindex;
// These version thresholds control whether nSproutValue/nSaplingValue are
// serialized in the block index. They must be <= CLIENT_VERSION or the
// values will never be persisted, causing nChainSaplingValue to reset
// to 0 after node restart. DragonX CLIENT_VERSION is 1000350 (v1.0.3.50).
// to 0 after node restart. DragonX CLIENT_VERSION is 1010050 (v1.1.0.50).
static const int SPROUT_VALUE_VERSION = 1000000;
static const int SAPLING_VALUE_VERSION = 1000000;
// Block-index records written at >= this version store nSaplingValue as a boost::optional

View File

@@ -5640,9 +5640,401 @@ void *chainparams_commandline() {
(2836000, uint256S("0x00000000004f1a5b9b0fad39c6751db29b99bfcb045181b6077d791ee0cf91f2"))
(2837000, uint256S("0x000000000027c61ed8745c18d6b00edec9414e30dd880d92d598a6a0ce0fc238"))
(2838000, uint256S("0x00000000010947813b04f02da1166a07ba213369ec83695f4d8a6270c57f1141"))
,(int64_t) 1770622731, // time of last checkpointed block
(int64_t) 2940000, // total txs
(double) 4576 // txs in the last day before block 2838000
// Extended 2026-08-21: the table previously ended at 2,838,000, which is 976
// blocks BELOW ASSETCHAINS_RANDOMX_VALIDATION (2,838,976), so the RandomX
// skip in RandomXValidationRequired() could never fire. Verified against 5
// independent full nodes, both directions, all on the active chain.
(2839000, uint256S("0x036b5f8f60733ef985d422bbf2367822dd032a9440fd3524420d4ecfa21ba9f6"))
(2840000, uint256S("0x0007994f4c5a67c3030e41980e8f28de88f8c0095a5148393e3ae11fb7116071"))
(2841000, uint256S("0x0002d6ec728f3a1aa76ffcda9a0bd713328a1d192bc76374cf9d819af755161d"))
(2842000, uint256S("0x00002cb9b235571f8bdfe133ac25f12a05cb33a1c7894d4fe3cab0b7a46eb30d"))
(2843000, uint256S("0x000007ad1b64a69892768646121a5d675ea425fa5ad1147207c7b5c286ee2e0d"))
(2844000, uint256S("0x00000347a2169ad507a954c5529fbbe52ea19c9d0953005bb8964825b4944133"))
(2845000, uint256S("0x0000037a9cbecf55c5100c890a83b6fb8c0d4413d86ea0a45f86f0644709fb79"))
(2846000, uint256S("0x00000011d9e2ae53be284f718e22461ad28dfd4ee6e1b9ef98412f5b2f5831b1"))
(2847000, uint256S("0x000004a70b7f18cec9623e7c5460093264ba00a44e66ac8c0d2551a488216907"))
(2848000, uint256S("0x000005dadf5e3f8d4837da9580ca28934d70ff7cb63bc326df5f44f91bbc22fe"))
(2849000, uint256S("0x000002f92bdffcf58b8dca4382a66af71e06447045758dab93c39c77c1d9c91e"))
(2850000, uint256S("0x0000003db45806f522f1cbf059bab8ddb1a428bcc9063bced294c7bf0289a3c3"))
(2851000, uint256S("0x000005352fb09271bb1f804cd55c57cabefcfbf34dad29d0209f16c98903a836"))
(2852000, uint256S("0x00000123edae432f080e11dcf7a0718e9f144f571746b0ca902a2e2f81eecccc"))
(2853000, uint256S("0x000006ae53f9318417fc4df6d3672f2a88a3f67a7b3e77d9f833cc23778e023b"))
(2854000, uint256S("0x000004715fdde7854b5b227fb5cc1c4cf006935fd80ce8390b0178e4a7c6367c"))
(2855000, uint256S("0x000001b53c3d0689ae4df1ad2b5bbd5ae61b2a392b2abc25cb8d866bff4cafe9"))
(2856000, uint256S("0x00000011f6bb05a702ffc936985dabbbb983a771b97a6a518798781325adc400"))
(2857000, uint256S("0x0000034dc8e9de8530491ae750fca3d7ea5b8992165a13e16aa90a35b690fb53"))
(2858000, uint256S("0x0000002ba3bc10f84ebaab3713c58016ce5320b0a68efafc542aaa954d6d2d05"))
(2859000, uint256S("0x000000b2740c0b463d424bc5630ebb80fc01823c701859bd4a0b92161880f11d"))
(2860000, uint256S("0x0000037007777311e559e889930c7590814e28f0bd3794796658b6cf588a9c19"))
(2861000, uint256S("0x000002f1fb62662b6d62addea67d78e6dafa3ec0e50d8098d6f593434022b4e3"))
(2862000, uint256S("0x0000056b00954317d9e2518bf462e839ba8cfbd2c522e307414b75869ec1483b"))
(2863000, uint256S("0x00000444ea6f3ff4bdaa22f7d9fddd1001530b5d96172799560ceca2cb7c9936"))
(2864000, uint256S("0x00000379a4d7ed4096c2715f05c154a6c18f1322eae69f76c0566f88b6372f6c"))
(2865000, uint256S("0x000004a98a372678405acceb0fe311a10fd6a57fd512b057675546b687cb81c4"))
(2866000, uint256S("0x000000301204227ce755e160607d720619b0f06e12664cdae92abcf2dc500ef7"))
(2867000, uint256S("0x00000229bdbc6f916558d279de61cad703fa4842041d373cfe19e9c6f05e5d0e"))
(2868000, uint256S("0x000001511aa190c24e92a4149741038221fd6dd794680ae8972dea6ab7d73a14"))
(2869000, uint256S("0x000001209e63f22310be9844155012a7736a8134028ce2f1b536caa95a87455a"))
(2870000, uint256S("0x000001d04778f2526ee1427709e733843e18decf20ee71d482271f04152737ac"))
(2871000, uint256S("0x000000e9bcee55b40cf6b5105c707d48e2fdd852c38ab2d9994203de560e6c22"))
(2872000, uint256S("0x0000020f46c3732afe95b72159ea059a7fbe443e2a9978a951321ab77608ecba"))
(2873000, uint256S("0x000000be45b4ec2b826ed385e13602a4034857736278722455128e134c8ca072"))
(2874000, uint256S("0x000002605ca5037e8a5cdc19c0c418fdb0b922c3eec713aa0357f0077ddc0173"))
(2875000, uint256S("0x0000028d16a1dcfa320bb247e2fbd31c318df0e9f0362b177f56f0cd193b79ca"))
(2876000, uint256S("0x0000000289fccbfdd88bb3bb1f22c5c5d4fbb16690922fd1a2ba9002c12c4df1"))
(2877000, uint256S("0x000000a4625231d51e8c65eeb8c13352e50aba0f570d4497849fef94434f2cd6"))
(2878000, uint256S("0x000000080fdf691ba03c1ade593abd758c9683452aa195121029e44614daeff4"))
(2879000, uint256S("0x000000956fec52943b4577e2959dede6411997a2720965bfb615b6bdd8fbfe3a"))
(2880000, uint256S("0x000002799452afc6905050bf9f1e0688b644b118c78051836e19e3d959a62c6e"))
(2881000, uint256S("0x000000a55304c9b02690aa867ba4b775887384a323e71ddb9486234aab7337ae"))
(2882000, uint256S("0x000000770d3b0e3eef93d5d652c236b7ac993f102984161bad8fff57d4b97f87"))
(2883000, uint256S("0x000000f0cd04f12eb06e250f1f81556310d2d7607a8aa7b56dd9bc8aa0f6a7ab"))
(2884000, uint256S("0x0000001701df19e9b84d616b6b9e38c2b0d040cf53f8b5f300d7d62a0da705d8"))
(2885000, uint256S("0x000000d08bc242a49321452104c7797a03e1c62206a9549e801f60a120374359"))
(2886000, uint256S("0x0000005b2559414fbd1150b6c981eb33b79b09b99cf741c2f085bb7856779e4f"))
(2887000, uint256S("0x000000f806f6b02d5b1a9c0fbeccb8c8590b368f577f0171fa309db1927b9731"))
(2888000, uint256S("0x0000015e75de0201216b1c0761a0193f3590d4b980d7e2685dd8ae17ce2e6b59"))
(2889000, uint256S("0x0000017c84d2d3187ebc80680087af61ba582c4cd34fb171c174aa5f2e689632"))
(2890000, uint256S("0x0000013c9ed9f4eaa71e4d8da5c986c6d031e0907afd134e8f1268b899141a16"))
(2891000, uint256S("0x0000018144f06b18c18d26863a57d157ede9137876334afab3641647e13b94b8"))
(2892000, uint256S("0x000000d6605f31785f1a1f1afe6058fc8d438b67017e88c8c13eb438808af7c8"))
(2893000, uint256S("0x0000001fbcaf6af29a76c408290d9169a58ea696473c7379cd5c675ab80069dc"))
(2894000, uint256S("0x000000173a49f8d72af0d6377c8e219e8f7c6f16fd193414bd2252a2b0e6b17f"))
(2895000, uint256S("0x0000003100994bacedd566893497bcb50aa4533a2a2c8199b9390eb9535945c7"))
(2896000, uint256S("0x000000b9b69f0dcd15d8b6f44502bef17e92c595d6564d729490d40021c82dc0"))
(2897000, uint256S("0x000001126a35ef0fbd43ac5450efe13750de16bb86bc1d735008f2ba6ddf202f"))
(2898000, uint256S("0x000001105d958e9dd8208bf5ddf552a3ee175e00ed64175e4260bf227d7038e3"))
(2899000, uint256S("0x0000012bfbf4516bdf5ccbdb420d02a2abdfced13e00729bfa6504f113ace2a7"))
(2900000, uint256S("0x000001ad94f932e03125a5cb8b97d65d8456cd5c5ac6c0d23a1e30dc66b722ab"))
(2901000, uint256S("0x0000015029fd0001c47219ba9eb923e8c19dc3b6bbb8986797fdcb796ba5e7d6"))
(2902000, uint256S("0x000001a9deda168129dcfd63eb8cc147e3278a4364dfcc7a55461598622bb2b9"))
(2903000, uint256S("0x0000010c3e62bbe5e083e0412f13d01c17aadfaa3bf45744732737e123779b41"))
(2904000, uint256S("0x00000108897e8acc0f3d0ef3e4f083768a8831669ff41505c6a9bb2e34bae7a5"))
(2905000, uint256S("0x0000025de11376fdc16dbfd98b7cecbf07f9b134b7baba9bb5aa65f4ea88baf1"))
(2906000, uint256S("0x000001685929800faea4c5d443eca9d8a45b441098a797f30360ad5fb6a40afc"))
(2907000, uint256S("0x00000181dbf2036c3d3e13d251b3398ff7b4b36073babffd207b7bd7234224be"))
(2908000, uint256S("0x0000014d9f26e7993aae49141cf86940844dfcd3315469d4a89fa925695651fa"))
(2909000, uint256S("0x000000e1f57f68602d01fa47f8c61f02d3448158429e064dce3b693298645360"))
(2910000, uint256S("0x0000000bff875f2e5dee11daff9357824dab610b81a38f8d24bebf4590bf7925"))
(2911000, uint256S("0x0000016d24cfc8b99f25e66324ed9c221fe5ba67ab64d410796dc52b510f18b3"))
(2912000, uint256S("0x00000153cd34e3d692ff6913b74bf604735b20e5b8fcf4b30fe2be08b4d9a9ad"))
(2913000, uint256S("0x00000001b5fa024c9e0af58b0e6fe95fbd80c9ed1f0905d1db9c511f64d8de4a"))
(2914000, uint256S("0x000000edf84a56f70de7e9ab444e9940bc1c5607dff2e5efcf6b7259bcd5bd53"))
(2915000, uint256S("0x0000023faea5d5354dd535323d7cad9ee5246b060be4e78bddbb15aece65d6af"))
(2916000, uint256S("0x000001242376ee75e582ab05898533a1dc0a48aa3a661ce3bf733b30c2d3eb8b"))
(2917000, uint256S("0x000001ee27b35fc196cba13fee45d75e3eddad3f1d40610045d0f445c579a6d3"))
(2918000, uint256S("0x000000f2749ba8b02fd0f008c7bbc084660274af778e03a17784413c84704185"))
(2919000, uint256S("0x00000189484712fa646fff55d24adc7c312f300b9b8d1c4b0b755779cb0e7794"))
(2920000, uint256S("0x0000012ac689d379a42028bfe52ca7b8ffe203202b525c1b0271ee2b35ad6402"))
(2921000, uint256S("0x00000041006270f03bbb9e37ede42e75e32e257cac45696f28d225a4b43e6777"))
(2922000, uint256S("0x0000012a7cd291103fc118e49a040c24a08ab1ee04745b2b0f2ea6b2f81e0cca"))
(2923000, uint256S("0x000002d505a407c118df5ed44edae441c0bdc228559ae5a74c5fc3069b92f446"))
(2924000, uint256S("0x000001cffdd7e67d52ba4218752fbf45dfeb9d9c595e00399517a99daecadf69"))
(2925000, uint256S("0x0000011d18edd59d9af62f5a028bc79ead29dee6f8c88e5f7dd87109588cbecb"))
(2926000, uint256S("0x0000015e595dfdb4b59126c9d867f148a7d921d38711bb1533324a52757a4272"))
(2927000, uint256S("0x00000096eb782ea4ba9e047359b02f5848cd6799877c7bc5835cb4bbb299b14f"))
(2928000, uint256S("0x000001c190235fb344abf38fc1dcaa75a3a9c55b8b5f5dd199b1f95cf8d201ed"))
(2929000, uint256S("0x000000c58a7468d05737541fa5df2c1aba6d310fc5c03303e8d72b13d3cb277f"))
(2930000, uint256S("0x00000169380c6a08fb48b379a5ca5d866f18fd9d453a13d6e0b8cf3348f75df1"))
(2931000, uint256S("0x0000019761664eb4d775090c4e24505b1add18096cf42ff856715afa0d1a3e1c"))
(2932000, uint256S("0x0000009f88c63b6c57d1a0b09ab2540eb5f09982234430d0630746991db4e2fb"))
(2933000, uint256S("0x000001829533eec65e9241b1b63742e1359cf55198f089c14f183effe76e8629"))
(2934000, uint256S("0x000000a4040f326a6b4c1fb64a6c3df87b4e9695a944bbac713fb0cfecd8d551"))
(2935000, uint256S("0x000001e58d485a5400e800643db600eb6df312d2832054bac1785207212ca059"))
(2936000, uint256S("0x000001d1bf65e88819f93e9fae9d7d682ac18708756b2f1b7d5d2c7779384942"))
(2937000, uint256S("0x000001df066b347d3037a0ff5769bce7a7832591775116847e52cfece1725c62"))
(2938000, uint256S("0x00000029d4607a9ef85fdfefd8427f6d3ba8585d3e96edf82487c6593fb4f22e"))
(2939000, uint256S("0x000001c21ec8f18b48b6a05817b133630d60c9880bc3be26610f1e3d9dd6a8b0"))
(2940000, uint256S("0x000001c936816ab5875198fe942e3a33bbb030ae8a5defb457e80d01246a5a71"))
(2941000, uint256S("0x0000016ba0563ce8e727c92689b0adf945071d95a6adcce7f65f5f4507762a94"))
(2942000, uint256S("0x00000118035772133444f5facdae2a84b939b12dae23a9658cd39684d4548908"))
(2943000, uint256S("0x000000fa1fbab3a011da37bbe13d2eda64496e69a531705720cb49d3cce8e714"))
(2944000, uint256S("0x0000018a1c728b6e2c6fa6e53ae329810134cceb1203795601cb5e196eba0525"))
(2945000, uint256S("0x000001abab339ba3e21bbc3c65d684f9da927abf65fd0a9308f116b76cf0888e"))
(2946000, uint256S("0x000000532f16a10c6e51877041ba94c946c15a607e4e4c1c654e7e810f95266b"))
(2947000, uint256S("0x00000020c9d00eb7d964325826f2d7d4313b4d24aeac9da2b96951a70ec8cd1a"))
(2948000, uint256S("0x00000189c6bd01c4d8a912dc6f68e59415033e9d99f20a6f8ab895d50af65a72"))
(2949000, uint256S("0x00000222769cdcb49042f93ba8e449292807a2d998123d30d3508c3759c82a93"))
(2950000, uint256S("0x00000116cbd21afdd1da358c3953de242eb4d2710540338e3f92fa66e768420e"))
(2951000, uint256S("0x000000bc86ed75d1e906cf75e7b471a4ffe31ee4ca13a8e69c1c7a4fef3b4069"))
(2952000, uint256S("0x0000020e923c424ae4769114c453a3e31d3215b355703daf631c0afa1dfaab20"))
(2953000, uint256S("0x00000113603a84890a04ce15deecfe025e26bf770a2ae8ec207774851ec851c6"))
(2954000, uint256S("0x000001f3ccfa445ef2603c3c3ed7d41582aeaf3e3da4fdff4587248a586e99b1"))
(2955000, uint256S("0x0000036a1b2e6ac72888d4b97002944f70b4285fe4ac10d970a96ae6e4e5e5b0"))
(2956000, uint256S("0x0000021057f0f2e55f888146dd54726b6b2e94192895a8990fa6e885cbf2cc84"))
(2957000, uint256S("0x00000029aeed195efd6421ec6906b5308a4f54fceaeb2c6066618bd170d9107a"))
(2958000, uint256S("0x000000ea5208d69660156ffacf152a63fc387e9bd92ace8f49e57fa1e6ef60e4"))
(2959000, uint256S("0x000001a8847661b4fdf27afbfb4b187ab405039a86a3d79e612d43649b311196"))
(2960000, uint256S("0x0000017cff8dc295e80f9ed4834bf017a316c2cdd0aff7f11976cc232d9fc30f"))
(2961000, uint256S("0x000001951de8a1b055c0ad7bb01a6042d8041889fba2435676161eaa96807770"))
(2962000, uint256S("0x000001955036b0967f1f30f7e34cf7b146a821b3058081354f4e6eca3263d11b"))
(2963000, uint256S("0x000000cb9e41e2a62dc2ce7678bd73d36f9094686a50ce981394c51f0a7727b6"))
(2964000, uint256S("0x000001277f60eecb3afefae842dcbc73afe966cf218637cb15bc96bfb7c248c4"))
(2965000, uint256S("0x00000024ab8135f931081c88304b55524a5786764da435a2b90109644b5d7386"))
(2966000, uint256S("0x000000dc386c492b9dec3e057c58e4890aeb3befde5b808cedde00d55bd25576"))
(2967000, uint256S("0x0000009a3b16a5fda24ddb6586b455924e604cc9253e257c022c205dc2d8840a"))
(2968000, uint256S("0x000001f135a1265c9b4db02b1e3e4f9cb0b29c97d6804d132c096ebb46330a33"))
(2969000, uint256S("0x000001bd3dc836d79d6b43f7ec6462451f507c49c2c3823cc03a3307c1e57d56"))
(2970000, uint256S("0x0000009c0b2841782b18762e4d14e69bd4c51e75de7c0f161150fc994c175823"))
(2971000, uint256S("0x0000012108b5e9367921844def97f020b0e7a1d80f222c0bf6027ca98535be5c"))
(2972000, uint256S("0x00000167122a71c4fbb63a0ebac10008a9fca86ac21bdcf4e8ae5a0dc4ac0652"))
(2973000, uint256S("0x000001379c7d87e695027690387dfd38512410872fa700b30d4c35dc46600bae"))
(2974000, uint256S("0x0000026fce7c4bb0697f528f2ea14c638e33f51ab2935bcffc7c02b310415ed4"))
(2975000, uint256S("0x0000018a159510b754e5a2ff7692f2235652130a585a13effe7da7294eedd724"))
(2976000, uint256S("0x0000015269b6e9cce2b30466d29abfc2e5d8ae0ae7e4f7cabeabeedd27ea36f2"))
(2977000, uint256S("0x000000243f50f7a27c54469830274f1ab8bf07b417c091aa43d2043d4c731b32"))
(2978000, uint256S("0x000001f171890e15e79ee12b1b3b9af9b788d13533fb09521d584d4989119246"))
(2979000, uint256S("0x000000957807cc7dc451bf5c4df3e2498aecde2927affb9c4b5cdcd824d5f6d9"))
(2980000, uint256S("0x00000207c9d63400d53e902d1ba329a3d80f2e19e412fa3642e21d8d4c642904"))
(2981000, uint256S("0x00000201716079224da4472579700f12449f6d75d34b83c4905d6ba3f3cb2ec7"))
(2982000, uint256S("0x0000002ad8bc77565cdd7e11975ba154c8d92f928bf1579d32e1934e8c44f865"))
(2983000, uint256S("0x000001b88c42d70e22f1907f7705ec09e22407525b1d015a38804a8b5663381a"))
(2984000, uint256S("0x0000006b31e10c313337ac9c8f95c7020bf5873fe9443c65fa8888bcf75cdff2"))
(2985000, uint256S("0x00000235bc5682cd7ffb0dae2255c774165f8a79e43e023db03412d80d128888"))
(2986000, uint256S("0x000000e0d64aa4eae99ce92c3daaa3c724d4771b465a3db45375f3317d77d42a"))
(2987000, uint256S("0x0000021ed226af96bd30a9d34d1944f4d0c9776eeb01bd1792c545a6bebfbf87"))
(2988000, uint256S("0x000000ab09b2d5cc61b7eacaf8c5c75d738c06eef778793569dbfe14249176c6"))
(2989000, uint256S("0x00000045fa48a265d64baa3d6fa0de317280792eb0c90a56d18a805685e1a548"))
(2990000, uint256S("0x0000019e091bf24b8c9b8c8797288dfa9a93f1fb455250b536c048ca491972bb"))
(2991000, uint256S("0x0000025c0b080404dcf8d102ca5e71fa763b3505665bf0d65d2531acea680d75"))
(2992000, uint256S("0x000003055b316b5b22ecd1717eb2190593b188c3b8c413b00cbffbdde0c34b27"))
(2993000, uint256S("0x000000a064bd95745fd853afd6ad73b731d1cd5ff9869c63ce63015fd2fb3bae"))
(2994000, uint256S("0x0000004b0af656104a4235f8aef67de14fafe2326c3141a0c789bec175f91bdc"))
(2995000, uint256S("0x000001c6c7295656d51de8c9707f4360de0d6e545067ff153fa89ec2e47e735e"))
(2996000, uint256S("0x00000125d4e3321be70bfca41bc6de3f7a13b866e1b55663f09d37c4847ed6c2"))
(2997000, uint256S("0x000001e84e1b66cdc616563f3396276635960026f26836f1c7a9baae69174039"))
(2998000, uint256S("0x000001206067731907e85eecc3e0d34b7c1b4fa3ba3dfae97c469ac32282cf9b"))
(2999000, uint256S("0x000000fb239083b6c855bf26455908662aa53c00f076e549e094a9aabbda6f89"))
(3000000, uint256S("0x00000219b1672bb1caabd27e2906287856a042529b15b15d05a2e2ff3efcfe41"))
(3001000, uint256S("0x000000d9d692717108a856946b98e588f49f7331e4f5c04f914d64104aedc61e"))
(3002000, uint256S("0x000000bee95e740ac3c6a6898958517542998189780a4992aa0b233089798653"))
(3003000, uint256S("0x00000102b4ce62b897c5c04eb20ab56a3d439f582e354e84fbb2ab5b03476569"))
(3004000, uint256S("0x00000117ad571f5197fe272be8c19a1b6ce1a72b2c58f0af82c4bb2a700055c9"))
(3005000, uint256S("0x0000018ff82a917dfb1125231f34bcf3dee4142d5b5591cbea8c52d6c97dd25c"))
(3006000, uint256S("0x0000023a1c1b99ee74b10bb4ca8271f9c94aaea42fee9155af21222f20a46f7d"))
(3007000, uint256S("0x0000027c56e38915a13965e04bae1c19968aa5302f463c3a05195fb9795736b6"))
(3008000, uint256S("0x000002dc226002e6df7aac0776a3a8691a5a8b8ac9ea8a44ec36e96c05e48ccb"))
(3009000, uint256S("0x0000018ebbed8ee0047e9797aa8ac2739a0796bfbdded7f862dbb91fbdeeb2cd"))
(3010000, uint256S("0x000002c5039f54af5d24f6b64463a6d5578c2122bc5393553557bfd991b8503d"))
(3011000, uint256S("0x0000015a1fdfc18774a9a984cead52e7fcddfbeda55981885fc0f3badc76d774"))
(3012000, uint256S("0x00000171fb194aee4f625996a18707883d2d6b381ec3696ea858170039f9c54c"))
(3013000, uint256S("0x0000015877955e47bb3a076dcc880facb15ca91a84c1173a534731f1ae327116"))
(3014000, uint256S("0x000000e6b8403f98e24474a424fb8e33c94bccaf98ab0ecbea35092b1ac41fe8"))
(3015000, uint256S("0x000001346352a5dc10c77ab0e3d201e3e1e5d40e747e0f572764267281deaa98"))
(3016000, uint256S("0x0000018e531c45772cf1b63764b899c50f3ad1d21f9f38c360e12030a90dbb8d"))
(3017000, uint256S("0x0000001794d412d1a32e065a3d3a152de00049d5bd64cd109a05d7c93a0bf8dd"))
(3018000, uint256S("0x000001eebb3aaf056a77d129564462bf989ce02693274a3356474d18c3dc9c09"))
(3019000, uint256S("0x000000a32aee249f482dba222b894d2fb42decbad676c18fd6377fd14050a3e9"))
(3020000, uint256S("0x000000e8ddbacb95d03f440ba2943b1ce76ffe4e9f26103554e5cdc7caed3c5a"))
(3021000, uint256S("0x000003b5471ea15bfc621cc24bcc900e4d4f8baa9be34eccb1cc0476180d9e87"))
(3022000, uint256S("0x0000007c7d8e8ca2d4f90bfaeb40362242771ff455b4001609cb1091e5f33870"))
(3023000, uint256S("0x0000015c222a8680576fde9424f9f694c17e3f19a62580e48986479b145cbb2f"))
(3024000, uint256S("0x000000913cdc5f2f4c35a0f342f02332f01a4b48f2a5a5a695bdedf915bc3634"))
(3025000, uint256S("0x0000026468663c251c23cb52805122582c4339d6c543b46259918ca318994b1c"))
(3026000, uint256S("0x000001198858f36489e2b27eea39cd62a8534b41dbc63b86a12d3eb99e7e22f0"))
(3027000, uint256S("0x000002008ec698cb00791d34a8e530c040d8b1aae8115afa1a26637dc645d28d"))
(3028000, uint256S("0x0000015e83194a90f0c59947d2b3f80948decf308b743bd6908ea936cd3894af"))
(3029000, uint256S("0x000000a95be914f4ad8055d9d005ab3d7ad4c8966e93cb5c44117a7bae92b2c2"))
(3030000, uint256S("0x000000368719c99a34efc081085fcefb4bd3b88dc4a720d40f8673702a9862e3"))
(3031000, uint256S("0x0000005f44110660dc10ebca6ca87673ddb85b9b61fca435e3a5d4ec04a17fc5"))
(3032000, uint256S("0x000002a6ffc3d6b4507102b9e2341f7a0a88928ffbdabf973b8e307f7e779ab6"))
(3033000, uint256S("0x0000010a99fa68c65193d4a987acc42ac3b8a5c5f30807be75ca055f93804dcb"))
(3034000, uint256S("0x00000247b6d12d8b55ab1967e27c60bce02715516cf5b87268c69759223a2eda"))
(3035000, uint256S("0x0000010cb46aac3ac625e91a3eb4a033256a2c78ff971f83c4d5a28be9491d3b"))
(3036000, uint256S("0x000000faabdf86474cc7cf1a79dfe3272a7fcf3329f3efa0111711db71dbe27d"))
(3037000, uint256S("0x00000066bf1146fc2b79f78fa0ebbee0145bfc8d378759b0945b1e8af5185f8d"))
(3038000, uint256S("0x000002200065ea197bd831f4395c52697f03b7fb010aafb930a5d4367c7948aa"))
(3039000, uint256S("0x00000351ffd7806659b7ed9ce9206e9d14c45906bc7948aed3288161bfb27c09"))
(3040000, uint256S("0x000002160907e189c825f3ae55081e0818d3a261884e18d56558441ebf95726b"))
(3041000, uint256S("0x00000124dbac3245d964761b3065fcfe998c1dcc17f0760f931531f980cad927"))
(3042000, uint256S("0x00000101ab03831aa195bae540dce8e424a35d4a81cb5c0caf9580939f1be380"))
(3043000, uint256S("0x0000004688fae99072abcf0106b12e06b5e75447cd16f022ad6eb74761249ba8"))
(3044000, uint256S("0x000000228198f80b17094a4bc4a81a4e207b0ef6f50a2ea79b27ed8e65f8a453"))
(3045000, uint256S("0x0000015986950a34fb2cc2698941d110c6eb83484ac62b77fd40931ec34ac62c"))
(3046000, uint256S("0x00000023339b1041301db20fcfd4b48141d6807105fd62f4e45d8b1b7d1f9688"))
(3047000, uint256S("0x0000002c5b54deaa41b058dcd310f82035b4336ed0d5f84e4d2f62446a63399c"))
(3048000, uint256S("0x0000001d191f3c24dcc0d6923b520b11598e7f53e17b11aab8d1de63a4f2c38d"))
(3049000, uint256S("0x000000875c9d83457911687246a5076f892bdf128f8c10bde1629f3d0f179b5d"))
(3050000, uint256S("0x0000043f02004859e2aa5f017529ac20e17bd0b79b9d87d0e7961095b2f68af8"))
(3051000, uint256S("0x000003ae5ab267c9880b89cfdc5015bdff626bd845b3fc5abec814b4ff7b0c2b"))
(3052000, uint256S("0x0000008c0e12c19e5e463a01028628bd462054fd04bc50cce15b85630eb920c3"))
(3053000, uint256S("0x000001d89eacab8a0a3932c575fbd608f418e3f608127255b3cc14ea18b40e5a"))
(3054000, uint256S("0x000003ce6e0bb1d7d4ba63ee9327125d7fd006fd4451f4f5895193cb1e2b5b3b"))
(3055000, uint256S("0x000001f78ddcf7f9240686c2a49dbdf75c0b194721e28dc879636d914ac62f9d"))
(3056000, uint256S("0x000000d8166fbb0615d907037478ea00e5901b92046918578e0257229775a682"))
(3057000, uint256S("0x00001cbe0ed4b0688520567a4bce719d14de4fc91bde5d4f4566680a681e937a"))
(3058000, uint256S("0x0000025f2460a0a968dda345c8e70f3e879176c2469c83e701f44cb38f52757c"))
(3059000, uint256S("0x00000057dcad6c12ea3c82e28f329870d6a2050c851897a0f56ee0c76e9f0162"))
(3060000, uint256S("0x000001a55a082a6b1d942b3b1b9cc28a03337aa256a1be0ac02d842acfb1466f"))
(3061000, uint256S("0x000000a9e328c963548c95730e3cda618efd9a4e59825c1d373a2608d0be7b5b"))
(3062000, uint256S("0x00000037877d10b82a5f27eb8d2e2a3ff036771b02d5822f4a7aa0d2ab6c9916"))
(3063000, uint256S("0x0000005285d80a1f134d5a93d4ab22c221ec8f81a18a39c2ce0d4b0fb141031a"))
(3064000, uint256S("0x00000025cec143c75f0a512e3764ef35d88248b98a32c2b3c70fa8eb1d4c484f"))
(3065000, uint256S("0x0000004d0a40e92fc32ee167cd1483acefc47f9c3829c3c53bdcd5071fbd8e4d"))
(3066000, uint256S("0x00000080e0be0ff49413f1c008a7c0efbee6489ba759e940b0cb0a24f3bef660"))
(3067000, uint256S("0x000000a588ec95f08994f6eb4a75b8c5c292122cbc84bd137c9256a296458c39"))
(3068000, uint256S("0x000000d3e004372c419d50838d88a884f5c9d16f2ac86f681a7b6a590ab58a81"))
(3069000, uint256S("0x000000343322d8cf3339d463ac84e0a3e824b62857ee531901e13fb690f29108"))
(3070000, uint256S("0x000000b5f47d0bedda6805322811da7960af2ba3fc44b327be7bad34b6e598c3"))
(3071000, uint256S("0x0000007812b4eb5028e26a03b46a13f72ace2881844dad9a05655fd4ac270039"))
(3072000, uint256S("0x00000040557067d6dd006fb7b9eed2092d9a61f2a623199f2f0df2222bf5f29e"))
(3073000, uint256S("0x0000004bab6defde89d8e44307cbc0fd37fc7d35d2a86ce3b89a7eaf42a4789f"))
(3074000, uint256S("0x000000234b6b83375ce7f10bb779926eab9369fc739dd0796d9eec0d90e3b2b1"))
(3075000, uint256S("0x0000009303a35d265c46875d5f2d7d60ed6717d84f682c27b7152cb18efb9def"))
(3076000, uint256S("0x000000a0e4cf5b19c37e1cc80175caa636d480b19837d76687a56faffbdac155"))
(3077000, uint256S("0x000000ce1e51298888d8632fda2a9dc0e28dd434fc49e18e290e61e82ed8034a"))
(3078000, uint256S("0x0000001a3446535b04bc8d09822fde3f28bc0d3346155c50dae160b63a4511cd"))
(3079000, uint256S("0x000000f671d6df74a89ec74cb3516fe9e438e60ece06854825372cf6f69830f2"))
(3080000, uint256S("0x000001188832e4820fe420a7ad184ea76c287073aebd09d5c2818492f8af2627"))
(3081000, uint256S("0x0000001320c27f48fa621f99379ecb84304df231bfb07b7e7c527b8a1d684dd7"))
(3082000, uint256S("0x000000b0157a3ecbc11024ba084073f52c2e0c6b65427c34cbf68c76d6ae571f"))
(3083000, uint256S("0x0000004799f50ffe153c16700d064b491f01d07a2e6a8176151fedc01991fcb0"))
(3084000, uint256S("0x00000052c88e3057099d4a2b755efd4addf29d27228093bb5f6783ec955a1706"))
(3085000, uint256S("0x000000033ea79bbc96b0ab2b75fe32b32209510b4768de003edefee17ea77826"))
(3086000, uint256S("0x000000711fcc88d264fc72b62a79e8e64d92f5ab02a3889c69cd61c0407e4474"))
(3087000, uint256S("0x0000002c333ba65cec4eadea04dbf6266af2b1adf0f135540e2d1a4613427204"))
(3088000, uint256S("0x00000098583783298ad125bd415604f068e9ce5579eb4cd9cea63a62b668c4b8"))
(3089000, uint256S("0x000000b28d476e2da580f7b6ea6375536076cd6761409ac219c283661c271681"))
(3090000, uint256S("0x000000529a6ab02121007876e3d940db0df8f66f0b6234170ed5d0cbdef6e85c"))
(3091000, uint256S("0x0000005bb23a78e9c124f2a541bd20e4891ff0ca2ec1b2bbc9a2690807000e5a"))
(3092000, uint256S("0x00000050dd2c2fd8831c30d0e83d18f2d9451e6bd9f73e753ab0b3e0f0ad8bf0"))
(3093000, uint256S("0x00000043ac606b0dce50c3e7ef9709084e99e6ee7abdecd698a0e8e8bbc764e9"))
(3094000, uint256S("0x00000002491170b68e6ba66dd0f8cd92bc6667b28675fcd8225b01870106cb8f"))
(3095000, uint256S("0x000000b05deaf78e215a7f0102065c57c31ce7d51e64980085b396ebd057a697"))
(3096000, uint256S("0x000000ac352984c4ef4a86d26bc35f7a0111c3602d243bc35dba96872d2a118e"))
(3097000, uint256S("0x000000ed65d9033eec729348ce9aa3f79d3d09a0a4c0a63f39c37240c00b9b02"))
(3098000, uint256S("0x000000391a40a7112eae5164d24add9beaf97f95bee6205afd96c1f8e106c4f9"))
(3099000, uint256S("0x000000bb66eedf2c837f6782ee6d3a409f07237e138ffd1b75fff541fc8233c9"))
(3100000, uint256S("0x0000005a501d4322cdb6d67504586362ab7902d116f5ad226b6bfd3d8df23d61"))
(3101000, uint256S("0x000001163c9fd3eec4dd127dc705286b6cf213d607d09074536be8b69188a1a0"))
(3102000, uint256S("0x000000c772df8f9650f3104e7b4db6de8b323da3057fa9a63f18fd101f4d01e9"))
(3103000, uint256S("0x000000c1383c6f337890136f3f59841e45eb0b4c5774ae200c9c01acf88c3e68"))
(3104000, uint256S("0x000000a9926b402d77d7fb5b8a0ee805a62ed40454d7b61ccede5af2af9dd0ea"))
(3105000, uint256S("0x0000006ee4adf77a7c6912fd9ea6e5b505b9d141a35d7159f8bc9a6b4910dbbe"))
(3106000, uint256S("0x0000002bbac066dc52c61e41e33c24f9db9906b5807fa4feff6e235e9f84c1a8"))
(3107000, uint256S("0x000000339fd4105912b6dff330902d45d78e5ab025e0c21b8e2f5d2bd69ecfa7"))
(3108000, uint256S("0x000000bf7d2c31afe10ce9a45aa74d01c5ffe2ab88687bbd9365f7ade64afd62"))
(3109000, uint256S("0x00000017d43890279cbbcf11c169b8c182aaa0e92ee9190ff4445a7eef3f900b"))
(3110000, uint256S("0x0000007c461b5ad5b99029d575f17c58d6ff8a4b662686f1cf7795500f785b43"))
(3111000, uint256S("0x0000008781b7a2e56d7ce5ef4ef2d82760a493dc208542a11a9d4d2b5b54c619"))
(3112000, uint256S("0x00000070c859a6d3763862b231e377bd53ea13e94e1126be02a8538f46404942"))
(3113000, uint256S("0x000000205e90cfa5844d046f76e3fafa4cdaa2f094344361d75b253294a2c679"))
(3114000, uint256S("0x00000029666033d0c6a4bee567d4b4dc124cd4f3b0cc1e9d9700cfbe10e010d3"))
(3115000, uint256S("0x000000a8460d428de6367c79ef5099da8bc2714f61b1c878af9a8ff1d0f591f2"))
(3116000, uint256S("0x0000002fdd9b54d39ea7e1e4c01c6d5e20439373d0d824e5fb8855c77e68355f"))
(3117000, uint256S("0x00000020f413e1f5b61f60bcd35b652cc2f54b8719baef556531975c6977fd47"))
(3118000, uint256S("0x000000742a686626900a16971ba608e3cfe6d892f6c9168d79b80677d3f099f2"))
(3119000, uint256S("0x00000076af5aecd68eddacf266095593ac46926dc746751b38c21498e5f0a3f3"))
(3120000, uint256S("0x000000725ba5ce0adcc752ea1eb5e9e7bf7154093ed5bf33f6895078900a98de"))
(3121000, uint256S("0x000000b8eb16919a565650daeb00701dabe2c8d44c8d9358ae5e48d3d9a7e2a2"))
(3122000, uint256S("0x0000007da03c22e99a008dc5386ed46bd06c5f77f270e08b6714bd4d14247381"))
(3123000, uint256S("0x0000015c6195b1e64f1834f13fdabf2877f5e63349a555c7c5061baebbb03273"))
(3124000, uint256S("0x000001030dd752304091361b353f028b5e2d9a514391e2c7fdb28d5d24858553"))
(3125000, uint256S("0x00000089c9ab264ae498bdb3597655a5ccf36c7139bf58c6c02c58c7162fd2f3"))
(3126000, uint256S("0x0000004d29525a897b4e6c1d70ed2f02cb38f5a27557497c23345ec69932884d"))
(3127000, uint256S("0x0000004efb4bc01160b06186a01ea40cd9ac331347dc2fb966cd52c7191d3aa6"))
(3128000, uint256S("0x000000ef678db1b12ba5fc0441d1c335b8bb630fb4d5a3d404187322861db039"))
(3129000, uint256S("0x000000527da1f996e1c87d030c4de67749ac78a1a725982237d879f314aa379f"))
(3130000, uint256S("0x000000daea0276b41195fd48ad9333ad82d2ee181af09da773eeecb0eed1c2cb"))
(3131000, uint256S("0x00000044826c11188c2bab69afe0f6b02772899bf714152fb10e03ee9b0661d0"))
(3132000, uint256S("0x00000079d9daef2612b90005ce8aaa67dab9e294ff2dd5aa9e513179610fc7ab"))
(3133000, uint256S("0x000000329fb093db7f21b7333bd70738b4c8691c878d8ffd33d0e9bf5ff6bd18"))
(3134000, uint256S("0x00000076b0392040b5c4c8cf5e790da58b639d3d775172592a91530120e5c35d"))
(3135000, uint256S("0x000000f47aae8156dba2b7366fbab94adb9e4d23a9d0ba32568bc5d633ac3924"))
(3136000, uint256S("0x00000139e9f09bd83f69ce3dc8a403e7bcabfc771c496f9a7b64b15a399ccaeb"))
(3137000, uint256S("0x000000b44c44279f8f87b8695d05c9baa32f6f988a7fafedd2e3e3a330c44735"))
(3138000, uint256S("0x000000000806995bac02073f889fce51b84b3cef43a7b874caa7047542442952"))
(3139000, uint256S("0x00000176bc78f1daefe43a2ef7554646a0238f74fe79c5fecca19f9ec65ef6c2"))
(3140000, uint256S("0x000000f1964b55b2f8ca7394fa922353c28457903de925fcb12d60e70d6442f5"))
(3141000, uint256S("0x000000ce310267356a033299a53413c09f503268aa42e730b9e18f14cd16de99"))
(3142000, uint256S("0x0000005ec5af349cadbf59506d69eac221b9802ee84a8aa4f85a380ee9206a85"))
(3143000, uint256S("0x0000000aebc3645e845eb5e7ee46e71891a31e9006fec22d16e5cf9461306783"))
(3144000, uint256S("0x000000ad31bce3cd6065c49f01dcba984c57d9cae9ee60d6b8f509ac0dbcd8bd"))
(3145000, uint256S("0x0000006aba80796f496e4449e57fe5e966ce50fb784564cb6b624e5ac51a7cc6"))
(3146000, uint256S("0x000000ea02b55f81a5baf0091fdb30001e233cbbe61e947f584d9fc0b678efca"))
(3147000, uint256S("0x00000109d8c6b002b587d710a1f573a9a917cfc5197a2bd37b9ec87f365c0535"))
(3148000, uint256S("0x000000b02ad9ce869bb38a6d778e89485f59a880bedbf515d894370e9a1c8fa4"))
(3149000, uint256S("0x00000000dfa5469377e5d5789bdec173669cf7e1421b4ce1538c54461ee62b17"))
(3150000, uint256S("0x0000002ecdc7a4fc85881fcb8c38bdc56f2c91511f9f9e6a8539d16bf2796ab3"))
(3151000, uint256S("0x000000b0ef3e86bf1c6d7cf0e7a9771125688cc034efdc4f0f81483bd6ac5188"))
(3152000, uint256S("0x000000e6b734457317240d3b4c6e1f557b87176d18066229c78c284ba56d8449"))
(3153000, uint256S("0x0000009179388d04e83e1e2ac9f841534dca48232ad5f07a8cd88ce76a9b5ed8"))
(3154000, uint256S("0x000000bf1af2c983b0d44ae46fd625e07c40bfd3e5a28c999344f7b20b373333"))
(3155000, uint256S("0x000000f04ac08ae6358b72b15c1ee77dfaa3746db6ab56da3dba3917ffc1043d"))
(3156000, uint256S("0x00000050afb4db81fd1bbd0cb815ad14af965bb7053bb4bb44720609ef04bf10"))
(3157000, uint256S("0x000001382033d8a5a5aa3f7b6e3ce071bd36c98deb149e226b07606b3a7b7e30"))
(3158000, uint256S("0x000001009ca876f6805a6cb96e6dbba41e9100e82f693e927fad6eebc05d22bc"))
(3159000, uint256S("0x00000183a3f3406d83c258608e4f3db8f4ed019a4a9899b7d297d4c83fb79223"))
(3160000, uint256S("0x000000960ba4a97484d3d16372084e139972634c7cf4d360d1c804178af88b3a"))
(3161000, uint256S("0x0000004db0a19d395855d9b7c2b4680536fdd11decf7d6867504cb67e41c8434"))
(3162000, uint256S("0x00000057f32d96d91e4af0c8c989c3fd3c3e553308b2c44f40ef3b2a548ce941"))
(3163000, uint256S("0x0000006f8bb942cdfc57f00817d4ae2dfa2e88fd1e0a9ab593c3ec34a0bb985f"))
(3164000, uint256S("0x000000f977348314bc2fd571056df11c26779f107de5523ccc05236a75f945e0"))
(3165000, uint256S("0x0000006e5e0cfeff6306b19ad8aa291e8df95bd002ce004085b1a2a5a086392e"))
(3166000, uint256S("0x0000014b28939d4a482e9db58d152328cc040b118379f9a5e104dd8fabb9505d"))
(3167000, uint256S("0x000000228c00ea599df8bb7af090901c243a3b6a9bb210420ab6a82fc0a379cd"))
(3168000, uint256S("0x000001c3f654d7d1f307060e3bd4f5102582d9f6dfc0bd826b735ef0d24eb7e3"))
(3169000, uint256S("0x00000294fbb1ba4fb799e28048eba52216b65130005f67c235129b43af2f2e6a"))
(3170000, uint256S("0x00000017d5572bcd7250eca0ee1533b30dbfd1288f53176a8c7210c3c12514a8"))
(3171000, uint256S("0x00000075a53ba47066a5c9956782166ab67694cf43a2eea255261c011baa8a1f"))
(3172000, uint256S("0x000000c6c61726ca942713df477ac245182809b3d80532e227aa1e3df01b7e9e"))
(3173000, uint256S("0x000000787fd12c128e6bdfe9701e528421cbfdf204ada723733167844fddadd8"))
(3174000, uint256S("0x00000084b72cfe91dbd1624ce8d6dd7b565c76f8b2aa52c05be49e1894580402"))
(3175000, uint256S("0x00000090df5c3a1a35ee87c4bbb67ed828211e320a783b4bd5aa504258856d9e"))
(3176000, uint256S("0x000000c9cd391b1f8baf52ffcd74114ddd6d062b1c74c0203bf16b19d8bc52f2"))
(3177000, uint256S("0x0000000e093102ffd8c978c4786ab345196000b2789325bae1777599de1a7cf4"))
(3178000, uint256S("0x000000e4d359d3b0a1730eac385d9d6bc0c9e2e4cac055492f98c7064c9a2385"))
(3179000, uint256S("0x000000c52858873d506ebf04ab7f767ac9f4144d211a791bfb4c81ac5960ae55"))
(3180000, uint256S("0x000000b70fd9ea8ccc360593d3a75f7ac6400acd40eee22364f39e40092f6772"))
(3181000, uint256S("0x0000010871b5c6ea2c3909d4e1740ecb1d0eb4f435d82f5dec3bfdeb0bf5e649"))
(3182000, uint256S("0x000000b5bf1cbe1871c3aa7bc4b280449803eae763a149ed70cbcafea7ae12ef"))
(3183000, uint256S("0x00000153bf68c72eb0975699099f292dd5987ca42fec7d9bb57dc8a594f92565"))
(3184000, uint256S("0x000000449b96d6723728a7c5e7c6cb7a0509224185f52e91fbb53132e8f993d5"))
(3185000, uint256S("0x0000015b0ec4dacd96c97d8a83cd9942ebc8ad877f9a088c6875391b3beb0523"))
(3186000, uint256S("0x0000003636a9508d1c33f5dc7132f9612eec3960e14aafff853d59d352d78756"))
(3187000, uint256S("0x0000013c19d114806898c4734dd653ebd28158de083adaec3101ddac3b28ee32"))
(3188000, uint256S("0x00000132e633d30682c9939fa914a5f1fbc6c56877a0ea131eb1e5341aa81683"))
(3189000, uint256S("0x0000001d74ce8246a098ac7d95eb5609245dcf7d9df0f0d337696efa33d5e66b"))
(3190000, uint256S("0x0000010e099fb54b916a9d5f0f4495adddad1bb9c947b33d837ed607fa257a7d"))
(3191000, uint256S("0x00000053c6beb3b7788f5ccd5877dc4162eef98a00fe13b0cdc87cd6faf10d6a"))
(3192000, uint256S("0x000000733a88ba30bee717951134f48cdef4acd021d368edaada61e8ec870d0c"))
(3193000, uint256S("0x0000002f8b6b7c7846f94e304b17888c060fb22170b944a46921b3af6461cc47"))
(3194000, uint256S("0x00000026340cc6a617885b3f0a77bd24750f89ab6d43560beb637399d1a45ce1"))
(3195000, uint256S("0x000000a21fad1caa6397cd68bef24a999bb8b61c229e1e450c09392255c93a30"))
(3196000, uint256S("0x00000008e283d3b23e7384d46896550024b1a78c21acb6969baee8a62b83448b"))
(3197000, uint256S("0x00000068f83587ceb858f477db5f0bcdd5934ca2d7e93168e5c64c0710d06f95"))
(3198000, uint256S("0x0000009d94f70e4fe72388913f110578c10aafd8466cd3f3b2f6b21c5066feba"))
(3199000, uint256S("0x00000040c554cfea75cbf9b1ce977c536173951011125c2cf37b32c3fb29e86b"))
(3200000, uint256S("0x0000004a1c3bc726562379a7d7805ba610113ba72816bcbc1f85788a9d6d4ef9"))
(3201000, uint256S("0x000000145ffdbfcfc521b2334509fe3201ad4eeb667ec7e183c4bf9e888fbbb1"))
(3202000, uint256S("0x0000001ded77185502b724ae961a8eac5734b75c0057b4d351e58aaa5962acd6"))
(3203000, uint256S("0x00000056740ca653e79406d309a91728c4825f546f3673757bde15f6ec3f468c"))
(3204000, uint256S("0x00000085da8e803be0813d5617a6a461cc6914ef533abaeaef5d824054516218"))
(3205000, uint256S("0x0000002b7f3ef3b42f132916aa94e4b3d61c0ffeb98c7aca57b51601a3ab61b9"))
(3206000, uint256S("0x00000077bb73b9f7df4c6a6d55217ebdc48ed36b44826479e320d0925eade58e"))
(3207000, uint256S("0x0000007522193a2ef39d4cd7e9512502af313e55d5d8968ccf3226bb9a94bba0"))
(3208000, uint256S("0x00000060832ff1400278327d1f2e5c1a5eb872bee8b9b383e7777ff90fcdd009"))
(3209000, uint256S("0x00000007d8dc51b7ff58651725dc01d65f5dc696d0757d6c14a26e4d8e750e43"))
(3210000, uint256S("0x000000541ff2ced91b5d31a42ec0f405b5e9aa24e95e76c38ce23e904ac10e52"))
(3211000, uint256S("0x000000727913ea052a4a00845fd19fc7024c57e8fae341f5022bfbd4a62bb774"))
(3212000, uint256S("0x0000000ea83220465ca147e8eab43d71776fdedad9cd79e33347db2c98a4c89e"))
(3213000, uint256S("0x00000013b82d62857feacb10aed6c86417c8310431037e386bdf6e352c906bc8"))
(3214000, uint256S("0x00000046e777e7e3a0a20f4e4a16bf8c881c4b32cec5ed6c46b9271c184922f8"))
(3215000, uint256S("0x0000002668dc21fe0252168397ce5b27acf6ff4c39c937c847e62ddacf0f4b36"))
(3216000, uint256S("0x00000060af593fe53d77063152de1bf341eb2e4c72c4f714b7be021a776cede0"))
(3217000, uint256S("0x000000905d46326bcfb55169956a7e73c0885b98bba32defde31c1b9c6ab723a"))
(3218000, uint256S("0x0000004c178fff51b6c7329417988724d19903f47e370ced6edd3ee2bec49c00"))
(3219000, uint256S("0x0000007819ed1bc19308a116eec0837b9826ed78e68ca41d4f718d2a164fcf6f"))
(3220000, uint256S("0x000000f29bdf2175086feb09ee2603d14685a4e58e2641be7ebb03e58402e9f3"))
(3221000, uint256S("0x000000726da4da04803e39d1ad6bc8bd4a956eee49f6629059154564cafe84eb"))
(3222000, uint256S("0x0000005c77415666ca339c55de179d1cd43f4db4de2ac105326b51de549c066c"))
(3223000, uint256S("0x000000a63e3d529ef124c34a64cf168d6db8ba8a22461a20298654bc70aded2c"))
(3224000, uint256S("0x00000068d7fdfc5bf76b1465afd89b2cc05c60f191a28dfedfedd54f68b1d632"))
(3225000, uint256S("0x00000063b8bd774216c7d438c1f07f6d7cc73a1bd03a38a0c7e2d861db679cee"))
(3226000, uint256S("0x0000005a5d9c7a9ffa27f23cad423c2c3e3fcd341be06c912819815cd64135cc"))
,(int64_t) 1787132038, // time of last checkpointed block
(int64_t) 3415780, // total txs
(double) 3012 // txs in the last day before block 3226000
};
} else {

View File

@@ -62,11 +62,15 @@ const std::string CLIENT_NAME = GetArg("-clientname", "DragonX");
#endif
//TODO: clean up this stuff
//! git will put "#define GIT_ARCHIVE 1" on the next line inside archives.
#define GIT_ARCHIVE 1
//! git archives get "#define GIT_ARCHIVE 1" substituted on the next line by export-subst.
//! Do NOT hardcode it: the BUILD_DESC chain below falls back to GIT_COMMIT_ID whenever
//! build.h carries no BUILD_DESC -- any build without git metadata, e.g. from a tarball --
//! so a hardcoded id makes those binaries claim an identity that is not theirs. Until
//! 2026-08-21 this asserted Komodo commit a86845f3dc, dated Feb 2018, on every such build.
//! With it gone that case reports "-unk", which is honest and greppable.
#ifdef GIT_ARCHIVE
#define GIT_COMMIT_ID "a86845f3dc"
#define GIT_COMMIT_DATE "Wed, 21 Feb 2018 16:15:11 +0200"
#define GIT_COMMIT_ID "$Format:%h$"
#define GIT_COMMIT_DATE "$Format:%cD$"
#endif
#define RENDER_BETA_STRING(num) "-beta" DO_STRINGIZE(num)

View File

@@ -29,8 +29,8 @@
//! These need to be macros, as clientversion.cpp's and bitcoin*-res.rc's voodoo requires it
// Must be kept in sync with configure.ac , ugh!
#define CLIENT_VERSION_MAJOR 1
#define CLIENT_VERSION_MINOR 0
#define CLIENT_VERSION_REVISION 3
#define CLIENT_VERSION_MINOR 1
#define CLIENT_VERSION_REVISION 0
#define CLIENT_VERSION_BUILD 50
//! Set to true for release, false for prerelease or test build

View File

@@ -139,3 +139,103 @@ TEST(mnemonic_compat, RawEntropyDiffersFromMnemonicSeed)
const std::string entropyT = DeriveTAddrFromSeedBytes(zeros); // wrong (32-byte)
EXPECT_NE(seedT, entropyT);
}
// New storage form: the HD seed IS the expanded 64-byte BIP39 seed, the chain is
// NOT flagged mnemonic, and the phrase comes from the separate entropy record.
TEST(mnemonic_compat, ExpandedSeedIsStoredDirectly)
{
SelectParams(CBaseChainParams::MAIN);
CWallet wallet;
ASSERT_TRUE(wallet.SetHDSeedFromMnemonic(ABANDON_ART));
// Stored bytes == the 64-byte BIP39 seed, fed to derivation unchanged.
HDSeed stored;
ASSERT_TRUE(wallet.GetHDSeed(stored));
auto raw = stored.RawSeed();
EXPECT_EQ(raw.size(), (size_t)64);
EXPECT_EQ(HexStr(raw.begin(), raw.end()), std::string(SEED64_HEX));
// No CHDChain version bump / no mnemonic flag: an older binary reads this
// wallet and derives the same tree.
EXPECT_FALSE(wallet.GetHDChain().fMnemonicSeed);
EXPECT_LT(wallet.GetHDChain().nVersion, CHDChain::VERSION_HD_MNEMONIC);
HDSeed forDerivation;
ASSERT_TRUE(wallet.GetHDSeedForDerivation(forDerivation));
EXPECT_EQ(forDerivation.RawSeed(), raw);
// The phrase is still exportable, and IsMnemonicSeed() (which gates
// z_exportmnemonic) still says yes.
EXPECT_TRUE(wallet.IsMnemonicSeed());
EXPECT_TRUE(wallet.HaveMnemonicEntropy());
std::string exported;
ASSERT_TRUE(wallet.GetMnemonicPhrase(exported));
EXPECT_EQ(exported, std::string(ABANDON_ART));
}
// Backwards compatibility: a wallet in the OLD form (stored HD seed == 32-byte
// entropy, fMnemonicSeed = true) must still derive and still export its phrase.
TEST(mnemonic_compat, LegacyEntropySeedStillWorks)
{
SelectParams(CBaseChainParams::MAIN);
RawHDSeed zeros(32, 0), seed64;
ASSERT_TRUE(Bip39SeedFromEntropy(zeros, seed64));
CWallet wallet;
{
LOCK(wallet.cs_wallet);
RawHDSeed entropy(32, 0);
HDSeed legacy(entropy);
ASSERT_TRUE(wallet.InstallHDSeed(legacy, true, 1));
}
EXPECT_TRUE(wallet.GetHDChain().fMnemonicSeed);
EXPECT_FALSE(wallet.HaveMnemonicEntropy());
EXPECT_TRUE(wallet.IsMnemonicSeed());
// Still expanded on read -> same key tree as the new form.
HDSeed forDerivation;
ASSERT_TRUE(wallet.GetHDSeedForDerivation(forDerivation));
EXPECT_EQ(forDerivation.RawSeed(), seed64);
{
LOCK(wallet.cs_wallet);
EXPECT_EQ(EncodePaymentAddress(wallet.GenerateNewSaplingZKey()),
DeriveZAddrFromSeed64(seed64));
}
std::string exported;
ASSERT_TRUE(wallet.GetMnemonicPhrase(exported));
EXPECT_EQ(exported, std::string(ABANDON_ART));
}
// The entropy record refuses replacement, and a phrase that does not restore the
// installed seed is never printed.
TEST(mnemonic_compat, MnemonicEntropyGuards)
{
SelectParams(CBaseChainParams::MAIN);
RawHDSeed zeros(32, 0), ones(32, 1);
// Refuse-to-replace.
CWallet wallet;
ASSERT_TRUE(wallet.SetHDSeedFromMnemonic(ABANDON_ART));
EXPECT_FALSE(wallet.SetMnemonicEntropy(ones));
EXPECT_FALSE(wallet.SetMnemonicEntropy(RawHDSeed())); // empty is not "installed"
// Mismatched entropy -> no phrase. Install a 64-byte seed that is NOT the
// expansion of `zeros`, then attach `zeros` as entropy.
RawHDSeed otherSeed64;
ASSERT_TRUE(Bip39SeedFromEntropy(ones, otherSeed64));
CWallet mismatched;
ASSERT_TRUE(mismatched.SetHDSeedFromHex(HexStr(otherSeed64.begin(), otherSeed64.end())));
ASSERT_TRUE(mismatched.SetMnemonicEntropy(zeros));
std::string phrase;
EXPECT_FALSE(mismatched.GetMnemonicPhrase(phrase));
// Matching entropy attached to a hex-restored wallet -> phrase available.
CWallet matched;
ASSERT_TRUE(matched.SetHDSeedFromHex(std::string(SEED64_HEX)));
ASSERT_TRUE(matched.SetMnemonicEntropy(zeros));
ASSERT_TRUE(matched.GetMnemonicPhrase(phrase));
EXPECT_EQ(phrase, std::string(ABANDON_ART));
}

View File

@@ -471,7 +471,7 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-hdtransparent", strprintf(_("Derive transparent addresses from the HD seed so they can be recovered from it (default: %u)"), 1));
strUsage += HelpMessageOpt("-hdseed=<hex>", _("Restore a fresh/empty wallet from a 32- or 64-byte HD seed hex (the value shown in z_exportwallet's '# HDSeed=' line). WARNING: exposes the seed to your shell history and process list."));
strUsage += HelpMessageOpt("-mnemonic=<words>", _("Restore/create a fresh/empty wallet from a BIP39 seed phrase, compatible with SilentDragonXLite (English, no passphrase; cross-wallet restore parity is mainnet-only -- testnet/regtest derive a different HD coin_type). WARNING: exposes the phrase to your shell history and process list; prefer DRAGONX.conf with tight permissions."));
strUsage += HelpMessageOpt("-usemnemonic", strprintf(_("Create new wallets from a fresh BIP39 seed phrase so the 24 words can be exported (z_exportmnemonic) and used in SilentDragonXLite (default: %u)"), 0));
strUsage += HelpMessageOpt("-usemnemonic", strprintf(_("Create new wallets from a fresh BIP39 seed phrase so the 24 words can be exported (z_exportmnemonic) and used in SilentDragonXLite. Set to 0 for a raw random seed with no recovery phrase; existing wallets are never changed (default: %u)"), 1));
strUsage += HelpMessageOpt("-hdtransparentgaplimit=<n>", strprintf(_("On -mnemonic/-hdseed restore, pre-derive this many HD transparent keys so a rescan can find coinbase paid to them (default: %u)"), 1000));
strUsage += HelpMessageOpt("-mnemonicsaplinggap=<n>", strprintf(_("On -mnemonic/-hdseed restore, pre-derive this many shielded (Sapling) addresses so a rescan can find notes sent to them (default: %u)"), 100));
strUsage += HelpMessageOpt("-consolidation", _("Enable auto Sapling note consolidation (default: false)"));
@@ -489,7 +489,7 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-zsweepexternal", _("Enable sweeping to an external wallet (default false)"));
strUsage += HelpMessageOpt("-zsweepexclude", _("Addresses to exclude from sweeping (default none)"));
strUsage += HelpMessageOpt("-autoshield", _("Automatically shield matured coinbase (mining rewards) into a wallet z-address (default: true). No-op when not mining or wallet is locked."));
strUsage += HelpMessageOpt("-autoshield", _("Automatically shield matured coinbase (mining rewards) into a seed-derived wallet z-address (default: true for wallets created or restored by this software, false when the HD seed provenance is unknown). No-op when not mining or wallet is locked."));
strUsage += HelpMessageOpt("-autoshieldinterval", strprintf(_("Block interval between automatic coinbase-shielding rounds (default: %i, min 5)"), 25));
strUsage += HelpMessageOpt("-autoshieldaddress=<zaddr>", _("Destination Sapling z-address for auto-shielded coinbase (default: reuse or create a wallet z-address). Must be spendable by this wallet."));
strUsage += HelpMessageOpt("-autoshieldfee", strprintf(_("Fee in puposhis for automatic coinbase-shielding transactions (default: %i)"), 10000));
@@ -2307,6 +2307,23 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
if (!pwalletMain->HaveHDSeed())
{
// Does this wallet predate the seed we are about to install? If so,
// that seed cannot appear in any backup the user already holds.
// Checked BEFORE installing, and before the restore path pre-derives
// its gap of keys.
bool fWalletHadContent = false;
{
LOCK(pwalletMain->cs_wallet);
std::set<CKeyID> setExistingKeys;
pwalletMain->GetKeys(setExistingKeys); // keystore.h:60 / crypter.h:212
std::set<libzcash::SaplingPaymentAddress> setExistingZAddrs;
pwalletMain->GetSaplingPaymentAddresses(setExistingZAddrs); // keystore.h:226-238
fWalletHadContent = !setExistingKeys.empty() ||
!setExistingZAddrs.empty() ||
!pwalletMain->mapWallet.empty() || // wallet.h:1041
pwalletMain->IsCrypted(); // crypter.h:174
}
std::string mnemonic = GetArg("-mnemonic", "");
std::string hdSeedHex = GetArg("-hdseed", "");
bool restoring = false;
@@ -2338,6 +2355,19 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
pwalletMain->GenerateNewSeed();
}
pwalletMain->SetHDSeedOrigin(restoring
? CWallet::HDSEED_ORIGIN_RESTORED
: (fWalletHadContent ? CWallet::HDSEED_ORIGIN_RETROFIT
: CWallet::HDSEED_ORIGIN_CREATED));
if (pwalletMain->hdSeedOrigin == CWallet::HDSEED_ORIGIN_RETROFIT)
{
LogPrintf("%s: WARNING: generated a new HD seed for a wallet that already held keys or "
"transactions. This seed is in NO backup you made before now.\n", __func__);
InitWarning(_("A new HD seed was generated for this pre-existing wallet. Any backup you "
"made before now does not contain it: back the wallet up again "
"(z_exportwallet) before receiving funds to newly derived addresses."));
}
if (restoring)
{
// Pre-derive keys (birthday = genesis) so the startup rescan finds
@@ -2356,6 +2386,15 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
LogPrintf("%s: pre-derived %d transparent and %d sapling keys for restore rescan\n", __func__, (int)tGap, (int)zGap);
}
}
else if (pwalletMain->hdSeedOrigin == CWallet::HDSEED_ORIGIN_UNRECORDED)
{
// The seed was installed by a build that predates this record, so
// we cannot tell whether it was minted onto a pre-existing wallet
// (and is therefore absent from the user's older backups). Assume
// the worst; the user can still opt in explicitly.
pwalletMain->SetHDSeedOrigin(CWallet::HDSEED_ORIGIN_UNKNOWN);
LogPrintf("%s: HD seed predates seed-provenance recording; recorded origin as unknown\n", __func__);
}
//Set Sapling Consolidation
pwalletMain->fSaplingConsolidationEnabled = GetBoolArg("-consolidation", false);
@@ -2460,7 +2499,21 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
//Set Automatic Coinbase Shielding (default ON, conditional: self-guards
//on nodes where it cannot act - no owned coinbase, external mineraddress,
//or locked wallet). Closes the transparent-coinbase leak for miners.
pwalletMain->fAutoShieldEnabled = GetBoolArg("-autoshield", true);
// Default ON only when this wallet's HD seed provenance says the
// destination is genuinely recoverable. Autoshield sends mined coinbase to
// a seed-derived z-address (resolveDestination), so for a seed retrofitted
// onto a pre-existing wallet - or one predating provenance recording - we
// cannot assume the user holds it. Those wallets opt in with -autoshield=1
// after backing the seed up.
const bool fAutoShieldSeedKnown =
(pwalletMain->hdSeedOrigin == CWallet::HDSEED_ORIGIN_CREATED ||
pwalletMain->hdSeedOrigin == CWallet::HDSEED_ORIGIN_RESTORED);
pwalletMain->fAutoShieldEnabled = GetBoolArg("-autoshield", fAutoShieldSeedKnown);
if (!fAutoShieldSeedKnown && !mapArgs.count("-autoshield")) {
LogPrintf("%s: autoshield left OFF by default: HD seed origin %d is not known-recoverable. "
"Back the seed up (z_exportwallet, or z_exportmnemonic on a mnemonic wallet) and "
"pass -autoshield=1 to enable.\n", __func__, pwalletMain->hdSeedOrigin);
}
if (pwalletMain->fAutoShieldEnabled) {
int autoShieldInterval = GetArg("-autoshieldinterval", 25);
if (autoShieldInterval < 5) {

View File

@@ -68,6 +68,42 @@ bool CBasicKeyStore::GetHDSeed(HDSeed& seedOut) const
}
}
bool CBasicKeyStore::SetMnemonicEntropy(const RawHDSeed& entropy)
{
LOCK(cs_SpendingKeyStore);
if (entropy.empty()) {
// Never "install" nothing: HaveMnemonicEntropy() would stay false while
// the caller was told the call succeeded.
return false;
}
if (!mnemonicEntropy.empty()) {
// Same refuse-to-replace rule as SetHDSeed above, for a sharper reason:
// this is the printable form of the seed. If it could be swapped while
// hdSeed stayed put, the wallet would print a seed phrase that does not
// restore it -- strictly worse than printing none.
return false;
}
mnemonicEntropy = entropy;
return true;
}
bool CBasicKeyStore::HaveMnemonicEntropy() const
{
LOCK(cs_SpendingKeyStore);
return !mnemonicEntropy.empty();
}
bool CBasicKeyStore::GetMnemonicEntropy(RawHDSeed& entropyOut) const
{
LOCK(cs_SpendingKeyStore);
if (mnemonicEntropy.empty()) {
return false;
} else {
entropyOut = mnemonicEntropy;
return true;
}
}
bool CBasicKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey)
{
LOCK(cs_KeyStore);

View File

@@ -117,6 +117,12 @@ class CBasicKeyStore : public CKeyStore
{
protected:
HDSeed hdSeed;
// BIP39 entropy for a mnemonic-recoverable wallet, kept BESIDE hdSeed, never
// instead of it. hdSeed holds the bytes actually fed to derivation (the
// expanded 64-byte BIP39 seed on new wallets); this record exists only so the
// seed phrase can be reprinted. Empty on legacy and hex-restored wallets,
// which is a normal state, not an error.
RawHDSeed mnemonicEntropy;
KeyMap mapKeys;
ScriptMap mapScripts;
WatchOnlySet setWatchOnly;
@@ -129,6 +135,18 @@ public:
bool SetHDSeed(const HDSeed& seed);
bool HaveHDSeed() const;
bool GetHDSeed(HDSeed& seedOut) const;
//! Mnemonic entropy: optional, present only on phrase-recoverable wallets.
//! Unlike the three seed accessors above -- which override pure virtuals on
//! CKeyStore (keystore.h:48-51) and therefore dispatch dynamically -- these
//! are plain non-virtual members: CKeyStore declares nothing for them and
//! nothing reaches the entropy through a base pointer. Every caller holds a
//! CWallet*, whose static type resolves to the CCryptoKeyStore overloads.
//! Do NOT add them to CKeyStore: that would force all four subclasses
//! (CBasicKeyStore, CCryptoKeyStore, CWallet, gtest's TestCCryptoKeyStore)
//! to implement them for zero call sites.
bool SetMnemonicEntropy(const RawHDSeed& entropy);
bool HaveMnemonicEntropy() const;
bool GetMnemonicEntropy(RawHDSeed& entropyOut) const;
bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey);
bool HaveKey(const CKeyID &address) const

View File

@@ -5155,10 +5155,15 @@ int32_t hush_checkPOW(int32_t slowflag,CBlock *pblock,int32_t height);
// RAII: save+restore the thread-local RandomX-skip flag around the verify-once dedup in CheckBlock,
// so it can never clobber the miner's own fSkipRandomXValidation (TestBlockValidity -> ConnectBlock
// re-entry) nor leak TRUE on an exception thrown out of hush_checkPOW.
// `arm` is false on paths where CheckBlockHeader did NOT reach its own RandomX check, so the
// dedup can never suppress the only verification a block gets.
struct ScopedRandomXSkip {
bool prev;
ScopedRandomXSkip() : prev(GetSkipRandomXValidation()) { SetSkipRandomXValidation(true); }
~ScopedRandomXSkip() { SetSkipRandomXValidation(prev); }
bool armed;
explicit ScopedRandomXSkip(bool arm) : prev(GetSkipRandomXValidation()), armed(arm) {
if (armed) SetSkipRandomXValidation(true);
}
~ScopedRandomXSkip() { if (armed) SetSkipRandomXValidation(prev); }
};
bool CheckBlock(int32_t *futureblockp,int32_t height,CBlockIndex *pindex,const CBlock& block, CValidationState& state,
libzcash::ProofVerifier& verifier,
@@ -5168,7 +5173,8 @@ bool CheckBlock(int32_t *futureblockp,int32_t height,CBlockIndex *pindex,const C
// These are checks that are independent of context.
hash = block.GetHash();
// Check that the header is valid (particularly PoW). This is mostly redundant with the call in AcceptBlockHeader.
if (!CheckBlockHeader(futureblockp,height,pindex,block,state,fCheckPOW))
const bool fHeaderChecked = CheckBlockHeader(futureblockp,height,pindex,block,state,fCheckPOW);
if (!fHeaderChecked)
{
if ( *futureblockp == 0 )
{
@@ -5196,7 +5202,11 @@ bool CheckBlock(int32_t *futureblockp,int32_t height,CBlockIndex *pindex,const C
// that dominates IBD). The scoped guard saves/restores the skip flag (never hardcodes false)
// so the miner's own skip is preserved and nothing leaks on throw. Equihash + PoW-target in
// hush_checkPOW still run.
ScopedRandomXSkip _rxskip;
// ARMED ONLY IF fHeaderChecked: CheckBlockHeader returns early -- BEFORE its RandomX
// check -- for a future-timestamped block (*futureblockp==1), and CheckBlock keeps
// going on that path. There hush_checkPOW is the ONLY RandomX verification, so
// arming unconditionally drops the check for that class of block.
ScopedRandomXSkip _rxskip(fHeaderChecked);
if ( hush_checkPOW(1,(CBlock *)&block,height) < 0 )
return state.DoS(100, error("CheckBlock: failed slow_checkPOW"),REJECT_INVALID, "failed-slow_checkPOW");
}

View File

@@ -93,13 +93,30 @@ void AsyncRPCOperation_autoshieldcoinbase::main() {
getId(), getStateAsString(), numTxCreated_, FormatMoney(amountShielded_));
}
// Enumerate wallet-owned Sapling addresses and pick a spendable one; if none
// exists, generate a fresh one (needs an unlocked wallet, which the caller has
// already ensured). Caller must hold cs_wallet.
// Resolve the Sapling destination for auto-shielded coinbase.
//
// Recoverability is the hard requirement: coinbase we shield must land in an
// address that a bare -mnemonic/-hdseed restore of THIS wallet's seed re-derives
// on its own. A restore pre-derives exactly -mnemonicsaplinggap sapling accounts
// starting at index 0, with saplingAccountCounter reset to 0 (init.cpp:2349-2355),
// so the only self-recoverable destinations are the default addresses of
// m/32'/<coin>'/i' for i < gap.
//
// We therefore DERIVE those accounts from the seed and pick the lowest index the
// wallet already holds. Deriving is the only authoritative test. In particular
// CKeyMetadata is NOT evidence of provenance: z_importkey / z_importwallet copy
// both hdKeypath and seedFp verbatim out of the import source
// (wallet.cpp:5522-5529 <- rpcdump.cpp:511-516), so a foreign key can claim any
// keypath and any seed fingerprint. Filtering on metadata would let an imported
// key win as "account 0" and silently receive every shielded reward.
//
// Caller must hold cs_wallet and must already have checked the wallet is unlocked.
bool AsyncRPCOperation_autoshieldcoinbase::resolveDestination(
libzcash::SaplingPaymentAddress& destOut, std::string& destStrOut) {
// 1. Explicit -autoshieldaddress override (validated + spend-key-checked at init)
// 1. Explicit -autoshieldaddress override (validated as a spendable Sapling
// zaddr at init.cpp:2494-2506). This also serves as the per-process cache
// for whatever step 2/3 resolved.
if (!pwalletMain->autoShieldAddress.empty()) {
auto decoded = DecodePaymentAddress(pwalletMain->autoShieldAddress);
if (boost::get<libzcash::SaplingPaymentAddress>(&decoded) != nullptr) {
@@ -111,30 +128,105 @@ bool AsyncRPCOperation_autoshieldcoinbase::resolveDestination(
return false;
}
// 2. Reuse the first spendable wallet-owned Sapling address (std::set order
// is deterministic, so this is stable across rounds/restarts).
std::set<libzcash::SaplingPaymentAddress> addrs;
pwalletMain->GetSaplingPaymentAddresses(addrs);
for (const auto& a : addrs) {
libzcash::SaplingExtendedSpendingKey extsk;
if (pwalletMain->GetSaplingExtendedSpendingKey(a, extsk)) {
destOut = a;
destStrOut = EncodePaymentAddress(a);
// Cache it so we keep reusing the same address.
// 2. Walk the restore window m/32'/coin'/[0, gap)' derived from the seed.
HDSeed seed;
if (!pwalletMain->GetHDSeedForDerivation(seed)) {
LogPrintf("%s: no HD seed available; refusing to pick an autoshield destination\n", getId());
return false;
}
// Mirror init.cpp:2349-2350's own clamp, and cap into the hardened index
// space so (i | ZIP32_HARDENED_KEY_LIMIT) below stays well formed.
int64_t gapArg = GetArg("-mnemonicsaplinggap", 100);
if (gapArg < 0) {
gapArg = 0;
}
if (gapArg > (int64_t)ZIP32_HARDENED_KEY_LIMIT) {
gapArg = (int64_t)ZIP32_HARDENED_KEY_LIMIT;
}
const uint32_t saplingGap = (uint32_t)gapArg;
// Same derivation path as CWallet::GenerateNewSaplingZKey (wallet.cpp:139-152).
const uint32_t bip44CoinType = Params().BIP44CoinType();
auto m = libzcash::SaplingExtendedSpendingKey::Master(seed);
auto m_32h = m.Derive(32 | ZIP32_HARDENED_KEY_LIMIT);
auto m_32h_cth = m_32h.Derive(bip44CoinType | ZIP32_HARDENED_KEY_LIMIT);
for (uint32_t i = 0; i < saplingGap; i++) {
auto xsk = m_32h_cth.Derive(i | ZIP32_HARDENED_KEY_LIMIT);
auto addr = xsk.DefaultAddress();
// Spendable AND registered: GetSaplingExtendedSpendingKey resolves
// addr -> ivk -> fvk -> spending key (keystore.cpp:215-223), so a hit
// means the wallet both recognises notes sent to `addr` and can spend
// them. Exactly the pair of properties the shield needs. Lowest index
// wins: stable for the life of the wallet and reproducible from the seed
// alone, unlike std::set order over the random diversifier
// (zcash/Address.hpp:95-98).
libzcash::SaplingExtendedSpendingKey held;
if (pwalletMain->GetSaplingExtendedSpendingKey(addr, held)) {
destOut = addr;
destStrOut = EncodePaymentAddress(addr);
// Cache for the life of the process; step 1 short-circuits later
// rounds. Safe: we only cache post-validation.
pwalletMain->autoShieldAddress = destStrOut;
LogPrintf("%s: autoshield destination %s (seed-derived sapling account %u, gap %u)\n",
getId(), destStrOut, (unsigned)i, (unsigned)saplingGap);
return true;
}
}
// 3. No spendable z-addr yet: create one (requires unlocked wallet / HD seed).
if (pwalletMain->IsLocked()) {
// 3. Nothing usable in the window yet: derive the next account, but only if
// GenerateNewSaplingZKey will land INSIDE the window. It does NOT derive
// at saplingAccountCounter: its do/while skips every index whose spending
// key we already hold (wallet.cpp:150-157), so a bare "counter < gap"
// test is unsound - counter 98 with gap 100 can still land on 100.
// Predict min{ i >= counter : we do not hold i } instead.
const uint32_t counter = pwalletMain->GetHDChain().saplingAccountCounter;
uint32_t predicted = saplingGap; // sentinel: "would land outside the window"
for (uint32_t i = counter; i < saplingGap; i++) {
auto xsk = m_32h_cth.Derive(i | ZIP32_HARDENED_KEY_LIMIT);
if (!pwalletMain->HaveSaplingSpendingKey(xsk.expsk.full_viewing_key())) {
predicted = i;
break;
}
}
if (predicted == saplingGap) {
LogPrintf("%s: no free sapling account below -mnemonicsaplinggap=%u (account counter is %u). "
"A newly derived z-address would NOT be re-derived by a seed restore, so the "
"shielded coinbase could not be recovered from the seed alone. Refusing to "
"autoshield this round. Fix: point -autoshieldaddress at an existing in-gap "
"wallet z-address, or raise -mnemonicsaplinggap here AND use the same value on "
"any future restore.\n",
getId(), (unsigned)saplingGap, (unsigned)counter);
return false;
}
if (pwalletMain->IsLocked()) {
LogPrintf("%s: wallet is locked; cannot derive an autoshield destination z-address\n", getId());
return false;
}
try {
destOut = pwalletMain->GenerateNewSaplingZKey();
destStrOut = EncodePaymentAddress(destOut);
auto expectedAddr = m_32h_cth.Derive(predicted | ZIP32_HARDENED_KEY_LIMIT).DefaultAddress();
libzcash::SaplingPaymentAddress newAddr = pwalletMain->GenerateNewSaplingZKey();
// Post-verify rather than trust the prediction: cheap, and it closes the
// whole class of "the counter moved further than expected" bugs.
if (!(newAddr == expectedAddr)) {
LogPrintf("%s: newly derived z-address is not sapling account %u as predicted "
"(counter %u -> %u); not using it as the autoshield destination\n",
getId(), (unsigned)predicted, (unsigned)counter,
(unsigned)pwalletMain->GetHDChain().saplingAccountCounter);
return false;
}
destOut = newAddr;
destStrOut = EncodePaymentAddress(newAddr);
pwalletMain->autoShieldAddress = destStrOut;
LogPrintf("%s: generated new autoshield destination z-address %s\n", getId(), destStrOut);
LogPrintf("%s: generated new autoshield destination z-address %s (seed-derived sapling account %u)\n",
getId(), destStrOut, (unsigned)predicted);
return true;
} catch (const std::exception& e) {
LogPrintf("%s: could not generate a destination z-address: %s\n", getId(), e.what());

View File

@@ -159,6 +159,34 @@ static bool DecryptHDSeed(
return seed.Fingerprint() == seedFp;
}
uint256 MnemonicEntropyFingerprint(const RawHDSeed& entropy)
{
// The local copy is not gratuitous -- see the declaration in crypter.h.
// It is secure_allocator-backed, so it is memory_cleanse()d on destruction
// (support/allocators/secure.h:45-52).
RawHDSeed tmp(entropy);
return HDSeed(tmp).Fingerprint();
}
static bool DecryptMnemonicEntropy(
const CKeyingMaterial& vMasterKey,
const std::vector<unsigned char>& vchCryptedSecret,
const uint256& entropyFp,
RawHDSeed& entropyOut)
{
CKeyingMaterial vchSecret;
// Use the entropy's fingerprint as IV, mirroring DecryptHDSeed above.
if (!DecryptSecret(vMasterKey, vchCryptedSecret, entropyFp, vchSecret))
return false;
// RawHDSeed and CKeyingMaterial are the SAME type (both are
// std::vector<unsigned char, secure_allocator<unsigned char>>), so this is a
// plain copy of the same bytes, not a reinterpretation.
entropyOut = vchSecret;
return MnemonicEntropyFingerprint(entropyOut) == entropyFp;
}
static bool DecryptKey(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& vchCryptedSecret, const CPubKey& vchPubKey, CKey& key)
{
CKeyingMaterial vchSecret;
@@ -233,6 +261,19 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn)
keyPass = true;
}
}
// Deliberately NO arm here for cryptedMnemonicEntropy. This function is
// the "some keys decrypt but not all" corruption detector and a keyFail
// ends at the assert(false) below. The mnemonic entropy is an optional,
// non-spending, display-only record: legacy wallets, hex-restored
// wallets and every wallet predating this feature legitimately have a
// seed and no entropy, and a wallet whose every key decrypts while its
// entropy does not is not corrupt in any sense that should abort the
// process -- it simply cannot print its seed phrase. It is decrypted
// lazily in GetMnemonicEntropy() instead, so that case becomes a false
// return from one RPC while derivation and spending (which read the
// seed, not the entropy) carry on. Note the arm above caches nothing
// either -- `seed` is discarded; it only votes keyPass/keyFail -- so
// nothing is lost by omitting one here.
CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin();
for (; mi != mapCryptedKeys.end(); ++mi)
{
@@ -344,6 +385,82 @@ bool CCryptoKeyStore::GetHDSeed(HDSeed& seedOut) const
return DecryptHDSeed(vMasterKey, cryptedHDSeed.second, cryptedHDSeed.first, seedOut);
}
bool CCryptoKeyStore::SetMnemonicEntropy(const RawHDSeed& entropy)
{
{
LOCK(cs_SpendingKeyStore);
if (!IsCrypted()) {
return CBasicKeyStore::SetMnemonicEntropy(entropy);
}
if (IsLocked())
return false;
if (entropy.empty())
return false;
std::vector<unsigned char> vchCryptedSecret;
// Use the entropy's fingerprint as IV
// TODO: Handle this properly when we make encryption a supported feature
auto entropyFp = MnemonicEntropyFingerprint(entropy);
// RawHDSeed IS CKeyingMaterial, so `entropy` binds directly here.
if (!EncryptSecret(vMasterKey, entropy, entropyFp, vchCryptedSecret))
return false;
// Virtual: this calls into CWallet to store the crypted entropy to disk.
if (!SetCryptedMnemonicEntropy(entropyFp, vchCryptedSecret))
return false;
}
return true;
}
bool CCryptoKeyStore::SetCryptedMnemonicEntropy(
const uint256& entropyFp,
const std::vector<unsigned char>& vchCryptedSecret)
{
{
LOCK(cs_SpendingKeyStore);
if (!IsCrypted()) {
return false;
}
if (!cryptedMnemonicEntropy.first.IsNull()) {
// Don't allow existing entropy to be changed, mirroring
// SetCryptedHDSeed: a phrase that no longer matches the installed
// seed is worse than no phrase at all.
return false;
}
cryptedMnemonicEntropy = std::make_pair(entropyFp, vchCryptedSecret);
}
return true;
}
bool CCryptoKeyStore::HaveMnemonicEntropy() const
{
LOCK(cs_SpendingKeyStore);
if (!IsCrypted())
return CBasicKeyStore::HaveMnemonicEntropy();
return !cryptedMnemonicEntropy.second.empty();
}
bool CCryptoKeyStore::GetMnemonicEntropy(RawHDSeed& entropyOut) const
{
LOCK(cs_SpendingKeyStore);
if (!IsCrypted())
return CBasicKeyStore::GetMnemonicEntropy(entropyOut);
if (cryptedMnemonicEntropy.second.empty())
return false;
// Decrypted lazily, on demand, and deliberately NOT in Unlock(): see the
// comment there for why the entropy must not vote in the keyPass/keyFail
// corruption detector.
return DecryptMnemonicEntropy(vMasterKey, cryptedMnemonicEntropy.second,
cryptedMnemonicEntropy.first, entropyOut);
}
bool CCryptoKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey)
{
{
@@ -505,6 +622,30 @@ bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn)
}
hdSeed = HDSeed();
}
if (!mnemonicEntropy.empty()) {
{
std::vector<unsigned char> vchCryptedSecret;
// Use the entropy's fingerprint as IV
// TODO: Handle this properly when we make encryption a supported feature
auto entropyFp = MnemonicEntropyFingerprint(mnemonicEntropy);
if (!EncryptSecret(vMasterKeyIn, mnemonicEntropy, entropyFp, vchCryptedSecret)) {
return false;
}
// Virtual: calls into CWallet to store the crypted entropy to disk.
if (!SetCryptedMnemonicEntropy(entropyFp, vchCryptedSecret)) {
return false;
}
}
// Drop the plaintext. swap() rather than `= RawHDSeed()`: assigning a
// shorter vector destroys the elements but KEEPS the capacity, so the
// old bytes would linger in the locked buffer. swap() hands the buffer
// to a temporary whose destructor deallocates it, and
// secure_allocator::deallocate memory_cleanse()s
// (support/allocators/secure.h:45-52). The `hdSeed = HDSeed();` above
// has the same weakness but cannot be fixed here: HDSeed's raw vector
// is private with no swap accessor (zip32.h:23-33).
RawHDSeed().swap(mnemonicEntropy);
}
BOOST_FOREACH(KeyMap::value_type& mKey, mapKeys)
{
const CKey &key = mKey.second;

View File

@@ -138,6 +138,21 @@ public:
}
};
/** Keystore which keeps the private keys encrypted.
* It derives from the basic key store, which is used if no encryption is active.
*/
//! Fingerprint of a BIP39 entropy blob, computed exactly as HDSeed::Fingerprint
//! does (BLAKE2b, ZCASH_HD_SEED_FP_PERSONAL). It is an IV / integrity tag and a
//! wallet.dat record key -- never a key-derivation input. Declared here rather
//! than duplicated because three call sites must produce identical bytes:
//! CCryptoKeyStore::SetMnemonicEntropy, CCryptoKeyStore::EncryptKeys, and
//! CWallet::SetMnemonicEntropy (which keys the plaintext record with it).
//!
//! It takes a copy internally on purpose: HDSeed's constructor takes a NON-const
//! RawHDSeed& (zip32.h:28), so HDSeed(entropy).Fingerprint() does not compile
//! against a const reference or a member read from a const method.
uint256 MnemonicEntropyFingerprint(const RawHDSeed& entropy);
/** Keystore which keeps the private keys encrypted.
* It derives from the basic key store, which is used if no encryption is active.
*/
@@ -145,6 +160,10 @@ class CCryptoKeyStore : public CBasicKeyStore
{
private:
std::pair<uint256, std::vector<unsigned char>> cryptedHDSeed;
// Encrypted mnemonic entropy, shaped exactly like cryptedHDSeed above:
// .first is the entropy's fingerprint (AES IV + integrity tag on decrypt),
// .second is the ciphertext.
std::pair<uint256, std::vector<unsigned char>> cryptedMnemonicEntropy;
CryptedKeyMap mapCryptedKeys;
//CryptedSproutSpendingKeyMap mapCryptedSproutSpendingKeys;
CryptedSaplingSpendingKeyMap mapCryptedSaplingSpendingKeys;
@@ -194,6 +213,14 @@ public:
bool SetHDSeed(const HDSeed& seed);
bool HaveHDSeed() const;
bool GetHDSeed(HDSeed& seedOut) const;
//! Mnemonic entropy, mirroring the four HD-seed members above.
//! SetCryptedMnemonicEntropy MUST stay virtual for the same reason
//! SetCryptedHDSeed is: CWallet overrides it to persist the record, and
//! SetMnemonicEntropy() below reaches that override through the vtable.
virtual bool SetCryptedMnemonicEntropy(const uint256& entropyFp, const std::vector<unsigned char> &vchCryptedSecret);
bool SetMnemonicEntropy(const RawHDSeed& entropy);
bool HaveMnemonicEntropy() const;
bool GetMnemonicEntropy(RawHDSeed& entropyOut) const;
virtual bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey);

View File

@@ -1039,8 +1039,10 @@ UniValue z_exportmnemonic(const UniValue& params, bool fHelp, const CPubKey& myp
"\nReveal the wallet's BIP39 seed phrase (24 words).\n"
"The phrase is byte-compatible with SilentDragonXLite: the same words\n"
"restore the same transparent and shielded addresses in either wallet.\n"
"Only works for wallets created or restored from a mnemonic (see the\n"
"-mnemonic and -usemnemonic options). Requires the wallet be unlocked.\n"
"New wallets get a seed phrase by default (-usemnemonic=0 opts out);\n"
"wallets restored with -mnemonic have one too. Wallets created before\n"
"this feature, or from a raw -hdseed, have no phrase -- use\n"
"z_exportwallet for those. Requires the wallet be unlocked.\n"
"\nResult:\n"
"{\n"
" \"mnemonic\" : \"word1 ... word24\", (string) the BIP39 seed phrase\n"

View File

@@ -2470,30 +2470,47 @@ void CWallet::GenerateNewSeed()
// Opt-in: create the wallet from a fresh BIP39 mnemonic so its 24 words can
// be exported (z_exportmnemonic) and used in SilentDragonXLite.
if (GetBoolArg("-usemnemonic", false)) {
//
// NO SILENT FALLBACK. Falling back to a random seed here produced a wallet
// that looks mnemonic-capable but whose words can never be exported
// (z_exportmnemonic refuses non-mnemonic wallets, rpcdump.cpp:1031+) and
// that no seed phrase can restore. A user who asked for -usemnemonic must
// get that or a hard failure.
if (GetBoolArg("-usemnemonic", true)) {
RawHDSeed entropy;
if (GenerateMnemonicEntropy(256, entropy)) {
HDSeed seed(entropy);
if (InstallHDSeed(seed, true, nCreationTime))
return;
}
LogPrintf("%s: -usemnemonic seed generation failed, falling back to a random seed\n", __func__);
if (!GenerateMnemonicEntropy(256, entropy))
throw std::runtime_error(std::string(__func__) + ": -usemnemonic entropy generation failed");
// Store the EXPANDED 64-byte BIP39 seed as the HD seed, with
// fMnemonic = false. Every binary -- old or new -- then feeds the stored
// bytes straight into derivation, so the key tree is identical
// everywhere and no CHDChain version bump or minversion fence is needed.
// The 32-byte entropy is kept in a separate, display-only record purely
// so the phrase can be reprinted. Addresses are unchanged from the
// previous format, which expanded the stored entropy on every read.
RawHDSeed seed64;
if (!Bip39SeedFromEntropy(entropy, seed64))
throw std::runtime_error(std::string(__func__) + ": BIP39 seed expansion failed");
HDSeed seed(seed64);
// ORDER IS LOAD-BEARING: seed first, entropy second, never the reverse.
// A crash between the two leaves a wallet with a seed and no phrase --
// recoverable via z_exportwallet, merely inconvenient. The reverse order
// would leave entropy with no seed; the next start would mint a
// DIFFERENT seed while the wallet still held a phrase for the old one.
// (GetMnemonicPhrase cross-checks the two and would refuse to print it,
// but do not rely on that here.)
if (!InstallHDSeed(seed, false, nCreationTime))
throw std::runtime_error(std::string(__func__) + ": installing the mnemonic HD seed failed");
if (!SetMnemonicEntropy(entropy))
throw std::runtime_error(std::string(__func__) + ": storing the mnemonic entropy failed");
return;
}
auto seed = HDSeed::Random(HD_WALLET_SEED_LENGTH);
// If the wallet is encrypted and locked, this will fail.
if (!SetHDSeed(seed))
auto seed = HDSeed::Random(HD_WALLET_SEED_LENGTH);
if (!InstallHDSeed(seed, false, nCreationTime))
throw std::runtime_error(std::string(__func__) + ": SetHDSeed failed");
// store the key creation time together with
// the child index counter in the database
// as a hdchain object
CHDChain newHdChain;
newHdChain.nVersion = CHDChain::VERSION_HD_TRANSPARENT;
newHdChain.seedFp = seed.Fingerprint();
newHdChain.nCreateTime = nCreationTime;
SetHDChain(newHdChain, false);
}
bool CWallet::SetHDSeed(const HDSeed& seed)
@@ -2527,14 +2544,38 @@ bool CWallet::SetCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned
{
LOCK(cs_wallet);
if (pwalletdbEncryption)
return pwalletdbEncryption->WriteCryptedHDSeed(seedFp, vchCryptedSecret);
else
return CWalletDB(strWalletFile).WriteCryptedHDSeed(seedFp, vchCryptedSecret);
// Write the encrypted record, then drop the plaintext one. Both go
// through the same CWalletDB (and therefore the same transaction when
// EncryptWallet supplied pwalletdbEncryption), because CDB::Rewrite at
// the end of EncryptWallet copies every surviving record into the fresh
// file -- a leftover plaintext "hdseed" would keep the unencrypted seed
// on disk for the life of the wallet.
//
// The erase is deliberately best-effort: a hard failure here propagates
// into CCryptoKeyStore::EncryptKeys, which CWallet::EncryptWallet turns
// into assert(false) with half the keys encrypted in memory. A logged
// warning is strictly better than that.
if (pwalletdbEncryption) {
if (!pwalletdbEncryption->WriteCryptedHDSeed(seedFp, vchCryptedSecret))
return false;
if (!pwalletdbEncryption->EraseHDSeed(seedFp))
LogPrintf("%s: WARNING: could not erase the plaintext hdseed record; "
"the unencrypted HD seed may remain in wallet.dat\n", __func__);
return true;
} else {
CWalletDB walletdb(strWalletFile);
if (!walletdb.WriteCryptedHDSeed(seedFp, vchCryptedSecret))
return false;
if (!walletdb.EraseHDSeed(seedFp))
LogPrintf("%s: WARNING: could not erase the plaintext hdseed record; "
"the unencrypted HD seed may remain in wallet.dat\n", __func__);
return true;
}
}
return false;
}
void CWallet::SetHDChain(const CHDChain& chain, bool memonly)
{
LOCK(cs_wallet);
@@ -2544,6 +2585,20 @@ void CWallet::SetHDChain(const CHDChain& chain, bool memonly)
hdChain = chain;
}
void CWallet::SetHDSeedOrigin(int origin)
{
LOCK(cs_wallet);
hdSeedOrigin = origin;
// Deliberately non-fatal, unlike SetHDChain: losing this record must never
// stop a node from starting. The cost of a failed write is that the next
// start re-classifies the wallet, and re-classification of an already-seeded
// wallet yields HDSEED_ORIGIN_UNKNOWN, i.e. the safe answer.
if (fFileBacked && !CWalletDB(strWalletFile).WriteHDSeedOrigin((int64_t)origin))
LogPrintf("%s: WARNING: could not record HD seed origin %d in wallet.dat\n", __func__, origin);
}
bool CWallet::LoadHDSeed(const HDSeed& seed)
{
return CBasicKeyStore::SetHDSeed(seed);
@@ -2553,21 +2608,94 @@ bool CWallet::LoadCryptedHDSeed(const uint256& seedFp, const std::vector<unsigne
{
return CCryptoKeyStore::SetCryptedHDSeed(seedFp, seed);
}
bool CWallet::SetMnemonicEntropy(const RawHDSeed& entropy)
{
if (!CCryptoKeyStore::SetMnemonicEntropy(entropy)) {
return false;
}
if (!fFileBacked) {
return true;
}
{
LOCK(cs_wallet);
if (!IsCrypted()) {
// Keyed by fingerprint exactly as "hdseed" is, so ReadKeyValue can
// integrity-check it and EraseMnemonicEntropy can find it later.
return CWalletDB(strWalletFile).WriteMnemonicEntropy(
MnemonicEntropyFingerprint(entropy), entropy);
}
}
return true;
}
bool CWallet::SetCryptedMnemonicEntropy(const uint256& entropyFp, const std::vector<unsigned char>& vchCryptedSecret)
{
if (!CCryptoKeyStore::SetCryptedMnemonicEntropy(entropyFp, vchCryptedSecret)) {
return false;
}
if (!fFileBacked) {
return true;
}
{
LOCK(cs_wallet);
// Same write-then-erase discipline as SetCryptedHDSeed: CDB::Rewrite at
// the end of EncryptWallet copies every surviving record, so a leftover
// plaintext "mnementropy" would keep the seed phrase recoverable from an
// encrypted wallet.dat. The erase is best-effort for the same reason: a
// hard failure would propagate into EncryptKeys -> assert(false).
if (pwalletdbEncryption) {
if (!pwalletdbEncryption->WriteCryptedMnemonicEntropy(entropyFp, vchCryptedSecret))
return false;
if (!pwalletdbEncryption->EraseMnemonicEntropy(entropyFp))
LogPrintf("%s: WARNING: could not erase the plaintext mnementropy record\n", __func__);
return true;
} else {
CWalletDB walletdb(strWalletFile);
if (!walletdb.WriteCryptedMnemonicEntropy(entropyFp, vchCryptedSecret))
return false;
if (!walletdb.EraseMnemonicEntropy(entropyFp))
LogPrintf("%s: WARNING: could not erase the plaintext mnementropy record\n", __func__);
return true;
}
}
return false;
}
bool CWallet::LoadMnemonicEntropy(const RawHDSeed& entropy)
{
return CBasicKeyStore::SetMnemonicEntropy(entropy);
}
bool CWallet::LoadCryptedMnemonicEntropy(const uint256& entropyFp, const std::vector<unsigned char>& vchCryptedSecret)
{
return CCryptoKeyStore::SetCryptedMnemonicEntropy(entropyFp, vchCryptedSecret);
}
bool CWallet::InstallHDSeed(const HDSeed& seed, bool fMnemonic, int64_t nCreateTime)
{
AssertLockHeld(cs_wallet);
if (!SetHDSeed(seed))
return false;
// Chain BEFORE seed. A crash between the two records must never leave a
// wallet that holds a seed with no hdchain: on the next load hdChain would
// silently revert to its defaults, clearing fMnemonicSeed (which switches
// the derivation input, wallet.cpp:2615-2633) and resetting
// saplingAccountCounter. The opposite torn state — chain without seed — is
// harmless and self-healing: HaveHDSeed() is false, so init installs a seed
// again and overwrites the chain.
CHDChain newHdChain;
newHdChain.nVersion = fMnemonic ? CHDChain::VERSION_HD_MNEMONIC
: CHDChain::VERSION_HD_TRANSPARENT;
newHdChain.seedFp = seed.Fingerprint();
newHdChain.nCreateTime = nCreateTime;
newHdChain.fMnemonicSeed = fMnemonic;
SetHDChain(newHdChain, false);
SetHDChain(newHdChain, false); // throws if the write fails
if (!SetHDSeed(seed))
return false;
return true;
}
@@ -2606,10 +2734,29 @@ bool CWallet::SetHDSeedFromMnemonic(const std::string& phrase)
if (!MnemonicToEntropy(phrase, entropy))
return false;
// Store the BIP39 entropy as the HDSeed (SilentDragonXLite's on-disk
// convention); the 64-byte seed is expanded from it on demand.
HDSeed seed(entropy);
return InstallHDSeed(seed, true, 1); // birthday = genesis for a restore
// Store the EXPANDED 64-byte BIP39 seed as the HD seed (fMnemonic = false);
// the entropy goes in its own record and is used only to reprint the phrase.
// Derivation therefore reads the stored bytes directly on any binary, and
// the resulting addresses are byte-identical to the previous format, which
// expanded the stored entropy on every derivation. SilentDragonXLite
// interop is unaffected: the same words still yield the same seed64.
RawHDSeed seed64;
if (!Bip39SeedFromEntropy(entropy, seed64))
return false;
HDSeed seed(seed64);
// Seed first, entropy second -- see the ordering note in GenerateNewSeed.
if (!InstallHDSeed(seed, false, 1)) // birthday = genesis for a restore
return false;
// Non-fatal on a restore, unlike GenerateNewSeed: the user already holds the
// phrase (they just typed it), the seed is installed and the wallet is fully
// functional; only z_exportmnemonic is lost.
if (!SetMnemonicEntropy(entropy)) {
LogPrintf("%s: WARNING: HD seed installed but the mnemonic entropy record could not be "
"stored; z_exportmnemonic will be unavailable on this wallet\n", __func__);
}
return true;
}
bool CWallet::GetHDSeedForDerivation(HDSeed& seedOut) const
@@ -2634,6 +2781,36 @@ bool CWallet::GetHDSeedForDerivation(HDSeed& seedOut) const
bool CWallet::GetMnemonicPhrase(std::string& phraseOut) const
{
// Preferred form: the HD seed is the EXPANDED 64-byte BIP39 seed and the
// entropy sits in its own record.
RawHDSeed entropy;
if (GetMnemonicEntropy(entropy)) { // false on an encrypted+locked wallet
// NEVER hand out a phrase that does not restore THIS wallet. Prove the
// entropy expands to the exact bytes derivation consumes; if it does
// not (a torn install, a wallet.dat edited by hand, an entropy record
// paired with a different seed), refuse rather than print a phrase that
// silently restores someone else's key tree. Costs one PBKDF2 on a
// user-initiated RPC.
RawHDSeed seed64;
if (!Bip39SeedFromEntropy(entropy, seed64))
return false;
HDSeed derivationSeed;
if (!GetHDSeedForDerivation(derivationSeed))
return false;
if (derivationSeed.RawSeed() != seed64) {
LogPrintf("%s: refusing to export a seed phrase: the stored mnemonic entropy does not "
"expand to this wallet's HD seed\n", __func__);
return false;
}
return EntropyToMnemonic(entropy, phraseOut);
}
// Legacy form (earlier builds of this branch): the stored HD seed IS the
// 32-byte BIP39 entropy, expanded on every derivation. Consistent by
// construction, so no cross-check is possible or needed.
if (!hdChain.fMnemonicSeed)
return false;

View File

@@ -855,6 +855,18 @@ public:
// Id of the in-flight autoshield op; read by the op to confirm it is still
// the current one before mutating scheduler state.
AsyncRPCOperationId saplingAutoShieldOperationId;
// Provenance of this wallet's HD seed, recorded once in wallet.dat the
// first time a build that knows about it opens the wallet. Features that
// move funds into addresses only the seed can re-derive must not turn
// themselves ON by default unless the user can actually restore that seed.
enum HDSeedOrigin {
HDSEED_ORIGIN_UNRECORDED = 0, // no record in wallet.dat yet
HDSEED_ORIGIN_CREATED = 1, // minted onto a brand-new empty wallet
HDSEED_ORIGIN_RESTORED = 2, // user supplied -mnemonic / -hdseed
HDSEED_ORIGIN_RETROFIT = 3, // minted onto a pre-existing seedless wallet
HDSEED_ORIGIN_UNKNOWN = 4, // seed predates this record
};
int hdSeedOrigin = HDSEED_ORIGIN_UNRECORDED;
void ClearNoteWitnessCache();
@@ -1333,6 +1345,14 @@ public:
bool SetHDSeed(const HDSeed& seed);
bool SetCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned char> &vchCryptedSecret);
/* Record this wallet's BIP39 entropy so its seed phrase can be reprinted.
Display-only: derivation never reads it (the HD seed holds the bytes that
are actually derived from). Refuses to replace an existing record.
SetCryptedMnemonicEntropy overrides the CCryptoKeyStore virtual so the
record reaches disk; SetMnemonicEntropy merely hides the base version,
which is safe because no call site holds a base pointer. */
bool SetMnemonicEntropy(const RawHDSeed& entropy);
bool SetCryptedMnemonicEntropy(const uint256& entropyFp, const std::vector<unsigned char> &vchCryptedSecret);
/* Restore a wallet's HD seed from a hex string (as exported in the
z_exportwallet "# HDSeed=" comment): 32 bytes for a legacy raw seed, or
@@ -1351,8 +1371,14 @@ public:
wallet and the seed is available (unlocked). Returns false otherwise. */
bool GetMnemonicPhrase(std::string& phraseOut) const;
/* True if the HD seed was derived from a BIP39 mnemonic (stored as entropy). */
bool IsMnemonicSeed() const { return hdChain.fMnemonicSeed; }
/* True if this wallet has a BIP39 seed phrase available. Two storage forms
qualify:
- current: the HD seed is the EXPANDED 64-byte BIP39 seed and the
entropy lives in its own record (HaveMnemonicEntropy());
- legacy: hdChain.fMnemonicSeed, where the stored HD seed IS the
32-byte entropy and is expanded on every derivation.
Gates z_exportmnemonic (rpcdump.cpp). */
bool IsMnemonicSeed() const { return hdChain.fMnemonicSeed || HaveMnemonicEntropy(); }
/* Return the seed to feed into HD derivation. For mnemonic wallets this
expands the stored 32-byte entropy into the 64-byte BIP39 seed; for legacy
@@ -1373,11 +1399,22 @@ public:
void SetHDChain(const CHDChain& chain, bool memonly);
const CHDChain& GetHDChain() const { return hdChain; }
/* Record (in memory and in wallet.dat) how this wallet's HD seed came to
exist. Best-effort: a failed write is logged, not fatal — the next start
simply re-classifies, and re-classification always errs toward
HDSEED_ORIGIN_UNKNOWN, which is the conservative answer. */
void SetHDSeedOrigin(int origin);
/* Set the current HD seed, without saving it to disk (used by LoadWallet) */
bool LoadHDSeed(const HDSeed& key);
/* Set the current encrypted HD seed, without saving it to disk (used by LoadWallet) */
bool LoadCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned char>& seed);
/* Set the mnemonic entropy, without saving it to disk (used by LoadWallet) */
bool LoadMnemonicEntropy(const RawHDSeed& entropy);
/* Set the encrypted mnemonic entropy, without saving it to disk (used by LoadWallet) */
bool LoadCryptedMnemonicEntropy(const uint256& entropyFp, const std::vector<unsigned char>& vchCryptedSecret);
/* Find notes filtered by payment address, min depth, ability to spend */
void GetFilteredNotes(std::vector<SaplingNoteEntry>& saplingEntries,

View File

@@ -34,6 +34,15 @@
#include <boost/scoped_ptr.hpp>
#include <boost/thread.hpp>
// Out-of-line definitions for CHDChain's in-class static constants. These are
// only initialised in the class body, so any ODR use -- binding one to a const
// reference, which is exactly what gtest's EXPECT_*/ASSERT_* macros do -- needs
// a definition or the link fails. hush-gtest hit this on VERSION_HD_MNEMONIC.
const int CHDChain::VERSION_HD_BASE;
const int CHDChain::VERSION_HD_TRANSPARENT;
const int CHDChain::VERSION_HD_MNEMONIC;
const int CHDChain::CURRENT_VERSION;
using namespace std;
static uint64_t nAccountingEntryNumber = 0;
@@ -216,6 +225,12 @@ bool CWalletDB::WriteWitnessCacheSize(int64_t nWitnessCacheSize)
return Write(std::string("witnesscachesize"), nWitnessCacheSize);
}
bool CWalletDB::WriteHDSeedOrigin(int64_t nOrigin)
{
nWalletDBUpdated++;
return Write(std::string("hdseedorigin"), nOrigin);
}
bool CWalletDB::ReadPool(int64_t nPool, CKeyPool& keypool)
{
return Read(std::make_pair(std::string("pool"), nPool), keypool);
@@ -403,12 +418,15 @@ public:
bool fAnyUnordered;
int nFileVersion;
vector<uint256> vWalletUpgrade;
// True once a well-formed "hdchain" record has been loaded.
bool fHDChainRead;
CWalletScanState() {
nKeys = nCKeys = nKeyMeta = nZKeys = nCZKeys = nZKeyMeta = nSapZAddrs = 0;
fIsEncrypted = false;
fAnyUnordered = false;
nFileVersion = 0;
fHDChainRead = false;
}
};
@@ -833,9 +851,56 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
else if (strType == "hdchain")
{
CHDChain chain;
ssValue >> chain;
try {
ssValue >> chain;
} catch (...) {
// Do not let this land in the "user can live with it" bucket:
// report it, and leave wss.fHDChainRead false so LoadWallet
// turns it into DB_CORRUPT when a seed is present.
strErr = "Error reading wallet database: hdchain record is corrupt";
return false;
}
wss.fHDChainRead = true;
pwallet->SetHDChain(chain, true);
}
else if (strType == "hdseedorigin")
{
int64_t nOrigin = 0;
ssValue >> nOrigin;
pwallet->hdSeedOrigin = (int)nOrigin;
}
else if (strType == "mnementropy")
{
uint256 entropyFp;
RawHDSeed entropy;
ssKey >> entropyFp;
ssValue >> entropy;
if (MnemonicEntropyFingerprint(entropy) != entropyFp)
{
strErr = "Error reading wallet database: mnemonic entropy corrupt";
return false;
}
if (!pwallet->LoadMnemonicEntropy(entropy))
{
strErr = "Error reading wallet database: LoadMnemonicEntropy failed";
return false;
}
}
else if (strType == "cmnementropy")
{
uint256 entropyFp;
vector<unsigned char> vchCryptedSecret;
ssKey >> entropyFp;
ssValue >> vchCryptedSecret;
if (!pwallet->LoadCryptedMnemonicEntropy(entropyFp, vchCryptedSecret))
{
strErr = "Error reading wallet database: LoadCryptedMnemonicEntropy failed";
return false;
}
wss.fIsEncrypted = true;
}
} catch (...)
{
return false;
@@ -847,6 +912,10 @@ static bool IsKeyType(string strType)
{
return (strType == "key" || strType == "wkey" ||
strType == "hdseed" || strType == "chdseed" ||
// The mnemonic entropy must survive a keys-only salvage: without it
// a recovered wallet keeps its seed (and stays fully spendable) but
// silently loses the ability to reprint its seed phrase.
strType == "mnementropy" || strType == "cmnementropy" ||
strType == "zkey" || strType == "czkey" ||
strType == "sapzkey" || strType == "csapzkey" ||
strType == "vkey" ||
@@ -947,6 +1016,21 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet)
if (fNoncriticalErrors && result == DB_LOAD_OK)
result = DB_NONCRITICAL_ERROR;
// A wallet that holds an HD seed but whose hdchain record is missing or
// unreadable is NOT safe to run. hdChain would fall back to its SetNull
// defaults (walletdb.h:105-113), which (a) clears fMnemonicSeed, switching
// HD derivation from the 64-byte BIP39 seed to the raw 32-byte entropy
// (CWallet::GetHDSeedForDerivation, wallet.cpp:2615-2633) -> an entirely
// different key tree, and (b) resets saplingAccountCounter to 0, so the
// next GenerateNewSaplingZKey walks back over accounts that already exist.
// Both are silent today (a bad hdchain read is only DB_NONCRITICAL_ERROR).
// Fail loud instead of quietly deriving into the wrong tree.
if (pwallet->HaveHDSeed() && !wss.fHDChainRead)
{
LogPrintf("Error loading wallet.dat: HD seed present but the hdchain record is missing or corrupt\n");
return DB_CORRUPT;
}
// Any wallet corruption at all: skip any rewriting or
// upgrading, we don't want to make it worse.
if (result != DB_LOAD_OK)
@@ -1240,7 +1324,13 @@ bool CWalletDB::Recover(CDBEnv& dbenv, const std::string& filename, bool fOnlyKe
fReadOK = ReadKeyValue(&dummyWallet, ssKey, ssValue,
wss, strType, strErr);
}
if (!IsKeyType(strType))
// "hdchain" is not a key type, but it must survive a keys-only
// salvage: a recovered wallet that keeps its seed while losing its
// hdchain silently derives from a different key tree (fMnemonicSeed
// cleared -> raw entropy instead of the 64-byte BIP39 seed) and
// re-issues sapling accounts from 0. CWalletDB::LoadWallet now
// refuses such a wallet outright, so preserve the record here.
if (!IsKeyType(strType) && strType != "hdchain")
continue;
if (!fReadOK)
{
@@ -1290,6 +1380,34 @@ bool CWalletDB::WriteCryptedHDSeed(const uint256& seedFp, const std::vector<unsi
return Write(std::make_pair(std::string("chdseed"), seedFp), vchCryptedSecret);
}
bool CWalletDB::EraseHDSeed(const uint256& seedFp)
{
nWalletDBUpdated++;
// CDB::Erase honours activeTxn, so when this runs inside EncryptWallet's
// transaction the erase commits or aborts atomically with the chdseed write.
// It also returns true for DB_NOTFOUND, so erasing a record that was never
// written (e.g. a wallet encrypted at creation time) is not a failure.
return Erase(std::make_pair(std::string("hdseed"), seedFp));
}
bool CWalletDB::WriteMnemonicEntropy(const uint256& entropyFp, const RawHDSeed& entropy)
{
nWalletDBUpdated++;
return Write(std::make_pair(std::string("mnementropy"), entropyFp), entropy);
}
bool CWalletDB::WriteCryptedMnemonicEntropy(const uint256& entropyFp, const std::vector<unsigned char>& vchCryptedSecret)
{
nWalletDBUpdated++;
return Write(std::make_pair(std::string("cmnementropy"), entropyFp), vchCryptedSecret);
}
bool CWalletDB::EraseMnemonicEntropy(const uint256& entropyFp)
{
nWalletDBUpdated++;
return Erase(std::make_pair(std::string("mnementropy"), entropyFp));
}
bool CWalletDB::WriteHDChain(const CHDChain& chain)
{
nWalletDBUpdated++;

View File

@@ -191,6 +191,9 @@ public:
bool WriteWitnessCacheSize(int64_t nWitnessCacheSize);
//! Record how this wallet's HD seed came to exist (CWallet::HDSeedOrigin).
bool WriteHDSeedOrigin(int64_t nOrigin);
bool ReadPool(int64_t nPool, CKeyPool& keypool);
bool WritePool(int64_t nPool, const CKeyPool& keypool);
bool ErasePool(int64_t nPool);
@@ -219,6 +222,17 @@ public:
bool WriteHDSeed(const HDSeed& seed);
bool WriteCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned char>& vchCryptedSecret);
//! Remove the PLAINTEXT hdseed record. Must be called once the seed has been
//! written in encrypted form: CDB::Rewrite (invoked at the end of
//! CWallet::EncryptWallet) copies whatever records still exist into the new
//! file, so a leftover "hdseed" leaves the unencrypted seed on disk forever.
bool EraseHDSeed(const uint256& seedFp);
//! BIP39 entropy for a phrase-recoverable wallet. Display-only: derivation
//! never reads it. Record names are deliberately distinct prefixes from
//! "hdseed"/"chdseed" so they cannot collide.
bool WriteMnemonicEntropy(const uint256& entropyFp, const RawHDSeed& entropy);
bool WriteCryptedMnemonicEntropy(const uint256& entropyFp, const std::vector<unsigned char>& vchCryptedSecret);
bool EraseMnemonicEntropy(const uint256& entropyFp);
//! write the hdchain model (external chain child index counter)
bool WriteHDChain(const CHDChain& chain);

View File

@@ -35,7 +35,13 @@ then
ls -la build/librandomx*
else
mkdir build && cd build
CC="${CC} -g " CXX="${CXX} -g " cmake -DARCH=native ..
# ARCH=default, NOT native. RandomX's CMakeLists maps ARCH=native to -march=native, which
# tunes the binary to the BUILD machine. Measured 2026-08-21: a build on the Zen4 pool box
# emitted 746 AVX-512 (zmm) instructions into librandomx.a, and every seed reports
# avx512f=no -- that binary SIGILLs inside RandomX on the whole fleet, and on any user CPU
# older than the build host. ARCH=default still enables -maes plus per-file -mssse3/-mavx2
# for argon2, so the portable baseline costs ~nothing.
CC="${CC} -g " CXX="${CXX} -g " cmake -DARCH=default ..
make
fi

View File

@@ -147,7 +147,13 @@ then
ls -la build/librandomx*
else
mkdir build && cd build
cmake -DARCH=native ..
# ARCH=default, NOT native. RandomX's CMakeLists maps ARCH=native to -march=native, which
# tunes the binary to the BUILD machine. Measured 2026-08-21: a build on the Zen4 pool box
# emitted 746 AVX-512 (zmm) instructions into librandomx.a, and every seed reports
# avx512f=no -- that binary SIGILLs inside RandomX on the whole fleet, and on any user CPU
# older than the build host. ARCH=default still enables -maes plus per-file -mssse3/-mavx2
# for argon2, so the portable baseline costs ~nothing.
cmake -DARCH=default ..
# pass along potential -jX and other args
time make "$@"
fi

View File

@@ -134,7 +134,13 @@ then
ls -la build/librandomx*
else
mkdir build && cd build
cmake -DARCH=native ..
# ARCH=default, NOT native. RandomX's CMakeLists maps ARCH=native to -march=native, which
# tunes the binary to the BUILD machine. Measured 2026-08-21: a build on the Zen4 pool box
# emitted 746 AVX-512 (zmm) instructions into librandomx.a, and every seed reports
# avx512f=no -- that binary SIGILLs inside RandomX on the whole fleet, and on any user CPU
# older than the build host. ARCH=default still enables -maes plus per-file -mssse3/-mavx2
# for argon2, so the portable baseline costs ~nothing.
cmake -DARCH=default ..
make
fi