×
Community Blog Build Your Own Blockchain with JavaScript

Build Your Own Blockchain with JavaScript

This article goes over the basics of blockchain and shows how you can create a blockchain and everything that goes with it using JavaScript.

You may think that blockchain is far bit too complicated for you to handle. Well, actually, it isn't all that bad if you think about blockchain from a more fundamental and conceptual level. Today, in this article, we're going to cover some the basics about blockchain, and show you how you can use Javascript to build your own blockchain system, just by writing a couple of lines of code to describe your blockchain, including the underlying data structure, PoW mining, and transaction process. Of course, real scenarios where you build your own blockchain are far more complex than this.

Getting to Know Blockchain

As its name suggests, a blockchain is a chain of blocks, and so the basic data structure of a blockchain is a block. The content of a block includes such information as a timestamp, data, hash, and previousHash. Among these components, the data portion is used to store data, naturally, and previousHash serves to store the hash value of the previous block. Here's a diagram of a basic blockchain:

1

Next, as you can see from the below code, a hash stores general block information. A hash allows information of any length to be mapped into fixed length strings. In the example below it's mapped to sha256:

calculateHash() {
    return SHA256(this.previousHash+ this.timestamp + JSON.stringify(this.data)).toString();
}

Next, the basic structure of a block data looks like the following:

class Block {
    constructor(timestamp, data, previousHash = '') {
        this.timestamp = timestamp;
        this.data = data;
        this.previousHash = previousHash;
        // The calculation of the hash must be at the end so to ensure that all data is assigned correctly before calculation
        this.hash = this.calculateHash(); }

calculateHash() {
        return SHA256(this.previousHash + this.timestamp + JSON.stringify(this.data)).toString();
    }
}

Next, because a blockchain is a chain of blocks, a blockchain can be represented as an array or chain, as shown below:

class BlockChain {
    constructor() {
        this.chain = [];
    }
}

Next, there's the genesis block, which is the first block in a blockchain always needs to be created manually. The previousHash of the first block is empty:

createGenesisBlock() {
    return new Block("2018-11-11 00:00:00", "Genesis block of simple chain", "");
}

The blockchain constructor method should also be changed to the following method:

class BlockChain {
    constructor() {
        this.chain = [this.createGenesisBlock()];
    }
}

Next, you can add blocks. Note that each block added must be connected to the original blockchain:

class BlockChain {
    getLatestBlock() {
        return this.chain[this.chain.length - 1];
    }
    
    addBlock(newBlock) {
        // The previous hash value of the new block is the hash value of the last block of the existing blockchain;
        newBlock.previousHash = this.getLatestBlock().hash;
        // Recalculate the hash value of the new block (because the previousHash is specified);
        newBlock.hash = newBlock.calculateHash(); 
        //Add new blocks to the chain;
        this.chain.push(newBlock); 
    }
    ...
}

Now you can verify the blockchain. The core of the structure of blockchain data is to ensure proper linking and tamper resistance. However, if a block is tampered with, how can we verify and find the block? The most labor-intensive yet the most common method is to go through, or traverse, all the blocks and verify them one by one:

isChainValid() {
    //Traverse all the blocks
    for (let i = 1; i < this.chain.length; i++) {
        const currentBlock = this.chain[i];
        const previousBlock = this.chain[i - 1];
        //Recalculate the has value of the current block. If the hash value is not matched, it indicates that data of the block was changed without permission, and therefore the has value is not recalculated.
        if (currentBlock.hash !== currentBlock.calculateHash()) {
            console.error("hash not equal: " + JSON.stringify(currentBlock));
            return false;
        }
        // Determine whether the previousHash of the current block is equal to the hash of the previous block. If they are not equal to each other, this means that the previous block was changed without permission. Although the hash value is recalculated correctly, the hash value of the subsequent block is not recalculated, resulting the the whole chain breaking.
        if (currentBlock.previousHash !== previousBlock.calculateHash) {
            console.error("previous hash not right: " + JSON.stringify(currentBlock));
            return false;
        }
    }
    return true;
}

Now, run the following code:

let simpleChain = new BlockChain();
simpleChain.addBlock(new Block("2018-11-11 00:00:01", {amount: 10}));
simpleChain.addBlock(new Block("2018-11-11 00:00:02", {amount: 20}));


console.log(JSON.stringify(simpleChain, null, 4));

console.log("is the chain valid? " + simpleChain.isChainValid());

The result is shown as follows:

ali-186590cc4a7f:simple-chain shanyao$ node main_1.js 
{
    "chain": [
        {
            "timestamp": "2018-11-11 00:00:00",
            "data": "Genesis block of simple chain",
            "previousHash": "",
            "hash": "fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89"
        },
        {
            "timestamp": "2018-11-11 00:00:01",
            "data": {
                "amount": 10
            },
            "previousHash": "fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89",
            "hash": "150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529"
        },
        {
            "timestamp": "2018-11-11 00:00:02",
            "data": {
                "amount": 20
            },
            "previousHash": "150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529",
            "hash": "274a7a13ed20118e8cb745654934a7e24a4d59333ba17dfbf5d4cfe0fa8a6e34"
        }
    ]
}
is the chain valid? true

Make sure that you pay attention to the previousHash and hash. You can see that the previousHash of the current block is actually the hash of the previous block.

Now, let's try to tamper with a block. A common claim is that blockchains are tamper proof. It that true? Well, let's try to tamper, specifically alter, with the second block and see what will happen:

let simpleChain = new BlockChain();
simpleChain.addBlock(new Block("2018-11-11 00:00:01", {amount: 10}));
simpleChain.addBlock(new Block("2018-11-11 00:00:02", {amount: 20}));

console.log("is the chain valid? " + simpleChain.isChainValid());

// Change the data of the second block from 10 to 15
simpleChain.chain[1].data = {amount: 15};

console.log("is the chain still valid? " + simpleChain.isChainValid());
console.log(JSON.stringify(simpleChain, null, 4));

The result is shown as follows:

ali-186590cc4a7f:simple-chain shanyao$ node main_1.js 
is the chain valid? true
hash not equal: {"timestamp":"2018-11-11 00:00:01","data":{"amount":15},"previousHash":"fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89","hash":"150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529"}
is the chain still valid? false
{
    "chain": [
        {
            "timestamp": "2018-11-11 00:00:00",
            "data": "Genesis block of simple chain",
            "previousHash": "",
            "hash": "fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89"
        },
        {
            "timestamp": "2018-11-11 00:00:01",
            "data": {
                "amount": 15
            },
            "previousHash": "fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89",
            "hash": "150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529"
        },
        {
            "timestamp": "2018-11-11 00:00:02",
            "data": {
                "amount": 20
            },
            "previousHash": "150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529",
            "hash": "274a7a13ed20118e8cb745654934a7e24a4d59333ba17dfbf5d4cfe0fa8a6e34"
        }
    ]
}

From the above result, you can see that, after the data has been tampered with, the hash value is not recalculated, causing the block to have a mismatching hash value.

Now, let's try to tamper with another block. Let's think. What if we do it in a smart way and recalculate the hash value after the block has been tampered with?

let simpleChain = new BlockChain();
simpleChain.addBlock(new Block("2018-11-11 00:00:01", {amount: 10}));
simpleChain.addBlock(new Block("2018-11-11 00:00:02", {amount: 20}));

console.log("is the chain valid? " + simpleChain.isChainValid());
// Recalculate the hash value after tampering with it
simpleChain.chain[1].data = {amount: 15};
simpleChain.chain[1].hash = simpleChain.chain[1].calculateHash();
console.log("is the chain still valid? " + simpleChain.isChainValid());
console.log(JSON.stringify(simpleChain, null, 4));

The result is shown as follows:

ali-186590cc4a7f:simple-chain shanyao$ node main_1.js 
is the chain valid? true
previous hash not right: {"timestamp":"2018-11-11 00:00:02","data":{"amount":20},"previousHash":"150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529","hash":"274a7a13ed20118e8cb745654934a7e24a4d59333ba17dfbf5d4cfe0fa8a6e34"}
is the chain still valid? false
{
    "chain": [
        {
            "timestamp": "2018-11-11 00:00:00",
            "data": "Genesis block of simple chain",
            "previousHash": "",
            "hash": "fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89"
        },
        {
            "timestamp": "2018-11-11 00:00:01",
            "data": {
                "amount": 15
            },
            "previousHash": "fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89",
            "hash": "74d139274fb692495b7c805dd5822faa0c5b5e6058b6beef96e87e18ab83a6b1"
        },
        {
            "timestamp": "2018-11-11 00:00:02",
            "data": {
                "amount": 20
            },
            "previousHash": "150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529",
            "hash": "274a7a13ed20118e8cb745654934a7e24a4d59333ba17dfbf5d4cfe0fa8a6e34"
        }
    ]
}

The previousHash of the third block is not the hash of the second hash.

So, is the blockchain really temper proof? In reality, a blockchain is not completely tamper proof. Let me show you. What if we can be much smarter and recalculate the hash values of all the subsequent blocks? It would look like this:

let simpleChain = new BlockChain();
simpleChain.addBlock(new Block("2018-11-11 00:00:01", {amount: 10}));
simpleChain.addBlock(new Block("2018-11-11 00:00:02", {amount: 20}));

console.log("is the chain valid? " + simpleChain.isChainValid());
// Tamper with the second block
simpleChain.chain[1].data = {amount: 15};
simpleChain.chain[1].hash = simpleChain.chain[1].calculateHash();
// Then, recalculate the third block
simpleChain.chain[2].previousHash = simpleChain.chain[1].hash;
simpleChain.chain[2].hash = simpleChain.chain[2].calculateHash();
console.log("is the chain still valid? " + simpleChain.isChainValid());
console.log(JSON.stringify(simpleChain, null, 4));

The result is shown as follows:

ali-186590cc4a7f:simple-chain shanyao$ node main_1.js 
is the chain valid? true
is the chain still valid? true
{
    "chain": [
        {
            "timestamp": "2018-11-11 00:00:00",
            "data": "Genesis block of simple chain",
            "previousHash": "",
            "hash": "fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89"
        },
        {
            "timestamp": "2018-11-11 00:00:01",
            "data": {
                "amount": 15
            },
            "previousHash": "fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89",
            "hash": "74d139274fb692495b7c805dd5822faa0c5b5e6058b6beef96e87e18ab83a6b1"
        },
        {
            "timestamp": "2018-11-11 00:00:02",
            "data": {
                "amount": 20
            },
            "previousHash": "74d139274fb692495b7c805dd5822faa0c5b5e6058b6beef96e87e18ab83a6b1",
            "hash": "cc294e763c51e9357bf22d96073e643f4d51e07dd0de6e9b15d1d4f6bf6b45a8"
        }
    ]
}

Now, the whole blockchain has been tampered with. In fact, after you tamper with a block, you can further tamper with the whole blockchain by tampering with all the blocks after that block. However, if the number of the blocks after a tampered block is very large, to tamper with the whole blockchain, you need to tamper with the subsequent blocks quickly enough so to not be found out. In addition, the cost of tampering is also very high in this case. Therefore, as you can see, a blockchain is not completely tamper-proof, but at the same time, tampering a block A blockchain is usually designed to have the tampering cost larger that the potential benefits from blockchain tampering. In this regard, the whole blockchain can be considered secure and essentially tamper proof.

Why Blockchains Are More Secure

So as we've established in the previous section, a blockchain is actually not completely tamper proof, but yet they're still pretty secure. To avoid the chances of our blockchain being tampered with, we need to increase the potential cost it would take to tamper with our blockchain. But how can we increase this cost? Well, the most labor-intensive method may be to artificially create barriers: So, do you want to participate in blockchain accounting? If so, then let's solve this difficult math problem first to prove your ability and willingness. This involves a very simple concept—PoW.

Before we can get into it, let's first solve this math problem. What is the math problem, you may ask? It is not that complicated, but may look a little silly, actually. Ensure that the first 10 digits of the hash value are 0. You can only solve this math problem by guessing and trying your luck. Hash calculation has the following characteristics:

  • Information can be of any length, whether it is a sentence, an article, or a song, and it can be calculated as a unique string of numbers.
  • This string of numbers has fixed length.
  • The calculation process is irreversible. That is to say, you can easily calculate the hash value of a text paragraph, but you cannot know the original information that a hash value corresponds to.

If you are given a text paragraph and allowed to add a nonce at the end of the text, you cannot find a way to ensure that the first 10 hash value of the text and nonce are 0 other than keep trying different numbers and hope that you are lucky enough to guess correctly as soon as possible.

Now, let's get into the coding part of all of this. First things first. Let's add a nonce to a block. First, note that the hash value of the previous block is fixed:

calculateHash() {
        return SHA256(this.previousHash + this.timestamp + JSON.stringify(this.data)).toString();
  }

This value cannot be changed. To solve this math problem, you need to manually add a nonce to the block:

constructor(timestamp, data, previousHash = '') {
    this.timestamp = timestamp;
    this.data = data;
    this.previousHash = previousHash;
    this.nonce = 0;
    this.hash = this.calculateHash();
}

The nonce does not have any special meaning. It is added to change the generated hash value so that the hash value can meet the requirement.

The hash calculation process needs to be changed accordingly:

calculateHash() {
    return SHA256(this.previousHash + this.timestamp + JSON.stringify(this.data) + this.nonce).toString();
}

So, as we discussed before, the math problem is to try to change the nonce to ensure that the first 10 digits of the hash are 0. For this, the corresponding code is as follows:

this.hash.substring(0, 10) === Array(10 + 1).join("0")

The first 10 digits of the hash are 0, which is preferred because this ensures the first 10 digits or the first 5 digits are 0. Well, the difficulty varies depending on the number of digits. It is much more difficult to ensure that the first 10 digits of the hash are 0 rather than the first 5 digits. The number of digits can be seen as difficulty. Mining is to try different nonces to make the hash value compliant:

mineBlock(difficulty) {
    while (this.hash.substring(0, difficulty) !== Array(difficulty + 1).join("0")) {
        this.nonce++;
        this.hash = this.calculateHash();
    }
    console.log("Block mined, nonce: " + this.nonce + ", hash: " + this.hash);
}

For the sake of simplicity, difficulty can be used as a fixed parameter in the blockchain. Note: In fact, the difficulty in Bitcoin is dynamically adjusted to keep the block generation time at about 10 minutes.

constructor() {
    this.chain = [this.createGenesisBlock()];
    this.difficulty = 2;
}

Adding blocks is no longer adding blocks directly. Instead, now the block addition process is mining:

addBlock(newBlock) {
    newBlock.previousHash = this.getLatestBlock().hash;
    newBlock.mineBlock(this.difficulty);
    this.chain.push(newBlock);
}

Only compliant blocks can be added. Now let's run the following code:

let simpleChain = new BlockChain();
console.log("Mining block 1...");
simpleChain.addBlock(new Block("2018-11-11 00:00:01", {amount: 10}));
console.log("Mining block 2...");
simpleChain.addBlock(new Block("2018-11-11 00:00:02", {amount: 20}));


console.log(JSON.stringify(simpleChain, null, 4));

You will find that the block generation is much slower:

ali-186590cc4a7f:simple-chain shanyao$ node main_2.js 
Mining block 1...
Block mined, nonce: 464064, hash: 0000e7e1aae4fae9d245f8d4b8ce030ffe13270218c362511db6840a824a1cdb
Mining block 2...
Block mined, nonce: 4305, hash: 000047b449537483d7f2861a12b53a59c971d3a928b2c0110a5945bff1a82616
{
    "chain": [
        {
            "timestamp": 0,
            "data": "2018-11-11 00:00:00",
            "previousHash": "Genesis block of simple chain",
            "nonce": 0,
            "hash": "8a7b66d194b1b0b795b0c45b3f11b60e8aa97d3668c831f39ec3343c83ae41c0"
        },
        {
            "timestamp": "2018-11-11 00:00:01",
            "data": {
                "amount": 10
            },
            "previousHash": "8a7b66d194b1b0b795b0c45b3f11b60e8aa97d3668c831f39ec3343c83ae41c0",
            "nonce": 464064,
            "hash": "0000e7e1aae4fae9d245f8d4b8ce030ffe13270218c362511db6840a824a1cdb"
        },
        {
            "timestamp": "2018-11-11 00:00:02",
            "data": {
                "amount": 20
            },
            "previousHash": "0000e7e1aae4fae9d245f8d4b8ce030ffe13270218c362511db6840a824a1cdb",
            "nonce": 4305,
            "hash": "000047b449537483d7f2861a12b53a59c971d3a928b2c0110a5945bff1a82616"
        }
    ],
    "difficulty": 4
}

In this example, the difficulty is 4. Therefore, the first 4 digits of the block hash value are 0.

What Is Proof of Work (PoW)

The above is an example of the concept of Proof of Work (usually abbreviated PoW). Although this concept seems simple, it has been proven to be highly effective and safe in several blockchains. For example, the PoW system in bitcoin guarantees consistency among millions of machines without human intervention and hasn't had a single error within 10 years. It is undoubtedly miraculous, one could say. In fact, more is behind this simple concept. The core of PoW is the one-cpu-one-vote method, that is, to express your views and opinions by using your CPU (power). Why is it one-cpu-one-vote instead of one-ip-one-vote, you may ask? Well, it's because you can simulate a large number of IP addresses at a very low cost. CPUs were used in PoW at the time (an example from 2008) because CPUs resources were quite expensive and could ensure the difficulty and fairness of mining (see the bitcoin whitepaper by Satoshi Nakamoto). Of course, what Satoshi Nakamoto did not realize at that time is that the birth of specific algorithm chips like application-specific integrated circuit (ASIC) ones have made ordinary CPU mining more and more difficult to accomplish. This is not described any further in this article due to space limitations.

What is the fundamental part of PoW? It is that PoW provides an anchor. That is, PoW implements anchoring between bitcoins in the virtual world and CPUs in the physical world and uses expensive resources in the physical world to ensure the security of bitcoins. Some may say that bitcoin mining consumes lots of electricity and is a wasteful process. However, this view is quite biased. In reality, bitcoin may well be the cheapest currency system in the world. After all, the circulation of many currents around the world have been backed by administrative bodies, which have been involved in massive expensive to protect and maintain the value of their currency. However, bitcoin only consumes computing power and electricity and this consumption is not completely meaningless, because the larger the computing power, the more secure the bitcoin system.

In fact, in addition to PoW, common consensus mechanisms include DPOS (delegate-proof-of-stake), PBFT (Practical Byzantine Fault Tolerance) widely used in private blockchains, and Raft. These consensus mechanisms are not within the scope of this article.

Profit-Driven Blockchain Development

The system of blockchain we discussed above is too simple and has the following major problems:

  • Each block only includes one transaction. This leads to high transaction cost. In fact, each block of a blockchain in real scenarios contains multiple transactions, which are packaged together in one block at the same time.
  • No reward is provided for mining. If there's no reward provided for mining, who would be willing to buy mining machines, foot the electricity bill, and verify and package your transactions? Just think, the world definitely needs selfless people who are willing to make some sacrifices, but self-sacrifice alone cannot maintain constant progress, right? To be completely honest, world progress is also clearly driven by profits. Reasonable blockchain system design and incentive systems are fundamental to the stability of the blockchain. In fact, mining is the only method used by many PoW-based cryptocurrencies. For example, there are only 21 million possible bitcoins in total. These bitcoins can only circulate in the secondary market after they are mined.

Below we will look into a solution to solve these two major problems.

First, you're going to want to define the transaction. The basic information included in a transaction is the transaction amount and the two parties involved:

class Transaction {
    constructor(fromAddress, toAddress, amount) {
        this.fromAddress = fromAddress;
        this.toAddress = toAddress;
        this.amount = amount;
    }
}

Each block should contain multiple transactions, so to update the previous data to be accurate with transactions:

class Block {
    constructor(timestamp, transactions, previousHash = '') {
        this.timestamp = timestamp;
        this.transactions = transactions;
        this.previousHash = previousHash;
        this.nonce = 0;
        this.hash = this.calculateHash();
    }
    ....
}

The data structure of the blockchain should also be upgraded accordingly. The transactions to be processed and the reward amount for each mining operation should be added:

class BlockChain {
    constructor() {
        this.chain = [this.createGenesisBlock()];
        this.difficulty = 3;
        this.pendingTransactions = [];
        this.miningReward = 100;
    }
    ....
}

Pay attention to the structural relationship:

  • One chain contains multiple blocks.
  • One block contains multiple transactions.

Next, let's look into mining. Accordingly, the previous addBlock method should be changed to minePendingTransactions, which mainly differs from the former in the following aspects:

  • A new block added is not just an ordinary block, but a block that contains information about all the pending transactions. For the sake of simplicity, all pending transactions are packaged into one block. However, this is not true in real scenarios. For example, an original bitcoin block is too small (1 MB) to contain all the pending transactions. Remaining transactions after one packaging operation have to be packaged into another block. In addition, miners usually give higher priority to transactions with higher miner fees.
  • Pay miner fees. Generally, after a miner mines a block, a batch of transfer transactions to the miner address will be generated and the transfer transactions will be packaged into the next block.

For example:

//Incoming miner address
minePendingTransactions(miningRewardAddress) {
    // Pakcage all pending transactions together in the same block
    let block = new Block(Date.now(), this.pendingTransactions);
    // Mining, that is, constantly trying nonce to make the hash Vluw meet the requirements
    block.mineBlock(this.difficulty);

    console.log('Block successfully mined!');
    this.chain.push(block);
    
    // Put the miner fee transaction into pendingTransactions for the next processing operation. The miner fee transaction is characterized by the source account being empty.
    this.pendingTransactions = [
        new Transaction(null, miningRewardAddress, this.miningReward)
    ];
}
// Create a transaction, put the transaction into the pending transaction pool.
createTransaction(transaction) {
    this.pendingTransactions.push(transaction);
}

Next, you can query the balance. In addition to transfer transactions, sometimes people may want to check the balance of an account. However, no real account exists in a blockchain. Common account models include the UTXO model of bitcoin and the Account/Balance model of Ethereum. Apparently, in our example, no real account exists, either. In this example, the blockchain transaction only records the transaction amount and the two transaction parties. It does not record the account balance. Then, with this being the case, how can we know the balance of an account? The most labor-consuming method is to iterate all the transaction information in the blockchain and deduce the balance of an account based on from, to, and amount information:

getBalanceOfAddress(address) {
    let balance = 0;
    for (const block of this.chain) {
        for (const transaction of block.transactions) {
            //账户转出,余额减少
            if (transaction.fromAddress === address) {
                balance -= transaction.amount;
            }
            //账户转入,余额增加
            if (transaction.toAddress === address) {
                balance += transaction.amount;
            }
        }
    }
    return balance;

So now was the transfer transaction successful? Did the miner receive the miner fees? Well, let's run the code and see what happens:

let simpleCoin = new BlockChain();
// First, create two transactions, one where address1 first transfers 100 to address2 and another where address2 transfers 60 to address1.
simpleCoin.createTransaction(new Transaction('address1', 'address2', 100));
simpleCoin.createTransaction(new Transaction('address2', 'address1', 60));

console.log('starting the miner...');
simpleCoin.minePendingTransactions('worker1-address');
// Then, if these actions succeed, this means that address2 will have 40.
console.log('Balance of address2 is: ', simpleCoin.getBalanceOfAddress('address2'));
// How much should a miner account then? For this transaction, the appropriate miner fee is 100.
console.log('Balance of miner is: ', simpleCoin.getBalanceOfAddress('worker1-address'));

// Create another transaction where address2 transfers 10 to address1.
simpleCoin.createTransaction(new Transaction('address2', 'address1', 10));

console.log('starting the miner again...');
simpleCoin.minePendingTransactions('worker1-address');
// Then, if this transfer was successful, then address2 will have 30.
console.log('Balance of address2 is: ', simpleCoin.getBalanceOfAddress('address2'));
// So now how much should a miner fee be then? Having handled two blocks, it should be set at 200, right?
console.log('Balance of miner is: ', simpleCoin.getBalanceOfAddress('worker1-address'));

The result is shown as follows:

ali-186590cc4a7f:simple-chain shanyao$ node main_3.js 
starting the miner...
Block mined, nonce: 2121, hash: 000cd629157ee59494dfc08329d4cf265180c26010935993171b6881f9bae578
Block successfully mined!
Balance of address2 is:  40
Balance of miner is:  0
starting the miner again...
Block mined, nonce: 1196, hash: 000d5f8278ea9bf4f30c9cc05b4cc36aab8831dc5860e42c775360eb85bc238e
Block successfully mined!
Balance of address2 is:  30
Balance of miner is:  100

The balance of address2 is as expected. However, the balance of the miner is still 0 after the block was successfully added. Why hasn't the miner received fees? This is because the miner fees are put into the next block and the miner of the current block can only receive miner fees after the block is added.

Transaction Signature and Verification

As described before, it seems that anyone can start a transaction. For example, I may want to start a transaction that transfers 100 coins from the account of, say, Satoshi Nakamoto to my account. Is this feasible? Well, if you use the models you used above, it does seem that anyone can start a transaction. However, this isn't completely in line with reality. So far, we have not gone over one important thing—which is the signing of transactions to ensure that only person, who just happens to be you, can transfer your money out and to also ensure that you cannot perform operations with another person's account if you do not have their relevant credentials, i.e. account name and password.

So, let's get into passwords, and more importantly irretrievable passwords. Passwords are common in the real world—for example, you've got a password for your credit card, your social media and e-commerce website accounts, and also you've got passwords for automatic gate controls. You must keep these passwords secure. Otherwise, once these passwords just so happened to be leaked, you may suffer asset loss or private information being exposed. If you suspect that your credit card password has been stolen, you need to change your password immediately. If you forget your password, you can take your ID card with you to the bank to set a new password.

However, in blockchain, you cannot simply modify or retrieve your password. Moreover, no ID card exists in the blockchain world. Rather, the password itself—or private key—is considered your ID card in blockchain.

Now, let's talk about asymmetric encryption. The only password in blockchain is called the private key. So, what is a private key, and how is a private key generated? A private key is generated through asymmetric encryption. Asymmetric encryption uses different keys for encryption and decryption. It sounds complicated. However, the principle is very simple:

  • An asymmetric encryption algorithm generates a pair of keys. One is a public key and the other is a private key. The two keys can encrypt and decrypt data: Only the corresponding private key can decrypt data encrypted with the public key; only the corresponding public key can decrypt data signed and encrypted with the user's private key.
  • The private key cannot be deduced from the corresponding public key, but the public key can be deduced from the corresponding private key. Most Rivest, Shamir, and Adelman (RSA) cryptosystem implementations follow Public Key Cryptography Standards (PKCS), which just means that the public key can be deduced from the private key, but the private key cannot be deduced from the public key.
  • The public key is used for encryption and the private key for decryption: Data encrypted with the public key can only be decrypted with the corresponding private key. The public key is like an email address, which is known to all and to which anyone can send emails. However, only the owner of the email has the password and can log on to the email account.
  • The private key is used for signatures and the public key for verification. For example, when A receives a letter from B, how can A confirm that this letter was really written by B? Well, B signed the letter with the private key. This is actually the encryption process). When A receives the letter, A uses the public key disclosed by B to decrypt the letter. If the decryption is successful, A can confirm that this letter was sent by B.

Let's generate a pair of public and private keys:

const EC = require('elliptic').ec;
const ec = new EC('secp256k1');

const key = ec.genKeyPair();
const publicKey = key.getPublic('hex');
const privateKey = key.getPrivate('hex');

console.log('Public key', publicKey);
console.log('Private key', privateKey);

The result is as follows:

Public key 04f1aa4d934e7f2035a6c2a2ebc9daf0e9ca7d13855c2a0fb8696ab8763e5ee263c803dfa7ac5ae23b25fb98151c99f91c55e89586717965758e6663772ebccd1b
Private key 1c258d67b50bda9377c1badddd33bc815eeac8fcb9aee5d097ad6cedc3d2310c

The private key has 32 bytes and is your unique password. The public key seems longer. However, why are bitcoin addresses usually short? The public key has 65 bytes and its first byte is fixed (0x04). Of the other 64 bytes, the first 32 bytes are the x-coordinates of the elliptic curve and the last 32 bytes the y coordinates of the elliptic curve. A bitcoin address is shorter because the string also experiences a series of transformations such as SHA256 encryption, RIPEMD160 encryption, and Base58 encoding before a Base58 address like 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa is generated. For the sake of simplicity, the address generation will not be described in this article. Generally, the public key can be considered your account address, which only differs from the public key in the format and can be reversely converted.

Now you can sign your transaction. To do it, first, you need to use your own private key to sign the transaction to prove that the transaction was really started by you:

class Transaction {
    // Calculate the hash in order to do the signature. You need to do this because the hash value will be signed instead of the original information directly. 
    calculateHash() {
        return SHA256(this.fromAddress + this.toAddress + this.amount).toString();
    }

    // Incoming key
    signTransaction(signingKey) {
        // Verify if the source account is the person's address, or more specifically, verify whether the source address is the public key corresponding to the private key.
        if (signingKey.getPublic('hex') !== this.fromAddress) {
            throw new Error('You cannot sign transactions for other wallets!')
        }
        const txHash = this.calculateHash();
        // Sign the transaction hash with the private key.
        const sig = signingKey.sign(txHash, 'base64');
        // Convert the signature to the DER format.
        this.signature = sig.toDER('hex');
        console.log("signature: "+this.signature)
    }
    ...
}

Next, you'll want to verify the transaction. When the other transaction party receives the transaction information, it is required to verify if this transaction is valid by using the public key of the source account to confirm that the signature is correct and from the fromAddress:

class Transaction {
    isValid() {
        // The miner fee transaction fromAddress is empty, verification cannot be completed.
        if (this.fromAddress === null) return true;
        // Determine if the signature exists
        if (!this.signature || this.signature.length === 0) {
            throw new Error('No signature in this transaction');
        }
        // Transcode fromAddress to get the public key (this process is reversible, as it is just a format conversion process.)
        const publicKey = ec.keyFromPublic(this.fromAddress, 'hex');
        // Use the public key to verify if the signature is correct, or more specifically if the transaction was actually initiated from fromAddress.
        return publicKey.verify(this.calculateHash(), this.signature);
    }
    ...
}

The preceding sample verifies the validity of a single transaction. Because a block contains multiple transactions, you also need to add a method to verify all the transactions in a block:

class Block {
    hasValidTransactions() {
        // Traverse all transactions within the block, verifying them one by one
        for (const tx of this.transactions) {
            if (!tx.isValid()) {
                return false;
            }
        }
        return true;
    }
    ...
}

Correspondingly, createTransaction needs to be replaced with addTransaction. That is, you cannot directly create transactions anymore. Instead, you need to verify signed transactions first and then only submit valid transactions. Example:

class BlockChain {
    addTransaction(transaction) {
        if (!transaction.fromAddress || !transaction.toAddress) {
            throw new Error('Transaction must include from and to address');
        }
        // Verify that the transaction is valid and valid before it can be submitted to the trading pool.
        if (!transaction.isValid()) {
            throw new Error('Cannot add invalid transaction to the chain');
        }
        this.pendingTransactions.push(transaction);
    }
    ...
}

Correspondingly, the isChainValid method of the blockchain should be used for verifying all the transactions in the block:

class BlockChain {
    isChainValid() {
        for (let i = 1; i < this.chain.length; i++) {
            const currentBlock = this.chain[i];
            const previousBlock = this.chain[i - 1];
            // Check if all transactions in the block are valid.
            if (!currentBlock.hasValidTransactions()) {
                return false;
            }
            if (currentBlock.hash !== currentBlock.calculateHash()) {
                console.error("hash not equal: " + JSON.stringify(currentBlock));
                return false;
            }
            if (currentBlock.previousHash !== previousBlock.calculateHash()) {
                console.error("previous hash not right: " + JSON.stringify(currentBlock));
                return false;
            }
        }
        return true;
    }
    ...
}

Now Run the following code:

const {BlockChain, Transaction} = require('./blockchain');
const EC = require('elliptic').ec;
const ec = new EC('secp256k1');
// Generate a pair of private and public keys with tools.
const myPrivateKey = '1c258d67b50bda9377c1badddd33bc815eeac8fcb9aee5d097ad6cedc3d2310c';
const myPublicKey = '04f1aa4d934e7f2035a6c2a2ebc9daf0e9ca7d13855c2a0fb8696ab8763e5ee263c803dfa7ac5ae23b25fb98151c99f91c55e89586717965758e6663772ebccd1b';

const myKey = ec.keyFromPrivate(myPrivateKey);
// Derive the public key from the private key
const myWalletAddress = myKey.getPublic('hex');
// Looking at the output, I did get the public key from the private key.
console.log("is the myWalletAddress from privateKey equals to publicKey?", myWalletAddress === myPublicKey);

let simpleCoin = new BlockChain();

const alice PublicKey = '047058e794dcd7d9fb0a256349a5e2d4d724b50ab8cfba2258e1759e5bd4c81bb6ac1b0490518287ac48f0f10a58dc00cda03ffd6d03d67158f8923847c8ad4e7d';
// Initiate a transaction and transfer 60 from your own account to Alice's account.
const tx1 = new Transaction(myWalletAddress, alicePublicKey, 60);
// Sign with private key
tx1.signTransaction(myKey);
// Submit a transaction
simpleCoin.addTransaction(tx1);

console.log('starting the miner...');
simpleCoin.minePendingTransactions(myWalletAddress);
// If the transfer is successful, the balance of Alice's account will be 60.
console.log('Balance of Alice's account is: ', simpleCoin.getBalanceOfAddress(alicePublicKey));

// Initiate a transaction and transfer back to your account from Alice's account
const tx2 = new Transaction(alicePublicKey, myWalletAddress, 20);
// You still sign with your private key, but doing so will cause an error. Because you don't know Alice's key, you cannot operate your account. That is, your key cannot open Alice's account.
tx2.signTransaction(myKey);
simpleCoin.minePendingTransactions(myWalletAddress);
console.log('Balance of Alice's account is: ', simpleCoin.getBalanceOfAddress(alicePublicKey));

The result is shown as follows:

ali-186590cc4a7f:simple-chain shanyao$ node main.js 
is the myWalletAddress from privateKey equals to publicKey? true
signature: 3045022100b87a9199c2b3fa31ac4092b27a41a616d99df884732dfd65972dc9eacd12da7702201f7957ef25d42c17cb2f6fb2888e6a0d5c521225d9b8851ba2d228f96d878f85
starting the miner...
Block mined, nonce: 15812, hash: 00081837c2ae46a1310a0873f5e3d6a1b14b072e3d32a538748fac71e0bfd91e
Block successfully mined!
Balance of trump is:  60
/Users/shanyao/front/simple-chain/blockchain.js:22
            throw new Error('You cannot sign transactions for other wallets!')

For the first transaction, you operate and sign your own account. The transaction is successful because you have the private key. For the second pending transaction, you operate the other person's account and fail to submit the transfer transaction because the private key is incorrect. The private key is your only password and passport. You can only own corresponding assets when you have the private key. Maybe this is the inviolability of private property in a real sense.

Summary

Now that you have read the article, how simple do you think blockchain is? Blockchain is not as complicated as you may think. However, it is also not very simple. In fact, the core consensus mechanism (or distributed consensus) and decentralized blockchain governance are not described in this article. This article only briefs on the basic blockchain structure, basic concepts, and general transaction process to give you a preliminary understanding of blockchain. Blockchain itself is a broad topic, which requires more research and exploration.

0 0 0
Share on

vic

3 posts | 0 followers

You may also like

Comments

vic

3 posts | 0 followers

Related Products