3

Let's say I have the following transaction information:

{
  "jsonrpc":"2.0"
  "id":0
  "result":{
    "blockHash":"0x43dd926aa138a58d3f4740dae387bcff3c7bc525db2d0a449f323f8b8f92a229"
    "blockNumber":"0xa4f285"
    "from":"0xea8a7ef30f894bce23b42314613458d13f9d43ea"
    "gas":"0x30d40"
    "gasPrice":"0x2e90edd000"
    "hash":"0x72ee43a3784cc6749f64fad1ecf0bbd51a54dd6892ae0573f211566809e0d511"
    "input":"0x"
    "nonce":"0x1e7"
    "to":"0xbd064928cdd4fd67fb99917c880e6560978d7ca1"
    "transactionIndex":"0x0"
    "value":"0xde0b6b3a7640000"
    "v":"0x25"
    "r":"0x7e833413ead52b8c538001b12ab5a85bac88db0b34b61251bb0fc81573ca093f"
    "s":"0x49634f1e439e3760265888434a2f9782928362412030db1429458ddc9dcee995"
  }
}

Along with anything else you may need about the network broadly (such as chain_id).

Is it possible to construct an analogous raw transaction from this information without knowing the private key of the sender?

Feel free to use your favorite Web3 SDK.

1 Answer 1

10

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.

3
  • Nice! I think your code creates an unsigned transaction which cannot be directly sent to a network to replay the transaction. Can you include the signing step with v, r, s also please.
    – Peteris
    Commented Sep 8, 2020 at 20:01
  • Correct, the raw transaction cannot be broadcast, since you need a valid signature to send a transaction. I updated the answer to include the signing.
    – Morten
    Commented Sep 8, 2020 at 20:17
  • @Morten From my understanding of the EIP-155, the params array should rather be [transaction.nonce, transaction.gasPrice, transaction.gasLimit, transaction.to, transaction.value, transaction.data, transaction.chainId, 0, 0].
    – clement
    Commented Aug 8, 2021 at 16:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.