构建加密比特币钱包:TypeScript 开发者实战指南

·

这是一个面向中级以上开发者的完整实战笔记,我们将使用 TypeScript 结合 BitcoinJS、ecpair、tiny-secp256k1 与 CryptoJS 完成一个可加密、可本地持久化的命令行 比特币钱包 原型。如果你对“钱包”、“私钥”、“测试网”这些基本概念已有认识,并开始寻找一份能够直接落地的代码级教程,这篇文章就是为你准备的。

为什么自己造轮子

核心关键词:加密钱包、比特币开发、TypeScript、私钥管理、本地持久化

市面上的钱包琳琅满目,但真正适合自己业务或研究需求的并不多见。自己动手,可以:


技术栈与前置准备

名称作用说明
TypeScript保证静态类型,消除低级错误
BitcoinJS-lib生成地址、创建并签名交易
ecpair生成与管理椭圆曲线密钥对
tiny-secp256k1secp256k1 曲线的高性能实现
CryptoJS对称加密 (AES-256-CBC)
fs (Node.js)本地文件读写,实现冷存储

环境要求

如果还没装过 TypeScript,执行:

npm i -g typescript ts-node

项目骨架 30 秒搭好

  1. 新建文件夹并初始化

    mkdir Bitcoin_Cli_Wallet && cd Bitcoin_Cli_Wallet
    npm init -y
  2. 安装依赖(一次到位)

    npm i bitcoinjs-lib ecpair tiny-secp256k1 crypto-js dotenv
    npm i -D typescript ts-node @types/node @types/crypto-js
  3. 目录结构(复制就能跑)

    Bitcoin_Cli_Wallet
    ├─ index.ts          # 主入口
    ├─ wallets/          # 存放加密的钱包文件
    └─ .env              # ENCRYPTION_KEY & ALGORITHM

核心实现步骤拆解

1. 定义数据结构

我们用两个接口描述「单个钱包」及「历史索引」:

interface SingleWalletData {
  keyPair: ECPairInterface;        // 私钥 & 公钥
  walletObject: Payment;           // 地址详情
  walletType: string;              // p2pkh、p2wpkh…
}

interface WalletHistory {
  totalWallets: number;
  allWallets: string[];            // 钱包名数组
  allAddresses: (string | undefined)[];
}

2. 钱包如何「凭空诞生」

2.1 随机密钥对

无论主网还是测试网,本质是给 ECPair 传入不同网络常量:

const ECPair = ECPairFactory(ecc);
function decodeNetwork(input: 'bitcoin' | 'testnet' | 'regtest') {
  switch (input) {
    case 'testnet': return ECPair.makeRandom({ network: TESTNET });
    case 'bitcoin': return ECPair.makeRandom({ network: BITCOIN });
    case 'regtest': return ECPair.makeRandom({ network: REGTEST });
    default: throw new Error('无效网络');
  }
}

2.2 地址形态

把随机公钥丢进 bitcoin.payments.*,即可拿到不一样格式的地址:

function decodeWalletType(type: string, keyPair: ECPairInterface) {
  switch (type) {
    case 'p2pkh':
      return bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: TESTNET });
    case 'p2wpkh':
      return bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey, network: TESTNET });
    // 其余 p2sh、p2tr 同理
    default: throw new Error('未知钱包类型');
  }
}

👉 想让地址更现代?试试原生隔离见证能省 42% 矿工费!


3. 加密 & 落盘:杜绝裸奔

使用 AES-256-CBC,同步方法一劳永逸:

const encryptData = (plain: string): string => {
  const cipher = crypto.createCipher(ALGORITHM, ENCRYPTION_KEY);
  let encrypted = cipher.update(plain, 'utf8', 'hex');
  encrypted += cipher.final('hex');
  return encrypted;
};

const decryptData = (hash: string): string => {
  const decipher = crypto.createDecipher(ALGORITHM, ENCRYPTION_KEY);
  let decrypted = decipher.update(hash, 'hex', 'utf8');
  decrypted += decipher.final('utf8');
  return decrypted;
};

再以 wallets/<walletName>.json 保存:

function writeWalletDataToFile(name: string, data: SingleWalletData) {
  fs.writeFileSync(
    `./wallets/${name}.json`,
    encryptData(JSON.stringify(data))
  );
}

4. 钱包历史索引:快速找回所有地址

建立 history.json,一份小索引,避免全盘扫描:

function fetchWalletHistory(): WalletHistory {
  if (!fs.existsSync('./wallets/history.json')) {
    return { totalWallets: 0, allWallets: [], allAddresses: [] };
  }
  const raw = fs.readFileSync('./wallets/history.json', 'utf8');
  return JSON.parse(decryptData(raw));
}

每次创建钱包即时更新索引:

writeWalletHistoryToFile({
  totalWallets: history.totalWallets + 1,
  allWallets: [...history.allWallets, walletname],
  allAddresses: [...history.allAddresses, address],
});

亲手跑一遍

index.ts 最底下加几行测试:

createWallet('alice', 'regtest', 'p2pkh');
createWallet('bob',   'regtest', 'p2wpkh');
createWallet('carol', 'regtest', 'p2tr');
listAllAddresses();

编译 & 运行:

tsc index.ts && node index.js

你将得到围绕 regtest 的三条不同格式的地址,外加一个统一索引,证明加密文件已全部就绪。


常见问题 (FAQ)

  1. Q: 为什么一定要用 TypeScript?普通 JavaScript 不能跑吗?
    A: 能跑,但 TypeScript 自带的类型推导能帮你在早期发现 keyPair.publicKey 拼写错误、接口不一致等低级 bug,代码量越大越划算。
  2. Q: 我把 ENCRYPTION_KEY 直接写进 .env 会不会不安全?
    A: 对于本机 CLI 原型或 CI 测试,.env 足够安全;若准备上云,请替换为硬件加密器 (HSM) 或密钥管理服务 (KMS),并禁用日志输出。
  3. Q: 生成的测试网 faucet 水到哪儿领?
    👉 点这里立刻领取 0.001 tBTC,用来测试转账、Utxo 选择、找零逻辑!
  4. Q: 文中只提到 regtest,如何切换到主网?
    A: 把 decodeNetwork('regtest') 改成 decodeNetwork('bitcoin') 即可;12 个助记词到位后,再把 faucet 换成真金白银的交易所或节点。
  5. Q: 下一篇会讲交易与签名流程吗?
    A: 是的,后续将演示如何构造 PSBT、如何合并 input、如何找零到 p2wpkh 地址;敬请期待。

收个尾

至此,我们已经拥有一个:

源码可直接继续迭代:加上助记词、BIP-39/BIP-44、HD 钱包派生、PSBT 交易签名……你我的下一行代码,就是产品级加密钱包的起航点。