ハッシュ関数は、任意のデータから別の値を得るための関数のことです。ハッシュ関数から得られた値をハッシュ値または単にハッシュといいます。長い文字列であっても短い固定長の文字列に変換することができるのです。今回はハッシュ関数をつかってマークル木を実装します。

ハッシュ関数の実装とオーバーフロー対策

その前提としてハッシュ関数を実装します。ハッシュ関数は以下を用いることにします。

H(s) = (s の 1 文字目の文字コード * Bm-1 + s の 2 文字目の文字コード * Bm-2 + … + s の m 文字目の文字コード * B0) % mod(ただし、B = 100,000,007, mod = 1,000,000,007)

ハッシュ値を出力させたいのですが、以下のコードはうまくいきません。オーバーフロー(桁あふれ)がおきるからです。

合同式とその性質

割り算の余りが等しいことを表現した式を合同式といいます。

例えば7を3で割るとあまりは1です。10を3で割ってもあまりは1で同じです。このようなとき 7≡10(mod3)と書きます。

a ≡ b(mod m) のとき以下のような性質があります。

足し算引き算かけ算の場合は普通の数と同じような性質があります。割り算の場合は無条件では成り立ちません。

a と m が互いに素という条件が満たす場合に限り、ax ≡ ay ならば x ≡ y が成り立ちます(逆も成り立つ)。

ということで剰余を計算するときは以下が成り立ちます。

(a + c) % m = ((a % m) + c) % m
(a * c) % m = ((a % m) * c) % m

これを使うと以下の方法でオーバーフローすることなくハッシュ値を計算することができます。

出力:477702323

データの検証とマークル木

ハッシュ関数を用いることでデータの検証が可能になります。そのひとつがマークル木(マークルツリー)です。マークル木はノードにハッシュ値をもつ二分木で、それぞれ親ノードには子ノードのハッシュ値を結合したものから得られるハッシュ値が割り当てられています。そして木構造のルート部分のハッシュ値をルートハッシュと呼びます。

これらのハッシュ値をそれぞれ計算します。

りんご ⇒ 80583901
バナナ ⇒ 680587148
ドーナツ ⇒ 634711725
チョコレート ⇒ 244903041

計算が終わったらそれらを2つずつつなげます。

80583901 と 680587148 ⇒ 80583901680587148
634711725 と 244903041 ⇒ 634711725244903041

そしてこれらのハッシュを計算します。これを得られるハッシュ値がひとつになるまで繰り返します。

80583901680587148 ⇒ 996712791
634711725244903041 ⇒ 198935277

996712791 と 198935277 ⇒ 996712791198935277

996712791198935277 ⇒ 431870295

JavaScriptでマークル木を実装する

それでは動作確認できるようにJavaScriptで実装してみることにします。

定数を示します。

index.js

ページが読み込まれたらBとmodの値をセットします。

[計算]ボタンがクリックされたらハッシュ値を計算します。

getHash関数は事前に計算しておいたpowsを使って第一引数のハッシュ値を求めます。