36

The question of signing and verifying signatures with Solidity has been asked in these questions, both of which I have tried to study:

How can I sign a piece of data with the private key of an Ethereum address?

How can I verify a cryptographic signature that was produced by an Ethereum address key pair

However, there are still pieces of information that I appear to be missing.

First off, I would like to start with the question about signing. In the example, the string Schoolbus is signed by the address 0xd1ade25ccd3d550a7eb532ac759cac7be09c2719, and the resulting signature is 0x2ac19db245478a06032e69cdbd2b54e648b78431d0a47bd1fbab18f79f820ba407466e37adbe9e84541cab97ab7d290f4a64a5825c876d22109f3bf813254e8601.

So far, so good. Now, what I'd like to do is verify using ecrecover that the signature is indeed correct for the given address and datum. The thing is, ecrecover takes four arguments: ecrecover(h, v, r, s). Aside from h being the hash, and {v, r, s} somehow comprising the signature, what exactly do v, r, and s stand for? And how do I obtain all the values necessary from the single string 0x2ac19db245478a06032e69cdbd2b54e648b78431d0a47bd1fbab18f79f820ba407466e37adbe9e84541cab97ab7d290f4a64a5825c876d22109f3bf813254e8601?

What I know is that ecrecover returns an address, and verifying a signature is essentially a matter of comparing the resulting address with the expected one. Yet the whole procedure seems unnecessarily complicated.

Aside from this, I was also wondering how I could produce a signature, either like this 0x2ac19db245478a06032e69cdbd2b54e648b78431d0a47bd1fbab18f79f820ba407466e37adbe9e84541cab97ab7d290f4a64a5825c876d22109f3bf813254e8601 or a set of {h, v, r, s} using Solidity alone, without the RPC-JSON detour as is the case in the question – provided I know a private key?

eth
  • 85,679
  • 53
  • 285
  • 406
arik
  • 503
  • 1
  • 4
  • 5
  • Check out https://ethereum.stackexchange.com/questions/1777/workflow-on-signing-a-string-with-private-key-followed-by-signature-verificatio/1794#1794 Here I also list the procedure for extracting r, v, and s from the signature string. I agree that this procedure is a bit too complicated, and I expressed this issue here: https://github.com/ethereum/EIPs/issues/79 – MrChico Mar 23 '16 at 00:47
  • I'm afraid that the question in question is not in Solidity. What I am trying to accomplish has to be executed within a contract. – arik Mar 23 '16 at 00:53
  • Can you expand a bit on why you would want to sign something inside a contract? As has been mentioned this will expose the private key of the signer to everyone – MrChico Mar 23 '16 at 00:54
  • I merely want to verify a signature in the format specified above within a contract. The signing part would be just for unit testing. – arik Mar 23 '16 at 00:55
  • The verification procedure is done in a solidity contract in the question I linked to – MrChico Mar 23 '16 at 00:57
  • The call to ecrecover is, yes. But I don't have an execution environment in JavaScript surrounding it that would be able to prepare the signature string into the separate components before feeding it into a Solidity contract. The contract itself has to be able to handle the raw signature string. – arik Mar 23 '16 at 00:58
  • In principle, I'm possibly just looking for Solidity equivalents of web3.toDecimal and String.prototype.slice. I'm trying out all the answers in a contract as they come. – arik Mar 23 '16 at 00:59
  • @arik "verifying a signature is essentially a matter of comparing the resulting address with the expected one" is not what ecrecover does. It compares the hash passed in with the hash message decrypted from the signature to see that they match, then returns the address if true. – Garen Vartanian Nov 15 '18 at 00:25

2 Answers2

19

v, r, and s are parameters that can be parsed from the signature. Here's a good example from the ethereumjs utils library:

  var sig = secp256k1.sign(msgHash, privateKey)
  var ret = {}
  ret.r = sig.signature.slice(0, 32)
  ret.s = sig.signature.slice(32, 64)
  ret.v = sig.recovery + 27

Note how you can parse each value from a given signature.

Even though you may sign two different msgs with the same private key, the signing process (internally) generates a random nonce value (k) that is used as part of the calculation and must be different for each generated signature.

r and s along with the associated public key help to validate if the signature is legit.

Note: I'm no cryptographer. This is what I've dug up trying to answer the same questions you have. I'd suggest looking at some of the online resources for elliptic curves.

Aside from this, I was also wondering how I could produce a signature, either like this 0x2ac19db245478a06032e69cdbd2b54e648b78431d0a47bd1fbab18f79f820ba407466e37adbe9e84541cab97ab7d290f4a64a5825c876d22109f3bf813254e8601 or a set of {h, v, r, s} using Solidity alone, without the RPC-JSON detour as is the case in the question – provided I know a private key?

The problem you'd have generating a signature from within Solidity is you'd have to expose the private key. Alternatively, you could generate a signature outside of the normal Ethereum transaction process using one of the many elliptic curve libraries and send that resulting signature to a contract.

dbryson
  • 6,403
  • 2
  • 27
  • 37
  • Thanks for the response! Now, if I have just the signature string, how do I get the signature object from it? The signature string itself does not contain a recovery property, I'm afraid. Also, just making sure: Is the hash necessary for ecrecover sha256? – arik Mar 22 '16 at 22:55
  • Is there any way v can be recalculated? I'm afraid ecrecover won't work without passing in the right value. – arik Mar 22 '16 at 23:20
  • IIUC when a signature is DER encoded, it only stores the r and s points, and throws away "v". When I want to supply a "v" value I just try them both. So where pubkey_decompressed is the public key that created the ethereum address:

    v_but_not_really, r, s = bitcoin.der_decode_sig(sig)

    for possible_v in [27, 28]: possible_pub = bitcoin.encode_pubkey(bitcoin.ecdsa_raw_recover(msg_hash, (possible_v, r, s)), 'hex') if pubkey_decompressed == possible_pub: return possible_v

    – Edmund Edgar Mar 22 '16 at 23:21
  • 2
    In the string that is returned when using eth.sign, v is included. The last two hex characters represent v. More on this here: https://ethereum.stackexchange.com/questions/1777/workflow-on-signing-a-string-with-private-key-followed-by-signature-verificatio/1794#1794 – MrChico Mar 23 '16 at 00:53
  • to accomplish this signing process, what client or what environment do I need? " var sig = secp256k1.sign(msgHash, privateKey) var ret = {} ret.r = sig.signature.slice(0, 32) ret.s = sig.signature.slice(32, 64) ret.v = sig.recovery + 27" – Wang May 10 '16 at 05:36
7

What exactly do v, r, and s stand for?

  • r is the R.x value of the signature's R point.
  • s is the signature proof for R.x
  • v is a recovery parameter used to ease the signature verification.

v is not required but often included. But what is v?

Since the signature only includes the x coordinate of the point R, there are either 0, 1, 2, 3, or 4 matching y coordinates over the Secp256k1 elliptic curve. These four potential candidates are encoded in something called recovery_id.

A recovery ID can have the values 0..3 depending on the following conditions:

  • Is R.y even and R.x less than the curve order n: recovery_id := 0
  • Is R.y odd and R.x less than the curve order n: recovery_id := 1
  • Is R.y even and R.x more than the curve order n: recovery_id := 2
  • Is R.y odd and R.x more than the curve order n: recovery_id := 3

Now we know how to get to the recovery ID. v is simply v = recory_id + 27 for Bitcoin. In addition to v values of 27..30 that only reflect the recovery ID, there is also the notion of recovering compressed public keys, using the same recovery ID but a v of v = recovery_id + 31.

But we are not talking about Bitcoin, so last but not least, you want to look at EIP-155 because we no longer use the + 27 part that Bitcoin used to prevent replay protection:

v = chain_id * 2 + 35 + recovery_id

In Ethereum, v reflects the chain for replay protection and the ID for signature recovery.

And how do I obtain all the values necessary from the single string 0x2ac19db245478a06032e69cdbd2b54e648b78431d0a47bd1fbab18f79f820ba407466e37adbe9e84541cab97ab7d290f4a64a5825c876d22109f3bf813254e8601?

This is just a concatenated string of "#{v}#{r}#{s}" with:

  • v = 0x2a
  • r = 0xc19db245478a06032e69cdbd2b54e648b78431d0a47bd1fbab18f79f820ba407
  • s = 0x466e37adbe9e84541cab97ab7d290f4a64a5825c876d22109f3bf813254e8601

The v is 42.

chain_id = (v - 35) / 2

Now, we can verify that the v of 42 is only valid on the chain with ID 3 (Ropsten).

What I know is that ecrecover returns an address, and verifying a signature is essentially a matter of comparing the resulting address with the expected one. Yet the whole procedure seems unnecessarily complicated.

I know this is not a question, but this is literally how elliptic-curve cryptography works: it's just mathematical operations with various points on a curve.

An address is just a pretty-formatted version of the public key; the public key is just a point on the Secp256k1 elliptic curve.

A signature is just another point. And if you do public key magic and signature magic, at the end of this math-heavy process you have two points (public keys) and if they are exact matches, the signature can be considered verified.

That ecrecover returns an address is just for your convenience: you can directly compare if the signature address matches the signer address - and that is much easier to do in Solidity as opposed to deal with uncompressed public keys.

I hope this sheds some light one this question.

q9f
  • 32,913
  • 47
  • 156
  • 395
  • Hi, in what case can there be more than 2 "y" coordinates for one x? as far as I understand, the curve is mirrored horizontally, which means there can be 0 (if the x point is to the left of the curve), 1 (if the x point is at the exact middle of the curve), and 2 everywhere else. – J3STER Jul 06 '23 at 08:42
  • The curve is not really a "curve," it looks more like a field of sprinkled dots. So, there are x-values where you don't have any corresponding y-value. Approximately half of the possible choices for x correspond to usually two points. https://crypto.stackexchange.com/a/98364 – q9f Jul 13 '23 at 10:24