LaravelZero 从零实现区块链:钱包、地址与密钥实战

·

关键词:LaravelZero、区块链钱包、比特币地址、数字签名、私钥、公钥、椭圆曲线加密、UTXO 交易、CLI 工具

引言:为什么要自己实现钱包

在比特币的世界里,钱包并不是用来“存钱”的,而是用来“保管钥匙”的关键工具。每一笔交易都需要数字签名才能被网络认可,而签名的背后正是私钥公钥这一对加密利器。本节我们将用 LaravelZero 快速搭建一套简化版的钱包系统,让你亲手控制密钥的生成、地址的编码以及交易的签名验证。👉 立即体验如何 30 秒创建专属钱包

密钥体系三连击:私钥、公钥、地址

1. 私钥:守护资产的最后一道门

2. 公钥:向世界证明“我是谁”

3. 地址:易读的收款二维码

👉 想知道更多椭圆曲线幕后原理?点这里一起深入

代码实战: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;
    }
}

交易改造:把数字签名写进区块链

精简字段

关键代码片段

输入校验

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:当前用本地文件 walletFileCache,对十万级交易无压力。未来可迁移 SQLite 或更持久方案。

Q5:能把钱包导入到真实比特币网络吗?
A5:本工程是教学 Demo,格式兼容但不具备网络地址、节点发现、UTXO 全网索引功能,请勿主网尝试。

下一节预告

我们将在 第六篇 引入 Merkle 树 加速 SPV 节点校验并优化数据存储,敬请期待!