Add post-quantum wallet integration
This commit is contained in:
parent
ad21afbfa9
commit
f283edf8f0
15 changed files with 1013 additions and 16 deletions
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue