Best way to verify klaytn account ownership

If an end user, wishes to link their pre-existing klaytn account to a non klaytn service account that we host, what is the best way to confirm that they own the account?

I’ve considered having the end user sign some text with their private key, but I’m unable to identify the best API for looking up their public key to verify the signature.

Looking up my own public key is very easy using the wallet api. But looking up someone else’s public key, to verify their signature, and therefore ownership of their account, does not seem to be possible via the API.

Am I going about this the wrong way? Is there a better strategy for verifying account ownership?

Hi, thanks for filing a question on Klaytn Forum.

Your approach to check the ownership of the account seems good to me.

You can check out someone else’s public key via klay_getAccountKey.

Note that there can be some cases that the account’s public key type is AccountKeyLegacy. In this case, the public key will not be shown. You can find the address like the following formula:

Address = keccak256(pubkey)[12:]

Hope this helps.

1개의 좋아요

I’m having trouble getting this to work. Can anyone comment on what is wrong with this request? ( note creds removed )

curl https://node-api.klaytnapi.com/v1/klaytn --header 'x-chain-id: 1001'  -H "Content-Type: application/json" -u ***:*** --data '{"jsonrpc":"2.0","method":"klay_getAccountKey","params":["0xE93a82CF4840666977b84f5a3AD63b351DC6eF35", "latest"],"id":1}'

I get the following response:

{"jsonrpc":"2.0","id":2,"result":null}

It seems the account may not be initialized (i.e., the account has not been used.)

Please try to use an account which has been sent/receive KLAY or executed any contract.

for the uninitialized accounts, you can follow the second rule as I told you before:

Address = keccak256(pubkey)[12:]

1개의 좋아요

Awesome! Thank you. I will initialize the address and test.

However, regarding this:

It looks like the address is derived from the keccak256 hash of the public key. Have I understood that correctly?

In my case, I’d like to do the reverse. I’d like to get a public key, from a known address.

The idea is that the user requests to link an address to our service.

We give the user keccak256 hashed value to sign.

The user signs this value using

https://wallet-api.klaytnapi.com/v2/key/krn:***:wallet:***:key-pool:default:***/sign

Then we lookup their public key, using the address they specified in their initial request, and verify the signature.

Okay, using a wallet that has sent Klay, I do get a response as follows:

curl https://node-api.klaytnapi.com/v1/klaytn --header 'x-chain-id: 1001'  -H "Content-Type: application/json" -u ***:*** --data '{"jsonrpc":"2.0","method":"klay_getAccountKey","params":["0x329d18c99f679d721100f1ca85e1fe0c986f3268", "latest"],"id":2}'

Returns

{"jsonrpc":"2.0","id":2,"result":{"keyType":1,"key":{}}}

As you can see the keytype is 1. Which appears to be legacy as you have mentioned above.

Actually, reversing keccak256 should be impossible. If it is easy, then the hash function should be replaced. :slight_smile:

So, I think this can be done like below:

  1. You received the signature from the sign API as you said.
  2. You can get the public key from the signature.
  3. You can derive the address from the signature.

At the moment, we do not provide function 2 in our SDK. Sorry for that. The function will be released soon. (We are implementing this.)

But you can get the address (combination of 2 and 3) via caver.utils.recover.

Hope this helps.

1개의 좋아요

This is an incredible help. That should give me everything I need.

Thanks @colin.kim!

Well, I’m back.

Here’s my signature


0xbf55f159f05b73e48a83806a7cf836c5e3efe8699532116995e22f64b6db315f783de8d75d35494b5b357c91fe750def0463be1b86777cb74a6068a9bf619d3d01

I’ve been looking through carver-java source code to try and find out how to get the public key out of this so I can verify it.

The problem is, I can’t tell how it’s encoded.

Is it raw encoding? rlp? der? I can’t seem to get it to parse no matter what I do.

How can I extract the R, S and V components needed to recover the public key?

You’ll notice that my key doesn’t end in “0x1b” as do the keys on carver-js.

I believe that is supposed to be the “recovery id” in a simple concatenation encoding of the signature.

But mine is below “27” and so is invalid. Therefore, the signatures obtained from the “key api” “sign” call must be in a different format.

Again I’m using this API

curl  --request POST 'https://wallet-api.klaytnapi.com/v2/key/krn:1001:wallet:2b90x***:key-pool:default:0x***/sign'

Rather than this one

curl https://node-api.klaytnapi.com/v1/klaytn --header 'x-chain-id: 1001'  -H "Content-Type: application/json" -u ***:*** --data '{"jsonrpc":"2.0","method":"klay_sign","params":["0x329d18c99f679d721100f1ca85e1fe0c986f3268", "0xdeadbeaf"],"id":1}'

Because the latter is documented as not supported by KAS and gives me the following error:

Unsupported method - klay_sign"

Looking closer at the signature. It can’t be a DER. There aren’t enough bytes to cover the R & S values of the signature plus all of the header bytes.

But it’s doesn’t appear to have a “recId” for recovering the public key. When you sign through the RPC api ( not available on KAS ) you get a proper “recovery id” byte at the end. That recovery id value has to be in a certain range and in this case, the value at that position is “1” which is not a valid recovery id.

It’s possible the last byte is just a version. That would mean the format includes the R & S segments of the signature, plus a version . The issue with that format is, I don’t believe I can recover the public key.

However, all of that is probably moot anyway, because I believe the wallet API will only apply to KAS hosted wallets. Which means that we’ll need a different strategy for enabling customers to sign messages that does not involve the KAS APIs at all.

Still, I’d love to know what the format of this signature is, if anyone is able to comment. Just for a sense of resolution :slight_smile:

On further reading. It seems that versions 1 & 0 are also technically valid versions. Though 27 and 28 are generally expected.

After a lot of digging and testing. I believe this is the appropriate way to deserialize the signature.


        final Sign.SignatureData signatureData = new Sign.SignatureData(
                Arrays.copyOfRange(signatureBytes, 64, 65),
                Arrays.copyOfRange(signatureBytes, 0, 32),
                Arrays.copyOfRange(signatureBytes, 32, 64)
                );

Similar implementation here:

. eth-lib/account.js at master · MaiaVictor/eth-lib · GitHub

Which I believe is used here

. caver-js/utils.js at dev · klaytn/caver-js · GitHub

However, using this approach, along with carver-java’s “Utils.recover()” method, I’m unable to correctly extract the address used to sign an arbitrary string with the Kaikas wallet on the kaikas tutorials site.

Address: 0x9462e1f20dd41d3def4a206e00a1767a6787411a
Message: test
Signature: 0x70b6343882d94dfc34f201b368d2747b0dab3f0db02804408680835ec3d513567c33a8bb58e19eafc371e2dc10a99fc9b1579577f1c21d0e616fce99d5971e391c

When I run a

Utils.recover("test", signatureData /* as deserialized above*/) 

I get the following address:

0x849963e0388746505e3839d3048a83fc007ef189

Which naturally is not a match and does not validate.

However, if I use the private key for this address

0x329d18c99f679d721100f1ca85e1fe0c986f3268

To sign a message using

org.web3j.crypto.Sign.signMessage()

And then run a Utils.recover on that, I get the correct address as a result.

What do I need to do differently to verify Kaikas signatures? It would seem that they are using a different algorithm.

Okay, here are all the details of my latest attempt to verify a signature produced by kaikas wallet.

( sans private key, although, this is a test only wallet )

Signature was produced here: https://tutorial.kaikas.io/ ( See attached screenshot )

Verification Details

        final String address = "0x50E97c2F3fEaD7ea27a0620a48272C16472636a2";
        final String message = "test";
        final String messageBytesHexEncoded = "0x74657374";
        final String preamble = "\\x19Klaytn Signed Message:\\n4";
        final String preambleBytesHexEncoded = "0x5c7831394b6c6179746e205369676e6564204d6573736167653a5c6e34";
        final String messageBytesPrependedWithKlaytnPreambleBytes = "0x5c7831394b6c6179746e205369676e6564204d6573736167653a5c6e3474657374";
        final String messagePrependedWithKlaytnPreambleHashedKeccak256 = "0x0f38a34b35e1508707d3ecc1d4fe01ce9f33f659897432f4a36048bdd207bcf6";
        final String signature = "0xd7e1d551839cd40679ea5d6188683c2e821695991e9de7c91a18f6c8bf100f3b4855ae255698a880c3d71c8e32af6da6877acdbce43980d43bef4b7c98728a4f1c";
        final String signatureV= "0x1c";
        final String signatureR= "0xd7e1d551839cd40679ea5d6188683c2e821695991e9de7c91a18f6c8bf100f3b";
        final String signatureS= "0x4855ae255698a880c3d71c8e32af6da6877acdbce43980d43bef4b7c98728a4f";

        com.klaytn.caver.utils.Utils.recover(mesasgePrependedWithKlaytnPreambleHashedKeccak256, new SignatureData(signatureV, signatureR, signatureS), true);
        org.opentest4j.AssertionFailedError:
        Expected :0x50E97c2F3fEaD7ea27a0620a48272C16472636a2
        Actual   :0x72088fd4a2594a0d3dcb17b2b259a42227ce7507

Can anyone comment on what I’m doing wrong trying to verify this signature?

Hi, @rlopez.
Sorry for the late reply.
There was a problem with caver-java’s Utils.recover() method.(Modified to hashMessage() logic and add a Utils.decodeSignature() by sirano11 · Pull Request #248 · klaytn/caver-java · GitHub)
We published caver-java v1.6.2-rc.4 version that solved this problem.(Of course, caver-java v1.6.2 version will also be published today.)
If you use this version and refer to the following code, I think the address through the signature will be verified.

String signData = "0xf6fa6ccc206eaff47d907304c25bd3d1af498a4d0a8dd014c0b4879d66d70aff7f4309981bb92a781248a612a78207587d0c4d8d69cf015afe02342c0ca1b8011b";
String expectedAddress = "0xb6a1f97502431e6f8d701f9e192c3cc43c07351a";

String recovered = caver.utils.recover("sign test", caver.utils.decodeSignature(signData));
System.out.println(recovered.equals(expectedAddress));
3개의 좋아요

SO AWESOME!!!

You guys!! I love that there is a decodeSignature() too! :slight_smile: THANK YOU!

Address recovered from Kaikas signature: 0x50e97c2f3fead7ea27a0620a48272c16472636a2

SUCCESS!!!

This unlocks so many doors. I’m super excited.

1개의 좋아요