Repository navigation
Releases
v0.5.0
v0.4.3
v0.4.2
v0.4.1
scitt-cose 0.4.0
Upgrade notes
- Breaking (wire shape):
scitt_cose.cll.RangeProof,
verify_rangeandverify_range_against_checkpointchanged shape
(#46).RangeProofnow carriesfrom_index/to_index/witness
instead ofinclusion_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_receiptnow populates
ReceiptResult.iat,issuer,subjectandprotected_header_ext(which
carries the-65537grade, when present) only whenokis true. On a
signature failure they stayNone/{}anderrorsgains the finding
"protected header claims withheld (iat/iss/sub/protected_header_ext):
receipt signature did not verify". Callers that readiator
protected_header_extfrom a failed result in 0.3.0 now get nothing
there; checkokfirst. (issuer/subjectare new in 0.4.0 and follow
the same rule.) The Rust and Go
verifiers apply the same rule. DRAFT_COSE_MERKLE_TREE_PROOFSnow equals"RFC 9942"(an alias of the
newRFC_COSE_RECEIPTS) instead of
"draft-ietf-cose-merkle-tree-proofs-18", andDRAFT_TRACKING_NOTICEno
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.subjectsurfaced 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.kidcontinues to
surface throughprotected_header_ext. The docstring states thatsubis
the registered statement's own subject, not the submitter.HDR_GRADE = -65537exported fromscitt_cose.receiptas 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):iatgoes into the
protected CWT claims map (label 15, claim 6),gradeunderHDR_GRADE.
With bothNonethe output is byte-identical to before; a test pins the
header bytes to the frozensynthetic-eddsa-iat-gradevector.RFC_COSE_RECEIPTS = "RFC 9942"status constant, exported from
scitt_cose;SUBSTRATE_RFCSnow 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.pycross-checks the Rust binary against the
Python library, required in CI viaSCITT_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).RangeProofis a byte-identical port of
cll.checkpoint.index.RangeProof(checkpointed-local-log, the reference):
it carriesfrom_index/to_index/witnessinstead of the old
inclusion_from/inclusion_topair ofInclusionProofs, and
verify_range/verify_range_against_checkpointtakebody_digests
(every record's own digest in the range) instead offrom_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_receiptno longer exposes header claims from a receipt whose
signature fails (#53). Previouslyiat/issuer/subject/
protected_header_extwere 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 alongsideok=False. The Rust
verifier had the same defect and is fixed the same way, with the same
"protected header claims withheld" finding. The frozenreceipt-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_claimsare 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_JSgainsverifyRange, a byte-identical port of
the referenceverify_range;BUNDLE_JS'scheckCompletenesscalls it
with every record's own digest rather than checking only the first/last
record's inclusion. Rendered copy,docs/verification-trust-model.mdand
README.mdupdated to match. - The viewer verifies through the canonical agent-action-capsule TypeScript
library, bundled asviewer/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_gradesis supplied; an ungraded receipt renders "ungraded"
(#48). Without grade data the output is unchanged. - Hostname canonicalisation (#51): verify-site references moved to
verify.agentactioncapsule.orgacross README, SECURITY, the demo and
docs;_referrer_domain()now counts same-site navigation on
verify.agentactioncapsule.orgas same-origin (both hostnames kept in the
allowlist). - Standards status text on the verify site, boundary table fits a 375px
viewport, andwitness.*is linked (#58). .gcloudignorecommitted so a--source .deploy keepsviewer/dist
(#55).
Tests and CI
tests/test_mmr_js_parity.py/tests/test_cll.pygain the full range
vector set generated byscripts/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-lintworkflow) fails
CI on a stale verify/witness hostname (#51). - Internal-leak lint (
.github/leak_lint.py,leak-lintworkflow),
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
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 theiat(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;Nonewhen 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) andCWT_CLAIM_IAT(6) fromscitt_cose.receipt. -
HDR_CWT_CLAIMS(15) added to_RECEIPT_UNDERSTOODso 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 andok/root/tree_size/
leaf_index/errorssemantics are unchanged. - This library carries no vocabulary for private-use labels or application profiles.
Surfacing-65537(or any other private label) inprotected_header_extis 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
Fixes the 0.2.1 CLL/packaging regressions.
- cll: drop
peaks_digestfromCheckpoint(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(importshosted_profiles) from the sdist — closes a neutrality leak the packaging guard caught. Un-redstest (3.9)/test (3.12)on main.
v0.2.1 — CLL log verification + packaging fixes
v0.1.1 — hardening release
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.
v0.1.0 — cross-implementation test-vector set v1
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.