pragma solidity ^0.8.13; interface IERC1271 { function isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4); } contract ReceiptVault { bytes32 private constant EIP712_DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); bytes32 private constant RECEIPT_TYPEHASH = keccak256("Receipt(address claimant,bytes32 actionId,bytes32 payloadHash)"); bytes32 private immutable domainSeparator; mapping(bytes32 => bool) public receiptHashes; error InvalidSignature(); error ReceiptAlreadyStored(); event ReceiptStored( address indexed claimant, bytes32 indexed actionId, bytes32 indexed payloadHash, bytes32 receiptHash ); struct Receipt { address claimant; bytes32 actionId; bytes32 payloadHash; } constructor() { domainSeparator = keccak256( abi.encode( EIP712_DOMAIN_TYPEHASH, keccak256("ReceiptVault"), keccak256("1"), block.chainid, address(this) ) ); } function submitReceipt(Receipt calldata receipt, bytes calldata signature) external { bytes32 receiptHash = keccak256(abi.encode(receipt)); if (receiptHashes[receiptHash]) revert ReceiptAlreadyStored(); bytes32 structHash = keccak256( abi.encode(RECEIPT_TYPEHASH, receipt.claimant, receipt.actionId, receipt.payloadHash) ); bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); _verify(receipt.claimant, digest, signature); receiptHashes[receiptHash] = true; emit ReceiptStored(receipt.claimant, receipt.actionId, receipt.payloadHash, receiptHash); } function _verify(address claimant, bytes32 digest, bytes calldata signature) internal view { if (claimant.code.length > 0) { if ( IERC1271(claimant).isValidSignature(digest, signature) != IERC1271.isValidSignature.selector ) revert InvalidSignature(); } else { (bytes32 r, bytes32 s, uint8 v) = _split(signature); address recovered = ecrecover(digest, v, r, s); if (recovered == address(0) || recovered != claimant) revert InvalidSignature(); } } function _split(bytes calldata sig) private pure returns (bytes32 r, bytes32 s, uint8 v) { if (sig.length != 65) revert InvalidSignature(); assembly { r := calldataload(sig.offset) s := calldataload(add(sig.offset, 0x20)) v := byte(0, calldataload(add(sig.offset, 0x40))) } } }