Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 93 additions & 16 deletions src/bip32.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,43 @@ static bool HmacSha512(const unsigned char* key, int keyLen,
len == 64;
}

static bool PrivateKeyToCompressedPubKey(const std::vector<unsigned char>& privateKey,
unsigned char out[33],
std::string& errorOut)
{
if (!IsValidPrivateKey(privateKey))
{
errorOut = "private key is invalid";
return false;
}

BIGNUM* bn = BN_bin2bn(&privateKey[0], 32, NULL);
BN_CTX* ctx = BN_CTX_new();
EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_secp256k1);
EC_POINT* pub = group ? EC_POINT_new(group) : NULL;

bool ok = false;
if (bn && ctx && group && pub &&
EC_POINT_mul(group, pub, bn, NULL, NULL, ctx))
{
size_t n = EC_POINT_point2oct(group, pub, POINT_CONVERSION_COMPRESSED,
out, 33, ctx);
ok = n == 33;
}

BN_free(bn);
BN_CTX_free(ctx);
EC_POINT_free(pub);
EC_GROUP_free(group);

if (!ok)
{
errorOut = "could not serialize parent public key";
return false;
}
return true;
}

bool BIP39EntropyToMnemonic(const std::vector<unsigned char>& entropy,
std::string& mnemonicOut,
std::string& errorOut)
Expand Down Expand Up @@ -284,10 +321,10 @@ bool BIP32MasterFromSeed(const std::vector<unsigned char>& seed,
return true;
}

bool BIP32DeriveHardenedChild(const BIP32PrivateNode& parent,
unsigned int childIndex,
BIP32PrivateNode& childOut,
std::string& errorOut)
bool BIP32DeriveChild(const BIP32PrivateNode& parent,
unsigned int childNumber,
BIP32PrivateNode& childOut,
std::string& errorOut)
{
childOut.privateKey.clear();
childOut.chainCode.clear();
Expand All @@ -297,20 +334,21 @@ bool BIP32DeriveHardenedChild(const BIP32PrivateNode& parent,
errorOut = "parent private key or chain code is invalid";
return false;
}
if (childIndex >= 0x80000000U)
unsigned char data[37];
if (childNumber & BIP32_HARDENED)
{
errorOut = "child index must be non-hardened; hardening is applied here";
return false;
data[0] = 0x00;
memcpy(data + 1, &parent.privateKey[0], 32);
}

const unsigned int hardened = childIndex | 0x80000000U;
unsigned char data[37];
data[0] = 0x00;
memcpy(data + 1, &parent.privateKey[0], 32);
data[33] = (unsigned char)((hardened >> 24) & 0xff);
data[34] = (unsigned char)((hardened >> 16) & 0xff);
data[35] = (unsigned char)((hardened >> 8) & 0xff);
data[36] = (unsigned char)(hardened & 0xff);
else
{
if (!PrivateKeyToCompressedPubKey(parent.privateKey, data, errorOut))
return false;
}
data[33] = (unsigned char)((childNumber >> 24) & 0xff);
data[34] = (unsigned char)((childNumber >> 16) & 0xff);
data[35] = (unsigned char)((childNumber >> 8) & 0xff);
data[36] = (unsigned char)(childNumber & 0xff);

unsigned char I[64];
if (!HmacSha512(&parent.chainCode[0], 32, data, sizeof(data), I))
Expand Down Expand Up @@ -363,4 +401,43 @@ bool BIP32DeriveHardenedChild(const BIP32PrivateNode& parent,
return true;
}

bool BIP32DerivePath(const BIP32PrivateNode& root,
const std::vector<unsigned int>& path,
BIP32PrivateNode& nodeOut,
std::string& errorOut)
{
nodeOut.privateKey.clear();
nodeOut.chainCode.clear();
errorOut.clear();
if (!IsValidPrivateKey(root.privateKey) || root.chainCode.size() != 32)
{
errorOut = "root private key or chain code is invalid";
return false;
}

BIP32PrivateNode cur = root;
for (size_t i = 0; i < path.size(); i++)
{
BIP32PrivateNode next;
if (!BIP32DeriveChild(cur, path[i], next, errorOut))
return false;
cur = next;
}
nodeOut = cur;
return true;
}

bool BIP32DeriveHardenedChild(const BIP32PrivateNode& parent,
unsigned int childIndex,
BIP32PrivateNode& childOut,
std::string& errorOut)
{
if (childIndex >= BIP32_HARDENED)
{
errorOut = "child index must be non-hardened; hardening is applied here";
return false;
}
return BIP32DeriveChild(parent, childIndex | BIP32_HARDENED, childOut, errorOut);
}

} // namespace bitflash
14 changes: 13 additions & 1 deletion src/bip32.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//
// This module is deliberately independent from wallet.dat and GUI code. It is
// the auditable foundation: mnemonic generation/validation, seed derivation,
// and hardened private child derivation over secp256k1.
// and private child derivation over secp256k1.

#ifndef BITFLASH_BIP32_H
#define BITFLASH_BIP32_H
Expand All @@ -16,6 +16,8 @@
namespace bitflash
{

static const unsigned int BIP32_HARDENED = 0x80000000U;

struct BIP32PrivateNode
{
std::vector<unsigned char> privateKey; // 32-byte scalar
Expand Down Expand Up @@ -43,6 +45,16 @@ bool BIP32MasterFromSeed(const std::vector<unsigned char>& seed,
BIP32PrivateNode& nodeOut,
std::string& errorOut);

bool BIP32DeriveChild(const BIP32PrivateNode& parent,
unsigned int childNumber,
BIP32PrivateNode& childOut,
std::string& errorOut);

bool BIP32DerivePath(const BIP32PrivateNode& root,
const std::vector<unsigned int>& path,
BIP32PrivateNode& nodeOut,
std::string& errorOut);

bool BIP32DeriveHardenedChild(const BIP32PrivateNode& parent,
unsigned int childIndex,
BIP32PrivateNode& childOut,
Expand Down
49 changes: 49 additions & 0 deletions src/db.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,7 @@ bool CWalletDB::LoadWallet(vector<unsigned char>& vchDefaultKeyRet)
// Whether wallet.dat actually carried a mining mode, as opposed to just the
// ancient fGenerateBitcoins flag. See the reconciliation further down.
bool fHaveStoredMineMode = false;
bool fHaveStoredHDCoinType = false;
vchDefaultKeyRet.clear();

// Satoshi's "todo: shouldn't we catch exceptions" sat here since 2009, and
Expand Down Expand Up @@ -651,6 +652,23 @@ bool CWalletDB::LoadWallet(vector<unsigned char>& vchDefaultKeyRet)
{
ssValue >> nHDNext;
}
else if (strType == "hdschema")
{
ssValue >> nHDKeySchema;
}
else if (strType == "hdcointype")
{
ssValue >> nHDCoinType;
fHaveStoredHDCoinType = true;
}
else if (strType == "hdreceivenext")
{
ssValue >> nHDReceiveNext;
}
else if (strType == "hdchangenext")
{
ssValue >> nHDChangeNext;
}
else if (strType == "pool")
{
int64 nIndex;
Expand Down Expand Up @@ -707,6 +725,37 @@ bool CWalletDB::LoadWallet(vector<unsigned char>& vchDefaultKeyRet)
return false;
}

if (HaveHDSeed())
{
if (nHDKeySchema == HD_SCHEMA_NONE)
{
nHDKeySchema = HD_SCHEMA_LEGACY;
printf("LoadWallet: deterministic seed has no schema metadata; "
"treating it as legacy m/index'\n");
}
else if (nHDKeySchema != HD_SCHEMA_LEGACY)
{
printf("LoadWallet: deterministic wallet schema %d (%s) is not "
"supported by this build\n",
nHDKeySchema, HDKeySchemaName(nHDKeySchema).c_str());
return false;
}
if (!fHaveStoredHDCoinType)
{
nHDCoinType = HD_BIP44_COIN_TYPE_BITFLASH_PROVISIONAL;
printf("LoadWallet: deterministic seed has no BIP44 coin type metadata; "
"using provisional Bitflash coin type %u\n", nHDCoinType);
}
}
else
{
nHDKeySchema = HD_SCHEMA_NONE;
nHDNext = 0;
nHDReceiveNext = 0;
nHDChangeNext = 0;
nHDCoinType = HD_BIP44_COIN_TYPE_BITFLASH_PROVISIONAL;
}

// fGenerateBitcoins and nMineMode only mean anything together, and a
// wallet.dat can easily hold one without the other: Bitcoin 0.1.0 already
// wrote fGenerateBitcoins when mining was toggled, years before this fork
Expand Down
27 changes: 25 additions & 2 deletions src/db.h
Original file line number Diff line number Diff line change
Expand Up @@ -392,8 +392,11 @@ class CWalletDB : public CDB
// twelve words are the user's to write down, and keeping a copy of them in
// the file they are meant to protect defeats the point of having them.
//
// "hdnext" is the next child index to derive, so the sequence a restore
// reproduces is the same one this wallet handed out.
// "hdschema" identifies the derivation layout. Wallets created before the
// field existed are inferred as HD_SCHEMA_LEGACY when a seed is present.
// "hdnext" is that legacy layout's next child index. The receive/change
// counters and coin type are reserved for the BIP44 layout that uses
// separate chains.
bool ReadHDMaster(vector<unsigned char>& vchMasterRet, vector<unsigned char>& vchChainCodeRet)
{
vchMasterRet.clear();
Expand All @@ -413,6 +416,26 @@ class CWalletDB : public CDB
return Write(string("hdnext"), nNext);
}

bool WriteHDSchema(int nSchema)
{
return Write(string("hdschema"), nSchema);
}

bool WriteHDCoinType(unsigned int nCoinType)
{
return Write(string("hdcointype"), nCoinType);
}

bool WriteHDReceiveNext(unsigned int nNext)
{
return Write(string("hdreceivenext"), nNext);
}

bool WriteHDChangeNext(unsigned int nNext)
{
return Write(string("hdchangenext"), nNext);
}

// Keys generated ahead of time and not yet handed out. The private key of
// each is already stored under its own "key" record by AddKey; a "pool"
// record only says that this one is still unspoken for.
Expand Down
5 changes: 5 additions & 0 deletions src/gui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1247,6 +1247,11 @@ static void DrawWalletSafetyDialog()
ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.25f, 1.0f),
"No recovery phrase. Only a file backup can rebuild this wallet.");

if (g_recoveryAudit.fHaveSeed)
{
ImGui::Text("Derivation schema: %s", HDKeySchemaName(g_recoveryAudit.nSchema).c_str());
ImGui::Text("BIP44 coin type: %u (provisional BITFLASH)", g_recoveryAudit.nCoinType);
}
ImGui::Text("Phrase-backed spendable balance: %s BTF",
FmtMoney(g_recoveryAudit.nRecoverableCredit).c_str());
ImGui::Text("Wallet.dat-only spendable balance: %s BTF",
Expand Down
57 changes: 54 additions & 3 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,40 @@ map<int64, vector<unsigned char> > mapKeyPool;
vector<unsigned char> vchHDMaster;
vector<unsigned char> vchHDChainCode;
unsigned int nHDNext = 0;
int nHDKeySchema = HD_SCHEMA_NONE;
unsigned int nHDReceiveNext = 0;
unsigned int nHDChangeNext = 0;
unsigned int nHDCoinType = HD_BIP44_COIN_TYPE_BITFLASH_PROVISIONAL;

string HDKeySchemaName(int nSchema)
{
if (nSchema == HD_SCHEMA_LEGACY)
return "legacy-hd";
if (nSchema == HD_SCHEMA_BIP44)
return "bip44";
return "none";
}

std::vector<unsigned int> HDLegacyPath(unsigned int nIndex)
{
std::vector<unsigned int> path;
path.push_back(nIndex | bitflash::BIP32_HARDENED);
return path;
}

std::vector<unsigned int> HDBIP44Path(unsigned int nCoinType,
unsigned int nAccount,
unsigned int nChain,
unsigned int nIndex)
{
std::vector<unsigned int> path;
path.push_back(HD_BIP44_PURPOSE | bitflash::BIP32_HARDENED);
path.push_back(nCoinType | bitflash::BIP32_HARDENED);
path.push_back(nAccount | bitflash::BIP32_HARDENED);
path.push_back(nChain);
path.push_back(nIndex);
return path;
}

static bool ClearKeyPoolRecords(string& strErrorRet)
{
Expand All @@ -254,13 +288,18 @@ bool DeriveHDKey(unsigned int nIndex, CKey& keyRet, string& strErrorRet)
strErrorRet = "no seed";
return false;
}
if (nIndex >= bitflash::BIP32_HARDENED)
{
strErrorRet = "child index must be non-hardened; hardening is applied here";
return false;
}

bitflash::BIP32PrivateNode parent;
parent.privateKey = vchHDMaster;
parent.chainCode = vchHDChainCode;

bitflash::BIP32PrivateNode child;
if (!bitflash::BIP32DeriveHardenedChild(parent, nIndex, child, strErrorRet))
if (!bitflash::BIP32DerivePath(parent, HDLegacyPath(nIndex), child, strErrorRet))
return false;

if (!keyRet.SetSecret(child.privateKey))
Expand Down Expand Up @@ -307,13 +346,21 @@ bool SetHDSeedFromMnemonic(const string& strMnemonic, string& strErrorRet)
return false;

if (!CWalletDB().WriteHDMaster(master.privateKey, master.chainCode) ||
!CWalletDB().WriteHDNext(1))
!CWalletDB().WriteHDSchema(HD_SCHEMA_LEGACY) ||
!CWalletDB().WriteHDCoinType(HD_BIP44_COIN_TYPE_BITFLASH_PROVISIONAL) ||
!CWalletDB().WriteHDNext(1) ||
!CWalletDB().WriteHDReceiveNext(0) ||
!CWalletDB().WriteHDChangeNext(0))
{
strErrorRet = "could not write the seed to wallet.dat";
strErrorRet = "could not write the seed metadata to wallet.dat";
return false;
}
vchHDMaster = master.privateKey;
vchHDChainCode = master.chainCode;
nHDKeySchema = HD_SCHEMA_LEGACY;
nHDReceiveNext = 0;
nHDChangeNext = 0;
nHDCoinType = HD_BIP44_COIN_TYPE_BITFLASH_PROVISIONAL;

// Index 0 is spoken for before it is derived, so a failure below cannot
// leave the pool free to hand it out as an ordinary key. The cost of
Expand Down Expand Up @@ -3668,7 +3715,11 @@ WalletRecoveryAudit GetWalletRecoveryAudit()
CRITICAL_BLOCK(cs_keyPool)
{
audit.fHaveSeed = HaveHDSeed();
audit.nSchema = nHDKeySchema;
audit.nDerivedKnown = nHDNext;
audit.nReceiveNext = nHDReceiveNext;
audit.nChangeNext = nHDChangeNext;
audit.nCoinType = nHDCoinType;
if (audit.fHaveSeed)
{
string strError;
Expand Down
Loading