前面说了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分钟左右就能转账成功啦, 是不是很简单?