『ブロックチェーンがどのように動いているのか学ぶ最速の方法は作ってみることだ』
『ブロックチェーンとは』みたいな記事を読んで概念はわかったつもりでも、「わかったような気がする」止まりで腹落ち感までは得られず、いつか自分で実装してみたいと思っていました。1
でも難しそうだし、本業とは関係ない(→時間が割けない)ので永遠にやらなそうだなぁ。。と思ってたら、こんな記事を発見。
なんとグッとくる副題でしょう2。記事の冒頭だけざっと読んだところ、**なんか、サッとできそうだぞ・・・!**と。実装量でいえば1日もかからなそうです。
Pythonはわからないので、Swiftで書いてみました。
以下、元記事のステップに沿ってSwift実装を載せていきます。引用記法の部分は元記事より引用したものです。また、「Swiftでブロックチェーンを実装するならSequence
プロトコルに準拠した形でつくるべき」とかよりよい実装のご指摘はあるかと思いますが、基本的には本記事は元記事の実装に忠実に沿うようにします。
ステップ1: ブロックチェーンを作る
ブロックチェーンを書く
チェーンの取り扱いを司るBlockchain
の雛形をこんな感じでつくります。
import Foundation
class Blockchain {
// トランザクションを納めるための空のリスト
private var currentTransactions: [Transaction] = []
// ブロックチェーンを納めるための最初の空のリスト
var chain: [Block] = []
// 新しいブロックを作り、チェーンに加える
func createBlock(proof: Int, previousHash: Data?) {
}
// 新しいトランザクションをリストに加える
func createTransaction(sender: String, recipient: String, amount: Int) {
}
// チェーンの最後のブロックを返す
func lastBlock() {
}
}
ビルドが通るように、戻り値とかは現段階では省略してます。Block
、Transaction
も、とりあえずビルドが通るようにつくっておきます。
struct Block {
}
struct Transaction {
}
ブロックとはどのようなものなのか
それぞれのブロックは、インデックス、タイムスタンプ(UNIXタイム)、トランザクションのリスト、プルーフ(詳細は後ほど)そしてそれまでのブロックのハッシュを持っている。
struct Block {
let index: Int
let timestamp: Double
let transactions: [Transaction]
let proof: Int
let previousHash: Data
}
構造体を言われた通りに定義しただけですが、これだけで以下の解説と合わせて一気に理解(実感)が進みました。
この時点で、チェーンのアイデアは明確だ -全ての新しいブロックはそれまでのブロックのハッシュを自分自身の中に含んでいる。これこそがまさにブロックチェーンに不変性を与えているものであり、そのために重要なポイントだ。もしアタッカーがチェーン初期のブロックを破壊した場合、それに続く全てのブロックが不正なハッシュを含むことになる。
トランザクションをブロックに加える
new_transaction()
メソッドは、新しいトランザクションをリストに加えた後、そのトランザクションが加えられるブロック -次に採掘されるブロックだ-のインデックスをリターンする。
これに相当するcreateTransaction
メソッドを実装します。
// 新しいトランザクションをリストに加える
func createTransaction(sender: String, recipient: String, amount: Int) -> Int {
// 次に採掘されるブロックに加える新しいトランザクションを作る
let transaction = Transaction(sender: sender, recipient: recipient, amount: amount)
currentTransactions.append(transaction)
// このトランザクションを含むブロックのアドレスを返す
return lastBlock().index + 1
}
struct Transaction {
let sender: String
let recipient: String
let amount: Int
}
新しいブロックを作る
createBlock
メソッドを実装します。
// 新しいブロックを作り、チェーンに加える
func createBlock(proof: Int, previousHash: Data? = nil) -> Block {
let prevHash: Data
if let previousHash = previousHash {
prevHash = previousHash
} else {
// 前のブロックのハッシュ
prevHash = lastBlock().hash()
}
let block = Block(index: chain.count+1,
timestamp: Date().timeIntervalSince1970,
transactions: currentTransactions,
proof: proof,
previousHash: prevHash)
// 現在のトランザクションリストをリセット
currentTransactions = []
chain.append(block)
return block
}
これに伴い、lastBlock()
メソッドと、Block
をハッシュ化するメソッドも実装します。
// チェーンの最後のブロックを返す
func lastBlock() -> Block {
guard let last = chain.last else {
fatalError("The chain should have at least one block as a genesis.")
}
return last
}
ここで、lastBlock()
利用時にはchain
は必ず要素を1つ以上もつ前提としています。なぜならBlockchain
は必ず「ジェネシス(genesis:起源)ブロック」というものを持つからです。
我々のBlockchainがインスタンス化されるとき、私たちはジェネシスブロック -先祖を持たないブロック- とともにシードする必要がある。
というわけでBlockchain
のイニシャライザでジェネシスブロックをつくります。
init() {
// ジェネシスブロックを作る
createBlock(proof: 100, previousHash: "1".data(using: .utf8))
}
Block
をハッシュ化するメソッドですが、Block
とTransaction
をCodable
に準拠させて、
struct Block: Codable
struct Transaction: Codable
次のようなData
型をSHA-256ハッシュ化するextensionメソッドを実装しておき、
extension Data {
// https://stackoverflow.com/questions/25388747/sha256-in-swift
func sha256() -> Data? {
guard let res = NSMutableData(length: Int(CC_SHA256_DIGEST_LENGTH)) else { return nil }
CC_SHA256((self as NSData).bytes, CC_LONG(self.count), res.mutableBytes.assumingMemoryBound(to: UInt8.self))
return res as Data
}
}
次のように自身のSHA-256ハッシュを返すメソッドをBlock
に追加しました。
struct Block: Codable {
...
// ブロックの SHA-256 ハッシュを作る
func hash() -> Data {
let encoder = JSONEncoder()
let data = try! encoder.encode(self)
return data.sha256()!
}
プルーフ・オブ・ワークを理解する
だんだんつかめてきたのでここは実装をさぼりました。
// シンプルなプルーフ・オブ・ワークのアルゴリズム:
// - hash(pp') の最初の4つが0となるような p' を探す
// - p は前のプルーフ、 p' は新しいプルーフ
class func proofOfWork(lastProof: Int) -> Int {
// FIXME: サボった
return 0
}
いや、実際のところここが計算量がかかるところで、ビットコインでは「採掘」(マイニング)と呼ばれ報酬が与えられるポイントでもあるらしいので、ちゃんと実装してみたほうがいいのは間違いないのですが。
プルーフ・オブ・ワークアルゴリズム (PoW) とは、ブロックチェーン上でどのように新しいブロックが作られるか、または採掘されるかということを表している。PoWのゴールは、問題を解く番号を発見することだ。その番号はネットワーク上の誰からも見つけるのは難しく、確認するのは簡単 -コンピュータ的に言えば- なものでなければならない。これがプルーフ・オブ・ワークのコアとなるアイデアだ。
ビットコインでは、プルーフ・オブ・ワークのアルゴリズムはハッシュキャッシュ (Hashcash)と呼ばれている。そしてそれはこの基本的な例とそこまで違うものではない。ハッシュキャッシュは、採掘者が競い合って新しいブロックを作るために問題を解く、というものだ。一般的に、難易度は探す文字の数によって決まる。採掘者はその解に対して、報酬としてトランザクションの中でコインを受け取る。
アルゴリズムの難易度を調整するためには、最初の0の数を変えることで出来る。しかし4は充分な数だ。0を一つ加えることで、解を見つけるための時間にマンモス級の違いが出ることに気がつくだろう。
なぜサボったかというと、これまでの実装でもう「それっぽく」動きそうだと思ったからです。
ステップ2: APIとしての私たちのブロックチェーン
ここは元記事では「サーバーを立ててこれまで実装した機能をAPIとして提供する」ところですが、雰囲気だけ分かればいいので、単純にエンドポイントを持つクラスを実装しました。
class BlockchainServer {
// ブロックチェーンクラスのインスタンス
let blockchain = Blockchain()
// トランザクションのエンドポイント
func send(sender: String, recipient: String, amount: Int) -> Int {
return blockchain.createTransaction(sender:sender, recipient:recipient, amount:amount)
}
// 採掘のエンドポイント
func mine(recipient: String) -> Block {
// 次のプルーフを見つけるためプルーフ・オブ・ワークアルゴリズムを使用する
let lastBlock = blockchain.lastBlock()
let lastProof = lastBlock.proof
let proof = Blockchain.proofOfWork(lastProof: lastProof)
// プルーフを見つけたことに対する報酬を得る
// 送信者は、採掘者が新しいコインを採掘したことを表すために"0"とする
blockchain.createTransaction(sender: "0", recipient: recipient, amount: 1)
// チェーンに新しいブロックを加えることで、新しいブロックを採掘する
let block = blockchain.createBlock(proof: proof)
return block
}
// フルのブロックチェーンを返すエンドポイント
func chain() -> [Block] {
return blockchain.chain
}
}
ステップ3: オリジナルブロックチェーンとのインタラクション
ステップ2でつくったAPIをたたいてみるステップです。本記事ではサーバーを立てず単に「そういうクラス」をつくっただけなので、UIにボタンを置いて、ボタンを押したらエンドポイントとしてのメソッドを叩くように実装しました。
@IBAction func mineBtnTapped(_ sender: UIButton) {
let block = server.mine(recipient: myId)
print("新しいブロックを採掘しました\n\(block.description())")
}
採掘を3回やった際のログ:
新しいブロックを採掘しました
{"timestamp":1515478824.7369962,"proof":0,"transactions":[{"amount":1,"recipient":"aaaa","sender":"0"}],"previousHash":"OJ8NGQBbfF6VYpIEA4t79xDHYpI0LSL6QI1aXLFjyKA=","index":2}
新しいブロックを採掘しました
{"timestamp":1515478826.0055161,"proof":0,"transactions":[{"amount":1,"recipient":"aaaa","sender":"0"}],"previousHash":"E1kSp3c\/U\/W0JrmtOLeOUtTWZRjgh+7FBvnJXVP4ulM=","index":3}
新しいブロックを採掘しました
{"timestamp":1515478827.254945,"proof":0,"transactions":[{"amount":1,"recipient":"aaaa","sender":"0"}],"previousHash":"uMBKuCdGYd+\/KRFgnAh+q69Kqc+kpES08HmUAT1BjfY=","index":4}
フルのブロックチェーンを取得:
チェーン全体:
{"timestamp":1515478823.8413019,"proof":100,"transactions":[],"previousHash":"MQ==","index":1}
{"timestamp":1515478824.7369962,"proof":0,"transactions":[{"amount":1,"recipient":"aaaa","sender":"0"}],"previousHash":"OJ8NGQBbfF6VYpIEA4t79xDHYpI0LSL6QI1aXLFjyKA=","index":2}
{"timestamp":1515478826.0055161,"proof":0,"transactions":[{"amount":1,"recipient":"aaaa","sender":"0"}],"previousHash":"E1kSp3c\/U\/W0JrmtOLeOUtTWZRjgh+7FBvnJXVP4ulM=","index":3}
{"timestamp":1515478827.254945,"proof":0,"transactions":[{"amount":1,"recipient":"aaaa","sender":"0"}],"previousHash":"uMBKuCdGYd+\/KRFgnAh+q69Kqc+kpES08HmUAT1BjfY=","index":4}
proofを計算するところはサボったのでダメですが、それっぽい結果にはなっているようです。
ステップ4: コンセンサス
ここ以降は実装してません。この「コンセンサス」は、各ノードが持つチェーンの正しさを**「非中央集権的に」**確認するステップなので、ブロックチェーンにとってものすごく重要なところなのですが、
これはクールだ。トランザクションを受け付けて、新しいブロックを採掘できるブロックチェーンを作ることが出来た。しかしブロックチェーンの重要なポイントは、非中央集権的であることだ。そしてもし非中央集権的であれば、我々はどのように地球上の全員が同じチェーンを反映していると確認することが出来るだろうか。これはコンセンサスの問題と呼ばれており、もし1つより多くのノードをネットワーク上に持ちたければ、コンセンサスのアルゴリズムを実装しなければならない。
実際にやることとしては、
- 他のすべてのノードのチェーンを取得する
- そのチェーンがより長く、有効かを確認する
- ブロックのハッシュが正しいか?(
previousHash
が実際に前のブロックのハッシュと一致するか?) - プルーフ・オブ・ワークが正しいか?
- ブロックのハッシュが正しいか?(
- 自らのチェーンより長く、かつ有効なチェーンを見つけた場合それで置き換える
ということのようなので、コードを見てわかった気になれたので省略しました。
ソースコード
Swiftでの実装コードはこちらにアップしました。
こんな感じで採掘(マイニング)と送信(トランザクション)を試せるようにしてます。
ちゃんと数えてませんが、ざっと見た感じブロックチェーンの実装部分は合計200行もなさそうです。PoWとコンセンサスの実装がまだですが。。(PR大歓迎です)
所感
いろいろ端折りましたが、やはり自分で手を動かすと納得感が違います。
たとえば、コードを用いない、文章だけの解説だと、
ビットコインは、一定期間ごとに、すべての取引記録を取引台帳に追記します。その追記の処理には、ネットワーク上に分散されて保存されている取引台帳のデータと、追記の対象期間に発生したすべての取引のデータの整合性を取りながら正確に記録することが求められます。
こんな感じで、「なるほどなるほど(台帳って・・・?)」といまいちピンとこなかったのが3、自分でBlock
構造体を書き、それにTransaction
構造体をもたせることで、またそのBlock
インスタンスをchain
にappend
していくことで、実感として理解できるようになりました。
他にも、ビットコインの「採掘」が「何らかのコンピュータリソースの提供に対する見返りとしてコインをもらえる」ぐらいの認識だったのが、PoWアルゴリズムに則ってプルーフを探し当てる計算が大変で、その計算の見返りとしてのコインであると実感できましたし、その「プルーフ」は各Block
に格納され、取引データの整合性を取るために用いられる、といったことも具体的に理解できました。
冒頭にも書いたように非常にシンプルで、途中でもビルド可能なようになってるので、「ブロックチェーンが気になってるけどモヤモヤしてる」という方はぜひちょっとでも(各自の得意な言語で)手を動かしてつくってみるといいかもしれません!
-
ディープラーニングも自分で実装してみて初めて腹落ちしました。 ↩
-
あまりにかっこいいので見出しにお借りしました。 ↩
-
この解説自体は、コードなしで非常にわかりやすく解説してくれていると思います。 ↩