Add post-quantum wallet integration

This commit is contained in:
Capri 2026-08-31 19:23:30 +00:00
commit f283edf8f0
Signed by: VextaProject
SSH key fingerprint: SHA256:739BZv8LvIDY2tR7lXLy/IQ6MaGTQtYcG9GNvCjmpVM
15 changed files with 1013 additions and 16 deletions

View file

@ -475,9 +475,9 @@ public:
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height = 0; // No activation delay
// VEXTA quantum-resistant witness v2/v3.
// Integrated but intentionally not active yet.
// Always active on regtest for development and testing.
consensus.vDeployments[Consensus::DEPLOYMENT_PQR].bit = 3;
consensus.vDeployments[Consensus::DEPLOYMENT_PQR].nStartTime = Consensus::BIP9Deployment::NEVER_ACTIVE;
consensus.vDeployments[Consensus::DEPLOYMENT_PQR].nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE;
consensus.vDeployments[Consensus::DEPLOYMENT_PQR].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;
consensus.vDeployments[Consensus::DEPLOYMENT_PQR].min_activation_height = 0;

View file

@ -5,6 +5,7 @@
#ifndef DIGIBYTE_INTERFACES_CHAIN_H
#define DIGIBYTE_INTERFACES_CHAIN_H
#include <consensus/params.h>
#include <primitives/transaction.h> // For CTransactionRef
#include <util/settings.h> // For util::SettingsValue
@ -96,6 +97,9 @@ public:
//! any blocks)
virtual std::optional<int> getHeight() = 0;
//! Return whether a versionbits deployment is active at the current chain tip.
virtual bool isDeploymentActive(Consensus::DeploymentPos deployment) = 0;
//! Get block hash. Height must be valid or this function will abort.
virtual uint256 getBlockHash(int height) = 0;

View file

@ -441,6 +441,15 @@ public:
}
return std::nullopt;
}
bool isDeploymentActive(Consensus::DeploymentPos deployment) override
{
LOCK(::cs_main);
const CBlockIndex* tip = Assert(m_node.chainman)->ActiveChain().Tip();
if (!tip) {
return false;
}
return DeploymentActiveAt(*tip, Params().GetConsensus(), deployment);
}
uint256 getBlockHash(int height) override
{
LOCK(::cs_main);

View file

@ -20,6 +20,8 @@ static const std::string OUTPUT_TYPE_STRING_LEGACY = "legacy";
static const std::string OUTPUT_TYPE_STRING_P2SH_SEGWIT = "p2sh-segwit";
static const std::string OUTPUT_TYPE_STRING_BECH32 = "bech32";
static const std::string OUTPUT_TYPE_STRING_BECH32M = "bech32m";
static const std::string OUTPUT_TYPE_STRING_MLDSA = "mldsa";
static const std::string OUTPUT_TYPE_STRING_SLHDSA = "slhdsa";
bool ParseOutputType(const std::string& type, OutputType& output_type)
{
@ -35,6 +37,12 @@ bool ParseOutputType(const std::string& type, OutputType& output_type)
} else if (type == OUTPUT_TYPE_STRING_BECH32M) {
output_type = OutputType::BECH32M;
return true;
} else if (type == OUTPUT_TYPE_STRING_MLDSA) {
output_type = OutputType::MLDSA;
return true;
} else if (type == OUTPUT_TYPE_STRING_SLHDSA) {
output_type = OutputType::SLHDSA;
return true;
}
return false;
}
@ -46,6 +54,8 @@ const std::string& FormatOutputType(OutputType type)
case OutputType::P2SH_SEGWIT: return OUTPUT_TYPE_STRING_P2SH_SEGWIT;
case OutputType::BECH32: return OUTPUT_TYPE_STRING_BECH32;
case OutputType::BECH32M: return OUTPUT_TYPE_STRING_BECH32M;
case OutputType::MLDSA: return OUTPUT_TYPE_STRING_MLDSA;
case OutputType::SLHDSA: return OUTPUT_TYPE_STRING_SLHDSA;
} // no default case, so the compiler can warn about missing cases
assert(false);
}
@ -65,7 +75,9 @@ CTxDestination GetDestinationForKey(const CPubKey& key, OutputType type)
return witdest;
}
}
case OutputType::BECH32M: {} // This function should never be used with BECH32M, so let it assert
case OutputType::BECH32M:
case OutputType::MLDSA:
case OutputType::SLHDSA: {} // These functions should never be used with non-EC output types, so let it assert
} // no default case, so the compiler can warn about missing cases
assert(false);
}

View file

@ -20,6 +20,8 @@ enum class OutputType {
P2SH_SEGWIT,
BECH32,
BECH32M,
MLDSA,
SLHDSA,
};
static constexpr auto OUTPUT_TYPES = std::array{
@ -27,6 +29,8 @@ static constexpr auto OUTPUT_TYPES = std::array{
OutputType::P2SH_SEGWIT,
OutputType::BECH32,
OutputType::BECH32M,
OutputType::MLDSA,
OutputType::SLHDSA,
};
[[nodiscard]] bool ParseOutputType(const std::string& str, OutputType& output_type);

View file

@ -2045,8 +2045,12 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion,
// VEXTA quantum-resistant witness:
// v2 = ML-DSA-65, v3 = SLH-DSA/SPHINCS+-128s.
//
// Before softfork activation, retain unknown-witness semantics.
// Before softfork activation, retain unknown-witness consensus semantics,
// but reject these spends under standard relay policy.
if (!(flags & SCRIPT_VERIFY_PQR)) {
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM) {
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM);
}
return set_success(serror);
}

View file

@ -257,6 +257,8 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator
case TxoutType::NONSTANDARD:
case TxoutType::NULL_DATA:
case TxoutType::WITNESS_UNKNOWN:
case TxoutType::WITNESS_V2_MLDSA:
case TxoutType::WITNESS_V3_SLHDSA:
return false;
case TxoutType::PUBKEY:
if (!CreateSig(creator, sigdata, provider, sig, CPubKey(vSolutions[0]), scriptPubKey, sigversion)) return false;
@ -389,7 +391,9 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato
sigdata.scriptWitness.stack = std::move(result);
}
result.clear();
} else if (solved && whichType == TxoutType::WITNESS_UNKNOWN) {
} else if (whichType == TxoutType::WITNESS_UNKNOWN ||
whichType == TxoutType::WITNESS_V2_MLDSA ||
whichType == TxoutType::WITNESS_V3_SLHDSA) {
sigdata.witness = true;
}

View file

@ -583,6 +583,26 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
if (fRequireStandard && !IsStandardTx(tx, reason))
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, reason);
// Do not relay transactions creating post-quantum outputs before PQR activation.
if (fRequireStandard) {
const CBlockIndex* tip = m_active_chainstate.m_chain.Tip();
const bool pqr_active =
tip && DeploymentActiveAt(*tip, Params().GetConsensus(), Consensus::DEPLOYMENT_PQR);
if (!pqr_active) {
for (const CTxOut& txout : tx.vout) {
std::vector<std::vector<unsigned char>> solutions;
const TxoutType type = Solver(txout.scriptPubKey, solutions);
if (type == TxoutType::WITNESS_V2_MLDSA ||
type == TxoutType::WITNESS_V3_SLHDSA) {
return state.Invalid(
TxValidationResult::TX_NOT_STANDARD,
"pqr-premature");
}
}
}
}
// Do not work on transactions that are too small.
// A transaction with 1 segwit input and 1 P2WPHK output has non-witness size of 82 bytes.
// Transactions smaller than this are not relayed to mitigate CVE-2017-12842 by not relaying
@ -932,7 +952,13 @@ bool MemPoolAccept::PolicyScriptChecks(const ATMPArgs& args, Workspace& ws, Prec
const CTransaction& tx = *ws.m_ptx;
TxValidationState& state = ws.m_state;
constexpr unsigned int scriptVerifyFlags = STANDARD_SCRIPT_VERIFY_FLAGS;
unsigned int scriptVerifyFlags = STANDARD_SCRIPT_VERIFY_FLAGS;
const CBlockIndex* tip = m_active_chainstate.m_chain.Tip();
if (tip &&
DeploymentActiveAt(*tip, Params().GetConsensus(), Consensus::DEPLOYMENT_PQR)) {
scriptVerifyFlags |= SCRIPT_VERIFY_PQR;
}
// Check input scripts and signatures.
// This is done last to help prevent CPU exhaustion denial-of-service attacks.

View file

@ -275,6 +275,13 @@ static RPCHelpMan getnewaddress()
}
}
if ((output_type == OutputType::MLDSA || output_type == OutputType::SLHDSA) &&
!pwallet->chain().isDeploymentActive(Consensus::DEPLOYMENT_PQR)) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Post-quantum addresses are not available before PQR activation");
}
CTxDestination dest;
std::string error;
if (!pwallet->GetNewDestination(output_type, label, dest, error)) {
@ -320,6 +327,11 @@ static RPCHelpMan getrawchangeaddress()
if (output_type == OutputType::BECH32M && pwallet->GetLegacyScriptPubKeyMan()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Legacy wallets cannot provide bech32m addresses");
}
if (output_type == OutputType::MLDSA || output_type == OutputType::SLHDSA) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Post-quantum address types cannot be used for change addresses");
}
}
CTxDestination dest;
@ -3137,6 +3149,11 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out,
if (!ParseOutputType(options["change_type"].get_str(), out_type)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown change type '%s'", options["change_type"].get_str()));
}
if (out_type == OutputType::MLDSA || out_type == OutputType::SLHDSA) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Post-quantum address types cannot be used for change addresses");
}
coinControl.m_change_type.emplace(out_type);
}

View file

@ -19,7 +19,7 @@ typedef std::pair<std::vector<unsigned char>, std::vector<unsigned char> > KeyVa
static bool KeyFilter(const std::string& type)
{
return WalletBatch::IsKeyType(type) || type == DBKeys::HDCHAIN;
return WalletBatch::IsKeyType(type) || type == DBKeys::HDCHAIN || type == DBKeys::PQHDCHAIN;
}
bool RecoverDatabaseFile(const fs::path& file_path, bilingual_str& error, std::vector<bilingual_str>& warnings)

View file

@ -2,10 +2,15 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <crypto/pq/pq.h>
#include <crypto/hmac_sha512.h>
#include <crypto/sha256.h>
#include <key_io.h>
#include <logging.h>
#include <outputtype.h>
#include <policy/policy.h>
#include <script/descriptor.h>
#include <script/interpreter.h>
#include <script/sign.h>
#include <util/bip32.h>
#include <util/strencodings.h>
@ -20,10 +25,168 @@
//! Value for the first BIP 32 hardened derivation. Can be used as a bit mask and as a value. See BIP 32 for more details.
const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
static std::array<unsigned char, 64> DerivePQSeedMaterial(
const CKey& wallet_seed,
OutputType type,
uint32_t counter)
{
static constexpr unsigned char DOMAIN[] = "Vexta-PQ-HD-v1";
std::array<unsigned char, 64> out{};
const uint8_t algo =
type == OutputType::MLDSA ? 2 :
type == OutputType::SLHDSA ? 3 : 0;
unsigned char data[sizeof(DOMAIN) - 1 + 1 + 4];
size_t pos = 0;
std::memcpy(data + pos, DOMAIN, sizeof(DOMAIN) - 1);
pos += sizeof(DOMAIN) - 1;
data[pos++] = algo;
data[pos++] = static_cast<unsigned char>((counter >> 24) & 0xff);
data[pos++] = static_cast<unsigned char>((counter >> 16) & 0xff);
data[pos++] = static_cast<unsigned char>((counter >> 8) & 0xff);
data[pos++] = static_cast<unsigned char>(counter & 0xff);
CHMAC_SHA512(wallet_seed.begin(), wallet_seed.size())
.Write(data, pos)
.Finalize(out.data());
return out;
}
bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error)
{
if (type == OutputType::MLDSA || type == OutputType::SLHDSA) {
if (m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
error = _("Error: Private keys are disabled for this wallet").translated;
return false;
}
if (m_storage.IsLocked()) {
error = _("Error: Please enter the wallet passphrase with walletpassphrase first.").translated;
return false;
}
// Serialize PQ derivation so two concurrent address requests cannot
// derive from the same counter.
LOCK(cs_KeyStore);
CKey wallet_seed;
uint32_t counter = 0;
{
LOCK(cs_KeyStore);
if (m_hd_chain.seed_id.IsNull() || !GetKey(m_hd_chain.seed_id, wallet_seed)) {
error = _("Error: Wallet HD seed is not available for post-quantum key derivation").translated;
return false;
}
if (m_pq_hd_chain.seed_id.IsNull() ||
m_pq_hd_chain.seed_id != m_hd_chain.seed_id) {
error = _("Error: Post-quantum HD chain does not match the active wallet seed").translated;
return false;
}
counter = type == OutputType::MLDSA
? m_pq_hd_chain.nMLDSAExternalCounter
: m_pq_hd_chain.nSLHDSAExternalCounter;
if (counter == std::numeric_limits<uint32_t>::max()) {
error = _("Error: Post-quantum address derivation counter exhausted").translated;
return false;
}
}
const auto seed_material = DerivePQSeedMaterial(wallet_seed, type, counter);
std::vector<unsigned char> pubkey;
CKeyingMaterial secret;
if (type == OutputType::MLDSA) {
pubkey.resize(pq::MLDSA65::PUBKEY_BYTES);
secret.resize(pq::MLDSA65::SECKEY_BYTES);
if (!pq::MLDSA65::keygen_from_seed(pubkey.data(), secret.data(), seed_material.data())) {
error = _("Error: Failed to derive ML-DSA key").translated;
return false;
}
} else {
pubkey.resize(pq::SPHINCS128s::PUBKEY_BYTES);
secret.resize(pq::SPHINCS128s::SECKEY_BYTES);
if (!pq::SPHINCS128s::keygen_from_seed(pubkey.data(), secret.data(), seed_material.data())) {
error = _("Error: Failed to derive SLH-DSA key").translated;
return false;
}
}
WalletBatch batch(m_storage.GetDatabase());
if (!batch.TxnBegin()) {
error = _("Error: Failed to begin post-quantum wallet database transaction").translated;
return false;
}
uint256 key_id;
if (!AddPQKey(batch, type, pubkey, secret, key_id)) {
batch.TxnAbort();
{
LOCK(cs_KeyStore);
mapPQKeys.erase(key_id);
mapCryptedPQKeys.erase(key_id);
}
error = _("Error: Failed to store post-quantum key").translated;
return false;
}
CKeyMetadata metadata(GetTime());
metadata.hd_seed_id = wallet_seed.GetPubKey().GetID();
metadata.hdKeypath = strprintf(
"pq/%s/%u",
type == OutputType::MLDSA ? "mldsa" : "slhdsa",
counter);
PQHDChain next_pq_hd_chain;
{
LOCK(cs_KeyStore);
next_pq_hd_chain = m_pq_hd_chain;
}
if (type == OutputType::MLDSA) {
++next_pq_hd_chain.nMLDSAExternalCounter;
} else {
++next_pq_hd_chain.nSLHDSAExternalCounter;
}
if (!batch.WritePQKeyMetadata(metadata, key_id, false) ||
!batch.WritePQHDChain(next_pq_hd_chain) ||
!batch.TxnCommit()) {
batch.TxnAbort();
{
LOCK(cs_KeyStore);
mapPQKeys.erase(key_id);
mapCryptedPQKeys.erase(key_id);
}
error = _("Error: Failed to commit post-quantum key data").translated;
return false;
}
{
LOCK(cs_KeyStore);
m_pq_hd_chain = next_pq_hd_chain;
mapPQKeyMetadata[key_id] = metadata;
UpdateTimeFirstKey(metadata.nCreateTime);
}
dest = type == OutputType::MLDSA
? CTxDestination{WitnessV2MLDSA(key_id)}
: CTxDestination{WitnessV3SLHDSA(key_id)};
error.clear();
return true;
}
if (LEGACY_OUTPUT_TYPES.count(type) == 0) {
error = _("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types").translated;
error = _("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", \"bech32\", \"mldsa\", and \"slhdsa\" address types").translated;
return false;
}
assert(type != OutputType::BECH32M);
@ -176,6 +339,21 @@ IsMineResult IsMineInner(const LegacyScriptPubKeyMan& keystore, const CScript& s
break;
}
case TxoutType::WITNESS_V2_MLDSA:
case TxoutType::WITNESS_V3_SLHDSA:
{
if (sigversion != IsMineSigVersion::TOP || vSolutions.size() != 1 || vSolutions[0].size() != 32) {
return IsMineResult::INVALID;
}
uint256 key_id;
std::copy(vSolutions[0].begin(), vSolutions[0].end(), key_id.begin());
if (keystore.HavePQKey(key_id)) {
ret = std::max(ret, IsMineResult::SPENDABLE);
}
break;
}
case TxoutType::MULTISIG:
{
// Never treat bare multisig outputs as ours (they can still be made watchonly-though)
@ -230,8 +408,9 @@ bool LegacyScriptPubKeyMan::CheckDecryptionKey(const CKeyingMaterial& master_key
{
LOCK(cs_KeyStore);
assert(mapKeys.empty());
assert(mapPQKeys.empty());
bool keyPass = mapCryptedKeys.empty(); // Always pass when there are no encrypted keys
bool keyPass = mapCryptedKeys.empty() && mapCryptedPQKeys.empty();
bool keyFail = false;
CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin();
WalletBatch batch(m_storage.GetDatabase());
@ -253,6 +432,48 @@ bool LegacyScriptPubKeyMan::CheckDecryptionKey(const CKeyingMaterial& master_key
batch.WriteCryptedKey(vchPubKey, vchCryptedSecret, mapKeyMetadata[vchPubKey.GetID()]);
}
}
if (!keyFail && !mapCryptedPQKeys.empty()) {
for (const auto& item : mapCryptedPQKeys) {
const uint256& key_id = item.first;
const CryptedPQKeyData& pq_key = item.second;
uint256 calculated_id;
CSHA256().Write(pq_key.pubkey.data(), pq_key.pubkey.size()).Finalize(calculated_id.begin());
if (calculated_id != key_id) {
keyFail = true;
break;
}
const size_t expected_secret_size =
pq_key.type == OutputType::MLDSA ? pq::MLDSA65::SECKEY_BYTES :
pq_key.type == OutputType::SLHDSA ? pq::SPHINCS128s::SECKEY_BYTES : 0;
const size_t expected_pubkey_size =
pq_key.type == OutputType::MLDSA ? pq::MLDSA65::PUBKEY_BYTES :
pq_key.type == OutputType::SLHDSA ? pq::SPHINCS128s::PUBKEY_BYTES : 0;
if (expected_secret_size == 0 || pq_key.pubkey.size() != expected_pubkey_size) {
keyFail = true;
break;
}
CKeyingMaterial plain_secret;
if (!DecryptSecret(master_key, pq_key.crypted_secret, key_id, plain_secret) ||
plain_secret.size() != expected_secret_size) {
keyFail = true;
break;
}
keyPass = true;
// After a full successful check, later unlocks only need to
// verify one PQ key. Before that, verify every PQ key.
if (fDecryptionThoroughlyChecked) {
break;
}
}
}
if (keyPass && keyFail)
{
LogPrintf("The wallet is probably corrupted: Some keys decrypt but not all.\n");
@ -269,7 +490,7 @@ bool LegacyScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, WalletBat
{
LOCK(cs_KeyStore);
encrypted_batch = batch;
if (!mapCryptedKeys.empty()) {
if (!mapCryptedKeys.empty() || !mapCryptedPQKeys.empty()) {
encrypted_batch = nullptr;
return false;
}
@ -291,12 +512,47 @@ bool LegacyScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, WalletBat
return false;
}
}
PQKeyMap pq_keys_to_encrypt;
pq_keys_to_encrypt.swap(mapPQKeys);
for (const auto& item : pq_keys_to_encrypt) {
const uint256& key_id = item.first;
const PQKeyData& pq_key = item.second;
CKeyingMaterial plain_secret(pq_key.secret.begin(), pq_key.secret.end());
std::vector<unsigned char> crypted_secret;
if (!EncryptSecret(master_key, plain_secret, key_id, crypted_secret)) {
encrypted_batch = nullptr;
return false;
}
if (!AddCryptedPQKeyInner(key_id, pq_key.type, pq_key.pubkey, crypted_secret)) {
encrypted_batch = nullptr;
return false;
}
bool write_ok = batch
? batch->WriteCryptedPQKey(key_id, pq_key.type, pq_key.pubkey, crypted_secret)
: WalletBatch(m_storage.GetDatabase()).WriteCryptedPQKey(
key_id, pq_key.type, pq_key.pubkey, crypted_secret);
if (!write_ok) {
encrypted_batch = nullptr;
return false;
}
}
encrypted_batch = nullptr;
return true;
}
bool LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, std::string& error)
{
if (type == OutputType::MLDSA || type == OutputType::SLHDSA) {
error = _("Error: Post-quantum addresses are not available from the legacy keypool").translated;
return false;
}
if (LEGACY_OUTPUT_TYPES.count(type) == 0) {
error = _("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types").translated;
return false;
@ -507,7 +763,10 @@ bool LegacyScriptPubKeyMan::Upgrade(int prev_version, int new_version, bilingual
bool LegacyScriptPubKeyMan::HavePrivateKeys() const
{
LOCK(cs_KeyStore);
return !mapKeys.empty() || !mapCryptedKeys.empty();
return !mapKeys.empty() ||
!mapCryptedKeys.empty() ||
!mapPQKeys.empty() ||
!mapCryptedPQKeys.empty();
}
void LegacyScriptPubKeyMan::RewriteDB()
@ -601,7 +860,141 @@ bool LegacyScriptPubKeyMan::CanProvide(const CScript& script, SignatureData& sig
bool LegacyScriptPubKeyMan::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const
{
return ::SignTransaction(tx, this, coins, sighash, input_errors);
const int effective_sighash = sighash == SIGHASH_DEFAULT ? SIGHASH_ALL : sighash;
if (effective_sighash != SIGHASH_ALL) {
return ::SignTransaction(tx, this, coins, effective_sighash, input_errors);
}
// Sign all conventional inputs first. Post-quantum inputs are handled
// separately below so their witness data and verification errors cannot
// be overwritten by the generic signing path.
(void)::SignTransaction(tx, this, coins, effective_sighash, input_errors);
const CTransaction tx_const(tx);
std::vector<CTxOut> spent_outputs(tx.vin.size());
bool have_all_spent_outputs = true;
for (unsigned int i = 0; i < tx.vin.size(); ++i) {
auto coin = coins.find(tx.vin[i].prevout);
if (coin == coins.end() || coin->second.IsSpent()) {
have_all_spent_outputs = false;
continue;
}
spent_outputs[i] = coin->second.out;
}
PrecomputedTransactionData txdata;
if (have_all_spent_outputs) {
txdata.Init(tx_const, std::move(spent_outputs), true);
} else {
txdata.Init(tx_const, {}, true);
}
for (unsigned int i = 0; i < tx.vin.size(); ++i) {
auto coin = coins.find(tx.vin[i].prevout);
if (coin == coins.end() || coin->second.IsSpent()) {
continue;
}
std::vector<valtype> solutions;
const TxoutType type = Solver(coin->second.out.scriptPubKey, solutions);
int witness_version = 0;
OutputType output_type;
if (type == TxoutType::WITNESS_V2_MLDSA) {
witness_version = 2;
output_type = OutputType::MLDSA;
} else if (type == TxoutType::WITNESS_V3_SLHDSA) {
witness_version = 3;
output_type = OutputType::SLHDSA;
} else {
continue;
}
if (solutions.size() != 1 || solutions[0].size() != 32) {
input_errors[i] = "Invalid post-quantum witness program";
continue;
}
uint256 key_id;
std::copy(solutions[0].begin(), solutions[0].end(), key_id.begin());
OutputType stored_type;
std::vector<unsigned char> pubkey;
CKeyingMaterial secret;
if (!GetPQKey(key_id, stored_type, pubkey, secret) || stored_type != output_type) {
input_errors[i] = "Post-quantum private key not available";
continue;
}
const uint256 hash = SignatureHashPQR(
tx_const,
i,
witness_version,
solutions[0],
coin->second.out.nValue,
txdata);
std::vector<unsigned char> signature(
output_type == OutputType::MLDSA
? pq::MLDSA65::SIG_BYTES
: pq::SPHINCS128s::SIG_BYTES);
size_t siglen = 0;
const bool signed_ok =
output_type == OutputType::MLDSA
? pq::MLDSA65::sign(
signature.data(),
&siglen,
hash.begin(),
hash.size(),
secret.data())
: pq::SPHINCS128s::sign(
signature.data(),
&siglen,
hash.begin(),
hash.size(),
secret.data());
if (!signed_ok || siglen != signature.size()) {
input_errors[i] = "Post-quantum signing failed";
continue;
}
tx.vin[i].scriptSig.clear();
tx.vin[i].scriptWitness.stack.clear();
tx.vin[i].scriptWitness.stack.push_back(std::move(signature));
tx.vin[i].scriptWitness.stack.push_back(std::move(pubkey));
ScriptError serror = SCRIPT_ERR_OK;
const unsigned int pqr_flags =
(STANDARD_SCRIPT_VERIFY_FLAGS & ~SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM) |
SCRIPT_VERIFY_PQR;
if (!VerifyScript(
tx.vin[i].scriptSig,
coin->second.out.scriptPubKey,
&tx.vin[i].scriptWitness,
pqr_flags,
TransactionSignatureChecker(
&tx_const,
i,
coin->second.out.nValue,
txdata,
MissingDataBehavior::FAIL),
&serror)) {
input_errors[i] = ScriptErrorString(serror);
continue;
}
input_errors.erase(i);
}
return input_errors.empty();
}
SigningResult LegacyScriptPubKeyMan::SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const
@ -669,6 +1062,24 @@ std::unique_ptr<CKeyMetadata> LegacyScriptPubKeyMan::GetMetadata(const CTxDestin
{
LOCK(cs_KeyStore);
uint256 pq_key_id;
bool have_pq_key_id = false;
if (const auto* mldsa = std::get_if<WitnessV2MLDSA>(&dest)) {
pq_key_id = uint256(*mldsa);
have_pq_key_id = true;
} else if (const auto* slhdsa = std::get_if<WitnessV3SLHDSA>(&dest)) {
pq_key_id = uint256(*slhdsa);
have_pq_key_id = true;
}
if (have_pq_key_id) {
auto it = mapPQKeyMetadata.find(pq_key_id);
if (it != mapPQKeyMetadata.end()) {
return std::make_unique<CKeyMetadata>(it->second);
}
}
CKeyID key_id = GetKeyForDestination(*this, dest);
if (!key_id.IsNull()) {
auto it = mapKeyMetadata.find(key_id);
@ -781,6 +1192,13 @@ void LegacyScriptPubKeyMan::LoadKeyMetadata(const CKeyID& keyID, const CKeyMetad
mapKeyMetadata[keyID] = meta;
}
void LegacyScriptPubKeyMan::LoadPQKeyMetadata(const uint256& key_id, const CKeyMetadata& meta)
{
LOCK(cs_KeyStore);
UpdateTimeFirstKey(meta.nCreateTime);
mapPQKeyMetadata[key_id] = meta;
}
void LegacyScriptPubKeyMan::LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata& meta)
{
LOCK(cs_KeyStore);
@ -821,6 +1239,174 @@ bool LegacyScriptPubKeyMan::LoadCryptedKey(const CPubKey &vchPubKey, const std::
return AddCryptedKeyInner(vchPubKey, vchCryptedSecret);
}
bool LegacyScriptPubKeyMan::AddPQKeyInner(const uint256& key_id, OutputType type,
const std::vector<unsigned char>& pubkey,
const CKeyingMaterial& secret)
{
LOCK(cs_KeyStore);
mapPQKeys[key_id] = PQKeyData{type, pubkey, secret};
return true;
}
bool LegacyScriptPubKeyMan::AddCryptedPQKeyInner(const uint256& key_id, OutputType type,
const std::vector<unsigned char>& pubkey,
const std::vector<unsigned char>& crypted_secret)
{
LOCK(cs_KeyStore);
mapCryptedPQKeys[key_id] = CryptedPQKeyData{type, pubkey, crypted_secret};
return true;
}
bool LegacyScriptPubKeyMan::AddPQKey(WalletBatch& batch,
OutputType type,
const std::vector<unsigned char>& pubkey,
const CKeyingMaterial& secret,
uint256& key_id)
{
const size_t expected_pubkey_size =
type == OutputType::MLDSA ? pq::MLDSA65::PUBKEY_BYTES :
type == OutputType::SLHDSA ? pq::SPHINCS128s::PUBKEY_BYTES : 0;
const size_t expected_secret_size =
type == OutputType::MLDSA ? pq::MLDSA65::SECKEY_BYTES :
type == OutputType::SLHDSA ? pq::SPHINCS128s::SECKEY_BYTES : 0;
if (expected_pubkey_size == 0 ||
pubkey.size() != expected_pubkey_size ||
secret.size() != expected_secret_size) {
return false;
}
CSHA256().Write(pubkey.data(), pubkey.size()).Finalize(key_id.begin());
LOCK(cs_KeyStore);
if (!m_storage.HasEncryptionKeys()) {
CKeyingMaterial secure_secret(secret.begin(), secret.end());
if (!AddPQKeyInner(key_id, type, pubkey, secure_secret)) {
return false;
}
const std::vector<unsigned char> db_secret(secret.begin(), secret.end());
return batch.WritePQKey(key_id, type, pubkey, db_secret);
}
if (m_storage.IsLocked()) {
return false;
}
CKeyingMaterial plain_secret(secret.begin(), secret.end());
std::vector<unsigned char> crypted_secret;
if (!EncryptSecret(m_storage.GetEncryptionKey(), plain_secret, key_id, crypted_secret)) {
return false;
}
if (!AddCryptedPQKeyInner(key_id, type, pubkey, crypted_secret)) {
return false;
}
return batch.WriteCryptedPQKey(
key_id, type, pubkey, crypted_secret);
}
bool LegacyScriptPubKeyMan::HavePQKey(const uint256& key_id) const
{
LOCK(cs_KeyStore);
return mapPQKeys.count(key_id) != 0 || mapCryptedPQKeys.count(key_id) != 0;
}
bool LegacyScriptPubKeyMan::GetPQKey(const uint256& key_id, OutputType& type,
std::vector<unsigned char>& pubkey,
CKeyingMaterial& secret) const
{
LOCK(cs_KeyStore);
auto plain_it = mapPQKeys.find(key_id);
if (plain_it != mapPQKeys.end()) {
type = plain_it->second.type;
pubkey = plain_it->second.pubkey;
secret.assign(plain_it->second.secret.begin(), plain_it->second.secret.end());
return true;
}
auto crypted_it = mapCryptedPQKeys.find(key_id);
if (crypted_it == mapCryptedPQKeys.end() || m_storage.IsLocked()) {
return false;
}
type = crypted_it->second.type;
pubkey = crypted_it->second.pubkey;
if (!DecryptSecret(
m_storage.GetEncryptionKey(),
crypted_it->second.crypted_secret,
key_id,
secret)) {
return false;
}
const size_t expected_secret_size =
type == OutputType::MLDSA ? pq::MLDSA65::SECKEY_BYTES :
type == OutputType::SLHDSA ? pq::SPHINCS128s::SECKEY_BYTES : 0;
return expected_secret_size != 0 && secret.size() == expected_secret_size;
}
bool LegacyScriptPubKeyMan::LoadPQKey(const uint256& key_id, OutputType type,
const std::vector<unsigned char>& pubkey,
const std::vector<unsigned char>& secret)
{
const size_t expected_pubkey_size =
type == OutputType::MLDSA ? pq::MLDSA65::PUBKEY_BYTES :
type == OutputType::SLHDSA ? pq::SPHINCS128s::PUBKEY_BYTES : 0;
const size_t expected_secret_size =
type == OutputType::MLDSA ? pq::MLDSA65::SECKEY_BYTES :
type == OutputType::SLHDSA ? pq::SPHINCS128s::SECKEY_BYTES : 0;
if (expected_pubkey_size == 0 ||
pubkey.size() != expected_pubkey_size ||
secret.size() != expected_secret_size) {
return false;
}
uint256 calculated_id;
CSHA256().Write(pubkey.data(), pubkey.size()).Finalize(calculated_id.begin());
if (calculated_id != key_id) {
return false;
}
LOCK(cs_KeyStore);
CKeyingMaterial secure_secret(secret.begin(), secret.end());
mapPQKeys[key_id] = PQKeyData{type, pubkey, std::move(secure_secret)};
return true;
}
bool LegacyScriptPubKeyMan::LoadCryptedPQKey(const uint256& key_id, OutputType type,
const std::vector<unsigned char>& pubkey,
const std::vector<unsigned char>& crypted_secret)
{
const size_t expected_pubkey_size =
type == OutputType::MLDSA ? pq::MLDSA65::PUBKEY_BYTES :
type == OutputType::SLHDSA ? pq::SPHINCS128s::PUBKEY_BYTES : 0;
if (expected_pubkey_size == 0 ||
pubkey.size() != expected_pubkey_size ||
crypted_secret.empty()) {
return false;
}
uint256 calculated_id;
CSHA256().Write(pubkey.data(), pubkey.size()).Finalize(calculated_id.begin());
if (calculated_id != key_id) {
return false;
}
LOCK(cs_KeyStore);
mapCryptedPQKeys[key_id] = CryptedPQKeyData{type, pubkey, crypted_secret};
return true;
}
bool LegacyScriptPubKeyMan::AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret)
{
LOCK(cs_KeyStore);
@ -944,6 +1530,95 @@ void LegacyScriptPubKeyMan::LoadHDChain(const CHDChain& chain)
m_hd_chain = chain;
}
void LegacyScriptPubKeyMan::LoadPQHDChain(const PQHDChain& chain)
{
LOCK(cs_KeyStore);
m_pq_hd_chain = chain;
}
bool LegacyScriptPubKeyMan::ReconcilePQHDChain(WalletBatch& batch, std::string& error)
{
LOCK(cs_KeyStore);
if (m_hd_chain.seed_id.IsNull()) {
return true;
}
uint32_t next_mldsa = 0;
uint32_t next_slhdsa = 0;
bool have_pq_metadata = false;
for (const auto& item : mapPQKeyMetadata) {
const CKeyMetadata& meta = item.second;
if (meta.hd_seed_id != m_hd_chain.seed_id) {
continue;
}
const std::string mldsa_prefix = "pq/mldsa/";
const std::string slhdsa_prefix = "pq/slhdsa/";
uint32_t index = 0;
if (meta.hdKeypath.rfind(mldsa_prefix, 0) == 0) {
if (!ParseUInt32(meta.hdKeypath.substr(mldsa_prefix.size()), &index)) {
error = "Invalid ML-DSA post-quantum derivation metadata";
return false;
}
if (index == std::numeric_limits<uint32_t>::max()) {
error = "ML-DSA post-quantum derivation counter exhausted";
return false;
}
next_mldsa = std::max(next_mldsa, index + 1);
have_pq_metadata = true;
} else if (meta.hdKeypath.rfind(slhdsa_prefix, 0) == 0) {
if (!ParseUInt32(meta.hdKeypath.substr(slhdsa_prefix.size()), &index)) {
error = "Invalid SLH-DSA post-quantum derivation metadata";
return false;
}
if (index == std::numeric_limits<uint32_t>::max()) {
error = "SLH-DSA post-quantum derivation counter exhausted";
return false;
}
next_slhdsa = std::max(next_slhdsa, index + 1);
have_pq_metadata = true;
}
}
PQHDChain reconciled = m_pq_hd_chain;
if (reconciled.seed_id.IsNull()) {
reconciled.seed_id = m_hd_chain.seed_id;
reconciled.nMLDSAExternalCounter = next_mldsa;
reconciled.nSLHDSAExternalCounter = next_slhdsa;
} else if (reconciled.seed_id != m_hd_chain.seed_id) {
if (!have_pq_metadata) {
error = "Post-quantum HD chain seed does not match the active wallet seed";
return false;
}
reconciled.seed_id = m_hd_chain.seed_id;
reconciled.nMLDSAExternalCounter = next_mldsa;
reconciled.nSLHDSAExternalCounter = next_slhdsa;
} else {
reconciled.nMLDSAExternalCounter =
std::max(reconciled.nMLDSAExternalCounter, next_mldsa);
reconciled.nSLHDSAExternalCounter =
std::max(reconciled.nSLHDSAExternalCounter, next_slhdsa);
}
if (reconciled.seed_id != m_pq_hd_chain.seed_id ||
reconciled.nMLDSAExternalCounter != m_pq_hd_chain.nMLDSAExternalCounter ||
reconciled.nSLHDSAExternalCounter != m_pq_hd_chain.nSLHDSAExternalCounter) {
if (!batch.WritePQHDChain(reconciled)) {
error = "Failed to write reconciled post-quantum HD chain";
return false;
}
m_pq_hd_chain = reconciled;
}
return true;
}
void LegacyScriptPubKeyMan::AddHDChain(const CHDChain& chain)
{
LOCK(cs_KeyStore);
@ -1208,6 +1883,14 @@ void LegacyScriptPubKeyMan::SetHDSeed(const CPubKey& seed)
newHdChain.nVersion = m_storage.CanSupportFeature(FEATURE_HD_SPLIT) ? CHDChain::VERSION_HD_CHAIN_SPLIT : CHDChain::VERSION_HD_BASE;
newHdChain.seed_id = seed.GetID();
AddHDChain(newHdChain);
PQHDChain newPQHDChain;
newPQHDChain.seed_id = seed.GetID();
if (!WalletBatch(m_storage.GetDatabase()).WritePQHDChain(newPQHDChain)) {
throw std::runtime_error(std::string(__func__) + ": writing post-quantum HD chain failed");
}
m_pq_hd_chain = newPQHDChain;
NotifyCanGetAddressesChanged();
WalletBatch batch(m_storage.GetDatabase());
m_storage.UnsetBlankWalletFlag(batch);

View file

@ -256,6 +256,8 @@ static const std::unordered_set<OutputType> LEGACY_OUTPUT_TYPES {
OutputType::LEGACY,
OutputType::P2SH_SEGWIT,
OutputType::BECH32,
OutputType::MLDSA,
OutputType::SLHDSA,
};
class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProvider
@ -271,7 +273,24 @@ private:
using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char>>>;
struct PQKeyData {
OutputType type;
std::vector<unsigned char> pubkey;
CKeyingMaterial secret;
};
struct CryptedPQKeyData {
OutputType type;
std::vector<unsigned char> pubkey;
std::vector<unsigned char> crypted_secret;
};
using PQKeyMap = std::map<uint256, PQKeyData>;
using CryptedPQKeyMap = std::map<uint256, CryptedPQKeyData>;
CryptedKeyMap mapCryptedKeys GUARDED_BY(cs_KeyStore);
PQKeyMap mapPQKeys GUARDED_BY(cs_KeyStore);
CryptedPQKeyMap mapCryptedPQKeys GUARDED_BY(cs_KeyStore);
WatchOnlySet setWatchOnly GUARDED_BY(cs_KeyStore);
WatchKeyMap mapWatchKeys GUARDED_BY(cs_KeyStore);
@ -280,6 +299,13 @@ private:
bool AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey);
bool AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
bool AddPQKeyInner(const uint256& key_id, OutputType type,
const std::vector<unsigned char>& pubkey,
const CKeyingMaterial& secret);
bool AddCryptedPQKeyInner(const uint256& key_id, OutputType type,
const std::vector<unsigned char>& pubkey,
const std::vector<unsigned char>& crypted_secret);
/**
* Private version of AddWatchOnly method which does not accept a
* timestamp, and which will reset the wallet's nTimeFirstKey value to 1 if
@ -308,6 +334,7 @@ private:
/* the HD chain data model (external chain counters) */
CHDChain m_hd_chain;
PQHDChain m_pq_hd_chain GUARDED_BY(cs_KeyStore);
std::unordered_map<CKeyID, CHDChain, SaltedSipHasher> m_inactive_hd_chains;
/* HD derive new child key (on internal or external chain) */
@ -405,6 +432,9 @@ public:
// Map from Key ID to key metadata.
std::map<CKeyID, CKeyMetadata> mapKeyMetadata GUARDED_BY(cs_KeyStore);
// Map from post-quantum key ID to key metadata.
std::map<uint256, CKeyMetadata> mapPQKeyMetadata GUARDED_BY(cs_KeyStore);
// Map from Script ID to key metadata (for watch-only keys).
std::map<CScriptID, CKeyMetadata> m_script_metadata GUARDED_BY(cs_KeyStore);
@ -416,11 +446,32 @@ public:
bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
//! Adds an encrypted key to the store, without saving it to disk (used by LoadWallet)
bool LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret, bool checksum_valid);
//! Load post-quantum keys from the wallet database.
bool LoadPQKey(const uint256& key_id, OutputType type,
const std::vector<unsigned char>& pubkey,
const std::vector<unsigned char>& secret);
bool LoadCryptedPQKey(const uint256& key_id, OutputType type,
const std::vector<unsigned char>& pubkey,
const std::vector<unsigned char>& crypted_secret);
bool AddPQKey(WalletBatch& batch,
OutputType type,
const std::vector<unsigned char>& pubkey,
const CKeyingMaterial& secret,
uint256& key_id);
bool HavePQKey(const uint256& key_id) const;
bool GetPQKey(const uint256& key_id, OutputType& type,
std::vector<unsigned char>& pubkey,
CKeyingMaterial& secret) const;
void UpdateTimeFirstKey(int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
//! Adds a CScript to the store
bool LoadCScript(const CScript& redeemScript);
//! Load metadata (used by LoadWallet)
void LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata);
void LoadPQKeyMetadata(const uint256& key_id, const CKeyMetadata &metadata);
void LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata);
//! Generate a new key
CPubKey GenerateNewKey(WalletBatch& batch, CHDChain& hd_chain, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
@ -429,7 +480,10 @@ public:
void AddHDChain(const CHDChain& chain);
//! Load a HD chain model (used by LoadWallet)
void LoadHDChain(const CHDChain& chain);
void LoadPQHDChain(const PQHDChain& chain);
bool ReconcilePQHDChain(WalletBatch& batch, std::string& error);
const CHDChain& GetHDChain() const { return m_hd_chain; }
const PQHDChain& GetPQHDChain() const { return m_pq_hd_chain; }
void AddInactiveHDChain(const CHDChain& chain);
//! Adds a watch-only address to the store, without saving it to disk (used by LoadWallet)

View file

@ -9,6 +9,7 @@
#include <chain.h>
#include <consensus/consensus.h>
#include <consensus/validation.h>
#include <crypto/pq/pq.h>
#include <external_signer.h>
#include <fs.h>
#include <interfaces/chain.h>
@ -1452,6 +1453,26 @@ bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig
const CScript& scriptPubKey = txout.scriptPubKey;
SignatureData sigdata;
TxoutType type;
std::vector<std::vector<unsigned char>> solutions;
type = Solver(scriptPubKey, solutions);
if (type == TxoutType::WITNESS_V2_MLDSA) {
tx_in.scriptWitness.stack = {
std::vector<unsigned char>(pq::MLDSA65::SIG_BYTES),
std::vector<unsigned char>(pq::MLDSA65::PUBKEY_BYTES),
};
return true;
}
if (type == TxoutType::WITNESS_V3_SLHDSA) {
tx_in.scriptWitness.stack = {
std::vector<unsigned char>(pq::SPHINCS128s::SIG_BYTES),
std::vector<unsigned char>(pq::SPHINCS128s::PUBKEY_BYTES),
};
return true;
}
std::unique_ptr<SigningProvider> provider = GetSolvingProvider(scriptPubKey);
if (!provider) {
// We don't know about this scriptpbuKey;
@ -2598,6 +2619,11 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain* chain, const std::st
error = strprintf(_("Unknown address type '%s'"), gArgs.GetArg("-addresstype", ""));
return nullptr;
}
if (walletInstance->m_default_address_type == OutputType::MLDSA ||
walletInstance->m_default_address_type == OutputType::SLHDSA) {
error = _("Post-quantum address types cannot be used as the default wallet address type");
return nullptr;
}
}
if (!gArgs.GetArg("-changetype", "").empty()) {
@ -2606,6 +2632,10 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain* chain, const std::st
error = strprintf(_("Unknown change type '%s'"), gArgs.GetArg("-changetype", ""));
return nullptr;
}
if (out_type == OutputType::MLDSA || out_type == OutputType::SLHDSA) {
error = _("Post-quantum address types cannot be used for change addresses");
return nullptr;
}
walletInstance->m_default_change_type = out_type;
}
@ -3123,9 +3153,10 @@ void CWallet::SetupDescriptorScriptPubKeyMans()
for (bool internal : {false, true}) {
for (OutputType t : OUTPUT_TYPES) {
if (t == OutputType::BECH32M) {
// Skip taproot (bech32m) for now
// TODO: Setup taproot (bech32m) descriptors by default
if (t == OutputType::BECH32M ||
t == OutputType::MLDSA ||
t == OutputType::SLHDSA) {
// Skip output types that do not use the standard EC descriptor path.
continue;
}
auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this));

View file

@ -34,6 +34,7 @@ const std::string ACTIVEINTERNALSPK{"activeinternalspk"};
const std::string BESTBLOCK_NOMERKLE{"bestblock_nomerkle"};
const std::string BESTBLOCK{"bestblock"};
const std::string CRYPTED_KEY{"ckey"};
const std::string CRYPTED_PQKEY{"cpqkey"};
const std::string CSCRIPT{"cscript"};
const std::string DEFAULTKEY{"defaultkey"};
const std::string DESTDATA{"destdata"};
@ -47,6 +48,9 @@ const std::string NAME{"name"};
const std::string OLD_KEY{"wkey"};
const std::string ORDERPOSNEXT{"orderposnext"};
const std::string POOL{"pool"};
const std::string PQKEY{"pqkey"};
const std::string PQKEYMETA{"pqkeymeta"};
const std::string PQHDCHAIN{"pqhdchain"};
const std::string PURPOSE{"purpose"};
const std::string SETTINGS{"settings"};
const std::string TX{"tx"};
@ -101,6 +105,11 @@ bool WalletBatch::WriteKeyMetadata(const CKeyMetadata& meta, const CPubKey& pubk
return WriteIC(std::make_pair(DBKeys::KEYMETA, pubkey), meta, overwrite);
}
bool WalletBatch::WritePQKeyMetadata(const CKeyMetadata& meta, const uint256& key_id, const bool overwrite)
{
return WriteIC(std::make_pair(DBKeys::PQKEYMETA, key_id), meta, overwrite);
}
bool WalletBatch::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, const CKeyMetadata& keyMeta)
{
if (!WriteKeyMetadata(keyMeta, vchPubKey, false)) {
@ -142,6 +151,32 @@ bool WalletBatch::WriteCryptedKey(const CPubKey& vchPubKey,
return true;
}
bool WalletBatch::WritePQKey(const uint256& key_id, OutputType type,
const std::vector<unsigned char>& pubkey,
const std::vector<unsigned char>& secret)
{
return WriteIC(std::make_pair(DBKeys::PQKEY, key_id),
PQKeyRecord{static_cast<uint8_t>(type), pubkey, secret}, false);
}
bool WalletBatch::WriteCryptedPQKey(const uint256& key_id, OutputType type,
const std::vector<unsigned char>& pubkey,
const std::vector<unsigned char>& crypted_secret)
{
if (!WriteIC(std::make_pair(DBKeys::CRYPTED_PQKEY, key_id),
PQKeyRecord{static_cast<uint8_t>(type), pubkey, crypted_secret}, false)) {
return false;
}
ErasePQKey(key_id);
return true;
}
bool WalletBatch::ErasePQKey(const uint256& key_id)
{
return EraseIC(std::make_pair(DBKeys::PQKEY, key_id));
}
bool WalletBatch::WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey)
{
return WriteIC(std::make_pair(DBKeys::MASTER_KEY, nID), kMasterKey, true);
@ -477,6 +512,52 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
return false;
}
wss.fIsEncrypted = true;
} else if (strType == DBKeys::PQKEY) {
uint256 key_id;
ssKey >> key_id;
PQKeyRecord record;
ssValue >> record;
const OutputType type = static_cast<OutputType>(record.type);
if (type != OutputType::MLDSA && type != OutputType::SLHDSA) {
strErr = "Error reading wallet database: invalid PQ key type";
return false;
}
if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadPQKey(
key_id, type, record.pubkey, record.secret)) {
strErr = "Error reading wallet database: LegacyScriptPubKeyMan::LoadPQKey failed";
return false;
}
wss.nKeys++;
} else if (strType == DBKeys::CRYPTED_PQKEY) {
uint256 key_id;
ssKey >> key_id;
PQKeyRecord record;
ssValue >> record;
const OutputType type = static_cast<OutputType>(record.type);
if (type != OutputType::MLDSA && type != OutputType::SLHDSA) {
strErr = "Error reading wallet database: invalid encrypted PQ key type";
return false;
}
if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadCryptedPQKey(
key_id, type, record.pubkey, record.secret)) {
strErr = "Error reading wallet database: LegacyScriptPubKeyMan::LoadCryptedPQKey failed";
return false;
}
wss.nCKeys++;
wss.fIsEncrypted = true;
} else if (strType == DBKeys::PQKEYMETA) {
uint256 key_id;
ssKey >> key_id;
CKeyMetadata keyMeta;
ssValue >> keyMeta;
wss.nKeyMeta++;
pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadPQKeyMetadata(key_id, keyMeta);
} else if (strType == DBKeys::KEYMETA) {
CPubKey vchPubKey;
ssKey >> vchPubKey;
@ -589,6 +670,10 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
CHDChain chain;
ssValue >> chain;
pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadHDChain(chain);
} else if (strType == DBKeys::PQHDCHAIN) {
PQHDChain chain;
ssValue >> chain;
pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadPQHDChain(chain);
} else if (strType == DBKeys::OLD_KEY) {
strErr = "Found unsupported 'wkey' record, try loading with version 0.18";
return false;
@ -732,7 +817,11 @@ bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, st
bool WalletBatch::IsKeyType(const std::string& strType)
{
return (strType == DBKeys::KEY ||
strType == DBKeys::MASTER_KEY || strType == DBKeys::CRYPTED_KEY);
strType == DBKeys::MASTER_KEY ||
strType == DBKeys::CRYPTED_KEY ||
strType == DBKeys::PQKEY ||
strType == DBKeys::CRYPTED_PQKEY ||
strType == DBKeys::PQKEYMETA);
}
DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
@ -851,6 +940,26 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
if (result != DBErrors::LOAD_OK)
return result;
// Reconcile the post-quantum HD derivation state after all wallet
// database records have been loaded.
if (LegacyScriptPubKeyMan* legacy_spkm = pwallet->GetLegacyScriptPubKeyMan()) {
std::string pq_error;
if (!legacy_spkm->ReconcilePQHDChain(*this, pq_error)) {
pwallet->WalletLogPrintf("Error reconciling post-quantum HD chain: %s\n", pq_error);
return DBErrors::CORRUPT;
}
}
// Reconcile the post-quantum HD derivation state after all wallet
// database records have been loaded.
if (LegacyScriptPubKeyMan* legacy_spkm = pwallet->GetLegacyScriptPubKeyMan()) {
std::string pq_error;
if (!legacy_spkm->ReconcilePQHDChain(*this, pq_error)) {
pwallet->WalletLogPrintf("Error reconciling post-quantum HD chain: %s\n", pq_error);
return DBErrors::CORRUPT;
}
}
// Last client version to open this wallet, was previously the file version number
int last_client = CLIENT_VERSION;
m_batch->Read(DBKeys::VERSION, last_client);
@ -1048,6 +1157,11 @@ bool WalletBatch::WriteHDChain(const CHDChain& chain)
return WriteIC(DBKeys::HDCHAIN, chain);
}
bool WalletBatch::WritePQHDChain(const PQHDChain& chain)
{
return WriteIC(DBKeys::PQHDCHAIN, chain);
}
bool WalletBatch::WriteWalletFlags(const uint64_t flags)
{
return WriteIC(DBKeys::FLAGS, flags);

View file

@ -39,6 +39,7 @@ class CWallet;
class CWalletTx;
class uint160;
class uint256;
enum class OutputType;
/** Error statuses for the wallet database */
enum class DBErrors
@ -51,6 +52,30 @@ enum class DBErrors
NEED_REWRITE
};
struct PQKeyRecord
{
uint8_t type;
std::vector<unsigned char> pubkey;
std::vector<unsigned char> secret;
SERIALIZE_METHODS(PQKeyRecord, obj)
{
READWRITE(obj.type, obj.pubkey, obj.secret);
}
};
struct PQHDChain
{
uint32_t nMLDSAExternalCounter{0};
uint32_t nSLHDSAExternalCounter{0};
CKeyID seed_id;
SERIALIZE_METHODS(PQHDChain, obj)
{
READWRITE(obj.nMLDSAExternalCounter, obj.nSLHDSAExternalCounter, obj.seed_id);
}
};
namespace DBKeys {
extern const std::string ACENTRY;
extern const std::string ACTIVEEXTERNALSPK;
@ -58,6 +83,7 @@ extern const std::string ACTIVEINTERNALSPK;
extern const std::string BESTBLOCK;
extern const std::string BESTBLOCK_NOMERKLE;
extern const std::string CRYPTED_KEY;
extern const std::string CRYPTED_PQKEY;
extern const std::string CSCRIPT;
extern const std::string DEFAULTKEY;
extern const std::string DESTDATA;
@ -71,6 +97,10 @@ extern const std::string NAME;
extern const std::string OLD_KEY;
extern const std::string ORDERPOSNEXT;
extern const std::string POOL;
extern const std::string PQKEY;
extern const std::string PQKEYMETA;
extern const std::string PQHDCHAIN;
extern const std::string PQHDCHAIN;
extern const std::string PURPOSE;
extern const std::string SETTINGS;
extern const std::string TX;
@ -222,8 +252,12 @@ public:
bool EraseTx(uint256 hash);
bool WriteKeyMetadata(const CKeyMetadata& meta, const CPubKey& pubkey, const bool overwrite);
bool WritePQKeyMetadata(const CKeyMetadata& meta, const uint256& key_id, const bool overwrite);
bool WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, const CKeyMetadata &keyMeta);
bool WriteCryptedKey(const CPubKey& vchPubKey, const std::vector<unsigned char>& vchCryptedSecret, const CKeyMetadata &keyMeta);
bool WritePQKey(const uint256& key_id, OutputType type, const std::vector<unsigned char>& pubkey, const std::vector<unsigned char>& secret);
bool WriteCryptedPQKey(const uint256& key_id, OutputType type, const std::vector<unsigned char>& pubkey, const std::vector<unsigned char>& crypted_secret);
bool ErasePQKey(const uint256& key_id);
bool WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey);
bool WriteCScript(const uint160& hash, const CScript& redeemScript);
@ -266,6 +300,7 @@ public:
//! write the hdchain model (external chain child index counter)
bool WriteHDChain(const CHDChain& chain);
bool WritePQHDChain(const PQHDChain& chain);
bool WriteWalletFlags(const uint64_t flags);
//! Begin a new transaction