{
  "spec": "touchstone-print-verification",
  "version": 1,
  "what_it_proves": "That a print's published JSON existed, byte for byte in its canonical form, no later than the block time a chain's TouchstoneAttestation contract recorded for its hash. It does not prove the print's numbers are right: the contract never computes or checks a print.",
  "print": {
    "raw_url_template": "https://raw.githubusercontent.com/petrkrulis2022/dated-siu-inference/main/data/prints/{print_id}.json",
    "index_url": "https://raw.githubusercontent.com/petrkrulis2022/dated-siu-inference/main/data/prints/index.json",
    "page_url_template": "https://prints.touchstoneassay.com/prints/{print_id}"
  },
  "body_hash": {
    "remove_fields": [
      "signature",
      "public_key",
      "anchor",
      "superseded_by",
      "correction_notes"
    ],
    "canonicalisation": "RFC 8785 JSON Canonicalization Scheme (JCS) of the remaining object",
    "hash": "keccak256 (the original Keccak padding Ethereum uses, not NIST SHA3-256) of the UTF-8 bytes of the canonical text",
    "output": "0x followed by 64 lowercase hexadecimal digits"
  },
  "signature": {
    "scheme": "secp256k1, a 64-byte compact signature over the 32-byte body hash with no further hashing (prehash false)",
    "public_key": "the print's public_key field, a compressed secp256k1 key",
    "expected_signer": "keccak256 of the uncompressed public key, last 20 bytes, must equal each chain's publisher address below. The signature carries no recovery id, so try both and accept if either recovers that address."
  },
  "anchor_field": {
    "location": "The print's `anchor` object. It describes the print's first chain (base-sepolia): chain, status, tx_hash, posted_at. Each further chain is an entry of `anchor.additional_anchors`, an array of {chain, status, tx_hash?, posted_at?, notes?}.",
    "status_values": [
      "anchored",
      "already-anchored",
      "failed",
      "skipped"
    ],
    "meaning": "anchored: tx_hash is the transaction that recorded the hash. already-anchored: the hash was already recorded by an earlier run and no transaction is recorded here. failed or skipped (further chains only): the attempt did not land and `notes` says why; the print is valid without it.",
    "hash_recipe_unchanged": "`anchor` is one of the five removed fields, so adding or changing anchor entries never changes the body hash."
  },
  "chains": [
    {
      "slug": "base-sepolia",
      "name": "Base Sepolia",
      "chain_id": 84532,
      "explorer": "https://base-sepolia.blockscout.com",
      "attestation_contract": "0xF60701793eD168ffd6e818e1DCcb600393297190",
      "contract_url": "https://base-sepolia.blockscout.com/address/0xF60701793eD168ffd6e818e1DCcb600393297190",
      "publisher": "0x284ff2F8605Ff8AFeDa6959B856Bb7E6d48f845a",
      "publisher_immutable": true,
      "anchoring": "live: every print is anchored here",
      "abi": [
        "function publisher() view returns (address)",
        "function postedAt(bytes32 bodyHash) view returns (uint64)",
        "function isPosted(bytes32 bodyHash) view returns (bool)",
        "event PrintPosted(bytes32 indexed bodyHash, string version, uint64 timestamp)"
      ]
    },
    {
      "slug": "arc-testnet",
      "name": "Arc Testnet",
      "chain_id": 5042002,
      "explorer": "https://testnet.arcscan.app",
      "attestation_contract": "0x12b886b043feABc3d90bBae3ae206d22b208160d",
      "contract_url": "https://testnet.arcscan.app/address/0x12b886b043feABc3d90bBae3ae206d22b208160d",
      "publisher": "0x284ff2F8605Ff8AFeDa6959B856Bb7E6d48f845a",
      "publisher_immutable": true,
      "anchoring": "live: prints dated 2026-10-15 and later are anchored here on a best-effort basis (a print may record \"failed\" or \"skipped\" for this chain; it is still valid)",
      "abi": [
        "function publisher() view returns (address)",
        "function postedAt(bytes32 bodyHash) view returns (uint64)",
        "function isPosted(bytes32 bodyHash) view returns (bool)",
        "event PrintPosted(bytes32 indexed bodyHash, string version, uint64 timestamp)"
      ]
    }
  ],
  "steps": [
    "1. GET the print JSON (print.raw_url_template).",
    "2. Remove body_hash.remove_fields. Keep every other field exactly as published.",
    "3. Canonicalise the rest as JCS, then take keccak256 of its UTF-8 bytes. That is the body hash.",
    "4. Optional: check the signature as described under signature.",
    "5. For each chain in which the print has an anchored entry (anchor, then each of anchor.additional_anchors, each naming chain and tx_hash): GET {explorer}/api/v2/transactions/{tx_hash}/logs. Find the log whose address equals that chain's attestation_contract and whose decoded PrintPosted event has bodyHash equal to yours. Its timestamp parameter is the block time (Unix seconds) at which the hash was first recorded.",
    "6. Alternatively call postedAt(bodyHash) on the contract through any RPC for that chain: a non-zero value is the same timestamp, zero means never anchored."
  ],
  "run_it_for_you": {
    "script_url": "https://prints.touchstoneassay.com/verify-print.mjs",
    "command": "curl -O https://prints.touchstoneassay.com/verify-print.mjs && node verify-print.mjs {print_id}",
    "requirements": "Node 18 or later. No npm install, no keys. Checks every chain the print carries. Exit code 0 only if the hash was computed, at least one anchor carrying it was found, and no anchor the print claims was missing or different; a further chain recorded as failed or skipped is printed, not an error.",
    "does_not_check": "the signature"
  },
  "worked_example": {
    "print_id": "2026-10-15",
    "body_hash": "0x7bf8c36bac70357a451445671182979e4d1cea279bc2e26c6c21fbdbb411408c",
    "anchor": {
      "chain": "base-sepolia",
      "tx_hash": "0x7780a80a4f2d42876296fce988ac11e36c907439ed789a04aace0b25e3f1976a"
    },
    "additional_anchors": [
      {
        "chain": "arc-testnet",
        "tx_hash": "0xe722f14f28c11c92e656fa0a4345208db390f6640159cec3001092eedd464ea8"
      }
    ]
  }
}
