多重签名
Multisig 组件实现了一种多重签名机制,以增强智能合约交易的安全性与 治理。它确保没有单个签名者可以单方面 执行关键操作,而是需要多个注册的签名者批准并共同 执行交易。
该组件旨在保护诸如资金管理或协议治理等操作, 在这些操作中,集体决策至关重要。Multisig 组件是自我管理的, 这意味着对签名者或法定人数的更改必须通过多重签名流程本身批准。
主要特性
-
多重签名安全性:交易必须由多个签名者批准,从而确保 分布式治理。
-
法定人数强制执行:定义了交易执行所需的最小批准数量。
-
自我管理:对组件的所有修改(例如,添加或删除签名者) 必须通过多重签名流程。
-
事件日志记录:提供全面的事件日志记录,以实现透明性和可审计性。
签名者管理
Multisig 组件引入了签名者和法定人数的概念:
-
签名者:只有注册的签名者才能提交、确认、撤销或执行交易。Multisig 组件支持添加、删除或替换签名者。
-
法定人数:法定人数定义了批准交易所需的最小确认数。
为了防止未经授权的修改,只有合约本身可以添加、删除或替换签名者,或者更改法定人数。 这确保了所有修改都通过多重签名批准流程。 |
交易生命周期
交易的状态由 TransactionState
枚举表示,可以通过
调用带有交易标识符的 get_transaction_state
函数来检索。
多重签名交易的标识符是一个 felt252
值,计算为交易的
调用和 salt 的 Pedersen 哈希值。可以通过调用实现合约的
hash_transaction
方法(对于单次调用交易)或 hash_transaction_batch
方法(对于多次调用
交易)来计算它。第二次提交具有相同调用和相同 salt 值的交易
将失败,因为交易标识符必须是唯一的。要解决此问题,请使用不同的 salt 值
来生成唯一的标识符。
Multisig 组件中的交易遵循特定的生命周期:
NotFound
→ Pending
→ Confirmed
→ Executed
-
NotFound:交易不存在。
-
Pending:交易存在,但尚未达到所需的确认数。
-
Confirmed:交易已达到法定人数,但尚未执行。
-
Executed:交易已成功执行。
用法
将 Multisig 功能集成到合约中需要实现 MultisigComponent。 合约的构造函数应使用法定人数值和初始签名者列表来初始化组件。
这是一个简单的钱包合约的示例,它具有 Multisig 功能:
#[starknet::contract]
mod MultisigWallet {
use openzeppelin_governance::multisig::MultisigComponent;
use starknet::ContractAddress;
component!(path: MultisigComponent, storage: multisig, event: MultisigEvent);
#[abi(embed_v0)]
impl MultisigImpl = MultisigComponent::MultisigImpl<ContractState>;
impl MultisigInternalImpl = MultisigComponent::InternalImpl<ContractState>;
#[storage]
struct Storage {
#[substorage(v0)]
multisig: MultisigComponent::Storage,
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
MultisigEvent: MultisigComponent::Event,
}
#[constructor]
fn constructor(ref self: ContractState, quorum: u32, signers: Span<ContractAddress>) {
self.multisig.initializer(quorum, signers);
}
}
接口
这是实现 MultisigComponent 的合约的接口:
#[starknet::interface]
pub trait MultisigABI<TState> {
// Read functions
fn get_quorum(self: @TState) -> u32;
fn is_signer(self: @TState, signer: ContractAddress) -> bool;
fn get_signers(self: @TState) -> Span<ContractAddress>;
fn is_confirmed(self: @TState, id: TransactionID) -> bool;
fn is_confirmed_by(self: @TState, id: TransactionID, signer: ContractAddress) -> bool;
fn is_executed(self: @TState, id: TransactionID) -> bool;
fn get_submitted_block(self: @TState, id: TransactionID) -> u64;
fn get_transaction_state(self: @TState, id: TransactionID) -> TransactionState;
fn get_transaction_confirmations(self: @TState, id: TransactionID) -> u32;
fn hash_transaction(
self: @TState,
to: ContractAddress,
selector: felt252,
calldata: Span<felt252>,
salt: felt252,
) -> TransactionID;
fn hash_transaction_batch(self: @TState, calls: Span<Call>, salt: felt252) -> TransactionID;
// Write functions
fn add_signers(ref self: TState, new_quorum: u32, signers_to_add: Span<ContractAddress>);
fn remove_signers(ref self: TState, new_quorum: u32, signers_to_remove: Span<ContractAddress>);
fn replace_signer(
ref self: TState, signer_to_remove: ContractAddress, signer_to_add: ContractAddress,
);
fn change_quorum(ref self: TState, new_quorum: u32);
fn submit_transaction(
ref self: TState,
to: ContractAddress,
selector: felt252,
calldata: Span<felt252>,
salt: felt252,
) -> TransactionID;
fn submit_transaction_batch(
ref self: TState, calls: Span<Call>, salt: felt252,
) -> TransactionID;
fn confirm_transaction(ref self: TState, id: TransactionID);
fn revoke_confirmation(ref self: TState, id: TransactionID);
fn execute_transaction(
ref self: TState,
to: ContractAddress,
selector: felt252,
calldata: Span<felt252>,
salt: felt252,
);
fn execute_transaction_batch(ref self: TState, calls: Span<Call>, salt: felt252);
}