// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; /// @title Neptuv trust attestation verifier /// @notice Checks EIP-712 TrustAttestation signatures issued by Neptuv (https://neptuv.xyz/docs#attestations) /// so a contract can gate AI agents by their Neptuv Score without calling any API. /// @dev The signing domain is {name: "Neptuv Attestation", version: "1"} with no chainId, so one /// attestation verifies the same way on every chain. Get the current attester address from /// https://neptuv.xyz/.well-known/neptuv-attester.json contract NeptuvAttestationVerifier { struct TrustAttestation { uint256 agentChainId; uint256 agentId; address registry; uint8 score; uint8 label; // 0 Not enough data, 1 Risky, 2 Caution, 3 Good, 4 Trusted, 5 Flagged bool enoughData; uint16 methodVersion; // 11 = method v1.1 uint64 issuedAt; uint64 expiresAt; } bytes32 public constant TYPEHASH = keccak256( "TrustAttestation(uint256 agentChainId,uint256 agentId,address registry,uint8 score,uint8 label,bool enoughData,uint16 methodVersion,uint64 issuedAt,uint64 expiresAt)" ); bytes32 public constant DOMAIN_SEPARATOR = keccak256( abi.encode( keccak256("EIP712Domain(string name,string version)"), keccak256(bytes("Neptuv Attestation")), keccak256(bytes("1")) ) ); uint8 private constant LABEL_FLAGGED = 5; address public immutable attester; error InvalidSignature(); constructor(address neptuvAttester) { attester = neptuvAttester; } function digest(TrustAttestation calldata a) public pure returns (bytes32) { bytes32 structHash = keccak256( abi.encode( TYPEHASH, a.agentChainId, a.agentId, a.registry, a.score, a.label, a.enoughData, a.methodVersion, a.issuedAt, a.expiresAt ) ); return keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, structHash)); } /// @notice True when Neptuv signed this attestation and it is still valid. function isAuthentic(TrustAttestation calldata a, bytes calldata signature) public view returns (bool) { return block.timestamp <= a.expiresAt && _recover(digest(a), signature) == attester; } /// @notice True when the attestation is authentic, has enough data, isn't flagged and meets minScore. function isTrusted(TrustAttestation calldata a, bytes calldata signature, uint8 minScore) external view returns (bool) { return isAuthentic(a, signature) && a.enoughData && a.label != LABEL_FLAGGED && a.score >= minScore; } function _recover(bytes32 hash, bytes calldata sig) private pure returns (address) { if (sig.length != 65) revert InvalidSignature(); bytes32 r = bytes32(sig[0:32]); bytes32 s = bytes32(sig[32:64]); uint8 v = uint8(sig[64]); if (v < 27) v += 27; // Reject malleable signatures (upper half of the curve order), like OpenZeppelin's ECDSA. if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) revert InvalidSignature(); address signer = ecrecover(hash, v, r, s); if (signer == address(0)) revert InvalidSignature(); return signer; } }