Skip to content

N-10: Curve Point Representation is Relied On by a Security Guard and Persisted State but Is Never Specified #815

Description

@andrew-fleming

N-04 Curve Point Representation is Relied On by a Security Guard and Persisted State but Is Never Specified

Severity: Note

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


Comparing a curve point against the identity is meaningful only when the representation those points take is specified, and this module leaves it unspecified while relying on such comparisons in two guards: ElGamal refusing an identity public key before encrypting and the hybrid module refusing both an identity recipient key and an ephemeral that reduces to the identity. The identity admits more than one spelling, since it can be obtained by multiplying the generator by zero or written directly as the coordinate pair with abscissa zero and ordinate one, and the module uses both. One production site takes the coordinate form while four take the generator form, among them the identity ciphertext helper, the placeholder written during an escrow refund and the two guards themselves.

Twisted Edwards points have a unique affine pair, and the two spellings do agree in the shipped runtime, so no defect follows today. What is missing is any statement of which form the type carries, whether the runtime keeps points normalised, or whether equality compares canonical representatives, and a reader cannot settle from the module that the two spellings denote one value. Were that property to fail, the guard could be satisfied by an identity key presented in whichever form the comparison did not recognise, and such a key removes the masking entirely, leaving the message point recoverable from an ElGamal ciphertext and the hybrid mask equal to a constant any observer can compute. That path is not reachable through this module, whose keys are derived inside the circuit at registration or read back from the registration map and never taken from a caller, so the guard stands here as protection for consumers that do accept a key as a parameter.

The same assumption has to survive across versions as well, because points are held in long-lived public state rather than converted to a fixed encoding first. The registration map stores each account's key as a curve point and both balance maps store ciphertexts whose components are themselves curve points, and every later operation reads those values back and compares or computes on them, the key check above included. A change to how the runtime encodes or normalises a point would therefore have to preserve agreement with everything written under the present scheme, and no versioning or migration path exists for that state. This follows from the storage choice as it stands rather than from any predicted change.

Consider using one construction for the identity throughout, so that no reader has to determine whether two spellings denote the same value. Consider also documenting that equality on curve points compares canonical representatives, since two guards depend on it, and recording whether the runtime offers a stability guarantee for the encoding of persisted points, or storing a contract-controlled representation and checking supplied points against it where such a guarantee does not exist.

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

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions