Files

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));
}
}