OpenZeppelin / OpenZeppelin/compact-contracts
L-05: Allowance Placeholder Writes a Zero Ciphertext Where an Encryption of Zero Is Required
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 55
- Forks
- 29
- Avg merge
- 5d 7h
- Merged PRs (30d)
- 25
Description
L-07 Allowance Placeholder Writes a Zero Ciphertext Where an Encryption of Zero Is Required
Severity: Low
Source: Midnight Foundation #02 — OpenZeppelin Compact Contracts Audit, Release 0.3.0-alpha.1 (2026-08-18)
When no allowance exists the getter returns a synthetic entry in place of a zeroed default, and its documentation states that the entry is well formed, that its encrypted amounts decrypt to zero, and that it is safe for a wallet to decrypt. The entry carries three encrypted fields and the claim holds for two of them. The pair of ElGamal ciphertexts is filled with the identity point in both components, which under an encoding that places the amount in the exponent is a genuine encryption of zero and recovers zero on decryption. The third field is a memo, and it is built by a different scheme.
That scheme masks the value additively, so an encryption of zero under it is the mask itself rather than the number zero. The placeholder instead sets the memo's ciphertext to zero, which is a zero written into the slot an encrypted value occupies rather than an encrypted zero. Recovering a value multiplies the ephemeral public key by the recipient's secret and subtracts a hash of the resulting point, so what the reader obtains is the negation of that hash. The same fill with the neutral element was applied to both constructions and only one of them treats zero as neutral.
The recovered figure is identical for every reader, since the ephemeral is the identity point and multiplying it by any secret yields the identity, making the hash of that fixed point under the escrow domain tag a constant rather than a per-account value. It is also large, the mask being a truncation to thirty one bytes and the result its negation in the field, which places it just below the field modulus and far above the maximum a transfer amount may take. A wallet that range checks what it decrypts against the amount type rejects it at once, which is harmless, whereas one that narrows the field element to the width of an amount, an ordinary mistake when handling arbitrary precision integers, obtains a plausible figure and reports a substantial allowance for a relationship that was never created. Nothing becomes spendable, since the spending paths test escrow membership separately, so the consequence falls on what integrators display and record rather than on what can be moved. The same placeholder shape is built when a prior escrow is refunded, but that occurrence carries a note that the slot exists only so the refund reads an on-curve no-op and is removed before anything decrypts it, which leaves the getter as the one place the value reaches a reader.
Consider setting the memo's ciphertext to the hash of the identity point under the same domain tag, which is the encryption of zero for this scheme and makes the subtraction cancel, so that all three fields satisfy the claim the documentation makes for them. Consider alternatively returning a value documented as a sentinel for an absent allowance, with the accompanying statement that wallets must recognise it rather than decrypt it, so that the getter's contract and its behaviour agree either way.
Notes & Additional Information
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at the allowance placeholder getter in contracts/src/token/ConfidentialFungibleToken.compact, especially lines 436-471, and compare its memo field with EcdhMask.compact lines 119-124 and 172-176. Trace the refunded-escrow placeholder around lines 1159-1174 to understand its separate handling. Done means the getter’s documented behavior and the memo ciphertext’s decryption behavior agree, with the chosen handling clearly defined for wallets.
Written by the indexing model from the issue text.
Assessment
- Domain
- cryptography
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100