Ethers.js: Ethers signs my request with a different from address

Created on 23 Jul 2020  路  7Comments  路  Source: ethers-io/ethers.js

Hi, I am trying to sign a request with ethers using serialize.

This is my code

const { utils } = require('ethers');
const bn = require('bignumber.js');
const { serializeTransaction, parseEther } = utils;
const tx2 = require('ethereumjs-tx');
async function serialize1() {
        const wei = parseEther('0.02');
        console.log(wei);
        const gasPrice = '0x' + (2000000000).toString(16);
        console.log(gasPrice);
        const tx = {
                gasPrice: gasPrice,
                gasLimit: 21000,
                to: '0x3e245df5a4de41e65cecd1f98b96ca06c3d319f0',
                value: wei,
                nonce: 0,
                data: '',
                chainId: 3,
        };
        const serializedTransaction = await serializeTransaction(tx);
        console.log(serializedTransaction);
        const signature = 'c4b71f516c36468022d61b0e22016c6bc69bc2c99666fb7e63e3d04841c82ff53d5c687ebe43b290d03caa6766e8e957192faa6b360f4102b536c54c3318304f1b';
        console.log('---------------------------------------------');
        const signedSerialized = await serializeTransaction(tx, Buffer.from(signature, 'hex'));
        console.log(signedSerialized);
}

serialize1();

The signature (in hex) is for the address: 0x518171C334AD3adc1cF990A884D67963201B675F
but the function returns 0xf86a808477359400825208943e245df5a4de41e65cecd1f98b96ca06c3d319f087470de4df8200008029a0c4b71f516c36468022d61b0e22016c6bc69bc2c99666fb7e63e3d04841c82ff5a03d5c687ebe43b290d03caa6766e8e957192faa6b360f4102b536c54c3318304f
which when decoded is

{
  "nonce": 0,
  "gasPrice": 2000000000,
  "gasLimit": 21000,
  "to": "0x3e245df5a4de41e65cecd1f98b96ca06c3d319f0",
  "value": 20000000000000000,
  "data": "",
  "from": "0x0d67ffdd9b1d8858582d903bf0093e22b9da682e",
  "r": "c4b71f516c36468022d61b0e22016c6bc69bc2c99666fb7e63e3d04841c82ff5",
  "v": "29",
  "s": "3d5c687ebe43b290d03caa6766e8e957192faa6b360f4102b536c54c3318304f"
}

Here the from address shown is 0x0d67ffdd9b1d8858582d903bf0093e22b9da682e
What I want to know is that what I am feeding into the serializeTransaction function is of the correct format or not? Does it need to go through any thing else when getting the final signed transaction? Am I using the function correctly?

discussion

All 7 comments

How did you get this signature: const signature = 'c4b71f516c36468022d61b0e22016c6bc69bc2c99666fb7e63e3d04841c82ff53d5c687ebe43b290d03caa6766e8e957192faa6b360f4102b536c54c3318304f1b';

If the tx parameters that were used while generating the signature are different from the above tx parameters, then the ECC would recover a different wallet address.

Where did you get your value for signature from? There is a lot going on there, so here is a short example doing the same thing you are trying to do. Note that the first part of the code is just to generate the correct signature:

(async function() {
    const wallet = new ethers.Wallet("0xdf110a5c824f5a6a932b8c6dac0c787e0eb0e5cd2d3790e407487b020eacc48a");
    console.log("ADDRESS", wallet.address);
    // "0x796C7619429805a2951f3eB9dAb6E87Ba6d8622b"

    const tx = {
        gasPrice: 2000000000,
        gasLimit: 21000,
        to: "0x3e245df5a4de41e65cecd1f98b96ca06c3d319f0",
        value: ethers.utils.parseEther('0.02'),
        nonce: 0,
        data: null,
        chainId: 3
    };

    // I'm not sure where your signature value came from; so I create one as an example
    const signedTx = await wallet.signTransaction(tx);
    const signature = ethers.utils.joinSignature(ethers.utils.parseTransaction(signedTx));
    console.log("SIGNATURE", signature);
    // "0x2e1fe98e926f10eeca5dd6d690fa5b24c026c46287c3f1a729ca2c596f3a772e4bd2d5fde3b848d58b45f958276b63ff54846fc9329dc68e9822c7fb699e9adf1c"

    // Note: no need for your await
    const serializedTransaction = ethers.utils.serializeTransaction(tx);
    console.log("UNSIGNED", serializedTransaction);
    // "0xea808477359400825208943e245df5a4de41e65cecd1f98b96ca06c3d319f087470de4df82000080038080"

    // Note: no need for your await
    const signedSerialized = ethers.utils.serializeTransaction(tx, signature);
    console.log("SIGNED", signedSerialized);
    // "0xf86a808477359400825208943e245df5a4de41e65cecd1f98b96ca06c3d319f087470de4df820000802aa02e1fe98e926f10eeca5dd6d690fa5b24c026c46287c3f1a729ca2c596f3a772ea04bd2d5fde3b848d58b45f958276b63ff54846fc9329dc68e9822c7fb699e9adf"

    // The decoded transaction
    console.log("DECODED", ethers.utils.parseTransaction(signedSerialized));
    // { nonce: 0,
    //   gasPrice: BigNumber { _hex: '0x77359400', _isBigNumber: true },
    //   gasLimit: BigNumber { _hex: '0x5208', _isBigNumber: true },
    //   to: '0x3e245dF5a4dE41E65cecD1f98B96Ca06C3D319F0',
    //   value: BigNumber { _hex: '0x470de4df820000', _isBigNumber: true },
    //   data: '0x',
    //   chainId: 3,
    //   v: 42,
    //   r: '0x2e1fe98e926f10eeca5dd6d690fa5b24c026c46287c3f1a729ca2c596f3a772e',
    //   s: '0x4bd2d5fde3b848d58b45f958276b63ff54846fc9329dc68e9822c7fb699e9adf',
    //   from: '0x796C7619429805a2951f3eB9dAb6E87Ba6d8622b',
    //   hash:
    //    '0x844dbdc24fb59480bba8597e588302fc1430ed9f7944b4d9c0c2908dabb75c2f' }

    // Note: The addresses match :)
})();

Hope that helps. If there is something you need more clarity on, let me know. :)

@ricmoo thanks for your example!

We get the signature by passing the serialized unsigned transaction

        const serializedTransaction = await serializeTransaction(tx);
        console.log(serializedTransaction);

and signing it with the the ECDSA private key hidden in an HSM and returning it in the format of r || s || v.
Could it be that I am passing the wrong message to be signed to get the signature?

Yeah, I think you are generating the signature incorrectly, possibly by signing incorrect data, or by not normalizing the chainId correctly, etc.

You may be signing a message instead of a transaction hash? I would need to see how you generated this:

const signature = 'c4b71f516c36468022d61b0e22016c6bc69bc2c99666fb7e63e3d04841c82ff53d5c687ebe43b290d03caa6766e8e957192faa6b360f4102b536c54c3318304f1b';

Ok I can't show you exactly how it is signed (I know how annoying it is to hear this), but it is something like this,

A sign ecsda function called sign_ecdsa where we pass the private key and the serialized unsigned transaction. In this case it would be 0xea808477359400825208943e245df5a4de41e65cecd1f98b96ca06c3d319f087470de4df82000080038080. The private key is not retrievable :( otherwise I would have provided it.
There are no other operations done on it and nothing is changed. The message is signed as it is. I guess when you say you may be signing a message instead of a transaction hash, I feel that may be the reason. Is the serialized unsigned transaction not the data that needs to be signed with the private key?

It's a little hard because the ecdsa signing in the security module is very generic used across a variety of blockchains and just returns a signature. So it really depends on the data we pass to it. In this case I was passing, 0xea808477359400825208943e245df5a4de41e65cecd1f98b96ca06c3d319f087470de4df82000080038080. Maybe you could have an idea from these limited resources (sorry) that I provided as to where I may be going wrong?

Yeah, my hunch is that you are not signing what you think you are signing. The ECDSA algorithm can only sign a 256-bit number, so usually data is hashed.

In the case of a transaction, the keccak256 of the unsigned, serialized transaction is hashed, depending on whether EIP-155 is in use or not, will dictate whether it is 6 RLP encoded fields or 9, which includes an encoded chain ID and 2 null data sets.

In the case of a message, it is prefixed with a preamble strong, and then the utf-8 bytes are hashed and this is signed.

So, you need to look up sign_ecdsa and see how it processes bytes. If it expects a digest, it may be truncating for example, which is not what you want to happen. If too short it might be padding. Whatever that method does, I think it is not doing what you need. :)

Yo ricmoooooooooooo you're a legend. I was supposed to pass the keccack of the transaction before sending it to the sign_ecdsa.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

adamdossa picture adamdossa  路  3Comments

jochenonline picture jochenonline  路  3Comments

crazyrabbitLTC picture crazyrabbitLTC  路  3Comments

thegostep picture thegostep  路  3Comments

bbarton picture bbarton  路  3Comments