区块链钱包之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分钟左右就能转账成功啦, 是不是很简单?

猜你喜欢

转载自blog.csdn.net/qq634416025/article/details/79686041