Skip to content

M-03: Pruning and Sweeping Authenticate the Account Secret Without The Encryption Secret #797

Description

@andrew-fleming

M-03 Pruning and Sweeping Authenticate the Account Secret Without The Encryption Secret

Severity: Medium

Source: Midnight Foundation #2 — OpenZeppelin Compact Contracts Audit, Release 0.3.0-alpha.1 (2026-08-18)


An attacker who obtains only an account's identity secret, and not its encryption secret, can leave that account permanently unable to spend. The module keeps the two secrets separate by design, the first fixing which account a caller is and the second protecting what that account can read, and every circuit that moves value proves both, as proving the balance against the registered encryption key. Two exported circuits prove only the first. Pruning replaces the caller's memo list after deriving the identifier and nothing else, and sweep folds pending value into the spendable balance using homomorphic addition, which needs no plaintext and therefore proves no knowledge of the key, its registration check confirming only that a key exists. Holding the identity secret alone is enough to call both, and calling both in order freezes the account:

  1. A third party credits the account. The amount lands in the pending pool and a memo carrying it is appended to the account's list, which is the only channel through which the holder learns that figure, the stored balance itself decrypting only to a curve point.

  2. The attacker prunes before the wallet has read that memo. The credited amount is now unknown to the holder.

  3. The attacker sweeps. The unknown amount is added into the spendable balance.

  4. The holder attempts any debit or approval. Each requires stating the plaintext of the spendable balance, which is now the previous balance plus an amount they cannot determine, so no claim satisfies the assertion and nothing can be moved.

Either step alone is survivable. Pruning by itself strands the credit in the pending pool while leaving the spendable balance intact and usable, and sweeping by itself is the ordinary operation the holder performs anyway. It is the pair that converts one unreadable credit into an account that cannot transact, and both halves are reachable with the same single secret. A small missing amount can still be recovered by searching for a discrete logarithm, but the per- operation ceiling is the full unsigned 128 bit range, so a large credit places it beyond any search.

The exposure assumes the two secrets hold different values. A wallet is permitted to derive both from the same material, and where it does an attacker holding one holds the other and these checks would change nothing. That is an argument for enforcing the separation wherever it is relied upon rather than against the separation itself.

Consider requiring an encryption secret witness in both circuits, asserted to re-derive the public key registered for the account, before either the memo list is replaced or pending value is merged, so that authority over what an account can read matches authority over what it can spend. Adding the check to only one of the two leaves the sequence above intact. Consider also making pruning remove an explicitly identified prefix rather than the whole list, which bounds what a caller holding one secret can destroy.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    1-mediumNormal priorityauditIssues reported by an audit

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions