※このブログではサーバー運用、技術の検証等の費用のため広告をいれています。
記事が見づらいなどの問題がありましたらContactからお知らせください。


【TypeScript】簡単なブロックチェーンのnodejsアプリを作成

web開発 ブロックチェーン Blockchain TypeScript nodejs

投稿日:2020年11月18日

このエントリーをはてなブックマークに追加
typescriptはjavascriptをトランスパイルするための言語です。この記事ではtypescriptを利用して簡単なnodejsのブロックチェーンのサンプルアプリを作成しています。

実践【基礎】

環境構築

はじめにワーキングディレクトリ内にプログラムの環境を作成していきます。

ターミナル
$ npm init
$ npm install --save-dev typescript @types/node

インストールしたtypescripttscコマンドを利用できる様にpackage.jsonを編集します。

package.json
{
  [...]
  "scripts": {
    "tsc": "tsc"
  },
  [...]
}

これでtypescriptをトランスパイルするCLIツールをnpm run tscで呼べるようになりました。

プロジェクトのルートディレクトリにtsconfig.jsonを作成します。

tsconfig.json
{
  "compilerOptions": {
  	"module": "commonjs",
    "outDir": "./dist",
    "target": "es2017",
    "lib": [
      "es2017"
    ]
  }
}

これで環境の作成は完了です。

実際にアプリケーションを作成していきましょう。

Blockの作成

まずはBlockを作成します。

Block.ts
import * as crypto from 'crypto';

class Block {
    readonly hash: string;   // このBlockのハッシュ値

    /**
     * コンストラクタ
     * @param index {number} ID
     * @param previousHash {string} 前のブロックのハッシュ値
     * @param timestamp {number} Block作成時のタイムスタンプ
     * @param data {string} アプリケーションで使用するデータ
     */
    constructor(
        readonly index: number,
        readonly previousHash: string,
        readonly timestamp: number,
        readonly data: string
    ){
     this.hash = this.calculateHash();   
    }

    /**
     * 自信のBlockのハッシュ値を計算
     */
    private calculateHash(): string {
        const dataConcated = this.index + this.previousHash + this.timestamp + this.data;
        return crypto
        .createHash('sha256')
        .update(dataConcated) 
        .digest('hex'); 
    }
}

Blockクラスは作成時に自信のメソッドcalculateHash()を呼び出し、自身と前のBlockの情報(indexpreviousHashtimestampdata)からハッシュ値を生成して自身のメンバ変数hashにセットします。

トランザクションのデータはdataというstring型のメンバ変数に格納されます。実際のアプリケーションでは構造化データになることが多いですが、今回はわかりやすさのためにstring型にしました。

Blockchainの作成

次にBlockを取りまとめるBlockchainを作成します。

Blockchain.ts
import Block from "./Block";

/**
 * Blockを配列で管理するBlockchain
 */
class Blockchain {
    private readonly chain: Block[] = [];

    /**
     * このBlockchとainの直近に追加されたBlockを返す
     */
    private get latestBlock(): Block{
        return this.chain[this.chain.length - 1]
    }


    /**
     * 新規で1つ目のBlockが紐付いたBlockchainを作成
     */
    constructor(){
        this.chain.push(new Block(0, '0', Date.now(), 'Genesis Block'));
    }

    /**
     * Blockchainに新規でBlockを追加する
     * @param data 追加するBlockのdata
     */
    addBlock(data: string): void {
        const block = new Block(
            this.latestBlock.index + 1,
            this.latestBlock.hash,
            Date.now(),
            data
        );
        this.chain.push(block);
    }
}

export default Blockchain;

Blockchainクラスはインスタンス作成時に起点となるBlockを作成し、自身のメンバ変数の配列にpushします。

Blockchainクラスは勝手に内部のBlockをいじられない様にBlockの配列はアクセスレベルがprivateになっています。

Blockの取得や追加はgetterやメソッドで行う様に設計されています。

エントリーポイントの作成

最後にBlockchainを組み立てるサンプルコードをエントリーポイントに作成してみます。

index.ts
import Blockchain from "./BlockChain";


console.log("新規でブロックチェーンを作成...")
const blockchain = new Blockchain()

console.log("1番目のブロックをマイニング")
blockchain.addBlock("First Block")

console.log("2番目のブロックをマイニング")
blockchain.addBlock("Second Block")

console.log(JSON.stringify(blockchain, null, 2));

今回のアプリケーシSecond Blockョンではデータはサンプルとして

  • First Block
  • Second Block

という文字列にしました。

実行

実行してみましょう。

ターミナル
$ npm run tsc
$ node dist/index.js
新規でブロックチェーンを作成...
1番目のブロックをマイニング
2番目のブロックをマイニング
{
  "chain": [
    {
      "index": 0,
      "previousHash": "0",
      "timestamp": 1605940178860,
      "data": "Genesis Block",
      "hash": "228ad4b78ac8bbb59c90ffc52faaaa68e03569c8d35ed9aa635abe09b4b572e3"
    },
    {
      "index": 1,
      "previousHash": "228ad4b78ac8bbb59c90ffc52faaaa68e03569c8d35ed9aa635abe09b4b572e3",
      "timestamp": 1605940178860,
      "data": "First Block",
      "hash": "2745bde02a1194fb5b7d858a1a0ac0250ea1c09bade9406419a0152037291189"
    },
    {
      "index": 2,
      "previousHash": "2745bde02a1194fb5b7d858a1a0ac0250ea1c09bade9406419a0152037291189",
      "timestamp": 1605940178860,
      "data": "Second Block",
      "hash": "847cfce73503ed171dc1ad22b975ea2c3e06fca12cd5d60c84009f2ffdc51da9"
    }
  ]
}

トランスパイルされたコードdist/index.jsの結果は実行のたびに変化します。

これはBlockのハッシュ値にDate.now()(現在のタイムスタンプ)を利用しているからです。

それぞれのBlockpreviousHashとその前のBlockhashがちゃんと一致しています。


実践【発展】

PoW(Proof of Work)

ブロックチェーンではネットワーク上の誰もがブロックを追加できます。つまり誰もが改竄できる可能性があるということです。

Proof Of Workはこの改竄を防ぐ方法のひとつで、ブロックのハッシュ値の生成にナンス値(nonce)という条件をつけます。ナンス値を見つける計算を1台のPCで行うことは基本的に不可能でそのネットワーク上の全てのPCが頑張ってそのナンス値(nonce)を見つけます。

不特定多数の大量のユーザーが監視しながら計算を行うために安全であるという考えですね。

ビットコインなどの仮想通貨ではこの方法でデータの有効性を保っています。

Blockの修正

今回はnonce値に以下の条件を加えます。

  • データの末尾にnonce値を追加しハッシュ化した場合に、ハッシュ値の先頭が00000で始まること

この条件はローカルのPCでデバッグできるように簡単なルールにしましたが、実際のアプリケーションでこの様な簡単なルールにすることはないので注意してください。

では実際にBlockを作成します。

Block.ts
import * as crypto from 'crypto';

class Block {
    readonly nonce: number   // ワンタイムパスワード
    readonly hash: string    // このBlockのハッシュ値

    /**
     * コンストラクタ
     * @param index {number} ID
     * @param previousHash {string} 前のブロックのハッシュ値
     * @param timestamp {number} Block作成時のタイムスタンプ
     * @param data {string} アプリケーションで使用するデータ
     */
    constructor(
        readonly index: number,
        readonly previousHash: string,
        readonly timestamp: number,
        readonly data: string
    ){
        // ハッシュ値とナンス値を計算
        const {nonce, hash} = this.mine()
        this.nonce = nonce
        this.hash = hash
    }

    /**
     * 自信のBlockのハッシュ値を計算
     */
    private calculateHash(nonce: number): string {
        const dataConcated = this.index + this.previousHash + this.timestamp + this.data + nonce;
        return crypto
        .createHash('sha256')
        .update(dataConcated) 
        .digest('hex')
    }

    /**
     * 正しいナンス値が見つかるまで再帰的にハッシュ値を計算
     */
    private mine(): {nonce: number, hash: string} {
        let hash: string
        let nonce: number = 0

        do {
            hash = this.calculateHash(++nonce)
        }while(hash.startsWith("00000") === false)

        return {nonce, hash}
    }
}

export default Block;

実行

実行してみます。

ターミナル
$ tsc
$ node dist/index.js 
新規でブロックチェーンを作成...
1番目のブロックをマイニング
2番目のブロックをマイニング
{
  "chain": [
    {
      "index": 0,
      "previousHash": "0",
      "timestamp": 1606032390978,
      "data": "Genesis Block",
      "nonce": 183020,
      "hash": "00000d9a5a6f537eed1c426c4ac04a4f36a4a9ccf117a92dc7a52234b41a5001"
    },
    {
      "index": 1,
      "previousHash": "00000d9a5a6f537eed1c426c4ac04a4f36a4a9ccf117a92dc7a52234b41a5001",
      "timestamp": 1606032391408,
      "data": "First Block",
      "nonce": 326288,
      "hash": "000004e47621980ad9bad672b3ebe3c1c9f157b5cc7af63ca0195025bbd6c6b5"
    },
    {
      "index": 2,
      "previousHash": "000004e47621980ad9bad672b3ebe3c1c9f157b5cc7af63ca0195025bbd6c6b5",
      "timestamp": 1606032392199,
      "data": "Second Block",
      "nonce": 728410,
      "hash": "0000059e650cfd1db2ae01e99c1ca02907ea27cd4a3706096e7f375d3f3d127f"
    }
  ]
}

さいごに

参考URL

このエントリーをはてなブックマークに追加


関連記事

記事へのコメント
1:宗清 直人
2021年11月7日14:34

はじめまして、株式会社inprogの宗清です。
MarsQuaiさんの記事を見て、google map libraryやNextについてお詳しいと感じました。
できればお仕事ご一緒したいと思っていますが、まずはカジュアルに情報交換させていただけませんか?

twitterで@_nm_inp のアカウントでフォローさせていただきました。
どうぞよろしくお願いします。