1

I am trying to sign a raw transaction in javascript without relying on dependencies like web3, ethereumjs, etc.

I am following the python tutorial found here, and porting to JS as I go: https://lsongnotes.wordpress.com/2018/01/14/signing-an-ethereum-transaction-the-hard-way/


I start by building out the RLP-encoded transaction:


var FROM_PRIV = "00d862c318d05de0a1c25242c21989e15e35e70c55996fbc4238cd2f2f6a8f62";
//var FROM_ADDR = "8d900bfa2353548a4631be870f99939575551b60";

var NOUNCE = "80";
var GAS_PRICE = "85" + "0BA43B7400";
var GAS_LIMIT = "82" + "5208";

var TO = "94" + "7917bc33eea648809c285607579c9919fb864f8f";

var VALUE = "87" + "03BAF82D03A000";

var CODE = "80";

var EIP_155 = "018080";

//

var SIGN_DATA_NO_LEN = NOUNCE + GAS_PRICE + GAS_LIMIT + TO + VALUE + CODE + EIP_155;

var SIGN_DATA = "EB" + SIGN_DATA_NO_LEN;

console.log("SIGN_DATA " + SIGN_DATA);

Which correctly logs:

SIGN_DATA EB80850BA43B7400825208947917bc33eea648809c285607579c9919fb864f8f8703BAF82D03A00080018080

Next I convert this to a Message Hash for later signing:

var TXN = new Buffer.from(SIGN_DATA, 'hex');

var SIGNING_HASH = keccak256(TXN);//Edit: This is using pre-nist variation of keccack256 from sha3: https://github.com/emn178/js-sha3

console.log("SIGNING_HASH " + SIGNING_HASH);

This also correctly logs:

SIGNING_HASH a4060d01d4add248db470b4121616cbe5b2015daf328809000ec9a1d0954d649

So far everything seems to match the outputs from the python tutorial,

Next, I sign the message hash (aka SIGNING_HASH) with the private key (aka FROM_PRIV)


const SIGNATURE = secp256k1.ecdsaSign(Buffer.from(SIGNING_HASH, 'hex'), Buffer.from(FROM_PRIV, 'hex'));

console.log("SIGNATURE " + toHexString(SIGNATURE.signature));

var R_HEX = toHexString(SIGNATURE.signature).slice(0,64);
var S_HEX = toHexString(SIGNATURE.signature).slice(64,64*2);

console.log("RECIEVED R_HEX " + R_HEX);
console.log("RECIEVED S_HEX " + S_HEX);

console.log("EXPECTED R_HEX " + "067940651530790861714b2e8fd8b080361d1ada048189000c07a66848afde46");
console.log("EXPECTED S_HEX " + "69b041db7c29dbcc6becf42017ca7ac086b12bd53ec8ee494596f790fb6a0a69");

The output R and S values do not match what the tutorial generates, here is log:

RECIEVED R_HEX 4c1c84da0afb3b38984fcb56953481bc2c8262f3473e28591384f63baa5e979e
RECIEVED S_HEX 7aa01ea9cb0fc0af56102045c69b46d00c18e4f19fbb612c4ae43e13302c475d

EXPECTED R_HEX 067940651530790861714b2e8fd8b080361d1ada048189000c07a66848afde46
EXPECTED S_HEX 69b041db7c29dbcc6becf42017ca7ac086b12bd53ec8ee494596f790fb6a0a69

I have tried three different secp256k1 npm dependencies for ecdsa signing, they all produce this same incorrect output. (I am currently using const secp256k1 = require('secp256k1');)

The expected signature from tutorial is 067940651530790861714b2e8fd8b080361d1ada048189000c07a66848afde4669b041db7c29dbcc6becf42017ca7ac086b12bd53ec8ee494596f790fb6a0a69 but I am getting signature 4c1c84da0afb3b38984fcb56953481bc2c8262f3473e28591384f63baa5e979e7aa01ea9cb0fc0af56102045c69b46d00c18e4f19fbb612c4ae43e13302c475d

I must be improperly formatting the SIGNING_HASH or Private key prior to signing, or is secp256k1-ecdsa not what I'm supposed to be using?


What's more interesting is, if I finish following the tutorial and build a signed TXN with these wrong R+S values, the txn works, I just broadcast one on main-net to confirm and the funds did move and received 12 confirmations already... does this imply that it doesn't necessarily matter that the R&S values from JS implementation of secp256k1 are different from what you get in python? The python tutorial also succeeded in broadcasting so it's unclear at this time... we both output different R,S values, but when push comes to shove we both succeeded in broadcasting? Very puzzling...

  • Does FROM_PRIV start with 0x? What about SIGN_DATA? In other words, make sure that whenever you do Buffer.from(str, 'hex'), the value of str consists of only hexadecimal digit characters, i.e., any of 0-9 and a-f and A-F (i.e., omit 0x if needed). – goodvibration May 14 at 21:17
  • @goodvibration No, they are being converted to byte arrays Buffer.from(FROM_PRIV, 'hex') so a 0x prefix would have to be striped anyways as far as I understand – Albert Renshaw May 14 at 21:22
  • So do you strip it? – goodvibration May 14 at 21:22
  • @goodvibration Sorry answered before your edit, there is no 0x prefix anywhere in the process at the moment. – Albert Renshaw May 14 at 21:23
  • Where is V in your code? – goodvibration May 14 at 21:29
2

if I finish following the tutorial and build a signed TXN with these wrong R+S values, the txn works

This seems to suggest the signing by secp256k1.ecdsaSign is correct, but just not deterministic, i.e. it changes every time you sign, even with the same key and data.

Notice the tutorial uses private_key.sign_deterministic, so, to produce the same result, one needs to also use a deterministic implementation, i.e. RFC 6979. If there is an npm module that supports the RFC, the results should match.

| improve this answer | |
  • What's interesting is the one I am using is deterministic (earlier I had one that changed each time) which makes me wonder, are many possible values of VRS valid? This implies to me that there are more than one deterministic secp256k1.ecdsaSigns? Also thanks so much for replying, I really appreciated your tutorial, it helped us a ton! – Albert Renshaw May 15 at 0:22
  • Follow up for future users, VRS can indeed be a number of values, non-deterministic is fine BUT the S value has to be less than the half-n value of the secp256k1 curve, if not you must re-generate. See ethereum.stackexchange.com/a/73371/17938 for more details – Albert Renshaw May 16 at 7:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.