如何自助恢復多簽資產?

最後更新:2026-10-28

Ownbit 多簽資產保存在鏈上:BTC 等幣種的多簽地址、EVM 多簽合約、設有多簽權限的 TRON 帳戶,或 Solana 多簽程式。轉出這些資產需要足夠多參與方的簽名,不需要 Ownbit 伺服器。伺服器無法使用時,每個參與方用 Ownbit App 裡的多簽恢復工具簽名(可以離線使用),再在電腦上組裝交易,透過任意公開節點廣播。

本文以 2-3 多簽為例,參與方為 A、B、C,由 A 和 B 簽名。建議先用小額試一遍,廣播前核對每一個地址。不要把助記詞或錢包密碼告訴任何人,包括自稱 Ownbit 客服的人。

開始之前

App 版本

以下功能需要 Ownbit 5.2.0 或更新版本,在本文中標為 Ownbit 5.2.0+,請先更新 App:

需要準備什麼

開啟多簽恢復工具

首頁選單 → 區塊鏈工具 → 多簽恢復工具 → 選擇幣種(BTC、ETH 或 TRX;Ownbit 5.2.0+ 另有 SOL)→ 選擇錢包(你的多簽錢包)。填寫各欄位,點 多簽簽名,輸入錢包密碼。工具顯示簽名結果,點一下即可複製。工具不需要連網。

在 Ownbit 5.2.0+ 中,每個工具頂部的 「選擇多簽 ›」 會列出本機保存且符合條件的多簽,選取後自動填寫欄位。本機沒有保存的多簽(例如在 Ownbit 伺服器無法使用時剛恢復的錢包)需要手動填寫。

每個簽名人填寫的內容必須完全一致。部分截圖來自舊版本,5.2.0 的工具畫面可能略有不同。工具裡的欄位名稱在所有語言下都顯示英文。

哪些鏈、用哪個工具、需要哪個版本

多簽 恢復方式 Ownbit 版本 需要 Ownbit 嗎?
BTC(bc1… 和 3… 地址)、LTC、DOGE、BCH、DGB 工具 BTC → 組裝 → 廣播(第 1 節) 5.2.0+ 不需要。m 個參與方簽名。
EVM:Ethereum、BNB Smart Chain、Base、HyperEVM、Polygon、Arbitrum One、Optimism 工具 ETH → 呼叫合約的 spend()(第 2 節) 5.2.0+ 不需要。m 個 owner 簽名(企業多簽:Signer 權重之和達到門檻)。意外防護:連續 416 天不活躍後,鏈上門檻自動降低。
TRON(TRX、TRC-20) 工具 TRX → 加入簽名 → 廣播(第 3 節) 5.2.0+ 不需要。m 個參與方簽名(企業多簽:Signer 權重之和達到門檻)。
Solana(SOL、SPL 代幣) 工具 SOL → 發起 / 批准 / 執行(第 4 節) 5.2.0+ 不需要。m 個參與方核准。
BTC、TRON、Solana 的意外防護多簽 同上;Ownbit 官方持有一把額外的金鑰(第 5 節) 同上 只有在原有金鑰剩下不足 m 把時才需要。是否協助簽名由 Ownbit 自行決定。
XRP、Stellar 沒有工具;使用 XRP Ledger / Stellar 的標準工具(第 6 節) 不需要 不需要。m 個簽名人簽名。
DASH、ZEC、Taproot、EVM 合約 v4 / v5 本文不涵蓋(第 7 節) — —

「需要 Ownbit 嗎?」指 Ownbit 伺服器和作為協助簽名方的 Ownbit 官方。參與方自己的金鑰始終是必需的。

1. BTC 及其他 UTXO 幣種(LTC、DOGE、BCH、DGB)

Ownbit 的 BTC 類多簽是標準的 m-of-n 腳本 OP_m <排序後的 pubkeys> OP_n OP_CHECKMULTISIG,沒有時間鎖。以 bc1 開頭的 BTC 地址是 P2WSH,以 3 開頭的 BTC 地址是 P2SH;LTC、DOGE、BCH、DGB 多簽都是 P2SH。在這些鏈上,每個參與方的金鑰都在其多簽錢包的 m/44'/0'/0'/0/0,所以同一個參與方在 BTC、BCH、LTC、DOGE、DGB 上的 pubkey 相同。

a. 第一步,查詢 UTXO。在區塊瀏覽器查詢多簽地址,例如 BTC 用 mempool.space,LTC、DOGE、BCH、DGB 用 blockchair.com。該地址收到且尚未花費的每一個輸出都是一個 UTXO。分別記下交易 Hash、輸出序號(第一個輸出為 0,第二個為 1,依此類推)和金額(單位 satoshi)。

b. 第二步,建構 Input Output JSON。inputs 是這些 UTXO;outputs 是收款地址,以及沒有全部轉出時的找零地址(通常就是多簽地址本身)。金額單位為 satoshi,inputs 總額減去 outputs 總額就是礦工費。input 的欄位取決於地址類型:

地址類型 每個 input 的欄位
BTC bc1…(P2WSH) "segwit": "D"、"txHash"、"index"、"satoshis"
BTC 3…(P2SH) "txHash"、"index"(不需要 satoshis,也不需要 segwit)
LTC、DOGE、BCH、DGB "txid"、"vout"、"satoshis"

每個 output 都是 "address" 和 "amount"(satoshi)。JSON 必須合法:不能有註解,也不能有多餘的逗號。

bc1 多簽範例(第一個 output 是收款,第二個是找零;礦工費為 469,800 − 468,144 = 1,656 satoshi):

        {
          "inputs": [{
            "segwit": "D",
            "txHash": "757ef70941ea9170cc918097630a7aa7e4c644e3f8d73a15ff1e98d321bf5b62",
            "index": 1,
            "satoshis": 13400
          }, {
            "segwit": "D",
            "txHash": "637dfeda367f051d34228badfef09726f0893653fe8baebbe6e150ea68e756f3",
            "index": 2,
            "satoshis": 456400
          }],
          "outputs": [{
            "address": "bc1q2dzu8fcrr66yq9n8q5u6qhwleslqystahdw923",
            "amount": 10000
          }, {
            "address": "bc1q8dl3vmrqmcs4xpyn884upnedlj2t6uewnfdnp034wqfjghpm5ghqupl9v9",
            "amount": 458144
          }]
        }
    

3 開頭多簽範例:

        {
          "inputs": [{
            "txHash": "<UTXO 的交易 Hash>",
            "index": 0
          }],
          "outputs": [{
            "address": "<收款地址>",
            "amount": 50000
          }]
        }
    

LTC 範例(DOGE、BCH、DGB 欄位相同):

        {
          "inputs": [{
            "txid": "b830d41437f2f1cb3c5303c6626cc9762979bf88bd9bcfbb5a356150e86ef0ad",
            "vout": 1,
            "satoshis": 94960
          }],
          "outputs": [{
            "address": "LeDXT3evSeAQyCwa2b4ohjS9YxcV9dx7AC",
            "amount": 10000
          }, {
            "address": "MHosP5QAgWrqLGrEvYDRQVDxDohSoUsZA4",
            "amount": 79920
          }]
        }
    

c. 第三步,準備 Pubkeys。多簽詳情頁會顯示每個參與方的 pubkey(Ownbit 5.2.0+ 的「多簽自助恢復」頁也有,「選擇多簽 ›」會自動填入):

填入這個多簽的全部 n 個 pubkey(有意外防護的多簽也要包括 Ownbit 官方的 pubkey),以冒號(:)分隔。順序不拘,工具會自動排序。每個 pubkey 是以 02 或 03 開頭、66 個十六進位字元的壓縮公開金鑰;工具接受 2 到 9 個。範例:

02810260c48b2b4e78dd70ae59b7a8a8858cfdba204ea28896ff7a4de20e0c2514:032f7e05649fbf0278076ab1e936998eddc81417131ed55b07d42fa2aea43c25f4:<C 的 pubkey>
    

pubkey 也可以由參與方多簽錢包的助記詞推導:

        助記詞(mnemonic,如有 BIP39 密碼短語一併使用)
            ↓
        Seed(BIP39)
            ↓
        主金鑰(BIP32)
            ↓
        路徑 m/44'/0'/0'/0
            ↓
        Index 0 的子公開金鑰(m/44'/0'/0'/0/0 的 pubkey,壓縮格式)
    

d. 第四步,簽名。A 和 B 分別在自己的手機上開啟 多簽恢復工具 → BTC,填寫:

欄位 填寫內容
選擇網路 Bitcoin (P2WSH, address bc1)、Bitcoin (P2SH, address 3)、Bitcoin Cash、Litecoin、Dogecoin 或 DigiByte
Input Output JSON 第二步的 JSON
Pubkeys (Separated by colon) 第三步的全部 pubkey
Min Signers 簽名門檻 m,例如 2-3 多簽填 2

點 多簽簽名。如果 Pubkeys 裡沒有你自己的 pubkey,工具會拒絕簽名。結果中每個 input 一個簽名,依 input 順序排列:sig1:<hex> 對應 input 0,sig2:<hex> 對應 input 1,依此類推。

e. 第五步,組裝交易。兩條規則:

以下是 bc1(P2WSH)多簽的範例,使用 bitcoinjs-lib v6。程式碼會印出由 pubkeys 和 Min Signers 算出的多簽地址,它必須和你的多簽地址一致。

        const bitcoin = require('bitcoinjs-lib'); // v6

        const M = 2; // Min Signers

        // 多簽的全部 n 個 pubkey(與 "Pubkeys" 中填寫的相同),依工具的方式排序
        const PUBKEYS = [
          '02810260c48b2b4e78dd70ae59b7a8a8858cfdba204ea28896ff7a4de20e0c2514', // A
          '032f7e05649fbf0278076ab1e936998eddc81417131ed55b07d42fa2aea43c25f4', // B
          // 範例資料來自一個 2-2 多簽;2-3 多簽請在這裡加上 C 的 pubkey
        ].map(k => k.toLowerCase()).sort();

        // 工具給出的簽名,依簽名人的 pubkey 歸類:[sig1(input 0), sig2(input 1), ...]
        const SIGS = {
          '02810260c48b2b4e78dd70ae59b7a8a8858cfdba204ea28896ff7a4de20e0c2514': [
            '3044022041fa6e561e5906e03148b540d47f9bcf77d9b3505fcc15b0b0c51843cb359052022069ffd710386f5f2c968ce5e39715b8a0f3a9667f7486ab6cec08a2afd2bc874901',
            '3045022100ee8dea3f7a8c1333c4a0e8a2e59206cb495bd7ecbbfec7122dea32cb90a7f9210220117a280d11d45c71b90047b5a430e2ec0ab18fe24064c911ac61926fee0eb53601'
          ],
          '032f7e05649fbf0278076ab1e936998eddc81417131ed55b07d42fa2aea43c25f4': [
            '3044022062d213c22e36d9929de57deb1e5b6649757d28779a63f55d635b0124a9f45e7702205a6c8818a9db1580eadfb39286a399e2508a4fb807963aba8597983183b59dcc01',
            '3045022100f1fe8473f8125313e01615ef0db9b6b76dc5328ad2ace90905ce77948a2dbc3f02203a5fd49dfe3a0317cd73cace688e62de001b496894863560a580f1693db166d201'
          ]
        };

        // 與 Input Output JSON 相同的 inputs 和 outputs,順序也相同
        const INPUTS = [
          { txHash: '757ef70941ea9170cc918097630a7aa7e4c644e3f8d73a15ff1e98d321bf5b62', index: 1 },
          { txHash: '637dfeda367f051d34228badfef09726f0893653fe8baebbe6e150ea68e756f3', index: 2 }
        ];
        const OUTPUTS = [
          { address: 'bc1q2dzu8fcrr66yq9n8q5u6qhwleslqystahdw923', amount: 10000 },
          { address: 'bc1q8dl3vmrqmcs4xpyn884upnedlj2t6uewnfdnp034wqfjghpm5ghqupl9v9', amount: 458144 }
        ];

        const redeem = bitcoin.payments.p2ms({ m: M, pubkeys: PUBKEYS.map(k => Buffer.from(k, 'hex')) });
        const p2wsh = bitcoin.payments.p2wsh({ redeem });
        console.log('MultiSig address:', p2wsh.address); // 必須是你的 bc1 多簽地址

        const tx = new bitcoin.Transaction();
        tx.version = 2;  // 工具簽的是 version 2 的交易(bitcoinjs-lib 預設是 1)
        tx.locktime = 0;
        INPUTS.forEach(i => tx.addInput(Buffer.from(i.txHash, 'hex').reverse(), i.index, 0xffffffff));
        OUTPUTS.forEach(o => tx.addOutput(bitcoin.address.toOutputScript(o.address, bitcoin.networks.bitcoin), o.amount));

        // 依排序後的 pubkey 順序取前 M 個簽名人
        const signers = PUBKEYS.filter(k => SIGS[k]).slice(0, M);
        if (signers.length < M) throw new Error('Not enough signatures');

        // 每個 input 的 witness:空元素、依 pubkey 順序的 M 個簽名、腳本
        INPUTS.forEach((_, n) => {
          tx.setWitness(n, [Buffer.alloc(0), ...signers.map(k => Buffer.from(SIGS[k][n], 'hex')), redeem.output]);
        });

        console.log(tx.toHex()); // finalHex
    

3 開頭(P2SH)的多簽:用 bitcoin.payments.p2sh({ redeem }) 計算地址(必須和你的 3 開頭地址一致),並把簽名放進 input script,而不是 witness:

        INPUTS.forEach((_, n) => {
          tx.setInputScript(n, bitcoin.script.compile([
            bitcoin.opcodes.OP_0,
            ...signers.map(k => Buffer.from(SIGS[k][n], 'hex')),
            redeem.output
          ]));
        });
    

工具給出的 BTC 簽名結尾已帶有 sighash 類型位元組(01)。LTC、DOGE、BCH、DGB 由工具以 bitcore 系列函式庫簽名(bitcore-lib-ltc 及其他幣種的對應函式庫),簽名是不帶 sighash 類型位元組的 DER。請用同一個函式庫組裝這些交易:依 JSON 的順序用 tx.from({ txId, outputIndex, satoshis, script }, pubkeys, m) 加入每個 input、用 tx.to(address, amount) 加入每個 output,再用 tx.applySignature(...) 加入 m 個簽名(sighash 類型 SIGHASH_ALL;BCH 為 SIGHASH_ALL | SIGHASH_FORKID)。

f. 第六步,廣播 finalHex。BTC 範例:

        curl -X POST https://mempool.space/api/tx \
          -H "Content-Type: text/plain" \
          -d '<finalHex>'
    

LTC、DOGE、BCH、DGB 請使用該幣種的節點或區塊瀏覽器的廣播頁面。

2. EVM 多簽(原生幣、ERC-20 和 NFT)

Ownbit 的 EVM 多簽是一個智慧合約。有人帶著足夠多的 owner 簽名呼叫它的 spend() 方法時,資產才會轉出。在多簽恢復工具(幣種 ETH)中,每個參與方產生一個簽名(r、s、v);接著你編碼 spend() 呼叫,從任意一個有 gas 的地址送給合約。每個參與方的簽名金鑰在其多簽錢包的 m/44'/60'/0'/0/0。

工具裡的 選擇網路 提供 Ethereum(chain ID 1)、BNB Smart Chain(56)、Base(8453)、HyperEVM(999)、Polygon(137)、Arbitrum One(42161)和 Optimism(10)。目前可以在 Ethereum、BNB Smart Chain、Base、Arbitrum、HyperEVM 和 Polygon 上新建多簽;Optimism 留在清單裡供既有的多簽使用。請選擇多簽所在的網路:chain ID 是簽名訊息的一部分。

以太坊主網注意。如果你的多簽在以太坊主網上,並且是用 Ownbit 4.67.1 或更新版本建立的(合約 v10、v11 或 v21),請用 Ownbit 5.2.0 或更新版本簽名。5.2.0 之前版本的恢復工具在以太坊主網上簽名時不包含 chain ID,而這些合約在所有鏈上都要求包含,所以合約會拒絕舊版本為它們產生的簽名。其他 EVM 網路上的多簽不受影響,以太坊主網上的舊版多簽(v6 / v8 / v20)也不受影響。如果暫時無法更新 App,可以手動簽名(見下文「不用 App 簽名」)。如何判斷合約版本見下方。

合約版本

版本 合約 簽名訊息中的 chain ID
新版(Ownbit 4.67.1 及以上版本建立) v10 標準、v11 意外防護、v21 企業 所有網路都包含,包括以太坊主網
舊版 v6 標準、v8 意外防護、v20 企業 只在以太坊主網以外的網路包含

兩代合約的簽名不能混用。舊式合約 v4 / v5(Ownbit 4.37.1 及更早版本)不受工具支援,見第 7 節。

判斷多簽屬於哪一代:

        curl -X POST <該網路的 RPC 位址> \
          -H "Content-Type: application/json" \
          -d '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"<多簽地址>","data":"0xb7869bac"},"latest"]}'
    

a. 第一步,取得 Spend Nonce。它是合約用有效簽名執行過的 spend() 呼叫次數:失敗回滾(revert)的 spend() 不計入;簽名有效但內部轉帳失敗的 spend() 也計入。它不是區塊瀏覽器上「從」多簽地址送出的交易數。用合約的 getSpendNonce() 讀取(selector 0xc6a2a9f1,v6 / v8 / v10 / v11 / v21 都有;新版合約另有 spendNonce(),0x843b1a09):

        curl -X POST <該網路的 RPC 位址> \
          -H "Content-Type: application/json" \
          -d '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"0xD0BF699Fb29810bB58b5F6EDb482227b529Dfc5a","data":"0xc6a2a9f1"},"latest"]}'

        {"jsonrpc":"2.0","id":1,"result":"0x0000000000000000000000000000000000000000000000000000000000000016"}
    

結果是十六進位:0x16 = 22,即 Spend Nonce 為 22。企業多簽另有一個用於修改 owner / signer 的 config nonce;花費資產時用 spend nonce。

b. 第二步,簽名。A 開啟 多簽恢復工具 → ETH,依要轉出的資產類型填寫:

欄位 原生幣(ETH、BNB、POL…) ERC-20 代幣 NFT(ERC-721 / ERC-1155)
選擇網路 多簽所在的網路
MS Address 多簽合約地址
Destination Address 收款地址
Erc20 Contract Address (0x0 = ETH) 0x0(代表該網路的原生幣) 代幣合約地址,例如以太坊上的 USDT:0xdac17f958d2ee523a2206206994597c13d831ec7 contractAddress_tokenId,例如 0xfcad2…009f7b_0x0000…00b9e
Decimals or ErcType 18 代幣精度(0–18;USDT 為 6) 1 = ERC-721,2 = ERC-1155
Amount 例如 1.2 例如 2000 NFT 數量,例如 1
Spend Nonce 第一步取得的值

5.2.0 之前的版本中第三個欄位名稱為 "Erc20 Contract Address"。在 Ownbit 5.2.0+ 中,「選擇多簽 ›」會自動填寫網路、MS Address、合約和精度。

點 多簽簽名。簽名格式類似 r:0x…,s:0x…,v:0(v 為 0 或 1)。

c. 第三步,B 在自己的手機上重複第二步。B 填寫的內容必須與 A 完全一致,包括 Spend Nonce。這樣就有了兩組簽名(r、s、v)。

企業多簽(v20 / v21):只有 Signer 能花費。收集 Signer 的簽名,直到他們的權重之和達到門檻。Owner(最多 7 個)只負責管理 owner 和 signer;不是 Signer 的 Owner 無法產生有效的花費簽名。v21 合約最多有 9 個 Signer,可在鏈上用 getSigners()(0x94cf795e)、getSignerWeights()(0x9f27a602)和 getSignRequired()(0x3fd3407e)查詢。白名單、限額和安全管理員由 Ownbit 伺服器和 App 執行,合約不檢查,所以自助恢復時不受這些限制:請仔細核對收款地址。

d. 第四步,建構 spend() 的 calldata。合約方法如下:

function spend(address destination, uint256 value, uint8[] vs, bytes32[] rs, bytes32[] ss, bytes data)
// selector 0x7c99df4a
    

各參數必須與工具簽名時完全一致:

資產 destination value data
原生幣 收款地址 金額(單位 wei) 0x(空值,不是 0x0)
ERC-20 代幣合約 0 transfer(收款地址, 以代幣最小單位計的金額),以 0xa9059cbb 開頭
ERC-721 NFT 合約 0 safeTransferFrom(多簽地址, 收款地址, tokenId),以 0x42842e0e 開頭
ERC-1155 NFT 合約 0 safeTransferFrom(多簽地址, 收款地址, tokenId, 數量, 0x),以 0xf242432a 開頭

vs、rs、ss 是陣列,每個簽名人一項;同一個簽名人的 r、s、v 要放在相同位置。v 依工具顯示的原樣填寫(0 或 1),合約會自己加 27。

web3.js 範例(在以太坊上發送 2000 USDT):

        const Web3 = require('web3');   // web3.js 1.x;4.x 請用:const { Web3 } = require('web3');
        const web3 = new Web3();

        const spendAbi = {
          name: 'spend', type: 'function',
          inputs: [
            { name: 'destination', type: 'address' },
            { name: 'value', type: 'uint256' },
            { name: 'vs', type: 'uint8[]' },
            { name: 'rs', type: 'bytes32[]' },
            { name: 'ss', type: 'bytes32[]' },
            { name: 'data', type: 'bytes' }
          ]
        };

        // ERC-20:data = transfer(收款地址, 2000 USDT = 2000 * 10^6)
        const tokenData = web3.eth.abi.encodeFunctionCall(
          { name: 'transfer', type: 'function',
            inputs: [{ name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }] },
          ['0xe36b7026178f17a52f85b47280cc069840c8fa84', '2000000000']
        );
        // = 0xa9059cbb000000000000000000000000e36b7026178f17a52f85b47280cc069840c8fa840000000000000000000000000000000000000000000000000000000077359400

        const calldata = web3.eth.abi.encodeFunctionCall(spendAbi, [
          '0xdac17f958d2ee523a2206206994597c13d831ec7',   // destination:USDT 合約
          '0',                                            // value:代幣填 0
          ['1', '1'],                                     // vs(A、B),依工具顯示的原樣
          ['0xaca5025bd6d2fcd3308aa3589ea7aa419ef67914275ce7be80558584232554f9',
           '0x9028a9e0fee249f874d747c0c082b1b9cdde12505cea47a2e8cc19b567c3fea8'],   // rs
          ['0x3a12af0ee1354b709b5312db6976150fc7ebc74f6f94404005593290c6322e26',
           '0x310e7534a5a0a7e71566c14462c10cf2022c966a390d0edd144895c0c032d599'],   // ss
          tokenData                                       // data
        ]);
        console.log(calldata);

        // 發送原生幣:destination = 收款地址,value = 金額(wei),data = '0x',例如
        // web3.eth.abi.encodeFunctionCall(spendAbi, ['<收款地址>', web3.utils.toWei('1.2', 'ether'), vs, rs, ss, '0x'])
    

e. 第五步,送出 calldata。從任意一個持有該網路原生幣(用於 gas)的地址,向多簽合約地址送出一筆交易:金額 0,data(hex data)為 calldata。送出方不必是參與方。先估算 gas(eth_estimateGas);估算失敗表示交易會回滾,請檢查網路、Spend Nonce、合約版本、簽名人,以及每個值是否與簽名時一致。

意外防護(v8 / v11)

合約本身執行 416 天不活躍規則(MAX_INACTIVE_TIME)。owner 在最後一次有效花費簽名或呼叫 active()(或多簽建立)後的 416 天內算作活躍。活躍 owner 不少於 m 個時,使用正常門檻;少於 m 個時,合約只要求活躍 owner 的人數(至少 1 個)。所以一個參與方連續 416 天不活躍後,其餘活躍的 owner 不需要 Ownbit 就能花費。簽名方式與標準多簽完全相同。

簽名花費會讓每個簽名人重新變為活躍,合約隨後會再檢查一次門檻。如果這讓門檻超過你提供的簽名數,spend() 會回滾並提示 "please call active() before calling spend"。這時,原本不活躍的簽名人先從自己的 owner 地址(m/44'/60'/0'/0/0,需要 gas)呼叫多簽合約的 active()(selector 0x02fb0c5e),再用符合新門檻的簽名數送出 spend()。

不用 App 簽名(進階)

工具簽名的訊息是 keccak256(多簽地址 ‖ destination ‖ value (uint256) ‖ data ‖ spendNonce (uint256) ‖ chainId (uint256)),依 abi.encodePacked 串接,再加上以太坊簽名訊息前綴("\x19Ethereum Signed Message:\n32")簽名。只有以太坊主網上的舊版合約(v6 / v8 / v20)不帶結尾的 chainId。在離線電腦上用 ethers.js v6:

        const { ethers } = require('ethers'); // v6

        const hash = ethers.solidityPackedKeccak256(
          ['address', 'address', 'uint256', 'bytes', 'uint256', 'uint256'],
          [msAddress, destination, value, data, spendNonce, chainId]
          // 以太坊主網上的舊版合約:去掉最後一個 'uint256' 和 chainId
        );
        const wallet = ethers.HDNodeWallet.fromPhrase(mnemonic, passphrase || '', "m/44'/60'/0'/0/0");
        const sig = ethers.Signature.from(await wallet.signMessage(ethers.getBytes(hash)));
        console.log({ r: sig.r, s: sig.s, v: sig.v - 27 }); // v 為 0 / 1,與工具相同
    

在電腦上輸入助記詞會讓助記詞外洩的風險升高。只在你信任的離線電腦上操作,並且只在無法使用 App 時這樣做。

3. TRON 多簽(TRX 和 TRC-20)

TRON 多簽是一個設有多簽權限的一般 TRON 帳戶,沒有合約。每個參與方用其多簽錢包 以太坊路徑 m/44'/60'/0'/0/0 上的金鑰簽名(參與方的 TRON 地址就是這把金鑰對應的 T 地址),而不是 m/44'/195'/…。

多簽類型 簽名人所在權限 建構交易時
普通、意外防護、權重多簽 owner 權限(Permission_id 0) 不設定 permissionId(預設為 0)
企業多簽 active 權限(Permission_id 2),只有 Signer permissionId: 2

Permission_id 不對,簽名就無法上鏈。企業多簽只需要 Signer 簽名,權重之和達到門檻即可。

a. 第一步,用 TronWeb 建構未簽名交易,送出方為多簽地址。TRC-20 範例:

        const { TronWeb } = require('tronweb');   // TronWeb 6;5.x 請用:const TronWeb = require('tronweb');
        const tronWeb = new TronWeb({ fullHost: 'https://api.trongrid.io' });

        (async () => {
          const from = 'T...';   // 多簽地址
          const to = 'T...';     // 收款地址
          const contract = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t';   // USDT TRC-20
          const amount = 2000000000;                               // 2000 USDT(6 位精度)

          const options = { feeLimit: 15000000, callValue: 0 };
          // 僅企業多簽:options.permissionId = 2;

          const res = await tronWeb.transactionBuilder.triggerSmartContract(
            contract, 'transfer(address,uint256)', options,
            [{ type: 'address', value: to }, { type: 'uint256', value: amount }],
            from
          );
          // 給簽名人留出時間:把有效期限延長 1 小時(TRON 最多允許 24 小時)
          const tx = await tronWeb.transactionBuilder.extendExpiration(res.transaction, 3600);
          console.log(JSON.stringify(tx));
        })();
    

TRX 範例:

        // tronWeb 同上例
        (async () => {
          const from = 'T...';   // 多簽地址
          const to = 'T...';
          const amount = 2000000;   // 2 TRX,單位 sun

          let tx = await tronWeb.transactionBuilder.sendTrx(to, amount, from);
          // 企業多簽:await tronWeb.transactionBuilder.sendTrx(to, amount, from, { permissionId: 2 });
          tx = await tronWeb.transactionBuilder.extendExpiration(tx, 3600);
          console.log(JSON.stringify(tx));
        })();
    

直接使用印出的 JSON:頂層包含 txID、raw_data 和 raw_data_hex 的未簽名交易(TRC-20 是 res.transaction,不是整個回傳結果)。請在任何人簽名之前延長有效期限:之後再改會改變 txID,已有的簽名隨之失效。

b. 第二步,A 和 B 分別在自己的手機上開啟 多簽恢復工具 → TRX,把同一個 JSON 貼到 Tron TX JSON,點 多簽簽名。簽名結果是一串十六進位字元。

在 Ownbit 5.2.0+ 中,用「選擇多簽 ›」選取多簽後,輸入框會顯示該多簽需要的 Permission_id,例如 "Tron TX JSON (Permission_id = 2)"。5.2.0 還會檢查 txID 是否等於 sha256(raw_data_hex)、交易是否只有一個 contract、Permission_id 是否符合多簽類型,並拒絕為企業多簽中不是 Signer 的人簽名。更早的版本不做這些檢查,請自行核對 Permission_id。

c. 第三步,把所有簽名放進交易 JSON 的 signature 陣列:

        {
          "txID": "4c214a020101e1775dd04036f2040caee7d78d5c2bbabf7fdc0318ddc2d9899f",
          "raw_data": { "contract": [ ... ], "ref_block_bytes": "...", "expiration": ..., ... },
          "raw_data_hex": "...",
          "visible": false,
          "signature": [
            "ad7373b902f185f9a33c1c13b248f4bf5be003dff9d907fa4683aa54ad73f2c3186b57b32f03e477d909bb286d38993896e8a186c5c39c7e5d9f900eb3cabc271c",
            "cf310bb59e38e7edd00d1e6ab6ac216b3ab80ba6ce2db140456744cce6b7ed610d599330669ca98ddf05e4211d02c54ff0e033cd28f82680ba8d50b9baf03b831b"
          ]
        }
    

d. 第四步,在過期前廣播,例如:

        curl -X POST https://api.trongrid.io/wallet/broadcasttransaction \
          -H "Content-Type: application/json" \
          -d '<已簽名的交易 JSON>'
    

也可以用 TronWeb:await tronWeb.trx.sendRawTransaction(signedTx)。

4. Solana 多簽(SOL 和 SPL 代幣)

Ownbit 的 Solana 多簽程式是 7uPTVnLrNM9bAdCGrfckz1ugJeRs5bEGcEgcwzPuFsAK(原始碼:github.com/bitbill/ownbit-multisig-contracts)。每個多簽有一個多簽資料帳戶(multisig data account);資產存放在由它推導出的多簽地址(PDA,findProgramAddress([資料帳戶], 程式 ID))。花費需要三個指令:

每個參與方用自己多簽錢包 m/44'/501'/0'/0' 上的 SOL 金鑰簽名並支付手續費。每個提案帳戶會占用約 0.0065 SOL 的租金。

使用 SOL 工具 Ownbit 5.2.0+

a. 準備。在「多簽自助恢復」頁或多簽詳情頁("data:" 後面的值)找到多簽資料帳戶;「選擇多簽 ›」會自動填入。每個簽名人自己的 SOL 地址需要有少量 SOL 支付手續費;5.2.0 的工具會顯示由哪個地址簽名並付費。

b. 每次簽名前取得最新的 blockhash。它大約 1 分鐘後過期,所以要在這段時間內簽名並廣播;過期就重新取得、重新簽名。

        curl https://api.mainnet-beta.solana.com -X POST -H "Content-Type: application/json" \
          -d '{"jsonrpc":"2.0","id":1,"method":"getLatestBlockhash"}'
    

c. 開啟 多簽恢復工具 → SOL(「多簽恢復工具 · SOL」),在頂部選擇步驟:發起、批准 或 執行。各步驟的欄位:

欄位 發起 批准 執行
Multisig data account 填寫 填寫 填寫
Transaction account — 提案帳戶 提案帳戶
Recipient (wallet address) 填寫 — 與發起時相同
Amount (e.g. 1.5) 填寫 — —
Token mint (empty = SOL) SOL 留空,代幣填 mint — 與發起時相同
Token decimals 填了 mint 時 — —
「Token-2022 代幣」開關 Token-2022 代幣開啟 — 與發起時相同
「為收款地址建立代幣帳戶」開關 — — 填了 mint 時
Recent blockhash 填寫 填寫 填寫
Priority fee (microLamports, optional) 選填 選填 選填
Rent exemption (lamports) 預設 6458880 — —

d. 廣播每一筆已簽名交易(發起和批准的交易也要廣播,鏈上才有提案和核准紀錄):

        curl https://api.mainnet-beta.solana.com -X POST -H "Content-Type: application/json" \
          -d '{"jsonrpc":"2.0","id":1,"method":"sendTransaction","params":["<base64 交易>",{"encoding":"base64"}]}'
    

不用 SOL 工具(手動)

5.2.0 之前的版本沒有 SOL 工具,但你仍然可以自行呼叫程式;這需要 Solana 開發經驗。

5. 意外防護與 Ownbit 協助簽名(BTC、TRON、Solana)

在 BTC、TRON 和 Solana 上,意外防護多簽會把 Ownbit 官方(錢包 ID "ownbit")加為一個額外的參與方,例如 3-3 多簽變成 3-4 多簽。這些鏈上沒有鏈上的不活躍規則(與 EVM 不同,見第 2 節)。

6. XRP 和 Stellar 多簽

多簽恢復工具不支援 XRP 和 Stellar。這兩種多簽使用帳本內建的簽名人清單,所以不需要 Ownbit,用標準工具即可。

XRP Ledger Stellar
設定 SignerListSet:每個簽名人權重 1,SignerQuorum = m;主金鑰已停用 每個簽名人權重 1,主金鑰權重 0,各級門檻 = m
簽名人金鑰 每個參與方多簽錢包的 m/44'/144'/0'/0/0 每個參與方多簽錢包的 m/44'/148'/0'
多簽帳戶 推導自 m/44'/144'/0'/0/i;它自己的金鑰已停用,不需要 推導自 m/44'/148'/i';它自己的金鑰權重為 0,不需要

XRP:從多簽帳戶建構一筆 Payment(例如用 xrpl.js 的 client.autofill(tx, m),手續費會依 m 個簽名人計算),由 m 個簽名人分別用 wallet.sign(tx, true) 簽名,用 xrpl.multisign([...]) 合併後提交。

Stellar:用 Stellar SDK 或 Stellar Laboratory 從多簽帳戶建構一筆付款,加上 m 個簽名人的簽名,提交到 Horizon。

在你信任的離線電腦上,依上面的路徑從每個參與方的多簽錢包助記詞推導簽名人金鑰。

7. 本文不涵蓋的情況

以上情況請聯絡 Ownbit 客服。