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 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 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.