JCR-1 — the **JouleClaw Receipt Specification** — is the propagation
wedge the 5-lens cross-talk audit named as the single highest-leverage
positioning move. A signed, per-resolution thermodynamic receipt
third-party-verifiable with the spec, the public key, and standard
CBOR + COSE tooling. No JouleClaw dependency required to *check* a
receipt; this crate is needed only to *mint* one.
This commit lands the complete propagation package — spec, suite,
producer, two independent verifiers — as a single workspace crate.
## What's in the crate
```
crates/jouleclaw-jcr1/
├── README.md
├── run.sh ← Rust mints, Python + Go each verify
├── docs/
│ ├── JCR-1-spec.md ← the receipt standard (CC-BY-4.0)
│ └── JCR-1-v1.1-intent.md ← additive proposal: intent in signed core
├── conformance/
│ ├── README.md
│ ├── vectors.json ← 10 signed canonical vectors
│ ├── test-keys.json ← published TEST keys
│ ├── verify.py ← reference verifier (cbor2 + cryptography)
│ └── verify.go ← independent Go verifier (stdlib only)
├── examples/
│ └── jcr1_mint.rs ← Rust producer: read energy → sign → emit
└── src/lib.rs ← det-CBOR encoder, COSE_Sign1, HMAC, projection
```
## Surface (Rust)
- `canonical_cbor(&Value)` — RFC 8949 §4.2.1 length-first-then-bytewise
key ordering, no floats/bools/nulls. Matches the Python
`cbor2.dumps(canonical=True)` and the Go from-scratch encoder
byte-for-byte across all 10 conformance vectors.
- `to_jcr1_wire(&Receipt) -> Value` — projects a Rust `Receipt` to
the JCR-1 wire form: `CascadeTier::L0Cache` becomes wire tag `"L0"`,
`Provenance` renders PascalCase, wave-8 fields `model_id` + `intent`
are intentionally dropped (deferred to JCR-1 v1.1 — see
`docs/JCR-1-v1.1-intent.md`).
- `signed_core_bytes(&Value)` — drops `token_attributions` + null
fields per JCR-1 §3.1, then canonically encodes.
- `canonical_signed_core(&Receipt)` — convenience wrapping both.
- `cose_sign1_envelope` / `verify_cose_sign1` — COSE_Sign1 (RFC 9052)
envelope with EdDSA / Ed25519 only. Build the protected header,
Sig_structure, signature; verify by parsing the tagged CBOR,
re-building Sig_structure, checking kid == expected_steward, and
Ed25519-verifying.
- `hmac_tool_hex` — gateway tool-receipt HMAC per JCR-1 §5
(NUL-delimited canonical payload, HMAC-SHA-256, lowercase hex).
HMAC implemented inline (30 LOC) since `hmac = 0.12` doesn't
satisfy the workspace's `sha2 = 0.11` CoreProxy bound.
- `check_honesty(&Value)` — JCR-1 §6 step 5: worst-counter rule,
joule sum vs tools_touched, mJ resolution floor for non-HwShunt.
- `VECTORS_JSON` / `TEST_KEYS_JSON` — the 10 vectors and test keys
embedded via `include_str!` so `cargo test` re-verifies the suite
on every workspace run.
## The decisive test: byte-equal re-encode across three encoders
`vector_signed_cores_re_encode_byte_identical` re-encodes each of
the 10 vectors' receipts via the Rust canonical encoder and asserts
the bytes equal the suite's `signed_core_cbor_hex` — which was
produced by Python's `cbor2.dumps(canonical=True)` and independently
re-produced by the Go verifier's hand-written encoder.
Three encoders, three languages, byte-for-byte identical output on
all 10 vectors — that is what makes the spec the source of truth.
## V5 ↔ V10 the promotion-collapse demo
`vector_5_and_10_share_input_hash_promotion_collapse` asserts the
two vectors share the same `input_hash` and that V5 (L3, 2,500,000
µJ) → V10 (L1, 9 µJ) is a ≥100,000× collapse. The "yang→yin"
turnover claim made auditable in a single diff.
## Tamper detection
`tampering_with_joules_uj_is_caught_by_re_encode` proves an
understated-joules forgery produces different canonical bytes —
matching the negative-test surface documented in the Python and Go
README. The 4 failure modes (re-encode mismatch, payload mismatch,
HMAC mismatch, Ed25519 mismatch) all surface different errors.
## Quickstart
`./crates/jouleclaw-jcr1/run.sh` runs the full demo: Rust mints,
Python verifier accepts the 10 canonical vectors, Go verifier
accepts the same 10 vectors, then BOTH verifiers accept the
freshly Rust-minted receipt.
## Honest scope (v0.1)
- JCR-1 v1 wire form only. Wave-8 `model_id` + `intent` are NOT in
the signed core; the additive v1.1 proposal in
`docs/JCR-1-v1.1-intent.md` describes the path.
- Ed25519 (`alg = -8`) only. ES256 (`alg = -7`) permitted by the
spec but not implemented here — `verify_cose_sign1` rejects other
algs with `UnsupportedAlg`.
- Key transparency / rotation is out of scope; KERI AID rotation is
recommended by §4 but resolution is the consumer's job.
- The 10 vectors are TOY joule values exercising every spec path.
Real evidence requires RAPL/NVML/IOReport cross-validated against
Zeus — see strategy-and-landscape.md from the source drop.
## Lineage
This commit ingests a May 31 conversation drop at
`joule-claw-web/notes-05312026/` into the open-standards workspace,
restructured per the 4-move plan: (1) adopt the 10 vectors as
fixtures, (2) decide on `steward` + adopt det-CBOR, (3) propose
v1.1 with `intent`, (4) move the producer + verifiers into the
workspace. All four closed in two commits (b39fe21 + this one).
Tests: 25 lib + 1 example smoke + 10 Python + 10 Go = the full suite
across three languages with byte-identical canonical CBOR.
Workspace regression: 2,427 lib tests pass, 0 failures.
107 lines
4.2 KiB
Python
107 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
JCR-1 reference verifier.
|
|
|
|
Independent of any JouleClaw code. Reads jcr1-vectors.json + jcr1-test-keys.json
|
|
and verifies each vector against the JCR-1 spec:
|
|
1 structural / re-encode determinism (RFC 8949 4.2.1 canonical CBOR)
|
|
2 COSE_Sign1 envelope signature (RFC 9052, EdDSA/Ed25519)
|
|
3 gateway tool-receipt HMAC-SHA-256
|
|
4 honesty consistency (worst-counter rule, joule sum, mJ resolution floor)
|
|
|
|
A second implementation in any language is "vector-conformant" if it reproduces
|
|
these PASS results. Deps: cbor2, cryptography (both pip-installable).
|
|
|
|
Usage: python3 verify.py [vectors.json] [test-keys.json]
|
|
"""
|
|
import sys, json, hmac, hashlib
|
|
import cbor2
|
|
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
|
|
|
|
PROV_ORDER = {"HwShunt": 3, "ModelBased": 2, "Estimator": 1}
|
|
TOKEN_FIELD = "token_attributions"
|
|
|
|
def signed_core_bytes(receipt: dict) -> bytes:
|
|
core = {k: v for k, v in receipt.items()
|
|
if k != TOKEN_FIELD and v is not None}
|
|
return cbor2.dumps(core, canonical=True)
|
|
|
|
def hmac_payload(tr: dict) -> bytes:
|
|
return b"\x00".join([
|
|
tr["tool_id"].encode(), str(tr["joules_uj"]).encode(),
|
|
tr["energy_provenance"].encode(), tr["minted_at"].encode(),
|
|
tr["gateway_id"].encode(),
|
|
])
|
|
|
|
def worst(provs):
|
|
return min(provs, key=lambda p: PROV_ORDER[p])
|
|
|
|
def verify_vector(v, pubkey, gw_keys):
|
|
r = v["receipt"]
|
|
errs = []
|
|
|
|
# 1 — re-encode determinism
|
|
core = bytes.fromhex(v["signed_core_cbor_hex"])
|
|
if signed_core_bytes(r) != core:
|
|
errs.append("signed core not reproducible")
|
|
|
|
# 2 — COSE_Sign1 envelope
|
|
try:
|
|
tag = cbor2.loads(bytes.fromhex(v["cose_sign1_hex"]))
|
|
protected_b, _unprotected, payload, signature = tag.value
|
|
if payload != core:
|
|
errs.append("envelope payload != signed core")
|
|
ph = cbor2.loads(protected_b)
|
|
if ph.get(1) != -8:
|
|
errs.append(f"unexpected alg {ph.get(1)} (want -8 EdDSA)")
|
|
sig_struct = cbor2.dumps(["Signature1", protected_b, b"", payload], canonical=True)
|
|
pubkey.verify(signature, sig_struct)
|
|
except Exception as e:
|
|
errs.append(f"envelope verify failed: {e}")
|
|
|
|
# 3 — gateway tool-receipt HMACs
|
|
for tr in r.get("tool_receipts", []):
|
|
key = gw_keys.get(tr["gateway_id"])
|
|
if key is None:
|
|
errs.append(f"no key for gateway {tr['gateway_id']}")
|
|
continue
|
|
expect = hmac.new(key, hmac_payload(tr), hashlib.sha256).hexdigest()
|
|
if not hmac.compare_digest(expect, tr["hmac_sha256_hex"]):
|
|
errs.append(f"HMAC mismatch {tr['tool_id']}")
|
|
|
|
# 4 — honesty consistency
|
|
provs = [t["energy_provenance"] for t in r.get("tools_touched", [])]
|
|
provs += [t["energy_provenance"] for t in r.get("tool_receipts", [])]
|
|
if provs and r["energy_provenance"] != worst(provs):
|
|
errs.append("energy_provenance != worst counter")
|
|
tsum = sum(t["joules_uj"] for t in r.get("tools_touched", []))
|
|
if r["joules_uj"] < tsum:
|
|
errs.append("joules_uj < sum(tools_touched)")
|
|
if r["energy_provenance"] in ("ModelBased", "Estimator") and r["joules_uj"] % 1000 != 0:
|
|
errs.append("mJ resolution floor violated")
|
|
|
|
return errs
|
|
|
|
def main():
|
|
vec_path = sys.argv[1] if len(sys.argv) > 1 else "/mnt/user-data/outputs/jcr1-vectors.json"
|
|
key_path = sys.argv[2] if len(sys.argv) > 2 else "/mnt/user-data/outputs/jcr1-test-keys.json"
|
|
data = json.load(open(vec_path))
|
|
keys = json.load(open(key_path))
|
|
|
|
pubkey = Ed25519PublicKey.from_public_bytes(bytes.fromhex(keys["ed25519_public_raw_hex"]))
|
|
gw_keys = {k: val.encode() for k, val in keys["gateway_hmac_keys"].items()}
|
|
|
|
print(f"JCR-1 verifier — {data['vector_count']} vectors — {data['envelope']}\n")
|
|
npass = 0
|
|
for v in data["vectors"]:
|
|
errs = verify_vector(v, pubkey, gw_keys)
|
|
ok = not errs
|
|
npass += ok
|
|
print(f" [{('PASS' if ok else 'FAIL')}] vector {v['n']:>2}: {v['description']}")
|
|
for e in errs:
|
|
print(f" - {e}")
|
|
print(f"\n{npass}/{data['vector_count']} vectors verified")
|
|
sys.exit(0 if npass == data["vector_count"] else 1)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|