mirror of
https://github.com/luxfi/safe-corona.git
synced 2026-10-30 15:19:32 +00:00
85 lines
4.3 KiB
Solidity
85 lines
4.3 KiB
Solidity
// SPDX-License-Identifier: GPL-3.0-only
|
|
pragma solidity =0.8.29;
|
|
|
|
import {Corona} from "./Corona.sol";
|
|
import {IERC1271, ILegacyERC1271} from "./interfaces/IERC1271.sol";
|
|
|
|
/// @title Safe Corona Signer
|
|
/// @notice Safe smart-account owner that verifies Corona Ring-LWE threshold
|
|
/// signatures through the `coronaThreshold` precompile, making a single Corona
|
|
/// group key a first-class Safe owner via EIP-1271.
|
|
///
|
|
/// @dev ONE key, ONE owner. The owner commits `(threshold, totalParties,
|
|
/// groupKeyHash)` at construction; all three 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 — why a group-key hash, not a public key. Corona is not byte-equal to
|
|
/// a single-party scheme: the aggregated signature carries its own group key
|
|
/// (matrix A, rounded public key b̃) inside the serialized blob, and there is no
|
|
/// separate public-key argument. The per-message-varying part of the signature
|
|
/// (c ‖ z ‖ Δ) precedes the stable group-key region (A ‖ b̃). This owner commits
|
|
/// `keccak256(group-key region)` (see {Corona.groupKeyHash}) so that a signature
|
|
/// only authorises this owner if it embeds the EXACT group key the owner was
|
|
/// constructed with — closing the "any valid threshold signature from any group
|
|
/// is accepted" gap. The threshold metadata `(t, n)` is additionally pinned.
|
|
///
|
|
/// REPLAY. The Safe passes the EIP-712 Safe-transaction hash, bound to this
|
|
/// Safe / chain / nonce. The precompile additionally binds the
|
|
/// `"lux-evm-precompile-corona-v1"` context into the verified message.
|
|
///
|
|
/// SIGNATURE PAYLOAD: the raw serialized Corona signature `bytes` (the embedded
|
|
/// group key is part of it; no separate public key is supplied).
|
|
contract SafeCoronaSigner is IERC1271, ILegacyERC1271 {
|
|
/// @notice The minimum number of parties (t) this owner verifies under.
|
|
uint32 private immutable _THRESHOLD;
|
|
/// @notice The total number of parties (n) this owner verifies under.
|
|
uint32 private immutable _TOTAL_PARTIES;
|
|
/// @notice keccak256 of the committed Corona group-key region.
|
|
bytes32 private immutable _GROUP_KEY_HASH;
|
|
|
|
/// @notice `(threshold, totalParties)` violate `0 < t <= n`.
|
|
error InvalidThreshold();
|
|
|
|
/// @param threshold The minimum number of parties required (t).
|
|
/// @param totalParties The total number of parties (n).
|
|
/// @param groupKey A reference serialized Corona signature from this key,
|
|
/// used solely to extract and commit the stable group-key region's hash.
|
|
constructor(uint32 threshold, uint32 totalParties, bytes memory groupKey) {
|
|
require(Corona.isValidThreshold(threshold, totalParties), InvalidThreshold());
|
|
_THRESHOLD = threshold;
|
|
_TOTAL_PARTIES = totalParties;
|
|
_GROUP_KEY_HASH = Corona.groupKeyHash(groupKey);
|
|
}
|
|
|
|
/// @notice The committed group-key hash (for off-chain reference / tooling).
|
|
function groupKeyHash() external view returns (bytes32) {
|
|
return _GROUP_KEY_HASH;
|
|
}
|
|
|
|
/// @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 The raw serialized Corona threshold signature.
|
|
/// @return ok Whether or not the signature is valid.
|
|
function _isValidSignature(bytes32 message, bytes calldata signature) public view returns (bool ok) {
|
|
// Bind the embedded group key to this owner's commitment.
|
|
if (signature.length < Corona.GROUP_KEY_OFFSET + Corona.GROUP_KEY_LEN) return false;
|
|
if (Corona.groupKeyHash(signature) != _GROUP_KEY_HASH) return false;
|
|
return Corona.verify(message, _THRESHOLD, _TOTAL_PARTIES, signature);
|
|
}
|
|
|
|
/// @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;
|
|
}
|
|
}
|
|
}
|