Jump to content

The Principia Machina/TPC-PY/tpc digest.py

From The Invariance Itself
Revision as of 04:37, 28 September 2026 by BFP Rauch (talk | contribs) (1 revision imported)
# -*- coding: utf-8 -*-
"""
TPC-PY --- digest primitive and the D(TFK) combination rule
===========================================================
Discharges the two OWED items in TPS 2.5.3 at the primitive + combination
level (C-02 the convention, C-03 the cross-implementation hash tests):

  OWED (C-02): the combination rule for D(TFK) over the three zone digests,
               fixed order, fixed algorithm, stated once, so independent
               verifiers agree.
  OWED (C-03): cross-implementation hash tests, since serialization drift
               is the known failure of hash chains.

STATUS: RATIFIED (decision D97.1, 2026-09-22). The convention is fixed and
digest VALUES are VERIFIED. Its text still lands in TPS 2.5 at revision 5
(owed spec edit). Standard-library only; Python 3.7+; Windows/Linux.

WHAT IS FIXED HERE (the proposal)
  ALG            SHA-256 (agrees with TPS 2.5.4 chain-link hash).
  OUTPUT         lowercase hex, 64 chars.
  ENCODING       a drift-proof, self-delimiting netstring of the ordered
                 fields, so no whitespace, delimiter, or number formatting
                 can drift between residences:
                     enc([b1..bn]) = concat_i( decimal(bytelen(bi)) + ":" + bi )
                 Each field is presented as raw UTF-8 bytes (a string) or as
                 base-10 ASCII (an integer). SHA-256 is taken over enc(...).
  FIELD ORDER    fixed per producer (the caller passes fields in TPS order).
  D(TFK)         combine_digests([D(TPK), D(TrM), D(TMS)]) --- exactly that
                 order (TPS 2.5.3). D(TrM) itself is header-then-entries in
                 fixed order (TPS 2.5.9 / 2.3.10); that per-zone field list
                 is produced by the zone serializer, still owed with the
                 zone schemas (2.2/2.3/2.4) and NOT fixed in this increment.

WHY NETSTRING. A hash chain fails on serialization drift. A self-delimiting
length-prefixed encoding removes every drift source JSON leaves open
(key order, insignificant whitespace, float/number formatting, escaping):
there is exactly one byte sequence for a given ordered field list, and it
is identical in Python and PHP.
"""

import hashlib

TPC_DIGEST_VERSION = "0.1.0"
TPC_DIGEST_STATUS = "RATIFIED (D97.1, 2026-09-22); values VERIFIED"


def _enc(byte_items):
    """The netstring encoding of an ordered list of byte-strings."""
    out = bytearray()
    for b in byte_items:
        out += str(len(b)).encode("ascii")
        out += b":"
        out += b
    return bytes(out)


def _as_bytes(field):
    """A field's canonical bytes: str -> UTF-8; int (not bool) -> base-10 ASCII."""
    if isinstance(field, bool):
        raise TypeError("digest field must not be bool; use an explicit token")
    if isinstance(field, str):
        return field.encode("utf-8")
    if isinstance(field, int):
        return str(field).encode("ascii")
    if isinstance(field, (bytes, bytearray)):
        return bytes(field)
    raise TypeError("digest field: unsupported type %r" % type(field))


def preimage_fields(fields):
    """The exact bytes hashed by digest_fields --- the auditable artifact.
    Returned as bytes; for ASCII content it prints as the netstring."""
    return _enc([_as_bytes(f) for f in fields])


def digest_fields(fields):
    """SHA-256 over the netstring of the ordered fields; lowercase hex.
    Use for a record's content digest and for each zone once its field
    order is fixed (the caller supplies fields in TPS order)."""
    return hashlib.sha256(preimage_fields(fields)).hexdigest()


def preimage_digests(hex_digests):
    """The exact bytes hashed by combine_digests (auditable)."""
    for h in hex_digests:
        if not _is_hex64(h):
            raise ValueError("combine: each input must be a 64-char lowercase hex digest")
    return _enc([h.encode("ascii") for h in hex_digests])


def combine_digests(hex_digests):
    """SHA-256 over the netstring of an ordered list of hex digests; the
    fixed-order, fixed-algorithm combination rule (TPS 2.5.3 C-02)."""
    return hashlib.sha256(preimage_digests(hex_digests)).hexdigest()


def d_tfk(d_tpk, d_trm, d_tms):
    """D(TFK) = combine_digests([D(TPK), D(TrM), D(TMS)]) --- TPS 2.5.3.
    The container binds to D(TFK)."""
    return combine_digests([d_tpk, d_trm, d_tms])


def _is_hex64(s):
    if not isinstance(s, str) or len(s) != 64:
        return False
    return all(c in "0123456789abcdef" for c in s)