Add post-quantum support for descriptor wallets
This commit is contained in:
parent
31e4f44475
commit
302a94e1a5
7 changed files with 253 additions and 28 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
AC_PREREQ([2.69])
|
AC_PREREQ([2.69])
|
||||||
define(_CLIENT_VERSION_MAJOR, 0)
|
define(_CLIENT_VERSION_MAJOR, 0)
|
||||||
define(_CLIENT_VERSION_MINOR, 2)
|
define(_CLIENT_VERSION_MINOR, 2)
|
||||||
define(_CLIENT_VERSION_BUILD, 1)
|
define(_CLIENT_VERSION_BUILD, 2)
|
||||||
define(_CLIENT_VERSION_RC, 1)
|
define(_CLIENT_VERSION_RC, 1)
|
||||||
define(_CLIENT_VERSION_IS_RELEASE, true)
|
define(_CLIENT_VERSION_IS_RELEASE, true)
|
||||||
define(_COPYRIGHT_YEAR, 2026)
|
define(_COPYRIGHT_YEAR, 2026)
|
||||||
|
|
|
||||||
|
|
@ -1102,6 +1102,67 @@ uint256 LegacyScriptPubKeyMan::GetID() const
|
||||||
return uint256::ONE;
|
return uint256::ONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint256 DescriptorPQScriptPubKeyMan::GetID() const
|
||||||
|
{
|
||||||
|
// Stable wallet database identity reserved for descriptor-wallet PQ keys.
|
||||||
|
return uint256S("5051522d56455854412d44455343524950544f522d50512d4d414e4147455201");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DescriptorPQScriptPubKeyMan::SetupGeneration(bool force)
|
||||||
|
{
|
||||||
|
if (m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsHDEnabled() && !force) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_storage.IsLocked()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create only the deterministic seed. No legacy EC keypool is generated.
|
||||||
|
SetHDSeed(GenerateNewSeed());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DescriptorPQScriptPubKeyMan::GetNewDestination(
|
||||||
|
const OutputType type,
|
||||||
|
CTxDestination& dest,
|
||||||
|
std::string& error)
|
||||||
|
{
|
||||||
|
if (type != OutputType::MLDSA && type != OutputType::SLHDSA) {
|
||||||
|
error = _("Error: Descriptor PQ manager only supports ML-DSA and SLH-DSA").translated;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Existing descriptor wallets receive their PQ seed lazily on the first
|
||||||
|
// PQ address request, after the wallet has been unlocked.
|
||||||
|
if (!IsHDEnabled()) {
|
||||||
|
if (m_storage.IsLocked()) {
|
||||||
|
error = _("Error: Please enter the wallet passphrase with walletpassphrase first.").translated;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SetupGeneration()) {
|
||||||
|
error = _("Error: Unable to initialize post-quantum wallet seed").translated;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return LegacyScriptPubKeyMan::GetNewDestination(type, dest, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DescriptorPQScriptPubKeyMan::CanGetAddresses(bool internal) const
|
||||||
|
{
|
||||||
|
if (internal || m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return IsHDEnabled() || !m_storage.IsLocked();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update wallet first key creation time. This should be called whenever keys
|
* Update wallet first key creation time. This should be called whenever keys
|
||||||
* are added to the wallet, with the oldest key creation time.
|
* are added to the wallet, with the oldest key creation time.
|
||||||
|
|
|
||||||
|
|
@ -570,6 +570,25 @@ public:
|
||||||
bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override { return m_spk_man.GetKeyOrigin(keyid, info); }
|
bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override { return m_spk_man.GetKeyOrigin(keyid, info); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post-quantum key manager used alongside descriptor wallets.
|
||||||
|
*
|
||||||
|
* Standard EC destinations remain managed by DescriptorScriptPubKeyMan.
|
||||||
|
* This manager owns deterministic ML-DSA / SLH-DSA receive keys only.
|
||||||
|
*/
|
||||||
|
class DescriptorPQScriptPubKeyMan final : public LegacyScriptPubKeyMan
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using LegacyScriptPubKeyMan::LegacyScriptPubKeyMan;
|
||||||
|
|
||||||
|
uint256 GetID() const override;
|
||||||
|
bool SetupGeneration(bool force = false) override;
|
||||||
|
bool GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error) override;
|
||||||
|
bool TopUp(unsigned int size = 0) override { return true; }
|
||||||
|
bool CanGetAddresses(bool internal = false) const override;
|
||||||
|
};
|
||||||
|
|
||||||
class DescriptorScriptPubKeyMan : public ScriptPubKeyMan
|
class DescriptorScriptPubKeyMan : public ScriptPubKeyMan
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
|
|
||||||
|
|
@ -390,8 +390,10 @@ void CWallet::UpgradeDescriptorCache()
|
||||||
|
|
||||||
for (ScriptPubKeyMan* spkm : GetAllScriptPubKeyMans()) {
|
for (ScriptPubKeyMan* spkm : GetAllScriptPubKeyMans()) {
|
||||||
DescriptorScriptPubKeyMan* desc_spkm = dynamic_cast<DescriptorScriptPubKeyMan*>(spkm);
|
DescriptorScriptPubKeyMan* desc_spkm = dynamic_cast<DescriptorScriptPubKeyMan*>(spkm);
|
||||||
|
if (desc_spkm) {
|
||||||
desc_spkm->UpgradeDescriptorCache();
|
desc_spkm->UpgradeDescriptorCache();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
SetWalletFlag(WALLET_FLAG_LAST_HARDENED_XPUB_CACHED);
|
SetWalletFlag(WALLET_FLAG_LAST_HARDENED_XPUB_CACHED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3093,6 +3095,41 @@ LegacyScriptPubKeyMan* CWallet::GetOrCreateLegacyScriptPubKeyMan()
|
||||||
return GetLegacyScriptPubKeyMan();
|
return GetLegacyScriptPubKeyMan();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DescriptorPQScriptPubKeyMan* CWallet::GetDescriptorPQScriptPubKeyMan() const
|
||||||
|
{
|
||||||
|
if (!IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& item : m_spk_managers) {
|
||||||
|
if (auto* pq = dynamic_cast<DescriptorPQScriptPubKeyMan*>(item.second.get())) {
|
||||||
|
return pq;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
DescriptorPQScriptPubKeyMan* CWallet::GetOrCreateDescriptorPQScriptPubKeyMan()
|
||||||
|
{
|
||||||
|
if (!IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) ||
|
||||||
|
IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER) ||
|
||||||
|
IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto* existing = GetDescriptorPQScriptPubKeyMan()) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto manager = std::make_unique<DescriptorPQScriptPubKeyMan>(*this);
|
||||||
|
auto* result = manager.get();
|
||||||
|
const uint256 id = result->GetID();
|
||||||
|
|
||||||
|
m_spk_managers[id] = std::move(manager);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void CWallet::SetupLegacyScriptPubKeyMan()
|
void CWallet::SetupLegacyScriptPubKeyMan()
|
||||||
{
|
{
|
||||||
if (!m_internal_spk_managers.empty() || !m_external_spk_managers.empty() || !m_spk_managers.empty() || IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
|
if (!m_internal_spk_managers.empty() || !m_external_spk_managers.empty() || !m_spk_managers.empty() || IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
|
||||||
|
|
@ -3174,6 +3211,24 @@ void CWallet::SetupDescriptorScriptPubKeyMans()
|
||||||
AddActiveScriptPubKeyMan(id, t, internal);
|
AddActiveScriptPubKeyMan(id, t, internal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ML-DSA and SLH-DSA are handled by a dedicated PQ manager.
|
||||||
|
// The PQ seed is initialized lazily on the first PQ address request.
|
||||||
|
if (!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
||||||
|
auto* pq_manager = GetOrCreateDescriptorPQScriptPubKeyMan();
|
||||||
|
if (!pq_manager) {
|
||||||
|
throw std::runtime_error(std::string(__func__) + ": Could not create post-quantum ScriptPubKeyMan");
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint256 pq_id = pq_manager->GetID();
|
||||||
|
|
||||||
|
if (GetScriptPubKeyMan(OutputType::MLDSA, false) != pq_manager) {
|
||||||
|
AddActiveScriptPubKeyMan(pq_id, OutputType::MLDSA, false);
|
||||||
|
}
|
||||||
|
if (GetScriptPubKeyMan(OutputType::SLHDSA, false) != pq_manager) {
|
||||||
|
AddActiveScriptPubKeyMan(pq_id, OutputType::SLHDSA, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ExternalSigner signer = ExternalSignerScriptPubKeyMan::GetExternalSigner();
|
ExternalSigner signer = ExternalSignerScriptPubKeyMan::GetExternalSigner();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -856,6 +856,8 @@ public:
|
||||||
//! Get the LegacyScriptPubKeyMan which is used for all types, internal, and external.
|
//! Get the LegacyScriptPubKeyMan which is used for all types, internal, and external.
|
||||||
LegacyScriptPubKeyMan* GetLegacyScriptPubKeyMan() const;
|
LegacyScriptPubKeyMan* GetLegacyScriptPubKeyMan() const;
|
||||||
LegacyScriptPubKeyMan* GetOrCreateLegacyScriptPubKeyMan();
|
LegacyScriptPubKeyMan* GetOrCreateLegacyScriptPubKeyMan();
|
||||||
|
DescriptorPQScriptPubKeyMan* GetDescriptorPQScriptPubKeyMan() const;
|
||||||
|
DescriptorPQScriptPubKeyMan* GetOrCreateDescriptorPQScriptPubKeyMan();
|
||||||
|
|
||||||
//! Make a LegacyScriptPubKeyMan and set it for all types, internal, and external.
|
//! Make a LegacyScriptPubKeyMan and set it for all types, internal, and external.
|
||||||
void SetupLegacyScriptPubKeyMan();
|
void SetupLegacyScriptPubKeyMan();
|
||||||
|
|
|
||||||
|
|
@ -463,7 +463,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||||
strErr = "Error reading wallet database: CPrivKey corrupt";
|
strErr = "Error reading wallet database: CPrivKey corrupt";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadKey(key, vchPubKey))
|
if (!(pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) ? static_cast<LegacyScriptPubKeyMan*>(pwallet->GetOrCreateDescriptorPQScriptPubKeyMan()) : pwallet->GetOrCreateLegacyScriptPubKeyMan())->LoadKey(key, vchPubKey))
|
||||||
{
|
{
|
||||||
strErr = "Error reading wallet database: LegacyScriptPubKeyMan::LoadKey failed";
|
strErr = "Error reading wallet database: LegacyScriptPubKeyMan::LoadKey failed";
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -506,7 +506,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||||
|
|
||||||
wss.nCKeys++;
|
wss.nCKeys++;
|
||||||
|
|
||||||
if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadCryptedKey(vchPubKey, vchPrivKey, checksum_valid))
|
if (!(pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) ? static_cast<LegacyScriptPubKeyMan*>(pwallet->GetOrCreateDescriptorPQScriptPubKeyMan()) : pwallet->GetOrCreateLegacyScriptPubKeyMan())->LoadCryptedKey(vchPubKey, vchPrivKey, checksum_valid))
|
||||||
{
|
{
|
||||||
strErr = "Error reading wallet database: LegacyScriptPubKeyMan::LoadCryptedKey failed";
|
strErr = "Error reading wallet database: LegacyScriptPubKeyMan::LoadCryptedKey failed";
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -525,7 +525,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadPQKey(
|
if (!(pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) ? static_cast<LegacyScriptPubKeyMan*>(pwallet->GetOrCreateDescriptorPQScriptPubKeyMan()) : pwallet->GetOrCreateLegacyScriptPubKeyMan())->LoadPQKey(
|
||||||
key_id, type, record.pubkey, record.secret)) {
|
key_id, type, record.pubkey, record.secret)) {
|
||||||
strErr = "Error reading wallet database: LegacyScriptPubKeyMan::LoadPQKey failed";
|
strErr = "Error reading wallet database: LegacyScriptPubKeyMan::LoadPQKey failed";
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -544,7 +544,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadCryptedPQKey(
|
if (!(pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) ? static_cast<LegacyScriptPubKeyMan*>(pwallet->GetOrCreateDescriptorPQScriptPubKeyMan()) : pwallet->GetOrCreateLegacyScriptPubKeyMan())->LoadCryptedPQKey(
|
||||||
key_id, type, record.pubkey, record.secret)) {
|
key_id, type, record.pubkey, record.secret)) {
|
||||||
strErr = "Error reading wallet database: LegacyScriptPubKeyMan::LoadCryptedPQKey failed";
|
strErr = "Error reading wallet database: LegacyScriptPubKeyMan::LoadCryptedPQKey failed";
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -557,14 +557,14 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||||
CKeyMetadata keyMeta;
|
CKeyMetadata keyMeta;
|
||||||
ssValue >> keyMeta;
|
ssValue >> keyMeta;
|
||||||
wss.nKeyMeta++;
|
wss.nKeyMeta++;
|
||||||
pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadPQKeyMetadata(key_id, keyMeta);
|
(pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) ? static_cast<LegacyScriptPubKeyMan*>(pwallet->GetOrCreateDescriptorPQScriptPubKeyMan()) : pwallet->GetOrCreateLegacyScriptPubKeyMan())->LoadPQKeyMetadata(key_id, keyMeta);
|
||||||
} else if (strType == DBKeys::KEYMETA) {
|
} else if (strType == DBKeys::KEYMETA) {
|
||||||
CPubKey vchPubKey;
|
CPubKey vchPubKey;
|
||||||
ssKey >> vchPubKey;
|
ssKey >> vchPubKey;
|
||||||
CKeyMetadata keyMeta;
|
CKeyMetadata keyMeta;
|
||||||
ssValue >> keyMeta;
|
ssValue >> keyMeta;
|
||||||
wss.nKeyMeta++;
|
wss.nKeyMeta++;
|
||||||
pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadKeyMetadata(vchPubKey.GetID(), keyMeta);
|
(pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) ? static_cast<LegacyScriptPubKeyMan*>(pwallet->GetOrCreateDescriptorPQScriptPubKeyMan()) : pwallet->GetOrCreateLegacyScriptPubKeyMan())->LoadKeyMetadata(vchPubKey.GetID(), keyMeta);
|
||||||
|
|
||||||
// Extract some CHDChain info from this metadata if it has any
|
// Extract some CHDChain info from this metadata if it has any
|
||||||
if (keyMeta.nVersion >= CKeyMetadata::VERSION_WITH_HDDATA && !keyMeta.hd_seed_id.IsNull() && keyMeta.hdKeypath.size() > 0) {
|
if (keyMeta.nVersion >= CKeyMetadata::VERSION_WITH_HDDATA && !keyMeta.hd_seed_id.IsNull() && keyMeta.hdKeypath.size() > 0) {
|
||||||
|
|
@ -669,11 +669,11 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||||
} else if (strType == DBKeys::HDCHAIN) {
|
} else if (strType == DBKeys::HDCHAIN) {
|
||||||
CHDChain chain;
|
CHDChain chain;
|
||||||
ssValue >> chain;
|
ssValue >> chain;
|
||||||
pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadHDChain(chain);
|
(pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) ? static_cast<LegacyScriptPubKeyMan*>(pwallet->GetOrCreateDescriptorPQScriptPubKeyMan()) : pwallet->GetOrCreateLegacyScriptPubKeyMan())->LoadHDChain(chain);
|
||||||
} else if (strType == DBKeys::PQHDCHAIN) {
|
} else if (strType == DBKeys::PQHDCHAIN) {
|
||||||
PQHDChain chain;
|
PQHDChain chain;
|
||||||
ssValue >> chain;
|
ssValue >> chain;
|
||||||
pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadPQHDChain(chain);
|
(pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) ? static_cast<LegacyScriptPubKeyMan*>(pwallet->GetOrCreateDescriptorPQScriptPubKeyMan()) : pwallet->GetOrCreateLegacyScriptPubKeyMan())->LoadPQHDChain(chain);
|
||||||
} else if (strType == DBKeys::OLD_KEY) {
|
} else if (strType == DBKeys::OLD_KEY) {
|
||||||
strErr = "Found unsupported 'wkey' record, try loading with version 0.18";
|
strErr = "Found unsupported 'wkey' record, try loading with version 0.18";
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -907,6 +907,15 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
|
||||||
}
|
}
|
||||||
m_batch->CloseCursor();
|
m_batch->CloseCursor();
|
||||||
|
|
||||||
|
// Descriptor wallets use one dedicated receive-side PQ manager.
|
||||||
|
// Existing v0.2.1 wallets may not have any PQ records yet, so create the
|
||||||
|
// empty manager before restoring active ScriptPubKeyMan mappings.
|
||||||
|
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) &&
|
||||||
|
!pwallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER) &&
|
||||||
|
!pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
||||||
|
pwallet->GetOrCreateDescriptorPQScriptPubKeyMan();
|
||||||
|
}
|
||||||
|
|
||||||
// Set the active ScriptPubKeyMans
|
// Set the active ScriptPubKeyMans
|
||||||
for (auto spk_man_pair : wss.m_active_external_spks) {
|
for (auto spk_man_pair : wss.m_active_external_spks) {
|
||||||
pwallet->LoadActiveScriptPubKeyMan(spk_man_pair.second, spk_man_pair.first, /* internal */ false);
|
pwallet->LoadActiveScriptPubKeyMan(spk_man_pair.second, spk_man_pair.first, /* internal */ false);
|
||||||
|
|
@ -915,6 +924,17 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
|
||||||
pwallet->LoadActiveScriptPubKeyMan(spk_man_pair.second, spk_man_pair.first, /* internal */ true);
|
pwallet->LoadActiveScriptPubKeyMan(spk_man_pair.second, spk_man_pair.first, /* internal */ true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auto* pq_manager = pwallet->GetDescriptorPQScriptPubKeyMan()) {
|
||||||
|
const uint256 pq_id = pq_manager->GetID();
|
||||||
|
|
||||||
|
if (pwallet->GetScriptPubKeyMan(OutputType::MLDSA, false) != pq_manager) {
|
||||||
|
pwallet->AddActiveScriptPubKeyMan(pq_id, OutputType::MLDSA, false);
|
||||||
|
}
|
||||||
|
if (pwallet->GetScriptPubKeyMan(OutputType::SLHDSA, false) != pq_manager) {
|
||||||
|
pwallet->AddActiveScriptPubKeyMan(pq_id, OutputType::SLHDSA, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set the descriptor caches
|
// Set the descriptor caches
|
||||||
for (auto desc_cache_pair : wss.m_descriptor_caches) {
|
for (auto desc_cache_pair : wss.m_descriptor_caches) {
|
||||||
auto spk_man = pwallet->GetScriptPubKeyMan(desc_cache_pair.first);
|
auto spk_man = pwallet->GetScriptPubKeyMan(desc_cache_pair.first);
|
||||||
|
|
@ -940,21 +960,16 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
|
||||||
if (result != DBErrors::LOAD_OK)
|
if (result != DBErrors::LOAD_OK)
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
// Reconcile the post-quantum HD derivation state after all wallet
|
// Reconcile the post-quantum derivation counters after all relevant
|
||||||
// database records have been loaded.
|
// wallet records have been loaded.
|
||||||
if (LegacyScriptPubKeyMan* legacy_spkm = pwallet->GetLegacyScriptPubKeyMan()) {
|
LegacyScriptPubKeyMan* pq_spkm =
|
||||||
std::string pq_error;
|
pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)
|
||||||
if (!legacy_spkm->ReconcilePQHDChain(*this, pq_error)) {
|
? static_cast<LegacyScriptPubKeyMan*>(pwallet->GetDescriptorPQScriptPubKeyMan())
|
||||||
pwallet->WalletLogPrintf("Error reconciling post-quantum HD chain: %s\n", pq_error);
|
: pwallet->GetLegacyScriptPubKeyMan();
|
||||||
return DBErrors::CORRUPT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reconcile the post-quantum HD derivation state after all wallet
|
if (pq_spkm) {
|
||||||
// database records have been loaded.
|
|
||||||
if (LegacyScriptPubKeyMan* legacy_spkm = pwallet->GetLegacyScriptPubKeyMan()) {
|
|
||||||
std::string pq_error;
|
std::string pq_error;
|
||||||
if (!legacy_spkm->ReconcilePQHDChain(*this, pq_error)) {
|
if (!pq_spkm->ReconcilePQHDChain(*this, pq_error)) {
|
||||||
pwallet->WalletLogPrintf("Error reconciling post-quantum HD chain: %s\n", pq_error);
|
pwallet->WalletLogPrintf("Error reconciling post-quantum HD chain: %s\n", pq_error);
|
||||||
return DBErrors::CORRUPT;
|
return DBErrors::CORRUPT;
|
||||||
}
|
}
|
||||||
|
|
@ -1010,14 +1025,19 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
|
||||||
|
|
||||||
// Set the inactive chain
|
// Set the inactive chain
|
||||||
if (wss.m_hd_chains.size() > 0) {
|
if (wss.m_hd_chains.size() > 0) {
|
||||||
LegacyScriptPubKeyMan* legacy_spkm = pwallet->GetLegacyScriptPubKeyMan();
|
LegacyScriptPubKeyMan* hd_spkm =
|
||||||
if (!legacy_spkm) {
|
pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)
|
||||||
pwallet->WalletLogPrintf("Inactive HD Chains found but no Legacy ScriptPubKeyMan\n");
|
? static_cast<LegacyScriptPubKeyMan*>(pwallet->GetDescriptorPQScriptPubKeyMan())
|
||||||
|
: pwallet->GetLegacyScriptPubKeyMan();
|
||||||
|
|
||||||
|
if (!hd_spkm) {
|
||||||
|
pwallet->WalletLogPrintf("Inactive HD Chains found but no compatible ScriptPubKeyMan\n");
|
||||||
return DBErrors::CORRUPT;
|
return DBErrors::CORRUPT;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& chain_pair : wss.m_hd_chains) {
|
for (const auto& chain_pair : wss.m_hd_chains) {
|
||||||
if (chain_pair.first != pwallet->GetLegacyScriptPubKeyMan()->GetHDChain().seed_id) {
|
if (chain_pair.first != hd_spkm->GetHDChain().seed_id) {
|
||||||
pwallet->GetLegacyScriptPubKeyMan()->AddInactiveHDChain(chain_pair.second);
|
hd_spkm->AddInactiveHDChain(chain_pair.second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,74 @@ class WalletDescriptorTest(DigiByteTestFramework):
|
||||||
enc_rpc = self.nodes[0].get_wallet_rpc('desc_enc')
|
enc_rpc = self.nodes[0].get_wallet_rpc('desc_enc')
|
||||||
enc_rpc.getnewaddress() # Makes sure that we can get a new address from a born encrypted wallet
|
enc_rpc.getnewaddress() # Makes sure that we can get a new address from a born encrypted wallet
|
||||||
|
|
||||||
|
self.log.info("Test post-quantum descriptor wallet support")
|
||||||
|
self.nodes[0].createwallet(wallet_name='desc_pq', descriptors=True)
|
||||||
|
pq_rpc = self.nodes[0].get_wallet_rpc('desc_pq')
|
||||||
|
|
||||||
|
mldsa_addr = pq_rpc.getnewaddress('', 'mldsa')
|
||||||
|
slhdsa_addr = pq_rpc.getnewaddress('', 'slhdsa')
|
||||||
|
|
||||||
|
mldsa_info = pq_rpc.getaddressinfo(mldsa_addr)
|
||||||
|
slhdsa_info = pq_rpc.getaddressinfo(slhdsa_addr)
|
||||||
|
|
||||||
|
assert_equal(mldsa_info['ismine'], True)
|
||||||
|
assert_equal(mldsa_info['iswitness'], True)
|
||||||
|
assert_equal(mldsa_info['witness_version'], 2)
|
||||||
|
assert_equal(slhdsa_info['ismine'], True)
|
||||||
|
assert_equal(slhdsa_info['iswitness'], True)
|
||||||
|
assert_equal(slhdsa_info['witness_version'], 3)
|
||||||
|
|
||||||
|
# Exercise wallet database persistence and active PQ manager restoration.
|
||||||
|
self.nodes[0].unloadwallet('desc_pq')
|
||||||
|
self.nodes[0].loadwallet('desc_pq')
|
||||||
|
pq_rpc = self.nodes[0].get_wallet_rpc('desc_pq')
|
||||||
|
|
||||||
|
assert_equal(pq_rpc.getaddressinfo(mldsa_addr)['ismine'], True)
|
||||||
|
assert_equal(pq_rpc.getaddressinfo(slhdsa_addr)['ismine'], True)
|
||||||
|
assert_equal(pq_rpc.getaddressinfo(pq_rpc.getnewaddress('', 'mldsa'))['witness_version'], 2)
|
||||||
|
assert_equal(pq_rpc.getaddressinfo(pq_rpc.getnewaddress('', 'slhdsa'))['witness_version'], 3)
|
||||||
|
|
||||||
|
self.log.info("Test encrypted post-quantum descriptor wallet support")
|
||||||
|
self.nodes[0].createwallet('desc_pq_enc', False, False, 'pqpass', False, True)
|
||||||
|
pq_enc_rpc = self.nodes[0].get_wallet_rpc('desc_pq_enc')
|
||||||
|
|
||||||
|
assert_raises_rpc_error(
|
||||||
|
-12,
|
||||||
|
'Please enter the wallet passphrase with walletpassphrase first',
|
||||||
|
pq_enc_rpc.getnewaddress,
|
||||||
|
'',
|
||||||
|
'mldsa',
|
||||||
|
)
|
||||||
|
|
||||||
|
pq_enc_rpc.walletpassphrase('pqpass', 10)
|
||||||
|
enc_mldsa_addr = pq_enc_rpc.getnewaddress('', 'mldsa')
|
||||||
|
enc_slhdsa_addr = pq_enc_rpc.getnewaddress('', 'slhdsa')
|
||||||
|
assert_equal(pq_enc_rpc.getaddressinfo(enc_mldsa_addr)['witness_version'], 2)
|
||||||
|
assert_equal(pq_enc_rpc.getaddressinfo(enc_slhdsa_addr)['witness_version'], 3)
|
||||||
|
pq_enc_rpc.walletlock()
|
||||||
|
|
||||||
|
# Reload must restore encrypted PQ keys and leave the wallet locked.
|
||||||
|
self.nodes[0].unloadwallet('desc_pq_enc')
|
||||||
|
self.nodes[0].loadwallet('desc_pq_enc')
|
||||||
|
pq_enc_rpc = self.nodes[0].get_wallet_rpc('desc_pq_enc')
|
||||||
|
|
||||||
|
assert_equal(pq_enc_rpc.getwalletinfo()['unlocked_until'], 0)
|
||||||
|
assert_equal(pq_enc_rpc.getaddressinfo(enc_mldsa_addr)['ismine'], True)
|
||||||
|
assert_equal(pq_enc_rpc.getaddressinfo(enc_slhdsa_addr)['ismine'], True)
|
||||||
|
|
||||||
|
assert_raises_rpc_error(
|
||||||
|
-12,
|
||||||
|
'Please enter the wallet passphrase with walletpassphrase first',
|
||||||
|
pq_enc_rpc.getnewaddress,
|
||||||
|
'',
|
||||||
|
'slhdsa',
|
||||||
|
)
|
||||||
|
|
||||||
|
pq_enc_rpc.walletpassphrase('pqpass', 10)
|
||||||
|
assert_equal(pq_enc_rpc.getaddressinfo(pq_enc_rpc.getnewaddress('', 'mldsa'))['witness_version'], 2)
|
||||||
|
assert_equal(pq_enc_rpc.getaddressinfo(pq_enc_rpc.getnewaddress('', 'slhdsa'))['witness_version'], 3)
|
||||||
|
pq_enc_rpc.walletlock()
|
||||||
|
|
||||||
self.log.info("Test blank descriptor wallets")
|
self.log.info("Test blank descriptor wallets")
|
||||||
self.nodes[0].createwallet(wallet_name='desc_blank', blank=True, descriptors=True)
|
self.nodes[0].createwallet(wallet_name='desc_blank', blank=True, descriptors=True)
|
||||||
blank_rpc = self.nodes[0].get_wallet_rpc('desc_blank')
|
blank_rpc = self.nodes[0].get_wallet_rpc('desc_blank')
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue