`keccak256(abi.encode(...))` gives a collision-resistant digest; prefer `encode` over `encodePacked` when two dynamic values could be ambiguous. `ecrecover` returns the signer of a hash, letting users authorise actions off chain.
bytes32 digest = keccak256(abi.encode(user, amount, nonce)); require(ecrecover(digest, v, r, s) == signer, "bad sig");
EIP-712 gives typed, human-readable signing and binds signatures to a domain (chain id and contract) so they cannot be replayed elsewhere. Always include a nonce and a deadline.