5

I'm trying to find out how to derive a public key (not the address) of an Ethereum account by using ethereumjs-util's ecrecover method.

Basically I get a transaction by hash using Web3, convert r, s and v like in this answer, and pass them to ecrecover. It does yield a public key, but that key does not correspond to the address of the sender of the transaction.

What am I doing wrong?

Code below:

const ethJsUtil = require('ethereumjs-util');

class EthPubKeyReg {

    constructor(web3, chainId) {
        this.chainId = chainId;
        this.web3 = web3;
    }

    async extractPubKey(txHash, chainId) {
        chainId = (chainId || this.chainId);
        let tx = await this.web3.eth.getTransaction(txHash);

        let v = parseInt(tx.v);

        let msgHash = ethJsUtil.toBuffer(tx.hash);
        let r = ethJsUtil.toBuffer(tx.r);
        let s = ethJsUtil.toBuffer(tx.s);

        let pubKey = ethJsUtil.ecrecover(msgHash, v, r, s);


        console.log("address", ethJsUtil.bufferToHex(ethJsUtil.pubToAddress(pubKey)));
        return pubKey;
    }

    async extractPubKeyString(txHash, chainId) {
        return ethJsUtil.bufferToHex(await this.extractPubKey(txHash, chainId));
    }
}

===== Update =====

I've updated my code the way @libertylocked suggested in his answer below:

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

const HDWalletProvider = require('truffle-hdwallet-provider');
let provider = new HDWalletProvider(
    'some words to instantiate this truffe hdwallet provider',
    "https://mainnet.infura.io/[ACCCESS TOKEN]"
);

let web3 = new Web3(provider);

/* 
*  Extracts the public key of the sender of a 
*  signed and mined transaction as Buffer
*/
async function extractPubKey(txHash, web3) {

    const tx = await web3.eth.getTransaction(txHash) // insert txhash here
    console.log("tx.from", tx.from);

    const txDetails = new Transaction({
        nonce: tx.nonce,
        gasPrice: `0x${tx.gasPrice.toString(16)}`,
        gasLimit: tx.gas,
        to: tx.to,
        value: `0x${tx.value.toString(16)}`,
        data: tx.input,
        chainId: web3.version.network,
        r: tx.r,
        s: tx.s,
        v: tx.v,
    });
    const pubkey = txDetails.getSenderPublicKey()
    console.log("txDetails.getSenderAddress", txDetails.getSenderAddress().toString('hex'));
    console.log("Pubkey to address", ethJsUtil.bufferToHex(ethJsUtil.pubToAddress(pubkey)));
    return pubkey;
}

/* 
 *  Extracts the public key of the sender of a 
 *  signed and mined transaction as hex string  
 */
async function extractPubKeyAsString(txHash, web3) {
    return ethJsUtil.bufferToHex(await extractPubKey(txHash, web3));
}

// link to tx on Etherscan: https://etherscan.io/tx/0xbee6f2a54e1c4921dec2d6834334acd3177ce42229e1d1c4d2490fb60839634e
extractPubKeyAsString('0xbee6f2a54e1c4921dec2d6834334acd3177ce42229e1d1c4d2490fb60839634e', web3)
    .then(pubkey => {
        console.log("pubkeystring", pubkey);
    })
    .finally(() => process.exit());

However, this still yields an incorrect sender address:

tx.from 0xBa34A08bcAe1AB74b5e52b6eE06b108945A387f0
txDetails.getSenderAddress efc92668485027e2d41bd229d66111ad0aaa8a16
Pubkey to address 0xefc92668485027e2d41bd229d66111ad0aaa8a16
pubkeystring 0x6d5441c4e53bb8c4dd69b16e79d5ca4f51a84eebaded3b6dfc5b51dd63ee7b5c5707ca1e788653b07872dd8c13293be840c93aeae582f2c080ff8a3aef79a24f
Henk
  • 4,261
  • 1
  • 13
  • 27
  • Take a look at https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md. The transaction hash isn't exactly what's signed in current transactions. ethereumjs-tx can help. – user19510 Jul 10 '18 at 15:48

1 Answers1

5

The easiest solution is to use ethereumjs-tx module.

If you use web3@0.20:

const Transaction = require('ethereumjs-tx')
const tx = web3.eth.getTransaction(txHash) // insert txhash here
const pubkey = new Transaction({
  nonce: tx.nonce,
  gasPrice: `0x${tx.gasPrice.toString(16)}`,
  gasLimit: tx.gas,
  to: tx.to,
  value: `0x${tx.value.toString(16)}`,
  data: tx.input,
  chainId: 1, // mainnet network ID is 1. or use web3.version.network to find out
  r: tx.r,
  s: tx.s,
  v: tx.v,
}).getSenderPublicKey()

console.log(pubkey.toString('hex'))

If you use web3 1.0 beta (and you use ethereumjs-util), you need to convert some values to 0x prefixed hex strings

new Transaction({
    nonce: tx.nonce,
    gasPrice: ethJsUtil.bufferToHex(new ethJsUtil.BN(tx.gasPrice)),
    gasLimit: tx.gas,
    to: tx.to,
    value: ethJsUtil.bufferToHex(new ethJsUtil.BN(tx.value)),
    data: tx.input,
    chainId: 1,
    r: tx.r,
    s: tx.s,
    v: tx.v,
});

To verify if the pub key can be derived to the same sender address you can either use publicToAddress(pubkey) from ethereumjs-util, or use tx.getSenderAddress() from ethereumjs-tx

libertylocked
  • 1,368
  • 8
  • 16