src/extkey.cpp
// Copyright (c) 2014-2015 The ShadowCoin developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "extkey.h"
#include "util.h"
#include "base58.h"
#include "state.h"
#include "wallet.h"
#include "init.h" // for pwalletMain
#include "wordlists/english.h"
#include "wordlists/french.h"
#include "wordlists/japanese.h"
#include "wordlists/spanish.h"
#include "wordlists/chinese_simplified.h"
#include "wordlists/chinese_traditional.h"
#include <stdint.h>
#include <openssl/rand.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
CCriticalSection cs_extKey;
const char *ExtKeyGetString(int ind)
{
switch (ind)
{
case 2: return "Path string too long";
case 3: return "Path string empty";
case 4: return "Integer conversion invalid character";
case 5: return "Integer conversion out of range";
case 6: return "'0' only valid at start or end";
case 7: return "Malformed path";
case 8: return "Offset is hardened already";
case 9: return "Can't use BIP44 key as master";
case 10: return "Ext key not found in wallet";
case 11: return "This key is already the master key";
case 12: return "Derived key already exists in wallet";
case 13: return "Failed to unlock";
default: return "Unknown error, check log";
};
};
std::vector<uint8_t> &SetCompressedInt64(std::vector<uint8_t> &v, uint64_t n)
{
int b = GetNumBytesReqForInt(n);
v.resize(b);
if (b > 0)
memcpy(&v[0], (uint8_t*) &n, b);
return v;
};
int64_t GetCompressedInt64(const std::vector<uint8_t> &v, uint64_t &n)
{
int b = v.size();
n = 0;
if (b < 1)
n = 0;
else
if (b < 9)
memcpy((uint8_t*) &n, &v[0], b);
return (int64_t)n;
};
std::vector<uint8_t> &SetCKeyID(std::vector<uint8_t> &v, CKeyID n)
{
v.resize(20);
memcpy(&v[0], (uint8_t*) &n, 20);
return v;
};
bool GetCKeyID(const std::vector<uint8_t> &v, CKeyID &n)
{
if (v.size() != 20)
return false;
memcpy((uint8_t*) &n, &v[0], 20);
return true;
};
std::vector<uint8_t> &SetString(std::vector<uint8_t> &v, const char *s)
{
size_t len = strlen(s);
v.resize(len);
memcpy(&v[0], (uint8_t*) &s, len);
return v;
};
std::vector<uint8_t> &SetChar(std::vector<uint8_t> &v, const uint8_t c)
{
v.resize(1);
v[0] = c;
return v;
};
std::vector<uint8_t> &PushUInt32(std::vector<uint8_t> &v, const uint32_t i)
{
size_t o = v.size();
v.resize(o+4);
memcpy(&v[o], (uint8_t*) &i, 4);
return v;
};
#define _UINT32_MAX (0xffffffff)
static uint32_t strtou32max(const char *nptr, int base)
{
const char *s;
uintmax_t acc;
char c;
uintmax_t cutoff;
int neg, any, cutlim;
s = nptr;
do {
c = *s++;
} while (isspace((unsigned char)c));
if (c == '-') {
neg = 1;
c = *s++;
} else {
neg = 0;
if (c == '+')
c = *s++;
}
if ((base == 0 || base == 16) &&
c == '0' && (*s == 'x' || *s == 'X') &&
((s[1] >= '0' && s[1] <= '9') ||
(s[1] >= 'A' && s[1] <= 'F') ||
(s[1] >= 'a' && s[1] <= 'f'))) {
c = s[1];
s += 2;
base = 16;
}
if (base == 0)
base = c == '0' ? 8 : 10;
acc = any = 0;
if (base < 2 || base > 36)
goto noconv;
cutoff = _UINT32_MAX / base;
cutlim = _UINT32_MAX % base;
for ( ; ; c = *s++) {
if (c >= '0' && c <= '9')
c -= '0';
else if (c >= 'A' && c <= 'Z')
c -= 'A' - 10;
else if (c >= 'a' && c <= 'z')
c -= 'a' - 10;
else
break;
if (c >= base)
break;
if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim))
any = -1;
else {
any = 1;
acc *= base;
acc += c;
}
}
if (any < 0) {
acc = _UINT32_MAX;
errno = ERANGE;
} else if (!any) {
noconv:
errno = EINVAL;
} else if (neg)
acc = -acc;
return (acc);
};
static inline int validDigit(char c, int base)
{
switch(base)
{
case 2: return c == '0' || c == '1';
case 10: return c >= '0' && c <= '9';
case 16: return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
default: errno = EINVAL;
};
return 0;
};
int ExtractExtKeyPath(const std::string &sPath, std::vector<uint32_t> &vPath)
{
char data[512];
vPath.clear();
if (sPath.length() > sizeof(data) -2)
return 2;
if (sPath.length() < 1)
return 3;
memcpy(data, sPath.data(), sPath.length());
data[sPath.length()] = '\0';
int nSlashes = 0;
for (size_t k = 0; k < sPath.length(); ++k)
{
if (sPath[k] == '/')
{
nSlashes++;
// - catch start or end '/', and '//'
if (k == 0
|| k == sPath.length()-1
|| (k < sPath.length()-1 && sPath[k+1] == '/'))
return 7;
};
};
vPath.reserve(nSlashes + 1);
char *p = strtok(data, "/");
while (p)
{
uint32_t nChild;
if (tolower(*p) == 'm')
{
nChild = 0;
} else
{
bool fHarden = false;
// - don't allow octal, only hex and binary
int nBase = *p == '0' && (*(p+1) == 'b' || *(p+1) == 'B') ? 2
: *p == '0' && (*(p+1) == 'x' || *(p+1) == 'X') ? 16 : 10;
if (nBase != 10)
p += 2; // step over 0b / 0x
char *ps = p;
for (; *p; ++p)
{
// -last char can be (h, H ,')
if (!*(p+1) && (tolower(*p) == 'h' || *p == '\''))
{
fHarden = true;
*p = '\0';
} else
if (!validDigit(*p, nBase))
return 4;
};
errno = 0;
nChild = strtou32max(ps, nBase);
if (errno != 0)
return 5;
if (fHarden)
{
if ((nChild >> 31) == 0)
{
nChild |= 1 << 31;
} else
{
return 8;
};
};
};
vPath.push_back(nChild);
p = strtok(NULL, "/");
if (nChild == 0
&& vPath.size() != 1
&& p)
return 6;
};
if (vPath.size() < 1)
return 3;
return 0;
};
int PathToString(const std::vector<uint8_t> &vPath, std::string &sPath, char cH)
{
sPath = "";
if (vPath.size() % 4 != 0)
return 1;
sPath = "m";
for (size_t o = 0; o < vPath.size(); o+=4)
{
uint32_t n;
memcpy(&n, &vPath[o], 4);
sPath += "/";
bool fHardened = false;
if ((n >> 31) == 1)
{
n &= ~(1 << 31);
fHardened = true;
};
sPath += strprintf("%u", n);
if (fHardened)
sPath += cH;
};
return 0;
};
std::string CEKAStealthKey::ToStealthAddress() const
{
// - return base58 encoded public stealth address
std::vector<uint8_t> raw;
raw = Params().Base58Prefix(CChainParams::STEALTH_ADDRESS);
raw.push_back(nFlags);
raw.insert(raw.end(), pkScan.begin(), pkScan.end());
raw.push_back(1); // number of spend pubkeys
raw.insert(raw.end(), pkSpend.begin(), pkSpend.end());
raw.push_back(0); // number of signatures
raw.push_back(0); // ?
AppendChecksum(raw);
return EncodeBase58(raw);
};
int CEKAStealthKey::SetSxAddr(CStealthAddress &sxAddr)
{
sxAddr.scan_pubkey = pkScan;
sxAddr.spend_pubkey = pkSpend;
sxAddr.scan_secret.resize(EC_SECRET_SIZE);
memcpy(&sxAddr.scan_secret[0], skScan.begin(), EC_SECRET_SIZE);
return 0;
};
std::string CStoredExtKey::GetIDString58() const
{
CBitcoinAddress addr;
addr.Set(kp.GetID(), CChainParams::EXT_KEY_HASH);
return addr.ToString();
};
std::string CExtKeyAccount::GetIDString58() const
{
// - 0th chain is always account chain
if (vExtKeyIDs.size() < 1)
return "Not Set";
CBitcoinAddress addr;
addr.Set(vExtKeyIDs[0], CChainParams::EXT_ACC_HASH);
return addr.ToString();
};
int CExtKeyAccount::HaveKey(const CKeyID &id, bool fUpdate, CEKAKey &ak)
{
// - rv 0 = no, 1 = yes, 2 = lookahead, 3 = lookahead + updated
LOCK(cs_account);
// - if fUpdate, promote key if found in look ahead
AccKeyMap::const_iterator mi = mapKeys.find(id);
if (mi != mapKeys.end())
return 1;
mi = mapLookAhead.find(id);
if (mi != mapLookAhead.end())
{
CBitcoinAddress addr(mi->first);
if (fDebug)
LogPrintf("HaveKey in lookAhead %s\n", addr.ToString().c_str());
if (fUpdate)
{
ak = mi->second; // pass up for save to db
return 3;
};
return 2;
};
AccKeySCMap::const_iterator miSck = mapStealthChildKeys.find(id);
if (miSck != mapStealthChildKeys.end())
return 1;
return 0;
};
bool CExtKeyAccount::GetKey(const CKeyID &id, CKey &keyOut) const
{
LOCK(cs_account);
AccKeyMap::const_iterator mi;
AccKeySCMap::const_iterator miSck;
if ((mi = mapKeys.find(id)) != mapKeys.end())
{
if (!GetKey(mi->second, keyOut))
return false;
} else
if ((miSck = mapStealthChildKeys.find(id)) != mapStealthChildKeys.end())
{
if (!GetKey(miSck->second, keyOut))
return false;
} else
{
return false;
};
// [rm] necessary?
if (fDebug && keyOut.GetPubKey().GetID() != id)
{
return error("Stored key mismatch.");
};
return true;
}
bool CExtKeyAccount::GetKey(const CEKAKey &ak, CKey &keyOut) const
{
LOCK(cs_account);
if (ak.nParent >= vExtKeys.size())
return error("%s: Account key invalid parent ext key %d, account %s.", __func__, ak.nParent, GetIDString58().c_str());
const CStoredExtKey *chain = vExtKeys[ak.nParent];
if (chain->fLocked)
return error("%s: Chain locked, account %s.", __func__, GetIDString58().c_str());
if (!chain->kp.Derive(keyOut, ak.nKey))
return false;
return true;
};
bool CExtKeyAccount::GetKey(const CEKASCKey &asck, CKey &keyOut) const
{
LOCK(cs_account);
AccStealthKeyMap::const_iterator miSk = mapStealthKeys.find(asck.idStealthKey);
if (miSk == mapStealthKeys.end())
return error("%s: CEKASCKey Stealth key not in this account!", __func__);
return (0 == ExpandStealthChildKey(&miSk->second, asck.sShared, keyOut));
};
bool CExtKeyAccount::GetPubKey(const CKeyID &id, CPubKey &pkOut) const
{
LOCK(cs_account);
AccKeyMap::const_iterator mi;
AccKeySCMap::const_iterator miSck;
if ((mi = mapKeys.find(id)) != mapKeys.end())
{
if (!GetPubKey(mi->second, pkOut))
return false;
} else
if ((miSck = mapStealthChildKeys.find(id)) != mapStealthChildKeys.end())
{
if (!GetPubKey(miSck->second, pkOut))
return false;
} else
{
return false;
};
if (fDebug) // [rm] necessary
{
if (pkOut.GetID() != id)
return errorN(1, "%s: Extracted public key mismatch.", __func__);
};
return true;
};
bool CExtKeyAccount::GetPubKey(const CEKAKey &ak, CPubKey &pkOut) const
{
LOCK(cs_account);
if (ak.nParent >= vExtKeys.size())
return error("%s: Account key invalid parent ext key %d, account %s.", __func__, ak.nParent, GetIDString58().c_str());
const CStoredExtKey *chain = GetChain(ak.nParent);
if (!chain)
return error("%s: Chain unknown, account %s.", __func__, GetIDString58().c_str());
if (!chain->kp.Derive(pkOut, ak.nKey))
return false;
return true;
};
bool CExtKeyAccount::GetPubKey(const CEKASCKey &asck, CPubKey &pkOut) const
{
LOCK(cs_account);
AccStealthKeyMap::const_iterator miSk = mapStealthKeys.find(asck.idStealthKey);
if (miSk == mapStealthKeys.end())
return error("%s: CEKASCKey Stealth key not in this account!", __func__);
return (0 == ExpandStealthChildPubKey(&miSk->second, asck.sShared, pkOut));
};
bool CExtKeyAccount::SaveKey(const CKeyID &id, CEKAKey &keyIn)
{
// TODO: rename? this is taking a key from lookahead and saving it
LOCK(cs_account);
AccKeyMap::const_iterator mi = mapKeys.find(id);
if (mi != mapKeys.end())
return false; // already saved
if (mapLookAhead.erase(id) != 1)
{
CBitcoinAddress addr(id);
LogPrintf("Warning: SaveKey %s key not found in look ahead %s.\n", GetIDString58().c_str(), addr.ToString().c_str());
};
mapKeys[id] = keyIn;
CStoredExtKey *pc;
if ((pc = GetChain(keyIn.nParent)) != NULL)
{
if (keyIn.nKey == pc->nGenerated) // TODO: gaps?
pc->nGenerated++;
if (pc->nFlags & EAF_ACTIVE
&& pc->nFlags & EAF_RECEIVE_ON)
AddLookAhead(keyIn.nParent, 1);
};
if (fDebug)
{
CBitcoinAddress addr(id);
LogPrintf("Saved key %s, %s.\n", GetIDString58().c_str(), addr.ToString().c_str());
// - check match
CStoredExtKey *pa;
if ((pa = GetChain(keyIn.nParent)) != NULL)
{
if ((keyIn.nKey >> 31) == 1
&& pa->fLocked == 1)
{
LogPrintf("Can't check hardened key when wallet locked.\n");
return true;
};
CPubKey pk;
if (!GetPubKey(keyIn, pk))
return error("GetPubKey failed.");
if (pk.GetID() != id)
return error("Key mismatch!!!");
LogPrintf("key match verified.\n");
} else
{
return error("Unknown chain.");
};
};
return true;
};
bool CExtKeyAccount::SaveKey(const CKeyID &id, CEKASCKey &keyIn)
{
LOCK(cs_account);
AccKeySCMap::const_iterator mi = mapStealthChildKeys.find(id);
if (mi != mapStealthChildKeys.end())
return false; // already saved
AccStealthKeyMap::const_iterator miSk = mapStealthKeys.find(keyIn.idStealthKey);
if (miSk == mapStealthKeys.end())
return error("SaveKey(): CEKASCKey Stealth key not in this account!");
mapStealthChildKeys[id] = keyIn;
if (fDebug)
{
CBitcoinAddress addr(id);
LogPrintf("SaveKey(): CEKASCKey %s, %s.\n", GetIDString58().c_str(), addr.ToString().c_str());
};
return true;
};
bool CExtKeyAccount::IsLocked(const CEKAStealthKey &aks)
{
// TODO: check aks belongs to account??
CStoredExtKey *pc = GetChain(aks.akSpend.nParent);
if (pc && !pc->fLocked)
return false;
return true;
};
int CExtKeyAccount::AddLookAhead(uint32_t nChain, uint32_t nKeys)
{
// -- start from key 0
CStoredExtKey *pc = GetChain(nChain);
if (!pc)
return errorN(1, "%s: Unknown chain, %d.", __func__, nChain);
if (fDebug)
LogPrintf("%s: chain %s, keys %d.\n", __func__, pc->GetIDString58(), nKeys);
AccKeyMap::const_iterator mi;
uint32_t nChild = pc->nGenerated;
uint32_t nChildOut = nChild;
CKeyID keyId;
CPubKey pk;
for (uint32_t k = 0; k < nKeys; ++k)
{
bool fGotKey = false;
for (uint32_t i = 0; i < MAX_DERIVE_TRIES; ++i) // MAX_DERIVE_TRIES > lookahead pool
{
if (pc->DeriveKey(pk, nChild, nChildOut, false) != 0)
{
LogPrintf("%s: DeriveKey failed, chain %d, child %d.\n", __func__, nChain, nChild);
nChild = nChildOut+1;
continue;
};
nChild = nChildOut+1;
keyId = pk.GetID();
if ((mi = mapKeys.find(keyId)) != mapKeys.end())
{
if (fDebug)
{
CBitcoinAddress addr(keyId);
LogPrintf("%s: key exists in map skipping %s.\n", __func__, addr.ToString().c_str());
};
continue;
};
fGotKey = true;
break;
};
if (!fGotKey)
{
LogPrintf("%s: DeriveKey loop failed, chain %d, child %d.\n", __func__, nChain, nChild);
continue;
};
mapLookAhead[keyId] = CEKAKey(nChain, nChildOut);
if (fDebug)
{
CBitcoinAddress addr(keyId);
LogPrintf("%s: added %s\n", __func__, addr.ToString().c_str());
};
};
return 0;
};
int CExtKeyAccount::ExpandStealthChildKey(const CEKAStealthKey *aks, const ec_secret &sShared, CKey &kOut) const
{
// - derive the secret key of the stealth address and then the secret key out
LOCK(cs_account);
if (!aks)
return errorN(1, "%s: Sanity checks failed.", __func__);
CKey kSpend;
if (!GetKey(aks->akSpend, kSpend))
return errorN(1, "%s: GetKey() failed.", __func__);
ec_secret sSpendR;
ec_secret sSpend;
memcpy(&sSpend.e[0], kSpend.begin(), EC_SECRET_SIZE);
if (StealthSharedToSecretSpend(sShared, sSpend, sSpendR) != 0)
return errorN(1, "%s: StealthSharedToSecretSpend() failed.", __func__);
kOut.Set(&sSpendR.e[0], true);
if (!kOut.IsValid())
return errorN(1, "%s: Invalid key.", __func__);
return 0;
};
int CExtKeyAccount::ExpandStealthChildPubKey(const CEKAStealthKey *aks, const ec_secret &sShared, CPubKey &pkOut) const
{
// - works with locked wallet
LOCK(cs_account);
if (!aks)
return errorN(1, "%s: Sanity checks failed.", __func__);
ec_point pkExtract;
if (StealthSharedToPublicKey(aks->pkSpend, sShared, pkExtract) != 0)
return errorN(1, "%s: StealthSharedToPublicKey() failed.", __func__);
pkOut = CPubKey(pkExtract);
if (!pkOut.IsValid())
return errorN(1, "%s: Invalid public key.", __func__);
return 0;
};
int CExtKeyAccount::WipeEncryption()
{
std::vector<CStoredExtKey*>::iterator it;
for (it = vExtKeys.begin(); it != vExtKeys.end(); ++it)
{
if (!((*it)->nFlags & EAF_IS_CRYPTED))
continue;
if ((*it)->fLocked)
return errorN(1, "Attempting to undo encryption of a locked key.");
(*it)->nFlags &= ~EAF_IS_CRYPTED;
(*it)->vchCryptedSecret.clear();
};
return 0;
};
bool IsBIP32(const char *base58)
{
std::vector<uint8_t> vchBytes;
if (!DecodeBase58(base58, vchBytes))
return false;
if (vchBytes.size() != BIP32_KEY_LEN)
return false;
if (0 == memcmp(&vchBytes[0], &Params().Base58Prefix(CChainParams::EXT_SECRET_KEY)[0], 4)
|| 0 == memcmp(&vchBytes[0], &Params().Base58Prefix(CChainParams::EXT_PUBLIC_KEY)[0], 4))
return true;
if (!VerifyChecksum(vchBytes))
return false;
return false;
};
int LoopExtKeysInDB(bool fInactive, bool fInAccount, LoopExtKeyCallback &callback)
{
AssertLockHeld(pwalletMain->cs_wallet);
CWalletDB wdb(pwalletMain->strWalletFile);
Dbc *pcursor;
if (!(pcursor = wdb.GetAtCursor()))
throw std::runtime_error(strprintf("%s : cannot create DB cursor", __func__).c_str());
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
CKeyID ckeyId;
CStoredExtKey sek;
std::string strType;
uint32_t fFlags = DB_SET_RANGE;
ssKey << std::string("ek32");
while (wdb.ReadAtCursor(pcursor, ssKey, ssValue, fFlags) == 0)
{
fFlags = DB_NEXT;
ssKey >> strType;
if (strType != "ek32")
break;
ssKey >> ckeyId;
ssValue >> sek;
if (!fInAccount
&& sek.nFlags & EAF_IN_ACCOUNT)
continue;
callback.ProcessKey(ckeyId, sek);
};
pcursor->close();
return 0;
};
int LoopExtAccountsInDB(bool fInactive, LoopExtKeyCallback &callback)
{
AssertLockHeld(pwalletMain->cs_wallet);
CWalletDB wdb(pwalletMain->strWalletFile);
// - list accounts
Dbc *pcursor;
if (!(pcursor = wdb.GetAtCursor()))
throw std::runtime_error(strprintf("%s : cannot create DB cursor", __func__).c_str());
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
CKeyID idAccount;
CExtKeyAccount sea;
CBitcoinAddress addr;
std::string strType, sError;
uint32_t fFlags = DB_SET_RANGE;
ssKey << std::string("eacc");
while (wdb.ReadAtCursor(pcursor, ssKey, ssValue, fFlags) == 0)
{
fFlags = DB_NEXT;
ssKey >> strType;
if (strType != "eacc")
break;
ssKey >> idAccount;
ssValue >> sea;
sea.vExtKeys.resize(sea.vExtKeyIDs.size());
for (size_t i = 0; i < sea.vExtKeyIDs.size(); ++i)
{
CKeyID &id = sea.vExtKeyIDs[i];
CStoredExtKey *sek = new CStoredExtKey();
if (wdb.ReadExtKey(id, *sek))
{
sea.vExtKeys[i] = sek;
} else
{
addr.Set(idAccount, CChainParams::EXT_ACC_HASH);
LogPrintf("WARNING: Could not read key %d of account %s\n", i, addr.ToString().c_str());
sea.vExtKeys[i] = NULL;
delete sek;
};
};
callback.ProcessAccount(idAccount, sea);
sea.FreeChains();
};
pcursor->close();
return 0;
};
static int GetWord(int o, const char *pwl, int max, std::string &sWord)
{
char *pt = (char*)pwl;
while (o > 0)
{
if (*pt == '\n')
o--;
pt++;
if (pt >= pwl+max)
return 1;
};
while (pt < (pwl+max))
{
if (*pt == '\n')
return 0;
sWord += *pt;
pt++;
};
return 1;
};
int GetWordOffset(const char *p, const char *pwl, int max, int &o)
{
// - list must end with \n
char *pt = (char*)pwl;
int l = strlen(p);
int i = 0;
int c = 0;
int f = 1;
while (pt < (pwl+max))
{
if (*pt == '\n')
{
if (f && c == l) // found
{
o = i;
return 0;
};
i++;
c = 0;
f = 1;
} else
{
if (c >= l)
f = 0;
else
if (f && *(p+c) != *pt)
f = 0;
c++;
};
pt++;
};
return 1;
};
static const unsigned char *mnLanguages[] =
{
NULL,
english_txt,
french_txt,
japanese_txt,
spanish_txt,
chinese_simplified_txt,
chinese_traditional_txt,
};
static const uint32_t mnLanguageLens[] =
{
0,
english_txt_len,
french_txt_len,
japanese_txt_len,
spanish_txt_len,
chinese_simplified_txt_len,
chinese_traditional_txt_len,
};
int MnemonicDetectLanguage(const std::string &sWordList)
{
char tmp[2048];
if (sWordList.size() >= 2048)
return errorN(-1, "%s: Word List too long.", __func__);
// try to detect the language
// try max 4 words
// allow errors to account for spelling mistakes
for (int l = 1; l < WLL_MAX; ++l)
{
strcpy(tmp, sWordList.c_str());
char *pwl = (char*) mnLanguages[l];
int m = mnLanguageLens[l];
int maxTries = 4;
int nHit = 0;
int nMiss = 0;
char *p;
p = strtok(tmp, " ");
while (p != NULL)
{
int ofs;
if (0 == GetWordOffset(p, pwl, m, ofs))
nHit++;
else
nMiss++;
if (!maxTries--)
break;
p = strtok(NULL, " ");
};
if (nHit > nMiss)
return l;
};
return 0;
};
int MnemonicEncode(int nLanguage, const std::vector<uint8_t> &vEntropy, std::string &sWordList, std::string &sError)
{
if (fDebug)
LogPrintf("%s: language %d.\n", __func__, nLanguage);
if (nLanguage < 1 || nLanguage > WLL_MAX)
{
sError = "Unknown language.";
return errorN(1, "%s: %s", __func__, sError.c_str());
};
// -- checksum is 1st n bytes of the sha256 hash
uint8_t hash[32];
SHA256(&vEntropy[0], vEntropy.size(), (uint8_t*)hash);
int nCsSize = vEntropy.size() / 4; // 32 / 8
if (nCsSize < 1 || nCsSize > 256)
{
sError = "Entropy bytes out of range.";
return errorN(2, "%s: %s", __func__, sError.c_str());
};
std::vector<uint8_t> vIn = vEntropy;
int ncb = nCsSize/8;
int r = nCsSize % 8;
if (r != 0)
ncb++;
std::vector<uint8_t> vTmp(32);
memcpy(&vTmp[0], &hash, ncb);
memset(&vTmp[ncb], 0, 32-ncb);
vIn.insert(vIn.end(), vTmp.begin(), vTmp.end());
std::vector<int> vWord;
int nBits = vEntropy.size() * 8 + nCsSize;
int i = 0;
while (i < nBits)
{
int o = 0;
int s = i / 8;
int r = i % 8;
uint8_t b1 = vIn[s];
uint8_t b2 = vIn[s+1];
o = (b1 << r) & 0xFF;
o = o << (11 - 8);
if (r > 5)
{
uint8_t b3 = vIn[s+2];
o |= (b2 << (r-5));
o |= (b3 >> (8-(r-5)));
} else
{
o |= ((int)b2) >> ((8 - (11 - 8))-r);
};
o = o & 0x7FF;
vWord.push_back(o);
i += 11;
};
char *pwl = (char*) mnLanguages[nLanguage];
int m = mnLanguageLens[nLanguage];
for (size_t k = 0; k < vWord.size(); ++k)
{
int o = vWord[k];
std::string sWord;
if (0 != GetWord(o, pwl, m, sWord))
{
sError = strprintf("Word extract failed %d.", o);
return errorN(3, "%s: %s", __func__, sError.c_str());
};
if (sWordList != "")
sWordList += " ";
sWordList += sWord;
};
if (nLanguage == WLL_JAPANESE)
ReplaceStrInPlace(sWordList, " ", "\u3000");
return 0;
};
int MnemonicDecode(int nLanguage, const std::string &sWordListIn, std::vector<uint8_t> &vEntropy, std::string &sError, bool fIgnoreChecksum)
{
if (fDebug)
LogPrintf("%s: Language %d.\n", __func__, nLanguage);
std::string sWordList = sWordListIn;
ReplaceStrInPlace(sWordList, "\u3000", " ");
if (nLanguage == -1)
nLanguage = MnemonicDetectLanguage(sWordList);
if (nLanguage < 1 || nLanguage > WLL_MAX)
{
sError = "Unknown language.";
return errorN(1, "%s: %s", __func__, sError.c_str());
};
char tmp[2048];
if (sWordList.size() >= 2048)
{
sError = "Word List too long.";
return errorN(2, "%s: %s", __func__, sError.c_str());
};
strcpy(tmp, sWordList.c_str());
char *pwl = (char*) mnLanguages[nLanguage];
int m = mnLanguageLens[nLanguage];
std::vector<int> vWordInts;
char *p;
p = strtok(tmp, " ");
while (p != NULL)
{
int ofs;
if (0 != GetWordOffset(p, pwl, m, ofs))
{
sError = strprintf("Unknown word: %s", p);
return errorN(3, "%s: %s", __func__, sError.c_str());
};
vWordInts.push_back(ofs);
p = strtok(NULL, " ");
};
if (!fIgnoreChecksum
&& vWordInts.size() % 3 != 0)
{
sError = "No. of words must be divisible by 3.";
return errorN(4, "%s: %s", __func__, sError.c_str());
};
int nBits = vWordInts.size() * 11;
int nBytes = nBits/8 + (nBits % 8 == 0 ? 0 : 1);
vEntropy.resize(nBytes);
memset(&vEntropy[0], 0, nBytes);
int i = 0;
size_t wl = vWordInts.size();
size_t el = vEntropy.size();
for (size_t k = 0; k < wl; ++k)
{
int o = vWordInts[k];
int s = i / 8;
int r = i % 8;
vEntropy[s] |= (o >> (r+3)) & 0x7FF;
if (s < (int)el-1)
{
if (r > 5)
{
vEntropy[s+1] |= ((o >> (r-5))) & 0x7FF;
if (s < (int)el-2)
{
vEntropy[s+2] |= (o << (8-(r-5))) & 0x7FF;
};
} else
{
vEntropy[s+1] |= (o << (5-r)) & 0x7FF;
};
};
i += 11;
};
if (fIgnoreChecksum)
return 0;
// -- checksum
int nLenChecksum = nBits / 32;
int nLenEntropy = nBits - nLenChecksum;
int nBytesEntropy = nLenEntropy / 8;
int nBytesChecksum = nLenChecksum / 8 + (nLenChecksum % 8 == 0 ? 0 : 1);
std::vector<uint8_t> vCS;
vCS.resize(nBytesChecksum);
memcpy(&vCS[0], &vEntropy[nBytesEntropy], nBytesChecksum);
vEntropy.resize(nBytesEntropy);
uint8_t hash[32];
SHA256(&vEntropy[0], vEntropy.size(), (uint8_t*)hash);
std::vector<uint8_t> vCSTest;
vCSTest.resize(nBytesChecksum);
memcpy(&vCSTest[0], &hash, nBytesChecksum);
int r = nLenChecksum % 8;
if (r > 0)
vCSTest[nBytesChecksum-1] &= (((1<<r)-1) << (8-r));
if (vCSTest != vCS)
{
sError = "Checksum mismatch.";
return errorN(5, "%s: %s", __func__, sError.c_str());
};
return 0;
};
int MnemonicToSeed(const std::string &sMnemonic, const std::string &sPasswordIn, std::vector<uint8_t> &vSeed)
{
if (fDebug)
LogPrintf("%s\n", __func__);
vSeed.resize(64);
std::string sWordList = sMnemonic;
ReplaceStrInPlace(sWordList, "\u3000", " ");
std::string sPassword = sPasswordIn;
ReplaceStrInPlace(sPassword, "\u3000", " ");
int nIterations = 2048;
std::string sSalt = std::string("mnemonic") + sPassword;
int nBytesOut = 64;
if (1 != PKCS5_PBKDF2_HMAC(
sWordList.data(), sWordList.size(),
(const unsigned char*)sSalt.data(), sSalt.size(),
nIterations, EVP_sha512(), nBytesOut, &vSeed[0]))
return errorN(1, "%s: PKCS5_PBKDF2_HMAC failed.", __func__);
return 0;
};
int MnemonicAddChecksum(int nLanguageIn, const std::string &sWordListIn, std::string &sWordListOut, std::string &sError)
{
int nLanguage = nLanguageIn;
if (nLanguage == -1)
nLanguage = MnemonicDetectLanguage(sWordListIn); // needed here for MnemonicEncode, MnemonicDecode will complain if in error
int rv;
std::vector<uint8_t> vEntropy;
if (0 != (rv = MnemonicDecode(nLanguage, sWordListIn, vEntropy, sError, true)))
return rv;
if (0 != (rv = MnemonicEncode(nLanguage, vEntropy, sWordListOut, sError)))
return rv;
if (0 != (rv = MnemonicDecode(nLanguage, sWordListOut, vEntropy, sError)))
return rv;
return 0;
};