比特币交易构建与签名 - 完整实践指南
· 阅读需 2 分钟
比特币交易的构建和签名是区块链开发中的核心技能。本文将详细介绍如何使用比特币工具包构建各种类型的比特币交易,包括输入选择、输出创建、签名和广播。
交易构建基础
1. 环境设置
首先需要安装我们的比特币工具包:
import { bech32 } from 'bech32';
import base58 from 'bs58';
import { sha256x2, sha256, ripemd160 } from '../utils/crypto';
export function toBech32Address(pubkey: Buffer, pubKeyHash = 0x00): string {
// pubKeyHash: Witness version. 0x00 for P2WPKH
const hash256 = sha256(pubkey);
const hash160 = ripemd160(hash256);
const words = bech32.toWords(hash160);
words.unshift(pubKeyHash);
return bech32.encode('bc', words);
}
// https://github.com/bitcoinbook/bitcoinbook/blob/develop/ch04_keys.adoc
// pubKeyHash: Version byte. 0x00 for Mainnet P2PKH, 0x6f for Testnet, etc.
export function toBase58Address(pubkey: Buffer, pubKeyHash = 0x00): string {
const hash256 = sha256(pubkey);
const hash160 = ripemd160(hash256);
// version + payload
const payload = Buffer.allocUnsafe(21);
payload.writeUInt8(pubKeyHash, 0);
hash160.copy(payload, 1);
// checksum, first 4 buffer
const checksum = sha256x2(payload).slice(0, 4);
// Buffer.concat([payload, checksum], payload.length + 4);
const buffer = Buffer.allocUnsafe(25);
payload.copy(buffer);
checksum.copy(buffer, 21);
// version + payload + checksum(first 4 bytes)
return base58.encode(buffer);
};
2. 密钥管理
import { bech32 } from 'bech32';
import base58 from 'bs58';
import { sha256x2, sha256, ripemd160 } from '../utils/crypto';
export function toBech32Address(pubkey: Buffer, pubKeyHash = 0x00): string {
// pubKeyHash: Witness version. 0x00 for P2WPKH
const hash256 = sha256(pubkey);
const hash160 = ripemd160(hash256);
const words = bech32.toWords(hash160);
words.unshift(pubKeyHash);
return bech32.encode('bc', words);
}
// https://github.com/bitcoinbook/bitcoinbook/blob/develop/ch04_keys.adoc
// pubKeyHash: Version byte. 0x00 for Mainnet P2PKH, 0x6f for Testnet, etc.
export function toBase58Address(pubkey: Buffer, pubKeyHash = 0x00): string {
const hash256 = sha256(pubkey);
const hash160 = ripemd160(hash256);
// version + payload
const payload = Buffer.allocUnsafe(21);
payload.writeUInt8(pubKeyHash, 0);
hash160.copy(payload, 1);
// checksum, first 4 buffer
const checksum = sha256x2(payload).slice(0, 4);
// Buffer.concat([payload, checksum], payload.length + 4);
const buffer = Buffer.allocUnsafe(25);
payload.copy(buffer);
checksum.copy(buffer, 21);
// version + payload + checksum(first 4 bytes)
return base58.encode(buffer);
};
PSBT 交易构建
PSBT (Partially Signed Bitcoin Transaction) 是现代比特币交易的标准格式。
require('dotenv').config();
const { ECPair, Psbt, networks, payments } = require('bitcoinjs-lib');
const network = networks.testnet;
var secret = process.env.ECDSA_SECRET;
const signer = ECPair.fromPrivateKey(Buffer.from(secret, 'hex'), network);
const pubkey = signer.publicKey;
const { address } = payments.p2pkh({ pubkey, network });
console.log('pubkey : ', pubkey.toString('hex'));
console.log('address : ', address);
// https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts
function createPsbtTx() {
const psbt = new Psbt({ network });
psbt.setVersion(2); // These are defaults. This line is not needed.
psbt.setLocktime(0)
psbt.addInput({
hash: '3b948165cfb707320ccdc9582b5acc7cdad213bc81b7edb37546e1334d802b38',
index: 0,
sequence: 0xffffffff, // These are defaults. This line is not needed.
// tx raw data
nonWitnessUtxo: Buffer.from('0100000001b2533a7e2329b92b576309969ef39e987f1ce62d76d1a3e714e1f73b830f7404010000006b483045022100e950df33415553a6cdd9665d4d6d3f03568ad0e2feb429efe19f4fc78da66714022059e3ffdf36d300a9352b971f8c48180ec0cef2325f61948488480430bc24d1ed012102ff26c5980685ae12d25312a8df8224c951a68272013425ffa60327d7d4b54231ffffffff0210270000000000001976a914dc6c3c43e5d2c934602095103d3cbf84ddc797f288ace71a0100000000001976a914dc6c3c43e5d2c934602095103d3cbf84ddc797f288ac00000000', 'hex')
});
psbt.addOutput({
address: 'n1cScasu6XVoDki38WYAJH4ZJGRAfG8XRN',
value: 9800
});
psbt.signInput(0, signer);
psbt.finalizeAllInputs();
const serialized = psbt.extractTransaction().toHex();
console.log('serialized : ', serialized);
}
createPsbtTx();
不同类型的交易构建
1. 地址生成
import { bech32 } from 'bech32';
import base58 from 'bs58';
import { sha256x2, sha256, ripemd160 } from '../utils/crypto';
export function toBech32Address(pubkey: Buffer, pubKeyHash = 0x00): string {
// pubKeyHash: Witness version. 0x00 for P2WPKH
const hash256 = sha256(pubkey);
const hash160 = ripemd160(hash256);
const words = bech32.toWords(hash160);
words.unshift(pubKeyHash);
return bech32.encode('bc', words);
}
// https://github.com/bitcoinbook/bitcoinbook/blob/develop/ch04_keys.adoc
// pubKeyHash: Version byte. 0x00 for Mainnet P2PKH, 0x6f for Testnet, etc.
export function toBase58Address(pubkey: Buffer, pubKeyHash = 0x00): string {
const hash256 = sha256(pubkey);
const hash160 = ripemd160(hash256);
// version + payload
const payload = Buffer.allocUnsafe(21);
payload.writeUInt8(pubKeyHash, 0);
hash160.copy(payload, 1);
// checksum, first 4 buffer
const checksum = sha256x2(payload).slice(0, 4);
// Buffer.concat([payload, checksum], payload.length + 4);
const buffer = Buffer.allocUnsafe(25);
payload.copy(buffer);
checksum.copy(buffer, 21);
// version + payload + checksum(first 4 bytes)
return base58.encode(buffer);
};
2. 多签名交易
import bitgoV1 from '@bitgo/utxo-lib';
import { payments } from 'bitcoinjs-lib';
const pubKeys = [
'02f4147da97162a214dbe25828ee4c4acc4dc721cd0c15b2761b43ed0292ed82b5', // 2 -3
'0377155e520059d3b85c6afc5c617b7eb519afadd0360f1ef03aff3f7e3f5438dd',
'02f44bce3eecd274e7aa24ec975388d12905dfc670a99b16e1d968e6ab5f69b266',
].map(function (hex) {
return Buffer.from(hex, 'hex');
});
const threshold = 2;
const network = bitgoV1.networks.testnet
var redeemScript = bitgoV1.script.multisig.output.encode(threshold, pubKeys); // 2 of 3
var scriptPubKey = bitgoV1.script.scriptHash.output.encode(bitgoV1.crypto.hash160(redeemScript));
var address = bitgoV1.address.fromOutputScript(scriptPubKey, network);
console.log('bitgo address : ', address);
//
var { address } = payments.p2sh({
redeem: payments.p2ms({ m: threshold, pubkeys: pubKeys, network }),
network,
});
console.log('bitcoin address: ', address);
交易签名
1. 单签名
require('dotenv').config();
const { ECPair, Psbt, networks, payments } = require('bitcoinjs-lib');
const network = networks.testnet;
var secret = process.env.ECDSA_SECRET;
const signer = ECPair.fromPrivateKey(Buffer.from(secret, 'hex'), network);
const pubkey = signer.publicKey;
const { address } = payments.p2pkh({ pubkey, network });
console.log('pubkey : ', pubkey.toString('hex'));
console.log('address : ', address);
// https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts
function createPsbtTx() {
const psbt = new Psbt({ network });
psbt.setVersion(2); // These are defaults. This line is not needed.
psbt.setLocktime(0)
psbt.addInput({
hash: '3b948165cfb707320ccdc9582b5acc7cdad213bc81b7edb37546e1334d802b38',
index: 0,
sequence: 0xffffffff, // These are defaults. This line is not needed.
// tx raw data
nonWitnessUtxo: Buffer.from('0100000001b2533a7e2329b92b576309969ef39e987f1ce62d76d1a3e714e1f73b830f7404010000006b483045022100e950df33415553a6cdd9665d4d6d3f03568ad0e2feb429efe19f4fc78da66714022059e3ffdf36d300a9352b971f8c48180ec0cef2325f61948488480430bc24d1ed012102ff26c5980685ae12d25312a8df8224c951a68272013425ffa60327d7d4b54231ffffffff0210270000000000001976a914dc6c3c43e5d2c934602095103d3cbf84ddc797f288ace71a0100000000001976a914dc6c3c43e5d2c934602095103d3cbf84ddc797f288ac00000000', 'hex')
});
psbt.addOutput({
address: 'n1cScasu6XVoDki38WYAJH4ZJGRAfG8XRN',
value: 9800
});
psbt.signInput(0, signer);
psbt.finalizeAllInputs();
const serialized = psbt.extractTransaction().toHex();
console.log('serialized : ', serialized);
}
createPsbtTx();
2. 多签名
function createPsbtTx() {
const psbt = new Psbt({ network });
psbt.setVersion(2); // These are defaults. This line is not needed.
psbt.setLocktime(0)
psbt.addInput({
hash: '3b948165cfb707320ccdc9582b5acc7cdad213bc81b7edb37546e1334d802b38',
index: 0,
sequence: 0xffffffff, // These are defaults. This line is not needed.
// tx raw data
nonWitnessUtxo: Buffer.from('0100000001b2533a7e2329b92b576309969ef39e987f1ce62d76d1a3e714e1f73b830f7404010000006b483045022100e950df33415553a6cdd9665d4d6d3f03568ad0e2feb429efe19f4fc78da66714022059e3ffdf36d300a9352b971f8c48180ec0cef2325f61948488480430bc24d1ed012102ff26c5980685ae12d25312a8df8224c951a68272013425ffa60327d7d4b54231ffffffff0210270000000000001976a914dc6c3c43e5d2c934602095103d3cbf84ddc797f288ace71a0100000000001976a914dc6c3c43e5d2c934602095103d3cbf84ddc797f288ac00000000', 'hex')
});
psbt.addOutput({
address: 'n1cScasu6XVoDki38WYAJH4ZJGRAfG8XRN',
value: 9800
});
psbt.signInput(0, signer);
psbt.finalizeAllInputs();
const serialized = psbt.extractTransaction().toHex();
console.log('serialized : ', serialized);
}
createPsbtTx();
RBF (Replace-By-Fee) 交易
RBF 允许用更高手续费的交易替换未确认的交易。
require('dotenv').config();
const { PrivateKey, Networks, Transaction } = require('bitcore-lib');
var secret = process.env.ECDSA_SECRET;
const privkey = new PrivateKey(secret, Networks.testnet);
console.log('address : ', privkey.toAddress().toString());
var utxo = {
txid: '5ab958a2a62d2e905128fbe0faebc998a92abf365aaa8db3f319c280dda32bc1',
outputIndex: 0,
address: 'n1cScasu6XVoDki38WYAJH4ZJGRAfG8XRN',
script: '76a914dc6c3c43e5d2c934602095103d3cbf84ddc797f288ac',
satoshis: 10000,
};
const tx = new Transaction();
tx.from(utxo);
tx.to('mnv5WqA2nw1L5SHepFFVNYZMeHUC9WCfRU', 8000);
tx.change('mnv5WqA2nw1L5SHepFFVNYZMeHUC9WCfRU');
tx.fee(300);
tx.enableRBF();
tx.sign(privkey);
const bl = tx.isRBF();
console.log('bl', bl);
const serialized = tx.serialize();
console.log('serialized', serialized);
const newTx = new Transaction();
console.log(newTx.isRBF());
newTx.from(utxo);
newTx.to('n1cScasu6XVoDki38WYAJH4ZJGRAfG8XRN', 7000);
newTx.change('n1cScasu6XVoDki38WYAJH4ZJGRAfG8XRN');
newTx.fee(1800);
newTx.enableRBF();
newTx.sign(privkey);
const newSerialized = newTx.serialize();
console.log('new serialized : ', newSerialized);
交易验证
交易广播
交易广播通常通过比特币节点或第三方 API 服务进行。你可以使用以下方式之一:
- 本地比特币节点: 使用
bitcoin-cli sendrawtransaction
- 第三方 API: 如 Blockstream、BlockCypher 等
- 测试网: 使用测试网 API 进行开发和测试
完整交易流程示例
安全最佳实践
- 私钥安全: 永远不要在代码中硬编码私钥
- 网络选择: 开发时使用测试网,生产时使用主网
- 手续费计算: 动态计算手续费以确保交易被确认
- 输入验证: 始终验证 UTXO 的有效性和所有权
- 错误处理: 实现完善的错误处理和回滚机制
总结
比特币交易构建是一个复杂但重要的过程。通过掌握 PSBT、各种交易类型和签名机制,开发者可以:
- 构建安全的比特币应用
- 实现复杂的交易逻辑
- 支持多种地址格式
- 优化交易费用和确认时间
在下一篇文章中,我们将探讨比特币工具函数和实用技巧。