1. 程式人生 > >區塊鏈錢包之BTC交易離線簽名

區塊鏈錢包之BTC交易離線簽名

前面說了ETH以及基於以太坊的代幣轉賬離線簽名方式, 現在我們來說說如何對BTC進行離線簽名, ETH及其代幣是隻用的賬戶系統, 所以轉賬簽名比較簡單, 但是BTC是基於UTXO的方式進行簽名的.

UTXO 代表 Unspent Transaction Output, 表示未花費的輸出。以現實的錢包舉例,一個錢包中有一個10元、1個5元,1個1元,一共16元。比特幣一個賬戶的餘額,也是根據這個賬戶UTXO計算的。 當花12元買東西時,可以把10元和5元拿出去,然後得到找零的3元, 那這個時候之前的10元和5元因為已經花出去了就不再是UTXO了,新找零的3元成為新的UTXO,再加上之前未動的1元UTXO,目前的餘額是4元。這次新的交易記錄在了新的區塊上,但沒有改變歷史區塊的資料。 比特幣使用前後連結的區塊鏈記錄所有交易記錄,當之前的UYXO出現在後續交易的輸入時,就表示這個UTXO已經花費掉了,不再是UTXO了。 如果從第一個區塊開始逐步計算所有比特幣地址中的餘額,就可以計算出不同時間的各個比特幣賬戶的餘額了。來源:

知乎

簡單來說就是, 一些列未消費列表就相當於你口袋裡面的零錢, 比如我包包裡面有10元, 5元, 2元, 這個時候需要給某人轉賬6元, 那就把10元當做輸入資料, 這個時候需要找零, 就給對方轉6元, 然後把剩餘的4元轉給自己, 這樣就完成了一次轉賬操作, 有點兒難以理解哈? 再說說, 假如要轉13元給對方, 這個時候就是把10元 5元當做輸入資料, 轉給對方13元, 把剩餘的2元轉給自己, 如果還不能理解的話, 下面我就直接看程式碼吧.

配置環境

在build.gradle裡面加入bitcoinj

implementation group: 'org.bitcoinj', name: 'bitcoinj-core'
, version: '0.14.6'

轉賬簽名

首先看一下未消費列表的資料格式, 一般是這樣的, 這裡的value就是未消費的零錢. 轉換成軟妹幣是 value / 10^8

{
    "errno": 0,
    "msg": "請求成功",
    "data": {
        "nonce": 0,
        "gas_limit": 250,
        "gas_price": 10,
        "unspent": [{
                "txid": "b6e98a2744bed62f0664d6685fb9ae943c71408db20f8b925d7e74d98d813f4e"
, "output_no": 1, "script_asm": "OP_DUP OP_HASH160 1b91327b42e521edd8b59d3fff23e45053d96fe9 OP_EQUALVERIFY OP_CHECKSIG", "script_hex": "76a9141b91327b42e521edd8b59d3fff23e45053d96fe988ac", "value": 107487500, "confirmations": 392, "time": 1521433670 }, { "txid": "b6e98a2744bed62f0664d6685fb9ae943c71408db20f8b925d7e74d98d813f4e", "output_no": 1, "script_asm": "OP_DUP OP_HASH160 1b91327b42e521edd8b59d3fff23e45053d96fe9 OP_EQUALVERIFY OP_CHECKSIG", "script_hex": "76a9141b91327b42e521edd8b59d3fff23e45053d96fe988ac", "value": 107487500, "confirmations": 392, "time": 1521433670 } ]
}
}

這個時候我們來看一下如何是用未消費列表進行轉賬離線簽名

fun signBTC(unspents: List<UnSpentItem>, //未消費列表
                value: Long, //需要轉賬的值
                fee: Long, //礦工費
                toAdress: String,//轉賬地址
                assetItem: AssetItem, 
                psw: String): String {
        //傳入主網引數
        val transaction = Transaction(getParams())
        val wallet = SPUtils.getInstance().getTianWallet(CoinType.BTC, psw)
        val privateKey = DumpedPrivateKey.fromBase58(getParams(), wallet?.privateKey)
        val ecKey = privateKey.key

        var money = 0L
        val utxos = arrayListOf<UTXO>()
        //遍歷unspents, 組裝合適的item
        unspents.forEach {
            //當消費列表某幾個item的值加起來大於實際轉賬金額+手續費, 就跳出迴圈, 這個時候就得到了合符條件的utxos陣列
            if (money >= (value + fee)) {
                return@forEach
            }

            val utxo = UTXO(
                    Sha256Hash.wrap(it.txid),
                    it.outputNo.toLong(),
                    Coin.valueOf(it.value),
                    it.confirmations,
                    true,
                    Script(HEX.decode(it.scriptHex))
            )
            utxos.add(utxo)
            //把消費列表的值加起來
            money += it.value
        }
        //輸出-轉給別人
        transaction.addOutput(Coin.valueOf(value), Address.fromBase58(getParams(), toAdress))
        //消費列表總金額 - 已經轉賬的金額 - 手續費 就等於需要返回給自己的金額了,  你不能在轉賬的錢上面減去手續費吧, 哈哈
        val leave = money - value - fee
        //輸出-轉給自己
        if (leave > 0) {
            transaction.addOutput(Coin.valueOf(leave), Address.fromBase58(getParams(), CommonUtils.getMyAddress(assetItem)))
        }
        //輸入未消費列表項
        utxos.forEach {
            val outPoint = TransactionOutPoint(getParams(), it.index, it.hash)
            transaction.addSignedInput(outPoint, it.script, ecKey, Transaction.SigHash.ALL, true)
        }

        return HEX.encode(transaction.bitcoinSerialize())
    }

就這樣我們就得到了具體的值了, 這裡會得到很長一串hex, 那麼去哪裡驗證是否簽名正確呢? 可以到這個網站測試https://live.blockcypher.com/btc-testnet/decodetx/

解析出來的資料如果是這樣的, 說明你簽名成功啦, 只需要廣播出去, 等待10分鐘左右就能轉賬成功啦, 是不是很簡單?