Web3 and Blockchain Development

Signing basic unstructured data with Web3j

Written by George Tebrean | Apr 19, 2022 4:45:00 PM

The capability to sign messages off chain with Web3j has been possible since 2017. Over time, improvements have been made to support basic string messages but also some more complex structured messages. In this post, we will focus on signing basic unstructured data.

To implement the Ethereum sign off chain:  

eth_sign(keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))) 

These are the steps to follow: 

  1. Generate the message with the prefix specific to Ethereum network;

  2. Hash the message from step 1 with keccak256;

  3. Using the result obtained at the previous step and applying ECSDA (Eliptic Curve Dgital Signature Algorithm) generates the final signature that consists in r, s and v elements.

In Web3j, all the steps are already implemented and the sign can be done directly by simply calling the static method Sign#signPrefixedMessage from crypto module.

To put things practically, let's say that we want to sign the string “Hello” with certain account credentials. So we have: 

String messageToBeSigned = “Hello”;
// the private key of the account
// 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 from
// https://remix.ethereum.org/ for JavaScript VM (London). This specific //account was selected in order to validate against latter.
String privateAccountKey = ”503f38a9c967ed597e47fe25643985f032b072db8075426a92110f82df48dfcb”

The first thing should be the creation of the Credential object. This represents the account object which holds the public and private keys, plus the address. 

Credentials credentials = Credentials.create(privateAccountKey);

Due to the numerous hashes and processes that have to be applied to the message to get it signed, we will convert it to bytes:

byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);

Now we can simply call the Sign#signPrefixedMessage to get our message signed:

Sign.SignatureData signature = Sign.signPrefixedMessage(messageBytes, credentials.getEcKeyPair());

 

The result will consist of a SignatureData object which contains the r, s and v elements as byte arrays. To display them in a more readable way, Remix the following code snippet:

byte[] retval = new byte[65];
System.arraycopy(signature.getR(), 0, retval, 0, 32);
System.arraycopy(signature.getS(), 0, retval, 32, 32);
System.arraycopy(signature.getV(), 0, retval, 64, 1);
System.out.println(Numeric.toHexString(retval));

Notice that with sign.java there are multiple sign methods that may be confusing. From all those there, only signPrefixedMessage creates signatures specific to the Ethereum network because it appends the particular prefix before hashing. 

The proper prefixed hash is computed in Sign#getEthereumMessageHash which appends the prefix by calling Sign#getEthereumMessagePrefix. Of course any sign available entry methods can be used but if the prefix is not placed properly the result won’t be an Ethereum valid signature similar to the ones returned by web3js.  

To ensure the result is valid, the signature can be checked against one online off chain signer like https://remix.ethereum.org/ or https://app.mycrypto.com/sign-message and select MetaMask as mode.

For example, to check against remix, follow these steps:

    1. Go to Deploy and run transactions section on https://remix.ethereum.org/;

    2. Set on Environment JavaScript VM (London);

    3. Use the account 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;

    4. Select Signed a message using this account key;

    5. In the pop-up window type in Hello and press OK;

    6. The following output has to be displayed: 
 

7. Go to java IDE, add the Web3j library dependency and run the following code in a main method: 

String privateAccountKey = "503f38a9c967ed597e47fe25643985f032b072db8075426a92110f82df48dfcb";
Credentials credentials = Credentials.create(privateAccountKey);
String message="Hello";
byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);
Sign.SignatureData signat
ure = Sign.signPrefixedMessage(messageBytes, credentials.getEcKeyPair());
byte[] value = new byte[65];
System.arraycopy(signature.getR(), 0, value, 0, 32);
System.arraycopy(signature.getS(), 0, value, 32, 32);
System.arraycopy(signature.getV(), 0, value, 64, 1);
System.out.println("hash: " + Numeric.toHexString(Sign.getEthereumMessageHash(messageBytes)));
System.out.println("signature: " + Numeric.toHexString(value));

8. The following result should be displayed: 

 
 

On both remix and java program can be noticed that the results on hash and signatures are the same! 

In my next blog post I'll be discussing recovering data from signature, so stay tuned!