Skip to main content

Create Signature (v,r,s)

In Ethereum, a signature is used to prove that a user has authorized a specific transaction or message. The signature is composed of three values: v, r, and s, which are combined to create a 65-byte array.

In more detail, the signature is created by first hashing the transaction or message using the Keccak-256 hashing algorithm. The resulting hash is then signed by the user's private key using the Elliptic Curve Digital Signature Algorithm (ECDSA), which produces the v, r, and s values.

  • v: This is a number representing the recovery identifier of the signature. It is either 27 or 28, depending on whether the signature was created using the older or newer ECDSA standard, respectively.
  • r: This is a number representing the x-coordinate of the point on the elliptic curve that was used to create the signature.
  • s: This is a number representing the signature's "s-value", which is used along with the r value to uniquely identify the signature. Together, the v, r, and s values uniquely identify the signature and can be used to verify that the transaction or message was authorized by the user who signed it.

In Solidity, you can create a signature using the ecrecover function, which takes as input the v, r, and s values along with the message hash that was signed. The ecrecover function returns the address of the user who signed the message.

pragma solidity ^0.8.0;

contract SignatureVerifier {
function verifySignature(bytes32 message, bytes memory signature, address signer) public pure returns (bool) {
bytes32 hash = keccak256(abi.encodePacked(message));
bytes32 r;
bytes32 s;
uint8 v;
if (signature.length != 65) {
return false;
}
assembly {
r := mload(add(signature, 32))
s := mload(add(signature, 64))
v := byte(0, mload(add(signature, 96)))
}
if (v < 27) {
v += 27;
}
if (v != 27 && v != 28) {
return false;
}
address recoveredSigner = ecrecover(hash, v, r, s);
return recoveredSigner == signer;
}


function verifySignature2(bytes32 message, uint256 r, uint256 s, uint8 v, address signer) public pure returns (bool) {
bytes32 hash = keccak256(abi.encodePacked(message));
if (v < 27) {
v += 27;
}
if (v != 27 && v != 28) {
return false;
}
address recoveredSigner = ecrecover(hash, v, bytes32(r), bytes32(s));
return recoveredSigner == signer;
}

}

The given contract has two functions that are responsible for verifying a provided signature and message. One of the functions expects the complete signature as a parameter, while the other expects the individual r, s, and v values. To call these functions in Brownie, a valid signature needs to be generated using the private key of the signing account.

>>> verifier = SignatureVerifier.deploy({'from': accounts[1]})
Transaction sent: 0xb145874f323fd0928f0eea17bf0d45c9e22b936764effc1d2b46d8ef63870069
Gas price: 0.0 gwei Gas limit: 2000000001 Nonce: 0
SignatureVerifier.constructor confirmed Block: 1 Gas used: 285105 (0.01%)
SignatureVerifier deployed at: 0xe7CB1c67752cBb975a56815Af242ce2Ce63d3113

We can generate a valid signature in Brownie by using the web3.eth.account.signHash function, which takes as input the message hash to sign, the private key of the signing account. In the case of the given contract, both functions generate a hash using keccak256(abi.encodePacked()), so we need to perform the same steps in Brownie to create a valid signature that can be passed to these functions.

In Brownie, the output of web3.eth.account.signHash is a dictionary with the following keys:

  • messageHash: the original message hash that was signed
  • r: the r value of the signature
  • s: the s value of the signature
  • v: the v value of the signature, which is either 27 or 28 depending on the Ethereum network and the version of the ECDSA algorithm used to create the signature.
>>> from brownie import convert
>>> some_message = b'this_is_my_message'
>>> hash = web3.sha3(convert.to_bytes(some_message, type_str='bytes32'))
>>> hash
HexBytes('0xbe89ac342a63d97636e391434447c57702f3aa911602fefa18fbf132356e3af7')
>>> accounts.add()
mnemonic: 'balcony audit sauce assume sunset wood urban humble copper manage sustain ready'
<LocalAccount '0x37D2A4985Bd8a575831793bCa53Df3103C1Cb000'>
>>> attacker = accounts[-1]
>>> attacker
<LocalAccount '0x37D2A4985Bd8a575831793bCa53Df3103C1Cb000'>
>>> sign = web3.eth.account.signHash(hash, attacker.private_key)
>>> sign
Sb'\xbe\x89\xac4*c\xd9v6\xe3\x91CDG\xc5w\x02\xf3\xaa\x91\x16\x02\xfe\xfa\x18\xfb\xf125n:\xf7', 29301953945621141465959149977923221887811532224583510127534515906181230014364, 23048115662216613465220091216084804517930756270855783936505287417212763574248, 27, b"@\xc8Qv\x05\xc7x\x86&`\x91u\x9e\xd7g\x83\xb3\x9c\xaa\x14\x97]\xad\xf4\xbb^(\xe3\xef\x9c\xaf\x9c2\xf4\xc5k\xc2\xafq\x181\xb3'g\xc5\x80<\x1a\x10\xcf\x19\x03\xb1\xf7\xd8S\x989\xd5\xbb\x99_\x0f\xe8\x1b")
>>> verifier.verifySignature(some_message, sign[4], attacker)
True
>>> verifier.verifySignature2(some_message, sign[1], sign[2], sign[3], attacker)
True