YuleYule
Changelogs

Crypto Integrity

Attestation, Merkle verification, hash-chained audit logs.

Cryptographic Integrity — Attestation, Keys, Audit

Every inference is now signed, chained, and auditable.


Model Manifests and Key Management

Built yule-verify with two core pieces:

Model manifests — JSON format describing a model's identity: name, architecture, publisher, merkle root, per-tensor hashes. Supports Ed25519 signatures (with ML-DSA hybrid placeholder for post-quantum readiness). The manifest's signable_bytes() function sets the signature field to Unsigned before hashing, so the signature doesn't sign itself.

Key store at ~/.yule/keys/:

  • device_key() auto-generates an Ed25519 signing key on first run, persists the 32-byte secret. Also saves the public key as device.pub for easy export.
  • trust_publisher() / publisher_key() / list_publishers() — trusted publisher registry. Save a publisher's Ed25519 public key as {name}.pub.

Key generation uses getrandom (OS CSPRNG) directly. I tried using ed25519-dalek's SigningKey::generate() but that pulls in the entire rand dependency chain. Instead: 32 random bytes from the OS → SigningKey::from_bytes(). Same cryptographic quality, zero extra dependencies.


Signed Inference Records

Built yule-attest. Each inference produces a signed record containing:

  • session_id — unique per inference (nanosecond timestamp, hex-encoded)
  • model — name, merkle root, publisher, whether signature was verified
  • sandbox — platform, active flag, memory limit
  • inference — tokens generated, blake3(prompt), blake3(output), temperature, top_p
  • signature — Ed25519 over all the above
  • prev_hash — blake3 of the previous record (chain integrity)

Prompt and output are hashed, not stored in plaintext. You can prove an inference happened without exposing what was said.


Audit Log

Append-only JSON-lines file at ~/.yule/audit.jsonl.

  • append() — write a signed record
  • last_hash() — get chain tip for linking the next record
  • verify_chain() — walk entire log, verify each record's prev_hash matches blake3 of the previous record
  • query_last(n) — read last N records

Why JSON-lines instead of a database? One file, append-only, human-readable, trivially parseable. No SQLite dependency. The hash chain provides integrity guarantees a database wouldn't — you can't silently delete a record without breaking the chain.


API Integration

The /yule/chat endpoint now creates an attestation after each inference, signs it with the device key, appends to the audit log, and returns attestation_id + device_pubkey in the response's integrity block. Both streaming and non-streaming responses include attestation.

Attestation is non-blocking — if the audit log fails (permissions, disk full), inference still succeeds with a warning. The response just won't have an attestation_id. Attestation should never slow down or prevent inference.


CLI: yule audit

yule audit --last 10        # show last 10 records
yule audit --verify-chain   # verify entire hash chain

Design Choices

blake3 for hashing — already using it for merkle trees, 2-3x faster than SHA-256 on x86, one dependency covers everything.

Ed25519 for signatures — 64-byte sigs, fast verify, broad support. There's a MlDsa variant in the manifest format for post-quantum hybrid, but Ed25519 is the pragmatic choice today.

Prompts/outputs are hashed, not stored — the audit log proves that an inference happened with specific parameters, without leaking what was said.

On this page