Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
304
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

Organization

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

『ブロックチェーンがどのように動いているのか学ぶ最速の方法は作ってみることだ』

『ブロックチェーンとは』みたいな記事を読んで概念はわかったつもりでも、「わかったような気がする」止まりで腹落ち感までは得られず、いつか自分で実装してみたいと思っていました。1

でも難しそうだし、本業とは関係ない(→時間が割けない)ので永遠にやらなそうだなぁ。。と思ってたら、こんな記事を発見。

なんとグッとくる副題でしょう2。記事の冒頭だけざっと読んだところ、なんか、サッとできそうだぞ・・・!と。実装量でいえば1日もかからなそうです。

Pythonはわからないので、Swiftで書いてみました。

以下、元記事のステップに沿ってSwift実装を載せていきます。引用記法の部分は元記事より引用したものです。また、「Swiftでブロックチェーンを実装するならSequenceプロトコルに準拠した形でつくるべき」とかよりよい実装のご指摘はあるかと思いますが、基本的には本記事は元記事の実装に忠実に沿うようにします。

ステップ1: ブロックチェーンを作る

ブロックチェーンを書く

チェーンの取り扱いを司るBlockchainの雛形をこんな感じでつくります。

Blockchain.swift
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() {
    }
}

ビルドが通るように、戻り値とかは現段階では省略してます。BlockTransactionも、とりあえずビルドが通るようにつくっておきます。

Block.swift
struct Block {
}
Transaction.swift
struct Transaction {
}

ブロックとはどのようなものなのか

それぞれのブロックは、インデックス、タイムスタンプ(UNIXタイム)、トランザクションのリスト、プルーフ(詳細は後ほど)そしてそれまでのブロックのハッシュを持っている。

Block.swift
struct Block {
    let index: Int
    let timestamp: Double
    let transactions: [Transaction]
    let proof: Int
    let previousHash: Data
}

構造体を言われた通りに定義しただけですが、これだけで以下の解説と合わせて一気に理解(実感)が進みました。

この時点で、チェーンのアイデアは明確だ -全ての新しいブロックはそれまでのブロックのハッシュを自分自身の中に含んでいる。これこそがまさにブロックチェーンに不変性を与えているものであり、そのために重要なポイントだ。もしアタッカーがチェーン初期のブロックを破壊した場合、それに続く全てのブロックが不正なハッシュを含むことになる。

トランザクションをブロックに加える

new_transaction()メソッドは、新しいトランザクションをリストに加えた後、そのトランザクションが加えられるブロック -次に採掘されるブロックだ-のインデックスをリターンする。

これに相当するcreateTransactionメソッドを実装します。

Blockchain.swift
// 新しいトランザクションをリストに加える
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
}
Transaction.swift
struct Transaction {
    let sender: String
    let recipient: String
    let amount: Int
}

新しいブロックを作る

createBlockメソッドを実装します。

Blockchain.swift
// 新しいブロックを作り、チェーンに加える
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をハッシュ化するメソッドも実装します。

Blockchain.swift
// チェーンの最後のブロックを返す
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のイニシャライザでジェネシスブロックをつくります。

Blockchain.swift
init() {        
    // ジェネシスブロックを作る
    createBlock(proof: 100, previousHash: "1".data(using: .utf8))
}

Blockをハッシュ化するメソッドですが、BlockTransactionCodableに準拠させて、

Block.swift
struct Block: Codable
Transaction.swift
struct Transaction: Codable

次のようなData型をSHA-256ハッシュ化するextensionメソッドを実装しておき、

Block.swift
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に追加しました。

Block.swift
struct Block: Codable {
    ...

    // ブロックの SHA-256 ハッシュを作る
    func hash() -> Data {
        let encoder = JSONEncoder()
        let data = try! encoder.encode(self)
        return data.sha256()!
    }

プルーフ・オブ・ワークを理解する

だんだんつかめてきたのでここは実装をさぼりました。

Blockchain.swift
// シンプルなプルーフ・オブ・ワークのアルゴリズム:
// - 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として提供する」ところですが、雰囲気だけ分かればいいので、単純にエンドポイントを持つクラスを実装しました。

BlockchainServer.swift
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にボタンを置いて、ボタンを押したらエンドポイントとしてのメソッドを叩くように実装しました。

ViewController.swift
@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での実装コードはこちらにアップしました。

こんな感じで採掘(マイニング)と送信(トランザクション)を試せるようにしてます。

blockchainsample.gif

ちゃんと数えてませんが、ざっと見た感じブロックチェーンの実装部分は合計200行もなさそうです。PoWとコンセンサスの実装がまだですが。。(PR大歓迎です)

所感

いろいろ端折りましたが、やはり自分で手を動かすと納得感が違います。

たとえば、コードを用いない、文章だけの解説だと、

ビットコインは、一定期間ごとに、すべての取引記録を取引台帳に追記します。その追記の処理には、ネットワーク上に分散されて保存されている取引台帳のデータと、追記の対象期間に発生したすべての取引のデータの整合性を取りながら正確に記録することが求められます。

こんな感じで、「なるほどなるほど(台帳って・・・?)」といまいちピンとこなかったのが3、自分でBlock構造体を書き、それにTransaction構造体をもたせることで、またそのBlockインスタンスをchainappendしていくことで、実感として理解できるようになりました。

他にも、ビットコインの「採掘」が「何らかのコンピュータリソースの提供に対する見返りとしてコインをもらえる」ぐらいの認識だったのが、PoWアルゴリズムに則ってプルーフを探し当てる計算が大変で、その計算の見返りとしてのコインであると実感できましたし、その「プルーフ」は各Blockに格納され、取引データの整合性を取るために用いられる、といったことも具体的に理解できました。

冒頭にも書いたように非常にシンプルで、途中でもビルド可能なようになってるので、「ブロックチェーンが気になってるけどモヤモヤしてる」という方はぜひちょっとでも(各自の得意な言語で)手を動かしてつくってみるといいかもしれません!


  1. ディープラーニングも自分で実装してみて初めて腹落ちしました。 

  2. あまりにかっこいいので見出しにお借りしました。 

  3. この解説自体は、コードなしで非常にわかりやすく解説してくれていると思います。 

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
304
Help us understand the problem. What are the problem?