1// SPDX-License-Identifier: MIT
2pragma solidity^0.8.24;
3
4import {MerkleProof} from "./MerkleProof.sol";
5
6contract ChainTickPoP {
7 struct CommitMeta {
8 uint32 pairId; // compact ids reduce gas
9 uint16 timeframeId; // e.g., 5m=1, 15m=2, 1h=3
10 uint16 modelId; // versioned model
11 address publisher; // EOA or contract
12 uint64 seq; // per-publisher sequence
13 uint40 minRevealAt; // unix seconds
14 uint40 maxRevealAt; // unix seconds
15 }
16
17 struct CommitRec {
18 bytes32 hashOrRoot; // single leaf hash or merkle root
19 CommitMeta meta;
20 bool isBatch;
21 }
22
23 event Committed(bytes32 indexed key, bytes32 hashOrRoot,
24 CommitMeta meta, bool isBatch);
25 event Revealed(bytes32 indexed key, bytes32 leafHash,
26 bytes payloadBytesOrCID);
27
28 mapping(bytes32 => CommitRec) public commits;
29
30 function commit(bytes32 leafHash, CommitMeta calldata meta) external {
31 bytes32 k = _key(meta);
32 require(commits[k].hashOrRoot == bytes32(0), "exists");
33 commits[k] = CommitRec(leafHash, meta, false);
34 emit Committed(k, leafHash, meta, false);
35 }
36
37 function revealSingle(
38 CommitMeta calldata meta,
39 bytes calldata canonicalPayloadNoNonce,
40 bytes32 nonce,
41 bytes calldata payloadBytesOrCID
42 ) external {
43 bytes32 k = _key(meta);
44 CommitRec memory rec = commits[k];
45 require(!rec.isBatch, "batch");
46 require(block.timestamp >= rec.meta.minRevealAt &&
47 block.timestamp <= rec.meta.maxRevealAt, "window");
48
49 bytes32 computed = keccak256(bytes.concat(canonicalPayloadNoNonce, nonce));
50 require(computed == leafHash, "leaf mismatch");
51 emit Revealed(k, computed, payloadBytesOrCID);
52 }
53
54 function revealLeaf(
55 CommitMeta calldata meta,
56 bytes calldata canonicalPayloadNoNonce,
57 bytes32 nonce,
58 bytes32 leafHash,
59 bytes32[] calldata proof,
60 bytes calldata payloadBytesOrCID
61 ) external {
62 bytes32 k = _key(meta);
63 CommitRec memory rec = commits[k];
64 require(rec.isBatch, "single");
65 require(block.timestamp >= rec.meta.minRevealAt &&
66 block.timestamp <= rec.meta.maxRevealAt, "window");
67
68 bytes32 computed = keccak256(bytes.concat(canonicalPayloadNoNonce, nonce));
69 require(computed == leafHash, "leaf mismatch");
70 require(MerkleProof.verify(proof, rec.hashOrRoot, leafHash), "badproof");
71 emit Revealed(k, computed, payloadBytesOrCID);
72 }
73}