kaymyg commited on
Commit
a74f5ab
·
1 Parent(s): f6c6557

Implement kaysentinel-hash (domain-separated BLAKE3 commitments, RC1 spec); update README and roadmap to reflect real progress

Browse files
README.md CHANGED
@@ -10,23 +10,42 @@ tags:
10
 
11
  A post-execution authorization framework for state-machine systems (e.g. Ethereum-class runtimes). Kaysentinel lifts execution traces into a canonical **Structural Sufficient Representation (SSR)** and evaluates policy purely over that quotient space, so authorization decisions are provably independent of *which* client (Geth, Reth, ...) produced the trace.
12
 
13
- > **Status:** conceptual specification + differential-test harness skeleton. There is not yet a working Geth/Reth extractor implementation the theorems below describe the target invariants that any implementation must satisfy, they are not machine-checked proofs.
14
 
15
  ## Repo contents
16
 
17
  - [`docs/framework.md`](docs/framework.md) — full formal spec: execution model, SSR canonical form, authorization/factorization theory, multi-client portability theorem, gating semantics, complexity model.
18
  - [`docs/differential_testing.md`](docs/differential_testing.md) — the differential testing engine design used to check byte-level convergence between a Geth-side and Reth-side SSR extractor.
19
  - [`docs/implementation_roadmap.md`](docs/implementation_roadmap.md) — concrete engineering tasks (Phase 1–3) to turn the spec into a working implementation.
 
 
 
20
  - Curated test fixtures now live in a separate dataset repo: [`Sahek/kaysentinel-fixtures`](https://huggingface.co/datasets/Sahek/kaysentinel-fixtures).
21
  - [`scripts/verify_ssr.py`](scripts/verify_ssr.py) — standalone verifier that hashes and byte-diffs two SSZ-encoded SSR outputs.
22
  - [`tests/transient_storage_case.json`](tests/transient_storage_case.json) — example test vector (EIP-1153 transient storage + reentrancy rollback).
23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  ## Roadmap
25
 
26
- Nothing below is implemented yet. See [`docs/implementation_roadmap.md`](docs/implementation_roadmap.md) for the full phased task breakdown (SSZ schema, Go/Rust extractors, CI pipeline). Short version:
27
 
28
  **Phase 1 — Canonical type system**
29
- - [ ] SSZ schema + Go/Rust serialization libraries
 
30
 
31
  **Phase 2 — Client extractors**
32
  - [ ] Geth-side extractor (`StateDB` journal hook)
@@ -36,6 +55,17 @@ Nothing below is implemented yet. See [`docs/implementation_roadmap.md`](docs/im
36
  - [x] Verification harness (`scripts/verify_ssr.py`) — comparison logic only, done
37
  - [ ] Test runner environment + GitHub Actions CI
38
 
 
 
 
 
 
 
 
 
 
 
 
39
  ## License
40
 
41
  MIT — see [`LICENSE`](LICENSE).
 
10
 
11
  A post-execution authorization framework for state-machine systems (e.g. Ethereum-class runtimes). Kaysentinel lifts execution traces into a canonical **Structural Sufficient Representation (SSR)** and evaluates policy purely over that quotient space, so authorization decisions are provably independent of *which* client (Geth, Reth, ...) produced the trace.
12
 
13
+ > **Status:** formal specification, differential-test harness skeleton, and a working Rust reference implementation of the canonical event ABI, the builder pipeline (partition → reduce → lifecycle resolution → certificate assembly), and domain-separated cryptographic commitments. There is not yet a working Geth/Reth extractor, SSZ canonical encoding, or storage-trie derivation see the roadmap below for exactly what's real vs. still open.
14
 
15
  ## Repo contents
16
 
17
  - [`docs/framework.md`](docs/framework.md) — full formal spec: execution model, SSR canonical form, authorization/factorization theory, multi-client portability theorem, gating semantics, complexity model.
18
  - [`docs/differential_testing.md`](docs/differential_testing.md) — the differential testing engine design used to check byte-level convergence between a Geth-side and Reth-side SSR extractor.
19
  - [`docs/implementation_roadmap.md`](docs/implementation_roadmap.md) — concrete engineering tasks (Phase 1–3) to turn the spec into a working implementation.
20
+ - [`docs/semantic_architecture_spec.md`](docs/semantic_architecture_spec.md) — formal stage contracts (partition → projection → canonicalization → serialization → commitment) with real implementation cross-references.
21
+ - [`docs/hash_specification.md`](docs/hash_specification.md) — normative cryptographic commitment protocol (domain-separated BLAKE3), implemented in `runtime/hash`.
22
+ - [`runtime/`](runtime/) — the Rust reference implementation. See below.
23
  - Curated test fixtures now live in a separate dataset repo: [`Sahek/kaysentinel-fixtures`](https://huggingface.co/datasets/Sahek/kaysentinel-fixtures).
24
  - [`scripts/verify_ssr.py`](scripts/verify_ssr.py) — standalone verifier that hashes and byte-diffs two SSZ-encoded SSR outputs.
25
  - [`tests/transient_storage_case.json`](tests/transient_storage_case.json) — example test vector (EIP-1153 transient storage + reentrancy rollback).
26
 
27
+ ## `runtime/` — Rust reference implementation
28
+
29
+ A Cargo workspace. Status per crate, honestly:
30
+
31
+ | Crate | Status |
32
+ | --- | --- |
33
+ | `cse` | **Done.** Canonical Semantic Event ABI: frozen event/payload types, execution context, a sequence/transaction-boundary validator. |
34
+ | `builder` | **In progress, core pipeline works.** Partitions a CSE stream into flat relational tables (`partition.rs`), reduces per-key timelines to terminal transitions (`reduce.rs`), resolves account lifecycle generations (`lifecycle/resolve.rs`, `canonicalize.rs`), and assembles canonical account certificates from real state + generation data (`lifecycle/certificate.rs`, `hydration.rs`). 21 passing unit tests across the workspace. |
35
+ | `hash` | **Core primitive done.** Domain-separated BLAKE3 commitments (`derive_commitment`), per the RC1 spec. 8 passing tests, including real (not hand-typed) pinned vectors — see `runtime/hash/vectors/candidate.json`. Not yet cross-language-verified (no Python/Go implementation exists to check against), so nothing has been promoted to `normative.json`. |
36
+ | `ssr` | Empty stub. |
37
+ | `ssz` | Empty stub — canonical encoding doesn't exist yet, so nothing has real bytes to hash yet either. |
38
+ | `verify` | Empty stub. |
39
+
40
+ Known open gaps (tracked in `docs/semantic_architecture_spec.md`): no real `AccountSnapshotSource` (prior-block state) implementation; storage roots are always `AwaitingDerivation` (no Phase 5 trie construction); access-list touches aren't tracked; several `TraceProvenance`/generation-chain fields (`frame_ordinal`, `ProvenanceMetadataDivergence`, `MalformedProvenanceChain`) are defined but not yet wired to real detection logic.
41
+
42
  ## Roadmap
43
 
44
+ See [`docs/implementation_roadmap.md`](docs/implementation_roadmap.md) for the full phased task breakdown. Short version:
45
 
46
  **Phase 1 — Canonical type system**
47
+ - [x] Canonical Semantic Event ABI (`runtime/cse`)
48
+ - [ ] SSZ schema + serialization (`runtime/ssz` — stub only)
49
 
50
  **Phase 2 — Client extractors**
51
  - [ ] Geth-side extractor (`StateDB` journal hook)
 
55
  - [x] Verification harness (`scripts/verify_ssr.py`) — comparison logic only, done
56
  - [ ] Test runner environment + GitHub Actions CI
57
 
58
+ **Phase 4 — Builder pipeline (Rust reference implementation)**
59
+ - [x] Partition + terminal projection (`runtime/builder/src/partition.rs`, `ir/timeline.rs`)
60
+ - [x] Lifecycle resolution + canonicalization (`runtime/builder/src/lifecycle/`)
61
+ - [x] Certificate hydration + assembly (`runtime/builder/src/lifecycle/{certificate,hydration}.rs`)
62
+ - [ ] Storage subtree/trie derivation (`StorageRootDeriver` trait exists, no implementation)
63
+
64
+ **Phase 5 — Cryptographic commitment**
65
+ - [x] Domain-separated BLAKE3 commitment primitive (`runtime/hash`)
66
+ - [ ] Cross-language (Python/Go) differential verification of vectors
67
+ - [ ] Wire real commitments into `VerifiedGeneration.state_table_proof_root` and `CanonicalAccountCertificate.storage_root`
68
+
69
  ## License
70
 
71
  MIT — see [`LICENSE`](LICENSE).
docs/hash_specification.md ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # KAYSentinel Hash Specification (RC1)
2
+
3
+ Normative cryptographic protocol for `kaysentinel-hash`.
4
+
5
+ **Status:** Release Candidate 1 (RC1). The Rust reference implementation exists and
6
+ is self-consistent (deterministic, domain-separated, regression-pinned by unit
7
+ tests). Cross-language verification (Python/Go) has **not** happened — no
8
+ implementations exist in those languages yet — so vectors in `vectors/candidate.json`
9
+ remain `"status": "candidate"`, not `"normative"`, per the promotion criteria below.
10
+
11
+ ## 1. Terminology
12
+
13
+ The key words "MUST", "MUST NOT", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", and
14
+ "MAY" in this document are to be interpreted as described in RFC 2119 and updated by
15
+ RFC 8174.
16
+
17
+ ## 2. Cryptographic Primitive & Configuration
18
+
19
+ * **Core Algorithm:** The protocol SHALL use the BLAKE3 algorithm. All commitments
20
+ MUST use the standard unkeyed BLAKE3 hash function. Keyed hashing, `derive_key`
21
+ mode, and extendable-output mode (XOF) are prohibited.
22
+ * **Commitment Layout:** The resulting digest is an immutable sequence of exactly 32
23
+ bytes (256 bits). No integer interpretation, byte-swapping, or endianness applies
24
+ to the final digest sequence.
25
+ * **Security Constraints:** The security architecture relies strictly on the
26
+ collision resistance and preimage resistance properties of the underlying BLAKE3
27
+ hash function.
28
+ * **Portability & Determinism:** Given identical `DomainBytes` and `CanonicalBytes`,
29
+ all conforming implementations SHALL produce identical digest bytes on every
30
+ platform, host architecture, and execution environment.
31
+
32
+ ## 3. Domain Separation Architecture
33
+
34
+ ```
35
+ HashInput := DomainBytes || CanonicalBytes
36
+ Digest := BLAKE3(HashInput)
37
+ ```
38
+
39
+ * **DomainBytes**: the raw, fixed byte sequence representing the unique protocol
40
+ constant.
41
+ * **CanonicalBytes**: the deterministic, byte-stable serialization of the underlying
42
+ data structure.
43
+ * **||**: strict byte concatenation.
44
+
45
+ Core invariants:
46
+
47
+ 1. **Structural Ordering:** domain separation SHALL precede all canonical bytes. No
48
+ protocol structure may insert additional bytes, padding, version flags,
49
+ delimiters, framing bytes, or metadata between the domain identifier and the
50
+ canonical serialization.
51
+ 2. **Infallibility by Contract:** commitment derivation is total and cannot fail once
52
+ valid canonical bytes have been produced.
53
+ 3. **No Normalization:** domain identifiers SHALL be matched strictly byte-for-byte.
54
+ Unicode normalization, case folding, locale conversion, whitespace trimming, or
55
+ character encoding transformations are prohibited.
56
+
57
+ ## 4. Canonical Encoding Separation
58
+
59
+ The canonical encoding of every protocol structure is defined independently of the
60
+ hashing function. Canonical encoding SHALL be deterministic, injective (no two
61
+ distinct logical objects serialize to identical byte sequences), and entirely
62
+ independent of the runtime environment. The hashing function operates exclusively on
63
+ raw, opaque byte sequences and is not responsible for serialization semantics.
64
+
65
+ **Status:** no canonical encoding (SSZ) exists yet — `runtime/ssz` is still an empty
66
+ stub crate. Everything hashed so far in tests/vectors is raw test bytes, not real
67
+ canonical-encoded protocol structures.
68
+
69
+ ## 5. Hash Domain Registry (Version: 1)
70
+
71
+ Future protocol versions MAY append new domain tags to this registry. Existing tags
72
+ SHALL remain permanently immutable, SHALL NOT change their underlying semantic
73
+ meaning, and SHALL NEVER be reused or recycled upon feature retirement. No two
74
+ protocol operations SHALL share the same domain byte sequence.
75
+
76
+ | Protocol Constant | Normative Value (ASCII) | Context / Usage |
77
+ | --- | --- | --- |
78
+ | `KAY_TRIE_LEAF` | `"KAY_TRIE_LEAF"` | Storage trie leaf nodes |
79
+ | `KAY_TRIE_BRANCH` | `"KAY_TRIE_BRANCH"` | Storage trie branch nodes |
80
+ | `KAY_LIFE_CERT` | `"KAY_LIFE_CERT"` | Verification certificates |
81
+ | `KAY_SSR_ROOT` | `"KAY_SSR_ROOT"` | Serialization Record roots |
82
+
83
+ ## 6. Reference Implementation
84
+
85
+ `runtime/hash/src/lib.rs` implements this specification: `Digest`, `Domain`,
86
+ `derive_commitment(domain, canonical_bytes) -> Digest`. 8 unit tests cover
87
+ determinism, domain separation, length validation, and pin real (not hand-typed)
88
+ BLAKE3 output for one vector per registered domain — see
89
+ `runtime/hash/vectors/candidate.json`.
90
+
91
+ ## 7. Vector Status & Promotion Criteria
92
+
93
+ Per the execution lifecycle, a vector is `"candidate"` once a reference
94
+ implementation computes and regression-pins it, and becomes `"normative"` only after
95
+ independent implementations in multiple languages are shown to converge on the same
96
+ digest via automated differential verification. `runtime/hash/vectors/candidate.json`
97
+ holds real, computed, Rust-self-consistent values — but since no Python or Go
98
+ implementation exists yet to check against, none have been promoted to
99
+ `vectors/normative.json`, and that file doesn't exist yet.
docs/implementation_roadmap.md CHANGED
@@ -1,6 +1,8 @@
1
  # Implementation Roadmap
2
 
3
- Concrete engineering tasks to turn the [framework spec](framework.md) and [differential testing design](differential_testing.md) into working code. Nothing here is implemented yet — see status per phase below.
 
 
4
 
5
  ## Phase 1 — Canonical Type System & Serialization
6
 
@@ -9,7 +11,20 @@ The serialization layer needs to be settled before client logic begins, to preve
9
  - [ ] **1.1 Define the strict SSZ schema.** Draft the official SSZ definitions for `CanonicalSSR` and its sub-containers. Enforce little-endian byte ordering for integers and explicit padding rules for `Uint256` (32-byte arrays).
10
  - [ ] **1.2 Implement serialization libraries.**
11
  - **Go:** integrate an SSZ library (e.g. [`fastssz`](https://github.com/ferranbt/fastssz)) to generate static encoder/decoder methods for the Go-side SSR types.
12
- - **Rust:** configure an SSZ crate (e.g. `ssz` or `lighthouse_types`) to derive encoding logic on the Rust structs.
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  ## Phase 2 — Client Extractor Modules
15
 
@@ -21,7 +36,7 @@ The serialization layer needs to be settled before client logic begins, to preve
21
 
22
  ### Reth extractor (Rust)
23
 
24
- - [ ] **3.1 Intercept execution outputs.** Inject the extraction module where `BlockExecutionResult` yields its finalized in-memory `BundleState`.
25
  - [ ] **3.2 Map BundleState structural diffs.** Map state maps to `AccountMutation` vectors; use `sort_by_key` to enforce the same lexicographic ordering as the Go side over addresses and slots.
26
 
27
  ## Phase 3 — Infrastructure & Differential Testing CI
@@ -63,14 +78,28 @@ Depends entirely on Phase 1 and Phase 2 being complete — there's no extractor
63
 
64
  See the companion dataset repo: [`Sahek/kaysentinel-fixtures`](https://huggingface.co/datasets/Sahek/kaysentinel-fixtures) for the curated Phase 3 test fixtures that exist today.
65
 
 
 
 
 
 
 
66
  ## Current status summary
67
 
68
  | Phase | Item | Status |
69
  |---|---|---|
70
  | 1 | SSZ schema definition | Not started |
71
  | 1 | Go/Rust serialization libs | Not started |
 
 
 
 
 
72
  | 2 | Geth extractor | Not started |
73
  | 2 | Reth extractor | Not started |
74
  | 3 | Verification harness (`verify_ssr.py`) | Done (comparison logic only — see script docstring) |
75
  | 3 | Test runner environment | Not started |
76
  | 3 | CI automation | Not started |
 
 
 
 
1
  # Implementation Roadmap
2
 
3
+ Concrete engineering tasks to turn the [framework spec](framework.md) and [differential testing design](differential_testing.md) into working code.
4
+
5
+ > **Update:** the Rust reference implementation (`runtime/`) now has real, tested code for the canonical event ABI and most of the builder pipeline (Phase 1.2's Rust half, partially) plus the cryptographic commitment primitive (Phase 5). Status per item below has been updated accordingly — everything not explicitly marked done is still not started.
6
 
7
  ## Phase 1 — Canonical Type System & Serialization
8
 
 
11
  - [ ] **1.1 Define the strict SSZ schema.** Draft the official SSZ definitions for `CanonicalSSR` and its sub-containers. Enforce little-endian byte ordering for integers and explicit padding rules for `Uint256` (32-byte arrays).
12
  - [ ] **1.2 Implement serialization libraries.**
13
  - **Go:** integrate an SSZ library (e.g. [`fastssz`](https://github.com/ferranbt/fastssz)) to generate static encoder/decoder methods for the Go-side SSR types.
14
+ - **Rust:** configure an SSZ crate (e.g. `ssz` or `lighthouse_types`) to derive encoding logic on the Rust structs. **Not started** — `runtime/ssz` is still an empty stub crate. The canonical event *types* that would eventually be serialized do exist (`runtime/cse`), but nothing encodes them to SSZ bytes yet.
15
+
16
+ ## Phase 1.5 — Rust Builder Pipeline (new, not in the original phase breakdown)
17
+
18
+ The event-to-certificate compiler pipeline that consumes a validated `CanonicalSemanticEvent` stream (from `runtime/cse`) and produces canonical per-account certificates. This sits logically between Phase 1 (types) and Phase 2 (extractors) — it's what a real extractor's output would eventually flow into, but it doesn't extract from a real client itself (see Phase 2 status, unchanged).
19
+
20
+ - [x] **Canonical Semantic Event ABI** (`runtime/cse`) — frozen event/payload types, execution context, sequence/transaction-boundary validation.
21
+ - [x] **Partition + terminal projection** (`runtime/builder/src/partition.rs`, `ir/timeline.rs`) — flattens an event stream into relational, `CanonicalKey`-keyed tables; reduces per-key timelines to verified terminal transitions.
22
+ - [x] **Lifecycle resolution + canonicalization** (`runtime/builder/src/lifecycle/{resolve,canonicalize}.rs`) — resolves account creation/destruction into identity "generations," with formal invariant verification (uniqueness, temporal well-formedness, chronological adjacency).
23
+ - [x] **Certificate hydration + assembly** (`runtime/builder/src/lifecycle/{certificate,hydration}.rs`) — cross-references real state facts (balance/nonce/code) to resolved generations to build `CanonicalAccountCertificate`s, with a real (if unimplemented) `AccountSnapshotSource`/`StorageRootDeriver` extension point for prior-state and Phase-5 storage-trie data.
24
+ - [ ] Storage subtree/trie derivation — `StorageRootDeriver` trait exists, no implementation. Every certificate's `storage_root` is currently `AwaitingDerivation`.
25
+ - [ ] Access-list tracking — `CsePayload::AccessListTouched` events are parsed but not recorded anywhere in the pipeline.
26
+
27
+ 21 passing unit tests across `runtime/cse` + `runtime/builder`. See `docs/semantic_architecture_spec.md` for the full stage-by-stage contract writeup and honest gap list.
28
 
29
  ## Phase 2 — Client Extractor Modules
30
 
 
36
 
37
  ### Reth extractor (Rust)
38
 
39
+ - [ ] **3.1 Intercept execution outputs.** Inject the extraction module where `BlockExecutionResult` yields its finalized in-memory `BundleState`. **Not started** — nothing in `runtime/` reads from a real Reth `BundleState` yet; the builder pipeline (Phase 1.5) consumes already-abstracted `CanonicalSemanticEvent`s, which still need to be produced by a real extractor hook.
40
  - [ ] **3.2 Map BundleState structural diffs.** Map state maps to `AccountMutation` vectors; use `sort_by_key` to enforce the same lexicographic ordering as the Go side over addresses and slots.
41
 
42
  ## Phase 3 — Infrastructure & Differential Testing CI
 
78
 
79
  See the companion dataset repo: [`Sahek/kaysentinel-fixtures`](https://huggingface.co/datasets/Sahek/kaysentinel-fixtures) for the curated Phase 3 test fixtures that exist today.
80
 
81
+ ## Phase 5 — Cryptographic Commitment (new, not in the original phase breakdown)
82
+
83
+ - [x] **Domain-separated BLAKE3 commitment primitive** (`runtime/hash`) — `derive_commitment(domain, canonical_bytes) -> Digest`, per `docs/hash_specification.md` (RC1). 8 passing tests, including real computed-and-pinned vectors per domain.
84
+ - [ ] Cross-language (Python/Go) differential verification of vectors — no Python/Go implementation exists yet, so nothing in `runtime/hash/vectors/candidate.json` has been promoted to `normative.json`.
85
+ - [ ] Wire real commitments into `VerifiedGeneration.state_table_proof_root` and `CanonicalAccountCertificate.storage_root` — both are still placeholder/`AwaitingDerivation` values in the builder pipeline.
86
+
87
  ## Current status summary
88
 
89
  | Phase | Item | Status |
90
  |---|---|---|
91
  | 1 | SSZ schema definition | Not started |
92
  | 1 | Go/Rust serialization libs | Not started |
93
+ | 1.5 | Canonical Semantic Event ABI (`runtime/cse`) | **Done** |
94
+ | 1.5 | Builder pipeline: partition + terminal projection | **Done** |
95
+ | 1.5 | Builder pipeline: lifecycle resolution + canonicalization | **Done** |
96
+ | 1.5 | Builder pipeline: certificate hydration + assembly | **Done** |
97
+ | 1.5 | Storage subtree/trie derivation | Not started (`StorageRootDeriver` trait only) |
98
  | 2 | Geth extractor | Not started |
99
  | 2 | Reth extractor | Not started |
100
  | 3 | Verification harness (`verify_ssr.py`) | Done (comparison logic only — see script docstring) |
101
  | 3 | Test runner environment | Not started |
102
  | 3 | CI automation | Not started |
103
+ | 5 | BLAKE3 commitment primitive (`runtime/hash`) | **Done** |
104
+ | 5 | Cross-language vector verification | Not started |
105
+ | 5 | Real commitments wired into builder pipeline | Not started |
runtime/Cargo.lock CHANGED
@@ -2,9 +2,65 @@
2
  # It is not intended for manual editing.
3
  version = 3
4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  [[package]]
6
  name = "kaysentinel-builder"
7
  version = "0.1.0"
 
 
 
8
 
9
  [[package]]
10
  name = "kaysentinel-cse"
@@ -13,6 +69,9 @@ version = "0.1.0"
13
  [[package]]
14
  name = "kaysentinel-hash"
15
  version = "0.1.0"
 
 
 
16
 
17
  [[package]]
18
  name = "kaysentinel-ssr"
@@ -25,3 +84,9 @@ version = "0.1.0"
25
  [[package]]
26
  name = "kaysentinel-verify"
27
  version = "0.1.0"
 
 
 
 
 
 
 
2
  # It is not intended for manual editing.
3
  version = 3
4
 
5
+ [[package]]
6
+ name = "arrayref"
7
+ version = "0.3.9"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
10
+
11
+ [[package]]
12
+ name = "arrayvec"
13
+ version = "0.7.8"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
16
+
17
+ [[package]]
18
+ name = "blake3"
19
+ version = "1.5.0"
20
+ source = "registry+https://github.com/rust-lang/crates.io-index"
21
+ checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87"
22
+ dependencies = [
23
+ "arrayref",
24
+ "arrayvec",
25
+ "cc",
26
+ "cfg-if",
27
+ "constant_time_eq",
28
+ ]
29
+
30
+ [[package]]
31
+ name = "cc"
32
+ version = "1.2.67"
33
+ source = "registry+https://github.com/rust-lang/crates.io-index"
34
+ checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38"
35
+ dependencies = [
36
+ "find-msvc-tools",
37
+ "shlex",
38
+ ]
39
+
40
+ [[package]]
41
+ name = "cfg-if"
42
+ version = "1.0.4"
43
+ source = "registry+https://github.com/rust-lang/crates.io-index"
44
+ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
45
+
46
+ [[package]]
47
+ name = "constant_time_eq"
48
+ version = "0.3.1"
49
+ source = "registry+https://github.com/rust-lang/crates.io-index"
50
+ checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
51
+
52
+ [[package]]
53
+ name = "find-msvc-tools"
54
+ version = "0.1.9"
55
+ source = "registry+https://github.com/rust-lang/crates.io-index"
56
+ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
57
+
58
  [[package]]
59
  name = "kaysentinel-builder"
60
  version = "0.1.0"
61
+ dependencies = [
62
+ "kaysentinel-cse",
63
+ ]
64
 
65
  [[package]]
66
  name = "kaysentinel-cse"
 
69
  [[package]]
70
  name = "kaysentinel-hash"
71
  version = "0.1.0"
72
+ dependencies = [
73
+ "blake3",
74
+ ]
75
 
76
  [[package]]
77
  name = "kaysentinel-ssr"
 
84
  [[package]]
85
  name = "kaysentinel-verify"
86
  version = "0.1.0"
87
+
88
+ [[package]]
89
+ name = "shlex"
90
+ version = "2.0.1"
91
+ source = "registry+https://github.com/rust-lang/crates.io-index"
92
+ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
runtime/hash/Cargo.toml CHANGED
@@ -5,3 +5,4 @@ edition.workspace = true
5
  license.workspace = true
6
 
7
  [dependencies]
 
 
5
  license.workspace = true
6
 
7
  [dependencies]
8
+ blake3 = "=1.5.0"
runtime/hash/src/lib.rs CHANGED
@@ -1 +1,210 @@
1
- //! placeholder stub for the hash crate (not yet specified in source doc)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! # Kaysentinel Hash
2
+ //!
3
+ //! Reference implementation of `SPECIFICATION.md` (Normative Cryptographic Protocol,
4
+ //! RC1): domain-separated BLAKE3 commitments over canonical byte sequences.
5
+ //!
6
+ //! `Digest := BLAKE3(DomainBytes || CanonicalBytes)` — unkeyed BLAKE3, no
7
+ //! `derive_key` mode, no XOF, no padding or framing bytes between the domain
8
+ //! identifier and the canonical serialization.
9
+
10
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
11
+ pub enum DigestError {
12
+ InvalidLength,
13
+ }
14
+
15
+ impl std::fmt::Display for DigestError {
16
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17
+ match self {
18
+ Self::InvalidLength => write!(f, "Invalid digest length: must be exactly 32 bytes"),
19
+ }
20
+ }
21
+ }
22
+
23
+ impl std::error::Error for DigestError {}
24
+
25
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
26
+ pub struct Digest([u8; 32]);
27
+
28
+ impl Digest {
29
+ /// Creates a Digest wrapper from an explicit 32-byte array.
30
+ pub const fn from_bytes(bytes: [u8; 32]) -> Self {
31
+ Self(bytes)
32
+ }
33
+
34
+ /// Returns the raw 32-byte digest.
35
+ pub const fn to_bytes(&self) -> [u8; 32] {
36
+ self.0
37
+ }
38
+
39
+ /// Lowercase hex encoding of the digest, matching the vector-file convention.
40
+ pub fn to_hex(&self) -> String {
41
+ self.0.iter().map(|b| format!("{:02x}", b)).collect()
42
+ }
43
+ }
44
+
45
+ impl AsRef<[u8]> for Digest {
46
+ fn as_ref(&self) -> &[u8] {
47
+ &self.0
48
+ }
49
+ }
50
+
51
+ impl std::borrow::Borrow<[u8]> for Digest {
52
+ fn borrow(&self) -> &[u8] {
53
+ &self.0
54
+ }
55
+ }
56
+
57
+ impl From<[u8; 32]> for Digest {
58
+ fn from(bytes: [u8; 32]) -> Self {
59
+ Self::from_bytes(bytes)
60
+ }
61
+ }
62
+
63
+ impl TryFrom<&[u8]> for Digest {
64
+ type Error = DigestError;
65
+
66
+ fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
67
+ if slice.len() == 32 {
68
+ let mut bytes = [0u8; 32];
69
+ bytes.copy_from_slice(slice);
70
+ Ok(Self::from_bytes(bytes))
71
+ } else {
72
+ Err(DigestError::InvalidLength)
73
+ }
74
+ }
75
+ }
76
+
77
+ /// Hash Domain Registry (Version: 1). Existing tags are permanently immutable once
78
+ /// published — new domains may be appended, but nothing here may be renamed,
79
+ /// reinterpreted, or recycled.
80
+ #[non_exhaustive]
81
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
82
+ pub enum Domain {
83
+ TrieLeaf,
84
+ TrieBranch,
85
+ LifeCert,
86
+ SsrRoot,
87
+ }
88
+
89
+ impl Domain {
90
+ pub const fn as_bytes(&self) -> &'static [u8] {
91
+ match self {
92
+ Domain::TrieLeaf => b"KAY_TRIE_LEAF",
93
+ Domain::TrieBranch => b"KAY_TRIE_BRANCH",
94
+ Domain::LifeCert => b"KAY_LIFE_CERT",
95
+ Domain::SsrRoot => b"KAY_SSR_ROOT",
96
+ }
97
+ }
98
+ }
99
+
100
+ /// Total function that computes the cryptographic commitment per the specification:
101
+ /// BLAKE3(DomainBytes || CanonicalBytes). Infallible once `canonical_bytes` exists —
102
+ /// there is no failure mode once you have valid input bytes.
103
+ pub fn derive_commitment(domain: Domain, canonical_bytes: &[u8]) -> Digest {
104
+ let mut hasher = blake3::Hasher::new();
105
+ hasher.update(domain.as_bytes());
106
+ hasher.update(canonical_bytes);
107
+ Digest::from_bytes(hasher.finalize().into())
108
+ }
109
+
110
+ #[cfg(test)]
111
+ mod tests {
112
+ use super::*;
113
+
114
+ #[test]
115
+ fn commitment_is_deterministic() {
116
+ let bytes = [1u8, 2, 3, 4];
117
+ let a = derive_commitment(Domain::TrieLeaf, &bytes);
118
+ let b = derive_commitment(Domain::TrieLeaf, &bytes);
119
+ assert_eq!(a, b);
120
+ }
121
+
122
+ #[test]
123
+ fn different_domains_produce_different_digests() {
124
+ let bytes = [1u8, 2, 3, 4];
125
+ let leaf = derive_commitment(Domain::TrieLeaf, &bytes);
126
+ let branch = derive_commitment(Domain::TrieBranch, &bytes);
127
+ assert_ne!(leaf, branch);
128
+ }
129
+
130
+ #[test]
131
+ fn different_canonical_bytes_produce_different_digests() {
132
+ let a = derive_commitment(Domain::LifeCert, &[1, 2, 3]);
133
+ let b = derive_commitment(Domain::LifeCert, &[1, 2, 4]);
134
+ assert_ne!(a, b);
135
+ }
136
+
137
+ #[test]
138
+ fn digest_try_from_rejects_wrong_length() {
139
+ let short = [0u8; 16];
140
+ assert_eq!(Digest::try_from(&short[..]), Err(DigestError::InvalidLength));
141
+
142
+ let exact = [0u8; 32];
143
+ assert!(Digest::try_from(&exact[..]).is_ok());
144
+ }
145
+
146
+ #[test]
147
+ fn digest_round_trips_through_bytes() {
148
+ let bytes = [0xABu8; 32];
149
+ let digest = Digest::from_bytes(bytes);
150
+ assert_eq!(digest.to_bytes(), bytes);
151
+ }
152
+
153
+ /// Computes the real BLAKE3 digest for the candidate vector in
154
+ /// `vectors/candidate.json` (domain = KAY_TRIE_LEAF, canonical_bytes = 01020304)
155
+ /// and prints it, so the actual output can be copied into `vectors/normative.json`
156
+ /// rather than trusting a hand-typed value. Run with `cargo test -- --nocapture`.
157
+ #[test]
158
+ fn print_candidate_vector_1_digest() {
159
+ let canonical_bytes = [0x01u8, 0x02, 0x03, 0x04];
160
+ let digest = derive_commitment(Domain::TrieLeaf, &canonical_bytes);
161
+ println!("candidate_vector_1 digest_hex = {}", digest.to_hex());
162
+ // 32 bytes -> 64 hex characters, always.
163
+ assert_eq!(digest.to_hex().len(), 64);
164
+ }
165
+
166
+ /// Regression pin for candidate_vector_1. The doc that specified this vector
167
+ /// claimed a digest_hex value that was actually 66 hex characters long — an
168
+ /// impossible length for a 32-byte BLAKE3 digest, so it was never real. This
169
+ /// pins the value this implementation actually computes, so any future change
170
+ /// to the hashing logic (or the blake3 dependency version) that silently
171
+ /// changes output is caught immediately.
172
+ #[test]
173
+ fn candidate_vector_1_matches_pinned_digest() {
174
+ let canonical_bytes = [0x01u8, 0x02, 0x03, 0x04];
175
+ let digest = derive_commitment(Domain::TrieLeaf, &canonical_bytes);
176
+ assert_eq!(
177
+ digest.to_hex(),
178
+ "80522501585b8ebf1831439e57add0002d22fe75521120104b000cc707b5a34a"
179
+ );
180
+ }
181
+
182
+ /// One pinned regression vector per registered domain, all against the same
183
+ /// canonical bytes — proves domain separation holds and gives every domain a
184
+ /// real, computed (not hand-typed) reference value.
185
+ #[test]
186
+ fn per_domain_vectors_are_pinned_and_distinct() {
187
+ let canonical_bytes = [0x01u8, 0x02, 0x03, 0x04];
188
+ let leaf = derive_commitment(Domain::TrieLeaf, &canonical_bytes).to_hex();
189
+ let branch = derive_commitment(Domain::TrieBranch, &canonical_bytes).to_hex();
190
+ let cert = derive_commitment(Domain::LifeCert, &canonical_bytes).to_hex();
191
+ let ssr = derive_commitment(Domain::SsrRoot, &canonical_bytes).to_hex();
192
+
193
+ println!("leaf = {leaf}");
194
+ println!("branch = {branch}");
195
+ println!("cert = {cert}");
196
+ println!("ssr = {ssr}");
197
+
198
+ assert_eq!(leaf, "80522501585b8ebf1831439e57add0002d22fe75521120104b000cc707b5a34a");
199
+ assert_eq!(branch, "51614b8a4a5f5a2b5b7b858e626cd19b710d670ebad2e0b9b296bf282b4bfe93");
200
+ assert_eq!(cert, "59fd735ed0c0923c4316ad197223aed69b6667f6a00a228aae1638a521d1f62f");
201
+ assert_eq!(ssr, "1007c1368120e75ff7f8a47d6933f1440e1a6e0fd1b5e10f8721329dd5d47617");
202
+
203
+ let all = [&leaf, &branch, &cert, &ssr];
204
+ for i in 0..all.len() {
205
+ for j in (i + 1)..all.len() {
206
+ assert_ne!(all[i], all[j], "domain separation violated between vectors {i} and {j}");
207
+ }
208
+ }
209
+ }
210
+ }
runtime/hash/vectors/candidate.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "name": "candidate_vector_1",
4
+ "domain": "KAY_TRIE_LEAF",
5
+ "canonical_hex": "01020304",
6
+ "digest_hex": "80522501585b8ebf1831439e57add0002d22fe75521120104b000cc707b5a34a",
7
+ "status": "candidate"
8
+ },
9
+ {
10
+ "name": "candidate_vector_2",
11
+ "domain": "KAY_TRIE_BRANCH",
12
+ "canonical_hex": "01020304",
13
+ "digest_hex": "51614b8a4a5f5a2b5b7b858e626cd19b710d670ebad2e0b9b296bf282b4bfe93",
14
+ "status": "candidate"
15
+ },
16
+ {
17
+ "name": "candidate_vector_3",
18
+ "domain": "KAY_LIFE_CERT",
19
+ "canonical_hex": "01020304",
20
+ "digest_hex": "59fd735ed0c0923c4316ad197223aed69b6667f6a00a228aae1638a521d1f62f",
21
+ "status": "candidate"
22
+ },
23
+ {
24
+ "name": "candidate_vector_4",
25
+ "domain": "KAY_SSR_ROOT",
26
+ "canonical_hex": "01020304",
27
+ "digest_hex": "1007c1368120e75ff7f8a47d6933f1440e1a6e0fd1b5e10f8721329dd5d47617",
28
+ "status": "candidate"
29
+ }
30
+ ]