ブロックチェーン学習ロードマップ

ブロックチェーンのデータ構造:ハッシュポインタと改ざん耐性を理解する

Tags: ブロックチェーン, データ構造, ハッシュ関数, ハッシュポインタ, 改ざん耐性, 基礎

ブロックチェーンのデータ構造がなぜ重要なのか

ブロックチェーン技術が注目される理由の一つに、その高い「改ざん耐性」があります。一度記録されたデータは容易に変更できない、あるいは変更されたことがすぐに検知できるという性質は、多くの応用分野で信頼性の基盤となります。この改ざん耐性を支えている技術的な根幹こそが、ブロックチェーンの独特なデータ構造です。

私たちは普段、データベースやファイルシステムなど、様々な方法でデータを管理しています。これらのシステムでは、特定の権限を持つユーザーやシステムによって、データの追加、更新、削除が行われることが一般的です。しかし、ブロックチェーンでは、データは「ブロック」という単位で管理され、それらのブロックが鎖のように連結されていく構造になっています。この連結方法に、他のデータ構造にはない工夫が凝らされており、それが改ざんを極めて困難にしています。

この章では、ブロックチェーンが採用しているデータ構造の基本を、皆さんが既にご存知であろうデータ構造(例えばリンクリスト)と比較しながら解説し、なぜこの構造が改ざん耐性を生むのかを技術的な視点から深く掘り下げていきます。

従来のリンクリストとブロックチェーンの類似点・相違点

プログラミング経験がある方であれば、「リンクリスト」というデータ構造をご存知かと思います。リンクリストは、データを保持する「ノード」が、次のノードへの参照(ポインタ)を持つことで、複数のノードを線形に連結した構造です。

例えば、以下のような構造です。

[データA | 次のノードへのポインタ] ---> [データB | 次のノードへのポインタ] ---> [データC | 次のノードへのポインタ] ---> NULL

この構造により、データの追加や削除を効率的に行える場合があります。

ブロックチェーンも、基本的にはこのようにブロックが前のブロックを参照することで、一連のブロックが鎖状につながっている構造です。しかし、ブロックチェーンが使用するのは単なる「ポインタ」ではなく、「ハッシュポインタ」と呼ばれる特殊なポインタです。

ブロックチェーンにおける「ブロック」とは

ブロックチェーンを構成する最小単位が「ブロック」です。各ブロックには、いくつかの重要な情報が含まれています。

構造を模式的に示すと、以下のようになります。

ブロック #1:
  データ: [トランザクション1, ...]
  前のブロックのハッシュ: (最初のブロックなので無し、特別な値)
  その他の情報: ...

ブロック #2:
  データ: [トランザクションN, ...]
  前のブロックのハッシュ: (ブロック #1 全体のハッシュ値)
  その他の情報: ...

ブロック #3:
  データ: [トランザクションM, ...]
  前のブロックのハッシュ: (ブロック #2 全体のハッシュ値)
  その他の情報: ...

このように、各ブロックは直前のブロックのハッシュ値を参照することで連結されています。

ハッシュ関数の役割と「ハッシュポインタ」

ここで鍵となるのが「ハッシュ関数」です。ハッシュ関数は、任意の長さの入力データを受け取り、固定長の短い文字列(ハッシュ値)を出力する関数です。暗号学的に安全なハッシュ関数(SHA-256など)には、以下の重要な性質があります。

  1. 一方向性: ハッシュ値から元のデータを効率的に復元することは非常に困難です。
  2. 衝突耐性: 異なる入力から全く同じハッシュ値が得られる可能性は極めて低い(事実上不可能とみなせる)です。
  3. 微細な変化に対する敏感性: 入力データがほんのわずかでも変化すると、出力されるハッシュ値は全く異なるものになります。

ブロックチェーンにおける「ハッシュポインタ」とは、単に前のブロックのアドレスを指すのではなく、「前のブロック全体のデータに対するハッシュ値」のことです。

つまり、ブロックチェーンは以下のように連結されています。

[データ | ハッシュ(前のブロック)] ---> [データ | ハッシュ(前のブロック)] ---> [データ | ハッシュ(前のブロック)]

各ブロックは自身の持つデータと、直前のブロック全体のハッシュ値を使って、自身のハッシュ値も計算します。そして、次のブロックは、現在のブロックのハッシュ値を「前のブロックのハッシュ」として記録するのです。

ハッシュポインタがいかに改ざん耐性を生むのか

このハッシュポインタによる連結が、ブロックチェーンの改ざん耐性の核心です。

あるブロック(例えばブロック #N)に格納されているデータを誰かが改ざんしようとしたとします。ブロック #N のデータが変更されると、ハッシュ関数の微細な変化に対する敏感性により、ブロック #N 全体のハッシュ値が全く別のものに変わってしまいます。

[データ(改ざん済み) | ハッシュ(前のブロック)] <-- ブロック #N

ブロック #N のハッシュ値が変わると、次のブロック(ブロック #N+1)が保持している「前のブロック(ブロック #N)のハッシュ値」と一致しなくなります。

ブロック #N (改ざん後):
  データ: [改ざんされたトランザクション, ...]
  前のブロックのハッシュ: (ブロック #N-1 のハッシュ値)
  **自身のハッシュ値: (改ざんによって変化した値)**

ブロック #N+1 (改ざん前):
  データ: [...]
  **前のブロックのハッシュ: (ブロック #N の**改ざん前の**ハッシュ値)**
  自身のハッシュ値: (...)

ブロック #N+1 は、前のブロックとして期待されるハッシュ値と、実際に前のブロック(ブロック #N)から計算されるハッシュ値が異なることを検知できます。これにより、「このブロックチェーンは途中で改ざんされている可能性がある」と判断できるのです。

改ざんを隠蔽するためには、ブロック #N のデータを改ざんした後、その結果として変わってしまったブロック #N の新しいハッシュ値を計算し直し、さらにブロック #N+1 が持つ「前のブロックのハッシュ値」も新しい値に書き換える必要があります。

しかし、ブロック #N+1 の情報(前のブロックのハッシュ値を含む)が変更されると、今度はブロック #N+1 全体のハッシュ値が変わってしまいます。すると、さらに次のブロック(ブロック #N+2)が持つ「前のブロック(ブロック #N+1)のハッシュ値」との不一致が生じます。

結果として、過去のブロックの一つを改ざんしようとすると、それ以降に連なる全てのブロックのハッシュ値を計算し直し、それぞれの「前のブロックのハッシュ」フィールドを更新し続けるという、連鎖的な作業が必要になります。

改ざん: ブロック #N のデータ変更
  ↓ ブロック #N のハッシュ値が変化
  ↓ ブロック #N+1 が前のブロックのハッシュ値と一致しない
  ↓ 隠蔽のためにブロック #N+1 の「前のブロックのハッシュ」を更新
  ↓ ブロック #N+1 のハッシュ値が変化
  ↓ ブロック #N+2 が前のブロックのハッシュ値と一致しない
  ↓ 隠蔽のためにブロック #N+2 の「前のブロックのハッシュ」を更新
  ↓ ...この連鎖がブロックチェーンの末尾まで続く

実装のイメージ:ハッシュ計算と連結

簡単な疑似コードで、この概念をイメージしてみましょう。

import hashlib
import json
import time

class Block:
    def __init__(self, index, transactions, previous_hash, timestamp=None):
        self.index = index
        self.transactions = transactions
        self.timestamp = timestamp or time.time()
        self.previous_hash = previous_hash # これがハッシュポインタ
        self.hash = self.calculate_hash() # 自身のハッシュ値を計算

    def calculate_hash(self):
        # ブロックの内容を文字列化し、ハッシュ関数に入力
        block_string = json.dumps({
            "index": self.index,
            "transactions": self.transactions,
            "timestamp": self.timestamp,
            "previous_hash": self.previous_hash
        }, sort_keys=True).encode() # 内容が完全に同じなら同じ文字列になるようソート
        return hashlib.sha256(block_string).hexdigest()

# ブロックチェーンの作成
# 最初のブロック(ジェネシスブロック)は前のハッシュ値がない
genesis_block = Block(0, ["トランザクションA"], "0")

# 次のブロックはジェネシスブロックのハッシュ値を参照
block1 = Block(1, ["トランザクションB"], genesis_block.hash)

# さらに次のブロックはblock1のハッシュ値を参照
block2 = Block(2, ["トランザクションC"], block1.hash)

# チェーンの状態を確認
print(f"Genesis Block Hash: {genesis_block.hash}")
print(f"Block 1 Previous Hash: {block1.previous_hash}, Block 1 Hash: {block1.hash}")
print(f"Block 2 Previous Hash: {block2.previous_hash}, Block 2 Hash: {block2.hash}")

# もし Block 1 のデータを改ざんしたら?
# (実際にはオブジェクトを直接書き換えるのではなく、新しいブロックを作成することになる)
# block1.transactions = ["改ざんされたトランザクションB"]
# block1.hash = block1.calculate_hash() # ハッシュ値が変わる!

# print(f"Block 1 (改ざん後) Hash: {block1.hash}")
# print(f"Block 2 Previous Hash (参照している値): {block2.previous_hash}") # 元のハッシュ値のまま

# Block 2 は Block 1 の改ざんに気づく!
# ブロックチェーンでは、チェーンの検証時に各ブロックのハッシュ値を計算し、
# 次のブロックが保持しているハッシュ値と比較することで改ざんを検知します。

(上記は概念理解のための簡易的な疑似コードであり、実際のブロックチェーン実装はより複雑です。)

この例からわかるように、各ブロックは前のブロックの内容に依存しています。前のブロックの内容(データやその他の情報)が変われば、そのハッシュ値も変わり、その影響が次のブロック、さらに次のブロックへと波及していくのです。

改ざん耐性だけではない側面

ハッシュポインタによる連結は、データの改ざん耐性を提供するだけでなく、「データの履歴」を容易に追跡できるという利点もあります。ブロックチェーンの末尾から先頭に向かってハッシュポインタをたどっていくことで、データがどのように追加されてきたかの完全な記録を確認できます。これは、なぜブロックチェーンが「分散型台帳」と呼ばれるのか、その一因でもあります。

まとめと次のステップ

ブロックチェーンのデータ構造は、単にデータを線形につなげたものではなく、前のブロック全体のハッシュ値をポインタとして利用する「ハッシュポインタ」を採用しています。この仕組みにより、過去のブロックを改ざんしようとすると、それ以降の全てのブロックのハッシュ値が芋づる式に無効となり、改ざんの試みが容易に検知できるようになっています。これが、ブロックチェーンの高い改ざん耐性の技術的な根拠の一つです。

Webエンジニアとして、データベースの整合性やAPI連携におけるデータの検証に馴染みがある方にとって、この「ハッシュ値による連結と検証」という考え方は、ブロックチェーンのセキュリティモデルを理解する上で非常に強力な出発点となるはずです。

次にブロックチェーンを学ぶ際には、この「ブロック」がどのように生成され(マイニングやステーキング)、分散されたネットワーク上でどのように合意が形成されるのか(コンセンサスアルゴリズム)といったトピックに進むと、ブロックチェーンの全体像がより明確になるでしょう。また、実際に簡単なブロックチェーンをPythonなどで実装してみることも、理解を深める上で非常に有効です。