// Copyright (c) 2012-2013 PPCoin developers // Copyright (c) 2014-2017 Primecoin Developers // Distributed under conditional MIT/X11 software license, // see the accompanying file COPYING // // The synchronized checkpoint system is first developed by Sunny King for // ppcoin network in 2012, giving cryptocurrency developers a tool to gain // additional network protection against 51% attack. // // Primecoin also adopts this security mechanism, and the enforcement of // checkpoints is explicitly granted by user, thus granting only temporary // consensual central control to developer at the threats of 51% attack. // // Concepts // // In the network there can be a privileged node known as 'checkpoint master'. // This node can send out checkpoint messages signed by the checkpoint master // key. Each checkpoint is a block hash, representing a block on the blockchain // that the network should reach consensus on. // // Besides verifying signatures of checkpoint messages, each node also verifies // the consistency of the checkpoints. If a conflicting checkpoint is received, // it means either the checkpoint master key is compromised, or there is an // operator mistake. In this situation the node would discard the conflicting // checkpoint message and display a warning message. This precaution controls // the damage to network caused by operator mistake or compromised key. // // Operations // // Checkpoint master key can be established by using the 'makekeypair' command // The public key in source code should then be updated and private key kept // in a safe place. // // Any node can be turned into checkpoint master by setting the 'checkpointkey' // configuration parameter with the private key of the checkpoint master key. // Operator should exercise caution such that at any moment there is at most // one node operating as checkpoint master. When switching master node, the // recommended procedure is to shutdown the master node and restart as // regular node, note down the current checkpoint by 'getcheckpoint', then // compare to the checkpoint at the new node to be upgraded to master node. // When the checkpoint on both nodes match then it is safe to switch the new // node to checkpoint master. // // The configuration parameter 'checkpointdepth' specifies how many blocks // should the checkpoints lag behind the latest block in auto checkpoint mode. // A depth of 0 is the minimum auto checkpoint policy and offers the strongest // protection against 51% attack. A negative depth means that the checkpoints // should not be automatically generated by the checkpoint master, but instead // be manually entered by operator via the 'sendcheckpoint' command. The manual // mode is also the default mode (default value -1 for checkpointdepth). // #include #include "checkpoints.h" #include "checkpointsync.h" #include "base58.h" #include "main.h" #include "txdb.h" #include "uint256.h" #include "txmempool.h" #include "consensus/validation.h" #include "consensus/consensus.h" #include using namespace std; // Synchronized checkpoint (centrally broadcasted) string CSyncCheckpoint::strMasterPrivKey = ""; uint256 hashSyncCheckpoint = ArithToUint256(arith_uint256(0)); uint256 hashPendingCheckpoint = ArithToUint256(arith_uint256(0)); CSyncCheckpoint checkpointMessage; CSyncCheckpoint checkpointMessagePending; uint256 hashInvalidCheckpoint = ArithToUint256(arith_uint256(0)); string strCheckpointWarning; // Only descendant of current sync-checkpoint is allowed bool ValidateSyncCheckpoint(uint256 hashCheckpoint) { if (!mapBlockIndex.count(hashSyncCheckpoint)) return error("%s: block index missing for current sync-checkpoint %s", __func__, hashSyncCheckpoint.ToString()); if (!mapBlockIndex.count(hashCheckpoint)) return error("%s: block index missing for received sync-checkpoint %s", __func__, hashCheckpoint.ToString()); CBlockIndex* pindexSyncCheckpoint = mapBlockIndex[hashSyncCheckpoint]; CBlockIndex* pindexCheckpointRecv = mapBlockIndex[hashCheckpoint]; if (pindexCheckpointRecv->nHeight <= pindexSyncCheckpoint->nHeight) { // Received an older checkpoint, trace back from current checkpoint // to the same height of the received checkpoint to verify // that current checkpoint should be a descendant block if (!chainActive.Contains(pindexCheckpointRecv)) { hashInvalidCheckpoint = hashCheckpoint; return error("%s: new sync-checkpoint %s is conflicting with current sync-checkpoint %s", __func__, hashCheckpoint.ToString(), hashSyncCheckpoint.ToString()); } return false; // ignore older checkpoint } // Received checkpoint should be a descendant block of the current // checkpoint. Trace back to the same height of current checkpoint // to verify. CBlockIndex* pindex = pindexCheckpointRecv; while (pindex->nHeight > pindexSyncCheckpoint->nHeight) if (!(pindex = pindex->pprev)) return error("%s: pprev2 null - block index structure failure", __func__); if (pindex->GetBlockHash() != hashSyncCheckpoint) { hashInvalidCheckpoint = hashCheckpoint; return error("%s: new sync-checkpoint %s is not a descendant of current sync-checkpoint %s", __func__, hashCheckpoint.ToString(), hashSyncCheckpoint.ToString()); } return true; } bool WriteSyncCheckpoint(const uint256& hashCheckpoint) { if (!pblocktree->WriteSyncCheckpoint(hashCheckpoint)) return error("%s: failed to write to txdb sync checkpoint %s", __func__, hashCheckpoint.ToString()); if (!pblocktree->Sync()) return error("%s: failed to commit to txdb sync checkpoint %s", __func__, hashCheckpoint.ToString().c_str()); hashSyncCheckpoint = hashCheckpoint; return true; } bool AcceptPendingSyncCheckpoint() { LOCK(cs_main); bool havePendingCheckpoint = hashPendingCheckpoint != ArithToUint256(arith_uint256(0)) && mapBlockIndex.count(hashPendingCheckpoint); if (!havePendingCheckpoint) return false; if (!ValidateSyncCheckpoint(hashPendingCheckpoint)) { hashPendingCheckpoint = ArithToUint256(arith_uint256(0)); checkpointMessagePending.SetNull(); return false; } if (!chainActive.Contains(mapBlockIndex[hashPendingCheckpoint])) return false; if (!WriteSyncCheckpoint(hashPendingCheckpoint)) return error("%s: failed to write sync checkpoint %s", __func__, hashPendingCheckpoint.ToString()); hashPendingCheckpoint = ArithToUint256(arith_uint256(0)); checkpointMessage = checkpointMessagePending; checkpointMessagePending.SetNull(); // Relay the checkpoint if (!checkpointMessage.IsNull()) { BOOST_FOREACH(CNode* pnode, vNodes) checkpointMessage.RelayTo(pnode); } return true; } // Automatically select a suitable sync-checkpoint uint256 AutoSelectSyncCheckpoint() { // Search backward for a block with specified depth policy const CBlockIndex *pindex = chainActive.Tip(); while (pindex->pprev && pindex->nHeight + (int)GetArg("-checkpointdepth", -1) > chainActive.Tip()->nHeight) pindex = pindex->pprev; return pindex->GetBlockHash(); } // Check against synchronized checkpoint bool CheckSyncCheckpoint(const CBlockIndex* pindexNew) { LOCK(cs_main); assert(pindexNew != NULL); if (pindexNew->nHeight == 0) return true; const uint256& hashBlock = pindexNew->GetBlockHash(); int nHeight = pindexNew->nHeight; // Checkpoint should always be accepted block assert(mapBlockIndex.count(hashSyncCheckpoint)); const CBlockIndex* pindexSync = mapBlockIndex[hashSyncCheckpoint]; // Blocks could have been rewound on startup, do not // return or false here or block could be marked invalid if (!chainActive.Contains(pindexSync)) return true; if (nHeight > pindexSync->nHeight) { // Trace back to same height as sync-checkpoint const CBlockIndex* pindex = pindexNew; while (pindex->nHeight > pindexSync->nHeight && !chainActive.Contains(pindex)) if (!(pindex = pindex->pprev)) return error("%s: pprev null - block index structure failure", __func__); // at this point we could have: // 1. found block in our blockchain // 2. reached pindexSync->nHeight without finding it if (!chainActive.Contains(pindex)) return error("%s: Only descendants of checkpoint accepted", __func__); } if (nHeight == pindexSync->nHeight && hashBlock != hashSyncCheckpoint) return error("%s: Same height with sync-checkpoint", __func__); if (nHeight < pindexSync->nHeight && !mapBlockIndex.count(hashBlock)) return error("%s: Lower height than sync-checkpoint", __func__); return true; } // Reset synchronized checkpoint to last hardened checkpoint bool ResetSyncCheckpoint() { LOCK(cs_main); if (!WriteSyncCheckpoint(Params().GetConsensus().hashGenesisBlock)) return error("%s: failed to write sync checkpoint %s", __func__, Params().GetConsensus().hashGenesisBlock.ToString()); return true; } // Verify sync checkpoint master pubkey and reset sync checkpoint if changed bool CheckCheckpointPubKey() { string strPubKey = ""; string strMasterPubKey = Params().GetConsensus().checkpointPubKey; if (!pblocktree->ReadCheckpointPubKey(strPubKey) || strPubKey != strMasterPubKey) { // write checkpoint master key to db if (!pblocktree->WriteCheckpointPubKey(strMasterPubKey)) return error("%s: failed to write new checkpoint master key to db", __func__); if (!pblocktree->Sync()) return error("%s: failed to commit new checkpoint master key to db", __func__); if (!ResetSyncCheckpoint()) return error("%s: failed to reset sync-checkpoint", __func__); } return true; } bool SetCheckpointPrivKey(string strPrivKey) { CBitcoinSecret vchSecret; if (!vchSecret.SetString(strPrivKey)) return error("%s: Checkpoint master key invalid", __func__); CKey key = vchSecret.GetKey(); if (!key.IsValid()) return false; CSyncCheckpoint::strMasterPrivKey = strPrivKey; return true; } bool SendSyncCheckpoint(uint256 hashCheckpoint) { CSyncCheckpoint checkpoint; checkpoint.hashCheckpoint = hashCheckpoint; CDataStream sMsg(SER_NETWORK, PROTOCOL_VERSION); sMsg << (CUnsignedSyncCheckpoint)checkpoint; checkpoint.vchMsg = vector(sMsg.begin(), sMsg.end()); if (CSyncCheckpoint::strMasterPrivKey.empty()) return error("%s: Checkpoint master key unavailable.", __func__); CBitcoinSecret vchSecret; if (!vchSecret.SetString(CSyncCheckpoint::strMasterPrivKey)) return error("%s: Checkpoint master key invalid", __func__); CKey key = vchSecret.GetKey(); // If key is not correct openssl may crash if (!key.Sign(Hash(checkpoint.vchMsg.begin(), checkpoint.vchMsg.end()), checkpoint.vchSig)) return error("%s: Unable to sign checkpoint, check private key?", __func__); if (!checkpoint.ProcessSyncCheckpoint()) return error("%s: Failed to process checkpoint.", __func__); // Relay checkpoint { LOCK(cs_vNodes); BOOST_FOREACH(CNode* pnode, vNodes) checkpoint.RelayTo(pnode); } return true; } // Verify signature of sync-checkpoint message bool CSyncCheckpoint::CheckSignature() { string strMasterPubKey = Params().GetConsensus().checkpointPubKey; CPubKey key(ParseHex(strMasterPubKey)); if (!key.Verify(Hash(vchMsg.begin(), vchMsg.end()), vchSig)) return error("%s: verify signature failed", __func__); // Now unserialize the data CDataStream sMsg(vchMsg, SER_NETWORK, PROTOCOL_VERSION); sMsg >> *(CUnsignedSyncCheckpoint*)this; return true; } // Process synchronized checkpoint bool CSyncCheckpoint::ProcessSyncCheckpoint() { if (!CheckSignature()) return false; LOCK(cs_main); if (!mapBlockIndex.count(hashCheckpoint)) { LogPrintf("Missing headers for received sync-checkpoint %s\n", hashCheckpoint.ToString()); return false; } if (!ValidateSyncCheckpoint(hashCheckpoint)) return false; bool pass = chainActive.Contains(mapBlockIndex[hashCheckpoint]); if (!pass) { // We haven't received the checkpoint chain, keep the checkpoint as pending hashPendingCheckpoint = hashCheckpoint; checkpointMessagePending = *this; LogPrintf("%s: pending for sync-checkpoint %s\n", __func__, hashCheckpoint.ToString()); return false; } if (!WriteSyncCheckpoint(hashCheckpoint)) return error("%s: failed to write sync checkpoint %s", __func__, hashCheckpoint.ToString()); checkpointMessage = *this; hashPendingCheckpoint = ArithToUint256(arith_uint256(0)); checkpointMessagePending.SetNull(); return true; }