Purpose: This is an educational demo to understand how a basic blockchain works (blocks, hashes, transactions, and Proof of Work).
Disclaimer: For learning and communication only. Do not use this code for illegal activity or to violate any local laws/regulations.
- PHP 5.6+
Each block contains:
index— block height (starting from 1)timestamp— Unix timestamptransactions— list of transactionsproof— Proof of Work resultprevious_hash— hash of the previous block
Example structure:
block = {
"index": 2,
"timestamp": 1506057125,
"transactions": [
{
"sender": "8527147fe1f5426f9dd545de4b27ee00",
"recipient": "a77f5cdfa2934df3954a5c7c7da5df1f",
"amount": 5
}
],
"proof": 324984774000,
"previous_hash": "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
}A blockchain becomes tamper-evident because every new block stores the hash of the previous block.
If an attacker modifies an earlier block, its hash changes, and all subsequent blocks will contain an invalid previous_hash.
A blockchain is made of many blocks. Value data (e.g., Bitcoin transactions) is stored inside blocks.
Blocks also contain technical metadata such as timestamps and the previous block hash.
<?php
/**
* Created by PhpStorm.
* User: Jimmy
* Date: 2020/2/2
* Time: 18:42
*/
class Block
{
/**
* @var int Block index
*/
private $index;
/**
* @var int Unix timestamp
*/
private $timestamp;
/**
* @var array Transaction list
*/
private $transactions;
/**
* @var string Previous block hash
*/
private $previousHash;
/**
* @var int Proof generated by PoW
*/
private $proof;
/**
* @var string Current block hash
*/
private $hash;
/**
* Expose hash via getter to prevent external modifications
* @return string
*/
public function getHash()
{
return $this->hash;
}
public function __construct($index, $timestamp, $transactions, $previousHash, $proof)
{
$this->index = $index;
$this->timestamp = $timestamp;
$this->transactions = $transactions;
$this->previousHash = $previousHash;
$this->proof = $proof;
$this->hash = $this->blockHash();
}
/**
* Calculate block hash (signature)
* @return string
*/
private function blockHash()
{
// Ensure consistent hashing by using a deterministic field order
$blockArray = [
'index' => $this->index,
'timestamp' => $this->timestamp,
'transactions' => $this->transactions,
'proof' => $this->proof,
'previous_hash' => $this->previousHash,
];
$blockString = json_encode($blockArray);
return hash('sha256', $blockString);
}
}Field meanings
index: current block indextimestamp: creation timetransactions: transaction list (one or many)previousHash: previous block signature hashhash: current block signature hashproof: Proof of Work (mining result)
Proof of Work (PoW) explains how a new block is created (“mined”).
- The goal is to find a number that satisfies a certain rule.
- It should be computationally hard to find, but easy to verify.
Bitcoin’s PoW is based on a Hashcash-like puzzle (but with much higher difficulty), where miners compete to find a valid proof to earn rewards.
We will create a Blockchain class. Its constructor:
- initializes an empty chain
- creates the genesis block
- initializes the pending transaction list
/**
* @var array Chain (block list)
*/
private $chain;
/**
* @var array Pending transactions (to be packed into the next block)
*/
private $currentTransactions;
public function __construct()
{
$this->chain = [$this->createGenesisBlock()];
$this->currentTransactions = [];
}
/**
* Create genesis block
* @return array
*/
private function createGenesisBlock()
{
$block = [
'index' => 1,
'timestamp' => time(),
'transactions' => [],
'proof' => 100,
// Similar to Bitcoin genesis style: previous hash is all zeros
'previous_hash' => str_attach('', 64, '0'),
];
$block['hash'] = (new Block(
$block['index'],
$block['timestamp'],
$block['transactions'],
$block['previous_hash'],
$block['proof']
))->getHash();
return $block;
}Note:
str_attach('', 64, '0')is a placeholder to represent 64 zeros.
If you don’t havestr_attach, simply hardcode the 64-zero string.
A transaction is added to the pending list, waiting to be included in the next block.
After a new block is created, the pending list will be cleared.
/**
* Create a new transaction
* @param string $senderPrivateKey
* @param string $senderAddress
* @param string $recipientAddress
* @param int|float $amount
* @return bool
*/
public function createTransaction($senderPrivateKey, $senderAddress, $recipientAddress, $amount)
{
$row = [
'from' => $senderAddress,
'to' => $recipientAddress,
'amount' => $amount,
'timestamp' => time(),
];
// TODO: Sign the transaction with the sender's private key (like signing a check)
// TODO: Nodes can derive the public key and verify signature integrity.
$this->currentTransactions[] = $row;
return true;
}In this demo, only a miner that finds a valid proof can add a new block (see Step 4).
/**
* Add a new block
* @param int $proof
* @return bool
*/
public function addBlock(int $proof)
{
// Get previous block
$preBlockInfo = $this->chain[count($this->chain) - 1];
// Validate proof
if ($this->checkProof((string)$proof, (string)$preBlockInfo['proof']) === false) {
return false;
}
// TODO: Reward miner (as a special transaction)
$block = [
'index' => count($this->chain) + 1,
'timestamp' => time(),
'transactions' => $this->currentTransactions,
'proof' => $proof,
'previous_hash' => $preBlockInfo['hash'],
'hash' => '',
];
$block['hash'] = (new Block(
$block['index'],
$block['timestamp'],
$block['transactions'],
$block['previous_hash'],
$block['proof']
))->getHash();
// Append block to chain
$this->chain[] = $block;
// Reset pending transactions
$this->currentTransactions = [];
return true;
}
/**
* Validate PoW
* Rule: sha256(proof + previousProof) starts with '0000'
* @param string $proof
* @param string $preProof
* @return bool
*/
private function checkProof(string $proof, string $preProof)
{
$string = $proof . $preProof;
$hash = hash('sha256', $string);
return substr($hash, 0, 4) === '0000';
}Mining is the “magic” part. In this simplified demo, it does three things:
- Compute PoW
- (Optional) reward the miner
- Create a new block and append it to the chain
Find a number P such that:
sha256(str(P) + str(previousProof))starts with four zeros (0000)
Implementation:
/**
* Mine a new block
* @return void
*/
public function mine()
{
$proof = 0;
// Latest block
$blockInfo = $this->chain[count($this->chain) - 1];
$preProof = $blockInfo['proof'];
while (true) {
$string = $proof . $preProof;
$hash = hash('sha256', $string);
if (substr($hash, 0, 4) === '0000') {
// Add new block
$this->addBlock($proof);
break;
}
$proof++;
}
}$blockChainObj = new Blockchain();
// Add a transaction
$blockChainObj->createTransaction(
'',
'8527147fe1f5426f9dd545de4b27ee00',
'a77f5cdfa2934df3954a5c7c7da5df1f',
1
);
// Start mining (will create a new block once found)
$blockChainObj->mine();
// Print current chain
$blockList = $blockChainObj->getChainList();
var_dump($blockList);Example output (simplified):
array(2) {
[0]=> genesis block ...
[1]=> block #2 with 1 transaction ...
}
Jimmy (@jimmybytes)
Full-Cycle Programmer · AI-Assisted Engineering