Skip to content
Tenbri pangolinTenbri

Verify as an auditor

You are the auditor. The operator gives you the stream’s fingerprint through a channel they cannot rewrite later (a signed email, a ticket, a contract annex). You verify the stream against it, keep a checkpoint, then play the attacker: edit one byte of a batch file on disk and watch verification fail with the first broken sequence number. Verifying needs only read access to the stream and the fingerprint, never the operator’s keys.

  • A copy of the stream, or read access to where it lives, and its fingerprint. Step 1 makes one.

  • The tenbri CLI to set up the stream and seal a checkpoint, plus jq:

    Terminal window
    cargo install tenbri-cli --version 0.1.0-beta.1 --locked
  • The tool you verify with:

The tenbri CLI above is all you need.

  1. Set up a stream to audit.

    The operator side: a stream with one intent and its outcome.

    Terminal window
    export TENBRI_KEYS=./keys
    tenbri init audit/support
    INTENT_ID=$(tenbri append audit/support <<'EOF' | jq -r .id
    {"kind":"decision","phase":"intent","source":"sdk",
    "principal":{"kind":"agent","id":"support-assistant"},
    "action":{"type":"reply_ticket","target":"zendesk:ticket/4821"}}
    EOF
    )
    tenbri append audit/support <<EOF > /dev/null
    {"kind":"decision","phase":"outcome","source":"sdk","intent":"$INTENT_ID","status":"ok",
    "principal":{"kind":"agent","id":"support-assistant"},
    "action":{"type":"reply_ticket","target":"zendesk:ticket/4821"}}
    EOF
    created stream tnb1:db888dbab715e39d31a8b0c7b6bd418a at audit/support
    stream key fingerprint (pin it): sha256:db888dbab715e39d31a8b0c7b6bd418acfe8482508fd345ffcb6d5da23adb6fe
    keys: ./keys/tnb1-db888dbab715e39d31a8b0c7b6bd418a
    verify with: tenbri verify audit/support --key sha256:db888dbab715e39d31a8b0c7b6bd418acfe8482508fd345ffcb6d5da23adb6fe

    The fingerprint is what the operator sends you. Keep it in PIN:

    Terminal window
    export PIN=sha256:db888dbab715e39d31a8b0c7b6bd418acfe8482508fd345ffcb6d5da23adb6fe
  2. Verify without a pin, then with it.

    The fingerprint is your trust root. Without it, verification can only say the stream agrees with its own genesis key, which anyone who rewrites the whole stream can also produce. The Python and TypeScript scripts below exit with the CLI’s codes: 0 verified, 1 broken, 3 not pinned.

    Terminal window
    tenbri verify audit/support; echo "exit $?"
    tenbri verify audit/support --key $PIN; echo "exit $?"
    consistent: 4 entries, stream tnb1:db888dbab715e39d31a8b0c7b6bd418a
    NOT pinned: this only shows the stream agrees with its own genesis key; pass --key <fingerprint>
    exit 3
    verified 4 entries, stream tnb1:db888dbab715e39d31a8b0c7b6bd418a, pinned to sha256:db888dbab715e39d31a8b0c7b6bd418acfe8482508fd345ffcb6d5da23adb6fe
    exit 0
  3. Keep a checkpoint.

    A checkpoint is a signed note of the stream’s size and Merkle root. The operator seals one; you keep a copy outside the operator’s reach.

    Terminal window
    tenbri checkpoint audit/support
    cp audit/support/checkpoints/000000000004.note seen.note
    head -3 seen.note
    checkpoint at size 4: checkpoints/000000000004.note
    tnb1:db888dbab715e39d31a8b0c7b6bd418a
    4
    0SREoiNR1ENpnImgPcXhjwWKjv1yTiX3aR+6YMRp57s=

    The three lines are the origin (the stream id), the size and the root hash. A blank line and the signature line follow.

    Next time, verify against the checkpoint you kept. The stream must still hold exactly the entries it covers:

    Terminal window
    tenbri verify audit/support --key $PIN --checkpoint seen.note; echo "exit $?"
    matches checkpoint tnb1:db888dbab715e39d31a8b0c7b6bd418a at size 4
    matches its latest checkpoint, at size 4
    verified 4 entries, stream tnb1:db888dbab715e39d31a8b0c7b6bd418a, pinned to sha256:db888dbab715e39d31a8b0c7b6bd418acfe8482508fd345ffcb6d5da23adb6fe
    exit 0
  4. Tamper with one byte.

    Each batch file holds consecutive entries, one canonical JSON line each. Here every append made its own batch, so entry 3 (the intent) is alone in 000000000003.jsonl.

    Terminal window
    ls audit/support/batches
    000000000001.jsonl
    000000000002.jsonl
    000000000003.jsonl
    000000000004.jsonl

    Change the ticket number in the intent from 4821 to 4822, one byte:

    Terminal window
    perl -pi -e 's#ticket/4821#ticket/4822#' audit/support/batches/000000000003.jsonl
  5. Verify again.

    Terminal window
    tenbri verify audit/support --key $PIN --checkpoint seen.note; echo "exit $?"
    broken at seq 3: bad_signature: the signature does not verify
    2 entries verified before it, stream tnb1:db888dbab715e39d31a8b0c7b6bd418a
    exit 1

    Reads that check the stream refuse it too. --verify makes query read the stream itself rather than trust a query index built earlier:

    Terminal window
    tenbri query audit/support --action reply_ticket --key $PIN --verify; echo "exit $?"
    tenbri: the stream does not verify: broken at seq 3: bad_signature: the signature does not verify
    exit 1

The edit changed the bytes the session key signed, so the signature on entry 3 no longer verifies and every verifier reports the first broken entry. A stream signed with another genesis key fails your pin with untrusted_root. An operator who holds the keys could still truncate or re-sign a rewritten stream; the checkpoint you kept catches that for the entries it covers (truncated, fork), which a pin alone cannot.