mirror of
https://github.com/luxfi/safe-magnetar.git
synced 2026-10-30 15:18:11 +00:00
117 lines
5.4 KiB
Solidity
117 lines
5.4 KiB
Solidity
// SPDX-License-Identifier: GPL-3.0-only
|
|
pragma solidity ^0.8.29;
|
|
|
|
import {Test} from "forge-std/Test.sol";
|
|
import {Magnetar} from "contracts/Magnetar.sol";
|
|
|
|
/// @notice A stand-in for the on-chain `magnetarVerify` precompile (`0x012207`),
|
|
/// used in `forge test` where the Go precompile is not present.
|
|
///
|
|
/// @dev GROUND-TRUTH ABI CONFORMANCE. This mock does NOT re-implement SLH-DSA.
|
|
/// It asserts the *calldata framing*: it returns the precompile's exact success
|
|
/// word `bytes32(1)` iff the incoming calldata is byte-for-byte equal to a
|
|
/// reference `input` blob whose keccak256 is stored in slot 0. That blob is a
|
|
/// real Known-Answer-Test vector from `precompile/cmd/safekatdump`,
|
|
/// independently *proven to verify against the real Go precompile* by
|
|
/// `precompile/cmd/safekatverify`. If {Magnetar.verify}'s framed calldata
|
|
/// matches it, the real precompile would accept it too; any wire-format drift
|
|
/// changes the calldata and this mock returns zero — failing the test.
|
|
contract MockMagnetarPrecompile {
|
|
fallback(bytes calldata input) external returns (bytes memory) {
|
|
bytes32 expected;
|
|
assembly {
|
|
expected := sload(0)
|
|
}
|
|
bool ok = keccak256(input) == expected;
|
|
return abi.encode(ok ? bytes32(uint256(1)) : bytes32(0));
|
|
}
|
|
}
|
|
|
|
contract MagnetarTest is Test {
|
|
uint8 internal mode;
|
|
bytes internal pubkey;
|
|
bytes32 internal msgHash;
|
|
bytes internal sig;
|
|
bytes internal expectedInput;
|
|
|
|
function setUp() public {
|
|
string memory json = vm.readFile(string.concat(vm.projectRoot(), "/tests/kat.json"));
|
|
mode = uint8(vm.parseJsonUint(json, ".mode"));
|
|
pubkey = vm.parseJsonBytes(json, ".pubkey");
|
|
msgHash = bytes32(vm.parseJsonBytes(json, ".msgHash"));
|
|
sig = vm.parseJsonBytes(json, ".sig");
|
|
expectedInput = vm.parseJsonBytes(json, ".input");
|
|
|
|
vm.etch(Magnetar.PRECOMPILE, type(MockMagnetarPrecompile).runtimeCode);
|
|
vm.store(Magnetar.PRECOMPILE, bytes32(uint256(0)), keccak256(expectedInput));
|
|
}
|
|
|
|
/// @notice The library frames calldata that byte-matches the KAT input the
|
|
/// real precompile accepts, so {Magnetar.verify} returns true.
|
|
function test_Verify_KAT() public view {
|
|
assertTrue(Magnetar.verify(msgHash, mode, pubkey, sig), "Magnetar KAT must verify");
|
|
}
|
|
|
|
/// @notice Independent proof the library frames the precompile calldata
|
|
/// EXACTLY as the Go `Run()` parses it:
|
|
/// mode(1) || pkLen:uint16 || pubkey || msgLen:uint16(32) || msg(32) || sig
|
|
function test_CalldataFramingMatchesPrecompileABI() public view {
|
|
bytes memory framed = abi.encodePacked(mode, uint16(pubkey.length), pubkey, uint16(32), msgHash, sig);
|
|
assertEq(keccak256(framed), keccak256(expectedInput), "framing must equal precompile wire bytes");
|
|
assertEq(framed.length, expectedInput.length, "framed length must equal precompile input length");
|
|
// 1 (mode) + 2 (pkLen) + 32 (pk-128) + 2 (msgLen) + 32 (msg) + 7856 (sig-SHA2-128s).
|
|
assertEq(framed.length, 1 + 2 + 32 + 2 + 32 + 7856, "Magnetar-SHA2-128s input size");
|
|
}
|
|
|
|
/// @notice A tampered signature changes the calldata, so verification fails.
|
|
function test_Verify_RejectsTamperedSignature() public view {
|
|
bytes memory bad = bytes.concat(sig);
|
|
bad[100] ^= 0xFF;
|
|
assertFalse(Magnetar.verify(msgHash, mode, pubkey, bad), "tampered sig must not verify");
|
|
}
|
|
|
|
/// @notice A wrong message changes the calldata, so verification fails.
|
|
function test_Verify_RejectsWrongMessage() public view {
|
|
bytes32 wrong = keccak256("not the signed message");
|
|
assertFalse(Magnetar.verify(wrong, mode, pubkey, sig), "wrong message must not verify");
|
|
}
|
|
|
|
/// @notice A public key of the wrong length for the mode is rejected before
|
|
/// any precompile call.
|
|
function test_Verify_RejectsWrongPubkeyLength() public view {
|
|
bytes memory shortPk = new bytes(pubkey.length - 1);
|
|
assertFalse(Magnetar.verify(msgHash, mode, shortPk, sig), "short pubkey must not verify");
|
|
}
|
|
|
|
/// @notice An unsupported mode byte is rejected (no precompile call).
|
|
function test_Verify_RejectsBadMode() public view {
|
|
assertFalse(Magnetar.verify(msgHash, 0x99, pubkey, sig), "bad mode must not verify");
|
|
}
|
|
|
|
/// @notice Strict success-word check.
|
|
function test_Verify_FailClosedOnNonOneWord() public {
|
|
vm.store(Magnetar.PRECOMPILE, bytes32(uint256(0)), keccak256("never matches"));
|
|
assertFalse(Magnetar.verify(msgHash, mode, pubkey, sig), "non-success word must be false");
|
|
}
|
|
|
|
/// @notice The published precompile address is the canonical LP-4200 slot.
|
|
function test_PrecompileAddress() public pure {
|
|
assertEq(Magnetar.PRECOMPILE, address(0x0000000000000000000000000000000000012207));
|
|
}
|
|
|
|
/// @notice The published context matches the precompile's domain separator.
|
|
function test_Context() public pure {
|
|
assertEq(string(Magnetar.CONTEXT), "lux-evm-precompile-magnetar-v1");
|
|
}
|
|
|
|
/// @notice Mode validity helper pins the supported FIPS 205 parameter sets.
|
|
function test_IsValidMode() public pure {
|
|
assertTrue(Magnetar.isValidMode(0x00));
|
|
assertTrue(Magnetar.isValidMode(0x05));
|
|
assertTrue(Magnetar.isValidMode(0x15));
|
|
assertFalse(Magnetar.isValidMode(0x06));
|
|
assertFalse(Magnetar.isValidMode(0x0F));
|
|
assertFalse(Magnetar.isValidMode(0x16));
|
|
}
|
|
}
|