Files
safe-magnetar/contracts/SafeMagnetarSigner.sol

73 lines
3.4 KiB
Solidity

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity =0.8.29;
import {Magnetar} from "./Magnetar.sol";
import {IERC1271, ILegacyERC1271} from "./interfaces/IERC1271.sol";
/// @title Safe Magnetar Signer
/// @notice Safe smart-account owner that verifies Magnetar MPC-threshold
/// SLH-DSA (FIPS 205) signatures through the `magnetarVerify` precompile, making
/// a single Magnetar group public key a first-class Safe owner via EIP-1271.
///
/// @dev ONE key, ONE owner. The owner commits to a single `(mode, pubKeyHash)`
/// pair at construction; both are `immutable`, so the contract holds NO mutable
/// storage and `isValidSignature` is a pure `view` (side-stepping the Safe's
/// state-changing-EIP-1271-validator guard entirely).
///
/// BINDING. We commit only `keccak256(pubkey)` (one 32-byte immutable); the full
/// key is supplied at verify time and re-hashed against the commitment, closing
/// the "attacker supplies an arbitrary public key" attack via second-preimage
/// resistance.
///
/// REPLAY. The Safe passes the EIP-712 Safe-transaction hash, bound to this
/// Safe / chain / nonce. The precompile additionally binds the
/// `"lux-evm-precompile-magnetar-v1"` context, preventing cross-precompile
/// replay against the single-party SLH-DSA slot.
///
/// SIGNATURE PAYLOAD: `abi.encode(bytes pubkey, bytes magnetarSignature)`.
contract SafeMagnetarSigner is IERC1271, ILegacyERC1271 {
/// @notice The Magnetar parameter-set byte this owner verifies under.
uint8 private immutable _MODE;
/// @notice keccak256 of the committed Magnetar group public key.
bytes32 private immutable _PUBKEY_HASH;
/// @notice The mode is not a recognised Magnetar parameter set.
error InvalidMode();
/// @notice The supplied public key length is wrong for the mode.
error InvalidPublicKey();
/// @param mode The Magnetar parameter-set byte (FIPS 205 parameter set).
/// @param pubkey The full FIPS 205 group public key to bind this owner to.
constructor(uint8 mode, bytes memory pubkey) {
require(Magnetar.isValidMode(mode), InvalidMode());
(uint256 pkLen,) = Magnetar.sizes(mode);
require(pubkey.length == pkLen, InvalidPublicKey());
_MODE = mode;
_PUBKEY_HASH = keccak256(pubkey);
}
/// @notice Checks if the given signature is valid for the given message.
/// @param message The message to be verified (the Safe tx hash).
/// @param signature `abi.encode(bytes pubkey, bytes magnetarSignature)`.
/// @return ok Whether or not the signature is valid.
function _isValidSignature(bytes32 message, bytes calldata signature) public view returns (bool ok) {
(bytes memory pubkey, bytes memory magnetarSignature) = abi.decode(signature, (bytes, bytes));
if (keccak256(pubkey) != _PUBKEY_HASH) return false;
return Magnetar.verify(message, _MODE, pubkey, magnetarSignature);
}
/// @inheritdoc IERC1271
function isValidSignature(bytes32 message, bytes calldata signature) public view returns (bytes4 magicValue) {
if (_isValidSignature(message, signature)) {
magicValue = IERC1271.isValidSignature.selector;
}
}
/// @inheritdoc ILegacyERC1271
function isValidSignature(bytes memory message, bytes calldata signature) public view returns (bytes4 magicValue) {
if (_isValidSignature(keccak256(message), signature)) {
magicValue = ILegacyERC1271.isValidSignature.selector;
}
}
}