一、挖矿原理
(一)区块头信息分析
数据分析 | 目的 | 更新时间 | 大小 |
---|---|---|---|
Version版本 | 区域版本号 | 更新软件后,指定一个新的版本号 | 4 |
hashPrevBlock前一区块的HASH | 前一区块的256位HASH值 | 新的区块进来时 | 32 |
hashMerkleRoot 默克尔根节点的HASH值 | 基于一个区块中所有交易的256位HASH值 | 接受一个交易时 | 32 |
Time时间戳 | 从1970-01-01 00:00 UTC开始到现在,以秒为单位的时间戳 | 每几秒就更新 | 4 |
Bits当前目标的HASH值 | 压缩格式的当前目标的HASH值 | 当挖矿难度调整时 | 4 |
Nonce随机数 | 从0开始的32位随机 | 产生HASH时A(每次产生HASH随机数要增长) | 4 |
(二)挖矿的本质
- 挖矿是比特币共识机制中的PoW算法(工作量证明机制)
- 挖矿的本质就是计算哈希值
- 挖矿的过程就是重复计算区块头的Hash值,不断修改随机数Nonce,直到该Hash值小于目标难度bits所计算出来的哈希就算挖矿成功
- 将区块头的6个信息拼凑在一起循环计算Hash值
- for循环的条件是Nonce从0到int的最大值(约为40亿)。利用时间戳和Nonce值得变化,计算出一个高位为0的数值。因为目标Hash值就是一个由多个0开头的64位数字。高位0的位数越多,那么小于目标难度的Hash的可能性就越大。当小于时,即挖矿成功。
- bits值越小,目标Hash值中的高位0就越多,这就导致每个节点要进行数以亿次的计算,才可以找到满足条件的Hash,简而言之,bits值越小,难度就越大
- 比特币系统会调整目标的Hash值,以达到控制难度的目的。
计算难度目标及比特币bits变化趋势
计算难度目标举例说明
以区块516532为例
- Bits = “0x17502ab7”
- bits是用来储存目标难度的16进制数值 - coefficient系数,coeffcient = 0x502ab7
- exponent指数,exponent = 0x17
- target = confficient * Math.pow(2,8*(exponent - 3))
快速计算难度目标的步骤
- 获取目标难度中的系数
- 获取目标难度中的指数
- 计算高位补0
- 计算低位补0
- 计算目标数值
我们平常读写,按照习惯是从做至右,左侧是大数,右侧是小数。计算机储存数据的方式是小头位序储存方式,即低位在前,高位在后。所以区块文件中记录的数据都是小头位序排列的数据。因此,在解析16进制数值时,要进行大头位序和小头位序重新排列后在进行计算。
- 经过两次hash256算法
- 将16进制大小端进行颠倒
三 GO 实现区块链PoW算法
package main
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"math/big"
"strconv"
"strings"
"time"
)
func main() {
CheckMiningResult()
}
//验证挖矿结果
func CheckMiningResult() {
//初始化区块头信息
var strVersion,strPre_hash,strMerkle_root,strTimeStamp,strBits,strNonce string
strVersion = "20000000"
strPre_hash = "0000000000000000002805302293df911371f53a595b2cb571c2536ceef62e60"
strMerkle_root = "2ce0a3d3ed22dfd8ab342f5692a51e4b3a2d7778ac0e12f75fe856c59890a8a9"
currenttime := "2018-04-26 18:40:17"
bits := 390680589
nonce := 3778663653
strTimeStamp = DataToTimeHexStr(currenttime)
fmt.Println("strTimeStamp",strTimeStamp)
strBits = strconv.FormatInt(int64(bits),16)
fmt.Println("strBits",strBits)
strNonce = strconv.FormatInt(int64(nonce),16)
fmt.Println("strNonce",strNonce)
//计算挖矿难度目标hash
targetHash := GetTargetHash(strBits)
fmt.Println("目标hash",targetHash)
//计算实际挖矿hash
miningHash := GetMiningHash(strVersion,strPre_hash,strMerkle_root,strTimeStamp,strNonce,strBits)
fmt.Println("实际hash",miningHash)
//比较hash大小
flag := CompareHash(miningHash,targetHash)
fmt.Println("挖矿结果",flag)
}
//计算挖矿目标的hash
func GetTargetHash(bits string) string {
//1. 获取目标难度中的系数
coefficient := bits[2:]
//2. 获取目标难度中的指数
exponent := bits[:2]
exp,_ := strconv.ParseInt(exponent,16,0)
//3. 计算高位补0
prefix := strings.Repeat("0",64 - int(exp) * 2)
//4. 计算低位补0
suffix := strings.Repeat("0",64-len(prefix) - 6)
//5. 计算目标数值
strTargetHash := prefix + coefficient + suffix
return strTargetHash
}
//计算实际挖矿的hash
func GetMiningHash(strVersion,strPre_hash,strMerkle_root,strTimeStamp,strBits,strNonce string) string {
//获取区块头中六个参数的十六进制数,转换成小头位序排列方式
_, version := ReverseHexString(strVersion)
_, pre_hash := ReverseHexString(strPre_hash)
_, merkle_root := ReverseHexString(strMerkle_root)
_, timeStamp := ReverseHexString(strTimeStamp)
_, bits := ReverseHexString(strBits)
_, nonce := ReverseHexString(strNonce)
//将6个小头位序的数值拼接,形成区块头信息字符串
header_hex := version + pre_hash + merkle_root + timeStamp + bits + nonce
//将区块头信息经过两次hash256算法
hashedStr := SHA256DOUSTR(header_hex,true)
//将十六进制字符串进行大小端颠倒
_,res := ReverseHexString(hashedStr)
return res
}
//比较目标hash与挖矿hash的大小
func CompareHash(stringMiningHash,strTargetHash string) bool {
var miningHash big.Int
var targetHash big.Int
miningHash.SetString(stringMiningHash,16)
targetHash.SetString(strTargetHash,16)
result := new(big.Int)
result.Sub(&miningHash,&targetHash)
value := fmt.Sprintf("%+d",result)
if strings.HasPrefix(value,"-") {
return true
} else if strings.HasPrefix(value,"+0"){
return true
} else {
return false
}
}
//将16进制进行大小端颠倒
func ReverseHexString(hexString string) ([]byte,string) {
data,_ := hex.DecodeString(hexString)
for i,j := 0,len(data) - 1;i < j;i,j = i + 1,j - 1 {
data[i],data[j] = data[j],data[i]
}
return data,fmt.Sprintf("%x",data)
}
func SHA256DOUSTR(text string,ishex bool)string {
a := SHA256double(text,ishex)
return fmt.Sprintf("%x",a)
}
func ReverseByte(data[]byte) {
for i,j := 0, len(data) - 1 ; i < j ; i,j = i + 1,j - 1 {
data[i],data[j] = data[j],data[i]
//fmt.Println(i,j)
//break
}
}
func DateToTimeStamp(_data string) int64 {
base_data := "2006-01-02 15:04:05"
t,_ := time.Parse(base_data,_data)
tm := t.Unix()
return tm
}
func DataToTimeHexStr(_data string) string {
timeStampInt := DateToTimeStamp(_data)
strTimeStamp := strconv.FormatInt(int64(timeStampInt),16)
return strTimeStamp
}
func SHA256double(text string,ishex bool) []byte {
hashInstance := sha256.New()
if ishex {
arr,_ := hex.DecodeString(text)
hashInstance.Write(arr)
}else {
hashInstance.Write([]byte(text))
}
bytes := hashInstance.Sum(nil)
hashInstance.Reset()
hashInstance.Write(bytes)
bytes = hashInstance.Sum(nil)
return bytes
}