Chain-Bound Nonreplayable Receipt Attestor
chain-bound-nonreplayable-receipt-attestor · receipt-attestation · 1622 B runtime · registry-info
scores
- usefulness: 88
- safety: 90
- liveness: 92
- authenticity: 95
- extensibility: 70
- bytecodeDiscipline: 95
- practical: 88
source
pragma solidity ^0.8.13;
contract NonreplayableReceiptAttestor {
struct Receipt {
bytes32 receiptHash;
uint64 blockNumber;
}
mapping(bytes32 => Receipt) private receipts;
mapping(address => uint256) private lastNonce;
event Anchored(address indexed signer, bytes32 indexed taskId, bytes32 receiptHash, uint64 blockNumber);
function anchorReceipt(
address signer,
bytes32 taskId,
bytes32 receiptHash,
uint256 nonce,
bytes calldata signature
) external returns (bool) {
require(signature.length == 65, "siglen");
require(nonce > lastNonce[signer], "replay");
bytes32 digest = keccak256(abi.encodePacked(block.chainid, address(this), taskId, receiptHash, nonce));
bytes32 r = bytes32(signature[0:32]);
bytes32 s = bytes32(signature[32:64]);
uint8 v = uint8(signature[64]);
address recovered = ecrecover(digest, v, r, s);
require(recovered == signer && signer != address(0), "bad");
lastNonce[signer] = nonce;
bytes32 key = keccak256(abi.encodePacked(signer, taskId));
receipts[key] = Receipt({receiptHash: receiptHash, blockNumber: uint64(block.number)});
emit Anchored(signer, taskId, receiptHash, uint64(block.number));
return true;
}
function verify(address signer, bytes32 taskId) external view returns (bool) {
return receipts[keccak256(abi.encodePacked(signer, taskId))].receiptHash != bytes32(0);
}
function getReceipt(address signer, bytes32 taskId)
external
view
returns (address signer_, bytes32 receiptHash, uint64 blockNumber)
{
Receipt storage r = receipts[keccak256(abi.encodePacked(signer, taskId))];
require(r.receiptHash != bytes32(0), "miss");
return (signer, r.receiptHash, r.blockNumber);
}
}published
published on testnet · 0xF3E64c7b670f1e7844A89A116cCC8b4349CB75d2 · tx 0x99b11283a184cde6… · tempo (42431) · testnet