開發環境:Go語言
本教程是學習Jeiwan的部落格後的學習筆記,程式碼實現也參考它的為主,精簡了敘述並在適當位置新增了一些必備的小知識和適當的程式碼註釋,如介紹雜湊。
本教程是為了逐步教你設計一款簡化的區塊鏈原型幣。通過我們不斷新增功能,完成一個可交易的原型幣。
本節我們設計一個單機版的僅支援儲存資訊的原型幣。
- 單機版,僅支援儲存資訊
區塊:
區塊鏈中的最基本單位,一個區塊包含區塊頭和區塊體。
區塊頭
包含這個區塊的相關資訊。 如:
- 區塊產生時間
- 本區塊雜湊值(保證本區塊不篡改)
- 上一個區塊雜湊值(通過該雜湊找到上一個區塊,能串起來區塊鏈)
區塊體
真正包含資料的是區塊體。
對於原型幣,為了簡便設計,將區塊體和區塊頭合二為一。
請看該區塊的程式碼
type Block struct {
Timestamp int64 //區塊產生時間時的當前系統時間,從1970開始的毫秒數
PrevHash []byte //上一個區塊雜湊值,指向上一個區塊
Hash []byte //本區塊雜湊值,根據本區塊的位元組經過雜湊演算法算的
Data []byte //區塊體的資訊,自己填寫希望儲存的資訊
}
複製程式碼
注意這裡都使用byte陣列,區塊的底層實現其實都是位元組byte。
雜湊
在我們的區塊中,唯一不明確的是,這個區塊的雜湊要如何計算呢?
先普及一下雜湊,雜湊是一個單向計算,將原資訊通過雜湊函式計算出雜湊值,即hash=HASH(bytes);得到一個固定位元組大小的字串hash,可以用來代表原來的bytes資料,我們常見的MD5就是一種雜湊演算法。
雜湊具有三大特性。
- 無碰撞(Collision-free),可認為hash和bytes一一對應,若bytes被篡改,則hash就變了。
- 隱藏資訊(Hiding),通過hash字串不能反推原資訊。
- 謎題友好(puzzle-friendly),意思是若要從hash字串反推原資訊,只能遍歷嘗試每一個原資訊。 我們在區塊鏈中就是應用了無碰撞的特性,保證了區塊不被串改資訊。
本節的原型幣的雜湊函式採用SHA256,公式如下:
Hash = SHA256(PrevBlockHash + Timestamp + Data)
複製程式碼
實現程式碼如下:
func (b *Block) SetHash() {
timestamp := []byte(strconv.FormatInt(b.Timestamp, 10))
//由於Timestamp是int64,需要轉化為bytes,再和其他資訊拼接,這裡暫時沒有要求用什麼方式轉,你可以直接按int64的字串意義轉成byte,也可以按int64的底層byte表示轉成byte,這裡取前者。只要還原成int64時按照同一個方式即可。
headers := bytes.Join([][]byte{b.PrevHash, b.Data, timestamp}, []byte{})
hash := sha256.Sum256(headers)
b.Hash = hash[:]
}
複製程式碼
瞭解了雜湊的計算,我們就知道如何製造一個區塊了。
func NewBlock(data string, prevBlockHash []byte) *Block {
block := &Block{time.Now().Unix(), []byte(data), prevHash, []byte{}}
block.SetHash()
return block
}
複製程式碼
區塊鏈
區塊鏈的本質就是串起來的區塊,是一種特殊的資料庫。通過每一個區塊中的上一個區塊的雜湊值,將區塊一個一個的串起來。 為了方便起見,我們單機版的幣就用陣列,按順序儲存。 程式碼如下:
type Blockchain struct {
Blocks []*Block
}
複製程式碼
給區塊鏈新增區塊的方法如下:
func (bc *Blockchain) AddBlock(data string) {
prevBlock := bc.blocks[len(bc.blocks)-1]
newBlock := NewBlock(data, prevBlock.Hash)
bc.Blocks = append(bc.blocks, newBlock)
}
複製程式碼
但是,特殊的是第一個創世區塊,需要我們手動製造一下,中本聰也是這麼做的。
func NewGenesisBlock() *Block {
return NewBlock("Genesis Block", []byte{})
}
複製程式碼
據此,我們可以建立創世區塊鏈,
func NewBlockchain() *Blockchain {
return &Blockchain{[]*Block{NewGenesisBlock()}}
}
複製程式碼
最後,讓我們來執行一下我們的原型幣。
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()
}
}
複製程式碼
所得結果,你的執行結果和我的都會不一樣,因為你的區塊產生時間是不一樣的:
Prev. hash:
Data: Genesis Block
Hash: 0a6a60a1def38b806d5e42410468d63d626171f75717a6a99ca7a88c98d69a70
Prev. hash: 0a6a60a1def38b806d5e42410468d63d626171f75717a6a99ca7a88c98d69a70
Data: Send 1 BTC to Lin
Hash: a2daba78c1c635c7b5570cac0eaa699455b940acaefc904f394e199dc2e22dee
Prev. hash: a2daba78c1c635c7b5570cac0eaa699455b940acaefc904f394e199dc2e22dee
Data: Send 2 BTC to Lin
Hash: cf17fc4e346328c957b97ef6e92cf91c7b3cd9a798363afbd84fc7bae9657273
複製程式碼
參考:
原始碼
關於我:
linxinzhe,全棧工程師,目前供職於某500強通訊企業。人工智慧,區塊鏈愛好者。
GitHub:https://github.com/linxinzhe
歡迎留言討論,也歡迎關注我,收穫更多區塊鏈開發相關的知識。