continuity-receipt 0.3.3

Independent Rust verifier for continuity-receipt/0.1-0.3 bundles (the Python implementation remains the reference)
Documentation
# continuity-receipt (Rust)

Second, independent implementation of the Continuity Receipt verifier for
`continuity-receipt/0.1`-`0.3` bundles and verification receipts (companion
v1). The Python implementation in `../continuity_receipt/` remains the
reference; this crate exists for the two-independent-implementations bar,
native embedding, and single-binary deployment.

It re-implements the pinned JCS subset (`canon.rs`), `did:key` Ed25519
verification (`didkey.rs`), and the full verification algorithm with the same
error codes, verdict precedence, and JSON output shape as
`continuity_receipt.verify` (`verify.rs`) and
`continuity_receipt.verification` (`verification.rs`).

## Install

```sh
cargo add continuity-receipt        # library
cargo install continuity-receipt    # CLIs: verify, disclose, anchor, verify-receipt
```

The crate tracks `continuity-receipt/0.1`-`0.3` bundles; the Python
implementation in this repository remains the reference. Publication
runbook: [`PUBLISH.md`](PUBLISH.md).

## Run

```sh
cargo run --bin continuity-receipt-verify -- ../vectors/02_happy_full.json
cargo run --bin continuity-receipt-verify -- ../vectors/10b_anchor_missing.json --require-anchor
```

The binary prints the same JSON shape as the Python CLI (`verdict`, `errors`,
`provisional_reasons`, `insufficient_reasons`, `summary`) and exits `0` iff the
verdict is `TRUSTED`, else `1`.

## Selective disclosure

`continuity-receipt-disclose` mirrors `continuity_receipt/disclose.py`
(`redact` / `verify` / `reveal` / `check`):

```sh
cargo run --bin continuity-receipt-disclose -- redact \
  --bundle ../vectors/02_happy_full.json \
  --path receipts[3].body.spec_ref \
  --out /tmp/redacted.json --map /tmp/map.json --gate-key /tmp/gate.key

cargo run --bin continuity-receipt-disclose -- verify \
  --bundle /tmp/redacted.json --map /tmp/map.json

cargo run --bin continuity-receipt-disclose -- reveal \
  --bundle /tmp/redacted.json --map /tmp/map.json \
  --path receipts[3].body.spec_ref --out /tmp/package.json

cargo run --bin continuity-receipt-disclose -- check \
  --salt <hex> --value '["quality-ok"]' --commit sha256:...
```

The gate-key file is the raw 32-byte Ed25519 seed. Redaction re-signs the
modified receipt and every receipt after it, so the signer must be the
chain's issuer; redacting a cross-checked field (for example `spend_cap`)
fails closed to `UNTRUSTED` because a commitment cannot prove the claim.

## Verification receipts

`continuity-receipt-verify-receipt` mirrors
`continuity_receipt/verification.py`: shape and consistency checks
(`error_codes` equals the codes in `errors`; the verdict equals the class
implied by the errors/reasons), the Ed25519 signature over the canonical view
minus `sig`, optional bundle-digest and issuer-revocation checks.

```sh
cargo run --bin continuity-receipt-verify-receipt -- \
  ../vectors/verification/01_valid.json --bundle ../vectors/verification/bundle.json
cargo run --bin continuity-receipt-verify-receipt -- \
  ../vectors/verification/12_revoked_issuer.json \
  --bundle ../vectors/verification/bundle.json \
  --revocations ../vectors/verification/12_revoked_issuer.revocations.json
cargo run --bin continuity-receipt-verify-receipt -- \
  ../vectors/verification/01_valid.json --canonical /tmp/receipt.canonical   # prints the anchor digest
```

Exit `0` iff the receipt is valid. `--revocations` reads a local revocation
document (the Python CLI also accepts URLs).

## OpenTimestamps anchors

`continuity-receipt-anchor` mirrors `continuity_receipt/anchor.py`: replay a
detached `.ots` proof from the file digest and verify a Bitcoin attestation
against a supplied 80-byte block header by exact merkle-root equality.

```sh
cargo run --bin continuity-receipt-anchor -- verify proof.ots \
  --digest sha256:<hex> [--header <80-byte hex> --height <n>] [--json]

cargo run --bin continuity-receipt-anchor -- verify proof.ots \
  --bundle ../vectors/08_redacted_disclosed.json --target <receipt_id> --json
```

Statuses `verified` / `unverified` / `mismatch` / `invalid` with the same
machine codes as Python; exit `0` iff `verified`. Header chain validation is
out of scope by design (`../ANCHORING.md`): the header is trusted input, not a
substitute for proof-of-work or confirmation checks.

## Tests

```sh
cargo test
```

The vector runner reads `../vectors/manifest.json` and checks all 26 bundle
vectors against their expected verdict and error code; `tests/verification.rs`
does the same for the 20 verification-receipt vectors
(`../vectors/verification/manifest.json`), including consistency violations
and receipt digests. Malformed-bundle smoke cases
(empty object, non-object, missing receipts) are included so the verifier
returns structured errors instead of panicking. `tests/disclose.rs` covers
redaction, tail re-signing, refusal cases, and commitment recomputation
against the frozen vectors. `tests/fuzz_corpus.rs` mutates every vector
(deterministic seed) plus synthetic malformed shapes and byte truncations:
every case must yield a structured verdict with coded errors and never
panic. Set `CR_FUZZ_CORPUS_DIR=<dir>` to write the generated corpus to disk.

`tests/anchor.rs` covers synthetic proofs (LEB128 varuints, reverse/prepend,
truncation, keccak refusal) and the five real `.ots` fixtures, including the
published keccak negative case.

The Python↔Rust differentials run in CI after `cargo build`:

```sh
python3 tools/differential_vectors.py               # verdict + error-code parity
python3 tools/differential_disclose.py              # maps, signatures, cross-verification
python3 tools/differential_anchor.py                # status/code/confirmed parity
python3 tools/differential_verification_receipts.py # validity/codes/digest parity
```

## Intentional, documented differences from Python

- Floats and canonicalization failures produce a structured `malformed` (or
  the relevant check's) error instead of a Python exception/traceback.
- Invalid JSON input to the CLI prints a structured `malformed` result and
  exits `1` instead of raising.
- Non-list `anchors`/`revocations` values and other deeply malformed shapes
  are failed closed with the corresponding structured error where Python would
  raise.
- `disclose redact` additionally accepts repeatable `--salt <path>=<hex>` for
  reproducible commitments; the Python CLI always randomizes salts.
- `disclose verify` does not accept `--revocations` until the Rust
  revocation-list loader lands; external lists remain Python-only. The
  verification-receipt CLI accepts local revocation documents (no URLs).
- `anchor verify` human (non-`--json`) output prints attestations as compact
  JSON rather than Python dict reprs; the `--json` shape is identical.