diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index e248d8085..b40da3c13 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -124,6 +124,7 @@ testScripts = [ 'wallet-accounts.py', 'wallet-dump.py', 'listtransactions.py', + 'multikeysporks.py', # vv Tests less than 60s vv 'sendheaders.py', # NOTE: needs dash_hash to pass 'zapwallettxes.py', diff --git a/qa/rpc-tests/multikeysporks.py b/qa/rpc-tests/multikeysporks.py new file mode 100755 index 000000000..8d4f8c38e --- /dev/null +++ b/qa/rpc-tests/multikeysporks.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +# Copyright (c) 2018 The Dash Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +from test_framework.mininode import * +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * +from time import * + +''' +multikeysporks.py + +Test logic for several signer keys usage for spork broadcast. + +We set 5 possible keys for sporks signing and set minimum +required signers to 3. We check 1 and 2 signers can't set the spork +value, any 3 signers can change spork value and other 3 signers +can change it again. +''' + + +class MultiKeySporkTest(BitcoinTestFramework): + def __init__(self): + super().__init__() + self.num_nodes = 5 + self.setup_clean_chain = True + self.is_network_split = False + + def setup_network(self): + self.nodes = [] + + # secret(base58): 931wyuRNVYvhg18Uu9bky5Qg1z4QbxaJ7fefNBzjBPiLRqcd33F + # keyid(hex): 60f0f57f71f0081f1aacdd8432340a33a526f91b + # address(base58): yNsMZhEhYqv14TgdYb1NS2UmNZjE8FSJxa + + # secret(base58): 91vbXGMSWKGHom62986XtL1q2mQDA12ngcuUNNe5NfMSj44j7g3 + # keyid(hex): 43dff2b09de2f904f688ec14ee6899087b889ad0 + # address(base58): yfLSXFfipnkgYioD6L8aUNyfRgEBuJv48h + + # secret(base58): 92bxUjPT5AhgXuXJwfGGXqhomY2SdQ55MYjXyx9DZNxCABCSsRH + # keyid(hex): d9aa5fa00cce99101a4044e65dc544d1579890de + # address(base58): ygcG5S2pQz2U1UAaHvU6EznKZW7yapKMA7 + + # secret(base58): 934yPXiVGf4RCY2qTs2Bt5k3TEtAiAg12sMxCt8yVWbSU7p3fuD + # keyid(hex): 0b23935ce0bea3b997a334f6fa276c9fa17687b2 + # address(base58): ycbRQWbovrhQMTuxg9p4LAuW5SCMAKqPrn + + # secret(base58): 92Cxwia363Wg2qGF1fE5z4GKi8u7r1nrWQXdtsj2ACZqaDPSihD + # keyid(hex): 1d1098b2b1f759b678a0a7a098637a9b898adcac + # address(base58): yc5TGfcHYoLCrcbVy4umsiDjsYUn39vLui + + self.nodes.append(start_node(0, self.options.tmpdir, + ["-debug", "-sporkkey=931wyuRNVYvhg18Uu9bky5Qg1z4QbxaJ7fefNBzjBPiLRqcd33F", + "-sporkaddr=ygcG5S2pQz2U1UAaHvU6EznKZW7yapKMA7", + "-sporkaddr=yfLSXFfipnkgYioD6L8aUNyfRgEBuJv48h", + "-sporkaddr=yNsMZhEhYqv14TgdYb1NS2UmNZjE8FSJxa", + "-sporkaddr=ycbRQWbovrhQMTuxg9p4LAuW5SCMAKqPrn", + "-sporkaddr=yc5TGfcHYoLCrcbVy4umsiDjsYUn39vLui", + "-minsporkkeys=3"])) + self.nodes.append(start_node(1, self.options.tmpdir, + ["-debug", "-sporkkey=91vbXGMSWKGHom62986XtL1q2mQDA12ngcuUNNe5NfMSj44j7g3", + "-sporkaddr=ygcG5S2pQz2U1UAaHvU6EznKZW7yapKMA7", + "-sporkaddr=yfLSXFfipnkgYioD6L8aUNyfRgEBuJv48h", + "-sporkaddr=yNsMZhEhYqv14TgdYb1NS2UmNZjE8FSJxa", + "-sporkaddr=ycbRQWbovrhQMTuxg9p4LAuW5SCMAKqPrn", + "-sporkaddr=yc5TGfcHYoLCrcbVy4umsiDjsYUn39vLui", + "-minsporkkeys=3"])) + self.nodes.append(start_node(2, self.options.tmpdir, + ["-debug", "-sporkkey=92bxUjPT5AhgXuXJwfGGXqhomY2SdQ55MYjXyx9DZNxCABCSsRH", + "-sporkaddr=ygcG5S2pQz2U1UAaHvU6EznKZW7yapKMA7", + "-sporkaddr=yfLSXFfipnkgYioD6L8aUNyfRgEBuJv48h", + "-sporkaddr=yNsMZhEhYqv14TgdYb1NS2UmNZjE8FSJxa", + "-sporkaddr=ycbRQWbovrhQMTuxg9p4LAuW5SCMAKqPrn", + "-sporkaddr=yc5TGfcHYoLCrcbVy4umsiDjsYUn39vLui", + "-minsporkkeys=3"])) + self.nodes.append(start_node(3, self.options.tmpdir, + ["-debug", "-sporkkey=934yPXiVGf4RCY2qTs2Bt5k3TEtAiAg12sMxCt8yVWbSU7p3fuD", + "-sporkaddr=ygcG5S2pQz2U1UAaHvU6EznKZW7yapKMA7", + "-sporkaddr=yfLSXFfipnkgYioD6L8aUNyfRgEBuJv48h", + "-sporkaddr=yNsMZhEhYqv14TgdYb1NS2UmNZjE8FSJxa", + "-sporkaddr=ycbRQWbovrhQMTuxg9p4LAuW5SCMAKqPrn", + "-sporkaddr=yc5TGfcHYoLCrcbVy4umsiDjsYUn39vLui", + "-minsporkkeys=3"])) + self.nodes.append(start_node(4, self.options.tmpdir, + ["-debug", "-sporkkey=92Cxwia363Wg2qGF1fE5z4GKi8u7r1nrWQXdtsj2ACZqaDPSihD", + "-sporkaddr=ygcG5S2pQz2U1UAaHvU6EznKZW7yapKMA7", + "-sporkaddr=yfLSXFfipnkgYioD6L8aUNyfRgEBuJv48h", + "-sporkaddr=yNsMZhEhYqv14TgdYb1NS2UmNZjE8FSJxa", + "-sporkaddr=ycbRQWbovrhQMTuxg9p4LAuW5SCMAKqPrn", + "-sporkaddr=yc5TGfcHYoLCrcbVy4umsiDjsYUn39vLui", + "-minsporkkeys=3"])) + # connect nodes at start + for i in range(0, 5): + for j in range(i, 5): + connect_nodes(self.nodes[i], j) + + def get_test_spork_state(self, node): + info = node.spork('show') + # use InstantSend spork for tests + return info['SPORK_2_INSTANTSEND_ENABLED'] + + def set_test_spork_state(self, node, value): + # use InstantSend spork for tests + node.spork('SPORK_2_INSTANTSEND_ENABLED', value) + + def wait_for_test_spork_state(self, node, value): + start = time() + got_state = False + while True: + if self.get_test_spork_state(node) == value: + got_state = True + break + if time() > start + 10: + break + sleep(0.1) + return got_state + + def run_test(self): + # check test spork default state + for node in self.nodes: + assert(self.get_test_spork_state(node) == 0) + + set_mocktime(get_mocktime() + 1) + set_node_times(self.nodes, get_mocktime()) + # first and second signers set spork value + self.set_test_spork_state(self.nodes[0], 1) + self.set_test_spork_state(self.nodes[1], 1) + # spork change requires at least 3 signers + for node in self.nodes: + assert(not self.wait_for_test_spork_state(node, 1)) + + # third signer set spork value + self.set_test_spork_state(self.nodes[2], 1) + # now spork state is changed + for node in self.nodes: + assert(self.wait_for_test_spork_state(node, 1)) + + set_mocktime(get_mocktime() + 1) + set_node_times(self.nodes, get_mocktime()) + # now set the spork again with other signers to test + # old and new spork messages interaction + self.set_test_spork_state(self.nodes[2], 2) + self.set_test_spork_state(self.nodes[3], 2) + self.set_test_spork_state(self.nodes[4], 2) + for node in self.nodes: + assert(self.wait_for_test_spork_state(node, 2)) + + +if __name__ == '__main__': + MultiKeySporkTest().main() diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 1beccf7f0..265d3c366 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -237,7 +237,8 @@ public: nPoolMaxTransactions = 3; nFulfilledRequestExpireTime = 60*60; // fulfilled requests expire in 1 hour - strSporkAddress = "Xgtyuk76vhuFW2iT7UAiHgNdWXCf3J34wh"; + vSporkAddresses = {"Xgtyuk76vhuFW2iT7UAiHgNdWXCf3J34wh"}; + nMinSporkKeys = 1; checkpointData = (CCheckpointData) { boost::assign::map_list_of @@ -393,7 +394,8 @@ public: nPoolMaxTransactions = 3; nFulfilledRequestExpireTime = 5*60; // fulfilled requests expire in 5 minutes - strSporkAddress = "yjPtiKh2uwk3bDutTEA2q9mCtXyiZRWn55"; + vSporkAddresses = {"yjPtiKh2uwk3bDutTEA2q9mCtXyiZRWn55"}; + nMinSporkKeys = 1; checkpointData = (CCheckpointData) { boost::assign::map_list_of @@ -537,7 +539,8 @@ public: nPoolMaxTransactions = 3; nFulfilledRequestExpireTime = 5*60; // fulfilled requests expire in 5 minutes - strSporkAddress = "yjPtiKh2uwk3bDutTEA2q9mCtXyiZRWn55"; + vSporkAddresses = {"yjPtiKh2uwk3bDutTEA2q9mCtXyiZRWn55"}; + nMinSporkKeys = 1; checkpointData = (CCheckpointData) { boost::assign::map_list_of @@ -643,7 +646,8 @@ public: nFulfilledRequestExpireTime = 5*60; // fulfilled requests expire in 5 minutes // privKey: cP4EKFyJsHT39LDqgdcB43Y3YXjNyjb5Fuas1GQSeAtjnZWmZEQK - strSporkAddress = "yj949n1UH6fDhw6HtVE5VMj2iSTaSWBMcW"; + vSporkAddresses = {"yj949n1UH6fDhw6HtVE5VMj2iSTaSWBMcW"}; + nMinSporkKeys = 1; checkpointData = (CCheckpointData){ boost::assign::map_list_of diff --git a/src/chainparams.h b/src/chainparams.h index fa67c9b73..29e3b9588 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -88,7 +88,8 @@ public: const ChainTxData& TxData() const { return chainTxData; } int PoolMaxTransactions() const { return nPoolMaxTransactions; } int FulfilledRequestExpireTime() const { return nFulfilledRequestExpireTime; } - const std::string& SporkAddress() const { return strSporkAddress; } + const std::vector& SporkAddresses() const { return vSporkAddresses; } + int MinSporkKeys() const { return nMinSporkKeys; } protected: CChainParams() {} @@ -116,7 +117,8 @@ protected: ChainTxData chainTxData; int nPoolMaxTransactions; int nFulfilledRequestExpireTime; - std::string strSporkAddress; + std::vector vSporkAddresses; + int nMinSporkKeys; }; /** diff --git a/src/init.cpp b/src/init.cpp index d33dfd96d..0cf30ccca 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -570,6 +570,7 @@ std::string HelpMessage(HelpMessageMode mode) AppendParamsHelpMessages(strUsage, showDebug); strUsage += HelpMessageOpt("-litemode=", strprintf(_("Disable all Dash specific functionality (Masternodes, PrivateSend, InstantSend, Governance) (0-1, default: %u)"), 0)); strUsage += HelpMessageOpt("-sporkaddr=", strprintf(_("Override spork address. Only useful for regtest and devnet. Using this on mainnet or testnet will ban you."))); + strUsage += HelpMessageOpt("-minsporkkeys=", strprintf(_("Overrides minimum spork signers to change spork value. Only useful for regtest and devnet. Using this on mainnet or testnet will ban you."))); strUsage += HelpMessageGroup(_("Masternode options:")); strUsage += HelpMessageOpt("-masternode=", strprintf(_("Enable the client to act as a masternode (0-1, default: %u)"), 0)); @@ -1398,13 +1399,28 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) threadGroup.create_thread(&ThreadScriptCheck); } - if (!sporkManager.SetSporkAddress(GetArg("-sporkaddr", Params().SporkAddress()))) - return InitError(_("Invalid spork address specified with -sporkaddr")); + std::vector vSporkAddresses; + if (mapMultiArgs.count("-sporkaddr")) { + vSporkAddresses = mapMultiArgs.at("-sporkaddr"); + } else { + vSporkAddresses = Params().SporkAddresses(); + } + for (const auto& address: vSporkAddresses) { + if (!sporkManager.SetSporkAddress(address)) { + return InitError(_("Invalid spork address specified with -sporkaddr")); + } + } - if (IsArgSet("-sporkkey")) // spork priv key - { - if (!sporkManager.SetPrivKey(GetArg("-sporkkey", ""))) + int minsporkkeys = GetArg("-minsporkkeys", Params().MinSporkKeys()); + if (!sporkManager.SetMinSporkKeys(minsporkkeys)) { + return InitError(_("Invalid minimum number of spork signers specified with -minsporkkeys")); + } + + + if (IsArgSet("-sporkkey")) { // spork priv key + if (!sporkManager.SetPrivKey(GetArg("-sporkkey", ""))) { return InitError(_("Unable to sign spork message, wrong key?")); + } } // Start the lightweight task scheduler thread diff --git a/src/spork.cpp b/src/spork.cpp index cc60cf7ff..a9963eb13 100644 --- a/src/spork.cpp +++ b/src/spork.cpp @@ -15,7 +15,7 @@ CSporkManager sporkManager; -const std::string CSporkManager::SERIALIZATION_VERSION_STRING = "CMasternodeMan-Version-1"; +const std::string CSporkManager::SERIALIZATION_VERSION_STRING = "CMasternodeMan-Version-2"; std::map mapSporkDefaults = { {SPORK_2_INSTANTSEND_ENABLED, 0}, // ON @@ -31,6 +31,27 @@ std::map mapSporkDefaults = { {SPORK_16_INSTANTSEND_AUTOLOCKS, 4070908800ULL}, // OFF }; +bool CSporkManager::SporkValueIsActive(int nSporkID, int64_t &nActiveValueRet) const +{ + LOCK(cs); + + if (!mapSporksActive.count(nSporkID)) return false; + + // calc how many values we have and how many signers vote for every value + std::map mapValueCounts; + for (const auto& pair: mapSporksActive.at(nSporkID)) { + mapValueCounts[pair.second.nValue]++; + if (mapValueCounts.at(pair.second.nValue) >= nMinSporkKeys) { + // nMinSporkKeys is always more than the half of the max spork keys number, + // so there is only one such value and we can stop here + nActiveValueRet = pair.second.nValue; + return true; + } + } + + return false; +} + void CSporkManager::Clear() { LOCK(cs); @@ -43,29 +64,47 @@ void CSporkManager::Clear() void CSporkManager::CheckAndRemove() { LOCK(cs); - bool fSporkAddressIsSet = !sporkPubKeyID.IsNull(); + bool fSporkAddressIsSet = !setSporkPubKeyIDs.empty(); assert(fSporkAddressIsSet); auto itActive = mapSporksActive.begin(); while (itActive != mapSporksActive.end()) { - if (!itActive->second.CheckSignature(sporkPubKeyID, false)) { - if (!itActive->second.CheckSignature(sporkPubKeyID, true)) { - mapSporksByHash.erase(itActive->second.GetHash()); - mapSporksActive.erase(itActive++); + auto itSignerPair = itActive->second.begin(); + while (itSignerPair != itActive->second.end()) { + if (setSporkPubKeyIDs.find(itSignerPair->first) == setSporkPubKeyIDs.end()) { + mapSporksByHash.erase(itSignerPair->second.GetHash()); continue; } + if (!itSignerPair->second.CheckSignature(itSignerPair->first, false)) { + if (!itSignerPair->second.CheckSignature(itSignerPair->first, true)) { + mapSporksByHash.erase(itSignerPair->second.GetHash()); + itActive->second.erase(itSignerPair++); + continue; + } + } + ++itSignerPair; + } + if (itActive->second.empty()) { + mapSporksActive.erase(itActive++); + continue; } ++itActive; } + auto itByHash = mapSporksByHash.begin(); while (itByHash != mapSporksByHash.end()) { - if (!itByHash->second.CheckSignature(sporkPubKeyID, false)) { - if (!itByHash->second.CheckSignature(sporkPubKeyID, true)) { - mapSporksActive.erase(itByHash->second.nSporkID); - mapSporksByHash.erase(itByHash++); - continue; + bool found = false; + for (const auto& signer: setSporkPubKeyIDs) { + if (itByHash->second.CheckSignature(signer, false) || + itByHash->second.CheckSignature(signer, true)) { + found = true; + break; } } + if (!found) { + mapSporksByHash.erase(itByHash++); + continue; + } ++itByHash; } } @@ -88,41 +127,61 @@ void CSporkManager::ProcessSpork(CNode* pfrom, const std::string& strCommand, CD if(!chainActive.Tip()) return; strLogMsg = strprintf("SPORK -- hash: %s id: %d value: %10d bestHeight: %d peer=%d", hash.ToString(), spork.nSporkID, spork.nValue, chainActive.Height(), pfrom->id); } + + CKeyID keyIDSigner; + bool fSpork6IsActive = IsSporkActive(SPORK_6_NEW_SIGS); + if (!spork.GetSignerKeyID(keyIDSigner, fSpork6IsActive) + || !setSporkPubKeyIDs.count(keyIDSigner)) { + // Note: unlike for other messages we have to check for new format even with SPORK_6_NEW_SIGS + // inactive because SPORK_6_NEW_SIGS default is OFF and it is not the first spork to sync + // (and even if it would, spork order can't be guaranteed anyway). + if (!spork.GetSignerKeyID(keyIDSigner, !fSpork6IsActive) + || !setSporkPubKeyIDs.count(keyIDSigner)) { + LOCK(cs_main); + LogPrintf("CSporkManager::ProcessSpork -- ERROR: invalid signature\n"); + Misbehaving(pfrom->GetId(), 100); + return; + } + } + { LOCK(cs); // make sure to not lock this together with cs_main if (mapSporksActive.count(spork.nSporkID)) { - if (mapSporksActive[spork.nSporkID].nTimeSigned >= spork.nTimeSigned) { - LogPrint("spork", "%s seen\n", strLogMsg); - return; + if (mapSporksActive[spork.nSporkID].count(keyIDSigner)) { + if (mapSporksActive[spork.nSporkID][keyIDSigner].nTimeSigned >= spork.nTimeSigned) { + LogPrint("spork", "%s seen\n", strLogMsg); + return; + } else { + LogPrintf("%s updated\n", strLogMsg); + } } else { - LogPrintf("%s updated\n", strLogMsg); + LogPrintf("%s new signer\n", strLogMsg); } } else { LogPrintf("%s new\n", strLogMsg); } } - if(!spork.CheckSignature(sporkPubKeyID, IsSporkActive(SPORK_6_NEW_SIGS))) { - LOCK(cs_main); - LogPrintf("CSporkManager::ProcessSpork -- ERROR: invalid signature\n"); - Misbehaving(pfrom->GetId(), 100); - return; - } { LOCK(cs); // make sure to not lock this together with cs_main mapSporksByHash[hash] = spork; - mapSporksActive[spork.nSporkID] = spork; + mapSporksActive[spork.nSporkID][keyIDSigner] = spork; } spork.Relay(connman); //does a task if needed - ExecuteSpork(spork.nSporkID, spork.nValue); + int64_t nActiveValue = 0; + if (SporkValueIsActive(spork.nSporkID, nActiveValue)) { + ExecuteSpork(spork.nSporkID, nActiveValue); + } } else if (strCommand == NetMsgType::GETSPORKS) { LOCK(cs); // make sure to not lock this together with cs_main for (const auto& pair : mapSporksActive) { - connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::SPORK, pair.second)); + for (const auto& signerSporkPair: pair.second) { + connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::SPORK, signerSporkPair.second)); + } } } @@ -161,11 +220,17 @@ bool CSporkManager::UpdateSpork(int nSporkID, int64_t nValue, CConnman& connman) { CSporkMessage spork = CSporkMessage(nSporkID, nValue, GetAdjustedTime()); - if(spork.Sign(sporkPrivKey, IsSporkActive(SPORK_6_NEW_SIGS))) { + bool fSpork6IsActive = IsSporkActive(SPORK_6_NEW_SIGS); + if(spork.Sign(sporkPrivKey, fSpork6IsActive)) { + CKeyID keyIDSigner; + if (!spork.GetSignerKeyID(keyIDSigner, fSpork6IsActive) || !setSporkPubKeyIDs.count(keyIDSigner)) { + LogPrintf("CSporkManager::UpdateSpork: failed to find keyid for private key\n"); + return false; + } spork.Relay(connman); LOCK(cs); mapSporksByHash[spork.GetHash()] = spork; - mapSporksActive[nSporkID] = spork; + mapSporksActive[nSporkID][keyIDSigner] = spork; return true; } @@ -176,26 +241,29 @@ bool CSporkManager::UpdateSpork(int nSporkID, int64_t nValue, CConnman& connman) bool CSporkManager::IsSporkActive(int nSporkID) { LOCK(cs); - int64_t r = -1; + int64_t nSporkValue = -1; - if(mapSporksActive.count(nSporkID)){ - r = mapSporksActive[nSporkID].nValue; - } else if (mapSporkDefaults.count(nSporkID)) { - r = mapSporkDefaults[nSporkID]; - } else { - LogPrint("spork", "CSporkManager::IsSporkActive -- Unknown Spork ID %d\n", nSporkID); - r = 4070908800ULL; // 2099-1-1 i.e. off by default + if (SporkValueIsActive(nSporkID, nSporkValue)) { + return nSporkValue < GetAdjustedTime(); } - return r < GetAdjustedTime(); + if (mapSporkDefaults.count(nSporkID)) { + return mapSporkDefaults[nSporkID] < GetAdjustedTime(); + } + + LogPrint("spork", "CSporkManager::IsSporkActive -- Unknown Spork ID %d\n", nSporkID); + return false; } // grab the value of the spork on the network, or the default int64_t CSporkManager::GetSporkValue(int nSporkID) { LOCK(cs); - if (mapSporksActive.count(nSporkID)) - return mapSporksActive[nSporkID].nValue; + + int64_t nSporkValue = -1; + if (SporkValueIsActive(nSporkID, nSporkValue)) { + return nSporkValue; + } if (mapSporkDefaults.count(nSporkID)) { return mapSporkDefaults[nSporkID]; @@ -260,10 +328,23 @@ bool CSporkManager::GetSporkByHash(const uint256& hash, CSporkMessage &sporkRet) bool CSporkManager::SetSporkAddress(const std::string& strAddress) { LOCK(cs); CBitcoinAddress address(strAddress); - if (!address.IsValid() || !address.GetKeyID(sporkPubKeyID)) { + CKeyID keyid; + if (!address.IsValid() || !address.GetKeyID(keyid)) { LogPrintf("CSporkManager::SetSporkAddress -- Failed to parse spork address\n"); return false; } + setSporkPubKeyIDs.insert(keyid); + return true; +} + +bool CSporkManager::SetMinSporkKeys(int minSporkKeys) +{ + int maxKeysNumber = setSporkPubKeyIDs.size(); + if ((minSporkKeys <= maxKeysNumber / 2) || (minSporkKeys > maxKeysNumber)) { + LogPrintf("CSporkManager::SetMinSporkKeys -- Invalid min spork signers number: %d\n", minSporkKeys); + return false; + } + nMinSporkKeys = minSporkKeys; return true; } @@ -276,8 +357,8 @@ bool CSporkManager::SetPrivKey(const std::string& strPrivKey) return false; } - if (pubKey.GetID() != sporkPubKeyID) { - LogPrintf("CSporkManager::SetPrivKey -- New private key does not belong to spork address\n"); + if (setSporkPubKeyIDs.find(pubKey.GetID()) == setSporkPubKeyIDs.end()) { + LogPrintf("CSporkManager::SetPrivKey -- New private key does not belong to spork addresses\n"); return false; } @@ -309,7 +390,11 @@ uint256 CSporkMessage::GetHash() const uint256 CSporkMessage::GetSignatureHash() const { - return GetHash(); + CHashWriter s(SER_GETHASH, 0); + s << nSporkID; + s << nValue; + s << nTimeSigned; + return s.GetHash(); } bool CSporkMessage::Sign(const CKey& key, bool fSporkSixActive) @@ -382,6 +467,27 @@ bool CSporkMessage::CheckSignature(const CKeyID& pubKeyId, bool fSporkSixActive) return true; } +bool CSporkMessage::GetSignerKeyID(CKeyID &retKeyidSporkSigner, bool fSporkSixActive) +{ + CPubKey pubkeyFromSig; + if (fSporkSixActive) { + if (!pubkeyFromSig.RecoverCompact(GetSignatureHash(), vchSig)) { + return false; + } + } else { + std::string strMessage = std::to_string(nSporkID) + std::to_string(nValue) + std::to_string(nTimeSigned); + CHashWriter ss(SER_GETHASH, 0); + ss << strMessageMagic; + ss << strMessage; + if (!pubkeyFromSig.RecoverCompact(ss.GetHash(), vchSig)) { + return false; + } + } + + retKeyidSporkSigner = pubkeyFromSig.GetID(); + return true; +} + void CSporkMessage::Relay(CConnman& connman) { CInv inv(MSG_SPORK, GetHash()); diff --git a/src/spork.h b/src/spork.h index efccd232f..85e44a33e 100644 --- a/src/spork.h +++ b/src/spork.h @@ -70,9 +70,7 @@ public: READWRITE(nSporkID); READWRITE(nValue); READWRITE(nTimeSigned); - if (!(s.GetType() & SER_GETHASH)) { - READWRITE(vchSig); - } + READWRITE(vchSig); } uint256 GetHash() const; @@ -80,6 +78,7 @@ public: bool Sign(const CKey& key, bool fSporkSixActive); bool CheckSignature(const CKeyID& pubKeyId, bool fSporkSixActive) const; + bool GetSignerKeyID(CKeyID& retKeyidSporkSigner, bool fSporkSixActive); void Relay(CConnman& connman); }; @@ -91,11 +90,13 @@ private: mutable CCriticalSection cs; std::map mapSporksByHash; - std::map mapSporksActive; + std::map > mapSporksActive; - CKeyID sporkPubKeyID; + std::set setSporkPubKeyIDs; + int nMinSporkKeys; CKey sporkPrivKey; + bool SporkValueIsActive(int nSporkID, int64_t& nActiveValueRet) const; public: CSporkManager() {} @@ -114,7 +115,9 @@ public: strVersion = SERIALIZATION_VERSION_STRING; READWRITE(strVersion); } - READWRITE(sporkPubKeyID); + // we don't serialize pubkey ids because pubkeys should be + // hardcoded or be setted with cmdline or options, should + // not reuse pubkeys from previous dashd run READWRITE(mapSporksByHash); READWRITE(mapSporksActive); // we don't serialize private key to prevent its leakage @@ -135,6 +138,7 @@ public: bool GetSporkByHash(const uint256& hash, CSporkMessage &sporkRet); bool SetSporkAddress(const std::string& strAddress); + bool SetMinSporkKeys(int minSporkKeys); bool SetPrivKey(const std::string& strPrivKey); std::string ToString() const;