一.比特币离线地址和私钥生成
下面是 BTC 离线生成地址和私钥的代码,废话不多说,直接上代码
package com.gingernet.bitcoin;
import com.gingernet.utils.Utils;
import org.bitcoinj.core.Base58;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.crypto.HDUtils;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.params.TestNet3Params;
import org.bitcoinj.wallet.DeterministicKeyChain;
import org.bitcoinj.wallet.DeterministicSeed;
import org.bitcoinj.wallet.UnreadableWalletException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.digests.RIPEMD160Digest;
import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECPoint;
import java.util.HashMap;
import java.util.Map;
public class Address {
private Logger logger = LoggerFactory.getLogger(getClass());
static NetworkParameters params;
// 生成 WIF 格式的地址(中心化钱包使用)
public Map generateBtcAddress() {
NetworkParameters paramsTest = TestNet3Params.get();
//NetworkParameters params = MainNetParams.get();
ECKey key = new ECKey();
Map btcMap = new HashMap<>();
btcMap.put("btcWifPk", key.getPrivateKeyAsWiF(paramsTest));
btcMap.put("btcPk", key.getPrivateKeyAsHex());
btcMap.put("btcPuKey", key.getPublicKeyAsHex());
btcMap.put("btcAddress", key.toAddress(paramsTest).toString());
return btcMap;
}
// 由助记词生成地址流程(去中心化钱包使用)
public String CreateAddressByWord(String wordsList) throws Exception {
NetworkParameters params = TestNet3Params.get();
DeterministicSeed deterministicSeed = new DeterministicSeed(wordsList, null, "", 0L);
DeterministicKeyChain deterministicKeyChain = DeterministicKeyChain.builder().seed(deterministicSeed).build();
BigInteger privKey = deterministicKeyChain.getKeyByPath(HDUtils.parsePath("44H / 1H / 0H / 0 / 2"), true).getPrivKey();
ECKey ecKey = ECKey.fromPrivate(privKey);
org.bitcoinj.core.Address address = ecKey.toAddress(params);
System.out.println(ecKey.getPrivateKeyAsWiF(params));
return address.toBase58();
}
// 比特币系列地址生成流程
public String bitcoinS(String version) {
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256k1");
keyGen.initialize(ecSpec);
KeyPair kp = keyGen.generateKeyPair();
PublicKey pub = kp.getPublic();
PrivateKey pvt = kp.getPrivate();
ECPrivateKey epvt = (ECPrivateKey) pvt;
String sepvt = Utils.adjustTo64(epvt.getS().toString(16)).toUpperCase();
logger.warn("s[" + sepvt.length() + "]: " + sepvt);
logger.warn("私钥{}", sepvt);
ECPublicKey epub = (ECPublicKey) pub;
ECPoint pt = epub.getW();
String sx = Utils.adjustTo64(pt.getAffineX().toString(16)).toUpperCase();
String sy = Utils.adjustTo64(pt.getAffineY().toString(16)).toUpperCase();
String bcPub = "04" + sx + sy;
logger.warn("公钥{}", bcPub);
MessageDigest sha = MessageDigest.getInstance("SHA-256");
byte[] s1 = sha.digest(bcPub.getBytes("UTF-8"));
logger.warn("sha256后{}", Utils.byte2Hex(s1).toUpperCase());
RIPEMD160Digest digest = new RIPEMD160Digest();
digest.update(s1, 0, s1.length);
byte[] ripemd160Bytes = new byte[digest.getDigestSize()];
digest.doFinal(ripemd160Bytes, 0);
logger.warn("ripemd160加密后{}", Utils.bytesToHexString(ripemd160Bytes));
byte[] networkID = new BigInteger(version, 16).toByteArray();
byte[] extendedRipemd160Bytes = Utils.add(networkID, ripemd160Bytes);
logger.warn("添加NetworkID后{}", Utils.bytesToHexString(extendedRipemd160Bytes));
byte[] twiceSha256Bytes = Utils.sha256(Utils.sha256(extendedRipemd160Bytes));
logger.warn("两次sha256加密后{}", Utils.bytesToHexString(twiceSha256Bytes));
byte[] checksum = new byte[4];
System.arraycopy(twiceSha256Bytes, 0, checksum, 0, 4);
logger.warn("checksum{}", Utils.bytesToHexString(checksum));
byte[] binaryBitcoinAddressBytes = Utils.add(extendedRipemd160Bytes, checksum);
logger.warn("添加checksum之后{}", Utils.bytesToHexString(binaryBitcoinAddressBytes));
String ltccoinAddress = Base58.encode(binaryBitcoinAddressBytes);
logger.warn("地址{}", ltccoinAddress);
return ltccoinAddress;
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
}
二. 比特币交易签名
获取手续费
查看比特币建议手续费的网站https://bitcoinfees.earn.com/
在上面这个网站上可以看到当前的手续费是多少聪每比特,一般来说,一个中等的交易是225比特,手续费的结果是8550聪。但是许多钱包使用每千字节satoshis或每千字节比特币,因此您可能需要转换单位。
此处显示的费用为每字节交易数据的Satoshis(0.00000001 BTC)。 矿工通常首先包括费用/字节最高的交易。钱包应根据用户需要确认的速度,根据此数字进行费用计算。
也可以通过其他建议手续费网站获取,或者通过节点获取
下面是离线签名的代码,下面代码是测试网络的,若需要主网测试,直接替换即可
package com.gingernet.bitcoin;
import java.text.Collator;
import java.util.ArrayList;
import java.util.List;
import com.gingernet.api.po.UnSpentUtxo;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.configuration2.Configuration;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Context;
import org.bitcoinj.core.DumpedPrivateKey;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionOutPoint;
import org.bitcoinj.core.UTXO;
import org.bitcoinj.core.Utils;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.params.TestNet3Params;
import org.bitcoinj.script.Script;
import org.omg.CORBA.UNKNOWN;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import org.bitcoinj.core.TransactionConfidence;
public class TransactionSign {
private static Logger LOG = LoggerFactory.getLogger(TransactionSign.class);
static NetworkParameters params;
static {
try {
params = TestNet3Params.get(); // MainNetParams.get();
LOG.info("=== [BTC] bitcoin client networkID:{} ===", params.getId());
} catch (Exception e) {
LOG.info("=== [BTC] com.bscoin.coldwallet.cointype.btc.rawtransaction:{} ===", e.getMessage(), e);
}
}
public String SignTransaction(String privKey, String recevieAddr, String formAddr, long amount, long fee, List unUtxos) {
if(!unUtxos.isEmpty() && null != unUtxos){
List utxos = new ArrayList();
DumpedPrivateKey dumpedPrivateKey = DumpedPrivateKey.fromBase58(params, privKey);
ECKey key = dumpedPrivateKey.getKey();
// 接收地址
Address receiveAddress = Address.fromBase58(params, recevieAddr);
// 构建交易
Transaction tx = new Transaction(params);
tx.addOutput(Coin.valueOf(amount), receiveAddress);
// 如果需要找零 消费列表总金额 - 已经转账的金额 - 手续费
long value = unUtxos.stream().mapToLong(UnSpentUtxo::getValue).sum();
Address toAddress = Address.fromBase58(params, formAddr);
long leave = value - amount - fee;
if(leave > 0){
tx.addOutput(Coin.valueOf(leave), toAddress);
}
// utxos is an array of inputs from my wallet
for (UnSpentUtxo unUtxo : unUtxos) {
utxos.add(new UTXO(Sha256Hash.wrap(unUtxo.getHash()),
unUtxo.getTxN(),
Coin.valueOf(unUtxo.getValue()),
unUtxo.getHeight(),
false,
new Script(Utils.HEX.decode(unUtxo.getScript())),
unUtxo.getAddress()));
}
for (UTXO utxo : utxos) {
TransactionOutPoint outPoint = new TransactionOutPoint(params, utxo.getIndex(), utxo.getHash());
tx.addSignedInput(outPoint, utxo.getScript(), key, Transaction.SigHash.ALL, true);
}
Context context = new Context(params);
tx.getConfidence().setSource(TransactionConfidence.Source.NETWORK);
tx.setPurpose(Transaction.Purpose.USER_PAYMENT);
LOG.info("Bitcoin Sign Success :{} ===",tx.getHashAsString());
return new String(Hex.encodeHex(tx.bitcoinSerialize()));
}
return null;
}
}
三. 发送比特币交易到区块链网络
// 通过第三方接口发送交易到 BTC 网络
public String SendBtcRawTx(String data) throws Exception {
String txid = "";
try {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.add("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36");
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
String requestBody = "{\"tx\":\"" + data + "\"}";
HttpEntity request = new HttpEntity(requestBody, headers);
ResponseEntity response = restTemplate.exchange("https://api.blockcypher.com/v1/btc/main/txs/push?token=5f4eb6f1fa3d4f6fa519ee93a5a3fb2e", HttpMethod.POST, request, String.class);
System.out.println(response.getBody());
String rsp = response.getBody();
JSONObject jsonObject = (JSONObject) JSONValue.parse(rsp);
JSONObject txObj = (JSONObject)jsonObject.get("tx");
txid = txObj.get("hash").toString();
} catch (Exception e) {
e.printStackTrace();
}
return txid;
}
四. 确认转账交易是否成功
流程:扫快获取到交易 Hash, 根据交易Hash 获取到交易,解码交易得到 Vout 和 Vin,根据业务需求入口。
用到的接口有:getblockchaininfo, getchaintips, decoderawtransaction 等接口
原创文章,作者:[email protected],如若转载,请注明出处:http://www.hwjiao.com/2021/03/05/java-%e5%bc%80%e5%8f%91-btc-%e9%92%b1%e5%8c%85/