- Contract uses ECDSA signatures for authorization or deduplication
mapping(bytes => bool)) to prevent replays value is in the lower half of the curve ordermapping(bytes => bool) public usedSignatures;
function claimReward(bytes memory signature, uint256 amount) external {
// Deduplication by raw signature bytes
require(!usedSignatures[signature], "already used");
bytes32 hash = keccak256(abi.encodePacked(msg.sender, amount));
address signer = ecrecover(hash, v, r, s);
require(signer == trustedSigner);
usedSignatures[signature] = true; // Attacker submits (r, n-s, flipped_v)
// to bypass this check with a valid but different signature
_payout(msg.sender, amount);
}
mapping(bytes => bool) or any mapping keyed by raw signature bytes(r, n-s) signatureecrecover is called directly without an s-value range checks < secp256k1n/2 check is present, flag itECDSA.recover is used, which rejects high-s signaturess <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0ECDSA.recover which enforces s in the lower half of the curve orderecrecover, add a manual s-value check// Use nonce-based deduplication instead of signature bytes
mapping(address => uint256) public nonces;
function claimReward(uint8 v, bytes32 r, bytes32 s, uint256 amount) external {
uint256 nonce = nonces[msg.sender]++;
bytes32 hash = keccak256(abi.encodePacked(msg.sender, amount, nonce));
address signer = ECDSA.recover(hash, v, r, s); // Rejects malleable sigs
require(signer == trustedSigner);
_payout(msg.sender, amount);
}