如何自助恢復多簽資產?
最後更新: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 是簽名訊息的一部分。
合約版本
| 版本 | 合約 | 簽名訊息中的 chain ID |
|---|---|---|
| 新版(Ownbit 4.67.1 及以上版本建立) | v10 標準、v11 意外防護、v21 企業 | 所有網路都包含,包括以太坊主網 |
| 舊版 | v6 標準、v8 意外防護、v20 企業 | 只在以太坊主網以外的網路包含 |
兩代合約的簽名不能混用。舊式合約 v4 / v5(Ownbit 4.37.1 及更早版本)不受工具支援,見第 7 節。
判斷多簽屬於哪一代:
getWalletConnectVersion() 方法(selector 0xb7869bac)。呼叫有回傳值的是新版合約;呼叫失敗(revert)或沒有回傳值的是舊版合約。
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 開發經驗。
m/44'/501'/0'/0' 的金鑰(在你信任的離線電腦上操作)。findProgramAddress([資料帳戶], 7uPTVnLrNM9bAdCGrfckz1ugJeRs5bEGcEgcwzPuFsAK)。create_transaction,其他 owner 呼叫 approve 直到達到門檻,然後任何人呼叫 execute_transaction。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 客服。