Signature replay vulnerabilities are simple yet common bugs in smart contracts that use signatures.
Signatures are incredibly useful and facilitate lots of use cases. Youβll often find smart contracts using signatures to allow users perform actions on behalf of another wallet.
Users will be able to provide a message
with action parameters, and a signature of that message provided by the owner of the other wallet. After verifying the signature the contract will execute the action.
Replaying
An example of such a contract would be a token where you can provide a signed message that can be used to execute a transfer.
A naive implementation of such a feature would be immensely vulnerably to signature replay attacks. As nothing is stopping an attacker from just executing the same transfer over and over again until the contract is drained.
To prevent this from happening developers will often implement one of two approaches:
nonce - Each account will be assigned a nonce value (a random integer value). Users will include the current nonce value in the signed message, and every time an action is executed the value will be incremented.
recording - Each time a signature is used the contract will record a unique reference to the signature or action. For example: hasVoted[signer][proposal]
.
Warning
Note that recording signature values (v, r, s) to determine whether theyβre being replayed might not be the best approach. This would be vulnerable to signature malleability attacks!
Domains
Code that deals with signatures can be deceptively simple.
Itβs possible that contracts remain vulnerable even when nonces / recording are properly applied. This can happen for example when multiple contracts accept the same message format.
Take the simple message: | _to | amount | nonce |.
In theory this should be sufficient to protect a simple signature based transfer, right? right? β¦
Not really.
An interesting problem occurs when someone else also deploys a token that allows you to execute transfers using signatures with the same message format. Letβs assume someone simply copies our code and initialises it with a different token name and symbol.
A (signature, message) tuple that can be used to transfer 10 tokens to Alice in token A, will now also be usable on token B (as long as nonces line up).
Youβll se a common solution to this problem. Namely, the introduction of a domain identifier in the message format. Now the message format will look something like this: | βthe name of my tokenβ | _to | amount | nonce |.
Unexpected Domains
The simple situation sketched out above might seem rather obvious.
However, there are some interesting variations of the problem which are more tricky to identify. Of particular interest are multi (chain) deployments. These are situations where we have multiple independent contract instances that would share the same domain identifier.
Ensuring signatures canβt be replayed between instances of the same contract (on different chains) requires additions to the message. For example:
- The chain id can be added to distinguish between deployments on different chains.
- The instance address can be added to distinguish between deployments on the same chain.
Takeaways
This covers just the tip of the iceberg, in general itβs often a good idea to thoroughly check code that handles signatures!