美文网首页区块链区块链研习社区块链研究
2.教你打造最简比特币之工作量证明

2.教你打造最简比特币之工作量证明

作者: 林檎果 | 来源:发表于2018-03-08 11:45 被阅读52次
    开发语言: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/2_proof_of_work

    下一节:

    1. 教你打造最简比特币之持久化

    全系列:

    1. 教你打造最简比特币之基本原型
    2. 教你打造最简比特币之工作量证明
    3. 教你打造最简比特币之持久化
    4. 教你打造最简比特币之交易
    5. 未完待续

    关于我:

    linxinzhe,全栈工程师,目前供职于某500强通信企业,人工智能,区块链爱好者。

    GitHub:https://github.com/linxinzhe

    欢迎留言讨论,也欢迎关注我,收获更多区块链开发相关的知识,我也会关注你的哦!

    相关文章

      网友评论

        本文标题:2.教你打造最简比特币之工作量证明

        本文链接:https://www.haomeiwen.com/subject/pjmsfftx.html