Skip to content

fix(evolution): preserve COSE_Sign1 protected-header bytes on decode (#480) - #640

Open
Kshot3000 wants to merge 1 commit into
IntersectMBO:mainfrom
Kshot3000:fix/cose-preserve-protected-header-bytes
Open

Kshot3000 wants to merge 1 commit into
IntersectMBO:mainfrom
Kshot3000:fix/cose-preserve-protected-header-bytes

Conversation

@Kshot3000

Copy link
Copy Markdown

Summary

Fixes #480: COSE_Sign1.signedData() rebuilt the Sig_structure by re-encoding the parsed protected HeaderMap with canonical CBOR, and the decoder discarded the original protected-header bytes. In COSE (RFC 9052) the protected header is a bstr whose exact bytes are what the signature covers, so verification must operate on the received bytes. Any signer whose protected-header bytes differ from this SDK's canonical re-encode (for example a non-minimal integer encoding) had a valid signature rejected — and, symmetrically, a signature was accepted against transmitted bytes it never covered, because verification used the SDK's own re-encode instead of the wire bytes.

Fix

packages/evolution/src/cose/Sign1.ts:

  • A module-level WeakMap (protectedBytesCache) captures a copy of the received protected-header bytes at decode time, following the formatCache pattern already used in Transaction.ts. Only the default codec options populate the cache — non-default options opt out, since custom options imply the caller wants re-canonicalization.
  • signedData() replays the preserved bytes when present and falls back to the existing canonical encode for instances built locally (the builder path signs over that same canonical encode, so locally built messages are unchanged).
  • The COSESign1FromCBORBytes encode path replays the preserved bytes too, so a decode/encode round trip reproduces the original encoding byte-for-byte. The parsed HeaderMap remains available as the on-demand view of the headers.

Tests

New file packages/evolution/test/Sign1.ProtectedBytes.test.ts (4 tests), each verified RED on main one at a time before the fix:

  • verifyData accepts a valid signature over non-minimally encoded protected headers (label 1 written as the two-byte uint 0x18 0x01) — on main it returned false.
  • signedData() replays the original protected-header bytes — on main it produced the canonical a20127… form instead of the received a2180127… bytes.
  • Re-encoding a decoded COSE_Sign1 reproduces the original bytes.
  • A signature made over the canonical re-encode does not verify when the transmitted protected bytes differ (on main this returned true, since verification ignored the wire bytes).

GREEN after the fix: the new file passes 4/4, and the full package suite passes from a clean run: 1682 passed / 75 skipped / 0 failed (test files 102 passed / 1 skipped of 103 — exactly main's 1678-test baseline plus the 4 new tests). tsc --noEmit and eslint are clean, and a patch changeset is included.


Independent builder — not affiliated with the Cardano Foundation, IOG or Intersect.

@Kshot9000 (https://x.com/kshot9000) · Tips (ADA): addr1q8hnl6vl5a6k3rw3n5g3jtte696zcl76kfatzv7gpswa9r0dj7fma6klq55y4ffm7tf0em09udnyhuk4ah92pl5x9jpqjae44v

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

COSE_Sign1 signedData() re-encodes protected headers instead of preserving the original signed bytes

1 participant