A Step-by-Step Guide to Generating Raw Ethereum Transactions
A few months ago I embarked on a project to create an application on the Appian BPM platform that could interact with the Ethereum blockchain. My goal was simple: create a library that could generate a raw transaction, complete with the hash of a document as the data input. I assumed it wouldn’t be too hard to find a guide that showed step-by-step how a transaction was generated. Here, I was wrong.
With no easy resource available to walk me through the transaction generation process, I realized my best best was to go through one of the existing Ethereum libraries. After spending a weekend learning Python, I was able to comb through the Pyethereum library and fully understand all of the steps to create a raw transaction.
I assume some non-coders may be interested in this process, or that others may take on similar projects in the future, so here’s a plain-English explanation of each of the steps to generate an Ethereum transaction.
Step 1: RLP encode an array of the basic transaction inputs
The six inputs to begin any transaction are: Nonce, Gas Price, Gas Limit, To Address, Amount, and Data (optional).
Nonce is a value that corresponds to the number of transactions sent from a given address. The first transaction from an address will have a nonce of 1, the second 2, and so forth. Note that this is different from the nonce used to generate randomness in solving PoW algorithms. More on nonces here.
Gas Price is the price (in wei) you are willing to pay for each unit of Gas. Gas Limit is the maximum gas that the transaction will be allowed to use. If you’re not familiar with the concept of Gas in Ethereum, this is a good intro.
To Address is the Ethereum address to which you are sending the transaction.
Amount is the amount of Ether (in wei) to send.
Data is an optional input that allows you to add additional information to the transaction. This is particularly important for interacting with smart contracts but is less used when just sending funds.
The first step to generating a transaction is to take these six inputs (in the order listed above) and put them into an array. We will then encode this array using Recursive Length Prefix (RLP) encoding. RLP encoding is the serialization format used in Ethereum. It’s somewhat complex on its own, so rather than trying to explain it here, read this primer.
RLP encoding requires that the values to be encoded are first converted to hexadecimal format.
Here’s an example, using:
Nonce = 1
Gas Price = 21000000000 (4E3B29200 in hex)
Gas Limit = 21000 (5208 in hex)
To Address = 0xc390cC49a32736a58733Cf46bE42f734dD4f53cb
Amount = 1000000000000000000 (DE0B6B3A7640000 in hex)
Data = 1
We end up with: 10E01854E3B29200825208950xc390cC49a32736a58733Cf46bE42f734dD4f53cb88DE0B6B3A764000001
Step 2: Hash it
This part is pretty simple. Take the output of step 1 and run it through the SHA256 hashing algorithm.
Taking our example from step 1, the hashed value is: 8b9d78ca2a022fd99ab29bbf3cf282570420c5c78ef799bdbf45e3d79842e10d
Step 3: Sign it
In this step, we’ll use our private key to sign the transaction, which will prove to the network that we authorized the transaction. This is done by using an Elliptic Curve Digital Signature Algorithim (ECDSA). For this, we will need to calculate three values: v, r, and s.
r and s are two standard values in ECDSA signatures. To calculate them, we’ll need the following variables:
k = random integer
G =generator point
e = message to be signed (output from step 2)
d = private key
We can then use these equations for our calculations:
C = kG
r = Cx
s = (e + rd) / k
Let’s break it down piece-by-piece.
First, we take k, a randomly chosen integer, and multiply it by G, the generator point (note: multiplication on an elliptic curve is not the same as normal multiplication). Since Ethereum uses the secp256k1 curve for signing, G is a fixed value.
Gx = 0x79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798
Gy = 0x483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8This gives us a new point on the curve, C. Taking the x coordinate of C will give us the value for r.
Next, we can calculate the value for s by adding our RLP-encoded transaction message, e, to the product of r and d (our private key) and then dividing by k.
After we have calculated r and s, we need to verify that the values are valid. In order to fit within the storage limitations, r and s must both be positive integers smaller than 2²⁵⁶. If this condition is satisfied, we can move on to the next step. If not, we start over again with a new value of k until we find one that works.
Once we have valid values for r and s, the last piece of the signature is v. This value allows you to calculate the public key from a transaction signature. There are a couple of different ways to derive this, but for our purposes, we’ll just use 37 if r is even and 38 if r is odd, as this follows EIP-155.
If you’re interested in more of the math behind ECDSA, this is an excellent post.
Using our example values, we could end up with the following values for v, r, and s:
v = 25 (37 as a decimal)
r = 5ab2f48bdc6752191440ce62088b9e42f20215ee4305403579aa2e1eba615ce8
s = 3b172e53874422756d48b449438407e5478c985680d4aaa39d762fe0d1a11683
Step 4: RLP Encode the signed transaction values
The last step is to use RLP encoding once more on our final transaction values, which should be made up of the following array (in this order).
{nonce, gas price, gas limit, to address, amount, data, v, r, s}
Concluding our example, we would RLP encode the following array:
{1, 4E3B29200, 5208, 0xc390cC49a32736a58733Cf46bE42f734dD4f53cb, DE0B6B3A7640000, 1, 25, 5ab2f48bdc6752191440ce62088b9e42f20215ee4305403579aa2e1eba615ce8, 3b172e53874422756d48b449438407e5478c985680d4aaa39d762fe0d1a11683}
Which will give us the following value as our raw transaction:
0xf86c808504e3b2920082524c94c390cc49a32736a58733cf46be42f734dd4f53cb880de0b6b3a76400000125a05ab2f48bdc6752191440ce62088b9e42f20215ee4305403579aa2e1eba615ce8a03b172e53874422756d48b449438407e5478c985680d4aaa39d762fe0d1a11683
Once you have this, you’re all finished! You can now send your raw transaction to a node connected to the Ethereum network so that your transaction can be added to the blockchain.
