From 490832959dc39c7864453c82efcf434b55c4b347 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 27 Jun 2024 20:32:11 +0700 Subject: [PATCH 1/2] refactor: new method to generate a signing message in CGovernanceVote --- src/governance/vote.cpp | 15 +++++++++------ src/governance/vote.h | 1 + 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/governance/vote.cpp b/src/governance/vote.cpp index 62f9a763aa..5eff322462 100644 --- a/src/governance/vote.cpp +++ b/src/governance/vote.cpp @@ -208,12 +208,7 @@ bool CGovernanceVote::CheckSignature(const CKeyID& keyID) const return false; } } else { - std::string strMessage = masternodeOutpoint.ToStringShort() + "|" + nParentHash.ToString() + "|" + - ::ToString(nVoteSignal) + "|" + - ::ToString(nVoteOutcome) + "|" + - ::ToString(nTime); - - if (!CMessageSigner::VerifyMessage(keyID, vchSig, strMessage, strError)) { + if (!CMessageSigner::VerifyMessage(keyID, vchSig, GetSignatureString(), strError)) { LogPrint(BCLog::GOBJECT, "CGovernanceVote::IsValid -- VerifyMessage() failed, error: %s\n", strError); return false; } @@ -275,6 +270,14 @@ bool CGovernanceVote::IsValid(const CDeterministicMNList& tip_mn_list, bool useV } } +std::string CGovernanceVote::GetSignatureString() const +{ + return masternodeOutpoint.ToStringShort() + "|" + nParentHash.ToString() + "|" + + ::ToString(nVoteSignal) + "|" + + ::ToString(nVoteOutcome) + "|" + + ::ToString(nTime); +} + bool operator==(const CGovernanceVote& vote1, const CGovernanceVote& vote2) { bool fResult = ((vote1.masternodeOutpoint == vote2.masternodeOutpoint) && diff --git a/src/governance/vote.h b/src/governance/vote.h index 5662ac839c..7844017ccc 100644 --- a/src/governance/vote.h +++ b/src/governance/vote.h @@ -105,6 +105,7 @@ public: bool Sign(const CActiveMasternodeManager& mn_activeman); bool CheckSignature(const CBLSPublicKey& pubKey) const; bool IsValid(const CDeterministicMNList& tip_mn_list, bool useVotingKey) const; + std::string GetSignatureString() const; void Relay(PeerManager& peerman, const CMasternodeSync& mn_sync, const CDeterministicMNList& tip_mn_list) const; const COutPoint& GetMasternodeOutpoint() const { return masternodeOutpoint; } From c72ec70fdfb1fbc1129defb9008ebfd4adf3e982 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 13 Jun 2024 03:31:00 +0700 Subject: [PATCH 2/2] feat: implement governance RPCs votealias and votemany for descriptor wallets --- src/governance/vote.cpp | 35 ---------------- src/rpc/governance.cpp | 77 +++++++++++++++++++++++----------- test/functional/test_runner.py | 2 + 3 files changed, 55 insertions(+), 59 deletions(-) diff --git a/src/governance/vote.cpp b/src/governance/vote.cpp index 5eff322462..2c1b9f1a04 100644 --- a/src/governance/vote.cpp +++ b/src/governance/vote.cpp @@ -162,41 +162,6 @@ uint256 CGovernanceVote::GetSignatureHash() const return SerializeHash(*this); } -bool CGovernanceVote::Sign(const CKey& key, const CKeyID& keyID) -{ - std::string strError; - - // Harden Spork6 so that it is active on testnet and no other networks - if (Params().NetworkIDString() == CBaseChainParams::TESTNET) { - uint256 signatureHash = GetSignatureHash(); - - if (!CHashSigner::SignHash(signatureHash, key, vchSig)) { - LogPrintf("CGovernanceVote::Sign -- SignHash() failed\n"); - return false; - } - - if (!CHashSigner::VerifyHash(signatureHash, keyID, vchSig, strError)) { - LogPrintf("CGovernanceVote::Sign -- VerifyHash() failed, error: %s\n", strError); - return false; - } - } else { - std::string strMessage = masternodeOutpoint.ToStringShort() + "|" + nParentHash.ToString() + "|" + - ::ToString(nVoteSignal) + "|" + ::ToString(nVoteOutcome) + "|" + ::ToString(nTime); - - if (!CMessageSigner::SignMessage(strMessage, vchSig, key)) { - LogPrintf("CGovernanceVote::Sign -- SignMessage() failed\n"); - return false; - } - - if (!CMessageSigner::VerifyMessage(keyID, vchSig, strMessage, strError)) { - LogPrintf("CGovernanceVote::Sign -- VerifyMessage() failed, error: %s\n", strError); - return false; - } - } - - return true; -} - bool CGovernanceVote::CheckSignature(const CKeyID& keyID) const { std::string strError; diff --git a/src/rpc/governance.cpp b/src/rpc/governance.cpp index 4b1af3dc3d..90f6903a5c 100644 --- a/src/rpc/governance.cpp +++ b/src/rpc/governance.cpp @@ -404,7 +404,37 @@ static RPCHelpMan gobject_submit() }; } -static UniValue VoteWithMasternodes(const JSONRPCRequest& request, const std::map& keys, +#ifdef ENABLE_WALLET +static bool SignVote(const CWallet& wallet, const CKeyID& keyID, CGovernanceVote& vote) +{ + // Special implementation for testnet (Harden Spork6 that has not been deployed to other networks) + if (Params().NetworkIDString() == CBaseChainParams::TESTNET) { + std::vector signature; + if (!wallet.SignSpecialTxPayload(vote.GetSignatureHash(), keyID, signature)) { + LogPrintf("SignVote -- SignHash() failed\n"); + return false; + } + vote.SetSignature(signature); + return true; + } // end of testnet implementation + + std::string strMessage{vote.GetSignatureString()}; + std::string signature; + SigningResult err = wallet.SignMessage(strMessage, PKHash{keyID}, signature); + if (err != SigningResult::OK) { + LogPrintf("SignVote failed due to: %s\n", SigningResultString(err)); + return false; + } + bool ret = true; + const auto decoded = DecodeBase64(signature, &ret); + assert(!ret); // it should not fail + + vote.SetSignature(std::vector(decoded.data(), decoded.data() + decoded.size())); + return true; +} + +static UniValue VoteWithMasternodes(const JSONRPCRequest& request, const CWallet& wallet, + const std::map& votingKeys, const uint256& hash, vote_signal_enum_t eVoteSignal, vote_outcome_enum_t eVoteOutcome) { @@ -425,9 +455,9 @@ static UniValue VoteWithMasternodes(const JSONRPCRequest& request, const std::ma UniValue resultsObj(UniValue::VOBJ); - for (const auto& p : keys) { + for (const auto& p : votingKeys) { const auto& proTxHash = p.first; - const auto& key = p.second; + const auto& keyID = p.second; UniValue statusObj(UniValue::VOBJ); @@ -441,7 +471,8 @@ static UniValue VoteWithMasternodes(const JSONRPCRequest& request, const std::ma } CGovernanceVote vote(dmn->collateralOutpoint, hash, eVoteSignal, eVoteOutcome); - if (!vote.Sign(key, key.GetPubKey().GetID())) { + + if (!SignVote(wallet, keyID, vote) || !vote.CheckSignature(keyID)) { nFailed++; statusObj.pushKV("result", "failed"); statusObj.pushKV("errorMessage", "Failure to sign."); @@ -471,7 +502,14 @@ static UniValue VoteWithMasternodes(const JSONRPCRequest& request, const std::ma return returnObj; } -#ifdef ENABLE_WALLET +static bool CheckWalletOwnsKey(const CWallet& wallet, const CKeyID& keyid) +{ + const CScript script{GetScriptForDestination(PKHash(keyid))}; + LOCK(wallet.cs_wallet); + + return wallet.IsMine(script) == isminetype::ISMINE_SPENDABLE; +} + static RPCHelpMan gobject_vote_many() { return RPCHelpMan{"gobject vote-many", @@ -510,22 +548,17 @@ static RPCHelpMan gobject_vote_many() EnsureWalletIsUnlocked(wallet.get()); - LegacyScriptPubKeyMan* spk_man = wallet->GetLegacyScriptPubKeyMan(); - if (!spk_man) { - throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); - } - - std::map votingKeys; + std::map votingKeys; auto mnList = node.dmnman->GetListAtChainTip(); mnList.ForEachMN(true, [&](auto& dmn) { - CKey votingKey; - if (spk_man->GetKey(dmn.pdmnState->keyIDVoting, votingKey)) { - votingKeys.emplace(dmn.proTxHash, votingKey); + const bool is_mine = CheckWalletOwnsKey(*wallet, dmn.pdmnState->keyIDVoting); + if (is_mine) { + votingKeys.emplace(dmn.proTxHash, dmn.pdmnState->keyIDVoting); } }); - return VoteWithMasternodes(request, votingKeys, hash, eVoteSignal, eVoteOutcome); + return VoteWithMasternodes(request, *wallet, votingKeys, hash, eVoteSignal, eVoteOutcome); }, }; } @@ -575,20 +608,16 @@ static RPCHelpMan gobject_vote_alias() throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid or unknown proTxHash"); } - LegacyScriptPubKeyMan* spk_man = wallet->GetLegacyScriptPubKeyMan(); - if (!spk_man) { - throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); - } - CKey votingKey; - if (!spk_man->GetKey(dmn->pdmnState->keyIDVoting, votingKey)) { + const bool is_mine = CheckWalletOwnsKey(*wallet, dmn->pdmnState->keyIDVoting); + if (!is_mine) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Private key for voting address %s not known by wallet", EncodeDestination(PKHash(dmn->pdmnState->keyIDVoting)))); } - std::map votingKeys; - votingKeys.emplace(proTxHash, votingKey); + std::map votingKeys; + votingKeys.emplace(proTxHash, dmn->pdmnState->keyIDVoting); - return VoteWithMasternodes(request, votingKeys, hash, eVoteSignal, eVoteOutcome); + return VoteWithMasternodes(request, *wallet, votingKeys, hash, eVoteSignal, eVoteOutcome); }, }; } diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 3af377ba30..2a87e9c6af 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -288,7 +288,9 @@ BASE_SCRIPTS = [ 'feature_new_quorum_type_activation.py', 'feature_governance_objects.py', 'feature_governance.py --legacy-wallet', + 'feature_governance.py --descriptors', 'feature_governance_cl.py --legacy-wallet', + 'feature_governance_cl.py --descriptors', 'rpc_uptime.py', 'feature_discover.py', 'wallet_resendwallettransactions.py --legacy-wallet',