はじめに
📝 本記事は、ブロックチェーンの改ざん耐性を扱ってきたシリーズの続きです。前々回「ブロックチェーンの『ブロック』と『チェーン』って結局なに?ハッシュで図解する」でデータ構造としての改ざん検知を、前回「ブロックチェーンのPoW(Proof of Work)って結局なに?nonceを探して図解する」で改ざん防止の仕組み(PoW)を扱いました。
前々回・前回で、ブロックチェーンが「ハッシュで前後を繋いだデータ構造」であり、PoW によって「書き換えには莫大な計算コストがかかる」ことを整理しました。ここまでは、すべて 1本のチェーンを1台のマシンの上で扱う 前提の話でした。
しかし、現実のブロックチェーンには管理者が存在しません。世界中に分散した多数の参加者が、それぞれ手元にチェーンの写しを保持しています。ここで一つの疑問が生じます。中央に「正しいチェーンはこれだ」と定める主体が存在しないにもかかわらず、なぜすべての参加者が同一の1本のチェーンに合意できるのか。分散ネットワークを学ぶうえで、最も理解に苦しんだのがこの点でした。
本記事では、この「合意の仕組み」と、それを逆手に取る 51%攻撃 を扱います。
この記事を読むとわかること:
- なぜ1台のサーバーではなく、多数のノードがチェーンを持ち合うのか(非中央集権の動機)
- 複数のノードがほぼ同時にブロックを見つけると何が起きるのか(フォーク)
- 「最長チェーンルール」で、どうやって1本のチェーンに合意するのか
- 51%攻撃とは何で、何ができて何ができないのか、なぜ現実には割に合わないのか
前回までの記事を読んでいなくても追えるように書いていますが、「ハッシュで繋がると改ざんが検知される」「ブロックを作るには PoW という重い計算が要る」という2点は前提知識として使います。なお本記事では、PoS など PoW 以外のコンセンサスや、スマートコントラクトには踏み込みません。「PoW を前提に、複数のノードがどう1本に合意するか」に集中します。
なぜ「みんなで」チェーンを持つのか
前々回・前回では、ブロックチェーンを「1本のチェーン」として、しかも「1台のマシンの上」で扱ってきました。しかし現実のブロックチェーンは、世界中に散らばった大勢の参加者が、それぞれチェーンの写しを持ち合う形で動いています。まずは「なぜ1台に任せず、わざわざ大勢で持つのか」をはっきりさせます。
1台のサーバーに任せると何が問題か
仮に、ある銀行のような中央サーバーが1台あって、そこだけがチェーンを管理しているとします。この構成には2つの弱点があります。
- 単一障害点: そのサーバーが故障・停止・攻撃されると、システム全体が止まります。記録が失われる可能性もあります。
- 管理者を信頼するしかない: そのサーバーの管理者は、ひそかに残高を書き換えたり、特定の取引を握りつぶしたり、自分に都合よくコインを発行したりできてしまいます。利用者には、それを防ぐ手段がありません。
前々回・前回で見た「ハッシュ」と「PoW」は、改ざんを検知し、書き換えを高コストにする仕組みでした。しかし、チェーンを保管している場所が1か所しかなければ、その1か所が丸ごと差し替えられたとき、利用者は「どちらが正しいチェーンか」を判断するよりどころを持ちません。
解決策: 全員が同じチェーンの写しを持つ
ブロックチェーンでは、特定の管理者を置かず、ネットワークに参加する多数のノードが、それぞれチェーンの完全な写しを持つ、という構成を取ります。ここでいう ノード とは、チェーンの写しを保持し、ネットワークに参加しているコンピューターのことです。現実には、採掘を行わず検証と中継だけを担うノードもありますが、本記事では「採掘も行うノード」として扱います。
全員が同じ写しを持っていれば、
- 1台のノードが落ちても、他のノードが同じチェーンを持っているのでネットワークは動き続ける(単一障害点がない)
- 1台のノードが嘘のチェーンを主張しても、他の多数のノードが持つ正しい写しと食い違うため、不正がすぐに明らかになる(管理者を信頼しなくてよい)
という状態になります。このように、「正しさ」の担保を1人の管理者ではなく ネットワーク全体の多数のノード に分散させることが、非中央集権の狙いです。
ノード同士はブロックを「伝播」させ合う
では、新しいブロックはどうやって全ノードに行き渡るのでしょうか。中央のサーバーがないので、ノード同士が直接つながり合う P2P(ピア・ツー・ピア) ネットワークで、ブロックや取引を 伝播(でんぱ) させます。
あるノードが新しいブロックを採掘(または受信)すると、つながっている隣のノードへそれを転送します。受け取ったノードは、ブロックが正当か(ハッシュが整合し、PoW を満たしているか)を自分で検証したうえで、さらに隣へ転送します。これがバケツリレーのように繰り返され、ブロックがネットワーク全体に広がっていきます。
図1: 新しいブロックがP2Pネットワークを伝播していく
ここで重要なのは、各ノードが「自分で検証してから受け入れる」 という点です。前々回・前回で見た検証(ハッシュの整合・PoW の充足・前ブロックとのリンク)を一台ごとに行うので、嘘のブロックを混ぜ込もうとしても検証を通らずに弾かれます。
ただし、この構成には1つ副作用があります。管理者がいないということは、「次の1個のブロックを誰が決めるか」を仕切る人もいない ということです。複数のノードがほぼ同時にブロックを見つけたら何が起きるのか。それが次章のテーマです。
フォーク:チェーンが枝分かれする瞬間
前章の最後で触れた「次の1個のブロックを誰が決めるか」という問題を掘り下げます。結論から言うと、チェーンは一時的に枝分かれします。この枝分かれを フォーク(fork) と呼びます。
なぜ枝分かれが起きるのか
前回見たとおり、ブロックの採掘は「条件を満たすハッシュを最初に見つけたノードが勝つ」という早い者勝ちの競争でした。世界中のノードが、同じ高さの次のブロックを同時に探しています。
ここで効いてくるのが、前章の 伝播には時間がかかる という性質です。あるノードXが採掘に成功しても、そのブロックがネットワークの全ノードに届くまでには、わずかとはいえ時間差があります。その時間差のあいだに、別のノードYが、Xのブロックをまだ受け取らないまま、自分のブロックを見つけてしまう ことがあります。
このとき、X も Y も自分が見つけたブロックを正しい次の1個だと思っています。両者とも PoW を正しく解いた、検証を通る正当なブロックで、どちらかが不正をしたわけではありません。
同じ高さに、異なるブロックが2つ並ぶ
結果として、同じブロックの次に、内容の異なるブロックが2つ並ぶ状態 が生まれます。この枝分かれはチェーンのどの位置でも起こりえますが、本記事ではミニ実装に合わせて、ジェネシスブロック(ブロック0)の直後で起きたケースを例にします。ノードXが見つけたほうを ブロック1、ノードYが見つけたほうを ブロック1' と呼ぶことにします。どちらも高さは同じ1です。
図2: 同時採掘でチェーンが枝分かれする(フォーク)
この瞬間、ネットワークは2つの陣営に分かれます。各ノードは 先に届いたほうのブロック を自分のチェーンの先端に繋ぎます。そのため、ブロック1が先に届いたノードはブロック1を先端として持ち、ブロック1'が先に届いたノードはブロック1'を先端として持ちます。同じネットワークなのに、参加者によって手元のチェーンの先端が違う、という状態です。
ここで強調しておきたいのは、フォークは 攻撃でも異常でもなく、分散ネットワークで自然に起きる現象 だということです。管理者がいない以上、「次の1個」を一意に決める瞬間的な方法は存在せず、伝播の時間差がある限りフォークは避けられません。
どちらのチェーンを正とするか
フォークが起きたままでは、「AがBに100円送った」のか「EがFに40円送った」のか、ネットワークとして1つの履歴に定まりません。放置すれば枝はさらに伸び、2本の歴史が並行して育ってしまいます。
分散ネットワークには、この枝分かれを 後から1本に収束させる ルールが必要です。しかも、管理者なしで、各ノードが同じ基準で同じ結論にたどり着けるものでなければなりません。それが次章の 最長チェーンルール です。
最長チェーンルールで1本に決める
フォークで枝分かれしたチェーンを、管理者なしで1本に収束させる仕組みが 最長チェーンルール です。本記事の前半の山場にあたります。
ルールはシンプル: 「一番長いチェーンを正とする」
各ノードは、次の1つのルールに従います。
自分が知っているなかで最も長いチェーンを「正しいチェーン」とみなし、それより長い正当なチェーンを受け取ったら、そちらに乗り換える。
フォークが起きて2本の枝が並んでも、やがてどちらかの枝に次のブロックが先に積まれます。すると、その枝のほうが長くなります。全ノードが「長いほうを選ぶ」という同じ基準を持っているので、短いほうの枝を持っていたノードも、長いほうへ乗り換えます。こうして、ネットワークは再び1本のチェーンに収束します。
図3: 片方の枝が伸び、フォークが解消される
ノードXの枝は高さ2まで伸びました。一方ノードYの枝は高さ1のままです。最長チェーンルールにより、ネットワーク全体がXの枝(高さ2)を正しいチェーンとして採用し、Yの枝は取り残されます。
正確には「最も長い」ではなく「最も仕事が積まれた」チェーン
ここで、よく使われる「最長チェーン」という言い方には、少し注意が必要です。本当の基準は 「最も多くの仕事(PoW)が積み上がったチェーン」 です。「最長」は、その近似的な表現にすぎません。
前回見たように、1つのブロックを作るには難易度に応じた量の計算(仕事)が必要でした。難易度がずっと同じなら「ブロックの数が多い = 積まれた仕事の総量が多い」となるので、「最長 = 最も仕事が多い」が成り立ちます。しかし現実の難易度は2016ブロックごと(約2週間)に調整され、一定ではありません。そのため実際のブロックチェーンは、本数ではなく、各ブロックの難易度を足し合わせた 「累積の仕事量」が最大のチェーン を正とします。
本記事のミニ実装では難易度を一定にしているため、「最長 = 最も仕事が多い」が単純に成り立ちます。ただし、頭の中では常に 「長さ」ではなく「積まれた仕事の量」で勝敗が決まる と置き換えておくと、次章の51%攻撃が理解しやすくなります。
負けた枝のブロックはどうなるか(孤立ブロック)
最長チェーンルールで取り残された枝のブロックを、孤立ブロック(orphan block / stale block) と呼びます。図3でいえば、ノードYのブロック1'(EがFに40円送った)がこれにあたります。
孤立ブロックは、正しいチェーンから外れて捨てられます。そのため、
- そのブロックに含まれていた取引は、いったん 「なかったこと」になる
- そのブロックを採掘したノードは、膨大な計算の末に採掘に成功したにもかかわらず、報酬を得られません
📝 「なかったこと」になるといっても、取引データ自体が消えるわけではありません。取引はブロックとは独立した署名済みのデータです。勝った枝に同じ取引が含まれていればその時点で確定済みであり(同時採掘では両者が同じ未確定取引から選ぶため、こうなる場合が大半です)、含まれていなければ未確定の取引の待機場所(プール)に戻り、後続のブロックであらためて取り込まれます。例外はブロック報酬の取引です。これはブロックの中で新規に生成されるため戻り先が存在せず、ブロックとともに消滅します。2つ目に挙げた「報酬を得られない」とは、この消滅を指しています。
採掘した本人にとっては、運悪く競争に負けただけで不正をしたわけではありません。それでも仕事が無駄になる点が分散ネットワークの厳しさであり、後で見る51%攻撃のコストにもつながります。
取引の確定: コンファメーション
最長チェーンルールには、もう1つ大事な帰結があります。「自分の取引が含まれたブロックの後ろに、ブロックが積み増されるほど、その取引は覆りにくくなる」 ということです。
取引がブロックに取り込まれた時点を「1コンファメーション(confirmation、承認)」と数え、その後ろにブロックが1個積まれるごとに「2コンファメーション」「3コンファメーション」と増えていきます。
図4: 後ろに積まれるほど、取引は覆りにくくなる
覆りにくくなる理由は、その取引を「なかったこと」にするには、その取引を含むブロックを別のブロックに差し替え、さらに 後ろに積まれた全ブロックを作り直したうえで、正規のチェーンより長くしなければならない からです。後ろのブロックが増えるほど、作り直すべき PoW の量が積み上がり、追い越しは現実離れしていきます。
そのため実際のブロックチェーンでは、「何コンファメーション積まれたら確定とみなすか」という運用上の目安があります(Bitcoin では6コンファメーションがよく使われます)。これは 「絶対に覆らない」ではなく「覆すコストが現実離れするほど大きくなった」という確率的な確定 である点に注意が必要です。コンファメーションが増えるほど覆る確率は限りなくゼロに近づきますが、原理的にゼロにはなりません。
この「積まれた仕事の量で勝敗が決まる」「後ろに積むほど覆りにくい」という性質を逆手に取ると、何が起こせるのか。それを突き詰めたのが、次章の 51%攻撃 です。
51%攻撃の正体
最長チェーンルールは「最も多くの仕事が積まれたチェーンが勝つ」という仕組みでした。ここで一つの疑問が生じます。もし1者が、残りの参加者全員を上回る計算能力を持っていたら、どうなるのか。これが 51%攻撃 です。
なぜ「51%(過半数)」なのか
51%攻撃とは、1者(または結託した集団)が、ネットワーク全体の過半数の計算能力(ハッシュレート)を握る ことを指します。
過半数が境目になるのは、最長チェーンルールが仕事量の勝負だからです。攻撃者が全体の過半数の計算力を持てば、残り全員が積む仕事よりも速いペースで、自分だけのチェーンに仕事を積み続けられます。すると、正規のチェーンを 追い越したいときにいつでも追い越せる 状態になります。最長チェーンルールのもとでは、追い越して一番長く(=仕事が多く)なったチェーンが正になるため、攻撃者は自分の都合のよいチェーンをネットワークに受け入れさせられます。
できること: 二重支払い
過半数の計算力で実際に何ができるのか。代表的なのが 二重支払い(double spend) です。同じコインを2回使う攻撃で、手順はおおよそ次のとおりです。
- 攻撃者が、あるショップへの支払い取引を行う。この取引は正規のチェーンに取り込まれ、ショップは数コンファメーションを確認して商品を渡す
- 攻撃者は裏で、その支払い取引を含まない別の枝 を、誰にも公開せずに採掘し続ける
- 過半数の計算力があるので、秘密の枝はやがて正規のチェーンより長く(仕事が多く)なる
- 攻撃者が秘密の枝を公開すると、最長チェーンルールにより全ノードがそちらへ乗り換える。支払い取引を含んでいた元のブロックは孤立ブロックになり、支払いはなかったこと になる
結果として、攻撃者は商品を受け取りながら、支払ったはずのコインを手元に戻せてしまいます。
図5: 二重支払い(秘密の枝が正規チェーンを追い越す)
ショップが商品を渡す前に待つコンファメーション数が多いほど、攻撃者は長い秘密の枝を用意しなければならず、攻撃は難しくなります。前章で「確認数が多いほど覆りにくい」と言ったのは、まさにこの攻撃に対する備えです。
できないこと: 他人のコインの窃取やルールの改変
51%攻撃は強力ですが、過半数の計算力さえあれば何でもできる、という誤解は禁物 です。できないことのほうが、むしろ本質を表しています。
- 他人のコインを盗むことはできない: 取引には、その持ち主の秘密鍵による電子署名が必要です。攻撃者は他人の秘密鍵を持たないため、他人の残高を動かす取引を作れません。署名のない(または偽の)取引は、計算力とは無関係に、各ノードの検証で弾かれます
- 存在しないコインを発行したり、ルールを破ったりできない: 報酬の額をごまかすなど、ルール違反のブロックは、各ノードが検証して拒否します。攻撃者が積んだチェーンであっても、不正なブロックは受け入れられません
- 遠い過去を書き換えるのは事実上不可能: 深く埋まったブロックを覆すには、そこから現在まで積まれた全ブロックを再採掘して追い越す必要があり、必要な仕事が天文学的になります
つまり攻撃者にできるのは、自分が関与した取引を取り消したり、特定の取引をブロックに入れず検閲したりする ことが中心です。署名で守られた他人の資産や、ネットワークのルールそのものは、過半数の計算力をもってしても壊せません。
なぜ現実には割に合わないのか
最大の防壁は、技術ではなく 経済合理性 です。
- 取得コストが莫大: Bitcoin 規模のネットワークで過半数のハッシュレートを揃えるには、膨大な採掘機材と電力が要り、その費用は現実離れした規模になります
- 成功しても自滅しやすい: 二重支払いが発覚すれば、その通貨の信頼は失われ、価格は暴落します。攻撃者自身が抱える大量の採掘設備や保有コインの価値も一緒に下がるため、攻撃の成功が攻撃者自身の損失に直結します
- 正直にやるほうが儲かる: 同じ計算力を正規の採掘に使えば、ブロック報酬を得られます。PoW は「正直でいることが最も得」になるよう設計されており、攻撃するインセンティブが働きにくくなっています
前回、PoW は「改ざんを不可能にする魔法ではなく、割に合わないほど高コストにする仕組み」だと整理しました。51%攻撃に対する答えも同じ線引きです。理論上は過半数の計算力で二重支払いが可能でも、それを実行するのは経済的に引き合わない。この線引きにブロックチェーンのセキュリティの本質があります。
Python によるミニ実装
ここまでの「フォーク → 最長チェーンで収束 → 孤立ブロック」を、最小構成で実装して確かめます。ブロック自体は前回のものをそのまま使い、新しく「ノード」を表すクラスを足すだけです。
前回のブロックをそのまま使う
PoW つきのブロックは、前回作ったものと完全に同じです。先頭にゼロが difficulty 個並ぶハッシュになるまで nonce を回して採掘します。
import hashlib
DIFFICULTY = 4 # 先頭にゼロがこの数だけ並ぶハッシュを探す
class Block:
def __init__(self, data: str, previous_hash: str, difficulty: int = DIFFICULTY):
self.data = data
self.previous_hash = previous_hash
self.difficulty = difficulty
self.nonce = 0
self.hash = self.mine()
def calc_hash(self) -> str:
# データ + 前のハッシュ + nonce をまとめてSHA-256でハッシュ化
body = self.data + self.previous_hash + str(self.nonce)
return hashlib.sha256(body.encode()).hexdigest()
def mine(self) -> str:
# 先頭にゼロが difficulty 個並ぶハッシュになるまで nonce を回す
target = "0" * self.difficulty
self.nonce = 0
while True:
h = self.calc_hash()
if h.startswith(target):
return h
self.nonce += 1
チェーンの検証も前回と同じ3つのチェック(ハッシュの整合・PoW の充足・前ブロックとのリンク)です。今回は他のメソッドから呼ぶので、真偽だけを返す形にまとめます。
def is_chain_valid(chain: list[Block]) -> bool:
for i, block in enumerate(chain):
target = "0" * block.difficulty
if block.hash != block.calc_hash():
return False
if not block.hash.startswith(target):
return False
if i > 0 and block.previous_hash != chain[i - 1].hash:
return False
return True
ノードを表すクラスを足す
新しいのは、チェーンの写しを持つ ノード です。ノードができることは2つだけです。「自分のチェーンの先端に新しいブロックを採掘して足す」ことと、「他のノードからチェーンを受け取り、それが自分のより長く正当なら乗り換える」ことです。後者が 最長チェーンルール そのものです。
class Node:
def __init__(self, name: str, chain: list[Block]):
self.name = name
self.chain = chain
def mine_block(self, data: str) -> Block:
# 自分のチェーンの先端に、新しいブロックを採掘して足す
block = Block(data, previous_hash=self.chain[-1].hash)
self.chain.append(block)
return block
def receive_chain(self, incoming: list[Block]) -> list[Block]:
# 最長チェーンルール: 受け取ったチェーンが自分のより長く、かつ正当なら乗り換える
if len(incoming) > len(self.chain) and is_chain_valid(incoming):
orphaned = [b for b in self.chain[1:] if b.hash not in {x.hash for x in incoming}]
self.chain = list(incoming)
return orphaned
return []
receive_chain() は、受け取ったチェーンが「より長い」かつ「検証を通る」ときだけ乗り換え、そのとき自分の枝から外れたブロック(= 孤立ブロック)のリストを返します。難易度を一定にしているので、ここでは「長さ」がそのまま「積まれた仕事の量」を表します。
なお、受け取ったチェーンが自分のチェーンと 同じ長さ の場合は乗り換えません。フォークの最中に各ノードが自分の枝を持ち続けるのは、この「先に知ったブロックを保持する」挙動によるものです。
フォークから収束までを再現する
準備として、全ノードが同じジェネシスブロックを共有します。その上で、2つのノードがほぼ同時に別のブロックを採掘し、片方が伸び、もう片方が乗り換える、という流れを再現します。
genesis = Block("最初の取引", previous_hash="0") # ブロック0(ジェネシス)
node_x = Node("ノードX", [genesis])
node_y = Node("ノードY", [genesis])
# --- シナリオ1: 2ノードがほぼ同時に別のブロックを採掘 → フォーク ---
x1 = node_x.mine_block("AがBに100円送った") # ノードX が見つけたブロック1
y1 = node_y.mine_block("EがFに40円送った") # ノードY が見つけたブロック1'
# --- シナリオ2: ノードX が次のブロックを先に見つけ、片方の枝が伸びる ---
x2 = node_x.mine_block("BがCに50円送った") # ノードX のブロック2
# --- シナリオ3: ノードY が長いチェーンを受信し、最長チェーンに乗り換える ---
orphaned = node_y.receive_chain(node_x.chain)
実行結果(準備):
=== 準備: 共有するジェネシスブロック(難易度4)===
genesis: nonce=15481(15482回試行) hash=000006eef6ed...
ジェネシスの nonce とハッシュ(000006eef6ed...)は、前回の採掘結果とそのまま一致しています。
実行結果(シナリオ1: フォーク):
=== シナリオ1: ほぼ同時の採掘でチェーンが枝分かれする(フォーク)===
ノードX のブロック1 : nonce=99644(99645回試行) hash=00006cdc5472... data='AがBに100円送った'
ノードY のブロック1': nonce=30944(30945回試行) hash=0000cb7ef72a... data='EがFに40円送った'
→ 同じ高さ1に、異なるブロックが2つ存在する状態(フォーク)
両ノードとも高さ1まで採掘できていますが、先端のハッシュが違います(00006cdc5472... と 0000cb7ef72a...)。これがフォークの状態です。
実行結果(シナリオ2: 片方が伸びる):
=== シナリオ2: ノードX が次のブロックを先に採掘し、枝が伸びる ===
ノードX のブロック2 : nonce=121103(121104回試行) hash=0000954ed529... data='BがCに50円送った'
ノードX が次のブロックを先に見つけ、高さ2まで伸びました。ノードY はまだ高さ1のままなので、長さ(= 積まれた仕事の量)で差がつきました。
実行結果(シナリオ3: 最長チェーンに収束):
=== シナリオ3: ノードY が最長チェーンに乗り換える(孤立ブロックの発生)===
乗り換え後:
ノードY: 高さ2 先端hash=0000954ed529...
最初の取引 -> AがBに100円送った -> BがCに50円送った
孤立ブロック(orphan): hash=0000cb7ef72a... data='EがFに40円送った' → チェーンから外れた
ネットワークは1本のチェーンに収束(X と Y の先端hashが一致: True)
ノードY は、より長いノードX のチェーンを受け取り、最長チェーンルールで乗り換えました。両ノードの先端ハッシュは 0000954ed529... で一致し、ネットワークは1本に収束しています。そして、ノードY が採掘したブロック1'(0000cb7ef72a... / EがFに40円送った)は、競争に負けた孤立ブロックとして外れました。
わずか数十行で、「フォークは自然に起きるが、最長チェーンルールで後から1本に収束し、負けた枝は孤立する」という分散ネットワークの合意の核を再現できました。
ミニ実装と現実のネットワークの違い
ミニ実装で合意の核は押さえられましたが、現実のネットワークはもう少し作り込まれています。差を整理しておきます。
| 項目 | 本記事のミニ実装 | 現実の Bitcoin など |
|---|---|---|
| ノード数 | 2つ | 世界中に数千〜数万 |
| ブロックの伝播 | 関数呼び出しで即座に渡す | P2P でネットワーク越しに伝播(遅延があり、これがフォークの原因) |
| 最長の判定基準 | チェーンの長さ(ブロック数) | 累積の仕事量(各ブロックの難易度の総和) |
| 難易度 | 一定(4) |
2016ブロックごと(約2週間)に自動調整 |
| 取引の中身 | 文字列1つ | 電子署名つきトランザクションの集合 |
| 取引の確定 | なし(長さだけ見る) | コンファメーション数で確率的に確定 |
特に効いてくる違いが2つあります。1つは 伝播の遅延 です。ミニ実装ではブロックを関数呼び出しで一瞬で渡しましたが、現実では世界中のノードに届くまでに時間差があり、これがフォークを日常的に生みます。もう1つは 最長の判定基準 です。難易度を一定にした本記事では「長さ = 仕事量」でしたが、現実は難易度が変動するため、ブロックの本数ではなく累積の仕事量で勝敗を決めます。
ただし、これらの違いがあっても、「各ノードが、最も多くの仕事が積まれたチェーンを選ぶ」ことで、管理者なしに1本へ収束する というコアはまったく同じです。本記事のミニ実装は、そこから伝播の遅延・難易度調整・署名といった肉付けを削った最小版にあたります。
ここまでで触れなかったこと
本記事は「PoW を前提に、複数のノードがどう1本に合意するか」に絞ったため、以下は意図的に省きました。いずれも、最長チェーンによる合意の先に広がるテーマです。
- PoW 以外のコンセンサス(PoS など): 計算量ではなくコインの保有量などで次のブロックを作る人を選ぶ仕組みです。イーサリアムは PoW から PoS へ移行しました。合意の取り方が変わると、51%攻撃に相当する攻撃の形も変わります
- ファイナリティ: 本記事の確定は「コンファメーションが増えるほど覆りにくい」という確率的なものでした。PoS や BFT 系のなかには、「ここから先は覆らない」という、より強い確定(ファイナリティ)を持つ仕組みもあります
- ネットワーク層の攻撃: 偽のノードを大量に作る Sybil 攻撃、特定ノードの接続を乗っ取る Eclipse 攻撃、見つけたブロックをわざと隠すセルフィッシュマイニングなど、計算能力の過半数を握らないタイプの攻撃もあります
- 経済設計の細部: ブロック報酬の半減期や取引手数料の市場など、マイナーのインセンティブを支える設計には踏み込みませんでした
まとめ
ここまでの内容をまとめると、
- 分散ネットワーク は、単一障害点をなくし、特定の管理者を信頼せずに済ませるための仕組み。多数のノードがチェーンの写しを持ち合い、各自で検証する
- フォーク は、伝播の時間差で複数のノードがほぼ同時にブロックを見つけたときに起きる、自然な枝分かれ
- 最長チェーンルール は、フォークを1本に収束させる基準。正確には「最も多くの仕事(PoW)が積まれたチェーン」を正とする。負けた枝のブロックは孤立ブロックになる
- コンファメーション は、取引が確定していく度合い。後ろにブロックが積まれるほど、覆すのに必要な再採掘が増え、覆りにくくなる
- 51%攻撃 は、過半数の計算能力で二重支払いを狙う攻撃。一方で、他人のコインを盗んだりルールを破ったりはできず、しかも経済的に割に合わない
「中央に決める人がいないのに、なぜ全員が同じ1本のチェーンに合意できるのか」という問いの答えは、「各ノードが、最も多くの仕事が積まれたチェーンを選ぶ」という同じルールを共有しているから でした。合意は多数決ではなく、積み上げた仕事量で決まります。51%攻撃は、その仕事量を1者が独占したときに起きる綻びですが、それを実行するコストが、攻撃の旨味を上回るように設計されています。
これまで、ブロックチェーンが「なぜ改ざんできないのか」を、ハッシュで繋ぐデータ構造(改ざんの検知)、PoW(改ざんの防止)、そして本記事の 多数のノードによる合意(正しい1本の決定) という角度から見てきました。これらが重なって、改ざん耐性は単一の仕組みではなく、複数の仕組みの積み重ねとして成り立っています。
最後までお読みいただきありがとうございました。