Signing and Verifying Ethereum messages
Prerequisites
It is absolutely critical that you would have gone through at least one of our onboarding guides that will teach you the way EthVigil handles user accounts, signing up, logging in, deploying contracts etc.
If you haven't, go check them out.
While you are at it, you might also want to check out working with an ERC20 contract.
Basics
The Ethereum platform comes batteries included with primitives that allow cryptographic signing and verification of messages. 'Messages' can be any form of data, the structure of which is agreed upon in both the signing and verifying logic.
Asymmetric/Public Key cryptography
'Key' points
You have a public and a private key. Public key can be distributed, well, publicly. Private, you keep it, private.
The two keys are mathematically related.
You can generate an encrypted version of a piece of data on signing it with your private key. Others can verify i.e. decrypt it with your public key.
Others can use your public key to sign a piece of data only intended for you. You can decrypt the same with your private key.
The encryption algorithms make use of one-way functions -- mathematical functions that are
- computationally cheap to execute to arrive at an output given two inputs
- but computationally expensive by many orders to arrive at an expected set of inputs given an output
Further reading
- Wikipedia - public key cryptography
- Reddit - What is the encryption algorithm used by Ethereum and how does it work?
Applications of signing and verifying of messages
Any use case which involves the blockchain being used to store proofs of computations/ business process lifecycle changes that are executed 'off-chain' to save transaction costs
State channels - submit proofs of settlements, challenges and verify the same to move the transaction lifecycle forward.
decentralized exchanges - orders take place on a network/chain/database separate from the main chain. As with state channels, the chain is used for settlement.
The signing and verification of data is purely computational and does not require any form of connection to the Ethereum networks.
Creating the signature
How is the signature computed?
Take a look at the eth_sign
method exposed by the JSON-RPC interface of Ethereum clients. The same is also available through other client libraries such as web3.js
, web3.py
etc.
The sign method calculates an Ethereum specific signature with:
sign(keccak256("\x19Ethereum Signed Message:\n" + len(message) + message)))
By adding a prefix to the message makes the calculated signature recognisable as an Ethereum specific signature. This prevents misuse where a malicious DApp can sign arbitrary data (e.g. transaction) and use the signature to impersonate the victim.
Formalizing the message to be signed
The message
noted above need not be only a string.
For this walkthrough, we have decided upon a unique identifier/counter packed with the address of the verifying contract. This is the smart contract method where the signed data is submitted.
We will get to recoverSigner
in a bit.
We are generating the signature from a constructed message
as described in the section How is the signature computed?
- Do a packed encoding of the individual data fields that make up the message.
uniqueID
andaddress(this
) i.e. the address of the verifying contract.- each data field is encoded according to a hexadecimal representation according to the encoding rules laid out in the formal spec
abi.encodePacked()
is available in Solidity that 'mashes' all the encoded values without any extra paddings. From the docs,- types shorter than 32 bytes are neither zero padded nor sign extended
- dynamic types are encoded in-place and without the length
- array elements are padded, but still encoded in-place
- Do a
keccak256
hash of the above packed data. This will always generate a message that is 32-bytes/256-bits long.
prefixed()
generates the Ethereum specific signature
We have replaced len(message)
with 32 since it is already known that a keccak256()
always returns a value that is 32-bytes long.
Recovering the message signer in the smart contract
ECDSA signatures in Ethereum consist of three parameters: v
, r
and s
. The signature is always 65-bytes in length.
r
= first 32 bytes of signatures
= second 32 bytes of signaturev
= final 1 byte of signature
ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)
Solidity has a built-in function ecrecover()
that accepts
- the expected
keccak256
hash correspondign to the correct reconstruction of the message:uniqueID + address of verifying contract
- the components of the ECDSA signature as described above
and returns the address used to sign the message.
uniqueID
and address(this)
in calculating signature
Why use We want to add as many unique fields in a message that would avoid "replay" attacks.
uniqueID
: Assume submitting a confirmation on the stringReleasePayment:invoice:0x00aabbccddeeff:amount:15000
. The 65-byte signature, if intercepted by a man-in-the-middle, can be resubmitted to the contract and could trigger the same payout function twice.address(this)
: let us the same payout contract code has been deployed on a new address, and the uniqueIDs that would have been recorded on the older contract don't hold valid on the new one any more. Hence, older unique identifiers can be used to re-release the amounts once more.
Generating signature and submitting to the smart contract
The code snippets can be found in the github repo that includes a command line script.
Setting up the contract
Deploy
5554
. We will tunnel to it through an ngrok
endpoint that will allow the EthVigil Beta API gateway to deliver event data payloads to our local server.
Launch the webhook listener that listens on port python webhook_listener.py
./ngrok http 5554
Copy the HTTPS forwarding URL, for example, https://c1c8b4fd.ngrok.io
Refer back to the CLI tool guide or web UI guide to learn how to add webhook integrations on EthVigil beta
Register the webhook listening endpoint with the EthVigil platform
Subscribe to all events emitted from this contract
Send a signed message to the contract
The command format is submitconfirmation <unique sequence ID/nonce> <private key used to sign the message>
The public Ethereum address corresponding to the private key 0x080a12470a639f95139e5e2d9fc7ca597869a42de9bfab4969a3a57a89b0c84a
is 0x774246187E1E2205C5920898eEde0945016080Df
Verify if the recovered signer address on the contract is the same as expected
The webhook listening endpoint receives the following update
As expected, the retrieved signer in the event data is the same: 0x774246187e1e2205c5920898eede0945016080df