Crypto Cheque! Introduction to Message Signing in Ethereum

ET
Coinmonks
Published in
13 min readJul 30, 2019

--

Let’s say I want to authorize my friend to withdraw a pre-determined amount of money from my bank account at some point in the future. We accomplish this by the use of a cheque. I simply write down the amount, the receiver, the date and I sign it before handing it to my friend, who then deposits it at the bank in exchange for the funds. In practice, the cheque is considered an IOU, which acts as a more versatile form of payment, instead of handing over money directly. Cheques also come pre-etched with elements such as holographic gold foil and heat sensitive ink which act as security measures. Every cheque is unique similarly to bank notes: they cannot be replicated.

Cheques are important for many reasons, but how are they related to signing messages in Ethereum? Although message signing is a versatile tool, let’s imagine it as the cryptocurrency equivalent of issuing a cheque. Using the cheque analogy, clear similarities and differences come to light. We’re interfacing with software to enter recipient/amount/signage details rather than writing them down on paper. The most significant difference is the method of signage, which involves the use of an Ethereum private wallet key instead of a pen signature. It’s also worth noting that this process shifts the intermediary from a private entity (a bank) that can stall, withhold, or freeze funds from behind the scenes, to a smart contract that releases funds based on a pre-determined set of rules in the form of written code.

This is the future of money and we’re gonna build it today.

This is an introductory to intermediate level tutorial. The prerequisite is a basic understanding of Solidity, Truffle & NodeJS. During this tutorial, it is highly encouraged to follow along while typing the code instead of copy + pasting. This process helps slow down the mind so it can absorb what’s going on. Be sure to cheque out my previous tutorial geared towards COMPLETE beginners if you are in fact, a complete beginner. Moving along….

Setup The Environment

  1. Ensure you have NodeJS installed. Copy and paste the following into your terminal:
brew install node

2. Create a new project directory and enter it.

mkdir message_signing
cd message_signing

3. Ensure you have Visual Studio Code installed or your preferred text editor. Open the project in the editor.

code .

4. Install Truffle globally on your machine and initialize a new project.

npm install -g truffle
truffle init

5. Create an index.js file. This will hold the javascript code for signing messages. Head into the Contracts directory and create cheque.sol. This will be the smart contract that validates the signed message and releases funds to the payee.

touch index.js
cd Contracts
touch Cheque.sol
cd ..

6. Create a package.json file and install web3.

npm init -y 
npm install web3

7. Install and open Ganache. This is a private blockchain that mimics the behavior of the real Ethereum network. It will serve as our platform for deploying the contract.

Your folder structure should appear as follows:

/message_signing
/contracts
Migrations.sol
Cheque.sol
/migrations
1_initial_migrations.js
/test index.js
package.json
truffle-config.js

Before we jump in, the entire program summary is as follows:

  1. The payer pre authorizes a payment by signing a message using their Ethereum private key.
  2. The payer deploys the smart contract, sending with it the desired amount of Ether for the payee.
  3. The payer sends the signed message to the payee via the desired medium (eg. e-mail).
  4. The payee claims the payment by presenting the signed message to the smart contract, which verifies the authenticity and releases the funds.

Writing The Cheque Contract

  • WARNING: This section is quite involved, so pay close attention. Each block of code will be explained thoroughly to ensure proper understanding.
  • For those interested, here is the github link for this project.

Head intoCheque.sol and write:

A few things are going on here.

Line 1 declares the Solidity version, which is standard to every smart contract. To find out which version of Solidity your compiler is using,

truffle version

in the terminal should output:

Truffle v5.0.0 (core: 5.0.0)
Solidity v0.5.0 (solc-js)
Node v11.13.0

In this case, we’re using v0.5.0.

Line 3 creates a mapping which stores the validity of nonces. Nonces are used to prevent replay attacks, in which the recipient could re-submit the same signature and withdraw funds more than once. The payer will embed this nonce within the message to be signed.

Line 4 declares a the public variable owner. This is used to keep track of the payer’s address who deployed the contract. Its only purpose is to be verified against the result of the message recovery function, which will return the public key of whoever signed it. If the key matches the owner, the funds will be released.

Lines 5–7 compromises the constructor function. It is marked payable so we can send Ether to the contract when we deploy it. This is the amount we are pre-authorizing for future withdrawal. Once deployed, it sets owner to the deployer’s Ethereum address.

Now, we’ll write our first function:

Line 11 instantiates splitSignature. It takes in one parameter, sig, which represents the signature produced from the owner/payer who signed the message. This is a utility function that is only executable from within the contract as denoted by internal. Furthermore, it does not modify the contract’s state and therefore does not cost any additional gas, as denoted by pure. Simply put, no variables are being modified, it is simply a calculation.

Ethereum uses the Elliptic Curve Digital Signature Algorithm to facilitate digital signatures. Each ECDSA signature consists of three paramaters, r, s and v. The purpose of this function is to de-structure the signature into these three components and return them to be used in a recovery function to determine the original signing address- more on that later.

Line 12 ensures the signature is 65 bytes long. If it isn’t, it is not a valid signature.

Lines 14–16 declare r, s and v. r and s are in the form of fixed length 32 byte arrays. v takes the form of a basic integer.

Lines 18–25 are where the main functionality occurs. Here, we’re using inline assembly to de-structure the signature into its 3 components. As denoted in the code comments, r consists of the first 32 bytes of the signature, s consists of the next 32 and v consists of the final byte (first of next 32 bytes) for a total of 65 bytes.

Line 27 returns the 3 components.

To recap, splitSignature takes a signature as the input, splits it into its components and returns them as output values. It will be used by the next helper function to return the wallet address of the original signer:

Line 30 creates recoverSigner, which accepts 2 parameters.

  1. message is the original hashed message created by the owner/payer, which w’ell demonstrate soon.
  2. sig is the signature created when the owner/payer signs the message.

Just like splitSignature, this is an internal utility function that does not modify the contract’s state. The objective of this function is to uncover the wallet address of the individual who signed the message and return it to be used in another function.

Lines 31–33 declare the variables r, s and v, just like splitSignature.

Line 35 assigns r, s and v to the return values of splitSignature.

Line 37 calls ecrecover with the resulting r, s and v values along with the message parameter. ecrecover is a globally available Solidity method that is used to determine the signing address of a hashed message, given the hashed message itself and the signature split into its 3 components.

To recap, recoverSigner takes in the original hashed message and the signature, feeds the signature into splitSignature, captures the return values and feeds them into ecrecover with the hashed message to obtain the original signing address.

Line 40 is the last utility function of our smart contract, prefixed. We’ve been talking about the ‘hashed message’ but what does this really mean? When the owner/payer creates the message, it must be put through Ethereum’s commonly used hashing algorithm, keccak256 (formerly referred to as SHA3), before being signed. This function accepts that hash as an input parameter and transforms it into a ‘prefixed’ hash to be fed into recoverSigner.

All transactions in Ethereum are encoded using Recursive Length Prefix(RLP) encoding. Without getting too deep into the details of RLP, the \x19at the beginning is an intentionally invalid byte for a transaction to begin with. This prevents an application from tricking you into signing what appears to be a message, but is actually a transaction in disguise. The Ethereum Signed Message:\n makes the contents of the message human-readable.

On to the final component of the Cheque contract:

Line 44 is claimPayment, which will serve as the entry point to the contract as denoted by public. This means that the function can (and in our case will) be called from outside the contract (our javascript code). It accepts 3 parameters:

  1. amount is the agreed upon transaction amount specified by the owner/payer in Ether.
  2. nonce is the nonce provided by the owner/payer which he/she has embedded within the message.
  3. sig is the signature produced from signing the message.

Line 45 converts the amount provided from Ether into Wei. Ethereum smart contracts always read Ether quantities in Wei. 1 Ether = 1e18 Wei. More on denominations here.

Line 46–47 ensures the provided nonce hasn’t already been used. If the provided nonce is valid, it marks it as used for the next time the function is called.

Line 49 is responsible for re-creating the message made by the owner/payer. The message includes:

  1. msg.sender — the receiver’s (caller’s) Ethereum address
  2. amountWei — the agreed upon amount presented by the owner/payer
  3. nonce — provided in the function parameter
  4. this — the contract’s address. This is necessary to prevent against a type of replay attack in which a new instance of the contract is created (which clears the old nonces) enabling the payee to call claimPayment again. In the newly created contract, the owner would reject all signatures that reference the old address.

The above 4 parameters are wrapped in abi.encodePacked, which converts the information to bytes in a ‘tightly packed’ form. Check this article for more information on abi functions.

This data is then hashed using the keccak256 (formerly known as SHA3) function. The hash is prefixed by the help of prefixed and is stored as message. At this point, the message is ready to be used for signer recovery.

Line 51 is the crux of the entire contract. It calls recoverSigner with message and signature as parameters and compares the return value (an address) with the contract’s owner. If the 2 addresses match, we can be sure that indeed, the payer has approved for this function to be called and for funds to be withdrawn.

Line 53 simply transfers the amountWei to the function caller/payee’s address.

Creating & Signing The Message

Now that we’ve finished our expertly written smart contract, let’s create our signed message that we can verify.

Open up index.js and type the following:

Lines 1–2 imports Web3 and creates an instance using our private Ganache blockchain. This allows us to use the accounts on Ganache to sign messages and call functions. Web3 also contains utility functions, one of which we will use to sign our message. On line 2, localhost:8545 refers to the address and port Ganache is using, which can be viewed under RPC SERVER. (localhost = 127.0.0.1)

Lines 5 retrieves the JSON contract representation created by Truffle upon migration using the NodeJs fs (filesystem) module. We’ll cover this in detail soon.

Line 6 parses the JSON so it is readable by our program.

Line 7 extracts the contract address from the JSON and stores it as contractAddress.

Line 9 declares the payment signing function signPayment. It takes in the recipient (payee) address along with the agreed amount. It’s marked async so we can take advantage of Javascript’s async/await pattern for retrieving data asynchronously.

Line 10 uses web3 to retrieve all the accounts associated with the given blockchain (Ganache) and stores them in a list as accounts

Line 11 assigns the first account in the list to payer.

Line 12 retrieves the transaction count for the given address (payer). We’ll be using this as our nonce to embed within our message.

Line 13 uses web3’s soliditySha3() hashing function to create a hash of our message. Our message contains the recipient (payee), amount, txCount (nonce) and the contract’s address (contractAddress). This particular hashing function mimics the behavior of the keccak256() function in solidity.

Lines 15 & 18 are try/catch blocks. The code will attempt to execute the contents of the try block, falling back to the catch block if it fails. This syntax is useful for the async/await pattern because it doesn’t have inherent error catching when compared to promises.

Line 16 signs the message hash we created using the private key from one of the Ganache accounts. Replace <GANACHE_PRIVATE_KEY> with the actual private key from the first account in Ganache. Retrieve it by clicking the key icon on the right side of the account box.

**IMPORTANT: Make sure to prepend the private key with 0x after pasting it in the code, otherwise the program won’t recognize it as a valid key. Example: 0x6efc5ffc3ec7609736a2299a4a80e8f4377039b947c310ffa6f9ddf7a1fd5398

It’s worth noting that web3.eth.accounts.sign automatically prefixes the message with \x19Ethereum Signed Message:\n before signing. We’d have to manually add the prefix if we were using an alternative signing method such as web3.eth.sign. Check the docs for more information on the difference between these methods.

The output value of web3.eth.accounts.sign is an object that contains the signature which we will explore momentarily.

Line 17 outputs the amount, txCount & sigObject to the console so we can use them when we interact with the contract.

Line 23 calls signPayment with the 2nd address on the Ganache list (the payee) and the send amount. Simply copy + paste the address from ganache. The second parameter is the amount (10e18). Remember, 10e18 Wei = 1 Ether, same as we wrote in the contract.

In the terminal, run:

node index.js

The output should resemble the following:

100000000000000 4 {
message:
[170,
80,
21,
140,
32,
200,
163,
173,
111,
250,
155,
168,
13,
9,
72,
192,
32,
64,
156,
14,
40,
109,
210,
180,
50,
126,
191,
205,
93,
137,
29,
30],
messageHash: '0xb6d4e6021a8eece755ee78881bb64a8ad35777f868ec6ceed13fbfb91f7ae9c6,
v: '0x1c',
r: '0xe94a8699fec3b955192921925a7a2d5aeeeca15af05e010f0633fef296a054a9,
s: '0x6a2fef0e41c9f150564f4e7c4f476dcdcc75605580f0c8ac39885a806d07b924,
signature: '0xe94a8699fec3b955192921925a7a2d5aeeeca15af05e010f0633fef296a054a96a2fef0e41c9f150564f4e7c4f476dcdcc75605580f0c8ac39885a806d07b9241c'
}

The first 2 numbers are the amount and the nonce (transaction count), those are important for verifying the message signer.

The signature object not only includes the signature at the bottom which we’ll use, but some other interesting data as well. Remember in our smart contract how we split the signature into the v, r, and s components? These are the same components outputted above. We can also see the message in a different format and the messageHash.

Claiming The Payment

Now that we’ve created the cheque contract and signed a message as the payer to approve a withdrawal, let’s deploy the contract and interact with it to claim the payment as the payee.

First off, open truffle-config.js located in the project’s root. Delete the contents of the file and replace it with the following:

This configuration is necessary so that Truffle knows which blockchain network to use when we deploy smart contracts. Here, we’ve set up a network called development, although the name is trivial. The host and port come directly from Ganache. Since this is the only network we’re working with, Truffle will use it by default when we deploy and interact with smart contracts.

Next, create a new file titled 2_cheque.js under the migrations directory.

cd migrations
touch cheque.js
cd ..

Open it and type:

Line 1 imports the smart contract using the Truffle Artifactor. This converts the raw solidity to a JSON representation that Truffle can read.

Lines 3–5 deploy the contract instance to Ganache using the Truffle Deployer, which handles everything behind the scenes. {value: 1e18 } represents metadata that’s passed in as a parameter to the deployment. In this case, it’s the amount of Ether we’re sending to the contract in the form of Wei.

Recall how we marked the constructor function as payable in the contract. This permits Ether to be sent to the contract upon deployment when the constructor is called.

constructor() public payable {
owner = msg.sender;
}

Now that we’ve set up our config and deployment settings, it’s time to play with our new toy. In the console, type the following:

truffle migrate 

This command tells Truffle to accomplish 2 things:

  1. Compile the raw solidity into a JSON readable representation of the contract. Use truffle compile as an alias.
  2. Deploy the JSON abstraction to the network based on the configuration we’ve provided. At present, Truffle will deploy this contract on our development (Ganache) network using the first address in the list by default, with 1e18 Ether. Use truffle deploy as an alias.

After running this command, open Ganache. Notice how the first account in the list has dropped in Ether balance. The difference is the deployment cost + the amount we sent to the contract.

You’ll also notice the new directory build created in the project’s root containing the JSON files.

truffle console

opens up the Truffle console for the default network (development). This is a javascript runtime environment complete with the modules used by Truffle.

let app;
Cheque.deployed().then((instance) => {
app = instance
})

Here, we’re assigning app to our deployed instance of Cheque.

let accounts;
web3.eth.getAccounts().then((result) => {
accounts = result
})

This retrieves the accounts from Ganache using web3 as an array and assigns them to accounts. Try running accounts in the console, you’ll see the list as the output:

[ '0x2642269FCDDbB58f76124b7f4a98Df93E99ae803',
'0xd09D4cF222ef0B4D5623815aE9a01682a1d17E88',
'0x66e0249709b4dD1A30Ab3E18cBcb6F944a95d48b',
'0xe15bcb2964339dF76341cAF10977F84CBd036ef1',
'0xa485b07b9C149757acA6c0B29F051fAAe84364f0',
'0x5bC383FB28F52a4aE42c5b4caD682163c0FE0654',
'0x8a8da609576A3Bd1ab6018285Ef1F020467F9ECB',
'0xebe47e36F5343e733917bd652d156B0005d2eb50',
'0x6A3C002BC49A1fE1a895a152CeF3c29831E8d46b',
'0xae9086C3A251D55bd5491E1Af1898608a788F263' ]

Finally, we’ll claim our payment as the rightful payee:

app.claimPayment(1, NONCE, SIGNATURE, {from: accounts[1]})

Since we stored the deployed contract instance in app, we can use it to call its functions. Here, we’re calling claimPayment as the payee. The 1 parameter refers to the amount in Ether. It is represented in Ether here because the smart contract is responsible for converting it back to Wei. Replace NONCE with the txCount output andSIGNATURE with the sig output, both from running index.js. {from: accounts[1]} tells the program to use the second address in our list of accounts as the function’s caller (payee!). You should see an output like this:

{ tx:
'0xb5e6703667cdb222fecdbb23c9be1515f2553e00a092b0968393a51d0be15a9e,
receipt:
{ transactionHash:
'0xb5e6703667cdb222fecdbb23c9be1515f2553e00a092b0968393a51d0be15a9e,
transactionIndex: 0,
blockHash:
'0xf888af1ad05caefd17a8b3c1f4d1e1449b58b65f4ad1453b519bacba22fc68ee,
blockNumber: 5,
gasUsed: 60691,
cumulativeGasUsed: 60691,
contractAddress: null,
logs: [],
status: true,
logsBloom:
'0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
rawLogs: [] },
logs: [] }

Now if we look at Ganache, the first account is deducted 1.02 ETH (cost of deployment + Ether sent) and the second account is credited 1 ETH for claiming the payment.

Congratulations! If you’ve stuck it out this far, you’ve just learned how to create a smart cheque on Ethereum!

In a real world scenario, the payer would send

🎉🎉 Stay tuned for more educational articles to come. Full length video courses on how to program the Ethereum Blockchain are also coming out soon! 🎉🎉

Get Best Software Deals Directly In Your Inbox

--

--

ET
Coinmonks

Dev at @Omakasea_ EVM | Solidity | Linux | Tutorials — — Follow me on my research journey!