M-01 Escrow Owner Memo Encrypted Under Randomness Controlled by Spender
Severity: Medium
Source: Midnight Foundation #2 — OpenZeppelin Compact Contracts Audit, Release 0.3.0-alpha.1 (2026-08-18)
An allowance is recorded as an escrow entry holding two ciphertexts and an owner-readable memo. Both ciphertexts encode the amount in the exponent, so neither party can cheaply recover the number, and the memo is therefore the owner's only channel for learning the live remaining allowance. That memo is an ECDH one-time pad addressed to the owner's public key. Because the recipient key is public ledger state, the ephemeral is the only secret standing between an observer and the plaintext, and the EcdhMask module states this requirement and assigns it: the ephemeral must be secret and unpredictable rather than merely distinct, and the caller supplies it and owns both properties.
In the escrow spend path that assignment breaks. The ephemeral is expanded from a seed supplied by the caller, but the caller at that moment is the spender rather than the owner. The expansion is a deterministic public hash, so possession of the seed is possession of the ephemeral, and the value protected belongs to a party absent from the call who contributes no entropy to it.
Two routes reach the plaintext: - A spender who publishes the seed allows any observer to recompute the mask and read the owner's remaining allowance directly from public state. - A spender who reuses one seed across two spends against the same owner repeats the pad, so the difference of the two stored memos equals the difference of the two plaintexts and requires no secret at all.
The second route requires no intent, leaves the spender unaware, and is the behavior of the only witness present in the repository. The same construction in approve is sound precisely because the caller there is the owner.
The design already models the spender as hostile for the integrity of this field, since the remaining amount is recomputed in-circuit so that the spender cannot forge it. Its confidentiality is nevertheless left to that same spender's discretion, and the owner has no remedy. An owner who rotates a fresh high-entropy seed on every call they make still leaks, because the leaking call is not theirs, and nothing in escrow state records that a pad repeated. The authority conferred by approve extends to spending up to the cap and not to disclosing it, and the disclosure survives revocation. Consider folding a per-escrow monotonic counter into the memo ephemeral. The addition worth noting is that the _credit path already applies exactly this mechanism, folding the recipient's memo-list length into its randomness so that each credit remains unique even when the seed repeats, and recording in its comment that the freshness is essential for the memo one-time pad. The remedy is therefore not a new construction but the one used a single function away. Applying it removes the accidental repetition that carries most of the practical exposure, though no construction prevents a spender who deliberately publishes the ephemeral, since any construction must let the spender compute the pad. It is further advisable to state which party each confidentiality property protects, so that a wallet author supplying the seed understands that the hygiene guards a counterparty rather than themselves.
M-01 Escrow Owner Memo Encrypted Under Randomness Controlled by Spender
Severity: Medium
Source: Midnight Foundation #2 — OpenZeppelin Compact Contracts Audit, Release 0.3.0-alpha.1 (2026-08-18)
An allowance is recorded as an escrow entry holding two ciphertexts and an owner-readable memo. Both ciphertexts encode the amount in the exponent, so neither party can cheaply recover the number, and the memo is therefore the owner's only channel for learning the live remaining allowance. That memo is an ECDH one-time pad addressed to the owner's public key. Because the recipient key is public ledger state, the ephemeral is the only secret standing between an observer and the plaintext, and the
EcdhMaskmodule states this requirement and assigns it: the ephemeral must be secret and unpredictable rather than merely distinct, and the caller supplies it and owns both properties.In the escrow spend path that assignment breaks. The ephemeral is expanded from a seed supplied by the caller, but the caller at that moment is the spender rather than the owner. The expansion is a deterministic public hash, so possession of the seed is possession of the ephemeral, and the value protected belongs to a party absent from the call who contributes no entropy to it.
Two routes reach the plaintext: - A spender who publishes the seed allows any observer to recompute the mask and read the owner's remaining allowance directly from public state. - A spender who reuses one seed across two spends against the same owner repeats the pad, so the difference of the two stored memos equals the difference of the two plaintexts and requires no secret at all.
The second route requires no intent, leaves the spender unaware, and is the behavior of the only witness present in the repository. The same construction in
approveis sound precisely because the caller there is the owner.The design already models the spender as hostile for the integrity of this field, since the remaining amount is recomputed in-circuit so that the spender cannot forge it. Its confidentiality is nevertheless left to that same spender's discretion, and the owner has no remedy. An owner who rotates a fresh high-entropy seed on every call they make still leaks, because the leaking call is not theirs, and nothing in escrow state records that a pad repeated. The authority conferred by
approveextends to spending up to the cap and not to disclosing it, and the disclosure survives revocation. Consider folding a per-escrow monotonic counter into the memo ephemeral. The addition worth noting is that the_creditpath already applies exactly this mechanism, folding the recipient's memo-list length into its randomness so that each credit remains unique even when the seed repeats, and recording in its comment that the freshness is essential for the memo one-time pad. The remedy is therefore not a new construction but the one used a single function away. Applying it removes the accidental repetition that carries most of the practical exposure, though no construction prevents a spender who deliberately publishes the ephemeral, since any construction must let the spender compute the pad. It is further advisable to state which party each confidentiality property protects, so that a wallet author supplying the seed understands that the hygiene guards a counterparty rather than themselves.