Direct 多重签名交易
Direct 多重签名交易使用 Protocol Buffers 编码格式,比 Amino 格式更高效,消耗更少的 Gas。本文档介绍如何使用 Direct 格式构建和签名多重签名交易。
基本概念
Direct 格式优势
- 更高效: 二进制编码,体积更小
- 更低 Gas: 减少交易费用
- 类型安全: 强类型定义
- 性能更好: 序列化/反序列化更快
环境配置
设置多个助记词
# .env 文件
AARON="first signer mnemonic phrase"
PHCC="second signer mnemonic phrase"
PENG="third signer mnemonic phrase"
初始化环境
require('dotenv').config();
const { AARON, PHCC, PENG } = process.env;
const rpcEndpoint = 'https://rpc.testnet.cosmos.network:443';
const client = await StargateClient.connect(rpcEndpoint);
多重签名账户创建
生成公钥和地址
async function getMnemonicPubKeyAndAddress(mnemonic, prefix) {
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, {
hdPaths: [makeCosmoshubPath(0)],
});
const [account] = await wallet.getAccounts();
const secp256k1PubKey = encodeSecp256k1Pubkey(account.pubkey);
const address = pubkeyToAddress(secp256k1PubKey, prefix);
return {
wallet,
pubkey: secp256k1PubKey,
address
};
}
// 并行生成所有密钥
const keys = await Promise.all([
AARON, PHCC, PENG
].map((mnemonic) => getMnemonicPubKeyAndAddress(mnemonic, prefix)));
const pubKeys = keys.map((item) => item.pubkey);
创建多重签名公钥
const threshold = 2;
const prefix = 'cosmos';
var multisigPubkey = createMultisigThresholdPubkey(pubKeys, threshold, true);
const multisigAddress = pubkeyToAddress(multisigPubkey, prefix);
console.log('Multisig address:', multisigAddress);
交易构建
查询账户信息
// 查询多重签名账户
const accountOnChain = await client.getAccount(multisigAddress);
assert(accountOnChain, 'Account does not exist on chain');
// 查询余额
const balance = await client.getBalance(multisigAddress, 'uphoton');
console.log('Balance:', balance);
构建发送消息
const msgSend = {
fromAddress: multisigAddress,
toAddress: receipt,
amount: coins(2000, 'uphoton'),
};
const msg = {
typeUrl: '/cosmos.bank.v1beta1.MsgSend',
value: msgSend,
};
const gasLimit = 200000;
const fee = {
amount: coins(2000, 'uphoton'),
gas: gasLimit.toString(),
};
const chainId = await client.getChainId();
const memo = 'happy';
签名指令创建
创建签名指令
const signingInstruction = {
accountNumber: accountOnChain.accountNumber,
sequence: accountOnChain.sequence,
chainId,
msgs: [msg],
fee,
memo,
};
离线签名
执行签名
async function signInstruction(mnemonic, instruction, prefix, rpcEndpoint) {
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, {
hdPaths: [makeCosmoshubPath(0)],
});
const [account] = await wallet.getAccounts();
const pubkey = encodeSecp256k1Pubkey(account.pubkey);
const address = pubkeyToAddress(pubkey, prefix);
const signerData = {
accountNumber: instruction.accountNumber,
sequence: instruction.sequence,
chainId: instruction.chainId,
};
// 使用客户端签名
const client = await SigningStargateClient.connectWithSigner(rpcEndpoint, wallet);
const { bodyBytes, signatures } = await client.sign(
address,
instruction.msgs,
instruction.fee,
instruction.memo,
signerData
);
return [pubkey, signatures[0], bodyBytes];
}
并行签名收集
收集所有签名
const [
[pubkey0, signature0, bodyBytes],
[pubkey1, signature1],
[pubkey2, signature2]
] = await Promise.all(
[AARON, PHCC, PENG].map(async (mnemonic) =>
signInstruction(mnemonic, signingInstruction, prefix, rpcEndpoint)
)
);
console.log('Public key 0:', pubkey0);
console.log('Public key 1:', pubkey1);
console.log('Public key 2:', pubkey2);
交易组装
创建 Direct 多重签名交易
const address0 = pubkeyToAddress(pubkey0, prefix);
const address1 = pubkeyToAddress(pubkey1, prefix);
const address2 = pubkeyToAddress(pubkey2, prefix);
// 重新创建多重签名公钥
var multisigPubkey = createMultisigThresholdPubkey(
[pubkey0, pubkey1, pubkey2],
threshold,
true
);
// 验证地址一致性
assert.equal(
multisigAccountAddress,
pubkeyToAddress(multisigPubkey, prefix),
'should be equal'
);
// 创建 Direct 多重签名交易
const signedTx = makeDirectMultisignedTx(
multisigPubkey,
signingInstruction.sequence,
signingInstruction.fee,
bodyBytes,
new Map([
[address0, signature0],
[address1, signature1],
// [address2, signature2], // 可选签名
])
);
Direct 多重签名交易构建
实现 makeDirectMultisignedTx 函数
function makeDirectMultisignedTx(multisigPubkey, sequence, fee, bodyBytes, signatures) {
const addresses = Array.from(signatures.keys());
const prefix = Bech32.decode(addresses[0]).prefix;
// 创建签名者位数组
const signers = Array(multisigPubkey.value.pubkeys.length).fill(false);
const signaturesList = new Array();
for (let i = 0; i < multisigPubkey.value.pubkeys.length; i++) {
const signerAddress = pubkeyToAddress(multisigPubkey.value.pubkeys[i], prefix);
const signature = signatures.get(signerAddress);
if (signature) {
signers[i] = true;
signaturesList.push(signature);
}
}
// 创建签名者信息
const signerInfo = {
publicKey: encodePubkey(multisigPubkey),
modeInfo: {
multi: {
bitarray: makeCompactBitArray(signers),
// modeInfos: signaturesList.map((_) => ({
// single: { mode: SignMode.SIGN_MODE_DIRECT }
// })),
},
},
sequence: Long.fromNumber(sequence),
};
// 创建认证信息
const authInfo = AuthInfo.fromPartial({
signerInfos: [signerInfo],
fee: {
amount: [...fee.amount],
gasLimit: Long.fromString(fee.gas),
},
});
const authInfoBytes = AuthInfo.encode(authInfo).finish();
// 构建最终交易
const signedTx = TxRaw.fromPartial({
bodyBytes: bodyBytes,
authInfoBytes: authInfoBytes,
signatures: [
multisig.MultiSignature.encode(
multisig.MultiSignature.fromPartial({
signatures: signaturesList
})
).finish()
],
});
return signedTx;
}
交易广播
编码和广播
const tx = TxRaw.encode(signedTx).finish();
console.log('Transaction bytes:', toHex(tx));
const result = await client.broadcastTx(tx);
console.log('Broadcast result:', result);
assertIsDeliverTxSuccess(result);
const { transactionHash } = result;
console.log('Transaction URL:', `https://api.testnet.cosmos.network/cosmos/tx/v1beta1/txs/${transactionHash}`);
依赖库
必要的导入
const { makeCompactBitArray, makeMultisignedTx } = require('@cosmjs/stargate/build/multisignature');
const { DirectSecp256k1HdWallet, Registry, makeAuthInfoBytes, encodePubkey, makeSignDoc } = require('@cosmjs/proto-signing');
const { TxRaw, AuthInfo, Fee } = require("cosmjs-types/cosmos/tx/v1beta1/tx");
const { SignMode } = require('cosmjs-types/cosmos/tx/signing/v1beta1/signing');
const multisig = require("cosmjs-types/cosmos/crypto/multisig/v1beta1/multisig");
const Long = require("long");
完整示例
查看 examples/cosmos/multisig/direct.js
文件获取完整的 Direct 多重签名交易示例代码。
注意事项
- Direct 格式比 Amino 格式更高效
- 确保所有签名者使用相同的签名指令
- 验证多重签名地址的一致性
- 合理设置阈值和签名者数量
- 在生产环境中使用硬件钱包
- 定期验证多重签名配置
- 备份所有参与者的助记词
- 注意 Direct 格式的兼容性要求