JavaScriptで60行でブロックチェーンを作る方法

近年、暗号通貨やブロックチェーンは注目を集めている分野です。そこで今日は、たった60行のコードでJavaScriptでブロックチェーンを作る方法をシェアしようと思います。

フルチュートリアルもYouTubeで公開しています。もっと詳しい情報が欲しい方はチェックしてみてください。

また、新しい記事も公開しました。私たちのブロックチェーンで取引を作成すること、つまり暗号通貨を構築する最初のステップについての内容です。ぜひ読んでみてください。

これらの話題が既に馴染み深い方は、暗号通貨をリリースするためにP2Pネットワークを作成する方法についての3番目の記事を検討してみてください。

ブロックチェーンとは?


コーディングを始める前に、ブロックチェーンが何なのかを理解する必要があります。技術的には、ブロックチェーンは最低限、タイムスタンプ、取引、ハッシュなどの基本情報を持つオブジェクトを含むリストです。そのデータは不変であり、ハッキングできないものでなければなりません。イーサリアム、カルダノ、ポルカドットなどの現代的なプラットフォームはもっと複雑ですが、この記事ではシンプルな話をしていきます。

セットアップ


このプロジェクトではNode.jsを使うので、まだインストールしていない方はインストールしてください。

記事全体を通して、オブジェクト指向のプログラミングスタイルを使用するので、基本知識を知っていることを前提としています。

ブロックの作成


前述した通り、ブロックは情報を持ったオブジェクトです。そのため、Block クラスを以下のように作成する必要があります。

class Block {
    constructor(timestamp = "", data = []) {
        this.timestamp = timestamp;
        // this.data には取引などの情報が含まれるべきです。
        this.data = data;
    }
}

ブロックチェーンには不変性が必要です。ブロック内のプロパティをすべてハッシュ化するハッシュ関数を使うことで、この効果を得ることができます。ハッシュについてはウィキペディアで読むことをお勧めします。それはブロックチェーンにおいて重要な役割を果たしています。基本的には、メッセージを取り込み、変更された長さに関わらず「ハッシュ化」された出力を生成します。

sha256 アルゴリズムを使用しています。ハッシング関数を実装するには、Node.jsのビルトインcryptoパッケージを使用するつもりです。

const crypto = require("crypto"), SHA256 = message => crypto.createHash("sha256").update(message).digest("hex");

上記のコードで求めているものが得られるはずですが、その仕組みについて知りたい方は、Node.jsの公式ドキュメントのハッシュクラスについてをチェックしてください。

次のような形になります。

// sha256 ハッシュ関数を取得します。
const crypto = require("crypto"), SHA256 = message => crypto.createHash("sha256").update(message).digest("hex");

class Block {
    constructor(timestamp = "", data = []) {
        this.timestamp = timestamp;
        this.data = data;
        this.hash = this.getHash();
        this.prevHash = ""; // 前のブロックのハッシュ
    }

    // ハッシュ関数。
    getHash() {
        return SHA256(this.prevHash + this.timestamp + JSON.stringify(this.data));
    }
}

どんな小さな変更もSHA256がまったく異なるものを生成するので、これで不変性がある程度保てます。

prevHash プロパティも不変性に大きな役割を果たします。ブロックがブロックチェーンの寿命を通じて変更されないことを保証します。前のブロックのハッシュを含んでいるので、わずかな変更で現在のブロックのgetHashが異なるものになってしまいます。空ですが、この記事の後半で何かを行います。

ブロックチェーン


ブロックチェーンクラスに移りましょう。

前述したように、ブロックチェーンはブロックのリストですから、基本的な形は以下のようになります。

class Blockchain {
    constructor() {
        // このプロパティにはすべてのブロックが含まれます。
        this.chain = [];
    }
}

最初のブロック、つまりジェネシスブロックが必要です。

class Blockchain {
    constructor() {
        // ジェネシスブロックを作成します
        this.chain = [new Block(Date.now().toString())];
    }
}

便宜上、最新のブロックを取得する関数を作成しましょう。

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

ブロックをブロックチェーンに追加する方法が必要です。

    addBlock(block) {
        // 新しいブロックを追加するので、prevHashは以前の最新ブロックのハッシュになります
        block.prevHash = this.getLastBlock().hash;
        // prevHashに値が設定されているので、ブロックのハッシュをリセットする必要があります
        block.hash = block.getHash();

        // Object.freezeはコード内での不変性を確保します
        this.chain.push(Object.freeze(block));
    }

検証


チェーンがまだ有効かどうか知る必要がありますので、検証を確認するメソッドが必要です。チェーンが有効であるためには、ブロックのハッシュがハッシュ化メソッドが返すものと等しく、ブロックのprevHashプロパティが前のブロックのハッシュと同じでなければなりません。

    isValid(blockchain = this) {
        // チェーンをイテレートします。ジェネシスブロックの前には何もないので、2番目のブロックから開始します。
        for (let i = 1; i < blockchain.chain.length; i++) {
            const currentBlock = blockchain.chain[i];
            const prevBlock = blockchain.chain[i-1];

            // 検証
            if (currentBlock.hash !== currentBlock.getHash() || prevBlock.hash !== currentBlock.prevHash) {
                return false;
            }
        }

        return true;
    }

このメソッドは、ブロックチェーンがp2pネットワークで実行されるときに非常に重要な役割を果たします。

パフォーマンス


P2Pネットワークでは、サードパーティのシステムがないため、合意形成メカニズムなしでは、ノード(簡単に言えば人々)は多数派の意見に合わせることになりますが、攻撃者になり、多数派をコントロールし始める可能性があります。合意形成メカニズムは、攻撃を止めるためだけでなく、人々が攻撃者にならないようにするために存在します。Proof-of-Work(パフォーマンス)はその一つです。

もっと詳しく説明する前に、システムはあなたがnonceと呼ばれる値を増やすことによって、難易度に等しい/関連するゼロの数で始まるハッシュを得るように機能します。

PoWは2つの面で役立ちます。攻撃者を抑止することができるため(一人で他のノードに追いつくのはほぼ不可能です)、マイニング報酬を提供するために人々が攻撃者になるよりも中立でいようとするでしょう。次の記事でトランザクションシステムを持つようになったときにマイニング報酬を実装します。

mineメソッドとnonceプロパティをブロックに追加することで、PoWシステムを実装できます。

class Block {
    constructor(timestamp = "", data = []) {
        this.timestamp = timestamp;
        this.data = data;
        this.hash = this.getHash();
        this.prevHash = ""; // 前のブロックのハッシュ
        this.nonce = 0;
    }

    // ハッシュ関数
    getHash() {
        return SHA256(this.prevHash + this.timestamp + JSON.stringify(this.data) + this.nonce);
    }

    mine(difficulty) {
        // おおよそ、ハッシュが難易度<difficulty>の文字列0...000で始まるまでループします。
        while (!this.hash.startsWith(Array(difficulty + 1).join("0"))) {
            // nonce値の増加によって全く異なるハッシュを得る。
            this.nonce++;
            // 新しいnonce値で新しいハッシュを更新。
            this.hash = this.getHash();
        }
    }
}

ブロックのわずかな詳細を変更するとハッシュが全く異なるものになるので、求めているハッシュが見つかるまでnonceを繰り返しインクリメントするだけです。

(注:Bitcoinなどは通常、難易度を確認するために異なる方法を使用しますが、私たちはシンプルなままでいます)

Blockchainクラスに移ると、難易度プロパティを作成する必要があります。

    this.difficulty = 1;

難易度はマイニングされたブロックの数に基づいて更新されるべきですので、1に設定します。

addBlockメソッドもBlockchainから更新する必要があります。

    addBlock(block) {
        block.prevHash = this.getLastBlock().hash;
        block.hash = block.getHash();
        block.mine(this.difficulty);
        this.chain.push(Object.freeze(block));
    }

これで、チェーンに追加される前にすべてのブロックがマイニングされる必要があります。

速いメモ


私たちはシンプルなために、このブロックチェーンのために証明作業のシステムを使用しました。ほとんどの現代的なブロックチェーンは、証明ステーク(またはその多くのアップグレードされたバリエーション)と呼ばれるはるかに良いシステムを使用していることに注意してください。

チェーンのテスト!


新しいファイルを作成し、そのファイルはエントリーファイルになります。

作成したばかりのブロックチェーンを使ってみましょう!今のところJeChainと呼ぶことにします。

必要なクラスを最初にエクスポートします。

module.exports = { Block, Blockchain };
const { Block, Blockchain } = require("./your-blockchain-file.js");

const JeChain = new Blockchain();
// 新しいブロックを追加
JeChain.addBlock(new Block(Date.now().toString(), { from: "John", to: "Bob", amount: 100 }));
// (これはちょっとした例ですが、本物の暗号通貨は通常、実装にいくつかのステップがあります)。

// 更新されたチェーンを出力
console.log(JeChain.chain);

実際の出現は以下のようになります。

画像説明

最初のブロックは私たちのジェネシスブロックで、2番目のブロックが追加されたブロックです。

難易度とブロック時間に関する更新されたボーナス


ブロック時間

ブロック時間は、チェーンにブロックが追加される見積もり時間に似た定数値です。Bitcoinのようなプラットフォームでは、ブロック時間は10分で、Ethereumは13秒です。

Bitcoinの難易度式

Bitcoinでは、2016ブロックがマイニングされるたびに難易度が更新されます。新しい難易度を計算するためにこの式を使用します。

old difficulty * (2016 blocks * 10 minutes) / mining time for the previous 2016 blocks

コーディングしましょう!

まず、私たちのブロック時間を設定する必要があります。単純に30000ミリ秒、つまり30秒に設定します。ミリ秒を使用するのはDate.now()との連携が良いためです。

    this.blockTime = 30000;

(注:ここではBlockchainクラス内でコーディングしています)。

例と

こちらの記事はdev.toの良い記事を日本人向けに翻訳しています。
https://dev.to/freakcdev297/creating-a-blockchain-in-60-lines-of-javascript-5fka