Files
lightning/libwally-core/src/bip39.c
2017-02-21 15:15:28 +10:30

241 lines
7.1 KiB
C

#include "internal.h"
#include "mnemonic.h"
#include "wordlist.h"
#include "hmac.h"
#include "ccan/ccan/crypto/sha256/sha256.h"
#include "ccan/ccan/crypto/sha512/sha512.h"
#include <include/wally_bip39.h>
#include <include/wally_crypto.h>
#include "data/wordlists/chinese_simplified.c"
#include "data/wordlists/chinese_traditional.c"
#include "data/wordlists/english.c"
#include "data/wordlists/french.c"
#include "data/wordlists/italian.c"
#include "data/wordlists/spanish.c"
#include "data/wordlists/japanese.c"
/* Maximum length including up to 2 bytes for checksum */
#define BIP39_ENTROPY_LEN_MAX (BIP39_ENTROPY_LEN_320 + sizeof(unsigned char) * 2)
static const struct {
const char name[4];
const struct words *words;
} lookup[] = {
{ "en", &en_words}, { "es", &es_words}, { "fr", &fr_words},
{ "it", &it_words}, { "jp", &jp_words}, { "zhs", &zhs_words},
{ "zht", &zht_words},
/* FIXME: Should 'zh' map to traditional or simplified? */
};
int bip39_get_languages(char **output)
{
if (!output)
return WALLY_EINVAL;
*output = wally_strdup("en es fr it jp zhs zht");
return *output ? WALLY_OK : WALLY_ENOMEM;
}
int bip39_get_wordlist(const char *lang, const struct words **output)
{
size_t i;
if (!output)
return WALLY_EINVAL;
*output = &en_words; /* Fallback to English if not found */
if (lang)
for (i = 0; i < sizeof(lookup) / sizeof(lookup[0]); ++i)
if (!strcmp(lang, lookup[i].name)) {
*output = lookup[i].words;
break;
}
return WALLY_OK;
}
int bip39_get_word(const struct words *w, size_t idx,
char **output)
{
const char *word;
if (output)
*output = NULL;
w = w ? w : &en_words;
if (!output || !(word = wordlist_lookup_index(w, idx)))
return WALLY_EINVAL;
*output = word ? wally_strdup(word) : NULL;
return *output ? WALLY_OK : WALLY_ENOMEM;
}
/* Convert an input entropy length to a mask for checksum bits. As it
* returns 0 for bad lengths, it serves as a validation function too.
*/
static size_t len_to_mask(size_t len)
{
switch (len) {
case BIP39_ENTROPY_LEN_128: return 0xf0;
case BIP39_ENTROPY_LEN_160: return 0xf8;
case BIP39_ENTROPY_LEN_192: return 0xfc;
case BIP39_ENTROPY_LEN_224: return 0xfe;
case BIP39_ENTROPY_LEN_256: return 0xff;
case BIP39_ENTROPY_LEN_288: return 0x80ff;
case BIP39_ENTROPY_LEN_320: return 0xC0ff;
}
return 0;
}
static size_t bip39_checksum(const unsigned char *bytes_in, size_t len_in, size_t mask)
{
struct sha256 sha;
size_t ret;
sha256(&sha, bytes_in, len_in);
ret = sha.u.u8[0] | (sha.u.u8[1] << 8);
clear(&sha, sizeof(sha));
return ret & mask;
}
int bip39_mnemonic_from_bytes(const struct words *w,
const unsigned char *bytes_in, size_t len_in,
char **output)
{
unsigned char tmp_bytes[BIP39_ENTROPY_LEN_MAX];
size_t checksum, mask;
if (output)
*output = NULL;
if (!bytes_in || !len_in || !output)
return WALLY_EINVAL;
w = w ? w : &en_words;
if (w->bits != 11u || !(mask = len_to_mask(len_in)))
return WALLY_EINVAL;
memcpy(tmp_bytes, bytes_in, len_in);
checksum = bip39_checksum(bytes_in, len_in, mask);
tmp_bytes[len_in] = checksum & 0xff;
if (mask > 0xff)
tmp_bytes[++len_in] = (checksum >> 8) & 0xff;
*output = mnemonic_from_bytes(w, tmp_bytes, len_in + 1);
clear(tmp_bytes, sizeof(tmp_bytes));
return *output ? WALLY_OK : WALLY_ENOMEM;
}
static bool checksum_ok(const unsigned char *bytes, size_t idx, size_t mask)
{
/* The checksum is stored after the data to sum */
size_t calculated = bip39_checksum(bytes, idx, mask);
size_t stored = bytes[idx];
if (mask > 0xff)
stored |= (bytes[idx + 1] << 8);
return (stored & mask) == calculated;
}
int bip39_mnemonic_to_bytes(const struct words *w, const char *mnemonic,
unsigned char *bytes_out, size_t len,
size_t *written)
{
unsigned char tmp_bytes[BIP39_ENTROPY_LEN_MAX];
size_t mask, tmp_len;
int ret;
/* Ideally we would infer the wordlist here. Unfortunately this cannot
* work reliably because the default word lists overlap. In combination
* with being sorted lexographically, this means the default lists
* were poorly chosen. But we are stuck with them now.
*
* If the caller doesn't know which word list to use, they should iterate
* over the available ones and try any resulting list that the mnemonic
* validates against.
*/
w = w ? w : &en_words;
if (written)
*written = 0;
if (w->bits != 11u || !mnemonic || !bytes_out)
return WALLY_EINVAL;
ret = mnemonic_to_bytes(w, mnemonic, tmp_bytes, sizeof(tmp_bytes), &tmp_len);
if (!ret) {
/* Remove checksum bytes from the output length */
--tmp_len;
if (tmp_len > BIP39_ENTROPY_LEN_256)
--tmp_len; /* Second byte required */
if (tmp_len > sizeof(tmp_bytes))
ret = WALLY_EINVAL; /* Too big for biggest supported entropy */
else {
if (tmp_len <= len) {
if (!(mask = len_to_mask(tmp_len)) ||
!checksum_ok(tmp_bytes, tmp_len, mask)) {
tmp_len = 0;
ret = WALLY_EINVAL; /* Bad checksum */
}
else
memcpy(bytes_out, tmp_bytes, tmp_len);
}
}
}
clear(tmp_bytes, sizeof(tmp_bytes));
if (!ret && written)
*written = tmp_len;
return ret;
}
int bip39_mnemonic_validate(const struct words *w, const char *mnemonic)
{
unsigned char buf[BIP39_ENTROPY_LEN_MAX];
size_t len;
int ret = bip39_mnemonic_to_bytes(w, mnemonic, buf, sizeof(buf), &len);
clear(buf, sizeof(buf));
return ret;
}
int bip39_mnemonic_to_seed(const char *mnemonic, const char *password,
unsigned char *bytes_out, size_t len,
size_t *written)
{
const size_t bip9_cost = 2048u;
const char *prefix = "mnemonic";
const size_t prefix_len = strlen(prefix);
const size_t password_len = password ? strlen(password) : 0;
const size_t salt_len = prefix_len + password_len + PBKDF2_HMAC_EXTRA_LEN;
unsigned char *salt;
int ret;
if (written)
*written = 0;
if (!mnemonic || !bytes_out || len != BIP39_SEED_LEN_512)
return WALLY_EINVAL;
salt = wally_malloc(salt_len);
if (!salt)
return WALLY_ENOMEM;
memcpy(salt, prefix, prefix_len);
if (password_len)
memcpy(salt + prefix_len, password, password_len);
ret = wally_pbkdf2_hmac_sha512((unsigned char *)mnemonic, strlen(mnemonic),
salt, salt_len,
PBKDF2_HMAC_FLAG_BLOCK_RESERVED,
bip9_cost, bytes_out, len);
if (!ret && written)
*written = BIP39_SEED_LEN_512; /* Succeeded */
clear(salt, salt_len);
wally_free(salt);
return ret;
}