Ⅱ.golang实现比特币挖矿

大家可能都听过比特币挖矿,在没接触之前你也许会对'挖矿'这个词心生畏惧,深不可测,高不可攀,但是当你真正尝试用代码去实现后,你会发现比特币挖矿也就那么回事嘛,嘿嘿,下面我们就用go语言来实现一下。

POW

工作量证明是一种区块的产生机制。产生一个区块不能随随便便,需要通过一番努力的工作,而完成工作的人,能得到相应的奖励。经过一番努力才能产生的区块,保证区块链的稳定和难以篡改的特性。

在比特币世界里,有一群按照工作量证明行事的矿工。他们就是一群提供网络、算力等计算机资源的人,他们的计算机不断按照比特币设计的工作量证明的规则计算数学谜题,解出谜题的人就得到了比特币的奖励,而大量的按照规则行事的诚实以期望获得奖励的比特币节点极大的保证了比特币网络的稳定,维持了它不可篡改的特性。

PoW,全称Proof of Work,即工作量证明,又称挖矿,那么具体是什么个意思呢,就是必须证明你做了很多工作,也就是说做这件事不是很容易随随便便想做成就能做成的,那么程序怎么体现你的工作量呢。下面我们就来看一下比特币的挖矿原理

挖矿原理

打开一个sha256在线加密解密的网站,

任意输入都能通过sha256变成64位的hash,

这次我输入的是yichenjun

得到

f3c3696ef82d507e49568c15a470f896ff331b5292c03c17790d4a0ac677a67f

我输入yichenjun1

得到

c7821e1849ac2c272a0ed9487de5cbe451932b8b6b771cd6314d20a260073c1f

每次输入发现任意的改变,得到的hash都会完全不一样,

那么比特币的做法就是,我每次给你一个规定(比如hash的前6位都是0时),这个hash就算是你经过一定的工作量找到了的,就像这样的hash

00000021e1849ac2c272a0ed9487de5cbe451932b8b6b771cd6314d20a260073

那么这个区块就算是你挖矿得到的了。规定前面的0越多,你得到这个hash就越难,0000000000009ac2c272a0ed9487de5cbe451932b8b6b771cd6314d20a260073肯定比00000021e1849ac2c272a0ed9487de5cbe451932b8b6b771cd6314d20a260073 得到的难,所以约定的前面几个零就相当与一个边界条件,我要前面六个0,你这个hash前面七个0,那么也是可以的,也就是说,你计算出来的hash要小于规定的边界值(比特币动态调整边界值的大小来确保每十分钟产生一个区块),前面要求的零越多,就越难得到,通过这种方法就可以证明你得到这个hash是花了一定工作量才得到的。

下面是哈希计算的方法

  1. 取区块的头信息
  2. 取区块头中的一个计数器,称为nonce,
  3. 组合2和1的信息,计算哈希值。
  4. 检查哈希是否符合一定的条件
    1. 符合条件,结束
    2. 不符合,递增2的计数器,

这里的条件是,哈希的前几位数是0,要求越多的0,则计算难度越大。比特币通过区块产生的速度,动态调节这个0的多少,来保证每10分钟产生一个区块。

算法实现

首先定义,算法的检查条件,即计算难度

const targetBits = 24 //对比的位数

 

“target bits” 代表了难度,也就是开头有多少个 0,计算方法是,SHA256得到256bit的哈希值,转换为字符串表示是256/8*2,因为2个16进制的字符才表示一个byte,因此24/8*2=6个0字符串。


type ProofOfWork struct {
	block *Block //区块
	target *big.Int ////big是go语言里的精确的大数的标准库,存储计算哈希对比的特定整数
}

//创建一个工作量证明的挖矿对象
func NewProofOfWork(block *Block)*ProofOfWork{
	target := big.NewInt(1);//初始化目标整数
	fmt.Printf("%x\n",target)
	target.Lsh(target,uint(256-targetBits)) //数据转换,小于这个数的都是符合条件的哈希值,二进制移位232位,16进制以为58位
	fmt.Printf("%x\n",target)
	pow:=&ProofOfWork{block,target} //创建一个对象
	return pow
}
//准备数据,进行挖矿计算
func (pow * ProofOfWork) prepareData(nonce int)[]byte{
	data := bytes.Join(
		[][]byte{
			pow.block.PreBlockHash,//上一块哈希
			pow.block.Data,//当前数据
			IntToHex(pow.block.Timestamp),//十六进制
			IntToHex(int64(targetBits)),//位数,十六进制
			IntToHex(int64(nonce)),//保存工作量的证明
		},[]byte{},
	)
	return data
}
//挖矿执行
func (pow * ProofOfWork) Run() (int,[]byte) {
	var hashInt big.Int
	var hash [32]byte
	nonce :=0
	fmt.Printf("当前挖矿计算的数据%s",pow.block.Data)
	for nonce<maxNonce{
		data :=pow.prepareData(nonce)//准备好的数据
		hash = sha256.Sum256(data)//计算出哈希
		fmt.Printf("\r%x",hash)//打印显示哈希

		hashInt.SetBytes(hash[:])//获取要对比的数据
		      //10000000000000000000000000000000000000000000000000000000000
		//000000ce7bdc24fb9b3ebc36939c21b3053013acc87dc890e673036e575c2a00
		if hashInt.Cmp(pow.target)==-1{ //挖矿的校验
			break
		}else {
			nonce++
		}
	}
	fmt.Println("\n\n")
	return nonce,hash[:]//nonce 解题的答案,hash当前的哈希
}
//校验挖矿是不是真的成功
func (pow * ProofOfWork) Validate()bool{
	var hashInt big.Int
	data :=pow.prepareData(pow.block.Nonce)//准备好的数据
	hash := sha256.Sum256(data)//计算出哈希
	hashInt.SetBytes(hash[:])//获取对比的数据

	isValid :=(hashInt.Cmp(pow.target))==-1//校验数据
	return isValid

}

挖矿的代码就在上面,代码有几点还是要注意一下,

/创建一个工作量证明的挖矿对象
func NewProofOfWork(block *Block)*ProofOfWork{
	target := big.NewInt(1);//初始化目标整数
	fmt.Printf("%x\n",target)
	target.Lsh(target,uint(256-targetBits)) //数据转换,小于这个数的都是符合条件的哈希值,二进制移位232位,16进制以为58位
	fmt.Printf("%x\n",target)
	pow:=&ProofOfWork{block,target} //创建一个对象
	return pow
}

这里的Lsh函数就是把它转成二进制右移,这里也就是把1右移256-24=232位,用十六进制表示也就是10000000000000000000000000000000000000000000000000000000000

一个1,58个0,

//准备数据,进行挖矿计算
func (pow * ProofOfWork) prepareData(nonce int)[]byte{
	data := bytes.Join(
		[][]byte{
			pow.block.PreBlockHash,//上一块哈希
			pow.block.Data,//当前数据
			IntToHex(pow.block.Timestamp),//十六进制
			IntToHex(int64(targetBits)),//位数,十六进制
			IntToHex(int64(nonce)),//保存工作量的证明
		},[]byte{},
	)
	return data
}

而这里准备出来的hash是64位的,因为我们的targetBits是24,24/8*2 我们需要前面是六个0,如果hash前面是六个0的话,那就是58位,肯定小于我们的10000000000000000000000000000000000000000000000000000000000,1加58个0,59位,

那么只要

for nonce<maxNonce{
		data :=pow.prepareData(nonce)//准备好的数据
		hash = sha256.Sum256(data)//计算出哈希
		fmt.Printf("\r%x",hash)//打印显示哈希

		hashInt.SetBytes(hash[:])//获取要对比的数据
		      //10000000000000000000000000000000000000000000000000000000000
		//000000ce7bdc24fb9b3ebc36939c21b3053013acc87dc890e673036e575c2a00
		if hashInt.Cmp(pow.target)==-1{ //挖矿的校验
			break
		}else {
			nonce++
		}
	}

挖矿得到的hash比我们规定的(前面六个0)小就满足条件。

运行结果

上一块哈希
数据:Genesis Block
当前00000068949aea1f1bf0f72c95825ab9b6541d62732adc9eaafd32d8d4d2bada
1
10000000000000000000000000000000000000000000000000000000000
pow true

上一块哈希00000068949aea1f1bf0f72c95825ab9b6541d62732adc9eaafd32d8d4d2bada
数据:黄帮景 pay 易达 10
当前0000009901a31020e00044e37ce87c869d42945921e4b90c667a1a66e291bd90
1
10000000000000000000000000000000000000000000000000000000000
pow true

上一块哈希0000009901a31020e00044e37ce87c869d42945921e4b90c667a1a66e291bd90
数据:黄帮景 pay 易达 20
当前0000007e73d075ab3a8f357972f93151b4e75600b3f3a51a3162cbbc74526948
1
10000000000000000000000000000000000000000000000000000000000
pow true

上一块哈希0000007e73d075ab3a8f357972f93151b4e75600b3f3a51a3162cbbc74526948
数据:黄帮景 pay 易达 30
当前0000007592a646880557c49cefeaa60ee0eba2b111fd4da2e3a5630d6ac4fd75
1
10000000000000000000000000000000000000000000000000000000000
pow true

参考

https://jeiwan.cc/posts/building-blockchain-in-go-part-2/

源码下载

https://github.com/Roninchen/golangBTC

发布了46 篇原创文章 · 获赞 24 · 访问量 7万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览