Repository navigation
Slice 124: what the receipts are, in the standard's words (and why the fields are not renamed) - #99
Merged
Conversation
The slice was specified as a rename of the signed payload's fields to RFC 9943, COSE and CWT names under a new scheme identifier. Writing the mapping table the same slice asked for is what showed the rename to be wrong, and amendment 1 replaced acceptance criteria 2, 3 and 4 before any code was written. RFC 9943 defines two objects where this tree has one word. A Signed Statement is issued by an Issuer. A Receipt is issued by a Transparency Service and MUST carry proof of inclusion in a Verifiable Data Structure. Trinity runs no Transparency Service, so it issues the first and never the second. Dressing its fields in iss, sub, kid and iat would tell any reader who knows those names that this is a SCITT object, and they would then expect a transparency service, a verifiable data structure and inclusion proofs. None of that exists here. That is a conformance claim made by appearance rather than by a row in the standards register, which is the precise failure that register exists to prevent; the slice's own Scope section had already refused the word "receipt" for that reason while its criteria asked for the same claim in field names. What landed instead: - docs/receipt-scheme-mapping.md: the terminology collision, the envelope Trinity signs with today (DSSE pre-authentication encoding, not COSE), the field by field mapping, and the three fields with no equivalent because they are the hash chain SCITT delegates to a service Trinity does not run. - An RFC 9943 row in docs/09-standards-register.md at `not claimed`. - A README paragraph saying Trinity issues Signed Statements and never SCITT Receipts, so nobody infers conformance from the house name. - Five checks asserting that nothing was renamed, that no receipt byte changed, and that the document cannot drift from the payload it describes. Two drafting errors were corrected against the tree before committing: the draft mapped `subject_ref` to CWT `sub`, but `subject_ref` and `meta` are row columns outside the signature and only `subject` is signed; and the draft never said what envelope Trinity signs with, while its own closing argument turns on replacing that envelope. The census checks were demonstrated against three planted violations rather than against agreement with today's tree: a renamed table row, an eleventh signed field with the document untouched, and a reformatted table that made the parser match nothing. The last of those is the guard against the check going vacuous. Nothing under lib/ changed; `git diff --stat lib/` is empty. Deferred to the owner: a COSE_Sign1 envelope with CWT claims, which is the change that would be real rather than cosmetic. Opened by a defect found while building this: slice 125. The slice 090 Logger redaction filter calls Enum.map/2 on the message body, OTP chardata is routinely an improper list whose tail is a binary, Enum.map/2 raises on one, and OTP removes a raising filter from the handler permanently. A db_connection disconnect is enough to trigger it, so the credential mask dies on the first connection wobble and stays dead for the life of the node while every test passes. Signed-off-by: Ayla Croft <aylacroft@proton.me>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
IETF RFC 9943 (SCITT) standardises the class of object Trinity's receipts belong to. This slice says exactly where Trinity sits relative to it, including the part that does not fit, and deliberately does not do the rename the slice was written to do.
The slice reversed itself, and that is the result
It was specified as: rename
signed_payload's fields to their RFC 9943, COSE and CWT names, emit under a new scheme, teach the verifier both. Writing AC1's mapping table is what killed it, because building the table meant reading what RFC 9943 actually defines:A SCITT Receipt MUST carry inclusion proofs. Trinity runs no Transparency Service and issues none. So what Trinity calls a receipt is a Signed Statement, and never a SCITT Receipt.
Renaming the fields to
iss,sub,kidandiatwould tell every reader who knows those names that this is a SCITT object, and they would then reasonably expect a transparency service, a verifiable data structure and inclusion proofs behind it. That is a conformance claim made by appearance rather than by a row indocs/09-standards-register.md, which is the precise failure that register exists to prevent. The slice's own Scope section had already refused the word "receipt" for exactly that reason, while its acceptance criteria asked for the same claim in field names. The Scope section was right.Amendment 1 replaced ACs 2, 3 and 4 before any code was written, and the replacements prove the inverse property: that nothing was renamed and no receipt byte changed.
What landed
docs/receipt-scheme-mapping.md: the terminology collision, the envelope Trinity signs with today (DSSE pre-authentication encoding oftrinity/receipt/<scheme>, not COSE), the field by field mapping to COSE, CWT and in-toto, and the three fields with no equivalent (seq,prev_hash,chain_scope) because they are the hash chain SCITT delegates to a service Trinity does not run. The one construct the standard requires and Trinity lacks, CWTiss, gets its own row: that is the more useful half of the answer.not claimed.Nothing under
lib/changed.git diff --stat lib/is empty.Two drafting errors, caught against the tree rather than against memory
subject_refto CWTsuband called it the closer match.subject_refis not signed at all:ChainWriter'sbodycarriessubjectonly, andsubject_refandmetaare row columns outside the signature. A mapping document that maps an unsigned column to a signed claim is the exact error it exists to prevent.COSE_Sign1.The census was proved by planting, not by agreeing with today's tree
key_idtoissuer_hintkey_id, extraissuer_hintpolicy_idadded toChainWriter, document untouchedpolicy_idunmapped, "document says ten keys, the payload has 11"The gate was run three times and only the third counted
trinity.names,trinity.reuseandtrinity.secrets.scanenumerate tracked files. The first two runs both said 642, the count from before this slice: the new document and test were untracked, so every census passed without looking at them. Staging first took it to 644.Carried with this slice: a security defect found by accident, opened as slice 125 and not fixed here
Driving the running app to capture README screenshots, the receipts page returned 500 and the log carried:
That is OTP reporting that the slice 090 credential-masking Logger filter raised and was permanently removed from the handler. Everything logged afterwards is unredacted and nothing says so. The event that did it:
Note the
|before the last element: an improper list, an iolist whose tail is a binary. OTP chardata is routinely improper, andEnum.map/2raisesFunctionClauseErroron one.redact_msg({format, args}) when is_list(args)catches{:string, iolist}because an iolist is a list, then walks off the end into the tail.Three things make it worse than a crash: the trigger is a
db_connectiondisconnect, which is what an ordinary connection blip logs; OTP's removal is permanent and silent; and the module's@moduledocpromises the filter never returns:stop, which is true and beside the point, because a filter that raises is discarded outright. In a long-running deployment the credential mask dies on the first connection wobble and stays dead for the life of the node while every test passes, because the suite only ever feeds it proper lists.For the reviewer
COSE_Sign1envelope with CWT claims in the protected header. That is the change that would be real rather than cosmetic, and it is a scheme bump with a dual-reading verifier behind it.