这是一个面向中级以上开发者的完整实战笔记,我们将使用 TypeScript 结合 BitcoinJS、ecpair、tiny-secp256k1 与 CryptoJS 完成一个可加密、可本地持久化的命令行 比特币钱包 原型。如果你对“钱包”、“私钥”、“测试网”这些基本概念已有认识,并开始寻找一份能够直接落地的代码级教程,这篇文章就是为你准备的。
为什么自己造轮子
核心关键词:加密钱包、比特币开发、TypeScript、私钥管理、本地持久化
市面上的钱包琳琅满目,但真正适合自己业务或研究需求的并不多见。自己动手,可以:
- 深入理解比特币网络、地址派生、交易结构;
- 完全掌控私钥与助记词的存储逻辑,杜绝第三方风险;
- 把钱包 SDK 打进任何你熟悉的业务系统里(CI/CD 测试、企业内部工具、空投脚本……)。
技术栈与前置准备
名称 | 作用说明 |
---|---|
TypeScript | 保证静态类型,消除低级错误 |
BitcoinJS-lib | 生成地址、创建并签名交易 |
ecpair | 生成与管理椭圆曲线密钥对 |
tiny-secp256k1 | secp256k1 曲线的高性能实现 |
CryptoJS | 对称加密 (AES-256-CBC) |
fs (Node.js) | 本地文件读写,实现冷存储 |
环境要求
- Node.js ≥ 18
- npm / pnpm / yarn
- 已能跑通
tsc
与node
基本命令
如果还没装过 TypeScript,执行:
npm i -g typescript ts-node
项目骨架 30 秒搭好
新建文件夹并初始化
mkdir Bitcoin_Cli_Wallet && cd Bitcoin_Cli_Wallet npm init -y
安装依赖(一次到位)
npm i bitcoinjs-lib ecpair tiny-secp256k1 crypto-js dotenv npm i -D typescript ts-node @types/node @types/crypto-js
目录结构(复制就能跑)
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('未知钱包类型');
}
}
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)
- Q: 为什么一定要用 TypeScript?普通 JavaScript 不能跑吗?
A: 能跑,但 TypeScript 自带的类型推导能帮你在早期发现keyPair.publicKey
拼写错误、接口不一致等低级 bug,代码量越大越划算。 - Q: 我把
ENCRYPTION_KEY
直接写进.env
会不会不安全?
A: 对于本机 CLI 原型或 CI 测试,.env
足够安全;若准备上云,请替换为硬件加密器 (HSM) 或密钥管理服务 (KMS),并禁用日志输出。 - Q: 生成的测试网 faucet 水到哪儿领?
👉 点这里立刻领取 0.001 tBTC,用来测试转账、Utxo 选择、找零逻辑! - Q: 文中只提到 regtest,如何切换到主网?
A: 把decodeNetwork('regtest')
改成decodeNetwork('bitcoin')
即可;12 个助记词到位后,再把 faucet 换成真金白银的交易所或节点。 - Q: 下一篇会讲交易与签名流程吗?
A: 是的,后续将演示如何构造 PSBT、如何合并 input、如何找零到 p2wpkh 地址;敬请期待。
收个尾
至此,我们已经拥有一个:
- 支持多种网络 (测试网、主网、私人 regtest)
- 支持多种地址规格 (p2pkh、p2wpkh、p2tr …)
- 全部 AES-256-CBC 加密、本地持久化
- 记录完整历史索引的极简 开发级比特币钱包
源码可直接继续迭代:加上助记词、BIP-39/BIP-44、HD 钱包派生、PSBT 交易签名……你我的下一行代码,就是产品级加密钱包的起航点。