关键词:LaravelZero、区块链钱包、比特币地址、数字签名、私钥、公钥、椭圆曲线加密、UTXO 交易、CLI 工具
引言:为什么要自己实现钱包
在比特币的世界里,钱包并不是用来“存钱”的,而是用来“保管钥匙”的关键工具。每一笔交易都需要数字签名才能被网络认可,而签名的背后正是私钥与公钥这一对加密利器。本节我们将用 LaravelZero 快速搭建一套简化版的钱包系统,让你亲手控制密钥的生成、地址的编码以及交易的签名验证。👉 立即体验如何 30 秒创建专属钱包
密钥体系三连击:私钥、公钥、地址
1. 私钥:守护资产的最后一道门
- 随机数决定:私钥本质上是一个 256 bit 的随机整数。
- 切记离线备份:一旦私钥丢失,永不可恢复。
- 切勿明文传输:泄露即意味着资产无密码裸奔。
2. 公钥:向世界证明“我是谁”
- 单向计算:通过椭圆曲线 secp256k1 算法由私钥推导出公钥,不可逆。
- 公开无妨:公钥可以安全分享给任何人用于验证签名。
3. 地址:易读的收款二维码
- 传递过程:公钥 → 两次哈希(SHA256+RIPEMD160)→ Base58Check 编码 → 最终地址。
- 安全属性:地址不泄露公钥,减少被量子计算“破解”的风险。
代码实战:LaravelZero 中的钱包模型
安装专用加密库
composer require mdanter/ecc bitwasp/bitcoin核心类 Wallet 与 Wallets
下面的示例省略异常处理以突出逻辑。
Wallet
class Wallet
{
public $privateKey; // 十六进制格式
public $publicKey; // 十六进制格式
public function __construct()
{
[$this->privateKey, $this->publicKey] = $this->newKeyPair();
}
private function newKeyPair(): array
{
$privFactory = new PrivateKeyFactory();
$privKey = $privFactory->generateCompressed(new Random());
return [
$privKey->getHex(),
$privKey->getPublicKey()->getHex()
];
}
public function getAddress(): string
{
$pubKey = (new PublicKeyFactory())->fromHex($this->publicKey);
$script = (new P2pkhScriptDataFactory())->convertKey($pubKey)->getScriptPubKey();
$address = (new AddressCreator())->fromOutputScript($script);
return $address->getAddress(Bitcoin::getNetwork());
}
}Wallets 容器
class Wallets
{
public array $wallets = [];
public function __construct()
{
$this->loadFromFile();
}
public function createWallet(): string
{
$wallet = new Wallet();
$address = $wallet->getAddress();
$this->wallets[$address] = $wallet;
return $address;
}
public function saveToFile(): void
{
file_put_contents(storage_path('walletFile'), serialize($this->wallets));
}
public function loadFromFile(): void
{
if (file_exists($path = storage_path('walletFile'))) {
$this->wallets = unserialize(file_get_contents($path)) ?: [];
}
}
public function getWallet(string $address): ?Wallet
{
return $this->wallets[$address] ?? null;
}
}交易改造:把数字签名写进区块链
精简字段
- 将原
scriptSig拆成signature+pubKey - 原
scriptPubKey则改为 公钥哈希pubKeyHash - 小白友好,不搞脚本引擎
关键代码片段
输入校验
class TXInput
{
// 省略字段声明
public function usesKey(string $pubKeyHash): bool
{
return (new PublicKeyFactory())
->fromHex($this->pubKey)
->getPubKeyHash()
->getHex() === $pubKeyHash;
}
}输出锁定
class TXOutput
{
public static function fromAddress(int $value, string $address): self
{
$creator = new AddressCreator();
$hashHex = substr(
$creator->fromString($address)->getScriptPubKey()->getHex(),
6,
40
); // 截取 20 字节公钥哈希
return new self($value, $hashHex);
}
}交易签名与验证
class Transaction
{
public function sign(string $privateKey, array $prevTXs): void
{
$txCopy = $this->trimmedCopy();
foreach ($txCopy->txInputs as $id => $input) {
$prevOutPkHash = $prevTXs[$input->txId]->txOutputs[$input->vOut]->pubKeyHash;
$txCopy->txInputs[$id]->pubKey = $prevOutPkHash;
$txCopy->setId();
$sig = (new PrivateKeyFactory())
->fromHexCompressed($privateKey)
->sign(new Buffer($txCopy->id))
->getHex();
$this->txInputs[$id]->signature = $sig;
}
}
public function verify(array $prevTXs): bool
{
$txCopy = $this->trimmedCopy();
foreach ($this->txInputs as $id => $input) {
$prevOutPkHash = $prevTXs[$input->txId]->txOutputs[$input->vOut]->pubKeyHash;
$txCopy->txInputs[$id]->pubKey = $prevOutPkHash;
$txCopy->setId();
$pubKey = (new PublicKeyFactory())->fromHex($input->pubKey);
$verified = $pubKey->verify(
new Buffer($txCopy->id),
SignatureFactory::fromHex($input->signature)
);
if (! $verified) return false;
}
return true;
}
}区块链验证阶段
class BlockChain
{
public function mineBlock(array $transactions): void
{
foreach ($transactions as $tx) {
if (! $this->verifyTransaction($tx)) {
abort('交易验证失败');
}
}
// ...
}
private function verifyTransaction(Transaction $tx): bool
{
$prevTXs = [];
foreach ($tx->txInputs as $input) {
$prevTXs[$input->txId] = $this->findTransaction($input->txId);
}
return $tx->verify($prevTXs);
}
}CLI 新命令:一分钟建钱包
| 命令 | 作用 |
|---|---|
php blockchain createwallet | 生成密钥对并返回新地址 |
php blockchain listaddresses | 查看本地所有地址 |
示例终端输出:
$ php blockchain createwallet
Your new address: 1LRqVSu8Kv9fPgdvXLWP5mTnMxC7TYiYjt
$ php blockchain send 1LRq...CTYiYjt 1PWi...fejX 30
send success
block hash: 000001515...
$ php blockchain getbalance 1PWi...fejX
Balance: 30常见问题 FAQ
Q1:私钥丢了还能补救吗?
A1:无法补救。再安全的私钥,一旦遗失或泄露,对应地址上的资产永远被动锁定或可直接盗取。
Q2:地址以 “3” 开头说明什么?
A2:以 “3” 开头的是 P2SH(支付到脚本哈希)地址,本节仅实现 P2PKH 以 “1” 开头的地址。如需扩展可通过其他脚本工厂类完成。
Q3:为什么交易中还要发回找零?
A3:UTXO 模型下,交易需消耗整个输出。找零会创建一笔返回你自身地址的新输出,防止“给矿工打赏”过大。
Q4:CLI 速度会随数据量增加而变慢吗?
A4:当前用本地文件 walletFile 与 Cache,对十万级交易无压力。未来可迁移 SQLite 或更持久方案。
Q5:能把钱包导入到真实比特币网络吗?
A5:本工程是教学 Demo,格式兼容但不具备网络地址、节点发现、UTXO 全网索引功能,请勿主网尝试。
下一节预告
我们将在 第六篇 引入 Merkle 树 加速 SPV 节点校验并优化数据存储,敬请期待!