如何自助恢复多签资产?

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/bitbill-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. 恢复其它币种的多签资产

其它币种(BCH/LTC/DASH…)等多签资产的恢复和BTC多签一致,它们也使用BTC私钥。