教你打造最简比特币—原型币之工作量证明

开发语言:Go语言

本教程是学习Jeiwan的博客后的学习笔记,代码实现也参考它的为主,精简了叙述并在适当位置添加了一些必备的小知识和适当的代码注释,如介绍哈希。

本教程是为了逐步教你设计一款简化的区块链原型币。通过我们不断添加功能,完成一个可交易的原型币。

本节增加基于工作量证明的区块产生机制

  1. 单机版,仅支持保存信息✅
  2. 工作量证明

工作量证明

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

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

工作量证明的设计要满足两点,即工作量和证明。工作量是完成工作需要耗时耗力;证明是验证工作能非常快捷。这两点设计要求都可以通过哈希计算的算法实现。

上一张我们提到哈希有三个特性,其中的谜题友好(puzzle-friendly)特性,保证了从哈希值倒退原信息只能通过遍历的土方法,而没有任何的其他的启发式算法捷径。这就保证了耗时耗力。而反之,验证倒推出来的信息和这个哈希值又是非常快速的。

下面是哈希计算的土方法

  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字符串。

字符串例子如下:

0fac49161af82ed938add1d8725835cc123a1a87b1b196488360e58d4bfb51e3
0000010000000000000000000000000000000000000000000000000000000000
0000008b0f41ec78bab747864db66bcb9fb89920ee75f43fdaaeb5544f7f76ca

构建工作量证明的代码如下

type ProofOfWork struct {
    Block  *Block
    Target *big.Int  //big是go语言里的精确的大数的标准库
}

func NewProofOfWork(b *Block) *ProofOfWork {
    target := big.NewInt(1)
    target.Lsh(target, uint(256-targetBits))   //小于这个数的都是符合条件的哈希值

    pow := &ProofOfWork{b, target}

    return pow
}

区块里需要增加一个nonce计数器,

type Block struct {
    Timestamp     int64
    Data          []byte
    PrevHash []byte
    Hash          []byte
    Nonce         int
}

现在,我们需要有数据来进行哈希,准备数据:

func (pow *ProofOfWork) prepareData(nonce int) []byte {
    data := bytes.Join(
        [][]byte{
            pow.block.PrevHash,
            pow.block.Data,
            IntToHex(pow.Block.Timestamp),   //这里该用IntToHex,没关系,只要还原回来一致即可
            IntToHex(int64(targetBits)),    //这里的[]byte,是int数字代表的从数组序号大的一端写到小的一端。
            IntToHex(int64(nonce)),  //
        },
        []byte{},
    )

    return data
}

// IntToHex converts an int64 to a byte array
func IntToHex(num int64) []byte {
   buff := new(bytes.Buffer)
   err := binary.Write(buff, binary.BigEndian, num)
   if err != nil {
      log.Panic(err)
   }

   return buff.Bytes()
}

现在就可以算法的实现:

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)
        hashInt.SetBytes(hash[:])

        if hashInt.Cmp(pow.target) == -1 {
            fmt.Printf("\r%x", hash)
            break
        } else {
            nonce++
        }
    }
    fmt.Print("\n\n")

    return nonce, hash[:]
}

成功后,我们就可以移除原来计算区块哈希的SetHash方法了,而且创建区块的NewBlock也需要引入工作量证明了。 修改如下

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
}

我们还要验证这个区块的哈希

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 main() {
   bc := datastruct.GenesisBlockchain()

   bc.AddBlock("Send 1 BTC to Lin")
   bc.AddBlock("Send 2 BTC to Lin")

   for _, block := range bc.Blocks {
      fmt.Printf("Prev. hash: %x\n", block.PrevHash)
      fmt.Printf("Data: %s\n", block.Data)
      fmt.Printf("Hash: %x\n", block.Hash)
      fmt.Println()

      pow := datastruct.NewProofOfWork(block)
      fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate()))
      fmt.Println()
   }
}

输出:

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

参考

Building Blockchain in Go. Part 2: Proof-of-Work

源码

https://github.com/linxinzhe/go-simple-coin/tree/2proofof_work

下一节:

3. 区块链原型币—持久化

全系列:

  1. 区块链原形币--工作量证明
  2. 区块链原型币—工作量证明
  3. 区块链原型币—持久化
  4. 未完待续

原文发布于微信公众号 - 林欣哲(gh_aba6caba3ac7)

原文发表时间:2018-03-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏逸鹏说道

大小端对齐,正码,反码,补码 ~ 附整数溢出的探讨

http://www.cnblogs.com/dotnetcrazy/p/8178175.html

11630
来自专栏aCloudDeveloper

算法导论第十九章 斐波那契堆

  《算法导论》第二版中在讨论斐波那契堆之前还讨论了二项堆,但是第三版中已经把这块的内容放到思考题中,究极原因我想大概是二项堆只是个引子,目的是为了引出斐波那契...

33280
来自专栏申龙斌的程序人生

310 BTC谜题的第二关详解

再来仔细看一下那张价值1400万的比特币谜图,里面有5条线段,另外还散乱地分布着几个字母和数字。

14220
来自专栏about云

区块链概念1:Hash 算法

问题导读 1.哈希算法在区块链的作用是什么? 2.什么是哈希算法? 3.哈希算法是否可逆? 4.比特币采用的是什么哈希算法? 作用 在学习哈希算法前,我们需...

55760
来自专栏申龙斌的程序人生

参加steemit数学x程式大赛(第八回)

前一段时间参加了Steemit社区的两个活动,比如“接龙”创作大赛,五个人根据几张图片素材编出一篇小说,事先没有任何沟通,人员报名之后,顺序是随机指定的,我第一...

31460
来自专栏逍遥剑客的游戏开发

Direct3D学习(六):动画基础(3)网格模型基础

14460
来自专栏数据结构与算法

P1474 货币系统 Money Systems

题目描述 母牛们不但创建了它们自己的政府而且选择了建立了自己的货币系统。由于它们特殊的思考方式,它们对货币的数值感到好奇。 传统地,一个货币系统是由1,5,10...

28360
来自专栏生信宝典

Python学习没有捷径,但可以加速,零基础九天你也可以会编程

在小学生都学Python了,你还不知道怎么开始文中介绍了Python的应用广泛,功能强大,提供了Python的在线学习视频和资料等 (收集资料是我们的最爱)。...

212100
来自专栏逍遥剑客的游戏开发

UE4中程序驱动的LookAt动画

43080
来自专栏SeanCheney的专栏

《利用Python进行数据分析·第2版》第14章 数据分析案例14.1 来自Bitly的USA.gov数据14.2 MovieLens 1M数据集14.3 1880-2010年间全美婴儿姓名14.4

本书正文的最后一章,我们来看一些真实世界的数据集。对于每个数据集,我们会用之前介绍的方法,从原始数据中提取有意义的内容。展示的方法适用于其它数据集,也包括你的。...

43150

扫码关注云+社区

领取腾讯云代金券