一.比特币离线地址和私钥生成
下面是 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<String, String> generateBtcAddress() { NetworkParameters paramsTest = TestNet3Params.get(); //NetworkParameters params = MainNetParams.get(); ECKey key = new ECKey(); Map<String, String> 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<UnSpentUtxo> unUtxos) { if(!unUtxos.isEmpty() && null != unUtxos){ List<UTXO> utxos = new ArrayList<UTXO>(); 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<String> 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 等接口