🗝️

【東京大学ブロックチェーン公開講座第3週】ビットコイン②

2024/04/26に公開

本記事は、2024年東京大学ブロックチェーン公開講座の3つ目のレポートです。

【東京大学ブロックチェーン公開講座第1週】ブロックチェーン概論
【東京大学ブロックチェーン公開講座第2週】ビットコイン①
【東京大学ブロックチェーン公開講座第3週】ビットコイン②(本記事)

なお、本記事は講座の要約ではなく、講座のポイントを抽出しつつ、筆者の理解や考察を添えてアウトプットしたものとなります。講座内容との差異や筆者の主張(オレンジ背景)が含まれます。

Base58Checkエンコーディング

Base58はビットコインで利用するために開発されたエンコード方式で、Base64から見間違いやすい6文字(0, O, I, l, +, /)を省いたもの。さらにチェックサム(転記ミスの防止)を付与したものがBase58Check。

具体的には、以下のプロセスで導出する。

  1. ペイロードを用意する(アドレスの場合は160bits/20bytesの公開鍵ハッシュ、詳細は先週分を参照)
  2. 先頭にバージョンプレフィックス(フォーマットを表す決められた文字列)を付与する
  3. 上記の値をインプットとしてSHA256ハッシュを2回かけ、出力されたハッシュ値の最初の4bytesをチェックサムとして末尾に付与
  4. Base58エンコードする

秘密鍵のフォーマット

秘密鍵も公開鍵も、いくつか異なる形式で表現される。

Type Prefix(hex) Prefix(Base58) Description
Raw None None 32bytes = 256bits
Hex None None 64hexadecimal digits
WIF(Wallet Import Format) 0x80 5 Base58Check
WIF-compressed 0x80 K or L Base58Check with 0x01 suffix

WIF-compressedは、圧縮(compressed)といいつつサフィックスを足しているのでむしろデータ量は1byte増える。これは圧縮された公開鍵(後述)に対応する秘密鍵であることを示す。

公開鍵のフォーマット

公開鍵は、圧縮されていないものと圧縮されたものの2つの表現がある。

圧縮されていない公開鍵

プレフィックス 0x04 + 公開鍵のx座標256bits + 公開鍵のy座標256bitsの520bits/65bytesで表現する。0x04は圧縮されていない公開鍵を示す。

圧縮された公開鍵

背景:トランザクションには署名に加えて公開鍵を含める必要があり、ブロックに取り込めるデータ量には制限(1MB)があるので、できるだけ小さくしたい。また、トランザクションサイズが小さければ手数料も安く済む。

プレフィックス8bits/1bytes + 公開鍵のx座標256bitsの264bits/33bytesで表現する。楕円曲線上のy座標は式をとけば導出できるため、xが分かればyは省略できる。

y^{2}\mod p = (x^{3} + 7) \mod p

プレフィックスは0203。方程式yの解は2つあり偶数と奇数が1つずつ(位数pは素数=奇数)になるため、02→偶数、03→奇数と表現する。

ウォレット

秘密鍵を管理し、トランザクションを生成するツール。複数の秘密鍵を生成・管理する方法で、2種類のウォレットがある。

  • 非決定性(ランダム)ウォレット: アドレスごとに1つずつ秘密鍵を生成・保持する
  • 決定性ウォレット: 1つのシードからハッシュ関数をつかって複数の秘密鍵を生成・保持する

決定性(Deterministic, Seeded)ウォレット

1つのシード値とインデックス、『chain code』というデータを組み合わせて複数の秘密鍵を生成できる。シード値さえあれば複数の秘密鍵を復元できるため、シードのみバックアップすればよい。おなじ仕組みのウォレットであればウォレット間でシード値を移動して再利用できる。

ニーモニックコードワード(Nemonic Code Words)

シードを表現する英単語列で、転記ミスを防ぐために英単語を利用する。

以下のプロセスでシードを表現する(例:128bits, 12words)。

  1. ランダムな128ビット列(エントロピー)を生成
  2. SHA256でハッシュ化し、最初の4bitsをチェックサムとして取り出す
  3. エントロピーの末尾にチェックサムを付与する
  4. 132ビットを11ビットずつ12つに分割
  5. あらかじめ作成した2048wordsの英単語リスト(ビット値と単語のマッピング)でそれぞれに相当する単語を取得
  6. 12単語のニーモニックコードを得る
  7. 任意のパスフレーズをSaltとして加える
  8. Key Stretching Function(ハッシュ化を2048回繰り返す、ブルートフォース対策)を使って512bitsのシード値を生成

HD(Hierarchical Deterministic)ウォレット

決定性ウォレットのひとつで、マスターキーをルートとしたツリー構造の秘密鍵を生成する。メリットとして、用途別にアドレスを割り当てたり、秘密鍵へのアクセスなしで公開鍵を複数生成できる。

以下のプロセスでシードからマスター鍵を生成する。

  1. 512bitsのシードをHMAC-SHA512ハッシュにかけ、512bitsのハッシュ値を得る
  2. 256bitsごと分割する
  3. 左の256bitsをマスター秘密鍵mとする
  4. マスター秘密鍵から、マスター公開鍵M(圧縮された264bits)を導出
  5. 右の256bitsをマスターchain codeとする

また、以下のプロセスで子秘密鍵を導出する。

  1. 親公開鍵264bits、親chain code256bits, インデックスx32bitsの552bitsを入力とする
  2. HMAC-SHA256ハッシュにかけ、512bitsのハッシュ値を得る
  3. 256bitsごと分割する
  4. 左の256bitsを親秘密鍵256bitsと足して、子インデックスxの秘密鍵256bitsを得る
  5. 子インデックスx秘密鍵からインデックスx公開鍵(圧縮された264bits)を導出
  6. 右の256bitsを子インデックスxのchain codeとする

拡張鍵

鍵+チェーンコード(chain code)を結びつけて1つの鍵として持つことができ、これを拡張鍵という。以下の2種類がある。なお、Base58Checkでエンコードする。

Name Format Prefix(Base58) Description
拡張秘密鍵 秘密鍵+チェーンコード xpriv 子秘密鍵が生成可能、流出すると子以下すべての秘密鍵が復元できてしまうので注意
拡張公開鍵 公開鍵+チェーンコード xpub 秘密鍵なしで子公開鍵を生成できる

以下のプロセスで公開秘密鍵から子公開鍵を導出する。

  1. 親公開鍵264bits、親chain code256bits, インデックスx32bitsの552bitsを入力とする
  2. HMAC-SHA256ハッシュにかけ、512bitsのハッシュ値を得る
  3. 256bitsごと分割する
  4. 左の256bitsを生成元G倍し、親公開鍵264bitsと足して子インデックスxの公開鍵264bitsを得る
  5. 右の256bitsを子インデックスxのchain codeとする

数式的には、以下のように説明できる。※Kは公開鍵、kは秘密鍵、Gは生成元

K_{parent} = k_{parent} * G, K_{child} = k_{child} * G
k_{child} = k_{parent} + h_{left}

なので、子公開鍵K childは

K_{child} = (k_{parent} + h_{left}) * G = k_{parent} * G + h_{left} * G

より

K_{child} = K_{parent} + h_{left} * G

秘密鍵を使わずに公開鍵を導出できるため、信頼できないサーバーには拡張公開鍵のみを配置することで秘密鍵が流出するリスクがなくなる。

HDウォレット鍵識別子(path)

HDウォレットの鍵を指定するための命名規則で、ツリー階層を /で区切って表現する。マスター秘密鍵から得られた秘密鍵は先頭がm、マスター公開鍵から得られた公開鍵は先頭がM

HD path Description
m/0 マスター秘密鍵から得られた最初の子秘密鍵
m/0/0 m/0の最初の子秘密鍵
m/0'/0 マスター秘密鍵から得られた最初の強化(割愛)子秘密鍵の最初の子秘密鍵
m/1/0 マスター秘密鍵から得られた2番目の子秘密鍵の最初の子秘密鍵

トランザクション

【東京大学ブロックチェーン公開講座②】ビットコイン①でざっくり説明したトランザクションについて、さらに詳しくみていく。

トランザクションのライフサイクル

  1. 組成: どのトランザクションからどのアドレスに送金するか、署名
  2. ブロードキャスト: ビットコインネットワークに送信
  3. 伝搬: 受け取ったノードはトランザクションを検証し、有効なら隣接ノードへ伝搬(無効なら破棄)
  4. 記録: マイニングノードによりブロックに取り込まれる

トランザクションデータ形式

固定長の16進データ。

トランザクションID

トランザクションを一意に識別する。トランザクションデータをダブルハッシュ(SHA256を2回)した値をバイトオーダー逆順にしたもの。トランザクションハッシュとも言われる。

Input(vin)

ビットコインにおけるトランザクションのInputは未使用のトランザクション(Unspent Transaction)のため、過去に自身が受け取ったトランザクションを指定する。

  • txid: 未使用のトランザクション(Unspent Transaction)のトランザクションID
  • vout: インプットトランザクション内のアウトプットインデックス(アウトプットは複数存在する可能性があるため指定が必要)

トランザクション手数料

マイナーに報酬として支払われる手数料で、トランザクションの作成者が決める。多くのウォレットでは自動的に算出してくれる。トランザクションの大きさに基づいて計算される。

マイナーは、ブロックに含めるトランザクションの優先度を手数料によって決めるため、サイズあたりの手数料を高くするとブロックに取り込まれるまでの時間が短くなる傾向がある。

トランザクションの大きさは送金額とは無関係で、データサイズによって決まる。たとえば10つのInputから1つのOutputへの送金は、1つのInputから1つのOutputへの送金よりも大きい。

Locking ScriptとUnlocking Script

Outputトランザクションは、未使用トランザクションとして、将来そのアドレスの所有者だけが使用できる。これを実現する仕組みがLocking ScriptとUnlocking Script。

OutputトランザクションにはLocking Scriptがついており、このトランザクションをInputとして使うには対応するUnlocking Scriptを書く。その際に秘密鍵を使う。

vinのscriptSigがUnlocking Script、voutのscriptPubKeyがLocking Script。これらはBitcoin Scriptで書かれており、Unlocking ScriptとLocking Scriptを左から処理(時系列ベースでの作成順はLocking→Unlockingなので逆になることに注意)した結果がTrueになればよい。

標準的なトランザクション

5つの標準的なトランザクション(Locking/Unlocking Script)がある。

Name Description
Pay-to-Public-Key-Hash(P2PKH) 標準的な送金トランザクション
Pay-to-Public-Key Coinbaseトランザクション(マイニング報酬)
データアウトプット(OP_RETURN) 支払いとは関係ないトランザクション
Multi-Signature 複数人の署名が必要なトランザクション
Pay-to-Script-Hash(P2SH) より複雑な処理

P2PKH

P2PKHでは、Locking Scriptに「受取人の公開鍵ハッシュ(アドレスから取得可能)」を記載し、受け取った人はそのトランザクションを利用する際に「自身の秘密鍵から作成した署名と公開鍵」でUnlocking Scriptを構成する。

このLocking/Unlocking Scriptをチェックすることで、公開鍵および秘密鍵を検証し、送金しようとしているユーザーが、そのトランザクションの受取人本人であることを確認する。

Pay-to-Public-Key

Locking Scriptには公開鍵ハッシュではなく公開鍵を使用する。公開鍵はアドレスだけでは導出できないため、マイナーへのマイニング報酬トランザクション(マイナーにとって送金先は自分自身なので公開鍵がわかる)で利用される。

Unlocking Scriptには自身の秘密鍵から作成した署名を使う。

データアウトプット(OP_RETURN)

ビットコインは送金ネットワークだが、タイムスタンプつき分散台帳のため、別の用途として使うニーズがあった。反対意見もあり、妥協案としてOP_RETURNコードが導入された。

OP_RETURNは80bytesで、多くの場合何かのハッシュ値のみを保存する。このトランザクションを使用するためのUnlocking Scriptは存在せず、UTXOにカウントされない。


第3回ここまで

第4回の記事を公開しました!
【東京大学ブロックチェーン公開講座第4週】ビットコイン③

Discussion