8

I need a way to convert a public key string (e.g. "EOS8KgkQikWK84J2jJ1Nvd3ttfJNRicYZsdbunbe9biR99dHGb24a") into a

struct public_key {
    char[34];
}

inside of a contract. The char[34] seems to be the compressed (base58 encoded?) version of the public key. Outside of a contract, that wouldn't be a problem, but the choice of libs is very limited inside.

I can't use the ABI because the public key string is provided as a string in a transfer memo, not as action parameter.

UPDATE:

I made some more research and I found the micro-ecc library which is fairly small and can be compiled by eosiocpp. Micro-ecc has a function to compress a public key, which is exactly what we need!

The only thing missing right now is a way to base58 decode the public key string into bytes so we can use it as input for micro-ecc.

UPDATE: Todd Fleming pointed me to the abieos-numeric library which does exactly what I need. Sadly it's c++17 and needs conversion to c++14 to run from inside a contract. I've started a port here.

  • If the public key is coming in as a string, can't string.c_str() be used to convert it? – John Haager Jun 27 '18 at 17:31
  • I wish it were that simple. But a public key string has 53 characters, while the native, compressed, data structure has 33 characters (plus null terminator). – Angelo Laub Jun 27 '18 at 19:07
  • Are you then looking for a way to convert the compressed representation into its uncompressed representation? – John Haager Jun 27 '18 at 21:36
  • No. The other way around. I'm looking to convert a public key string, which is 53 characters long, into a struct public_key from "types.h". – Angelo Laub Jun 28 '18 at 5:51
  • Can you please give a link to a library that does this outside of the contract? I found this explanation as a reference, bitcointalk.org/index.php?topic=205490.0 should be similar in EOS with minor modifications for header bytes. split the public key (less the leading 04) in half and look at the last digit of the second half. If that digit is even, prepend the first half with 02; if odd, prepend the first half with 03. Push the result through the same hash and base58-encoding process to get the compressed address – Ami Heines Jun 28 '18 at 6:40
7
+50

The following code is in the link below.

https://bitcoin.stackexchange.com/questions/76480/encode-decode-base-58-c

The important thing is that the first three characters (EOS) in the public key are prefix, so you can truncate it.

EOS8KgkQikWK84J2jJ1Nvd3ttfJNRicYZsdbunbe9biR99dHGb24a => 8KgkQikWK84J2jJ1Nvd3ttfJNRicYZsdbunbe9biR99dHGb24a

#include <iostream>
using namespace std;

const char * const ALPHABET =
    "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
const char ALPHABET_MAP[128] = {
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1,  0,  1,  2,  3,  4,  5,  6,  7,  8, -1, -1, -1, -1, -1, -1,
    -1,  9, 10, 11, 12, 13, 14, 15, 16, -1, 17, 18, 19, 20, 21, -1,
    22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1, -1,
    -1, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, -1, 44, 45, 46,
    47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, -1, -1, -1, -1
};

int EncodeBase58(const string input, int len, unsigned char result[]) {
    unsigned char const* bytes = (unsigned const char*)(input.c_str()); 
    unsigned char digits[len * 137 / 100];
    int digitslen = 1;
    for (int i = 0; i < len; i++) {
        unsigned int carry = (unsigned int) bytes[i];
        for (int j = 0; j < digitslen; j++) {
            carry += (unsigned int) (digits[j]) << 8;
            digits[j] = (unsigned char) (carry % 58);
            carry /= 58;
        }
        while (carry > 0) {
            digits[digitslen++] = (unsigned char) (carry % 58);
            carry /= 58;
        }
    }
    int resultlen = 0;
    // leading zero bytes
    for (; resultlen < len && bytes[resultlen] == 0;)
        result[resultlen++] = '1';
    // reverse
    for (int i = 0; i < digitslen; i++)
        result[resultlen + i] = ALPHABET[digits[digitslen - 1 - i]];
    result[digitslen + resultlen] = 0;
    return digitslen + resultlen;
}

int main(void) {
    unsigned char version_pubkey_checksum[1 + 20 + 4]; // = something
    unsigned char encoded[(1 + 20 + 4) * 137 / 100];
    EncodeBase58("6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV", 1 + 20 + 4, encoded);
    printf("%s", encoded);
}
  • Wow, I'm impressed. The code doesn't even need a bignum library. Is there a decode version of it as well? I think I first need to decode the EOS public key, which is a base58 encoded string, into bytes. We're getting closer... – Angelo Laub Jun 28 '18 at 16:53
  • The decode is also in the link. There is no description of how to set the argument. This part needs to be examined further. – smarteasy Jun 28 '18 at 18:37
  • Ah! I didn't see the decode function at first. This seems to be working! – Angelo Laub Jun 28 '18 at 19:12
  • Funny thing. The first code you sent (from the stackexchange link) works if I compile it with g++. But it stops working (produces different result) when I compile it with eosiocpp and run it inside a contract. – Angelo Laub Jun 29 '18 at 7:51
3
void test::test(std::string data, signature sig, public_key pub ) {
      checksum256 hash;
      sha256(&data[0], data.size(), &hash);
      assert_recover_key(&hash, (const char*)&sig, sizeof(sig), (const char*)&pub, sizeof(pub));
}

use the public_key type directly, and you can got your real public_key.

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.