Smart Contract Diff Audit L-04: `PswapNote::remainder_note` Recomputes a Tag that the On-Chain Script Only Ever Copies
- Linguagem predominante
- Rust
- Estrelas
- 132
- Forks
- 167
- Merge médio
- 1d 23h
- PRs com merge (30d)
- 110
Descrição
On a partial fill, the `PSWAP` note script builds the remainder note's tag by [reading it directly off the parent note's own metadata](https://github.com/0xMiden/protocol/blob/8411bf093bde25285708faac152b6d7269009617/crates/miden-standards/asm/standards/notes/pswap.masm#L712-L728) via `exec.active_note::get_metadata` followed by `exec.note::metadata_into_tag`, then passing that value unchanged into `create_remainder_note`. The kernel accepts whatever tag arrives this way, [checking only that it fits a `u32`](https://github.com/0xMiden/protocol/blob/8411bf093bde25285708faac152b6d7269009617/crates/miden-protocol/asm/kernels/transaction-core/src/output_note.masm#L476-L481). The remainder therefore always carries the exact same tag as its parent, whatever that tag happens to be.
[`PswapNote::remainder_note`](https://github.com/0xMiden/protocol/blob/8411bf093bde25285708faac152b6d7269009617/crates/miden-standards/src/note/pswap.rs#L598-L643) does not mirror this. It derives the remainder's tag by calling [`PswapNote::create_tag`](https://github.com/0xMiden/protocol/blob/8411bf093bde25285708faac152b6d7269009617/crates/miden-standards/src/note/pswap.rs#L648-L682), which recomputes a canonical tag from the note type, script root, and the two faucet IDs, rather than reusing the parent's actual tag. `PswapNote` has no field for the original tag to begin with: [`TryFrom<&Note> for PswapNote`](https://github.com/0xMiden/protocol/blob/8411bf093bde25285708faac152b6d7269009617/crates/miden-standards/src/note/pswap.rs#L844-L880) never reads it off the parsed note, and [`From for Note`](https://github.com/0xMiden/protocol/blob/8411bf093bde25285708faac152b6d7269009617/crates/miden-standards/src/note/pswap.rs#L822-L838) always re-derives it through the same `create_tag` call. As long as a `PSWAP` note was itself built through this library, its tag already equals what `create_tag` would produce, and the two paths agree. They only diverge when a `PSWAP` note was constructed by hand with a tag `create_tag` would not have chosen, for instance a private, non-canonical tag aimed at a specific counterparty. The kernel does not require the canonical tag and does not reject one, so such a note executes normally on chain, including its own remainder handling, which faithfully copies the non-canonical tag forward at every partial fill.
Since the tag is part of the metadata word hashed into both the [`NoteId`](https://github.com/0xMiden/protocol/blob/8411bf093bde25285708faac152b6d7269009617/crates/miden-protocol/src/note/note_id.rs#L19-L31) and the [`Nullifier`](https://github.com/0xMiden/protocol/blob/8411bf093bde25285708faac152b6d7269009617/crates/miden-protocol/src/note/nullifier.rs#L29-L74), a Rust-side caller reconstructing the remainder note off chain with `remainder_note` computes a different tag than the one the chain actually used, and ends up with the wrong `NoteId` and `Nullifier` for it. The real remainder note that was created on chain is unaffected: its assets, recipient, and consumption all proceed correctly, and no value is misrouted. The consumer of the parent note cannot influence the tag either, since the script only ever copies it forward. What breaks is off-chain tracking: a wallet or indexer computing the expected remainder identifier through `remainder_note` looks for the wrong note and cannot find the real one. Because the party who chose the non-canonical tag in the first place is the same party holding the parent `Note`, they already have the true tag available and are the one best positioned to reconstruct the remainder correctly; the gap is in the library's own reconstruction path rather than in anyone else's ability to track the note.
Consider having `PswapNote` retain the parent note's tag when parsed, and having `remainder_note` reuse that stored tag instead of recomputing it through `create_tag`, matching the on-chain script's copy-forward behavior. Consider also validating at parse time that a `PswapNote`'s tag matches what `create_tag` would produce, so that reconstruction stays well defined for any note the library did not itself construct, or so that non-canonical tags are rejected outright if canonical tags are meant to be required.
---
_Copied verbatim from finding [L-04](https://audits.openzeppelin.com/miden/miden-01-07-smart-contract-diff-audit-nfts/issues/pswapnoteremainder_note-recomputes-a-tag-that-the-on-chain-script-only-ever-copies-b0e06c26) (low severity) of the OpenZeppelin [smart contract diff audit (NFTs)](https://audits.openzeppelin.com/miden/miden-01-07-smart-contract-diff-audit-nfts). The audit was performed against commit `8411bf093bde25285708faac152b6d7269009617`._
Guia de contribuição
Avaliação
Esta issue ainda não foi avaliada.