Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recover public key from contract deployment transaction #700

Closed
protortyp opened this issue Jan 9, 2020 · 11 comments
Closed

Recover public key from contract deployment transaction #700

protortyp opened this issue Jan 9, 2020 · 11 comments
Labels
bug Verified to be an issue. fixed/complete This Bug is fixed or Enhancement is complete and published.

Comments

@protortyp
Copy link

I'm trying to get the public key of a user during the contract deployment but I'm not sure if it's possible with ethers.js. I'd like to solve it using only this package as I don't want to bloat my react builds with web3.js. I'm using ethers 4.0.42.

I took a deep dive into your source code, ECDSA and looked at a related issue and came up with the following code.

I think my biggest issue is to get the signing hash. So here's my attempt:

// before: contract = await factory.deploy(...);

// get the signing hash
const deployTx = contract.deployTransaction;
const txData = {
  gasPrice: deployTx.gasPrice,
  gasLimit: deployTx.gasLimit,
  value: deployTx.value,
  nonce: deployTx.nonce,
  data: deployTx.data,
  chainId: deployTx.chainId
};
const tx = await ethers.utils.resolveProperties(txData);
const rawTx = ethers.utils.serializeTransaction(tx); // returns RLP encoded tx
const msgHash = ethers.utils.keccak256(rawTx); // as specified by ECDSA

Note that msgHash is the signing hash.

// get flat signature for recovery
const expandedSig = {
  r: deployTx.r,
  s: deployTx.s,
  recoveryParam: 0, // not really sure what this does
  v: deployTx.v // i think this value is only needed because of EIP155 (CHAIN_ID * 2 + 35 or 36)
};
const signature = ethers.utils.joinSignature(expandedSig);


const msgBytes = ethers.utils.arrayify(msgHash); // create binary hash
const recoveredPubKey = ethers.utils.recoverPublicKey(
  msgBytes,
  signature
);
const recoveredAddress = ethers.utils.recoverAddress(msgBytes, signature);

Unfortunately both the recoveredPubKey and the recoveredAddress are false.

@protortyp
Copy link
Author

I just noticed that deployTx.raw and my rawTx are equal. So I could skip the first step until the hashing part.

@ricmoo
Copy link
Member

ricmoo commented Jan 9, 2020

That looks right to me, but I may be missing something. Can you provide an example transaction on Etherscan (Ropsten is fine) and I can double check where the mis-agreement is happening. :)

@ricmoo ricmoo added the discussion Questions, feedback and general information. label Jan 9, 2020
@protortyp
Copy link
Author

protortyp commented Jan 10, 2020

Cool, thanks for your help! Here's the transaction on ropsten: 0x35097c5644dfa2e7f1231212cf26e5040bcdb799ccfd3f7c57bb88569d749ab9.

I did a signing test with this address using utils.signMessage and the public key should be:

0x0471c746523d16e93d4738f882d9b0beebf66c68caa0f895db15686b57b878cfc7b3e09813ba94f1bbfaa91a06566d3d18bbf69d10bcc947325bbcd6fea97ed692

@ricmoo ricmoo added bug Verified to be an issue. and removed discussion Questions, feedback and general information. labels Jan 11, 2020
@ricmoo
Copy link
Member

ricmoo commented Jan 11, 2020

Ah, so I think I've found the problem.

In v4, the transaction returned doesn't have chainId like it is supposed to, but instead it uses networkId. I guess I missed a spot when updating the API. I'll fix it right now (I'll continue injecting networkId, which as long as there is no conflict will set chainId to the same value).

If you change your tx.chainId to tx.networkId, I think you'll get the right answer. :)

I'll make this change and push it out once the CI tests are complete too.

You also helped me find a bug in the automatic v calculation in v5, so I'll be fixing that soon too. :)

@ricmoo ricmoo added the on-deck This Enhancement or Bug is currently being worked on. label Jan 11, 2020
@ricmoo
Copy link
Member

ricmoo commented Jan 11, 2020

This should be fixed in 4.0.43 and in 5.0.0-beta.167. Can you try it out (in whichever version you use) and let me know if there are any problems?

Thanks! :)

@ricmoo ricmoo added fixed/complete This Bug is fixed or Enhancement is complete and published. and removed on-deck This Enhancement or Bug is currently being worked on. labels Jan 11, 2020
@protortyp
Copy link
Author

protortyp commented Jan 11, 2020

I tried 4.0.43 but I still can't correctly recover the public key. I wrote this gist to perform the test for this contract deployment.

@ricmoo
Copy link
Member

ricmoo commented Jan 12, 2020

Ah, I see the issue. So, if you get rid of the recoveryParam in your example, it all works fine and I get both true. :)

I'm not sure why it isn't detecting you are setting the value incorrectly. If the recoveryParam and the v disagree I would have thought it throws an error (in v5 it will), but it seems it takes the recoveryParam and ignores the v if it is present in v4... It might break things to fix that though, so the easiest thing is to either let it compute recoveryParam from the v itself, or correctly set the recoveryParam (you can use 1 - (v % 2); but it's a lot easier just to omit it).

@protortyp
Copy link
Author

Perfect! Thanks for helping out :)

@ricmoo
Copy link
Member

ricmoo commented Jan 12, 2020

Sorry I didn’t think of that sooner, but glad it worked. :)

@lastmjs
Copy link

lastmjs commented Jun 12, 2020

I'm having a hard time getting the public key from a transaction response on ropsten. Would anyone mind giving me an example of how to do this with a simple transaction response, just a normal transaction from an external account?

Here's the function from my application:

async function deriveEthereumPublicKeyFromEthereumAddress(ethereumAddress: string): Promise<string> {

    console.log('ethereumAddress', ethereumAddress);

    const etherscanProvider = new ethers.providers.EtherscanProvider('ropsten');

    const transactionResponses: ReadonlyArray<ethers.providers.TransactionResponse> = await etherscanProvider.getHistory(ethereumAddress);

    const aSignedTransaction: Readonly<ethers.providers.TransactionResponse> | undefined = transactionResponses.find((transactionResponse: Readonly<ethers.providers.TransactionResponse>) => {
        return transactionResponse.from === ethereumAddress;
    });

    if (aSignedTransaction === undefined) {
        throw new Error(`The Ethereum address has no signed transactions`);
    }

    const provider = ethers.getDefaultProvider('ropsten');

    const transactionResponse = await provider.getTransaction(aSignedTransaction.hash);

    console.log('transactionResponse', transactionResponse);

    const signature: string = ethers.utils.joinSignature({
        r: transactionResponse.r,
        s: transactionResponse.s,
        v: transactionResponse.v
    });

    console.log('signature', signature);

    const txData = {
        gasPrice: transactionResponse.gasPrice,
        gasLimit: transactionResponse.gasLimit,
        value: transactionResponse.value,
        nonce: transactionResponse.nonce,
        data: transactionResponse.data,
        chainId: transactionResponse.chainId
    };

    const transaction = await ethers.utils.resolveProperties(txData);
    const rawTransaction = ethers.utils.serializeTransaction(transaction);
    const hashedTransaction = ethers.utils.keccak256(rawTransaction);
    const hashedTransactionBytes = ethers.utils.arrayify(hashedTransaction);
    
    console.log('hashedTransactionBytes', hashedTransactionBytes);

    const publicKey: string = ethers.utils.recoverPublicKey(hashedTransactionBytes, signature)

    console.log('publicKey', publicKey);

    const originalAddress: string = ethers.utils.recoverAddress(hashedTransactionBytes, signature);

    console.log('originalAddress', originalAddress);

    return publicKey;
}

It doesn't come out correctly, and I'm not sure why

@lastmjs
Copy link

lastmjs commented Jun 13, 2020

So it looks like I'm missing the to property in my txData...I found that out after looking here: https://gist.github.com/chrsengel/2b29809b8f7281b8f10bbe041c1b5e00

I think of got it now, so thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Verified to be an issue. fixed/complete This Bug is fixed or Enhancement is complete and published.
Projects
None yet
Development

No branches or pull requests

3 participants