F-2025-0004·missing-signature-expiration

Missing signature expiration in EIP-712 mint authorization enables perpetual replay attacks

Acknowledgednftstakingeip-712
TL;DR

EIP-712 mint authorisations have no deadline. Once issued, a signature is valid indefinitely until consumed, so revoking an outstanding authorisation is not possible.

Severity
LOW
Impact
LOW
Likelihood
MEDIUM
Method
MManual review
CAT.
Complexity
LOW
Exploitability
MEDIUM
02Section · Description

Description

The GenesisLicense contract implements EIP-712 signature-based authorization for minting NFTs, but lacks any form of signature expiration mechanism. Once a signature is created, it remains valid indefinitely until used, creating a significant security vulnerability.

In the current implementation, signatures only include the recipient address, token value, and staking pool address:

solidity
bytes32 structHash = keccak256(abi.encode(MINT_INFO_TYPEHASH, to_, value_, stakingPool_));

Without an expiration timestamp or block number, these signatures cannot be invalidated except by using them. This creates several security risks:

  1. Perpetual validity: Signatures remain valid indefinitely, even if protocol conditions change.
  2. Protocol parameter bypass: Old signatures could authorize mints under conditions that are no longer acceptable.
03Section · Impact

Impact

If a signing key is rotated or revoked, all previously-issued signatures remain valid. An adversary who obtains an old signature can mint at any time in the future under whatever conditions were valid when it was signed.

04Section · Recommendation

Recommendation

  1. Add expiration parameter. Modify the EIP-712 signature structure to include an expiration timestamp:
solidity
// Update typehash
bytes32 internal constant MINT_INFO_TYPEHASH = keccak256(
"Mint(address to,uint256 value,address stakingPool,uint256 deadline)"
);
function mint(
address to_,
uint256 value_,
address stakingPool_,
uint256 deadline_,
bytes calldata signature_
) external nonReentrant {
require(block.timestamp <= deadline_, "Signature expired");
bytes32 structHash = keccak256(abi.encode(
MINT_INFO_TYPEHASH,
to_,
value_,
stakingPool_,
deadline_
));
// verification and minting logic
}
  1. Implement nonce system. As an alternative or additional protection, implement a nonce system where each address has an incrementing nonce that must be included in the signature:
solidity
mapping(address => uint256) public nonces;
bytes32 structHash = keccak256(abi.encode(
MINT_INFO_TYPEHASH,
to_,
value_,
stakingPool_,
nonces[to_]
));
// Increment nonce after use
nonces[to_]++;
F-2025-0004

oog
zealynx

Smart Contract Security Digest

Monthly exploit breakdowns, audit checklists, and DeFi security research — straight to your inbox

© 2026 Zealynx