Add post-quantum wallet integration
This commit is contained in:
parent
ad21afbfa9
commit
f283edf8f0
15 changed files with 1013 additions and 16 deletions
|
|
@ -475,9 +475,9 @@ public:
|
||||||
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height = 0; // No activation delay
|
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height = 0; // No activation delay
|
||||||
|
|
||||||
// VEXTA quantum-resistant witness v2/v3.
|
// 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].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].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;
|
||||||
consensus.vDeployments[Consensus::DEPLOYMENT_PQR].min_activation_height = 0;
|
consensus.vDeployments[Consensus::DEPLOYMENT_PQR].min_activation_height = 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#ifndef DIGIBYTE_INTERFACES_CHAIN_H
|
#ifndef DIGIBYTE_INTERFACES_CHAIN_H
|
||||||
#define DIGIBYTE_INTERFACES_CHAIN_H
|
#define DIGIBYTE_INTERFACES_CHAIN_H
|
||||||
|
|
||||||
|
#include <consensus/params.h>
|
||||||
#include <primitives/transaction.h> // For CTransactionRef
|
#include <primitives/transaction.h> // For CTransactionRef
|
||||||
#include <util/settings.h> // For util::SettingsValue
|
#include <util/settings.h> // For util::SettingsValue
|
||||||
|
|
||||||
|
|
@ -96,6 +97,9 @@ public:
|
||||||
//! any blocks)
|
//! any blocks)
|
||||||
virtual std::optional<int> getHeight() = 0;
|
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.
|
//! Get block hash. Height must be valid or this function will abort.
|
||||||
virtual uint256 getBlockHash(int height) = 0;
|
virtual uint256 getBlockHash(int height) = 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -441,6 +441,15 @@ public:
|
||||||
}
|
}
|
||||||
return std::nullopt;
|
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
|
uint256 getBlockHash(int height) override
|
||||||
{
|
{
|
||||||
LOCK(::cs_main);
|
LOCK(::cs_main);
|
||||||
|
|
|
||||||
|
|
@ -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_P2SH_SEGWIT = "p2sh-segwit";
|
||||||
static const std::string OUTPUT_TYPE_STRING_BECH32 = "bech32";
|
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_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)
|
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) {
|
} else if (type == OUTPUT_TYPE_STRING_BECH32M) {
|
||||||
output_type = OutputType::BECH32M;
|
output_type = OutputType::BECH32M;
|
||||||
return true;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -46,6 +54,8 @@ const std::string& FormatOutputType(OutputType type)
|
||||||
case OutputType::P2SH_SEGWIT: return OUTPUT_TYPE_STRING_P2SH_SEGWIT;
|
case OutputType::P2SH_SEGWIT: return OUTPUT_TYPE_STRING_P2SH_SEGWIT;
|
||||||
case OutputType::BECH32: return OUTPUT_TYPE_STRING_BECH32;
|
case OutputType::BECH32: return OUTPUT_TYPE_STRING_BECH32;
|
||||||
case OutputType::BECH32M: return OUTPUT_TYPE_STRING_BECH32M;
|
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
|
} // no default case, so the compiler can warn about missing cases
|
||||||
assert(false);
|
assert(false);
|
||||||
}
|
}
|
||||||
|
|
@ -65,7 +75,9 @@ CTxDestination GetDestinationForKey(const CPubKey& key, OutputType type)
|
||||||
return witdest;
|
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
|
} // no default case, so the compiler can warn about missing cases
|
||||||
assert(false);
|
assert(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ enum class OutputType {
|
||||||
P2SH_SEGWIT,
|
P2SH_SEGWIT,
|
||||||
BECH32,
|
BECH32,
|
||||||
BECH32M,
|
BECH32M,
|
||||||
|
MLDSA,
|
||||||
|
SLHDSA,
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr auto OUTPUT_TYPES = std::array{
|
static constexpr auto OUTPUT_TYPES = std::array{
|
||||||
|
|
@ -27,6 +29,8 @@ static constexpr auto OUTPUT_TYPES = std::array{
|
||||||
OutputType::P2SH_SEGWIT,
|
OutputType::P2SH_SEGWIT,
|
||||||
OutputType::BECH32,
|
OutputType::BECH32,
|
||||||
OutputType::BECH32M,
|
OutputType::BECH32M,
|
||||||
|
OutputType::MLDSA,
|
||||||
|
OutputType::SLHDSA,
|
||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]] bool ParseOutputType(const std::string& str, OutputType& output_type);
|
[[nodiscard]] bool ParseOutputType(const std::string& str, OutputType& output_type);
|
||||||
|
|
|
||||||
|
|
@ -2045,8 +2045,12 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion,
|
||||||
// VEXTA quantum-resistant witness:
|
// VEXTA quantum-resistant witness:
|
||||||
// v2 = ML-DSA-65, v3 = SLH-DSA/SPHINCS+-128s.
|
// 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_PQR)) {
|
||||||
|
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM) {
|
||||||
|
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM);
|
||||||
|
}
|
||||||
return set_success(serror);
|
return set_success(serror);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -257,6 +257,8 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator
|
||||||
case TxoutType::NONSTANDARD:
|
case TxoutType::NONSTANDARD:
|
||||||
case TxoutType::NULL_DATA:
|
case TxoutType::NULL_DATA:
|
||||||
case TxoutType::WITNESS_UNKNOWN:
|
case TxoutType::WITNESS_UNKNOWN:
|
||||||
|
case TxoutType::WITNESS_V2_MLDSA:
|
||||||
|
case TxoutType::WITNESS_V3_SLHDSA:
|
||||||
return false;
|
return false;
|
||||||
case TxoutType::PUBKEY:
|
case TxoutType::PUBKEY:
|
||||||
if (!CreateSig(creator, sigdata, provider, sig, CPubKey(vSolutions[0]), scriptPubKey, sigversion)) return false;
|
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);
|
sigdata.scriptWitness.stack = std::move(result);
|
||||||
}
|
}
|
||||||
result.clear();
|
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;
|
sigdata.witness = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -583,6 +583,26 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||||
if (fRequireStandard && !IsStandardTx(tx, reason))
|
if (fRequireStandard && !IsStandardTx(tx, reason))
|
||||||
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, 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.
|
// 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.
|
// 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
|
// 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;
|
const CTransaction& tx = *ws.m_ptx;
|
||||||
TxValidationState& state = ws.m_state;
|
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.
|
// Check input scripts and signatures.
|
||||||
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
|
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
|
||||||
|
|
|
||||||
|
|
@ -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;
|
CTxDestination dest;
|
||||||
std::string error;
|
std::string error;
|
||||||
if (!pwallet->GetNewDestination(output_type, label, dest, error)) {
|
if (!pwallet->GetNewDestination(output_type, label, dest, error)) {
|
||||||
|
|
@ -320,6 +327,11 @@ static RPCHelpMan getrawchangeaddress()
|
||||||
if (output_type == OutputType::BECH32M && pwallet->GetLegacyScriptPubKeyMan()) {
|
if (output_type == OutputType::BECH32M && pwallet->GetLegacyScriptPubKeyMan()) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Legacy wallets cannot provide bech32m addresses");
|
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;
|
CTxDestination dest;
|
||||||
|
|
@ -3137,6 +3149,11 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out,
|
||||||
if (!ParseOutputType(options["change_type"].get_str(), out_type)) {
|
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()));
|
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);
|
coinControl.m_change_type.emplace(out_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ typedef std::pair<std::vector<unsigned char>, std::vector<unsigned char> > KeyVa
|
||||||
|
|
||||||
static bool KeyFilter(const std::string& type)
|
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)
|
bool RecoverDatabaseFile(const fs::path& file_path, bilingual_str& error, std::vector<bilingual_str>& warnings)
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,15 @@
|
||||||
// Distributed under the MIT software license, see the accompanying
|
// Distributed under the MIT software license, see the accompanying
|
||||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
// 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 <key_io.h>
|
||||||
#include <logging.h>
|
#include <logging.h>
|
||||||
#include <outputtype.h>
|
#include <outputtype.h>
|
||||||
|
#include <policy/policy.h>
|
||||||
#include <script/descriptor.h>
|
#include <script/descriptor.h>
|
||||||
|
#include <script/interpreter.h>
|
||||||
#include <script/sign.h>
|
#include <script/sign.h>
|
||||||
#include <util/bip32.h>
|
#include <util/bip32.h>
|
||||||
#include <util/strencodings.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.
|
//! 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;
|
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)
|
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) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
assert(type != OutputType::BECH32M);
|
assert(type != OutputType::BECH32M);
|
||||||
|
|
@ -176,6 +339,21 @@ IsMineResult IsMineInner(const LegacyScriptPubKeyMan& keystore, const CScript& s
|
||||||
break;
|
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:
|
case TxoutType::MULTISIG:
|
||||||
{
|
{
|
||||||
// Never treat bare multisig outputs as ours (they can still be made watchonly-though)
|
// 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);
|
LOCK(cs_KeyStore);
|
||||||
assert(mapKeys.empty());
|
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;
|
bool keyFail = false;
|
||||||
CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin();
|
CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin();
|
||||||
WalletBatch batch(m_storage.GetDatabase());
|
WalletBatch batch(m_storage.GetDatabase());
|
||||||
|
|
@ -253,6 +432,48 @@ bool LegacyScriptPubKeyMan::CheckDecryptionKey(const CKeyingMaterial& master_key
|
||||||
batch.WriteCryptedKey(vchPubKey, vchCryptedSecret, mapKeyMetadata[vchPubKey.GetID()]);
|
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)
|
if (keyPass && keyFail)
|
||||||
{
|
{
|
||||||
LogPrintf("The wallet is probably corrupted: Some keys decrypt but not all.\n");
|
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);
|
LOCK(cs_KeyStore);
|
||||||
encrypted_batch = batch;
|
encrypted_batch = batch;
|
||||||
if (!mapCryptedKeys.empty()) {
|
if (!mapCryptedKeys.empty() || !mapCryptedPQKeys.empty()) {
|
||||||
encrypted_batch = nullptr;
|
encrypted_batch = nullptr;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -291,12 +512,47 @@ bool LegacyScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, WalletBat
|
||||||
return false;
|
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;
|
encrypted_batch = nullptr;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, std::string& error)
|
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) {
|
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\", and \"bech32\" address types").translated;
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -507,7 +763,10 @@ bool LegacyScriptPubKeyMan::Upgrade(int prev_version, int new_version, bilingual
|
||||||
bool LegacyScriptPubKeyMan::HavePrivateKeys() const
|
bool LegacyScriptPubKeyMan::HavePrivateKeys() const
|
||||||
{
|
{
|
||||||
LOCK(cs_KeyStore);
|
LOCK(cs_KeyStore);
|
||||||
return !mapKeys.empty() || !mapCryptedKeys.empty();
|
return !mapKeys.empty() ||
|
||||||
|
!mapCryptedKeys.empty() ||
|
||||||
|
!mapPQKeys.empty() ||
|
||||||
|
!mapCryptedPQKeys.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LegacyScriptPubKeyMan::RewriteDB()
|
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
|
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
|
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);
|
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);
|
CKeyID key_id = GetKeyForDestination(*this, dest);
|
||||||
if (!key_id.IsNull()) {
|
if (!key_id.IsNull()) {
|
||||||
auto it = mapKeyMetadata.find(key_id);
|
auto it = mapKeyMetadata.find(key_id);
|
||||||
|
|
@ -781,6 +1192,13 @@ void LegacyScriptPubKeyMan::LoadKeyMetadata(const CKeyID& keyID, const CKeyMetad
|
||||||
mapKeyMetadata[keyID] = meta;
|
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)
|
void LegacyScriptPubKeyMan::LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata& meta)
|
||||||
{
|
{
|
||||||
LOCK(cs_KeyStore);
|
LOCK(cs_KeyStore);
|
||||||
|
|
@ -821,6 +1239,174 @@ bool LegacyScriptPubKeyMan::LoadCryptedKey(const CPubKey &vchPubKey, const std::
|
||||||
return AddCryptedKeyInner(vchPubKey, vchCryptedSecret);
|
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)
|
bool LegacyScriptPubKeyMan::AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret)
|
||||||
{
|
{
|
||||||
LOCK(cs_KeyStore);
|
LOCK(cs_KeyStore);
|
||||||
|
|
@ -944,6 +1530,95 @@ void LegacyScriptPubKeyMan::LoadHDChain(const CHDChain& chain)
|
||||||
m_hd_chain = 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)
|
void LegacyScriptPubKeyMan::AddHDChain(const CHDChain& chain)
|
||||||
{
|
{
|
||||||
LOCK(cs_KeyStore);
|
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.nVersion = m_storage.CanSupportFeature(FEATURE_HD_SPLIT) ? CHDChain::VERSION_HD_CHAIN_SPLIT : CHDChain::VERSION_HD_BASE;
|
||||||
newHdChain.seed_id = seed.GetID();
|
newHdChain.seed_id = seed.GetID();
|
||||||
AddHDChain(newHdChain);
|
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();
|
NotifyCanGetAddressesChanged();
|
||||||
WalletBatch batch(m_storage.GetDatabase());
|
WalletBatch batch(m_storage.GetDatabase());
|
||||||
m_storage.UnsetBlankWalletFlag(batch);
|
m_storage.UnsetBlankWalletFlag(batch);
|
||||||
|
|
|
||||||
|
|
@ -256,6 +256,8 @@ static const std::unordered_set<OutputType> LEGACY_OUTPUT_TYPES {
|
||||||
OutputType::LEGACY,
|
OutputType::LEGACY,
|
||||||
OutputType::P2SH_SEGWIT,
|
OutputType::P2SH_SEGWIT,
|
||||||
OutputType::BECH32,
|
OutputType::BECH32,
|
||||||
|
OutputType::MLDSA,
|
||||||
|
OutputType::SLHDSA,
|
||||||
};
|
};
|
||||||
|
|
||||||
class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProvider
|
class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProvider
|
||||||
|
|
@ -271,7 +273,24 @@ private:
|
||||||
|
|
||||||
using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char>>>;
|
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);
|
CryptedKeyMap mapCryptedKeys GUARDED_BY(cs_KeyStore);
|
||||||
|
PQKeyMap mapPQKeys GUARDED_BY(cs_KeyStore);
|
||||||
|
CryptedPQKeyMap mapCryptedPQKeys GUARDED_BY(cs_KeyStore);
|
||||||
WatchOnlySet setWatchOnly GUARDED_BY(cs_KeyStore);
|
WatchOnlySet setWatchOnly GUARDED_BY(cs_KeyStore);
|
||||||
WatchKeyMap mapWatchKeys GUARDED_BY(cs_KeyStore);
|
WatchKeyMap mapWatchKeys GUARDED_BY(cs_KeyStore);
|
||||||
|
|
||||||
|
|
@ -280,6 +299,13 @@ private:
|
||||||
bool AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey);
|
bool AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey);
|
||||||
bool AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
|
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
|
* Private version of AddWatchOnly method which does not accept a
|
||||||
* timestamp, and which will reset the wallet's nTimeFirstKey value to 1 if
|
* 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) */
|
/* the HD chain data model (external chain counters) */
|
||||||
CHDChain m_hd_chain;
|
CHDChain m_hd_chain;
|
||||||
|
PQHDChain m_pq_hd_chain GUARDED_BY(cs_KeyStore);
|
||||||
std::unordered_map<CKeyID, CHDChain, SaltedSipHasher> m_inactive_hd_chains;
|
std::unordered_map<CKeyID, CHDChain, SaltedSipHasher> m_inactive_hd_chains;
|
||||||
|
|
||||||
/* HD derive new child key (on internal or external chain) */
|
/* HD derive new child key (on internal or external chain) */
|
||||||
|
|
@ -405,6 +432,9 @@ public:
|
||||||
// Map from Key ID to key metadata.
|
// Map from Key ID to key metadata.
|
||||||
std::map<CKeyID, CKeyMetadata> mapKeyMetadata GUARDED_BY(cs_KeyStore);
|
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).
|
// Map from Script ID to key metadata (for watch-only keys).
|
||||||
std::map<CScriptID, CKeyMetadata> m_script_metadata GUARDED_BY(cs_KeyStore);
|
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);
|
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)
|
//! 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);
|
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);
|
void UpdateTimeFirstKey(int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
|
||||||
//! Adds a CScript to the store
|
//! Adds a CScript to the store
|
||||||
bool LoadCScript(const CScript& redeemScript);
|
bool LoadCScript(const CScript& redeemScript);
|
||||||
//! Load metadata (used by LoadWallet)
|
//! Load metadata (used by LoadWallet)
|
||||||
void LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata);
|
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);
|
void LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata);
|
||||||
//! Generate a new key
|
//! Generate a new key
|
||||||
CPubKey GenerateNewKey(WalletBatch& batch, CHDChain& hd_chain, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
|
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);
|
void AddHDChain(const CHDChain& chain);
|
||||||
//! Load a HD chain model (used by LoadWallet)
|
//! Load a HD chain model (used by LoadWallet)
|
||||||
void LoadHDChain(const CHDChain& chain);
|
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 CHDChain& GetHDChain() const { return m_hd_chain; }
|
||||||
|
const PQHDChain& GetPQHDChain() const { return m_pq_hd_chain; }
|
||||||
void AddInactiveHDChain(const CHDChain& chain);
|
void AddInactiveHDChain(const CHDChain& chain);
|
||||||
|
|
||||||
//! Adds a watch-only address to the store, without saving it to disk (used by LoadWallet)
|
//! Adds a watch-only address to the store, without saving it to disk (used by LoadWallet)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
#include <chain.h>
|
#include <chain.h>
|
||||||
#include <consensus/consensus.h>
|
#include <consensus/consensus.h>
|
||||||
#include <consensus/validation.h>
|
#include <consensus/validation.h>
|
||||||
|
#include <crypto/pq/pq.h>
|
||||||
#include <external_signer.h>
|
#include <external_signer.h>
|
||||||
#include <fs.h>
|
#include <fs.h>
|
||||||
#include <interfaces/chain.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;
|
const CScript& scriptPubKey = txout.scriptPubKey;
|
||||||
SignatureData sigdata;
|
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);
|
std::unique_ptr<SigningProvider> provider = GetSolvingProvider(scriptPubKey);
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
// We don't know about this scriptpbuKey;
|
// 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", ""));
|
error = strprintf(_("Unknown address type '%s'"), gArgs.GetArg("-addresstype", ""));
|
||||||
return nullptr;
|
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()) {
|
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", ""));
|
error = strprintf(_("Unknown change type '%s'"), gArgs.GetArg("-changetype", ""));
|
||||||
return nullptr;
|
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;
|
walletInstance->m_default_change_type = out_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3123,9 +3153,10 @@ void CWallet::SetupDescriptorScriptPubKeyMans()
|
||||||
|
|
||||||
for (bool internal : {false, true}) {
|
for (bool internal : {false, true}) {
|
||||||
for (OutputType t : OUTPUT_TYPES) {
|
for (OutputType t : OUTPUT_TYPES) {
|
||||||
if (t == OutputType::BECH32M) {
|
if (t == OutputType::BECH32M ||
|
||||||
// Skip taproot (bech32m) for now
|
t == OutputType::MLDSA ||
|
||||||
// TODO: Setup taproot (bech32m) descriptors by default
|
t == OutputType::SLHDSA) {
|
||||||
|
// Skip output types that do not use the standard EC descriptor path.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this));
|
auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this));
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ const std::string ACTIVEINTERNALSPK{"activeinternalspk"};
|
||||||
const std::string BESTBLOCK_NOMERKLE{"bestblock_nomerkle"};
|
const std::string BESTBLOCK_NOMERKLE{"bestblock_nomerkle"};
|
||||||
const std::string BESTBLOCK{"bestblock"};
|
const std::string BESTBLOCK{"bestblock"};
|
||||||
const std::string CRYPTED_KEY{"ckey"};
|
const std::string CRYPTED_KEY{"ckey"};
|
||||||
|
const std::string CRYPTED_PQKEY{"cpqkey"};
|
||||||
const std::string CSCRIPT{"cscript"};
|
const std::string CSCRIPT{"cscript"};
|
||||||
const std::string DEFAULTKEY{"defaultkey"};
|
const std::string DEFAULTKEY{"defaultkey"};
|
||||||
const std::string DESTDATA{"destdata"};
|
const std::string DESTDATA{"destdata"};
|
||||||
|
|
@ -47,6 +48,9 @@ const std::string NAME{"name"};
|
||||||
const std::string OLD_KEY{"wkey"};
|
const std::string OLD_KEY{"wkey"};
|
||||||
const std::string ORDERPOSNEXT{"orderposnext"};
|
const std::string ORDERPOSNEXT{"orderposnext"};
|
||||||
const std::string POOL{"pool"};
|
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 PURPOSE{"purpose"};
|
||||||
const std::string SETTINGS{"settings"};
|
const std::string SETTINGS{"settings"};
|
||||||
const std::string TX{"tx"};
|
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);
|
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)
|
bool WalletBatch::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, const CKeyMetadata& keyMeta)
|
||||||
{
|
{
|
||||||
if (!WriteKeyMetadata(keyMeta, vchPubKey, false)) {
|
if (!WriteKeyMetadata(keyMeta, vchPubKey, false)) {
|
||||||
|
|
@ -142,6 +151,32 @@ bool WalletBatch::WriteCryptedKey(const CPubKey& vchPubKey,
|
||||||
return true;
|
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)
|
bool WalletBatch::WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey)
|
||||||
{
|
{
|
||||||
return WriteIC(std::make_pair(DBKeys::MASTER_KEY, nID), kMasterKey, true);
|
return WriteIC(std::make_pair(DBKeys::MASTER_KEY, nID), kMasterKey, true);
|
||||||
|
|
@ -477,6 +512,52 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
wss.fIsEncrypted = true;
|
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) {
|
} else if (strType == DBKeys::KEYMETA) {
|
||||||
CPubKey vchPubKey;
|
CPubKey vchPubKey;
|
||||||
ssKey >> vchPubKey;
|
ssKey >> vchPubKey;
|
||||||
|
|
@ -589,6 +670,10 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||||
CHDChain chain;
|
CHDChain chain;
|
||||||
ssValue >> chain;
|
ssValue >> chain;
|
||||||
pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadHDChain(chain);
|
pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadHDChain(chain);
|
||||||
|
} else if (strType == DBKeys::PQHDCHAIN) {
|
||||||
|
PQHDChain chain;
|
||||||
|
ssValue >> chain;
|
||||||
|
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;
|
||||||
|
|
@ -732,7 +817,11 @@ bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, st
|
||||||
bool WalletBatch::IsKeyType(const std::string& strType)
|
bool WalletBatch::IsKeyType(const std::string& strType)
|
||||||
{
|
{
|
||||||
return (strType == DBKeys::KEY ||
|
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)
|
DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
|
||||||
|
|
@ -851,6 +940,26 @@ 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
|
||||||
|
// 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
|
// Last client version to open this wallet, was previously the file version number
|
||||||
int last_client = CLIENT_VERSION;
|
int last_client = CLIENT_VERSION;
|
||||||
m_batch->Read(DBKeys::VERSION, last_client);
|
m_batch->Read(DBKeys::VERSION, last_client);
|
||||||
|
|
@ -1048,6 +1157,11 @@ bool WalletBatch::WriteHDChain(const CHDChain& chain)
|
||||||
return WriteIC(DBKeys::HDCHAIN, chain);
|
return WriteIC(DBKeys::HDCHAIN, chain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WalletBatch::WritePQHDChain(const PQHDChain& chain)
|
||||||
|
{
|
||||||
|
return WriteIC(DBKeys::PQHDCHAIN, chain);
|
||||||
|
}
|
||||||
|
|
||||||
bool WalletBatch::WriteWalletFlags(const uint64_t flags)
|
bool WalletBatch::WriteWalletFlags(const uint64_t flags)
|
||||||
{
|
{
|
||||||
return WriteIC(DBKeys::FLAGS, flags);
|
return WriteIC(DBKeys::FLAGS, flags);
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ class CWallet;
|
||||||
class CWalletTx;
|
class CWalletTx;
|
||||||
class uint160;
|
class uint160;
|
||||||
class uint256;
|
class uint256;
|
||||||
|
enum class OutputType;
|
||||||
|
|
||||||
/** Error statuses for the wallet database */
|
/** Error statuses for the wallet database */
|
||||||
enum class DBErrors
|
enum class DBErrors
|
||||||
|
|
@ -51,6 +52,30 @@ enum class DBErrors
|
||||||
NEED_REWRITE
|
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 {
|
namespace DBKeys {
|
||||||
extern const std::string ACENTRY;
|
extern const std::string ACENTRY;
|
||||||
extern const std::string ACTIVEEXTERNALSPK;
|
extern const std::string ACTIVEEXTERNALSPK;
|
||||||
|
|
@ -58,6 +83,7 @@ extern const std::string ACTIVEINTERNALSPK;
|
||||||
extern const std::string BESTBLOCK;
|
extern const std::string BESTBLOCK;
|
||||||
extern const std::string BESTBLOCK_NOMERKLE;
|
extern const std::string BESTBLOCK_NOMERKLE;
|
||||||
extern const std::string CRYPTED_KEY;
|
extern const std::string CRYPTED_KEY;
|
||||||
|
extern const std::string CRYPTED_PQKEY;
|
||||||
extern const std::string CSCRIPT;
|
extern const std::string CSCRIPT;
|
||||||
extern const std::string DEFAULTKEY;
|
extern const std::string DEFAULTKEY;
|
||||||
extern const std::string DESTDATA;
|
extern const std::string DESTDATA;
|
||||||
|
|
@ -71,6 +97,10 @@ extern const std::string NAME;
|
||||||
extern const std::string OLD_KEY;
|
extern const std::string OLD_KEY;
|
||||||
extern const std::string ORDERPOSNEXT;
|
extern const std::string ORDERPOSNEXT;
|
||||||
extern const std::string POOL;
|
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 PURPOSE;
|
||||||
extern const std::string SETTINGS;
|
extern const std::string SETTINGS;
|
||||||
extern const std::string TX;
|
extern const std::string TX;
|
||||||
|
|
@ -222,8 +252,12 @@ public:
|
||||||
bool EraseTx(uint256 hash);
|
bool EraseTx(uint256 hash);
|
||||||
|
|
||||||
bool WriteKeyMetadata(const CKeyMetadata& meta, const CPubKey& pubkey, const bool overwrite);
|
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 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 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 WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey);
|
||||||
|
|
||||||
bool WriteCScript(const uint160& hash, const CScript& redeemScript);
|
bool WriteCScript(const uint160& hash, const CScript& redeemScript);
|
||||||
|
|
@ -266,6 +300,7 @@ public:
|
||||||
|
|
||||||
//! write the hdchain model (external chain child index counter)
|
//! write the hdchain model (external chain child index counter)
|
||||||
bool WriteHDChain(const CHDChain& chain);
|
bool WriteHDChain(const CHDChain& chain);
|
||||||
|
bool WritePQHDChain(const PQHDChain& chain);
|
||||||
|
|
||||||
bool WriteWalletFlags(const uint64_t flags);
|
bool WriteWalletFlags(const uint64_t flags);
|
||||||
//! Begin a new transaction
|
//! Begin a new transaction
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue