如何自助恢复多签资产?
Ownbit多签资产的安全性和可用性不依赖于Onwbit服务器。本文教您如何在Ownbit服务器出现宕机或其它意外情况时,通过自助恢复多签资产。本文以 2-3 多签为例,参与方分别为:A、B、C 三人。
1. 恢复 BTC 多签资产
BTC 多签资产的地址是已知和公开的。例如地址为:3CkvfWhYd6behUYnrA5rMQJoyr6oLGM6Ed,并期望将该多签地址上余额发送至地址: receiveAddress = 1GC3q7JYPu…VDv5pcZF2Va。
a. 通过浏览器查询出多签地址上的可用UTXO,每个币种都有相应的浏览器,例如 BTC 可用:https://www.blockchain.com/btc/address/3CkvfWhYd6behUYnrA5rMQJoyr6oLGM6Ed 进行查询。显示为该地址接收,并且未花费的交易即为可用UTXO。分别记录下相应交易Hash和在该地址在Output中所处的位置(第一个位置为0,第二个为1,依次类推),即为:tx_hash 和 index。
b. 在 钱包管理 -> 导出私钥 中分别取得三人多签钱包的BTC私钥:privateKey_BTC_A、privateKey_BTC_B 和 privateKey_BTC_C。
c. 通过私钥分别获取对应的公钥:publicKey_BTC_A、publicKey_BTC_B 和 publicKey_BTC_C。可通过公私钥工具生成,如网址:https://iancoleman.io/bitcoin-key-compression/ (重要:将网站源码下载至本地运行,以免在网络上泄漏私钥!!!)
d. 生成 redeemScript: <OP_2> 21 <A pubkey> 21 <B pubkey> 21 <C pubkey> <OP_3> <OP_CHECKMULTISIG>。其中ABC的顺序为:公钥按字符顺序从低到高排序。例如:ABC公钥分别是:
publicKey_BTC_A = 03d2e38696c82a0570366f8c4eafc5be915fc1fb53c8b39cd8cbfaba97567388e0
publicKey_BTC_B = 032aa5165dbbbacf31a561486b448f620342f3ba5a5bfb60e0a9a3f4f7a9b21350
publicKey_BTC_C = 021988ac7af60cf4f8192cc1294a1b72856ece6c0e9583953bd87102884353f222
那么ABC的顺序应为: CBA(C 的 0219 小于 A 的 032a,B 的 032a 小于 A 的 03d2)。
其它操作符:
<OP_1> - <OP_16> 值为:0x51-0x60
<OP_CHECKMULTISIG> 值为:0xae
21: 十六进制数字,表示push33个字节,固定值
如果是其它模式的多签,相应替换<OP_2>和<OP_3>即可,如 3-4 多签,则为:<OP_3> 21 <A_PUB> 21 <B_PUB> 21 <C_PUB> 21 <D_PUB> <OP_4> <OP_CHECKMULTISIG>。因此,本列子中最终的redeemScript为:
5221021988ac7af60cf4f8192cc1294a1b72856ece6c0e9583953bd87102884353f22221032aa5165dbbbacf31a561486b448f620342f3ba5a5bfb60e0a9a3f4f7a9b213502103d2e38696c82a0570366f8c4eafc5be915fc1fb53c8b39cd8cbfaba97567388e053ae
e. 用 JS 或其它编程语言生成交易(以下JS代码基于bitcoinjs-lib):
const multisig = createPayment('p2sh-p2ms(2 of 3)'); const inputData1 = await getInputData([2e4], multisig.payment, false, 'p2sh'); { const { [tx_hash], [index], true, [redeemScript], } = inputData1; } const psbt = new bitcoin.Psbt({ network: mainnet }) .addInput(inputData1) .addOutput({ address: [receiveAddress] value: [1e4], }) .signInput(0, [privateKey_BTC_B]) .signInput(0, [privateKey_BTC_C]); assert.strictEqual(psbt.validateSignaturesOfInput(0), true); assert.strictEqual( psbt.validateSignaturesOfInput(0, multisig.keys[0].publicKey), true, ); assert.throws(() => { psbt.validateSignaturesOfInput(0, multisig.keys[1].publicKey); }, new RegExp('No signatures for this pubkey')); psbt.finalizeAllInputs(); const tx = psbt.extractTransaction(); //broadacast tx.toHex();
代码中的[xxx]替换成以上步骤得到的数据。
f: 将上面步骤中得到的交易Hex广播至BTC网络即可。
2. 恢复 ETH/ERC20 多签资产
a. ETH/ERC20多签资产存在您部署的多签合约中,该多签合约地址是已知和公开的,例如您部署的多签合约地址为:contractAddress = 0xc314bCbD4e8044b03C98381F715782a815B57fF3(注意:以下计算中需要用到 contractAddress 的地方,一律将其转化为小写)。
b. 在 钱包管理 -> 导出私钥 中分别取得任意两人的多签钱包ETH私钥(因为是 2-3 多签,只需 2人 签名即可),例如:privateKey_ETH_A和 privateKey_ETH_C。
c. 阅读合约源码:https://github.com/bitbill/ownbit-multisig-contracts 并获取spendNonce。spendNonce的初始值为0,每发送一次交易Nonce值加1(接收交易不计算在内),可以通过数已发送交易数获得spendNonce,也可以通过调用合约中的 getSpendNonce 方法获取:
function getSpendNonce() public constant returns (uint256) { return spendNonce; }
d. 获取私钥签名,以下代码显示了获取私钥签名的逻辑(代码基于ethereumjs):
//msContractAddress is the address of the MS //erc20Contract is the erc20 contract address, pass: 0x0000000000000000000000000000000000000000 for ETH function generateMultiSigV2(msContractAddress, erc20Contract, destination, value, spendNonce, privateKey) { privateKey = Buffer.from(privateKey, 'hex'); var msgHex = '0x' + web3.toHex(msContractAddress).substr(2) + web3.toHex(erc20Contract).substr(2) + web3.toHex(destination).substr(2) + web3.padLeft(web3.toHex(value).substr(2), 64) + web3.padLeft(web3.toHex(spendNonce).substr(2), 64); console.log('msgHex: ' + msgHex); var msgHash = ethereumjsUtil.hashPersonalMessage(ethereumjsUtil.sha3(msgHex)); console.log('msgHash: ' + msgHash.toString('hex')); var sig = ethereumjsUtil.ecsign(msgHash, privateKey); return [sig.v-27, '0x'+sig.r.toString('hex'), '0x'+sig.s.toString('hex')]; }
其中 value 为本次要转账的金额,根据币种的精度(decimal)显示为最小单位字符串,如转账 1ETH(ETH的精度为18),value为:1000000000000000000,再如转账1200USDT,value为:1200000000(USDT-ERC20精度为6)。
msContractAddress 在步骤a中获得(转化为小写),spendNonce 在步骤c中获得,destination为本次要转账的目标地址,privateKey 在b步骤中获得。调用 generateMultiSigV2 方法,获取任意两个人的签名(因为是 2-3 多签,至少需 2人 签名)。
e. 调用多签合约的 spend 或 spendERC20 方法。
如果要发送ETH,调用 spend 方法:function spend(address destination, uint256 value, uint8[] vs, bytes32[] rs, bytes32[] ss),传入参数:destination 和 value 等同于步骤 d 中的,v, r, s 是 步骤 d 中方法的返回结果,多人签名任意顺序排列成数组传入即可。
如果要发送ERC20,调用 spendERC20 方法:function spendERC20(address destination, address erc20contract, uint256 value, uint8[] vs, bytes32[] rs, bytes32[] ss),其它参数和 spend 方法一致,erc20contract 为 ERC20币种的合约地址,如 USDT-ERC20 为:0xdac17f958d2ee523a2206206994597c13d831ec7。
成功调用多签合约的 spend 或 spendERC20 方法即可花费资金。
注:其它Layer2网络(如:BSC、AVAX)多签恢复方式和ETH一致。
3. 恢复 TRX/TRC20 多签资产
a. 通过网址:https://api.trongrid.io/v1/accounts/TTG6b8LB52gQNoLjiD26QzyWAtLJooAuaR 查看多签账号的 Owner/Active Permission(将 TTG6b8LB52gQNoLjiD26QzyWAtLJooAuaR 替换为您的多签地址)。
b. 在 钱包管理 -> 导出私钥 中分别取得任意两人的多签钱包ETH私钥(因为是 2-3 多签,只需 2人 签名即可),例如:privateKey_ETH_A和 privateKey_ETH_C。
c. ETH私钥即为TRX私钥,用获取的私钥编程即可花费多签资产,参考波场官方文档:https://developers.tron.network/docs/multi-signature。
4. 恢复 SOL/SPL 多签资产
a. 通过多签助记词生成各参与方的 SOL 地址和签名所需私钥(生成路径:m/44'/501'/0'/0')。
b. 在 多签钱包 -> 多签页面 -> 菜单“多签详情”中获得 /multisig data/ 值(这是在调用合约过程中要用到的 multisig data account。
c. 当你同时拥有多签地址(multisigPda)、multisig data account,以及各参与方的 SOL 地址和签名私钥,就可以根据 Ownbit 多签源码进行调用以花费多签资产,多签源代码地址:https://github.com/bitbill/ownbit-multisig-contracts。
d. 一个完整的调用流程:先由任意参与方发送调用 create_transaction 指令,然后再由其他参与方调用 approve 指令。当达到足够人数时,再由任意一方发送调用 execute_transaction 指令。
5. 恢复其它币种的多签资产
其它币种(BCH/LTC/DASH…)等多签资产的恢复和BTC多签一致,它们也使用BTC私钥。