Coinbase

Coinbase: SignatureDiscountValidator.sol

Cantina Security Report

Organization

@coinbase

Engagement Type

Cantina Reviews

Period

-

Researchers


Findings

Informational

3 findings

1 fixed

2 acknowledged


Informational3 findings

  1. Missing event for signer rotations

    State

    Acknowledged

    Severity

    Severity: Informational

    Submitted by

    r0bert


    Description

    The SignatureDiscountValidator.setSigner function mutates the signer without emitting an event and the constructor also initializes the signer silently. Off-chain services (discount portals, caching layers, monitoring dashboards) cannot detect signer rotations or invalidate cached signatures promptly without polling storage directly, which both increases operational risk and delays revocations.

    Recommendation

    Declare event SignerUpdated(address indexed oldSigner, address indexed newSigner); and emit it in the constructor and setSigner, passing the previous value so downstream systems can react to signer rotations.

    Coinbase: Acknowledged. Won't fix as we're expecting this to be used rarely (~1/yr maximally). Additionally we wouldn't expect to listen to this event since we would be the party executing the change in the first place. We can't come up with an external party that would care to listen to this event.

  2. Incorrect NatSpec Comment for Signature Expiration Boundary Condition

    Severity

    Severity: Informational

    Submitted by

    r0bert


    Description

    SybilResistanceVerifier.verifySignature treats a signature as valid when the packed expires equals the current block timestamp because it only reverts when expires < block.timestamp.

    if (expires < block.timestamp) revert SignatureExpired();

    On the flipside, the NatSpec comment for SignatureExpired states, “Thrown when the signature expiry date >= block.timestamp,”:

    /// @notice Thrown when the signature expiry date >= block.timestamp.    error SignatureExpired();

    implying equality should already be rejected. The NatSpec is wrong as signatures should still be valid when their expiry date is equal to block.timestamp.

    The mismatch between documentation and behavior risks engineers relying on the wrong edge condition.

    Recommendation

    Update the NatSpec to reference only an expired signature when block.timestamp is bigger than the expiry date:

    -  /// @notice Thrown when the signature expiry date >= block.timestamp.+ /// @notice Thrown when the signature expiry date < block.timestamp.

    Coinbase: Fixed in 791f5a0.

    Cantina: Fix verified.

  3. Discount signatures scoped per validator instance

    State

    Acknowledged

    Severity

    Severity: Informational

    Submitted by

    r0bert


    Description

    SignatureDiscountValidator.isValidDiscountRegistration verifies only (claimer, expires, signature) and does not receive the discountKey being claimed. In theory, if multiple discount keys were mapped to the same validator, one signed authorization could be replayed across tiers because the signature payload omits the specific key. However, the deployment plan explicitly assigns a single discountKey per validator/signer pair, so there is no path for a signature to “jump” to a different tier. Under that operational constraint the previously identified risk is not exploitable and should be treated as informational documentation of the coupling assumption instead of an active issue.

    Recommendation

    Maintain (and ideally enforce in configuration/tests) the invariant that each validator instance serves exactly one discountKey. If future requirements ever call for multiple tiers behind the same validator, revisit this design and include the key or equivalent salt inside the signed payload to prevent cross-tier reuse.

    Coinbase: Acknowledged. This is known and each validator will be ever assigned a single discountKey.