Open artrepreneur opened 2 years ago
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.
Are you fix this problem?
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.
@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.
@alexshchur I'm running into the same issue now, would you be able to explain how to resolve this?
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
@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?
@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
@alexreyes @alexshchur From my testing, A message can be generated with two different signatures twice, but the public key will be recovered the same.
@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!
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!
@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
Ahh I see! Will check that out 💯
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 @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);
Hey! @alexshchur yea unfortunately I kept getting different addresses after signing. 🤣🤣 I think I'll do a fresh clone and go from there.
Problem is solved for me too. Sigs are consistent. If using Web3.js use something like:
let myMsgHashAndPrefix = web3.eth.accounts.hashMessage(
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);
@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:
Any help would be appreciated.
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".
will check soon
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?