// SPDX-License-Identifier: MIT pragma solidity 0.8.24; /// @notice One signer attests a SHA-256 of an X archive ZIP. /// The archive never comes here. The contract stores the hash, the handle, /// the claimed time, and the signature that authorized them. contract XArchiveAttest { address public immutable signer; struct Record { string handle; uint256 timestamp; bytes signature; uint256 recordedAt; bool exists; } mapping(bytes32 => Record) private records; error EmptyHash(); error BadHandle(); error BadSignature(); error AlreadyAttested(); event Attested( bytes32 indexed archiveHash, string handle, uint256 timestamp, uint256 recordedAt ); constructor(address trustedSigner) { if (trustedSigner == address(0)) revert BadSignature(); signer = trustedSigner; } function attest( bytes32 archiveHash, string calldata handle, uint256 timestamp, bytes calldata signature ) external { if (archiveHash == bytes32(0)) revert EmptyHash(); if (records[archiveHash].exists) revert AlreadyAttested(); if (!_handleOk(handle)) revert BadHandle(); if (timestamp == 0) revert BadSignature(); bytes32 payload = keccak256( abi.encode(archiveHash, keccak256(bytes(handle)), timestamp) ); if (_recover(payload, signature) != signer) revert BadSignature(); records[archiveHash] = Record(handle, timestamp, signature, block.timestamp, true); emit Attested(archiveHash, handle, timestamp, block.timestamp); } function lookup(bytes32 archiveHash) external view returns ( string memory handle, uint256 timestamp, uint256 recordedAt, bool found ) { Record storage row = records[archiveHash]; return (row.handle, row.timestamp, row.recordedAt, row.exists); } function _handleOk(string calldata handle) private pure returns (bool) { bytes calldata raw = bytes(handle); uint256 n = raw.length; if (n < 1 || n > 15) return false; for (uint256 i = 0; i < n; i++) { bytes1 c = raw[i]; bool digit = c >= 0x30 && c <= 0x39; bool upper = c >= 0x41 && c <= 0x5A; bool lower = c >= 0x61 && c <= 0x7A; if (!digit && !upper && !lower && c != 0x5F) return false; } return true; } function _recover(bytes32 payload, bytes calldata signature) private pure returns (address) { if (signature.length != 65) return address(0); bytes32 r; bytes32 s; uint8 v; assembly { r := calldataload(signature.offset) s := calldataload(add(signature.offset, 32)) v := byte(0, calldataload(add(signature.offset, 64))) } if (v < 27) v += 27; if (v != 27 && v != 28) return address(0); bytes32 digest = keccak256( abi.encodePacked("\x19Ethereum Signed Message:\n32", payload) ); return ecrecover(digest, v, r, s); } }