如何自助恢复多签资产?
Ownbit多签资产的安全性和可用性不依赖于Onwbit服务器。借助于 Ownbit 多签恢复工具(多签恢复工具可以在没有网络的环境下运行,因此不论 Ownbit 服务器是否永久宕机,您均可以自助恢复多签资产),您总是可以相对轻松地、随时随地自助恢复多签资产。本文以 2-3 多签为例,参与方分别为:A、B、C 三人。
1. 恢复 BTC 多签资产
a. 第一步,通过浏览器查询出多签地址上的可用UTXO,每个币种都有相应的浏览器,例如 BTC 可用:https://www.blockchain.com/btc/address/3CkvfWhYd6behUYnrA5rMQJoyr6oLGM6Ed 进行查询。显示为该地址接收,并且未花费的交易即为可用UTXO。分别记录下相应交易Hash、该地址在Output中所处的位置(第一个位置为0,第二个为1,依次类推)、对应金额,即分别为:txHash、index 和 satoshi。
b. 第二步,构建 BTC input output JSON, 示例如下:
{
"inputs": [{
"segwit": "D",
"txHash": "757ef70941ea9170cc918097630a7aa7e4c644e3f8d73a15ff1e98d321bf5b62",
"index": 1,
"satoshis": 13400
}, {
"segwit": "D",
"txHash": "637dfeda367f051d34228badfef09726f0893653fe8baebbe6e150ea68e756f3",
"index": 2,
"satoshis": 456400
}],
"outputs": [{
"amount": 10000,
"address": "bc1q2dzu8fcrr66yq9n8q5u6qhwleslqystahdw923" //receiver address
}, {
"address": "bc1q8dl3vmrqmcs4xpyn884upnedlj2t6uewnfdnp034wqfjghpm5ghqupl9v9", //change address
"amount": 458144
}]
}
bc1 开头的多签地址需要有 segwit = D 的参数,3 开头的多签地址无需该参数。input/output 的 sum amount 差额即为本笔交易的矿工费。
c. 第三步,准备 Pubkeys。Pubkeys 是所有多签参与方的 pubkey 的数组。可以在多签详情里查看每个参与方的 pubkey,如图:

将所有参与方 pubkey 通过 : 分隔相连即得到 Pubkeys,示例如下:
02810260c48b2b4e78dd70ae59b7a8a8858cfdba204ea28896ff7a4de20e0c2514:032f7e05649fbf0278076ab1e936998eddc81417131ed55b07d42fa2aea43c25f4
pubkey 也可以通过助记词推导而得,逻辑如下:
助记词(mnemonic)
↓
Seed(BIP39)
↓
Master Key(BIP32)
↓
BIP44 路径:m/44'/0'/0'/0
↓
扩展公钥(xpub)
↓
Index 0 的子地址公钥(m/44'/0'/0'/0/0 的 pubkey)
d. 第四步,分别在 A、B 用户的手机上打开:恢复多签工具(位于:首页菜单 - 区块链工具 - 多签恢复工具),多签恢复工具可以在没有网络的环境下运行,因此不论 Ownbit 服务器是否永久宕机,您均可以自助恢复多签资产。如图:
选择币种:BTC,进入 BTC 多签工具页面,如下图:
参数详解如下:
| 参数名称 | 参数内容详解 |
|---|---|
| Network | 选择网络,BTC bc1 地址还是 3 地址,LTC、DOGE 等其他网络 |
| Input Output JSON | 第一步中生成的 Input/Output JSON |
| Pubkeys | 第三步中生成的 Pubkeys,以 : 分隔 |
| Min Singers | 最少签名人数,例如 2-3 多签,最少签名人数为:2 |
BTC 多签的参数的说明。
e. 第五步,填入相应内容,并点击按钮签名,获得签名结果。

提示:根据 input 的个数会生成相应的签名个数,例如 2 个input会生成 2 个签名结果。
f. 第六步,这时我们可以组装交易生成 finalHex。
const bitcoin = require('bitcoinjs-lib');
const ecc = require('tiny-secp256k1');
bitcoin.initEccLib(ecc);
// 签名(已从用户A+B获得)
const sigs = {
input0: [
'3044022062d213c22e36d9929de57deb1e5b6649757d28779a63f55d635b0124a9f45e7702205a6c8818a9db1580eadfb39286a399e2508a4fb807963aba8597983183b59dcc01',
'3044022041fa6e561e5906e03148b540d47f9bcf77d9b3505fcc15b0b0c51843cb359052022069ffd710386f5f2c968ce5e39715b8a0f3a9667f7486ab6cec08a2afd2bc874901'
],
input1: [
'3045022100f1fe8473f8125313e01615ef0db9b6b76dc5328ad2ace90905ce77948a2dbc3f02203a5fd49dfe3a0317cd73cace688e62de001b496894863560a580f1693db166d201',
'3045022100ee8dea3f7a8c1333c4a0e8a2e59206cb495bd7ecbbfec7122dea32cb90a7f9210220117a280d11d45c71b90047b5a430e2ec0ab18fe24064c911ac61926fee0eb53601'
]
};
// 构建 2-of-2 多签脚本
const pubkey1 = Buffer.from('02810260c48b2b4e78dd70ae59b7a8a8858cfdba204ea28896ff7a4de20e0c2514', 'hex');
const pubkey2 = Buffer.from('032f7e05649fbf0278076ab1e936998eddc81417131ed55b07d42fa2aea43c25f4', 'hex');
const multisig = bitcoin.payments.p2ms({ m: 2, pubkeys: [pubkey1, pubkey2] });
const p2wsh = bitcoin.payments.p2wsh({ redeem: multisig });
const tx = new bitcoin.Transaction();
// 添加 input 0
tx.addInput(
Buffer.from('757ef70941ea9170cc918097630a7aa7e4c644e3f8d73a15ff1e98d321bf5b62', 'hex').reverse(),
1,
0xffffffff
);
// 添加 input 1
tx.addInput(
Buffer.from('637dfeda367f051d34228badfef09726f0893653fe8baebbe6e150ea68e756f3', 'hex').reverse(),
2,
0xffffffff
);
// 添加 output 0
tx.addOutput(
bitcoin.address.toOutputScript('bc1q2dzu8fcrr66yq9n8q5u6qhwleslqystahdw923', bitcoin.networks.bitcoin),
10000
);
// 添加 output 1
tx.addOutput(
bitcoin.address.toOutputScript('bc1q8dl3vmrqmcs4xpyn884upnedlj2t6uewnfdnp034wqfjghpm5ghqupl9v9', bitcoin.networks.bitcoin),
458144
);
// 为每个 input 设置 witness
[sigs.input0, sigs.input1].forEach((sigList, index) => {
tx.setWitness(index, [
Buffer.alloc(0),
Buffer.from(sigList[0], 'hex'),
Buffer.from(sigList[1], 'hex'),
p2wsh.redeem.output
]);
});
// 输出交易 hex
const finalHex = tx.toHex();
console.log('Final Transaction Hex:\n', finalHex);
g. 第七步,将 finalHex 广播至网络,即可花费多签资产,简单示例如下:
curl -X POST https://mempool.space/api/tx \
-H "Content-Type: text/plain" \
-d ''
提示:LTC、DOGE 等其他 UTXO 币种和 BTC 类似,但是组建 input/output JSON 参数略有不同,下面是一个 LTC 多签的 input/output JSON 例子:
{
"inputs": [{
"vout": 1,
"satoshis": 94960,
"txid": "b830d41437f2f1cb3c5303c6626cc9762979bf88bd9bcfbb5a356150e86ef0ad"
}],
"outputs": [{
"address": "LeDXT3evSeAQyCwa2b4ohjS9YxcV9dx7AC",
"amount": 10000
}, {
"address": "MHosP5QAgWrqLGrEvYDRQVDxDohSoUsZA4",
"amount": 79920
}]
}
LTC 等其他 UTXO 币种使用 txid、vout 参数(而不是 BTC 的 txHash, index 参数)。
2. 恢复 ETH/ERC20 多签资产
a. 第一步,用户A打开“多签恢复工具”,选择币种:ETH,进入 ETH 多签工具页面,如下图:
根据你要恢复的资产类型填写每个空格,详解如下:
| 参数名称 | 参数内容详解 |
|---|---|
| Network | 根据您的多签选择相应网络:Ethereum、BNB Smart Chain、Base 等等 |
| MS Address | 要恢复资产的多签地址 |
| Destination | 您要将资产发送到哪个地址 |
| Erc20 Contract Address | 发送 ETH 时填写:0x0 |
| Decimals or ErcType | 发送 ETH 时填写:18 |
| Amount | 填写要发送的金额,如填写:1.2(表示发送 1.2 ETH) |
| Spend Nonce | 填写当前多签地址的 spend nonce,下面有如何获得 Spend Nonce 的详细解释 |
发送 ETH 的参数的说明。
b. 第二步,获取 Spend Nonce:Spend Nonce 是您的多签地址发送交易的次数(失败的交易也算在内),您有两个方法获得 Spend Nonce:1. 去浏览器中数从您的多签地址中发出的交易。2. 通过调用 RPC 获得,如下:
% curl -X POST https://mainnet.infura.io/v3/your_api_key \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0",
"method":"eth_call",
"params":[
{
"to": "0xD0BF699Fb29810bB58b5F6EDb482227b529Dfc5a",
"data": "0xc6a2a9f1"
},
"latest"
],
"id":1
}'
{"jsonrpc":"2.0","id":1,"result":"0x0000000000000000000000000000000000000000000000000000000000000016"}
获得的 result 是十六进制的,需转化成十进制,如例子中 0016 转化成:22。
发送 ERC20 时按以下填写各参数:
| 参数名称 | 参数内容详解 |
|---|---|
| Network | 根据您的多签选择相应网络:Ethereum、BNB Smart Chain、Base 等等 |
| MS Address | 要恢复资产的多签地址 |
| Destination | 您要将资产发送到哪个地址 |
| Erc20 Contract Address | 相应的 ERC20 地址,如发送 USDT,对应于:0xdac17f95...3d831ec7 |
| Decimals or ErcType | 每个 ERC20 token 有自己对应的 decimals,例如 USDT 是 6,可以在 etherscan.io 中查看到每个 token 的 decimals |
| Amount | 填写要发送的金额,如填写:1.2(表示发送 1.2 USDT) |
| Spend Nonce | 见前面解释 |
发送 ERC20 的参数的说明。
发送 NFT(ERC721/ERC1155)时按以下填写各参数:
| 参数名称 | 参数内容详解 |
|---|---|
| Network | 根据您的多签选择相应网络:Ethereum、BNB Smart Chain、Base 等等 |
| MS Address | 要恢复资产的多签地址 |
| Destination | 您要将资产发送到哪个地址 |
| Erc20 Contract Address | 填写 contractAddress_tokenId(中间用 _ 分割),如:0xfcad2...009f7b_0x0000...00b9e |
| Decimals or ErcType | ERC721 填写 1,ERC1155 填写 2 |
| Amount | 填写要发送的金额,如填写:1 |
| Spend Nonce | 见前面解释 |
发送 ERC721/ERC1155 的参数的说明。
c. 第三步,填写完成后点击签名按钮获得签名(r、s、v),如下图:

d. 第四步,在用户B的手机上重复上述过程,得到B的签名结果(r、s、v),注意,用户B填写的内容应该和A填写的一摸一样。这时,您已经获得两组(r、s、v)签名结果,现在可以开始调用多签合约花费资产了。
下面开始调用 spend 方法进行多签花费,相应的方法结构如下:
//destination is the receiver address for sending ether, or an ERC20 contract address for sending ERC20.
//value is the wei to be trasnferred to the destination, when sending ERC20 value should be 0.
//data for transfer ether: 0x
//data for transfer erc20 example: 0xa9059cbb000000000000000000000000ac6342a7efb995d63cc91db49f6023e95873d25000000000000000000000000000000000000000000000000000000000000003e8
//data for transfer erc721 example: 0x42842e0e00000000000000000000000097b65ad59c8c96f2dd786751e6279a1a6d34a4810000000000000000000000006cb33e7179860d24635c66850f1f6a5d4f8eee6d0000000000000000000000000000000000000000000000000000000000042134
//data can contain any data to be executed.
function spend(address destination, uint256 value, uint8[] memory vs, bytes32[] memory rs, bytes32[] memory ss, bytes calldata data) external {
require(destination != address(this), "Not allow sending to yourself");
require(_validSignature(destination, value, vs, rs, ss, data), "invalid signatures");
spendNonce = spendNonce + 1;
//transfer tokens from this contract to the destination address
(bool sent,) = destination.call{value: value}(data);
if (sent) {
emit Spent(destination, value);
}
}
e. 第五步,根据已经有的两组(r、s、v)你可以构建出 spend 方法所需的 calldata,下面是示例代码:
const Web3 = require("web3");
const web3 = new Web3();
const abi = [{
"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" }
]
}];
const contract = new web3.eth.Contract(abi);
const calldata = contract.methods.spend(
"0xdac17f958d2ee523a2206206994597c13d831ec7", //destination
"0", //ether-value
[1, 1], //vs
[
"0xaca5025bd6d2fcd3308aa3589ea7aa419ef67914275ce7be80558584232554f9",
"0x9028a9e0fee249f874d747c0c082b1b9cdde12505cea47a2e8cc19b567c3fea8" //rs
],
[
"0x3a12af0ee1354b709b5312db6976150fc7ebc74f6f94404005593290c6322e26",
"0x310e7534a5a0a7e71566c14462c10cf2022c966a390d0edd144895c0c032d599" //ss
],
"0xa9059cbb000000000000000000000000e36b7026178f17a52f85b47280cc069840c8fa840000000000000000000000000000000000000000000000000000000077359400" //inner-calldata
).encodeABI();
console.log(calldata);
发送 ERC20 spend 的 calldata 构建
发送 ETH spend 的 calldata 构建类似,只将 ether-value 改成相应的 value,并将 inner-calldata 改成 0x0 即可。
f. 第六步,生成了 calldata 您可以发送任何 ETH 交易,将 calldata 设为该交易的 hexdata,即可广播交易,使多签花费生效。
注:其它 EVM 网络(如:BSC、BASE)多签恢复方式和ETH一致。
3. 恢复 TRX/TRC20 多签资产
a. 第一步,生成要发送交易的 txJson。下面是代码示例:
const TronWeb = require('tronweb');
const tronWeb = new TronWeb({
fullHost: 'https://api.trongrid.io'
});
(async () => {
const from = 'TXeDpnE...';
const to = 'TQwbMLM...';
const contract = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t'; // USDT TRC20
const amount = 2000000000; // 2000 USDT (6 decimals)
const feeLimit = 15000000;
const functionSelector = 'transfer(address,uint256)';
const parameter = [
{ type: 'address', value: to },
{ type: 'uint256', value: amount }
];
const options = {
feeLimit,
callValue: 0,
shouldPollResponse: false
};
const tx = await tronWeb.transactionBuilder.triggerSmartContract(
contract,
functionSelector,
options,
parameter,
from
);
const txJson = JSON.stringify(tx.transaction);
console.log(txJson);
})();
发送 TRC20 交易的 txJson 生成
const TronWeb = require('tronweb');
const tronWeb = new TronWeb({
fullHost: 'https://api.trongrid.io'
});
(async () => {
const from = 'TXeDpnE...';
const to = 'TQwbMLM...';
const amount = 2_000_000; // 2 TRX
const tx = await tronWeb.transactionBuilder.sendTrx(to, amount, from);
const txJson = JSON.stringify(tx);
console.log(txJson);
})();
发送 TRX 交易的 txJson 生成
提示:默认生成的 txJson 的 expiration 时间较短,可适当扩展 expiration 时间,以防止广播交易时已经失效。
b. 第二步,分别在 A、B 用户的手机上打开:恢复多签工具 - TRX,输入 txJson,并点击按钮签名,得到各自的签名结果。如图:

3. 第三步,现在将得到的签名加入到 txJson 的 signature 字段中,即生成完整的 txJson,可用于广播至网络。示例如下:
{
"txID": "4c214a020101e1775dd04036f2040caee7d78d5c2bbabf7fdc0318ddc2d9899f",
"raw_data": {
"contract": [...],
"ref_block_bytes": "...",
...
},
"raw_data_hex": "...",
"visible": false,
"signature": [
"ad7373b902f185f9a33c1c13b248f4bf5be003dff9d907fa4683aa54ad73f2c3186b57b32f03e477d909bb286d38993896e8a186c5c39c7e5d9f900eb3cabc271c",
"cf310bb59e38e7edd00d1e6ab6ac216b3ab80ba6ce2db140456744cce6b7ed610d599330669ca98ddf05e4211d02c54ff0e033cd28f82680ba8d50b9baf03b831b"
]
}
4. 第四步,将完整的 txJson 的广播至 TRON 网络,多签花费即可生效。
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 指令。