Signature Replay

Olympix
4 min readDec 22, 2022

Blockchains’ core fundamental element is cryptographic signatures. Transactions are signed using the matching private keys, enabling the senders of the transactions to be associated with their accounts. Blockchain’s accounting would simply not function without this capability.

In order to enable multiple verifiers to approve operations by providing signatures established off-chain, digital signatures are frequently verified immediately in smart contracts implemented on Ethereum. This is frequently used in voting contracts or multi-signature vaults to combine signatures or delegate authority.

Cryptographic Signatures

Public-private key pairings underlie the majority of cryptographic signature techniques. A private key can be used to encrypt data, and the associated public key can be used to verify the signature. As implied by the name, a user’s public key is shared publicly while the private key needs to be kept a secret.

Data that is signed cryptographically can have the following two benefits:

  • It is possible to locate the data signer. The ability to recoup the public key of the signer enables this.
  • Verifying data integrity means using the signature to show that the involved data hasn’t changed since signing.

It is vital to remember that cryptographically signing information does not by itself offer any additional insurance. The presence of a signature doesn’t really guarantee a message’s originality or imply that the message’s sender is also its signer. These facts can of course be verified using cryptographic signatures, but the program must still carry out the relevant tests. In the context of Ethereum smart contracts, let’s look into this fact.

Signature Replay Vulnerability

Example of Code

Let’s examine the flaw we discovered during the recently completed audit mentioned above

function unlock ( 
address _to,
uint256 _amount,
uint8[] _v,
bytes32[] _r,
bytes32[] _s
) external {
require(_v.length >= 5);
bytes32 hashData = keccak256(_to, _amount);
for (uint i = 0; i < _v.length; i++) {
address recAddr = ecrecover(hashData, _v[i], _r[i], _s[i]);
require(_isValidator(recAddr));
}
to.transfer(_amount);
}

The code we examined has been condensed into the example above. To simplify it and simpler to grasp, it has been simplified to the fundamentals. The vulnerability itself, however, is unaffected.

The observed contract is a component of a Cross-blockchain relay link that enables the transfer of virtual assets between blockchains. When the equivalent asset is generated on the other blockchain, ether is locked in the contract. When the balance is locked or deleted in one chain, the unlock function sends formerly locked Ether to the address at the same moment. In order to accomplish this, an across-chain relayer may submit a variety of signatures of the validator, a desired unlock balance, and a final destination address.

An address’ validity is verified by the inner _isValidator function.

Attack Case

The signal that the validators sign utilizing the ECDSA algorithm is where the issue with the aforementioned code lies. The recipient’s address and the required money are the sole information in the message. Nothing contained in the message could be utilized to avoid using the same signatures more than once. Consider the following case:

  • Bob transfers 20 ETH from the connected chain to the Ethereum chain using an identical amount of currency.
  • Processing this cross-blockchain transaction is Sara, a relayer. In order to release 20 ETH in the contract and send it to Bob, she gathers the required signatures of the validator, freezes the right amount on the chain connected, and after using the unlock function.
  • On the blockchain, the operation including the signature data arrays is seen by everyone.
  • Now that Dev has copied the signature arrays, he can independently execute an unlockcall. Once more, the unlocking process will be successful, resulting in the transfer of 20 ETH to Bob.
  • Bob is able to carry on in this manner till the contract had been exhausted.

Mitigation

A Signature Replay attack is what is described in the example above. It is conceivable given that there is no means to determine if this specific signed message is unique or whether it has been used previously.

Include a consecutive message number, sometimes known as a nonce, inside the signed data as a straightforward defense against this kind of attack. The updated code for the aforementioned line would look like this:

public uint256 nonce; 
function unlock(
address _to,
uint256 _amount,
uint256 _nonce,
uint8[] _v,
bytes32[] _r,
bytes32[] _s
) external {
require(_v.length >= 5);
require(_nonce == nonce++);
bytes32 hashData = keccak256(_to, _amount, _nonce);
for (uint i = 0; i < _v.length; i++) {
address recAddr = ecrecover(hashData, _v[i], _r[i], _s[i]);
require(_isValidator(recAddr));
}
to.transfer(_amount);
}

A sequence number is now needed for each completed unlock call with this code. The signatures necessary for each completed call are distinct because they include this special number. As a result, an attacker cannot utilize a message they have already seen because they cannot successfully replay it.

Conclusion

The most common reason for verification of the signature flaws is a lack of understanding of the fundamentals of cryptography and the use of signatures. Since you now understand the functioning of cryptographic signatures, you can now take proper measures to use them to your benefit.

Join our beta program now https://www.olympix.ai/

Originally published at https://www.linkedin.com.

--

--