前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用Go构建区块链 第2部分:工作量证明

使用Go构建区块链 第2部分:工作量证明

作者头像
银河1号
发布2019-04-12 10:11:50
4640
发布2019-04-12 10:11:50
举报
文章被收录于专栏:银河系资讯银河系资讯

Introduction

在上一篇文章中,我们构建了一个非常简单的数据结构,这是区块链数据库的本质。我们可以通过它们之间的链状关系为它添加区块:每个区块都链接到前一个块。我们的区块链实现有一个重大缺陷:向链中添加区块很容易。区块链和比特币的核心之一是:添加新区块是一项艰苦的工作。今天我们要解决这个缺陷。

Proof-of-Work(工作量证明)

区块链的一个关键思想是,必须进行一些艰苦的工作才能将数据放入其中。正是这项艰苦的工作使区块链变得安全和一致。此外,这项艰苦的工作也得到了回报(这也就是通过挖矿获得币)。

这种机制与现实生活中的机制非常相似:人们必须努力工作,才能获得奖励并维持生命。在区块链中,网络的一些参与者(矿工)努力维持网络,向其添加新区块,并获得对其工作的奖励。由于他们的工作,区块以安全的方式被整合到区块链中,这保持了整个区块链数据库的稳定性。值得注意的是,完成工作的人必须证明这一点。

这整个“努力工作和证明”的机制被称为工作量证明。这很难,因为它需要大量的计算能力:即使是高性能的计算机也无法快速完成。另外,这个工作的困难度会随着时间不断增长,以保持每 10 分钟出 1 个新块的速度。在比特币中,这种工作的目标是找到一个块的哈希,满足一些要求。这个哈希,也就充当了证明的角色。因此,寻求证明(寻找有效哈希),就是矿工实际要做的事情。

最后要注意的一点是。工作量证明算法必须满足要求:完成工作很难,但验证证明很容易。证明通常会交给其他人,因此对他们而言,验证它不应该花费太多时间。

Hashing

在本段中,我们将讨论哈希。如果您熟悉这个概念,可以跳过这一部分。

哈希是获取指定数据的哈希的过程。哈希是计算数据的唯一表示。哈希函数是一种获取任意大小数据并生成固定大小哈希的函数。以下是哈希的一些主要功能:

1、无法从哈希中恢复原始数据。因此,哈希不是加密。

2、某些数据只能有一个哈希值,哈希值是唯一的。

3、改变输入数据中的一个字节将导致完全不同的哈希。

哈希函数广泛用于检查数据的一致性。除软件包外,某些软件提供商还会发布校验和。下载文件后,您可以将其提供给哈希函数,并将生成的哈希与软件开发人员提供的哈希进行比较。

在区块链中,哈希用于保证区块的一致性。哈希算法的输入数据包含前一个区块的哈希,因此无法(或者至少非常困难)修改链中的区块:必须重新计算其哈希和其后所有区块的哈希值。

Hashcash

比特币使用 Hashcash,一种最初开发用于防止电子邮件垃圾邮件的工作量证明算法。它可以分为以下几个步骤:

1、拿一些公开的数据(如果是电子邮件,它是接收者的电子邮件地址;对于比特币,它是块头)。

2、添加一个计数器。

3、计数器从0开始。将 data(数据) 和 counter(计数器) 组合到一起,获得一个哈希

4、检查哈希符合某些要求。如果确实如此,那就完成了。如果没有,请增加计数器并重复步骤3和4。

因此,这是一个暴力算法:你改变计数器,计算一个新的哈希,检查它,增加计数器,计算一个哈希等。这就是为什么它的计算成本很高。

现在让我们仔细看看哈希必须满足的要求。在最初的Hashcash实现中,它的要求是 “一个哈希的前 20 位必须是 0”。在比特币中,需求会不时调整,因为,尽管计算能力随着时间的推移而增加,并且越来越多的矿工加入网络,但必须保证每 10 分钟生成一个块。

为了演示这个算法,我从前面的例子中获取了数据(“我喜欢甜甜圈”)并找到了一个以3个零字节开头的哈希:

ca07ca是计数器的十六进制值,十进制系统中为13240266。

Implementation

好的,我们已经完成了理论,让我们编写代码!首先,让我们来定义挖掘的难度:

代码语言:javascript
复制
const targetBits = 24

在比特币中,当一个块被挖出来以后,“target bits” 代表了区块头里存储的难度,也就是开头有多少个 0。目前我们不会实现目标调整算法,因此我们可以将难度定义为全局常量。

24是一个任意数字,我们的目标是让一个目标在内存中占用少于256位。我们希望差异足够大,但不要太大,因为差异越大,找到合适的散列越困难。

代码语言:javascript
复制
type ProofOfWork struct {
	block  *Block
	target *big.Int
}

func NewProofOfWork(b *Block) *ProofOfWork {
	target := big.NewInt(1)
	target.Lsh(target, uint(256-targetBits))

	pow := &ProofOfWork{b, target}

	return pow
}

这里,我们构造了 ProofOfWork 结构,里面存储了指向一个块(block)和一个目标(target)的指针。这里的 “目标” ,也就是前一节中所描述的必要条件。这里使用了一个 大整数 ,我们会将哈希与目标进行比较:先把哈希转换成一个大整数,然后检测它是否小于目标。

在 NewProofOfWork 函数中,我们将 big.Int 初始化为 1,然后左移 256 - targetBits 位。256 是一个 SHA-256 哈希的位数,我们将要使用的是 SHA-256 哈希算法。target(目标) 的 16 进制形式为:

代码语言:javascript
复制
0x10000000000000000000000000000000000000000000000000000000000

它在内存中占用29个字节。这里是与前面例子中的哈希的视觉比较:

代码语言:javascript
复制
0fac49161af82ed938add1d8725835cc123a1a87b1b196488360e58d4bfb51e3
0000010000000000000000000000000000000000000000000000000000000000
0000008b0f41ec78bab747864db66bcb9fb89920ee75f43fdaaeb5544f7f76ca

第一个哈希值(根据“我喜欢甜甜圈”计算)比目标大,因此它不是有效的工作证明。第二个哈希(计算在“我喜欢donutsca07ca”)小于目标,因此它是一个有效的证据。

您可以将目标视为范围的上边界:如果数字(哈希)低于边界,则它是有效的,反之亦然。降低边界将导致有效数字减少,因此找到有效数字所需的工作更加困难。

现在,我们需要数据进行哈希处理。我们准备吧:

代码语言:javascript
复制
func (pow *ProofOfWork) prepareData(nonce int) []byte {
	data := bytes.Join(
		[][]byte{
			pow.block.PrevBlockHash,
			pow.block.Data,
			IntToHex(pow.block.Timestamp),
			IntToHex(int64(targetBits)),
			IntToHex(int64(nonce)),
		},
		[]byte{},
	)

	return data
}

这个部分很简单:我们只是将 target ,nonce 与 Block 进行合并。nonce这里是上面Hashcash描述的计数器,这是一个加密术语。

好的,所有准备工作都已完成,让我们实现PoW算法的核心:

代码语言:javascript
复制
func (pow *ProofOfWork) Run() (int, []byte) {
	var hashInt big.Int
	var hash [32]byte
	nonce := 0

	fmt.Printf("Mining the block containing \"%s\"\n", pow.block.Data)
	for nonce < maxNonce {
		data := pow.prepareData(nonce)
		hash = sha256.Sum256(data)
		fmt.Printf("\r%x", hash)
		hashInt.SetBytes(hash[:])

		if hashInt.Cmp(pow.target) == -1 {
			break
		} else {
			nonce++
		}
	}
	fmt.Print("\n\n")

	return nonce, hash[:]
}

首先,我们初始化变量:hashInt,它是整数表示hash; nonce是计数器。接下来,我们运行一个“无限”循环:它受限于maxNonce, 它等于 math.MaxInt64;这样做是为了避免nonce的溢出。虽然我们的PoW实现的难度太低而不能使计数器溢出,但为了以防万一,进行此检查仍然更好。

在这个循环中,我们做的事情有:

  1. 准备数据
  2. 用 SHA-256 对数据进行哈希
  3. 将哈希转换成一个大整数
  4. 将这个大整数与目标进行比较

跟之前所讲的一样简单。现在我们可以移除 BlockSetHash 方法,然后修改 NewBlock 函数:

代码语言:javascript
复制
func NewBlock(data string, prevBlockHash []byte) *Block {
	block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}, 0}
	pow := NewProofOfWork(block)
	nonce, hash := pow.Run()

	block.Hash = hash[:]
	block.Nonce = nonce

	return block
}

在这里,你可以看到 nonce 被保存为 Block 的一个属性。这是十分有必要的,因为待会儿我们对这个工作量进行验证时会用到 nonceBlock 结构现在看起来像是这样:

代码语言:javascript
复制
type Block struct {
	Timestamp     int64
	Data          []byte
	PrevBlockHash []byte
	Hash          []byte
	Nonce         int
}

好的!让我们运行程序,看看一切是否正常:

代码语言:javascript
复制
Mining the block containing "Genesis Block"
00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1

Mining the block containing "Send 1 BTC to Ivan"
00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804

Mining the block containing "Send 2 more BTC to Ivan"
000000b33185e927c9a989cc7d5aaaed739c56dad9fd9361dea558b9bfaf5fbe

Prev. hash:
Data: Genesis Block
Hash: 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1

Prev. hash: 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1
Data: Send 1 BTC to Ivan
Hash: 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804

Prev. hash: 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804
Data: Send 2 more BTC to Ivan
Hash: 000000b33185e927c9a989cc7d5aaaed739c56dad9fd9361dea558b9bfaf5fbe

好极了!您可以看到每个哈希现在以三个零字节开始,并且需要一些时间来获取这些哈希值。还有一件事要做:对工作量证明进行验证。

代码语言:javascript
复制
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
}

这就是我们需要保存的随机数的地方。让我们再检查一切都没问题:

代码语言:javascript
复制
func main() {
	...

	for _, block := range bc.blocks {
		...
		pow := NewProofOfWork(block)
		fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate()))
		fmt.Println()
	}
}

Output:

代码语言:javascript
复制
...

Prev. hash:
Data: Genesis Block
Hash: 00000093253acb814afb942e652a84a8f245069a67b5eaa709df8ac612075038
PoW: true

Prev. hash: 00000093253acb814afb942e652a84a8f245069a67b5eaa709df8ac612075038
Data: Send 1 BTC to Ivan
Hash: 0000003eeb3743ee42020e4a15262fd110a72823d804ce8e49643b5fd9d1062b
PoW: true

Prev. hash: 0000003eeb3743ee42020e4a15262fd110a72823d804ce8e49643b5fd9d1062b
Data: Send 2 more BTC to Ivan
Hash: 000000e42afddf57a3daa11b43b2e0923f23e894f96d1f24bfd9b8d2d494c57a
PoW: true

Conclusion

我们的区块链更接近其实际架构:现在添加区块需要努力工作,因此可以进行挖掘。但它仍然缺乏一些关键特征:区块链数据库不是持久的,没有钱包,地址,交易,也没有共识机制。所有这些我们将在未来的文章中实现,现在,快乐的采矿吧!

(未经同意,请勿转载)

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-02-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 银河系1号 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Introduction
  • Proof-of-Work(工作量证明)
  • Hashing
  • Hashcash
  • Implementation
  • Conclusion
相关产品与服务
区块链
云链聚未来,协同无边界。腾讯云区块链作为中国领先的区块链服务平台和技术提供商,致力于构建技术、数据、价值、产业互联互通的区块链基础设施,引领区块链底层技术及行业应用创新,助力传统产业转型升级,推动实体经济与数字经济深度融合。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档