Skip to main content

Crate decern_ledger

Crate decern_ledger 

Source
Expand description

decern-ledger — the tamper-evident decision ledger (the audit column).

Every authority decision is appended as a hash-chained, Ed25519-signed record: hash = SHA-256(entry_bytes ‖ prev_hash), signature over hash. Any edit, reorder or in-place deletion breaks the chain; a wholesale rewrite fails signature verification against the ledger key. What the chain alone cannot detect is truncation of the tail — that is what root() is for: export the head hash and anchor it externally (a regulator, a notary, another system). Anchored root + intact chain = complete, unmodified history.

The chain hash covers the EXACT entry bytes as stored on disk (captured via serde_json’s RawValue at verify time), never a re-serialization — so byte-stability is structural, not an assumption about JSON round-trips. (Float round-tripping is NOT stable in serde_json without the float_roundtrip feature; hashing re-serialized bytes was a confirmed false-tamper bug that could brick an honest ledger.)

Re-exports§

pub use jcs::canonicalize;
pub use jcs::digest;
pub use sharded::ShardVerification;
pub use sharded::ShardedLedger;
pub use sharded::UNATTRIBUTED_SHARD;
pub use sharded::verify_sharded_dir;

Modules§

jcs
RFC 8785 JSON Canonicalization Scheme (JCS) + digest.
merkle
RFC 9162 (Certificate Transparency 2.0) Merkle tree — the compact-proof layer over the linear hash chain. The chain already proves a record’s bytes and its position relative to its neighbours; a Merkle tree over the per-record hashes adds two things the chain cannot give compactly:
sharded
ShardedLedger — the hosted-topology ledger: one hash chain per authority-domain (“shard”), safely extendable by any number of concurrent decern-server replicas via a decern_store::LedgerHeadStore.

Structs§

BundleVerdict
The per-check result of verifying an exported evidence bundle offline. accepted is the AND of every APPLICABLE check (an Option check that is None did not apply and does not gate acceptance). Serializable so a CLI can emit it as --json verbatim.
ChallengeRecord
A challenge and its answer, on the record.
Checkpoint
A signed, externally-anchorable commitment to the ledger’s state at a moment: the head root over the first count entries, timestamped and signed by the ledger key. It leaks no entry content — only a hash, a count, and a signature — so it is safe to publish, hand to a notary, or submit to a SCITT transparency service. Because the log is append-only, the root over the first count entries is fixed forever; an external party holding a checkpoint can re-derive that root from the file and, if it disagrees, prove the operator rewrote history — the operator-INDEPENDENT verification a log inside the operator’s own stack lacks.
ConsistencyProof
A compact RFC 9162 consistency proof (hex-encoded) that the tree of the first first_size leaves is an exact prefix of the tree of second_size leaves.
DecisionSubject
The party a decision is about, as a pseudonymous reference.
Entry
One record in the ledger: a decision — “what happened, with everything needed to replay it”. Several fields below are reserved and inert (see each field’s note): no shipped path sets them, they are retained only for struct/type stability, and a plain decision leaves them at their defaults, which serialize to no bytes — so every existing writer and stored line is unchanged.
InclusionProof
A compact RFC 9162 inclusion proof (hex-encoded): the record at leaf_index in a tree of tree_size leaves is committed by a TreeHead’s root. leaf_data is the record’s chain hash (the Merkle leaf data — a verifier hashes it with the 0x00 leaf prefix); audit_path is the sibling hashes bottom-up.
Ledger
Append-only writer. Opening an existing ledger verifies the whole chain (and, with this key, every signature) before accepting new entries — fail-closed: a corrupt audit trail refuses further writes.
MissionRef
A Mission reference recorded on a decision Entry.
Party
A party referenced by a record — the accountable owner named by sponsor, or the decision_subject. The acting subject is subject_id on the entry.
Record
RolloverPolicy
When a segmented ledger rolls its active segment over to a new file. Both may be set (roll over on whichever fires first); both None is legal but pointless (a segmented ledger with exactly one ever-growing segment).
TreeHead
A signed, externally-anchorable commitment to the ledger’s MERKLE state: the RFC 9162 tree root over the first tree_size record hashes, timestamped and signed by the ledger key. Parallel to Checkpoint (the linear-chain head): a TreeHead enables COMPACT third-party proofs — an inclusion proof shows one record is in the log without shipping the whole tail, and a consistency proof between an anchored earlier TreeHead and a later one proves nothing below the earlier size was rewritten or dropped (closing equivocation). Leaks no entry content — only a root, a size, and a signature.
VerifyReport

Enums§

EdgeType
How an authority-graph edge came to be — the attenuate-vs-mint distinction. Typing every issuance edge lets the record tell offline narrowing apart from a trusted-issuer crossing; the two carry different safety properties (only the former is safe to delegate offline). Only Attenuate is used; Mint is reserved and inert, retained so the enum stays stable.
LedgerError
SponsorSource
How Entry::sponsor was determined (see Entry::sponsor).

Constants§

DIGEST_AUTHORITY
The authority a decision was taken against — policy, schema and entity graph.
DIGEST_PARAMETERS
The exact arguments a decision authorized — what it was asked, not what it knew. Binding them means a later reading can tell that the thing authorized is the thing that was requested, rather than something substituted after the check.
GENESIS

Functions§

ledger_extends_checkpoint
The operator-independent tamper check: does the ledger at path still extend a previously issued cp? Re-derives the head over the first cp.count records from the stored bytes and confirms it equals cp.root. If the operator edited, reordered, or truncated any entry at or before count, the re-derived root diverges and this returns Ok(false) — caught by anyone holding the old checkpoint, without trusting the operator. A well-behaved append-only log always extends its past checkpoints.
load_anchor
Load the persisted anchor, or None if no anchor file exists yet. A present but unparseable anchor is a hard error (fail-closed — a corrupt anchor is never silently treated as “no committed height”).
merkle_leaves_at
Merkle leaves of the ledger at path, read-only — no signing key required.
read_verified
Verify the whole chain (fail-closed on any tamper) AND return a window of the parsed records — the OFFLINE auditor read: an auditor holds the ledger file and, out of band, the public key, but not the private signing key Ledger::open demands. With pubkey each record’s signature is checked too; without it, only the hash chain (still fail-closed). The whole log is scanned to verify integrity; only records in [offset, offset+limit) are materialized (memory stays bounded to the window). The records are the exact stored JSON (as read_records returns them), NOT verbatim bytes.
save_anchor
Persist a checkpoint as the ledger’s anchor file, atomically (temp + fsync + rename + parent-dir fsync) so a crash cannot leave a half-written or non-durable anchor — a lost anchor write would silently lower the height truncation is checked against. See Ledger::seal_anchor.
verify
Verify a ledger file: the hash chain always; every entry signature when a key is supplied. Single-key convenience over verify_with_keys — for a key-ROTATED log (entries under more than one key) use that with the full keyring.
verify_checkpoint_sig
verify_evidence_bundle
Verify an exported decern-evidence-bundle OFFLINE against a PINNED keyring — the standalone third-party check with no call back to the server. bundle_json is the RAW bundle file text (NOT a re-serialized Value): each record’s entry is captured as verbatim bytes, because the chain commits to those exact bytes. keys is the pinned current + retired public keys (obtained OUT OF BAND — never from the bundle). against, if given, is an independently-anchored EARLIER tree head; a consistency check then proves the bundle did not rewrite or drop anything below that earlier size (equivocation/truncation).
verify_tree_head_sig
Verify a tree head’s own signature against a pinned key — the Merkle counterpart of verify_checkpoint_sig. The domain-separated decern-ledger-tree-head tag means this never cross-verifies a checkpoint signature. Does not read the ledger.
verify_with_keys
Verify a ledger file against a KEYRING — the rotation-aware form. Each record is checked against the key its kid names; a legacy record with no kid (written before rotation support) is accepted against any key in the ring. A record whose kid names a key NOT in the ring is tamper (fail-closed: an unknown signer is never trusted). An empty ring means “signatures not checked” (chain only), same as verify with None.

Type Aliases§

EvidenceSnapshot
A single-snapshot evidence bundle: record count, raw bytes, and signed commitments (checkpoint and merkle tree head) all derived from the same log state at one moment. This is the return type of Ledger::snapshot_for_bundle and ShardedLedger::evidence_snapshot.