Replace INT_MAX with INT32_MAX to avoid compile err
[novacoin.git] / src / test / base58_tests.cpp
index c7fa74e..7602fa9 100644 (file)
 #include <boost/test/unit_test.hpp>
+#include "json/json_spirit_reader_template.h"
+#include "json/json_spirit_writer_template.h"
+#include "json/json_spirit_utils.h"
 
-#include "../util.h"
+#include "base58.h"
+#include "util.h"
+
+using namespace json_spirit;
+extern Array read_json(const std::string& filename);
 
 BOOST_AUTO_TEST_SUITE(base58_tests)
 
-// TODO:
-// EncodeBase58Check
-// DecodeBase58Check
-// CBase58Data
-//    bool SetString(const char* psz)
-    // bool SetString(const std::string& str)
-    // std::string ToString() const
-    // int CompareTo(const CBase58Data& b58) const
-    // bool operator==(const CBase58Data& b58) const
-    // bool operator<=(const CBase58Data& b58) const
-    // bool operator>=(const CBase58Data& b58) const
-    // bool operator< (const CBase58Data& b58) const
-    // bool operator> (const CBase58Data& b58) const
-
-// CBitcoinAddress
-    // bool SetHash160(const uint160& hash160)
-    // bool SetPubKey(const std::vector<unsigned char>& vchPubKey)
-    // bool IsValid() const
-    // CBitcoinAddress()
-    // CBitcoinAddress(uint160 hash160In)
-    // CBitcoinAddress(const std::vector<unsigned char>& vchPubKey)
-    // CBitcoinAddress(const std::string& strAddress)
-    // CBitcoinAddress(const char* pszAddress)
-    // uint160 GetHash160() const
-
-#define U(x) (reinterpret_cast<const unsigned char*>(x))
-static struct {
-    const unsigned char *data;
-    int size;
-} vstrIn[] = {
-{U(""), 0},
-{U("\x61"), 1},
-{U("\x62\x62\x62"), 3},
-{U("\x63\x63\x63"), 3},
-{U("\x73\x69\x6d\x70\x6c\x79\x20\x61\x20\x6c\x6f\x6e\x67\x20\x73\x74\x72\x69\x6e\x67"), 20},
-{U("\x00\xeb\x15\x23\x1d\xfc\xeb\x60\x92\x58\x86\xb6\x7d\x06\x52\x99\x92\x59\x15\xae\xb1\x72\xc0\x66\x47"), 25},
-{U("\x51\x6b\x6f\xcd\x0f"), 5},
-{U("\xbf\x4f\x89\x00\x1e\x67\x02\x74\xdd"), 9},
-{U("\x57\x2e\x47\x94"), 4},
-{U("\xec\xac\x89\xca\xd9\x39\x23\xc0\x23\x21"), 10},
-{U("\x10\xc8\x51\x1e"), 4},
-{U("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), 10},
+// Goal: test low-level base58 encoding functionality
+BOOST_AUTO_TEST_CASE(base58_EncodeBase58)
+{
+    Array tests = read_json("base58_encode_decode.json");
+
+    BOOST_FOREACH(Value& tv, tests)
+    {
+        Array test = tv.get_array();
+        std::string strTest = write_string(tv, false);
+        if (test.size() < 2) // Allow for extra stuff (useful for comments)
+        {
+            BOOST_ERROR("Bad test: " << strTest);
+            continue;
+        }
+        std::vector<unsigned char> sourcedata = ParseHex(test[0].get_str());
+        std::string base58string = test[1].get_str();
+        BOOST_CHECK_MESSAGE(
+                    EncodeBase58(&sourcedata[0], &sourcedata[sourcedata.size()]) == base58string,
+                    strTest);
+    }
+}
+
+// Goal: test low-level base58 decoding functionality
+BOOST_AUTO_TEST_CASE(base58_DecodeBase58)
+{
+    Array tests = read_json("base58_encode_decode.json");
+    std::vector<unsigned char> result;
+
+    BOOST_FOREACH(Value& tv, tests)
+    {
+        Array test = tv.get_array();
+        std::string strTest = write_string(tv, false);
+        if (test.size() < 2) // Allow for extra stuff (useful for comments)
+        {
+            BOOST_ERROR("Bad test: " << strTest);
+            continue;
+        }
+        std::vector<unsigned char> expected = ParseHex(test[0].get_str());
+        std::string base58string = test[1].get_str();
+        BOOST_CHECK_MESSAGE(DecodeBase58(base58string, result), strTest);
+        BOOST_CHECK_MESSAGE(result.size() == expected.size() && std::equal(result.begin(), result.end(), expected.begin()), strTest);
+    }
+
+    BOOST_CHECK(!DecodeBase58("invalid", result));
+}
+
+// Visitor to check address type
+class TestAddrTypeVisitor : public boost::static_visitor<bool>
+{
+private:
+    std::string exp_addrType;
+public:
+    TestAddrTypeVisitor(const std::string &exp_addrType) : exp_addrType(exp_addrType) { }
+    bool operator()(const CKeyID &id) const
+    {
+        return (exp_addrType == "pubkey");
+    }
+    bool operator()(const CScriptID &id) const
+    {
+        return (exp_addrType == "script");
+    }
+    bool operator()(const CNoDestination &no) const
+    {
+        return (exp_addrType == "none");
+    }
 };
 
-const char *vstrOut[] = {
-"",
-"2g",
-"a3gV",
-"aPEr",
-"2cFupjhnEsSn59qHXstmK2ffpLv2",
-"1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L",
-"ABnLTmg",
-"3SEo3LWLoPntC",
-"3EFU7m",
-"EJDM8drfXA6uyA",
-"Rt5zm",
-"1111111111"
+// Visitor to check address payload
+class TestPayloadVisitor : public boost::static_visitor<bool>
+{
+private:
+    std::vector<unsigned char> exp_payload;
+public:
+    TestPayloadVisitor(std::vector<unsigned char> &exp_payload) : exp_payload(exp_payload) { }
+    bool operator()(const CKeyID &id) const
+    {
+        uint160 exp_key(exp_payload);
+        return exp_key == id;
+    }
+    bool operator()(const CScriptID &id) const
+    {
+        uint160 exp_key(exp_payload);
+        return exp_key == id;
+    }
+    bool operator()(const CNoDestination &no) const
+    {
+        return exp_payload.size() == 0;
+    }
 };
 
-BOOST_AUTO_TEST_CASE(base58_EncodeBase58)
+// Goal: check that parsed keys match test payload
+BOOST_AUTO_TEST_CASE(base58_keys_valid_parse)
 {
-    for (int i=0; i<sizeof(vstrIn)/sizeof(vstrIn[0]); i++)
+    Array tests = read_json("base58_keys_valid.json");
+    std::vector<unsigned char> result;
+    CBitcoinSecret secret;
+    CBitcoinAddress addr;
+    // Save global state
+    bool fTestNet_stored = fTestNet;
+
+    BOOST_FOREACH(Value& tv, tests)
     {
-        BOOST_CHECK_EQUAL(EncodeBase58(vstrIn[i].data, vstrIn[i].data + vstrIn[i].size), vstrOut[i]);
+        Array test = tv.get_array();
+        std::string strTest = write_string(tv, false);
+        if (test.size() < 3) // Allow for extra stuff (useful for comments)
+        {
+            BOOST_ERROR("Bad test: " << strTest);
+            continue;
+        }
+        std::string exp_base58string = test[0].get_str();
+        std::vector<unsigned char> exp_payload = ParseHex(test[1].get_str());
+        const Object &metadata = test[2].get_obj();
+        bool isPrivkey = find_value(metadata, "isPrivkey").get_bool();
+        bool isTestnet = find_value(metadata, "isTestnet").get_bool();
+        fTestNet = isTestnet; // Override testnet flag
+        if(isPrivkey)
+        {
+            bool isCompressed = find_value(metadata, "isCompressed").get_bool();
+            // Must be valid private key
+            // Note: CBitcoinSecret::SetString tests isValid, whereas CBitcoinAddress does not!
+            BOOST_CHECK_MESSAGE(secret.SetString(exp_base58string), "!SetString:"+ strTest);
+            BOOST_CHECK_MESSAGE(secret.IsValid(), "!IsValid:" + strTest);
+            bool fCompressedOut = false;
+            CSecret privkey = secret.GetSecret(fCompressedOut);
+            BOOST_CHECK_MESSAGE(fCompressedOut == isCompressed, "compressed mismatch:" + strTest);
+            BOOST_CHECK_MESSAGE(privkey.size() == exp_payload.size() && std::equal(privkey.begin(), privkey.end(), exp_payload.begin()), "key mismatch:" + strTest);
+
+            // Private key must be invalid public key
+            addr.SetString(exp_base58string);
+            BOOST_CHECK_MESSAGE(!addr.IsValid(), "IsValid privkey as pubkey:" + strTest);
+        }
+        else
+        {
+            std::string exp_addrType = find_value(metadata, "addrType").get_str(); // "script" or "pubkey"
+            // Must be valid public key
+            BOOST_CHECK_MESSAGE(addr.SetString(exp_base58string), "SetString:" + strTest);
+            BOOST_CHECK_MESSAGE(addr.IsValid(), "!IsValid:" + strTest);
+            BOOST_CHECK_MESSAGE(addr.IsScript() == (exp_addrType == "script"), "isScript mismatch" + strTest);
+            CTxDestination dest = addr.Get();
+            BOOST_CHECK_MESSAGE(boost::apply_visitor(TestAddrTypeVisitor(exp_addrType), dest), "addrType mismatch" + strTest);
+
+            // Public key must be invalid private key
+            secret.SetString(exp_base58string);
+            BOOST_CHECK_MESSAGE(!secret.IsValid(), "IsValid pubkey as privkey:" + strTest);
+        }
     }
+    // Restore global state
+    fTestNet = fTestNet_stored;
 }
 
-BOOST_AUTO_TEST_CASE(base58_DecodeBase58)
+// Goal: check that generated keys match test vectors
+BOOST_AUTO_TEST_CASE(base58_keys_valid_gen)
 {
+    Array tests = read_json("base58_keys_valid.json");
     std::vector<unsigned char> result;
-    for (int i=0; i<sizeof(vstrIn)/sizeof(vstrIn[0]); i++)
+    // Save global state
+    bool fTestNet_stored = fTestNet;
+
+    BOOST_FOREACH(Value& tv, tests)
     {
-        std::vector<unsigned char> expected(vstrIn[i].data, vstrIn[i].data + vstrIn[i].size);
-        BOOST_CHECK(DecodeBase58(vstrOut[i], result));
-        BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(), expected.begin(), expected.end());
+        Array test = tv.get_array();
+        std::string strTest = write_string(tv, false);
+        if (test.size() < 3) // Allow for extra stuff (useful for comments)
+        {
+            BOOST_ERROR("Bad test: " << strTest);
+            continue;
+        }
+        std::string exp_base58string = test[0].get_str();
+        std::vector<unsigned char> exp_payload = ParseHex(test[1].get_str());
+        const Object &metadata = test[2].get_obj();
+        bool isPrivkey = find_value(metadata, "isPrivkey").get_bool();
+        bool isTestnet = find_value(metadata, "isTestnet").get_bool();
+        fTestNet = isTestnet; // Override testnet flag
+        if(isPrivkey)
+        {
+            bool isCompressed = find_value(metadata, "isCompressed").get_bool();
+            CBitcoinSecret secret;
+            secret.SetSecret(CSecret(exp_payload.begin(), exp_payload.end()), isCompressed);
+            BOOST_CHECK_MESSAGE(secret.ToString() == exp_base58string, "result mismatch: " + strTest);
+        }
+        else
+        {
+            std::string exp_addrType = find_value(metadata, "addrType").get_str();
+            CTxDestination dest;
+            if(exp_addrType == "pubkey")
+            {
+                dest = CKeyID(uint160(exp_payload));
+            }
+            else if(exp_addrType == "script")
+            {
+                dest = CScriptID(uint160(exp_payload));
+            }
+            else if(exp_addrType == "none")
+            {
+                dest = CNoDestination();
+            }
+            else
+            {
+                BOOST_ERROR("Bad addrtype: " << strTest);
+                continue;
+            }
+            CBitcoinAddress addrOut;
+            BOOST_CHECK_MESSAGE(boost::apply_visitor(CBitcoinAddressVisitor(&addrOut), dest), "encode dest: " + strTest);
+            BOOST_CHECK_MESSAGE(addrOut.ToString() == exp_base58string, "mismatch: " + strTest);
+        }
+    }
+
+    // Visiting a CNoDestination must fail
+    CBitcoinAddress dummyAddr;
+    CTxDestination nodest = CNoDestination();
+    BOOST_CHECK(!boost::apply_visitor(CBitcoinAddressVisitor(&dummyAddr), nodest));
+
+    // Restore global state
+    fTestNet = fTestNet_stored;
+}
+
+// Goal: check that base58 parsing code is robust against a variety of corrupted data
+BOOST_AUTO_TEST_CASE(base58_keys_invalid)
+{
+    Array tests = read_json("base58_keys_invalid.json"); // Negative testcases
+    std::vector<unsigned char> result;
+    CBitcoinSecret secret;
+    CBitcoinAddress addr;
+
+    BOOST_FOREACH(Value& tv, tests)
+    {
+        Array test = tv.get_array();
+        std::string strTest = write_string(tv, false);
+        if (test.size() < 1) // Allow for extra stuff (useful for comments)
+        {
+            BOOST_ERROR("Bad test: " << strTest);
+            continue;
+        }
+        std::string exp_base58string = test[0].get_str();
+
+        // must be invalid as public and as private key
+        addr.SetString(exp_base58string);
+        BOOST_CHECK_MESSAGE(!addr.IsValid(), "IsValid pubkey:" + strTest);
+        secret.SetString(exp_base58string);
+        BOOST_CHECK_MESSAGE(!secret.IsValid(), "IsValid privkey:" + strTest);
     }
-    BOOST_CHECK(!DecodeBase58("invalid", result));
 }
 
+
 BOOST_AUTO_TEST_SUITE_END()