12
\$\begingroup\$

Here is a Bitcoin address validator I am looking to have reviewed in C. Normally I would have the enum and function prototypes declared in a header file, but I decided for the purpose of this question to integrate them into one for easy copying and compilation.

/**
 * @file bitcoin.c
 * @brief Bitcoin address validation
 */

#include <stdio.h>
#include <string.h>
#include <openssl/sha.h>

typedef enum {VALID, BAD_CHAR, BAD_LENGTH, BAD_DIGEST} BitcoinAddressState;

/**
 * @fn BitcoinAddressState unbase58(const char *s, unsigned char *out)
 * @brief Takes a base58 encoded address and decodes it into the receiver. Errors are returned if the argument is not valid base58 or if the decoded value does not fit in the 25 byte address.
 * @param addr The Bitcoin address
 * @param out The receiver of the decoded address
 * @return The state of decoding the Bitcoin address
 */
BitcoinAddressState unbase58(const char *addr, unsigned char *out)
{
    /* A bitcoin address uses a base58 encoding, which uses an alphabet of the characters 0 .. 9, A ..Z, a .. z, but without the four characters 0, O, I and l. */
    const char *tmpl = "123456789"
                       "ABCDEFGHJKLMNPQRSTUVWXYZ"
                       "abcdefghijkmnopqrstuvwxyz";
    const char *p;

    memset(out, 0, 25);
    for (int i = 0; addr[i]; ++i)
    {
        if (!(p = strchr(tmpl, addr[i]))) return BAD_CHAR;

        size_t c = p - tmpl;
        for (int j = 25; --j; )
        {
            c += 58 * out[j];
            out[j] = c % 256;
            c /= 256;
        }
        if (c) return BAD_LENGTH;
    }
    return VALID;
}

/**
 * @fn BitcoinAddressState validateAddress(const char *s)
 * @brief Takes a Bitcoin address as argument, and checks whether or not the address is valid.
 * @param addr The Bitcoin address
 * @return The state of the Bitcoin address
 */
BitcoinAddressState validateAddress(const char *addr)
{
    unsigned char dec[32];
    unsigned char d1[SHA256_DIGEST_LENGTH];
    unsigned char d2[SHA256_DIGEST_LENGTH];
    BitcoinAddressState val = unbase58(addr, dec);

    if (val) return val;

    SHA256(SHA256(dec, 21, d1), SHA256_DIGEST_LENGTH, d2);

    if (memcmp(dec + 21, d2, 4)) return BAD_DIGEST;

    return VALID;
}

int main (void) {
    const char *addresses[] =
    {
        "1EYDqUKuLKF9di1pSEtEfNA8pj2CgL7Wne", // my actual Bitcoin wallet address
        "1EYDqUKuLKF9di1pSEtEfNA8pj3CgL7Wne", // bad digest
        "1EYDqUKuLKF9di1pSEtEfNA8pj2CgL0Wne", // bad char
        "1EYDqUKuLKF9di1pSEtEfNA8pj2cgL7Wnef", // bad length
        0
    };
    for (int i = 0; addresses[i]; ++i)
    {
        printf("%s: ", addresses[i]);
        switch (validateAddress(addresses[i]))
        {
            case VALID:
                puts("Okay");
                break;
            case BAD_CHAR:
                puts("Invalid character in address");
                break;
            case BAD_LENGTH:
                puts("Address length is incorrect");
                break;
            case BAD_DIGEST:
                puts("Bad digest");
                break;
            default:
                puts("An unknown error occurred");
                break;
        }
    }
}
\$\endgroup\$
1
  • \$\begingroup\$ there is a c99 and a c11 I didn't edit either in because I don't know which one this is I was thinking that I just put in c99 because it sounds like that is the lowest one that would be possible given your comment on @vnp's answer \$\endgroup\$
    – Malachi
    Jul 28, 2014 at 13:32

1 Answer 1

7
\$\begingroup\$
  • It would be nice to include a link to an authoritative reference. Until then, no comment on the algorithm compliance is possible.

  • Instead of switch I'd rather have a table of strings indexed by BitcoinAddressState values.

  • In C, main must return a value explicitly.

\$\endgroup\$
3
  • 3
    \$\begingroup\$ Rebuttling your last point: C99 & C11 §5.1.2.2(3) says, "...reaching the } that terminates the main() function returns a value of 0." \$\endgroup\$
    – syb0rg
    Jul 24, 2014 at 22:25
  • 2
    \$\begingroup\$ I was probably too picky. The tag says "C" with no qualification... \$\endgroup\$
    – vnp
    Jul 24, 2014 at 22:39
  • \$\begingroup\$ When no standard version is mentioned, I feel it makes little sense to assume a 30 year old version (C89) rather than the current version (C11) or the one before that (C99), even for C. (That said, I agree that main should nonetheless return an explicit value.) \$\endgroup\$
    – Will
    Aug 1, 2019 at 17:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.