文章原文來自:Code your own blockchain mining algorithm in Go! ,原始文章通過 Go 語言來實現的,這里仍然是承接上一篇文章,來使用 C# + .Net Core 實現我們的挖礦算法。
強烈建議閱讀前先閱讀這篇文章
什么是加密貨幣挖掘?
一個加密貨幣的價值體現在它的稀缺性上,如果任何人都可以任意構造一個比特幣,那么比特幣就毫無價值,所以比特幣的區塊鏈會讓參與者完成一項“工作”,根據這個工作的最終結果還分發比特幣,這個過程就被叫做“挖礦”。這就類似於一個黃金礦工花一些時間來工作,然后獲得一點黃金。
挖礦的原理
如果你百度/谷歌搜索 比特幣挖礦的原理
的話,都會給你說是計算一個復雜的數學問題而已,但是這么說的話太籠統而且也太簡單。采礦引擎如何工作這是一個重要的知識點,所以我們需要了解一些密碼學知識和哈希算法相關的知識,才能知道挖礦的基本原理。
哈希/散列介紹
單向加密人類能夠理解的輸入,例如 Hello World
,並將其扔到某個加密函數(即所謂的復雜的數學問題),加密函數的算法越復雜,逆向工程就越困難。
例如一個 SHA - 256 的例子,這個網站可以很快的計算散列值,讓我們來散列 “Hello World” 看看會得到什么結果:
不管你試驗幾次都會得到一樣的散列值,在編程中這種被稱之為冪等性。
加密算法的一個基本特性就是,它們很難通過逆向工程來得到明文結果,但是十分容易驗證他們的加密結果,例如這里的 “Hello World” 很難通過逆向工程得到他的原明文結果,比特幣采用的是 Double SHA-256
也就是將明文通過 SHA-256 計算過一次之后,再拿 SHA-256 針對散列值再次進行計算,在這里我們只使用 SHA-256 來進行加密。
工作證明
比特幣通過讓參與者散列隨機的字母與數字的組合,直到計算出來的散列包含前導 0
。
例如我們計算 886
的散列值可以得到如下結果:
000f21ac06aceb9cdd0575e82d0d85fc39bed0a7a1d71970ba1641666a44f530
它返回了 3
個 0
作為前綴的散列值,但是我們怎么知道 886
計算出來的散列結果產生了 3
個 0
呢?
答案是我並不需要知道。。。我需要知道礦工給我的散列值前導有幾個零就好了,並不需要復雜的算法來驗證整個散列值的有效性。
比特幣則稍微復雜一點,它每隔 10 分鍾生成一個新的區塊,新區塊的散列值的難度它可以動態調整,就類似於 CLR 的 GC 一樣,它可以根據目前挖礦的人數來進行難度動態調整,如果挖礦的人多的話,則調高難度,少則調低。
動手開發
1.項目配置
首先新建一個 Asp.Net Core 項目,然后選擇 Empty Project(空項目) 類型,建立完成后無需進行任何配置。
2.數據模型
這里我們來創建一個具體的區塊數據模型,使用的是 Struct 結構體。
public struct Block
{
/// <summary>
/// 區塊位置
/// </summary>
public int Index { get; set; }
/// <summary>
/// 區塊生成時間戳
/// </summary>
public string TimeStamp { get; set; }
/// <summary>
/// 心率數值
/// </summary>
public int BPM { get; set; }
/// <summary>
/// 區塊 SHA-256 散列值
/// </summary>
public string Hash { get; set; }
/// <summary>
/// 前一個區塊 SHA-256 散列值
/// </summary>
public string PrevHash { get; set; }
/// <summary>
/// 下一個區塊生成難度
/// </summary>
public int Difficulty { get; set; }
/// <summary>
/// 隨機值
/// </summary>
public string Nonce { get; set; }
}
Difficulty
是一個整形,他定義了我們希望得到哈希前導 0 的數量,前導 0 越多,生成正確的散列值就越困難,我們現在從 1 開始。
Nonce
則是每次計算塊散列值所需要的隨機值。
3. 工作證明
我們首先添加一個新的方法來驗證生成的散列值是否包含指定數量的前導 0 :
/// <summary>
/// 校驗 Hash 是否有效
/// </summary>
/// <param name="hashStr">Hash 值</param>
/// <param name="difficulty">難度</param>
/// <returns></returns>
public static bool IsHashValid(string hashStr, int difficulty)
{
var bytes = Enumerable.Range(0, hashStr.Length)
.Where(n => n % 2 == 0)
.Select(n => Convert.ToByte(hashStr.Substring(n, 2), 16))
.ToArray();
var bits = new BitArray(bytes);
for (var i = 0; i < difficulty; i++)
{
if (bits[i]) return false;
}
return true;
}
然后我們更改了之前區塊 Hash 的生成方法:
/// <summary>
/// 計算區塊 HASH 值
/// </summary>
/// <param name="block">區塊實例</param>
/// <returns>計算完成的區塊散列值</returns>
public static string CalculateHash(Block block)
{
string calculationStr = $"{block.Index}{block.TimeStamp}{block.BPM}{block.PrevHash}{block.Nonce}";
SHA256 sha256Generator = SHA256.Create();
byte[] sha256HashBytes = sha256Generator.ComputeHash(Encoding.UTF8.GetBytes(calculationStr));
StringBuilder sha256StrBuilder = new StringBuilder();
foreach (byte @byte in sha256HashBytes)
{
sha256StrBuilder.Append(@byte.ToString("x2"));
}
return sha256StrBuilder.ToString();
}
在這里我們新增新增了 Nonce 隨機值作為散列生成的依據。
那么我們生成新區塊的時候就順便來挖礦吧:
/// <summary>
/// 生成新的區塊
/// </summary>
/// <param name="oldBlock">舊的區塊數據</param>
/// <param name="BPM">心率</param>
/// <returns>新的區塊</returns>
public static Block GenerateBlock(Block oldBlock, int BPM)
{
Block newBlock = new Block()
{
Index = oldBlock.Index + 1,
TimeStamp = CalculateCurrentTimeUTC(),
BPM = BPM,
PrevHash = oldBlock.Hash,
Difficulty = Difficulty
};
// 挖礦 ing...
for (int i = 0; ; i++)
{
newBlock.Nonce = i.ToString("x2");
if (!IsHashValid(CalculateHash(newBlock), Difficulty))
{
Console.WriteLine($"目前結果:{CalculateHash(newBlock)} ,正在計算中...");
Task.Delay(1);
continue;
}
else
{
Console.WriteLine($"目前結果:{CalculateHash(newBlock)} ,計算完畢...");
newBlock.Hash = CalculateHash(newBlock);
break;
}
}
// 原有代碼
// newBlock.Hash = CalculateHash(newBlock);
return newBlock;
}
效果
結語
其實代碼並不復雜,但是這幾十行代碼表明了區塊鏈挖礦的本質,后面你可以參考原文實現 P2P 與 股權權益證明方法與智能合約。
項目代碼地址:http://git.myzony.com/Zony/BlockChain.git