Implementing Wallets, Addresses, and Cryptographic Keys in LaravelZero Blockchain

·

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:

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:

⚠️ 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:

  1. Hash public key with SHA-256
  2. Apply RIPEMD-160 hashing (resulting in a "public key hash")
  3. Add network version byte (e.g., 0x00 for mainnet)
  4. 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/bitcoin

These 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:


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
> 1PWiJKQzxdWnePvWjfD3EPnfskAxiGfejX

These 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: 30

All 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:

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