文章原文来自: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