Building a blockchain from scratch is both a technical challenge and an intellectual journey into the foundations of decentralized systems. In this installment of our LaravelZero blockchain series, we dive deep into one of the most critical components: wallets, addresses, and cryptographic keys. These elements form the backbone of ownership and security in any cryptocurrency system—without them, there would be no way to prove who owns what.
This guide walks you through implementing secure key generation, address creation, digital signatures, and wallet management—all within a custom PHP-based blockchain built using LaravelZero. We’ll maintain the educational tone of the original while optimizing for clarity, structure, and search engine visibility.
Understanding Ownership in Blockchain
Unlike traditional banking systems that rely on identity verification and centralized databases, blockchain uses cryptography to establish ownership. There are no usernames or personal details stored on-chain. Instead, control over funds is proven mathematically using private keys, public keys, and digital signatures.
At its core:
- A private key is a secret number known only to the owner.
- From it, a public key is derived via irreversible elliptic curve mathematics.
- The public key is then hashed and encoded into a bitcoin address, which can be safely shared.
- To spend funds, users must create a digital signature using their private key—proof they own the associated address without revealing the key itself.
This trustless model enables true decentralization and empowers users with full control over their assets.
🔐 Core Concept: Your private key is your identity. Lose it, and your funds are gone forever.
Public-Key Cryptography Explained
Public-key cryptography (also known as asymmetric encryption) relies on key pairs: one private, one public.
Private Key
A randomly generated 256-bit number. It must remain confidential at all times. This key allows:
- Signing transactions
- Proving ownership of funds
- Accessing associated addresses
⚠️ Never expose your private key—doing so compromises all funds linked to it.
Public Key
Computed from the private key using elliptic curve multiplication—a one-way function. While derived from the private key, it’s computationally impossible to reverse-engineer the private key from the public one.
Bitcoin Address
The address isn't the public key itself but a processed version:
- Hash public key with SHA-256
- Apply RIPEMD-160 hashing (resulting in a "public key hash")
- Add network version byte (e.g.,
0x00for mainnet) - Perform Base58Check encoding
This results in a human-readable string like 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa.
👉 Learn how real-world wallets secure cryptographic keys today.
Building the Wallet System in PHP
To manage keys securely, we implement two classes: Wallet and Wallets.
Install Required Libraries
We use established libraries to avoid reinventing cryptographic wheels:
composer require mdanter/ecc
composer require bitwasp/bitcoinThese provide robust implementations of ECDSA, hashing, and address formatting.
Wallet Class
class Wallet
{
public $privateKey;
public $publicKey;
public function __construct()
{
list($privateKey, $publicKey) = $this->newKeyPair();
$this->privateKey = $privateKey;
$this->publicKey = $publicKey;
}
private function newKeyPair(): array
{
$privateKeyFactory = new PrivateKeyFactory();
$privateKey = $privateKeyFactory->generateCompressed(new Random());
$publicKey = $privateKey->getPublicKey();
return [$privateKey->getHex(), $publicKey->getHex()];
}
public function getAddress(): string
{
$addrCreator = new AddressCreator();
$factory = new P2pkhScriptDataFactory();
$scriptPubKey = $factory->convertKey(
(new PublicKeyFactory())->fromHex($this->publicKey)
)->getScriptPubKey();
return $addrCreator->fromOutputScript($scriptPubKey)
->getAddress(Bitcoin::getNetwork());
}
}✅ P2PKH Note: We implement Pay-to-PubKey-Hash (P2PKH), the most common transaction type in Bitcoin. The address format starts with "1" on the mainnet.
Wallets Manager Class
Manages multiple wallets and persists them locally:
class Wallets
{
public $wallets = [];
public function createWallet(): string
{
$wallet = new Wallet();
$address = $wallet->getAddress();
$this->wallets[$address] = $wallet;
return $address;
}
public function saveToFile()
{
$walletsSer = serialize($this->wallets);
if (!is_dir(storage_path())) mkdir(storage_path(), 0777, true);
file_put_contents(storage_path() . '/walletFile', $walletsSer);
}
public function loadFromFile()
{
if (file_exists($path = storage_path() . '/walletFile')) {
$contents = file_get_contents($path);
if (!empty($contents)) {
$this->wallets = unserialize($contents);
}
}
}
public function getAddresses(): array
{
return array_keys($this->wallets);
}
}This ensures wallet data survives application restarts.
Transaction Inputs & Outputs: Securing Funds
We now update TXInput and TXOutput to support cryptographic verification.
TXOutput – Locking Funds to an Address
class TXOutput
{
public $value;
public $pubKeyHash;
public static function NewTxOutput(int $value, string $address)
{
$txOut = new TXOutput($value, '');
$pubKeyHash = $txOut->lock($address);
$txOut->pubKeyHash = $pubKeyHash;
return $txOut;
}
private function lock(string $address): string
{
$addCreator = new AddressCreator();
$addInstance = $addCreator->fromString($address);
$pubKeyHashHex = $addInstance->getScriptPubKey()->getHex();
// Extract raw pubKeyHash (remove version & checksum)
return substr($pubKeyHashHex, 6, strlen($pubKeyHashHex) - 10);
}
public function isLockedWithKey(string $pubKeyHash): bool
{
return $this->pubKeyHash === $pubKeyHash;
}
}Funds are locked using the recipient’s public key hash.
TXInput – Unlocking Spent Outputs
class TXInput
{
public $txId;
public $vOut;
public $signature;
public $pubKey;
public function usesKey(string $pubKeyHash): bool
{
$pubKeyIns = (new PublicKeyFactory())->fromHex($this->pubKey);
return $pubKeyIns->getPubKeyHash()->getHex() === $pubKeyHash;
}
}During validation, nodes check whether:
- The input references a valid unspent output
- The provided public key hashes to the same value as the output’s
pubKeyHash - The signature matches the transaction data
Signing and Verifying Transactions
Digital signatures ensure transaction integrity and authenticity.
Sign Method
public function sign(string $privateKey, array $prevTXs)
{
if ($this->isCoinbase()) return;
$txCopy = $this->trimmedCopy();
foreach ($txCopy->txInputs as $inId => $txInput) {
$prevTx = $prevTXs[$txInput->txId];
$txCopy->txInputs[$inId]->signature = '';
$txCopy->txInputs[$inId]->pubKey = $prevTx->txOutputs[$txInput->vOut]->pubKeyHash;
$txCopy->setId();
$txCopy->txInputs[$inId]->pubKey = '';
$signature = (new PrivateKeyFactory())
->fromHexCompressed($privateKey)
->sign(new Buffer($txCopy->id))
->getHex();
$this->txInputs[$inId]->signature = $signature;
}
}We sign a trimmed copy of the transaction where other inputs are temporarily cleared to prevent interference.
Verify Method
public function verify(array $prevTXs): bool
{
foreach ($this->txInputs as $inId => $txInput) {
// Rebuild signed copy
...
if (!$pubKeyInstance->verify(new Buffer($txCopy->id), $signatureInstance)) {
return false;
}
}
return true;
}Verification confirms each input was signed by the rightful owner.
CLI Commands for User Interaction
Add user-friendly commands:
php blockchain createwallet
> Your new address: 1LRqVSu8Kv9fPgdvXLWP5mTnMxC7TYiYjt
php blockchain listaddresses
> 1LRqVSu8Kv9fPgdvXLWP5mTnMxC7TYiYjt
> 1PWiJKQzxdWnePvWjfD3EPnfskAxiGfejXThese allow easy wallet creation and balance tracking.
Testing the Implementation
Run end-to-end tests:
php blockchain init-blockchain <your_address>
php blockchain send <from> <to> <amount>
php blockchain getbalance <address>Expected output:
send success
balance of '1LRqVSu...' is: 20
balance of '1PWiJKQ...' is: 30All operations succeed only if signatures are valid and funds are unspent.
Frequently Asked Questions
What is a private key in blockchain?
A private key is a secret 256-bit number used to sign transactions. It proves ownership and must never be shared.
How is a Bitcoin address generated?
An address is created by hashing the public key (SHA-256 + RIPEMD-160), adding a version byte, and encoding with Base58Check for readability.
Can I recover my funds if I lose my private key?
No. Unlike traditional accounts, there's no password reset. Losing your private key means permanent loss of access to funds.
Why use digital signatures?
They ensure transaction authenticity, prevent tampering, and enable non-repudiation—proving a specific user authorized a transfer.
Is storing wallets in files secure?
For production systems, use encrypted keystores or hardware wallets. File-based storage is suitable only for learning purposes.
👉 Discover best practices for securing crypto assets in modern wallets.
Summary
In this part, we implemented:
- Secure key pair generation
- Wallet management with persistence
- Address derivation using P2PKH standards
- Transaction signing and verification
These components bring our LaravelZero blockchain much closer to real-world functionality. In future parts, we’ll refine transaction structures further and explore advanced scripting concepts.
Remember: your code is your bank. Always prioritize security when handling cryptographic material.
🔑 Core Keywords: blockchain wallet, private key, public key, digital signature, Bitcoin address, cryptographic security, LaravelZero blockchain