Hi all, in the previous article, I have covered how to create a simple Blockchain in just 60 lines of code. So today, I will start working on the second part of the series - transactions.
Please read the previous article first if you haven't known anything about blockchains yet.
Also, if this is already too familiar for you, consider checking out the third article about how to create a p2p network and release your cryptocurrency.
Also, you should definitely check out this tutorial on my new videos on Youtube for detailed information:
- Part 1: Building a Blockchain (proof of work) in 60 lines of Javascript.
- Part 2: Creating Transaction, Mining reward, Mint and Gas fee on the Blockchain in Javascript.
- Part 3: Building P2P network and releasing your cryptocurrency on your blockchain.
What we are trying to achieve
Basically, we need to have a representation of a transaction that includes the sender's wallet address, the receiver's wallet address and the amount we are sending. We will add it to a transaction pool, and when we create a new block, we will move all pending transactions to that block's data.
To prevent faulty transactions, we will use a signing mechanism along with a key pair. That key pair will include 2 keys: a private key, and a public key. The public key can be shown to others as a wallet address, the private key is used for signing transactions. Because only you hold the private key, only you can sign your account's transactions, ensuring safety.
We will talk about some other stuff like minting, initial coin release and gas fee.
No more saying, let's start coding!
The transaction class
So we will have a basic class like this:
class Transaction {
constructor(from, to, amount) {
this.from = from;
this.to = to;
this.amount = amount;
}
}
Mining transactions
Moving back to the Blockchain
class, first of all, we need a transactions pool which holds all the pending transactions.
this.transactions = [];
Now, we will have a method to create a transaction:
addTransaction(transaction) {
this.transactions.push(transaction);
}
Mining the transactions:
mineTransactions() {
this.addBlock(new Block(Date.now().toString(), this.transactions));
this.transactions = [];
}
We just basically pass in the pending transactions and then clear the current pending transactions pool.
Mining reward
No one would like to lose their computational power to mine transactions for you for free, so you need to have some form of reward for the miners/validators.
First, we will have a mining reward property, you can set it to whatever you like, I'll set it to 297 for no real reason.
this.reward = 297;
Now, we shall create a transaction that transfers the reward to the miner.
mineTransactions(rewardAddress) {
this.addBlock(new Block(Date.now().toString(), [new Transaction(CREATE_REWARD_ADDRESS, rewardAddress, this.reward), ...this.transactions]));
// Right now, we are just going assume the "from" address is something like this,
// we will get back to this later in the next part of the article.
this.transactions = [];
}
Minting
This is a well-known term in blockchain development, it simply refers to the act of adding more coins, or printing more money to be simple. When the chain gives miners reward, it is actually minting coins in the mean time.
Signing
We can't miss the signing mechanism, it's ESSENTIAL!
Before we proceed, it's important to do some research about signing
first, it's a popular mechanism in cryptography.
I will use an algorithm used by Bitcoin and Ethereum - secp256k1 for generating key pairs.
Since we are staying simple, we wouldn't want to implement the algorithm on our own since it's terribly long and can even be longer than this article.
We are using a package called elliptic
, note that elliptic also supports Curve25519
and other algorithms.
Install it through npm:
npm i elliptic
Generate a keypair
This is an example of generating a key pair:
const EC = require("elliptic").ec, ec = new EC("secp256k1");
const keyPair = ec.genKeyPair();
// public key: keyPair.getPublic("hex")
// private key: keyPair.getPrivate("hex")
Signing transactions
Create a sign
method in the Transaction
class:
sign(keyPair) {
// Check if the public key matches the "from" address of the transaction
if (keyPair.getPublic("hex") === this.from) {
// Sign the transaction
this.signature = keyPair.sign(SHA256(this.from + this.to + this.amount), "base64").toDER("hex");
}
}
Validation
The chain is valid when all blocks have valid transactions, transactions are valid only when:
- From, to, amount are not empty.
- Sender's address has more money than the amount sent.
- The signature matches with the data of the transaction.
First, we will create a method in the Blockchain
class to get an address's balance for convenience.
We can implement a method to get an address's balance based on the transaction history of the chain:
getBalance(address) {
let balance = 0;
this.chain.forEach(block => {
block.data.forEach(transaction => {
// Because if you are the sender, you are sending money away, so your balance will be decremented.
if (transaction.from === address) {
balance -= transaction.amount;
}
// But if you are the receiver, you are receiving money, so your balance will be incremented.
if (transaction.to === address) {
balance += transaction.amount;
}
})
});
return balance;
}
So we will have a method like this in our Transaction
class:
isValid(tx, chain) {
return (
tx.from &&
tx.to &&
tx.amount &&
chain.getBalance(tx.from) >= tx.amount &&
ec.keyFromPublic(tx.from, "hex").verify(SHA256(tx.from + tx.to + tx.amount + tx.gas), tx.signature)
);
}
Inside the Block
class, create a method to check if it has valid transactions or not.
hasValidTransactions(chain) {
return this.data.every(transaction => transaction.isValid(transaction, chain));
}
Update the isValid
method of the Blockchain
class:
if (
currentBlock.hash !== currentBlock.getHash() ||
prevBlock.hash !== currentBlock.prevHash ||
!currentBlock.hasValidTransactions(blockchain)
) {
return false;
}
Now, we also need to check if a transaction is valid before pushing it to the pool:
addTransaction(transaction) {
if (transaction.isValid(transaction, this)) {
this.transactions.push(transaction);
}
}
Now, let's get back to minting
as I have promised. First, I'll create an address just for minting.
const MINT_KEY_PAIR = ec.genKeyPair();
const MINT_PUBLIC_ADDRESS = MINT_KEY_PAIR.getPublic("hex");
New method:
mineTransactions(rewardAddress) {
// Create a mint transaction for reward.
const rewardTransaction = new Transaction(MINT_PUBLIC_ADDRESS, rewardAddress, this.reward);
rewardTransaction.sign(MINT_KEY_PAIR);
// We will add the reward transaction into the pool.
this.addBlock(new Block(Date.now().toString(), [rewardTransaction, ...this.transactions]));
this.transactions = [];
}
Chain's address will have an exception: Its balance will not be checked since we are printing money, so we need to update Transaction.isValid
as well. Also, its amount must be the exact same as the defined reward.
isValid(tx, chain) {
return (
tx.from &&
tx.to &&
tx.amount &&
(chain.getBalance(tx.from) >= tx.amount || tx.from === MINT_PUBLIC_ADDRESS) &&
ec.keyFromPublic(tx.from, "hex").verify(SHA256(tx.from + tx.to + tx.amount), tx.signature)
);
}
Releasing the first few coins ever
Going back to the Blockchain
class, we will make some changes to our genesis block. We will mint some coins for one address (creating a max diluted marketcap). If anyone wants to buy our currency, they will lend us some money, and we will send them coins. This is also called a contract.
Create the keyPair first
const holderKeyPair = ec.genKeyPair();
In the genesis block, simply create a transaction for inital coin release.
// We will release 100000 coin
const initalCoinRelease = new Transaction(MINT_PUBLIC_ADDRESS, holderKeyPair.getPublic("hex"), 100000);
this.chain = [new Block(Date.now().toString(), [initalCoinRelease])];
The problem with minting
If you are wondering if anyone can access the minting address, can't we print out a lot and a lot faulty money? You would be right, but we are going to handle all of our problems with a peer-to-peer network, which I'm going to make in the next article.
The peer-to-peer network handles this problem by simply dismissing the chain of which block:
- Has more or less than 1 transaction for minting.
- Has lower than 1 transaction other than the mint transaction, it basically means that he's constantly minting blocks without actually producing some real transactions.
Gas fees
There is also a kind of miner's reward called gas fee
, but it's a little different. It's basically user's reward for miners. This makes mining more appealing to miners, and it also pays for the energy used for mining, and people would have to pay a higher gas fee to be picked by miners quicker.
We are adding the gas
property into our Transaction
class.
class Transaction {
// Gas will be set to 0 because we are making it optional
constructor(from, to, amount, gas = 0) {
this.from = from;
this.to = to;
this.amount = amount;
this.gas = gas;
}
sign(keyPair) {
if (keyPair.getPublic("hex") === this.from) {
// Add gas
this.signature = keyPair.sign(SHA256(this.from + this.to + this.amount + this.gas), "base64").toDER("hex");
}
}
isValid(tx, chain) {
return (
tx.from &&
tx.to &&
tx.amount &&
// Add gas
(chain.getBalance(tx.from) >= tx.amount + tx.gas || tx.from === MINT_PUBLIC_ADDRESS) &&
ec.keyFromPublic(tx.from, "hex").verify(SHA256(tx.from + tx.to + tx.amount + tx.gas), tx.signature)
);
}
}
We will update the getBalance
method too:
getBalance(address) {
let balance = 0;
this.chain.forEach(block => {
block.data.forEach(transaction => {
if (transaction.from === address) {
balance -= transaction.amount;
balance -= transaction.gas
}
if (transaction.to === address) {
balance += transaction.amount;
}
})
});
return balance;
}
Now, we should give the gas fee to the miner:
mineTransactions(rewardAddress) {
let gas = 0;
this.transactions.forEach(transaction => {
gas += transaction.gas;
});
const rewardTransaction = new Transaction(MINT_PUBLIC_ADDRESS, rewardAddress, this.reward + gas);
rewardTransaction.sign(MINT_KEY_PAIR);
// Prevent people from minting coins and mine the minting transaction.
if (this.transactions.length !== 0) this.addBlock(new Block(Date.now().toString(), [rewardTransaction, ...this.transactions]));
this.transactions = [];
}
Our validation method for the block must also be changed:
hasValidTransactions(chain) {
let gas = 0, reward = 0;
this.data.forEach(transaction => {
if (transaction.from !== MINT_PUBLIC_ADDRESS) {
gas += transaction.gas;
} else {
reward = transaction.amount;
}
});
return (
reward - gas === chain.reward &&
this.data.every(transaction => transaction.isValid(transaction, chain)) &&
this.data.filter(transaction => transaction.from === MINT_PUBLIC_ADDRESS).length === 1
);
}
Testing
// Your original balance is 100000
const girlfriendWallet = ec.genKeyPair();
// Create a transaction
const transaction = new Transaction(holderKeyPair.getPublic("hex"), girlfriendWallet.getPublic("hex"), 100, 10);
// Sign the transaction
transaction.sign(holderKeyPair);
// Add transaction to pool
JeChain.addTransaction(transaction);
// Mine transaction
JeChain.mineTransactions(holderKeyPair.getPublic("hex"));
// Prints out balance of both address
console.log("Your balance:", JeChain.getBalance(holderKeyPair.getPublic("hex")));
console.log("Your girlfriend's balance:", JeChain.getBalance(girlfriendWallet.getPublic("hex")));
So, that's it! Here's the full source code if you want:
Honourable mention
The code from this video was made by me and my friend, whose name is Apple.
You can check his Github account in here: https://github.com/apple096/
Resources
Find me on:
Check out the Youtube version of this article:
Top comments (5)
This article assumes a prerequisite of a girlfriend :( /j
I always keep giving examples as things I don't have lol.
we, developers are the most single entities in the world!
BTW thanks for your great article, I need to read it several times to get it all.
Hmm signature verification fails for me. Always returns false
isValid(tx, chain) {
....
ec.keyFromPublic(tx.from, "hex").verify(SHA256(tx.from + tx.to + tx.amount + tx.gas), tx.signature)
...
If we want to create a miner using gpu power, how can we do it?