1. 程式人生 > >比特幣中的工作量證明

比特幣中的工作量證明

比特幣網路中任何一個節點,如果想生成一個新的區塊並寫入區塊鏈,必須解出比特幣網路出的工作量證明的迷題。這道題關鍵的三個要素是工作量證明函式、區塊及難度值。工作量證明函式是這道題的計算方法,區塊決定了這道題的輸入資料,難度值決定了這道題的所需要的計算量。

工作量證明函式

SHA是安全雜湊演算法(Secure Hash Algorithm)的縮寫,是一個密碼雜湊函式家族。這一組函式是由美國國家安全域性(NSA)設計,美國國家標準與技術研究院(NIST) 釋出的,主要適用於數字簽名標準。SHA256就是這個函式家族中的一個,是輸出值為256位的雜湊演算法。到目前為止,還沒有出現對SHA256演算法的有效攻擊。

區塊

比特幣區塊由區塊頭和該區塊所包含的交易列表組成。區塊頭大小為80位元組,其構成包括:

   4位元組:版本號
  32位元組:上一個區塊的雜湊值
  32位元組:交易列表的Merkle根雜湊值
   4位元組:當前時間戳
   4位元組:當前難度值
   4位元組:隨機數Nonce值
此80位元組長度的區塊頭,即為比特幣Pow演算法的輸入字串。交易列表附加在區塊頭之後,其中第一筆交易為礦工獲得獎勵和手續費的特殊交易。

bitcoin-0.15.1原始碼中區塊頭和區塊定義

class CBlockHeader
{
public:
    //版本號
    int32_t nVersion;
    //上一個區塊的雜湊值
uint256 hashPrevBlock; //交易列表的Merkle根雜湊值 uint256 hashMerkleRoot; //當前時間戳 uint32_t nTime; //當前挖礦難度,nBits越小難度越大 uint32_t nBits; //隨機數Nonce值 uint32_t nNonce; //其它程式碼略 }; class CBlock : public CBlockHeader { public: //交易列表 std::vector<CTransactionRef> vtx; //其它程式碼略
}; //程式碼位置src/primitives/block.h

難度值

難度值(difficulty)是礦工們在挖礦時候的重要參考指標,它決定了礦工大約需要經過多少次雜湊運算才能產生一個合法的區塊。比特幣的區塊大約每10分鐘生成一個,如果要在不同的全網算力條件下,新區塊的產生保持都基本這個速率,難度值必須根據全網算力的變化進行調整。簡單地說,難度值被設定在無論挖礦能力如何,新區塊產生速率都保持在10分鐘一個。

難度的調整是在每個完整節點中獨立自動發生的。每2016個區塊,所有節點都會按統一的公式自動調整難度,這個公式是由最新2016個區塊的花費時長與期望時長(期望時長為20160分鐘即兩週,是按每10分鐘一個區塊的產生速率計算出的總時長)比較得出的,根據實際時長與期望時長的比值,進行相應調整(或變難或變易)。也就是說,如果區塊產生的速率比10分鐘快則增加難度,比10分鐘慢則降低難度。

這個公式可以總結為如下形式:

新難度值 = 舊難度值 * ( 過去2016個區塊花費時長 / 20160 分鐘 )

工作量證明需要有一個目標值。比特幣工作量證明的目標值(Target)的計算公式如下:

目標值 = 最大目標值 / 難度值
其中最大目標值為一個恆定值:
0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

目標值的大小與難度值成反比。比特幣工作量證明的達成就是礦工計算出來的區塊雜湊值必須小於目標值。

比特幣工作量證明的過程,就是通過不停的變換區塊頭(即嘗試不同的nouce值)作為輸入進行SHA256雜湊運算,找出一個特定格式雜湊值的過程(即要求有一定數量的前導0)。而要求的前導0的個數越多,代表難度越大。

bitcoin-0.15.1原始碼中計算挖礦難度程式碼如下:

//nFirstBlockTime即前2016個塊的第一個塊的時間戳
unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nFirstBlockTime, const Consensus::Params& params)
{
    if (params.fPowNoRetargeting)
        return pindexLast->nBits;

    //計算生成這2016個塊花費的時間
    int64_t nActualTimespan = pindexLast->GetBlockTime() - nFirstBlockTime;
    //不小於3.5天
    if (nActualTimespan < params.nPowTargetTimespan/4)
        nActualTimespan = params.nPowTargetTimespan/4;
    //不大於56天
    if (nActualTimespan > params.nPowTargetTimespan*4)
        nActualTimespan = params.nPowTargetTimespan*4;

    // Retarget
    const arith_uint256 bnPowLimit = UintToArith256(params.powLimit);
    arith_uint256 bnNew;
    bnNew.SetCompact(pindexLast->nBits);
    //計算前2016個塊的難度總和
    //即單個塊的難度*總時間
    bnNew *= nActualTimespan;
    //計算新的難度
    //即2016個塊的難度總和/14天的秒數
    bnNew /= params.nPowTargetTimespan;

    //bnNew越小,難度越大
    //bnNew越大,難度越小
    //要求新的難度,難度不低於引數定義的最小難度
    if (bnNew > bnPowLimit)
        bnNew = bnPowLimit;

    return bnNew.GetCompact();
}
//程式碼位置src/pow.cpp

工作量證明的過程

我們可以把比特幣礦工解這道工作量證明迷題的步驟大致歸納如下:

  1. 生成Coinbase交易,並與其他所有準備打包進區塊的交易組成交易列表,通過Merkle Tree演算法生成Merkle Root Hash

  2. 把Merkle Root Hash及其他相關欄位組裝成區塊頭,將區塊頭的80位元組資料(Block Header)作為工作量證明的輸入

  3. 不停的變更區塊頭中的隨機數即nonce的數值,並對每次變更後的的區塊頭做雙重SHA256運算(即SHA256(SHA256(Block_Header))),將結果值與當前網路的目標值做對比,如果小於目標值,則解題成功,工作量證明完成。

Pow完成的區塊向全網廣播,其他節點將驗證其是否符合規則,如果驗證有效,其他節點將接收此區塊,並附加在已有區塊鏈之後。之後將進入下一輪挖礦。

bitcoin-0.15.1原始碼中Pow演算法實現:

UniValue generateBlocks(std::shared_ptr<CReserveScript> coinbaseScript, int nGenerate, uint64_t nMaxTries, bool keepScript)
{
    static const int nInnerLoopCount = 0x10000;
    int nHeightEnd = 0;
    int nHeight = 0;

    {   // Don't keep cs_main locked
        LOCK(cs_main);
        nHeight = chainActive.Height();
        nHeightEnd = nHeight+nGenerate;
    }
    unsigned int nExtraNonce = 0;
    UniValue blockHashes(UniValue::VARR);
    while (nHeight < nHeightEnd)
    {
        std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(Params()).CreateNewBlock(coinbaseScript->reserveScript));
        if (!pblocktemplate.get())
            throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block");
        CBlock *pblock = &pblocktemplate->block;
        {
            LOCK(cs_main);
            IncrementExtraNonce(pblock, chainActive.Tip(), nExtraNonce);
        }
        //不斷變更區塊頭中的隨機數Nonce
        //對變更後的區塊頭做雙重SHA256雜湊運算
        //與當前難度的目標值做比對,如果小於目標難度,即Pow完成
        //uint64_t nMaxTries = 1000000;即重試100萬次
        while (nMaxTries > 0 && pblock->nNonce < nInnerLoopCount && !CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus())) {
            ++pblock->nNonce;
            --nMaxTries;
        }
        if (nMaxTries == 0) {
            break;
        }
        if (pblock->nNonce == nInnerLoopCount) {
            continue;
        }
        std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock);
        if (!ProcessNewBlock(Params(), shared_pblock, true, nullptr))
            throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted");
        ++nHeight;
        blockHashes.push_back(pblock->GetHash().GetHex());

        //mark script as important because it was used at least for one coinbase output if the script came from the wallet
        if (keepScript)
        {
            coinbaseScript->KeepScript();
        }
    }
    return blockHashes;
}
//程式碼位置src/rpc/mining.cpp

另附bitcoin-0.15.1原始碼中生成鑄幣交易和建立新塊:

std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn, bool fMineWitnessTx)
{
    int64_t nTimeStart = GetTimeMicros();

    resetBlock();

    pblocktemplate.reset(new CBlockTemplate());

    if(!pblocktemplate.get())
        return nullptr;
    pblock = &pblocktemplate->block; // pointer for convenience

    pblock->vtx.emplace_back();
    pblocktemplate->vTxFees.push_back(-1); // updated at end
    pblocktemplate->vTxSigOpsCost.push_back(-1); // updated at end

    LOCK2(cs_main, mempool.cs);
    CBlockIndex* pindexPrev = chainActive.Tip();
    nHeight = pindexPrev->nHeight + 1;

    //版本號
    pblock->nVersion = ComputeBlockVersion(pindexPrev, chainparams.GetConsensus());
    if (chainparams.MineBlocksOnDemand())
        pblock->nVersion = gArgs.GetArg("-blockversion", pblock->nVersion);

    //當前時間戳
    pblock->nTime = GetAdjustedTime();
    const int64_t nMedianTimePast = pindexPrev->GetMedianTimePast();

    nLockTimeCutoff = (STANDARD_LOCKTIME_VERIFY_FLAGS & LOCKTIME_MEDIAN_TIME_PAST)
                       ? nMedianTimePast
                       : pblock->GetBlockTime();
    fIncludeWitness = IsWitnessEnabled(pindexPrev, chainparams.GetConsensus()) && fMineWitnessTx;

    int nPackagesSelected = 0;
    int nDescendantsUpdated = 0;
    addPackageTxs(nPackagesSelected, nDescendantsUpdated);

    int64_t nTime1 = GetTimeMicros();

    nLastBlockTx = nBlockTx;
    nLastBlockWeight = nBlockWeight;

    //建立鑄幣交易
    CMutableTransaction coinbaseTx;
    coinbaseTx.vin.resize(1);
    coinbaseTx.vin[0].prevout.SetNull();
    coinbaseTx.vout.resize(1);
    //挖礦獎勵和手續費
    coinbaseTx.vout[0].scriptPubKey = scriptPubKeyIn;
    coinbaseTx.vout[0].nValue = nFees + GetBlockSubsidy(nHeight, chainparams.GetConsensus());
    coinbaseTx.vin[0].scriptSig = CScript() << nHeight << OP_0;
    //第一筆交易即為礦工獲得獎勵和手續費的特殊交易
    pblock->vtx[0] = MakeTransactionRef(std::move(coinbaseTx));
    pblocktemplate->vchCoinbaseCommitment = GenerateCoinbaseCommitment(*pblock, pindexPrev, chainparams.GetConsensus());
    pblocktemplate->vTxFees[0] = -nFees;

    LogPrintf("CreateNewBlock(): block weight: %u txs: %u fees: %ld sigops %d\n", GetBlockWeight(*pblock), nBlockTx, nFees, nBlockSigOpsCost);

    //上一個區塊的雜湊值
    pblock->hashPrevBlock  = pindexPrev->GetBlockHash();
    UpdateTime(pblock, chainparams.GetConsensus(), pindexPrev);
    //當前挖礦難度
    pblock->nBits          = GetNextWorkRequired(pindexPrev, pblock, chainparams.GetConsensus());
    //隨機數Nonce值
    pblock->nNonce         = 0;
    pblocktemplate->vTxSigOpsCost[0] = WITNESS_SCALE_FACTOR * GetLegacySigOpCount(*pblock->vtx[0]);

    CValidationState state;
    if (!TestBlockValidity(state, chainparams, *pblock, pindexPrev, false, false)) {
        throw std::runtime_error(strprintf("%s: TestBlockValidity failed: %s", __func__, FormatStateMessage(state)));
    }
    int64_t nTime2 = GetTimeMicros();

    LogPrint(BCLog::BENCH, "CreateNewBlock() packages: %.2fms (%d packages, %d updated descendants), validity: %.2fms (total %.2fms)\n", 0.001 * (nTime1 - nTimeStart), nPackagesSelected, nDescendantsUpdated, 0.001 * (nTime2 - nTime1), 0.001 * (nTime2 - nTimeStart));

    return std::move(pblocktemplate);
}
//程式碼位置src/miner.cpp

相關推薦

工作量證明

比特幣網路中任何一個節點,如果想生成一個新的區塊並寫入區塊鏈,必須解出比特幣網路出的工作量證明的迷題。這道題關鍵的三個要素是工作量證明函式、區塊及難度值。工作量證明函式是這道題的計算方法,區塊決定了這道題的輸入資料,難度值決定了這道題的所需要的計算量。 工作量

價值飆升,黑客如何從盜取近8千萬美元的獲利?

比特幣 區塊鏈 分布式 Hardy(晗狄) 架構師技術聯盟目前,有兩個商機可以讓人一夜暴富,一是中國房地產,另一個就是比特幣。由於比特幣的價值繼續飆升,黑客從比特幣挖掘服務中盜取了價值近8000萬美元的比特幣,短短數小時就獲取暴利,看來唯一能跟中國房地產匹敵的,也就只有比特幣了。就在12月7號,網

經常在看到的merkle樹是什麽?

span tree AS com 使用 QQ 特點 和數 是什麽 區塊基礎-merkle樹 Merkle tree中文叫做梅克爾樹,這當然不是一棵真正的植物樹,merkle tree是計算機數據結構中的一種樹,是由計算機科學家 Ralph Merkle 提出的,並以他本

的Base58 編碼

字母 結果 描述 eve nsh ner .cpp 不同的應用 fin base58和base64一樣是一種二進制轉可視字符串的算法,主要用來轉換大整數值。區別是,轉換出來的字符串,去除了幾個看起來會產生歧義的字符,如 0 (零), O (大寫字母O), I (大寫的字母

區塊鏈(1)—— 的區塊、賬戶驗證和記賬

上一篇說到比特幣是一種去中心化的電子現金系統。去中心化說起來似乎挺簡單,但是不用細想就會發現很多問題:賬本儲存在每個節點中,如何保證每個節點中的資料一致,或者說如何防止某些節點的賬本被惡意篡改而影響到整個網路的交易? 如果說交易的驗證由各個節點完成,那麼如何在不把密碼洩露給其

區塊鏈教程(一):的術語

cat 在一起 生成 過程 算法 一次 表結構 art 白皮書 如果你已經看過了上篇文章提到過的比特幣白皮書和《精通比特幣》,本篇文章基本可以忽略!本篇文章稍微介紹一下比特幣中出現頻率比較高的術語; 地址、私鑰、公鑰比特幣地址由一串字符和數字組成,以阿拉伯數字“1”開頭。

什麼是Satoshi?和本聰有什麼關係?

Satoshi Nakamoto(中本聰)是發起比特幣和原始比特幣客戶端建立者。不過,我經常聽到“Satoshi ”這個詞,好像它是一個貨幣單位。 什麼是Satoshi呢?Satoshi是0.00000001 BTC,目前是最小的比特幣交易單位。 ------------ 場外交易中Satoshi是指:

交易延展性問題

修改自 交易延展性 (Transaction Malleability) 當交易被簽名時,簽名並沒有覆蓋交易中所有的資料 (比如位於 txin 中的 scriptSig,由於 scriptSig 中包含公鑰和簽名資料,不可能對自身自簽名),而交易中所有的

的SHA256是何方神聖?

 在比特幣中錢包地址的產生過程中用到了一種叫做SHA256的雜湊演算法,SHA-256是SHA-1演算法的改進版本,前不久,google安全部落格上發不了世界上第一例公開的SHA-1雜湊碰撞例項,證明SHA-1已經不安全了,而作為SHA-1改進版本的SHA-256演算法暫時還是安全的。不得不佩服中本聰在最初

的默克爾樹Merkle

簡介 Merkle Tree,通常也被稱作Hash Tree,顧名思義,就是儲存hash值的一棵樹。Merkle樹的葉子是資料塊(例如,檔案或者檔案的集合)的hash值。非葉節點是其對應子節點串聯字串的hash。 比特幣中對應的默克爾樹 比特幣中

區塊鏈在的應用和原理

區塊鏈記賬原理 區塊鏈(1.0)是一個基於密碼學安全的分散式賬本,是一個方便驗證,不可篡改的賬本。通常認為與智慧合約相結合的區塊鏈為區塊鏈2.0, 如以太坊是典型的區塊鏈2.0很多人只瞭解過比特幣,不知道區塊鏈,比特幣實際是一個使用了區塊鏈技術的應用,只是比特幣當前太熱,把區

如何挖礦(挖礦原理)-工作量證明

區塊鏈 比特幣 在區塊鏈記賬原理 一篇,我們了解到記賬是把交易記錄、交易時間、賬本序號、上一個Hash值等信息計算Hash打包的過程。我們知道所有的計算和存貯是需要消耗計算機資源的,既然要付出成本,那節點為什麽還要參與記賬呢?在中本聰(比特幣之父)的設計裏,完成記賬的節點可以獲得系統給與的一定數量的

《詳解白皮書》- Proof-of-Work(PoW工作量證明機制)

工作量證明:要求使用者進行一些耗時適當的複雜運算,並且答案能被服務方快速驗算,以此耗用的時間、裝置、能源作為擔保成本,以確保服務與資源是被真正的需求所使用。 To implement a distributed timestamp server on a peer-to-peer basis,

共識協議之工作量證明(的挖礦原理)

記賬工作 由於記賬是有獎勵的,每次記賬都可以給自己憑空增加一定數量的個比特幣(當前是12.5比特幣,博文寫作時每個比特幣是4萬人民幣以上,大家可以算算多少錢),因此就出現大家爭相記賬,大家一起記賬就會引起問題:出現記賬不一致的問題,比特幣系統引入工作量證明來解

病毒事件是否證明中國網絡安全不堪一擊?

比特幣病毒事件是否證明中國網絡安全不堪一擊?先簡單聊聊這兩天比特幣事件:時間:2017-05-12 08:00起風險:電腦只要聯網,有445端口隱患,就有可能中比特幣病毒,然後數據被加密,勒索不少錢財。影響範圍:Windows ALL。(微軟甚至最後連xp 2003都更新了補丁)處理方式:斷網數據備份,防火墻

PoW挖礦算法原理及其在、以太坊的實現

print 惡意攻擊 actions 規則 rom header() const state divisor PoW,全稱Proof of Work,即工作量證明,又稱挖礦。大部分公有鏈或虛擬貨幣,如比特幣、以太坊,均基於PoW算法,來實現其共識機制。即根據挖礦貢獻的有效工

你不知道的圈江湖,ETH(以太坊)VS,V神看不上本聰

語言 也有 第一個 基礎 sha 重復 擴展 創新 復用 我們知道,ETH(以太坊)和比特幣是目前最受幣圈用戶青睞的兩種數字貨幣。但你或許不知道,他們背後的創始人,V神和中本聰並沒有多少交集,甚至從某些方面來說,V神是看不上中本聰的,雖然他是比特幣的創造者。 一、密碼朋克,

獨角獸圈在5月份新機構客戶錄得30%的

nag 靈活 com AR CI 增加 們的 吸引 size 就像你古怪的叔叔發誓看到大腳野人,尼斯湖怪物,或者 - 對於我們中間的西弗吉尼亞讀者 - Mothman,許多行業專業人士已經聲稱已有數月的證據顯示機構投資者正準備進入加密貨幣市場一個主要方法。 這個事件,預言的

除了創造本聰還教了我們如何保護賬號安全

作者 | Jack Dossman 譯者 | 李曉泉 編輯 | 波波 "In Satoshi We Trust" 區塊鏈時代最大的祕密,就是比特幣之父中本聰。坐擁100萬比特幣(峰值時接近200億美元)的中本聰,自從2011年離場後,竟再也沒有動過

本聰論文

翻譯一遍,加深理解,也可以移步到 我的個人部落格 瀏覽翻譯 中本聰(Satoshi Nakamoto),又譯中本哲史,於2008年釋出了比特幣的創世論文《Bitcoin: A Peer-to-Peer Electronic Cash System》。在論文中描述了一種基於加