Skip to main content

Module sign

Module sign 

Source
Expand description

Signing a verdict, and verifying one without asking anybody.

§Why a verdict has to be signed at all

An unsigned verdict is a row in a log file. Anyone who can write the log can write “the tests passed”, and nobody reading it later can tell the difference between a checker that ran and a line someone typed. That is the whole gap between logging and evidence, and Law III sits on the far side of it: a state change commits on Evidence, so evidence has to be worth something.

ed25519 over the canonical CBOR encoding closes it. The signature covers the verdict, the checker’s identity and version, the digests of exactly what was checked, the key identity, and how the verdict was reached — everything except the signature itself, which is what Evidence::signing_payload returns. Change any of it and the signature stops verifying.

§Deterministic signatures, deliberately

ed25519 derives its per-signature nonce from the private key and the message (RFC 8032 §5.1.6) rather than sampling one. Signing the same evidence twice therefore produces the same 64 bytes. That is load-bearing here rather than incidental: US-0801 requires a checker to be a pure function of its inputs, and replay (US-1006) re-runs a recorded check and compares. A randomised signature scheme would make every replayed run differ in a field that says nothing about whether the check agreed.

§Verification is a free function

verify takes evidence and a public key and returns an answer. No runtime, no registry, no async, no I/O. A compliance reviewer with a ledger file and a published key can check every verdict in it without running BoundGraph at all — which is the only version of “auditable” that means anything.

§There is no path to unsigned evidence

sign_evidence is the only constructor in this crate, and it returns Result. There is no unsigned(), no feature flag, and no fallback that degrades to an empty signature when a key is unavailable. A signing failure propagates and the step does not commit. That asymmetry is deliberate: a blocked commit is an outage, an unsigned commit is a lie in the audit trail, and only one of those can be fixed after the fact.

§Key management

Which keys exist, and who holds them, is unresolved — ARCHITECTURE.md §17 lists it as the one open question blocking M3. docs/evidence-keys.md records the three candidate strategies and what each costs. This module is written so that the decision can be made later without changing any caller:

  • Sign is a trait, so an external KMS or an HSM is an implementation of it rather than a rewrite. LocalKey is the in-process one, which is what M3 runs on.
  • The key identity travels in the evidence, not the key material, so a verifier looks the key up rather than trusting what it was handed.
  • KeyRing is the smallest possible resolver of identity to public key. A KMS-backed one satisfies the same call.

Structs§

KeyRing
Public keys, by the identity the evidence names.
LocalKey
An ed25519 key held in this process.
PublicKey
An ed25519 public key, as bytes.

Enums§

SignError
Signing failed, so nothing commits.
VerifyError
Verification failed, so the evidence proves nothing.

Constants§

PUBLIC_KEY_LEN
How many bytes an ed25519 public key is.
SEED_LEN
How many bytes a private seed is.

Traits§

Sign
Whatever can produce a signature for a key it holds.

Functions§

sign_evidence
Build evidence and sign it, in that order, as one operation.
verify
Check evidence against a public key.