Derive transparent (t-addr) keys from the HD seed and add BIP39 mnemonic seed phrases that are byte-for-byte compatible with SilentDragonXLite, so the same 24 words recover the same shielded and transparent addresses in either wallet. HD transparent keys: - Derive t-keys from the seed at m/44'/coin'/0'/0/i (were random CKeys). - CHDChain gains a version-gated transparent counter; existing wallets load unchanged. GenerateNewKey routes through DeriveNewChildKey when enabled (-hdtransparent, default on). - Restore from a seed hex via -hdseed with gap-limit pre-derivation; birthday pinned to genesis so the rescan is not clipped. BIP39 seed phrases: - Wire the vendored trezor BIP39 lib (src/crypto/bip39) into the build, fix its BIP39_WORDS guard, and disable the insecure mnemonic cache. - Match SDXLite exactly: English wordlist, empty passphrase, PBKDF2 64-byte seed, coin type 141, ZIP-32 m/32'/141'/i' and BIP44 m/44'/141'/0'/0/i. Store the 32-byte entropy and expand to the 64-byte seed on demand. - Restore via -mnemonic, create via -usemnemonic, reveal via z_exportmnemonic. Verified by gtests including a known-answer BIP39 seed vector and z/t address derivation checks (src/gtest/test_hdtransparent.cpp, test_mnemonic_compat.cpp). Docs in doc/hd-transparent-keys.md and doc/seed-phrase.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
100 lines
3.2 KiB
C++
100 lines
3.2 KiB
C++
// Copyright (c) 2016-2024 The Hush developers
|
|
// Distributed under the GPLv3 software license, see the accompanying
|
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
|
#include "wallet/mnemonic.h"
|
|
|
|
#include "random.h"
|
|
#include "support/cleanse.h"
|
|
|
|
#include <mutex>
|
|
#include <cstring>
|
|
|
|
extern "C" {
|
|
#include "crypto/bip39/bip39.h"
|
|
}
|
|
|
|
// The vendored BIP39 library references random_buffer() (used by its
|
|
// mnemonic_generate()). We do not compile trezor's insecure rand.c; instead we
|
|
// route it to the node CSPRNG so any BIP39 randomness is cryptographically
|
|
// sound. random_buffer is declared weak in rand.c, so this strong definition
|
|
// is the one that links.
|
|
extern "C" void random_buffer(uint8_t* buf, size_t len)
|
|
{
|
|
GetRandBytes(buf, (int)len);
|
|
}
|
|
|
|
// mnemonic_from_data()/mnemonic_to_seed() use process-static scratch buffers,
|
|
// so serialize all access behind one lock and copy results out immediately.
|
|
static std::mutex cs_bip39;
|
|
|
|
bool MnemonicIsValid(const std::string& phrase)
|
|
{
|
|
std::lock_guard<std::mutex> lock(cs_bip39);
|
|
return mnemonic_check(phrase.c_str()) != 0;
|
|
}
|
|
|
|
bool MnemonicToEntropy(const std::string& phrase, RawHDSeed& entropyOut)
|
|
{
|
|
std::lock_guard<std::mutex> lock(cs_bip39);
|
|
// Reject bad checksum / unknown words first.
|
|
if (mnemonic_check(phrase.c_str()) == 0) {
|
|
return false;
|
|
}
|
|
// mnemonic_to_entropy() writes 33 bytes (entropy || 1 checksum byte) and
|
|
// returns the total bit count (words * 11).
|
|
uint8_t buf[33];
|
|
int totalBits = mnemonic_to_entropy(phrase.c_str(), buf);
|
|
if (totalBits <= 0) {
|
|
return false;
|
|
}
|
|
int words = totalBits / 11;
|
|
if (words != 12 && words != 18 && words != 24) {
|
|
memory_cleanse(buf, sizeof(buf));
|
|
return false;
|
|
}
|
|
int entropyBytes = words * 4 / 3; // 12->16, 18->24, 24->32
|
|
entropyOut.assign(buf, buf + entropyBytes);
|
|
memory_cleanse(buf, sizeof(buf));
|
|
return true;
|
|
}
|
|
|
|
bool EntropyToMnemonic(const RawHDSeed& entropy, std::string& phraseOut)
|
|
{
|
|
std::lock_guard<std::mutex> lock(cs_bip39);
|
|
const char* phrase = mnemonic_from_data(entropy.data(), (int)entropy.size());
|
|
if (phrase == nullptr) {
|
|
return false;
|
|
}
|
|
phraseOut.assign(phrase);
|
|
mnemonic_clear(); // wipe the static buffer
|
|
return true;
|
|
}
|
|
|
|
bool Bip39SeedFromEntropy(const RawHDSeed& entropy, RawHDSeed& seed64Out)
|
|
{
|
|
std::lock_guard<std::mutex> lock(cs_bip39);
|
|
// Regenerate the canonical phrase from entropy (matches SDXLite's
|
|
// Mnemonic::from_entropy(entropy).phrase()), then PBKDF2 with an EMPTY
|
|
// passphrase to get the standard 64-byte BIP39 seed.
|
|
const char* phrase = mnemonic_from_data(entropy.data(), (int)entropy.size());
|
|
if (phrase == nullptr) {
|
|
return false;
|
|
}
|
|
uint8_t seed[64];
|
|
mnemonic_to_seed(phrase, "", seed, nullptr);
|
|
mnemonic_clear();
|
|
seed64Out.assign(seed, seed + 64);
|
|
memory_cleanse(seed, sizeof(seed));
|
|
return true;
|
|
}
|
|
|
|
bool GenerateMnemonicEntropy(int bits, RawHDSeed& entropyOut)
|
|
{
|
|
if (bits != 128 && bits != 160 && bits != 192 && bits != 224 && bits != 256) {
|
|
return false;
|
|
}
|
|
entropyOut.resize(bits / 8);
|
|
GetRandBytes(entropyOut.data(), (int)entropyOut.size());
|
|
return true;
|
|
}
|