L-01 Credit Randomness Nonce Can Be Reset By The Recipient
Severity: Low
Source: Midnight Foundation #2 — OpenZeppelin Compact Contracts Audit, Release 0.3.0-alpha.1 (2026-08-18)
The credit path derives both its balance randomness and its memo ephemeral by hashing the recipient account together with the memo-list length. The comment beside it explains that the length serves as a monotonic nonce so that each credit differs even when the wallet reuses its seed. This protection matters because the memo is an additive one-time pad: a repeated ephemeral to one recipient makes the difference of two stored memo values equal the difference of the two delivered amounts exactly, readable by anyone.
However, the length is not monotonic. Pruning replaces the recipient's list with an empty one, after which the counter walks the same sequence a second time and each credit following the prune pairs with the credit that held the same count before it. Every input to the derivation must match for a pair to form, the seed among them, so credits from different senders never pair with one another and the party that gains is a passive observer rather than either participant. What such an observer obtains is a relation between amounts rather than an amount, though two equal amounts produce byte-identical entries and a single amount learned outside the system converts every connected difference into an exact figure.
The module header records the reset and calls it an in-epoch backstop rather than a substitute for seed freshness. What it does not record is who ends an epoch. Pruning is available only to the account holder, so the counter guarding a sender's pad is advanced and reset by the recipient of that sender's transfers. The exposure still requires the sender to have reused a seed, so this describes the conditions under which a defence-in-depth measure stops functioning rather than a failure reachable against a correctly implemented wallet.
The addition worth noting is that the counter feeds both derived values and not only the memo ephemeral, so a revisited count repeats the balance randomness as well. Two such credits produce pending deltas that share an identical first component, which makes the reuse detectable without any secret, and whose second components differ by the amount difference expressed as a curve point.
Consider deriving the nonce from a per-account counter that only ever increases, stored independently of the memo list so that pruning cannot return it to an earlier value, which keeps the measure effective for exactly the wallets it exists to protect and removes a counterparty's influence over it. Moreover, consider describing in the seed guidance that the current counter is advanced by the recipient rather than by the sender whose randomness it protects.
L-01 Credit Randomness Nonce Can Be Reset By The Recipient
Severity: Low
Source: Midnight Foundation #2 — OpenZeppelin Compact Contracts Audit, Release 0.3.0-alpha.1 (2026-08-18)
The credit path derives both its balance randomness and its memo ephemeral by hashing the recipient account together with the memo-list length. The comment beside it explains that the length serves as a monotonic nonce so that each credit differs even when the wallet reuses its seed. This protection matters because the memo is an additive one-time pad: a repeated ephemeral to one recipient makes the difference of two stored memo values equal the difference of the two delivered amounts exactly, readable by anyone.
However, the length is not monotonic. Pruning replaces the recipient's list with an empty one, after which the counter walks the same sequence a second time and each credit following the prune pairs with the credit that held the same count before it. Every input to the derivation must match for a pair to form, the seed among them, so credits from different senders never pair with one another and the party that gains is a passive observer rather than either participant. What such an observer obtains is a relation between amounts rather than an amount, though two equal amounts produce byte-identical entries and a single amount learned outside the system converts every connected difference into an exact figure.
The module header records the reset and calls it an in-epoch backstop rather than a substitute for seed freshness. What it does not record is who ends an epoch. Pruning is available only to the account holder, so the counter guarding a sender's pad is advanced and reset by the recipient of that sender's transfers. The exposure still requires the sender to have reused a seed, so this describes the conditions under which a defence-in-depth measure stops functioning rather than a failure reachable against a correctly implemented wallet.
The addition worth noting is that the counter feeds both derived values and not only the memo ephemeral, so a revisited count repeats the balance randomness as well. Two such credits produce pending deltas that share an identical first component, which makes the reuse detectable without any secret, and whose second components differ by the amount difference expressed as a curve point.
Consider deriving the nonce from a per-account counter that only ever increases, stored independently of the memo list so that pruning cannot return it to an earlier value, which keeps the measure effective for exactly the wallets it exists to protect and removes a counterparty's influence over it. Moreover, consider describing in the seed guidance that the current counter is advanced by the recipient rather than by the sender whose randomness it protects.