Skip to content

Releases

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 21 Oct 17:32
a5baf5e

python 0.5.0. See CHANGELOG.md.

v0.4.3

Choose a tag to compare

@github-actions github-actions released this 16 Oct 23:10
1c9c9b9

python 0.4.3. See CHANGELOG.md.

v0.4.2

Choose a tag to compare

@github-actions github-actions released this 14 Oct 19:38
7867298

python 0.4.2. See CHANGELOG.md.

v0.4.1

Choose a tag to compare

@github-actions github-actions released this 14 Oct 19:23
41f4e54

python 0.4.1. See CHANGELOG.md.

scitt-cose 0.4.0

Choose a tag to compare

@StevenMih StevenMih released this 28 Sep 03:28
7ee9a41

Upgrade notes

  • Breaking (wire shape): scitt_cose.cll.RangeProof,
    verify_range and verify_range_against_checkpoint changed shape
    (#46).
    RangeProof now carries from_index/to_index/witness
    instead of inclusion_from/inclusion_to, and the verify functions take
    body_digests (every record's digest in the range) instead of
    from_digest/to_digest. Range proofs produced in the old two-boundary
    shape do not verify under 0.4.0; regenerate them with a producer that emits
    the new shape.
  • Behaviour change: protected-header claims are surfaced only when the
    signature verifies (#53, #56).
    verify_receipt now populates
    ReceiptResult.iat, issuer, subject and protected_header_ext (which
    carries the -65537 grade, when present) only when ok is true. On a
    signature failure they stay None/{} and errors gains the finding
    "protected header claims withheld (iat/iss/sub/protected_header_ext):
    receipt signature did not verify". Callers that read iat or
    protected_header_ext from a failed result in 0.3.0 now get nothing
    there; check ok first. (issuer/subject are new in 0.4.0 and follow
    the same rule.) The Rust and Go
    verifiers apply the same rule.
  • DRAFT_COSE_MERKLE_TREE_PROOFS now equals "RFC 9942" (an alias of the
    new RFC_COSE_RECEIPTS) instead of
    "draft-ietf-cose-merkle-tree-proofs-18", and DRAFT_TRACKING_NOTICE no
    longer describes COSE Receipts as an Internet-Draft (#58). Code that
    compared against the old draft string needs updating.

Added

  • build_receipt(iss=, sub=, kid=) optional kwargs, and
    ReceiptResult.issuer / ReceiptResult.subject surfaced from the CWT
    claims map (claims 1 and 2), per RFC 9943 §6 (#49). Omitting all three
    reproduces the pre-existing wire shape byte-for-byte. kid continues to
    surface through protected_header_ext. The docstring states that sub is
    the registered statement's own subject, not the submitter.
  • HDR_GRADE = -65537 exported from scitt_cose.receipt as the single
    documented constant for the private-use witness-grade label (#49). The
    library still does not interpret the label's value.
  • build_receipt(iat=, grade=) optional kwargs (#57): iat goes into the
    protected CWT claims map (label 15, claim 6), grade under HDR_GRADE.
    With both None the output is byte-identical to before; a test pins the
    header bytes to the frozen synthetic-eddsa-iat-grade vector.
  • RFC_COSE_RECEIPTS = "RFC 9942" status constant, exported from
    scitt_cose; SUBSTRATE_RFCS now includes RFC 9942 (#58).
  • Rust receipt verifier (rust/scitt-cose, crate version 0.1.0) (#50): an
    offline, receipt-only verifier (coset + ed25519-dalek/p256) for RFC 9162
    SHA-256 COSE receipts against a pinned key, EdDSA and ES256, surfacing
    iat/grade. Not published to crates.io by this release. New
    test-vectors/receipt-v1/ set (a captured witness receipt, synthetic
    EdDSA/ES256 iat+grade and iat-only receipts, two tamper vectors);
    tests/test_crosslang_rust.py cross-checks the Rust binary against the
    Python library, required in CI via SCITT_REQUIRE_RUST=1.

Changed

  • RangeProof/verify_range/verify_range_against_checkpoint
    (scitt_cose.cll) now bind every leaf in the claimed range, not just the
    two boundary leaves (#46).
    RangeProof is a byte-identical port of
    cll.checkpoint.index.RangeProof (checkpointed-local-log, the reference):
    it carries from_index/to_index/witness instead of the old
    inclusion_from/inclusion_to pair of InclusionProofs, and
    verify_range/verify_range_against_checkpoint take body_digests
    (every record's own digest in the range) instead of from_digest/
    to_digest. Breaking, wire-incompatible with the old shape: the old
    two-boundary proof never touched any leaf strictly between the endpoints,
    so a deleted or replaced interior record verified anyway — this closes
    that gap. scope_note/verify_range_against_checkpoint's docstring now
    read "records from–to are present, unaltered, and bound to checkpoint
    C — this does not show that no other records exist" in place of the old
    "N of N claimed records" phrasing.

  • Standards status: SCITT Architecture (RFC 9943) and COSE Receipts
    (RFC 9942) are both described as published RFCs in _status.py, the CLI
    banner, module docstrings, README and CONTRIBUTING (#58). The wire shape
    (vds 395, vdp 396, RFC9162_SHA256 = 1) is unchanged from draft -18.

Fixed

  • verify_receipt no longer exposes header claims from a receipt whose
    signature fails (#53).
    Previously iat/issuer/subject/
    protected_header_ext were filled from the protected header before the
    signature check and left set on failure, so a receipt signed under a
    non-pinned key still exposed its grade alongside ok=False. The Rust
    verifier had the same defect and is fixed the same way, with the same
    "protected header claims withheld" finding. The frozen receipt-v1
    vectors now also run in the Python suite.
  • Go verifier (scitt-cose-go-verify): the statement path withholds
    header claims unless the signature verifies (#56).
    content_type/kid/
    iss/sub/string_claims are left empty on failure, with the finding
    "protected header claims withheld (content_type/kid/iss/sub/string_claims):
    statement signature did not verify", matching Python
    parse_signed_statement.

Hosted verifier (hosted_profiles/, viewer/ — not part of the PyPI package)

  • Bundle viewer: the "Completeness" ritual stage is renamed "Range
    membership" (#46). MMR_JS gains verifyRange, a byte-identical port of
    the reference verify_range; BUNDLE_JS's checkCompleteness calls it
    with every record's own digest rather than checking only the first/last
    record's inclusion. Rendered copy, docs/verification-trust-model.md and
    README.md updated to match.
  • The viewer verifies through the canonical agent-action-capsule TypeScript
    library, bundled as viewer/dist/aac-crypto.js, instead of inlined copies
    (#47). This fixes nested disclosed payloads false-mismatching (DE-3) and
    rendering empty, and format-4 chained capsule_id recompute. Bundle records
    render as a provenance tree. Capsule-id parity fixtures regenerated as
    format 4.
  • The Witness ritual stage renders receipt grades in words when
    receipt_grades is supplied; an ungraded receipt renders "ungraded"
    (#48). Without grade data the output is unchanged.
  • Hostname canonicalisation (#51): verify-site references moved to
    verify.agentactioncapsule.org across README, SECURITY, the demo and
    docs; _referrer_domain() now counts same-site navigation on
    verify.agentactioncapsule.org as same-origin (both hostnames kept in the
    allowlist).
  • Standards status text on the verify site, boundary table fits a 375px
    viewport, and witness.* is linked (#58).
  • .gcloudignore committed so a --source . deploy keeps viewer/dist
    (#55).

Tests and CI

  • tests/test_mmr_js_parity.py/tests/test_cll.py gain the full range
    vector set generated by scripts/generate_mmr_range_vectors.py (ported
    1:1 from checkpointed-local-log's own vectors): positive cases
    (single-leaf, three-leaf, first-leaf i.e. from_seq=1, cross-peak) and
    negative cases (replaced-interior-record, deleted-interior-record,
    sparse-selection-wrong-window, mismatched-checkpoint-root) — the exact
    bug class a two-boundary-inclusion proof could never catch (#46).
  • Hostname lint (.github/hostname_lint.py, hostname-lint workflow) fails
    CI on a stale verify/witness hostname (#51).
  • Internal-leak lint (.github/leak_lint.py, leak-lint workflow),
    fail-closed with an exact-text allowlist and a mutant-tested suite; the
    existing tree was cleaned to pass it (#52). Attribute-selector exemption
    and line-end handling synced (#54).
  • CI builds, tests and clippy-lints the Rust crate and requires the Rust
    cross-language check (#50).

v0.3.0

Choose a tag to compare

@StevenMih StevenMih released this 11 Sep 23:39
0432fa2

scitt-cose v0.3.0 — release notes (paste into the GitHub release body)

Tag: v0.3.0 (not yet created — see runbook)
Commit: 0432fa21b608dcc6b4a795b39b4c54e38bc9e68a (origin/main, PR #45, merged 2026-09-08)
Prior release: v0.2.2

Added

  • ReceiptResult.iat (int | None) — surfaces the iat (issued-at) integer from the
    protected header's CWT claims map (COSE label 15, CWT claim 6 per RFC 8392 §3.1.6) when
    present; None when absent. Read-only; no interpretation is applied by this library.

  • ReceiptResult.protected_header_ext (dict) — exposes every protected-header label
    that is not actively processed by this verifier (alg/1, crit/2, vds/395, CWT_Claims/15) as
    a plain {label: decoded_value} map. Gives any caller transparent access to profile-specific
    or private-use labels (e.g. negative label numbers) that were integrity-protected by the
    receipt signature, without the neutral library ascribing meaning to any specific label. Empty
    dict when no unrecognized labels are present.

  • Exported constants HDR_CWT_CLAIMS (15) and CWT_CLAIM_IAT (6) from scitt_cose.receipt.

  • HDR_CWT_CLAIMS (15) added to _RECEIPT_UNDERSTOOD so receipts that mark it critical are
    accepted by the verifier (RFC 9052 §3.1 compliance).

Changed

  • Version bumped 0.2.2 → 0.3.0 (minor bump: additive API surface only).

Compatibility

  • Fully backward-compatible. Existing call sites receive two additional fields (iat=None,
    protected_header_ext={}) when verifying receipts that carry no CWT claims or unrecognized
    protected labels. The byte-for-byte verification logic and ok/root/tree_size/
    leaf_index/errors semantics are unchanged.
  • This library carries no vocabulary for private-use labels or application profiles.
    Surfacing -65537 (or any other private label) in protected_header_ext is generic
    pass-through; the neutral library never names or interprets such labels.

Motivation

capsule-anchor commit 38455a8 (merged, undeployed as of this writing) signs iat (RFC 8392
§3.1.6 claim 6, inside HDR_CWT_CLAIMS label 15) and a witness grade (private-use protected
label -65537) into the COSE receipt's protected header. Before this release, neutral-lib
verifiers silently dropped both signed fields from ReceiptResult — they verified the signature
but never surfaced the values programmatically. This release adds a way to read them without
hand-decoding CBOR.

Note (release-prep finding, 2026-09-11): this is not on the deploy's critical path.
capsule-anchor never calls ReceiptResult.iat or .protected_header_ext anywhere in its own
codebase (checked: packages/capsule_anchor/cross_witness_conformance/checker.py's
verify_receipt call only reads .ok/.root/.tree_size/.errors, unchanged since before
0.2.2). capsule-anchor builds its own receipt bytes by hand in
packages/capsule_anchor/anchoring/service.py::build_cose_receipt using locally-defined
constants (_CWT_IAT = 6, _COSE_GRADE_LABEL = -65537), not anything from scitt_cose.receipt.
Its only runtime import from this library is HDR_CWT_CLAIMS/CWT_SUB from
scitt_cose.statement, both present unchanged since before v0.2.2. capsule-anchor's full test
suite (241 passed, 7 skipped) is byte-identical whether scitt-cose 0.2.2 or 0.3.0 is installed
(verified both ways in this pass). So the anchor's floor scitt-cose>=0.2.2 is sufficient at
runtime; 0.3.0 only benefits external verifiers that want ReceiptResult.iat /
.protected_header_ext instead of hand-decoding the CBOR protected header themselves.

Test results (this pass, 2026-09-11, against origin/main @ 0432fa2)

381 passed, 12 skipped in 18.59s

Known drift — read before tagging

scitt-cose 0.3.0 is already live on PyPI (uploaded 2026-09-09T02:02:50Z — confirmed via
https://pypi.org/pypi/scitt-cose/0.3.0/json), despite there being no v0.3.0 git tag and no
GitHub Release
for it (gh release list tops out at v0.2.2, and the release.yml workflow —
which fires on release: published via the PyPI trusted publisher — shows no run for 0.3.0). The
package is real and installable (pip install scitt-cose resolves to 0.3.0 today; confirmed by
a fresh install in this pass). How it reached PyPI without a tag/release is not established by
this task — possibly a manual twine upload during the trusted-publisher workflow setup
(release-workflow-pypi-trusted-publisher branch, PR merged the release.yml workflow itself).
Practical effect: the PyPI-publish step in the runbook is very likely a no-op — it is already
done. What is still missing is the git tag and the GitHub Release (cosmetic/provenance, not a
runtime blocker). Steven should decide whether to tag+release now to match what is already on
PyPI, or investigate the drift first.

scitt-cose 0.2.2

Choose a tag to compare

@StevenMih StevenMih released this 22 Aug 23:39
1fd1cee

Fixes the 0.2.1 CLL/packaging regressions.

  • cll: drop peaks_digest from Checkpoint (field/to_dict/from_dict) to match capsule-emit 0.4.0's single-commitment (Option-C) checkpoint shape. 0.2.1's verifier hard-required a field the 0.4.0 producer no longer emits; this restores producer↔verifier compatibility.
  • packaging: exclude test_ritual_authenticity_js.py (imports hosted_profiles) from the sdist — closes a neutrality leak the packaging guard caught. Un-reds test (3.9)/test (3.12) on main.

v0.2.1 — CLL log verification + packaging fixes

Choose a tag to compare

@StevenMih StevenMih released this 22 Aug 21:27
9cebba4

CLL log verification (inclusion/consistency/range proofs against a checkpoint, #38), viewer authenticity three-valued (#36), sdist packaging fixes (#22). Unblocks the capsule_emit.checkpoint consumers. Trusted-publisher auto-publish to PyPI.

v0.1.1 — hardening release

Choose a tag to compare

@StevenMih StevenMih released this 12 Jun 18:29

The hardening release: applies every fix from the pre-Vienna verifier hardening review (#2, #3). The differential-fuzz baseline is now empty — fuzzing the Python and Go runtimes against each other finds zero known divergences, and the fuzz CI job fails on any divergence that reopens. Every baseline class was eliminated by a fix, not silenced.

Fixed (high): trailing/indefinite CBOR malleability (H4), Go int64-overflow infinite loop (H2), RecursionError escape (H3), bytes(int) memory DoS (H1). Fixed (medium): duplicate protected-header keys (M2), implicit path-length cap (M5), leaked parser exceptions (M6), unverified identity surfaced before signature check (M3), hosted fail-open on partial input (M1). Fixed (low): attacker CBOR reflected in errors (L1), receipt crit-label gap (L3). Accepted with documentation: ES256 high-s malleability (L2, see Security Considerations).

No frozen v1 vector bytes or well-formed-input verdicts changed: the full cross-implementation vector suite passes unchanged, 172 tests green with the Go cross-check required.

On PyPI: https://pypi.org/project/scitt-cose/0.1.1/

v0.1.0 — cross-implementation test-vector set v1

Choose a tag to compare

@StevenMih StevenMih released this 12 Jun 08:49
50156e3

Adds the frozen, append-only cross-implementation vector set (test-vectors/): two happy-path vectors (EdDSA, ES256) and three negative vectors (tampered inclusion path, unsupported vds, bad statement signature), each with committed bytes, TEST-ONLY keys, and an expected.json. The runner ships in the package: pip install "scitt-cose>=0.1.0", then python -m scitt_cose.vectors path/to/test-vectors (--json for machine-readable output). The Go clean-room implementation runs the same manifest. test-vectors/SHA256SUMS pins every published byte and CI fails on any mutation. On PyPI: https://pypi.org/project/scitt-cose/0.1.0/ — a mismatch against your implementation is exactly the report we want; please open an issue.