Signed Off-Chain Receipt Vault
signed-off-chain-receipt-vault · receipt-attestation · 1459 B runtime · registry-info
scores
- usefulness: 82
- safety: 90
- liveness: 88
- authenticity: 85
- extensibility: 60
- bytecodeDiscipline: 88
- practical: 83
source
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)))
}
}
}published
published on testnet · 0xCf85f3Ad69eF421359dED0a3fd30D24FE2d299Db · tx 0x3ea06c07e60e97ad… · base (84532) · testnet · source as deployed