canon

package
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 11, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

ast.go — the internal canonicalization AST and its builder.

The canonicalizer is deliberately schema-agnostic (spec §8: "it doesn't care about semantic meaning, just structure"). It therefore works on a generic tree mirroring arbitrary TOML structure, NOT on the typed pod.Spec — it must, for example, reject hand-authored `_`-prefixed keys that no typed struct would even surface.

The tree is built from BurntSushi/toml's decode into a generic map. That decode is faithful enough for canonicalization because:

  • integers arrive as int64, floats as float64, datetimes as time.Time — type identity is preserved;
  • inline tables and `[table]` headers both decode to a map, which is exactly what R11 wants (inline tables are expanded to tables);
  • array-of-tables (`[[x]]`) decodes to []map[string]interface{}, while every other array — including an array of inline-table literals — decodes to []interface{}. That type split is the sole, reliable signal distinguishing R13 array-of-tables from the R-less array-of-inline-tables construct v1 rejects.

Package canon implements konareef-toml/v1: the canonical TOML serializer and the pod-hash construction it feeds.

A pod's pod.toml may be authored in any of TOML's many equivalent spellings — key order, comments, whitespace, integer bases, string quoting, datetime precision. canon collapses all of them to one byte-exact form so that two semantically identical pods always produce the same SHA-256 pod_hash, and any later tampering changes it. This package is the Go reference implementation of the canonical TOML rule set used for pod hashing.

Public surface:

  • Canonicalize — pod.toml bytes + pod directory → canonical bytes
  • CanonicalizeWithWarnings — same, plus non-fatal advisories
  • PodHash — the SHA-256 of the canonical bytes
  • Error / Code — the stable, machine-readable failure codes
  • Warning — a non-fatal advisory

Every failure is fatal and total: on any rejection the canonicalizer returns a *canon.Error and no output. There is no partial or best-effort result — a pod either has one canonical form or none.

commitparse.go — verifier-side parser for the konareef-toml/v2 `[_commit]` trailer's `fields_root` commitment.

This is the reverse of fieldsroot.go's producer: where FieldsRoot COMPUTES the Poseidon commitment from the four manifest fields, this file READS the already-committed value straight out of a canonical-v2 manifest's final `[_commit]` section. The CL-5(a) verifier needs it to bind `[_commit].fields_root` to the proof's carried genesis lane (genesis_fields_root) without recomputing canon.FieldsRoot — the disclosed manifest is h_manifest-bound, so its committed fields_root is itself bound to the proven computation.

errors.go — fatal canonicalization failures.

Every canonicalizer rejection is fatal: no partial output is ever produced. An Error carries a stable, machine-readable Code (the strings frozen in konareef-toml/v1 spec §7) plus a human-readable Message that callers may surface but MUST NOT pattern-match on — only Code is contractually stable.

fieldsroot.go — the konareef-toml/v2 manifest field-commitment (`fields_root`), issue #3 item #4 slice 4a. The canonicalizer commits the four manifest fields the pod-step circuit consumes (declared_models, declared_tools, c_max, r_init) into a Poseidon `fields_root` the circuit can prove inclusion against (PRD 1 §5.3 Approach B), instead of parsing canonical TOML text in-circuit. The construction is byte-identical to the in-circuit inclusion gadget (4b) and reuses internal/poseidon throughout.

files.go — the synthetic `[_files]` manifest section (R15).

After the author tree, the canonicalizer appends a `[_files]` section binding the manifest hash to every other file in the pod directory. This is what defeats the swap-after-signing attack: an adversary cannot alter prompts/system.md without changing pod_hash.

The walk is intentionally strict. Symlinks are a hard failure, never a silent skip — a symlink can escape the pod directory and pull an arbitrary host file into the signed set. `.gitignore` is NOT honored: a hidden input that two publishers configure differently would let identical content produce different hashes, the exact non-determinism the canonicalizer exists to eliminate. The 1 MiB size cap is the tripwire for accidental bloat instead.

serialize.go — the deterministic byte emitter (rules R3–R14).

Serialization is a total function on the AST: given an admissible tree it produces exactly one byte sequence. Every formatting choice the spec leaves to "one canonical form" is resolved here.

Table headers are emitted in one global order: ascending over the UTF-8 byte encoding of each table's full dotted key path (R5). The emitter flattens the whole plain-table forest into that single sort rather than walking it depth-first — the two differ whenever a sibling key sorts between a parent and the parent's children (`-`, 0x2D, sorts before the `.`, 0x2E, that joins a child path), and the global sort is the canonical one.

Array-of-tables are the exception: their entries are positional, and a table nested inside an entry stays contiguous with that entry. Each entry is therefore its own emission scope with its own local sort.

Within any table, scalar (and array) fields are emitted before any sub-table header: TOML attaches every `key = value` line after a `[header]` to that header, so scalars must precede the first sub-table. A parent's dotted path is always a proper prefix of its children's, so the global sort always emits a parent header before the children that depend on it.

validate.go — the domain-rejection gates of konareef-toml/v1.

These run after TOML parsing and AST construction, before any serialization (spec §4 step 3). Each gate corresponds to a row in the spec §7 failure table. The first violation found aborts the whole canonicalization — there is no partial output and no issue list, unlike pod.Validate.

Integer range is not gated here: BurntSushi/toml decodes integers into int64 and rejects an out-of-i64-range literal at parse time, so an over-range integer surfaces as TOML_PARSE_ERROR before this file runs. The spec §7 documents that behavior; there is no separate INT_OVERFLOW code.

Index

Constants

View Source
const (
	ErrEncodingNotUTF8         = "ENCODING_NOT_UTF8"
	ErrEncodingBOMPresent      = "ENCODING_BOM_PRESENT"
	ErrWrongVersion            = "WRONG_VERSION"
	ErrTOMLParseError          = "TOML_PARSE_ERROR"
	ErrFloatNaNForbidden       = "FLOAT_NAN_FORBIDDEN"
	ErrFloatInfForbidden       = "FLOAT_INF_FORBIDDEN"
	ErrFloatOutOfRange         = "FLOAT_OUT_OF_RANGE"
	ErrFloatSubnormalForbidden = "FLOAT_SUBNORMAL_FORBIDDEN"
	ErrReservedKeyFiles        = "RESERVED_KEY_FILES"
	ErrReservedKeyUnderscore   = "RESERVED_KEY_UNDERSCORE_PREFIX"
	ErrDatetimeNotUTC          = "DATETIME_NOT_UTC"
	ErrDatetimeDateOnly        = "DATETIME_DATE_ONLY"
	ErrTimeOnly                = "TIME_ONLY"
	ErrKeyInvalidChar          = "KEY_INVALID_CHAR"
	ErrFileNotReadable         = "FILE_NOT_READABLE"
	ErrSymlinkForbidden        = "SYMLINK_FORBIDDEN"
	ErrPathInvalid             = "PATH_INVALID"
	ErrPodSizeExceeded         = "POD_SIZE_EXCEEDED"
	ErrNonRegularFile          = "NON_REGULAR_FILE_FORBIDDEN"

	// ErrArrayOfInlineTables rejects an array whose elements are inline
	// tables (`key = [{a=1}, {b=2}]`). Spec §11 implementation found
	// this TOML construct has no defined canonical form — R11 expands
	// inline tables into `[table]` headers, but an array *element*
	// cannot become a table header. Rather than invent an ambiguous
	// form, v1 rejects it. konareef pod schemas never produce it; all
	// repeating structures use array-of-tables (`[[...]]`).
	ErrArrayOfInlineTables = "ARRAY_OF_INLINE_TABLES_FORBIDDEN"
)

Error codes — stable strings, konareef-toml/v1 spec §7. These MUST NOT change once the spec is locked; a verifier in any language keys off these exact bytes.

View Source
const (
	MaxModels = 16
	MaxTools  = 32
)

MaxModels / MaxTools are the fixed-max fields_root tree leaf counts, equal to the circuit's PRD 1 §7 v1 input caps (crates/konareef-circuit circuit.rs MAX_MODELS=16 / MAX_TOOLS=32). The trees are always padded to these counts so the in-circuit inclusion proof has a constant depth (vkey-stable).

View Source
const WarnPodSizeLarge = "pod-size-large"

WarnPodSizeLarge is raised when the total pod size exceeds the R15 advisory threshold (100 KiB) while staying within the 1 MiB hard cap. It flags a pod that is unusually heavy — a likely sign of accidentally vendored content — without rejecting it.

Variables

This section is empty.

Functions

func Canonicalize

func Canonicalize(input []byte, dir string) ([]byte, error)

Canonicalize converts a pod.toml document into its konareef-toml/v1 canonical byte form. It is the spec §11 entry point and discards any non-fatal warnings; use CanonicalizeWithWarnings to receive them.

input is the raw pod.toml bytes. dir is the pod directory whose other files are enumerated, hashed, and bound into the synthetic `[_files]` section (R15); the root pod.toml within dir is excluded from that walk since it is the document being canonicalized.

On success the returned bytes begin with `#!konareef-toml/v1\n` and end with exactly one newline. On any rejection the error is a *canon.Error whose Code is one of the stable spec §7 identifiers; no output is produced.

func Code

func Code(err error) string

Code extracts the stable error code from err. It returns "" when err is nil or is not a *canon.Error — letting callers branch on the canonicalizer's documented failure modes without string matching.

func FieldsRoot

func FieldsRoot(models, tools []string, cMax uint64, rInit [32]byte) ([32]byte, error)

FieldsRoot computes the konareef-toml/v2 fields_root commitment:

fields_root = InternalHash( InternalHash(models_root, tools_root),
                            InternalHash(leaf(c_max), leaf(r_init)) )

models_root / tools_root are fixed-max (16 / 32) Z-padded balanced Poseidon trees, sorted ascending by canonical UTF-8 NAME bytes with duplicates rejected. leaf(c_max) and leaf(r_init) are LeafHash over the directly-packed fixed-width field elements (c_max as an 8-byte LE u64 in a 32-byte word; r_init as its 32-byte LE field element). Returns an error if a list exceeds its cap, contains duplicates, or if r_init is a non-canonical field element.

AMENDED 2026-07-05 — tools_root leaves commit the tool digest (decision 2026-07-05-konareef-digest-tool-ids, PRD 1 §5.3 amendment):

leaf(model_j) = LeafHash(RecordToField(utf8(model_j)))          UNCHANGED
leaf(tool_j)  = LeafHash(RecordToField(tool_id_digest_j))       tool_id_digest_j = SHA-256(utf8(tool_j))

tools_root leaves now fold tool_id_digest_j = SHA-256(UTF-8 NFC name bytes), not the name bytes directly, so they agree digest-to-digest with the §5.4 T_log record's tool_id_digest field (Stage-2 tool_incl parity, paygate-zk crates/konareef-circuit/src/gadgets/troot_sha.rs tool_id_digest). models_root leaves are UNCHANGED — m_id does not appear in per-record encodings, so there is no record-side digest to keep parity with. Sort order for both trees is unaffected (tools are still sorted/deduped by NAME, since declared_tools is a name array) — only the tool leaf PREIMAGE changes.

func ParseCommitFieldsRoot

func ParseCommitFieldsRoot(manifest []byte) ([32]byte, error)

ParseCommitFieldsRoot extracts the 32-byte fields_root commitment from a canonical konareef-toml/v2 manifest's `[_commit]` trailer.

Input:

manifest — the canonical-v2 manifest bytes (the same h_manifest-bound
bytes the verifier discloses). MUST start with the `#!konareef-toml/v2`
magic line and end with a `[_commit]` section whose sole key is
`fields_root = "poseidon:<64 lowercase hex>"`.

Output:

[32]byte — the 32 little-endian bytes the 64 hex chars decode to
(the same orientation as genesis_fields_root =
fq_to_le_bytes(witness.fields_root)), for a byte-for-byte compare.
error — non-nil (fail-closed) on any of: missing/wrong v2 magic
prefix; no `[_commit]` section; missing/duplicate/malformed
`fields_root` line; missing `poseidon:` prefix; hex not exactly 64
lowercase-hex chars; bad hex. On error the returned array is the
zero value and MUST NOT be used.

The function is deliberately strict and simple: in v1 of the format the `[_commit]` section is the FINAL section and `fields_root` is its only key, so a line-oriented scan suffices.

func PodHash

func PodHash(input []byte, dir string) ([32]byte, error)

PodHash returns the SHA-256 of the canonical form of input. It is the identity bound by a publisher's signature. Any error is the *canon.Error that Canonicalize would return for the same arguments.

Types

type Error

type Error struct {
	Code    string
	Message string
}

Error is a fatal canonicalization failure.

Code is one of the constants below — a stable identifier safe for machine dispatch and golden-vector expected.error files. Message is a human-readable elaboration and may change between releases.

func (*Error) Error

func (e *Error) Error() string

Error renders the failure as "CODE: message", or just "CODE" when no message is attached.

type Warning

type Warning struct {
	Code    string
	Message string
}

Warning is a non-fatal advisory raised during canonicalization. Unlike an Error it never aborts the process: canonical output is still produced and the pod_hash is unaffected. Warnings surface only through CanonicalizeWithWarnings; the spec-mandated Canonicalize discards them.

func CanonicalizeWithWarnings

func CanonicalizeWithWarnings(input []byte, dir string) ([]byte, []Warning, error)

CanonicalizeWithWarnings is Canonicalize plus the non-fatal advisory channel. The byte output and any error are identical to what Canonicalize returns for the same arguments; the extra return is the slice of Warnings raised during the run (nil when there are none). Warnings never affect the canonical bytes or the pod_hash.

Directories

Path Synopsis
Command floatparity generates a deterministic sweep of IEEE-754 doubles in the konareef-toml/v1 R8 range [0.0, 1.0e6], formats each through the Go canonical float rule, and emits the set as JSON for cross-implementation comparison against Erlang.
Command floatparity generates a deterministic sweep of IEEE-754 doubles in the konareef-toml/v1 R8 range [0.0, 1.0e6], formats each through the Go canonical float rule, and emits the set as JSON for cross-implementation comparison against Erlang.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL