// SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.24; import { GovernanceControlsV3 } from "./common/GovernanceControlsV3.sol"; import { RuntimeCodeGuardV3 } from "./common/RuntimeCodeGuardV3.sol"; interface ISP1VerifierV5 { function verifyProof(bytes32 programVKey, bytes calldata publicValues, bytes calldata proofBytes) external view; function VERIFIER_HASH() external pure returns (bytes32); } /// @notice Authorization-only adapter for the fixed Vaultfire SP1 ML-DSA-65 guest. /// @dev Consumers must derive payloadLength/payloadDigest from their own canonical action payload. /// @dev ADAPTER SOURCE ONLY: deployment remains blocked until every release pin is populated from real artifacts. contract MldsaSp1AuthorizationV3 is GovernanceControlsV3, RuntimeCodeGuardV3 { uint256 public constant PUBLIC_VALUES_LENGTH = 636; uint64 public constant AUTHORIZATION_PAUSE_DELAY = 2 days; bytes16 public constant PUBLIC_VALUES_MAGIC = bytes16("VF-SP1-MLDSA1!!!"); bytes32 public constant SP1_V5_GROTH16_VERIFIER_HASH = 0xa4594c59bbc142f3b81c3ecb7f50a7c34bc9af7c4c444b5d48b795427e285913; struct ReleaseConfig { address approvedVerifier; bytes32 verifierRuntimeCodehash; bytes20 sp1ContractsCommit; bytes32 sp1ContractsRelease; bytes32 finalGuestElfSha256; bytes32 programVKey; bytes32 proofSystem; } ISP1VerifierV5 public immutable sp1Verifier; bytes32 public immutable verifierRuntimeCodehash; bytes32 public immutable programVKey; bytes20 public immutable sp1ContractsCommit; bytes32 public immutable sp1ContractsRelease; bytes32 public immutable finalGuestElfSha256; bytes32 public immutable proofSystem; bytes32 public immutable releaseIdentity; struct Scope { bytes32 policyId; bytes32 payloadCodec; bool enabled; } struct CurrentKey { uint64 epoch; bytes32 fingerprintHi; bytes32 fingerprintLo; bool active; } mapping(bytes32 => Scope) public scopes; mapping(bytes32 => uint64) public currentScopeVersion; mapping(bytes32 => CurrentKey) public currentKeys; mapping(bytes32 => bool) public consumedNonces; bool public authorizationPaused; uint64 public authorizationPausedAt; error Unauthorized(); error InvalidConfiguration(); error InvalidPublicValues(); error InvalidScope(); error InvalidKey(); error InvalidValidity(); error Replay(); error VerifierIdentityChanged(); error AuthorizationPaused(); error AuthorizationNotPaused(); error AuthorizationUnpauseTooEarly(); event ScopeConfigured( address indexed consumer, bytes32 indexed moduleId, bytes32 indexed operationId, uint64 configVersion, bytes32 policyId, bytes32 payloadCodec, bool enabled ); event CurrentKeySet( bytes32 indexed subjectHash, uint64 indexed epoch, bytes32 fingerprintHi, bytes32 fingerprintLo, bool active ); event MldsaAuthorizationConsumed( bytes32 indexed subjectHash, bytes32 indexed policyId, uint128 indexed nonce, bytes32 envelopeHashHi, bytes32 envelopeHashLo ); event CurrentKeyRevoked(bytes32 indexed subjectHash, uint64 indexed epoch); event AuthorizationPausedV3(address indexed caller, uint64 at); event AuthorizationUnpausedV3(address indexed caller, uint64 at); constructor( address verifier_, bytes32 programVKey_, ReleaseConfig memory release_, address governance_, address governanceGuardian_ ) GovernanceControlsV3(governance_, governanceGuardian_) { if ( verifier_ == address(0) || verifier_ != release_.approvedVerifier || verifier_.code.length == 0 || release_.verifierRuntimeCodehash == bytes32(0) || verifier_.codehash != release_.verifierRuntimeCodehash || programVKey_ == bytes32(0) || programVKey_ != release_.programVKey || release_.sp1ContractsCommit == bytes20(0) || release_.sp1ContractsRelease == bytes32(0) || release_.finalGuestElfSha256 == bytes32(0) || release_.proofSystem == bytes32(0) ) { revert InvalidConfiguration(); } _rejectDelegatingRuntime(verifier_); if (ISP1VerifierV5(verifier_).VERIFIER_HASH() != SP1_V5_GROTH16_VERIFIER_HASH) revert InvalidConfiguration(); sp1Verifier = ISP1VerifierV5(verifier_); verifierRuntimeCodehash = release_.verifierRuntimeCodehash; programVKey = programVKey_; sp1ContractsCommit = release_.sp1ContractsCommit; sp1ContractsRelease = release_.sp1ContractsRelease; finalGuestElfSha256 = release_.finalGuestElfSha256; proofSystem = release_.proofSystem; releaseIdentity = keccak256( abi.encode( verifier_, release_.verifierRuntimeCodehash, SP1_V5_GROTH16_VERIFIER_HASH, release_.sp1ContractsCommit, release_.sp1ContractsRelease, release_.finalGuestElfSha256, programVKey_, release_.proofSystem ) ); } function configureScope( address consumer, bytes32 moduleId, bytes32 operationId, uint64 configVersion, bytes32 policyId, bytes32 payloadCodec, bool enabled ) external onlyGovernance { if ( consumer == address(0) || moduleId == bytes32(0) || operationId == bytes32(0) || configVersion == 0 || policyId == bytes32(0) || payloadCodec == bytes32(0) ) revert InvalidConfiguration(); bytes32 family = scopeFamilyKey(consumer, moduleId, operationId); if (configVersion <= currentScopeVersion[family]) revert InvalidConfiguration(); bytes32 key = scopeKey(consumer, moduleId, operationId, configVersion); if (scopes[key].policyId != bytes32(0)) revert InvalidConfiguration(); currentScopeVersion[family] = configVersion; scopes[key] = Scope(policyId, payloadCodec, enabled); emit ScopeConfigured(consumer, moduleId, operationId, configVersion, policyId, payloadCodec, enabled); } /// @notice Irreversibly disables a versioned scope. Re-enable requires a new configVersion. function disableScope(address consumer, bytes32 moduleId, bytes32 operationId, uint64 configVersion) external { if (msg.sender != governance && msg.sender != governanceGuardian) revert Unauthorized(); if (configVersion != currentScopeVersion[scopeFamilyKey(consumer, moduleId, operationId)]) { revert InvalidScope(); } bytes32 key = scopeKey(consumer, moduleId, operationId, configVersion); if (scopes[key].policyId == bytes32(0) || !scopes[key].enabled) revert InvalidScope(); scopes[key].enabled = false; emit ScopeConfigured( consumer, moduleId, operationId, configVersion, scopes[key].policyId, scopes[key].payloadCodec, false ); } function setCurrentKey(bytes32 subjectHash, uint64 epoch, bytes32 fingerprintHi, bytes32 fingerprintLo, bool active) external onlyGovernance { CurrentKey storage old = currentKeys[subjectHash]; if ( subjectHash == bytes32(0) || epoch == 0 || fingerprintHi == bytes32(0) || fingerprintLo == bytes32(0) || !active ) revert InvalidConfiguration(); if (epoch == old.epoch) { if ( old.epoch == 0 || old.fingerprintHi != fingerprintHi || old.fingerprintLo != fingerprintLo || !old.active ) revert InvalidConfiguration(); return; } if (epoch < old.epoch) revert InvalidConfiguration(); currentKeys[subjectHash] = CurrentKey(epoch, fingerprintHi, fingerprintLo, active); emit CurrentKeySet(subjectHash, epoch, fingerprintHi, fingerprintLo, active); } /// @notice Revocation is a separate lifecycle transition and cannot be undone at the same epoch. function revokeCurrentKey(bytes32 subjectHash, uint64 epoch) external { if (msg.sender != governance && msg.sender != governanceGuardian) revert Unauthorized(); CurrentKey storage key = currentKeys[subjectHash]; if (subjectHash == bytes32(0) || key.epoch != epoch || !key.active) revert InvalidConfiguration(); key.active = false; emit CurrentKeyRevoked(subjectHash, epoch); } function authorizeMldsa( bytes calldata pv, bytes calldata proof, uint32 expectedPayloadLength, bytes32 expectedPayloadDigestHi, bytes32 expectedPayloadDigestLo ) external returns (bytes32 envelopeHashHi, bytes32 envelopeHashLo) { if (authorizationPaused) revert AuthorizationPaused(); if ( pv.length != PUBLIC_VALUES_LENGTH || bytes16(_word(pv, 0)) != PUBLIC_VALUES_MAGIC || _u16(pv, 16) != 1 || _u16(pv, 18) != PUBLIC_VALUES_LENGTH || _u16(pv, 20) != 1 || _u16(pv, 22) != 0x0201 || _u16(pv, 24) != 1 || _u8(pv, 26) != 1 || !(_u8(pv, 27) == 1 || _u8(pv, 27) == 3) || _u8(pv, 28) != 20 || _u8(pv, 29) != 20 || _u16(pv, 30) != 0 ) revert InvalidPublicValues(); bytes32 policyId = _word(pv, 32); bytes32 chainDomain = _word(pv, 64); if (chainDomain != bytes32(block.chainid) || bytes20(_word(pv, 96)) != bytes20(msg.sender)) { revert InvalidScope(); } bytes32 moduleId = _word(pv, 128); bytes32 operationId = _word(pv, 160); uint64 configVersion = _u64(pv, 304); if (configVersion != currentScopeVersion[scopeFamilyKey(msg.sender, moduleId, operationId)]) { revert InvalidScope(); } Scope memory scope = scopes[scopeKey(msg.sender, moduleId, operationId, configVersion)]; if (!scope.enabled || scope.policyId != policyId || scope.payloadCodec != _word(pv, 312)) revert InvalidScope(); if ( _u32(pv, 344) != expectedPayloadLength || _word(pv, 348) != expectedPayloadDigestHi || _word(pv, 380) != expectedPayloadDigestLo ) revert InvalidScope(); uint64 classicalEpoch = _u64(pv, 256); uint64 mldsaEpoch = _u64(pv, 264); uint128 nonce = _u128(pv, 272); uint64 notBefore = _u64(pv, 288); uint64 notAfter = _u64(pv, 296); if (classicalEpoch == 0 || notAfter < notBefore || block.timestamp < notBefore || block.timestamp > notAfter) { revert InvalidValidity(); } bytes32 subjectHash = keccak256(pv[192:212]); CurrentKey memory key = currentKeys[subjectHash]; if ( !key.active || key.epoch != mldsaEpoch || key.fingerprintHi != _word(pv, 508) || key.fingerprintLo != _word(pv, 540) ) revert InvalidKey(); bytes32 replayKey = keccak256(abi.encodePacked(subjectHash, policyId, nonce)); if (consumedNonces[replayKey]) revert Replay(); if (_word(pv, 572) == bytes32(0) || _word(pv, 604) == bytes32(0)) revert InvalidPublicValues(); if (address(sp1Verifier).codehash != verifierRuntimeCodehash) revert VerifierIdentityChanged(); sp1Verifier.verifyProof(programVKey, pv, proof); consumedNonces[replayKey] = true; envelopeHashHi = _word(pv, 444); envelopeHashLo = _word(pv, 476); emit MldsaAuthorizationConsumed(subjectHash, policyId, nonce, envelopeHashHi, envelopeHashLo); } function pauseAuthorization() external { if (msg.sender != governance && msg.sender != governanceGuardian) revert Unauthorized(); if (!authorizationPaused) { authorizationPaused = true; authorizationPausedAt = uint64(block.timestamp); emit AuthorizationPausedV3(msg.sender, authorizationPausedAt); } } function unpauseAuthorization() external onlyGovernance { if (!authorizationPaused) revert AuthorizationNotPaused(); if (block.timestamp < uint256(authorizationPausedAt) + AUTHORIZATION_PAUSE_DELAY) { revert AuthorizationUnpauseTooEarly(); } authorizationPaused = false; emit AuthorizationUnpausedV3(msg.sender, uint64(block.timestamp)); } function scopeKey(address consumer, bytes32 moduleId, bytes32 operationId, uint64 configVersion) public pure returns (bytes32) { return keccak256(abi.encode(consumer, moduleId, operationId, configVersion)); } function scopeFamilyKey(address consumer, bytes32 moduleId, bytes32 operationId) public pure returns (bytes32) { return keccak256(abi.encode(consumer, moduleId, operationId)); } function _word(bytes calldata b, uint256 p) private pure returns (bytes32 v) { assembly { v := calldataload(add(b.offset, p)) } } function _u8(bytes calldata b, uint256 p) private pure returns (uint8) { return uint8(uint256(_word(b, p)) >> 248); } function _u16(bytes calldata b, uint256 p) private pure returns (uint16) { return uint16(uint256(_word(b, p)) >> 240); } function _u32(bytes calldata b, uint256 p) private pure returns (uint32) { return uint32(uint256(_word(b, p)) >> 224); } function _u64(bytes calldata b, uint256 p) private pure returns (uint64) { return uint64(uint256(_word(b, p)) >> 192); } function _u128(bytes calldata b, uint256 p) private pure returns (uint128) { return uint128(uint256(_word(b, p)) >> 128); } }