LoginSignup
26
18

More than 3 years have passed since last update.

[体験会資料] JavaScriptでブロックチェーンを作る!

Last updated at Posted at 2019-07-17

概要

9月27日, 10月4日に開催される「ブロックチェーンアプリ開発集中講座無料体験会 : JSでブロックチェーンのデータ構造を作ってみよう」のイベントのハンズオン資料です.
本イベントはブロックチェーンアプリ開発集中講座の体験会となっております.

JavaScriptでブロックチェーンのデータ構造とマイニングアルゴリズムを実装します.
P2Pネットワークなどは実装しないのでご注意ください.

実装していく

セットアップ

Node.jsをインストールしていない方はこちらからインストールを行ってください.

次にプロジェクトのセットアップです.

$ mkdir js-blockchain && cd js-blockchain
$ npm init -y
$ touch blockchain.js
$ touch test.js

コンストラクタ

コンストラクタ(初期化時のみ実行される関数)を作りましょう.

blockchain.js
module.exports = class Blockchain {
    constructor() {
        this.chain = [];
        this.pendingTransactions = [];
    }
}

chainはブロックを格納する配列, pendingTransactionsが未確定のトランザクションを格納する配列です.

ブロックを生成するメソッド

次にブロックを生成するメソッドを実装します.
newBlockはブロックのデータ構造です.
ブロックにはトランザクションを格納します.

  • newBlockを作成
  • chainの配列にnewBlockを追加
  • newBlockを返す
blockchain.js
module.exports = class Blockchain {
    constructor() {
        this.chain = [];
        this.pendingTransactions = [];
    }

    createNewBlock(nonce, previousBlockHash, hash) {
        const newBlock = {
            index: this.chain.length + 1,
            timestamp: Date.now(),
            transaction: this.pendingTransactions,
            nonce: nonce,
            hash: hash,
            previousBlockHash: previousBlockHash
        };

        this.pendingTransactions = [];
        this.chain.push(newBlock);

        return newBlock;
    }
}

test.jsファイルでcreateNewBlockを呼び出してみましょう.
引数は適当に設定します.

test.js
const Blockchain = require('./blockchain');
const bitcoin = new Blockchain();

bitcoin.createNewBlock(7653, "00KNWRUBWEJWENFOJNWO", "07HDFSKBWESUFBWEIBWIEUFBNW");

console.log(bitcoin);

コンソールでnode test.jsを実行し,結果を確認します.
以下のように返ってきたら成功です.
これが1つのブロックで, これが繋がっていきます.

$ node test.js

Blockchain {
  chain:
   [ { index: 1,
       timestamp: 1563284119109,
       transactions: [],
       nonce: 7653,
       hash: '00NDGHEWNEJWHBRNMNWO',
       previousBlockHash: '00KNWRUBWEJWENFOJNWO' } ],
  pendingTransactions: [] }

データを3つに増やしてみます.

test.js
const Blockchain = require('./blockchain');
const bitcoin = new Blockchain();

bitcoin.createNewBlock(7653, "00KNWRUBWEJWENFOJNWO", "00NDGHEWNEJWHBRNMNWO");
bitcoin.createNewBlock(8971, "00HDNFHEWEDGRBCHRNKG", "00HDYENRHFBKDURNFHNE");
bitcoin.createNewBlock(9761, "00JOIRNNOIHWEOUBNEWO", "00NJKRUOQWNOIWHRNOWQ");

console.log(bitcoin);

この時点ではブロックは繋がっていませんが, 増えるとこんな感じになります.

$ node test.js

Blockchain {
  chain:
   [ { index: 1,
       timestamp: 1563284451328,
       transactions: [],
       nonce: 7653,
       hash: '00NDGHEWNEJWHBRNMNWO',
       previousBlockHash: '00KNWRUBWEJWENFOJNWO' },
     { index: 2,
       timestamp: 1563284451328,
       transactions: [],
       nonce: 8971,
       hash: '00HDYENRHFBKDURNFHNE',
       previousBlockHash: '00HDNFHEWEDGRBCHRNKG' },
     { index: 3,
       timestamp: 1563284451328,
       transactions: [],
       nonce: 9761,
       hash: '00NJKRUOQWNOIWHRNOWQ',
       previousBlockHash: '00JOIRNNOIHWEOUBNEWO' } ],
  pendingTransactions: [] }

最新のブロックを返すメソッド

最新のブロックを返すだけです.

blockchain.js
getLastBlock() {
    return this.chain[this.chain.length - 1];
}

トランザクションを作成するメソッド

newTransactionというオブジェクトを作成して配列にpushします.
トランザクションとはAさんからBさんに1BTC送るというような取引の記録です.

blockchain.js
createNewTransaction(amount, sender, recipient) {
    const newTransaction = {
        amount: amount,
        sender: sender,
        recipient: recipient
    }

    this.pendingTransactions.push(newTransaction);
}

createNewTransactionをテストしましょう.
1BTCをAliceからBobに送ってみます.
まずブロックを作成し, トランザクションを作成します.

test.js
const Blockchain = require("./blockchain");
const bitcoin = new Blockchain();

bitcoin.createNewBlock(80103, "00KNWRUBWEJWENFOJNWO", "00NDGHEWNEJWHBRNMNWO");

bitcoin.createNewTransaction(
 1,
 "ALICEJSJSNWNN",
 "BOBDKENINOMDO"
);

console.log(bitcoin);

実行結果は以下です.
トランザクションがpendingTransactionsに入っています.

この時点ではトランザクションはブロックに含まれておらず, 未確定の状態になっています.
トランザクションはブロックをチェーンに追加する「マイニング」によって確定されます.

$ node test.js

Blockchain {
  chain:
   [ { index: 1,
       timestamp: 1563285267935,
       transactions: [],
       nonce: 80103,
       hash: '00NDGHEWNEJWHBRNMNWO',
       previousBlockHash: '00KNWRUBWEJWENFOJNWO' } ],
  pendingTransactions:
   [ { amount: 1,
       sender: 'ALICEJSJSNWNN',
       recipient: 'BOBDKENINOMDO' } ] }

test.jsに新しいブロックを追加しましょう.
これは即興のマイニング処理です.

test.js
const Blockchain = require("./blockchain");
const bitcoin = new Blockchain();

bitcoin.createNewBlock(80103, "00KNWRUBWEJWENFOJNWO", "00NDGHEWNEJWHBRNMNWO");

bitcoin.createNewTransaction(
 1,
 "ALICEJSJSNWNN",
 "BOBDKENINOMDO"
);

bitcoin.createNewBlock(99282, "00HDNFHEWEDGRBCHRNKG", "00HDYENRHFBKDURNFHNE");

console.log(bitcoin);

以下が実行結果です. pendingTransactionsがなくなり, トランザクションは2番目のブロックに格納されました.

$ node test.js

Blockchain {
  chain:
   [ { index: 1,
       timestamp: 1563285569054,
       transactions: [],
       nonce: 80103,
       hash: '00NDGHEWNEJWHBRNMNWO',
       previousBlockHash: '00KNWRUBWEJWENFOJNWO' },
     { index: 2,
       timestamp: 1563285569054,
       transactions: [Array],
       nonce: 99282,
       hash: '00HDYENRHFBKDURNFHNE',
       previousBlockHash: '00HDNFHEWEDGRBCHRNKG' } ],
  pendingTransactions: [] }

ブロックにハッシュ関数をかけるメソッド

sha256のnpmパッケージをインストールします.

$ npm i sha256

ファイルの先頭でrequireして使える状態にします.

blockchain.js
const sha256 = require('sha256');

ブロックにハッシュ関数をかけるメソッドを作成します.
引数を足し合わせてハッシュ関数にかけます.
previousBlockHashが前のブロックのハッシュ, currentBlockDataが現在のブロックのデータ, nonceはマイニングでブロックをチェーンに追加するために必要な数字です.

※「前のブロックのハッシュを現在のブロックの情報に加える」ということが繰り返されるので,ブロックが「チェーン」のように繋がっているのです.

blockchain.js
hashBlock(previousBlockHash, currentBlockData, nonce) {
    const dataAsString = previousBlockHash + nonce.toString() + JSON.stringify(currentBlockData);
    const hash = sha256(dataAsString);
    return hash;
}

ではこのメソッドをテストしましょう.
この時点でのテストデータも適当です.
トランザクションは3つでテストを行います.

test.js
const Blockchain = require("./blockchain");
const bitcoin = new Blockchain();

const previousBlockHash = "0AA0IAIJIJUIGGUGUYG";

const currentBlockData = [
 {
   amount: 10,
   sender: "ALICE090970FYFFYFYFIF",
   recipient: "BOB797789790JFJFFGFJF"
 },
 {
   amount: 30,
   sender: "ALICGHIUGUGOOIGODYGDHFD",
   recipient: "BOBTYSHGHOUHOHOHOHOHO"
 },
 {
   amount: 200,
   sender: "ALICEHJGUGUTETEEUUCVVUVUV",
   recipient: "BOBGIUGIUGIUDRTESREAREUY"
 }
];

const nonce = 100;

console.log(bitcoin.hashBlock(previousBlockHash, currentBlockData, nonce));

テストコード通りに実行すると以下のハッシュが返ってくるはずです.

$ node test.js

86e8acd0646dd795aa3604031df124af403113e50e8f23c7be846b990c5d0b42

Proof Of Workメソッド

最後はProof Of Work(PoW)メソッドです.
本来ならたくさんマイナーがいてP2Pでマイニング競争をするのですが, この記事ではマイナーは自分一人だけのケースを実装します.

blockchain.js
proofOfWork(previousBlockHash, currentBlockData) {
    let nonce = 0;
    let hash = this.hashBlock(previousBlockHash, currentBlockData, nonce);
    while(hash.substring(0, 4) !== '0000') {
        nonce++;
        hash = this.hashBlock(previousBlockHash, currentBlockData, nonce);
    }
    return nonce;
}

hashの先頭が0000になるまでブロックのハッシュ化を繰り返します.その度にnonceを増やしていきます.

これがProof Of Workです. ハッシュ化を繰り返すことがマシンパワーをつぎ込むということになり, マシンパワーをつぎ込んだことが取引の正当性の担保になります.

逆に取引内容を改ざんすると, そのトランザクションが含まれているブロックとそれ以降のブロックのハッシュを再計算しなくてはならず, 相当なマシンパワーが必要になります.

マイニング報酬をもらいたいというインセンティブでマイナーはマイニングをするのです. つまり, ビットコインでは人の欲望が取引の正当性を担保しているということになります.

では proofOfWorkメソッドをテストしましょう.

test.js
const Blockchain = require("./blockchain");
const bitcoin = new Blockchain();

const previousBlockHash = "0AA0IAIJIJUIGGUGUYG";

const currentBlockData = [
 {
   amount: 10,
   sender: "ALICE090970FYFFYFYFIF",
   recipient: "BOB797789790JFJFFGFJF"
 },
 {
   amount: 30,
   sender: "ALICGHIUGUGOOIGODYGDHFD",
   recipient: "BOBTYSHGHOUHOHOHOHOHO"
 },
 {
   amount: 200,
   sender: "ALICEHJGUGUTETEEUUCVVUVUV",
   recipient: "BOBGIUGIUGIUDRTESREAREUY"
 }
];

console.log(bitcoin.proofOfWork(previousBlockHash, currentBlockData));

少し, 表示されるのに時間がかかったことがわかります.
上記のテストコードをそのまま実行すれば以下のnonceになります.
105106回ハッシュ化を行なったということになります.

$ node test.js

105106

正解のnonceをhashBlockに与えて正しいかどうかを検証します.

test.js
const Blockchain = require("./blockchain");
const bitcoin = new Blockchain();

const previousBlockHash = "0AA0IAIJIJUIGGUGUYG";

const currentBlockData = [
 {
   amount: 10,
   sender: "ALICE090970FYFFYFYFIF",
   recipient: "BOB797789790JFJFFGFJF"
 },
 {
   amount: 30,
   sender: "ALICGHIUGUGOOIGODYGDHFD",
   recipient: "BOBTYSHGHOUHOHOHOHOHO"
 },
 {
   amount: 200,
   sender: "ALICEHJGUGUTETEEUUCVVUVUV",
   recipient: "BOBGIUGIUGIUDRTESREAREUY"
 }
];

console.log(bitcoin.hashBlock(previousBlockHash, currentBlockData, 105106));

先頭に0が4つ並んでいますね!
このようにマイニングには時間がかかりますが, 検証は一瞬ですみます.
この検証をビットコインノードたちは繰り返し行ってるわけです.

$ node test.js

0000eaf3ccc12559217ff4c0786a422935f36823936be0a7343c3e6b0e0ba48b

GenesisBlockの作成

最後に一番はじめのブロックGenesis Blockを作成しておきます.
これは最初のブロックを初期化することです.

blockchain.js

constructor() {
    this.chain = [];
    this.pendingTransaction= [];
    this.createNewBlock(100, '0', '0');
}

テストすると

test.js
const Blockchain = require("./blockchain");
const bitcoin = new Blockchain();
console.log(bitcoin);

以下のように初期化されます.

$ node test.js

Blockchain {
  chain:
   [ { index: 1,
       timestamp: 1563288921693,
       transactions: [],
       nonce: 100,
       hash: '0',
       previousBlockHash: '0' } ],
  pendingTransactions: [] }

最後に一連の流れをテストしましょう.
トランザクションの生成, マイニング, ブロックの生成という流れです.

test.js
const Blockchain = require('./blockchain');
const bitcoin = new Blockchain();

bitcoin.createNewTransaction(
    100,
    "ALICE090970FYFFYFYFIF",
    "BOB797789790JFJFFGFJF"
);

function mining(bitcoin) {
    //前のブロックを取得
    const lastBlock = bitcoin.getLastBlock();

   //前のブロックハッシュを取得
    const previousBlockHash = lastBlock["hash"];

    //現在のブロックのデータ
    const currentBlockData = {
      transactions: bitcoin.pendingTransactions,
      index: lastBlock["index"] + 1
    };

    const nonce = bitcoin.proofOfWork(previousBlockHash, currentBlockData);
    console.log(nonce);

    const blockHash = bitcoin.hashBlock(
      previousBlockHash,
      currentBlockData,
      nonce
    );

    const newBlock = bitcoin.createNewBlock(nonce, previousBlockHash, blockHash);
};

mining(bitcoin);

bitcoin.createNewTransaction(
    200,
    "ALICE090970FYFFYFYFIF",
    "BOB797789790JFJFFGFJF"
);

mining(bitcoin);

bitcoin.createNewTransaction(
 300,
 "ALICE090970FYFFYFYFIF",
 "BOB797789790JFJFFGFJF"
);

mining(bitcoin);

console.log(bitcoin);

3回マイニングしました.
現在のブロックのハッシュが次のブロックのpreviousBlockHashに入っていると成功です.
ブロックが連結できました!

$ node test.js

63679
18278
142244
Blockchain {
  chain:
   [ { index: 1,
       timestamp: 1563289082675,
       transactions: [],
       nonce: 100,
       hash: '0',
       previousBlockHash: '0' },
     { index: 2,
       timestamp: 1563289083265,
       transactions: [Array],
       nonce: 63679,
       hash:
        '0000736e9955903284bece68af5677725595659fd30c5ee5bd43842407434cf4',
       previousBlockHash: '0' },
     { index: 3,
       timestamp: 1563289083369,
       transactions: [Array],
       nonce: 18278,
       hash:
        '0000512c406dcbf6bf694fa549248a26a2b9f828a3b6fa698cdf7fbf794207bb',
       previousBlockHash:
        '0000736e9955903284bece68af5677725595659fd30c5ee5bd43842407434cf4' },
     { index: 4,
       timestamp: 1563289084129,
       transactions: [Array],
       nonce: 142244,
       hash:
        '00002f343be1ced1e1878551b7aad6a5e94b07248f4efdf5f7d4174ddffe8873',
       previousBlockHash:
        '0000512c406dcbf6bf694fa549248a26a2b9f828a3b6fa698cdf7fbf794207bb' } ],
  pendingTransactions: [] }

最終のblockchain.jsの状態は以下です

blockchain.js
const sha256 = require('sha256');

module.exports = class Blockchain {
    constructor() {
        this.chain = [];
        this.pendingTransactions = [];
        this.createNewBlock(100, '0', '0')
    }

    createNewBlock(nonce, previousBlockHash, hash) {
        const newBlock = {
            index: this.chain.length + 1,
            timestamp: Date.now(),
            transactions: this.pendingTransactions,
            nonce: nonce,
            hash: hash,
            previousBlockHash: previousBlockHash
        };

        this.pendingTransactions = [];
        this.chain.push(newBlock);

        return newBlock;
    }

    getLastBlock() {
        return this.chain[this.chain.length - 1];
    }

    createNewTransaction(amount, sender, recipient) {
        const newTransaction = {
            amount: amount,
            sender: sender,
            recipient: recipient
        }

        this.pendingTransactions.push(newTransaction);
    }

    hashBlock(previousBlockHash, currentBlockData, nonce) {
        const dataAsString = previousBlockHash + nonce.toString() + JSON.stringify(currentBlockData);
        const hash = sha256(dataAsString);
        return hash;
    }

    proofOfWork(previousBlockHash, currentBlockData) {
        let nonce = 0;
        let hash = this.hashBlock(previousBlockHash, currentBlockData, nonce);
        while(hash.substring(0, 4) !== '0000') {
            nonce++;
            hash = this.hashBlock(previousBlockHash, currentBlockData, nonce);
        }
        return nonce;
    }
}

最後に

ブロックチェーンのデータ構造が作れました!
ブロックが連結しているということが, 実際に実装することで実感できたのではないかと思います!

以下のようにproofOfWorkメソッドのhashの先頭の0の数を増やすと, マイニングに時間がかかってよりリアルさが増すので試してみてください.

blockchain.js
 proofOfWork(previousBlockHash, currentBlockData) {
    let nonce = 0;
    let hash = this.hashBlock(previousBlockHash, currentBlockData, nonce);
    while(hash.substring(0, 5) !== '00000') {
        nonce++;
        hash = this.hashBlock(previousBlockHash, currentBlockData, nonce);
    }
    return nonce;
}

参考資料

26
18
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
18