// SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.24; import { TestBase } from "./TestBase.sol"; import { MldsaSp1AuthorizationV3, ISP1VerifierV5 } from "../src/MldsaSp1AuthorizationV3.sol"; import { RuntimeCodeGuardV3 } from "../src/common/RuntimeCodeGuardV3.sol"; /// @dev TEST-ONLY MOCK. Passing these tests is adapter-logic evidence, not real-proof evidence. contract RecordingSp1Verifier is ISP1VerifierV5 { bool immutable fail; constructor(bool fail_) { fail = fail_; } function VERIFIER_HASH() external pure returns (bytes32) { return 0xa4594c59bbc142f3b81c3ecb7f50a7c34bc9af7c4c444b5d48b795427e285913; } function verifyProof(bytes32, bytes calldata, bytes calldata) external view { require(!fail, "invalid mock proof"); } } contract WrongHashVerifier is ISP1VerifierV5 { function VERIFIER_HASH() external pure returns (bytes32) { return keccak256("wrong"); } function verifyProof(bytes32, bytes calldata, bytes calldata) external pure { } } contract DelegateProxyMock { address immutable implementation; constructor(address implementation_) { implementation = implementation_; } fallback() external { address target = implementation; assembly { calldatacopy(0, 0, calldatasize()) let ok := delegatecall(gas(), target, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) if iszero(ok) { revert(0, returndatasize()) } return(0, returndatasize()) } } } contract Consumer { function consume(MldsaSp1AuthorizationV3 a, bytes calldata pv, bytes calldata proof, uint32 n, bytes32 h, bytes32 l) external { a.authorizeMldsa(pv, proof, n, h, l); } } contract MldsaSp1AuthorizationTest is TestBase { bytes32 internal constant MOCK_VKEY = bytes32(uint256(1) << 248); bytes20 internal constant MOCK_CONTRACTS_COMMIT = bytes20(uint160(0x1234)); bytes32 internal constant MOCK_RELEASE = keccak256("TEST-ONLY SP1 contracts release"); bytes32 internal constant MOCK_ELF = keccak256("TEST-ONLY guest ELF"); bytes32 internal constant MOCK_PROOF_SYSTEM = keccak256("TEST-ONLY SP1 v5 Groth16 BN254"); address internal constant GOVERNANCE_GUARDIAN = address(0xBEEF); function releaseConfig(address verifier) internal view returns (MldsaSp1AuthorizationV3.ReleaseConfig memory c) { c = MldsaSp1AuthorizationV3.ReleaseConfig({ approvedVerifier: verifier, verifierRuntimeCodehash: verifier.codehash, sp1ContractsCommit: MOCK_CONTRACTS_COMMIT, sp1ContractsRelease: MOCK_RELEASE, finalGuestElfSha256: MOCK_ELF, programVKey: MOCK_VKEY, proofSystem: MOCK_PROOF_SYSTEM }); } function deployMock(RecordingSp1Verifier verifier) internal returns (MldsaSp1AuthorizationV3) { return new MldsaSp1AuthorizationV3( address(verifier), MOCK_VKEY, releaseConfig(address(verifier)), address(this), GOVERNANCE_GUARDIAN ); } function put(bytes memory b, uint256 p, bytes memory v) internal pure { for (uint256 i; i < v.length; i++) { b[p + i] = v[i]; } } function pv(address consumer) internal view returns (bytes memory b) { b = new bytes(636); put(b, 0, bytes("VF-SP1-MLDSA1!!!")); b[17] = bytes1(uint8(1)); b[18] = bytes1(uint8(2)); b[19] = bytes1(uint8(0x7c)); b[21] = bytes1(uint8(1)); b[22] = bytes1(uint8(2)); b[23] = bytes1(uint8(1)); b[25] = bytes1(uint8(1)); b[26] = bytes1(uint8(1)); b[27] = bytes1(uint8(1)); b[28] = bytes1(uint8(20)); b[29] = bytes1(uint8(20)); b[32] = bytes1(uint8(1)); put(b, 64, abi.encodePacked(bytes32(block.chainid))); put(b, 96, abi.encodePacked(bytes20(consumer))); b[128] = bytes1(uint8(2)); b[160] = bytes1(uint8(3)); for (uint256 i; i < 20; i++) { b[192 + i] = bytes1(uint8(0x40 + i)); } b[263] = bytes1(uint8(1)); b[271] = bytes1(uint8(7)); b[287] = bytes1(uint8(9)); put(b, 288, abi.encodePacked(uint64(block.timestamp))); put(b, 296, abi.encodePacked(uint64(block.timestamp + 100))); b[311] = bytes1(uint8(1)); b[312] = bytes1(uint8(4)); b[347] = bytes1(uint8(5)); b[348] = bytes1(uint8(5)); b[380] = bytes1(uint8(6)); b[508] = bytes1(uint8(7)); b[540] = bytes1(uint8(8)); b[572] = bytes1(uint8(9)); b[604] = bytes1(uint8(10)); } function configure(MldsaSp1AuthorizationV3 a, Consumer c, bytes memory x) internal { a.configureScope( address(c), bytes32(uint256(2) << 248), bytes32(uint256(3) << 248), 1, bytes32(uint256(1) << 248), bytes32(uint256(4) << 248), true ); a.setCurrentKey(keccak256(_slice(x, 192, 20)), 7, bytes32(uint256(7) << 248), bytes32(uint256(8) << 248), true); } function testMockAdapterConsumesOnlyAfterVerifierSuccessAndRejectsReplay() external { MldsaSp1AuthorizationV3 a = deployMock(new RecordingSp1Verifier(false)); Consumer c = new Consumer(); bytes memory x = pv(address(c)); configure(a, c, x); c.consume(a, x, hex"01", 5, bytes32(uint256(5) << 248), bytes32(uint256(6) << 248)); vm.expectRevert(MldsaSp1AuthorizationV3.Replay.selector); c.consume(a, x, hex"01", 5, bytes32(uint256(5) << 248), bytes32(uint256(6) << 248)); } function testMockProofFailureDoesNotConsumeNonce() external { MldsaSp1AuthorizationV3 a = deployMock(new RecordingSp1Verifier(true)); Consumer c = new Consumer(); bytes memory x = pv(address(c)); configure(a, c, x); vm.expectRevert(); c.consume(a, x, hex"", 5, bytes32(uint256(5) << 248), bytes32(uint256(6) << 248)); bytes32 sh = keccak256(_slice(x, 192, 20)); assertFalse(a.consumedNonces(keccak256(abi.encodePacked(sh, bytes32(uint256(1) << 248), uint128(9))))); } function testSelfReportedExpectedHashAtUnapprovedSpoofAddressFails() external { RecordingSp1Verifier approved = new RecordingSp1Verifier(false); RecordingSp1Verifier spoof = new RecordingSp1Verifier(false); MldsaSp1AuthorizationV3.ReleaseConfig memory c = releaseConfig(address(approved)); vm.expectRevert(MldsaSp1AuthorizationV3.InvalidConfiguration.selector); new MldsaSp1AuthorizationV3(address(spoof), MOCK_VKEY, c, address(this), GOVERNANCE_GUARDIAN); } function testWrongRuntimeCodehashAndWrongReportedHashFail() external { RecordingSp1Verifier verifier = new RecordingSp1Verifier(false); MldsaSp1AuthorizationV3.ReleaseConfig memory c = releaseConfig(address(verifier)); c.verifierRuntimeCodehash = bytes32(uint256(1)); vm.expectRevert(MldsaSp1AuthorizationV3.InvalidConfiguration.selector); new MldsaSp1AuthorizationV3(address(verifier), MOCK_VKEY, c, address(this), GOVERNANCE_GUARDIAN); WrongHashVerifier wrong = new WrongHashVerifier(); vm.expectRevert(MldsaSp1AuthorizationV3.InvalidConfiguration.selector); new MldsaSp1AuthorizationV3( address(wrong), MOCK_VKEY, releaseConfig(address(wrong)), address(this), GOVERNANCE_GUARDIAN ); } function testDelegateProxyIsRejectedEvenWhenItForwardsExpectedHash() external { RecordingSp1Verifier implementation = new RecordingSp1Verifier(false); DelegateProxyMock proxy = new DelegateProxyMock(address(implementation)); vm.expectRevert(RuntimeCodeGuardV3.DelegatingRuntime.selector); new MldsaSp1AuthorizationV3( address(proxy), MOCK_VKEY, releaseConfig(address(proxy)), address(this), GOVERNANCE_GUARDIAN ); } function testWrongVkeyAndEveryMissingReleasePinFailClosed() external { RecordingSp1Verifier verifier = new RecordingSp1Verifier(false); MldsaSp1AuthorizationV3.ReleaseConfig memory c = releaseConfig(address(verifier)); vm.expectRevert(MldsaSp1AuthorizationV3.InvalidConfiguration.selector); new MldsaSp1AuthorizationV3(address(verifier), bytes32(uint256(2)), c, address(this), GOVERNANCE_GUARDIAN); c = releaseConfig(address(verifier)); c.finalGuestElfSha256 = bytes32(0); _expectConfigFailure(verifier, c); c = releaseConfig(address(verifier)); c.programVKey = bytes32(0); _expectConfigFailure(verifier, c); c = releaseConfig(address(verifier)); c.sp1ContractsCommit = bytes20(0); _expectConfigFailure(verifier, c); c = releaseConfig(address(verifier)); c.sp1ContractsRelease = bytes32(0); _expectConfigFailure(verifier, c); c = releaseConfig(address(verifier)); c.proofSystem = bytes32(0); _expectConfigFailure(verifier, c); c = releaseConfig(address(verifier)); c.approvedVerifier = address(0); _expectConfigFailure(verifier, c); } function testKeyFingerprintChangeRequiresHigherEpochAndRevocationIsSeparate() external { MldsaSp1AuthorizationV3 a = deployMock(new RecordingSp1Verifier(false)); bytes32 subject = keccak256("subject"); bytes32 hi1 = keccak256("hi1"); bytes32 lo1 = keccak256("lo1"); a.setCurrentKey(subject, 7, hi1, lo1, true); a.setCurrentKey(subject, 7, hi1, lo1, true); // exact idempotent no-op vm.expectRevert(MldsaSp1AuthorizationV3.InvalidConfiguration.selector); a.setCurrentKey(subject, 7, keccak256("replacement"), lo1, true); vm.expectRevert(MldsaSp1AuthorizationV3.InvalidConfiguration.selector); a.setCurrentKey(subject, 6, hi1, lo1, true); a.setCurrentKey(subject, 8, keccak256("hi2"), keccak256("lo2"), true); (uint64 epoch,,, bool active) = a.currentKeys(subject); assertEq(epoch, 8); assertTrue(active); a.revokeCurrentKey(subject, 8); (,,, active) = a.currentKeys(subject); assertFalse(active); vm.expectRevert(MldsaSp1AuthorizationV3.InvalidConfiguration.selector); a.setCurrentKey(subject, 8, keccak256("hi2"), keccak256("lo2"), true); } function _expectConfigFailure(RecordingSp1Verifier verifier, MldsaSp1AuthorizationV3.ReleaseConfig memory c) internal { vm.expectRevert(MldsaSp1AuthorizationV3.InvalidConfiguration.selector); new MldsaSp1AuthorizationV3(address(verifier), MOCK_VKEY, c, address(this), GOVERNANCE_GUARDIAN); } function _slice(bytes memory b, uint256 p, uint256 n) internal pure returns (bytes memory o) { o = new bytes(n); for (uint256 i; i < n; i++) { o[i] = b[p + i]; } } }