Search code examples
c#c++bitcoin

Decoding Bitcoin Base58 address to byte array


I'm trying to decode bitcoin address from Base58 string into byte array, and to do that I rewrited original function from Satoshi repository (https://github.com/bitcoin/bitcoin/blob/master/src/base58.cpp), written in c++, to c# (which I'm using).

Original code

static const char* pszBase58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

bool DecodeBase58(const char *psz, std::vector<unsigned char>& vch) {
    // Skip leading spaces.
    while (*psz && isspace(*psz))
        psz++;
    // Skip and count leading '1's.
    int zeroes = 0;
    while (*psz == '1') {
        zeroes++;
        psz++;
    }
    // Allocate enough space in big-endian base256 representation.
    std::vector<unsigned char> b256(strlen(psz) * 733 / 1000 + 1); // log(58) / log(256), rounded up.
    // Process the characters.
    while (*psz && !isspace(*psz)) {
        // Decode base58 character
        const char *ch = strchr(pszBase58, *psz);
        if (ch == NULL)
            return false;
        // Apply "b256 = b256 * 58 + ch".
        int carry = ch - pszBase58;
        for (std::vector<unsigned char>::reverse_iterator it = b256.rbegin(); it != b256.rend(); it++) {
            carry += 58 * (*it);
            *it = carry % 256;
            carry /= 256;
        }
        assert(carry == 0);
        psz++;
    }
    // Skip trailing spaces.
    while (isspace(*psz))
        psz++;
    if (*psz != 0)
        return false;
    // Skip leading zeroes in b256.
    std::vector<unsigned char>::iterator it = b256.begin();
    while (it != b256.end() && *it == 0)
        it++;
    // Copy result into output vector.
    vch.reserve(zeroes + (b256.end() - it));
    vch.assign(zeroes, 0x00);
    while (it != b256.end())
      vch.push_back(*(it++));
    return true;
}

Mine rewrited c# version

 private static string Base58characters = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
public static bool Decode(string source, ref byte[] destination)
        {
            int i = 0;
            while (i < source.Length)
            {
                if (source[i] == 0 || !Char.IsWhiteSpace(source[i]))
                {
                    break;
                }
                i++;
            }
            int zeros = 0;
            while (source[i] == '1')
            {
                zeros++;
                i++;
            }
            byte[] b256 = new byte[(source.Length - i) * 733 / 1000 + 1];
            while (i < source.Length && !Char.IsWhiteSpace(source[i]))
            {
                int ch = Base58characters.IndexOf(source[i]);
                if (ch == -1) //null
                {
                    return false;
            }
            int carry = Base58characters.IndexOf(source[i]);
            for (int k = b256.Length - 1; k > 0; k--)
            {
                carry += 58 * b256[k];
                b256[k] = (byte)(carry % 256);
                carry /= 256;
            }
            i++;
        }
        while (i < source.Length && Char.IsWhiteSpace(source[i]))
        {
            i++;
        }
        if (i != source.Length)
        {
            return false;
        }
        int j = 0;
        while (j < b256.Length && b256[j] == 0)
        {
            j++;
        }
        destination = new byte[zeros + (b256.Length - j)];
        for (int kk = 0; kk < destination.Length; kk++)
        {
            if (kk < zeros)
            {
                destination[kk] = 0x00;
            }
            else
            {
                destination[kk] = b256[j++];
            }
        }
        return true;
    }

Function that I'm using for converting from byte-array to HexString

public static string ByteArrayToHexString(byte[] source)
        {
            return BitConverter.ToString(source).Replace("-", "");
        }

To test if everything is working correctly I've used test cases found online here (https://github.com/ThePiachu/Bitcoin-Unit-Tests/blob/master/Address/Address%20Generation%20Test%201.txt). Good thing is that 97% of this test are passed correctly but for 3 there is a little error and I do not know where it is coming from. So my ask to you is to point me what could for these test go wrong or where in rewriting I've made an error. Thank you in advance.

The test cases where errors occures are 1, 21 and 25.

1. Input: 16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM Output: 000966776006953D5567439E5E39F86A0D273BEED61967F6 Should be: 00010966776006953D5567439E5E39F86A0D273BEED61967F6

21. Input: 1v3VUYGogXD7S1E8kipahj7QXgC568dz1 Output: 0008201462985DF5255E4A6C9D493C932FAC98EF791E2F22 Should be: 000A08201462985DF5255E4A6C9D493C932FAC98EF791E2F22

25. Input: 1axVFjCkMWDFCHjQHf99AsszXTuzxLxxg Output: 006C0B8995C7464E89F6760900EA6978DF18157388421561 Should be: 00066C0B8995C7464E89F6760900EA6978DF18157388421561


Solution

  • In your for-loop:

    for (int k = b256.Length - 1; k > 0; k--)
    

    The loop condition should be k >= 0 so that you don't skip the first byte in b256.