本篇文章,我们使用开源项目bitcoinj解决BTC离线签名,矿工费计算,生成BTC秘钥等问题。
org.bitcoinj
bitcoinj-core
0.14.7
org.apache.commons
commons-lang3
3.8
//是否主網(default:true)
public Boolean isMainNet = true;
public void setIsMainNet(Boolean isMainNet) {
this.isMainNet = isMainNet;
}
public static final String privateKey_K = "privateKey";
public static final String publicKey_K = "privateKey";
public static final String address_K = "address";
public static final String mnemonics_K = "mnemonics";
/**
* btc交易签名
* @param fromAddress
* @param toAddress
* @param privateKey
* @param amount
* @param fee
* @param utxos
* @return
* @throws Exception
*/
public String sign(String fromAddress, String toAddress, String privateKey, long amount, long fee, List utxos) throws Exception {
Res res = new Res();
NetworkParameters networkParameters = isMainNet ? MainNetParams.get() : TestNet3Params.get();
Transaction transaction = new Transaction(networkParameters);
String changeAddress = fromAddress;//找零地址
Long changeAmount = 0L;
Long utxoAmount = 0L;
List needUtxos = new ArrayList();
//获取未消费列表
if (utxos == null || utxos.size() == 0) {
throw new Exception("未消费列表为空");
}
//遍历未花费列表,组装合适的item
for (UTXO utxo : utxos) {
if (utxoAmount >= (amount + fee)) {
break;
} else {
needUtxos.add(utxo);
utxoAmount += utxo.getValue().value;
}
}
transaction.addOutput(Coin.valueOf(amount), Address.fromBase58(networkParameters, toAddress));
//消费列表总金额 - 已经转账的金额 - 手续费 就等于需要返回给自己的金额了
changeAmount = utxoAmount - (amount + fee);
//余额判断
if (changeAmount < 0) {
throw new Exception("utxo余额不足");
}
//输出-转给自己(找零)
if (changeAmount > 0) {
transaction.addOutput(Coin.valueOf(changeAmount), Address.fromBase58(networkParameters, changeAddress));
}
//输入未消费列表项
DumpedPrivateKey dumpedPrivateKey = DumpedPrivateKey.fromBase58(networkParameters, privateKey);
ECKey ecKey = dumpedPrivateKey.getKey();
for (UTXO utxo : needUtxos) {
TransactionOutPoint outPoint = new TransactionOutPoint(networkParameters, utxo.getIndex(), utxo.getHash());
transaction.addSignedInput(outPoint, utxo.getScript(), ecKey, Transaction.SigHash.ALL, true);
}
byte[] bytes = transaction.bitcoinSerialize();
String hash = Hex.toHexString(transaction.bitcoinSerialize());
logger.info("fee:{},utxoAmount:{},changeAmount{}", fee, utxoAmount, changeAmount);
return hash;
}
/**
* 获取矿工费用
* @param amount
* @param utxos
* @return
*/
public Long getFee(long amount, List utxos) {
Long feeRate = getFeeRate();//获取费率
Long utxoAmount = 0L;
Long fee = 0L;
Long utxoSize = 0L;
for (UTXO us : utxos) {
utxoSize++;
if (utxoAmount >= (amount + fee)) {
break;
} else {
utxoAmount += us.getValue().value;
fee = (utxoSize * 148 * 34 * 3 + 10) * feeRate;
}
}
return fee;
}
/**
* 创建钱包地址
*
* @return
*/
public Map createWalletToJson() {
NetworkParameters networkParameters = isMainNet ? MainNetParams.get() : TestNet3Params.get();
DeterministicSeed seed = new DeterministicSeed(new SecureRandom(), 128, "", Utils.currentTimeSeconds());
Wallet wallet;
String mnemonics = "";
String privateKey = "";
String publicKey = "";
String address = "";
String pwd = "";
try {
wallet = Wallet.fromSeed(networkParameters, seed);
//私钥
privateKey = wallet.currentReceiveKey().getPrivateKeyAsWiF(networkParameters);
//助记词
mnemonics = wallet.getKeyChainSeed().getMnemonicCode().toString();
publicKey = Hex.toHexString(ECKey.publicKeyFromPrivate(wallet.currentReceiveKey().getPrivKey(), true));
//地址
address = wallet.currentReceiveAddress().toBase58();
} catch (Exception e) {
logger.error("【比特币钱包创建】失败,原因", e);
return null;
}
Map resultMap = new LinkedHashMap();
resultMap.put(mnemonics_K, mnemonics);
resultMap.put(privateKey_K, privateKey);
resultMap.put(publicKey_K, publicKey);
resultMap.put(address_K, address);
return resultMap;
}
/***
* 获取未消费列表
* @param address :地址
* @return
*/
public List getUnspent(String address) {
List utxos = Lists.newArrayList();
String host = this.isMainNet ? "blockchain.info" : "testnet.blockchain.info";
String url = "https://" + host + "/zh-cn/unspent?active=" + address;
try {
String httpGet = HttpUtil.sendGet(url, null);//TODO;联网
if (StringUtils.equals("No free outputs to spend", httpGet)) {
return utxos;
}
JSONObject jsonObject = JSON.parseObject(httpGet);
JSONArray unspentOutputs = jsonObject.getJSONArray("unspent_outputs");
List
//1.创建秘钥对
@Test
public void createPk() {
Map resultMap = api.createWalletToJson();
System.out.println(resultMap);
}
//2.离线签名交易
@Test
public void tranaction() throws Exception {
api.setIsMainNet(false);
String from = "monpT1RbuVuKJJocCfBruYbaYCVD6miprYx";
String to = "mm26iTQBLEga8zJxvqURo81xuKE7y4m3hB";
String fromPrivate = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
Long amount = 1000l;
List utxos = api.getUnspent(from);
Long fee = api.getFee(amount,utxos);
String sig = api.sign(from, to, fromPrivate, amount,fee, utxos);
String txid = api.publishTx(sig);
System.out.println(txid);
}