changeset 3578:797b79db87a4 draft

Merge branch '2012_09_base58tests' of github.com:laanwj/bitcoin
author Gavin Andresen <gavinandresen@gmail.com>
date Mon, 01 Oct 2012 11:22:38 -0400
parents e5daeb17ca83 (current diff) 1c8c9bf0faf4 (diff)
children 7f40eb03f34f
files
diffstat 7 files changed, 1086 insertions(+), 65 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/contrib/testgen/README
@@ -0,0 +1,1 @@
+Utilities to generate test vectors for the data-driven Bitcoin tests
new file mode 100644
--- /dev/null
+++ b/contrib/testgen/base58.py
@@ -0,0 +1,104 @@
+'''
+Bitcoin base58 encoding and decoding.
+
+Based on https://bitcointalk.org/index.php?topic=1026.0 (public domain)
+'''
+import hashlib
+
+# for compatibility with following code...
+class SHA256:
+    new = hashlib.sha256
+
+if str != bytes:
+    # Python 3.x
+    def ord(c):
+        return c
+    def chr(n):
+        return bytes( (n,) )
+
+__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
+__b58base = len(__b58chars)
+b58chars = __b58chars
+
+def b58encode(v):
+    """ encode v, which is a string of bytes, to base58.
+    """
+    long_value = 0
+    for (i, c) in enumerate(v[::-1]):
+        long_value += (256**i) * ord(c)
+
+    result = ''
+    while long_value >= __b58base:
+        div, mod = divmod(long_value, __b58base)
+        result = __b58chars[mod] + result
+        long_value = div
+    result = __b58chars[long_value] + result
+
+    # Bitcoin does a little leading-zero-compression:
+    # leading 0-bytes in the input become leading-1s
+    nPad = 0
+    for c in v:
+        if c == '\0': nPad += 1
+        else: break
+
+    return (__b58chars[0]*nPad) + result
+
+def b58decode(v, length = None):
+    """ decode v into a string of len bytes
+    """
+    long_value = 0
+    for (i, c) in enumerate(v[::-1]):
+        long_value += __b58chars.find(c) * (__b58base**i)
+
+    result = bytes()
+    while long_value >= 256:
+        div, mod = divmod(long_value, 256)
+        result = chr(mod) + result
+        long_value = div
+    result = chr(long_value) + result
+
+    nPad = 0
+    for c in v:
+        if c == __b58chars[0]: nPad += 1
+        else: break
+
+    result = chr(0)*nPad + result
+    if length is not None and len(result) != length:
+        return None
+
+    return result
+
+def checksum(v):
+    """Return 32-bit checksum based on SHA256"""
+    return SHA256.new(SHA256.new(v).digest()).digest()[0:4]
+
+def b58encode_chk(v):
+    """b58encode a string, with 32-bit checksum"""
+    return b58encode(v + checksum(v))
+
+def b58decode_chk(v):
+    """decode a base58 string, check and remove checksum"""
+    result = b58decode(v)
+    if result is None:
+        return None
+    h3 = checksum(result[:-4])
+    if result[-4:] == checksum(result[:-4]):
+        return result[:-4]
+    else:
+        return None
+
+def get_bcaddress_version(strAddress):
+    """ Returns None if strAddress is invalid.  Otherwise returns integer version of address. """
+    addr = b58decode_chk(strAddress)
+    if addr is None or len(addr)!=21: return None
+    version = addr[0]
+    return ord(version)
+
+if __name__ == '__main__':
+    # Test case (from http://gitorious.org/bitcoin/python-base58.git)
+    assert get_bcaddress_version('15VjRaDX9zpbA8LVnbrCAFzrVzN7ixHNsC') is 0
+    _ohai = 'o hai'.encode('ascii')
+    _tmp = b58encode(_ohai)
+    assert _tmp == 'DYB3oMS'
+    assert b58decode(_tmp, 5) == _ohai
+    print("Tests passed")
new file mode 100755
--- /dev/null
+++ b/contrib/testgen/gen_base58_test_vectors.py
@@ -0,0 +1,126 @@
+#!/usr/bin/env python
+'''
+Generate valid and invalid base58 address and private key test vectors.
+
+Usage: 
+    gen_base58_test_vectors.py valid 50 > ../../src/test/data/base58_keys_valid.json
+    gen_base58_test_vectors.py invalid 50 > ../../src/test/data/base58_keys_invalid.json
+'''
+# 2012 Wladimir J. van der Laan
+# Released under MIT License
+import os
+from itertools import islice
+from base58 import b58encode, b58decode, b58encode_chk, b58decode_chk, b58chars
+import random
+from binascii import b2a_hex
+
+# key types
+PUBKEY_ADDRESS = 0
+SCRIPT_ADDRESS = 5
+PUBKEY_ADDRESS_TEST = 111
+SCRIPT_ADDRESS_TEST = 196
+PRIVKEY = 128
+PRIVKEY_TEST = 239
+
+metadata_keys = ['isPrivkey', 'isTestnet', 'addrType', 'isCompressed']
+# templates for valid sequences
+templates = [
+  # prefix, payload_size, suffix, metadata
+  #                                  None = N/A
+  ((PUBKEY_ADDRESS,),      20, (),   (False, False, 'pubkey', None)),
+  ((SCRIPT_ADDRESS,),      20, (),   (False, False, 'script',  None)),
+  ((PUBKEY_ADDRESS_TEST,), 20, (),   (False, True,  'pubkey', None)),
+  ((SCRIPT_ADDRESS_TEST,), 20, (),   (False, True,  'script',  None)),
+  ((PRIVKEY,),             32, (),   (True,  False, None,  False)),
+  ((PRIVKEY,),             32, (1,), (True,  False, None,  True)),
+  ((PRIVKEY_TEST,),        32, (),   (True,  True,  None,  False)),
+  ((PRIVKEY_TEST,),        32, (1,), (True,  True,  None,  True))
+]
+
+def is_valid(v):
+    '''Check vector v for validity'''
+    result = b58decode_chk(v)
+    if result is None:
+        return False
+    valid = False
+    for template in templates:
+        prefix = str(bytearray(template[0]))
+        suffix = str(bytearray(template[2]))
+        if result.startswith(prefix) and result.endswith(suffix):
+            if (len(result) - len(prefix) - len(suffix)) == template[1]:
+                return True
+    return False
+
+def gen_valid_vectors():
+    '''Generate valid test vectors'''
+    while True:
+        for template in templates:
+            prefix = str(bytearray(template[0]))
+            payload = os.urandom(template[1]) 
+            suffix = str(bytearray(template[2]))
+            rv = b58encode_chk(prefix + payload + suffix)
+            assert is_valid(rv)
+            metadata = dict([(x,y) for (x,y) in zip(metadata_keys,template[3]) if y is not None])
+            yield (rv, b2a_hex(payload), metadata)
+
+def gen_invalid_vector(template, corrupt_prefix, randomize_payload_size, corrupt_suffix):
+    '''Generate possibly invalid vector'''
+    if corrupt_prefix:
+        prefix = os.urandom(1)
+    else:
+        prefix = str(bytearray(template[0]))
+    
+    if randomize_payload_size:
+        payload = os.urandom(max(int(random.expovariate(0.5)), 50))
+    else:
+        payload = os.urandom(template[1])
+    
+    if corrupt_suffix:
+        suffix = os.urandom(len(template[2]))
+    else:
+        suffix = str(bytearray(template[2]))
+
+    return b58encode_chk(prefix + payload + suffix)
+
+def randbool(p = 0.5):
+    '''Return True with P(p)'''
+    return random.random() < p
+
+def gen_invalid_vectors():
+    '''Generate invalid test vectors'''
+    # start with some manual edge-cases
+    yield "",
+    yield "x",
+    while True:
+        # kinds of invalid vectors:
+        #   invalid prefix
+        #   invalid payload length
+        #   invalid (randomized) suffix (add random data)
+        #   corrupt checksum
+        for template in templates:
+            val = gen_invalid_vector(template, randbool(0.2), randbool(0.2), randbool(0.2))
+            if random.randint(0,10)<1: # line corruption
+                if randbool(): # add random character to end
+                    val += random.choice(b58chars)
+                else: # replace random character in the middle
+                    n = random.randint(0, len(val))
+                    val = val[0:n] + random.choice(b58chars) + val[n+1:]
+            if not is_valid(val):
+                yield val,
+
+if __name__ == '__main__':
+    import sys, json
+    iters = {'valid':gen_valid_vectors, 'invalid':gen_invalid_vectors}
+    try:
+        uiter = iters[sys.argv[1]]
+    except IndexError:
+        uiter = gen_valid_vectors
+    try:
+        count = int(sys.argv[2])
+    except IndexError:
+        count = 0
+   
+    data = list(islice(uiter(), count))
+    json.dump(data, sys.stdout, sort_keys=True, indent=4)
+    sys.stdout.write('\n')
+
--- a/src/test/base58_tests.cpp
+++ b/src/test/base58_tests.cpp
@@ -1,87 +1,259 @@
 #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 "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},
-};
-
-const char *vstrOut[] = {
-"",
-"2g",
-"a3gV",
-"aPEr",
-"2cFupjhnEsSn59qHXstmK2ffpLv2",
-"1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L",
-"ABnLTmg",
-"3SEo3LWLoPntC",
-"3EFU7m",
-"EJDM8drfXA6uyA",
-"Rt5zm",
-"1111111111"
-};
-
+// Goal: test low-level base58 encoding functionality
 BOOST_AUTO_TEST_CASE(base58_EncodeBase58)
 {
-    for (unsigned int i=0; i<sizeof(vstrIn)/sizeof(vstrIn[0]); i++)
+    Array tests = read_json("base58_encode_decode.json");
+
+    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() < 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;
-    for (unsigned int i=0; i<sizeof(vstrIn)/sizeof(vstrIn[0]); i++)
+
+    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() < 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");
+    }
+};
+
+// 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;
+    }
+};
+
+// Goal: check that parsed keys match test payload
+BOOST_AUTO_TEST_CASE(base58_keys_valid_parse)
+{
+    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)
+    {
+        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;
+}
+
+// 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;
+    // Save global state
+    bool fTestNet_stored = fTestNet;
+
+    BOOST_FOREACH(Value& tv, tests)
+    {
+        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_AUTO_TEST_SUITE_END()
 
new file mode 100644
--- /dev/null
+++ b/src/test/data/base58_encode_decode.json
@@ -0,0 +1,14 @@
+[
+["", ""],
+["61", "2g"],
+["626262", "a3gV"],
+["636363", "aPEr"],
+["73696d706c792061206c6f6e6720737472696e67", "2cFupjhnEsSn59qHXstmK2ffpLv2"],
+["00eb15231dfceb60925886b67d065299925915aeb172c06647", "1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L"],
+["516b6fcd0f", "ABnLTmg"],
+["bf4f89001e670274dd", "3SEo3LWLoPntC"],
+["572e4794", "3EFU7m"],
+["ecac89cad93923c02321", "EJDM8drfXA6uyA"],
+["10c8511e", "Rt5zm"],
+["00000000000000000000", "1111111111"]
+]
new file mode 100644
--- /dev/null
+++ b/src/test/data/base58_keys_invalid.json
@@ -0,0 +1,152 @@
+[
+    [
+        ""
+    ], 
+    [
+        "x"
+    ], 
+    [
+        "37qgekLpCCHrQuSjvX3fs496FWTGsHFHizjJAs6NPcR47aefnnCWECAhHV6E3g4YN7u7Yuwod5Y"
+    ], 
+    [
+        "dzb7VV1Ui55BARxv7ATxAtCUeJsANKovDGWFVgpTbhq9gvPqP3yv"
+    ], 
+    [
+        "MuNu7ZAEDFiHthiunm7dPjwKqrVNCM3mAz6rP9zFveQu14YA8CxExSJTHcVP9DErn6u84E6Ej7S"
+    ], 
+    [
+        "rPpQpYknyNQ5AEHuY6H8ijJJrYc2nDKKk9jjmKEXsWzyAQcFGpDLU2Zvsmoi8JLR7hAwoy3RQWf"
+    ], 
+    [
+        "4Uc3FmN6NQ6zLBK5QQBXRBUREaaHwCZYsGCueHauuDmJpZKn6jkEskMB2Zi2CNgtb5r6epWEFfUJq"
+    ], 
+    [
+        "7aQgR5DFQ25vyXmqZAWmnVCjL3PkBcdVkBUpjrjMTcghHx3E8wb"
+    ], 
+    [
+        "17QpPprjeg69fW1DV8DcYYCKvWjYhXvWkov6MJ1iTTvMFj6weAqW7wybZeH57WTNxXVCRH4veVs"
+    ], 
+    [
+        "KxuACDviz8Xvpn1xAh9MfopySZNuyajYMZWz16Dv2mHHryznWUp3"
+    ], 
+    [
+        "7nK3GSmqdXJQtdohvGfJ7KsSmn3TmGqExug49583bDAL91pVSGq5xS9SHoAYL3Wv3ijKTit65th"
+    ], 
+    [
+        "cTivdBmq7bay3RFGEBBuNfMh2P1pDCgRYN2Wbxmgwr4ki3jNUL2va"
+    ], 
+    [
+        "gjMV4vjNjyMrna4fsAr8bWxAbwtmMUBXJS3zL4NJt5qjozpbQLmAfK1uA3CquSqsZQMpoD1g2nk"
+    ], 
+    [
+        "emXm1naBMoVzPjbk7xpeTVMFy4oDEe25UmoyGgKEB1gGWsK8kRGs"
+    ], 
+    [
+        "7VThQnNRj1o3Zyvc7XHPRrjDf8j2oivPTeDXnRPYWeYGE4pXeRJDZgf28ppti5hsHWXS2GSobdqyo"
+    ], 
+    [
+        "1G9u6oCVCPh2o8m3t55ACiYvG1y5BHewUkDSdiQarDcYXXhFHYdzMdYfUAhfxn5vNZBwpgUNpso"
+    ], 
+    [
+        "31QQ7ZMLkScDiB4VyZjuptr7AEc9j1SjstF7pRoLhHTGkW4Q2y9XELobQmhhWxeRvqcukGd1XCq"
+    ], 
+    [
+        "DHqKSnpxa8ZdQyH8keAhvLTrfkyBMQxqngcQA5N8LQ9KVt25kmGN"
+    ], 
+    [
+        "2LUHcJPbwLCy9GLH1qXmfmAwvadWw4bp4PCpDfduLqV17s6iDcy1imUwhQJhAoNoN1XNmweiJP4i"
+    ], 
+    [
+        "7USRzBXAnmck8fX9HmW7RAb4qt92VFX6soCnts9s74wxm4gguVhtG5of8fZGbNPJA83irHVY6bCos"
+    ], 
+    [
+        "1DGezo7BfVebZxAbNT3XGujdeHyNNBF3vnficYoTSp4PfK2QaML9bHzAMxke3wdKdHYWmsMTJVu"
+    ], 
+    [
+        "2D12DqDZKwCxxkzs1ZATJWvgJGhQ4cFi3WrizQ5zLAyhN5HxuAJ1yMYaJp8GuYsTLLxTAz6otCfb"
+    ], 
+    [
+        "8AFJzuTujXjw1Z6M3fWhQ1ujDW7zsV4ePeVjVo7D1egERqSW9nZ"
+    ], 
+    [
+        "163Q17qLbTCue8YY3AvjpUhotuaodLm2uqMhpYirsKjVqnxJRWTEoywMVY3NbBAHuhAJ2cF9GAZ"
+    ], 
+    [
+        "2MnmgiRH4eGLyLc9eAqStzk7dFgBjFtUCtu"
+    ], 
+    [
+        "461QQ2sYWxU7H2PV4oBwJGNch8XVTYYbZxU"
+    ], 
+    [
+        "2UCtv53VttmQYkVU4VMtXB31REvQg4ABzs41AEKZ8UcB7DAfVzdkV9JDErwGwyj5AUHLkmgZeobs"
+    ], 
+    [
+        "cSNjAsnhgtiFMi6MtfvgscMB2Cbhn2v1FUYfviJ1CdjfidvmeW6mn"
+    ], 
+    [
+        "gmsow2Y6EWAFDFE1CE4Hd3Tpu2BvfmBfG1SXsuRARbnt1WjkZnFh1qGTiptWWbjsq2Q6qvpgJVj"
+    ], 
+    [
+        "nksUKSkzS76v8EsSgozXGMoQFiCoCHzCVajFKAXqzK5on9ZJYVHMD5CKwgmX3S3c7M1U3xabUny"
+    ], 
+    [
+        "L3favK1UzFGgdzYBF2oBT5tbayCo4vtVBLJhg2iYuMeePxWG8SQc"
+    ], 
+    [
+        "7VxLxGGtYT6N99GdEfi6xz56xdQ8nP2dG1CavuXx7Rf2PrvNMTBNevjkfgs9JmkcGm6EXpj8ipyPZ"
+    ], 
+    [
+        "2mbZwFXF6cxShaCo2czTRB62WTx9LxhTtpP"
+    ], 
+    [
+        "dB7cwYdcPSgiyAwKWL3JwCVwSk6epU2txw"
+    ], 
+    [
+        "HPhFUhUAh8ZQQisH8QQWafAxtQYju3SFTX"
+    ], 
+    [
+        "4ctAH6AkHzq5ioiM1m9T3E2hiYEev5mTsB"
+    ], 
+    [
+        "Hn1uFi4dNexWrqARpjMqgT6cX1UsNPuV3cHdGg9ExyXw8HTKadbktRDtdeVmY3M1BxJStiL4vjJ"
+    ], 
+    [
+        "Sq3fDbvutABmnAHHExJDgPLQn44KnNC7UsXuT7KZecpaYDMU9Txs"
+    ], 
+    [
+        "6TqWyrqdgUEYDQU1aChMuFMMEimHX44qHFzCUgGfqxGgZNMUVWJ"
+    ], 
+    [
+        "giqJo7oWqFxNKWyrgcBxAVHXnjJ1t6cGoEffce5Y1y7u649Noj5wJ4mmiUAKEVVrYAGg2KPB3Y4"
+    ], 
+    [
+        "cNzHY5e8vcmM3QVJUcjCyiKMYfeYvyueq5qCMV3kqcySoLyGLYUK"
+    ], 
+    [
+        "37uTe568EYc9WLoHEd9jXEvUiWbq5LFLscNyqvAzLU5vBArUJA6eydkLmnMwJDjkL5kXc2VK7ig"
+    ], 
+    [
+        "EsYbG4tWWWY45G31nox838qNdzksbPySWc"
+    ], 
+    [
+        "nbuzhfwMoNzA3PaFnyLcRxE9bTJPDkjZ6Rf6Y6o2ckXZfzZzXBT"
+    ], 
+    [
+        "cQN9PoxZeCWK1x56xnz6QYAsvR11XAce3Ehp3gMUdfSQ53Y2mPzx"
+    ], 
+    [
+        "1Gm3N3rkef6iMbx4voBzaxtXcmmiMTqZPhcuAepRzYUJQW4qRpEnHvMojzof42hjFRf8PE2jPde"
+    ], 
+    [
+        "2TAq2tuN6x6m233bpT7yqdYQPELdTDJn1eU"
+    ], 
+    [
+        "ntEtnnGhqPii4joABvBtSEJG6BxjT2tUZqE8PcVYgk3RHpgxgHDCQxNbLJf7ardf1dDk2oCQ7Cf"
+    ], 
+    [
+        "Ky1YjoZNgQ196HJV3HpdkecfhRBmRZdMJk89Hi5KGfpfPwS2bUbfd"
+    ], 
+    [
+        "2A1q1YsMZowabbvta7kTy2Fd6qN4r5ZCeG3qLpvZBMzCixMUdkN2Y4dHB1wPsZAeVXUGD83MfRED"
+    ]
+]
new file mode 100644
--- /dev/null
+++ b/src/test/data/base58_keys_valid.json
@@ -0,0 +1,452 @@
+[
+    [
+        "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i", 
+        "65a16059864a2fdbc7c99a4723a8395bc6f188eb", 
+        {
+            "addrType": "pubkey", 
+            "isPrivkey": false, 
+            "isTestnet": false
+        }
+    ], 
+    [
+        "3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou", 
+        "74f209f6ea907e2ea48f74fae05782ae8a665257", 
+        {
+            "addrType": "script", 
+            "isPrivkey": false, 
+            "isTestnet": false
+        }
+    ], 
+    [
+        "mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs", 
+        "53c0307d6851aa0ce7825ba883c6bd9ad242b486", 
+        {
+            "addrType": "pubkey", 
+            "isPrivkey": false, 
+            "isTestnet": true
+        }
+    ], 
+    [
+        "2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br", 
+        "6349a418fc4578d10a372b54b45c280cc8c4382f", 
+        {
+            "addrType": "script", 
+            "isPrivkey": false, 
+            "isTestnet": true
+        }
+    ], 
+    [
+        "5Kd3NBUAdUnhyzenEwVLy9pBKxSwXvE9FMPyR4UKZvpe6E3AgLr", 
+        "eddbdc1168f1daeadbd3e44c1e3f8f5a284c2029f78ad26af98583a499de5b19", 
+        {
+            "isCompressed": false, 
+            "isPrivkey": true, 
+            "isTestnet": false
+        }
+    ], 
+    [
+        "Kz6UJmQACJmLtaQj5A3JAge4kVTNQ8gbvXuwbmCj7bsaabudb3RD", 
+        "55c9bccb9ed68446d1b75273bbce89d7fe013a8acd1625514420fb2aca1a21c4", 
+        {
+            "isCompressed": true, 
+            "isPrivkey": true, 
+            "isTestnet": false
+        }
+    ], 
+    [
+        "9213qJab2HNEpMpYNBa7wHGFKKbkDn24jpANDs2huN3yi4J11ko", 
+        "36cb93b9ab1bdabf7fb9f2c04f1b9cc879933530ae7842398eef5a63a56800c2", 
+        {
+            "isCompressed": false, 
+            "isPrivkey": true, 
+            "isTestnet": true
+        }
+    ], 
+    [
+        "cTpB4YiyKiBcPxnefsDpbnDxFDffjqJob8wGCEDXxgQ7zQoMXJdH", 
+        "b9f4892c9e8282028fea1d2667c4dc5213564d41fc5783896a0d843fc15089f3", 
+        {
+            "isCompressed": true, 
+            "isPrivkey": true, 
+            "isTestnet": true
+        }
+    ], 
+    [
+        "1Ax4gZtb7gAit2TivwejZHYtNNLT18PUXJ", 
+        "6d23156cbbdcc82a5a47eee4c2c7c583c18b6bf4", 
+        {
+            "addrType": "pubkey", 
+            "isPrivkey": false, 
+            "isTestnet": false
+        }
+    ], 
+    [
+        "3QjYXhTkvuj8qPaXHTTWb5wjXhdsLAAWVy", 
+        "fcc5460dd6e2487c7d75b1963625da0e8f4c5975", 
+        {
+            "addrType": "script", 
+            "isPrivkey": false, 
+            "isTestnet": false
+        }
+    ], 
+    [
+        "n3ZddxzLvAY9o7184TB4c6FJasAybsw4HZ", 
+        "f1d470f9b02370fdec2e6b708b08ac431bf7a5f7", 
+        {
+            "addrType": "pubkey", 
+            "isPrivkey": false, 
+            "isTestnet": true
+        }
+    ], 
+    [
+        "2NBFNJTktNa7GZusGbDbGKRZTxdK9VVez3n", 
+        "c579342c2c4c9220205e2cdc285617040c924a0a", 
+        {
+            "addrType": "script", 
+            "isPrivkey": false, 
+            "isTestnet": true
+        }
+    ], 
+    [
+        "5K494XZwps2bGyeL71pWid4noiSNA2cfCibrvRWqcHSptoFn7rc", 
+        "a326b95ebae30164217d7a7f57d72ab2b54e3be64928a19da0210b9568d4015e", 
+        {
+            "isCompressed": false, 
+            "isPrivkey": true, 
+            "isTestnet": false
+        }
+    ], 
+    [
+        "L1RrrnXkcKut5DEMwtDthjwRcTTwED36thyL1DebVrKuwvohjMNi", 
+        "7d998b45c219a1e38e99e7cbd312ef67f77a455a9b50c730c27f02c6f730dfb4", 
+        {
+            "isCompressed": true, 
+            "isPrivkey": true, 
+            "isTestnet": false
+        }
+    ], 
+    [
+        "93DVKyFYwSN6wEo3E2fCrFPUp17FtrtNi2Lf7n4G3garFb16CRj", 
+        "d6bca256b5abc5602ec2e1c121a08b0da2556587430bcf7e1898af2224885203", 
+        {
+            "isCompressed": false, 
+            "isPrivkey": true, 
+            "isTestnet": true
+        }
+    ], 
+    [
+        "cTDVKtMGVYWTHCb1AFjmVbEbWjvKpKqKgMaR3QJxToMSQAhmCeTN", 
+        "a81ca4e8f90181ec4b61b6a7eb998af17b2cb04de8a03b504b9e34c4c61db7d9", 
+        {
+            "isCompressed": true, 
+            "isPrivkey": true, 
+            "isTestnet": true
+        }
+    ], 
+    [
+        "1C5bSj1iEGUgSTbziymG7Cn18ENQuT36vv", 
+        "7987ccaa53d02c8873487ef919677cd3db7a6912", 
+        {
+            "addrType": "pubkey", 
+            "isPrivkey": false, 
+            "isTestnet": false
+        }
+    ], 
+    [
+        "3AnNxabYGoTxYiTEZwFEnerUoeFXK2Zoks", 
+        "63bcc565f9e68ee0189dd5cc67f1b0e5f02f45cb", 
+        {
+            "addrType": "script", 
+            "isPrivkey": false, 
+            "isTestnet": false
+        }
+    ], 
+    [
+        "n3LnJXCqbPjghuVs8ph9CYsAe4Sh4j97wk", 
+        "ef66444b5b17f14e8fae6e7e19b045a78c54fd79", 
+        {
+            "addrType": "pubkey", 
+            "isPrivkey": false, 
+            "isTestnet": true
+        }
+    ], 
+    [
+        "2NB72XtkjpnATMggui83aEtPawyyKvnbX2o", 
+        "c3e55fceceaa4391ed2a9677f4a4d34eacd021a0", 
+        {
+            "addrType": "script", 
+            "isPrivkey": false, 
+            "isTestnet": true
+        }
+    ], 
+    [
+        "5KaBW9vNtWNhc3ZEDyNCiXLPdVPHCikRxSBWwV9NrpLLa4LsXi9", 
+        "e75d936d56377f432f404aabb406601f892fd49da90eb6ac558a733c93b47252", 
+        {
+            "isCompressed": false, 
+            "isPrivkey": true, 
+            "isTestnet": false
+        }
+    ], 
+    [
+        "L1axzbSyynNYA8mCAhzxkipKkfHtAXYF4YQnhSKcLV8YXA874fgT", 
+        "8248bd0375f2f75d7e274ae544fb920f51784480866b102384190b1addfbaa5c", 
+        {
+            "isCompressed": true, 
+            "isPrivkey": true, 
+            "isTestnet": false
+        }
+    ], 
+    [
+        "927CnUkUbasYtDwYwVn2j8GdTuACNnKkjZ1rpZd2yBB1CLcnXpo", 
+        "44c4f6a096eac5238291a94cc24c01e3b19b8d8cef72874a079e00a242237a52", 
+        {
+            "isCompressed": false, 
+            "isPrivkey": true, 
+            "isTestnet": true
+        }
+    ], 
+    [
+        "cUcfCMRjiQf85YMzzQEk9d1s5A4K7xL5SmBCLrezqXFuTVefyhY7", 
+        "d1de707020a9059d6d3abaf85e17967c6555151143db13dbb06db78df0f15c69", 
+        {
+            "isCompressed": true, 
+            "isPrivkey": true, 
+            "isTestnet": true
+        }
+    ], 
+    [
+        "1Gqk4Tv79P91Cc1STQtU3s1W6277M2CVWu", 
+        "adc1cc2081a27206fae25792f28bbc55b831549d", 
+        {
+            "addrType": "pubkey", 
+            "isPrivkey": false, 
+            "isTestnet": false
+        }
+    ], 
+    [
+        "33vt8ViH5jsr115AGkW6cEmEz9MpvJSwDk", 
+        "188f91a931947eddd7432d6e614387e32b244709", 
+        {
+            "addrType": "script", 
+            "isPrivkey": false, 
+            "isTestnet": false
+        }
+    ], 
+    [
+        "mhaMcBxNh5cqXm4aTQ6EcVbKtfL6LGyK2H", 
+        "1694f5bc1a7295b600f40018a618a6ea48eeb498", 
+        {
+            "addrType": "pubkey", 
+            "isPrivkey": false, 
+            "isTestnet": true
+        }
+    ], 
+    [
+        "2MxgPqX1iThW3oZVk9KoFcE5M4JpiETssVN", 
+        "3b9b3fd7a50d4f08d1a5b0f62f644fa7115ae2f3", 
+        {
+            "addrType": "script", 
+            "isPrivkey": false, 
+            "isTestnet": true
+        }
+    ], 
+    [
+        "5HtH6GdcwCJA4ggWEL1B3jzBBUB8HPiBi9SBc5h9i4Wk4PSeApR", 
+        "091035445ef105fa1bb125eccfb1882f3fe69592265956ade751fd095033d8d0", 
+        {
+            "isCompressed": false, 
+            "isPrivkey": true, 
+            "isTestnet": false
+        }
+    ], 
+    [
+        "L2xSYmMeVo3Zek3ZTsv9xUrXVAmrWxJ8Ua4cw8pkfbQhcEFhkXT8", 
+        "ab2b4bcdfc91d34dee0ae2a8c6b6668dadaeb3a88b9859743156f462325187af", 
+        {
+            "isCompressed": true, 
+            "isPrivkey": true, 
+            "isTestnet": false
+        }
+    ], 
+    [
+        "92xFEve1Z9N8Z641KQQS7ByCSb8kGjsDzw6fAmjHN1LZGKQXyMq", 
+        "b4204389cef18bbe2b353623cbf93e8678fbc92a475b664ae98ed594e6cf0856", 
+        {
+            "isCompressed": false, 
+            "isPrivkey": true, 
+            "isTestnet": true
+        }
+    ], 
+    [
+        "cVM65tdYu1YK37tNoAyGoJTR13VBYFva1vg9FLuPAsJijGvG6NEA", 
+        "e7b230133f1b5489843260236b06edca25f66adb1be455fbd38d4010d48faeef", 
+        {
+            "isCompressed": true, 
+            "isPrivkey": true, 
+            "isTestnet": true
+        }
+    ], 
+    [
+        "1JwMWBVLtiqtscbaRHai4pqHokhFCbtoB4", 
+        "c4c1b72491ede1eedaca00618407ee0b772cad0d", 
+        {
+            "addrType": "pubkey", 
+            "isPrivkey": false, 
+            "isTestnet": false
+        }
+    ], 
+    [
+        "3QCzvfL4ZRvmJFiWWBVwxfdaNBT8EtxB5y", 
+        "f6fe69bcb548a829cce4c57bf6fff8af3a5981f9", 
+        {
+            "addrType": "script", 
+            "isPrivkey": false, 
+            "isTestnet": false
+        }
+    ], 
+    [
+        "mizXiucXRCsEriQCHUkCqef9ph9qtPbZZ6", 
+        "261f83568a098a8638844bd7aeca039d5f2352c0", 
+        {
+            "addrType": "pubkey", 
+            "isPrivkey": false, 
+            "isTestnet": true
+        }
+    ], 
+    [
+        "2NEWDzHWwY5ZZp8CQWbB7ouNMLqCia6YRda", 
+        "e930e1834a4d234702773951d627cce82fbb5d2e", 
+        {
+            "addrType": "script", 
+            "isPrivkey": false, 
+            "isTestnet": true
+        }
+    ], 
+    [
+        "5KQmDryMNDcisTzRp3zEq9e4awRmJrEVU1j5vFRTKpRNYPqYrMg", 
+        "d1fab7ab7385ad26872237f1eb9789aa25cc986bacc695e07ac571d6cdac8bc0", 
+        {
+            "isCompressed": false, 
+            "isPrivkey": true, 
+            "isTestnet": false
+        }
+    ], 
+    [
+        "L39Fy7AC2Hhj95gh3Yb2AU5YHh1mQSAHgpNixvm27poizcJyLtUi", 
+        "b0bbede33ef254e8376aceb1510253fc3550efd0fcf84dcd0c9998b288f166b3", 
+        {
+            "isCompressed": true, 
+            "isPrivkey": true, 
+            "isTestnet": false
+        }
+    ], 
+    [
+        "91cTVUcgydqyZLgaANpf1fvL55FH53QMm4BsnCADVNYuWuqdVys", 
+        "037f4192c630f399d9271e26c575269b1d15be553ea1a7217f0cb8513cef41cb", 
+        {
+            "isCompressed": false, 
+            "isPrivkey": true, 
+            "isTestnet": true
+        }
+    ], 
+    [
+        "cQspfSzsgLeiJGB2u8vrAiWpCU4MxUT6JseWo2SjXy4Qbzn2fwDw", 
+        "6251e205e8ad508bab5596bee086ef16cd4b239e0cc0c5d7c4e6035441e7d5de", 
+        {
+            "isCompressed": true, 
+            "isPrivkey": true, 
+            "isTestnet": true
+        }
+    ], 
+    [
+        "19dcawoKcZdQz365WpXWMhX6QCUpR9SY4r", 
+        "5eadaf9bb7121f0f192561a5a62f5e5f54210292", 
+        {
+            "addrType": "pubkey", 
+            "isPrivkey": false, 
+            "isTestnet": false
+        }
+    ], 
+    [
+        "37Sp6Rv3y4kVd1nQ1JV5pfqXccHNyZm1x3", 
+        "3f210e7277c899c3a155cc1c90f4106cbddeec6e", 
+        {
+            "addrType": "script", 
+            "isPrivkey": false, 
+            "isTestnet": false
+        }
+    ], 
+    [
+        "myoqcgYiehufrsnnkqdqbp69dddVDMopJu", 
+        "c8a3c2a09a298592c3e180f02487cd91ba3400b5", 
+        {
+            "addrType": "pubkey", 
+            "isPrivkey": false, 
+            "isTestnet": true
+        }
+    ], 
+    [
+        "2N7FuwuUuoTBrDFdrAZ9KxBmtqMLxce9i1C", 
+        "99b31df7c9068d1481b596578ddbb4d3bd90baeb", 
+        {
+            "addrType": "script", 
+            "isPrivkey": false, 
+            "isTestnet": true
+        }
+    ], 
+    [
+        "5KL6zEaMtPRXZKo1bbMq7JDjjo1bJuQcsgL33je3oY8uSJCR5b4", 
+        "c7666842503db6dc6ea061f092cfb9c388448629a6fe868d068c42a488b478ae", 
+        {
+            "isCompressed": false, 
+            "isPrivkey": true, 
+            "isTestnet": false
+        }
+    ], 
+    [
+        "KwV9KAfwbwt51veZWNscRTeZs9CKpojyu1MsPnaKTF5kz69H1UN2", 
+        "07f0803fc5399e773555ab1e8939907e9badacc17ca129e67a2f5f2ff84351dd", 
+        {
+            "isCompressed": true, 
+            "isPrivkey": true, 
+            "isTestnet": false
+        }
+    ], 
+    [
+        "93N87D6uxSBzwXvpokpzg8FFmfQPmvX4xHoWQe3pLdYpbiwT5YV", 
+        "ea577acfb5d1d14d3b7b195c321566f12f87d2b77ea3a53f68df7ebf8604a801", 
+        {
+            "isCompressed": false, 
+            "isPrivkey": true, 
+            "isTestnet": true
+        }
+    ], 
+    [
+        "cMxXusSihaX58wpJ3tNuuUcZEQGt6DKJ1wEpxys88FFaQCYjku9h", 
+        "0b3b34f0958d8a268193a9814da92c3e8b58b4a4378a542863e34ac289cd830c", 
+        {
+            "isCompressed": true, 
+            "isPrivkey": true, 
+            "isTestnet": true
+        }
+    ], 
+    [
+        "13p1ijLwsnrcuyqcTvJXkq2ASdXqcnEBLE", 
+        "1ed467017f043e91ed4c44b4e8dd674db211c4e6", 
+        {
+            "addrType": "pubkey", 
+            "isPrivkey": false, 
+            "isTestnet": false
+        }
+    ], 
+    [
+        "3ALJH9Y951VCGcVZYAdpA3KchoP9McEj1G", 
+        "5ece0cadddc415b1980f001785947120acdb36fc", 
+        {
+            "addrType": "script", 
+            "isPrivkey": false, 
+            "isTestnet": false
+        }
+    ]
+]