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;
}
}
}