Verifying an Ethereum signature on the server - PHP
It is fairly easy to utilise open source projects (like ethereumjs-util) to sign arbitrary data messages. What is less easy however is to tell a server that someone has successfully verified their ownership of account x.
Well.. that is not strictly true - it is really easy to do exactly that. Simply build a simple API endpoint and fire off a request to it upon successful authentication.
The real problem is that it is really easy to create a 'fake' request and send it off to the aforementioned (easily discernible - just look in the console) endpoint. I could easily fire off a request saying that I had verified ownership of any account.
With cutting edge technology.. especially technology that 'handles' real value it is especially important that security is given the importance and respect that it deserves. The is especially the case in light of the various attack vectors that have historically been exploited.
Furthermore, in its infancy Ethereum has attracted the best of the best - the people that know what they are doing. If there is a security vulnerability, someone will find it.
Now.. whilst it is possible to secure AJAX requests and make forgery harder, it is nigh on impossible to make things 100% secure. I needed another way.
The way I eventually settled on was simple - server side authentication.
Everyone can see
One great thing about interacting with the blockchain in the client is that within reason anyone so inclined can see exactly what you are doing. They can feel confident knowing that you are not sending their private key to someone else. How? They can look in the console and see each and every outgoing request.
If a service were POSTing my private key anywhere I would be extremely concerned.
Within our implemented authentication flow a user can see that we are not sending any data anywhere - everything is done in the client.
Sadly however my authentication resolution does require POSTing data.. but nothing important (some may disagree).
POST the authenticated public key to our API endpoint. Whilst you can not verify what we do with your public key on the server, there is not really anything nefarious we can do with just your public key - that is why it is public.
On the server we utilise the submitted public key to verify that the submitted signature was created by someone with knowledge of the corresponding private key. To be explicitly clear here - we do not know your private key, yet elliptical curve cryptography allows us to verify that the signature was created using it by simply using the public key.
On the Ethereum forum chriseth gives the following useful explanation of
"The idea of ecrecover is that it is possible to compute the public key corresponding to the private key that was used to create an ECDSA signature given two additional bits which are usually supplied with the signature. The signature itself is the two (encoding of the) elliptic curve points r and s and v is the two additional bits needed to recover the public key.
This also explains why the return type is address: It returns the address corresponding to the recovered public key (i.e. its sha3/keccak hash). This means to actually verify the signature, you check whether the returned address is equal to the one whose corresponding private key should have signed the hash."
We want the same functionality on the server.
ecrecover returns an address whereas ethereumjs-utils
ecrecover returns a public key
Note: Whilst researching I found a number of interesting StackExchange questions on the subject matter. These are as follows:
The web3.js API docs also provide some insight into the parameters of ecrecover noting:
After the hex prefix, characters correspond to ECDSA values like this: r = signature[0:64] s = signature[64:128] v = signature[128:130] Note that if you are using ecrecover, v will be either "00" or "01". As a result, in order to use this value, you will have to parse it to an integer and then add 27. This will result in either a 27 or a 28.
EthTools.com is built on the Phalcon PHP framework.
There is no real Ethereum PHP community, and PHP has its shortcomings when it comes to dealing with numerical representations.
Then of course there is the small issue of Elliptical curve cryptography being extremely complex, and me lacking any prior knowledge of its workings..
That said after a significant amount of research, and a significant amount of playing I managed to implement the
ecrecover functionality in PHP.
Whilst discerning how to do this I wrote some 'notes' which I have tidied up and included below on the off chance they help someone else in the right direction.
My logic of action was to sign a transaction using
ethereumjs-util using a known Ethereum private key. I would then mimic the code path of their
ecrecover method in PHP and play until the outputted public key 'recovered' from the signature matched that of the original signing account.
Within Node, Buffers are arrays of unsigned 8 bit integers. The digits are their base 10 (decimal) representations.
With 8 bits there are 2^8 = 255 decimal options. These integers are the numeric code point representations of characters from the UTF-8 character set.
Node utilises these buffers for data manipulation of the sort required for doing these kind of computations.
On the server we have various strings (the message hash, and the signature), but PHP does not know that the characters in these string are base 16 numerical representations (hexadecimal).
Each character is a 'nibble' which requires 4 bits of data to represent (allowed hexadecimal characters are 0-9 and A-F).
As such 8 bits of data is two hexadecimal characters.
In Node, the string '61bf09' is converted into a Buffer by taking each set of two nibbles and converting it to its decimal form.
- 61 becomes 97
- bf becomes 191
- 09 becomes 9
To do the equivalent in PHP we execute something like the following:
$r_byte_array = unpack('C*', hex2bin($r));
hex2bin which converts the hexadecimal string (without 0x) to its binary representation (base 2). By calling this method we are implicitly stating that the initial format is hexadecimal.
unpack then converts the string to an array of code points - our Buffer equivalent.
Initially PHP just thinks the string is UTF-8. If we dont call
hex2bin first the first int is 54.
This is because unpack simply converts the first character (6) to its binary code in utf8 (54). 64 characters = 64 code points.
When we tell unpack that we are dealing with hexadecimal, it converts each two character hexidecimal set (each character representing 4 bits of data) to its decimal representation. 61 (0x61) becomes 97. Our 64 character hexadecimal string becomes 32 8 bit integers.
You can see these different representations by having a play with this convertor.
Now that you have an appropriately formatted representation of the message hash and the signature you can cheat..
I like to think myself relatively intelligent. That said trying to fully understand, appreciate, and implement the 'secp256k1' elliptical curve is simply not going to happen. Furthermore.. why bother? It is another case of not reinventing the wheel.
I found a few libraries pertaining to secp256k1 in PHP. For example:
- phpecc - the more generic library from which the first is based
I ended up using a combination of bits from all three libraries - I like to know what I am using, and have a basic (at least) understanding of what I am pushing to our servers. Given that the above libraries are fairly feature rich/complex it seemed pragmatic to simply extract what I needed for my relatively simple functionality.
After spending a significant amount of time getting my head around what is going on I have finally managed to achieve what I was trying to achieve - I have managed to verify that a signature created in the client was signed my a particular private key.
I will now move forward with implementing the functionality that I had in mind when I first climbed into this rabbit hole.
NOTE. January 2018 I have written a second post detailling exactly how I verified the authenticity of a previously signed message in PHP Part 2.