Goでブロックチェーンを実装してみる Part1


この記事は2019新卒 エンジニア Advent Calendar 2019の7日目の記事です。

こんにちは。この記事ではGoで超シンプルなブロックチェーンを作っていきます。
普段から業務でブロックチェーンに携わってはいるのですが、自分で実装したことはなかったので、理解を深めるためにやってみようという次第です。

なお本記事はシリーズ化してお届けする予定です。

はじめに

今回用いるコードはこちらに載っています。
https://github.com/gunnsoo/blockchain_go/tree/qiita_part1

Block

まずはブロックを作りましょう。ブロックにはトランザクションの情報や技術的に必要な情報が含まれています。
ビットコインなどで実装されているブロックはもっと複雑ですが、ここでは簡略版をつくります。

block.go
type Block struct {
    Timestamp     int64
    Data          []byte
    PrevBlockhash []byte
    Hash          []byte
}

・Timestampはブロック生成時点のタイムスタンプ
・Dataはトランザクション情報(誰が誰にいくら送ったか)
・PrevBlockHashは前のブロックのハッシュ
・Hashはこのブロックのハッシュ

「なんでハッシュがいるの?」と思った方がいるかもしれませんが、これはブロックチェーンの堅牢性を担保するために必要なものです。
ブロックチェーンとは、前のブロックのハッシュを保持したブロックが連なって構成されるものです。
ハッシュはブロック全体の情報を入力とするため、ブロック内のほんの一部の情報でも変更されれば、出力されるハッシュは全く異なるものとなります。

例えば、下図のようにトランザクション情報を書き換えると、全く違うハッシュが生成されます。

そのため、あるブロックを改竄するということは、後続するブロック全ても改竄しなければならなくなることを意味します。
通常、ブロックチェーンにおけるプルーフ・オブ・ワークシステムのハッシュ計算には多大なマシンパワーを必要とするので、このような変更は事実上不可能です。ブロックチェーンにおいては1番長いチェーンが正当なチェーンとみなされるため、ネットワーク全体の計算能力のかなりの部分を支配していないとダメだからです。
(と言いつつも、ハッシュレートの低い通貨は実際に攻撃されていたりします。https://crypto.watch.impress.co.jp/docs/event/1125410.html)

ではハッシュの計算を実装していきましょう。
とりあえずここでは、ブロックの情報をまとめたヘッダーを入力としてSHA-256ハッシュを算出します。

block.go
func (b *Block) SetHash() {
    timestamp := []byte(strconv.FormatInt(b.Timestamp, 10))
    headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{})
    hash := sha256.Sum256(headers)

    b.Hash = hash[:]
}

ブロック生成メソッドも追加しておきましょう。

block.go
func NewBlock(data string, prevBlockHash []byte) *Block {
    block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}}
    block.SetHash()
    return block
}

以上でブロックが完成しました!

Blockchain

次はブロックチェーンを実装してみましょう。
単純にブロックの配列として定義します。簡単ですね。

blockchain.go
type Blockchain struct {
    blocks []*Block
}

ブロックの追加もできるようにしましょう。

blockchain.go
func (bc *Blockchain) AddBlock(data string) {
    prevBlock := bc.blocks[len(bc.blocks)-1]
    newBlock := NewBlock(data, prevBlock.Hash)
    bc.blocks = append(bc.blocks, newBlock)
}

はい完成!と言いたいところですが、まだやることがあります。

ブロックを追加するには前のブロックのハッシュ値を知る必要がありますが、このブロックチェーンにはまだブロックが1つもありません。
なので最初のブロックを追加するメソッドを実装しましょう。最初のブロックはgenesis blockと呼ばれています。

blockchain.go
func NewGenesisBlock() *Block {
    return NewBlock("Genesis Block", []byte{})
}

ブロックチェーンを生成する関数も追加します。

blockchain.go
func NewBlockchain() *Blockchain {
    return &Blockchain{[]*Block{NewGenesisBlock()}}
}

これで完成です。

動かす

実際に動かしてみましょう。

mainメソッドを追加。

main.go
func main() {
    bc := NewBlockchain()

    bc.AddBlock("Yamashita send 5000yen to Nishino")
    bc.AddBlock("Nishino send 5000yen to Yamashita")

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

実行しましょう。

$ go run *.go

動いていますね。

Prev. hash: 
Data: Genesis Block
Hash: 89ecd220428dd13a25da244b14cbcb058a3cd3da59f798fc29fefb8bc3e75fff

Prev. hash: 89ecd220428dd13a25da244b14cbcb058a3cd3da59f798fc29fefb8bc3e75fff
Data: Yamashita send 5000yen to Nishino
Hash: 3d8d1d0101d45ca3046d2f62ff2741783b4ee839825c6733437ae8039f985d0a

Prev. hash: 3d8d1d0101d45ca3046d2f62ff2741783b4ee839825c6733437ae8039f985d0a
Data: Nishino send 5000yen to Yamashita
Hash: 914f06a21e5439bc95fa0544dd4646b10d1d5fe08faeeeaa774130f36c6a5d77

まとめ

ここでは、とてもシンプルなブロックチェーンを作成しました。それぞれが1つ前のブロックと繋がったブロックの配列で、新しいブロックを追加するのも簡単です。ウォレット、アドレス、トランザクションや、Proof of Workのようなコンセンサスアルゴリズムもありません。
次回からはこれらの特徴を順次実装していきます。

(参考)
https://github.com/Jeiwan/blockchain_go
https://jeiwan.net/posts/building-blockchain-in-go-part-1/
https://blog.liquid.com/ja/knowledge/what-is-hash-bitcoin/