diff --git a/include/libeosio/WIF.hpp b/include/libeosio/WIF.hpp index dffee92..07f2a94 100644 --- a/include/libeosio/WIF.hpp +++ b/include/libeosio/WIF.hpp @@ -34,13 +34,14 @@ namespace libeosio { */ const std::string WIF_PUB_LEG = "EOS"; const std::string WIF_PUB_K1 = "PUB_K1_"; +const std::string WIF_PVT_LEG = ""; const std::string WIF_PVT_K1 = "PVT_K1_"; /** * Encode an EC private key to WIF String. */ -std::string wif_priv_encode(const ec_privkey_t& priv); +std::string wif_priv_encode(const ec_privkey_t& priv, const std::string& prefix = WIF_PVT_LEG); /** * Decode an WIF String to EC private key diff --git a/src/WIF.cpp b/src/WIF.cpp index 9e904c6..0ace329 100644 --- a/src/WIF.cpp +++ b/src/WIF.cpp @@ -30,8 +30,6 @@ namespace libeosio { -#define PRIV_KEY_PREFIX 0x80 /* 0x80 for "Bitcoin mainnet". Always used by EOS. */ - // Just to make it "harder" the calculated checksum for a signature (k1) and pub/priv keys in k1/r1 format. // has a suffix that is not present in the WIF encoded string. // So this function is a quick hack to calculate it. @@ -46,51 +44,48 @@ checksum_t _checksum_suffix(const unsigned char *in, size_t size, const char *su return checksum_ripemd160(buf, size + 2); } -std::string wif_priv_encode(const ec_privkey_t& priv) { +std::string wif_priv_encode(const ec_privkey_t& priv, const std::string& prefix) { checksum_t check; - // 1 byte extra for prefix. - unsigned char buf[1 + EC_PRIVKEY_SIZE + CHECKSUM_SIZE] = { PRIV_KEY_PREFIX }; + // 1 byte extra for legacy prefix prefix. + unsigned char buf[1 + EC_PRIVKEY_SIZE + CHECKSUM_SIZE] = { 0 }; + size_t len; - memcpy(buf + 1, priv.data(), priv.size()); + if (prefix == WIF_PVT_K1) { + len = internal::priv_encoder_k1(priv, buf); + } else if (prefix == WIF_PVT_LEG) { + len = internal::priv_encoder_legacy(priv, buf); + } else { + return ""; + } - // Checksum - check = checksum_sha256d(buf, 1 + EC_PRIVKEY_SIZE); - memcpy(buf + 1 + EC_PRIVKEY_SIZE, check.data(), check.size()); - - return base58_encode(buf, buf + sizeof(buf)); + return prefix + base58_encode(buf, buf + len); } bool wif_priv_decode(ec_privkey_t& priv, const std::string& data) { + uint8_t offset; std::vector buf; + internal::priv_decoder_t decoder = internal::priv_decoder_legacy; - if (!base58_decode(data, buf)) { + // Check prefix + if (data.substr(0, WIF_PVT_K1.size()) == WIF_PVT_K1) { + offset = WIF_PVT_K1.size(); + decoder = internal::priv_decoder_k1; + } else { + // Legacy + offset = 0; + } + + if (!base58_decode(data.c_str() + offset, buf)) { return false; } - if (buf.size() != 1 + EC_PRIVKEY_SIZE + CHECKSUM_SIZE) { - return false; - } - - // First byte is the prefix - if (buf[0] != PRIV_KEY_PREFIX) { - return false; - } - - // Calculate and validate checksum - if (!checksum_validate(buf.data(), buf.size())) { - return false; - } - - // Copy data to output - memcpy(priv.data(), buf.data() + 1, priv.size()); - return true; + return decoder(buf, priv); } std::string wif_pub_encode(const ec_pubkey_t& pub, const std::string& prefix) { - checksum_t check; unsigned char buf[EC_PUBKEY_SIZE + CHECKSUM_SIZE]; internal::pub_encoder_t encoder; @@ -136,7 +131,7 @@ bool wif_pub_decode(ec_pubkey_t& pub, const std::string& data) { void wif_print_key(const struct ec_keypair *key, const std::string& prefix) { std::cout << "Public: " << wif_pub_encode(key->pub, prefix) << std::endl; - std::cout << "Private: " << wif_priv_encode(key->secret) << std::endl; + std::cout << "Private: " << wif_priv_encode(key->secret, prefix) << std::endl; } bool wif_sig_decode(ec_signature_t& sig, const std::string& data) { diff --git a/src/wif/codec.hpp b/src/wif/codec.hpp index 2c38d44..3e235d3 100644 --- a/src/wif/codec.hpp +++ b/src/wif/codec.hpp @@ -47,6 +47,24 @@ bool pub_decoder_legacy(const std::vector& buf, ec_pubkey_t& key) bool pub_decoder_k1(const std::vector& buf, ec_pubkey_t& key); +/** + * Private-key encoders + */ +typedef size_t (*priv_encoder_t)(const ec_privkey_t&, unsigned char *); + +size_t priv_encoder_legacy(const ec_privkey_t& priv, unsigned char *buf); + +size_t priv_encoder_k1(const ec_privkey_t& priv, unsigned char *buf); + +/** + * Private-key decoders + */ +typedef bool (*priv_decoder_t)(const std::vector&, ec_privkey_t&); + +bool priv_decoder_legacy(const std::vector& buf, ec_privkey_t& priv); + +bool priv_decoder_k1(const std::vector& buf, ec_privkey_t& priv); + }} // namespace libeosio::internal #endif /* LIBEOSIO_CODEC_H */ diff --git a/src/wif/k1.cpp b/src/wif/k1.cpp index ea29f4e..3792dd1 100644 --- a/src/wif/k1.cpp +++ b/src/wif/k1.cpp @@ -61,4 +61,28 @@ bool pub_decoder_k1(const std::vector& buf, ec_pubkey_t& key) { return true; } +size_t priv_encoder_k1(const ec_privkey_t& priv, unsigned char *buf) { + checksum_t check = _checksum_suffix(priv.data(), EC_PRIVKEY_SIZE, "K1"); + + memcpy(buf, priv.data(), priv.size()); + memcpy(buf + EC_PRIVKEY_SIZE, check.data(), check.size()); + + return EC_PRIVKEY_SIZE + CHECKSUM_SIZE; +} + +bool priv_decoder_k1(const std::vector& buf, ec_privkey_t& priv) { + + if (buf.size() != EC_PRIVKEY_SIZE + CHECKSUM_SIZE) { + return false; + } + + checksum_t check = _checksum_suffix(buf.data(), EC_PRIVKEY_SIZE, "K1"); + if (memcmp(buf.data() + EC_PRIVKEY_SIZE, check.data(), CHECKSUM_SIZE)) { + return false; + } + + memcpy(priv.data(), buf.data(), priv.size()); + return true; +} + }} // namespace libeosio::internal \ No newline at end of file diff --git a/src/wif/legacy.cpp b/src/wif/legacy.cpp index 47a8251..96578e6 100644 --- a/src/wif/legacy.cpp +++ b/src/wif/legacy.cpp @@ -27,6 +27,8 @@ namespace libeosio { namespace internal { +#define PRIV_KEY_PREFIX 0x80 /* 0x80 for "Bitcoin mainnet". Always used by EOS. */ + void pub_encoder_legacy(const ec_pubkey_t& key, unsigned char *buf) { checksum_t check = checksum_ripemd160(key.data(), EC_PUBKEY_SIZE); @@ -45,4 +47,32 @@ bool pub_decoder_legacy(const std::vector& buf, ec_pubkey_t& key) return true; } +size_t priv_encoder_legacy(const ec_privkey_t& priv, unsigned char *buf) { + checksum_t check; + + buf[0] = PRIV_KEY_PREFIX; + memcpy(buf + 1, priv.data(), EC_PRIVKEY_SIZE); + check = checksum_sha256d(buf, 1 + EC_PRIVKEY_SIZE); + memcpy(buf + 1 + EC_PRIVKEY_SIZE, check.data(), check.size()); + + return 1 + EC_PRIVKEY_SIZE + CHECKSUM_SIZE; +} + +bool priv_decoder_legacy(const std::vector& buf, ec_privkey_t& priv) { + if (buf[0] != PRIV_KEY_PREFIX) { + return false; + } + + if (buf.size() != 1 + EC_PRIVKEY_SIZE + CHECKSUM_SIZE) { + return false; + } + + if (!checksum_validate(buf.data(), buf.size())) { + return false; + } + + memcpy(priv.data(), buf.data() + 1, priv.size()); + return true; +} + }} // namespace libeosio::internal \ No newline at end of file diff --git a/tests/WIF/priv_decode.cpp b/tests/WIF/priv_decode.cpp index d3d8c6a..5105e66 100644 --- a/tests/WIF/priv_decode.cpp +++ b/tests/WIF/priv_decode.cpp @@ -4,7 +4,7 @@ #include #include -TEST_CASE("WIF::wif_priv_decode") { +TEST_CASE("WIF::wif_priv_decode [legacy]") { struct testcase { std::string name; std::string key; @@ -29,4 +29,31 @@ TEST_CASE("WIF::wif_priv_decode") { } } +} + +TEST_CASE("WIF::wif_priv_decode [K1]") { + struct testcase { + std::string name; + std::string key; + libeosio::ec_privkey_t expected; + }; + + + std::vector tests { + { "one", "PVT_K1_6Mcb23muAxyXaSMhmB6B1mqkvLdWhtuFZmnZsxDczHRvQdp32", { 0x0C, 0x28, 0xFC, 0xA3, 0x86, 0xC7, 0xA2, 0x27, 0x60, 0x0B, 0x2F, 0xE5, 0x0B, 0x7C, 0xAE, 0x11, 0xEC, 0x86, 0xD3, 0xBF, 0x1F, 0xBE, 0x47, 0x1B, 0xE8, 0x98, 0x27, 0xE1, 0x9D, 0x72, 0xAA, 0x1D } }, + { "two", "PVT_K1_2DRBT8jmXT8k9ywNSSbufvhk1hLFhPzWJBpsE2jo12CDoFhcc1", { 0x9F, 0xE3, 0xE3, 0x2B, 0x3C, 0x4B, 0x6B, 0x91, 0x6E, 0x20, 0x6C, 0xB0, 0x91, 0xDF, 0x1F, 0x5E, 0x34, 0x32, 0x88, 0x0B, 0x41, 0x33, 0x86, 0xBD, 0xF2, 0x92, 0xFF, 0x23, 0x06, 0x43, 0xF2, 0x8C } }, + { "three", "PVT_K1_gJCsP4CwMv4gTkDXiZT8QFhs3NrSB7Sv22ANGrc8Svun9uC9C", { 0x59, 0x3A, 0x51, 0xB5, 0x5D, 0x56, 0xAA, 0xF0, 0x5B, 0xD9, 0xD1, 0x0E, 0x6B, 0x88, 0x6D, 0xF9, 0xC4, 0x37, 0x09, 0xB2, 0x4C, 0xEC, 0xBB, 0x63, 0x68, 0x92, 0xC2, 0x94, 0x31, 0x48, 0x71, 0x8C } } + }; + + for(auto it = tests.begin(); it != tests.end(); it++) { + + SUBCASE(it->name.c_str()) { + + libeosio::ec_privkey_t result; + + CHECK( libeosio::wif_priv_decode(result, it->key) ); + CHECK( result == it->expected ); + } + } + } \ No newline at end of file diff --git a/tests/WIF/priv_encode.cpp b/tests/WIF/priv_encode.cpp index 57a7173..bb08bd1 100644 --- a/tests/WIF/priv_encode.cpp +++ b/tests/WIF/priv_encode.cpp @@ -4,24 +4,48 @@ #include #include -TEST_CASE("WIF::wif_priv_encode") { +TEST_CASE("WIF::wif_priv_encode [Legacy]") { struct testcase { std::string name; + const std::string prefix; libeosio::ec_privkey_t key; std::string expected; }; std::vector tests { - { "one", { 0x0C, 0x28, 0xFC, 0xA3, 0x86, 0xC7, 0xA2, 0x27, 0x60, 0x0B, 0x2F, 0xE5, 0x0B, 0x7C, 0xAE, 0x11, 0xEC, 0x86, 0xD3, 0xBF, 0x1F, 0xBE, 0x47, 0x1B, 0xE8, 0x98, 0x27, 0xE1, 0x9D,0x72,0xAA,0x1D}, "5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ" }, - { "two", { 0x9F, 0xE3, 0xE3, 0x2B, 0x3C, 0x4B, 0x6B, 0x91, 0x6E, 0x20, 0x6C, 0xB0, 0x91, 0xDF, 0x1F, 0x5E, 0x34, 0x32, 0x88, 0x0B, 0x41, 0x33, 0x86, 0xBD, 0xF2, 0x92, 0xFF, 0x23, 0x06, 0x43, 0xF2, 0x8C}, "5K2hm8apqz281ANDQdtVzifpxcXFTqG5E7Fc6Q5V2ssqPRQ3urJ" }, - { "three", { 0x59, 0x3A, 0x51, 0xB5, 0x5D, 0x56, 0xAA, 0xF0, 0x5B, 0xD9, 0xD1, 0x0E, 0x6B, 0x88, 0x6D, 0xF9, 0xC4, 0x37, 0x09, 0xB2, 0x4C, 0xEC, 0xBB, 0x63, 0x68, 0x92, 0xC2, 0x94, 0x31, 0x48, 0x71, 0x8C}, "5JVanYq9HPvuKgr2FjATYB9NvTsJ4a3CAj5oPYKbr1Ja5MRLsZX" } + { "one", libeosio::WIF_PVT_LEG, { 0x0C, 0x28, 0xFC, 0xA3, 0x86, 0xC7, 0xA2, 0x27, 0x60, 0x0B, 0x2F, 0xE5, 0x0B, 0x7C, 0xAE, 0x11, 0xEC, 0x86, 0xD3, 0xBF, 0x1F, 0xBE, 0x47, 0x1B, 0xE8, 0x98, 0x27, 0xE1, 0x9D,0x72,0xAA,0x1D}, "5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ" }, + { "two", libeosio::WIF_PVT_LEG, { 0x9F, 0xE3, 0xE3, 0x2B, 0x3C, 0x4B, 0x6B, 0x91, 0x6E, 0x20, 0x6C, 0xB0, 0x91, 0xDF, 0x1F, 0x5E, 0x34, 0x32, 0x88, 0x0B, 0x41, 0x33, 0x86, 0xBD, 0xF2, 0x92, 0xFF, 0x23, 0x06, 0x43, 0xF2, 0x8C}, "5K2hm8apqz281ANDQdtVzifpxcXFTqG5E7Fc6Q5V2ssqPRQ3urJ" }, + { "three", libeosio::WIF_PVT_LEG, { 0x59, 0x3A, 0x51, 0xB5, 0x5D, 0x56, 0xAA, 0xF0, 0x5B, 0xD9, 0xD1, 0x0E, 0x6B, 0x88, 0x6D, 0xF9, 0xC4, 0x37, 0x09, 0xB2, 0x4C, 0xEC, 0xBB, 0x63, 0x68, 0x92, 0xC2, 0x94, 0x31, 0x48, 0x71, 0x8C}, "5JVanYq9HPvuKgr2FjATYB9NvTsJ4a3CAj5oPYKbr1Ja5MRLsZX" } }; for(auto it = tests.begin(); it != tests.end(); it++) { SUBCASE(it->name.c_str()) { - CHECK( libeosio::wif_priv_encode(it->key) == it->expected ); + CHECK( libeosio::wif_priv_encode(it->key, it->prefix) == it->expected ); + } + } +} + +TEST_CASE("WIF::wif_priv_encode [K1]") { + + struct testcase { + std::string name; + const std::string prefix; + libeosio::ec_privkey_t key; + std::string expected; + }; + + std::vector tests { + { "one", libeosio::WIF_PVT_K1, { 0x0C, 0x28, 0xFC, 0xA3, 0x86, 0xC7, 0xA2, 0x27, 0x60, 0x0B, 0x2F, 0xE5, 0x0B, 0x7C, 0xAE, 0x11, 0xEC, 0x86, 0xD3, 0xBF, 0x1F, 0xBE, 0x47, 0x1B, 0xE8, 0x98, 0x27, 0xE1, 0x9D,0x72,0xAA,0x1D}, "PVT_K1_6Mcb23muAxyXaSMhmB6B1mqkvLdWhtuFZmnZsxDczHRvQdp32" }, + { "two", libeosio::WIF_PVT_K1, { 0x9F, 0xE3, 0xE3, 0x2B, 0x3C, 0x4B, 0x6B, 0x91, 0x6E, 0x20, 0x6C, 0xB0, 0x91, 0xDF, 0x1F, 0x5E, 0x34, 0x32, 0x88, 0x0B, 0x41, 0x33, 0x86, 0xBD, 0xF2, 0x92, 0xFF, 0x23, 0x06, 0x43, 0xF2, 0x8C}, "PVT_K1_2DRBT8jmXT8k9ywNSSbufvhk1hLFhPzWJBpsE2jo12CDoFhcc1" }, + { "three", libeosio::WIF_PVT_K1, { 0x59, 0x3A, 0x51, 0xB5, 0x5D, 0x56, 0xAA, 0xF0, 0x5B, 0xD9, 0xD1, 0x0E, 0x6B, 0x88, 0x6D, 0xF9, 0xC4, 0x37, 0x09, 0xB2, 0x4C, 0xEC, 0xBB, 0x63, 0x68, 0x92, 0xC2, 0x94, 0x31, 0x48, 0x71, 0x8C}, "PVT_K1_gJCsP4CwMv4gTkDXiZT8QFhs3NrSB7Sv22ANGrc8Svun9uC9C" } + }; + + for(auto it = tests.begin(); it != tests.end(); it++) { + + SUBCASE(it->name.c_str()) { + CHECK( libeosio::wif_priv_encode(it->key, it->prefix) == it->expected ); } } }