Java创建比特币交易,技术原理与实践指南
摘要:比特币作为全球首个去中心化数字货币,其交易核心是“数字签名”与“UTXO(未花费交易输出)模型”,通过Java实现比特币交易创建,需要理解比特币交易结构、密钥管理、签名算法等核心概念,本文将结合技术原...
比特币作为全球首个去中心化数字货币,其交易核心是“数字签名”与“UTXO(未花费交易输出)模型”,通过Java实现比特币交易创建,需要理解比特币交易结构、密钥管理、签名算法等核心概念,本文将结合技术原理与代码实践,详细介绍如何用Java从零开始构建一笔合法的比特币交易。
比特币交易的核心概念
UTXO模型
比特币不采用传统账户余额模式,而是基于UTXO(Unspent Transaction Output)模型,每一笔交易消耗(输入)之前交易未被使用的输出(UTXO),并生成新的输出(可能包含找零),用户A有一笔“输入”(来自之前交易的UTXO),现在想向用户B转账1 BTC,若该UTXO价值2 BTC,则交易需包含两个输出:用户B的1 BTC(目标输出)和用户A的1 BTC(找零输出)。
交易结构
一笔标准比特币交易包含以下字段:
- 版本号:交易协议版本(如2)。
- 输入列表(TxIn):包含输入的UTXO引用(前一笔交易的哈希与输出索引)、解锁脚本(ScriptSig,用于证明所有权)。
- 输出列表(TxOut):包含输出金额(以“聪”为单位,1 BTC=1亿聪)和锁定脚本(ScriptPubKey,定义谁能花费该输出)。
- 锁定时间:交易生效的区块高度或时间戳(通常为0,立即生效)。
密钥与签名
比特币交易所有权通过数字签名证明,发送方需用私钥对交易数据进行签名,接收方通过公钥验证签名合法性,常见的签名算法是ECDSA(椭圆曲线数字签名算法),基于secp256k1曲线。
Java开发环境准备
依赖库
推荐使用以下Java库简化比特币开发:
- BitcoinJ:最流行的Java比特币库,提供交易构建、密钥管理、网络交互等功能。
- Bouncy Castle:加密库,用于ECDSA签名等操作。
在Maven项目中添加依赖:
<dependencies>
<!-- BitcoinJ核心库 -->
<dependency>
<groupId>org.bitcoinj</groupId>
<artifactId>bitcoinj-core</artifactId>
<version>0.16.1</version>
</dependency>
<!-- Bouncy Castle加密库 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
</dependencies>
Java创建比特币交易的完整流程
步骤1:生成或加载密钥对
比特币交易的所有权由私钥控制,私钥通过WIF(Wallet Import Format)或Base58编码表示,使用BitcoinJ生成密钥对:
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.params.TestNet3Params; // 测试网参数
public class KeyGeneration {
public static void main(String[] args) {
// 使用测试网(实际开发中用MainNetParams)
NetworkParameters params = TestNet3Params.get();
// 生成新密钥对
ECKey key = new ECKey();
// 私钥(WIF格式)
String privateKeyWIF = key.getPrivateKeyAsWiF(params);
System.out.println("Private Key (WIF): " + privateKeyWIF);
// 公钥(压缩格式)
String publicKey = key.getPublicKeyAsHex();
System.out.println("Public Key: " + publicKey);
// 地址(测试网以m或n开头)
String address = key.toAddress(params).toString();
System.out.println("Address: " + address);
}
}
步骤2:获取未花费的UTXO(输入来源)
交易需要消耗已有的UTXO,需通过区块链查询接口获取,BitcoinJ提供了Wallet类管理UTXO,但实际开发中需连接比特币节点或使用区块链浏览器API(如BlockCypher、Blockchain.com)。
假设我们已获取一个UTXO:
- 交易哈希:
a3b2c1d4...(前一笔交易的ID) - 输出索引:
0(该交易的第1个输出) - 金额:
50000000聪(0.5 BTC) - 锁定脚本:目标地址的锁定脚本(如
76a914...88ac)
步骤3:构建交易输入(TxIn)
输入需引用UTXO,并包含解锁脚本(ScriptSig)证明所有权,解锁脚本通常由“签名+公钥”组成:
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder;
public class TxInBuilder {
public static TransactionInput createTxIn(
Sha256Hash txHash, int outputIndex, ECKey key, TransactionOutput utxo) {
// 创建输入(引用UTXO)
TransactionInput txIn = new TransactionInput(
null, // 交易上下文(可为null)
utxo.getValue(), // 输入金额(从UTXO获取)
new ScriptBuilder() // 解锁脚本
.op(ScriptOpCodes.OP_0) // OP_0表示签名
.data(key.sign(utxo.getOutPointFor().hash)) // 签名
.data(key.getPubKey()) // 公钥
.build(),
utxo.getOutPointFor() // 引用的UTXO
);
return txIn;
}
}
步骤4:构建交易输出(TxOut)
输出包含目标地址和金额,锁定脚本定义谁能花费(如P2PKH:Pay-to-Public-Key-Hash):
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.core.Address;
import org.bitcoinj.script.ScriptBuilder;
public class TxOutBuilder {
public static TransactionOutput createTxOut(
NetworkParameters params, long amount, Address toAddress) {
// 锁定脚本:目标地址的P2PKH脚本
Script scriptPubKey = new ScriptBuilder()
.op(ScriptOpCodes.OP_DUP)
.op(ScriptOpCodes.OP_HASH160)
.data(toAddress.getHash160())
.op(ScriptOpCodes.OP_EQUALVERIFY)
.op(ScriptOpCodes.OP_CHECKSIG)
.build();
// 创建输出
return new TransactionOutput(null, amount, scriptPubKey);
}
}
步骤5:组装完整交易并签名
将输入、输出组合为交易,并对输入进行签名(确保交易合法性):
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.script.Script;
public class TransactionBuilder {
public static Transaction createAndSignTransaction(
NetworkParameters params,
List<TransactionInput> txIns,
List<TransactionOutput> txOuts,
ECKey signingKey) {
// 创建交易
Transaction tx = new Transaction(params);
txIns.forEach(tx::addInput);
txOuts.forEach(tx::addOutput);
// 设置版本和锁定时间
tx.setVersion(2);
tx.setLockTime(0);
// 对输入进行签名(ECDSA)
for (int i = 0; i < txIns.size(); i++) {
TransactionInput txIn = txIns.get(i);
Script scriptSig = new ScriptBuilder()
.data(signingKey.sign(tx.hashForSignature(i, txIn.getOutput().getScript(), Transaction.SigHash.ALL, false)))
.data(signingKey.getPubKey())
.build();
txIn.setScriptSig(scriptSig);
}
return tx;
}
}
步骤6:序列化交易并广播
交易构建完成后,需序列化为字节流(十六进制字符串)才能广播到比特币网络:
import org.bitcoinj.core.Utils;
public class TransactionBroadcast {
public static void main(String[] args) {
// 假设已构建好交易tx
Transaction tx = ...; // 步骤5中的交易对象
// 序列化为十六进制字符串
String txHex = Utils.HEX.encode(tx.bitcoinSerialize());
System.out.println("Raw Transaction: " + txHex);
// 广播交易(需连接比特币节点或使用第三方API)
// BitcoinJ的PeerGroup.broadcastTransaction(tx)
// 或通过Blockchain.com的广播接口
}
}
完整代码示例
将上述步骤整合为完整示例(测试网环境):
import org.bitcoinj.core.*; import org.bitcoinj.params.TestNet3Params; import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptBuilder; import java.util.Arrays; import java.util.List; public
