投稿日:2020年11月18日
typescriptはjavascriptをトランスパイルするための言語です。この記事ではtypescriptを利用して簡単なnodejsのブロックチェーンのサンプルアプリを作成しています。
はじめにワーキングディレクトリ内にプログラムの環境を作成していきます。
$ npm init
$ npm install --save-dev typescript @types/node
インストールしたtypescriptのtscコマンドを利用できる様にpackage.jsonを編集します。
{
[...]
"scripts": {
"tsc": "tsc"
},
[...]
}
これでtypescriptをトランスパイルするCLIツールをnpm run tscで呼べるようになりました。
プロジェクトのルートディレクトリにtsconfig.jsonを作成します。
{
"compilerOptions": {
"module": "commonjs",
"outDir": "./dist",
"target": "es2017",
"lib": [
"es2017"
]
}
}
これで環境の作成は完了です。
実際にアプリケーションを作成していきましょう。
まずはBlockを作成します。
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の情報(index、previousHash、timestamp、data)からハッシュ値を生成して自身のメンバ変数hashにセットします。
トランザクションのデータはdataというstring型のメンバ変数に格納されます。実際のアプリケーションでは構造化データになることが多いですが、今回はわかりやすさのためにstring型にしました。
次にBlockを取りまとめるBlockchainを作成します。
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を組み立てるサンプルコードをエントリーポイントに作成してみます。
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ョンではデータはサンプルとして
という文字列にしました。
実行してみましょう。
$ 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()(現在のタイムスタンプ)を利用しているからです。
それぞれのBlockのpreviousHashとその前のBlockのhashがちゃんと一致しています。
ブロックチェーンではネットワーク上の誰もがブロックを追加できます。つまり誰もが改竄できる可能性があるということです。
Proof Of Workはこの改竄を防ぐ方法のひとつで、ブロックのハッシュ値の生成にナンス値(nonce)という条件をつけます。ナンス値を見つける計算を1台のPCで行うことは基本的に不可能でそのネットワーク上の全てのPCが頑張ってそのナンス値(nonce)を見つけます。
不特定多数の大量のユーザーが監視しながら計算を行うために安全であるという考えですね。
ビットコインなどの仮想通貨ではこの方法でデータの有効性を保っています。
今回はnonce値に以下の条件を加えます。
この条件はローカルのPCでデバッグできるように簡単なルールにしましたが、実際のアプリケーションでこの様な簡単なルールにすることはないので注意してください。
では実際にBlockを作成します。
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"
}
]
}
はじめまして、株式会社inprogの宗清です。
MarsQuaiさんの記事を見て、google map libraryやNextについてお詳しいと感じました。
できればお仕事ご一緒したいと思っていますが、まずはカジュアルに情報交換させていただけませんか?
twitterで@_nm_inp のアカウントでフォローさせていただきました。
どうぞよろしくお願いします。