跳到主要内容

比特币交易构建与签名 - 完整实践指南

· 阅读需 2 分钟
iamnivekx
Blockchain Developer

比特币交易的构建和签名是区块链开发中的核心技能。本文将详细介绍如何使用比特币工具包构建各种类型的比特币交易,包括输入选择、输出创建、签名和广播。

交易构建基础

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 服务进行。你可以使用以下方式之一:

  1. 本地比特币节点: 使用 bitcoin-cli sendrawtransaction
  2. 第三方 API: 如 Blockstream、BlockCypher 等
  3. 测试网: 使用测试网 API 进行开发和测试

完整交易流程示例

安全最佳实践

  1. 私钥安全: 永远不要在代码中硬编码私钥
  2. 网络选择: 开发时使用测试网,生产时使用主网
  3. 手续费计算: 动态计算手续费以确保交易被确认
  4. 输入验证: 始终验证 UTXO 的有效性和所有权
  5. 错误处理: 实现完善的错误处理和回滚机制

总结

比特币交易构建是一个复杂但重要的过程。通过掌握 PSBT、各种交易类型和签名机制,开发者可以:

  • 构建安全的比特币应用
  • 实现复杂的交易逻辑
  • 支持多种地址格式
  • 优化交易费用和确认时间

在下一篇文章中,我们将探讨比特币工具函数和实用技巧。