くりぷと日記

ブロックチェーンをJavaScriptで作ってみる

動機

ビットコインをはじめ、仮想通貨の基礎となる技術がブロックチェーンです。

今回はそれを理解するために、身近な(?)言語であるJavaScriptで、シンプルに作ってみたいと思います。

(ブラウザのJSではなくNode.jsを使っています。)

なお、今回実装するのはビットコインなど実在の仮想通貨に準拠したブロックチェーンではありません。

ブロックチェーンの仕組み

ブロックチェーンは、ブロックと呼ばれるデータの塊の連なりです。

特に、一番最初のブロックはジェネシスブロックと呼ばれます。

送金の記録などといったデータは、直前のブロックから計算されるハッシュ値と一緒に、新しいブロックに突っ込まれます。

ハッシュ値を計算する関数をハッシュ関数といいますが、これらの関数は

という特徴を持つよう設計されています。

ジェネシスブロックからハッシュ値を辿っていけば、正しいブロックの連なりがわかりますね。

実装

今回はブロックとマイナー(の一部)を実装するだけで、通信などには触れません。

ハッシュ関数

ハッシュ関数は前述した特徴を満たさなければなりませんし、さらにPoWの仕組みにより不正を防ぐためには不可逆性(出力から入力を逆算することがとても困難な性質)を持たなくてはなりません。

今回は出来合いのものを使います。

npm init
npm install crypto-js --save

次のように、hashという関数を定義してみます。

入力をSHA256にそのまま渡すだけです。

123456
const SHA256 = require('crypto-js/sha256');

function hash (x) {
  return SHA256(x);
}

適当な入力を与えてみるとわかりますが、出力は次のような形をしています。

> SHA256('foo')
{ words:
  [ 740734059,
    1761592975,
    -107264708,
    489701684,
    323104112,
    1686355872,
    -108372344,
    1650911150 ],
    sigBytes: 32 }

wordsが結果本体、sigBytesが1 wordのビット数となります。

スコア

ビットコインでは、新しいブロックのハッシュ値を2進数にしたとき、最初の桁から0が所定の数だけ続いていなければ認められません。

その所定の0の数を難易度(difficulty)と呼びます。

ここでは、新しいブロックの0の数をスコアと呼ぶことにします。

本来なら実際に2進数を扱って、上から何桁0が続くかをチェックするのですが、今回は簡単のため2を底とする対数を使いました。

 1 2 3 4 5 6 7 8 91011121314151617181920212223
function score (hash) {
  let zeros = 0;
  for (let i = 0; i < hash.words.length; i++) {
    const h = hash.words[i];
    if (h < 0) break; // 負の数は2進数表記で一番左が1になるため打ち切り
    if (h == 0) {
      // 0は対数をとると-Infinityになってしまうので、別に扱う
      zeros += hash.sigBytes; // もちろん、全桁が0だから
    } else {
      // 右から数えて最後に1が出る桁
      // (2進数を扱うのは少し面倒なので、2を底とした対数で代用)
      const biggestOne = Math.floor(Math.log2(h)) + 1;

      // 左から連続する0の個数は、次のようになる
      zeros += hash.sigBytes - biggestOne;

      // 0以外が出てきたので、計算打ち切り
      break;
    }
  }
  return zeros;
}

ブロック

ブロックには、

が含まれます。

 1 2 3 4 5 6 7 8 9101112131415161718
class Block {
  constructor (prevHash, data, nonce) {
    this.prevHash = prevHash; // 前のブロックのhash
    this.data = data; // 記録する内容
    this.nonce = nonce; // ころころ変える値

    // 上記から定まるこのブロックのhash
    this.hash = hash(this.toString());

    // スコア(左から連続する0の個数)を記録
    this.score = score(this.hash);
  }

  toString () {
    return this.prevHash.words + '\n' + this.data + '\n' + this.nonce;
  }
}

toString()で、ブロックの文字列表現を、統一した形式で出力できるようにしておきます。

文字列表現は、例えば次のようになります。

1319486728,-1504285066,604324856,-21991468,1467448291,-1790327807,1681305562,1708274446
testdata
1423

今回は改行(\n)を区切りに使いましたが、もちろんデータが改行を含む場合はダメですね。

データはBase64でエンコードしたりするのがいいかもしれません。

この内容がハッシュ関数に渡されることになります。

マイナー

最後に、採掘(マイニング)を行うクラスの例を紹介します。

 1 2 3 4 5 6 7 8 9101112131415161718192021
// 採掘用クラス
// 非nullが返るまでmine()を呼び出して使う
class Miner {
  constructor (prevHash, data, difficulty) {
    this.prevHash = prevHash;
    this.data = data;
    this.difficulty = difficulty;
    this.nonce = 0;
  }

  mine () {
    const block = new Block(this.prevHash, this.data, this.nonce);
    this.nonce++;
    if (block.score >= this.difficulty) {
      return block;
    } else {
      return null;
    }
  }
}

おわりに

勉強のために、ブロックチェーンの基本を実装してみました。

ブロックチェーン自体はかなり親しみやすい概念だと思います。

次回は実際に取引のシミュレーションなどができるようにしていきたいと思います。