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?
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.