Blockchain Development with PHP
- Tram Ho
2021 is sure to be a boom year for Blockchain, when family members make Blockchain, NFT games, Token…. So what if Blockchain is developed on PHP language?
What is Blockchain?
Blockchain (also known as the ledger) is a database system that allows the storage and transmission of information blocks (blocks). They are linked together by encryption. These information blocks operate independently and can expand over time. They are managed by system participants and not through an intermediary. That is, when a block of information is written to the Blockchain system, there is no way to change it. More can only be added when everyone agrees.
Create a block
We will initialize a class called Block, this class will do the job of encrypting all input data into a key chain.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | <?php class Block { public $index; //index là key của 1 block public $previousHash; // đây là hash của block trước public $timeHash; // là thời gian mã hóa của block public $data; // data là của block ( nội dung transaction chẳng hạn ) public $hash; // hash của block này public function __construct($index = 0, $previousHash = '', $timeHash = '', $data = '') { $this->index = $index; $this->previousHash = $previousHash; $this->timeHash = $timeHash; $this->data = $data; $this->hash = $this->execHash(); } public function execHash() { if(is_array($this->data)) { $dataContent = json_encode($this->data); } else { $dataContent = $this->data; } return hash('sha256', $this->index . $this->previousHash . $dataContent . $this->timeHash); } } |
Next we create a Blockchain class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | <?php namespace App\Services; use Carbon\Carbon; class BlockChain extends Block { public $chain = array(); public function __construct() { $this->chain[] = $this->createGenesisBlock(); parent::__construct(); } public function createGenesisBlock() { return new Block(0000,'the first', Carbon::now()->format('d/m/Y H:i:s'), ['from' => 'hunghd', 'to' => 'hunghd2', 'amount'=> 3000]); } private function getLatestBlock() { return $this->chain[(count($this->chain) - 1)]; } public function addBlock($index, $timeHash, $data) { $previousHash = $this->getLatestBlock()->hash; $newBlock = new Block($index, $previousHash, $timeHash, $data); $this->chain[] = $newBlock; } } |
Here, I will create a block when there is 1 data passed in. If the data is a transaction hunghd send money to hunghd2 , at this time I will create a new Block. This block is the first block so index = 0 , previousHash = 'the first'
.And when there is more transaction hunghd2 sent to hunghd3 , the next block will have previousHash = hash of the previous block
. Since then, every new transaction added will create a new block connecting with the previous ones. From there we have a basic blockchain. This is the resulting code when I run the block:
1 2 3 4 5 | $bl = new \App\Services\BlockChain(); $bl->addBlock(1, Carbon::now()->format('d/m/Y H:i:s'), ['from' => 'hunghd2', 'to' => 'hunghd3', 'amount'=> 4000]); $bl->addBlock(2, Carbon::now()->format('d/m/Y H:i:s'), ['from' => 'hunghd3', 'to' => 'hunghd4', 'amount'=> 5000]); $bl->addBlock(3, Carbon::now()->format('d/m/Y H:i:s'), ['from' => 'hunghd4', 'to' => 'hunghd', 'amount'=> 6000]); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | array:4 [▼ 0 => App\Services\Block {#1578 ▼ +index: 0 +previousHash: "the first" +timeHash: "10/12/2021 11:23:14" +data: array:3 [▼ "from" => "hunghd" "to" => "hunghd2" "amount" => 3000 ] +hash: "ff0525def6dcb4ad33d5784f37c88e3ca7ac356c1ac186ae13796b756faf9a05" } 1 => App\Services\Block {#1579 ▼ +index: 1 +previousHash: "ff0525def6dcb4ad33d5784f37c88e3ca7ac356c1ac186ae13796b756faf9a05" +timeHash: "10/12/2021 11:23:14" +data: array:3 [▼ "from" => "hunghd2" "to" => "hunghd3" "amount" => 4000 ] +hash: "3b67c70f4e22032a7d4d0d2d0252b7b41f7ea218bfb199e647d16e4e9398f18c" } 2 => App\Services\Block {#1580 ▼ +index: 2 +previousHash: "3b67c70f4e22032a7d4d0d2d0252b7b41f7ea218bfb199e647d16e4e9398f18c" +timeHash: "10/12/2021 11:23:14" +data: array:3 [▼ "from" => "hunghd3" "to" => "hunghd4" "amount" => 5000 ] +hash: "ceabfe6fe846ea0ad4b44cabfebbdd1c5694d33247eba5f95210dd6d738ccadf" } 3 => App\Services\Block {#1581 ▼ +index: 3 +previousHash: "ceabfe6fe846ea0ad4b44cabfebbdd1c5694d33247eba5f95210dd6d738ccadf" +timeHash: "10/12/2021 11:23:14" +data: array:3 [▼ "from" => "hunghd4" "to" => "hunghd" "amount" => 6000 ] +hash: "c77a07b3e0f8987c95310f82546237e541191a05f940dc4e9d2ca04e5c8ad3be" } ] |
Check if the chain is changed or not
We have created a chain above, but now what if the hacker wants to change any block in the chain? If it was so easy to change, the block chain wouldn’t be as popular as it is now. To prevent that, I’ll have to check the chain’s validity as follows previousHash
=== hash
before.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public function inValidBlock() { for ($i = 1; $i < count($this->chain); $i++) { $currentBlock = $this->chain[$i]; $previousBlock = $this->chain[$i - 1]; if ($currentBlock->hash !== $currentBlock->execHash()) { return false; } if ($currentBlock->previousHash !== $previousBlock->hash) { return false; } } return true; } |
Now I will try to change 1 block (change data or hash ) in the chain, the result will be as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | $bl = new \App\Services\BlockChain(); $bl->addBlock(1, Carbon::now()->format('d/m/Y H:i:s'), ['from' => 'hunghd2', 'to' => 'hunghd3', 'amount'=> 4000]); $bl->addBlock(2, Carbon::now()->format('d/m/Y H:i:s'), ['from' => 'hunghd3', 'to' => 'hunghd4', 'amount'=> 5000]); $bl->addBlock(3, Carbon::now()->format('d/m/Y H:i:s'), ['from' => 'hunghd4', 'to' => 'hunghd', 'amount'=> 6000]); $bl->chain[2]->data = [ 'from' => 'hunghd3', 'to' => 'hunghd', 'amount' => 5000 ]; $bl->chain[2]->hash = $bl->chain[2]->execHash(); $bl->invalid = $bl->inValidBlock(); |
Here, the hash of the 2nd block has been changed, but the 3rd block has saved the hash of the 2nd block before the change. So the test result is false
Proof-of-work
But real, hash
and previousHash
when people can change the data 1 block and then change previousHash
and hash
of the following blocks is still creating a valid chain and we also want the users to agree on a unique history of the chain. And proof-of-work was born to solve this problem.
If you want to modify a previous block, you will have to re-mine all the blocks after it. It requires scanning for a value that starts with a certain number of zeroes when hashed. The value is called the nonce value, the number of leading 0 bits is called difficulty. By increasing the difficulty of mining, it means that mining will become harder and harder. We can make this system by creating mine . method
Now, my Block class will finally code like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | <?php class Block { public $index; public $previousHash; public $timeHash; public $data; public $hash; public $mineVar; public function __construct($index = 0, $previousHash = '', $timeHash = '', $data = '') { $this->index = $index; $this->previousHash = $previousHash; $this->timeHash = $timeHash; $this->data = $data; $this->hash = $this->execHash(); $this->mineVar = 0; } public function execHash() { if(is_array($this->data)) { $dataContent = json_encode($this->data); } else { $dataContent = $this->data; } return hash('sha256', $this->index . $this->previousHash . $dataContent . $this->timeHash . $this->mineVar ); } public function mine($difficulty) { while (!str_starts_with($this->execHash(), str_repeat('0', $difficulty))) { $this->mineVar++; $this->hash = $this->execHash(); } return $this->mineVar; } } |
In the Blockchain class also need to add code like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | <?php use Carbon\Carbon; class BlockChain extends Block { public $chain = array(); public $difficulty; public function __construct($difficulty) { $this->chain[] = $this->createGenesisBlock(); $this->difficulty = $difficulty; parent::__construct(); } public function createGenesisBlock() { return new Block(0000,'the first', Carbon::now()->format('d/m/Y H:i:s'), ['from' => 'hunghd', 'to' => 'hunghd2', 'amount'=> 3000]); } private function getLatestBlock() { return $this->chain[(count($this->chain) - 1)]; } public function addBlock($index, $timeHash, $data) { $previousHash = $this->getLatestBlock()->hash; $newBlock = new Block($index, $previousHash, $timeHash, $data); $newBlock->mine($this->difficulty); $this->chain[] = $newBlock; } public function inValidBlock() { for ($i = 1; $i < count($this->chain); $i++) { $currentBlock = $this->chain[$i]; $previousBlock = $this->chain[$i - 1]; if ($currentBlock->hash !== $currentBlock->execHash()) { return false; } if ($currentBlock->previousHash !== $previousBlock->hash) { return false; } } return true; } } |
Ok!! Now let’s try to run it
1 2 3 4 5 | $bl = new \App\Services\BlockChain(4); $bl->addBlock(1, Carbon::now()->format('d/m/Y H:i:s'), ['from' => 'hunghd2', 'to' => 'hunghd3', 'amount'=> 4000]); $bl->addBlock(2, Carbon::now()->format('d/m/Y H:i:s'), ['from' => 'hunghd3', 'to' => 'hunghd4', 'amount'=> 5000]); $bl->addBlock(3, Carbon::now()->format('d/m/Y H:i:s'), ['from' => 'hunghd4', 'to' => 'hunghd', 'amount'=> 6000]); |
As a result, we will have blocks with numbers nonce
as desired. To increase the difficulty to 5 zeros, I just need to change difficulty = 5
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | array:4 [▼ 0 => App\Services\Block {#1578 ▼ +index: 0 +previousHash: "the first" +timeHash: "10/12/2021 11:58:04" +data: array:3 [▶] +hash: "f636898ed8e8d5ddf3db99134694921a491ebd1fc80a1b4d3eb28f8d2c7e9ed5" +mineVar: 0 } 1 => App\Services\Block {#1579 ▼ +index: 1 +previousHash: "f636898ed8e8d5ddf3db99134694921a491ebd1fc80a1b4d3eb28f8d2c7e9ed5" +timeHash: "10/12/2021 11:58:04" +data: array:3 [▶] +hash: "00001c87474ccbf7cc6ef4eefa0fd4499989583716f41e6373e70a1e1b89585b" +mineVar: 13413 } 2 => App\Services\Block {#1580 ▼ +index: 2 +previousHash: "00001c87474ccbf7cc6ef4eefa0fd4499989583716f41e6373e70a1e1b89585b" +timeHash: "10/12/2021 11:58:04" +data: array:3 [▶] +hash: "0000dd0d602d647ac6e3aa4d6672073c5d229a0f2a2a12322c11b18661751639" +mineVar: 25881 } 3 => App\Services\Block {#1581 ▼ +index: 3 +previousHash: "0000dd0d602d647ac6e3aa4d6672073c5d229a0f2a2a12322c11b18661751639" +timeHash: "10/12/2021 11:58:04" +data: array:3 [▶] +hash: "00008ae256d9ccb10c578dc77629e89c3f2bf4cc4a146758b5c57e284d9fef86" +mineVar: 5349 } ] |
However, this article is basic, so we will use proof-of-work. In fact, this technology is outdated, slow, resource-intensive and harmful to the environment, so today’s platforms often use proof-of-stake.
This is an article based on my personal research, so there are many mistakes I hope everyone can contribute!!!