mirror of
https://github.com/luxfi/precompile.git
synced 2026-09-24 00:26:21 +00:00
238 lines
7.1 KiB
Solidity
238 lines
7.1 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.24;
|
|
|
|
/**
|
|
* @title IFROST
|
|
* @dev Interface for FROST threshold signature verification precompile
|
|
*
|
|
* FROST (Flexible Round-Optimized Schnorr Threshold) is a threshold signature
|
|
* scheme based on Schnorr signatures. It enables t-of-n signing where any t
|
|
* parties can collaboratively produce a signature that appears to be from a
|
|
* single signer.
|
|
*
|
|
* Features:
|
|
* - Efficient Schnorr-based threshold signatures
|
|
* - Compatible with Ed25519 and secp256k1 Schnorr
|
|
* - Used for Bitcoin Taproot multisig
|
|
* - Lower gas cost than ECDSA threshold (CGGMP21)
|
|
*
|
|
* Address: 0x020000000000000000000000000000000000000C
|
|
*/
|
|
interface IFROST {
|
|
/**
|
|
* @notice Verify a FROST threshold signature
|
|
* @param threshold The minimum number of signers required (t)
|
|
* @param totalSigners The total number of parties (n)
|
|
* @param publicKey The aggregated public key (32 bytes)
|
|
* @param messageHash The hash of the message (32 bytes)
|
|
* @param signature The Schnorr signature (64 bytes: R || s)
|
|
* @return valid True if the signature is valid
|
|
*/
|
|
function verify(
|
|
uint32 threshold,
|
|
uint32 totalSigners,
|
|
bytes32 publicKey,
|
|
bytes32 messageHash,
|
|
bytes calldata signature
|
|
) external view returns (bool valid);
|
|
}
|
|
|
|
/**
|
|
* @title FROSTLib
|
|
* @dev Library for FROST threshold signature operations
|
|
*/
|
|
library FROSTLib {
|
|
/// @dev Address of the FROST precompile
|
|
address constant FROST_PRECOMPILE = 0x020000000000000000000000000000000000000c;
|
|
|
|
/// @dev Gas cost constants
|
|
uint256 constant BASE_GAS = 50_000;
|
|
uint256 constant PER_SIGNER_GAS = 5_000;
|
|
|
|
error InvalidThreshold();
|
|
error InvalidSignature();
|
|
error SignatureVerificationFailed();
|
|
|
|
/**
|
|
* @notice Verify FROST signature and revert on failure
|
|
* @param threshold Minimum signers required
|
|
* @param totalSigners Total number of parties
|
|
* @param publicKey Aggregated public key
|
|
* @param messageHash Message hash
|
|
* @param signature Schnorr signature
|
|
*/
|
|
function verifyOrRevert(
|
|
uint32 threshold,
|
|
uint32 totalSigners,
|
|
bytes32 publicKey,
|
|
bytes32 messageHash,
|
|
bytes calldata signature
|
|
) internal view {
|
|
if (threshold == 0 || threshold > totalSigners) {
|
|
revert InvalidThreshold();
|
|
}
|
|
if (signature.length != 64) {
|
|
revert InvalidSignature();
|
|
}
|
|
|
|
bytes memory input = abi.encodePacked(
|
|
threshold,
|
|
totalSigners,
|
|
publicKey,
|
|
messageHash,
|
|
signature
|
|
);
|
|
|
|
(bool success, bytes memory result) = FROST_PRECOMPILE.staticcall(input);
|
|
require(success, "FROST precompile call failed");
|
|
|
|
bool valid = abi.decode(result, (bool));
|
|
if (!valid) {
|
|
revert SignatureVerificationFailed();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @notice Estimate gas for FROST verification
|
|
* @param totalSigners Total number of parties
|
|
* @return gas Estimated gas cost
|
|
*/
|
|
function estimateGas(uint32 totalSigners) internal pure returns (uint256 gas) {
|
|
return BASE_GAS + (uint256(totalSigners) * PER_SIGNER_GAS);
|
|
}
|
|
|
|
/**
|
|
* @notice Check if threshold parameters are valid
|
|
* @param threshold Minimum signers required
|
|
* @param totalSigners Total number of parties
|
|
* @return valid True if parameters are valid
|
|
*/
|
|
function isValidThreshold(uint32 threshold, uint32 totalSigners) internal pure returns (bool valid) {
|
|
return threshold > 0 && threshold <= totalSigners;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @title FROSTVerifier
|
|
* @dev Abstract contract for FROST signature verification
|
|
*/
|
|
abstract contract FROSTVerifier {
|
|
using FROSTLib for *;
|
|
|
|
event FROSTSignatureVerified(
|
|
uint32 threshold,
|
|
uint32 totalSigners,
|
|
bytes32 indexed publicKey,
|
|
bytes32 indexed messageHash
|
|
);
|
|
|
|
/**
|
|
* @notice Verify FROST threshold signature
|
|
* @param threshold Minimum signers required
|
|
* @param totalSigners Total number of parties
|
|
* @param publicKey Aggregated public key
|
|
* @param messageHash Message hash
|
|
* @param signature Schnorr signature
|
|
*/
|
|
function verifyFROSTSignature(
|
|
uint32 threshold,
|
|
uint32 totalSigners,
|
|
bytes32 publicKey,
|
|
bytes32 messageHash,
|
|
bytes calldata signature
|
|
) internal view {
|
|
FROSTLib.verifyOrRevert(
|
|
threshold,
|
|
totalSigners,
|
|
publicKey,
|
|
messageHash,
|
|
signature
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @notice Verify FROST signature with event emission
|
|
* @param threshold Minimum signers required
|
|
* @param totalSigners Total number of parties
|
|
* @param publicKey Aggregated public key
|
|
* @param messageHash Message hash
|
|
* @param signature Schnorr signature
|
|
*/
|
|
function verifyFROSTSignatureWithEvent(
|
|
uint32 threshold,
|
|
uint32 totalSigners,
|
|
bytes32 publicKey,
|
|
bytes32 messageHash,
|
|
bytes calldata signature
|
|
) internal {
|
|
verifyFROSTSignature(threshold, totalSigners, publicKey, messageHash, signature);
|
|
emit FROSTSignatureVerified(threshold, totalSigners, publicKey, messageHash);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @title ExampleFROSTContract
|
|
* @dev Example contract using FROST threshold signatures
|
|
*/
|
|
contract ExampleFROSTContract is FROSTVerifier {
|
|
struct ThresholdConfig {
|
|
uint32 threshold;
|
|
uint32 totalSigners;
|
|
bytes32 publicKey;
|
|
}
|
|
|
|
mapping(bytes32 => ThresholdConfig) public configs;
|
|
|
|
event ConfigRegistered(bytes32 indexed configId, uint32 threshold, uint32 totalSigners);
|
|
event MessageProcessed(bytes32 indexed configId, bytes32 messageHash);
|
|
|
|
/**
|
|
* @notice Register a threshold configuration
|
|
* @param configId Unique identifier for the configuration
|
|
* @param threshold Minimum signers required
|
|
* @param totalSigners Total number of parties
|
|
* @param publicKey Aggregated public key
|
|
*/
|
|
function registerConfig(
|
|
bytes32 configId,
|
|
uint32 threshold,
|
|
uint32 totalSigners,
|
|
bytes32 publicKey
|
|
) external {
|
|
require(FROSTLib.isValidThreshold(threshold, totalSigners), "Invalid threshold");
|
|
|
|
configs[configId] = ThresholdConfig({
|
|
threshold: threshold,
|
|
totalSigners: totalSigners,
|
|
publicKey: publicKey
|
|
});
|
|
|
|
emit ConfigRegistered(configId, threshold, totalSigners);
|
|
}
|
|
|
|
/**
|
|
* @notice Process a signed message
|
|
* @param configId Configuration to use
|
|
* @param messageHash Message hash
|
|
* @param signature FROST threshold signature
|
|
*/
|
|
function processSignedMessage(
|
|
bytes32 configId,
|
|
bytes32 messageHash,
|
|
bytes calldata signature
|
|
) external {
|
|
ThresholdConfig memory config = configs[configId];
|
|
require(config.threshold > 0, "Config not found");
|
|
|
|
verifyFROSTSignatureWithEvent(
|
|
config.threshold,
|
|
config.totalSigners,
|
|
config.publicKey,
|
|
messageHash,
|
|
signature
|
|
);
|
|
|
|
emit MessageProcessed(configId, messageHash);
|
|
}
|
|
}
|