Skip to main content

Ledger

Struct Ledger 

Source
pub struct Ledger { /* private fields */ }
Expand description

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.

Implementations§

Source§

impl Ledger

Source

pub fn open(path: &Path, key: SigningKey) -> Result<Self, LedgerError>

Open a single-key ledger (the common case). Equivalent to open_with_verifiers with no retired keys.

Source

pub fn open_with_verifiers( path: &Path, key: SigningKey, retired: Vec<VerifyingKey>, ) -> Result<Self, LedgerError>

Open a ledger that may have been KEY-ROTATED: key is the current signing key, retired the public keys of every previously-active signing key. The existing chain is verified against the whole keyring (each record by the key its kid names; legacy kid-less records against any trusted key), so a rotated log reopens cleanly. Fail-closed: a record signed by a key NOT in the ring is tamper.

Source

pub fn open_anchored( path: &Path, key: SigningKey, retired: Vec<VerifyingKey>, anchor_path: &Path, ) -> Result<Self, LedgerError>

Open a ledger AND fail-closed check it against its persisted anchor in one step — the constructor a server’s startup uses. Equivalent to open_with_verifiers followed by verify_against_anchor: the log must be internally consistent under the keyring AND still extend its last committed height, so a truncation across a restart refuses the open rather than silently serving a shortened audit trail.

Source

pub fn open_segmented( dir: &Path, key: SigningKey, retired: Vec<VerifyingKey>, policy: RolloverPolicy, ) -> Result<Self, LedgerError>

Open (or create) a SEGMENTED ledger at dir — the opt-in alternative to the single ever-growing file, for a long-lived sovereign deployment. dir becomes a directory of numbered segment files plus a manifest.json; policy controls when append rolls the active segment over to a new one. An existing single-file ledger is never silently upgraded — this only ever creates or reopens a directory, and open_with_verifiers refuses to open a directory in the other direction, so the two modes can’t be confused for each other by accident.

Reopen is fail-closed exactly like the single-file constructors: the whole chain (every segment, in order) is re-verified against the keyring before any further append is accepted. Every SEALED segment is (re-)marked read-only (0444 on Unix) on open — self-healing after a crash that landed between committing the manifest and applying that permission, since the permission bit is defense-in-depth only, never the source of truth (see the segment module).

Source

pub fn open_segmented_anchored( dir: &Path, key: SigningKey, retired: Vec<VerifyingKey>, policy: RolloverPolicy, anchor_path: &Path, ) -> Result<Self, LedgerError>

open_segmented plus the same fail-closed anchor check open_anchored applies — a segmented ledger’s committed height is anchored exactly the same way as a single-file one’s (root+count don’t care how many files the bytes span).

Source

pub fn rotate(&mut self, new_key: SigningKey)

Rotate the signing key. Entries already written stay verifiable under the retired key (kept in the keyring); every subsequent entry is signed by new_key and carries its kid. The chain is uninterrupted — no re-signing of the past, and no republish. On the next restart, pass the retired public key to open_with_verifiers so the whole log still verifies.

Source

pub fn verifier_fingerprints(&self) -> Vec<String>

The public keys of every key trusted to have signed this log (retired + current), as hex fingerprints — what an auditor pins across a rotation.

Source

pub fn set_sync(&mut self, sync: bool) -> &mut Self

Enable/disable fsync-per-append (see the sync field). Returns self for builder-style config right after open.

Source

pub fn append(&mut self, entry: Entry) -> Result<Record, LedgerError>

Append entry to the log. entry.seq is assigned here; the serialized bytes are hashed into the chain, signed, and written verbatim, so an external verifier recomputes the exact same hash from what is on disk.

Source

pub fn root(&self) -> &str

The current head hash — export and anchor this externally.

Source

pub fn count(&self) -> u64

Number of entries appended so far (the next sequence number).

Source

pub fn self_verify(&self) -> Result<VerifyReport, LedgerError>

Re-read and verify this ledger’s own file against its whole keyring (every retired key plus the current one), so a rotated log verifies end-to-end. Used by the admin summary; O(entries), so not for the request hot path.

Source

pub fn pubkey_hex(&self) -> String

Hex of the Ed25519 public key that signs this ledger’s entries — the fingerprint an auditor pins.

Source

pub fn raw_records(&self) -> Result<Vec<String>, LedgerError>

Every stored line, verbatim and unparsed — for a caller who must hold this ledger’s lock as briefly as possible. The audit projection copies the bytes out under the lock and does every parse, match and proof after releasing it; what stays under the lock is one sequential read, not three parsing passes.

Source

pub fn read_records( &self, offset: usize, limit: usize, ) -> Result<Vec<Value>, LedgerError>

A window of records for the admin ledger browser: skip offset, take up to limit, each as its stored JSON object. Reads the file, so it’s an admin/audit path, never the decision hot path.

Source

pub fn read_raw_records( &self, offset: usize, limit: usize, ) -> Result<Vec<Box<RawValue>>, LedgerError>

A window of records as their VERBATIM stored bytes — the exact line each record was written as, preserved byte-for-byte (RawValue, no reparse). This is what an EXTERNALLY-VERIFIABLE evidence bundle must ship: the hash chain commits to the entry’s stored bytes (chain_hash(entry_bytes, prev)), so a third party can only reproduce a record’s hash from those exact bytes. read_records (which parses to Value) would re-serialize and reorder keys, breaking the hash — use this whenever the bytes are the proof, not the data.

Source

pub fn checkpoint(&self, ts_ms: u64) -> Checkpoint

Sign the current head into a Checkpoint for external anchoring — the operator-independent half of the audit story. Hand it to a notary / SCITT transparency service / another party; they can later prove the log was not rewritten below this point without trusting the operator.

Source

pub fn tree_head(&self, ts_ms: u64) -> Result<TreeHead, LedgerError>

Sign the current MERKLE tree head — the RFC 9162 root over all record hashes, externally anchorable like checkpoint but enabling COMPACT third-party inclusion/consistency proofs. tree_size == count. Signs through the ledger key (a keyless hash committed by a key, same as a checkpoint).

Source

pub fn sign_tree_head( &self, merkle_root: String, tree_size: u64, ts_ms: u64, ) -> TreeHead

Sign a tree head over an already-computed root — the signing half of tree_head, for a caller who derived the leaves outside the lock. Signs exactly what it is given: a root computed from a prefix that has since been appended past is still a consistent commitment to that prefix, the same answer the caller would have gotten before the append.

Source

pub fn inclusion_proof(&self, seq: u64) -> Result<InclusionProof, LedgerError>

A compact RFC 9162 inclusion proof that the record at seq (0-based) is committed by the current tree head’s root. Err if seq is past the end of the log.

Source

pub fn inclusion_proofs( &self, seqs: &[u64], ) -> Result<Vec<InclusionProof>, LedgerError>

Inclusion proofs for several records, over one pass of the log.

inclusion_proof derives every leaf in the log to prove one record is in it, which is the right shape for one proof and the wrong shape for a page of them: asking for m proofs that way reads and parses the whole log m times, and does it holding the lock an append needs. This derives the leaves once.

Returns proofs in the order the sequences were given. A sequence past the end of the log fails the whole call rather than being skipped — a page of proofs with a hole in it, where the hole is silent, is worse than no page.

Source

pub fn consistency_proof( &self, first_size: u64, ) -> Result<ConsistencyProof, LedgerError>

A compact RFC 9162 consistency proof that the log of the first first_size records is an exact prefix of the current log — the operator-independent equivocation / truncation check against an EARLIER anchored tree head. 1 <= first_size <= count.

Source

pub fn snapshot_for_bundle( &self, ts_ms: u64, ) -> Result<EvidenceSnapshot, LedgerError>

A single-snapshot read for an evidence bundle: checkpoint, tree_head, and raw records are ALL derived from the SAME in-memory (self.last_hash, self.next_seq) state captured at one moment — unlike calling checkpoint()/tree_head()/read_raw_records() separately (which lets an append() land between calls and make the three mutually inconsistent: checkpoint.count != tree_head.tree_size or checkpoint.root computed over different records than tree_head).

This is the single-file analog of ShardedLedger::evidence_snapshot. The snapshot captures the log’s state at call time; a concurrent append() does not change the returned values. Returns (count, raw_records, checkpoint, tree_head).

Source

pub fn seal_anchor( &self, anchor_path: &Path, ts_ms: u64, ) -> Result<Checkpoint, LedgerError>

Seal the current head into the persisted ANCHOR file — the last committed height, durably recorded on THIS node (not only handed to an external notary). On the next open_anchored / verify_against_anchor the log must still extend it, which is what makes a tail-truncation across a restart detectable — a plain reopen accepts any internally-consistent shorter chain and cannot.

Source

pub fn verify_against_anchor( &self, anchor_path: &Path, ) -> Result<(), LedgerError>

Fail-closed truncation/rewrite check against the persisted anchor. Call it right after opening (or use open_anchored): if an anchor exists it must (a) be signed by a key in this ledger’s keyring — a forged anchor cannot be used to downgrade the committed height — and (b) still be extended by the log (at least count records that re-derive root). Any failure is Tamper: the log was truncated below, or rewritten at/below, its last committed height. No anchor file ⇒ Ok (nothing committed yet).

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.