Error 'Invalid Signature' While getSenderPublicKey with ethereumjs-tx on Testnet

Objective and Problem

When you try to retrive public key from a transaction (or an address with a valid outgoing tx), you may use the following script (only works for web3 1.0.x, for 0.2x.x, please leave a msg if needed).

/**
 * for web3 1.0.0-beta.37
 * */

const Web3 = require('web3');
const utils = require('ethereumjs-util');
const Transaction = require('ethereumjs-tx').Transaction

http_provider = "https://rinkeby.infura.io"
var web3 = new Web3(new Web3.providers.HttpProvider(http_provider))

txHash = "0x...." // tx hash on rinkeby

web3.eth.getTransaction(txHash).then(function(tx){
    const pubkey = new Transaction({
        nonce: tx.nonce,
        gasPrice: utils.bufferToHex(new utils.BN(tx.gasPrice)),
        gasLimit: tx.gas,
        to: tx.to,
        value: utils.bufferToHex(new utils.BN(tx.value)),
        data: tx.input,
        r: tx.r,
        s: tx.s,
        v: tx.v,
    },{
        chain: 4,//'rinkeby',
    }).getSenderPublicKey();
    console.log(pubkey.toString('hex'))
}).catch(console.log)

By running this, you may success on mainnet but may encounter 'Error: Invalid Signature' on chainId != 1:

$ node crypto.js 
Error: Invalid Signature
    at Transaction.getSenderPublicKey (/.../node_modules/ethereumjs-tx/dist/transaction.js:195:19)
    at /.../crypto.js:46:8
    at process._tickCallback (internal/process/next_tick.js:68:7)

Root Cause

This mainly due to EIP-155, which proposed a simple algorithm calculating the 'v' value in the signature (r,s,v) to avoid the tx crossing-chain replay attack.

EIP-155 went into effect after the 'spuriousDragon' hardfork. Thus, to comply with it (the tx you're using probably happened after that fork), you need to enable this fork when create 'new Transaction'.

Code Proof

https://github.com/ethereumjs/ethereumjs-tx/blob/master/src/transaction.ts#L243

  verifySignature(): boolean {
    ...
    try {
      ...
      const useChainIdWhileRecoveringPubKey =
        v >= this.getChainId() * 2 + 35 && this._common.gteHardfork('spuriousDragon')
      this._senderPubKey = ecrecover(
        msgHash,
        v,
        this.r,
        this.s,
        useChainIdWhileRecoveringPubKey ? this.getChainId() : undefined,
      )
    } catch (e) {
      return false
    }

    return !!this._senderPubKey
  }

'this._common.gteHardfork('spuriousDragon') == false' which causes 'useChainIdWhileRecoveringPubKey == false'

Solution

Add hardfork: 'spuriousDragon' when 'new Transaction':

web3.eth.getTransaction(txHash).then(function(tx){
    const pubkey = new Transaction({
        nonce: tx.nonce,
        gasPrice: utils.bufferToHex(new utils.BN(tx.gasPrice)),
        gasLimit: tx.gas,
        to: tx.to,
        value: utils.bufferToHex(new utils.BN(tx.value)),
        data: tx.input,
        r: tx.r,
        s: tx.s,
        v: tx.v,
    },{
        chain: 4,//'rinkeby',
        hardfork: 'spuriousDragon'
    }).getSenderPublicKey();
    console.log(pubkey.toString('hex'))
}).catch(console.log)
Error 'Invalid Signature' While getSenderPublicKey with ethereumjs-tx on Testnet
Share this