open-standards/jouleclaw/jouleclaw-rs/crates/jouleclaw-jcr1/conformance/verify.go
Transaction Science 2dc5b0c4cb jouleclaw-jcr1: JCR-1 reference + 10 vectors + Rust producer + Python/Go verifiers
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.
2026-06-01 03:32:44 -04:00

422 lines
10 KiB
Go

// JCR-1 independent reference verifier — Go, standard library only.
//
// No external modules: a from-scratch canonical CBOR encoder (RFC 8949 4.2.1),
// a minimal COSE_Sign1 reader, crypto/ed25519, crypto/hmac, crypto/sha256.
// This is the *second implementation* whose agreement with the Python suite
// makes JCR-1 a standard rather than a house format.
//
// It does more than verify: it RE-ENCODES each signed core from the receipt
// fields and checks the bytes equal the Python-produced signed core exactly.
// Two independent encoders producing identical canonical bytes is the proof.
//
// Usage: go run jcr1_verify.go jcr1-vectors.json jcr1-test-keys.json
package main
import (
"bytes"
"crypto/ed25519"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"sort"
"strconv"
)
// ---------- canonical CBOR encoder (subset: uint, text, bstr, array, map) ----
func cborUint(buf *bytes.Buffer, major byte, n uint64) {
mt := major << 5
switch {
case n < 24:
buf.WriteByte(mt | byte(n))
case n < 1<<8:
buf.WriteByte(mt | 24)
buf.WriteByte(byte(n))
case n < 1<<16:
buf.WriteByte(mt | 25)
buf.WriteByte(byte(n >> 8))
buf.WriteByte(byte(n))
case n < 1<<32:
buf.WriteByte(mt | 26)
for s := 24; s >= 0; s -= 8 {
buf.WriteByte(byte(n >> uint(s)))
}
default:
buf.WriteByte(mt | 27)
for s := 56; s >= 0; s -= 8 {
buf.WriteByte(byte(n >> uint(s)))
}
}
}
func cborText(buf *bytes.Buffer, s string) {
cborUint(buf, 3, uint64(len(s)))
buf.WriteString(s)
}
func cborBytes(buf *bytes.Buffer, b []byte) {
cborUint(buf, 2, uint64(len(b)))
buf.Write(b)
}
// encodeValue handles the value tree produced by json with UseNumber.
func encodeValue(buf *bytes.Buffer, v interface{}) error {
switch x := v.(type) {
case string:
cborText(buf, x)
case json.Number:
// signed core integers are all non-negative
u, err := strconv.ParseUint(x.String(), 10, 64)
if err != nil {
return fmt.Errorf("non-integer in signed core: %s", x.String())
}
cborUint(buf, 0, u)
case bool, float64, nil:
return fmt.Errorf("disallowed type in signed core: %T", x)
case []interface{}:
cborUint(buf, 4, uint64(len(x)))
for _, e := range x {
if err := encodeValue(buf, e); err != nil {
return err
}
}
case map[string]interface{}:
return encodeMap(buf, x)
default:
return fmt.Errorf("unsupported type %T", x)
}
return nil
}
// encodeMap: RFC 8949 4.2.1 ordering — length-first, then bytewise on key.
func encodeMap(buf *bytes.Buffer, m map[string]interface{}) error {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Slice(keys, func(i, j int) bool {
if len(keys[i]) != len(keys[j]) {
return len(keys[i]) < len(keys[j])
}
return keys[i] < keys[j]
})
cborUint(buf, 5, uint64(len(keys)))
for _, k := range keys {
cborText(buf, k)
if err := encodeValue(buf, m[k]); err != nil {
return err
}
}
return nil
}
// signedCore: drop token_attributions and null fields, canonical-encode.
func signedCore(receipt map[string]interface{}) ([]byte, error) {
core := map[string]interface{}{}
for k, v := range receipt {
if k == "token_attributions" || v == nil {
continue
}
core[k] = v
}
var buf bytes.Buffer
if err := encodeMap(&buf, core); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// ---------- minimal CBOR reader (only what COSE_Sign1 needs) ----------------
type reader struct {
b []byte
pos int
}
func (r *reader) byte() (byte, error) {
if r.pos >= len(r.b) {
return 0, fmt.Errorf("eof")
}
c := r.b[r.pos]
r.pos++
return c, nil
}
func (r *reader) head() (major byte, arg uint64, err error) {
c, err := r.byte()
if err != nil {
return
}
major = c >> 5
ai := c & 0x1f
switch {
case ai < 24:
arg = uint64(ai)
case ai == 24:
var v byte
v, err = r.byte()
arg = uint64(v)
case ai == 25:
for i := 0; i < 2; i++ {
var v byte
if v, err = r.byte(); err != nil {
return
}
arg = arg<<8 | uint64(v)
}
case ai == 26:
for i := 0; i < 4; i++ {
var v byte
if v, err = r.byte(); err != nil {
return
}
arg = arg<<8 | uint64(v)
}
case ai == 27:
for i := 0; i < 8; i++ {
var v byte
if v, err = r.byte(); err != nil {
return
}
arg = arg<<8 | uint64(v)
}
default:
err = fmt.Errorf("unsupported additional info %d", ai)
}
return
}
func (r *reader) bstr() ([]byte, error) {
major, n, err := r.head()
if err != nil {
return nil, err
}
if major != 2 {
return nil, fmt.Errorf("expected bstr, got major %d", major)
}
if r.pos+int(n) > len(r.b) {
return nil, fmt.Errorf("bstr overrun")
}
out := r.b[r.pos : r.pos+int(n)]
r.pos += int(n)
return out, nil
}
// parseCOSESign1 extracts protected, payload, signature from a tag-18 array.
func parseCOSESign1(b []byte) (protected, payload, signature []byte, err error) {
r := &reader{b: b}
major, tag, err := r.head()
if err != nil {
return
}
if major == 6 { // optional tag 18
if tag != 18 {
err = fmt.Errorf("unexpected tag %d", tag)
return
}
major, tag, err = r.head()
if err != nil {
return
}
}
if major != 4 || tag != 4 {
err = fmt.Errorf("expected 4-element array, got major %d len %d", major, tag)
return
}
if protected, err = r.bstr(); err != nil { // [0] protected
return
}
if m, a, e := r.head(); e != nil || m != 5 { // [1] unprotected map
err = fmt.Errorf("expected map for unprotected header (major %d)", m)
_ = a
return
}
if payload, err = r.bstr(); err != nil { // [2] payload
return
}
signature, err = r.bstr() // [3] signature
return
}
// ---------- verification --------------------------------------------------
var provOrder = map[string]int{"HwShunt": 3, "ModelBased": 2, "Estimator": 1}
func worst(provs []string) string {
w := provs[0]
for _, p := range provs[1:] {
if provOrder[p] < provOrder[w] {
w = p
}
}
return w
}
type Vectors struct {
VectorCount int `json:"vector_count"`
Envelope string `json:"envelope"`
Vectors []struct {
N int `json:"n"`
Description string `json:"description"`
Receipt map[string]interface{} `json:"receipt"`
CoreHex string `json:"signed_core_cbor_hex"`
CoseHex string `json:"cose_sign1_hex"`
} `json:"vectors"`
}
type Keys struct {
PubHex string `json:"ed25519_public_raw_hex"`
GwKeys map[string]string `json:"gateway_hmac_keys"`
}
func num(v interface{}) uint64 {
u, _ := strconv.ParseUint(v.(json.Number).String(), 10, 64)
return u
}
func verify(rec map[string]interface{}, coreHex, coseHex string, pub ed25519.PublicKey, gw map[string][]byte) []string {
var errs []string
// 1 — re-encode determinism (independent encoder must match Python bytes)
core, err := signedCore(rec)
if err != nil {
errs = append(errs, "encode error: "+err.Error())
}
want, _ := hex.DecodeString(coreHex)
if !bytes.Equal(core, want) {
errs = append(errs, "signed core re-encode mismatch")
}
// 2 — COSE_Sign1 envelope signature
coseBytes, _ := hex.DecodeString(coseHex)
protected, payload, signature, perr := parseCOSESign1(coseBytes)
if perr != nil {
errs = append(errs, "cose parse: "+perr.Error())
} else {
if !bytes.Equal(payload, want) {
errs = append(errs, "envelope payload != signed core")
}
var sig bytes.Buffer
sig.WriteByte(0x84) // array(4)
cborText(&sig, "Signature1")
cborBytes(&sig, protected)
cborBytes(&sig, []byte{})
cborBytes(&sig, payload)
if !ed25519.Verify(pub, sig.Bytes(), signature) {
errs = append(errs, "ed25519 verification failed")
}
}
// 3 — gateway tool-receipt HMACs
if trs, ok := rec["tool_receipts"].([]interface{}); ok {
for _, t := range trs {
tr := t.(map[string]interface{})
gid := tr["gateway_id"].(string)
key, ok := gw[gid]
if !ok {
errs = append(errs, "no key for gateway "+gid)
continue
}
payload := bytes.Join([][]byte{
[]byte(tr["tool_id"].(string)),
[]byte(strconv.FormatUint(num(tr["joules_uj"]), 10)),
[]byte(tr["energy_provenance"].(string)),
[]byte(tr["minted_at"].(string)),
[]byte(gid),
}, []byte{0})
mac := hmac.New(sha256.New, key)
mac.Write(payload)
got := hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(got), []byte(tr["hmac_sha256_hex"].(string))) {
errs = append(errs, "HMAC mismatch "+tr["tool_id"].(string))
}
}
}
// 4 — honesty consistency
var provs []string
collect := func(field string) {
if arr, ok := rec[field].([]interface{}); ok {
for _, e := range arr {
provs = append(provs, e.(map[string]interface{})["energy_provenance"].(string))
}
}
}
collect("tools_touched")
collect("tool_receipts")
if len(provs) > 0 && rec["energy_provenance"].(string) != worst(provs) {
errs = append(errs, "energy_provenance != worst counter")
}
var sum uint64
if arr, ok := rec["tools_touched"].([]interface{}); ok {
for _, e := range arr {
sum += num(e.(map[string]interface{})["joules_uj"])
}
}
total := num(rec["joules_uj"])
if total < sum {
errs = append(errs, "joules_uj < sum(tools_touched)")
}
prov := rec["energy_provenance"].(string)
if (prov == "ModelBased" || prov == "Estimator") && total%1000 != 0 {
errs = append(errs, "mJ resolution floor violated")
}
return errs
}
func mustDecodeJSON(path string, v interface{}) {
f, err := os.Open(path)
if err != nil {
panic(err)
}
defer f.Close()
dec := json.NewDecoder(f)
dec.UseNumber()
if err := dec.Decode(v); err != nil {
panic(err)
}
}
func main() {
vecPath := "jcr1-vectors.json"
keyPath := "jcr1-test-keys.json"
if len(os.Args) > 2 {
vecPath, keyPath = os.Args[1], os.Args[2]
}
var vs Vectors
var ks Keys
mustDecodeJSON(vecPath, &vs)
mustDecodeJSON(keyPath, &ks)
pubBytes, _ := hex.DecodeString(ks.PubHex)
pub := ed25519.PublicKey(pubBytes)
gw := map[string][]byte{}
for k, v := range ks.GwKeys {
gw[k] = []byte(v)
}
fmt.Printf("JCR-1 independent Go verifier — %d vectors — %s\n\n", vs.VectorCount, vs.Envelope)
pass := 0
for _, v := range vs.Vectors {
errs := verify(v.Receipt, v.CoreHex, v.CoseHex, pub, gw)
ok := len(errs) == 0
if ok {
pass++
}
status := "PASS"
if !ok {
status = "FAIL"
}
fmt.Printf(" [%s] vector %2d: %s\n", status, v.N, v.Description)
for _, e := range errs {
fmt.Printf(" - %s\n", e)
}
}
fmt.Printf("\n%d/%d vectors verified by the independent Go implementation\n", pass, vs.VectorCount)
if pass != vs.VectorCount {
os.Exit(1)
}
}