Skip to content

[PM-22028] fix(toolkit): enforce EOF on untagged CLI hex decode (audit Issue H)#1437

Merged
m2ux merged 4 commits into
mainfrom
fix/307-unconditional-fallback-untagged-decoding
May 6, 2026
Merged

[PM-22028] fix(toolkit): enforce EOF on untagged CLI hex decode (audit Issue H)#1437
m2ux merged 4 commits into
mainfrom
fix/307-unconditional-fallback-untagged-decoding

Conversation

@m2ux

@m2ux m2ux commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Address audit Issue H (#307) by enforcing EOF on the untagged hex-decode path in the toolkit CLI. This closes the silent-fallback ambiguity surface (trailing bytes after a valid parse were tolerated and could enable type re-interpretation) without changing the wire format. coin_public_decode and contract_address_decode keep their untagged decoding, which is the format prescribed by ADR-0022.

🎫 Ticket 📐 Engineering


Motivation

The toolkit CLI accepts hex-encoded inputs for ledger types (contract addresses, coin public keys, hash outputs). The shared decoder previously tolerated trailing bytes after a successful parse, allowing subtly malformed inputs to be silently accepted and re-interpreted as a different shape than the user intended. The Least Authority audit (Issue H) flagged this silent-fallback behaviour as an ambiguity attack surface.

The first iteration of this PR mitigated by switching the two CLI parsers from untagged to tagged decoding. Review feedback (ozgb, 2026-05-01) flagged that change as a violation of ADR-0022, which prescribes untagged serialization for wallet keys, contract addresses, and types behaving like keys/addresses (Bech32m HRP carries the type at the user-facing boundary). The same change was previously attempted and reverted in PR #853. This revision restores the architecturally-correct scope: EOF enforcement on the untagged path is the defense-in-depth fix.


Changes

  • hex_ledger_untagged_decode — Adds EOF enforcement: trailing bytes after a successful deserialize are rejected with a clear error message indicating how many extra bytes were found. This is the central audit hardening.
  • coin_public_decode / contract_address_decode — Keep their existing untagged decoding (no wire-format change), with a new inline comment citing ADR-0022 + PR revert tagged serialization for contract addresses and coin public #853 to prevent future re-introduction of the tagged switch.
  • Error messages"failed to parse seed""invalid hex input" in the generic hex decode helpers (cosmetic, kept from the original PR).
  • Tests — Rewritten to assert untagged-acceptance for the two parsers, EOF-rejection on hex_ledger_untagged_decode, and invalid-hex / truncated-input rejection.
  • Test fixture — Removed res/test-contract/contract_address_undeployed_tagged.mn (added by the original PR; no longer used after revert).

📌 Submission Checklist

  • Changes are backward-compatible (or flagged if breaking)
  • Pull request description explains why the change is needed
  • Self-reviewed the diff
  • I have included a change file, or skipped for this reason: N/A
  • If the changes introduce a new feature, I have bumped the node minor version
  • Update documentation (if relevant)
  • No new todos introduced

🔱 Fork Strategy

  • Node Runtime Update
  • Node Client Update
  • Other
  • N/A

🗹 TODO before merging

  • CI passes (build, test, clippy)

Closes #307

@m2ux m2ux requested a review from a team as a code owner April 28, 2026 10:37
@m2ux m2ux force-pushed the fix/307-unconditional-fallback-untagged-decoding branch 2 times, most recently from 1259c20 to 27da62c Compare April 28, 2026 10:52
Switch coin_public_decode and contract_address_decode from
hex_ledger_untagged_decode to hex_ledger_decode (tagged), eliminating
the unconditional fallback that accepted untagged input for types that
implement Tagged.

- Replace hex_ledger_untagged_decode with hex_ledger_decode in
  coin_public_decode and contract_address_decode
- Add EOF enforcement to hex_ledger_untagged_decode (defense-in-depth)
- Update test fixture in generate_txs.rs to use tagged hex format
- Add 11 unit tests covering tagged/untagged/wrong-tag/trailing-bytes/
  invalid-hex scenarios

Closes #307

Signed-off-by: Mike Clay <mike.clay@shielded.io>
@m2ux m2ux force-pushed the fix/307-unconditional-fallback-untagged-decoding branch from 27da62c to 1c53d03 Compare April 28, 2026 10:52
@m2ux m2ux self-assigned this Apr 28, 2026
@m2ux m2ux changed the title fix(toolkit): switch CLI parsers to tagged deserialization [PM-22028] fix(toolkit): switch CLI parsers to tagged deserialization Apr 29, 2026
@m2ux

m2ux commented May 1, 2026

Copy link
Copy Markdown
Contributor Author

/bot rebuild-metadata

@github-actions

github-actions Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

✅ Metadata rebuild complete. No changes detected.

@ozgb ozgb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should stay as untagged: https://github.com/midnightntwrk/midnight-architecture/blob/main/adrs/0022-when-to-use-tagged-serialization.md#adopt-tagged-serialization-in-most-cases-but-use-untagged-one-for-wallet-keys-and-addresses-as-well-as-couple-other-types-which-behave-like-keys-or-addresses

We could update this PR to add a comment to that effect - this change was made and reverted in a PR before, we should add some comment to prevent it happening again!

@m2ux m2ux changed the title [PM-22028] fix(toolkit): switch CLI parsers to tagged deserialization [PM-22028] fix(toolkit): enforce EOF on untagged CLI hex decode (audit Issue H) May 5, 2026
Revert coin_public_decode and contract_address_decode to use
hex_ledger_untagged_decode. Per ADR-0022 wallet keys and addresses use
untagged serialization; Bech32m HRP plays the role of a tag at the
user-facing boundary. The same tagged-switch was attempted and reverted
in PR #853; inline comments above both parsers cite ADR-0022 and PR #853
to prevent future re-introduction.

EOF enforcement on hex_ledger_untagged_decode (the audit-#307 fix) and
the improved error-message wording ("invalid hex input") are retained.
The unit tests are rewritten to assert untagged-acceptance plus EOF /
truncation / invalid-hex rejection on coin_public_decode,
contract_address_decode, and hex_ledger_untagged_decode::<HashOutput>.
The tagged test fixture (contract_address_undeployed_tagged.mn) is
removed and generate_txs.rs reverts to the existing untagged fixture.

Closes #307

Signed-off-by: Mike Clay <mike.clay@shielded.io>
@m2ux

m2ux commented May 5, 2026

Copy link
Copy Markdown
Contributor Author

@ozgb — addressing your review feedback in commit 4329d0b.

Summary of changes (per your review comment):

  1. Reverted the parser switches: coin_public_decode and contract_address_decode are back on hex_ledger_untagged_decode. Per ADR-0022, wallet keys, contract addresses, and types behaving like keys/addresses use untagged serialization (Bech32m HRP at the user-facing boundary plays the role of a tag).
  2. Added inline comments above both coin_public_decode and contract_address_decode referencing ADR-0022 and PR #853, so the next reader (or contributor) is warned off re-doing the same change.
  3. Removed the tagged test fixture res/test-contract/contract_address_undeployed_tagged.mn that the first iteration added; generate_txs.rs is back on the pre-existing untagged fixture.
  4. Kept the actual audit hardening: hex_ledger_untagged_decode now enforces EOF (rejects trailing bytes after a successful deserialize, with a clear error). This is the defense-in-depth response to Issue H's silent-fallback concern, on the architecturally-correct (untagged) path.
  5. Tests rewritten in cli_parsers.rs (12 cases): untagged-acceptance for both parsers, EOF-rejection, truncation rejection, invalid-hex rejection.

Verification: cargo fmt --check clean, cargo clippy -p midnight-node-toolkit --lib -- -D warnings clean, cargo test -p midnight-node-toolkit --lib 89/89 pass.

The PR description has been updated to reflect the EOF-only scope. Full re-plan analysis (architectural reasoning, what stays vs. reverts, open questions) is in the engineering planning folder: 09-pr-review-response.md. The companion artifacts (05-work-package-plan.md, 05-test-plan.md, 01-design-philosophy.md, 01-assumptions-log.md, 06-*-2.md, 07-*-2.md, 10-validation-summary.md) were re-issued under the corrected ADR-0022-aligned framing; the first-pass artifacts are retained as historical record.

Ready for another look when you have time.

@m2ux m2ux added this pull request to the merge queue May 6, 2026
Merged via the queue into main with commit e533b53 May 6, 2026
33 checks passed
@m2ux m2ux deleted the fix/307-unconditional-fallback-untagged-decoding branch May 6, 2026 09:40
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.

2 participants