A raw transaction is just all the transaction parameters encoded in the RLP format. A raw transaction consists of (in this order):
- Nonce;
- Gas price;
- Gas limit;
- To address;
- Value;
- Data.
You don't need the private key of the sender: to get the raw transaction, you can simply encode it using RLP(Nonce, Gas Price, Gas limit, To, Value, Data)
. Here's an example implementation in JavaScript (using ethereumjs/rlp):
import { encode } from 'rlp';
const transaction = {
nonce: '0x1e7',
gasPrice: '0x2e90edd000',
gasLimit: '0x30d40',
to: '0xbd064928cdd4fd67fb99917c880e6560978d7ca1',
value: '0xde0b6b3a7640000',
data: '0x'
};
// Raw transaction as Buffer
const rawTransaction = encode([transaction.nonce, transaction.gasPrice, transaction.gasLimit, transaction.to, transaction.value, transaction.data]);
console.log(rawTransaction.toString('hex'));
This results in the following raw transaction:
ec8201e7852e90edd00083030d4094bd064928cdd4fd67fb99917c880e6560978d7ca1880de0b6b3a764000080
The only part where a private key is required, is to sign the transaction. A signed transaction is basically the raw transaction with the v
, r
and s
signature (of the raw transaction) added to it: RLP(Nonce, Gas Price, Gas limit, To, Value, Data, v, r, s)
. The chain ID is encoded in the v
parameter (see EIP-155).
Here's an example of how to sign the transaction and get the signed transaction:
import { randomBytes } from 'crypto';
import { ecsign, keccak256 } from 'ethereumjs-util';
import { encode } from 'rlp';
// Randomly generated 32 byte private key
const privateKey = randomBytes(32);
const transaction = {
nonce: '0x1e7',
gasPrice: '0x2e90edd000',
gasLimit: '0x30d40',
to: '0xbd064928cdd4fd67fb99917c880e6560978d7ca1',
value: '0xde0b6b3a7640000',
data: '0x',
chainId: 1
};
const params = [transaction.nonce, transaction.gasPrice, transaction.gasLimit, transaction.to, transaction.value, transaction.data];
// Raw transaction as Buffer
const rawTransaction = encode(params);
const hash = keccak256(rawTransaction);
// Signature of the hash of the raw transaction
const { v, r, s } = ecsign(hash, privateKey, transaction.chainId);
// RLP encoded signed transaction as Buffer
const signedTransaction = encode([...params, v, r, s]);
console.log(signedTransaction.toString('hex'));
Note the extra chainId
property of transaction
, this is required to get the correct value for v
. If we assume our signature is the one mentioned in the example transaction, the signed transaction looks like this:
f86f8201e7852e90edd00083030d4094bd064928cdd4fd67fb99917c880e6560978d7ca1880de0b6b3a76400008025a07e833413ead52b8c538001b12ab5a85bac88db0b34b61251bb0fc81573ca093fa049634f1e439e3760265888434a2f9782928362412030db1429458ddc9dcee995
This is the full transaction that can be broadcast to the network. You can verify this through MyCrypto's broadcast tool for example.