The Principia Machina/TPC-PY/tpc digest.py: Difference between revisions
Appearance
TPC-PY source tpc_digest.py (0.1.0), SHA-256 43baf0ec8675622b; conformed TPS 0.3.0; PROPOSED, not declared (CO-8) |
m 1 revision imported |
(No difference)
| |
Revision as of 04:37, 28 September 2026
# -*- 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)