BTC隔离见证(3开头)地址离线签名(亲测可行)
关于BTC隔离见证
(3开头)地址
的离线签名
,貌似相关介绍比较少,主要以理论介绍居多。
所以在这里,我就不再赘述什么是隔离见证地址以及如何完成相关签名的理论部分啦。
如果想快速了解BTC相关资料的,请阅读精通比特币
一书,介绍的很好。
只有理论搞清楚了,才能更好地进行代码层面的相关操作。我查阅了一些资料,通过github上一些开源的代码,对其稍加整理。经过BTC主网实际测试,完成了BTC隔离见证(3开头)地址的离线签名功能。
测试的交易hash -> f8f58dec79c6dc0c576f20c89733568facf3cda38a969e0b9ca2b7ed1a97b352
由于没有多少测试币,所以只能按照尽可能低的金额来测试,所幸当前网络不拥堵,否则恐怕是无法打包了!
具体代码实现(java),请大家参考 这里,具体我就不多介绍了,上面都写得很清楚啦~
———————————我是无情的分割线———————————
本着复制粘贴合作分享的原则,根据 bitcoinj 自行总结了一下。
关于bc1开头地址离线签名(附上代码):
注意:本地网络测试ok,主网未完全测试,请自行测试,确保无误!!!
/**
* BTC交易离线签名
*
* @param from 转账地址 支持 1 3 bc1开头地址
* @param fromPrk 转账地址私钥 WFI格式
* @param to 收款地址
* @param amount 转账数量 单位:聪
* @param fee 手续费 单位:聪
* @return
*/
public static String btcOfflineSignatureAll(String from, String fromPrk, String to, long amount, long fee) {
NetworkParameters networkParameters = RegTestParams.get();
List<UTXO> utxos = getUnspents(from);
if (utxos.size() == 0) {
throw new RuntimeException("utxo为空 BTC INSUFFICIENT FEE");
}
//组装转账所需的UTXO
long utxoAmount = 0L;
List<UTXO> needUtxos = new ArrayList<UTXO>();
//遍历未花费列表,组装合适的item
for (UTXO utxo : utxos) {
if (utxoAmount >= (amount + fee)) {
break;
} else {
needUtxos.add(utxo);
utxoAmount += utxo.getValue().value;
}
}
//找零 找零金额必须大于等于546聪 否则会视为粉尘攻击
long changeAmount = utxoAmount - (amount + fee);
if (changeAmount < 0L) {
log.info("BTC转账地址余额不足 地址={}, 余额={}", from, utxoAmount);
throw new RuntimeException("BTC INSUFFICIENT FEE");
}
//BTC交易构建
Transaction tx = new Transaction(networkParameters);
//添加未签名交易输入
for (UTXO utxo : needUtxos) {
//bc1开头地址 即P2WPKH 交易输入不能加入 Script
if (getAddressType(networkParameters, from).equals(Script.ScriptType.P2WPKH)) {
tx.addInput(utxo.getHash(), utxo.getIndex(), new Script(Hex.decode("")));
} else {
tx.addInput(utxo.getHash(), utxo.getIndex(), utxo.getScript());
}
}
//添加交易输出
//收款地址
tx.addOutput(Coin.valueOf(amount), Address.fromString(networkParameters, to));
if (changeAmount >= 546L) {
//找零
tx.addOutput(Coin.valueOf(changeAmount), Address.fromString(networkParameters, from));
}
//未签名交易16进制格式
String txHex = Hex.toHexString(tx.bitcoinSerialize());
tx = new Transaction(networkParameters, HEX.decode(txHex));
DumpedPrivateKey dumpedPrivateKey = DumpedPrivateKey.fromBase58(networkParameters, fromPrk);
ECKey ecKey = dumpedPrivateKey.getKey();
//对交易输入进行签名
int size = needUtxos.size();
for (int i = 0; i < size; i++) {
if (getAddressType(networkParameters, from).equals(Script.ScriptType.P2PKH)) {
//1开头地址 普通地址 手续费较高
tx.clearInputs();
TransactionOutPoint outPoint = new TransactionOutPoint(networkParameters, needUtxos.get(i).getIndex(), needUtxos.get(i).getHash());
tx.addSignedInput(outPoint, needUtxos.get(i).getScript(), ecKey, Transaction.SigHash.ALL, true);
} else if (getAddressType(networkParameters, from).equals(Script.ScriptType.P2SH)) {
//3开头 隔离见证兼容地址 手续费较低
TransactionInput txIn = tx.getInput(i);
Script redeemScript = ScriptBuilder.createP2WPKHOutputScript(ecKey);
Script witnessScript = ScriptBuilder.createP2PKHOutputScript(ecKey);
TransactionSignature txSig = tx.calculateWitnessSignature(
i,
ecKey,
witnessScript,
needUtxos.get(i).getValue(),
Transaction.SigHash.ALL,
false
);
txIn.setWitness(TransactionWitness.redeemP2WPKH(txSig, ecKey));
txIn.setScriptSig(new ScriptBuilder().data(redeemScript.getProgram()).build());
} else if (getAddressType(networkParameters, from).equals(Script.ScriptType.P2WPKH)) {
//bc1开头 隔离见证原生地址 手续费最低
TransactionInput txIn = tx.getInput(i);
Script witnessScript = ScriptBuilder.createP2PKHOutputScript(ecKey);
TransactionSignature txSig = tx.calculateWitnessSignature(
i,
ecKey,
witnessScript,
needUtxos.get(i).getValue(),
Transaction.SigHash.ALL,
false
);
txIn.setWitness(TransactionWitness.redeemP2WPKH(txSig, ecKey));
} else if (getAddressType(networkParameters, from).equals(Script.ScriptType.P2WSH)) {
//TODO 多重签名地址
throw new RuntimeException("暂不支持该地址转账");
} else {
//其他
throw new RuntimeException("暂不支持该地址转账");
}
}
String txSigned = Hex.toHexString(tx.bitcoinSerialize());
System.out.println("签名结果=" + txSigned);
//广播交易
return client().sendRawTransaction(txSigned);
}
/**
* 获取BitcoinJavaRpcClient
*
* @return BitcoinJSONRPCClient
*/
public static BitcoinJSONRPCClient client() {
try {
return new BitcoinJSONRPCClient(new URL("url"));
} catch (MalformedURLException e) {
log.error("connect error e={}", e.getMessage());
throw new RuntimeException(e.getMessage());
}
}
/**
* 校验地址类型
*
* @param networkParameters 网络类型
* @param address 地址
* @return
*/
public static Script.ScriptType getAddressType(NetworkParameters networkParameters, String address) {
try {
return Address.fromString(networkParameters, address).getOutputScriptType();
} catch (Exception e) {
log.warn("地址格式错误 address={}", address);
throw new RuntimeException("地址格式错误");
}
}
/**
* 获取BTC余额
*
* @param address
* @return
* @throws RuntimeException
*/
public static List<UTXO> getUnspents(String address) throws RuntimeException {
List<UTXO> utxos = new ArrayList<>();
List<BitcoindRpcClient.Unspent> unspentOutPuts = client().listUnspent(0, 99999999, address);
if (unspentOutPuts == null || unspentOutPuts.size() == 0) {
throw new RuntimeException("utxo为空 BTC INSUFFICIENT FEE");
}
for (BitcoindRpcClient.Unspent unspent : unspentOutPuts) {
long vOut = unspent.vout();
String txId = unspent.txid();
String addressStr = unspent.address();
String script = unspent.scriptPubKey();
//USDT节点这里的余额单位是BTC,所以需要转换成聪
BigDecimal bigDecimal = unspent.amount().multiply(new BigDecimal(100000000));
UTXO utxo = new UTXO(Sha256Hash.wrap(txId), vOut, Coin.valueOf(bigDecimal.longValue()),
0, false, new Script(Hex.decode(script)), addressStr);
utxos.add(utxo);
}
return utxos;
}
如果觉得有帮助,麻烦点个赞吧~