帶你玩轉區塊鏈--基於GoLang創建區塊鏈、重構BTC-第一章【比特幣篇】

一、意義:

ps:基於bolt的v3版本已上傳github,點擊鏈接下載源碼:

https://github.com/lsy-zhaoshuaiji/boltBlockChain.git

V4、V5版皆已上傳,點擊鏈接下載源碼(請注意分支):

https://github.com/lsy-zhaoshuaiji/pow.git

區塊鏈是什麼?(個人理解非百度..)

       區塊鏈,就是一個分佈式的超級賬本。是比特幣的底層技術。在區塊鏈中每一個節點都是數據校驗者和守護者,數據的真實性,由所有節點共同守護,一旦出現了某個節點作弊的行爲,就會被踢出節點。而新數據驗證存儲的過程就叫做工作量證明(pow),也就是挖礦。說到分佈式,我們會想起以前比較火的p2p平臺p2p也是分佈式,但缺少校驗機制,所以是缺乏第三方監管和擔保的點對點交易平臺,所以容易發生跑路現象。依賴中心數據庫來處理信息的平臺往往在安全性方面存在弊端。區塊鏈上的p2p因爲全網無特殊節點,每個節點都可以提供全網所需的全部服務,任何一個節點垮掉,都不會對整個網絡的穩定性構成威脅,所以是非常安全的。


區塊的構成?

區塊的構成分爲區塊頭和區塊體,區塊頭中記錄了版本號、上一個區塊的Hash地址、merkle根、區塊創建時間戳、區塊的工作量難度目標以及用於計算目標的參數值。區塊體中記錄了該區塊存儲的交易數量以及交易數據。

區塊頭:

字段  大小   描述
version 4字節 版本號,⽤於跟蹤軟件/協議的更新
prevBlockHash  32字節 上一個區塊的Hash地址
merkleRoot 32字節 該區塊中交易的merkl e樹根的哈希值
time  4字節 該區塊的創建時間戳
difficultyTarget 4字節 該區塊鏈工作量證明難度目標(稍後講解工作量證明)
nonce  4字節  用於證明工作量的計算參數(挖礦要求出的值)

 區塊體:

字段 大小 描述
numTransactionsBytes 1字節 交易數量佔用的字節數
numTransactions 0-8個字節 區塊內存儲的交易數量
transactions 不確定 區塊內存的多個交易數據

 

 二、最簡單的區塊鏈的創建:

    通過上述描述,我們知道了區塊鏈是由區塊頭加區塊體組成,區塊體存放了各種其他數據,我們暫不做考慮。我們先從創建一個區塊頭開始學習。

1.首先我們定義一個結構體並起名爲block,裏面存放區塊頭字段,爲了簡單起見,字段中只包含前哈希,當前哈希和某些字符串數據(可以理解爲區塊鏈中的轉賬數據)。

2.我們創建一個newblock方法,此方法的作用是爲block結構體賦值,並返回區塊地址。

3.創建一個setHash方法,模擬挖礦生成當前哈希。

4.定義區塊鏈結構體,blockChain,存放區塊地址。

5.創建一個genesisBlock方法,此方法的目的是創建一個創世塊(第一個區塊),

5.創建一個newBlockChain方法,此方法的目的是將創世塊地址放入結構體變量blockChain.blockchain中,初始化區塊鏈。

6.創建一個AddBlockChain方法,此方法的目的是將新增區塊的地址,存放在blockChain.blockchain中,將新區塊連接到區塊鏈中。

ps:在區塊鏈中區塊生成當前哈希的過程就是pow(工作量證明/挖礦),爲了簡單起見,我們將pow放到後面詳細講,此部分我們利用前哈希和data拼接後的數據做sha256得到當前哈希

 程序分爲三個文件,分別是負責區塊的block.go文件,負責區塊鏈的blockchain模塊,負責程序運行的main.go文件

main.go:

package main

import (
	"fmt"
)
/*
區塊的創建分爲7步
1.定義結構體
2.創建區塊
3.生成哈希(pow/工作量證明/)
4.定義區塊鏈結構體
5.生成區塊鏈並添加創世
6.生成創世塊塊
7.添加其他區塊
*/


func main(){
	BC:=NewBlockChain()
	BC.AddBlockChain("A向laughing轉賬580個BTC")
	for i,block:=range BC.blockChain{
		fmt.Printf("當前區塊高度:%d\n",i)
		fmt.Printf("當前哈希:%x\n",block.Hash)
		fmt.Printf("上一級哈希:%x\n",block.prevHash)
		fmt.Printf("交易信息:%s\n",block.data)
	}
}

block .go

package main

import "crypto/sha256"
//1.定義區塊結構體

type Block struct {
	prevHash []byte
	Hash []byte
	data []byte
}
//2.創建區塊
func NewBlock(data string,prevHash []byte) *Block{
	block:=Block{
		prevHash:prevHash,
		Hash:[]byte{},
		data:[]byte(data),
	}
	block.SetHash()
	return &block
}
//3.生成哈希
func (this *Block)SetHash (){
	blockInfo:=append(this.prevHash,this.data...)
	//func Sum256(data []byte) [Size]byte {
	Hash:=sha256.Sum256(blockInfo)
	this.Hash=Hash[:]
}

blockChain.go

package main
//4.定義區塊鏈結構體
type BlockChain struct {
	blockChain []*Block
}
//5.生成創世塊
func GenesisBlock()*Block{
	return NewBlock("laughing 轉賬給 fance 1BTC",[]byte{})
}
//6.生成區塊鏈
func NewBlockChain()*BlockChain{
	genesisBlock:=GenesisBlock()
	return &BlockChain{
		blockChain:[]*Block{genesisBlock},
	}
}
//添加其他區塊
func (this *BlockChain)AddBlockChain(data string){
	//1.找到上一個區塊,並獲得其哈希
	lastBlock:=this.blockChain[len(this.blockChain)-1]
	prevHash:=lastBlock.Hash
	//2.生成新的區塊
	block:=NewBlock(data,prevHash)
	//3.將新的區塊添加到區塊鏈中
	this.blockChain=append(this.blockChain,block)
}

三、完整字段的區塊鏈的創建:

          以上代碼,區塊頭中字段過於簡單。而在真實的區塊頭中,是存在很多字段的。所以現在我們將區塊頭完整字段加入到代碼中。修改如下:

1.修改了區塊頭block結構體,新增了version等區塊字段。

2.修改了哈希生成函數,利用bytes.join()方法,將區塊頭字段拼接起來。

ps:鏈上(blockchain)的代碼是沒有進行修改的。完整字段包含版本號、前哈希,markleRoot、時間戳、難度、目標值、當前哈希和某些字符串數據。 代碼如下:

package main

import (
	"bytes"
	"crypto/sha256"
	"encoding/binary"
	"fmt"
	"time"
)
//1.定義區塊結構體

type Block struct {
	//這裏進行了修改,新增了變量
	version uint64
	prevHash []byte
	markleRoot []byte
	timeStamp uint64
	diffculty uint64
	nonce uint64
	Hash []byte
	data []byte
}
//2.創建區塊
func NewBlock(data string,prevHash []byte) *Block{
	block:=Block{
		version:00,
		prevHash:prevHash,
		markleRoot:[]byte{},
		timeStamp:uint64(time.Now().Unix()),
		diffculty:0,
		nonce:0,
		Hash:[]byte{},
		data:[]byte(data),
	}
	block.SetHash()
	return &block
}
func IntToByte(n uint64)[]byte{
	x:=int64(n)
	bytesBuffer:=bytes.NewBuffer([]byte{})
	err:=binary.Write(bytesBuffer,binary.BigEndian,x)
	if err!=nil{
		fmt.Println(err)
		return []byte{}
	}
	return bytesBuffer.Bytes()
}
//3.生成哈希
func (this *Block)SetHash (){
	byteList:=[][]byte{
		IntToByte(this.version),
		this.prevHash,
		this.markleRoot,
		IntToByte(this.timeStamp),
		IntToByte(this.diffculty),
		IntToByte(this.nonce),
		this.Hash,
		this.data,
	}
	//利用bytes.join()方法將[]byte拼接起來,不會的同學請自行百度
	blockInfo:=bytes.Join(byteList,[]byte{})
	Hash:=sha256.Sum256(blockInfo)
	this.Hash=Hash[:]
}

四、實現pow(工作量證明/挖礦):

        通過上述的描述,相信大家也意識到了pow其實就是生成當前哈希的過程。我們將pow有關的信息存放到proofofwork.go文件中。文件中定義了pow的結構體,有一個創建pow的方法,和一個挖礦(工作量證明)的run方法。現在我們詳細分析pow的整個過程。如下:

 1.創建pow結構體,並在結構體中定義變量--“挖礦難度值”(diffculty),由於挖礦競爭很大,int存儲可能不夠,所以我們使用big.int型作爲diffculty的基礎類型。

 2.創建run方法,實現當前區塊的數據拼接(包括版本號、前哈希、難度值、一個隨機數(nonce)等....)

 3.將拼接後的數據做哈希sha256,得到一個哈希值

4.將此哈希值轉化爲big.int類型

5.將轉換後的數據與diffculty進行比較,

   5.1 若小於diffculty則,代表挖礦成功,繼而將此區塊添加進區塊鏈中,並返回隨機數nonce。

   5.2若大於diffculty則表達,挖礦失敗,nonce++,繼續循環,直到滿足5.1條件。如圖:

 實現代碼如下:

1.首先,我們創建一個proofofwork.go(pow.go)文件:

package main

import (
	"bytes"
	"crypto/sha256"
	"fmt"
	"math/big"
)

type ProofOfWork struct {
	block *Block
	target *big.Int
}
func NewProofOfWork(block *Block)*ProofOfWork{
	pow:=ProofOfWork{
		block:block,
	}
	//挖礦難度值
	diffculty:="0000100000000000000000000000000000000000000000000000000000000000"
	tmp:=big.Int{}
	tmp.SetString(diffculty,16)
	pow.target=&tmp
	pow.block.diffculty=pow.target.Uint64()
	return &pow
}
func (this *ProofOfWork)run()([]byte,uint64){
	var nonce uint64
	var hash [32]byte
	for {
		//1.拼接區塊數據
		byteList:=[][]byte{
			IntToByte(this.block.version),
			this.block.prevHash,
			this.block.markleRoot,
			IntToByte(this.block.timeStamp),
			IntToByte(this.block.diffculty),
			IntToByte(nonce),
			this.block.data,
		}
		blockinfo:=bytes.Join(byteList,[]byte{})
		//2.將拼接後的數據進行哈希256運算
		hash=sha256.Sum256(blockinfo)
		//3.將哈希數據轉爲big.int類型
		tmp:=big.Int{}
		tmp.SetBytes(hash[:])
		//4.比較
		status:=tmp.Cmp(this.target)
		if status==-1{
			fmt.Printf("挖礦成功,難度爲:%d,   當前哈希爲:%x,  nonce爲:%d\n",this.block.diffculty,hash,nonce)
			break;
		}else {
			//fmt.Println(nonce)
			nonce++
		}
	}
	return hash[:],nonce
}

2.修改block.go文件,將原生成哈希的方法修改爲pow的方式:

package main

import (
	"bytes"
	"crypto/sha256"
	"encoding/binary"
	"fmt"
	"time"
)
//1.定義區塊結構體

type Block struct {
	version uint64
	prevHash []byte
	markleRoot []byte
	timeStamp uint64
	diffculty uint64
	nonce uint64
	Hash []byte
	data []byte
}
//2.創建區塊
func NewBlock(data string,prevHash []byte) *Block{
	block:=Block{
		version:00,
		prevHash:prevHash,
		markleRoot:[]byte{},
		timeStamp:uint64(time.Now().Unix()),
		diffculty:0,
		nonce:0,
		Hash:[]byte{},
		data:[]byte(data),
	}
	//這裏註釋了原生成哈希的方法,並通過NewProofOfWork方法創建了pow結構體,最後再調用pow的run方法,開啓挖礦,生成hash
	//block.SetHash()
	pow:=NewProofOfWork(&block)
	hash,nonce:=pow.run()
	block.Hash=hash
	block.nonce=nonce
	return &block
}
func IntToByte(n uint64)[]byte{
	x:=int64(n)
	bytesBuffer:=bytes.NewBuffer([]byte{})
	err:=binary.Write(bytesBuffer,binary.BigEndian,x)
	if err!=nil{
		fmt.Println(err)
		return []byte{}
	}
	return bytesBuffer.Bytes()
}
//3.生成哈希
func (this *Block)SetHash (){
	byteList:=[][]byte{
		IntToByte(this.version),
		this.prevHash,
		this.markleRoot,
		IntToByte(this.timeStamp),
		IntToByte(this.diffculty),
		IntToByte(this.nonce),
		this.Hash,
		this.data,
	}
	//利用bytes.join()方法將[]byte拼接起來,不會的同學請自行百度
	blockInfo:=bytes.Join(byteList,[]byte{})
	Hash:=sha256.Sum256(blockInfo)
	this.Hash=Hash[:]
}

blockchain和main文件爲進行修改,可以繼續使用。

五、基於Bolt數據庫實現可持續化的區塊鏈

           上文中,我們已經可以實現一個最基本的區塊鏈結構了。但是我們在運行過程中發現,目前的程序數據都存在內存中,換句話來說就是,程序一關閉數據就不見了,所以我們需要引入數據庫。這時輕量級數據庫Bolt就映入眼簾了

什麼是Bolt數據庫呢?

Bolt是一個用Go編寫的鍵值數據庫。其目標是爲了給程序提供一個簡單、快捷、穩定的數據庫。

安裝:

go get github.com/boltdb/bolt/...

基於BOLT的讀寫、

func main() {
	//打開數據庫

	// func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
	db, err := bolt.Open("testDb.db", 0600, nil)//第二個參數爲權限
	
	if err != nil {
		fmt.Println(" bolt Open err :", err)
		return
	}

	defer db.Close()

	//創建bucket
    //Updata參數爲一個函數類型,是一個事務
	err = db.Update(func(tx *bolt.Tx) error {
		//打開一個bucket
		b1 := tx.Bucket([]byte("bucket1"))

		//沒有這個bucket
		if b1 == nil {
			//創建一個bucket
			b1, err = tx.CreateBucket([]byte("bucket1"))
			if err != nil {
				fmt.Printf("tx.CreateBucket err:", err)
				return err
			}

			//寫入數據
			b1.Put([]byte("key1"), []byte("hello"))
			b1.Put([]byte("key2"), []byte("world"))

			//讀取數據
			v1 := b1.Get([]byte("key1"))
			v2 := b1.Get([]byte("key2"))
			v3 := b1.Get([]byte("key3"))

			fmt.Printf("v1:%s\n", string(v1))
			fmt.Printf("v2:%s\n", string(v2))
			fmt.Printf("v3:%s\n", string(v3))
		}
		return nil
	})

	if err != nil {
		fmt.Printf("db.Update err:", err)
	}

	return
}

只讀:

func main() {
	//打開數據庫

	// func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
	db, err := bolt.Open("testDb.db", 0400, nil)//第二個參數爲權限
	
	if err != nil {
		fmt.Println(" bolt Open err :", err)
		return
	}

	defer db.Close()

	//創建bucket
    //View參數爲一個函數類型,是一個事務
	err = db.View(func(tx *bolt.Tx) error {
		//打開一個bucket
		b1 := tx.Bucket([]byte("bucket1"))

		//沒有這個bucket
		if b1 == nil {
			return errors.New("bucket do not exist!")
		}
       		v1 := b1.Get([]byte("key1"))
			v2 := b1.Get([]byte("key2"))
			v3 := b1.Get([]byte("key3"))

			fmt.Printf("v1:%s\n", string(v1))
			fmt.Printf("v2:%s\n", string(v2))
			fmt.Printf("v3:%s\n", string(v3))
        
		return nil
	})

	if err != nil {
		fmt.Printf("db.View err:", err)
	}

	return
}

 

特別注意--血坑,博主在因爲此原因排查了一下午(~~):

切記結構體中的變量要大寫:!!!

 結構體中參數首字母沒有大寫時,別的包雖然可以調用這個結構體,但是找不到這個結構體中沒有首字母大寫的參數。

附上block.go中修改結構體變量後的代碼:

package main

import (
	"bytes"
	"encoding/binary"
	"encoding/gob"
	"fmt"
	"log"
	"time"
)
//1.定義區塊結構體

type Block struct {
	//1.版本號
	Version uint64
	//2. 前區塊哈希
	PrevHash []byte
	//3. Merkel根(梅克爾根,這就是一個哈希值,我們先不管,我們後面v4再介紹)
	MerkelRoot []byte
	//4. 時間戳
	TimeStamp uint64
	//5. 難度值
	Difficulty uint64
	//6. 隨機數,也就是挖礦要找的數據
	Nonce uint64

	//a. 當前區塊哈希,正常比特幣區塊中沒有當前區塊的哈希,我們爲了是方便做了簡化!
	Hash []byte
	//b. 數據
	Data []byte
}
//2.創建區塊
func NewBlock(data string, prevBlockHash []byte) *Block {
	block := Block{
		Version:    00,
		PrevHash:   prevBlockHash,
		MerkelRoot: []byte{},
		TimeStamp:  uint64(time.Now().Unix()),
		Difficulty: 0, //隨便填寫的無效值
		Nonce:      0, //同上
		Hash:       []byte{},
		Data:       []byte(data),
	}

	//block.SetHash()
	//創建一個pow對象
	pow := NewProofOfWork(&block)
	//查找隨機數,不停的進行哈希運算
	hash, nonce := pow.run()

	//根據挖礦結果對區塊數據進行更新(補充)
	block.Hash = hash
	block.Nonce = nonce

	return &block
}

func IntToByte(n uint64)[]byte{
	x:=int64(n)
	bytesBuffer:=bytes.NewBuffer([]byte{})
	err:=binary.Write(bytesBuffer,binary.BigEndian,x)
	if err!=nil{
		fmt.Println(err)
		return []byte{}
	}
	return bytesBuffer.Bytes()
}
//3.生成哈希,目前不再使用此方法,而是使用pow
//序列化
func (block *Block) Serialize() []byte {
	var buffer bytes.Buffer

	//- 使用gob進行序列化(編碼)得到字節流
	//1. 定義一個編碼器
	//2. 使用編碼器進行編碼
	encoder := gob.NewEncoder(&buffer)
	err := encoder.Encode(&block)
	if err != nil {
		log.Panic("編碼出錯!")
	}

	//fmt.Printf("編碼後的小明:%v\n", buffer.Bytes())

	return buffer.Bytes()
}

//反序列化
func Deserialize(data []byte) Block {

	decoder := gob.NewDecoder(bytes.NewReader(data))

	var block Block
	//2. 使用解碼器進行解碼
	err := decoder.Decode(&block)
	if err != nil {
		log.Panic("解碼出錯!")
	}
	return block
}

基於Bolt數據庫實現可持續化的區塊鏈

1.重構blockChain結構體,結構體變量爲:1. bolt數據庫對象db,2.存放最後一塊區塊的哈希tail。

type BlockChain struct {
	db *bolt.DB
	tail []byte
}

2.在block.go中實現能將結構體block系列化爲字節的Serialize方法,和將字節反序列化爲結構體的Deserialize方法。

//序列化
func (block *Block) Serialize() []byte {
	var buffer bytes.Buffer

	//- 使用gob進行序列化(編碼)得到字節流
	//1. 定義一個編碼器
	//2. 使用編碼器進行編碼
	encoder := gob.NewEncoder(&buffer)
	err := encoder.Encode(&block)
	if err != nil {
		log.Panic("編碼出錯!")
	}

	//fmt.Printf("編碼後的小明:%v\n", buffer.Bytes())

	return buffer.Bytes()
}

//反序列化
func Deserialize(data []byte) Block {

	decoder := gob.NewDecoder(bytes.NewReader(data))

	var block Block
	//2. 使用解碼器進行解碼
	err := decoder.Decode(&block)
	if err != nil {
		log.Panic("解碼出錯!")
	}
	return block
}

2.重構newBlockChain方法,將創世塊的哈希和序列化後的結構體,存放到boltDB中。並返回db對象,和最後一塊區塊哈希。

const blockChainDb = "blockChain.db"
const blockBucket = "blockBucket"

//5. 定義一個區塊鏈
func NewBlockChain() *BlockChain {
	//return &BlockChain{
	//	blocks: []*Block{genesisBlock},
	//}

	//最後一個區塊的哈希, 從數據庫中讀出來的
	var lastHash []byte

	//1. 打開數據庫
	db, err := bolt.Open(blockChainDb, 0600, nil)
	//defer db.Close()

	if err != nil {
		log.Panic("打開數據庫失敗!")
	}

	//將要操作數據庫(改寫)
	_=db.Update(func(tx *bolt.Tx) error {
		//2. 找到抽屜bucket(如果沒有,就創建)
		bucket := tx.Bucket([]byte(blockBucket))
		if bucket == nil {
			//沒有抽屜,我們需要創建
			bucket, err = tx.CreateBucket([]byte(blockBucket))
			if err != nil {
				log.Panic("創建bucket(b1)失敗")
			}

			//創建一個創世塊,並作爲第一個區塊添加到區塊鏈中
			genesisBlock := GenesisBlock()

			//3. 寫數據
			//hash作爲key, block的字節流作爲value,尚未實現
			_=bucket.Put(genesisBlock.Hash, genesisBlock.Serialize())
			_=bucket.Put([]byte("LastHashKey"), genesisBlock.Hash)
			lastHash = genesisBlock.Hash

			////這是爲了讀數據測試,馬上刪掉,套路!
			//blockBytes := bucket.Get(genesisBlock.Hash)
			//block := Deserialize(blockBytes)
			//fmt.Printf("block info : %s\n", block)

		} else {
			lastHash = bucket.Get([]byte("LastHashKey"))
		}

		return nil
	})

	return &BlockChain{db, lastHash}
}

3.重構blockChain結構體的AddBlock方法,獲取lastHash,然後在函數中執行Newblock方法,並將該block的hash和信息序列化到boltDB中。

func (bc *BlockChain) AddBlock(data string) {
	//如何獲取前區塊的哈希呢??
	db := bc.db //區塊鏈數據庫
	lastHash := bc.tail //最後一個區塊的哈希

	_=db.Update(func(tx *bolt.Tx) error {

		//完成數據添加
		bucket := tx.Bucket([]byte(blockBucket))
		if bucket == nil {
			log.Panic("bucket 不應該爲空,請檢查!")
		}


		//a. 創建新的區塊
		block := NewBlock(data, lastHash)

		//b. 添加到區塊鏈db中
		//hash作爲key, block的字節流作爲value,尚未實現
		_=bucket.Put(block.Hash, block.Serialize())
		_=bucket.Put([]byte("LastHashKey"), block.Hash)

		//c. 更新一下內存中的區塊鏈,指的是把最後的小尾巴tail更新一下
		bc.tail = block.Hash

		return nil
	})
}

4.定義迭代器,方便後續遍歷。

package main

import (
	"github.com/boltdb/bolt"
	"log"
)

type BlockChainIterator struct {
	db *bolt.DB
	//遊標,用於不斷索引
	currentHashPointer []byte
}

//func NewIterator(bc *BlockChain)  {
//
//}

func (bc *BlockChain) NewIterator() *BlockChainIterator {
	return &BlockChainIterator{
		bc.db,
		//最初指向區塊鏈的最後一個區塊,隨着Next的調用,不斷變化
		bc.tail,
	}
}

//迭代器是屬於區塊鏈的
//Next方式是屬於迭代器的
//1. 返回當前的區塊
//2. 指針前移
func (it *BlockChainIterator) Next() *Block {
	var block Block
	_=it.db.View(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(blockBucket))
		if bucket == nil {
			log.Panic("迭代器遍歷時bucket不應該爲空,請檢查!")
		}

		blockTmp := bucket.Get(it.currentHashPointer)
		//解碼動作
		block = Deserialize(blockTmp)
		//遊標哈希左移
		it.currentHashPointer = block.PrevHash

		return nil
	})

	return &block
}
func (it *BlockChainIterator)Restore(){
	//用於將迭代器遊標移回初始值位置
	_=it.db.View(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(blockBucket))
		if bucket == nil {
			log.Panic("迭代器遍歷時bucket不應該爲空,請檢查!")
		}

		blockTmp := bucket.Get([]byte("LastHashKey"))
		//遊標哈希左移
		it.currentHashPointer = blockTmp

		return nil
	})
}

5.新建cli.go文件用於接收外部參數,執行命令。

package main

import (
	"flag"
	"fmt"
	"os"
)

const Usage  = `
	AddBlock --data  	"add block to blockChain" 	example:./block AddBlock {DATA}
	PrintBlockChain     "print all blockChain data"
`
const AddBlockString  ="AddBlock"
const PrintBlockString  = "PrintBlockChain"
type Cli struct {
	Bc *BlockChain
}
func PrintUsage (){
	println(Usage)
}
func (cli *Cli)CheckInputLenth(){
	if len(os.Args)<2{
		fmt.Println("Invalid ARGS")
		PrintUsage()
		os.Exit(1)
	}
}
func (this *Cli)Run(){
	this.CheckInputLenth()
	AddBlocker:=flag.NewFlagSet(AddBlockString,flag.ExitOnError)
	PrintBlockChainer:=flag.NewFlagSet(PrintBlockString,flag.ExitOnError)
	AddBlockerParam:=AddBlocker.String("data","","AddBlock {data}")
	switch os.Args[1] {
	case AddBlockString:
		//AddBlock
		err:=AddBlocker.Parse(os.Args[2:])
		if err!=nil{fmt.Println(err)}
		if AddBlocker.Parsed(){
			if *AddBlockerParam==""{PrintUsage()}else {
				this.AddBlock(*AddBlockerParam)
			}
		}
	case PrintBlockString:
		//PrintBlockChain
		err:=PrintBlockChainer.Parse(os.Args[2:])
		if err!=nil{fmt.Println(err)}
		if PrintBlockChainer.Parsed(){
			this.PrintBlockChain()
		}
	default:
		fmt.Println("Invalid input ")
		PrintUsage()
	}
}

6.新建command.go文件,用於實現cli的方法。

package main

import "fmt"

func (this *Cli)AddBlock(data string){
	this.Bc.AddBlock(data)
}
func (this *Cli)PrintBlockChain(){
	//TODO
	Iterator:=this.Bc.NewIterator()
	HeightblockChain:=0
	for{
		HeightblockChain++
		block:=Iterator.Next()
		if len(block.PrevHash)==0{
			Iterator.Restore()
			break
		}

	}

	for{
		block:=Iterator.Next()
		fmt.Printf("=======當前區塊高度:%d======\n",HeightblockChain)
		fmt.Printf("當前哈希:%x\n",block.Hash)
		fmt.Printf("上一級哈希:%x\n",block.PrevHash)
		fmt.Printf("交易信息:%s\n",block.Data)
		HeightblockChain--
		if len(block.PrevHash)==0{
			break
		}
	}
}

7.修改main函數,

package main

/*
區塊的創建分爲7步
1.定義結構體
2.創建區塊
3.生成哈希
4.定義區塊鏈結構體
5.生成區塊鏈並添加創世
6.生成創世塊塊
7.添加其他區塊
*/

func main(){
	bc:=NewBlockChain()
	cli:=Cli{bc}
	cli.Run()
}

 

六、基於UTXO實現區塊鏈交易(V版本)

UTXO(Unspent Transaction Outputs)是未花費的交易輸出,它是比特幣交易生成及驗證的一個核心概念。交易構成了一組鏈式結構,所有合法的比特幣交易都可以追溯到前向一個或多個交易的輸出,這些鏈條的源頭都是挖礦獎勵,末尾則是當前未花費的交易輸出。

一、新建Trancastions.go文件,並定義結構體

type Transaction struct {
	TXID []byte           //交易ID
	TXinputs []TXinput   //交易輸入數組
	TXoutputs []TXoutput //交易輸出數組
}
type TXinput struct {
	TXid []byte//引用的交易ID
	index int64//引用的output索引值
	Sig string//解鎖腳本
}
type TXoutput struct {
	value float64//轉賬金額
	PubKeyHash string//鎖定腳本
}
//設置交易id

二、序列化Transcation結構體,並設置TXID

func (tx *Transaction)SetHash(){
	var buffer bytes.Buffer
	encoder:=gob.NewEncoder(&buffer)
	err:=encoder.Encode(tx)
	if err!=nil {
		fmt.Println(err)
	}
	hash:=sha256.Sum256(buffer.Bytes())
	tx.TXID=hash[:]
}

三、提供創建Transcation的挖礦方法

func NewCoinBaseTX(address string,data string) *Transaction{
	//1.挖礦交易只有一個Input和一個output
	//2.在input時,TXid爲空,index爲-1,解鎖腳本爲:礦池地址
	input:=TXinput{[]byte{},-1,address}
	//3.在output中,金額爲btc常量,reward{12.5},鎖定腳本爲address
	output:=TXoutput{reward,address}
	//4.創建Transcation交易,並設置TXid
	tx:=Transaction{[]byte{},[]TXinput{input},[]TXoutput{output}}
	//通過SetHash方法創建交易ID
	tx.SetHash()
	return &tx
}


四、修改block.go和blockChain.go(略,報錯的文件都需要修改,可以下載源碼等比)

五、修改cli.go新建getbalance命令

const AddBlockString  ="addblock"
const PrintBlockString  = "print"
const GetBlanceString  = "getbalance"
type Cli struct {
	Bc *BlockChain
}
func PrintUsage (){
	println(Usage)
}
func (cli *Cli)CheckInputLenth(){
	if len(os.Args)<2{
		fmt.Println("Invalid ARGS")
		PrintUsage()
		os.Exit(1)
	}
}
func (this *Cli)Run(){
	this.CheckInputLenth()
	AddBlocker:=flag.NewFlagSet(AddBlockString,flag.ExitOnError)
	PrintBlockChainer:=flag.NewFlagSet(PrintBlockString,flag.ExitOnError)
	getBalancer:=flag.NewFlagSet(GetBlanceString,flag.ExitOnError)
	AddBlockerParam:=AddBlocker.String("data","","AddBlock {data}")
	getBalancerParam:=getBalancer.String("address","","打印餘額")
	switch os.Args[1] {
	case AddBlockString:
		//AddBlock
		err:=AddBlocker.Parse(os.Args[2:])
		if err!=nil{fmt.Println(err)}
		if AddBlocker.Parsed(){
			if *AddBlockerParam==""{PrintUsage()}else {
				this.AddBlock(*AddBlockerParam)
			}
		}
	case GetBlanceString:
		err:=getBalancer.Parse(os.Args[2:])
		if err!=nil{
			PrintUsage()
			log.Panic(err)
		}
		if getBalancer.Parsed(){
			if *getBalancerParam==""{fmt.Println(PrintUsage)}else {
				this.getBalance(*getBalancerParam)
			}
		}

六、修改command.go

func (this *Cli)getBalance(address string){
	utxos:=this.Bc.FindUTXOs(address)
	total:=0.0
	for _,utxo:=range utxos{
		total+=utxo.value
	}
	fmt.Printf("/%s/的餘額爲:%f\n",address,total)
}

七、在blockChain.go中新建FindCoinbaseUTXOs方法,獲取UTXOs

func (this *BlockChain)FindUTXOs(address string)[]TXoutput{
	var UTXO []TXoutput
	spentOutputs := make(map[string][]int64)
	it := this.NewIterator()
	for {
		block := it.Next()
		for _, tx := range block.Transactions {
		OUTPUT:
			for i, output := range tx.TXoutputs {
				if spentOutputs[string(tx.TXID)] != nil {
					fmt.Println(spentOutputs[string(tx.TXID)])
					for _, j := range spentOutputs[string(tx.TXID)] {
						if int64(i) == j {
							continue OUTPUT
						}
					}
				}
				if output.PubKeyHash == address {
					UTXO=append(UTXO, output)
				}
			}
			if !tx.IsCoinBaseTX(tx) {
				for _, input := range tx.TXinputs {
					if input.Sig == address {
						spentOutputs[string(input.TXid)] = append(spentOutputs[string(input.TXid)], input.Index)
					}
				}
			}
		}

		if len(block.PrevHash) == 0 {
			fmt.Printf("區塊遍歷完成退出!")
			break
		}
	}

	return UTXO
	//
}

八、在Trancations.go中新增判斷是否爲挖礦交易的方法,如果是則跳過input記錄前output

func (tx *Transaction)IsCoinBaseTX(txs *Transaction)bool{
	//1.TXid爲空
	//2.index爲-1
	//3.只有一個Input
	if len(txs.TXinputs) ==1{
		if len(txs.TXinputs[0].TXid)==0 && txs.TXinputs[0].Index==-1{
			return true
		}
	}
	return false
}


/*個人認爲沒有這個方法,也不會影響收益查看,因爲餘額雖然會在多個交易中查詢UTXOS,但是挖礦交易始終是收益的源頭,也就是第一條數據,而當遍歷到第一條交易數據時,遍歷output的循環不會在下一次執行了,所以大家可以發現就算沒有添加此函數,餘額也不會受影響,
但添加此函數有也添加的好處,比如會提交代碼運行效率等

*/

九、創建普通交易

思路

[創建普通交易邏輯上要實現以下:(var utxos []output)

1.找到匹配的utxos,遍歷餘額,返回map[tanscation.TXid][outputIndex],賬戶餘額(resvalue)

2.判斷餘額與轉賬金額的大小,若餘額小於轉賬金額,則返回nil,若餘額大於轉賬金額則:

2.1新建TXinputs,記錄output的ID和索引

2.2.新建txoutput,進行找零,output[amount,to]   output[resValuea - mount,from],且返回*Transcation

實現:

1.在Transcation中實現普通交易的NewTranscations方法

func NewTranscations(from, to string,amount float64,bc *BlockChain )*Transaction{
	utxos,resValue:=bc.FindNeedUtxos(from,amount)
	if resValue<amount{
		fmt.Println("餘額不足,請檢查錢包額餘")
		return nil
	}else {
		//額餘充足,進行轉賬和找零
		var inputs []TXinput
		var outputs []TXoutput
		for id,indexList :=range utxos{
			for _,index :=range indexList{
				inputs=append(inputs, TXinput{[]byte(id),int64(index),from})
			}
		}
		outputs=append(outputs,TXoutput{amount,to})
		//找零
		outputs=append(outputs,TXoutput{resValue-amount,from})
		tx:=Transaction{
			TXID:[]byte{},
			TXinputs:inputs,
			TXoutputs:outputs,
		}
		tx.SetHash()
		return &tx
	}
}

 2.在blockChain中實現查找所需餘額的FindNeedUTXOS方法

func (bc *BlockChain)FindNeedUtxos(from string,amount float64)(map[string][]uint64,float64){
	it:=bc.NewIterator()
	utxos:=make(map[string][]uint64)
	resValue:=float64(0.0)
	spentoutput :=make(map[string][]uint64)
	for {
		block:=it.Next()
		for _,transcation :=range block.Transactions{
			outputList:=[]uint64{}
			//output獲取
		OUTPUT:
			for index,output :=range transcation.TXoutputs{
				if resValue>=amount{
					return utxos,resValue
				}
				if spentoutput[string(transcation.TXID)]!=nil{
					for _,value :=range spentoutput[string(transcation.TXID)]{
						if value==uint64(index){
							continue OUTPUT
						}
					}
				}
				if output.PubKeyHash==from{
					outputList=append(outputList, uint64(index))
					utxos[string(transcation.TXID)]=outputList
					resValue+=output.Value
					fmt.Printf("找到滿足的金額:%f\n",output.Value)
				}
			}
			//input篩選
			if !transcation.IsCoinBaseTX(transcation){
				inputList:=[]uint64{}
				for _,input :=range transcation.TXinputs{
					if input.Sig==from{
						inputList=append(inputList, uint64(input.Index))
						spentoutput[string(input.TXid)]=inputList
					}
				}
			}
		}
		if len(block.PrevHash)==0{
			break
		}
	}
	fmt.Printf("轉賬結束\n")
	return utxos,resValue
}

至此,我們已經實現了UTXO的轉賬,特別提醒一點,一個交易中是不會存在兩個同地址output(其中一個已經用過,另一個沒有用過。)兩個同地output,要麼同時沒有被用過,要麼都被用過。因爲區塊鏈轉賬時實時,是全部轉完的,即使自己有剩餘,也會先拿出來,最後轉給自己。所以,我們可以通過這一點,將兩個utxo函數,高聚合化,但是爲了節約時候,這裏我就不再詳細說明。

十、補充makerkleRoot生成函數,以及優化時間戳

func (this *Block)MakeMakerkleRoot()[]byte{
	//我們進行哈希拼接
	final:=[]byte{}
	for _,j :=range this.Transactions{
		final=append(final, j.TXID...)
	}
	hash:=sha256.Sum256(final)
	return hash[:]
}


//時間戳
fmt.Printf("時間戳:%s\n",time.Unix(int64(block.TimeStamp),0).Format("2006-1-2 15:04:05"))

七、實現錢包地址和交易加密(V5版本)

在V4版本中,我們已經模擬實現了比特幣UTXO,但是我們的錢包還是字符串,亟需修改。我們知道比特幣中使用的數字簽名算法是橢圓曲線數字籤。所以我們有必要開發一個新的版本。

一、非對稱加密簽名算法--(橢圓曲線加密/ECDSA)的實現

package main

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/sha256"
	"fmt"
	"math/big"
)

func main(){
	//創建曲線
	curve:=elliptic.P256()
	//生成私匙
	privateKey,err:=ecdsa.GenerateKey(curve,rand.Reader)
	if err!=nil{
		fmt.Println(err)
	}
	//生成公鑰
	publicKey:=privateKey.PublicKey
	//對數據進行哈希運算
	data:="666666666"
	hash:=sha256.Sum256([]byte(data))
	//數據簽名
	//func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error) {
	r,s,er:=ecdsa.Sign(rand.Reader,privateKey,hash[:])
	if er!=nil{
		fmt.Println(err)
	}
	//把r、s進行序列化傳輸
	//1.傳輸
	signature:=append(r.Bytes(),s.Bytes()...)
	//2.獲取、定義兩個輔助的BIG.INT
	r1:=big.Int{}
	s1:=big.Int{}
	//3.拆分並賦值
	r1.SetBytes(signature[:len(signature)/2])
	s1.SetBytes(signature[len(signature)/2:])


	//數據校驗
	//func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool {
	status:=ecdsa.Verify(&publicKey,hash[:],&r1,&s1)
	fmt.Println(status)

}

二、創建公鑰、私匙,以及實現NewWallet命令

1.新建Wallet.go

package main

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"fmt"
)

type Wallet struct {
	PrivateKey *ecdsa.PrivateKey
	PublicKey []byte
}
func NewWallet()*Wallet{
	curve:=elliptic.P256()
	privateKey,err:=ecdsa.GenerateKey(curve,rand.Reader)
	if err!=nil{fmt.Println(err)}
	publicKeyOrign:=privateKey.PublicKey
	publicKey:=append(publicKeyOrign.X.Bytes(),publicKeyOrign.Y.Bytes()...)
	return &Wallet{privateKey,publicKey}
}

2.修改cl.go,並添加命令(略)

3.地址生成

3.1下載 ripemd160源碼包

由於偉大的牆,我們需要手動下載源碼包
1.在{GOPATH}中的golang.org\x 進行gitlone

git clone https://github.com/golang/crypto.git

生成地址分爲幾個過程

1.將publicKey進行哈希運算,生成hash,並將生成的hash進行ripemd160運算,生成哈希

2.將生成的哈希與version拼接,並進行哈希運算,生成hash1

3.拷貝hash1,並再對hash1進行哈希運算生成hash2,截取hash2的前四個字節,並命名爲checkCode

4.將hash1和checkCode拼接,並進行base58編碼,得到錢包地址

流程如圖:

實現:

func (this *Wallet)NewAdress()string{
	//1.獲取pubKey
	pubKey:=this.PublicKey
	//2.獲取ripemd160哈希
	ripeHash:=Newripe160Hash(pubKey)
	//3.將ripeHash與version進行拼接
	version:=byte(00)
	payload:=append(ripeHash,[]byte{version}...)
	//4.拷貝一份payload做hash後截取前4個字節
	hash1:=sha256.Sum256(payload)
	CheckCode:=CheckCode(hash1[:])
	//5.再次拼接
	payload=append(payload,CheckCode...)
	//6.做base58
	address:=base58.Encode(payload)
	return address
}
func CheckCode(hash1 []byte)[]byte{
	//再做一次hash
	hash2:=sha256.Sum256(hash1)
	return hash2[:4]
}
func Newripe160Hash(pubKey []byte)[]byte{
	hash:=sha256.Sum256(pubKey)
	//創建編碼器
	ripe:=ripemd160.New()
	//寫入
	_,err:=ripe.Write(hash[:])
	if err!=nil {
		fmt.Println(err)
	}
	//生成哈希
	ripehash:=ripe.Sum(nil)
	return ripehash

}

三、創建wallets,實現本地存儲地址

老問題,上面創建的地址,在關閉程序後就會消失,這時候我們需要把地址存在某個地方。因爲地址是加密處理過的,所以我們選擇最簡單的方式--存在本地。

一、創建Wallets結構體,保存[address]*wallet

二、創建NewWallets方法,從本地獲取反序列化的結構體,對對Wallets賦值,(在賦值之前,要先判斷本地文件是否存在)

三、創建CreateWallets方法,生成的address的生成和對Wallets結構體通過map,序列化到本地文件中。

代碼如下:

package main

import (
	"btcutil/base58"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/sha256"
	"fmt"
	"golang.org/x/crypto/ripemd160"
)

type Wallet struct {
	PrivateKey *ecdsa.PrivateKey
	PublicKey []byte
}
func NewWallet()*Wallet{
	curve:=elliptic.P256()
	privateKey,err:=ecdsa.GenerateKey(curve,rand.Reader)
	if err!=nil{fmt.Println(err)}
	publicKeyOrign:=privateKey.PublicKey
	publicKey:=append(publicKeyOrign.X.Bytes(),publicKeyOrign.Y.Bytes()...)
	return &Wallet{privateKey,publicKey}
}
func (this *Wallet)NewAdress()string{
	//1.獲取pubKey
	pubKey:=this.PublicKey
	//2.獲取ripemd160哈希
	ripeHash:=Newripe160Hash(pubKey)
	//3.將ripeHash與version進行拼接
	version:=byte(00)
	payload:=append(ripeHash,[]byte{version}...)
	//4.拷貝一份payload做hash後截取前4個字節
	hash1:=sha256.Sum256(payload)
	CheckCode:=CheckCode(hash1[:])
	//5.再次拼接
	payload=append(payload,CheckCode...)
	//6.做base58
	address:=base58.Encode(payload)
	return address
}
func CheckCode(hash1 []byte)[]byte{
	//再做一次hash
	hash2:=sha256.Sum256(hash1)
	return hash2[:4]
}
func Newripe160Hash(pubKey []byte)[]byte{
	hash:=sha256.Sum256(pubKey)
	//創建編碼器
	ripe:=ripemd160.New()
	//寫入
	_,err:=ripe.Write(hash[:])
	if err!=nil {
		fmt.Println(err)
	}
	//生成哈希
	ripehash:=ripe.Sum(nil)
	return ripehash

}

四、添加Cli命令,編寫PrintAddressList方法,對錢包地址進行遍歷

func (this *Cli)PrintAddressList(){
	wallets:=NewWallets()
	addressList:=wallets.PrintAdress()
	for _,address :=range addressList{
		fmt.Printf("地址爲:%s\n",address)
	}

}

四、修改Transcation.go等文件,將錢包地址與轉賬融合

1.修改TXinput和TXoutput結構體

type TXinput struct {
	TXid []byte//引用的交易ID
	Index int64//引用的output索引值
	//Sig string//解鎖腳本
	//簽名,由r、s拼接成的hash
	Sigrnature []byte
	//公鑰,由X Y拼接的公鑰
	PublicKey  []byte
}
type TXoutput struct {
	Value float64//轉賬金額
	//PubKeyHash string//鎖定腳本
	//公鑰的哈希
	PublickHash []byte
}

2.創建TXout結構體的Lock函數,實現反解到PublicHash,

func (TX *TXoutput)Lock(address string){
	//1.base58解碼
	payLoad:=base58.Decode(address)
	PublicHash:=payLoad[1:len(payLoad)-4]
	TX.PublickHash=PublicHash
}

3.創建NewTXoutput,實現對TX的賦值

func NewTXoutput(value float64,address string)*TXoutput{
	TX:=TXoutput{
		Value:value,
	}
	TX.Lock(address)
	return &TX
}

五、實現交易驗證

交易加密:

1.拷貝一份新生成的Transcation並命名爲txCopy,並txCopy進行trimmed修剪,txCopy的所有TXnput的sinature和publik賦值爲[]byte

2.循環整個區塊鏈,找到與TXinput的Tid相同Transcation,並生成爲map[string(TXinput.Tid)]Transcation,

3.循環txCopy.TXinput,通過TXid和索引找到map中的Transcation的TXoutput,並將TXoutput的publicHash賦值給TXinput.public

4.對txCopy進行setHash,並賦值給Signature

5.將signature做ecdsa.sign簽名,得到R/S/ERR ,並將r、s進行append拼接,得到最終的signature並賦值給交易生成的Transcation.TXinput.signature

6.將txCopy的signature和public賦值爲[]byte,方便下一次循環

實現:

func (bc *BlockChain) SignTransaction(tx *Transaction, privateKey *ecdsa.PrivateKey){
	prevTXs := make(map[string]Transaction)
	for _,input :=range tx.TXinputs{
		Tid:=input.TXid
		tx,err:=bc.FindTransactionByTXid(Tid)
		if err!=nil{
			continue
		}
		prevTXs[string(input.TXid)] = tx
	}
	tx.Sign(privateKey, prevTXs)
}
func (bc *BlockChain) FindTransactionByTXid(TXid []byte)(Transaction,error){
	it:=bc.NewIterator()
	for {
		block:=it.Next()
		for _,tx :=range block.Transactions{
			if bytes.Equal(tx.TXID,TXid){
				return *tx,nil
			}
		}
		if len(block.PrevHash)==0{
			break
		}
	}
	return Transaction{},errors.New("not find ")
}


func (tx *Transaction)Sign(privateKey *ecdsa.PrivateKey, prevTXs map[string]Transaction){
	//1.循環tx交易,獲取TXinput,並賦值output的publickHash
	txCopy:=tx.TrimmedCopy()
	for index,input :=range txCopy.TXinputs{
		prevTranscation:=prevTXs[string(input.TXid)]
		if len(prevTranscation.TXID)==0{
			log.Panic("交易錯誤,..........")
		}
		txCopy.TXinputs[index].PublicKey=prevTranscation.TXinputs[input.Index].PublicKey
		txCopy.SetHash()
		txCopy.TXinputs[index].PublicKey=[]byte{}
		r,s,err:=ecdsa.Sign(rand.Reader,privateKey,txCopy.TXID)
		if err!=nil{log.Panic(err)}
		signature:=append(r.Bytes(),s.Bytes()...)
		tx.TXinputs[index].Sigrnature=signature
	}
}
func (tx *Transaction)TrimmedCopy()Transaction{
	var inputs[]TXinput
	var outputs[]TXoutput
	for _,input :=range tx.TXinputs{
		inputs=append(inputs, TXinput{input.TXid,input.Index,nil,nil})
	}
	for _,output :=range tx.TXoutputs{
		outputs=append(outputs, output)
	}
	return Transaction{tx.TXID,inputs,outputs}
}

校驗:

1.檢驗函數在AddBlock時執行,屬於TX的方法

2.得到交易的簽名前的數據signature

3.得到rs 

4.通過public得到X  Y從而  得到originPublicKey、

5.進行ecdsa.verify

實現:

func (tx *Transaction)Verify(prevTXs map[string]Transaction)bool{
	if !tx.IsCoinBaseTX(tx){
		return true
	}
	txCopy:=tx.TrimmedCopy()
	for index,input :=range tx.TXinputs{
		prevTranscation:=prevTXs[string(input.TXid)]
		if len(prevTranscation.TXID)==0{
			log.Panic("error......")
		}
		txCopy.TXinputs[index].PublicKey=prevTranscation.TXoutputs[input.Index].PublickHash
		txCopy.SetHash()
		txCopy.TXinputs[index].PublicKey=nil
		originSignature:=txCopy.TXID
		signature:=input.Sigrnature
		publicKey:=input.PublicKey
		r :=big.Int{}
		s :=big.Int{}
		r.SetBytes(signature[:len(signature)/2])
		s.SetBytes(signature[len(signature)/2:])
		x :=big.Int{}
		y :=big.Int{}
		x.SetBytes(publicKey[:len(publicKey)/2])
		y.SetBytes(publicKey[len(publicKey)/2:])
		originPublicKey:=ecdsa.PublicKey{elliptic.P256(),&x,&y}
		//func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool {
		if !ecdsa.Verify(&originPublicKey,originSignature,&r,&s){
			return false
		}
	}
	return true
}



//以下在blockChain.go中實現
func (bc *BlockChain) VerifyTranscation(tx *Transaction)bool{
	if tx.IsCoinBaseTX(tx){
		return true
	}
	prevTXs := make(map[string]Transaction)
	for _,input :=range tx.TXinputs{
		Tid:=input.TXid
		tx,err:=bc.FindTransactionByTXid(Tid)
		if err!=nil{
			continue
		}
		prevTXs[string(input.TXid)] = tx
	}
	return tx.Verify(prevTXs)
}

對交易添加Sting方法進行遍歷:

func (tx Transaction) String() string {
	var lines []string

	lines = append(lines, fmt.Sprintf("--- Transaction %x:", tx.TXID))

	for i, input := range tx.TXinputs {

		lines = append(lines, fmt.Sprintf("     Input %d:", i))
		lines = append(lines, fmt.Sprintf("       TXID:      %x", input.TXid))
		lines = append(lines, fmt.Sprintf("       Out:       %d", input.Index))
		lines = append(lines, fmt.Sprintf("       Signature: %x", input.Sigrnature))
		lines = append(lines, fmt.Sprintf("       PubKey:    %x", input.PublicKey))
	}

	for i, output := range tx.TXoutputs{
		lines = append(lines, fmt.Sprintf("     Output %d:", i))
		lines = append(lines, fmt.Sprintf("       Value:  %f", output.Value))
		lines = append(lines, fmt.Sprintf("       Script: %x", output.PublickHash))
	}

	return strings.Join(lines, "\n")
}

          至此,go重構BTC源碼結束。反過來回顧的時候,我發現區塊鏈也不是用很難的知識點實現的,沒有複雜的算法,也沒用複雜的結構。僅是簡單的密碼學基礎和結構體的反覆調用。區塊鏈讓21世紀的我們眼前一亮。究其原因、是中本聰先生將密碼學和去中心化的思想融會貫通了。對於我們普通的開發人員來說,並不是一定要用多牛逼的技術實現某些程序,而是要將最簡單的理論識融會貫通。非精不是明其理,非博不能制其約。我想大概就是區塊鏈誕生的名言警句吧~~

小夥伴們,下一節,我們將繼續學習區塊鏈知識。剖析--以太坊(ETH)源碼

                                                                                                                                                 您身邊喜歡綠色的朋友:

                                                                                                                                                      wechat:laughing_jk

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章