Java实现比特币交易签名,原理与实践
摘要:比特币交易的核心安全性依赖于数字签名技术,而Java作为企业级开发的主流语言,提供了丰富的加密库支持比特币交易的签名与验证,本文将从比特币交易签名的原理出发,详细介绍如何使用Java实现比特币交易的签...
比特币交易的核心安全性依赖于数字签名技术,而Java作为企业级开发的主流语言,提供了丰富的加密库支持比特币交易的签名与验证,本文将从比特币交易签名的原理出发,详细介绍如何使用Java实现比特币交易的签名过程,包括关键步骤、代码示例及注意事项。
比特币交易签名原理
比特币交易签名本质是非对称加密的应用,其核心流程如下:
密钥对生成
比特币使用椭圆曲线数字签名算法(ECDSA),每个用户拥有一对密钥:
- 私钥:随机生成,用于对交易进行签名,必须严格保密。
- 公钥:由私钥通过椭圆曲线算法(
secp256k1)派生,用于验证签名,可公开。
交易数据哈希
交易签名前需对交易数据进行哈希处理:
- 将交易序列化(遵循比特币交易格式),得到原始字节数组。
- 对字节数组进行
SHA-256哈希,再进行RIPEMD-160哈希(或仅SHA-256,取决于实现),得到交易哈希值(Transaction Hash)。
ECDSA签名
使用私钥对交易哈希值进行ECDSA签名,生成签名数据(包含r和s两个值,通常编码为DER格式)。
签名验证
交易广播到网络后,节点使用签名者的公钥验证签名:
- 通过公钥、交易哈希和签名数据,验证签名是否有效(确保交易未被篡改且由私钥持有者发起)。
Java实现比特币交易签名
Java实现比特币交易签名需依赖以下工具:
- Bouncy Castle:提供加密算法库(如ECDSA、
secp256k1曲线)。 - 比特币核心库:如
bitcoinj(简化交易构建与签名)或手动实现交易序列化。
本文以手动实现为例,结合Bouncy Castle库,逐步完成交易签名。
1 环境准备
添加Bouncy Castle依赖(Maven)
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
导入关键类
import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.ec.CustomNamedCurves; import org.bouncycastle.crypto.signers.ECDSASigner; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.math.ec.ECPoint; import java.math.BigInteger; import java.security.SecureRandom;
2 关键步骤实现
步骤1:生成比特币密钥对(secp256k1曲线)
比特币使用secp256k1椭圆曲线,其参数可通过Bouncy Castle获取:
public class BitcoinKeyPair {
// secp256k1曲线参数
private static final X9ECParameters CURVE_PARAMS = CustomNamedCurves.getByName("secp256k1");
private static final ECDomainParameters CURVE = new ECDomainParameters(
CURVE_PARAMS.getCurve(),
CURVE_PARAMS.getG(),
CURVE_PARAMS.getN(),
CURVE_PARAMS.getH()
);
/**
* 生成比特币私钥(32字节,随机数)
*/
public static BigInteger generatePrivateKey() {
SecureRandom random = new SecureRandom();
BigInteger privateKey;
do {
privateKey = new BigInteger(256, random);
} while (privateKey.compareTo(BigInteger.ONE) <= 0 ||
privateKey.compareTo(CURVE.getN().subtract(BigInteger.ONE)) >= 0);
return privateKey;
}
/**
* 从私钥生成公钥(未压缩格式,0x04前缀 + x + y,共65字节)
*/
public static byte[] getPublicKey(BigInteger privateKey) {
ECPoint point = CURVE.getG().multiply(privateKey);
byte[] x = point.getAffineXCoord().getEncoded();
byte[] y = point.getAffineYCoord().getEncoded();
byte[] publicKey = new byte[65];
publicKey[0] = 0x04; // 未压缩格式标识
System.arraycopy(x, 1, publicKey, 1, 32);
System.arraycopy(y, 1, publicKey, 33, 32);
return publicKey;
}
}
步骤2:构建比特币交易
比特币交易包含输入(Vin)和输出(Vout),需遵循严格的序列化格式,以下是一个简化交易示例:
public class BitcoinTransaction {
private int version; // 版本号(如1)
private List<TransactionInput> inputs; // 交易输入
private List<TransactionOutput> outputs; // 交易输出
private int lockTime; // 锁定时间(0表示立即生效)
// 交易输入类
public static class TransactionInput {
private byte[] prevTxHash; // 前一笔交易的哈希(小端序)
private int prevTxOutIndex; // 前一笔交易的输出索引
private byte[] scriptSig; // 签名脚本(签名时留空,签名后填充)
private int sequence; // 序列号(0xFFFFFFFF表示非最终)
}
// 交易输出类
public static class TransactionOutput {
private long amount; // 输出金额(聪,1 BTC = 1亿聪)
private byte[] scriptPubKey; // 公钥脚本(锁定脚本,如接收方地址的公钥哈希)
}
/**
* 序列化交易(签名前需对原始交易签名,排除scriptSig)
*/
public byte[] serializeForSigning() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
try {
dos.writeInt(version);
dos.writeVarInt(inputs.size());
for (TransactionInput input : inputs) {
dos.write(input.prevTxHash);
dos.writeInt(input.prevTxOutIndex);
// 签名时scriptSig长度为0
dos.writeVarInt(0);
dos.writeInt(input.sequence);
}
dos.writeVarInt(outputs.size());
for (TransactionOutput output : outputs) {
dos.writeLong(output.amount);
dos.write(output.scriptPubKey);
}
dos.writeInt(lockTime);
} catch (IOException e) {
throw new RuntimeException("Transaction serialization failed", e);
}
return baos.toByteArray();
}
}
步骤3:计算交易哈希
对序列化后的交易数据进行SHA-256哈希:
import java.security.MessageDigest;
public class TransactionHash {
public static byte[] calculateHash(byte[] transactionData) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
return digest.digest(digest.digest(transactionData));
} catch (Exception e) {
throw new RuntimeException("Hash calculation failed", e);
}
}
}
步骤4:ECDSA签名
使用私钥对交易哈希进行签名,生成DER格式的签名数据:
public class TransactionSigner {
/**
* ECDSA签名(secp256k1曲线)
*/
public static byte[] sign(BigInteger privateKey, byte[] hash) {
ECDSASigner signer = new ECDSASigner();
ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(privateKey, BitcoinKeyPair.CURVE);
signer.init(true, privKey); // true表示签名
BigInteger[] sig = signer.generateSignature(hash);
return encodeDER(sig[0], sig[1]); // 转换为DER格式
}
/**
* 将r,s编码为DER格式
*/
private static byte[] encodeDER(BigInteger r, BigInteger s) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
try {
// DER结构:0x30 [总长度] 0x02 [r长度] [r] 0x02 [s长度] [s]
byte[] rBytes = r.toByteArray();
byte[] sBytes = s.toByteArray();
int totalLength = 2 + rBytes.length + 2 + sBytes.length;
dos.writeByte(0x30); // SEQUENCE标记
dos.writeByte(totalLength);
// r部分
dos.writeByte(0x02); // INTEGER标记
dos.writeByte(rBytes.length);
dos.write(rBytes);
// s部分
dos.writeByte(0x02); // INTEGER标记
dos.writeByte(sBytes.length);
dos.write(sBytes);
} catch (IOException e) {
throw new RuntimeException("DER encoding failed", e);
}
return baos.toByteArray();
}
}
步骤5:填充签名脚本
签名完成后,需将签名数据和公钥填充到输入的`script
上一篇:
下一篇:
