Skip to content

fathom.verify_chain

fathom.verify_chain(path, public_key, *, expected_head=None, anchor_token=None)

Offline verifier: structure, hash linkage, and every JWS signature.

Parameters:

Name Type Description Default
path str | Path

Chained JSONL log file.

required
public_key Ed25519PublicKey | bytes | str | Path

Ed25519 public key (object, PEM bytes, or PEM path).

required
expected_head str | None

Out-of-band mirrored line hash. Verification fails (anchor_ok=False) if it is not one of the log's line hashes — i.e. the tail containing it was truncated.

None
anchor_token str | None

A checkpoint JWS previously emitted via :class:AnchorEvent. The checkpoint line carrying this exact token must still be present in the log, so truncating the checkpoint line itself is detected.

None

All supplied anchors must hold. Never raises on log content; malformed input is reported via error/error_line.

Source code in src/fathom/chained_log.py
def verify_chain(
    path: str | Path,
    public_key: Ed25519PublicKey | bytes | str | Path,
    *,
    expected_head: str | None = None,
    anchor_token: str | None = None,
) -> ChainVerification:
    """Offline verifier: structure, hash linkage, and every JWS signature.

    Args:
        path: Chained JSONL log file.
        public_key: Ed25519 public key (object, PEM bytes, or PEM path).
        expected_head: Out-of-band mirrored line hash. Verification fails
            (``anchor_ok=False``) if it is not one of the log's line
            hashes — i.e. the tail containing it was truncated.
        anchor_token: A checkpoint JWS previously emitted via
            :class:`AnchorEvent`. The checkpoint line carrying this exact
            token must still be present in the log, so truncating the
            checkpoint line itself is detected.

    All supplied anchors must hold. Never raises on log content; malformed
    input is reported via ``error``/``error_line``.
    """
    log_path = Path(path)
    key = _load_public_key(public_key)
    state = _scan(log_path, collect_hashes=True, verify_key=key)

    if state.error is not None:
        return ChainVerification(
            ok=False,
            count=state.record_count,
            head_seq=state.head_seq,
            head_sha256=state.head_sha256,
            error=state.error,
            error_line=state.error_line,
            anchor_ok=None,
            log_id=state.log_id,
            key_fingerprint=state.genesis_fingerprint,
        )

    if state.count == 0:
        # A nonempty log always opens with a mandatory genesis record, so
        # zero lines means the log is missing or was wiped — never a valid
        # chain. Reporting ok=True here would green-light a deleted log.
        return ChainVerification(
            ok=False,
            count=0,
            head_seq=None,
            head_sha256=None,
            error="log is empty or missing: no genesis record",
            error_line=None,
            anchor_ok=None,
            log_id=None,
        )

    anchor_supplied = anchor_token is not None or expected_head is not None
    anchor_error: str | None = None
    if anchor_token is not None:
        try:
            verify_token(anchor_token, key)
        except AttestationError as exc:
            anchor_error = f"anchor token verification failed: {exc}"
        else:
            if anchor_token not in state.jws_tokens:
                anchor_error = "anchor checkpoint line not present in log — tail truncated?"
    if (
        anchor_error is None
        and expected_head is not None
        and (expected_head not in state.line_hashes)
    ):
        anchor_error = f"expected head {expected_head!r} not present in log — tail truncated?"

    return ChainVerification(
        ok=anchor_error is None,
        count=state.record_count,
        head_seq=state.head_seq,
        head_sha256=state.head_sha256,
        error=anchor_error,
        error_line=None,
        anchor_ok=anchor_error is None if anchor_supplied else None,
        log_id=state.log_id,
        key_fingerprint=state.genesis_fingerprint,
    )