Safe Multisig Standard
Safe (formerly Gnosis Safe) multisig wallet integration for institutional-grade custody on Lux Network.
Abstract
This LP documents the integration of Safe (formerly Gnosis Safe) smart contract wallet protocol into the Lux Standard Library. Safe is the most battle-tested and widely-deployed multisig infrastructure in the EVM ecosystem, securing over $100 billion in digital assets. This specification covers the core Safe contracts, module system, guard contracts, signature schemes, and Lux-specific adaptations including FROST threshold signatures and cross-chain Safe via Warp messaging.
Motivation
Multi-signature wallets are essential infrastructure for secure asset management, particularly for:
- Institutional Custody: Enterprises, DAOs, and protocols require robust multisig for treasury management
- Battle-Tested Security: Safe has undergone multiple audits and secures significant TVL across chains
- Modular Architecture: Extensible via modules and guards without modifying core contracts
- Standards Compliance: Full support for EIP-712 typed data signing and EIP-1271 contract signatures
- Cross-Chain Requirements: Lux's multi-chain architecture demands Safe compatibility across C-Chain, chains, and via Warp messaging
Why Safe over Custom Solutions
| Criteria | Safe | Custom Implementation |
|---|---|---|
| Audits | 6+ comprehensive audits | New code = new risks |
| TVL Secured | $100B+ | Unproven |
| Ecosystem Tools | Safe{Wallet}, SDK, Transaction Service | Build from scratch |
| Community | Large developer community | Internal only |
| Module System | Battle-tested extensibility | Design required |
Specification
Core Contracts
The Safe protocol implementation is located at /Users/z/work/lux/standard/src/safe/.
Safe.sol (Main Contract)
The core multisig wallet contract supporting:
- Threshold Signatures: Configurable M-of-N owner scheme
- EIP-712 Typed Data: Structured transaction hashing for secure signing
- Nonce Management: Replay protection via sequential nonces
- Gas Refunds: Optional transaction fee refunds to relayers
- Delegate Calls: Execute arbitrary logic via delegatecall operations
contract Safe is
Singleton,
NativeCurrencyPaymentFallback,
ModuleManager,
GuardManager,
OwnerManager,
SignatureDecoder,
SecuredTokenTransfer,
ISignatureValidatorConstants,
FallbackManager,
StorageAccessible,
ISafe
{
string public constant VERSION = "1.4.1";
function execTransaction(
address to,
uint256 value,
bytes calldata data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address payable refundReceiver,
bytes memory signatures
) external payable returns (bool success);
}
SafeL2.sol
Layer 2 optimized variant that emits events for all operations, enabling indexing without archive nodes:
- Additional events for transaction execution
- Optimized for rollup and L2 deployments
- Compatible with standard Safe SDK tooling
SafeProxy.sol
Minimal proxy contract (EIP-1167 pattern) for gas-efficient Safe deployments:
contract SafeProxy {
address internal singleton;
constructor(address _singleton) {
singleton = _singleton;
}
fallback() external payable {
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), sload(0), 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
}
SafeProxyFactory.sol
Factory contract for deterministic Safe deployments:
- CREATE2 for predictable addresses
- Callback support for post-deployment initialization
- Batch deployment capabilities
Base Contracts
OwnerManager.sol
Manages the set of Safe owners:
- Add/remove owners with threshold adjustment
- Swap owners atomically
- Linked list storage pattern for gas efficiency
ModuleManager.sol
Module system for extending Safe functionality:
- Enable/disable modules via Safe transaction
- Module guard for transaction validation
- Paginated module enumeration
GuardManager.sol
Transaction guard system:
- Pre-transaction validation hooks
- Post-execution verification
- Interface:
ITransactionGuard
FallbackManager.sol
Fallback handler for extended functionality:
- Token callback handling (ERC-721, ERC-1155, ERC-777)
- Custom view function support
- Handler delegation pattern
Signature Schemes
Safe supports multiple signature types via the v parameter encoding:
| v Value | Signature Type | Description |
|---|---|---|
| 0 | Contract Signature | EIP-1271 isValidSignature check |
| 1 | Approved Hash | Pre-approved via approveHash() |
| 27, 28 | ECDSA | Standard Ethereum signatures |
| 31, 32 | eth_sign | Legacy \x19Ethereum Signed Message prefix |
EIP-712 Domain Separator
bytes32 private constant DOMAIN_SEPARATOR_TYPEHASH =
keccak256("EIP712Domain(uint256 chainId,address verifyingContract)");
bytes32 private constant SAFE_TX_TYPEHASH = keccak256(
"SafeTx(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,address gasToken,address refundReceiver,uint256 nonce)"
);
EIP-1271 Contract Signatures
Enables smart contract wallets as Safe owners:
interface ISignatureValidator {
function isValidSignature(bytes32 _hash, bytes memory _signature)
external view returns (bytes4 magicValue);
}
// Magic value: 0x1626ba7e
bytes4 constant EIP1271_MAGIC_VALUE = 0x1626ba7e;
Guard Contracts
Example guards included in the implementation:
BaseGuard.sol
Abstract base for custom guards:
abstract contract BaseTransactionGuard is ITransactionGuard {
function supportsInterface(bytes4 interfaceId) external view virtual returns (bool) {
return interfaceId == type(ITransactionGuard).interfaceId ||
interfaceId == type(IERC165).interfaceId;
}
}
DelegateCallTransactionGuard.sol
Prevents delegate calls to unauthorized targets:
- Whitelist of allowed delegate call destinations
- Blocks potential proxy upgrade attacks
OnlyOwnersGuard.sol
Restricts module transactions to owner-only:
- Prevents external module abuse
- Additional security layer for sensitive operations
ReentrancyTransactionGuard.sol
Reentrancy protection for Safe transactions:
- Mutex pattern for transaction execution
- Guards against callback-based attacks
DebugTransactionGuard.sol
Development and debugging guard:
- Emits detailed transaction events
- Gas usage tracking
- For testing environments only
Library Contracts
MultiSend.sol
Batch transaction execution:
function multiSend(bytes memory transactions) public payable {
assembly {
let length := mload(transactions)
let i := 0x20
for {} lt(i, length) {} {
let operation := shr(0xf8, mload(add(transactions, i)))
let to := shr(0x60, mload(add(transactions, add(i, 0x01))))
let value := mload(add(transactions, add(i, 0x15)))
let dataLength := mload(add(transactions, add(i, 0x35)))
let data := add(transactions, add(i, 0x55))
// Execute transaction...
}
}
}
MultiSendCallOnly.sol
Restricted variant allowing only CALL operations (no DELEGATECALL).
SignMessageLib.sol
EIP-191 message signing support for the Safe:
function signMessage(bytes calldata _data) external authorized {
bytes32 msgHash = getMessageHash(_data);
signedMessages[msgHash] = 1;
emit SignMsg(msgHash);
}
CreateCall.sol
Contract deployment from Safe:
- CREATE and CREATE2 support
- Deterministic deployment addresses
Interface Contracts
| Interface | Purpose |
|---|---|
ISafe.sol | Main Safe interface |
IModuleManager.sol | Module management |
IOwnerManager.sol | Owner management |
IGuardManager.sol | Guard management |
IFallbackManager.sol | Fallback handler |
ISignatureValidator.sol | EIP-1271 validation |
Handler Contracts
CompatibilityFallbackHandler.sol
Combined fallback handler supporting:
- ERC-721 token receiving (
onERC721Received) - ERC-1155 token receiving (
onERC1155Received,onERC1155BatchReceived) - ERC-777 token receiving (
tokensReceived) - EIP-1271 signature validation
- View function delegation
TokenCallbackHandler.sol
Minimal token callback support.
Dependencies
NPM Package
{
"name": "@safe-global/safe-smart-account",
"version": "1.4.1-build.0",
"license": "LGPL-3.0",
"devDependencies": {
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
"@openzeppelin/contracts": "^3.4.0",
"@safe-global/mock-contract": "^4.1.0",
"@safe-global/safe-singleton-factory": "^1.0.24",
"hardhat": "^2.22.6",
"solc": "0.7.6"
}
}
Solidity Version
Safe contracts target Solidity >=0.7.0 <0.9.0 for broad compatibility.
Mock Contracts for Testing
Located in /contracts/test/:
ERC20Token.sol- Test ERC-20 tokenERC1155Token.sol- Test ERC-1155 tokenToken.sol- Legacy token interfaceDelegateCaller.sol- Delegate call testingTestHandler.sol- Fallback handler testingTest4337ModuleAndHandler.sol- ERC-4337 compatibility testing
Lux-Specific Adaptations
Integration with FROST Threshold Signatures
Safe can be extended to support FROST threshold signatures via a custom module:
interface IFROSTModule {
function verifyFROSTSignature(
bytes32 messageHash,
bytes calldata signature,
uint8 threshold,
uint8 totalParties
) external view returns (bool);
function executeFROSTTransaction(
address to,
uint256 value,
bytes calldata data,
bytes calldata frostSignature
) external returns (bool);
}
Benefits:
- Single 64-byte signature regardless of threshold
- Off-chain signature aggregation
- Compatible with LP-321 FROST precompile
Reference: See LP-321 (FROST Threshold Signature Precompile) and LP-104 (FROST Threshold Signatures).
Cross-Chain Safe via Warp Messaging
Enable Safe governance across Lux chains:
interface ICrossChainSafeModule {
function sendCrossChainTransaction(
bytes32 destinationChainId,
address destinationSafe,
bytes calldata encodedTransaction
) external;
function executeCrossChainTransaction(
uint32 warpIndex,
bytes calldata encodedTransaction
) external returns (bool);
}
Use Cases:
- Unified treasury management across chains
- Cross-chain governance execution
- Multi-chain protocol administration
Reference: See LP-603 (Warp Messaging Protocol).
Post-Quantum Signature Module
The Post-Quantum Module enables quantum-resistant signatures for Lux Safe, supporting three complementary schemes:
| Algorithm | Type | Reusable | Gas Cost | Best For |
|---|---|---|---|---|
| ML-DSA-65 | Lattice | ✅ Yes | ~500K | Single-signer wallets |
| SLH-DSA-128s | Hash-based | ✅ Yes | ~800K | Long-term archives |
| Ringtail | Lattice Threshold | ✅ Yes | ~200K | Multisig/MPC |
| Lamport OTS | Hash-based | ❌ One-time | ~800K | Remote chains (LP-4105) |
Ringtail Threshold Integration
Ringtail is the recommended PQ algorithm for Safe multisig because it natively supports threshold signing:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IRingtailThreshold} from "@luxfi/precompiles/IRingtailThreshold.sol";
/**
* @title RingtailSafeModule
* @notice Quantum-safe threshold signatures for Lux Safe
* @dev Uses Ringtail precompile at 0x020000000000000000000000000000000000000B
*/
contract RingtailSafeModule {
address constant RINGTAIL_PRECOMPILE = 0x020000000000000000000000000000000000000B;
// Ringtail group key for this Safe (Ring-LWE public key)
bytes public ringtailGroupKey;
// Threshold configuration (t-of-n)
uint16 public threshold;
uint16 public totalSigners;
// Migration phase: CLASSICAL_ONLY → HYBRID → RINGTAIL_ONLY
enum MigrationPhase { CLASSICAL_ONLY, HYBRID, RINGTAIL_PREFERRED, RINGTAIL_ONLY }
MigrationPhase public phase;
/**
* @notice Register Ringtail group key for threshold signing
* @param groupKey The Ring-LWE group public key from DKG ceremony
* @param t Threshold (minimum signers required)
* @param n Total signers in the group
*/
function registerRingtailGroup(
bytes calldata groupKey,
uint16 t,
uint16 n
) external onlyOwner {
require(t > 0 && t <= n, "Invalid threshold");
require(n >= 2, "Need at least 2 signers");
ringtailGroupKey = groupKey;
threshold = t;
totalSigners = n;
}
/**
* @notice Verify Ringtail threshold signature via precompile
* @param messageHash Hash of the Safe transaction
* @param signature Aggregated Ringtail threshold signature
* @return valid True if t-of-n signers produced valid signature
*/
function verifyRingtailSignature(
bytes32 messageHash,
bytes calldata signature
) public view returns (bool valid) {
// Call Ringtail precompile for verification
(bool success, bytes memory result) = RINGTAIL_PRECOMPILE.staticcall(
abi.encode(
uint8(1), // VERIFY opcode
ringtailGroupKey, // Group public key
messageHash, // Message
signature, // Threshold signature
threshold // Required signers
)
);
return success && abi.decode(result, (bool));
}
/**
* @notice Execute Safe transaction with Ringtail signature
* @dev Wraps Safe.execTransaction with PQ verification
*/
function execTransactionWithRingtail(
address to,
uint256 value,
bytes calldata data,
bytes calldata ringtailSig,
bytes calldata classicalSigs // For hybrid mode
) external returns (bool) {
bytes32 txHash = safe.getTransactionHash(to, value, data, ...);
if (phase == MigrationPhase.RINGTAIL_ONLY) {
// Quantum-only: Ringtail signature required
require(verifyRingtailSignature(txHash, ringtailSig), "Invalid Ringtail sig");
} else if (phase == MigrationPhase.HYBRID) {
// Hybrid: Both classical AND Ringtail required
require(verifyRingtailSignature(txHash, ringtailSig), "Invalid Ringtail sig");
// Classical sigs verified by Safe.execTransaction
}
// CLASSICAL_ONLY or RINGTAIL_PREFERRED: classical sigs verified by Safe
return safe.execTransaction(to, value, data, ..., classicalSigs);
}
}
Migration Phases
Phase 0: CLASSICAL_ONLY - ECDSA multisig (current state)
Phase 1: HYBRID - ECDSA + Ringtail both required
Phase 2: RINGTAIL_PREFERRED - Ringtail primary, ECDSA fallback
Phase 3: RINGTAIL_ONLY - Full quantum resistance
T-Chain MPC Integration
For institutional custody, Safe can delegate to T-Chain MPC signer sets:
contract TChainCustodySafe is RingtailSafeModule {
// T-Chain signer set ID (registered on T-Chain)
bytes32 public tChainSignerSet;
/**
* @notice Link Safe to T-Chain MPC custody
* @param signerSetId The T-Chain threshold signer group
* @param attestation Proof of signer set registration
*/
function linkTChainCustody(
bytes32 signerSetId,
bytes calldata attestation
) external onlyOwner {
// Verify T-Chain attestation (Warp message from T-Chain)
require(verifyTChainAttestation(signerSetId, attestation));
tChainSignerSet = signerSetId;
// Import Ringtail group key from T-Chain
ringtailGroupKey = getTChainGroupKey(signerSetId);
}
// Transactions now require T-Chain MPC threshold signatures
// using Ringtail for quantum safety
}
Use Cases:
- DAO Treasuries: Quantum-safe governance with threshold control
- Bridge Vaults: External asset custody with T-Chain MPC
- Institutional Wallets: Enterprise custody with quantum resistance
- Protocol Upgrades: Time-locked upgrades with PQ attestations
Reference: See LP-7324 (Ringtail), LP-4105 (Lamport OTS), LP-7000 (T-Chain).
Formal Verification
Certora Verification
The implementation includes Certora formal verification:
Location: /Users/z/work/lux/standard/src/safe/certora/
Specifications (specs/):
- Ownership invariants
- Module security properties
- Guard correctness
- Signature validation
Harnesses (harnesses/):
SafeHarness.sol- Test harness for Safe contract
Configuration (conf/):
- Certora verification configurations
Audit History
Safe v1.4.0/1.4.1 audits:
- Ackee Blockchain (v1.4.0/1.4.1)
- G0 Group (v1.3.0, v1.2.0, v1.1.1)
- Runtime Verification (v1.0.0)
- Alexey Akhunov (v0.0.1)
OpenZeppelin 5.x Migration
Required Updates
Safe currently uses @openzeppelin/contracts@^3.4.0. Migration to OZ 5.x requires:
- SafeMath Removal: Native Solidity 0.8.x overflow checks
- Access Control Updates: New
OwnableandAccessControlpatterns - ERC Token Updates: Updated token interfaces and implementations
- ReentrancyGuard: Updated modifier patterns
Migration Path
// Before (OZ 3.x / Safe current)
import {SafeMath} from "./external/SafeMath.sol";
using SafeMath for uint256;
uint256 result = a.add(b);
// After (OZ 5.x / Solidity 0.8+)
// Native overflow checking
uint256 result = a + b;
Note: Safe's use of SafeMath is for compatibility with Solidity >=0.7.0. Migration to Solidity 0.8+ removes this dependency.
Rationale
Design Decisions
- Proxy Pattern: Minimal proxy reduces deployment costs (~60,000 gas vs ~2,000,000)
- Linked List Storage: Gas-efficient owner/module iteration
- Modular Architecture: Extend without modifying audited core
- Guard System: Pre/post transaction hooks without core changes
- EIP-712 Signing: Human-readable transaction signing in wallets
Security Model
- Threshold Enforcement: Cannot bypass M-of-N requirement
- Nonce Sequencing: Prevents replay and front-running
- Module Isolation: Modules cannot modify owner/threshold
- Guard Composition: Stack multiple guards for defense in depth
Backwards Compatibility
Gnosis Safe Compatibility
Full compatibility with existing Safe ecosystem:
- Safe{Wallet} UI
- Safe Transaction Service
- Safe SDK (ethers-lib, web3-lib, core-sdk)
- Safe Apps
LP-42 Alignment
Complements LP-42 (Multi-Signature Wallet Standard):
- Safe implements
ILuxMultiSiginterface patterns - Extended with Safe-specific module/guard system
- Additional signature schemes beyond basic threshold
Test Cases
Unit Tests
Located at /Users/z/work/lux/standard/src/safe/test/:
cd /Users/z/work/lux/standard/src/safe
npm run build
npm run test
Test Coverage
| Component | Coverage |
|---|---|
| Safe.sol | 95%+ |
| ModuleManager | 95%+ |
| GuardManager | 95%+ |
| OwnerManager | 95%+ |
| Proxy | 100% |
Example Test
describe("Safe", () => {
it("should execute transaction with threshold signatures", async () => {
const safe = await deploySafe([owner1, owner2, owner3], 2);
const tx = buildSafeTransaction({
to: recipient.address,
value: parseEther("1"),
nonce: await safe.nonce()
});
const sig1 = await signTypedData(owner1, safe.address, tx);
const sig2 = await signTypedData(owner2, safe.address, tx);
await safe.execTransaction(
tx.to, tx.value, tx.data, tx.operation,
tx.safeTxGas, tx.baseGas, tx.gasPrice,
tx.gasToken, tx.refundReceiver,
buildSignatureBytes([sig1, sig2])
);
expect(await provider.getBalance(recipient.address)).to.equal(parseEther("1"));
});
});
Reference Implementation
Repository: https://github.com/luxfi/standard
Local Path: /Users/z/work/lux/standard/
Contracts
| Contract | Description |
|---|---|
src/safe/contracts/Safe.sol | Safe core contract |
src/safe/contracts/base/ModuleManager.sol | Module management |
src/safe/contracts/base/OwnerManager.sol | Owner management |
Build and Test
cd /Users/z/work/lux/standard
# Build all contracts
forge build
# Run tests
forge test -vvv
# Gas report
forge test --gas-report
Security Considerations
Signature Security
- Replay Protection: Nonces prevent transaction replay
- Chain ID Binding: EIP-712 domain includes chain ID
- Signature Ordering: Signatures must be sorted by owner address
- Contract Signatures: EIP-1271 validation for smart contract owners
Module Security
- Module Risk: Modules have unlimited access; only add trusted, audited modules
- Module Guard: Additional validation layer for module transactions
- Disable Mechanism: Owners can disable compromised modules
Guard Security
- Guard Bypass: Cannot bypass guard checks without removing guard
- Guard Composition: Multiple guards can be stacked via delegation
- Gas Considerations: Guards consume gas; malicious guards could cause DoS
Upgrade Considerations
- Singleton Upgrade: Change
singletonstorage slot to upgrade - Migration Libraries:
SafeMigration.solfor controlled upgrades - State Preservation: Proxy pattern preserves all Safe state
License
LGPL-3.0-only
The LGPL-3.0 license permits:
- Linking Safe contracts with proprietary code
- Modifications must remain open source
- Derivative works of Safe contracts must use LGPL-3.0
References
Standards
- EIP-712: Typed structured data hashing and signing
- EIP-1271: Standard Signature Validation Method for Contracts
- EIP-1167: Minimal Proxy Contract
- EIP-4337: Account Abstraction
Related LPs
- LP-40 - Wallet standards
- LP-42 - Multisig standard
- LP-3320 - Lamport OTS for quantum-safe signing
- LP-3337 - Account abstraction
- LP-3500 - Post-quantum signatures
- LP-4105 - Lamport OTS implementation
- LP-4200 - Complete PQC ecosystem
- LP-7000 - T-Chain MPC custody
- LP-7324 - Ringtail threshold signatures
External Resources
Copyright
Copyright and related rights waived via CC0.