美文网首页区块链研习社区块链
区块链开发之BTC钱包APP

区块链开发之BTC钱包APP

作者: zhangchunlin | 来源:发表于2020-05-11 08:54 被阅读0次

    目录

    基础知识

    1、什么是比特币?

    • 比特币是一种创新的支付网络和一种新型的货币。
    • 比特币本质上就是一个分布式的P2P网络系统。它由一系列电脑(或其它计算设备)相互连通构成一个P2P网络。每个电脑上都装有一系列的软件,这些软件就构成一个分布式计算系统,用于协调这些电脑完成相互链接、相互传递消息和通信、协调各自的任务和分工。最终这些电脑彼此交互要实现一个共同的目标——维护一套数据库的完整和更新。
    • 从技术层面来看,比特币是最早和最成功的区块链应用,它可以被看作一个由加密算法,共识机制,p2p网络等技术组合而成的系统。

    2、比特币钱包又是什么?

    • 比特币钱包相当于与比特币交易的实体钱包。不同平台有不同的钱包。要开始使用比特币(比特币),首先你需要一个比特币钱包。它允许您进行交易,即买卖加密货币。比特币钱包的主要任务是存储访问比特币地址所需的密钥,以及相应的资金。
    • 从技术角度来看,比特币本身不存储在任何地方,只存储秘密数字密钥,这可以访问公共比特币地址和“签署”交易的能力。这是为了这个信息,需要一个比特币钱包。钱包是不同的,取决于它们所针对的设备,您甚至可以不使用计算机并将密钥写在纸上。当然,钱包有备份副本并且受到保护以防止未经授权的访问是非常重要的。

    3、比特币交易又是什么?

    • 比特币的交易(Transation,缩写Tx),并不是通常意义的交易,例如一手交钱一手交货,而是转账。交易由N个输入和M个输出两部分组成。交易的每个输入便是前向交易的某个输出,那么追踪到源头,必然出现一个没有输入的交易,此类交易称为CoinBase Tx。CoinBase类交易是奖励挖矿者而产生的交易,该交易总是位于Block块的第一笔。


      image

      注:此图来自比特币白皮书

    开发准备

    1、bitcoinj基础:

    bitcoinj是用于处理比特币协议的库。它可以维护一个钱包,发送/接收交易,而无需本地的Bitcoin Core副本,并具有许多其他高级功能。它用Java实现。

    2、bitcoinj特征:

    • 高度优化的轻量级简化付款验证(SPV)模式。在这种模式下,仅下载了一小部分区块链,从而使比特币适合在受约束的设备(如智能手机或廉价的虚拟专用服务器)上使用。
    • 实验性完整验证模式,其验证工作与Bitcoin Core相同。在这种模式下,将计算未使用的事务输出集(UTXO集),并且由于有PostgreSQL存储,因此可以将其索引到数据库中,从而可以按地址快速查找余额。
    • 带有加密,费用计算,多重签名,确定性密钥派生,可插入硬币选择/硬币控制,扩展支持和事件监听器的钱包类,可让您随时了解余额的变化。
    • 支持小额支付通道,使您可以在客户端和服务器之间建立多签名合同,然后在该通道上进行协商,从而实现快速的小额支付,从而避免了矿工费用。
    • 为网络IO 提供异步和每连接线程数,从而允许您在可伸缩性和仅阻止功能(例如SOCKS代理)之间进行选择。
    • 轻松实现使用比特币合约功能的应用程序。
    • 一个简单的GUI钱包应用程序,您可以将其用作自己的应用程序的基础。观看或阅读有关如何对其进行自定义并构建不需要Java的本机安装程序的教程。
    • 用于处理钱包和链式文件,支付协议,网络等的命令行工具。

    3、我们怎么使用bitcoinj:

    1. 在Github下载源码,自己编译。
    gradle clean build
    gradle clean assemble
    
    1. 使用Maven或Gradle依赖。
     compile  'org.bitcoinj:bitcoinj-core:0.15.6'
     for Android
     implementation  'org.bitcoinj:bitcoinj-core:0.15.6'
    

    实战开发

    生成账号:
    1、随机生成账号

    String mnemonic = ChainUtil.genMnemonic(Words.TWELVE);
    ECKey ecKey = ChainUtil.genECKey(mnemonic, "m/44'/0'/0'/0/0");
    Address address = Address.fromKey(MainNetParams.get(), ecKey, Script.ScriptType.P2PKH);
    System.out.println("mnemonic:" + mnemonic);
    System.out.println("privateKey:" +ecKey.getPrivateKeyAsWiF(MainNetParams.get()));
    System.out.println("publicKey:" + ecKey.getPublicKeyAsHex());
    System.out.println("address:" + address.toString());
    输出:
    mnemonic:across slow mechanic portion hospital perfect zone best capable champion pond exhaust
    privateKey:L1QVhXZ4bBnQGwAWYDRCNQrkyeSLCiBjKhp5ewLKdXwDpxYVoifj 
    publicKey:020afa4282079e6898fe04da1ba2c0eb6eec6e0c04d691c281869672ea212f1fe1
    address:17TBeyxjCpTcZb3TUg8b1GRR6DCJ5mi1UU
    

    2、助记词导入

    String mnemonic = "across slow mechanic portion hospital perfect zone best capable champion pond exhaust";
    ECKey ecKey = ChainUtil.genECKey(mnemonic, "m/44'/0'/0'/0/0");
    Address address = Address.fromKey(MainNetParams.get(), ecKey, Script.ScriptType.P2PKH);
    System.out.println("mnemonic:" + mnemonic);
    System.out.println("privateKey:" + ecKey.getPrivateKeyAsWiF(MainNetParams.get()));
    System.out.println("publicKey:" + ecKey.getPublicKeyAsHex());
    System.out.println("address:" + address.toString());
    输出:
    mnemonic:across slow mechanic portion hospital perfect zone best capable champion pond exhaust
    privateKey:L1QVhXZ4bBnQGwAWYDRCNQrkyeSLCiBjKhp5ewLKdXwDpxYVoifj 
    publicKey:020afa4282079e6898fe04da1ba2c0eb6eec6e0c04d691c281869672ea212f1fe1
    address:17TBeyxjCpTcZb3TUg8b1GRR6DCJ5mi1UU
    

    3、私钥导入

    String privateKey = "L1QVhXZ4bBnQGwAWYDRCNQrkyeSLCiBjKhp5ewLKdXwDpxYVoifj";
    ECKey ecKey = DumpedPrivateKey.fromBase58(MainNetParams.get(), privateKey).getKey();
    Address address = Address.fromKey(MainNetParams.get(), ecKey, Script.ScriptType.P2PKH);
    System.out.println("privateKey:" +ecKey.getPrivateKeyAsWiF(MainNetParams.get()));
    System.out.println("publicKey:" + ecKey.getPublicKeyAsHex());
    System.out.println("address:" + address.toString());
    输出:
    privateKey:L1QVhXZ4bBnQGwAWYDRCNQrkyeSLCiBjKhp5ewLKdXwDpxYVoifj
    publicKey:020afa4282079e6898fe04da1ba2c0eb6eec6e0c04d691c281869672ea212f1fe1
    address:17TBeyxjCpTcZb3TUg8b1GRR6DCJ5mi1UU
    

    4、获取余额:
    https://blockchain.info/balance?active=$address

    curl --location --request GET 'https://blockchain.info/balance?active=39rgKZGLDfNcQ9nLrNEujpm53c6SAqfM8M'
    结果:
    {
        "39rgKZGLDfNcQ9nLrNEujpm53c6SAqfM8M": {
            "final_balance": 0,
            "n_tx": 2,
            "total_received": 712880
        }
    }
    地址:39rgKZGLDfNcQ9nLrNEujpm53c6SAqfM8M
    解析final_balance字段就可以拿到余额了
    

    5、获取未花费的交易输出(Unspent outputs)
    https://blockchain.info/unspent?active=$address

    curl --location --request GET 'https://blockchain.info/unspent?&active=1KFHE7w8BhaENAswwryaoccDb6qcT6DbYY'
    结果:
    {
        "notice": "",
        "unspent_outputs": [
            {
                "tx_hash": "9b14b25798b431d2d0f749e958fd8cec0bbe109e16d890754671aec3a0602628",
                "tx_hash_big_endian": "282660a0c3ae71467590d8169e10be0bec8cfd58e949f7d0d231b49857b2149b",
                "tx_output_n": 0,
                "script": "76a914c825a1ecf2a6830c4401620c3a16f1995057c2ab88ac",
                "value": 1274264286,
                "value_hex": "4bf3bade",
                "confirmations": 138,
                "tx_index": 0
            }
        ]
    }
    
    说明:unspent_outputs就是你未花费的列表。
    

    6、费用估算API

    curl --location --request GET 'https://bitcoinfees.earn.com/api/v1/fees/recommended' \
    --header 'Cookie: __cfduid=d402b8e2522ca17f6b6cbb1392842166b1572919642'
    结果:
    {
        "fastestFee": 8,
        "halfHourFee": 4,
        "hourFee": 2
    }
    

    7、手续费计算。

    • size 的计算公式
    size = 148 x inputNum + 34 x outputNum + 10
    算出字节数后,再乘以rate(Satoshi/byte),rate通过费用估算API获取
    提示:所以为了转账少花手续费,最好把utxo列表根据余额从大到小做个排序
    
    • 手续费计算:
    unSpentBTCList
    value
    rate sta/byte
    -1发送的value超出了你的余额
    public static long getFee(@NonNull List<UnSpentBTC> unSpentBTCList, long value, int rate) {
        long fee = 0L;
        int inputNum = 0;
        long totalMoney = 0;
        for (UnSpentBTC us : unSpentBTCList) {
            inputNum++;
            totalMoney += us.getSatoshis();
            if (totalMoney > value) {
                fee = (148 * inputNum + 34 * 1 + 10) * rate;
                if (totalMoney == (value + fee))
                    return fee;
                else if (totalMoney > (value + fee)) {
                    fee = (148 * inputNum + 34 * 2 + 10) * rate;
                    if (totalMoney >= (value + fee))
                        return fee;
                }
            }
        }
         return -1;
    }
    

    有的朋友可能还会需要算出,最大能够发送的钱数,我这里也给一下代码,仅做参考:

    public static long getMaxSendValue(List<UnSpentBTC> unSpentBTCList, long totalMoney, int rate) {
    
            while (true) {
                long fee = getFee(unSpentBTCList, totalMoney, rate);
                if (fee == -1)
                    totalMoney -= 100;
                else
                    break;
            }
           /* long fee = getFee(unSpentBTCList, totalMoney, rate);
            if (fee == -1) {
                totalMoney -= 100;
                getMaxSendValue(unSpentBTCList, totalMoney, rate);
            }*/
            return totalMoney;
        }
    

    8、签名交易

    NetworkParameters params = MainNetParams.get();
            String formAddr = "你的地址";
            String privateKey = "你的私钥";
            String recevieAddr = "接收地址";
            long amount = 1000; // "你要转多少"
            List<UnspentOutputsBean.UnspentOutputsBean> unUtxos = getUnspentOutputs(formAddr);
            int sat = 10;//手续费比列
            if (null != unUtxos && !unUtxos.isEmpty()) {
                Collections.sort(unUtxos, (o1, o2) -> o2.getValue() - o1.getValue());
                List<UTXO> utxos = new ArrayList<>();
                DumpedPrivateKey dumpedPrivateKey = null;
                ECKey key = null;
                if (!TextUtils.isEmpty(privateKey)) {
                    dumpedPrivateKey = DumpedPrivateKey.fromBase58(params, privateKey);
                    key = dumpedPrivateKey.getKey();
                }
                // 接收地址
                Address receiveAddress = Address.fromString(params, recevieAddr);
                // 构建交易
                Transaction tx = new Transaction(params);
                tx.addOutput(Coin.valueOf(amount), receiveAddress); // 转出
                // 如果需要找零 消费列表总金额 - 已经转账的金额 - 手续费
                long value = 0;
                int fee = getFee(unUtxos, amount, sat);
                int size = fee / sat;
                for (UnspentOutputsBean.UnspentOutputsBean unUtxo : unUtxos) {
                    value += unUtxo.getValue();
                    utxos.add(new UTXO(Sha256Hash.wrap(unUtxo.getTx_hash_big_endian()),
                            unUtxo.getTx_output_n(),
                            Coin.valueOf(unUtxo.getValue()),
                            unUtxo.getConfirmations(),
                            false,
                            new Script(HexUtils.toBytes(unUtxo.getScript()))));
                    if (value > fee + amount) {
                        break;
                    }
                }
                Address toAddress = Address.fromString(params, formAddr);
                tx.addOutput(Coin.valueOf(value - amount - fee), toAddress); //找零地址
    
                for (UTXO utxo : utxos) {
                    TransactionOutPoint outPoint = new TransactionOutPoint(params, utxo.getIndex(), utxo.getHash());
                    // YOU HAVE TO CHANGE THIS
                    if (key != null) {
                        tx.addSignedInput(outPoint, utxo.getScript(), key, Transaction.SigHash.ALL, true);
                    }
                }
                org.bitcoinj.core.Context context = new org.bitcoinj.core.Context(params);
                tx.getConfidence().setSource(TransactionConfidence.Source.NETWORK);
                tx.setPurpose(Transaction.Purpose.USER_PAYMENT);
     
                System.out.println("size: " + size);
                System.out.println("sat: " + sat);
                System.out.println("fee: " + fee);
                System.out.println("hash: " + tx.getTxId().toString());
                System.out.println("serializeHex: " + Hex.toHexString(tx.bitcoinSerialize()));
            }
    

    9、广播
    在上一步拿到了序列化的16进制交易,我们拿到了后直接广播就可以了。

    curl --location --request POST 'https://blockchain.info/pushtx' \
    --header 'Content-Type: application/x-www-form-urlencoded' \
    --data-urlencode 'tx=你的16进制交易数据'
    
    返回状态码200就广播成功了,接下来就等着被确认了。
    

    10、获取交易详情。
    在第7步的时候已经拿到了交易hash,并在8步交易成功后,就可以查询交易详情就,具体操作如下。
    https://blockchain.info/rawtx/$tx_hash

    curl --location --request GET 'https://blockchain.info/rawtx/e56b59f60f45842e43e4f3cf418d0e090b358b3c7433bde6cecb26f04f3b1b3a'
    
    结果:
    {
       "ver": 2,
       "inputs": [
          {
             "sequence": 4294967295,
             "witness": "0000000000000000000000000000000000000000000000000000000000000000",
             "script": "032c800904eb7e785e45552f48756f42692ffabe6d6d177b70cf4446e4298c1c2d14e8eec6d95846065d0e6fc12cd1a9b0726bdf16400800000090fed2180358dcf2a44b140000000000"
          }
       ],
       "weight": 1048,
       "block_height": 622636,
       "relayed_by": "0.0.0.0",
       "out": [
          {
             "spent": true,
             "spending_outpoints": [
                {
                   "tx_index": 0,
                   "n": 13
                }
             ],
             "tx_index": 0,
             "type": 0,
             "addr": "1MvYASoHjqynMaMnP7SBmenyEWiLsTqoU6",
             "value": 1261606981,
             "n": 0,
             "script": "76a914e582933875bedfdc448473c00b474f8f053a467588ac"
          },
          {
             "spent": false,
             "tx_index": 0,
             "type": 0,
             "value": 0,
             "n": 1,
             "script": "6a24aa21a9edf8ce97a293f7d1ba0a8e5634d3a12080bbfdf22a3392005c52a2ca9ad6ea65ca"
          },
          {
             "spent": false,
             "tx_index": 0,
             "type": 0,
             "value": 0,
             "n": 2,
             "script": "6a24b9e11b6d8d12ceae709815f42c2aedf406aa65d9e25b4c6075a7ce5f8505c0ff18c946ef"
          }
       ],
       "lock_time": 0,
       "size": 289,
       "block_index": 0,
       "time": 1584955126,
       "tx_index": 0,
       "vin_sz": 1,
       "hash": "e56b59f60f45842e43e4f3cf418d0e090b358b3c7433bde6cecb26f04f3b1b3a",
       "vout_sz": 3
    }
    

    总结

    1、生成比特币账号相对简单点,都提供了相关的API,调用即可。
    2、交易就相对麻烦一点,要知道比特币的交易模型,添加输入输出,计算手续费,并构建16进制的交易数据,广播即可。
    3、在创建交易的时候一定要添加找零地址,不然的话后果就是,你没用完的比特币会全部交给矿工,切记 切记 切记。

    参考文献

    [1] 比特币白皮书:https://bitcoin.org/files/bitcoin-paper/bitcoin_zh_cn.pdf
    [2] BitcoinJ Github:https://github.com/bitcoinj/bitcoinj
    [3] 比特币官网:https://bitcoin.org/zh_CN/
    [4]感谢获取手续费文章:https://blog.csdn.net/wypeng2010/article/details/81357684
    [5]blockchain:https://www.blockchain.com/zh-cn/api/blockchain_api

    相关文章

      网友评论

        本文标题:区块链开发之BTC钱包APP

        本文链接:https://www.haomeiwen.com/subject/fasynhtx.html