diff --git a/include/libeosio/WIF.hpp b/include/libeosio/WIF.hpp index 5259e4f..9eca7fc 100644 --- a/include/libeosio/WIF.hpp +++ b/include/libeosio/WIF.hpp @@ -54,6 +54,20 @@ bool wif_pub_decode(ec_pubkey_t& pub, const std::string& data, size_t prefix_len */ void wif_print_key(const struct ec_keypair *key, const std::string& prefix = "EOS"); +/** + * Signatures + */ + +/** + * Encode an EC signature to WIF String. + */ +std::string wif_sig_encode(const ec_signature_t& sig); + +/** + * Decode an WIF String to EC signature + */ +bool wif_sig_decode(ec_signature_t& sig, const std::string& data); + } // namespace libeosio #endif /* LIBEOSIO_WIF_H */ diff --git a/src/WIF.cpp b/src/WIF.cpp index 6a54dbb..7a6f5c8 100644 --- a/src/WIF.cpp +++ b/src/WIF.cpp @@ -112,4 +112,60 @@ void wif_print_key(const struct ec_keypair *key, const std::string& prefix) { std::cout << "Private: " << wif_priv_encode(key->secret) << std::endl; } +// Just to make it "harder" the calculated checksum for a signature +// has a "K1" suffix that is not present in the WIF encoded string. +// So this function is a quick hack to calculate it. +// +// Should implement and use Init/Update/Finalize hash functions to do it inplace. +checksum_t _calculate_sig_checksum(const unsigned char *in) { + unsigned char buf[EC_SIGNATURE_SIZE + 2]; + + memcpy(buf, in, EC_SIGNATURE_SIZE); + memcpy(buf + EC_SIGNATURE_SIZE, "K1", 2); + + return checksum_ripemd160(buf, EC_SIGNATURE_SIZE + 2); +} + +bool wif_sig_decode(ec_signature_t& sig, const std::string& data) { + + checksum_t checksum; + std::vector buf; + + if (data.substr(0, 7) != "SIG_K1_") { + // Invalid prefix + return false; + } + + if (!base58_decode(data.c_str() + 7, buf)) { + return false; + } + + if (buf.size() != EC_SIGNATURE_SIZE + CHECKSUM_SIZE) { + return false; + } + + // Calculate checksum + checksum = _calculate_sig_checksum(buf.data()); + + // And validate + if (memcmp(buf.data() + EC_SIGNATURE_SIZE, checksum.data(), CHECKSUM_SIZE)) { + return false; + } + + // Copy data to output + memcpy(sig.data(), buf.data(), sig.size()); + return true; +} + +std::string wif_sig_encode(const ec_signature_t& sig) { + + unsigned char buf[EC_SIGNATURE_SIZE + CHECKSUM_SIZE]; + checksum_t check = _calculate_sig_checksum(sig.data()); + + memcpy(buf, sig.data(), sig.size()); + memcpy(buf + EC_SIGNATURE_SIZE, check.data(), check.size()); + + return "SIG_K1_" + base58_encode(buf, buf + sizeof(buf)); +} + } // namespace libeosio diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 813d0d5..73b1523 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -16,7 +16,8 @@ set(TEST_SRC WIF/priv_decode.cpp WIF/pub_encode.cpp WIF/pub_decode.cpp -) + WIF/sig_encode.cpp + WIF/sig_decode.cpp) add_executable(doctest ${TEST_SRC}) target_link_libraries(doctest PRIVATE ${LIB_NAME}) diff --git a/tests/WIF/sig_decode.cpp b/tests/WIF/sig_decode.cpp new file mode 100644 index 0000000..e0c77e6 --- /dev/null +++ b/tests/WIF/sig_decode.cpp @@ -0,0 +1,100 @@ +#include +#include +#include +#include + +TEST_CASE("WIF::wif_sig_decode") { + + struct testcase { + const char *name; + std::string input; + libeosio::ec_signature_t expected; + bool expectedRet; + }; + + std::vector tests = { + { + "valid #1", + "SIG_K1_KYq4LKCQ1Pdk38TY4FqwxiHRQd53b2kffB7G2Lt5WiV8VzZAvwCdbRVC5AjZvEkmXSEwyFkAFACHj1hYos8hB7Ass7RY2f", + { + 0x20,0x1f,0x32,0xfb,0x5f,0x24,0xd2,0x57, + 0x5c,0xcc,0x51,0xf3,0xf1,0x60,0x47,0xf7, + 0x5c,0x5e,0x8e,0xb0,0xb1,0xc2,0x6d,0x76, + 0x07,0xc1,0x9e,0x24,0xd7,0xbb,0xc1,0x69, + 0x9a,0x04,0xba,0xa7,0x32,0xc7,0xef,0x83, + 0x1d,0xa9,0x40,0xde,0x9c,0xc8,0xf1,0xd9, + 0x7b,0xe5,0x0e,0xaf,0x90,0xdf,0xce,0x98, + 0xc5,0x34,0x55,0x04,0x9b,0x20,0x72,0x9a, + 0x96 + }, + true + }, + { + "valid #2", + "SIG_K1_K2Liiq4wXeeWfndxGM23xms5AR5oK99RvKRR9NpW9eemKWKD1FmpmnwEbpZUSBzQC77KwYptvW6cwGjWR6D3qDddH3w69J", + { + 0x1f,0x36,0x28,0x1c,0xe3,0xda,0x53,0x40, + 0x09,0x28,0xa8,0xad,0x68,0xb3,0x3a,0xb7, + 0x90,0xf7,0x55,0xff,0x60,0xf0,0x51,0x9b, + 0xb6,0xd8,0x48,0xff,0x09,0xbb,0x5d,0x17, + 0xa2,0x1a,0xe0,0x55,0xe5,0x75,0xf4,0xb9, + 0x67,0x5a,0x42,0x2c,0xf3,0x8f,0x40,0x32, + 0x1d,0x76,0x23,0x54,0xae,0xdc,0xfb,0xb9, + 0xf3,0x16,0x88,0x3e,0x62,0xec,0x7f,0x0d, + 0x9f + }, + true + }, + { + "valid #3", + "SIG_K1_Jxm4D2csP298MurVbsntqZvU6RrMvLufVGQ1URtjdKQ6tdbkkifW5ptcbhW7oGP9nfJ6rzW7Jqhgu2RsDm9ToDyCmy9yk7", + { + 0x1f,0x1a,0xca,0x19,0x60,0x39,0x18,0x63,0x53,0x18,0xea,0x29,0x6e,0x4a,0x16,0x81,0x8f,0xf0,0xdc,0xe,0xad,0x38,0x1e,0x5f,0x0,0xde,0xb1,0xd5,0x1d,0xf5,0xe4,0xfb,0x8e,0x6d,0xdd,0x5d,0x79,0xe2,0x1,0x5d,0xac,0x75,0x72,0xcd,0xe7,0x84,0x47,0x8d,0x49,0x68,0xa0,0x7f,0x31,0x22,0xbb,0x6d,0x49,0x9b,0x43,0x92,0x83,0xec,0xbf,0xf8,0x4 + }, + true + }, + { + "invalid #1 - prefix", + "PREF_KaK2DE1we98JKmQRfEP1TXcySbHAfVqUBCqhZ2VtUo3v4QFyFNPg2YRmsiRHk5ePFxqxhX1Y8VS2NC5DYfhQyTFmouTLBi", + {}, + false, + }, + { + "invalid #2 - wrong checksum", + "SIG_K1_K2Liiq4wXeeWfndxGM23xms5AR5oK99RvKRR9NpW9eemKWKD1FmpmnwEbpZUSBzQC77KwYptvW6cwGjWR6D3qDddH3w6xx", + {}, + false, + }, + { + "invalid #3 - to long", + "SIG_K1_uns8EQ4m3QJgVPPNNNc9HFDgvmsXe7yrAB66nsCnNjhH3zhBvzoBLf22GtWDwmpUQWByr7VExHZ8aZnuTMEDkHEaS4dzP8oT2qupW7HsCzeRKfSXMZ48jKnMV4aqiK8VqH66KD9Mn", + {}, + false, + }, + { + "invalid #4 - to short", + "SIG_K1_8BTSpezp9ywsEwVPhMgMLgDMZV1xHuycq2DNbSrVcSZjriyK2FBrh5p518", + {}, + false, + }, + { + "invalid #5 - non-base58", + "SIG_K1_6sCX2LiY2EpKdJtK7DJMGUETSNdDBNP3MjoZlF1i2V6RFhjoqd1jZbIAobdeARzQxpqHBvJpOWhKxBdA28CsVYJQe0VdcL", + {}, + false, + } + }; + + for(auto it = tests.begin(); it != tests.end(); it++) { + SUBCASE(it->name) { + libeosio::ec_signature_t result; + + CHECK( libeosio::wif_sig_decode(result, it->input) == it->expectedRet ); + + if (it->expectedRet == true) { + CHECK( result == it->expected ); + } + } + } +} + diff --git a/tests/WIF/sig_encode.cpp b/tests/WIF/sig_encode.cpp new file mode 100644 index 0000000..000fd19 --- /dev/null +++ b/tests/WIF/sig_encode.cpp @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +TEST_CASE("WIF::wif_sig_encode") { + + struct testcase { + const char *name; + libeosio::ec_signature_t sig; + std::string expected; + + }; + + std::vector tests = { + { + "first", + { + 0x20,0x1f,0x32,0xfb,0x5f,0x24,0xd2,0x57, + 0x5c,0xcc,0x51,0xf3,0xf1,0x60,0x47,0xf7, + 0x5c,0x5e,0x8e,0xb0,0xb1,0xc2,0x6d,0x76, + 0x07,0xc1,0x9e,0x24,0xd7,0xbb,0xc1,0x69, + 0x9a,0x04,0xba,0xa7,0x32,0xc7,0xef,0x83, + 0x1d,0xa9,0x40,0xde,0x9c,0xc8,0xf1,0xd9, + 0x7b,0xe5,0x0e,0xaf,0x90,0xdf,0xce,0x98, + 0xc5,0x34,0x55,0x04,0x9b,0x20,0x72,0x9a, + 0x96 + }, + "SIG_K1_KYq4LKCQ1Pdk38TY4FqwxiHRQd53b2kffB7G2Lt5WiV8VzZAvwCdbRVC5AjZvEkmXSEwyFkAFACHj1hYos8hB7Ass7RY2f" + }, + { + "second", + { + 0x1f,0x36,0x28,0x1c,0xe3,0xda,0x53,0x40, + 0x09,0x28,0xa8,0xad,0x68,0xb3,0x3a,0xb7, + 0x90,0xf7,0x55,0xff,0x60,0xf0,0x51,0x9b, + 0xb6,0xd8,0x48,0xff,0x09,0xbb,0x5d,0x17, + 0xa2,0x1a,0xe0,0x55,0xe5,0x75,0xf4,0xb9, + 0x67,0x5a,0x42,0x2c,0xf3,0x8f,0x40,0x32, + 0x1d,0x76,0x23,0x54,0xae,0xdc,0xfb,0xb9, + 0xf3,0x16,0x88,0x3e,0x62,0xec,0x7f,0x0d, + 0x9f + }, + "SIG_K1_K2Liiq4wXeeWfndxGM23xms5AR5oK99RvKRR9NpW9eemKWKD1FmpmnwEbpZUSBzQC77KwYptvW6cwGjWR6D3qDddH3w69J" + }, + }; + + for(auto it = tests.begin(); it != tests.end(); it++) { + SUBCASE(it->name) { + CHECK( libeosio::wif_sig_encode(it->sig) == it->expected ); + } + } +} \ No newline at end of file