Skip to content

Edition facet signature is replayable#

Medium Risk

Fixed

Fixed according to the recommendation.

In EditionFacet.sol, signature can be replayed in certain cases.

    function _requireSignature(
        EditionStorage.Edition storage edition,
        uint256 editionId,
        bytes calldata signature
    ) internal view {
        require(
            keccak256(
                abi.encodePacked(editionId + edition.nonce, block.chainid)
            ).toEthSignedMessageHash().recover(signature) == edition.signer,
            "Invalid signature"
        );
    }

editionId is incremented at each new edition. edition.nonce starts at zero for each new edition and can be incremented to invalidate a signature.

As the data used to make the hash is abi.encodePacked(editionId + edition.nonce, block.chainid), some combinations of editionId and edition.nonce will recover to the same signer.

For example editionId = 0 and edition.nonce = 1,

will be replayable if editionId = 1 and edition.nonce = 0, because the 0 + 1 == 1 + 0. So the signature could be replayed with the other combination.

A replay of the transaction on another Edition means someone could give themselves permission to mint illegitimately.

Recommendation#

I see two options:

  • Use a global nonce instead of a per Edition nonce, so that editionId + edition.nonce will never be repeated across the Editions
  • Use abi.encodePacked(editionId,edition.nonce, block.chainid) instead of abi.encodePacked(editionId + edition.nonce, block.chainid)