Files
shadowsocks-libev/src/bitcoin.c
T
2015-03-11 14:33:13 +08:00

703 lines
21 KiB
C

/*
* bitcoin.c
*
* Copyright (C) 2015, Kevin Pan <bit.kevin@gmail.com>
*
* This file is part of the shadowsocks-libev.
*
* shadowsocks-libev is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-libev is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with shadowsocks-libev; see the file COPYING. If not, see
* <http://www.gnu.org/licenses/>.
*/
#include "bitcoin.h"
#include <assert.h>
#include <ctype.h>
#include <inttypes.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pthread.h>
#include <unistd.h>
#include <openssl/bn.h>
#include <openssl/ecdsa.h>
#include <openssl/evp.h>
#include <openssl/ripemd.h>
#include <openssl/sha.h>
//
// address prefix
// @see https://en.bitcoin.it/wiki/List_of_address_prefixes
//
#define BITCOIN_ADDRESS_PREFIX_PUBKEY 0x00
#define BITCOIN_ADDRESS_PREFIX_PUBKEY_TESTNET 0x6F
#define BITCOIN_PUBKEY_UNCOMPRESSED 0x00
#define BITCOIN_PUBKEY_COMPRESSED 0x01
#define skip_char(c) \
(((c) == '\r') || ((c) == '\n') || ((c) == ' ') || ((c) == '\t'))
const char *vg_b58_alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
const signed char vg_b58_reverse_map[256] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, -1, -1, -1, -1, -1, -1,
-1, 9, 10, 11, 12, 13, 14, 15, 16, -1, 17, 18, 19, 20, 21, -1,
22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1, -1,
-1, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, -1, 44, 45, 46,
47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};
static int vg_b58_decode_check(const char *input, void *buf, size_t len) {
int i, l, c;
unsigned char *xbuf = NULL;
BIGNUM bn, bnw, bnbase;
BN_CTX *bnctx;
unsigned char hash1[32], hash2[32];
int zpfx;
int res = 0;
BN_init(&bn);
BN_init(&bnw);
BN_init(&bnbase);
BN_set_word(&bnbase, 58);
bnctx = BN_CTX_new();
/* Build a bignum from the encoded value */
l = strlen(input);
for (i = 0; i < l; i++) {
if (skip_char(input[i]))
continue;
c = vg_b58_reverse_map[(int)input[i]];
if (c < 0)
goto out;
BN_clear(&bnw);
BN_set_word(&bnw, c);
BN_mul(&bn, &bn, &bnbase, bnctx);
BN_add(&bn, &bn, &bnw);
}
/* Copy the bignum to a byte buffer */
for (i = 0, zpfx = 0; input[i]; i++) {
if (skip_char(input[i]))
continue;
if (input[i] != vg_b58_alphabet[0])
break;
zpfx++;
}
c = BN_num_bytes(&bn);
l = zpfx + c;
if (l < 5)
goto out;
xbuf = (unsigned char *) malloc(l);
if (!xbuf)
goto out;
if (zpfx)
memset(xbuf, 0, zpfx);
if (c)
BN_bn2bin(&bn, xbuf + zpfx);
/* Check the hash code */
l -= 4;
SHA256(xbuf, l, hash1);
SHA256(hash1, sizeof(hash1), hash2);
if (memcmp(hash2, xbuf + l, 4))
goto out;
/* Buffer verified */
if (len) {
if (len > l)
len = l;
memcpy(buf, xbuf, len);
}
res = l;
out:
if (xbuf)
free(xbuf);
BN_clear_free(&bn);
BN_clear_free(&bnw);
BN_clear_free(&bnbase);
BN_CTX_free(bnctx);
return res;
}
static void vg_b58_encode_check(void *buf, size_t len, char *result)
{
unsigned char hash1[32];
unsigned char hash2[32];
int d, p;
BN_CTX *bnctx;
BIGNUM *bn, *bndiv, *bntmp;
BIGNUM bna, bnb, bnbase, bnrem;
unsigned char *binres;
int brlen, zpfx;
bnctx = BN_CTX_new();
BN_init(&bna);
BN_init(&bnb);
BN_init(&bnbase);
BN_init(&bnrem);
BN_set_word(&bnbase, 58);
bn = &bna;
bndiv = &bnb;
brlen = (2 * len) + 4;
binres = (unsigned char*) malloc(brlen);
memcpy(binres, buf, len);
SHA256(binres, len, hash1);
SHA256(hash1, sizeof(hash1), hash2);
memcpy(&binres[len], hash2, 4);
BN_bin2bn(binres, len + 4, bn);
for (zpfx = 0; zpfx < (len + 4) && binres[zpfx] == 0; zpfx++);
p = (int)brlen;
while (!BN_is_zero(bn)) {
BN_div(bndiv, &bnrem, bn, &bnbase, bnctx);
bntmp = bn;
bn = bndiv;
bndiv = bntmp;
d = BN_get_word(&bnrem);
binres[--p] = vg_b58_alphabet[d];
}
while (zpfx--) {
binres[--p] = vg_b58_alphabet[0];
}
memcpy(result, &binres[p], brlen - p);
result[brlen - p] = '\0';
free(binres);
BN_clear_free(&bna);
BN_clear_free(&bnb);
BN_clear_free(&bnbase);
BN_clear_free(&bnrem);
BN_CTX_free(bnctx);
}
static void vg_encode_address(const EC_POINT *ppoint, const EC_GROUP *pgroup,
point_conversion_form_t form, int addr_type, char *result)
{
unsigned char eckey_buf[128] = {0};
unsigned char binres[21] = {0,};
unsigned char hash1[32];
size_t len = 0;
len = EC_POINT_point2oct(pgroup, ppoint, form,
eckey_buf, sizeof(eckey_buf), NULL);
binres[0] = addr_type;
SHA256(eckey_buf, len, hash1);
RIPEMD160(hash1, sizeof(hash1), &binres[1]);
vg_b58_encode_check(binres, sizeof(binres), result);
}
static size_t write_compact_size(const uint64_t val, uint8_t *dest) {
if (val < 0xfd) {
*dest++ = (unsigned char)val;
return 1;
} else if (val <= 0xffff) {
*dest++ = 0xfd;
*(uint16_t *)dest = (uint16_t)val;
return 2;
} else if (val <= 0xffffffff) {
*dest++ = 0xfe;
*(uint32_t *)dest = (uint32_t)val;
return 4;
} else {
*dest++ = 0xff;
*(uint64_t *)dest = (uint64_t)val;
return 8;
}
return 0;
}
static void dsha265_message(uint8_t *hash,
const uint8_t *msg, const size_t len_msg) {
const char *magic = "Bitcoin Signed Message:\n"; // bitcoin message magic
const size_t len_magic = strlen(magic);
size_t buf_size = len_magic + len_msg + 9/*max_compact_size*/ * 2;
char *buf = (char *)malloc(buf_size);
size_t buf_len = 0;
memset(buf, 0, buf_size);
buf_len += write_compact_size(len_magic, (uint8_t *)buf);
memcpy(buf + buf_len, magic, len_magic);
buf_len += len_magic;
buf_len += write_compact_size(len_msg, (uint8_t *)buf + buf_len);
memcpy(buf + buf_len, msg, len_msg);
buf_len += len_msg;
assert(buf_len <= buf_size);
uint8_t hash1[32];
SHA256((uint8_t *)buf, buf_len, hash1);
SHA256(hash1, sizeof(hash1), hash);
free(buf);
}
static int EC_KEY_regenerate_key(EC_KEY *eckey, BIGNUM *priv_key) {
int ok = 0;
BN_CTX *ctx = NULL;
EC_POINT *pub_key = NULL;
if (!eckey) return 0;
const EC_GROUP *group = EC_KEY_get0_group(eckey);
if ((ctx = BN_CTX_new()) == NULL)
goto err;
pub_key = EC_POINT_new(group);
if (pub_key == NULL)
goto err;
if (!EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, ctx))
goto err;
EC_KEY_set_private_key(eckey,priv_key);
EC_KEY_set_public_key(eckey,pub_key);
ok = 1;
err:
if (pub_key)
EC_POINT_free(pub_key);
if (ctx != NULL)
BN_CTX_free(ctx);
return ok;
}
// Perform ECDSA key recovery (see SEC1 4.1.6) for curves over (mod p)-fields
// recid selects which key is recovered
// if check is non-zero, additional checks are performed
static int ECDSA_SIG_recover_key_GFp(EC_KEY *eckey, ECDSA_SIG *ecsig,
const unsigned char *msg,
int msglen, int recid, int check) {
if (!eckey) return 0;
int ret = 0;
BN_CTX *ctx = NULL;
BIGNUM *x = NULL;
BIGNUM *e = NULL;
BIGNUM *order = NULL;
BIGNUM *sor = NULL;
BIGNUM *eor = NULL;
BIGNUM *field = NULL;
EC_POINT *R = NULL;
EC_POINT *O = NULL;
EC_POINT *Q = NULL;
BIGNUM *rr = NULL;
BIGNUM *zero = NULL;
int n = 0;
int i = recid / 2;
const EC_GROUP *group = EC_KEY_get0_group(eckey);
if ((ctx = BN_CTX_new()) == NULL) { ret = -1; goto err; }
BN_CTX_start(ctx);
order = BN_CTX_get(ctx);
if (!EC_GROUP_get_order(group, order, ctx)) { ret = -2; goto err; }
x = BN_CTX_get(ctx);
if (!BN_copy(x, order)) { ret=-1; goto err; }
if (!BN_mul_word(x, i)) { ret=-1; goto err; }
if (!BN_add(x, x, ecsig->r)) { ret=-1; goto err; }
field = BN_CTX_get(ctx);
if (!EC_GROUP_get_curve_GFp(group, field, NULL, NULL, ctx)) { ret=-2; goto err; }
if (BN_cmp(x, field) >= 0) { ret=0; goto err; }
if ((R = EC_POINT_new(group)) == NULL) { ret = -2; goto err; }
if (!EC_POINT_set_compressed_coordinates_GFp(group, R, x, recid % 2, ctx)) { ret=0; goto err; }
if (check) {
if ((O = EC_POINT_new(group)) == NULL) { ret = -2; goto err; }
if (!EC_POINT_mul(group, O, NULL, R, order, ctx)) { ret=-2; goto err; }
if (!EC_POINT_is_at_infinity(group, O)) { ret = 0; goto err; }
}
if ((Q = EC_POINT_new(group)) == NULL) { ret = -2; goto err; }
n = EC_GROUP_get_degree(group);
e = BN_CTX_get(ctx);
if (!BN_bin2bn(msg, msglen, e)) { ret=-1; goto err; }
if (8*msglen > n) BN_rshift(e, e, 8-(n & 7));
zero = BN_CTX_get(ctx);
if (!BN_zero(zero)) { ret=-1; goto err; }
if (!BN_mod_sub(e, zero, e, order, ctx)) { ret=-1; goto err; }
rr = BN_CTX_get(ctx);
if (!BN_mod_inverse(rr, ecsig->r, order, ctx)) { ret=-1; goto err; }
sor = BN_CTX_get(ctx);
if (!BN_mod_mul(sor, ecsig->s, rr, order, ctx)) { ret=-1; goto err; }
eor = BN_CTX_get(ctx);
if (!BN_mod_mul(eor, e, rr, order, ctx)) { ret=-1; goto err; }
if (!EC_POINT_mul(group, Q, eor, R, sor, ctx)) { ret=-2; goto err; }
if (!EC_KEY_set_public_key(eckey, Q)) { ret=-2; goto err; }
ret = 1;
err:
if (ctx) {
BN_CTX_end(ctx);
BN_CTX_free(ctx);
}
if (R != NULL) EC_POINT_free(R);
if (O != NULL) EC_POINT_free(O);
if (Q != NULL) EC_POINT_free(Q);
return ret;
}
static int priv_key_b58_to_address(const char *priv_key_b58,
const int is_compressed_pubkey,
const int addr_type,
char *address) {
EC_KEY *pkey = NULL;
unsigned char buf[128] = {0};
uint8_t pubKey[65]; // public key max size is 65 bytes
char ecprot[128];
unsigned char *pbegin = NULL;
int res, pubkey_size = 0;
int fOk = 0;
pkey = EC_KEY_new_by_curve_name(NID_secp256k1);
EC_KEY_set_conv_form(pkey, is_compressed_pubkey ?
POINT_CONVERSION_COMPRESSED : POINT_CONVERSION_UNCOMPRESSED);
// import secret
vg_b58_decode_check(priv_key_b58, buf, 33);
BIGNUM *bn = BN_bin2bn(buf + 1, 32, BN_new());
res = EC_KEY_regenerate_key(pkey, bn);
BN_clear_free(bn);
if (!res){ goto error; }
// get pubkey
pubkey_size = i2o_ECPublicKey(pkey, NULL);
if (!pubkey_size) { goto error; }
pbegin = pubKey;
if (i2o_ECPublicKey(pkey, &pbegin) != pubkey_size) { goto error; }
// encode address
vg_encode_address(EC_KEY_get0_public_key(pkey),
EC_KEY_get0_group(pkey),
is_compressed_pubkey ?
POINT_CONVERSION_COMPRESSED : POINT_CONVERSION_UNCOMPRESSED,
addr_type, ecprot);
strcpy(address, ecprot);
fOk = 1;
error:
if (pkey) { EC_KEY_free(pkey); }
return fOk;
}
//
// return:
// -1 : invalid private key
// 1 : compressed
// 0 : uncompressed
static int isCompressedAddress(const char *priv_key_b58, const char *address) {
int is_compressed_pubkey;
char buf[64];
int res;
// try compressed
is_compressed_pubkey = 1;
memset(buf, 0, sizeof(buf));
res = priv_key_b58_to_address(priv_key_b58, is_compressed_pubkey,
BITCOIN_ADDRESS_PREFIX_PUBKEY, buf);
if (res != 1) { return -1; }
if (memcmp(buf, address, strlen(address)) == 0) {
return 1; // compressed
}
// try uncompressed
is_compressed_pubkey = 0;
memset(buf, 0, sizeof(buf));
res = priv_key_b58_to_address(priv_key_b58, is_compressed_pubkey,
BITCOIN_ADDRESS_PREFIX_PUBKEY, buf);
if (res != 1) { return -1; }
if (memcmp(buf, address, strlen(address)) == 0) {
return 0; // uncompressed
}
return -1;
}
static int sign_message(uint8_t *signature_65,
const uint8_t *msg, const size_t msg_len,
const char *priv_key_b58, int is_compressed_pubkey) {
EC_KEY *pkey = NULL;
ECDSA_SIG *sig = NULL;
EC_KEY *eckey = NULL; // recover key
uint8_t pubKey[65]; // public key max size is 65 bytes
uint8_t pubKey_rc[65];
int pubkey_size, pubkey_rc_size;
uint8_t sigbuf[65];
unsigned char *pbegin = NULL;
unsigned char buf[128] = {0};
int res, fOK = 0;
int nBitsR, nBitsS;
unsigned char hash[32];
dsha265_message(hash, msg, msg_len); // message double sha256
pkey = EC_KEY_new_by_curve_name(NID_secp256k1);
EC_KEY_set_conv_form(pkey, is_compressed_pubkey ?
POINT_CONVERSION_COMPRESSED : POINT_CONVERSION_UNCOMPRESSED);
// import secret
vg_b58_decode_check(priv_key_b58, buf, 33);
BIGNUM *bn = BN_bin2bn(buf + 1, 32, BN_new());
res = EC_KEY_regenerate_key(pkey, bn);
BN_clear_free(bn);
memset(buf, 0, sizeof(buf));
if (!res){ goto error; }
// get pubkey
pubkey_size = i2o_ECPublicKey(pkey, NULL);
if (!pubkey_size) { goto error; }
pbegin = pubKey;
if (i2o_ECPublicKey(pkey, &pbegin) != pubkey_size) { goto error; }
// do sign
sig = ECDSA_do_sign(hash, sizeof(hash), pkey);
if (!sig) { goto error; }
nBitsR = BN_num_bits(sig->r);
nBitsS = BN_num_bits(sig->s);
if (nBitsR <= 256 && nBitsS <= 256) {
int nRecId = -1;
int i;
eckey = EC_KEY_new_by_curve_name(NID_secp256k1);
EC_KEY_set_conv_form(eckey, is_compressed_pubkey ?
POINT_CONVERSION_COMPRESSED : POINT_CONVERSION_UNCOMPRESSED);
for (i = 0; i < 4; i++) {
if (ECDSA_SIG_recover_key_GFp(eckey, sig, (unsigned char*)hash,
sizeof(hash), i, 1) == 1) {
// get recover pubkey
pubkey_rc_size = i2o_ECPublicKey(pkey, NULL);
if (!pubkey_rc_size) { goto error; }
pbegin = pubKey_rc;
if (i2o_ECPublicKey(eckey, &pbegin) != pubkey_rc_size) {
goto error;
}
// check recover key
if (pubkey_size == pubkey_rc_size &&
memcmp(pubKey, pubKey_rc, pubkey_rc_size) == 0) {
nRecId = i;
break;
}
}
}
if (nRecId == -1) { goto error; }
sigbuf[0] = nRecId + 27 + (is_compressed_pubkey ? 4 : 0);
BN_bn2bin(sig->r, sigbuf + 33 - (nBitsR+7)/8);
BN_bn2bin(sig->s, sigbuf + 65 - (nBitsS+7)/8);
memcpy(signature_65, sigbuf, 65);
fOK = 1;
}
error:
if (pkey) { EC_KEY_free(pkey); }
if (eckey) { EC_KEY_free(eckey); }
if (sig) { ECDSA_SIG_free(sig); }
return fOK;
}
int bitcoin_sign_message(unsigned char *buf_65,
const void *msg, const size_t msg_len,
const char *priv_key_b58, const char *address) {
int is_compressed = isCompressedAddress(priv_key_b58, address);
return sign_message(buf_65, (uint8_t *)msg, msg_len,
priv_key_b58, is_compressed);
}
int bitcoin_verify_message(const char *address, const unsigned char *sig,
const void *msg, const size_t msglen) {
EC_KEY *pkey = EC_KEY_new_by_curve_name(NID_secp256k1);
uint8_t hash[32] = {0};
char ecprot[128] = {0};
int fOK = 0;
// message double sha256
dsha265_message(hash, msg, msglen);
// recover
ECDSA_SIG *esig = ECDSA_SIG_new();
BN_bin2bn(&sig[1], 32, esig->r);
BN_bin2bn(&sig[33], 32, esig->s);
int ret = ECDSA_SIG_recover_key_GFp(pkey, esig, hash, sizeof(hash),
((sig[0] - 27) & ~4), 0) == 1;
ECDSA_SIG_free(esig);
if (!ret) { goto error; }
int is_compressed_pubkey = (sig[0] - 27) & 4;
// encode address
vg_encode_address(EC_KEY_get0_public_key(pkey),
EC_KEY_get0_group(pkey),
is_compressed_pubkey ?
POINT_CONVERSION_COMPRESSED : POINT_CONVERSION_UNCOMPRESSED,
BITCOIN_ADDRESS_PREFIX_PUBKEY, ecprot);
if (memcmp(address, ecprot, strlen(address)) == 0) {
fOK = 1;
}
error:
if (pkey) { EC_KEY_free(pkey); }
return fOK;
}
struct btc_client {
char address[36];
};
struct btc_list {
struct btc_client *clients;
size_t number;
char *file;
time_t last_check_time;
time_t last_modify_time;
pthread_mutex_t lock;
};
static int cmp_btc_client(const void *l, const void *r) {
struct btc_client *pl = (struct btc_client *)l;
struct btc_client *pr = (struct btc_client *)r;
return strcmp(pl->address, pr->address);
}
extern struct btc_list *bitcoin_init_list(const char *file) {
struct btc_list *l = calloc(1, sizeof(struct btc_list));
l->clients = NULL;
pthread_mutex_init(&l->lock, NULL);
l->number = 0;
l->file = strdup(file);
l->last_check_time = 0;
l->last_modify_time = 0;
return l;
}
extern size_t bitcoin_tryload_list(struct btc_list *list) {
struct stat attrib;
FILE *f = NULL;
size_t size = 8;
size_t idx = 0;
struct btc_client *clients = NULL;
char line[64];
int is_need_update = 0;
pthread_mutex_lock(&list->lock);
// update when: 1. last check time over than 10 seconds
// 2. file has been modified
if (time(NULL) > list->last_check_time + 10) {
list->last_check_time = time(NULL);
stat(list->file, &attrib);
if (list->last_modify_time != attrib.st_mtime) {
is_need_update = 1;
}
}
pthread_mutex_unlock(&list->lock);
if (is_need_update == 0) {
return 0;
}
f = fopen(list->file, "rb");
if (f == NULL) {
return 0;
}
clients = calloc(size, sizeof(struct btc_client));
while (fgets(line, sizeof(line), f)) {
while (strlen(line) > 0 && !isalpha(line[strlen(line) - 1])) {
line[strlen(line) - 1] = '\0';
}
if (strlen(line) > 35 || strlen(line) < 26) {
continue; // bitcoin address length range: [26, 35]
}
if (idx >= size) {
size *= 2;
clients = realloc(clients, size * sizeof(struct btc_client));
}
struct btc_client *c = clients + idx;
strcpy(c->address, line);
idx++;
}
fclose(f);
if (idx == 0) {
free(clients);
return 0;
}
assert(idx <= size);
if (idx != size) {
clients = realloc(clients, idx * sizeof(struct btc_client));
}
stat(list->file, &attrib);
qsort(clients, idx, sizeof(struct btc_client), cmp_btc_client);
pthread_mutex_lock(&list->lock);
if (list->clients != NULL) {
free(list->clients);
}
list->clients = clients;
list->number = idx;
list->last_check_time = time(NULL);
list->last_modify_time = attrib.st_mtime;
pthread_mutex_unlock(&list->lock);
return idx;
}
extern int bitcoin_check_address(struct btc_list *list,
const char *address) {
struct btc_client key, *res;
memset(&key, 0, sizeof(struct btc_client));
strncpy(key.address, address, 35);
bitcoin_tryload_list(list);
pthread_mutex_lock(&list->lock);
res = bsearch(&key, list->clients, list->number,
sizeof(struct btc_client), cmp_btc_client);
pthread_mutex_unlock(&list->lock);
if (res != NULL) {
return 1;
}
return 0;
}