{
  "$schema": "https://docs.nukez.xyz/schemas/nukez.spec.v1.schema.json",
  "name": "nukez-merkle-v1",
  "schema_version": "1.0",
  "title": "Nukez Merkle V1 — Attestation & Verification Spec",
  "status": "normative",
  "canonical_doc": "https://docs.nukez.xyz/docs/verify/merkle-v1",
  "machine_readable_url": "https://docs.nukez.xyz/specs/nukez-merkle-v1.json",
  "identity": {
    "hash": "SHA-256",
    "encoding": "UTF-8",
    "digest_format": "lowercase hex",
    "root_format": "sha256:<64 lowercase hex>"
  },
  "file_entry_schema": {
    "filename": "string",
    "size_bytes": "integer >= 0",
    "content_hash": "sha256:<64 lowercase hex> OR <64 lowercase hex>"
  },
  "normalization": {
    "content_hash": "If content_hash starts with 'sha256:', strip that prefix before Merkle leaf hashing. For byte-level file verification, SHA-256 the raw downloaded bytes and compare to content_hash after normalizing both sides to the same prefix format.",
    "tree_math": "Tree math uses bare 64-character lowercase hex strings.",
    "published_values": "Published roots and result hashes are represented as 'sha256:<hex>'."
  },
  "algorithm": {
    "ordering": "Sort file entries lexicographically by filename (raw Unicode codepoint order over UTF-8) before computing leaves.",
    "leaf_formula": "SHA256('{filename}:{size_bytes}:{content_hash_without_sha256_prefix}'.encode('utf-8')).hexdigest()",
    "parent_formula": "SHA256((left_hex + right_hex).encode('utf-8')).hexdigest()",
    "parent_note": "Concatenation is over hex strings (not raw bytes), then UTF-8 encoded and hashed.",
    "tree_direction": "bottom-up",
    "pairing": "Pair nodes left-to-right at each level.",
    "odd_node_rule": "If a level has an odd number of nodes, duplicate the final node.",
    "single_file_rule": "A single-file tree root IS that file's leaf hash.",
    "empty_file_rule": "Empty file lists are INVALID for Nukez attestations.",
    "pseudocode_python": "content_hash = content_hash.removeprefix('sha256:')\nleaf_material = f'{filename}:{size_bytes}:{content_hash}'\nleaf_hash = sha256(leaf_material.encode('utf-8')).hexdigest()"
  },
  "attestation_object_schema": {
    "receipt_id": "string",
    "locker_id": "string",
    "result_hash": "sha256:<64 lowercase hex>",
    "merkle_root": "sha256:<64 lowercase hex>",
    "manifest_signature": "string",
    "file_count": "integer",
    "total_bytes": "integer",
    "files": "array of file entries sorted by filename",
    "attestation_status": "pending | computed | complete",
    "attested_at": "ISO-8601 UTC timestamp",
    "schema_version": "1.0",
    "switchboard_slot": "integer | null",
    "switchboard_tx": "string | null",
    "switchboard_feed": "string | null"
  },
  "result_hash": {
    "description": "Deterministic digest over the locker's canonicalized manifest summary. NOT the merkle root.",
    "manifest_summary_fields": ["locker_id", "files"],
    "file_fields": ["filename", "size_bytes", "content_hash"],
    "canonical_json": {
      "separators": [",", ":"],
      "sort_keys": true,
      "ensure_ascii": false,
      "rules": [
        "Keys sorted alphabetically at every level",
        "No whitespace between elements",
        "Separators ',' and ':' without spaces",
        "ensure_ascii=False (UTF-8 preserved as-is)"
      ]
    },
    "pseudocode": "manifest_summary = {'locker_id': locker_id, 'files': sorted([{'filename': f.filename, 'size_bytes': f.size_bytes, 'content_hash': f.content_hash} for f in files], key=lambda e: e['filename'])}\ncanonical_json = json.dumps(manifest_summary, separators=(',', ':'), sort_keys=True, ensure_ascii=False)\nresult_hash = 'sha256:' + sha256(canonical_json.encode('utf-8')).hexdigest()"
  },
  "att_code": {
    "authoritative": false,
    "purpose": "Compact oracle/display value. NOT a security primitive. Suitable for UIs and on-chain oracle feeds where a full 256-bit hash is impractical.",
    "derived_from": "result_hash (NOT merkle_root)",
    "formula": "int(result_hash_without_sha256_prefix[:12], 16) % 1000000000",
    "pseudocode": "def att_code_from_hash(h):\n    h = h.removeprefix('sha256:')\n    return int(h[:12], 16) % 1_000_000_000",
    "authoritative_values": ["result_hash", "merkle_root", "files", "on_chain_anchors"]
  },
  "inclusion_proof_schema": {
    "receipt_id": "string",
    "filename": "string",
    "leaf_hash": "<64 lowercase hex>",
    "leaf_index": "integer",
    "merkle_root": "sha256:<64 lowercase hex>",
    "proof": [
      {
        "hash": "<64 lowercase hex>",
        "position": "left | right"
      }
    ],
    "tree_depth": "integer",
    "file_count": "integer",
    "file_entry": {
      "filename": "string",
      "size_bytes": "integer",
      "content_hash": "string"
    },
    "schema_version": "1.0"
  },
  "proof_verification": {
    "steps": [
      "Compute the leaf from file_entry using the Merkle leaf formula.",
      "For each proof step, strip 'sha256:' from step.hash if present.",
      "If position is 'left', current = SHA256((sibling + current).encode('utf-8')).hexdigest().",
      "If position is 'right', current = SHA256((current + sibling).encode('utf-8')).hexdigest().",
      "The final current value, prefixed with 'sha256:', must equal merkle_root."
    ],
    "position_semantics": {
      "left": "sibling sits on the LEFT of current → hash(sibling || current)",
      "right": "sibling sits on the RIGHT of current → hash(current || sibling)"
    },
    "pseudocode": "current = leaf_hash\nfor step in proof:\n    sibling = step['hash'].removeprefix('sha256:')\n    if step['position'] == 'left':\n        current = sha256((sibling + current).encode('utf-8')).hexdigest()\n    else:\n        current = sha256((current + sibling).encode('utf-8')).hexdigest()\nassert 'sha256:' + current == merkle_root"
  },
  "public_endpoints": {
    "verification_bundle": "GET /v1/storage/verification-bundle?receipt_id={receipt_id}",
    "merkle_proof": "GET /v1/storage/merkle-proof?receipt_id={receipt_id}&filename={filename}",
    "verify_storage_get": "GET /v1/storage/verify?receipt_id={receipt_id}",
    "verify_storage_post": "POST /v1/storage/verify with {\"receipt_id\":\"...\"}",
    "storage_attest": "POST /v1/storage/attest?receipt_id={receipt_id}",
    "attest_code": "GET /v1/attest-code?receipt_id={receipt_id}",
    "receipt": "GET /v1/receipts/{receipt_id}",
    "receipt_verify": "GET /v1/receipts/{receipt_id}/verify"
  },
  "on_chain_anchors": {
    "solana_switchboard": {
      "pull_feed": "Stores the oracle att_code (compact display value, not authoritative).",
      "spl_memo": {
        "purpose": "Stores authoritative metadata in a Solana SPL Memo instruction.",
        "fields": ["schema", "receipt_id", "merkle_root", "file_count", "attested_at"],
        "authoritative": true
      }
    },
    "monad": {
      "receipt_id_encoding": "bytes16(keccak256(receipt_id_string))",
      "verify_merkle_returns": ["merkleRoot", "solanaSlot", "fileCount", "attestedAt"]
    }
  },
  "test_vectors": [
    {
      "name": "three-file odd-node tree",
      "files": [
        {
          "filename": "a.txt",
          "size_bytes": 3,
          "content_hash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
        },
        {
          "filename": "b.txt",
          "size_bytes": 5,
          "content_hash": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
        },
        {
          "filename": "c.txt",
          "size_bytes": 7,
          "content_hash": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
        }
      ],
      "leaf_hashes": [
        "91481cbebb6c2f6438ed263b130212193ef908a9864c2b9b77d511bd07072879",
        "7c40d39c9c1ff4c390d418fb405744507ec2edbbafe0e560b2a19389b99af722",
        "7ed8fb8628d67677c2915c0640a8511775de14907f6d7fd6fcf28a8c255162c1"
      ],
      "merkle_root": "sha256:a80128f3298c7b6bf0b894576066d61a1e270d8bf4638d01ddd6d8e626f45528",
      "proof_for_b_txt": [
        {
          "hash": "91481cbebb6c2f6438ed263b130212193ef908a9864c2b9b77d511bd07072879",
          "position": "left"
        },
        {
          "hash": "539d42382ade0da0fe370b9f86b80739b31db6f06ac8a482ef1f7390251f6262",
          "position": "right"
        }
      ]
    }
  ],
  "reference_implementation": {
    "canonical_doc": "https://docs.nukez.xyz/docs/verify/merkle-v1",
    "note": "Python and JavaScript reference implementations are published on the canonical doc page."
  },
  "conformance": [
    "Produces the exact leaves from test_vectors[0].leaf_hashes.",
    "Produces the exact root from test_vectors[0].merkle_root.",
    "Produces the exact proof from test_vectors[0].proof_for_b_txt.",
    "Rejects empty file lists with a clear error.",
    "Treats att_code as a display value only, never as a security primitive.",
    "Applies the leaf-duplication rule on odd-count tree levels."
  ]
}
