LoginSignup
0
0

More than 1 year has passed since last update.

メモ:node.jsでブロックチェーンの基本を見る

Posted at

node.jsでブロックチェーン周辺の基本を見ておくことにしたので、その記録。

こことかこことか
を参考にした。

準備

とりあえずtypescriptを入れる。
npm install typescript @types/node

ブロックチェーン

予備知識

nodeで作成している記事に従ってブロックとチェーンのコードを書いてみる。
微妙に自分の趣向で書き換えてるけど、ほぼそのままパクリ。

ブロックは、前のブロックのハッシュ値+自分のデータでハッシュ値を作成するが、時刻も含めているのでそれで扱う。

import * as crypto from 'crypto';

export class Block {
    readonly hash: string;   // このBlockのハッシュ値
    readonly timestamp: number; // 作成時刻

    /**
     * コンストラクタ
     * @param index {number} ID
     * @param previousHash {string} 前のブロックのハッシュ値
     * @param data {string} アプリケーションで使用するデータ(バイト列の場合が多い?)
     */
    constructor(
        readonly index: number,
        readonly previousHash: string,
        readonly data: string
    ){
        this.timestamp = Date.now();
        this.hash = this.calculateHash(); // インスタンス作成時に自身のハッシュを作成
    }

    /**
     * 自分自身のハッシュ値を計算
     * インデックス、前のハッシュ、タイムスタンプ、データから自分のハッシュ値を計算する
     * SHA256でハッシュ値を計算して16進数にする。
     */
    private calculateHash(): string {
        const dataConcated = this.index + this.previousHash + this.timestamp + this.data;
        return crypto
        .createHash('sha256')
        .update(dataConcated) 
        .digest('hex'); 
    }
}

チェーン

import { Block } from './Block';

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

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


    /**
     * 最初のブロックを作成
     */
    constructor(){
        this.chain.push(new Block(0, '0', 'Genesis First Block'));
    }

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

export default Chain;

「既定のエクスポートがありませんという」エラーが出たので、
プロジェクトルートにtsconfig.jsonを作成したりと試した結果、BlockのClassにexportを付けると問題なくなった。

これを使って実際にブロックチェーンを作成する。

import Chain from "./Chain";

console.log("新規ブロックチェーン作成");
let blockchain = new Chain();

console.log("最初のブロック");
blockchain.addBlock("Block1");

console.log("二番目のブロック");
blockchain.addBlock("Block2");

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

これで実行すると
(node:2772) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
エラーが発生したので、tsconfig.jsonに"module": "commonjs"を追加して解決。

{
  "chain": [
    {
      "index": 0,
      "previousHash": "0",
      "data": "Genesis First Block",
      "timestamp": 1640215989067,
      "hash": "d97b03898da1ac699777543ec29a9bcd6484c1fd5c0de1f136a505d6827749b4"
    },
    {
      "index": 1,
      "previousHash": "d97b03898da1ac699777543ec29a9bcd6484c1fd5c0de1f136a505d6827749b4",
      "data": "Block1",
      "timestamp": 1640215989076,
      "hash": "3aee610e345f68c66a5c818e20ecf1b0debf76e07cf5083cf86c9228d2ee1ac3"
    },
    {
      "index": 2,
      "previousHash": "3aee610e345f68c66a5c818e20ecf1b0debf76e07cf5083cf86c9228d2ee1ac3",
      "data": "Block2",
      "timestamp": 1640215989077,
      "hash": "8329a5182704084628a178679a58e1a89da266840ac1a2ff99b11687cf6cde20"
    }
  ]
}

ナンス値

何らかの数字を含めてハッシュ値を計算して、その結果が何らかの条件(00000から始まる、等)に当てはまるようにする事で
改ざんしようとする場合の計算量を高めて、不正チェーンが伸び辛くする(最長チェーンが正規と判断されるため)という理屈。

ブロックにナンス値を追加して、それを正しい状態になるまでカウントアップしながら計算する、というロジックを組めば良い。

import * as crypto from 'crypto';

export class Block {
    readonly hash: string;   // このBlockのハッシュ値
    readonly nonce:number; // ナンス値
    readonly timestamp: number; // 作成時刻


    /**
     * コンストラクタ
     * @param index {number} ID
     * @param previousHash {string} 前のブロックのハッシュ値
     * @param data {string} アプリケーションで使用するデータ(バイト列の場合が多い?)
     */
    constructor(
        readonly index: number,
        readonly previousHash: string,
        readonly data: string
    ){
        this.timestamp = Date.now(); //マイニング前に確定させておく
        //this.hash = this.calculateHash(); // インスタンス作成時に自身のハッシュを作成
        const {p_nonce, p_hash}  = this.mine(); // マイニング
        this.nonce = p_nonce;
        this.hash = p_hash;
    }

    /**
     * 自分自身のハッシュ値を計算
     * インデックス、前のハッシュ、タイムスタンプ、データ、ナンス値から自分のハッシュ値を計算する
     * SHA256でハッシュ値を計算して16進数にする。
     */
    private calculateHash(p_nonce:number): string {
        const dataConcated = this.index + this.previousHash + this.timestamp + this.data + p_nonce;
        return crypto
        .createHash('sha256')
        .update(dataConcated) 
        .digest('hex'); 
    }

    private mine():{p_nonce:number, p_hash:string} {
        let p_nonce:number = -1;
        let p_hash:string = "AAAAA";

        do{
            p_hash = this.calculateHash(++p_nonce);
        }while(p_hash.startsWith('000')===false) // ハッシュ値の条件に当たるまでループ

        return { p_nonce, p_hash};
    }
}

実行するとnonceが設定されて、ハッシュの先頭が000になっている。

{
  "chain": [
    {
      "index": 0,
      "previousHash": "0",
      "data": "Genesis First Block",
      "timestamp": 1640220923504,
      "nonce": 2233,
      "hash": "0002e40c78aafdbf2ac8faaac4b95f72be399b5b864bf029f657d6e47676c867"
    },
    {
      "index": 1,
      "previousHash": "0002e40c78aafdbf2ac8faaac4b95f72be399b5b864bf029f657d6e47676c867",
      "data": "Block1",
      "timestamp": 1640220923529,
      "nonce": 205,
      "hash": "000378af5b48e24e3aea122e0debeb97775ed42fbe81b3fc3886a3bf7de80fc4"
    },
    {
      "index": 2,
      "previousHash": "000378af5b48e24e3aea122e0debeb97775ed42fbe81b3fc3886a3bf7de80fc4",
      "data": "Block2",
      "timestamp": 1640220923531,
      "nonce": 639,
      "hash": "0009ed811373333199b4d1c0c39ce7d3553b42e8ae22c296dd1afd0f2f06c08e"
    }
  ]
}

実際の処理

単純にブロックが作れたら終わりではなく、
* チェーン台帳とやりとりするための仕組み
* 正しいチェーンと判断するための合意形成の仕組み
* チェーンを保持する台帳の仕組み(データベース)
が必要となり、nodejsだけで自前でスケーラブルに組むのは大変なので、
こういったものが例えばAWSにはAmazon Managed Blockchainとして存在している。

計算量が膨大になる=CPUを使う=電力を使う=環境に優しくない
のは言われているが、AI含めて電力はこれからも必要になるので日本も電力戦略もどうするのかというのは必要そう。

NFT

ブロックチェーン応用例としてここに説明がある。
全体的な動きはこれで、特徴として

  • プログラマビリティ(付加機能の付与)
  • 取引が自由
  • ERC721に沿えば一定の相互運用性あり

ERC721については

ERC721では個々のトークンはユニークで代替不可能すなわち唯一無二あることから、トークンの量(_value)ではなく、トークンのID(_tokenId)を引数で指定し転送します。
その他のtransfer系の関数、approve関数でも同様にトークンの量を指定するかわりにトークンのIDを指定します。

という代替可能なもの=量指定とは違ってIDで取引する。
これらの扱いには少なくとも2018年ではOpenZeppelinというものがある。

ERC721トークンやそれを扱うスマートコントラクトを開発する場合、
ERC20同様、OpenZeppelinというSolidityで書かれたスマートコントラクトのオープンフレームワークを使うとよいでしょう

使い方はここにまとめられている。。

それ以外だとSatellitesのようなものや
ReserveBlock Foundationのもの
Deptのもの
などがオープンソースなプラットフォームとして出てきている。

というくらいでとりあえず終わり

0
0
0

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
0
0