Java 开发 BTC 钱包

一.比特币离线地址和私钥生成

下面是 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 等接口




0
835