Introduction
You may have heard that Bitcoin is based on cryptography—a field of mathematics that allows proofs of secret knowledge without revealing the secret (digital signatures) or verifying data authenticity (digital fingerprints). These cryptographic proofs serve as fundamental mathematical tools in Bitcoin and are widely used in its applications.
Today, we'll implement the cryptographic mechanisms Bitcoin uses to control ownership of funds, including keys, addresses, and wallets. The code changes are substantial, so refer to the GitHub comparison for details.
Overview
Bitcoin doesn't store any personal account information. However, when someone sends me coins, there needs to be a way to identify me as the owner of the transaction outputs (i.e., the funds locked in those outputs). Bitcoin ownership is established through digital keys, addresses, and digital signatures.
Digital keys aren't stored on the network. Instead, users generate and store them in a file or simple database called a wallet. These keys are entirely independent of the Bitcoin protocol—users generate and manage them using wallet software without needing to reference the blockchain or access the network. Keys enable many of Bitcoin's fascinating features, including decentralized trust, ownership authentication, and a security model based on cryptographic proofs.
Public Key Cryptography
Public-key cryptography uses paired keys:
- Private Key: A randomly selected number used to generate signatures proving ownership of funds.
- Public Key: Derived from the private key using elliptic curve multiplication (a one-way mathematical process).
Addresses
A Bitcoin address is an alphanumeric string derived from a public key hash. It includes a version prefix and is encoded using Base58Check (e.g., 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa).
Digital Signatures
A digital signature ensures:
- Data integrity (no modification during transmission).
- Authenticity (created by the claimed sender).
- Non-repudiation (sender cannot deny sending the data).
Implementation
Wallet and Address Generation
We start by installing two libraries:
composer require mdanter/ecc
composer require bitwasp/bitcoinCreate Wallet.php and Wallets.php:
class Wallet {
public $privateKey;
public $publicKey;
public function __construct() {
list($this->privateKey, $this->publicKey) = $this->newKeyPair();
}
public function getAddress(): string {
$addrCreator = new AddressCreator();
$factory = new P2pkhScriptDataFactory();
$scriptPubKey = $factory->convertKey((new PublicKeyFactory())->fromHex($this->publicKey))->getScriptPubKey();
$address = $addrCreator->fromOutputScript($scriptPubKey);
return $address->getAddress(Bitcoin::getNetwork());
}
private function newKeyPair(): array {
$privateKeyFactory = new PrivateKeyFactory();
$privateKey = $privateKeyFactory->generateCompressed(new Random());
$publicKey = $privateKey->getPublicKey();
return [$privateKey->getHex(), $publicKey->getHex()];
}
}
class Wallets {
public $wallets = [];
public function createWallet(): string {
$wallet = new Wallet();
$address = $wallet->getAddress();
$this->wallets[$address] = $wallet;
return $address;
}
public function saveToFile() {
file_put_contents('walletFile', serialize($this->wallets));
}
}Transaction Updates
Modify TXInput and TXOutput to handle public key hashes (pubKeyHash) instead of scripts:
class TXInput {
public $txId, $vOut, $signature, $pubKey;
public function usesKey(string $pubKeyHash): bool {
$pubKeyIns = (new PublicKeyFactory())->fromHex($this->pubKey);
return $pubKeyIns->getPubKeyHash()->getHex() == $pubKeyHash;
}
}
class TXOutput {
public $value, $pubKeyHash;
public static function NewTxOutput(int $value, string $address) {
$txOut = new TXOutput($value, '');
$txOut->pubKeyHash = $txOut->lock($address);
return $txOut;
}
}Signing Transactions
class Transaction {
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]->pubKey = $prevTx->txOutputs[$txInput->vOut]->pubKeyHash;
$txCopy->setId();
$this->txInputs[$inId]->signature = (new PrivateKeyFactory())->fromHexCompressed($privateKey)
->sign(new Buffer($txCopy->id))->getHex();
}
}
public function verify(array $prevTXs): bool {
$txCopy = $this->trimmedCopy();
foreach ($this->txInputs as $inId => $txInput) {
$prevTx = $prevTXs[$txInput->txId];
$txCopy->txInputs[$inId]->pubKey = $prevTx->txOutputs[$txInput->vOut]->pubKeyHash;
$txCopy->setId();
$signature = $txInput->signature;
if (!(new PublicKeyFactory())->fromHex($txInput->pubKey)
->verify(new Buffer($txCopy->id), SignatureFactory::fromHex($signature)))
return false;
}
return true;
}
}CLI Commands
Add commands to create wallets and list addresses:
php blockchain createwallet
php blockchain listaddressesTesting
$ php blockchain createwallet
Your new address: 1LRqVSu8Kv9fPgdvXLWP5mTnMxC7TYiYjt
$ php blockchain init-blockchain 1LRqVSu8Kv9fPgdvXLWP5mTnMxC7TYiYjt
$ php blockchain send 1LRqVSu8Kv9fPgdvXLWP5mTnMxC7TYiYjt 1PWiJKQzxdWnePvWjfD3EPnfskAxiGfejX 30
Send success!FAQs
Q: Why are private keys crucial in Bitcoin?
A: Private keys generate signatures to prove ownership of funds. Losing a private key means losing access to the associated Bitcoin forever.
Q: How does a Bitcoin address differ from a public key?
A: An address is a hashed version of a public key, making it shorter and more secure for sharing.
Q: What prevents someone from forging a transaction?
A: Digital signatures require the private key. Without it, forging a valid signature is computationally impossible.
👉 Learn more about Bitcoin security
Conclusion
We've implemented wallets, addresses, and transaction signing. In the next section, we'll refine transactions to align more closely with real-world blockchain systems.
For a full code review, check the GitHub diff.