如何自助恢復多簽資產?

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私鑰。唯一不同的是生成redeemScript的順序。排序時不區分發起者,全部按照公鑰字符順序從低到高排序。