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.
422 lines
10 KiB
Go
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)
|
|
}
|
|
}
|