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

Inconsistent Public Keys #173

Open
artrepreneur opened this issue Jun 10, 2022 · 20 comments
Open

Inconsistent Public Keys #173

artrepreneur opened this issue Jun 10, 2022 · 20 comments

Comments

@artrepreneur
Copy link

When signing using gg18, if the same message is signed multiple times as in: it will result in a unique (r,s) pair each time.

Example, Round 1:
./gg18_sign_client http://127.0.0.1:8000 keys.store "68656c6c6f20776f726c64"
./gg18_sign_client http://127.0.0.1:8000 keys2.store "68656c6c6f20776f726c64"

R: Secp256k1Scalar { purpose: "from_bigint", fe: Zeroizing(Some(SK(SecretKey(9870cfd7bc3fa04b93976960579d316bb9d015d2abcbc1e204747d023f95f0c3)))) }
s: Secp256k1Scalar { purpose: "add", fe: Zeroizing(Some(SK(SecretKey(46f5dfd387beb601cac41fce7079a6564740986a0ac186624de44e4d3860cf0f)))) }
recid: 0

Round 2:
./gg18_sign_client http://127.0.0.1:8000 keys.store "68656c6c6f20776f726c64"
./gg18_sign_client http://127.0.0.1:8000 keys2.store "68656c6c6f20776f726c64"

R: Secp256k1Scalar { purpose: "from_bigint", fe: Zeroizing(Some(SK(SecretKey(82bfefea43e679127cf6932bcb06ecec281f1a7e5586727cc853346cacaaae0f)))) }
s: Secp256k1Scalar { purpose: "from_bigint", fe: Zeroizing(Some(SK(SecretKey(33ab25e2d97be2af94e1046a5b29a11c3a46d7b7093b1cc03333a20c81006dee)))) }
recid: 0

This results in different public keys as well. Also public keys do not match the public keys in keys.store. What explains this discrepancy?

@artrepreneur
Copy link
Author

The issue seems to be that the message is not keccak256 or sha3 hashed before signing. Instead, it's hex'd and byte padded with leading zeros.

@KunPengRen
Copy link

Are you fix this problem?

@KunPengRen
Copy link

I try the public key recovery in python where I got the code from https://replit.com/@nakov/ECDSA-public-key-recovery-in-Python#main.py
Message: hello
sha3 of message: 0x3338be694f50c5f338814986cdf0686453a888b84f424d792af4b9202398f392
round1:
party 1 Output Signature:
R: SecretKey(3c2bf68ef8bb5860c664f6d4cc7067b3f678853f2cdbc9dc793b45cdb5fc7529)
s: SecretKey(49d104fe4d2d8bb2953e4e1434f4d675d4049c95592a669571f36dfb1682ece5)

Public key recovery:
Recovered public key from signature: (0xe71cc44b7a738250fe80c40f1b64a95686a927d4400ca7d404a010c0e7366b40, 0xef39e52814806ebe53dafa767315f13c176e7c281350cc0800d64435e6671fc)
Recovered public key from signature: (0xadef7738efde07bd3e2400513222c73fde564fb55ff4f637a7a05ba6e626921, 0xd27f1343640de071b479135d8004c6856bf626460dd4c7e6a2209a298d645854)

round2:
party 1 Output Signature:
R: SecretKey(e0eb7000356ab7d2b2b5ef3e0c8894e701f88f064e81b0c0b177abe5d205b716)
s: SecretKey(57d1c4a16811439c98b00ec69daea6ee370ba3524a407d7095552edb09dd3dc7)

Public key recovery:
Recovered public key from signature: (0xe71cc44b7a738250fe80c40f1b64a95686a927d4400ca7d404a010c0e7366b40, 0xef39e52814806ebe53dafa767315f13c176e7c281350cc0800d64435e6671fc)
Recovered public key from signature: (0xcd6e8976589705f0f4cc22f553e64340dbc6b44ee581b41d463dd6c054a5a805, 0x9f08c42e2bf1768fb5925c788798c6b9e238d87e4b4d8793d46dc60bb481e26d)

The two results have a same public key (0xe71cc44b7a738250fe80c40f1b64a95686a927d4400ca7d404a010c0e7366b40, 0xef39e52814806ebe53dafa767315f13c176e7c281350cc0800d64435e6671fc),
So I think it's consistent.

@alexshchur
Copy link

@artrepreneur , reading your logs I can make an assumption that in each of the rounds you're running the command from within the same/single directory and just use different keystores.

If you confirm this is the case, and if you still experience this problem -- let me know, and I would explain how to solve it :)

I ran into this issue by myself, though it got manifested in a different way in my case.
This makes me think the documentation describing the demo might be improved. I'll be happy to create a PR if anyone's interested.

@alexreyes
Copy link

@alexshchur I'm running into the same issue now, would you be able to explain how to resolve this?

@alexshchur
Copy link

alexshchur commented Jul 8, 2022

So, I think the intent and the way the codebase is designed assumes that, given the initial params config defining signers amount as 3, you would deploy this codebases onto 3 separate machines (or at least 3 separate folders).

After doing that, as explained in the doc, you probably want to run gg18 demo by launching sm_manager service and then consequently run ./gg18_keygen_client http://127.0.0.1:8001 keys.store in each of your "signer" folders.

You will end up with 3 similar folders, each having /keys.store, but with their own/distinct content (private keys + some protocol-specific data)

After that you would be able to launch commands like ./gg18_sign_client http://127.0.0.1:8001 keys.store {your_message_encoded_as_hex} by collecting the quorum of signers. The produced output should be consistent.

Let me know if that works

@alexreyes
Copy link

@alexshchur Thanks for response! I just tried that and ended up with the same problems as @artrepreneur.

In client1 folder I ran:
./gg18_sign_client http://127.0.0.1:8000 keys.store "68656c6c6f20776f726c64"

In client2 folder I ran:
./gg18_sign_client http://127.0.0.1:8000 keys.store "68656c6c6f20776f726c64"

The output for the above was:

R: Secp256k1Scalar { purpose: "from_bigint", fe: Zeroizing(Some(SK(SecretKey(c04f7900cbc14b656f84bb02d95a432b59744a7fa9ac5bef09e19b6a7ec4fd95)))) }
s: Secp256k1Scalar { purpose: "from_bigint", fe: Zeroizing(Some(SK(SecretKey(3b9801c860eaa4f2f0fc93a324ccc3c254a1aa6c007d1b27bdb994f445e1690f)))) } 

Afterwards, I ran the above two commands in the same folders again, and the output I received was:

R: Secp256k1Scalar { purpose: "from_bigint", fe: Zeroizing(Some(SK(SecretKey(6eca8f618e625263f7192944d08fea1c003855a50b433a17d75139be8a7f1cdc)))) }
s: Secp256k1Scalar { purpose: "add", fe: Zeroizing(Some(SK(SecretKey(7723a5de15ff670bcf827b7454a41937bbbb9ae47736b1bac319bba7d7fa4428)))) } 

Which is a different signature (with a different public key) than the first signature, despite signing the same message.

My goal is to sign a tx on ethereum that sends eth, but right now I'm stuck on even generating consistent eth addresses/signatures.

Were you able to get consistent public keys when signing messages? If so, how?

@alexshchur
Copy link

@alexreyes hm hm it sounds like you're doing everything right. It's weird that you don't receive consistent output.

Check out this my playground codebase. I've recorded a video demonstrating the whole process of creating signatures and then consistently recovering the same public key: https://github.com/alexshchur/tss-experiments#video

@KunPengRen
Copy link

@alexreyes @alexshchur From my testing, A message can be generated with two different signatures twice, but the public key will be recovered the same.

@alexreyes
Copy link

@alexshchur thanks for the video + sending your repo! That was super helpful and we were able to get it working. I really appreciate it!

@KunPengRen Yup, that seems to be the case. Thanks for confirming!

@neocho
Copy link

neocho commented Jul 9, 2022

hey @alexshchur, thanks for the video. I tried out your script. I did the signing process and ended up with the signature.

But when I tried to use ethers.utils.verifyMessage() it outputted a different address than ethers.utils.recoverAddress().

I also tried to verify the message on Etherscan (https://etherscan.io/verifiedSignatures) from ethers.utils.recoverAddress() but it fails.

I generated the signature for the message on Etherscan using ethers.utils.joinSignature().

Do you have any thoughts on this? Maybe I'm messing up somewhere? Thanks!

Screen Shot 2022-07-09 at 2 39 45 AM

@alexshchur
Copy link

@neocho verifyMessage is a bit different. If you look under the hood of what it does with the supplied message arg, you'll find out that it prepends that ethereum prefix:

export const messagePrefix = "\x19Ethereum Signed Message:\n";

Essentially you end up dealing with a totally different message to recover against

@neocho
Copy link

neocho commented Jul 9, 2022

Ahh I see! Will check that out 💯

@neocho
Copy link

neocho commented Jul 11, 2022

Hey @alexshchur, have you been able to verify a signature with the recovered address with a tool like this https://etherscan.io/verifiedSignatures? I've been trying to get it to work with recoveredAddress but no luck still. Thanks :)

@alexshchur
Copy link

Hey @alexshchur, have you been able to verify a signature with the recovered address with a tool like this https://etherscan.io/verifiedSignatures? I've been trying to get it to work with recoveredAddress but no luck still. Thanks :)

Hey @neocho , I suppose it's already solved for you? Saw the conversations in TG channel ;)

Will repost Steph's response from there for others who might be curious

const { ethers } = require("ethers");

var R = "50a0feece3643fc91bd9eadd760da2c4f1da20cfae78cda5f296c393d67ce78e"
var s = "0c566fa493e38bd18d77904165bd8cb8a7f30687a286d2ff3f20b8e3f5304ddf"
var r = "1c" // 0: 1b, 1: 1c

var raw_msg = "68656c6c6f20776f726c64"

var msg = ethers.utils.hashMessage(raw_msg)

console.log("hashed msg", msg)

var signature = "0x" + R + s + r;

var pk = "0x02e65f1f00fd4207a3ea57f450dc1447de38280620c08f78872a39c042ec77a41a"

let reAddr = ethers.utils.computeAddress(pk)
console.log(reAddr)

let rPk = ethers.utils.recoverPublicKey(
  msg,
  signature
);
console.log(pk, ethers.utils.computePublicKey(rPk, true))

let signingAddress = ethers.utils.verifyMessage(raw_msg, signature);     
console.log(signingAddress);

@neocho
Copy link

neocho commented Jul 13, 2022

Hey! @alexshchur yea unfortunately I kept getting different addresses after signing. 🤣🤣 I think I'll do a fresh clone and go from there.

@artrepreneur
Copy link
Author

artrepreneur commented Jul 14, 2022

Problem is solved for me too. Sigs are consistent. If using Web3.js use something like:

let myMsgHashAndPrefix = web3.eth.accounts.hashMessage(<your_message_here>);
let netSigningMsg = myMsgHashAndPrefix.substr(2);
let keyFileName = 'keys.store';
./gg18_sign_client http://127.0.0.1:8000 '+ keyFileName +' ' + netSigningMsg;

Then combine R, s and recid to create a concatenated signature as say "sig", and recover the address with ethers.js like:

sigAddress = ethers.utils.recoverAddress(myMsgHashAndPrefix, sig);

@lazarus521
Copy link

@alexreyes @neocho @alexshchur can you explain what you did to get this working? I'm running into this same issue (same message, different signatures, different public keys) for ETH. If it makes a difference, I'm signing an EIP-1559 transaction type.

For greater context, I using a 2-2 scheme that's based on the gotham-city repo (which uses multi-party-ecdsa). Each node is on its own process and storing its own data. I was able to get Bitcoin signing working no problem.

The message I'm signing is a curv::BigInt that represents the keccak256 hash of the RLP-encoded transaction. Steps to produce this:

  • RLP encode EIP-1559 transaction
  • keccak256 hash of the encoding
  • convert hash to bytes
  • hex::encode it
  • convert to BigInt with BigInt::from_hex

Any help would be appreciated.

@luluzhou1
Copy link

luluzhou1 commented Dec 27, 2023

The problem of the example given in the readme is that: "68656c6c6f20776f726c64" is not a hash value with size = 32 byte. However, the signing protocol assumed that the signer input a hash value with size = 32. If the input size is < 32 byte, it will be padded. That's why the recovery of public key does not work properly when using ethers.util.
The proper way is to sign with the command:
./gg18_sign_client http://127.0.0.1:8000/ keys.store1 "50b2c43fd39106bafbba0da34fc430e1f91e3c96ea2acee2bc34119f92b37750" and
./gg18_sign_client http://127.0.0.1:8000/ keys.store2 "50b2c43fd39106bafbba0da34fc430e1f91e3c96ea2acee2bc34119f92b37750"
, where "50b2c43fd39106bafbba0da34fc430e1f91e3c96ea2acee2bc34119f92b37750" is the hash value of "hello".

@venuswhispers
Copy link

will check soon

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants