From c731d72b27aacf0ce887af01866ad1c08e52a491 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Mon, 20 Mar 2017 01:23:02 +0300 Subject: [PATCH 01/31] HD wallet Minimal set of changes (no refactoring) backported from Bitcoin upstream to make HD wallets work in Dash 0.12.1.x+ --- qa/rpc-tests/keypool.py | 10 ++ src/Makefile.qt.include | 8 ++ src/init.cpp | 15 +++ src/key.h | 17 +++ src/pubkey.h | 24 +++++ src/qt/bitcoingui.cpp | 14 +++ src/qt/bitcoingui.h | 7 ++ src/qt/dash.qrc | 8 ++ src/qt/res/icons/crownium/hd_disabled.png | Bin 0 -> 4328 bytes src/qt/res/icons/crownium/hd_enabled.png | Bin 0 -> 1889 bytes src/qt/res/icons/drkblue/hd_disabled.png | Bin 0 -> 4328 bytes src/qt/res/icons/drkblue/hd_enabled.png | Bin 0 -> 1889 bytes src/qt/res/icons/light/hd_disabled.png | Bin 0 -> 4328 bytes src/qt/res/icons/light/hd_enabled.png | Bin 0 -> 1889 bytes src/qt/res/icons/trad/hd_disabled.png | Bin 0 -> 4328 bytes src/qt/res/icons/trad/hd_enabled.png | Bin 0 -> 1889 bytes src/qt/res/src/hd_disabled.svg | 26 +++++ src/qt/res/src/hd_enabled.svg | 13 +++ src/qt/walletmodel.cpp | 5 + src/qt/walletmodel.h | 2 + src/qt/walletview.cpp | 6 ++ src/qt/walletview.h | 2 + src/rpcmisc.cpp | 8 ++ src/test/bip32_tests.cpp | 16 +++ src/wallet/rpcdump.cpp | 30 +++++- src/wallet/rpcwallet.cpp | 10 +- src/wallet/wallet.cpp | 123 +++++++++++++++++++++- src/wallet/wallet.h | 22 ++++ src/wallet/walletdb.cpp | 18 +++- src/wallet/walletdb.h | 47 ++++++++- 30 files changed, 421 insertions(+), 10 deletions(-) create mode 100644 src/qt/res/icons/crownium/hd_disabled.png create mode 100644 src/qt/res/icons/crownium/hd_enabled.png create mode 100644 src/qt/res/icons/drkblue/hd_disabled.png create mode 100644 src/qt/res/icons/drkblue/hd_enabled.png create mode 100644 src/qt/res/icons/light/hd_disabled.png create mode 100644 src/qt/res/icons/light/hd_enabled.png create mode 100644 src/qt/res/icons/trad/hd_disabled.png create mode 100644 src/qt/res/icons/trad/hd_enabled.png create mode 100644 src/qt/res/src/hd_disabled.svg create mode 100644 src/qt/res/src/hd_enabled.svg diff --git a/qa/rpc-tests/keypool.py b/qa/rpc-tests/keypool.py index b86c085e0009..6e001f09c3bc 100755 --- a/qa/rpc-tests/keypool.py +++ b/qa/rpc-tests/keypool.py @@ -14,6 +14,11 @@ class KeyPoolTest(BitcoinTestFramework): def run_test(self): nodes = self.nodes + addr_before_encrypting = nodes[0].getnewaddress() + addr_before_encrypting_data = nodes[0].validateaddress(addr_before_encrypting) + wallet_info_old = nodes[0].getwalletinfo() + assert(addr_before_encrypting_data['hdmasterkeyid'] == wallet_info_old['hdmasterkeyid']) + # Encrypt wallet and wait to terminate nodes[0].encryptwallet('test') bitcoind_processes[0].wait() @@ -21,6 +26,11 @@ def run_test(self): nodes[0] = start_node(0, self.options.tmpdir) # Keep creating keys addr = nodes[0].getnewaddress() + addr_data = nodes[0].validateaddress(addr) + wallet_info = nodes[0].getwalletinfo() + assert(addr_before_encrypting_data['hdmasterkeyid'] != wallet_info['hdmasterkeyid']) + assert(addr_data['hdmasterkeyid'] == wallet_info['hdmasterkeyid']) + try: addr = nodes[0].getnewaddress() raise AssertionError('Keypool should be exhausted after one address') diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 7a98e4b74b0e..992aae2f27e6 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -194,6 +194,8 @@ RES_ICONS = \ qt/res/icons/drkblue/eye_minus.png \ qt/res/icons/drkblue/eye_plus.png \ qt/res/icons/drkblue/filesave.png \ + qt/res/icons/drkblue/hd_disabled.png \ + qt/res/icons/drkblue/hd_enabled.png \ qt/res/icons/drkblue/history.png \ qt/res/icons/drkblue/key.png \ qt/res/icons/drkblue/lock_closed.png \ @@ -242,6 +244,8 @@ RES_ICONS = \ qt/res/icons/crownium/eye_minus.png \ qt/res/icons/crownium/eye_plus.png \ qt/res/icons/crownium/filesave.png \ + qt/res/icons/crownium/hd_disabled.png \ + qt/res/icons/crownium/hd_enabled.png \ qt/res/icons/crownium/history.png \ qt/res/icons/crownium/key.png \ qt/res/icons/crownium/lock_closed.png \ @@ -290,6 +294,8 @@ RES_ICONS = \ qt/res/icons/light/eye_minus.png \ qt/res/icons/light/eye_plus.png \ qt/res/icons/light/filesave.png \ + qt/res/icons/light/hd_disabled.png \ + qt/res/icons/light/hd_enabled.png \ qt/res/icons/light/history.png \ qt/res/icons/light/key.png \ qt/res/icons/light/lock_closed.png \ @@ -338,6 +344,8 @@ RES_ICONS = \ qt/res/icons/trad/eye_minus.png \ qt/res/icons/trad/eye_plus.png \ qt/res/icons/trad/filesave.png \ + qt/res/icons/trad/hd_disabled.png \ + qt/res/icons/trad/hd_enabled.png \ qt/res/icons/trad/history.png \ qt/res/icons/trad/key.png \ qt/res/icons/trad/lock_closed.png \ diff --git a/src/init.cpp b/src/init.cpp index 8435caad413a..1e6956ed8846 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -477,6 +477,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-txconfirmtarget=", strprintf(_("If paytxfee is not set, include enough fee so transactions begin confirmation on average within n blocks (default: %u)"), DEFAULT_TX_CONFIRM_TARGET)); strUsage += HelpMessageOpt("-maxtxfee=", strprintf(_("Maximum total fees (in %s) to use in a single wallet transaction; setting this too low may abort large transactions (default: %s)"), CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MAXFEE))); + strUsage += HelpMessageOpt("-usehd", _("Use hierarchical deterministic key generation (HD) after bip32. Only has effect during wallet creation/first start") + " " + strprintf(_("(default: %u)"), DEFAULT_USE_HD_WALLET)); strUsage += HelpMessageOpt("-upgradewallet", _("Upgrade wallet to latest format on startup")); strUsage += HelpMessageOpt("-wallet=", _("Specify wallet file (within data directory)") + " " + strprintf(_("(default: %s)"), "wallet.dat")); strUsage += HelpMessageOpt("-walletbroadcast", _("Make the wallet broadcast transactions") + " " + strprintf(_("(default: %u)"), DEFAULT_WALLETBROADCAST)); @@ -1661,6 +1662,13 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) // Create new keyUser and set as default key RandAddSeedPerfmon(); + if (GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET) && !pwalletMain->IsHDEnabled()) { + // generate a new master key + CPubKey masterPubKey = pwalletMain->GenerateNewHDMasterKey(); + if (!pwalletMain->SetHDMasterKey(masterPubKey)) + throw std::runtime_error(std::string(__func__) + ": Storing master key failed"); + } + CPubKey newDefaultKey; if (pwalletMain->GetKeyFromPool(newDefaultKey)) { pwalletMain->SetDefaultKey(newDefaultKey); @@ -1681,6 +1689,13 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) } } + else if (mapArgs.count("-usehd")) { + bool useHD = GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET); + if (pwalletMain->IsHDEnabled() && !useHD) + return InitError(strprintf(_("Error loading %s: You can't disable HD on a already existing HD wallet"), strWalletFile)); + if (!pwalletMain->IsHDEnabled() && useHD) + return InitError(strprintf(_("Error loading %s: You can't enable HD on a already existing non-HD wallet"), strWalletFile)); + } LogPrintf("%s", strErrors.str()); LogPrintf(" wallet %15dms\n", GetTimeMillis() - nStart); diff --git a/src/key.h b/src/key.h index 6c820d49cd5f..d4e85e61a0dc 100644 --- a/src/key.h +++ b/src/key.h @@ -169,6 +169,23 @@ struct CExtKey { bool Derive(CExtKey& out, unsigned int nChild) const; CExtPubKey Neuter() const; void SetMaster(const unsigned char* seed, unsigned int nSeedLen); + template + void Serialize(Stream& s, int nType, int nVersion) const + { + unsigned int len = 74; + ::WriteCompactSize(s, len); + unsigned char code[74]; + Encode(code); + s.write((const char *)&code[0], len); + } + template + void Unserialize(Stream& s, int nType, int nVersion) + { + unsigned int len = ::ReadCompactSize(s); + unsigned char code[74]; + s.read((char *)&code[0], len); + Decode(code); + } }; /** Initialize the elliptic curve support. May not be called twice without calling ECC_Stop first. */ diff --git a/src/pubkey.h b/src/pubkey.h index e1a17b658261..bb47485a7361 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -208,6 +208,30 @@ struct CExtPubKey { void Encode(unsigned char code[74]) const; void Decode(const unsigned char code[74]); bool Derive(CExtPubKey& out, unsigned int nChild) const; + + unsigned int GetSerializeSize(int nType, int nVersion) const + { + return 74+1; //add one byte for the size (compact int) + } + template + void Serialize(Stream& s, int nType, int nVersion) const + { + unsigned int len = 74; + ::WriteCompactSize(s, len); + unsigned char code[74]; + Encode(code); + s.write((const char *)&code[0], len); + } + template + void Unserialize(Stream& s, int nType, int nVersion) + { + unsigned int len = ::ReadCompactSize(s); + unsigned char code[74]; + if (len != 74) + throw std::runtime_error("Invalid extended key size\n"); + s.read((char *)&code[0], len); + Decode(code); + } }; /** Users of this module must hold an ECCVerifyHandle. The constructor and diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index aeca0641af26..d9e666eac3df 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -80,6 +80,7 @@ BitcoinGUI::BitcoinGUI(const PlatformStyle *platformStyle, const NetworkStyle *n walletFrame(0), unitDisplayControl(0), labelEncryptionIcon(0), + labelWalletHDStatusIcon(0), labelConnectionsIcon(0), labelBlocksIcon(0), progressBarLabel(0), @@ -201,6 +202,7 @@ BitcoinGUI::BitcoinGUI(const PlatformStyle *platformStyle, const NetworkStyle *n frameBlocksLayout->setSpacing(3); unitDisplayControl = new UnitDisplayStatusBarControl(platformStyle); labelEncryptionIcon = new QLabel(); + labelWalletHDStatusIcon = new QLabel(); labelConnectionsIcon = new QPushButton(); labelConnectionsIcon->setFlat(true); // Make the button look like a label, but clickable labelConnectionsIcon->setStyleSheet(".QPushButton { background-color: rgba(255, 255, 255, 0);}"); @@ -215,6 +217,7 @@ BitcoinGUI::BitcoinGUI(const PlatformStyle *platformStyle, const NetworkStyle *n frameBlocksLayout->addWidget(unitDisplayControl); frameBlocksLayout->addStretch(); frameBlocksLayout->addWidget(labelEncryptionIcon); + frameBlocksLayout->addWidget(labelWalletHDStatusIcon); } frameBlocksLayout->addStretch(); frameBlocksLayout->addWidget(labelConnectionsIcon); @@ -1208,6 +1211,17 @@ bool BitcoinGUI::handlePaymentRequest(const SendCoinsRecipient& recipient) return false; } +void BitcoinGUI::setHDStatus(int hdEnabled) +{ + QString theme = GUIUtil::getThemeName(); + + labelWalletHDStatusIcon->setPixmap(platformStyle->SingleColorIcon(hdEnabled ? ":/icons/" + theme + "/hd_enabled" : ":/icons/" + theme + "/hd_disabled").pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE)); + labelWalletHDStatusIcon->setToolTip(hdEnabled ? tr("HD key generation is enabled") : tr("HD key generation is disabled")); + + // eventually disable the QLabel to set its opacity to 50% + labelWalletHDStatusIcon->setEnabled(hdEnabled); +} + void BitcoinGUI::setEncryptionStatus(int status) { QString theme = GUIUtil::getThemeName(); diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index c42b6cd14a65..7f518be50062 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -85,6 +85,7 @@ class BitcoinGUI : public QMainWindow UnitDisplayStatusBarControl *unitDisplayControl; QLabel *labelEncryptionIcon; + QLabel *labelWalletHDStatusIcon; QPushButton *labelConnectionsIcon; QLabel *labelBlocksIcon; QLabel *progressBarLabel; @@ -183,6 +184,12 @@ public Q_SLOTS: void message(const QString &title, const QString &message, unsigned int style, bool *ret = NULL); #ifdef ENABLE_WALLET + /** Set the hd-enabled status as shown in the UI. + @param[in] status current hd enabled status + @see WalletModel::EncryptionStatus + */ + void setHDStatus(int hdEnabled); + /** Set the encryption status as shown in the UI. @param[in] status current encryption status @see WalletModel::EncryptionStatus diff --git a/src/qt/dash.qrc b/src/qt/dash.qrc index 8f733941c7ab..29d6025c9e2f 100644 --- a/src/qt/dash.qrc +++ b/src/qt/dash.qrc @@ -51,6 +51,8 @@ res/icons/drkblue/about.png res/icons/drkblue/about_qt.png res/icons/drkblue/verify.png + res/icons/drkblue/hd_enabled.png + res/icons/drkblue/hd_disabled.png res/icons/crownium/address-book.png @@ -101,6 +103,8 @@ res/icons/crownium/about.png res/icons/crownium/about_qt.png res/icons/crownium/verify.png + res/icons/crownium/hd_enabled.png + res/icons/crownium/hd_disabled.png res/icons/light/address-book.png @@ -151,6 +155,8 @@ res/icons/light/about.png res/icons/light/about_qt.png res/icons/light/verify.png + res/icons/light/hd_enabled.png + res/icons/light/hd_disabled.png res/icons/trad/address-book.png @@ -201,6 +207,8 @@ res/icons/trad/about.png res/icons/trad/about_qt.png res/icons/trad/verify.png + res/icons/trad/hd_enabled.png + res/icons/trad/hd_disabled.png res/css/drkblue.css diff --git a/src/qt/res/icons/crownium/hd_disabled.png b/src/qt/res/icons/crownium/hd_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..687b6d2e38e9e3f8955072574ce644735a249982 GIT binary patch literal 4328 zcma)A2{>D6zds?j5~lW{wu(^IS|WBKg2XnceQ9kCVkr_!nh;yGmKH@#YpJc2ZYWx% zi=gOdI$BiKE@-QETCtVl#>{lSd!Of<``q_=&Uu&f|Ns8$yPeCnr_2s>i*o}2aM;2e zXV0$b2M^>B``vDU?_yVCWD{4i1Idp}#fK9BBVUp?0cH_`_b1pB@V-%DuL$}8z$qK( z=t_3AwnF)kLR9bvG%ArHp=@pd(8olE;(dY$WSBR>KahxquRL#o!vcNLa2HK$RqIeJ zAt2D47EW-WopSV{1^MXu!Z8Lg{YVrWAcR21!y-e1i4mwsH2e=-6uW&ejDW-bARz~# z;eRUSYHbU{lEMivO%+XLA5}FZOj}n4si~ohL@L45RFUckq$)y9R~d;!X=tO6TCl$g zoGmTf*AHcnGyO{zyN8AckjbGa1cFMXs!-KcNa6knq^_zTqlJSws z#0c3R2slE7Pk3M`Igmtz9U$VpN$1IEI9t;{O$Z778LpFm`%dpJ9Y~xbLx?@9yV6@-$*OWW{P9^9}Cc-;MU)BP~;xJV(AIx}&-!rGB0)oWK(G~{^}mOcD)G8`xrx{|8))K$kb&%&AazNi&A?bhrZ3ma zjZ+AwQCNcAGO7=j7R=5Erlz-!w2913y}M(RHd7MsS$_L=>fY{7n#Hg3m$jLZ*s*8F zNRYCe-V!MP(QX^L7&-LwxLw-YrxE@-^ZTkXTcLIi4zd&5&`)$I|46sZ@xl~s60_;V z=kB6~cVC%u0l~r71Q|Q5Xj|~*;z?nsa(+X%aao5> z3)jZavw{h_skvZk{;2Qjg$ox_<`))RUb2g}me#qaPoIu7gp6K0G{}giRzG>-+V%SN zwfJrIDQ#S80YzOqPp_3KlJ~s`i+L8D)^hg(Flzm{Z@DRowPqqYl*A*7Gc`?KRojTdzs$yZdKP0Cd|>NDq!myA?S16mW5&(HgKp`?rtl7zZ93ex5*V!U8XWD2XM3$` z?6$!0;tO!?Wnc|GX7+2HO*xCYI5b7Yc-GQvHb^&Y;ZYx55(SMJ-a8BVfEGW!`J(WJ zV&^n8g&n?z1@y2NWV}fw7Bj_{=w{@3=c?)@=uJaF&R&rGqJ=z{eMH@lZ5G#Iak&g> z3^8Ly)=94{>FZhif@{{`j*t)x!qNNW7Ubv)Ow3m8>}X@nLsm)5s>SY8rYa=<=9K&F zw-h7yM+y8r;95gKJc85~O91?#8Ge196PZ7alB@=$96|RS4w=Kmk=YLUH|yO0ldfNAxIFOtBo`8itm)MmG)GA3hU3;2278E%{S4Ljx4$0dDZ}vO0S3L#sU+N_zFPogdF+I@ zkI(fYwwd-MM*MG|k$YX}C*ESx6f?l&_u8Mb0F`6@s zCfsr%7`I)u#sW237#47hR!qchdStTZc+aLWTed!=9c9#1RS9F0f4aI)s%fY8{*o3e zHH?SSm-_DBzPEB+-oMm0b)41`zaRJZmKh%_x=`LQ|D^%utqjzVhKSpmBI=HlT6Ue& zr7@mQt^1!j#^%=Ky(hJY4>;kVgNTMu&qb zIj>q;|0Rn3-8W4C*0>0?N>%}p#omG_ClnQTJnZfJdcHg>FUNJgdi9pbbR1Q-?koLS zMC6>Eon7SV$9`{a zIV_irv{!DJUXz+sPusoBQ~44$?NF#(l>Vjmm2*C?N4O9O6-a%@ z;8?P>9;!M7FKsig`Z9~=P`}?TtxYT}hIk(l#QH^6UP*&w5$g~Hyb+*OEpA!$Ohz~@ ztLi?hPB{G-zP;8|e{7K4ZUT;c@9i~^s1M3=#LKzN(lVF&D6{BnL#t%pUgqBC0u>p+ zuj0MP&MGTQ9_YK5UVjq(hI`JLcNX2(R(6Iq843mYxh-<#TzQ@-2eP8o44N5TI3}pP z)W4IQd`R-%IvHkDr~(64f%{4uA4$!{%kDM+iD7o`eAZ~i3do)@jXY-#El?x;e%lbs zs{(74zIcSylqCyjS?8@EEjZ~YmH9?$=+l(I&L;ESug01wzjx>w(ua-GcakZM&G=b< zV6-AV#Gzph-EbZsom>AnXmEboLxXDpJYq$cTv~39uk`U+K(x-FIF8Ut{DbrAtyk(w zg(URw3X60OzB3ZSsr8ou$mTM4_~MXfv9KVxbs)eet)4i9x}x0AUgvpF83&41Upbj{ zk{^~#M}gd+n3L)8CYCaB+DeCB@$PnPJ&YR}8>^b0nF*?Nsc|kiNnNfAVK$xa>h2!! zlGcy3EkH*%m}d9oq-kab7&Q1do{e;rFh4?kz2UTo`B!*dwvS{BC)?I(J6jbKc1k={ zR+xYvCU{uUCUU6s4UPebX~XA1%5|V>qQ|2p5SbIdP~FJhyfW=W6owCBq1qUl^fK60V77?i0il8w6fhC5 zV6TMBio#*_y{1q2Z?>k^H|m0HmmP_@recol88Us^xlx_fCP z9q5g&8haHUpg>PI{*CP+v6GrT$nC801cd}d)=+?9_R1kpAvQyB_CBXqeZW<- z6`5~|jtH;5R z1CnO~Bt7pgpm{YN6{yQLD`8t7I2Vm`1%S@$;+;A3JFB6E_c&>pJ}$S0V^w4UUX^QK zVmMdWo7Bg8;W>qpIUeQcWVv%;>SG93LGR3vDY^eVAuX<({4HCcao zcX$ylD%h%{i*0=PP>u1q_zleq8+mQ~qcI2na&BhSbLt9jy?&|}&de-&VV5frOt}&b z%{u<*oReb6%g)ZU)z$EU9e!DQ^DX1YBhF{e+_?MTfggjJEdEx85uLXD{uVMbJ!ZBmXF!~ zyL%Rm4;q!+vfX>E)nWCBrsSi(8?2e`qAr@WANZ2oEZ6bD{{F3FpjDPv^pfVlj_MV# zH|VZ26WTx=zuyC`Eeh(mB(SaGe){Hc?VGAiJPr_oE)OS7wG+RvV)F!Bw?fOq+T?$~ zUQzzMx#0cxh0X$nPQHc%T3#y${~jz%PT?LH HpS|$k8tbb^ literal 0 HcmV?d00001 diff --git a/src/qt/res/icons/crownium/hd_enabled.png b/src/qt/res/icons/crownium/hd_enabled.png new file mode 100644 index 0000000000000000000000000000000000000000..568dde1cd1c1dba2dc1d575365ecd89aa7e55f68 GIT binary patch literal 1889 zcmb_dYfuwc6kZ@AP*Lea@Frmh+xa8 zbwEae`r;v0d=zN4ty6q}jt?jxR%DP@E!N3cD+mQgu~6&=0sEu$PrG+_&pmg}_nmX^ z`F3`XI40cD!Nmap0LRD(frPX{bo+V<4bbKUL9bv?fIAe@dY22!0tEeGs!5{;NB+wv)0yJpSv^d8QOr7H8kaNp2 zLc!g;B8u%^-rQWTmgSpwZ3JYVqk~A`c8QD0C1^XIQp06DSh>d#FY; z8;uhZvgB8w+wP-=3BQpySOuY?J2{BXyPVJeap1T(QoP5(*)!elR z=EOo|F_5LRf9v$j5-A_cbXK>dGpl?=OUfN1b$#9b#5no6bVLexQbW%`X~OP>u|VVP zbM%^){RXf`PIYRpl1GL1Q1b%XF9d-;nI4e4*Ne*MJgqr*zI*ld58Ackp--;b)TL4P z4B$Z%X(Ts}|V%ezc*YdeN&_ z>+5RelBeD~4%{y^4eXV|lJ1H=>1OZVqK%ZM6>&v(qdq_9W;N2{XFiWbJAVqiyy5@u z&}z@cU_)!X`^w`^q!srRaVSkte)CIADhT0b8H*CLX>sLOjQ3XEYTiIOMei-fy(T-) zKRn4h^d(Jlxaca}nH^epBCG%HLX)vrukdW!ru@p@6~9=#&*_O zot|`6M+9|X|8a>d4={-r4ACY7OA_*d z$ze%dzbrYdsMkn|fNBa_7O{8%u(cs{SNou>>Q07N@vrvP`;6JYeGPB;;M^0jve|E^nljXT~{qHBbl`Jm+Chr_j zne7TqCXid7?hJSA1zs+x=--&>#o0NKv}?Y7$C|i92W_n6_pQc?gjSo6s4nar6rWjg zTVJ2Op#zy{80{C+tsW;|csTNtUH{VBbuY?1a}sH|y#6h#iL3SVE?qwxy~29zny@k9 zO3H_ZZ8<<$-}q2E4TqXPTMU+V>22iYgNZyKM0RRGPg90F=|S%EL&xR5PnQ!lvGKcs z$BNPsv9nqB)YV#br29eaj&8yBhN0bies4NxPyHn>C91rx#a*Y2X3VcBbveH>XfWKI zj|H~f^yFs6ms_7}i8TxRq85Wky6in)6d$Kjn%tk8cAQ9er56BYNhkLw zo|&M<=h+oH4(M$b9!^rNKX&vzd?~3Uz0H6dcQ8`g<_uo$dax-$#2EW7_;j((HezXm zXjlWRNl8i=(^rDrjIZyd_vVgvQc$4x?xjC-Iu2m|!%?}nZ<^gGT!6g)j}hVVin0uN XRMO0!1b*IZ`RPT5#RzIclXm_E9we}T literal 0 HcmV?d00001 diff --git a/src/qt/res/icons/drkblue/hd_disabled.png b/src/qt/res/icons/drkblue/hd_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..687b6d2e38e9e3f8955072574ce644735a249982 GIT binary patch literal 4328 zcma)A2{>D6zds?j5~lW{wu(^IS|WBKg2XnceQ9kCVkr_!nh;yGmKH@#YpJc2ZYWx% zi=gOdI$BiKE@-QETCtVl#>{lSd!Of<``q_=&Uu&f|Ns8$yPeCnr_2s>i*o}2aM;2e zXV0$b2M^>B``vDU?_yVCWD{4i1Idp}#fK9BBVUp?0cH_`_b1pB@V-%DuL$}8z$qK( z=t_3AwnF)kLR9bvG%ArHp=@pd(8olE;(dY$WSBR>KahxquRL#o!vcNLa2HK$RqIeJ zAt2D47EW-WopSV{1^MXu!Z8Lg{YVrWAcR21!y-e1i4mwsH2e=-6uW&ejDW-bARz~# z;eRUSYHbU{lEMivO%+XLA5}FZOj}n4si~ohL@L45RFUckq$)y9R~d;!X=tO6TCl$g zoGmTf*AHcnGyO{zyN8AckjbGa1cFMXs!-KcNa6knq^_zTqlJSws z#0c3R2slE7Pk3M`Igmtz9U$VpN$1IEI9t;{O$Z778LpFm`%dpJ9Y~xbLx?@9yV6@-$*OWW{P9^9}Cc-;MU)BP~;xJV(AIx}&-!rGB0)oWK(G~{^}mOcD)G8`xrx{|8))K$kb&%&AazNi&A?bhrZ3ma zjZ+AwQCNcAGO7=j7R=5Erlz-!w2913y}M(RHd7MsS$_L=>fY{7n#Hg3m$jLZ*s*8F zNRYCe-V!MP(QX^L7&-LwxLw-YrxE@-^ZTkXTcLIi4zd&5&`)$I|46sZ@xl~s60_;V z=kB6~cVC%u0l~r71Q|Q5Xj|~*;z?nsa(+X%aao5> z3)jZavw{h_skvZk{;2Qjg$ox_<`))RUb2g}me#qaPoIu7gp6K0G{}giRzG>-+V%SN zwfJrIDQ#S80YzOqPp_3KlJ~s`i+L8D)^hg(Flzm{Z@DRowPqqYl*A*7Gc`?KRojTdzs$yZdKP0Cd|>NDq!myA?S16mW5&(HgKp`?rtl7zZ93ex5*V!U8XWD2XM3$` z?6$!0;tO!?Wnc|GX7+2HO*xCYI5b7Yc-GQvHb^&Y;ZYx55(SMJ-a8BVfEGW!`J(WJ zV&^n8g&n?z1@y2NWV}fw7Bj_{=w{@3=c?)@=uJaF&R&rGqJ=z{eMH@lZ5G#Iak&g> z3^8Ly)=94{>FZhif@{{`j*t)x!qNNW7Ubv)Ow3m8>}X@nLsm)5s>SY8rYa=<=9K&F zw-h7yM+y8r;95gKJc85~O91?#8Ge196PZ7alB@=$96|RS4w=Kmk=YLUH|yO0ldfNAxIFOtBo`8itm)MmG)GA3hU3;2278E%{S4Ljx4$0dDZ}vO0S3L#sU+N_zFPogdF+I@ zkI(fYwwd-MM*MG|k$YX}C*ESx6f?l&_u8Mb0F`6@s zCfsr%7`I)u#sW237#47hR!qchdStTZc+aLWTed!=9c9#1RS9F0f4aI)s%fY8{*o3e zHH?SSm-_DBzPEB+-oMm0b)41`zaRJZmKh%_x=`LQ|D^%utqjzVhKSpmBI=HlT6Ue& zr7@mQt^1!j#^%=Ky(hJY4>;kVgNTMu&qb zIj>q;|0Rn3-8W4C*0>0?N>%}p#omG_ClnQTJnZfJdcHg>FUNJgdi9pbbR1Q-?koLS zMC6>Eon7SV$9`{a zIV_irv{!DJUXz+sPusoBQ~44$?NF#(l>Vjmm2*C?N4O9O6-a%@ z;8?P>9;!M7FKsig`Z9~=P`}?TtxYT}hIk(l#QH^6UP*&w5$g~Hyb+*OEpA!$Ohz~@ ztLi?hPB{G-zP;8|e{7K4ZUT;c@9i~^s1M3=#LKzN(lVF&D6{BnL#t%pUgqBC0u>p+ zuj0MP&MGTQ9_YK5UVjq(hI`JLcNX2(R(6Iq843mYxh-<#TzQ@-2eP8o44N5TI3}pP z)W4IQd`R-%IvHkDr~(64f%{4uA4$!{%kDM+iD7o`eAZ~i3do)@jXY-#El?x;e%lbs zs{(74zIcSylqCyjS?8@EEjZ~YmH9?$=+l(I&L;ESug01wzjx>w(ua-GcakZM&G=b< zV6-AV#Gzph-EbZsom>AnXmEboLxXDpJYq$cTv~39uk`U+K(x-FIF8Ut{DbrAtyk(w zg(URw3X60OzB3ZSsr8ou$mTM4_~MXfv9KVxbs)eet)4i9x}x0AUgvpF83&41Upbj{ zk{^~#M}gd+n3L)8CYCaB+DeCB@$PnPJ&YR}8>^b0nF*?Nsc|kiNnNfAVK$xa>h2!! zlGcy3EkH*%m}d9oq-kab7&Q1do{e;rFh4?kz2UTo`B!*dwvS{BC)?I(J6jbKc1k={ zR+xYvCU{uUCUU6s4UPebX~XA1%5|V>qQ|2p5SbIdP~FJhyfW=W6owCBq1qUl^fK60V77?i0il8w6fhC5 zV6TMBio#*_y{1q2Z?>k^H|m0HmmP_@recol88Us^xlx_fCP z9q5g&8haHUpg>PI{*CP+v6GrT$nC801cd}d)=+?9_R1kpAvQyB_CBXqeZW<- z6`5~|jtH;5R z1CnO~Bt7pgpm{YN6{yQLD`8t7I2Vm`1%S@$;+;A3JFB6E_c&>pJ}$S0V^w4UUX^QK zVmMdWo7Bg8;W>qpIUeQcWVv%;>SG93LGR3vDY^eVAuX<({4HCcao zcX$ylD%h%{i*0=PP>u1q_zleq8+mQ~qcI2na&BhSbLt9jy?&|}&de-&VV5frOt}&b z%{u<*oReb6%g)ZU)z$EU9e!DQ^DX1YBhF{e+_?MTfggjJEdEx85uLXD{uVMbJ!ZBmXF!~ zyL%Rm4;q!+vfX>E)nWCBrsSi(8?2e`qAr@WANZ2oEZ6bD{{F3FpjDPv^pfVlj_MV# zH|VZ26WTx=zuyC`Eeh(mB(SaGe){Hc?VGAiJPr_oE)OS7wG+RvV)F!Bw?fOq+T?$~ zUQzzMx#0cxh0X$nPQHc%T3#y${~jz%PT?LH HpS|$k8tbb^ literal 0 HcmV?d00001 diff --git a/src/qt/res/icons/drkblue/hd_enabled.png b/src/qt/res/icons/drkblue/hd_enabled.png new file mode 100644 index 0000000000000000000000000000000000000000..568dde1cd1c1dba2dc1d575365ecd89aa7e55f68 GIT binary patch literal 1889 zcmb_dYfuwc6kZ@AP*Lea@Frmh+xa8 zbwEae`r;v0d=zN4ty6q}jt?jxR%DP@E!N3cD+mQgu~6&=0sEu$PrG+_&pmg}_nmX^ z`F3`XI40cD!Nmap0LRD(frPX{bo+V<4bbKUL9bv?fIAe@dY22!0tEeGs!5{;NB+wv)0yJpSv^d8QOr7H8kaNp2 zLc!g;B8u%^-rQWTmgSpwZ3JYVqk~A`c8QD0C1^XIQp06DSh>d#FY; z8;uhZvgB8w+wP-=3BQpySOuY?J2{BXyPVJeap1T(QoP5(*)!elR z=EOo|F_5LRf9v$j5-A_cbXK>dGpl?=OUfN1b$#9b#5no6bVLexQbW%`X~OP>u|VVP zbM%^){RXf`PIYRpl1GL1Q1b%XF9d-;nI4e4*Ne*MJgqr*zI*ld58Ackp--;b)TL4P z4B$Z%X(Ts}|V%ezc*YdeN&_ z>+5RelBeD~4%{y^4eXV|lJ1H=>1OZVqK%ZM6>&v(qdq_9W;N2{XFiWbJAVqiyy5@u z&}z@cU_)!X`^w`^q!srRaVSkte)CIADhT0b8H*CLX>sLOjQ3XEYTiIOMei-fy(T-) zKRn4h^d(Jlxaca}nH^epBCG%HLX)vrukdW!ru@p@6~9=#&*_O zot|`6M+9|X|8a>d4={-r4ACY7OA_*d z$ze%dzbrYdsMkn|fNBa_7O{8%u(cs{SNou>>Q07N@vrvP`;6JYeGPB;;M^0jve|E^nljXT~{qHBbl`Jm+Chr_j zne7TqCXid7?hJSA1zs+x=--&>#o0NKv}?Y7$C|i92W_n6_pQc?gjSo6s4nar6rWjg zTVJ2Op#zy{80{C+tsW;|csTNtUH{VBbuY?1a}sH|y#6h#iL3SVE?qwxy~29zny@k9 zO3H_ZZ8<<$-}q2E4TqXPTMU+V>22iYgNZyKM0RRGPg90F=|S%EL&xR5PnQ!lvGKcs z$BNPsv9nqB)YV#br29eaj&8yBhN0bies4NxPyHn>C91rx#a*Y2X3VcBbveH>XfWKI zj|H~f^yFs6ms_7}i8TxRq85Wky6in)6d$Kjn%tk8cAQ9er56BYNhkLw zo|&M<=h+oH4(M$b9!^rNKX&vzd?~3Uz0H6dcQ8`g<_uo$dax-$#2EW7_;j((HezXm zXjlWRNl8i=(^rDrjIZyd_vVgvQc$4x?xjC-Iu2m|!%?}nZ<^gGT!6g)j}hVVin0uN XRMO0!1b*IZ`RPT5#RzIclXm_E9we}T literal 0 HcmV?d00001 diff --git a/src/qt/res/icons/light/hd_disabled.png b/src/qt/res/icons/light/hd_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..687b6d2e38e9e3f8955072574ce644735a249982 GIT binary patch literal 4328 zcma)A2{>D6zds?j5~lW{wu(^IS|WBKg2XnceQ9kCVkr_!nh;yGmKH@#YpJc2ZYWx% zi=gOdI$BiKE@-QETCtVl#>{lSd!Of<``q_=&Uu&f|Ns8$yPeCnr_2s>i*o}2aM;2e zXV0$b2M^>B``vDU?_yVCWD{4i1Idp}#fK9BBVUp?0cH_`_b1pB@V-%DuL$}8z$qK( z=t_3AwnF)kLR9bvG%ArHp=@pd(8olE;(dY$WSBR>KahxquRL#o!vcNLa2HK$RqIeJ zAt2D47EW-WopSV{1^MXu!Z8Lg{YVrWAcR21!y-e1i4mwsH2e=-6uW&ejDW-bARz~# z;eRUSYHbU{lEMivO%+XLA5}FZOj}n4si~ohL@L45RFUckq$)y9R~d;!X=tO6TCl$g zoGmTf*AHcnGyO{zyN8AckjbGa1cFMXs!-KcNa6knq^_zTqlJSws z#0c3R2slE7Pk3M`Igmtz9U$VpN$1IEI9t;{O$Z778LpFm`%dpJ9Y~xbLx?@9yV6@-$*OWW{P9^9}Cc-;MU)BP~;xJV(AIx}&-!rGB0)oWK(G~{^}mOcD)G8`xrx{|8))K$kb&%&AazNi&A?bhrZ3ma zjZ+AwQCNcAGO7=j7R=5Erlz-!w2913y}M(RHd7MsS$_L=>fY{7n#Hg3m$jLZ*s*8F zNRYCe-V!MP(QX^L7&-LwxLw-YrxE@-^ZTkXTcLIi4zd&5&`)$I|46sZ@xl~s60_;V z=kB6~cVC%u0l~r71Q|Q5Xj|~*;z?nsa(+X%aao5> z3)jZavw{h_skvZk{;2Qjg$ox_<`))RUb2g}me#qaPoIu7gp6K0G{}giRzG>-+V%SN zwfJrIDQ#S80YzOqPp_3KlJ~s`i+L8D)^hg(Flzm{Z@DRowPqqYl*A*7Gc`?KRojTdzs$yZdKP0Cd|>NDq!myA?S16mW5&(HgKp`?rtl7zZ93ex5*V!U8XWD2XM3$` z?6$!0;tO!?Wnc|GX7+2HO*xCYI5b7Yc-GQvHb^&Y;ZYx55(SMJ-a8BVfEGW!`J(WJ zV&^n8g&n?z1@y2NWV}fw7Bj_{=w{@3=c?)@=uJaF&R&rGqJ=z{eMH@lZ5G#Iak&g> z3^8Ly)=94{>FZhif@{{`j*t)x!qNNW7Ubv)Ow3m8>}X@nLsm)5s>SY8rYa=<=9K&F zw-h7yM+y8r;95gKJc85~O91?#8Ge196PZ7alB@=$96|RS4w=Kmk=YLUH|yO0ldfNAxIFOtBo`8itm)MmG)GA3hU3;2278E%{S4Ljx4$0dDZ}vO0S3L#sU+N_zFPogdF+I@ zkI(fYwwd-MM*MG|k$YX}C*ESx6f?l&_u8Mb0F`6@s zCfsr%7`I)u#sW237#47hR!qchdStTZc+aLWTed!=9c9#1RS9F0f4aI)s%fY8{*o3e zHH?SSm-_DBzPEB+-oMm0b)41`zaRJZmKh%_x=`LQ|D^%utqjzVhKSpmBI=HlT6Ue& zr7@mQt^1!j#^%=Ky(hJY4>;kVgNTMu&qb zIj>q;|0Rn3-8W4C*0>0?N>%}p#omG_ClnQTJnZfJdcHg>FUNJgdi9pbbR1Q-?koLS zMC6>Eon7SV$9`{a zIV_irv{!DJUXz+sPusoBQ~44$?NF#(l>Vjmm2*C?N4O9O6-a%@ z;8?P>9;!M7FKsig`Z9~=P`}?TtxYT}hIk(l#QH^6UP*&w5$g~Hyb+*OEpA!$Ohz~@ ztLi?hPB{G-zP;8|e{7K4ZUT;c@9i~^s1M3=#LKzN(lVF&D6{BnL#t%pUgqBC0u>p+ zuj0MP&MGTQ9_YK5UVjq(hI`JLcNX2(R(6Iq843mYxh-<#TzQ@-2eP8o44N5TI3}pP z)W4IQd`R-%IvHkDr~(64f%{4uA4$!{%kDM+iD7o`eAZ~i3do)@jXY-#El?x;e%lbs zs{(74zIcSylqCyjS?8@EEjZ~YmH9?$=+l(I&L;ESug01wzjx>w(ua-GcakZM&G=b< zV6-AV#Gzph-EbZsom>AnXmEboLxXDpJYq$cTv~39uk`U+K(x-FIF8Ut{DbrAtyk(w zg(URw3X60OzB3ZSsr8ou$mTM4_~MXfv9KVxbs)eet)4i9x}x0AUgvpF83&41Upbj{ zk{^~#M}gd+n3L)8CYCaB+DeCB@$PnPJ&YR}8>^b0nF*?Nsc|kiNnNfAVK$xa>h2!! zlGcy3EkH*%m}d9oq-kab7&Q1do{e;rFh4?kz2UTo`B!*dwvS{BC)?I(J6jbKc1k={ zR+xYvCU{uUCUU6s4UPebX~XA1%5|V>qQ|2p5SbIdP~FJhyfW=W6owCBq1qUl^fK60V77?i0il8w6fhC5 zV6TMBio#*_y{1q2Z?>k^H|m0HmmP_@recol88Us^xlx_fCP z9q5g&8haHUpg>PI{*CP+v6GrT$nC801cd}d)=+?9_R1kpAvQyB_CBXqeZW<- z6`5~|jtH;5R z1CnO~Bt7pgpm{YN6{yQLD`8t7I2Vm`1%S@$;+;A3JFB6E_c&>pJ}$S0V^w4UUX^QK zVmMdWo7Bg8;W>qpIUeQcWVv%;>SG93LGR3vDY^eVAuX<({4HCcao zcX$ylD%h%{i*0=PP>u1q_zleq8+mQ~qcI2na&BhSbLt9jy?&|}&de-&VV5frOt}&b z%{u<*oReb6%g)ZU)z$EU9e!DQ^DX1YBhF{e+_?MTfggjJEdEx85uLXD{uVMbJ!ZBmXF!~ zyL%Rm4;q!+vfX>E)nWCBrsSi(8?2e`qAr@WANZ2oEZ6bD{{F3FpjDPv^pfVlj_MV# zH|VZ26WTx=zuyC`Eeh(mB(SaGe){Hc?VGAiJPr_oE)OS7wG+RvV)F!Bw?fOq+T?$~ zUQzzMx#0cxh0X$nPQHc%T3#y${~jz%PT?LH HpS|$k8tbb^ literal 0 HcmV?d00001 diff --git a/src/qt/res/icons/light/hd_enabled.png b/src/qt/res/icons/light/hd_enabled.png new file mode 100644 index 0000000000000000000000000000000000000000..568dde1cd1c1dba2dc1d575365ecd89aa7e55f68 GIT binary patch literal 1889 zcmb_dYfuwc6kZ@AP*Lea@Frmh+xa8 zbwEae`r;v0d=zN4ty6q}jt?jxR%DP@E!N3cD+mQgu~6&=0sEu$PrG+_&pmg}_nmX^ z`F3`XI40cD!Nmap0LRD(frPX{bo+V<4bbKUL9bv?fIAe@dY22!0tEeGs!5{;NB+wv)0yJpSv^d8QOr7H8kaNp2 zLc!g;B8u%^-rQWTmgSpwZ3JYVqk~A`c8QD0C1^XIQp06DSh>d#FY; z8;uhZvgB8w+wP-=3BQpySOuY?J2{BXyPVJeap1T(QoP5(*)!elR z=EOo|F_5LRf9v$j5-A_cbXK>dGpl?=OUfN1b$#9b#5no6bVLexQbW%`X~OP>u|VVP zbM%^){RXf`PIYRpl1GL1Q1b%XF9d-;nI4e4*Ne*MJgqr*zI*ld58Ackp--;b)TL4P z4B$Z%X(Ts}|V%ezc*YdeN&_ z>+5RelBeD~4%{y^4eXV|lJ1H=>1OZVqK%ZM6>&v(qdq_9W;N2{XFiWbJAVqiyy5@u z&}z@cU_)!X`^w`^q!srRaVSkte)CIADhT0b8H*CLX>sLOjQ3XEYTiIOMei-fy(T-) zKRn4h^d(Jlxaca}nH^epBCG%HLX)vrukdW!ru@p@6~9=#&*_O zot|`6M+9|X|8a>d4={-r4ACY7OA_*d z$ze%dzbrYdsMkn|fNBa_7O{8%u(cs{SNou>>Q07N@vrvP`;6JYeGPB;;M^0jve|E^nljXT~{qHBbl`Jm+Chr_j zne7TqCXid7?hJSA1zs+x=--&>#o0NKv}?Y7$C|i92W_n6_pQc?gjSo6s4nar6rWjg zTVJ2Op#zy{80{C+tsW;|csTNtUH{VBbuY?1a}sH|y#6h#iL3SVE?qwxy~29zny@k9 zO3H_ZZ8<<$-}q2E4TqXPTMU+V>22iYgNZyKM0RRGPg90F=|S%EL&xR5PnQ!lvGKcs z$BNPsv9nqB)YV#br29eaj&8yBhN0bies4NxPyHn>C91rx#a*Y2X3VcBbveH>XfWKI zj|H~f^yFs6ms_7}i8TxRq85Wky6in)6d$Kjn%tk8cAQ9er56BYNhkLw zo|&M<=h+oH4(M$b9!^rNKX&vzd?~3Uz0H6dcQ8`g<_uo$dax-$#2EW7_;j((HezXm zXjlWRNl8i=(^rDrjIZyd_vVgvQc$4x?xjC-Iu2m|!%?}nZ<^gGT!6g)j}hVVin0uN XRMO0!1b*IZ`RPT5#RzIclXm_E9we}T literal 0 HcmV?d00001 diff --git a/src/qt/res/icons/trad/hd_disabled.png b/src/qt/res/icons/trad/hd_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..687b6d2e38e9e3f8955072574ce644735a249982 GIT binary patch literal 4328 zcma)A2{>D6zds?j5~lW{wu(^IS|WBKg2XnceQ9kCVkr_!nh;yGmKH@#YpJc2ZYWx% zi=gOdI$BiKE@-QETCtVl#>{lSd!Of<``q_=&Uu&f|Ns8$yPeCnr_2s>i*o}2aM;2e zXV0$b2M^>B``vDU?_yVCWD{4i1Idp}#fK9BBVUp?0cH_`_b1pB@V-%DuL$}8z$qK( z=t_3AwnF)kLR9bvG%ArHp=@pd(8olE;(dY$WSBR>KahxquRL#o!vcNLa2HK$RqIeJ zAt2D47EW-WopSV{1^MXu!Z8Lg{YVrWAcR21!y-e1i4mwsH2e=-6uW&ejDW-bARz~# z;eRUSYHbU{lEMivO%+XLA5}FZOj}n4si~ohL@L45RFUckq$)y9R~d;!X=tO6TCl$g zoGmTf*AHcnGyO{zyN8AckjbGa1cFMXs!-KcNa6knq^_zTqlJSws z#0c3R2slE7Pk3M`Igmtz9U$VpN$1IEI9t;{O$Z778LpFm`%dpJ9Y~xbLx?@9yV6@-$*OWW{P9^9}Cc-;MU)BP~;xJV(AIx}&-!rGB0)oWK(G~{^}mOcD)G8`xrx{|8))K$kb&%&AazNi&A?bhrZ3ma zjZ+AwQCNcAGO7=j7R=5Erlz-!w2913y}M(RHd7MsS$_L=>fY{7n#Hg3m$jLZ*s*8F zNRYCe-V!MP(QX^L7&-LwxLw-YrxE@-^ZTkXTcLIi4zd&5&`)$I|46sZ@xl~s60_;V z=kB6~cVC%u0l~r71Q|Q5Xj|~*;z?nsa(+X%aao5> z3)jZavw{h_skvZk{;2Qjg$ox_<`))RUb2g}me#qaPoIu7gp6K0G{}giRzG>-+V%SN zwfJrIDQ#S80YzOqPp_3KlJ~s`i+L8D)^hg(Flzm{Z@DRowPqqYl*A*7Gc`?KRojTdzs$yZdKP0Cd|>NDq!myA?S16mW5&(HgKp`?rtl7zZ93ex5*V!U8XWD2XM3$` z?6$!0;tO!?Wnc|GX7+2HO*xCYI5b7Yc-GQvHb^&Y;ZYx55(SMJ-a8BVfEGW!`J(WJ zV&^n8g&n?z1@y2NWV}fw7Bj_{=w{@3=c?)@=uJaF&R&rGqJ=z{eMH@lZ5G#Iak&g> z3^8Ly)=94{>FZhif@{{`j*t)x!qNNW7Ubv)Ow3m8>}X@nLsm)5s>SY8rYa=<=9K&F zw-h7yM+y8r;95gKJc85~O91?#8Ge196PZ7alB@=$96|RS4w=Kmk=YLUH|yO0ldfNAxIFOtBo`8itm)MmG)GA3hU3;2278E%{S4Ljx4$0dDZ}vO0S3L#sU+N_zFPogdF+I@ zkI(fYwwd-MM*MG|k$YX}C*ESx6f?l&_u8Mb0F`6@s zCfsr%7`I)u#sW237#47hR!qchdStTZc+aLWTed!=9c9#1RS9F0f4aI)s%fY8{*o3e zHH?SSm-_DBzPEB+-oMm0b)41`zaRJZmKh%_x=`LQ|D^%utqjzVhKSpmBI=HlT6Ue& zr7@mQt^1!j#^%=Ky(hJY4>;kVgNTMu&qb zIj>q;|0Rn3-8W4C*0>0?N>%}p#omG_ClnQTJnZfJdcHg>FUNJgdi9pbbR1Q-?koLS zMC6>Eon7SV$9`{a zIV_irv{!DJUXz+sPusoBQ~44$?NF#(l>Vjmm2*C?N4O9O6-a%@ z;8?P>9;!M7FKsig`Z9~=P`}?TtxYT}hIk(l#QH^6UP*&w5$g~Hyb+*OEpA!$Ohz~@ ztLi?hPB{G-zP;8|e{7K4ZUT;c@9i~^s1M3=#LKzN(lVF&D6{BnL#t%pUgqBC0u>p+ zuj0MP&MGTQ9_YK5UVjq(hI`JLcNX2(R(6Iq843mYxh-<#TzQ@-2eP8o44N5TI3}pP z)W4IQd`R-%IvHkDr~(64f%{4uA4$!{%kDM+iD7o`eAZ~i3do)@jXY-#El?x;e%lbs zs{(74zIcSylqCyjS?8@EEjZ~YmH9?$=+l(I&L;ESug01wzjx>w(ua-GcakZM&G=b< zV6-AV#Gzph-EbZsom>AnXmEboLxXDpJYq$cTv~39uk`U+K(x-FIF8Ut{DbrAtyk(w zg(URw3X60OzB3ZSsr8ou$mTM4_~MXfv9KVxbs)eet)4i9x}x0AUgvpF83&41Upbj{ zk{^~#M}gd+n3L)8CYCaB+DeCB@$PnPJ&YR}8>^b0nF*?Nsc|kiNnNfAVK$xa>h2!! zlGcy3EkH*%m}d9oq-kab7&Q1do{e;rFh4?kz2UTo`B!*dwvS{BC)?I(J6jbKc1k={ zR+xYvCU{uUCUU6s4UPebX~XA1%5|V>qQ|2p5SbIdP~FJhyfW=W6owCBq1qUl^fK60V77?i0il8w6fhC5 zV6TMBio#*_y{1q2Z?>k^H|m0HmmP_@recol88Us^xlx_fCP z9q5g&8haHUpg>PI{*CP+v6GrT$nC801cd}d)=+?9_R1kpAvQyB_CBXqeZW<- z6`5~|jtH;5R z1CnO~Bt7pgpm{YN6{yQLD`8t7I2Vm`1%S@$;+;A3JFB6E_c&>pJ}$S0V^w4UUX^QK zVmMdWo7Bg8;W>qpIUeQcWVv%;>SG93LGR3vDY^eVAuX<({4HCcao zcX$ylD%h%{i*0=PP>u1q_zleq8+mQ~qcI2na&BhSbLt9jy?&|}&de-&VV5frOt}&b z%{u<*oReb6%g)ZU)z$EU9e!DQ^DX1YBhF{e+_?MTfggjJEdEx85uLXD{uVMbJ!ZBmXF!~ zyL%Rm4;q!+vfX>E)nWCBrsSi(8?2e`qAr@WANZ2oEZ6bD{{F3FpjDPv^pfVlj_MV# zH|VZ26WTx=zuyC`Eeh(mB(SaGe){Hc?VGAiJPr_oE)OS7wG+RvV)F!Bw?fOq+T?$~ zUQzzMx#0cxh0X$nPQHc%T3#y${~jz%PT?LH HpS|$k8tbb^ literal 0 HcmV?d00001 diff --git a/src/qt/res/icons/trad/hd_enabled.png b/src/qt/res/icons/trad/hd_enabled.png new file mode 100644 index 0000000000000000000000000000000000000000..568dde1cd1c1dba2dc1d575365ecd89aa7e55f68 GIT binary patch literal 1889 zcmb_dYfuwc6kZ@AP*Lea@Frmh+xa8 zbwEae`r;v0d=zN4ty6q}jt?jxR%DP@E!N3cD+mQgu~6&=0sEu$PrG+_&pmg}_nmX^ z`F3`XI40cD!Nmap0LRD(frPX{bo+V<4bbKUL9bv?fIAe@dY22!0tEeGs!5{;NB+wv)0yJpSv^d8QOr7H8kaNp2 zLc!g;B8u%^-rQWTmgSpwZ3JYVqk~A`c8QD0C1^XIQp06DSh>d#FY; z8;uhZvgB8w+wP-=3BQpySOuY?J2{BXyPVJeap1T(QoP5(*)!elR z=EOo|F_5LRf9v$j5-A_cbXK>dGpl?=OUfN1b$#9b#5no6bVLexQbW%`X~OP>u|VVP zbM%^){RXf`PIYRpl1GL1Q1b%XF9d-;nI4e4*Ne*MJgqr*zI*ld58Ackp--;b)TL4P z4B$Z%X(Ts}|V%ezc*YdeN&_ z>+5RelBeD~4%{y^4eXV|lJ1H=>1OZVqK%ZM6>&v(qdq_9W;N2{XFiWbJAVqiyy5@u z&}z@cU_)!X`^w`^q!srRaVSkte)CIADhT0b8H*CLX>sLOjQ3XEYTiIOMei-fy(T-) zKRn4h^d(Jlxaca}nH^epBCG%HLX)vrukdW!ru@p@6~9=#&*_O zot|`6M+9|X|8a>d4={-r4ACY7OA_*d z$ze%dzbrYdsMkn|fNBa_7O{8%u(cs{SNou>>Q07N@vrvP`;6JYeGPB;;M^0jve|E^nljXT~{qHBbl`Jm+Chr_j zne7TqCXid7?hJSA1zs+x=--&>#o0NKv}?Y7$C|i92W_n6_pQc?gjSo6s4nar6rWjg zTVJ2Op#zy{80{C+tsW;|csTNtUH{VBbuY?1a}sH|y#6h#iL3SVE?qwxy~29zny@k9 zO3H_ZZ8<<$-}q2E4TqXPTMU+V>22iYgNZyKM0RRGPg90F=|S%EL&xR5PnQ!lvGKcs z$BNPsv9nqB)YV#br29eaj&8yBhN0bies4NxPyHn>C91rx#a*Y2X3VcBbveH>XfWKI zj|H~f^yFs6ms_7}i8TxRq85Wky6in)6d$Kjn%tk8cAQ9er56BYNhkLw zo|&M<=h+oH4(M$b9!^rNKX&vzd?~3Uz0H6dcQ8`g<_uo$dax-$#2EW7_;j((HezXm zXjlWRNl8i=(^rDrjIZyd_vVgvQc$4x?xjC-Iu2m|!%?}nZ<^gGT!6g)j}hVVin0uN XRMO0!1b*IZ`RPT5#RzIclXm_E9we}T literal 0 HcmV?d00001 diff --git a/src/qt/res/src/hd_disabled.svg b/src/qt/res/src/hd_disabled.svg new file mode 100644 index 000000000000..ab576cdd6751 --- /dev/null +++ b/src/qt/res/src/hd_disabled.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/qt/res/src/hd_enabled.svg b/src/qt/res/src/hd_enabled.svg new file mode 100644 index 000000000000..e55fbecc83a6 --- /dev/null +++ b/src/qt/res/src/hd_enabled.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index d8f5836ded0d..8261d685d1d3 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -742,3 +742,8 @@ bool WalletModel::saveReceiveRequest(const std::string &sAddress, const int64_t else return wallet->AddDestData(dest, key, sRequest); } + +bool WalletModel::hdEnabled() const +{ + return wallet->IsHDEnabled(); +} diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 369df01f0f2f..055a94fbbc2c 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -207,6 +207,8 @@ class WalletModel : public QObject void loadReceiveRequests(std::vector& vReceiveRequests); bool saveReceiveRequest(const std::string &sAddress, const int64_t nId, const std::string &sRequest); + bool hdEnabled() const; + private: CWallet *wallet; bool fHaveWatchOnly; diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index f03fceab1ac7..e36faf9e8c02 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -125,6 +125,9 @@ void WalletView::setBitcoinGUI(BitcoinGUI *gui) // Pass through transaction notifications connect(this, SIGNAL(incomingTransaction(QString,int,CAmount,QString,QString,QString)), gui, SLOT(incomingTransaction(QString,int,CAmount,QString,QString,QString))); + + // Connect HD enabled state signal + connect(this, SIGNAL(hdEnabledStatusChanged(int)), gui, SLOT(setHDStatus(int))); } } @@ -165,6 +168,9 @@ void WalletView::setWalletModel(WalletModel *walletModel) connect(walletModel, SIGNAL(encryptionStatusChanged(int)), this, SIGNAL(encryptionStatusChanged(int))); updateEncryptionStatus(); + // update HD status + Q_EMIT hdEnabledStatusChanged(walletModel->hdEnabled()); + // Balloon pop-up for new transaction connect(walletModel->getTransactionTableModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(processNewTransaction(QModelIndex,int,int))); diff --git a/src/qt/walletview.h b/src/qt/walletview.h index 6e6be2ab3cbe..b28b30502974 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -128,6 +128,8 @@ public Q_SLOTS: void message(const QString &title, const QString &message, unsigned int style); /** Encryption status of wallet changed */ void encryptionStatusChanged(int status); + /** HD-Enabled status of wallet changed (only possible during startup) */ + void hdEnabledStatusChanged(int hdEnabled); /** Notify that a new transaction appeared */ void incomingTransaction(const QString& date, int unit, const CAmount& amount, const QString& type, const QString& address, const QString& label); }; diff --git a/src/rpcmisc.cpp b/src/rpcmisc.cpp index 5f4ce18ff5fb..74539a3f43f1 100644 --- a/src/rpcmisc.cpp +++ b/src/rpcmisc.cpp @@ -280,6 +280,8 @@ UniValue validateaddress(const UniValue& params, bool fHelp) " \"pubkey\" : \"publickeyhex\", (string) The hex value of the raw public key\n" " \"iscompressed\" : true|false, (boolean) If the address is compressed\n" " \"account\" : \"account\" (string) DEPRECATED. The account associated with the address, \"\" is the default account\n" + " \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath if the key is HD and available\n" + " \"hdmasterkeyid\" : \"\" (string, optional) The Hash160 of the HD master pubkey\n" "}\n" "\nExamples:\n" + HelpExampleCli("validateaddress", "\"XwnLY9Tf7Zsef8gMGL2fhWA9ZmMjt4KPwg\"") @@ -314,6 +316,12 @@ UniValue validateaddress(const UniValue& params, bool fHelp) ret.pushKVs(detail); if (pwalletMain && pwalletMain->mapAddressBook.count(dest)) ret.push_back(Pair("account", pwalletMain->mapAddressBook[dest].name)); + CKeyID keyID; + if (pwalletMain && address.GetKeyID(keyID) && pwalletMain->mapKeyMetadata.count(keyID) && !pwalletMain->mapKeyMetadata[keyID].hdKeypath.empty()) + { + ret.push_back(Pair("hdkeypath", pwalletMain->mapKeyMetadata[keyID].hdKeypath)); + ret.push_back(Pair("hdmasterkeyid", pwalletMain->mapKeyMetadata[keyID].hdMasterKeyID.GetHex())); + } #endif } return ret; diff --git a/src/test/bip32_tests.cpp b/src/test/bip32_tests.cpp index 1d4cd7af2d9e..1dfbcdfd56db 100644 --- a/src/test/bip32_tests.cpp +++ b/src/test/bip32_tests.cpp @@ -117,6 +117,22 @@ void RunTest(const TestVector &test) { } key = keyNew; pubkey = pubkeyNew; + + CDataStream ssPub(SER_DISK, CLIENT_VERSION); + ssPub << pubkeyNew; + BOOST_CHECK(ssPub.size() == 74+1); + + CDataStream ssPriv(SER_DISK, CLIENT_VERSION); + ssPriv << keyNew; + BOOST_CHECK(ssPriv.size() == 74+1); + + CExtPubKey pubCheck; + CExtKey privCheck; + ssPub >> pubCheck; + ssPriv >> privCheck; + + BOOST_CHECK(pubCheck == pubkeyNew); + BOOST_CHECK(privCheck == keyNew); } } diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 8c7f8c802b68..0f876fe07031 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -625,19 +625,43 @@ UniValue dumpwallet(const UniValue& params, bool fHelp) file << strprintf("# * Best block at time of backup was %i (%s),\n", chainActive.Height(), chainActive.Tip()->GetBlockHash().ToString()); file << strprintf("# mined on %s\n", EncodeDumpTime(chainActive.Tip()->GetBlockTime())); file << "\n"; + + // add the base58check encoded extended master if the wallet uses HD + CKeyID masterKeyID = pwalletMain->GetHDChain().masterKeyID; + if (!masterKeyID.IsNull()) + { + CKey key; + if (pwalletMain->GetKey(masterKeyID, key)) + { + CExtKey masterKey; + masterKey.SetMaster(key.begin(), key.size()); + + CBitcoinExtKey b58extkey; + b58extkey.SetKey(masterKey); + + file << "# extended private masterkey: " << b58extkey.ToString() << "\n\n"; + } + } + for (std::vector >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) { const CKeyID &keyid = it->second; std::string strTime = EncodeDumpTime(it->first); std::string strAddr = CBitcoinAddress(keyid).ToString(); CKey key; if (pwalletMain->GetKey(keyid, key)) { + file << strprintf("%s %s ", CBitcoinSecret(key).ToString(), strTime); if (pwalletMain->mapAddressBook.count(keyid)) { - file << strprintf("%s %s label=%s # addr=%s\n", CBitcoinSecret(key).ToString(), strTime, EncodeDumpString(pwalletMain->mapAddressBook[keyid].name), strAddr); + file << strprintf("label=%s", EncodeDumpString(pwalletMain->mapAddressBook[keyid].name)); + } else if (keyid == masterKeyID) { + file << "hdmaster=1"; } else if (setKeyPool.count(keyid)) { - file << strprintf("%s %s reserve=1 # addr=%s\n", CBitcoinSecret(key).ToString(), strTime, strAddr); + file << "reserve=1"; + } else if (pwalletMain->mapKeyMetadata[keyid].hdKeypath == "m") { + file << "inactivehdmaster=1"; } else { - file << strprintf("%s %s change=1 # addr=%s\n", CBitcoinSecret(key).ToString(), strTime, strAddr); + file << "change=1"; } + file << strprintf(" # addr=%s%s\n", strAddr, (pwalletMain->mapKeyMetadata[keyid].hdKeypath.size() > 0 ? " hdkeypath="+pwalletMain->mapKeyMetadata[keyid].hdKeypath : "")); } } file << "\n"; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 42e9362e08e2..83aaccad4b0a 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2197,7 +2197,7 @@ UniValue encryptwallet(const UniValue& params, bool fHelp) // slack space in .dat files; that is bad if the old data is // unencrypted private keys. So: StartShutdown(); - return "Wallet encrypted; Dash Core server stopping, restart to run with encrypted wallet. The keypool has been flushed, you need to make a new backup."; + return "Wallet encrypted; Dash Core server stopping, restart to run with encrypted wallet. The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup."; } UniValue lockunspent(const UniValue& params, bool fHelp) @@ -2381,6 +2381,8 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp) " \"keys_left\": xxxx, (numeric) how many new keys are left since last automatic backup\n" " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n" " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n" + " \"hdmasterkeyid\": \"\", (string) the Hash160 of the HD master pubkey\n" + " \"hdchildkeyindex\": xxxx, (numeric) current childkey index\n" "}\n" "\nExamples:\n" + HelpExampleCli("getwalletinfo", "") @@ -2401,6 +2403,12 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp) if (pwalletMain->IsCrypted()) obj.push_back(Pair("unlocked_until", nWalletUnlockTime)); obj.push_back(Pair("paytxfee", ValueFromAmount(payTxFee.GetFeePerK()))); + CHDChain hdChain = pwalletMain->GetHDChain(); + CKeyID masterKeyID = hdChain.masterKeyID; + if (!masterKeyID.IsNull()) { + obj.push_back(Pair("hdmasterkeyid", masterKeyID.GetHex())); + obj.push_back(Pair("hdchildkeyindex", (int64_t)hdChain.nExternalChainCounter)); + } return obj; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index c91f7a167b25..4295df7633cf 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -106,7 +106,17 @@ CPubKey CWallet::GenerateNewKey() bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets CKey secret; - secret.MakeNewKey(fCompressed); + + // Create new metadata + int64_t nCreationTime = GetTime(); + CKeyMetadata metadata(nCreationTime); + + // use HD key derivation if HD was enabled during wallet creation + if (IsHDEnabled()) { + DeriveNewChildKey(metadata, secret); + } else { + secret.MakeNewKey(fCompressed); + } // Compressed public keys were introduced in version 0.6.0 if (fCompressed) @@ -116,8 +126,7 @@ CPubKey CWallet::GenerateNewKey() assert(secret.VerifyPubKey(pubkey)); // Create new metadata - int64_t nCreationTime = GetTime(); - mapKeyMetadata[pubkey.GetID()] = CKeyMetadata(nCreationTime); + mapKeyMetadata[pubkey.GetID()] = metadata; if (!nTimeFirstKey || nCreationTime < nTimeFirstKey) nTimeFirstKey = nCreationTime; @@ -126,6 +135,46 @@ CPubKey CWallet::GenerateNewKey() return pubkey; } +void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret) +{ + // for now we use a fixed keypath scheme of m/0'/0'/k + CKey key; //master key seed (256bit) + CExtKey masterKey; //hd master key + CExtKey accountKey; //key at m/0' + CExtKey externalChainChildKey; //key at m/0'/0' + CExtKey childKey; //key at m/0'/0'/' + + // try to get the master key + if (!GetKey(hdChain.masterKeyID, key)) + throw std::runtime_error(std::string(__func__) + ": Master key not found"); + + masterKey.SetMaster(key.begin(), key.size()); + + // derive m/0' + // use hardened derivation (child keys >= 0x80000000 are hardened after bip32) + masterKey.Derive(accountKey, 0x80000000); + + // derive m/0'/0' + accountKey.Derive(externalChainChildKey, 0x80000000); + + // derive child key at next index, skip keys already known to the wallet + do { + // always derive hardened keys + // childIndex | 0x80000000 = derive childIndex in hardened child-index-range + // example: 1 | 0x80000000 == 0x80000001 == 2147483649 + externalChainChildKey.Derive(childKey, hdChain.nExternalChainCounter | 0x80000000); + metadata.hdKeypath = "m/0'/0'/" + boost::to_string(hdChain.nExternalChainCounter) + "'"; + metadata.hdMasterKeyID = hdChain.masterKeyID; + // increment childkey index + hdChain.nExternalChainCounter++; + } while (HaveKey(childKey.key.GetPubKey().GetID())); + secret = childKey.key; + + // update the chain model in the database + if (!CWalletDB(strWalletFile).WriteHDChain(hdChain)) + throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed"); +} + bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey) { AssertLockHeld(cs_wallet); // mapKeyMetadata @@ -640,6 +689,15 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) Lock(); Unlock(strWalletPassphrase); + + // if we are using HD, replace the HD master key (seed) with a new one + if (IsHDEnabled()) { + CKey key; + CPubKey masterPubKey = GenerateNewHDMasterKey(); + if (!SetHDMasterKey(masterPubKey)) + return false; + } + NewKeyPool(); Lock(); @@ -1194,6 +1252,65 @@ CAmount CWallet::GetChange(const CTxOut& txout) const return (IsChange(txout) ? txout.nValue : 0); } +CPubKey CWallet::GenerateNewHDMasterKey() + { + CKey key; + key.MakeNewKey(true); + + int64_t nCreationTime = GetTime(); + CKeyMetadata metadata(nCreationTime); + + // calculate the pubkey + CPubKey pubkey = key.GetPubKey(); + assert(key.VerifyPubKey(pubkey)); + + // set the hd keypath to "m" -> Master, refers the masterkeyid to itself + metadata.hdKeypath = "m"; + metadata.hdMasterKeyID = pubkey.GetID(); + + { + LOCK(cs_wallet); + + // mem store the metadata + mapKeyMetadata[pubkey.GetID()] = metadata; + + // write the key&metadata to the database + if (!AddKeyPubKey(key, pubkey)) + throw std::runtime_error("CWallet::GenerateNewKey(): AddKey failed"); + } + + return pubkey; +} + +bool CWallet::SetHDMasterKey(const CPubKey& pubkey) +{ + LOCK(cs_wallet); + + // store the keyid (hash160) together with + // the child index counter in the database + // as a hdchain object + CHDChain newHdChain; + newHdChain.masterKeyID = pubkey.GetID(); + SetHDChain(newHdChain, false); + + return true; +} + +bool CWallet::SetHDChain(const CHDChain& chain, bool memonly) +{ + LOCK(cs_wallet); + if (!memonly && !CWalletDB(strWalletFile).WriteHDChain(chain)) + throw runtime_error("AddHDChain(): writing chain failed"); + + hdChain = chain; + return true; +} + +bool CWallet::IsHDEnabled() +{ + return !hdChain.masterKeyID.IsNull(); +} + bool CWallet::IsMine(const CTransaction& tx) const { BOOST_FOREACH(const CTxOut& txout, tx.vout) diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index b5ce77ccec4f..34811f92e56a 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -71,6 +71,9 @@ static const CAmount nHighTransactionMaxFeeWarning = 100 * nHighTransactionFeeWa static const unsigned int MAX_FREE_TRANSACTION_CREATE_SIZE = 1000; static const bool DEFAULT_WALLETBROADCAST = true; +//! if set, all keys will be derived by using BIP32 +static const bool DEFAULT_USE_HD_WALLET = true; + class CBlockIndex; class CCoinControl; class COutput; @@ -633,6 +636,9 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface void SyncMetaData(std::pair); + /* the HD chain data model (external chain counters) */ + CHDChain hdChain; + public: /* * Main wallet lock. @@ -762,6 +768,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface * Generate a new key */ CPubKey GenerateNewKey(); + void DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret); //! Adds a key to the store, and saves it to disk. bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey); //! Adds a key to the store, without saving it to disk (used by LoadWallet) @@ -971,6 +978,21 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface /* Mark a transaction (and it in-wallet descendants) as abandoned so its inputs may be respent. */ bool AbandonTransaction(const uint256& hashTx); + + /** + * HD Wallet Functions + */ + + const CHDChain& GetHDChain() { return hdChain; } + + /* Returns true if HD is enabled */ + bool IsHDEnabled(); + /* Set the HD chain model (chain child index counters) */ + bool SetHDChain(const CHDChain& chain, bool memonly); + /* Generates a new HD master key (will not be activated) */ + CPubKey GenerateNewHDMasterKey(); + /* Set the current HD master key (will reset the chain child index counters) */ + bool SetHDMasterKey(const CPubKey& key); }; /** A key allocated from the key pool. */ diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 0076b52d055a..ce1ff6909494 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -599,6 +599,16 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, return false; } } + else if (strType == "hdchain") + { + CHDChain chain; + ssValue >> chain; + if (!pwallet->SetHDChain(chain, true)) + { + strErr = "Error reading wallet database: SetHDChain failed"; + return false; + } + } } catch (...) { return false; @@ -1091,7 +1101,7 @@ bool CWalletDB::Recover(CDBEnv& dbenv, const std::string& filename, bool fOnlyKe fReadOK = ReadKeyValue(&dummyWallet, ssKey, ssValue, wss, strType, strErr); } - if (!IsKeyType(strType)) + if (!IsKeyType(strType) && strType != "hdchain") continue; if (!fReadOK) { @@ -1127,3 +1137,9 @@ bool CWalletDB::EraseDestData(const std::string &address, const std::string &key nWalletDBUpdated++; return Erase(std::make_pair(std::string("destdata"), std::make_pair(address, key))); } + +bool CWalletDB::WriteHDChain(const CHDChain& chain) +{ + nWalletDBUpdated++; + return Write(std::string("hdchain"), chain); +} diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 64431ef0b6ab..6fbcbb1b5021 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -40,12 +40,45 @@ enum DBErrors DB_NEED_REWRITE }; +/* simple HD chain data model */ +class CHDChain +{ +public: + uint32_t nExternalChainCounter; + CKeyID masterKeyID; //!< master key hash160 + + static const int CURRENT_VERSION = 1; + int nVersion; + + CHDChain() { SetNull(); } + ADD_SERIALIZE_METHODS; + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) + { + READWRITE(this->nVersion); + nVersion = this->nVersion; + READWRITE(nExternalChainCounter); + READWRITE(masterKeyID); + } + + void SetNull() + { + nVersion = CHDChain::CURRENT_VERSION; + nExternalChainCounter = 0; + masterKeyID.SetNull(); + } +}; + class CKeyMetadata { public: - static const int CURRENT_VERSION=1; + static const int VERSION_BASIC=1; + static const int VERSION_WITH_HDDATA=10; + static const int CURRENT_VERSION=VERSION_WITH_HDDATA; int nVersion; int64_t nCreateTime; // 0 means unknown + std::string hdKeypath; //optional HD/bip32 keypath + CKeyID hdMasterKeyID; //id of the HD masterkey used to derive this key CKeyMetadata() { @@ -53,7 +86,7 @@ class CKeyMetadata } CKeyMetadata(int64_t nCreateTime_) { - nVersion = CKeyMetadata::CURRENT_VERSION; + SetNull(); nCreateTime = nCreateTime_; } @@ -64,12 +97,19 @@ class CKeyMetadata READWRITE(this->nVersion); nVersion = this->nVersion; READWRITE(nCreateTime); + if (this->nVersion >= VERSION_WITH_HDDATA) + { + READWRITE(hdKeypath); + READWRITE(hdMasterKeyID); + } } void SetNull() { nVersion = CKeyMetadata::CURRENT_VERSION; nCreateTime = 0; + hdKeypath.clear(); + hdMasterKeyID.SetNull(); } }; @@ -133,6 +173,9 @@ class CWalletDB : public CDB static bool Recover(CDBEnv& dbenv, const std::string& filename, bool fOnlyKeys); static bool Recover(CDBEnv& dbenv, const std::string& filename); + //! write the hdchain model (external chain child index counter) + bool WriteHDChain(const CHDChain& chain); + private: CWalletDB(const CWalletDB&); void operator=(const CWalletDB&); From 48ad3a0ff3fda5426258d56ba4b11b4bb8e5ead4 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Mon, 20 Mar 2017 18:37:34 +0300 Subject: [PATCH 02/31] minimal bip44 (hardcoded account and change) --- src/chainparams.cpp | 9 ++++--- src/chainparams.h | 3 ++- src/rpcmisc.cpp | 6 ++--- src/wallet/rpcdump.cpp | 4 +-- src/wallet/wallet.cpp | 50 ++++++++++++++++++++---------------- src/wallet/walletdb.cpp | 13 ++++++++++ src/wallet/walletdb.h | 56 +++++++++++++++++++++++++++++++++++------ 7 files changed, 103 insertions(+), 38 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 435afb7d1527..c7ba5f1b5ff8 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -142,8 +142,9 @@ class CMainParams : public CChainParams { base58Prefixes[EXT_PUBLIC_KEY] = boost::assign::list_of(0x04)(0x88)(0xB2)(0x1E).convert_to_container >(); // Dash BIP32 prvkeys start with 'xprv' (Bitcoin defaults) base58Prefixes[EXT_SECRET_KEY] = boost::assign::list_of(0x04)(0x88)(0xAD)(0xE4).convert_to_container >(); + // Dash BIP44 coin type is '5' - base58Prefixes[EXT_COIN_TYPE] = boost::assign::list_of(0x80)(0x00)(0x00)(0x05).convert_to_container >(); + nExtCoinType = 5; vFixedSeeds = std::vector(pnSeed6_main, pnSeed6_main + ARRAYLEN(pnSeed6_main)); @@ -260,8 +261,9 @@ class CTestNetParams : public CChainParams { base58Prefixes[EXT_PUBLIC_KEY] = boost::assign::list_of(0x04)(0x35)(0x87)(0xCF).convert_to_container >(); // Testnet Dash BIP32 prvkeys start with 'tprv' (Bitcoin defaults) base58Prefixes[EXT_SECRET_KEY] = boost::assign::list_of(0x04)(0x35)(0x83)(0x94).convert_to_container >(); + // Testnet Dash BIP44 coin type is '1' (All coin's testnet default) - base58Prefixes[EXT_COIN_TYPE] = boost::assign::list_of(0x80)(0x00)(0x00)(0x01).convert_to_container >(); + nExtCoinType = 1; vFixedSeeds = std::vector(pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test)); @@ -377,8 +379,9 @@ class CRegTestParams : public CChainParams { base58Prefixes[EXT_PUBLIC_KEY] = boost::assign::list_of(0x04)(0x35)(0x87)(0xCF).convert_to_container >(); // Regtest Dash BIP32 prvkeys start with 'tprv' (Bitcoin defaults) base58Prefixes[EXT_SECRET_KEY] = boost::assign::list_of(0x04)(0x35)(0x83)(0x94).convert_to_container >(); + // Regtest Dash BIP44 coin type is '1' (All coin's testnet default) - base58Prefixes[EXT_COIN_TYPE] = boost::assign::list_of(0x80)(0x00)(0x00)(0x01).convert_to_container >(); + nExtCoinType = 1; } }; static CRegTestParams regTestParams; diff --git a/src/chainparams.h b/src/chainparams.h index 4588983cd6a7..d1697a088150 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -48,7 +48,6 @@ class CChainParams SECRET_KEY, // BIP16 EXT_PUBLIC_KEY, // BIP32 EXT_SECRET_KEY, // BIP32 - EXT_COIN_TYPE, // BIP44 MAX_BASE58_TYPES }; @@ -75,6 +74,7 @@ class CChainParams std::string NetworkIDString() const { return strNetworkID; } const std::vector& DNSSeeds() const { return vSeeds; } const std::vector& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; } + int ExtCoinType() const { return nExtCoinType; } const std::vector& FixedSeeds() const { return vFixedSeeds; } const CCheckpointData& Checkpoints() const { return checkpointData; } int PoolMaxTransactions() const { return nPoolMaxTransactions; } @@ -93,6 +93,7 @@ class CChainParams uint64_t nPruneAfterHeight; std::vector vSeeds; std::vector base58Prefixes[MAX_BASE58_TYPES]; + int nExtCoinType; std::string strNetworkID; CBlock genesis; std::vector vFixedSeeds; diff --git a/src/rpcmisc.cpp b/src/rpcmisc.cpp index 74539a3f43f1..8366109313ea 100644 --- a/src/rpcmisc.cpp +++ b/src/rpcmisc.cpp @@ -317,10 +317,10 @@ UniValue validateaddress(const UniValue& params, bool fHelp) if (pwalletMain && pwalletMain->mapAddressBook.count(dest)) ret.push_back(Pair("account", pwalletMain->mapAddressBook[dest].name)); CKeyID keyID; - if (pwalletMain && address.GetKeyID(keyID) && pwalletMain->mapKeyMetadata.count(keyID) && !pwalletMain->mapKeyMetadata[keyID].hdKeypath.empty()) + if (pwalletMain && address.GetKeyID(keyID) && pwalletMain->mapKeyMetadata.count(keyID) && !pwalletMain->mapKeyMetadata[keyID].extkeyMetadata.IsNull()) { - ret.push_back(Pair("hdkeypath", pwalletMain->mapKeyMetadata[keyID].hdKeypath)); - ret.push_back(Pair("hdmasterkeyid", pwalletMain->mapKeyMetadata[keyID].hdMasterKeyID.GetHex())); + ret.push_back(Pair("hdkeypath", pwalletMain->mapKeyMetadata[keyID].extkeyMetadata.GetKeyPath())); + ret.push_back(Pair("hdmasterkeyid", pwalletMain->mapKeyMetadata[keyID].extkeyMetadata.hdMasterKeyID.GetHex())); } #endif } diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 0f876fe07031..96df4553abd8 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -656,12 +656,12 @@ UniValue dumpwallet(const UniValue& params, bool fHelp) file << "hdmaster=1"; } else if (setKeyPool.count(keyid)) { file << "reserve=1"; - } else if (pwalletMain->mapKeyMetadata[keyid].hdKeypath == "m") { + } else if (pwalletMain->mapKeyMetadata[keyid].extkeyMetadata.GetKeyPath() == "m") { file << "inactivehdmaster=1"; } else { file << "change=1"; } - file << strprintf(" # addr=%s%s\n", strAddr, (pwalletMain->mapKeyMetadata[keyid].hdKeypath.size() > 0 ? " hdkeypath="+pwalletMain->mapKeyMetadata[keyid].hdKeypath : "")); + file << strprintf(" # addr=%s%s\n", strAddr, (pwalletMain->mapKeyMetadata[keyid].extkeyMetadata.IsNull() ? "" : " hdkeypath="+pwalletMain->mapKeyMetadata[keyid].extkeyMetadata.GetKeyPath())); } } file << "\n"; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 4295df7633cf..eb4295bdbfa6 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -137,12 +137,14 @@ CPubKey CWallet::GenerateNewKey() void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret) { - // for now we use a fixed keypath scheme of m/0'/0'/k - CKey key; //master key seed (256bit) - CExtKey masterKey; //hd master key - CExtKey accountKey; //key at m/0' - CExtKey externalChainChildKey; //key at m/0'/0' - CExtKey childKey; //key at m/0'/0'/' + // Use BIP44 keypath scheme i.e. m / purpose' / coin_type' / account' / change / address_index + CKey key; //master key seed (256bit) + CExtKey masterKey; //hd master key + CExtKey purposeKey; //key at m/purpose' + CExtKey cointypeKey; //key at m/purpose'/coin_type' + CExtKey accountKey; //key at m/purpose'/coin_type'/account' + CExtKey changeKey; //key at m/purpose'/coin_type'/account'/change + CExtKey childKey; //key at m/purpose'/coin_type'/account'/change/address_index // try to get the master key if (!GetKey(hdChain.masterKeyID, key)) @@ -150,24 +152,31 @@ void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret) masterKey.SetMaster(key.begin(), key.size()); - // derive m/0' - // use hardened derivation (child keys >= 0x80000000 are hardened after bip32) - masterKey.Derive(accountKey, 0x80000000); + CExtKeyMetadata extkeyMetadata; + extkeyMetadata.hdMasterKeyID = hdChain.masterKeyID; + // TODO: support multiple accounts, external/internal addresses, and multiple index per each + extkeyMetadata.nAccount = 0; + extkeyMetadata.nChange = 0; - // derive m/0'/0' - accountKey.Derive(externalChainChildKey, 0x80000000); + // derive m/purpose' + // use hardened derivation (child keys >= 0x80000000 are hardened after bip32) + masterKey.Derive(purposeKey, 44 | 0x80000000); + // derive m/purpose'/coin_type' + purposeKey.Derive(cointypeKey, Params().ExtCoinType() | 0x80000000); + // derive m/purpose'/coin_type'/account' + cointypeKey.Derive(accountKey, extkeyMetadata.nAccount | 0x80000000); + // derive m/purpose'/coin_type'/account/change + accountKey.Derive(changeKey, extkeyMetadata.nChange); // derive child key at next index, skip keys already known to the wallet do { - // always derive hardened keys - // childIndex | 0x80000000 = derive childIndex in hardened child-index-range - // example: 1 | 0x80000000 == 0x80000001 == 2147483649 - externalChainChildKey.Derive(childKey, hdChain.nExternalChainCounter | 0x80000000); - metadata.hdKeypath = "m/0'/0'/" + boost::to_string(hdChain.nExternalChainCounter) + "'"; - metadata.hdMasterKeyID = hdChain.masterKeyID; + // derive m/purpose'/coin_type'/account/change/address_index + extkeyMetadata.nChild = hdChain.nExternalChainCounter; + changeKey.Derive(childKey, extkeyMetadata.nChild); // increment childkey index hdChain.nExternalChainCounter++; } while (HaveKey(childKey.key.GetPubKey().GetID())); + metadata.extkeyMetadata = extkeyMetadata; secret = childKey.key; // update the chain model in the database @@ -1261,12 +1270,11 @@ CPubKey CWallet::GenerateNewHDMasterKey() CKeyMetadata metadata(nCreationTime); // calculate the pubkey - CPubKey pubkey = key.GetPubKey(); + CPubKey pubkey = key.GetPubKey(); assert(key.VerifyPubKey(pubkey)); - // set the hd keypath to "m" -> Master, refers the masterkeyid to itself - metadata.hdKeypath = "m"; - metadata.hdMasterKeyID = pubkey.GetID(); + metadata.extkeyMetadata.fMaster = true; + metadata.extkeyMetadata.hdMasterKeyID = pubkey.GetID(); { LOCK(cs_wallet); diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index ce1ff6909494..c7fd2fca05f2 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -25,6 +25,19 @@ using namespace std; static uint64_t nAccountingEntryNumber = 0; + +std::string CExtKeyMetadata::GetKeyPath() +{ + if(fMaster) + return "m"; + + return "m/44'/" + + boost::to_string(Params().ExtCoinType()) + "'/" + + boost::to_string(nAccount) + "'/" + + boost::to_string(nChange) + "/" + + boost::to_string(nChild); +} + // // CWalletDB // diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 6fbcbb1b5021..124c79ccfe60 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -69,16 +69,58 @@ class CHDChain } }; +class CExtKeyMetadata +{ +public: + CKeyID hdMasterKeyID; //id of the HD masterkey used to derive this key + unsigned int nAccount; + unsigned int nChange; + unsigned int nChild; + bool fMaster; + + CExtKeyMetadata() + { + SetNull(); + } + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { + READWRITE(hdMasterKeyID); + READWRITE(nAccount); + READWRITE(nChange); + READWRITE(nChild); + READWRITE(fMaster); + } + + void SetNull() + { + hdMasterKeyID.SetNull(); + nAccount = 0; + nChange = 0; + nChild = 0; + fMaster = false; + } + + bool IsNull() + { + return hdMasterKeyID == CKeyID(); + } + + std::string GetKeyPath(); +}; + class CKeyMetadata { public: static const int VERSION_BASIC=1; static const int VERSION_WITH_HDDATA=10; - static const int CURRENT_VERSION=VERSION_WITH_HDDATA; + static const int VERSION_WITH_HDDATA_BIP44=11; + static const int CURRENT_VERSION=VERSION_WITH_HDDATA_BIP44; int nVersion; int64_t nCreateTime; // 0 means unknown - std::string hdKeypath; //optional HD/bip32 keypath - CKeyID hdMasterKeyID; //id of the HD masterkey used to derive this key + CExtKeyMetadata extkeyMetadata; CKeyMetadata() { @@ -97,10 +139,9 @@ class CKeyMetadata READWRITE(this->nVersion); nVersion = this->nVersion; READWRITE(nCreateTime); - if (this->nVersion >= VERSION_WITH_HDDATA) + if (this->nVersion >= VERSION_WITH_HDDATA_BIP44) { - READWRITE(hdKeypath); - READWRITE(hdMasterKeyID); + READWRITE(extkeyMetadata); } } @@ -108,8 +149,7 @@ class CKeyMetadata { nVersion = CKeyMetadata::CURRENT_VERSION; nCreateTime = 0; - hdKeypath.clear(); - hdMasterKeyID.SetNull(); + extkeyMetadata.SetNull(); } }; From 267e9c50c04a27a3b36cb3f5f0cf0416cf04479a Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Thu, 30 Mar 2017 14:36:47 +0300 Subject: [PATCH 03/31] Do not recreate HD wallet on encryption Adjusted keypool.py test --- qa/rpc-tests/keypool.py | 6 +++--- src/init.cpp | 5 +++++ src/wallet/wallet.cpp | 11 +++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/qa/rpc-tests/keypool.py b/qa/rpc-tests/keypool.py index 6e001f09c3bc..607727bc407a 100755 --- a/qa/rpc-tests/keypool.py +++ b/qa/rpc-tests/keypool.py @@ -28,14 +28,14 @@ def run_test(self): addr = nodes[0].getnewaddress() addr_data = nodes[0].validateaddress(addr) wallet_info = nodes[0].getwalletinfo() - assert(addr_before_encrypting_data['hdmasterkeyid'] != wallet_info['hdmasterkeyid']) + assert(addr_before_encrypting_data['hdmasterkeyid'] == wallet_info['hdmasterkeyid']) assert(addr_data['hdmasterkeyid'] == wallet_info['hdmasterkeyid']) try: addr = nodes[0].getnewaddress() - raise AssertionError('Keypool should be exhausted after one address') except JSONRPCException as e: - assert(e.error['code']==-12) + raise AssertionError('Keypool should not be exhausted after one address') + # assert(e.error['code']==-12) # put three new keys in the keypool nodes[0].walletpassphrase('test', 12000) diff --git a/src/init.cpp b/src/init.cpp index 1e6956ed8846..e812fd9473c3 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1697,6 +1697,11 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) return InitError(strprintf(_("Error loading %s: You can't enable HD on a already existing non-HD wallet"), strWalletFile)); } + // Warn user every time he starts non-encrypted HD wallet + if (GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET) && !pwalletMain->IsLocked()) { + InitWarning(_("Make sure to encrypt your wallet and delete all non-encrypted backups after you verified that wallet works!")); + } + LogPrintf("%s", strErrors.str()); LogPrintf(" wallet %15dms\n", GetTimeMillis() - nStart); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index eb4295bdbfa6..cd34e4853d7c 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -699,15 +699,10 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) Lock(); Unlock(strWalletPassphrase); - // if we are using HD, replace the HD master key (seed) with a new one - if (IsHDEnabled()) { - CKey key; - CPubKey masterPubKey = GenerateNewHDMasterKey(); - if (!SetHDMasterKey(masterPubKey)) - return false; - } + // if we are not using HD, generate new keypool + if(!IsHDEnabled()) + NewKeyPool(); - NewKeyPool(); Lock(); // Need to completely rewrite the wallet file; if we don't, bdb might keep From 300e10c8717617e6d812e32609b3582d248c1c2e Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Fri, 31 Mar 2017 06:05:11 +0300 Subject: [PATCH 04/31] Do not store any private keys for hd wallet besides the master one Derive all keys on the fly. Original idea/implementation - btc PR9298, backported and improved --- src/init.cpp | 4 +- src/rpcmisc.cpp | 6 +- src/wallet/rpcdump.cpp | 4 +- src/wallet/wallet.cpp | 157 ++++++++++++++++++++++++++++++++++------ src/wallet/wallet.h | 16 +++- src/wallet/walletdb.cpp | 38 +++++++++- src/wallet/walletdb.h | 55 +++++--------- 7 files changed, 205 insertions(+), 75 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index e812fd9473c3..70131fcb80f2 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1664,9 +1664,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if (GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET) && !pwalletMain->IsHDEnabled()) { // generate a new master key - CPubKey masterPubKey = pwalletMain->GenerateNewHDMasterKey(); - if (!pwalletMain->SetHDMasterKey(masterPubKey)) - throw std::runtime_error(std::string(__func__) + ": Storing master key failed"); + pwalletMain->GenerateNewHDMasterKey(); } CPubKey newDefaultKey; diff --git a/src/rpcmisc.cpp b/src/rpcmisc.cpp index 8366109313ea..99fc15db46a6 100644 --- a/src/rpcmisc.cpp +++ b/src/rpcmisc.cpp @@ -317,10 +317,10 @@ UniValue validateaddress(const UniValue& params, bool fHelp) if (pwalletMain && pwalletMain->mapAddressBook.count(dest)) ret.push_back(Pair("account", pwalletMain->mapAddressBook[dest].name)); CKeyID keyID; - if (pwalletMain && address.GetKeyID(keyID) && pwalletMain->mapKeyMetadata.count(keyID) && !pwalletMain->mapKeyMetadata[keyID].extkeyMetadata.IsNull()) + if (pwalletMain && address.GetKeyID(keyID) && pwalletMain->mapHdPubKeys.count(keyID)) { - ret.push_back(Pair("hdkeypath", pwalletMain->mapKeyMetadata[keyID].extkeyMetadata.GetKeyPath())); - ret.push_back(Pair("hdmasterkeyid", pwalletMain->mapKeyMetadata[keyID].extkeyMetadata.hdMasterKeyID.GetHex())); + ret.push_back(Pair("hdkeypath", pwalletMain->mapHdPubKeys[keyID].GetKeyPath())); + ret.push_back(Pair("hdmasterkeyid", pwalletMain->mapHdPubKeys[keyID].masterKeyID.GetHex())); } #endif } diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 96df4553abd8..8a469955ed91 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -656,12 +656,10 @@ UniValue dumpwallet(const UniValue& params, bool fHelp) file << "hdmaster=1"; } else if (setKeyPool.count(keyid)) { file << "reserve=1"; - } else if (pwalletMain->mapKeyMetadata[keyid].extkeyMetadata.GetKeyPath() == "m") { - file << "inactivehdmaster=1"; } else { file << "change=1"; } - file << strprintf(" # addr=%s%s\n", strAddr, (pwalletMain->mapKeyMetadata[keyid].extkeyMetadata.IsNull() ? "" : " hdkeypath="+pwalletMain->mapKeyMetadata[keyid].extkeyMetadata.GetKeyPath())); + file << strprintf(" # addr=%s%s\n", strAddr, (pwalletMain->mapHdPubKeys.count(keyid) ? " hdkeypath="+pwalletMain->mapHdPubKeys[keyid].GetKeyPath() : "")); } } file << "\n"; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index cd34e4853d7c..b1e83084f690 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -111,27 +111,29 @@ CPubKey CWallet::GenerateNewKey() int64_t nCreationTime = GetTime(); CKeyMetadata metadata(nCreationTime); + CPubKey pubkey; // use HD key derivation if HD was enabled during wallet creation if (IsHDEnabled()) { DeriveNewChildKey(metadata, secret); + pubkey = secret.GetPubKey(); } else { secret.MakeNewKey(fCompressed); - } - // Compressed public keys were introduced in version 0.6.0 - if (fCompressed) - SetMinVersion(FEATURE_COMPRPUBKEY); + // Compressed public keys were introduced in version 0.6.0 + if (fCompressed) + SetMinVersion(FEATURE_COMPRPUBKEY); - CPubKey pubkey = secret.GetPubKey(); - assert(secret.VerifyPubKey(pubkey)); + pubkey = secret.GetPubKey(); + assert(secret.VerifyPubKey(pubkey)); - // Create new metadata - mapKeyMetadata[pubkey.GetID()] = metadata; - if (!nTimeFirstKey || nCreationTime < nTimeFirstKey) - nTimeFirstKey = nCreationTime; + // Create new metadata + mapKeyMetadata[pubkey.GetID()] = metadata; + if (!nTimeFirstKey || nCreationTime < nTimeFirstKey) + nTimeFirstKey = nCreationTime; - if (!AddKeyPubKey(secret, pubkey)) - throw std::runtime_error("CWallet::GenerateNewKey(): AddKey failed"); + if (!AddKeyPubKey(secret, pubkey)) + throw std::runtime_error(std::string(__func__) + ": AddKey failed"); + } return pubkey; } @@ -152,36 +154,144 @@ void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret) masterKey.SetMaster(key.begin(), key.size()); - CExtKeyMetadata extkeyMetadata; - extkeyMetadata.hdMasterKeyID = hdChain.masterKeyID; + // Use hardened derivation for purpose, coin_type and account + // (keys >= 0x80000000 are hardened after bip32) // TODO: support multiple accounts, external/internal addresses, and multiple index per each - extkeyMetadata.nAccount = 0; - extkeyMetadata.nChange = 0; // derive m/purpose' - // use hardened derivation (child keys >= 0x80000000 are hardened after bip32) masterKey.Derive(purposeKey, 44 | 0x80000000); // derive m/purpose'/coin_type' purposeKey.Derive(cointypeKey, Params().ExtCoinType() | 0x80000000); // derive m/purpose'/coin_type'/account' - cointypeKey.Derive(accountKey, extkeyMetadata.nAccount | 0x80000000); + cointypeKey.Derive(accountKey, 0x80000000); // derive m/purpose'/coin_type'/account/change - accountKey.Derive(changeKey, extkeyMetadata.nChange); + accountKey.Derive(changeKey, 0); // derive child key at next index, skip keys already known to the wallet do { // derive m/purpose'/coin_type'/account/change/address_index - extkeyMetadata.nChild = hdChain.nExternalChainCounter; - changeKey.Derive(childKey, extkeyMetadata.nChild); + changeKey.Derive(childKey, hdChain.nExternalChainCounter); // increment childkey index hdChain.nExternalChainCounter++; } while (HaveKey(childKey.key.GetPubKey().GetID())); - metadata.extkeyMetadata = extkeyMetadata; secret = childKey.key; + CPubKey pubkey = secret.GetPubKey(); + assert(secret.VerifyPubKey(pubkey)); + + // store metadata + mapKeyMetadata[pubkey.GetID()] = metadata; + if (!nTimeFirstKey || metadata.nCreateTime < nTimeFirstKey) + nTimeFirstKey = metadata.nCreateTime; + // update the chain model in the database if (!CWalletDB(strWalletFile).WriteHDChain(hdChain)) throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed"); + + if (!AddHDPubKey(childKey.Neuter())) + throw std::runtime_error(std::string(__func__) + ": AddHDPubKey failed"); +} + +bool CWallet::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const +{ + LOCK(cs_wallet); + std::map::const_iterator mi = mapHdPubKeys.find(address); + if (mi != mapHdPubKeys.end()) + { + const CHDPubKey &hdPubKey = (*mi).second; + vchPubKeyOut = hdPubKey.extPubKey.pubkey; + return true; + } + else + return CCryptoKeyStore::GetPubKey(address, vchPubKeyOut); +} + +bool CWallet::GetKey(const CKeyID &address, CKey& keyOut) const +{ + LOCK(cs_wallet); + std::map::const_iterator mi = mapHdPubKeys.find(address); + if (mi != mapHdPubKeys.end() && mi->first != mi->second.masterKeyID) + { + // if the key has been found in mapHdPubKeys, derive it on the fly + const CHDPubKey &hdPubKey = (*mi).second; + + // TODO: refactor with DeriveNewChildKey + + // Use BIP44 keypath scheme i.e. m / purpose' / coin_type' / account' / change / address_index + CKey key; //master key seed (256bit) + CExtKey masterKey; //hd master key + CExtKey purposeKey; //key at m/purpose' + CExtKey cointypeKey; //key at m/purpose'/coin_type' + CExtKey accountKey; //key at m/purpose'/coin_type'/account' + CExtKey changeKey; //key at m/purpose'/coin_type'/account'/change + CExtKey childKey; //key at m/purpose'/coin_type'/account'/change/address_index + + // try to get the master key + if (!GetKey(hdPubKey.masterKeyID, key)) + return false; + + masterKey.SetMaster(key.begin(), key.size()); + + // Use hardened derivation for purpose, coin_type and account + // (keys >= 0x80000000 are hardened after bip32) + // TODO: support multiple accounts, external/internal addresses, and multiple index per each + + // derive m/purpose' + masterKey.Derive(purposeKey, 44 | 0x80000000); + // derive m/purpose'/coin_type' + purposeKey.Derive(cointypeKey, Params().ExtCoinType() | 0x80000000); + // derive m/purpose'/coin_type'/account' + cointypeKey.Derive(accountKey, 0x80000000); + // derive m/purpose'/coin_type'/account/change + accountKey.Derive(changeKey, 0); + // derive m/purpose'/coin_type'/account/change/address_index + changeKey.Derive(childKey, hdPubKey.extPubKey.nChild); + keyOut = childKey.key; + + return true; + } + else + return CCryptoKeyStore::GetKey(address, keyOut); +} + +bool CWallet::HaveKey(const CKeyID &address) const +{ + LOCK(cs_wallet); + if (mapHdPubKeys.count(address) > 0) + return true; + return CCryptoKeyStore::HaveKey(address); +} + +bool CWallet::LoadHDPubKey(const CHDPubKey &hdPubKey) +{ + AssertLockHeld(cs_wallet); + + mapHdPubKeys[hdPubKey.extPubKey.pubkey.GetID()] = hdPubKey; + return true; +} + +bool CWallet::AddHDPubKey(const CExtPubKey &extPubKey) +{ + AssertLockHeld(cs_wallet); + + CHDPubKey hdPubKey; + hdPubKey.extPubKey = extPubKey; + hdPubKey.masterKeyID = hdChain.masterKeyID; + mapHdPubKeys[extPubKey.pubkey.GetID()] = hdPubKey; + + // check if we need to remove from watch-only + CScript script; + script = GetScriptForDestination(extPubKey.pubkey.GetID()); + if (HaveWatchOnly(script)) + RemoveWatchOnly(script); + script = GetScriptForRawPubKey(extPubKey.pubkey); + if (HaveWatchOnly(script)) + RemoveWatchOnly(script); + + if (!fFileBacked) + return true; + + return CWalletDB(strWalletFile).WriteHDPubKey(hdPubKey, mapKeyMetadata[extPubKey.pubkey.GetID()]); } bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey) @@ -1279,7 +1389,7 @@ CPubKey CWallet::GenerateNewHDMasterKey() // write the key&metadata to the database if (!AddKeyPubKey(key, pubkey)) - throw std::runtime_error("CWallet::GenerateNewKey(): AddKey failed"); + throw std::runtime_error("CWallet::GenerateNewHDMasterKey(): AddKey failed"); } return pubkey; @@ -1289,6 +1399,7 @@ bool CWallet::SetHDMasterKey(const CPubKey& pubkey) { LOCK(cs_wallet); + SetMinVersion(FEATURE_HD); // store the keyid (hash160) together with // the child index counter in the database // as a hdchain object diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 34811f92e56a..21660a7bd59a 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -89,6 +89,8 @@ enum WalletFeature FEATURE_WALLETCRYPT = 40000, // wallet encryption FEATURE_COMPRPUBKEY = 60000, // compressed public keys + FEATURE_HD = 120200, // Hierarchical key derivation after BIP32 (HD Wallet), BIP44 (multi-coin), BIP39 (mnemonic) + // which uses on-the-fly private key derivation FEATURE_LATEST = 61000 }; @@ -715,6 +717,8 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface int64_t nTimeFirstKey; int64_t nKeysLeftSinceAutoBackup; + std::map mapHdPubKeys; //> vchPubKey; + + CHDPubKey hdPubKey; + ssValue >> hdPubKey; + + if(vchPubKey != hdPubKey.extPubKey.pubkey) + { + strErr = "Error reading wallet database: CHDPubKey corrupt"; + return false; + } + if (!pwallet->LoadHDPubKey(hdPubKey)) + { + strErr = "Error reading wallet database: LoadHDPubKey failed"; + return false; + } + } } catch (...) { return false; @@ -1114,7 +1133,7 @@ bool CWalletDB::Recover(CDBEnv& dbenv, const std::string& filename, bool fOnlyKe fReadOK = ReadKeyValue(&dummyWallet, ssKey, ssValue, wss, strType, strErr); } - if (!IsKeyType(strType) && strType != "hdchain") + if (!IsKeyType(strType) && strType != "hdchain" && strType != "hdpubkey") continue; if (!fReadOK) { @@ -1156,3 +1175,14 @@ bool CWalletDB::WriteHDChain(const CHDChain& chain) nWalletDBUpdated++; return Write(std::string("hdchain"), chain); } + +bool CWalletDB::WriteHDPubKey(const CHDPubKey& hdPubKey, const CKeyMetadata& keyMeta) +{ + nWalletDBUpdated++; + + // NOTE: allow metadata overwriting for master HD pubkey + if (!Write(std::make_pair(std::string("keymeta"), hdPubKey.extPubKey.pubkey), keyMeta, hdPubKey.IsMaster())) + return false; + + return Write(std::make_pair(std::string("hdpubkey"), hdPubKey.extPubKey.pubkey), hdPubKey, false); +} diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 124c79ccfe60..dd530a32376c 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -69,58 +69,40 @@ class CHDChain } }; -class CExtKeyMetadata +/* hd pubkey data model */ +class CHDPubKey { public: - CKeyID hdMasterKeyID; //id of the HD masterkey used to derive this key + static const int CURRENT_VERSION = 1; + int nVersion; + CExtPubKey extPubKey; + CKeyID masterKeyID; unsigned int nAccount; unsigned int nChange; - unsigned int nChild; - bool fMaster; - CExtKeyMetadata() - { - SetNull(); - } + CHDPubKey() : nVersion(CHDPubKey::CURRENT_VERSION), nAccount(0), nChange(0) {} ADD_SERIALIZE_METHODS; - template - inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { - READWRITE(hdMasterKeyID); + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) + { + READWRITE(this->nVersion); + READWRITE(extPubKey); + READWRITE(masterKeyID); READWRITE(nAccount); READWRITE(nChange); - READWRITE(nChild); - READWRITE(fMaster); - } - - void SetNull() - { - hdMasterKeyID.SetNull(); - nAccount = 0; - nChange = 0; - nChild = 0; - fMaster = false; } - bool IsNull() - { - return hdMasterKeyID == CKeyID(); - } - - std::string GetKeyPath(); + bool IsMaster() const { return extPubKey.pubkey.GetID() == masterKeyID; } + std::string GetKeyPath() const; }; class CKeyMetadata { public: - static const int VERSION_BASIC=1; - static const int VERSION_WITH_HDDATA=10; - static const int VERSION_WITH_HDDATA_BIP44=11; - static const int CURRENT_VERSION=VERSION_WITH_HDDATA_BIP44; + static const int CURRENT_VERSION=1; int nVersion; int64_t nCreateTime; // 0 means unknown - CExtKeyMetadata extkeyMetadata; CKeyMetadata() { @@ -139,17 +121,12 @@ class CKeyMetadata READWRITE(this->nVersion); nVersion = this->nVersion; READWRITE(nCreateTime); - if (this->nVersion >= VERSION_WITH_HDDATA_BIP44) - { - READWRITE(extkeyMetadata); - } } void SetNull() { nVersion = CKeyMetadata::CURRENT_VERSION; nCreateTime = 0; - extkeyMetadata.SetNull(); } }; @@ -216,6 +193,8 @@ class CWalletDB : public CDB //! write the hdchain model (external chain child index counter) bool WriteHDChain(const CHDChain& chain); + bool WriteHDPubKey(const CHDPubKey& hdPubKey, const CKeyMetadata& keyMeta); + private: CWalletDB(const CWalletDB&); void operator=(const CWalletDB&); From 7e3d2e3c48323600dbf428cac5af3a7f88755452 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Wed, 29 Mar 2017 15:02:17 +0300 Subject: [PATCH 05/31] minimal bip39 Additional cmd-line options for new wallet: -mnemonic -mnemonicpassphrase --- src/Makefile.am | 5 +- src/Makefile.test.include | 2 + src/bip39.cpp | 179 +++ src/bip39.h | 42 + src/bip39_english.h | 2074 ++++++++++++++++++++++++++++++ src/init.cpp | 2 + src/test/bip39_tests.cpp | 68 + src/test/data/bip39_vectors.json | 146 +++ src/wallet/wallet.cpp | 38 +- 9 files changed, 2550 insertions(+), 6 deletions(-) create mode 100644 src/bip39.cpp create mode 100644 src/bip39.h create mode 100644 src/bip39_english.h create mode 100644 src/test/bip39_tests.cpp create mode 100644 src/test/data/bip39_vectors.json diff --git a/src/Makefile.am b/src/Makefile.am index 4dfec82fd058..0992c8bf219c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -73,6 +73,8 @@ BITCOIN_CORE_H = \ amount.h \ arith_uint256.h \ base58.h \ + bip39.h \ + bip39_english.h \ bloom.h \ cachemap.h \ cachemultimap.h \ @@ -260,7 +262,7 @@ libbitcoin_wallet_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) libbitcoin_wallet_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_wallet_a_SOURCES = \ activemasternode.cpp \ - privatesend-client.cpp \ + bip39.cpp \ dsnotificationinterface.cpp \ instantx.cpp \ masternode.cpp \ @@ -269,6 +271,7 @@ libbitcoin_wallet_a_SOURCES = \ masternodeconfig.cpp \ masternodeman.cpp \ keepass.cpp \ + privatesend-client.cpp \ wallet/crypter.cpp \ wallet/db.cpp \ wallet/rpcdump.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index eb72b785acc4..b6ef12394f02 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -24,6 +24,7 @@ JSON_TEST_FILES = \ test/data/base58_keys_valid.json \ test/data/base58_encode_decode.json \ test/data/base58_keys_invalid.json \ + test/data/bip39_vectors.json \ test/data/tx_invalid.json \ test/data/tx_valid.json \ test/data/sighash.json @@ -42,6 +43,7 @@ BITCOIN_TESTS =\ test/base58_tests.cpp \ test/base64_tests.cpp \ test/bip32_tests.cpp \ + test/bip39_tests.cpp \ test/bloom_tests.cpp \ test/bswap_tests.cpp \ test/cachemap_tests.cpp \ diff --git a/src/bip39.cpp b/src/bip39.cpp new file mode 100644 index 000000000000..41d2f0362dba --- /dev/null +++ b/src/bip39.cpp @@ -0,0 +1,179 @@ +/** + * Copyright (c) 2013-2014 Tomas Dzetkulic + * Copyright (c) 2013-2014 Pavol Rusnak + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +// Source: +// https://github.com/trezor/trezor-crypto + +#include +#include + +#include "bip39.h" +// #include "hmac.h" +// #include "rand.h" +// #include "sha2.h" +// #include "pbkdf2.h" +#include "bip39_english.h" +// #include "options.h" +#include "crypto/sha256.h" +#include "random.h" + +#include + +const char *mnemonic_generate(int strength) +{ + if (strength % 32 || strength < 128 || strength > 256) { + return 0; + } + uint8_t data[32]; + // random_buffer(data, 32); + GetRandBytes(data, 32); + return mnemonic_from_data(data, strength / 8); +} + +const char *mnemonic_from_data(const uint8_t *data, int len) +{ + if (len % 4 || len < 16 || len > 32) { + return 0; + } + + uint8_t bits[32 + 1]; + + // sha256_Raw(data, len, bits); + CSHA256().Write(data, len).Finalize(bits); + // checksum + bits[len] = bits[0]; + // data + memcpy(bits, data, len); + + int mlen = len * 3 / 4; + static char mnemo[24 * 10]; + + int i, j, idx; + char *p = mnemo; + for (i = 0; i < mlen; i++) { + idx = 0; + for (j = 0; j < 11; j++) { + idx <<= 1; + idx += (bits[(i * 11 + j) / 8] & (1 << (7 - ((i * 11 + j) % 8)))) > 0; + } + strcpy(p, wordlist[idx]); + p += strlen(wordlist[idx]); + *p = (i < mlen - 1) ? ' ' : 0; + p++; + } + + return mnemo; +} + +int mnemonic_check(const char *mnemonic) +{ + if (!mnemonic) { + return 0; + } + + uint32_t i, n; + + i = 0; n = 0; + while (mnemonic[i]) { + if (mnemonic[i] == ' ') { + n++; + } + i++; + } + n++; + // check number of words + if (n != 12 && n != 18 && n != 24) { + return 0; + } + + char current_word[10]; + uint32_t j, k, ki, bi; + uint8_t bits[32 + 1]; + memset(bits, 0, sizeof(bits)); + i = 0; bi = 0; + while (mnemonic[i]) { + j = 0; + while (mnemonic[i] != ' ' && mnemonic[i] != 0) { + if (j >= sizeof(current_word) - 1) { + return 0; + } + current_word[j] = mnemonic[i]; + i++; j++; + } + current_word[j] = 0; + if (mnemonic[i] != 0) i++; + k = 0; + for (;;) { + if (!wordlist[k]) { // word not found + return 0; + } + if (strcmp(current_word, wordlist[k]) == 0) { // word found on index k + for (ki = 0; ki < 11; ki++) { + if (k & (1 << (10 - ki))) { + bits[bi / 8] |= 1 << (7 - (bi % 8)); + } + bi++; + } + break; + } + k++; + } + } + if (bi != n * 11) { + return 0; + } + bits[32] = bits[n * 4 / 3]; + // sha256_Raw(bits, n * 4 / 3, bits); + CSHA256().Write(bits, n * 4 / 3).Finalize(bits); + + if (n == 12) { + return (bits[0] & 0xF0) == (bits[32] & 0xF0); // compare first 4 bits + } else + if (n == 18) { + return (bits[0] & 0xFC) == (bits[32] & 0xFC); // compare first 6 bits + } else + if (n == 24) { + return bits[0] == bits[32]; // compare 8 bits + } + return 0; +} + +// passphrase must be at most 256 characters or code may crash +void mnemonic_to_seed(const char *mnemonic, const char *passphrase, uint8_t seed[512 / 8], void (*progress_callback)(uint32_t current, uint32_t total)) +{ + int passphraselen = strlen(passphrase); + uint8_t salt[8 + 256 + 4]; + memcpy(salt, "mnemonic", 8); + memcpy(salt + 8, passphrase, passphraselen); + // pbkdf2_hmac_sha512((const uint8_t *)mnemonic, strlen(mnemonic), salt, passphraselen + 8, BIP39_PBKDF2_ROUNDS, seed, 512 / 8, progress_callback); + // int PKCS5_PBKDF2_HMAC(const char *pass, int passlen, + // const unsigned char *salt, int saltlen, int iter, + // const EVP_MD *digest, + // int keylen, unsigned char *out); + PKCS5_PBKDF2_HMAC(mnemonic, strlen(mnemonic), salt, passphraselen + 8, 2048, EVP_sha512(), 64, seed); +} + +const char * const *mnemonic_wordlist(void) +{ + return wordlist; +} diff --git a/src/bip39.h b/src/bip39.h new file mode 100644 index 000000000000..1c368238422c --- /dev/null +++ b/src/bip39.h @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2013-2014 Tomas Dzetkulic + * Copyright (c) 2013-2014 Pavol Rusnak + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __BIP39_H__ +#define __BIP39_H__ + +#include + +#define BIP39_PBKDF2_ROUNDS 2048 + +const char *mnemonic_generate(int strength); // strength in bits + +const char *mnemonic_from_data(const uint8_t *data, int len); + +int mnemonic_check(const char *mnemonic); + +// passphrase must be at most 256 characters or code may crash +void mnemonic_to_seed(const char *mnemonic, const char *passphrase, uint8_t seed[512 / 8], void (*progress_callback)(uint32_t current, uint32_t total)); + +const char * const *mnemonic_wordlist(void); + +#endif diff --git a/src/bip39_english.h b/src/bip39_english.h new file mode 100644 index 000000000000..233acc2f85a9 --- /dev/null +++ b/src/bip39_english.h @@ -0,0 +1,2074 @@ +/** + * Copyright (c) 2013-2014 Tomas Dzetkulic + * Copyright (c) 2013-2014 Pavol Rusnak + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +const char * const wordlist[] = { +"abandon", +"ability", +"able", +"about", +"above", +"absent", +"absorb", +"abstract", +"absurd", +"abuse", +"access", +"accident", +"account", +"accuse", +"achieve", +"acid", +"acoustic", +"acquire", +"across", +"act", +"action", +"actor", +"actress", +"actual", +"adapt", +"add", +"addict", +"address", +"adjust", +"admit", +"adult", +"advance", +"advice", +"aerobic", +"affair", +"afford", +"afraid", +"again", +"age", +"agent", +"agree", +"ahead", +"aim", +"air", +"airport", +"aisle", +"alarm", +"album", +"alcohol", +"alert", +"alien", +"all", +"alley", +"allow", +"almost", +"alone", +"alpha", +"already", +"also", +"alter", +"always", +"amateur", +"amazing", +"among", +"amount", +"amused", +"analyst", +"anchor", +"ancient", +"anger", +"angle", +"angry", +"animal", +"ankle", +"announce", +"annual", +"another", +"answer", +"antenna", +"antique", +"anxiety", +"any", +"apart", +"apology", +"appear", +"apple", +"approve", +"april", +"arch", +"arctic", +"area", +"arena", +"argue", +"arm", +"armed", +"armor", +"army", +"around", +"arrange", +"arrest", +"arrive", +"arrow", +"art", +"artefact", +"artist", +"artwork", +"ask", +"aspect", +"assault", +"asset", +"assist", +"assume", +"asthma", +"athlete", +"atom", +"attack", +"attend", +"attitude", +"attract", +"auction", +"audit", +"august", +"aunt", +"author", +"auto", +"autumn", +"average", +"avocado", +"avoid", +"awake", +"aware", +"away", +"awesome", +"awful", +"awkward", +"axis", +"baby", +"bachelor", +"bacon", +"badge", +"bag", +"balance", +"balcony", +"ball", +"bamboo", +"banana", +"banner", +"bar", +"barely", +"bargain", +"barrel", +"base", +"basic", +"basket", +"battle", +"beach", +"bean", +"beauty", +"because", +"become", +"beef", +"before", +"begin", +"behave", +"behind", +"believe", +"below", +"belt", +"bench", +"benefit", +"best", +"betray", +"better", +"between", +"beyond", +"bicycle", +"bid", +"bike", +"bind", +"biology", +"bird", +"birth", +"bitter", +"black", +"blade", +"blame", +"blanket", +"blast", +"bleak", +"bless", +"blind", +"blood", +"blossom", +"blouse", +"blue", +"blur", +"blush", +"board", +"boat", +"body", +"boil", +"bomb", +"bone", +"bonus", +"book", +"boost", +"border", +"boring", +"borrow", +"boss", +"bottom", +"bounce", +"box", +"boy", +"bracket", +"brain", +"brand", +"brass", +"brave", +"bread", +"breeze", +"brick", +"bridge", +"brief", +"bright", +"bring", +"brisk", +"broccoli", +"broken", +"bronze", +"broom", +"brother", +"brown", +"brush", +"bubble", +"buddy", +"budget", +"buffalo", +"build", +"bulb", +"bulk", +"bullet", +"bundle", +"bunker", +"burden", +"burger", +"burst", +"bus", +"business", +"busy", +"butter", +"buyer", +"buzz", +"cabbage", +"cabin", +"cable", +"cactus", +"cage", +"cake", +"call", +"calm", +"camera", +"camp", +"can", +"canal", +"cancel", +"candy", +"cannon", +"canoe", +"canvas", +"canyon", +"capable", +"capital", +"captain", +"car", +"carbon", +"card", +"cargo", +"carpet", +"carry", +"cart", +"case", +"cash", +"casino", +"castle", +"casual", +"cat", +"catalog", +"catch", +"category", +"cattle", +"caught", +"cause", +"caution", +"cave", +"ceiling", +"celery", +"cement", +"census", +"century", +"cereal", +"certain", +"chair", +"chalk", +"champion", +"change", +"chaos", +"chapter", +"charge", +"chase", +"chat", +"cheap", +"check", +"cheese", +"chef", +"cherry", +"chest", +"chicken", +"chief", +"child", +"chimney", +"choice", +"choose", +"chronic", +"chuckle", +"chunk", +"churn", +"cigar", +"cinnamon", +"circle", +"citizen", +"city", +"civil", +"claim", +"clap", +"clarify", +"claw", +"clay", +"clean", +"clerk", +"clever", +"click", +"client", +"cliff", +"climb", +"clinic", +"clip", +"clock", +"clog", +"close", +"cloth", +"cloud", +"clown", +"club", +"clump", +"cluster", +"clutch", +"coach", +"coast", +"coconut", +"code", +"coffee", +"coil", +"coin", +"collect", +"color", +"column", +"combine", +"come", +"comfort", +"comic", +"common", +"company", +"concert", +"conduct", +"confirm", +"congress", +"connect", +"consider", +"control", +"convince", +"cook", +"cool", +"copper", +"copy", +"coral", +"core", +"corn", +"correct", +"cost", +"cotton", +"couch", +"country", +"couple", +"course", +"cousin", +"cover", +"coyote", +"crack", +"cradle", +"craft", +"cram", +"crane", +"crash", +"crater", +"crawl", +"crazy", +"cream", +"credit", +"creek", +"crew", +"cricket", +"crime", +"crisp", +"critic", +"crop", +"cross", +"crouch", +"crowd", +"crucial", +"cruel", +"cruise", +"crumble", +"crunch", +"crush", +"cry", +"crystal", +"cube", +"culture", +"cup", +"cupboard", +"curious", +"current", +"curtain", +"curve", +"cushion", +"custom", +"cute", +"cycle", +"dad", +"damage", +"damp", +"dance", +"danger", +"daring", +"dash", +"daughter", +"dawn", +"day", +"deal", +"debate", +"debris", +"decade", +"december", +"decide", +"decline", +"decorate", +"decrease", +"deer", +"defense", +"define", +"defy", +"degree", +"delay", +"deliver", +"demand", +"demise", +"denial", +"dentist", +"deny", +"depart", +"depend", +"deposit", +"depth", +"deputy", +"derive", +"describe", +"desert", +"design", +"desk", +"despair", +"destroy", +"detail", +"detect", +"develop", +"device", +"devote", +"diagram", +"dial", +"diamond", +"diary", +"dice", +"diesel", +"diet", +"differ", +"digital", +"dignity", +"dilemma", +"dinner", +"dinosaur", +"direct", +"dirt", +"disagree", +"discover", +"disease", +"dish", +"dismiss", +"disorder", +"display", +"distance", +"divert", +"divide", +"divorce", +"dizzy", +"doctor", +"document", +"dog", +"doll", +"dolphin", +"domain", +"donate", +"donkey", +"donor", +"door", +"dose", +"double", +"dove", +"draft", +"dragon", +"drama", +"drastic", +"draw", +"dream", +"dress", +"drift", +"drill", +"drink", +"drip", +"drive", +"drop", +"drum", +"dry", +"duck", +"dumb", +"dune", +"during", +"dust", +"dutch", +"duty", +"dwarf", +"dynamic", +"eager", +"eagle", +"early", +"earn", +"earth", +"easily", +"east", +"easy", +"echo", +"ecology", +"economy", +"edge", +"edit", +"educate", +"effort", +"egg", +"eight", +"either", +"elbow", +"elder", +"electric", +"elegant", +"element", +"elephant", +"elevator", +"elite", +"else", +"embark", +"embody", +"embrace", +"emerge", +"emotion", +"employ", +"empower", +"empty", +"enable", +"enact", +"end", +"endless", +"endorse", +"enemy", +"energy", +"enforce", +"engage", +"engine", +"enhance", +"enjoy", +"enlist", +"enough", +"enrich", +"enroll", +"ensure", +"enter", +"entire", +"entry", +"envelope", +"episode", +"equal", +"equip", +"era", +"erase", +"erode", +"erosion", +"error", +"erupt", +"escape", +"essay", +"essence", +"estate", +"eternal", +"ethics", +"evidence", +"evil", +"evoke", +"evolve", +"exact", +"example", +"excess", +"exchange", +"excite", +"exclude", +"excuse", +"execute", +"exercise", +"exhaust", +"exhibit", +"exile", +"exist", +"exit", +"exotic", +"expand", +"expect", +"expire", +"explain", +"expose", +"express", +"extend", +"extra", +"eye", +"eyebrow", +"fabric", +"face", +"faculty", +"fade", +"faint", +"faith", +"fall", +"false", +"fame", +"family", +"famous", +"fan", +"fancy", +"fantasy", +"farm", +"fashion", +"fat", +"fatal", +"father", +"fatigue", +"fault", +"favorite", +"feature", +"february", +"federal", +"fee", +"feed", +"feel", +"female", +"fence", +"festival", +"fetch", +"fever", +"few", +"fiber", +"fiction", +"field", +"figure", +"file", +"film", +"filter", +"final", +"find", +"fine", +"finger", +"finish", +"fire", +"firm", +"first", +"fiscal", +"fish", +"fit", +"fitness", +"fix", +"flag", +"flame", +"flash", +"flat", +"flavor", +"flee", +"flight", +"flip", +"float", +"flock", +"floor", +"flower", +"fluid", +"flush", +"fly", +"foam", +"focus", +"fog", +"foil", +"fold", +"follow", +"food", +"foot", +"force", +"forest", +"forget", +"fork", +"fortune", +"forum", +"forward", +"fossil", +"foster", +"found", +"fox", +"fragile", +"frame", +"frequent", +"fresh", +"friend", +"fringe", +"frog", +"front", +"frost", +"frown", +"frozen", +"fruit", +"fuel", +"fun", +"funny", +"furnace", +"fury", +"future", +"gadget", +"gain", +"galaxy", +"gallery", +"game", +"gap", +"garage", +"garbage", +"garden", +"garlic", +"garment", +"gas", +"gasp", +"gate", +"gather", +"gauge", +"gaze", +"general", +"genius", +"genre", +"gentle", +"genuine", +"gesture", +"ghost", +"giant", +"gift", +"giggle", +"ginger", +"giraffe", +"girl", +"give", +"glad", +"glance", +"glare", +"glass", +"glide", +"glimpse", +"globe", +"gloom", +"glory", +"glove", +"glow", +"glue", +"goat", +"goddess", +"gold", +"good", +"goose", +"gorilla", +"gospel", +"gossip", +"govern", +"gown", +"grab", +"grace", +"grain", +"grant", +"grape", +"grass", +"gravity", +"great", +"green", +"grid", +"grief", +"grit", +"grocery", +"group", +"grow", +"grunt", +"guard", +"guess", +"guide", +"guilt", +"guitar", +"gun", +"gym", +"habit", +"hair", +"half", +"hammer", +"hamster", +"hand", +"happy", +"harbor", +"hard", +"harsh", +"harvest", +"hat", +"have", +"hawk", +"hazard", +"head", +"health", +"heart", +"heavy", +"hedgehog", +"height", +"hello", +"helmet", +"help", +"hen", +"hero", +"hidden", +"high", +"hill", +"hint", +"hip", +"hire", +"history", +"hobby", +"hockey", +"hold", +"hole", +"holiday", +"hollow", +"home", +"honey", +"hood", +"hope", +"horn", +"horror", +"horse", +"hospital", +"host", +"hotel", +"hour", +"hover", +"hub", +"huge", +"human", +"humble", +"humor", +"hundred", +"hungry", +"hunt", +"hurdle", +"hurry", +"hurt", +"husband", +"hybrid", +"ice", +"icon", +"idea", +"identify", +"idle", +"ignore", +"ill", +"illegal", +"illness", +"image", +"imitate", +"immense", +"immune", +"impact", +"impose", +"improve", +"impulse", +"inch", +"include", +"income", +"increase", +"index", +"indicate", +"indoor", +"industry", +"infant", +"inflict", +"inform", +"inhale", +"inherit", +"initial", +"inject", +"injury", +"inmate", +"inner", +"innocent", +"input", +"inquiry", +"insane", +"insect", +"inside", +"inspire", +"install", +"intact", +"interest", +"into", +"invest", +"invite", +"involve", +"iron", +"island", +"isolate", +"issue", +"item", +"ivory", +"jacket", +"jaguar", +"jar", +"jazz", +"jealous", +"jeans", +"jelly", +"jewel", +"job", +"join", +"joke", +"journey", +"joy", +"judge", +"juice", +"jump", +"jungle", +"junior", +"junk", +"just", +"kangaroo", +"keen", +"keep", +"ketchup", +"key", +"kick", +"kid", +"kidney", +"kind", +"kingdom", +"kiss", +"kit", +"kitchen", +"kite", +"kitten", +"kiwi", +"knee", +"knife", +"knock", +"know", +"lab", +"label", +"labor", +"ladder", +"lady", +"lake", +"lamp", +"language", +"laptop", +"large", +"later", +"latin", +"laugh", +"laundry", +"lava", +"law", +"lawn", +"lawsuit", +"layer", +"lazy", +"leader", +"leaf", +"learn", +"leave", +"lecture", +"left", +"leg", +"legal", +"legend", +"leisure", +"lemon", +"lend", +"length", +"lens", +"leopard", +"lesson", +"letter", +"level", +"liar", +"liberty", +"library", +"license", +"life", +"lift", +"light", +"like", +"limb", +"limit", +"link", +"lion", +"liquid", +"list", +"little", +"live", +"lizard", +"load", +"loan", +"lobster", +"local", +"lock", +"logic", +"lonely", +"long", +"loop", +"lottery", +"loud", +"lounge", +"love", +"loyal", +"lucky", +"luggage", +"lumber", +"lunar", +"lunch", +"luxury", +"lyrics", +"machine", +"mad", +"magic", +"magnet", +"maid", +"mail", +"main", +"major", +"make", +"mammal", +"man", +"manage", +"mandate", +"mango", +"mansion", +"manual", +"maple", +"marble", +"march", +"margin", +"marine", +"market", +"marriage", +"mask", +"mass", +"master", +"match", +"material", +"math", +"matrix", +"matter", +"maximum", +"maze", +"meadow", +"mean", +"measure", +"meat", +"mechanic", +"medal", +"media", +"melody", +"melt", +"member", +"memory", +"mention", +"menu", +"mercy", +"merge", +"merit", +"merry", +"mesh", +"message", +"metal", +"method", +"middle", +"midnight", +"milk", +"million", +"mimic", +"mind", +"minimum", +"minor", +"minute", +"miracle", +"mirror", +"misery", +"miss", +"mistake", +"mix", +"mixed", +"mixture", +"mobile", +"model", +"modify", +"mom", +"moment", +"monitor", +"monkey", +"monster", +"month", +"moon", +"moral", +"more", +"morning", +"mosquito", +"mother", +"motion", +"motor", +"mountain", +"mouse", +"move", +"movie", +"much", +"muffin", +"mule", +"multiply", +"muscle", +"museum", +"mushroom", +"music", +"must", +"mutual", +"myself", +"mystery", +"myth", +"naive", +"name", +"napkin", +"narrow", +"nasty", +"nation", +"nature", +"near", +"neck", +"need", +"negative", +"neglect", +"neither", +"nephew", +"nerve", +"nest", +"net", +"network", +"neutral", +"never", +"news", +"next", +"nice", +"night", +"noble", +"noise", +"nominee", +"noodle", +"normal", +"north", +"nose", +"notable", +"note", +"nothing", +"notice", +"novel", +"now", +"nuclear", +"number", +"nurse", +"nut", +"oak", +"obey", +"object", +"oblige", +"obscure", +"observe", +"obtain", +"obvious", +"occur", +"ocean", +"october", +"odor", +"off", +"offer", +"office", +"often", +"oil", +"okay", +"old", +"olive", +"olympic", +"omit", +"once", +"one", +"onion", +"online", +"only", +"open", +"opera", +"opinion", +"oppose", +"option", +"orange", +"orbit", +"orchard", +"order", +"ordinary", +"organ", +"orient", +"original", +"orphan", +"ostrich", +"other", +"outdoor", +"outer", +"output", +"outside", +"oval", +"oven", +"over", +"own", +"owner", +"oxygen", +"oyster", +"ozone", +"pact", +"paddle", +"page", +"pair", +"palace", +"palm", +"panda", +"panel", +"panic", +"panther", +"paper", +"parade", +"parent", +"park", +"parrot", +"party", +"pass", +"patch", +"path", +"patient", +"patrol", +"pattern", +"pause", +"pave", +"payment", +"peace", +"peanut", +"pear", +"peasant", +"pelican", +"pen", +"penalty", +"pencil", +"people", +"pepper", +"perfect", +"permit", +"person", +"pet", +"phone", +"photo", +"phrase", +"physical", +"piano", +"picnic", +"picture", +"piece", +"pig", +"pigeon", +"pill", +"pilot", +"pink", +"pioneer", +"pipe", +"pistol", +"pitch", +"pizza", +"place", +"planet", +"plastic", +"plate", +"play", +"please", +"pledge", +"pluck", +"plug", +"plunge", +"poem", +"poet", +"point", +"polar", +"pole", +"police", +"pond", +"pony", +"pool", +"popular", +"portion", +"position", +"possible", +"post", +"potato", +"pottery", +"poverty", +"powder", +"power", +"practice", +"praise", +"predict", +"prefer", +"prepare", +"present", +"pretty", +"prevent", +"price", +"pride", +"primary", +"print", +"priority", +"prison", +"private", +"prize", +"problem", +"process", +"produce", +"profit", +"program", +"project", +"promote", +"proof", +"property", +"prosper", +"protect", +"proud", +"provide", +"public", +"pudding", +"pull", +"pulp", +"pulse", +"pumpkin", +"punch", +"pupil", +"puppy", +"purchase", +"purity", +"purpose", +"purse", +"push", +"put", +"puzzle", +"pyramid", +"quality", +"quantum", +"quarter", +"question", +"quick", +"quit", +"quiz", +"quote", +"rabbit", +"raccoon", +"race", +"rack", +"radar", +"radio", +"rail", +"rain", +"raise", +"rally", +"ramp", +"ranch", +"random", +"range", +"rapid", +"rare", +"rate", +"rather", +"raven", +"raw", +"razor", +"ready", +"real", +"reason", +"rebel", +"rebuild", +"recall", +"receive", +"recipe", +"record", +"recycle", +"reduce", +"reflect", +"reform", +"refuse", +"region", +"regret", +"regular", +"reject", +"relax", +"release", +"relief", +"rely", +"remain", +"remember", +"remind", +"remove", +"render", +"renew", +"rent", +"reopen", +"repair", +"repeat", +"replace", +"report", +"require", +"rescue", +"resemble", +"resist", +"resource", +"response", +"result", +"retire", +"retreat", +"return", +"reunion", +"reveal", +"review", +"reward", +"rhythm", +"rib", +"ribbon", +"rice", +"rich", +"ride", +"ridge", +"rifle", +"right", +"rigid", +"ring", +"riot", +"ripple", +"risk", +"ritual", +"rival", +"river", +"road", +"roast", +"robot", +"robust", +"rocket", +"romance", +"roof", +"rookie", +"room", +"rose", +"rotate", +"rough", +"round", +"route", +"royal", +"rubber", +"rude", +"rug", +"rule", +"run", +"runway", +"rural", +"sad", +"saddle", +"sadness", +"safe", +"sail", +"salad", +"salmon", +"salon", +"salt", +"salute", +"same", +"sample", +"sand", +"satisfy", +"satoshi", +"sauce", +"sausage", +"save", +"say", +"scale", +"scan", +"scare", +"scatter", +"scene", +"scheme", +"school", +"science", +"scissors", +"scorpion", +"scout", +"scrap", +"screen", +"script", +"scrub", +"sea", +"search", +"season", +"seat", +"second", +"secret", +"section", +"security", +"seed", +"seek", +"segment", +"select", +"sell", +"seminar", +"senior", +"sense", +"sentence", +"series", +"service", +"session", +"settle", +"setup", +"seven", +"shadow", +"shaft", +"shallow", +"share", +"shed", +"shell", +"sheriff", +"shield", +"shift", +"shine", +"ship", +"shiver", +"shock", +"shoe", +"shoot", +"shop", +"short", +"shoulder", +"shove", +"shrimp", +"shrug", +"shuffle", +"shy", +"sibling", +"sick", +"side", +"siege", +"sight", +"sign", +"silent", +"silk", +"silly", +"silver", +"similar", +"simple", +"since", +"sing", +"siren", +"sister", +"situate", +"six", +"size", +"skate", +"sketch", +"ski", +"skill", +"skin", +"skirt", +"skull", +"slab", +"slam", +"sleep", +"slender", +"slice", +"slide", +"slight", +"slim", +"slogan", +"slot", +"slow", +"slush", +"small", +"smart", +"smile", +"smoke", +"smooth", +"snack", +"snake", +"snap", +"sniff", +"snow", +"soap", +"soccer", +"social", +"sock", +"soda", +"soft", +"solar", +"soldier", +"solid", +"solution", +"solve", +"someone", +"song", +"soon", +"sorry", +"sort", +"soul", +"sound", +"soup", +"source", +"south", +"space", +"spare", +"spatial", +"spawn", +"speak", +"special", +"speed", +"spell", +"spend", +"sphere", +"spice", +"spider", +"spike", +"spin", +"spirit", +"split", +"spoil", +"sponsor", +"spoon", +"sport", +"spot", +"spray", +"spread", +"spring", +"spy", +"square", +"squeeze", +"squirrel", +"stable", +"stadium", +"staff", +"stage", +"stairs", +"stamp", +"stand", +"start", +"state", +"stay", +"steak", +"steel", +"stem", +"step", +"stereo", +"stick", +"still", +"sting", +"stock", +"stomach", +"stone", +"stool", +"story", +"stove", +"strategy", +"street", +"strike", +"strong", +"struggle", +"student", +"stuff", +"stumble", +"style", +"subject", +"submit", +"subway", +"success", +"such", +"sudden", +"suffer", +"sugar", +"suggest", +"suit", +"summer", +"sun", +"sunny", +"sunset", +"super", +"supply", +"supreme", +"sure", +"surface", +"surge", +"surprise", +"surround", +"survey", +"suspect", +"sustain", +"swallow", +"swamp", +"swap", +"swarm", +"swear", +"sweet", +"swift", +"swim", +"swing", +"switch", +"sword", +"symbol", +"symptom", +"syrup", +"system", +"table", +"tackle", +"tag", +"tail", +"talent", +"talk", +"tank", +"tape", +"target", +"task", +"taste", +"tattoo", +"taxi", +"teach", +"team", +"tell", +"ten", +"tenant", +"tennis", +"tent", +"term", +"test", +"text", +"thank", +"that", +"theme", +"then", +"theory", +"there", +"they", +"thing", +"this", +"thought", +"three", +"thrive", +"throw", +"thumb", +"thunder", +"ticket", +"tide", +"tiger", +"tilt", +"timber", +"time", +"tiny", +"tip", +"tired", +"tissue", +"title", +"toast", +"tobacco", +"today", +"toddler", +"toe", +"together", +"toilet", +"token", +"tomato", +"tomorrow", +"tone", +"tongue", +"tonight", +"tool", +"tooth", +"top", +"topic", +"topple", +"torch", +"tornado", +"tortoise", +"toss", +"total", +"tourist", +"toward", +"tower", +"town", +"toy", +"track", +"trade", +"traffic", +"tragic", +"train", +"transfer", +"trap", +"trash", +"travel", +"tray", +"treat", +"tree", +"trend", +"trial", +"tribe", +"trick", +"trigger", +"trim", +"trip", +"trophy", +"trouble", +"truck", +"true", +"truly", +"trumpet", +"trust", +"truth", +"try", +"tube", +"tuition", +"tumble", +"tuna", +"tunnel", +"turkey", +"turn", +"turtle", +"twelve", +"twenty", +"twice", +"twin", +"twist", +"two", +"type", +"typical", +"ugly", +"umbrella", +"unable", +"unaware", +"uncle", +"uncover", +"under", +"undo", +"unfair", +"unfold", +"unhappy", +"uniform", +"unique", +"unit", +"universe", +"unknown", +"unlock", +"until", +"unusual", +"unveil", +"update", +"upgrade", +"uphold", +"upon", +"upper", +"upset", +"urban", +"urge", +"usage", +"use", +"used", +"useful", +"useless", +"usual", +"utility", +"vacant", +"vacuum", +"vague", +"valid", +"valley", +"valve", +"van", +"vanish", +"vapor", +"various", +"vast", +"vault", +"vehicle", +"velvet", +"vendor", +"venture", +"venue", +"verb", +"verify", +"version", +"very", +"vessel", +"veteran", +"viable", +"vibrant", +"vicious", +"victory", +"video", +"view", +"village", +"vintage", +"violin", +"virtual", +"virus", +"visa", +"visit", +"visual", +"vital", +"vivid", +"vocal", +"voice", +"void", +"volcano", +"volume", +"vote", +"voyage", +"wage", +"wagon", +"wait", +"walk", +"wall", +"walnut", +"want", +"warfare", +"warm", +"warrior", +"wash", +"wasp", +"waste", +"water", +"wave", +"way", +"wealth", +"weapon", +"wear", +"weasel", +"weather", +"web", +"wedding", +"weekend", +"weird", +"welcome", +"west", +"wet", +"whale", +"what", +"wheat", +"wheel", +"when", +"where", +"whip", +"whisper", +"wide", +"width", +"wife", +"wild", +"will", +"win", +"window", +"wine", +"wing", +"wink", +"winner", +"winter", +"wire", +"wisdom", +"wise", +"wish", +"witness", +"wolf", +"woman", +"wonder", +"wood", +"wool", +"word", +"work", +"world", +"worry", +"worth", +"wrap", +"wreck", +"wrestle", +"wrist", +"write", +"wrong", +"yard", +"year", +"yellow", +"you", +"young", +"youth", +"zebra", +"zero", +"zone", +"zoo", +0, +}; diff --git a/src/init.cpp b/src/init.cpp index 70131fcb80f2..771f03bf319c 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -478,6 +478,8 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-maxtxfee=", strprintf(_("Maximum total fees (in %s) to use in a single wallet transaction; setting this too low may abort large transactions (default: %s)"), CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MAXFEE))); strUsage += HelpMessageOpt("-usehd", _("Use hierarchical deterministic key generation (HD) after bip32. Only has effect during wallet creation/first start") + " " + strprintf(_("(default: %u)"), DEFAULT_USE_HD_WALLET)); + strUsage += HelpMessageOpt("-mnemonic", _("User defined mnemonic for HD wallet (bip39). Only has effect during wallet creation/first start (default: randomly generated)")); + strUsage += HelpMessageOpt("-mnemonicpassphrase", _("User defined memonic passphrase for HD wallet (bip39). Only has effect during wallet creation/first start (default: randomly generated)")); strUsage += HelpMessageOpt("-upgradewallet", _("Upgrade wallet to latest format on startup")); strUsage += HelpMessageOpt("-wallet=", _("Specify wallet file (within data directory)") + " " + strprintf(_("(default: %s)"), "wallet.dat")); strUsage += HelpMessageOpt("-walletbroadcast", _("Make the wallet broadcast transactions") + " " + strprintf(_("(default: %u)"), DEFAULT_WALLETBROADCAST)); diff --git a/src/test/bip39_tests.cpp b/src/test/bip39_tests.cpp new file mode 100644 index 000000000000..f811708dcf68 --- /dev/null +++ b/src/test/bip39_tests.cpp @@ -0,0 +1,68 @@ +// Copyright (c) 2014-2017 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "base58.h" +#include "data/bip39_vectors.json.h" +#include "key.h" +#include "util.h" +#include "utilstrencodings.h" +#include "test/test_dash.h" +#include "bip39.h" + +#include + +#include + +// In script_tests.cpp +extern UniValue read_json(const std::string& jsondata); + +BOOST_FIXTURE_TEST_SUITE(bip39_tests, BasicTestingSetup) + +// https://github.com/trezor/python-mnemonic/blob/b502451a33a440783926e04428115e0bed87d01f/vectors.json +BOOST_AUTO_TEST_CASE(bip39_vectors) +{ + UniValue tests = read_json(std::string(json_tests::bip39_vectors, json_tests::bip39_vectors + sizeof(json_tests::bip39_vectors))); + + for (unsigned int i = 0; i < tests.size(); i++) { + // printf("%d\n", i); + UniValue test = tests[i]; + std::string strTest = test.write(); + if (test.size() < 4) // Allow for extra stuff (useful for comments) + { + BOOST_ERROR("Bad test: " << strTest); + continue; + } + + const char *m; + std::vector vData = ParseHex(test[0].get_str()); + m = mnemonic_from_data(&vData[0], vData.size()); + + const char *mnemonic = test[1].get_str().c_str(); + + // printf("%s\n%s\n", m, mnemonic); + BOOST_CHECK(*m == *mnemonic); + + BOOST_CHECK(mnemonic_check(mnemonic)); + + uint8_t seed[64]; + const char *passphrase = "TREZOR"; + + mnemonic_to_seed(mnemonic, passphrase, seed, 0); + // printf("seed: %s\n", HexStr(seed, seed + 64).c_str()); + BOOST_CHECK(HexStr(seed, seed + 64) == test[2].get_str()); + + CExtKey key; + CExtPubKey pubkey; + + key.SetMaster(seed, 64); + pubkey = key.Neuter(); + + CBitcoinExtKey b58key; + b58key.SetKey(key); + // printf("CBitcoinExtKey: %s\n", b58key.ToString().c_str()); + BOOST_CHECK(b58key.ToString() == test[3].get_str()); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/data/bip39_vectors.json b/src/test/data/bip39_vectors.json new file mode 100644 index 000000000000..86b38162815e --- /dev/null +++ b/src/test/data/bip39_vectors.json @@ -0,0 +1,146 @@ +[ + [ + "00000000000000000000000000000000", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + "c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04", + "xprv9s21ZrQH143K3h3fDYiay8mocZ3afhfULfb5GX8kCBdno77K4HiA15Tg23wpbeF1pLfs1c5SPmYHrEpTuuRhxMwvKDwqdKiGJS9XFKzUsAF" + ], + [ + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + "legal winner thank year wave sausage worth useful legal winner thank yellow", + "2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6fa457fe1296106559a3c80937a1c1069be3a3a5bd381ee6260e8d9739fce1f607", + "xprv9s21ZrQH143K2gA81bYFHqU68xz1cX2APaSq5tt6MFSLeXnCKV1RVUJt9FWNTbrrryem4ZckN8k4Ls1H6nwdvDTvnV7zEXs2HgPezuVccsq" + ], + [ + "80808080808080808080808080808080", + "letter advice cage absurd amount doctor acoustic avoid letter advice cage above", + "d71de856f81a8acc65e6fc851a38d4d7ec216fd0796d0a6827a3ad6ed5511a30fa280f12eb2e47ed2ac03b5c462a0358d18d69fe4f985ec81778c1b370b652a8", + "xprv9s21ZrQH143K2shfP28KM3nr5Ap1SXjz8gc2rAqqMEynmjt6o1qboCDpxckqXavCwdnYds6yBHZGKHv7ef2eTXy461PXUjBFQg6PrwY4Gzq" + ], + [ + "ffffffffffffffffffffffffffffffff", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong", + "ac27495480225222079d7be181583751e86f571027b0497b5b5d11218e0a8a13332572917f0f8e5a589620c6f15b11c61dee327651a14c34e18231052e48c069", + "xprv9s21ZrQH143K2V4oox4M8Zmhi2Fjx5XK4Lf7GKRvPSgydU3mjZuKGCTg7UPiBUD7ydVPvSLtg9hjp7MQTYsW67rZHAXeccqYqrsx8LcXnyd" + ], + [ + "000000000000000000000000000000000000000000000000", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent", + "035895f2f481b1b0f01fcf8c289c794660b289981a78f8106447707fdd9666ca06da5a9a565181599b79f53b844d8a71dd9f439c52a3d7b3e8a79c906ac845fa", + "xprv9s21ZrQH143K3mEDrypcZ2usWqFgzKB6jBBx9B6GfC7fu26X6hPRzVjzkqkPvDqp6g5eypdk6cyhGnBngbjeHTe4LsuLG1cCmKJka5SMkmU" + ], + [ + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will", + "f2b94508732bcbacbcc020faefecfc89feafa6649a5491b8c952cede496c214a0c7b3c392d168748f2d4a612bada0753b52a1c7ac53c1e93abd5c6320b9e95dd", + "xprv9s21ZrQH143K3Lv9MZLj16np5GzLe7tDKQfVusBni7toqJGcnKRtHSxUwbKUyUWiwpK55g1DUSsw76TF1T93VT4gz4wt5RM23pkaQLnvBh7" + ], + [ + "808080808080808080808080808080808080808080808080", + "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter always", + "107d7c02a5aa6f38c58083ff74f04c607c2d2c0ecc55501dadd72d025b751bc27fe913ffb796f841c49b1d33b610cf0e91d3aa239027f5e99fe4ce9e5088cd65", + "xprv9s21ZrQH143K3VPCbxbUtpkh9pRG371UCLDz3BjceqP1jz7XZsQ5EnNkYAEkfeZp62cDNj13ZTEVG1TEro9sZ9grfRmcYWLBhCocViKEJae" + ], + [ + "ffffffffffffffffffffffffffffffffffffffffffffffff", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when", + "0cd6e5d827bb62eb8fc1e262254223817fd068a74b5b449cc2f667c3f1f985a76379b43348d952e2265b4cd129090758b3e3c2c49103b5051aac2eaeb890a528", + "xprv9s21ZrQH143K36Ao5jHRVhFGDbLP6FCx8BEEmpru77ef3bmA928BxsqvVM27WnvvyfWywiFN8K6yToqMaGYfzS6Db1EHAXT5TuyCLBXUfdm" + ], + [ + "0000000000000000000000000000000000000000000000000000000000000000", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art", + "bda85446c68413707090a52022edd26a1c9462295029f2e60cd7c4f2bbd3097170af7a4d73245cafa9c3cca8d561a7c3de6f5d4a10be8ed2a5e608d68f92fcc8", + "xprv9s21ZrQH143K32qBagUJAMU2LsHg3ka7jqMcV98Y7gVeVyNStwYS3U7yVVoDZ4btbRNf4h6ibWpY22iRmXq35qgLs79f312g2kj5539ebPM" + ], + [ + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title", + "bc09fca1804f7e69da93c2f2028eb238c227f2e9dda30cd63699232578480a4021b146ad717fbb7e451ce9eb835f43620bf5c514db0f8add49f5d121449d3e87", + "xprv9s21ZrQH143K3Y1sd2XVu9wtqxJRvybCfAetjUrMMco6r3v9qZTBeXiBZkS8JxWbcGJZyio8TrZtm6pkbzG8SYt1sxwNLh3Wx7to5pgiVFU" + ], + [ + "8080808080808080808080808080808080808080808080808080808080808080", + "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless", + "c0c519bd0e91a2ed54357d9d1ebef6f5af218a153624cf4f2da911a0ed8f7a09e2ef61af0aca007096df430022f7a2b6fb91661a9589097069720d015e4e982f", + "xprv9s21ZrQH143K3CSnQNYC3MqAAqHwxeTLhDbhF43A4ss4ciWNmCY9zQGvAKUSqVUf2vPHBTSE1rB2pg4avopqSiLVzXEU8KziNnVPauTqLRo" + ], + [ + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote", + "dd48c104698c30cfe2b6142103248622fb7bb0ff692eebb00089b32d22484e1613912f0a5b694407be899ffd31ed3992c456cdf60f5d4564b8ba3f05a69890ad", + "xprv9s21ZrQH143K2WFF16X85T2QCpndrGwx6GueB72Zf3AHwHJaknRXNF37ZmDrtHrrLSHvbuRejXcnYxoZKvRquTPyp2JiNG3XcjQyzSEgqCB" + ], + [ + "9e885d952ad362caeb4efe34a8e91bd2", + "ozone drill grab fiber curtain grace pudding thank cruise elder eight picnic", + "274ddc525802f7c828d8ef7ddbcdc5304e87ac3535913611fbbfa986d0c9e5476c91689f9c8a54fd55bd38606aa6a8595ad213d4c9c9f9aca3fb217069a41028", + "xprv9s21ZrQH143K2oZ9stBYpoaZ2ktHj7jLz7iMqpgg1En8kKFTXJHsjxry1JbKH19YrDTicVwKPehFKTbmaxgVEc5TpHdS1aYhB2s9aFJBeJH" + ], + [ + "6610b25967cdcca9d59875f5cb50b0ea75433311869e930b", + "gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow fog", + "628c3827a8823298ee685db84f55caa34b5cc195a778e52d45f59bcf75aba68e4d7590e101dc414bc1bbd5737666fbbef35d1f1903953b66624f910feef245ac", + "xprv9s21ZrQH143K3uT8eQowUjsxrmsA9YUuQQK1RLqFufzybxD6DH6gPY7NjJ5G3EPHjsWDrs9iivSbmvjc9DQJbJGatfa9pv4MZ3wjr8qWPAK" + ], + [ + "68a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc74e6ce7c", + "hamster diagram private dutch cause delay private meat slide toddler razor book happy fancy gospel tennis maple dilemma loan word shrug inflict delay length", + "64c87cde7e12ecf6704ab95bb1408bef047c22db4cc7491c4271d170a1b213d20b385bc1588d9c7b38f1b39d415665b8a9030c9ec653d75e65f847d8fc1fc440", + "xprv9s21ZrQH143K2XTAhys3pMNcGn261Fi5Ta2Pw8PwaVPhg3D8DWkzWQwjTJfskj8ofb81i9NP2cUNKxwjueJHHMQAnxtivTA75uUFqPFeWzk" + ], + [ + "c0ba5a8e914111210f2bd131f3d5e08d", + "scheme spot photo card baby mountain device kick cradle pact join borrow", + "ea725895aaae8d4c1cf682c1bfd2d358d52ed9f0f0591131b559e2724bb234fca05aa9c02c57407e04ee9dc3b454aa63fbff483a8b11de949624b9f1831a9612", + "xprv9s21ZrQH143K3FperxDp8vFsFycKCRcJGAFmcV7umQmcnMZaLtZRt13QJDsoS5F6oYT6BB4sS6zmTmyQAEkJKxJ7yByDNtRe5asP2jFGhT6" + ], + [ + "6d9be1ee6ebd27a258115aad99b7317b9c8d28b6d76431c3", + "horn tenant knee talent sponsor spell gate clip pulse soap slush warm silver nephew swap uncle crack brave", + "fd579828af3da1d32544ce4db5c73d53fc8acc4ddb1e3b251a31179cdb71e853c56d2fcb11aed39898ce6c34b10b5382772db8796e52837b54468aeb312cfc3d", + "xprv9s21ZrQH143K3R1SfVZZLtVbXEB9ryVxmVtVMsMwmEyEvgXN6Q84LKkLRmf4ST6QrLeBm3jQsb9gx1uo23TS7vo3vAkZGZz71uuLCcywUkt" + ], + [ + "9f6a2878b2520799a44ef18bc7df394e7061a224d2c33cd015b157d746869863", + "panda eyebrow bullet gorilla call smoke muffin taste mesh discover soft ostrich alcohol speed nation flash devote level hobby quick inner drive ghost inside", + "72be8e052fc4919d2adf28d5306b5474b0069df35b02303de8c1729c9538dbb6fc2d731d5f832193cd9fb6aeecbc469594a70e3dd50811b5067f3b88b28c3e8d", + "xprv9s21ZrQH143K2WNnKmssvZYM96VAr47iHUQUTUyUXH3sAGNjhJANddnhw3i3y3pBbRAVk5M5qUGFr4rHbEWwXgX4qrvrceifCYQJbbFDems" + ], + [ + "23db8160a31d3e0dca3688ed941adbf3", + "cat swing flag economy stadium alone churn speed unique patch report train", + "deb5f45449e615feff5640f2e49f933ff51895de3b4381832b3139941c57b59205a42480c52175b6efcffaa58a2503887c1e8b363a707256bdd2b587b46541f5", + "xprv9s21ZrQH143K4G28omGMogEoYgDQuigBo8AFHAGDaJdqQ99QKMQ5J6fYTMfANTJy6xBmhvsNZ1CJzRZ64PWbnTFUn6CDV2FxoMDLXdk95DQ" + ], + [ + "8197a4a47f0425faeaa69deebc05ca29c0a5b5cc76ceacc0", + "light rule cinnamon wrap drastic word pride squirrel upgrade then income fatal apart sustain crack supply proud access", + "4cbdff1ca2db800fd61cae72a57475fdc6bab03e441fd63f96dabd1f183ef5b782925f00105f318309a7e9c3ea6967c7801e46c8a58082674c860a37b93eda02", + "xprv9s21ZrQH143K3wtsvY8L2aZyxkiWULZH4vyQE5XkHTXkmx8gHo6RUEfH3Jyr6NwkJhvano7Xb2o6UqFKWHVo5scE31SGDCAUsgVhiUuUDyh" + ], + [ + "066dca1a2bb7e8a1db2832148ce9933eea0f3ac9548d793112d9a95c9407efad", + "all hour make first leader extend hole alien behind guard gospel lava path output census museum junior mass reopen famous sing advance salt reform", + "26e975ec644423f4a4c4f4215ef09b4bd7ef924e85d1d17c4cf3f136c2863cf6df0a475045652c57eb5fb41513ca2a2d67722b77e954b4b3fc11f7590449191d", + "xprv9s21ZrQH143K3rEfqSM4QZRVmiMuSWY9wugscmaCjYja3SbUD3KPEB1a7QXJoajyR2T1SiXU7rFVRXMV9XdYVSZe7JoUXdP4SRHTxsT1nzm" + ], + [ + "f30f8c1da665478f49b001d94c5fc452", + "vessel ladder alter error federal sibling chat ability sun glass valve picture", + "2aaa9242daafcee6aa9d7269f17d4efe271e1b9a529178d7dc139cd18747090bf9d60295d0ce74309a78852a9caadf0af48aae1c6253839624076224374bc63f", + "xprv9s21ZrQH143K2QWV9Wn8Vvs6jbqfF1YbTCdURQW9dLFKDovpKaKrqS3SEWsXCu6ZNky9PSAENg6c9AQYHcg4PjopRGGKmdD313ZHszymnps" + ], + [ + "c10ec20dc3cd9f652c7fac2f1230f7a3c828389a14392f05", + "scissors invite lock maple supreme raw rapid void congress muscle digital elegant little brisk hair mango congress clump", + "7b4a10be9d98e6cba265566db7f136718e1398c71cb581e1b2f464cac1ceedf4f3e274dc270003c670ad8d02c4558b2f8e39edea2775c9e232c7cb798b069e88", + "xprv9s21ZrQH143K4aERa2bq7559eMCCEs2QmmqVjUuzfy5eAeDX4mqZffkYwpzGQRE2YEEeLVRoH4CSHxianrFaVnMN2RYaPUZJhJx8S5j6puX" + ], + [ + "f585c11aec520db57dd353c69554b21a89b20fb0650966fa0a9d6f74fd989d8f", + "void come effort suffer camp survey warrior heavy shoot primary clutch crush open amazing screen patrol group space point ten exist slush involve unfold", + "01f5bced59dec48e362f2c45b5de68b9fd6c92c6634f44d6d40aab69056506f0e35524a518034ddc1192e1dacd32c1ed3eaa3c3b131c88ed8e7e54c49a5d0998", + "xprv9s21ZrQH143K39rnQJknpH1WEPFJrzmAqqasiDcVrNuk926oizzJDDQkdiTvNPr2FYDYzWgiMiC63YmfPAa2oPyNB23r2g7d1yiK6WpqaQS" + ] +] diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index b1e83084f690..7f654c82f405 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -26,6 +26,7 @@ #include "util.h" #include "utilmoneystr.h" +#include "bip39.h" #include "governance.h" #include "instantx.h" #include "keepass.h" @@ -1367,17 +1368,44 @@ CAmount CWallet::GetChange(const CTxOut& txout) const } CPubKey CWallet::GenerateNewHDMasterKey() - { - CKey key; - key.MakeNewKey(true); +{ + const char *mnemonic; - int64_t nCreationTime = GetTime(); - CKeyMetadata metadata(nCreationTime); + if(mapArgs.count("-mnemonic")) { + mnemonic = GetArg("-mnemonic", "").c_str(); + } else { + mnemonic = mnemonic_generate(128); + } + + if(!mnemonic_check(mnemonic)) { + throw std::runtime_error("CWallet::GenerateNewHDMasterKey(): invalid mnemonic"); + } + // printf("mnemonic: %s\n", mnemonic); + + const char *passphrase; + if(mapArgs.count("-mnemonicpassphrase")) { + passphrase = GetArg("-mnemonicpassphrase", "").c_str(); + } else { + unsigned char rand_pwd[32]; + GetRandBytes(rand_pwd, 32); + passphrase = EncodeBase58(&rand_pwd[0],&rand_pwd[0]+32).c_str(); + } + // printf("mnemonicpassphrase: %s\n", passphrase); + + uint8_t seed[64]; + mnemonic_to_seed(mnemonic, passphrase, seed, 0); + + CExtKey extkey; + extkey.SetMaster(seed, 64); + CKey key = extkey.key; // calculate the pubkey CPubKey pubkey = key.GetPubKey(); assert(key.VerifyPubKey(pubkey)); + int64_t nCreationTime = GetTime(); + CKeyMetadata metadata(nCreationTime); + metadata.extkeyMetadata.fMaster = true; metadata.extkeyMetadata.hdMasterKeyID = pubkey.GetID(); From e670ee0f5e123658a46f8d2148bd9edfb2c9623e Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Mon, 17 Apr 2017 23:21:36 +0300 Subject: [PATCH 06/31] actually use bip39 --- qa/rpc-tests/keypool.py | 6 +- src/Makefile.am | 2 + src/hdchain.cpp | 71 ++++++++++++ src/hdchain.h | 71 ++++++++++++ src/init.cpp | 2 +- src/keystore.cpp | 6 + src/keystore.h | 6 + src/rpcmisc.cpp | 7 +- src/wallet/crypter.cpp | 108 +++++++++++++++++- src/wallet/crypter.h | 8 ++ src/wallet/rpcdump.cpp | 22 ++-- src/wallet/rpcwallet.cpp | 13 +-- src/wallet/wallet.cpp | 230 ++++++++++++++++++++------------------- src/wallet/wallet.h | 12 +- src/wallet/walletdb.cpp | 43 +++++--- src/wallet/walletdb.h | 60 +--------- 16 files changed, 446 insertions(+), 221 deletions(-) create mode 100644 src/hdchain.cpp create mode 100644 src/hdchain.h diff --git a/qa/rpc-tests/keypool.py b/qa/rpc-tests/keypool.py index 607727bc407a..59470da7a7a3 100755 --- a/qa/rpc-tests/keypool.py +++ b/qa/rpc-tests/keypool.py @@ -17,7 +17,7 @@ def run_test(self): addr_before_encrypting = nodes[0].getnewaddress() addr_before_encrypting_data = nodes[0].validateaddress(addr_before_encrypting) wallet_info_old = nodes[0].getwalletinfo() - assert(addr_before_encrypting_data['hdmasterkeyid'] == wallet_info_old['hdmasterkeyid']) + assert(addr_before_encrypting_data['hdchainid'] == wallet_info_old['hdchainid']) # Encrypt wallet and wait to terminate nodes[0].encryptwallet('test') @@ -28,8 +28,8 @@ def run_test(self): addr = nodes[0].getnewaddress() addr_data = nodes[0].validateaddress(addr) wallet_info = nodes[0].getwalletinfo() - assert(addr_before_encrypting_data['hdmasterkeyid'] == wallet_info['hdmasterkeyid']) - assert(addr_data['hdmasterkeyid'] == wallet_info['hdmasterkeyid']) + assert(addr_before_encrypting_data['hdchainid'] == wallet_info['hdchainid']) + assert(addr_data['hdchainid'] == wallet_info['hdchainid']) try: addr = nodes[0].getnewaddress() diff --git a/src/Makefile.am b/src/Makefile.am index 0992c8bf219c..7c433b05d754 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -110,6 +110,7 @@ BITCOIN_CORE_H = \ governance-votedb.h \ flat-database.h \ hash.h \ + hdchain.h \ httprpc.h \ httpserver.h \ init.h \ @@ -339,6 +340,7 @@ libbitcoin_common_a_SOURCES = \ core_read.cpp \ core_write.cpp \ hash.cpp \ + hdchain.cpp \ key.cpp \ keystore.cpp \ netbase.cpp \ diff --git a/src/hdchain.cpp b/src/hdchain.cpp new file mode 100644 index 000000000000..fd99b99acb67 --- /dev/null +++ b/src/hdchain.cpp @@ -0,0 +1,71 @@ +// Copyright (c) 2014-2017 The Dash Core developers +// Distributed under the MIT software license, see the accompanying + +#include "chainparams.h" +#include "hdchain.h" +#include "tinyformat.h" + +bool CHDChain::SetNull() +{ + nVersion = CHDChain::CURRENT_VERSION; + nExternalChainCounter = 0; + vchSeed.clear(); + id = uint256(); + return IsNull(); +} + +bool CHDChain::IsNull() const +{ + return vchSeed.empty() || id == uint256(); +} + +bool CHDChain::SetSeed(const std::vector& vchSeedIn, bool fUpdateID) +{ + vchSeed = vchSeedIn; + if (fUpdateID) + id = GetSeedHash(); + return !IsNull(); +} + +std::vector CHDChain::GetSeed() const +{ + return vchSeed; +} + +uint256 CHDChain::GetSeedHash() +{ + return Hash(vchSeed.begin(), vchSeed.end()); +} + +void CHDChain::DeriveChildExtKey(uint32_t childIndex, CExtKey& extKeyRet) +{ + // Use BIP44 keypath scheme i.e. m / purpose' / coin_type' / account' / change / address_index + CExtKey masterKey; //hd master key + CExtKey purposeKey; //key at m/purpose' + CExtKey cointypeKey; //key at m/purpose'/coin_type' + CExtKey accountKey; //key at m/purpose'/coin_type'/account' + CExtKey changeKey; //key at m/purpose'/coin_type'/account'/change + CExtKey childKey; //key at m/purpose'/coin_type'/account'/change/address_index + + masterKey.SetMaster(&vchSeed[0], vchSeed.size()); + + // Use hardened derivation for purpose, coin_type and account + // (keys >= 0x80000000 are hardened after bip32) + // TODO: support multiple accounts, external/internal addresses, and multiple index per each + + // derive m/purpose' + masterKey.Derive(purposeKey, 44 | 0x80000000); + // derive m/purpose'/coin_type' + purposeKey.Derive(cointypeKey, Params().ExtCoinType() | 0x80000000); + // derive m/purpose'/coin_type'/account' + cointypeKey.Derive(accountKey, 0x80000000); + // derive m/purpose'/coin_type'/account/change + accountKey.Derive(changeKey, 0); + // derive m/purpose'/coin_type'/account/change/address_index + changeKey.Derive(extKeyRet, childIndex); +} + +std::string CHDPubKey::GetKeyPath() const +{ + return strprintf("m/44'/%d'/%d'/%d/%d", Params().ExtCoinType(), nAccount, nChange, extPubKey.nChild); +} diff --git a/src/hdchain.h b/src/hdchain.h new file mode 100644 index 000000000000..9a3168bf9aac --- /dev/null +++ b/src/hdchain.h @@ -0,0 +1,71 @@ +// Copyright (c) 2014-2017 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +#ifndef DASH_HDCHAIN_H +#define DASH_HDCHAIN_H + +#include "key.h" + +/* simple HD chain data model */ +class CHDChain +{ +private: + std::vector vchSeed; + +public: + static const int CURRENT_VERSION = 1; + int nVersion; + uint256 id; + uint32_t nExternalChainCounter; + + CHDChain() : nVersion(CHDChain::CURRENT_VERSION), id(uint256()), nExternalChainCounter(0) { SetNull(); } + + ADD_SERIALIZE_METHODS; + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) + { + READWRITE(this->nVersion); + nVersion = this->nVersion; + READWRITE(vchSeed); + READWRITE(id); + READWRITE(nExternalChainCounter); + } + + bool SetNull(); + bool IsNull() const; + + bool SetSeed(const std::vector& vchSeedIn, bool fUpdateID); + std::vector GetSeed() const; + + uint256 GetSeedHash(); + void DeriveChildExtKey(uint32_t childIndex, CExtKey& extKeyRet); +}; + +/* hd pubkey data model */ +class CHDPubKey +{ +public: + static const int CURRENT_VERSION = 1; + int nVersion; + CExtPubKey extPubKey; + uint256 hdchainID; + unsigned int nAccount; + unsigned int nChange; + + CHDPubKey() : nVersion(CHDPubKey::CURRENT_VERSION), nAccount(0), nChange(0) {} + + ADD_SERIALIZE_METHODS; + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) + { + READWRITE(this->nVersion); + nVersion = this->nVersion; + READWRITE(extPubKey); + READWRITE(hdchainID); + READWRITE(nAccount); + READWRITE(nChange); + } + + std::string GetKeyPath() const; +}; + +#endif // DASH_HDCHAIN_H diff --git a/src/init.cpp b/src/init.cpp index 771f03bf319c..8531ff29efcf 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1666,7 +1666,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if (GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET) && !pwalletMain->IsHDEnabled()) { // generate a new master key - pwalletMain->GenerateNewHDMasterKey(); + pwalletMain->GenerateNewHDChain(); } CPubKey newDefaultKey; diff --git a/src/keystore.cpp b/src/keystore.cpp index d568a7435098..cc4b1dc4f086 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -113,3 +113,9 @@ bool CBasicKeyStore::HaveWatchOnly() const LOCK(cs_KeyStore); return (!setWatchOnly.empty()); } + +bool CBasicKeyStore::GetHDChain(CHDChain& hdChainRet) const +{ + hdChainRet = hdChain; + return !hdChain.IsNull(); +} diff --git a/src/keystore.h b/src/keystore.h index d9290722e1b1..6fee25c837af 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -6,6 +6,7 @@ #ifndef BITCOIN_KEYSTORE_H #define BITCOIN_KEYSTORE_H +#include "hdchain.h" #include "key.h" #include "pubkey.h" #include "script/script.h" @@ -59,6 +60,8 @@ class CBasicKeyStore : public CKeyStore WatchKeyMap mapWatchKeys; ScriptMap mapScripts; WatchOnlySet setWatchOnly; + /* the HD chain data model*/ + CHDChain hdChain; public: bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey); @@ -106,6 +109,9 @@ class CBasicKeyStore : public CKeyStore virtual bool RemoveWatchOnly(const CScript &dest); virtual bool HaveWatchOnly(const CScript &dest) const; virtual bool HaveWatchOnly() const; + + bool AddHDChain(const CHDChain& hdChainIn); + bool GetHDChain(CHDChain& hdChainRet) const; }; typedef std::vector > CKeyingMaterial; diff --git a/src/rpcmisc.cpp b/src/rpcmisc.cpp index 99fc15db46a6..1c72efdcc22b 100644 --- a/src/rpcmisc.cpp +++ b/src/rpcmisc.cpp @@ -281,7 +281,7 @@ UniValue validateaddress(const UniValue& params, bool fHelp) " \"iscompressed\" : true|false, (boolean) If the address is compressed\n" " \"account\" : \"account\" (string) DEPRECATED. The account associated with the address, \"\" is the default account\n" " \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath if the key is HD and available\n" - " \"hdmasterkeyid\" : \"\" (string, optional) The Hash160 of the HD master pubkey\n" + " \"hdchainid\" : \"\" (string, optional) The ID of the HD chain\n" "}\n" "\nExamples:\n" + HelpExampleCli("validateaddress", "\"XwnLY9Tf7Zsef8gMGL2fhWA9ZmMjt4KPwg\"") @@ -317,10 +317,11 @@ UniValue validateaddress(const UniValue& params, bool fHelp) if (pwalletMain && pwalletMain->mapAddressBook.count(dest)) ret.push_back(Pair("account", pwalletMain->mapAddressBook[dest].name)); CKeyID keyID; - if (pwalletMain && address.GetKeyID(keyID) && pwalletMain->mapHdPubKeys.count(keyID)) + CHDChain hdChainCurrent; + if (pwalletMain && address.GetKeyID(keyID) && pwalletMain->mapHdPubKeys.count(keyID) && pwalletMain->GetHDChain(hdChainCurrent)) { ret.push_back(Pair("hdkeypath", pwalletMain->mapHdPubKeys[keyID].GetKeyPath())); - ret.push_back(Pair("hdmasterkeyid", pwalletMain->mapHdPubKeys[keyID].masterKeyID.GetHex())); + ret.push_back(Pair("hdchainid", hdChainCurrent.id.GetHex())); } #endif } diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index c6562518726b..50d3ba814a14 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -274,9 +274,26 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn, bool fForMixin LogPrintf("The wallet is probably corrupted: Some keys decrypt but not all.\n"); assert(false); } - if (keyFail || !keyPass) + if (keyFail || (!keyPass && cryptedHDChain.IsNull())) return false; + vMasterKey = vMasterKeyIn; + + if(!cryptedHDChain.IsNull()) { + bool chainPass = false; + // try to decrypt seed and make sure it matches + std::vector vchSeed; + if (DecryptHDChainSeed(vchSeed)) { + CHDChain hdChainTmp; + hdChainTmp.SetSeed(vchSeed, false); + // make sure seed matches this chain + chainPass = cryptedHDChain.id == hdChainTmp.GetSeedHash(); + } + if (!chainPass) { + vMasterKey.clear(); + return false; + } + } fDecryptionThoroughlyChecked = true; } fOnlyMixingAllowed = fForMixingOnly; @@ -378,3 +395,92 @@ bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn) } return true; } + +bool CCryptoKeyStore::EncryptHDChainSeed(CKeyingMaterial& vMasterKeyIn) +{ + // should call EncryptKeys first + if (!IsCrypted()) + return false; + + if (!cryptedHDChain.IsNull()) + return true; + + std::vector vchCryptedSeed; + + const std::vector vchSeed = hdChain.GetSeed(); + + uint8_t secret[64]; + memcpy(&secret[0], (unsigned char*)&(vchSeed.begin())[0], 64); + CKeyingMaterial vchSecret(secret, secret + 64); + + // make sure seed matches this chain + if (hdChain.id != hdChain.GetSeedHash()) + return false; + + if (!EncryptSecret(vMasterKeyIn, vchSecret, hdChain.id, vchCryptedSeed)) + return false; + + cryptedHDChain = hdChain; + if (!cryptedHDChain.SetSeed(vchCryptedSeed, false)) + return false; + + if (!hdChain.SetNull()) + return false; + + return true; +} + +bool CCryptoKeyStore::DecryptHDChainSeed(std::vector& vchSeedRet) const +{ + if (!IsCrypted()) + return true; + + if (cryptedHDChain.IsNull()) + return false; + + CKeyingMaterial vchSecret; + std::vector vchCryptedSecret = cryptedHDChain.GetSeed(); + + if(!DecryptSecret(vMasterKey, vchCryptedSecret, cryptedHDChain.id, vchSecret)) + return false; + + if (vchSecret.size() != 64) + return false; + + uint8_t seed[64]; + memcpy(&seed[0], (unsigned char*)&(vchSecret.begin())[0], 64); + std::vector vchSeed(seed, seed + 64); + + vchSeedRet = vchSeed; + + return true; +} + +bool CCryptoKeyStore::SetHDChain(const CHDChain& chain) +{ + if (IsCrypted()) + return false; + + hdChain = chain; + return true; +} + +bool CCryptoKeyStore::SetCryptedHDChain(const CHDChain& chain) +{ + if (!SetCrypted()) + return false; + + cryptedHDChain = chain; + return true; +} + +bool CCryptoKeyStore::GetHDChain(CHDChain& hdChainRet) const +{ + if(IsCrypted()) { + hdChainRet = cryptedHDChain; + return !cryptedHDChain.IsNull(); + } + + hdChainRet = hdChain; + return !hdChain.IsNull(); +} diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h index e30341213836..44b49c5f19f3 100644 --- a/src/wallet/crypter.h +++ b/src/wallet/crypter.h @@ -118,6 +118,7 @@ class CCryptoKeyStore : public CBasicKeyStore { private: CryptedKeyMap mapCryptedKeys; + CHDChain cryptedHDChain; CKeyingMaterial vMasterKey; @@ -137,6 +138,11 @@ class CCryptoKeyStore : public CBasicKeyStore //! will encrypt previously unencrypted keys bool EncryptKeys(CKeyingMaterial& vMasterKeyIn); + bool EncryptHDChainSeed(CKeyingMaterial& vMasterKeyIn); + bool DecryptHDChainSeed(std::vector& vchSeedRet) const; + bool SetHDChain(const CHDChain& chain); + bool SetCryptedHDChain(const CHDChain& chain); + bool Unlock(const CKeyingMaterial& vMasterKeyIn, bool fForMixingOnly = false); public: @@ -210,6 +216,8 @@ class CCryptoKeyStore : public CBasicKeyStore } } + bool GetHDChain(CHDChain& hdChainRet) const; + /** * Wallet status (encrypted, locked) changed. * Note: Called without locks held. diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 8a469955ed91..16189b33eb41 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -627,20 +627,18 @@ UniValue dumpwallet(const UniValue& params, bool fHelp) file << "\n"; // add the base58check encoded extended master if the wallet uses HD - CKeyID masterKeyID = pwalletMain->GetHDChain().masterKeyID; - if (!masterKeyID.IsNull()) + CHDChain hdChainCurrent; + if (pwalletMain->GetHDChain(hdChainCurrent)) { - CKey key; - if (pwalletMain->GetKey(masterKeyID, key)) - { - CExtKey masterKey; - masterKey.SetMaster(key.begin(), key.size()); + std::vector vchSeed = hdChainCurrent.GetSeed(); - CBitcoinExtKey b58extkey; - b58extkey.SetKey(masterKey); + CExtKey masterKey; + masterKey.SetMaster(&vchSeed[0], vchSeed.size()); - file << "# extended private masterkey: " << b58extkey.ToString() << "\n\n"; - } + CBitcoinExtKey b58extkey; + b58extkey.SetKey(masterKey); + + file << "# extended private masterkey: " << b58extkey.ToString() << "\n\n"; } for (std::vector >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) { @@ -652,8 +650,6 @@ UniValue dumpwallet(const UniValue& params, bool fHelp) file << strprintf("%s %s ", CBitcoinSecret(key).ToString(), strTime); if (pwalletMain->mapAddressBook.count(keyid)) { file << strprintf("label=%s", EncodeDumpString(pwalletMain->mapAddressBook[keyid].name)); - } else if (keyid == masterKeyID) { - file << "hdmaster=1"; } else if (setKeyPool.count(keyid)) { file << "reserve=1"; } else { diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 83aaccad4b0a..ea49e964e028 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2381,7 +2381,7 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp) " \"keys_left\": xxxx, (numeric) how many new keys are left since last automatic backup\n" " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n" " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n" - " \"hdmasterkeyid\": \"\", (string) the Hash160 of the HD master pubkey\n" + " \"hdchainid\": \"\", (string) the ID of the HD chain\n" " \"hdchildkeyindex\": xxxx, (numeric) current childkey index\n" "}\n" "\nExamples:\n" @@ -2403,12 +2403,11 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp) if (pwalletMain->IsCrypted()) obj.push_back(Pair("unlocked_until", nWalletUnlockTime)); obj.push_back(Pair("paytxfee", ValueFromAmount(payTxFee.GetFeePerK()))); - CHDChain hdChain = pwalletMain->GetHDChain(); - CKeyID masterKeyID = hdChain.masterKeyID; - if (!masterKeyID.IsNull()) { - obj.push_back(Pair("hdmasterkeyid", masterKeyID.GetHex())); - obj.push_back(Pair("hdchildkeyindex", (int64_t)hdChain.nExternalChainCounter)); - } + CHDChain hdChainCurrent; + if (pwalletMain->GetHDChain(hdChainCurrent)) { + obj.push_back(Pair("hdchainid", hdChainCurrent.id.GetHex())); + obj.push_back(Pair("hdchildkeyindex", (int64_t)hdChainCurrent.nExternalChainCounter)); + } return obj; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 7f654c82f405..f9c6c8b01fb0 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -140,40 +140,26 @@ CPubKey CWallet::GenerateNewKey() void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret) { - // Use BIP44 keypath scheme i.e. m / purpose' / coin_type' / account' / change / address_index - CKey key; //master key seed (256bit) - CExtKey masterKey; //hd master key - CExtKey purposeKey; //key at m/purpose' - CExtKey cointypeKey; //key at m/purpose'/coin_type' - CExtKey accountKey; //key at m/purpose'/coin_type'/account' - CExtKey changeKey; //key at m/purpose'/coin_type'/account'/change - CExtKey childKey; //key at m/purpose'/coin_type'/account'/change/address_index - - // try to get the master key - if (!GetKey(hdChain.masterKeyID, key)) - throw std::runtime_error(std::string(__func__) + ": Master key not found"); - - masterKey.SetMaster(key.begin(), key.size()); - - // Use hardened derivation for purpose, coin_type and account - // (keys >= 0x80000000 are hardened after bip32) - // TODO: support multiple accounts, external/internal addresses, and multiple index per each - - // derive m/purpose' - masterKey.Derive(purposeKey, 44 | 0x80000000); - // derive m/purpose'/coin_type' - purposeKey.Derive(cointypeKey, Params().ExtCoinType() | 0x80000000); - // derive m/purpose'/coin_type'/account' - cointypeKey.Derive(accountKey, 0x80000000); - // derive m/purpose'/coin_type'/account/change - accountKey.Derive(changeKey, 0); + CHDChain hdChainTmp; + if (!GetHDChain(hdChainTmp)) { + throw std::runtime_error(std::string(__func__) + ": GetHDChain failed"); + } + + std::vector vchSeed = hdChainTmp.GetSeed(); + if (!DecryptHDChainSeed(vchSeed)) + throw std::runtime_error(std::string(__func__) + ": DecryptHDChainSeed failed"); + hdChainTmp.SetSeed(vchSeed, false); + // make sure seed matches this chain + if (hdChainTmp.id != hdChainTmp.GetSeedHash()) + throw std::runtime_error(std::string(__func__) + ": Wrong HD chain!"); // derive child key at next index, skip keys already known to the wallet + CExtKey childKey; + uint32_t childIndex = hdChainTmp.nExternalChainCounter; do { - // derive m/purpose'/coin_type'/account/change/address_index - changeKey.Derive(childKey, hdChain.nExternalChainCounter); + hdChainTmp.DeriveChildExtKey(childIndex, childKey); // increment childkey index - hdChain.nExternalChainCounter++; + childIndex++; } while (HaveKey(childKey.key.GetPubKey().GetID())); secret = childKey.key; @@ -186,8 +172,17 @@ void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret) nTimeFirstKey = metadata.nCreateTime; // update the chain model in the database - if (!CWalletDB(strWalletFile).WriteHDChain(hdChain)) - throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed"); + CHDChain hdChainCurrent; + GetHDChain(hdChainCurrent); + hdChainCurrent.nExternalChainCounter = childIndex; + if (IsCrypted()) { + if (!SetCryptedHDChain(hdChainCurrent, false)) + throw std::runtime_error(std::string(__func__) + ": SetCryptedHDChain failed"); + } + else { + if (!SetHDChain(hdChainCurrent, false)) + throw std::runtime_error(std::string(__func__) + ": SetHDChain failed"); + } if (!AddHDPubKey(childKey.Neuter())) throw std::runtime_error(std::string(__func__) + ": AddHDPubKey failed"); @@ -211,48 +206,30 @@ bool CWallet::GetKey(const CKeyID &address, CKey& keyOut) const { LOCK(cs_wallet); std::map::const_iterator mi = mapHdPubKeys.find(address); - if (mi != mapHdPubKeys.end() && mi->first != mi->second.masterKeyID) + if (mi != mapHdPubKeys.end()) { // if the key has been found in mapHdPubKeys, derive it on the fly const CHDPubKey &hdPubKey = (*mi).second; - - // TODO: refactor with DeriveNewChildKey - - // Use BIP44 keypath scheme i.e. m / purpose' / coin_type' / account' / change / address_index - CKey key; //master key seed (256bit) - CExtKey masterKey; //hd master key - CExtKey purposeKey; //key at m/purpose' - CExtKey cointypeKey; //key at m/purpose'/coin_type' - CExtKey accountKey; //key at m/purpose'/coin_type'/account' - CExtKey changeKey; //key at m/purpose'/coin_type'/account'/change - CExtKey childKey; //key at m/purpose'/coin_type'/account'/change/address_index - - // try to get the master key - if (!GetKey(hdPubKey.masterKeyID, key)) - return false; - - masterKey.SetMaster(key.begin(), key.size()); - - // Use hardened derivation for purpose, coin_type and account - // (keys >= 0x80000000 are hardened after bip32) - // TODO: support multiple accounts, external/internal addresses, and multiple index per each - - // derive m/purpose' - masterKey.Derive(purposeKey, 44 | 0x80000000); - // derive m/purpose'/coin_type' - purposeKey.Derive(cointypeKey, Params().ExtCoinType() | 0x80000000); - // derive m/purpose'/coin_type'/account' - cointypeKey.Derive(accountKey, 0x80000000); - // derive m/purpose'/coin_type'/account/change - accountKey.Derive(changeKey, 0); - // derive m/purpose'/coin_type'/account/change/address_index - changeKey.Derive(childKey, hdPubKey.extPubKey.nChild); - keyOut = childKey.key; + CHDChain hdChainCurrent; + if (!GetHDChain(hdChainCurrent)) + throw std::runtime_error(std::string(__func__) + ": GetHDChain failed"); + std::vector vchSeed = hdChainCurrent.GetSeed(); + if (!DecryptHDChainSeed(vchSeed)) + throw std::runtime_error(std::string(__func__) + ": DecryptHDChainSeed failed"); + hdChainCurrent.SetSeed(vchSeed, false); + // make sure seed matches this chain + if (hdChainCurrent.id != hdChainCurrent.GetSeedHash()) + throw std::runtime_error(std::string(__func__) + ": Wrong HD chain!"); + + CExtKey extkey; + hdChainCurrent.DeriveChildExtKey(hdPubKey.extPubKey.nChild, extkey); + keyOut = extkey.key; return true; } - else + else { return CCryptoKeyStore::GetKey(address, keyOut); + } } bool CWallet::HaveKey(const CKeyID &address) const @@ -275,9 +252,12 @@ bool CWallet::AddHDPubKey(const CExtPubKey &extPubKey) { AssertLockHeld(cs_wallet); + CHDChain hdChainCurrent; + GetHDChain(hdChainCurrent); + CHDPubKey hdPubKey; hdPubKey.extPubKey = extPubKey; - hdPubKey.masterKeyID = hdChain.masterKeyID; + hdPubKey.hdchainID = hdChainCurrent.id; mapHdPubKeys[extPubKey.pubkey.GetID()] = hdPubKey; // check if we need to remove from watch-only @@ -780,6 +760,10 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) pwalletdbEncryption->WriteMasterKey(nMasterKeyMaxID, kMasterKey); } + // must get current HD chain before EncryptKeys + CHDChain hdChainCurrent; + GetHDChain(hdChainCurrent); + if (!EncryptKeys(vMasterKey)) { if (fFileBacked) { @@ -791,6 +775,24 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) assert(false); } + if (!hdChainCurrent.IsNull()) { + assert(EncryptHDChainSeed(vMasterKey)); + + CHDChain hdChainCrypted; + assert(GetHDChain(hdChainCrypted)); + + DBG( + printf("EncryptWallet -- current seed: '%s'\n", HexStr(hdChainCurrent.GetSeed()).c_str()); + printf("EncryptWallet -- crypted seed: '%s'\n", HexStr(hdChainCrypted.GetSeed()).c_str()); + ); + + // ids should match, seed hashes should not + assert(hdChainCurrent.id == hdChainCrypted.id); + assert(hdChainCurrent.GetSeedHash() != hdChainCrypted.GetSeedHash()); + + assert(SetCryptedHDChain(hdChainCrypted, false)); + } + // Encryption was introduced in version 0.4.0 SetMinVersion(FEATURE_WALLETCRYPT, pwalletdbEncryption, true); @@ -1367,7 +1369,7 @@ CAmount CWallet::GetChange(const CTxOut& txout) const return (IsChange(txout) ? txout.nValue : 0); } -CPubKey CWallet::GenerateNewHDMasterKey() +void CWallet::GenerateNewHDChain() { const char *mnemonic; @@ -1378,79 +1380,89 @@ CPubKey CWallet::GenerateNewHDMasterKey() } if(!mnemonic_check(mnemonic)) { - throw std::runtime_error("CWallet::GenerateNewHDMasterKey(): invalid mnemonic"); + throw std::runtime_error(std::string(__func__) + ": invalid mnemonic"); } - // printf("mnemonic: %s\n", mnemonic); + DBG( printf("mnemonic: '%s'\n", mnemonic); ); const char *passphrase; - if(mapArgs.count("-mnemonicpassphrase")) { - passphrase = GetArg("-mnemonicpassphrase", "").c_str(); - } else { + // default mnemonic passphrase is empty string + // however if no mnemonic was specified by the user, use random passphrase instead + passphrase = GetArg("-mnemonicpassphrase", "").c_str(); + if (!mapArgs.count("-mnemonic")) { unsigned char rand_pwd[32]; GetRandBytes(rand_pwd, 32); passphrase = EncodeBase58(&rand_pwd[0],&rand_pwd[0]+32).c_str(); } - // printf("mnemonicpassphrase: %s\n", passphrase); + DBG( printf("mnemonicpassphrase: '%s'\n", passphrase); ); uint8_t seed[64]; mnemonic_to_seed(mnemonic, passphrase, seed, 0); + std::vector vchSeed(seed, seed + 64); - CExtKey extkey; - extkey.SetMaster(seed, 64); - CKey key = extkey.key; + DBG( + printf("seed: '%s'\n", HexStr(vchSeed).c_str()); - // calculate the pubkey - CPubKey pubkey = key.GetPubKey(); - assert(key.VerifyPubKey(pubkey)); + CExtKey extkey; + extkey.SetMaster(seed, 64); - int64_t nCreationTime = GetTime(); - CKeyMetadata metadata(nCreationTime); - - metadata.extkeyMetadata.fMaster = true; - metadata.extkeyMetadata.hdMasterKeyID = pubkey.GetID(); - - { - LOCK(cs_wallet); + CBitcoinExtKey b58extkey; + b58extkey.SetKey(extkey); + printf("extended private masterkey: '%s'\n", b58extkey.ToString().c_str()); - // mem store the metadata - mapKeyMetadata[pubkey.GetID()] = metadata; + CExtPubKey extpubkey; + extpubkey = extkey.Neuter(); - // write the key&metadata to the database - if (!AddKeyPubKey(key, pubkey)) - throw std::runtime_error("CWallet::GenerateNewHDMasterKey(): AddKey failed"); - } + CBitcoinExtPubKey b58extpubkey; + b58extpubkey.SetKey(extpubkey); + printf("extended public masterkey: '%s'\n", b58extpubkey.ToString().c_str()); + ); - return pubkey; + CHDChain newHdChain; + if (!newHdChain.SetSeed(vchSeed, true)) + throw std::runtime_error(std::string(__func__) + ": SetSeed failed"); + if (!SetHDChain(newHdChain, false)) + throw std::runtime_error(std::string(__func__) + ": SetHDChain failed"); } -bool CWallet::SetHDMasterKey(const CPubKey& pubkey) +bool CWallet::SetHDChain(const CHDChain& chain, bool memonly) { LOCK(cs_wallet); - SetMinVersion(FEATURE_HD); - // store the keyid (hash160) together with - // the child index counter in the database - // as a hdchain object - CHDChain newHdChain; - newHdChain.masterKeyID = pubkey.GetID(); - SetHDChain(newHdChain, false); + if (!CCryptoKeyStore::SetHDChain(chain)) + return false; + + if (!memonly && !CWalletDB(strWalletFile).WriteHDChain(chain)) + throw std::runtime_error(std::string(__func__) + ": WriteHDChain failed"); return true; } -bool CWallet::SetHDChain(const CHDChain& chain, bool memonly) +bool CWallet::SetCryptedHDChain(const CHDChain& chain, bool memonly) { LOCK(cs_wallet); - if (!memonly && !CWalletDB(strWalletFile).WriteHDChain(chain)) - throw runtime_error("AddHDChain(): writing chain failed"); - hdChain = chain; + if (!CCryptoKeyStore::SetCryptedHDChain(chain)) + return false; + + if (!memonly) { + if (!fFileBacked) + return false; + if (pwalletdbEncryption) { + if (!pwalletdbEncryption->WriteCryptedHDChain(chain)) + throw std::runtime_error(std::string(__func__) + ": WriteCryptedHDChain failed"); + } else { + if (!CWalletDB(strWalletFile).WriteCryptedHDChain(chain)) + throw std::runtime_error(std::string(__func__) + ": WriteCryptedHDChain failed"); + } + } + return true; } bool CWallet::IsHDEnabled() { - return !hdChain.masterKeyID.IsNull(); + CHDChain hdChainCurrent; + return GetHDChain(hdChainCurrent); } bool CWallet::IsMine(const CTransaction& tx) const diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 21660a7bd59a..356d3e6723fc 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -638,9 +638,6 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface void SyncMetaData(std::pair); - /* the HD chain data model (external chain counters) */ - CHDChain hdChain; - public: /* * Main wallet lock. @@ -997,16 +994,13 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface * HD Wallet Functions */ - const CHDChain& GetHDChain() { return hdChain; } - /* Returns true if HD is enabled */ bool IsHDEnabled(); + /* Generates a new HD chain */ + void GenerateNewHDChain(); /* Set the HD chain model (chain child index counters) */ bool SetHDChain(const CHDChain& chain, bool memonly); - /* Generates a new HD master key (will not be activated) */ - void GenerateNewHDMasterKey(); - /* Set the current HD master key (will reset the chain child index counters) */ - bool SetHDMasterKey(const CPubKey& key); + bool SetCryptedHDChain(const CHDChain& chain, bool memonly); }; /** A key allocated from the key pool. */ diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 14cb314cd30c..de0995226ff4 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -25,19 +25,6 @@ using namespace std; static uint64_t nAccountingEntryNumber = 0; - -std::string CHDPubKey::GetKeyPath() const -{ - if(IsMaster()) - return "m"; - - return "m/44'/" + - boost::to_string(Params().ExtCoinType()) + "'/" + - boost::to_string(nAccount) + "'/" + - boost::to_string(nChange) + "/" + - boost::to_string(extPubKey.nChild); -} - // // CWalletDB // @@ -622,6 +609,16 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, return false; } } + else if (strType == "chdchain") + { + CHDChain chain; + ssValue >> chain; + if (!pwallet->SetCryptedHDChain(chain, true)) + { + strErr = "Error reading wallet database: SetHDCryptedChain failed"; + return false; + } + } else if (strType == "hdpubkey") { CPubKey vchPubKey; @@ -651,7 +648,8 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, static bool IsKeyType(string strType) { return (strType== "key" || strType == "wkey" || - strType == "mkey" || strType == "ckey"); + strType == "mkey" || strType == "ckey" || + strType == "hdchain" || strType == "chdchain"); } DBErrors CWalletDB::LoadWallet(CWallet* pwallet) @@ -1133,7 +1131,7 @@ bool CWalletDB::Recover(CDBEnv& dbenv, const std::string& filename, bool fOnlyKe fReadOK = ReadKeyValue(&dummyWallet, ssKey, ssValue, wss, strType, strErr); } - if (!IsKeyType(strType) && strType != "hdchain" && strType != "hdpubkey") + if (!IsKeyType(strType) && strType != "hdpubkey") continue; if (!fReadOK) { @@ -1176,12 +1174,23 @@ bool CWalletDB::WriteHDChain(const CHDChain& chain) return Write(std::string("hdchain"), chain); } +bool CWalletDB::WriteCryptedHDChain(const CHDChain& chain) +{ + nWalletDBUpdated++; + + if (!Write(std::string("chdchain"), chain)) + return false; + + Erase(std::string("hdchain")); + + return true; +} + bool CWalletDB::WriteHDPubKey(const CHDPubKey& hdPubKey, const CKeyMetadata& keyMeta) { nWalletDBUpdated++; - // NOTE: allow metadata overwriting for master HD pubkey - if (!Write(std::make_pair(std::string("keymeta"), hdPubKey.extPubKey.pubkey), keyMeta, hdPubKey.IsMaster())) + if (!Write(std::make_pair(std::string("keymeta"), hdPubKey.extPubKey.pubkey), keyMeta, false)) return false; return Write(std::make_pair(std::string("hdpubkey"), hdPubKey.extPubKey.pubkey), hdPubKey, false); diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index dd530a32376c..4e8333381a9a 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -8,6 +8,7 @@ #include "amount.h" #include "wallet/db.h" +#include "hdchain.h" #include "key.h" #include @@ -40,63 +41,6 @@ enum DBErrors DB_NEED_REWRITE }; -/* simple HD chain data model */ -class CHDChain -{ -public: - uint32_t nExternalChainCounter; - CKeyID masterKeyID; //!< master key hash160 - - static const int CURRENT_VERSION = 1; - int nVersion; - - CHDChain() { SetNull(); } - ADD_SERIALIZE_METHODS; - template - inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) - { - READWRITE(this->nVersion); - nVersion = this->nVersion; - READWRITE(nExternalChainCounter); - READWRITE(masterKeyID); - } - - void SetNull() - { - nVersion = CHDChain::CURRENT_VERSION; - nExternalChainCounter = 0; - masterKeyID.SetNull(); - } -}; - -/* hd pubkey data model */ -class CHDPubKey -{ -public: - static const int CURRENT_VERSION = 1; - int nVersion; - CExtPubKey extPubKey; - CKeyID masterKeyID; - unsigned int nAccount; - unsigned int nChange; - - CHDPubKey() : nVersion(CHDPubKey::CURRENT_VERSION), nAccount(0), nChange(0) {} - - ADD_SERIALIZE_METHODS; - template - inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) - { - READWRITE(this->nVersion); - READWRITE(extPubKey); - READWRITE(masterKeyID); - READWRITE(nAccount); - READWRITE(nChange); - } - - bool IsMaster() const { return extPubKey.pubkey.GetID() == masterKeyID; } - std::string GetKeyPath() const; -}; - class CKeyMetadata { public: @@ -192,7 +136,7 @@ class CWalletDB : public CDB //! write the hdchain model (external chain child index counter) bool WriteHDChain(const CHDChain& chain); - + bool WriteCryptedHDChain(const CHDChain& chain); bool WriteHDPubKey(const CHDPubKey& hdPubKey, const CKeyMetadata& keyMeta); private: From 26d568e2a7fb4a09ff73e5ae8b8ccd4cb3297f7a Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Wed, 29 Mar 2017 15:12:47 +0300 Subject: [PATCH 07/31] pbkdf2 test --- src/test/crypto_tests.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index 9e2467e60b33..4102c07f3ea4 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -17,6 +17,8 @@ #include #include +#include + BOOST_FIXTURE_TEST_SUITE(crypto_tests, BasicTestingSetup) template @@ -248,4 +250,27 @@ BOOST_AUTO_TEST_CASE(hmac_sha512_testvectors) { "b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58"); } +BOOST_AUTO_TEST_CASE(pbkdf2_hmac_sha512_test) { + // test vectors from + // https://github.com/trezor/trezor-crypto/blob/87c920a7e747f7ed40b6ae841327868ab914435b/tests.c#L1936-L1957 + // https://stackoverflow.com/questions/15593184/pbkdf2-hmac-sha-512-test-vectors + uint8_t k[64], s[40]; + + strcpy((char *)s, "salt"); + PKCS5_PBKDF2_HMAC("password", 8, s, 4, 1, EVP_sha512(), 64, k); + BOOST_CHECK(HexStr(k, k + 64) == "867f70cf1ade02cff3752599a3a53dc4af34c7a669815ae5d513554e1c8cf252c02d470a285a0501bad999bfe943c08f050235d7d68b1da55e63f73b60a57fce"); + + strcpy((char *)s, "salt"); + PKCS5_PBKDF2_HMAC("password", 8, s, 4, 2, EVP_sha512(), 64, k); + BOOST_CHECK(HexStr(k, k + 64) == "e1d9c16aa681708a45f5c7c4e215ceb66e011a2e9f0040713f18aefdb866d53cf76cab2868a39b9f7840edce4fef5a82be67335c77a6068e04112754f27ccf4e"); + + strcpy((char *)s, "salt"); + PKCS5_PBKDF2_HMAC("password", 8, s, 4, 4096, EVP_sha512(), 64, k); + BOOST_CHECK(HexStr(k, k + 64) == "d197b1b33db0143e018b12f3d1d1479e6cdebdcc97c5c0f87f6902e072f457b5143f30602641b3d55cd335988cb36b84376060ecd532e039b742a239434af2d5"); + + strcpy((char *)s, "saltSALTsaltSALTsaltSALTsaltSALTsalt"); + PKCS5_PBKDF2_HMAC("passwordPASSWORDpassword", 3*8, s, 9*4, 4096, EVP_sha512(), 64, k); + BOOST_CHECK(HexStr(k, k + 64) == "8c0511f4c6e597c6ac6315d8f0362e225f3c501495ba23b868c005174dc4ee71115b59f9e60cd9532fa33e0f75aefe30225c583a186cd82bd4daea9724a3d3b8"); +} + BOOST_AUTO_TEST_SUITE_END() From d19f74c759aeaaa24e27caaa0e4d7eaa85ea7929 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Fri, 31 Mar 2017 17:26:21 +0300 Subject: [PATCH 08/31] backport wallet-hd.py test --- qa/pull-tester/rpc-tests.py | 1 + qa/rpc-tests/wallet-hd.py | 93 +++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100755 qa/rpc-tests/wallet-hd.py diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index f56422f3a9bd..99e96aab6c5e 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -90,6 +90,7 @@ testScripts = [ 'bip68-112-113-p2p.py', 'wallet.py', + 'wallet-hd.py', 'listtransactions.py', 'receivedby.py', 'mempool_resurrect_test.py', diff --git a/qa/rpc-tests/wallet-hd.py b/qa/rpc-tests/wallet-hd.py new file mode 100755 index 000000000000..99cdfbedf6f3 --- /dev/null +++ b/qa/rpc-tests/wallet-hd.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python2 +# coding=utf-8 +# ^^^^^^^^^^^^ TODO remove when supporting only Python3 +# Copyright (c) 2016 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test Hierarchical Deterministic wallet function.""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * + +class WalletHDTest(BitcoinTestFramework): + + def setup_chain(self): + print("Initializing test directory "+self.options.tmpdir) + initialize_chain_clean(self.options.tmpdir, 2) + + def setup_network(self): + self.nodes = start_nodes(2, self.options.tmpdir, [['-usehd=0'], ['-usehd=1', '-keypool=0']]) + self.is_network_split = False + connect_nodes_bi(self.nodes, 0, 1) + self.is_network_split=False + self.sync_all() + + def run_test (self): + tmpdir = self.options.tmpdir + + # Make sure can't switch off usehd after wallet creation + stop_node(self.nodes[1],1) + try: + start_node(1, self.options.tmpdir, ['-usehd=0']) + raise AssertionError("Must not allow to turn off HD on an already existing HD wallet") + except Exception as e: + assert("dashd exited with status 1 during initialization" in str(e)) + # assert_start_raises_init_error(1, self.options.tmpdir, ['-usehd=0'], 'already existing HD wallet') + # self.nodes[1] = start_node(1, self.options.tmpdir, self.node_args[1]) + self.nodes[1] = start_node(1, self.options.tmpdir, ['-usehd=1', '-keypool=0']) + connect_nodes_bi(self.nodes, 0, 1) + + # Make sure we use hd, keep chainid + chainid = self.nodes[1].getwalletinfo()['hdchainid'] + assert_equal(len(chainid), 64) + + # Import a non-HD private key in the HD wallet + non_hd_add = self.nodes[0].getnewaddress() + self.nodes[1].importprivkey(self.nodes[0].dumpprivkey(non_hd_add)) + + # This should be enough to keep the master key and the non-HD key + self.nodes[1].backupwallet(tmpdir + "/hd.bak") + #self.nodes[1].dumpwallet(tmpdir + "/hd.dump") + + # Derive some HD addresses and remember the last + # Also send funds to each add + self.nodes[0].generate(101) + hd_add = None + num_hd_adds = 300 + for i in range(num_hd_adds): + hd_add = self.nodes[1].getnewaddress() + hd_info = self.nodes[1].validateaddress(hd_add) + assert_equal(hd_info["hdkeypath"], "m/44'/1'/0'/0/"+str(i+1)) + assert_equal(hd_info["hdchainid"], chainid) + self.nodes[0].sendtoaddress(hd_add, 1) + self.nodes[0].generate(1) + self.nodes[0].sendtoaddress(non_hd_add, 1) + self.nodes[0].generate(1) + + self.sync_all() + assert_equal(self.nodes[1].getbalance(), num_hd_adds + 1) + + print("Restore backup ...") + stop_node(self.nodes[1],1) + os.remove(self.options.tmpdir + "/node1/regtest/wallet.dat") + shutil.copyfile(tmpdir + "/hd.bak", tmpdir + "/node1/regtest/wallet.dat") + self.nodes[1] = start_node(1, self.options.tmpdir, ['-usehd=1', '-keypool=0']) + #connect_nodes_bi(self.nodes, 0, 1) + + # Assert that derivation is deterministic + hd_add_2 = None + for _ in range(num_hd_adds): + hd_add_2 = self.nodes[1].getnewaddress() + hd_info_2 = self.nodes[1].validateaddress(hd_add_2) + assert_equal(hd_info_2["hdkeypath"], "m/44'/1'/0'/0/"+str(_+1)) + assert_equal(hd_info_2["hdchainid"], chainid) + assert_equal(hd_add, hd_add_2) + + # Needs rescan + stop_node(self.nodes[1],1) + self.nodes[1] = start_node(1, self.options.tmpdir, ['-usehd=1', '-keypool=0', '-rescan']) + #connect_nodes_bi(self.nodes, 0, 1) + assert_equal(self.nodes[1].getbalance(), num_hd_adds + 1) + +if __name__ == '__main__': + WalletHDTest().main () From 7386b8293981bc34386ba640a4f12785d8747801 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Thu, 20 Apr 2017 07:41:25 +0300 Subject: [PATCH 09/31] Allow specifying hd seed, add dumphdseed rpc, fix bugs - -hdseed cmd-line param to specify HD seed on wallet creation - dumphdseed rpc to dump HD seed - allow seed of any size - fix dumpwallet rpc bug (wasn't decrypting HD seed) - print HD seed and extended public masterkey on dumpwallet --- src/init.cpp | 7 ++++ src/rpcserver.cpp | 1 + src/rpcserver.h | 1 + src/wallet/crypter.cpp | 15 ++++----- src/wallet/rpcdump.cpp | 43 ++++++++++++++++++++++++ src/wallet/wallet.cpp | 76 ++++++++++++++++++++++++++++++------------ src/wallet/wallet.h | 1 + 7 files changed, 113 insertions(+), 31 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 8531ff29efcf..0db4f72e0deb 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -480,6 +480,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-usehd", _("Use hierarchical deterministic key generation (HD) after bip32. Only has effect during wallet creation/first start") + " " + strprintf(_("(default: %u)"), DEFAULT_USE_HD_WALLET)); strUsage += HelpMessageOpt("-mnemonic", _("User defined mnemonic for HD wallet (bip39). Only has effect during wallet creation/first start (default: randomly generated)")); strUsage += HelpMessageOpt("-mnemonicpassphrase", _("User defined memonic passphrase for HD wallet (bip39). Only has effect during wallet creation/first start (default: randomly generated)")); + strUsage += HelpMessageOpt("-hdseed", _("User defined seed for HD wallet (should be in hex). Only has effect during wallet creation/first start (default: randomly generated)")); strUsage += HelpMessageOpt("-upgradewallet", _("Upgrade wallet to latest format on startup")); strUsage += HelpMessageOpt("-wallet=", _("Specify wallet file (within data directory)") + " " + strprintf(_("(default: %s)"), "wallet.dat")); strUsage += HelpMessageOpt("-walletbroadcast", _("Make the wallet broadcast transactions") + " " + strprintf(_("(default: %u)"), DEFAULT_WALLETBROADCAST)); @@ -895,6 +896,12 @@ void InitParameterInteraction() mapArgs["-privatesendmultisession"] = "0"; LogPrintf("%s: parameter interaction: -liquidityprovider=%d -> setting -privatesendmultisession=0\n", __func__, nLiqProvTmp); } + + if (mapArgs.count("-hdseed") && IsHex(GetArg("-hdseed", "not hex")) && (mapArgs.count("-mnemonic") || mapArgs.count("-mnemonicpassphrase"))) { + mapArgs.erase("-mnemonic"); + mapArgs.erase("-mnemonicpassphrase"); + LogPrintf("%s: parameter interaction: can't use -hdseed and -mnemonic/-mnemonicpassphrase together, will prefer -seed\n", __func__); + } } void InitLogging() diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 984128ebd6a8..d2c5b9a07cf4 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -362,6 +362,7 @@ static const CRPCCommand vRPCCommands[] = { "wallet", "addmultisigaddress", &addmultisigaddress, true }, { "wallet", "backupwallet", &backupwallet, true }, { "wallet", "dumpprivkey", &dumpprivkey, true }, + { "wallet", "dumphdseed", &dumphdseed, true }, { "wallet", "dumpwallet", &dumpwallet, true }, { "wallet", "encryptwallet", &encryptwallet, true }, { "wallet", "getaccountaddress", &getaccountaddress, true }, diff --git a/src/rpcserver.h b/src/rpcserver.h index dd852803f8be..ad975958cfad 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -192,6 +192,7 @@ extern UniValue dumpprivkey(const UniValue& params, bool fHelp); // in rpcdump.c extern UniValue importprivkey(const UniValue& params, bool fHelp); extern UniValue importaddress(const UniValue& params, bool fHelp); extern UniValue importpubkey(const UniValue& params, bool fHelp); +extern UniValue dumphdseed(const UniValue& params, bool fHelp); extern UniValue dumpwallet(const UniValue& params, bool fHelp); extern UniValue importwallet(const UniValue& params, bool fHelp); extern UniValue importelectrumwallet(const UniValue& params, bool fHelp); diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index 50d3ba814a14..38abba2953df 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -409,9 +409,9 @@ bool CCryptoKeyStore::EncryptHDChainSeed(CKeyingMaterial& vMasterKeyIn) const std::vector vchSeed = hdChain.GetSeed(); - uint8_t secret[64]; - memcpy(&secret[0], (unsigned char*)&(vchSeed.begin())[0], 64); - CKeyingMaterial vchSecret(secret, secret + 64); + uint8_t secret[vchSeed.size()]; + memcpy(&secret[0], (unsigned char*)&(vchSeed.begin())[0], vchSeed.size()); + CKeyingMaterial vchSecret(secret, secret + vchSeed.size()); // make sure seed matches this chain if (hdChain.id != hdChain.GetSeedHash()) @@ -444,12 +444,9 @@ bool CCryptoKeyStore::DecryptHDChainSeed(std::vector& vchSeedRet) if(!DecryptSecret(vMasterKey, vchCryptedSecret, cryptedHDChain.id, vchSecret)) return false; - if (vchSecret.size() != 64) - return false; - - uint8_t seed[64]; - memcpy(&seed[0], (unsigned char*)&(vchSecret.begin())[0], 64); - std::vector vchSeed(seed, seed + 64); + uint8_t seed[vchSecret.size()]; + memcpy(&seed[0], (unsigned char*)&(vchSecret.begin())[0], vchSecret.size()); + std::vector vchSeed(seed, seed + vchSecret.size()); vchSeedRet = vchSeed; diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 16189b33eb41..3889e1888a17 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -580,6 +580,38 @@ UniValue dumpprivkey(const UniValue& params, bool fHelp) return CBitcoinSecret(vchSecret).ToString(); } +UniValue dumphdseed(const UniValue& params, bool fHelp) +{ + if (!EnsureWalletIsAvailable(fHelp)) + return NullUniValue; + + if (fHelp || params.size() != 0) + throw runtime_error( + "dumphdseed\n" + "\nReveals the HD seed for this wallet\n" + "\nResult:\n" + "\"HD seed\" (string) The HD seed in hex\n" + "\nExamples:\n" + + HelpExampleCli("dumphdseed", "") + + HelpExampleRpc("dumphdseed", "") + ); + + LOCK(pwalletMain->cs_wallet); + + EnsureWalletIsUnlocked(); + + // add the base58check encoded extended master if the wallet uses HD + CHDChain hdChainCurrent; + if (pwalletMain->GetHDChain(hdChainCurrent)) + { + std::vector vchSeed = hdChainCurrent.GetSeed(); + if (!pwalletMain->GetDecryptedHDChainSeed(vchSeed)) + throw JSONRPCError(RPC_INTERNAL_ERROR, "Cannot decrypt HD seed"); + return HexStr(vchSeed); + } + + return NullUniValue; +} UniValue dumpwallet(const UniValue& params, bool fHelp) { @@ -632,13 +664,24 @@ UniValue dumpwallet(const UniValue& params, bool fHelp) { std::vector vchSeed = hdChainCurrent.GetSeed(); + if (!pwalletMain->GetDecryptedHDChainSeed(vchSeed)) + throw JSONRPCError(RPC_INTERNAL_ERROR, "Cannot decrypt HD seed"); + CExtKey masterKey; masterKey.SetMaster(&vchSeed[0], vchSeed.size()); + file << "# HD seed: " << HexStr(vchSeed) << "\n\n"; CBitcoinExtKey b58extkey; b58extkey.SetKey(masterKey); file << "# extended private masterkey: " << b58extkey.ToString() << "\n\n"; + + CExtPubKey masterPubkey; + masterPubkey = masterKey.Neuter(); + + CBitcoinExtPubKey b58extpubkey; + b58extpubkey.SetKey(masterPubkey); + file << "# extended public masterkey: " << b58extpubkey.ToString() << "\n\n"; } for (std::vector >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) { diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index f9c6c8b01fb0..27cef7da7e8b 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1372,38 +1372,48 @@ CAmount CWallet::GetChange(const CTxOut& txout) const void CWallet::GenerateNewHDChain() { const char *mnemonic; + std::vector vchSeed; + std::string strSeed = GetArg("-hdseed", "not hex"); - if(mapArgs.count("-mnemonic")) { - mnemonic = GetArg("-mnemonic", "").c_str(); + if(mapArgs.count("-hdseed") && IsHex(strSeed)) { + vchSeed = ParseHex(strSeed); } else { - mnemonic = mnemonic_generate(128); - } - if(!mnemonic_check(mnemonic)) { - throw std::runtime_error(std::string(__func__) + ": invalid mnemonic"); - } - DBG( printf("mnemonic: '%s'\n", mnemonic); ); + if (mapArgs.count("-hdseed") && !IsHex(GetArg("-hdseed", ""))) + LogPrintf("CWallet::GenerateNewHDChain -- Incorrect seed, generating random one instead\n"); - const char *passphrase; - // default mnemonic passphrase is empty string - // however if no mnemonic was specified by the user, use random passphrase instead - passphrase = GetArg("-mnemonicpassphrase", "").c_str(); - if (!mapArgs.count("-mnemonic")) { - unsigned char rand_pwd[32]; - GetRandBytes(rand_pwd, 32); - passphrase = EncodeBase58(&rand_pwd[0],&rand_pwd[0]+32).c_str(); - } - DBG( printf("mnemonicpassphrase: '%s'\n", passphrase); ); + if(mapArgs.count("-mnemonic")) { + mnemonic = GetArg("-mnemonic", "").c_str(); + } else { + mnemonic = mnemonic_generate(128); + } - uint8_t seed[64]; - mnemonic_to_seed(mnemonic, passphrase, seed, 0); - std::vector vchSeed(seed, seed + 64); + if(!mnemonic_check(mnemonic)) { + throw std::runtime_error(std::string(__func__) + ": invalid mnemonic"); + } + DBG( printf("mnemonic: '%s'\n", mnemonic); ); + + const char *passphrase; + // default mnemonic passphrase is empty string + // however if no mnemonic was specified by the user, use random passphrase instead + passphrase = GetArg("-mnemonicpassphrase", "").c_str(); + if (!mapArgs.count("-mnemonic")) { + unsigned char rand_pwd[32]; + GetRandBytes(rand_pwd, 32); + passphrase = EncodeBase58(&rand_pwd[0],&rand_pwd[0]+32).c_str(); + } + DBG( printf("mnemonicpassphrase: '%s'\n", passphrase); ); + + uint8_t seed[64]; + mnemonic_to_seed(mnemonic, passphrase, seed, 0); + vchSeed = std::vector(seed, seed + 64); + } DBG( printf("seed: '%s'\n", HexStr(vchSeed).c_str()); CExtKey extkey; - extkey.SetMaster(seed, 64); + extkey.SetMaster(&vchSeed[0], vchSeed.size()); CBitcoinExtKey b58extkey; b58extkey.SetKey(extkey); @@ -1459,6 +1469,28 @@ bool CWallet::SetCryptedHDChain(const CHDChain& chain, bool memonly) return true; } +bool CWallet::GetDecryptedHDChainSeed(std::vector& vchSeedRet) +{ + LOCK(cs_wallet); + + CHDChain hdChainTmp; + if (!GetHDChain(hdChainTmp)) { + return false; + } + + std::vector vchSeed = hdChainTmp.GetSeed(); + if (!DecryptHDChainSeed(vchSeed)) + return false; + hdChainTmp.SetSeed(vchSeed, false); + // make sure seed matches this chain + if (hdChainTmp.id != hdChainTmp.GetSeedHash()) + return false; + + vchSeedRet = vchSeed; + + return true; +} + bool CWallet::IsHDEnabled() { CHDChain hdChainCurrent; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 356d3e6723fc..4905a0d1dc07 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1001,6 +1001,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface /* Set the HD chain model (chain child index counters) */ bool SetHDChain(const CHDChain& chain, bool memonly); bool SetCryptedHDChain(const CHDChain& chain, bool memonly); + bool GetDecryptedHDChainSeed(std::vector& vchSeedRet); }; /** A key allocated from the key pool. */ From 912946c90cd0f6bcdb4c7e0adf884af564308930 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Wed, 26 Apr 2017 02:55:26 +0300 Subject: [PATCH 10/31] top up keypool on HD wallet encryption --- qa/rpc-tests/keypool.py | 4 ++-- src/wallet/wallet.cpp | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/qa/rpc-tests/keypool.py b/qa/rpc-tests/keypool.py index 59470da7a7a3..c6a6601e7eac 100755 --- a/qa/rpc-tests/keypool.py +++ b/qa/rpc-tests/keypool.py @@ -33,9 +33,9 @@ def run_test(self): try: addr = nodes[0].getnewaddress() + raise AssertionError('Keypool should be exhausted after one address') except JSONRPCException as e: - raise AssertionError('Keypool should not be exhausted after one address') - # assert(e.error['code']==-12) + assert(e.error['code']==-12) # put three new keys in the keypool nodes[0].walletpassphrase('test', 12000) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 27cef7da7e8b..76c5b14adf38 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -813,8 +813,12 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) Unlock(strWalletPassphrase); // if we are not using HD, generate new keypool - if(!IsHDEnabled()) + if(IsHDEnabled()) { + TopUpKeyPool(); + } + else { NewKeyPool(); + } Lock(); From ef2aec706b754823c49d00d0a5f119a12ea26e94 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Wed, 26 Apr 2017 03:13:59 +0300 Subject: [PATCH 11/31] split HD chain: external/internal --- qa/rpc-tests/fundrawtransaction.py | 2 + qa/rpc-tests/keypool.py | 47 ++++-- qa/rpc-tests/wallet-hd.py | 20 +++ src/hdchain.cpp | 5 +- src/hdchain.h | 4 +- src/init.cpp | 9 +- src/privatesend-client.cpp | 18 +-- src/qt/addresstablemodel.cpp | 4 +- src/qt/paymentserver.cpp | 2 +- src/test/rpc_wallet_tests.cpp | 4 +- src/wallet/rpcwallet.cpp | 27 ++-- src/wallet/wallet.cpp | 228 ++++++++++++++++++++--------- src/wallet/wallet.h | 59 ++++++-- src/wallet/walletdb.cpp | 17 +-- 14 files changed, 314 insertions(+), 132 deletions(-) diff --git a/qa/rpc-tests/fundrawtransaction.py b/qa/rpc-tests/fundrawtransaction.py index e204a691360c..467ba1423f5c 100755 --- a/qa/rpc-tests/fundrawtransaction.py +++ b/qa/rpc-tests/fundrawtransaction.py @@ -459,6 +459,7 @@ def run_test(self): # drain the keypool self.nodes[1].getnewaddress() + self.nodes[1].getrawchangeaddress() inputs = [] outputs = {self.nodes[0].getnewaddress():1.1} rawTx = self.nodes[1].createrawtransaction(inputs, outputs) @@ -472,6 +473,7 @@ def run_test(self): #refill the keypool self.nodes[1].walletpassphrase("test", 100) + self.nodes[1].keypoolrefill(2) #need to refill the keypool to get an internal change address self.nodes[1].walletlock() try: diff --git a/qa/rpc-tests/keypool.py b/qa/rpc-tests/keypool.py index c6a6601e7eac..05f946831cd1 100755 --- a/qa/rpc-tests/keypool.py +++ b/qa/rpc-tests/keypool.py @@ -37,23 +37,41 @@ def run_test(self): except JSONRPCException as e: assert(e.error['code']==-12) - # put three new keys in the keypool + # put six (plus 2) new keys in the keypool (100% external-, +100% internal-keys, 1 in min) nodes[0].walletpassphrase('test', 12000) - nodes[0].keypoolrefill(3) + nodes[0].keypoolrefill(6) nodes[0].walletlock() + wi = nodes[0].getwalletinfo() + assert_equal(wi['keypoolsize_hd_internal'], 6) + assert_equal(wi['keypoolsize'], 6) + + # drain the internal keys + nodes[0].getrawchangeaddress() + nodes[0].getrawchangeaddress() + nodes[0].getrawchangeaddress() + nodes[0].getrawchangeaddress() + nodes[0].getrawchangeaddress() + nodes[0].getrawchangeaddress() + # the next one should fail + try: + nodes[0].getrawchangeaddress() + raise AssertionError('Keypool should be exhausted after six addresses') + except JSONRPCException as e: + assert(e.error['code']==-12) - # drain the keys addr = set() - addr.add(nodes[0].getrawchangeaddress()) - addr.add(nodes[0].getrawchangeaddress()) - addr.add(nodes[0].getrawchangeaddress()) - addr.add(nodes[0].getrawchangeaddress()) - # assert that four unique addresses were returned - assert(len(addr) == 4) + # drain the external keys + addr.add(nodes[0].getnewaddress()) + addr.add(nodes[0].getnewaddress()) + addr.add(nodes[0].getnewaddress()) + addr.add(nodes[0].getnewaddress()) + addr.add(nodes[0].getnewaddress()) + addr.add(nodes[0].getnewaddress()) + assert(len(addr) == 6) # the next one should fail try: - addr = nodes[0].getrawchangeaddress() - raise AssertionError('Keypool should be exhausted after three addresses') + addr = nodes[0].getnewaddress() + raise AssertionError('Keypool should be exhausted after six addresses') except JSONRPCException as e: assert(e.error['code']==-12) @@ -68,13 +86,18 @@ def run_test(self): nodes[0].generate(1) nodes[0].generate(1) nodes[0].generate(1) - nodes[0].generate(1) try: nodes[0].generate(1) raise AssertionError('Keypool should be exhausted after three addesses') except JSONRPCException as e: assert(e.error['code']==-12) + nodes[0].walletpassphrase('test', 100) + nodes[0].keypoolrefill(100) + wi = nodes[0].getwalletinfo() + assert_equal(wi['keypoolsize_hd_internal'], 100) + assert_equal(wi['keypoolsize'], 100) + def setup_chain(self): print("Initializing test directory "+self.options.tmpdir) initialize_chain(self.options.tmpdir) diff --git a/qa/rpc-tests/wallet-hd.py b/qa/rpc-tests/wallet-hd.py index 99cdfbedf6f3..6553a62eadab 100755 --- a/qa/rpc-tests/wallet-hd.py +++ b/qa/rpc-tests/wallet-hd.py @@ -41,6 +41,11 @@ def run_test (self): chainid = self.nodes[1].getwalletinfo()['hdchainid'] assert_equal(len(chainid), 64) + # create an internal key + change_addr = self.nodes[1].getrawchangeaddress() + change_addrV= self.nodes[1].validateaddress(change_addr); + assert_equal(change_addrV["hdkeypath"], "m/44'/1'/0'/1/0") #first internal child key + # Import a non-HD private key in the HD wallet non_hd_add = self.nodes[0].getnewaddress() self.nodes[1].importprivkey(self.nodes[0].dumpprivkey(non_hd_add)) @@ -64,6 +69,11 @@ def run_test (self): self.nodes[0].sendtoaddress(non_hd_add, 1) self.nodes[0].generate(1) + # create an internal key (again) + change_addr = self.nodes[1].getrawchangeaddress() + change_addrV= self.nodes[1].validateaddress(change_addr); + assert_equal(change_addrV["hdkeypath"], "m/44'/1'/0'/1/1") #second internal child key + self.sync_all() assert_equal(self.nodes[1].getbalance(), num_hd_adds + 1) @@ -89,5 +99,15 @@ def run_test (self): #connect_nodes_bi(self.nodes, 0, 1) assert_equal(self.nodes[1].getbalance(), num_hd_adds + 1) + # send a tx and make sure its using the internal chain for the changeoutput + txid = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1) + outs = self.nodes[1].decoderawtransaction(self.nodes[1].gettransaction(txid)['hex'])['vout']; + keypath = "" + for out in outs: + if out['value'] != 1: + keypath = self.nodes[1].validateaddress(out['scriptPubKey']['addresses'][0])['hdkeypath'] + + assert_equal(keypath[0:13], "m/44'/1'/0'/1") + if __name__ == '__main__': WalletHDTest().main () diff --git a/src/hdchain.cpp b/src/hdchain.cpp index fd99b99acb67..8e0dee5b4e8b 100644 --- a/src/hdchain.cpp +++ b/src/hdchain.cpp @@ -9,6 +9,7 @@ bool CHDChain::SetNull() { nVersion = CHDChain::CURRENT_VERSION; nExternalChainCounter = 0; + nInternalChainCounter = 0; vchSeed.clear(); id = uint256(); return IsNull(); @@ -37,7 +38,7 @@ uint256 CHDChain::GetSeedHash() return Hash(vchSeed.begin(), vchSeed.end()); } -void CHDChain::DeriveChildExtKey(uint32_t childIndex, CExtKey& extKeyRet) +void CHDChain::DeriveChildExtKey(uint32_t childIndex, CExtKey& extKeyRet, bool fInternal) { // Use BIP44 keypath scheme i.e. m / purpose' / coin_type' / account' / change / address_index CExtKey masterKey; //hd master key @@ -60,7 +61,7 @@ void CHDChain::DeriveChildExtKey(uint32_t childIndex, CExtKey& extKeyRet) // derive m/purpose'/coin_type'/account' cointypeKey.Derive(accountKey, 0x80000000); // derive m/purpose'/coin_type'/account/change - accountKey.Derive(changeKey, 0); + accountKey.Derive(changeKey, fInternal ? 1 : 0); // derive m/purpose'/coin_type'/account/change/address_index changeKey.Derive(extKeyRet, childIndex); } diff --git a/src/hdchain.h b/src/hdchain.h index 9a3168bf9aac..288172640c13 100644 --- a/src/hdchain.h +++ b/src/hdchain.h @@ -16,6 +16,7 @@ class CHDChain int nVersion; uint256 id; uint32_t nExternalChainCounter; + uint32_t nInternalChainCounter; CHDChain() : nVersion(CHDChain::CURRENT_VERSION), id(uint256()), nExternalChainCounter(0) { SetNull(); } @@ -28,6 +29,7 @@ class CHDChain READWRITE(vchSeed); READWRITE(id); READWRITE(nExternalChainCounter); + READWRITE(nInternalChainCounter); } bool SetNull(); @@ -37,7 +39,7 @@ class CHDChain std::vector GetSeed() const; uint256 GetSeedHash(); - void DeriveChildExtKey(uint32_t childIndex, CExtKey& extKeyRet); + void DeriveChildExtKey(uint32_t childIndex, CExtKey& extKeyRet, bool fInternal); }; /* hd pubkey data model */ diff --git a/src/init.cpp b/src/init.cpp index 0db4f72e0deb..a1455b0ab2c6 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1677,7 +1677,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) } CPubKey newDefaultKey; - if (pwalletMain->GetKeyFromPool(newDefaultKey)) { + if (pwalletMain->GetKeyFromPool(newDefaultKey, false)) { pwalletMain->SetDefaultKey(newDefaultKey); if (!pwalletMain->SetAddressBook(pwalletMain->vchDefaultKey.GetID(), "", "receive")) strErrors << _("Cannot write default address") << "\n"; @@ -1966,9 +1966,10 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) LogPrintf("mapBlockIndex.size() = %u\n", mapBlockIndex.size()); LogPrintf("chainActive.Height() = %d\n", chainActive.Height()); #ifdef ENABLE_WALLET - LogPrintf("setKeyPool.size() = %u\n", pwalletMain ? pwalletMain->setKeyPool.size() : 0); - LogPrintf("mapWallet.size() = %u\n", pwalletMain ? pwalletMain->mapWallet.size() : 0); - LogPrintf("mapAddressBook.size() = %u\n", pwalletMain ? pwalletMain->mapAddressBook.size() : 0); + LogPrintf("setExternalKeyPool.size() = %u\n", pwalletMain ? pwalletMain->KeypoolCountExternalKeys() : 0); + LogPrintf("setInternalKeyPool.size() = %u\n", pwalletMain ? pwalletMain->KeypoolCountInternalKeys() : 0); + LogPrintf("mapWallet.size() = %u\n", pwalletMain ? pwalletMain->mapWallet.size() : 0); + LogPrintf("mapAddressBook.size() = %u\n", pwalletMain ? pwalletMain->mapAddressBook.size() : 0); #endif if (GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)) diff --git a/src/privatesend-client.cpp b/src/privatesend-client.cpp index 46ae26056abf..064e9c7bbbff 100644 --- a/src/privatesend-client.cpp +++ b/src/privatesend-client.cpp @@ -1070,15 +1070,15 @@ bool CPrivateSendClient::PrepareDenominate(int nMinRounds, int nMaxRounds, std:: vecTxIn.erase(it); vCoins.erase(it2); - CScript scriptChange; + CScript scriptDenom; CPubKey vchPubKey; - // use a unique change address - assert(reservekey.GetReservedKey(vchPubKey)); // should never fail, as we just unlocked - scriptChange = GetScriptForDestination(vchPubKey.GetID()); + // use unique address + assert(reservekey.GetReservedKey(vchPubKey, false)); // should never fail, as we just unlocked + scriptDenom = GetScriptForDestination(vchPubKey.GetID()); reservekey.KeepKey(); // add new output - CTxOut txout(nValueDenom, scriptChange); + CTxOut txout(nValueDenom, scriptDenom); vecTxOutRet.push_back(txout); // subtract denomination amount @@ -1151,7 +1151,7 @@ bool CPrivateSendClient::MakeCollateralAmounts(const CompactTallyItem& tallyItem CScript scriptCollateral; CPubKey vchPubKey; - assert(reservekeyCollateral.GetReservedKey(vchPubKey)); // should never fail, as we just unlocked + assert(reservekeyCollateral.GetReservedKey(vchPubKey, false)); // should never fail, as we just unlocked scriptCollateral = GetScriptForDestination(vchPubKey.GetID()); vecSend.push_back((CRecipient){scriptCollateral, PRIVATESEND_COLLATERAL*4, false}); @@ -1231,7 +1231,7 @@ bool CPrivateSendClient::CreateDenominated(const CompactTallyItem& tallyItem, bo CScript scriptCollateral; CPubKey vchPubKey; - assert(reservekeyCollateral.GetReservedKey(vchPubKey)); // should never fail, as we just unlocked + assert(reservekeyCollateral.GetReservedKey(vchPubKey, false)); // should never fail, as we just unlocked scriptCollateral = GetScriptForDestination(vchPubKey.GetID()); // ****** Add collateral outputs ************ / @@ -1275,11 +1275,11 @@ bool CPrivateSendClient::CreateDenominated(const CompactTallyItem& tallyItem, bo while(nValueLeft - nDenomValue >= 0 && nOutputs <= 10) { CScript scriptDenom; CPubKey vchPubKey; - //use a unique change address + // use a unique address std::shared_ptr reservekeyDenom = std::make_shared(pwalletMain); reservekeyDenomVec.push_back(reservekeyDenom); - assert(reservekeyDenom->GetReservedKey(vchPubKey)); // should never fail, as we just unlocked + assert(reservekeyDenom->GetReservedKey(vchPubKey, false)); // should never fail, as we just unlocked scriptDenom = GetScriptForDestination(vchPubKey.GetID()); vecSend.push_back((CRecipient){ scriptDenom, nDenomValue, false }); diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index 92e0a3946bb7..3339abf83db9 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -371,7 +371,7 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con { // Generate a new address to associate with given label CPubKey newKey; - if(!wallet->GetKeyFromPool(newKey)) + if(!wallet->GetKeyFromPool(newKey, false)) { WalletModel::UnlockContext ctx(walletModel->requestUnlock()); if(!ctx.isValid()) @@ -380,7 +380,7 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con editStatus = WALLET_UNLOCK_FAILURE; return QString(); } - if(!wallet->GetKeyFromPool(newKey)) + if(!wallet->GetKeyFromPool(newKey, false)) { editStatus = KEY_GENERATION_FAILURE; return QString(); diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index 440eaf77e68b..a15a43e5f33c 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -649,7 +649,7 @@ void PaymentServer::fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipien } else { CPubKey newKey; - if (wallet->GetKeyFromPool(newKey)) { + if (wallet->GetKeyFromPool(newKey, false)) { CKeyID keyID = newKey.GetID(); wallet->SetAddressBook(keyID, strAccount, "refund"); diff --git a/src/test/rpc_wallet_tests.cpp b/src/test/rpc_wallet_tests.cpp index f75037ec27f5..51b3e1386a31 100644 --- a/src/test/rpc_wallet_tests.cpp +++ b/src/test/rpc_wallet_tests.cpp @@ -74,7 +74,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet) { LOCK(pwalletMain->cs_wallet); - demoPubkey = pwalletMain->GenerateNewKey(); + demoPubkey = pwalletMain->GenerateNewKey(false); demoAddress = CBitcoinAddress(CTxDestination(demoPubkey.GetID())); string strPurpose = "receive"; BOOST_CHECK_NO_THROW({ /*Initialize Wallet with an account */ @@ -85,7 +85,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet) walletdb.WriteAccount(strAccount, account); }); - CPubKey setaccountDemoPubkey = pwalletMain->GenerateNewKey(); + CPubKey setaccountDemoPubkey = pwalletMain->GenerateNewKey(false); setaccountDemoAddress = CBitcoinAddress(CTxDestination(setaccountDemoPubkey.GetID())); } /********************************* diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index ea49e964e028..68613db56196 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -142,7 +142,7 @@ UniValue getnewaddress(const UniValue& params, bool fHelp) // Generate a new key that is added to wallet CPubKey newKey; - if (!pwalletMain->GetKeyFromPool(newKey)) + if (!pwalletMain->GetKeyFromPool(newKey, false)) throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); CKeyID keyID = newKey.GetID(); @@ -179,7 +179,7 @@ CBitcoinAddress GetAccountAddress(string strAccount, bool bForceNew=false) // Generate a new key if (!account.vchPubKey.IsValid() || bForceNew || bKeyUsed) { - if (!pwalletMain->GetKeyFromPool(account.vchPubKey)) + if (!pwalletMain->GetKeyFromPool(account.vchPubKey, false)) throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); pwalletMain->SetAddressBook(account.vchPubKey.GetID(), strAccount, "receive"); @@ -245,7 +245,7 @@ UniValue getrawchangeaddress(const UniValue& params, bool fHelp) CReserveKey reservekey(pwalletMain); CPubKey vchPubKey; - if (!reservekey.GetReservedKey(vchPubKey)) + if (!reservekey.GetReservedKey(vchPubKey, true)) throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); reservekey.KeepKey(); @@ -1977,7 +1977,7 @@ UniValue keypoolrefill(const UniValue& params, bool fHelp) EnsureWalletIsUnlocked(); pwalletMain->TopUpKeyPool(kpSize); - if (pwalletMain->GetKeyPoolSize() < kpSize) + if (pwalletMain->GetKeyPoolSize() < (pwalletMain->IsHDEnabled() ? kpSize * 2 : kpSize)) throw JSONRPCError(RPC_WALLET_ERROR, "Error refreshing keypool."); return NullUniValue; @@ -2377,12 +2377,14 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp) " \"immature_balance\": xxxxxx, (numeric) the total immature balance of the wallet in " + CURRENCY_UNIT + "\n" " \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n" " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since GMT epoch) of the oldest pre-generated key in the key pool\n" - " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated\n" + " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated (only counts external keys)\n" + " \"keypoolsize_hd_internal\": xxxx, (numeric) how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)\n" " \"keys_left\": xxxx, (numeric) how many new keys are left since last automatic backup\n" " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n" " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n" " \"hdchainid\": \"\", (string) the ID of the HD chain\n" - " \"hdchildkeyindex\": xxxx, (numeric) current childkey index\n" + " \"hdexternalkeyindex\": xxxx, (numeric) current external childkey index\n" + " \"hdinternalkeyindex\": xxxx, (numeric) current internal childkey index\n" "}\n" "\nExamples:\n" + HelpExampleCli("getwalletinfo", "") @@ -2391,6 +2393,8 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp) LOCK2(cs_main, pwalletMain->cs_wallet); + CHDChain hdChainCurrent; + bool fHDEnabled = pwalletMain->GetHDChain(hdChainCurrent); UniValue obj(UniValue::VOBJ); obj.push_back(Pair("walletversion", pwalletMain->GetVersion())); obj.push_back(Pair("balance", ValueFromAmount(pwalletMain->GetBalance()))); @@ -2398,15 +2402,18 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp) obj.push_back(Pair("immature_balance", ValueFromAmount(pwalletMain->GetImmatureBalance()))); obj.push_back(Pair("txcount", (int)pwalletMain->mapWallet.size())); obj.push_back(Pair("keypoololdest", pwalletMain->GetOldestKeyPoolTime())); - obj.push_back(Pair("keypoolsize", (int)pwalletMain->GetKeyPoolSize())); + obj.push_back(Pair("keypoolsize", (int64_t)pwalletMain->KeypoolCountExternalKeys())); + if (fHDEnabled) { + obj.push_back(Pair("keypoolsize_hd_internal", (int64_t)(pwalletMain->KeypoolCountInternalKeys()))); + } obj.push_back(Pair("keys_left", pwalletMain->nKeysLeftSinceAutoBackup)); if (pwalletMain->IsCrypted()) obj.push_back(Pair("unlocked_until", nWalletUnlockTime)); obj.push_back(Pair("paytxfee", ValueFromAmount(payTxFee.GetFeePerK()))); - CHDChain hdChainCurrent; - if (pwalletMain->GetHDChain(hdChainCurrent)) { + if (fHDEnabled) { obj.push_back(Pair("hdchainid", hdChainCurrent.id.GetHex())); - obj.push_back(Pair("hdchildkeyindex", (int64_t)hdChainCurrent.nExternalChainCounter)); + obj.push_back(Pair("hdexternalkeyindex", (int64_t)hdChainCurrent.nExternalChainCounter)); + obj.push_back(Pair("hdinternalkeyindex", (int64_t)hdChainCurrent.nInternalChainCounter)); } return obj; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 76c5b14adf38..a135d529580b 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -101,7 +101,7 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const return &(it->second); } -CPubKey CWallet::GenerateNewKey() +CPubKey CWallet::GenerateNewKey(bool fInternal) { AssertLockHeld(cs_wallet); // mapKeyMetadata bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets @@ -115,7 +115,7 @@ CPubKey CWallet::GenerateNewKey() CPubKey pubkey; // use HD key derivation if HD was enabled during wallet creation if (IsHDEnabled()) { - DeriveNewChildKey(metadata, secret); + DeriveNewChildKey(metadata, secret, fInternal); pubkey = secret.GetPubKey(); } else { secret.MakeNewKey(fCompressed); @@ -138,7 +138,7 @@ CPubKey CWallet::GenerateNewKey() return pubkey; } -void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret) +void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret, bool fInternal) { CHDChain hdChainTmp; if (!GetHDChain(hdChainTmp)) { @@ -155,9 +155,9 @@ void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret) // derive child key at next index, skip keys already known to the wallet CExtKey childKey; - uint32_t childIndex = hdChainTmp.nExternalChainCounter; + uint32_t childIndex = fInternal ? hdChainTmp.nInternalChainCounter : hdChainTmp.nExternalChainCounter; do { - hdChainTmp.DeriveChildExtKey(childIndex, childKey); + hdChainTmp.DeriveChildExtKey(childIndex, childKey, fInternal); // increment childkey index childIndex++; } while (HaveKey(childKey.key.GetPubKey().GetID())); @@ -174,7 +174,14 @@ void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret) // update the chain model in the database CHDChain hdChainCurrent; GetHDChain(hdChainCurrent); - hdChainCurrent.nExternalChainCounter = childIndex; + + if (fInternal) { + hdChainCurrent.nInternalChainCounter = childIndex; + } + else { + hdChainCurrent.nExternalChainCounter = childIndex; + } + if (IsCrypted()) { if (!SetCryptedHDChain(hdChainCurrent, false)) throw std::runtime_error(std::string(__func__) + ": SetCryptedHDChain failed"); @@ -184,7 +191,7 @@ void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret) throw std::runtime_error(std::string(__func__) + ": SetHDChain failed"); } - if (!AddHDPubKey(childKey.Neuter())) + if (!AddHDPubKey(childKey.Neuter(), fInternal)) throw std::runtime_error(std::string(__func__) + ": AddHDPubKey failed"); } @@ -222,7 +229,7 @@ bool CWallet::GetKey(const CKeyID &address, CKey& keyOut) const throw std::runtime_error(std::string(__func__) + ": Wrong HD chain!"); CExtKey extkey; - hdChainCurrent.DeriveChildExtKey(hdPubKey.extPubKey.nChild, extkey); + hdChainCurrent.DeriveChildExtKey(hdPubKey.extPubKey.nChild, extkey, hdPubKey.nChange != 0); keyOut = extkey.key; return true; @@ -248,7 +255,7 @@ bool CWallet::LoadHDPubKey(const CHDPubKey &hdPubKey) return true; } -bool CWallet::AddHDPubKey(const CExtPubKey &extPubKey) +bool CWallet::AddHDPubKey(const CExtPubKey &extPubKey, bool fInternal) { AssertLockHeld(cs_wallet); @@ -258,6 +265,7 @@ bool CWallet::AddHDPubKey(const CExtPubKey &extPubKey) CHDPubKey hdPubKey; hdPubKey.extPubKey = extPubKey; hdPubKey.hdchainID = hdChainCurrent.id; + hdPubKey.nChange = fInternal ? 1 : 0; mapHdPubKeys[extPubKey.pubkey.GetID()] = hdPubKey; // check if we need to remove from watch-only @@ -3118,7 +3126,7 @@ bool CWallet::CreateCollateralTransaction(CMutableTransaction& txCollateral, std // make our change address CScript scriptChange; CPubKey vchPubKey; - assert(reservekey.GetReservedKey(vchPubKey)); // should never fail, as we just unlocked + assert(reservekey.GetReservedKey(vchPubKey, true)); // should never fail, as we just unlocked scriptChange = GetScriptForDestination(vchPubKey.GetID()); reservekey.KeepKey(); @@ -3376,7 +3384,7 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt // Reserve a new key pair from key pool CPubKey vchPubKey; - if (!reservekey.GetReservedKey(vchPubKey)) + if (!reservekey.GetReservedKey(vchPubKey, true)) { strFailReason = _("Keypool ran out, please call keypoolrefill first"); return false; @@ -3642,7 +3650,8 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) if (CDB::Rewrite(strWalletFile, "\x04pool")) { LOCK(cs_wallet); - setKeyPool.clear(); + setInternalKeyPool.clear(); + setExternalKeyPool.clear(); nKeysLeftSinceAutoBackup = 0; // Note: can't top-up keypool here, because wallet is locked. // User will be prompted to unlock wallet the next operation @@ -3670,7 +3679,8 @@ DBErrors CWallet::ZapWalletTx(std::vector& vWtx) if (CDB::Rewrite(strWalletFile, "\x04pool")) { LOCK(cs_wallet); - setKeyPool.clear(); + setInternalKeyPool.clear(); + setExternalKeyPool.clear(); nKeysLeftSinceAutoBackup = 0; // Note: can't top-up keypool here, because wallet is locked. // User will be prompted to unlock wallet the next operation @@ -3750,27 +3760,37 @@ bool CWallet::NewKeyPool() { LOCK(cs_wallet); CWalletDB walletdb(strWalletFile); - BOOST_FOREACH(int64_t nIndex, setKeyPool) + BOOST_FOREACH(int64_t nIndex, setInternalKeyPool) { walletdb.ErasePool(nIndex); - setKeyPool.clear(); + } + setInternalKeyPool.clear(); + BOOST_FOREACH(int64_t nIndex, setExternalKeyPool) { + walletdb.ErasePool(nIndex); + } + setExternalKeyPool.clear(); privateSendClient.fEnablePrivateSend = false; nKeysLeftSinceAutoBackup = 0; - if (IsLocked(true)) + if (!TopUpKeyPool()) return false; - int64_t nKeys = max(GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t)0); - for (int i = 0; i < nKeys; i++) - { - int64_t nIndex = i+1; - walletdb.WritePool(nIndex, CKeyPool(GenerateNewKey())); - setKeyPool.insert(nIndex); - } - LogPrintf("CWallet::NewKeyPool wrote %d new keys\n", nKeys); + LogPrintf("CWallet::NewKeyPool rewrote keypool\n"); } return true; } +size_t CWallet::KeypoolCountExternalKeys() +{ + AssertLockHeld(cs_wallet); // setExternalKeyPool + return setExternalKeyPool.size(); +} + +size_t CWallet::KeypoolCountInternalKeys() +{ + AssertLockHeld(cs_wallet); // setInternalKeyPool + return setInternalKeyPool.size(); +} + bool CWallet::TopUpKeyPool(unsigned int kpSize) { { @@ -3779,8 +3799,6 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize) if (IsLocked(true)) return false; - CWalletDB walletdb(strWalletFile); - // Top up key pool unsigned int nTargetSize; if (kpSize > 0) @@ -3788,15 +3806,44 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize) else nTargetSize = max(GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t) 0); - while (setKeyPool.size() < (nTargetSize + 1)) + // count amount of available keys (internal, external) + // make sure the keypool of external and internal keys fits the user selected target (-keypool) + int64_t amountExternal = setExternalKeyPool.size(); + int64_t amountInternal = setInternalKeyPool.size(); + int64_t missingExternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - amountExternal, (int64_t) 0); + int64_t missingInternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - amountInternal, (int64_t) 0); + + if (!IsHDEnabled()) + { + // don't create extra internal keys + missingInternal = 0; + } else { + nTargetSize *= 2; + } + bool fInternal = false; + CWalletDB walletdb(strWalletFile); + for (int64_t i = missingInternal + missingExternal; i--;) { int64_t nEnd = 1; - if (!setKeyPool.empty()) - nEnd = *(--setKeyPool.end()) + 1; - if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey()))) + if (i < missingInternal) { + fInternal = true; + } + if (!setInternalKeyPool.empty()) { + nEnd = *(--setInternalKeyPool.end()) + 1; + } + if (!setExternalKeyPool.empty()) { + nEnd = std::max(nEnd, *(--setExternalKeyPool.end()) + 1); + } + if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey(fInternal), fInternal))) throw runtime_error("TopUpKeyPool(): writing generated key failed"); - setKeyPool.insert(nEnd); - LogPrintf("keypool added key %d, size=%u\n", nEnd, setKeyPool.size()); + + if (fInternal) { + setInternalKeyPool.insert(nEnd); + } else { + setExternalKeyPool.insert(nEnd); + } + LogPrintf("keypool added key %d, size=%u, internal=%d\n", nEnd, setInternalKeyPool.size() + setExternalKeyPool.size(), fInternal); + double dProgress = 100.f * nEnd / (nTargetSize + 1); std::string strMsg = strprintf(_("Loading wallet... (%3.2f %%)"), dProgress); uiInterface.InitMessage(strMsg); @@ -3805,7 +3852,7 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize) return true; } -void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool) +void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fInternal) { nIndex = -1; keypool.vchPubKey = CPubKey(); @@ -3815,18 +3862,27 @@ void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool) if (!IsLocked(true)) TopUpKeyPool(); + fInternal = fInternal && IsHDEnabled(); + std::set& setKeyPool = fInternal ? setInternalKeyPool : setExternalKeyPool; + // Get the oldest key if(setKeyPool.empty()) return; CWalletDB walletdb(strWalletFile); - nIndex = *(setKeyPool.begin()); - setKeyPool.erase(setKeyPool.begin()); - if (!walletdb.ReadPool(nIndex, keypool)) - throw runtime_error("ReserveKeyFromKeyPool(): read failed"); - if (!HaveKey(keypool.vchPubKey.GetID())) - throw runtime_error("ReserveKeyFromKeyPool(): unknown key in key pool"); + nIndex = *setKeyPool.begin(); + setKeyPool.erase(nIndex); + if (!walletdb.ReadPool(nIndex, keypool)) { + throw std::runtime_error(std::string(__func__) + ": read failed"); + } + if (!HaveKey(keypool.vchPubKey.GetID())) { + throw std::runtime_error(std::string(__func__) + ": unknown key in key pool"); + } + if (keypool.fInternal != fInternal) { + throw std::runtime_error(std::string(__func__) + ": keypool entry misclassified"); + } + assert(keypool.vchPubKey.IsValid()); LogPrintf("keypool reserve %d\n", nIndex); } @@ -3844,27 +3900,31 @@ void CWallet::KeepKey(int64_t nIndex) LogPrintf("keypool keep %d\n", nIndex); } -void CWallet::ReturnKey(int64_t nIndex) +void CWallet::ReturnKey(int64_t nIndex, bool fInternal) { // Return to key pool { LOCK(cs_wallet); - setKeyPool.insert(nIndex); + if (fInternal) { + setInternalKeyPool.insert(nIndex); + } else { + setExternalKeyPool.insert(nIndex); + } } LogPrintf("keypool return %d\n", nIndex); } -bool CWallet::GetKeyFromPool(CPubKey& result) +bool CWallet::GetKeyFromPool(CPubKey& result, bool fInternal) { int64_t nIndex = 0; CKeyPool keypool; { LOCK(cs_wallet); - ReserveKeyFromKeyPool(nIndex, keypool); + ReserveKeyFromKeyPool(nIndex, keypool, fInternal); if (nIndex == -1) { if (IsLocked(true)) return false; - result = GenerateNewKey(); + result = GenerateNewKey(fInternal); return true; } KeepKey(nIndex); @@ -3873,15 +3933,35 @@ bool CWallet::GetKeyFromPool(CPubKey& result) return true; } +static int64_t GetOldestKeyInPool(const std::set& setKeyPool, CWalletDB& walletdb) { + CKeyPool keypool; + int64_t nIndex = *(setKeyPool.begin()); + if (!walletdb.ReadPool(nIndex, keypool)) { + throw std::runtime_error(std::string(__func__) + ": read oldest key in keypool failed"); + } + assert(keypool.vchPubKey.IsValid()); + return keypool.nTime; +} + int64_t CWallet::GetOldestKeyPoolTime() { - int64_t nIndex = 0; - CKeyPool keypool; - ReserveKeyFromKeyPool(nIndex, keypool); - if (nIndex == -1) + LOCK(cs_wallet); + + // if the keypool is empty, return + if (setExternalKeyPool.empty() && setInternalKeyPool.empty()) return GetTime(); - ReturnKey(nIndex); - return keypool.nTime; + + CWalletDB walletdb(strWalletFile); + int64_t oldestKey = -1; + + // load oldest key from keypool, get time and return + if (!setInternalKeyPool.empty()) { + oldestKey = std::max(GetOldestKeyInPool(setInternalKeyPool, walletdb), oldestKey); + } + if (!setExternalKeyPool.empty()) { + oldestKey = std::max(GetOldestKeyInPool(setExternalKeyPool, walletdb), oldestKey); + } + return oldestKey; } std::map CWallet::GetAddressBalances() @@ -4031,17 +4111,19 @@ std::set CWallet::GetAccountAddresses(const std::string& strAcco return result; } -bool CReserveKey::GetReservedKey(CPubKey& pubkey) +bool CReserveKey::GetReservedKey(CPubKey& pubkey, bool fInternalIn) { if (nIndex == -1) { CKeyPool keypool; - pwallet->ReserveKeyFromKeyPool(nIndex, keypool); - if (nIndex != -1) + pwallet->ReserveKeyFromKeyPool(nIndex, keypool, fInternalIn); + if (nIndex != -1) { vchPubKey = keypool.vchPubKey; + } else { return false; } + fInternal = keypool.fInternal; } assert(vchPubKey.IsValid()); pubkey = vchPubKey; @@ -4050,27 +4132,24 @@ bool CReserveKey::GetReservedKey(CPubKey& pubkey) void CReserveKey::KeepKey() { - if (nIndex != -1) + if (nIndex != -1) { pwallet->KeepKey(nIndex); + } nIndex = -1; vchPubKey = CPubKey(); } void CReserveKey::ReturnKey() { - if (nIndex != -1) - pwallet->ReturnKey(nIndex); + if (nIndex != -1) { + pwallet->ReturnKey(nIndex, fInternal); + } nIndex = -1; vchPubKey = CPubKey(); } -void CWallet::GetAllReserveKeys(set& setAddress) const +static void LoadReserveKeysToSet(std::set& setAddress, const std::set& setKeyPool, CWalletDB& walletdb) { - setAddress.clear(); - - CWalletDB walletdb(strWalletFile); - - LOCK2(cs_main, cs_wallet); BOOST_FOREACH(const int64_t& id, setKeyPool) { CKeyPool keypool; @@ -4078,12 +4157,27 @@ void CWallet::GetAllReserveKeys(set& setAddress) const throw runtime_error("GetAllReserveKeyHashes(): read failed"); assert(keypool.vchPubKey.IsValid()); CKeyID keyID = keypool.vchPubKey.GetID(); - if (!HaveKey(keyID)) - throw runtime_error("GetAllReserveKeyHashes(): unknown key in key pool"); setAddress.insert(keyID); } } +void CWallet::GetAllReserveKeys(std::set& setAddress) const +{ + setAddress.clear(); + + CWalletDB walletdb(strWalletFile); + + LOCK2(cs_main, cs_wallet); + LoadReserveKeysToSet(setAddress, setInternalKeyPool, walletdb); + LoadReserveKeysToSet(setAddress, setExternalKeyPool, walletdb); + + BOOST_FOREACH (const CKeyID& keyID, setAddress) { + if (!HaveKey(keyID)) { + throw std::runtime_error(std::string(__func__) + ": unknown key in key pool"); + } + } +} + bool CWallet::UpdatedTransaction(const uint256 &hashTx) { { @@ -4102,7 +4196,7 @@ void CWallet::GetScriptForMining(boost::shared_ptr &script) { boost::shared_ptr rKey(new CReserveKey(this)); CPubKey pubkey; - if (!rKey->GetReservedKey(pubkey)) + if (!rKey->GetReservedKey(pubkey, false)) return; script = rKey; @@ -4286,12 +4380,14 @@ bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key, st CKeyPool::CKeyPool() { nTime = GetTime(); + fInternal = false; } -CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn) +CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool fInternalIn) { nTime = GetTime(); vchPubKey = vchPubKeyIn; + fInternal = fInternalIn; } CWalletKey::CWalletKey(int64_t nExpires) diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 4905a0d1dc07..8ffd64a9beab 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -122,9 +122,10 @@ class CKeyPool public: int64_t nTime; CPubKey vchPubKey; + bool fInternal; // for change outputs CKeyPool(); - CKeyPool(const CPubKey& vchPubKeyIn); + CKeyPool(const CPubKey& vchPubKeyIn, bool fInternalIn); ADD_SERIALIZE_METHODS; @@ -134,6 +135,19 @@ class CKeyPool READWRITE(nVersion); READWRITE(nTime); READWRITE(vchPubKey); + if (ser_action.ForRead()) { + try { + READWRITE(fInternal); + } + catch (std::ios_base::failure&) { + /* flag as external address if we can't read the internal boolean + (this will be the case for any wallet before the HD chain split version) */ + fInternal = false; + } + } + else { + READWRITE(fInternal); + } } }; @@ -638,6 +652,9 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface void SyncMetaData(std::pair); + /* HD derive new child key (on internal or external chain) */ + void DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret, bool fInternal /*= false*/); + public: /* * Main wallet lock. @@ -651,7 +668,24 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface bool fFileBacked; const std::string strWalletFile; - std::set setKeyPool; + void LoadKeyPool(int nIndex, const CKeyPool &keypool) + { + if (keypool.fInternal) { + setInternalKeyPool.insert(nIndex); + } else { + setExternalKeyPool.insert(nIndex); + } + + // If no metadata exists yet, create a default with the pool key's + // creation time. Note that this may be overwritten by actually + // stored metadata for that key later, which is fine. + CKeyID keyid = keypool.vchPubKey.GetID(); + if (mapKeyMetadata.count(keyid) == 0) + mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime); + } + + std::set setInternalKeyPool; + std::set setExternalKeyPool; std::map mapKeyMetadata; typedef std::map MasterKeyMap; @@ -768,8 +802,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface * keystore implementation * Generate a new key */ - CPubKey GenerateNewKey(); - void DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret); + CPubKey GenerateNewKey(bool fInternal /*= false*/); //! HaveKey implementation that also checks the mapHdPubKeys bool HaveKey(const CKeyID &address) const; //! GetPubKey implementation that also checks the mapHdPubKeys @@ -777,7 +810,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface //! GetKey implementation that can derive a HD private key on the fly bool GetKey(const CKeyID &address, CKey& keyOut) const; //! Adds a HDPubKey into the wallet(database) - bool AddHDPubKey(const CExtPubKey &extPubKey); + bool AddHDPubKey(const CExtPubKey &extPubKey, bool fInternal); //! loads a HDPubKey into the wallets memory bool LoadHDPubKey(const CHDPubKey &hdPubKey); //! Adds a key to the store, and saves it to disk. @@ -881,11 +914,13 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface static CAmount GetRequiredFee(unsigned int nTxBytes); bool NewKeyPool(); + size_t KeypoolCountExternalKeys(); + size_t KeypoolCountInternalKeys(); bool TopUpKeyPool(unsigned int kpSize = 0); - void ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool); + void ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fInternal); void KeepKey(int64_t nIndex); - void ReturnKey(int64_t nIndex); - bool GetKeyFromPool(CPubKey &key); + void ReturnKey(int64_t nIndex, bool fInternal); + bool GetKeyFromPool(CPubKey &key, bool fInternal /*= false*/); int64_t GetOldestKeyPoolTime(); void GetAllReserveKeys(std::set& setAddress) const; @@ -936,8 +971,8 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface unsigned int GetKeyPoolSize() { - AssertLockHeld(cs_wallet); // setKeyPool - return setKeyPool.size(); + AssertLockHeld(cs_wallet); // set{Ex,In}ternalKeyPool + return setInternalKeyPool.size() + setExternalKeyPool.size(); } bool SetDefaultKey(const CPubKey &vchPubKey); @@ -1011,11 +1046,13 @@ class CReserveKey : public CReserveScript CWallet* pwallet; int64_t nIndex; CPubKey vchPubKey; + bool fInternal; public: CReserveKey(CWallet* pwalletIn) { nIndex = -1; pwallet = pwalletIn; + fInternal = false; } ~CReserveKey() @@ -1024,7 +1061,7 @@ class CReserveKey : public CReserveScript } void ReturnKey(); - bool GetReservedKey(CPubKey &pubkey); + bool GetReservedKey(CPubKey &pubkey, bool fInternalIn /*= false*/); void KeepKey(); void KeepScript() { KeepKey(); } }; diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index de0995226ff4..cad954f5b3a9 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -556,14 +556,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, ssKey >> nIndex; CKeyPool keypool; ssValue >> keypool; - pwallet->setKeyPool.insert(nIndex); - - // If no metadata exists yet, create a default with the pool key's - // creation time. Note that this may be overwritten by actually - // stored metadata for that key later, which is fine. - CKeyID keyid = keypool.vchPubKey.GetID(); - if (pwallet->mapKeyMetadata.count(keyid) == 0) - pwallet->mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime); + pwallet->LoadKeyPool(nIndex, keypool); } else if (strType == "version") { @@ -713,8 +706,8 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet) } pcursor->close(); - // Store initial pool size - pwallet->nKeysLeftSinceAutoBackup = pwallet->GetKeyPoolSize(); + // Store initial external keypool size since we mostly use external keys in mixing + pwallet->nKeysLeftSinceAutoBackup = pwallet->KeypoolCountExternalKeys(); LogPrintf("nKeysLeftSinceAutoBackup: %d\n", pwallet->nKeysLeftSinceAutoBackup); } catch (const boost::thread_interrupted&) { @@ -987,8 +980,8 @@ bool AutoBackupWallet (CWallet* wallet, std::string strWalletFile, std::string& nWalletBackups = -1; return false; } - // Update nKeysLeftSinceAutoBackup using current pool size - wallet->nKeysLeftSinceAutoBackup = wallet->GetKeyPoolSize(); + // Update nKeysLeftSinceAutoBackup using current external keypool size + wallet->nKeysLeftSinceAutoBackup = wallet->KeypoolCountExternalKeys(); LogPrintf("nKeysLeftSinceAutoBackup: %d\n", wallet->nKeysLeftSinceAutoBackup); if(wallet->IsLocked(true)) { strBackupWarning = _("Wallet is locked, can't replenish keypool! Automatic backups and mixing are disabled, please unlock your wallet to replenish keypool."); From 7f0c74e788abcd1a8175b593c5ac52eff5b23c1e Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Wed, 26 Apr 2017 15:52:02 +0300 Subject: [PATCH 12/31] add missing cs_wallet lock in init.cpp --- src/init.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index a1455b0ab2c6..6870f9d98a56 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1966,10 +1966,15 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) LogPrintf("mapBlockIndex.size() = %u\n", mapBlockIndex.size()); LogPrintf("chainActive.Height() = %d\n", chainActive.Height()); #ifdef ENABLE_WALLET - LogPrintf("setExternalKeyPool.size() = %u\n", pwalletMain ? pwalletMain->KeypoolCountExternalKeys() : 0); - LogPrintf("setInternalKeyPool.size() = %u\n", pwalletMain ? pwalletMain->KeypoolCountInternalKeys() : 0); - LogPrintf("mapWallet.size() = %u\n", pwalletMain ? pwalletMain->mapWallet.size() : 0); - LogPrintf("mapAddressBook.size() = %u\n", pwalletMain ? pwalletMain->mapAddressBook.size() : 0); + if (pwalletMain) { + LOCK(pwalletMain->cs_wallet); + LogPrintf("setExternalKeyPool.size() = %u\n", pwalletMain->KeypoolCountExternalKeys()); + LogPrintf("setInternalKeyPool.size() = %u\n", pwalletMain->KeypoolCountInternalKeys()); + LogPrintf("mapWallet.size() = %u\n", pwalletMain->mapWallet.size()); + LogPrintf("mapAddressBook.size() = %u\n", pwalletMain->mapAddressBook.size()); + } else { + LogPrintf("wallet is NULL\n"); + } #endif if (GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)) From a17ad864f9cb6b5e575b330100c34579fe04f483 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Thu, 27 Apr 2017 10:01:22 +0300 Subject: [PATCH 13/31] fix `const char *` issues (use strings) --- src/test/bip39_tests.cpp | 14 ++++++-------- src/wallet/wallet.cpp | 17 ++++++++--------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/test/bip39_tests.cpp b/src/test/bip39_tests.cpp index f811708dcf68..e78d70540425 100644 --- a/src/test/bip39_tests.cpp +++ b/src/test/bip39_tests.cpp @@ -34,21 +34,19 @@ BOOST_AUTO_TEST_CASE(bip39_vectors) continue; } - const char *m; std::vector vData = ParseHex(test[0].get_str()); - m = mnemonic_from_data(&vData[0], vData.size()); - const char *mnemonic = test[1].get_str().c_str(); + std::string m = mnemonic_from_data(&vData[0], vData.size()); + std::string mnemonic = test[1].get_str(); - // printf("%s\n%s\n", m, mnemonic); - BOOST_CHECK(*m == *mnemonic); - - BOOST_CHECK(mnemonic_check(mnemonic)); + // printf("%s\n%s\n", m.c_str(), mnemonic.c_str()); + BOOST_CHECK(m == mnemonic); + BOOST_CHECK(mnemonic_check(mnemonic.c_str())); uint8_t seed[64]; const char *passphrase = "TREZOR"; - mnemonic_to_seed(mnemonic, passphrase, seed, 0); + mnemonic_to_seed(mnemonic.c_str(), passphrase, seed, 0); // printf("seed: %s\n", HexStr(seed, seed + 64).c_str()); BOOST_CHECK(HexStr(seed, seed + 64) == test[2].get_str()); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index a135d529580b..97a1eefa7422 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1383,7 +1383,7 @@ CAmount CWallet::GetChange(const CTxOut& txout) const void CWallet::GenerateNewHDChain() { - const char *mnemonic; + std::string mnemonic; std::vector vchSeed; std::string strSeed = GetArg("-hdseed", "not hex"); @@ -1395,29 +1395,28 @@ void CWallet::GenerateNewHDChain() LogPrintf("CWallet::GenerateNewHDChain -- Incorrect seed, generating random one instead\n"); if(mapArgs.count("-mnemonic")) { - mnemonic = GetArg("-mnemonic", "").c_str(); + mnemonic = GetArg("-mnemonic", ""); } else { mnemonic = mnemonic_generate(128); } - if(!mnemonic_check(mnemonic)) { + if(!mnemonic_check(mnemonic.c_str())) { throw std::runtime_error(std::string(__func__) + ": invalid mnemonic"); } - DBG( printf("mnemonic: '%s'\n", mnemonic); ); + DBG( printf("mnemonic: '%s'\n", mnemonic.c_str()); ); - const char *passphrase; // default mnemonic passphrase is empty string // however if no mnemonic was specified by the user, use random passphrase instead - passphrase = GetArg("-mnemonicpassphrase", "").c_str(); + std::string passphrase = GetArg("-mnemonicpassphrase", ""); if (!mapArgs.count("-mnemonic")) { unsigned char rand_pwd[32]; GetRandBytes(rand_pwd, 32); - passphrase = EncodeBase58(&rand_pwd[0],&rand_pwd[0]+32).c_str(); + passphrase = EncodeBase58(&rand_pwd[0],&rand_pwd[0]+32); } - DBG( printf("mnemonicpassphrase: '%s'\n", passphrase); ); + DBG( printf("mnemonicpassphrase: '%s'\n", passphrase.c_str()); ); uint8_t seed[64]; - mnemonic_to_seed(mnemonic, passphrase, seed, 0); + mnemonic_to_seed(mnemonic.c_str(), passphrase.c_str(), seed, 0); vchSeed = std::vector(seed, seed + 64); } From 35381df2acb09fa9fbcc4162101599db222a957e Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Thu, 27 Apr 2017 19:12:03 +0300 Subject: [PATCH 14/31] default mnemonic passphrase is an empty string in all cases --- src/wallet/wallet.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 97a1eefa7422..d6b1154e4380 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1405,14 +1405,8 @@ void CWallet::GenerateNewHDChain() } DBG( printf("mnemonic: '%s'\n", mnemonic.c_str()); ); - // default mnemonic passphrase is empty string - // however if no mnemonic was specified by the user, use random passphrase instead + // default mnemonic passphrase is an empty string std::string passphrase = GetArg("-mnemonicpassphrase", ""); - if (!mapArgs.count("-mnemonic")) { - unsigned char rand_pwd[32]; - GetRandBytes(rand_pwd, 32); - passphrase = EncodeBase58(&rand_pwd[0],&rand_pwd[0]+32); - } DBG( printf("mnemonicpassphrase: '%s'\n", passphrase.c_str()); ); uint8_t seed[64]; From 75670d89a62622ae93a344ba0bd733284953080f Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Fri, 28 Apr 2017 16:24:55 +0300 Subject: [PATCH 15/31] store mnemonic/mnemonicpassphrase replace dumphdseed with dumphdinfo --- src/Makefile.am | 2 +- src/hdchain.cpp | 97 +++++++++++++++++++++++++++++++++++++++++- src/hdchain.h | 10 +++++ src/rpcserver.cpp | 2 +- src/rpcserver.h | 2 +- src/wallet/crypter.cpp | 90 +++++++++++++++++++++++++++++---------- src/wallet/crypter.h | 4 +- src/wallet/rpcdump.cpp | 51 ++++++++++++++++------ src/wallet/wallet.cpp | 77 ++++++++++----------------------- src/wallet/wallet.h | 2 +- 10 files changed, 239 insertions(+), 98 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 7c433b05d754..085645e5298d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -263,7 +263,6 @@ libbitcoin_wallet_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) libbitcoin_wallet_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_wallet_a_SOURCES = \ activemasternode.cpp \ - bip39.cpp \ dsnotificationinterface.cpp \ instantx.cpp \ masternode.cpp \ @@ -333,6 +332,7 @@ libbitcoin_common_a_SOURCES = \ amount.cpp \ arith_uint256.cpp \ base58.cpp \ + bip39.cpp \ chainparams.cpp \ coins.cpp \ compressor.cpp \ diff --git a/src/hdchain.cpp b/src/hdchain.cpp index 8e0dee5b4e8b..a80e5537d83e 100644 --- a/src/hdchain.cpp +++ b/src/hdchain.cpp @@ -1,9 +1,13 @@ // Copyright (c) 2014-2017 The Dash Core developers // Distributed under the MIT software license, see the accompanying +#include "base58.h" +#include "bip39.h" #include "chainparams.h" #include "hdchain.h" #include "tinyformat.h" +#include "util.h" +#include "utilstrencodings.h" bool CHDChain::SetNull() { @@ -11,6 +15,8 @@ bool CHDChain::SetNull() nExternalChainCounter = 0; nInternalChainCounter = 0; vchSeed.clear(); + vchMnemonic.clear(); + vchMnemonicPassphrase.clear(); id = uint256(); return IsNull(); } @@ -20,11 +26,100 @@ bool CHDChain::IsNull() const return vchSeed.empty() || id == uint256(); } +void CHDChain::Debug() +{ + DBG( + std::cout << "mnemonic: " << std::string(vchMnemonic.begin(), vchMnemonic.end()).c_str() << std::endl; + std::cout << "mnemonicpassphrase: " << std::string(vchMnemonicPassphrase.begin(), vchMnemonicPassphrase.end()).c_str() << std::endl; + + std::cout << "seed: " << HexStr(vchSeed).c_str() << std::endl; + + CExtKey extkey; + extkey.SetMaster(&vchSeed[0], vchSeed.size()); + + CBitcoinExtKey b58extkey; + b58extkey.SetKey(extkey); + std::cout << "extended private masterkey: " << b58extkey.ToString().c_str() << std::endl; + + CExtPubKey extpubkey; + extpubkey = extkey.Neuter(); + + CBitcoinExtPubKey b58extpubkey; + b58extpubkey.SetKey(extpubkey); + std::cout << "extended public masterkey: " << b58extpubkey.ToString().c_str() << std::endl; + ); +} + +bool CHDChain::SetMnemonic(const std::vector& vchMnemonicIn, const std::vector& vchMnemonicPassphraseIn, bool fUpdateID) +{ + std::vector vchMnemonicTmp = vchMnemonicIn; + + if (fUpdateID) { + // can't (re)set mnemonic if seed was already set + if (!IsNull()) + return false; + + std::string strMnemonic(vchMnemonicIn.begin(), vchMnemonicIn.end()); + std::string strMnemonicPassphrase(vchMnemonicPassphraseIn.begin(), vchMnemonicPassphraseIn.end()); + + // empty mnemonic i.e. "generate a new one" + if (vchMnemonicIn.empty()) { + strMnemonic = mnemonic_generate(128); + vchMnemonicTmp = std::vector(strMnemonic.begin(), strMnemonic.end()); + } + // NOTE: default mnemonic passphrase is an empty string + + if (!mnemonic_check(strMnemonic.c_str())) { + throw std::runtime_error(std::string(__func__) + ": invalid mnemonic: `" + strMnemonic + "`"); + } + + uint8_t seed[64]; + mnemonic_to_seed(strMnemonic.c_str(), strMnemonicPassphrase.c_str(), seed, 0); + vchSeed = std::vector(seed, seed + 64); + id = GetSeedHash(); + } + + vchMnemonic = vchMnemonicTmp; + vchMnemonicPassphrase = vchMnemonicPassphraseIn; + + Debug(); + + return !IsNull(); +} + +bool CHDChain::GetMnemonic(std::vector& vchMnemonicRet, std::vector& vchMnemonicPassphraseRet) const +{ + // mnemonic was not set, fail + if (vchMnemonic.empty()) + return false; + + vchMnemonicRet = vchMnemonic; + vchMnemonicPassphraseRet = vchMnemonicPassphrase; + return true; +} + +bool CHDChain::GetMnemonic(std::string& strMnemonicRet, std::string& strMnemonicPassphraseRet) const +{ + // mnemonic was not set, fail + if (vchMnemonic.empty()) + return false; + + strMnemonicRet = std::string(vchMnemonic.begin(), vchMnemonic.end()); + strMnemonicPassphraseRet = std::string(vchMnemonicPassphrase.begin(), vchMnemonicPassphrase.end()); + + return true; +} + bool CHDChain::SetSeed(const std::vector& vchSeedIn, bool fUpdateID) { vchSeed = vchSeedIn; - if (fUpdateID) + + if (fUpdateID) { id = GetSeedHash(); + } + + Debug(); + return !IsNull(); } diff --git a/src/hdchain.h b/src/hdchain.h index 288172640c13..aaae13c22bd0 100644 --- a/src/hdchain.h +++ b/src/hdchain.h @@ -10,6 +10,10 @@ class CHDChain { private: std::vector vchSeed; + std::vector vchMnemonic; + std::vector vchMnemonicPassphrase; + + void Debug(); public: static const int CURRENT_VERSION = 1; @@ -27,6 +31,8 @@ class CHDChain READWRITE(this->nVersion); nVersion = this->nVersion; READWRITE(vchSeed); + READWRITE(vchMnemonic); + READWRITE(vchMnemonicPassphrase); READWRITE(id); READWRITE(nExternalChainCounter); READWRITE(nInternalChainCounter); @@ -35,6 +41,10 @@ class CHDChain bool SetNull(); bool IsNull() const; + bool SetMnemonic(const std::vector& vchMnemonicIn, const std::vector& vchMnemonicPassphraseIn, bool fUpdateID); + bool GetMnemonic(std::vector& vchMnemonicRet, std::vector& vchMnemonicPassphraseRet) const; + bool GetMnemonic(std::string& strMnemonicRet, std::string& strMnemonicPassphraseRet) const; + bool SetSeed(const std::vector& vchSeedIn, bool fUpdateID); std::vector GetSeed() const; diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index d2c5b9a07cf4..f687b9a5ae5d 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -362,7 +362,7 @@ static const CRPCCommand vRPCCommands[] = { "wallet", "addmultisigaddress", &addmultisigaddress, true }, { "wallet", "backupwallet", &backupwallet, true }, { "wallet", "dumpprivkey", &dumpprivkey, true }, - { "wallet", "dumphdseed", &dumphdseed, true }, + { "wallet", "dumphdinfo", &dumphdinfo, true }, { "wallet", "dumpwallet", &dumpwallet, true }, { "wallet", "encryptwallet", &encryptwallet, true }, { "wallet", "getaccountaddress", &getaccountaddress, true }, diff --git a/src/rpcserver.h b/src/rpcserver.h index ad975958cfad..8f81ba1ed2ac 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -192,7 +192,7 @@ extern UniValue dumpprivkey(const UniValue& params, bool fHelp); // in rpcdump.c extern UniValue importprivkey(const UniValue& params, bool fHelp); extern UniValue importaddress(const UniValue& params, bool fHelp); extern UniValue importpubkey(const UniValue& params, bool fHelp); -extern UniValue dumphdseed(const UniValue& params, bool fHelp); +extern UniValue dumphdinfo(const UniValue& params, bool fHelp); extern UniValue dumpwallet(const UniValue& params, bool fHelp); extern UniValue importwallet(const UniValue& params, bool fHelp); extern UniValue importelectrumwallet(const UniValue& params, bool fHelp); diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index 38abba2953df..5cd616575125 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -282,10 +282,8 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn, bool fForMixin if(!cryptedHDChain.IsNull()) { bool chainPass = false; // try to decrypt seed and make sure it matches - std::vector vchSeed; - if (DecryptHDChainSeed(vchSeed)) { - CHDChain hdChainTmp; - hdChainTmp.SetSeed(vchSeed, false); + CHDChain hdChainTmp; + if (DecryptHDChain(hdChainTmp)) { // make sure seed matches this chain chainPass = cryptedHDChain.id == hdChainTmp.GetSeedHash(); } @@ -396,7 +394,29 @@ bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn) return true; } -bool CCryptoKeyStore::EncryptHDChainSeed(CKeyingMaterial& vMasterKeyIn) +bool EncryptVector(const CKeyingMaterial& vMasterKeyIn, const std::vector &vchSecretIn, const uint256& nIV, std::vector &vchCryptedSecretRet) +{ + uint8_t secret[vchSecretIn.size()]; + memcpy(&secret[0], (unsigned char*)&(vchSecretIn.begin())[0], vchSecretIn.size()); + CKeyingMaterial vchSecret(secret, secret + vchSecretIn.size()); + + return EncryptSecret(vMasterKeyIn, vchSecret, nIV, vchCryptedSecretRet); +} + +bool DecryptVector(const CKeyingMaterial& vMasterKeyIn, const std::vector &vchCryptedSecretIn, const uint256& nIV, std::vector &vchSecretRet) +{ + CKeyingMaterial vchSecret; + if(!DecryptSecret(vMasterKeyIn, vchCryptedSecretIn, nIV, vchSecret)) + return false; + + uint8_t secret[vchSecret.size()]; + memcpy(&secret[0], (unsigned char*)&(vchSecret.begin())[0], vchSecret.size()); + vchSecretRet = std::vector(secret, secret + vchSecret.size()); + + return true; +} + +bool CCryptoKeyStore::EncryptHDChain(const CKeyingMaterial& vMasterKeyIn) { // should call EncryptKeys first if (!IsCrypted()) @@ -405,32 +425,42 @@ bool CCryptoKeyStore::EncryptHDChainSeed(CKeyingMaterial& vMasterKeyIn) if (!cryptedHDChain.IsNull()) return true; - std::vector vchCryptedSeed; - - const std::vector vchSeed = hdChain.GetSeed(); - - uint8_t secret[vchSeed.size()]; - memcpy(&secret[0], (unsigned char*)&(vchSeed.begin())[0], vchSeed.size()); - CKeyingMaterial vchSecret(secret, secret + vchSeed.size()); - // make sure seed matches this chain if (hdChain.id != hdChain.GetSeedHash()) return false; - if (!EncryptSecret(vMasterKeyIn, vchSecret, hdChain.id, vchCryptedSeed)) + std::vector vchCryptedSeed; + if (!EncryptVector(vMasterKeyIn, hdChain.GetSeed(), hdChain.id, vchCryptedSeed)) return false; cryptedHDChain = hdChain; if (!cryptedHDChain.SetSeed(vchCryptedSeed, false)) return false; + std::vector vchMnemonic; + std::vector vchMnemonicPassphrase; + + // it's ok to have no mnemonic if wallet was initialized via hdseed + if (hdChain.GetMnemonic(vchMnemonic, vchMnemonicPassphrase)) { + std::vector vchCryptedMnemonic; + std::vector vchCryptedMnemonicPassphrase; + + if (!EncryptVector(vMasterKeyIn, vchMnemonic, hdChain.id, vchCryptedMnemonic)) + return false; + if (!EncryptVector(vMasterKeyIn, vchMnemonicPassphrase, hdChain.id, vchCryptedMnemonicPassphrase)) + return false; + + if (!cryptedHDChain.SetMnemonic(vchCryptedMnemonic, vchCryptedMnemonicPassphrase, false)) + return false; + } + if (!hdChain.SetNull()) return false; return true; } -bool CCryptoKeyStore::DecryptHDChainSeed(std::vector& vchSeedRet) const +bool CCryptoKeyStore::DecryptHDChain(CHDChain& hdChainRet) const { if (!IsCrypted()) return true; @@ -438,17 +468,33 @@ bool CCryptoKeyStore::DecryptHDChainSeed(std::vector& vchSeedRet) if (cryptedHDChain.IsNull()) return false; - CKeyingMaterial vchSecret; - std::vector vchCryptedSecret = cryptedHDChain.GetSeed(); + std::vector vchSeed; + if (!DecryptVector(vMasterKey, cryptedHDChain.GetSeed(), cryptedHDChain.id, vchSeed)) + return false; + + hdChainRet = cryptedHDChain; + hdChainRet.SetSeed(vchSeed, false); - if(!DecryptSecret(vMasterKey, vchCryptedSecret, cryptedHDChain.id, vchSecret)) + // hash of decrypted seed must match chain id + if (hdChainRet.GetSeedHash() != cryptedHDChain.id) return false; - uint8_t seed[vchSecret.size()]; - memcpy(&seed[0], (unsigned char*)&(vchSecret.begin())[0], vchSecret.size()); - std::vector vchSeed(seed, seed + vchSecret.size()); + std::vector vchCryptedMnemonic; + std::vector vchCryptedMnemonicPassphrase; + + // it's ok to have no mnemonic if wallet was initialized via hdseed + if (cryptedHDChain.GetMnemonic(vchCryptedMnemonic, vchCryptedMnemonicPassphrase)) { + std::vector vchMnemonic; + std::vector vchMnemonicPassphrase; + + if (!DecryptVector(vMasterKey, vchCryptedMnemonic, cryptedHDChain.id, vchMnemonic)) + return false; + if (!DecryptVector(vMasterKey, vchCryptedMnemonicPassphrase, cryptedHDChain.id, vchMnemonicPassphrase)) + return false; - vchSeedRet = vchSeed; + if (!hdChainRet.SetMnemonic(vchMnemonic, vchMnemonicPassphrase, false)) + return false; + } return true; } diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h index 44b49c5f19f3..2c0a4c8a21ce 100644 --- a/src/wallet/crypter.h +++ b/src/wallet/crypter.h @@ -138,8 +138,8 @@ class CCryptoKeyStore : public CBasicKeyStore //! will encrypt previously unencrypted keys bool EncryptKeys(CKeyingMaterial& vMasterKeyIn); - bool EncryptHDChainSeed(CKeyingMaterial& vMasterKeyIn); - bool DecryptHDChainSeed(std::vector& vchSeedRet) const; + bool EncryptHDChain(const CKeyingMaterial& vMasterKeyIn); + bool DecryptHDChain(CHDChain& hdChainRet) const; bool SetHDChain(const CHDChain& chain); bool SetCryptedHDChain(const CHDChain& chain); diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 3889e1888a17..60daebbe938f 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -580,20 +580,24 @@ UniValue dumpprivkey(const UniValue& params, bool fHelp) return CBitcoinSecret(vchSecret).ToString(); } -UniValue dumphdseed(const UniValue& params, bool fHelp) +UniValue dumphdinfo(const UniValue& params, bool fHelp) { if (!EnsureWalletIsAvailable(fHelp)) return NullUniValue; if (fHelp || params.size() != 0) throw runtime_error( - "dumphdseed\n" - "\nReveals the HD seed for this wallet\n" + "dumphdinfo\n" + "Returns an object containing sensitive private info about this HD wallet.\n" "\nResult:\n" - "\"HD seed\" (string) The HD seed in hex\n" + "{\n" + " \"hdseed\": \"seed\", (string) The HD seed (bip32, in hex)\n" + " \"mnemonic\": \"words\", (string) The mnemonic for this HD wallet (bip39, english words) \n" + " \"mnemonicpassphrase\": \"passphrase\", (string) The mnemonic passphrase for this HD wallet (bip39)\n" + "}\n" "\nExamples:\n" - + HelpExampleCli("dumphdseed", "") - + HelpExampleRpc("dumphdseed", "") + + HelpExampleCli("dumphdinfo", "") + + HelpExampleRpc("dumphdinfo", "") ); LOCK(pwalletMain->cs_wallet); @@ -604,10 +608,19 @@ UniValue dumphdseed(const UniValue& params, bool fHelp) CHDChain hdChainCurrent; if (pwalletMain->GetHDChain(hdChainCurrent)) { - std::vector vchSeed = hdChainCurrent.GetSeed(); - if (!pwalletMain->GetDecryptedHDChainSeed(vchSeed)) + if (!pwalletMain->GetDecryptedHDChain(hdChainCurrent)) throw JSONRPCError(RPC_INTERNAL_ERROR, "Cannot decrypt HD seed"); - return HexStr(vchSeed); + + std::string strMnemonic; + std::string strMnemonicPassphrase; + hdChainCurrent.GetMnemonic(strMnemonic, strMnemonicPassphrase); + + UniValue obj(UniValue::VOBJ); + obj.push_back(Pair("hdseed", HexStr(hdChainCurrent.GetSeed()))); + obj.push_back(Pair("mnemonic", strMnemonic)); + obj.push_back(Pair("mnemonicpassphrase", strMnemonicPassphrase)); + + return obj; } return NullUniValue; @@ -662,19 +675,26 @@ UniValue dumpwallet(const UniValue& params, bool fHelp) CHDChain hdChainCurrent; if (pwalletMain->GetHDChain(hdChainCurrent)) { - std::vector vchSeed = hdChainCurrent.GetSeed(); - if (!pwalletMain->GetDecryptedHDChainSeed(vchSeed)) - throw JSONRPCError(RPC_INTERNAL_ERROR, "Cannot decrypt HD seed"); + if (!pwalletMain->GetDecryptedHDChain(hdChainCurrent)) + throw JSONRPCError(RPC_INTERNAL_ERROR, "Cannot decrypt HD chain"); + + std::string strMnemonic; + std::string strMnemonicPassphrase; + hdChainCurrent.GetMnemonic(strMnemonic, strMnemonicPassphrase); + file << "# mnemonic: " << strMnemonic << "\n"; + file << "# mnemonic passphrase: " << strMnemonicPassphrase << "\n\n"; + + std::vector vchSeed = hdChainCurrent.GetSeed(); + file << "# HD seed: " << HexStr(vchSeed) << "\n\n"; CExtKey masterKey; masterKey.SetMaster(&vchSeed[0], vchSeed.size()); - file << "# HD seed: " << HexStr(vchSeed) << "\n\n"; CBitcoinExtKey b58extkey; b58extkey.SetKey(masterKey); - file << "# extended private masterkey: " << b58extkey.ToString() << "\n\n"; + file << "# extended private masterkey: " << b58extkey.ToString() << "\n"; CExtPubKey masterPubkey; masterPubkey = masterKey.Neuter(); @@ -682,6 +702,9 @@ UniValue dumpwallet(const UniValue& params, bool fHelp) CBitcoinExtPubKey b58extpubkey; b58extpubkey.SetKey(masterPubkey); file << "# extended public masterkey: " << b58extpubkey.ToString() << "\n\n"; + + file << "# external chain counter: " << hdChainCurrent.nExternalChainCounter << "\n"; + file << "# internal chain counter: " << hdChainCurrent.nInternalChainCounter << "\n\n"; } for (std::vector >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) { diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index d6b1154e4380..4036aa7d038e 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -26,7 +26,6 @@ #include "util.h" #include "utilmoneystr.h" -#include "bip39.h" #include "governance.h" #include "instantx.h" #include "keepass.h" @@ -145,10 +144,8 @@ void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret, bool fInte throw std::runtime_error(std::string(__func__) + ": GetHDChain failed"); } - std::vector vchSeed = hdChainTmp.GetSeed(); - if (!DecryptHDChainSeed(vchSeed)) + if (!DecryptHDChain(hdChainTmp)) throw std::runtime_error(std::string(__func__) + ": DecryptHDChainSeed failed"); - hdChainTmp.SetSeed(vchSeed, false); // make sure seed matches this chain if (hdChainTmp.id != hdChainTmp.GetSeedHash()) throw std::runtime_error(std::string(__func__) + ": Wrong HD chain!"); @@ -220,10 +217,8 @@ bool CWallet::GetKey(const CKeyID &address, CKey& keyOut) const CHDChain hdChainCurrent; if (!GetHDChain(hdChainCurrent)) throw std::runtime_error(std::string(__func__) + ": GetHDChain failed"); - std::vector vchSeed = hdChainCurrent.GetSeed(); - if (!DecryptHDChainSeed(vchSeed)) + if (!DecryptHDChain(hdChainCurrent)) throw std::runtime_error(std::string(__func__) + ": DecryptHDChainSeed failed"); - hdChainCurrent.SetSeed(vchSeed, false); // make sure seed matches this chain if (hdChainCurrent.id != hdChainCurrent.GetSeedHash()) throw std::runtime_error(std::string(__func__) + ": Wrong HD chain!"); @@ -784,7 +779,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) } if (!hdChainCurrent.IsNull()) { - assert(EncryptHDChainSeed(vMasterKey)); + assert(EncryptHDChain(vMasterKey)); CHDChain hdChainCrypted; assert(GetHDChain(hdChainCrypted)); @@ -1383,58 +1378,31 @@ CAmount CWallet::GetChange(const CTxOut& txout) const void CWallet::GenerateNewHDChain() { - std::string mnemonic; - std::vector vchSeed; + CHDChain newHdChain; + std::string strSeed = GetArg("-hdseed", "not hex"); if(mapArgs.count("-hdseed") && IsHex(strSeed)) { - vchSeed = ParseHex(strSeed); - } else { - + std::vector vchSeed = ParseHex(strSeed); + if (!newHdChain.SetSeed(vchSeed, true)) + throw std::runtime_error(std::string(__func__) + ": SetSeed failed"); + } + else { if (mapArgs.count("-hdseed") && !IsHex(GetArg("-hdseed", ""))) LogPrintf("CWallet::GenerateNewHDChain -- Incorrect seed, generating random one instead\n"); - if(mapArgs.count("-mnemonic")) { - mnemonic = GetArg("-mnemonic", ""); - } else { - mnemonic = mnemonic_generate(128); - } - - if(!mnemonic_check(mnemonic.c_str())) { - throw std::runtime_error(std::string(__func__) + ": invalid mnemonic"); - } - DBG( printf("mnemonic: '%s'\n", mnemonic.c_str()); ); + // NOTE: empty mnemonic means "generate a new one for me" + std::string strMnemonic = GetArg("-mnemonic", ""); + // NOTE: default mnemonic passphrase is an empty string + std::string strMnemonicPassphrase = GetArg("-mnemonicpassphrase", ""); - // default mnemonic passphrase is an empty string - std::string passphrase = GetArg("-mnemonicpassphrase", ""); - DBG( printf("mnemonicpassphrase: '%s'\n", passphrase.c_str()); ); + std::vector vchMnemonic(strMnemonic.begin(), strMnemonic.end()); + std::vector vchMnemonicPassphrase(strMnemonicPassphrase.begin(), strMnemonicPassphrase.end()); - uint8_t seed[64]; - mnemonic_to_seed(mnemonic.c_str(), passphrase.c_str(), seed, 0); - vchSeed = std::vector(seed, seed + 64); + if (!newHdChain.SetMnemonic(vchMnemonic, vchMnemonicPassphrase, true)) + throw std::runtime_error(std::string(__func__) + ": SetMnemonic failed"); } - DBG( - printf("seed: '%s'\n", HexStr(vchSeed).c_str()); - - CExtKey extkey; - extkey.SetMaster(&vchSeed[0], vchSeed.size()); - - CBitcoinExtKey b58extkey; - b58extkey.SetKey(extkey); - printf("extended private masterkey: '%s'\n", b58extkey.ToString().c_str()); - - CExtPubKey extpubkey; - extpubkey = extkey.Neuter(); - - CBitcoinExtPubKey b58extpubkey; - b58extpubkey.SetKey(extpubkey); - printf("extended public masterkey: '%s'\n", b58extpubkey.ToString().c_str()); - ); - - CHDChain newHdChain; - if (!newHdChain.SetSeed(vchSeed, true)) - throw std::runtime_error(std::string(__func__) + ": SetSeed failed"); if (!SetHDChain(newHdChain, false)) throw std::runtime_error(std::string(__func__) + ": SetHDChain failed"); } @@ -1474,7 +1442,7 @@ bool CWallet::SetCryptedHDChain(const CHDChain& chain, bool memonly) return true; } -bool CWallet::GetDecryptedHDChainSeed(std::vector& vchSeedRet) +bool CWallet::GetDecryptedHDChain(CHDChain& hdChainRet) { LOCK(cs_wallet); @@ -1483,15 +1451,14 @@ bool CWallet::GetDecryptedHDChainSeed(std::vector& vchSeedRet) return false; } - std::vector vchSeed = hdChainTmp.GetSeed(); - if (!DecryptHDChainSeed(vchSeed)) + if (!DecryptHDChain(hdChainTmp)) return false; - hdChainTmp.SetSeed(vchSeed, false); + // make sure seed matches this chain if (hdChainTmp.id != hdChainTmp.GetSeedHash()) return false; - vchSeedRet = vchSeed; + hdChainRet = hdChainTmp; return true; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 8ffd64a9beab..db663f316dbc 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1036,7 +1036,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface /* Set the HD chain model (chain child index counters) */ bool SetHDChain(const CHDChain& chain, bool memonly); bool SetCryptedHDChain(const CHDChain& chain, bool memonly); - bool GetDecryptedHDChainSeed(std::vector& vchSeedRet); + bool GetDecryptedHDChain(CHDChain& hdChainRet); }; /** A key allocated from the key pool. */ From 9db0bd34bcef8e95675955fefae2a8800549ffac Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Sun, 30 Apr 2017 03:17:44 +0300 Subject: [PATCH 16/31] Add fCrypted flag to CHDChain --- src/hdchain.cpp | 62 +++++++++++++++++++++++++----------------- src/hdchain.h | 10 +++++-- src/wallet/crypter.cpp | 29 ++++++++++++++++---- src/wallet/wallet.cpp | 1 + 4 files changed, 70 insertions(+), 32 deletions(-) diff --git a/src/hdchain.cpp b/src/hdchain.cpp index a80e5537d83e..fa7b7cec37a2 100644 --- a/src/hdchain.cpp +++ b/src/hdchain.cpp @@ -11,13 +11,13 @@ bool CHDChain::SetNull() { - nVersion = CHDChain::CURRENT_VERSION; - nExternalChainCounter = 0; - nInternalChainCounter = 0; vchSeed.clear(); vchMnemonic.clear(); vchMnemonicPassphrase.clear(); + fCrypted = false; id = uint256(); + nExternalChainCounter = 0; + nInternalChainCounter = 0; return IsNull(); } @@ -26,27 +26,43 @@ bool CHDChain::IsNull() const return vchSeed.empty() || id == uint256(); } -void CHDChain::Debug() +void CHDChain::SetCrypted(bool fCryptedIn) { - DBG( - std::cout << "mnemonic: " << std::string(vchMnemonic.begin(), vchMnemonic.end()).c_str() << std::endl; - std::cout << "mnemonicpassphrase: " << std::string(vchMnemonicPassphrase.begin(), vchMnemonicPassphrase.end()).c_str() << std::endl; - - std::cout << "seed: " << HexStr(vchSeed).c_str() << std::endl; - - CExtKey extkey; - extkey.SetMaster(&vchSeed[0], vchSeed.size()); - - CBitcoinExtKey b58extkey; - b58extkey.SetKey(extkey); - std::cout << "extended private masterkey: " << b58extkey.ToString().c_str() << std::endl; + fCrypted = fCryptedIn; +} - CExtPubKey extpubkey; - extpubkey = extkey.Neuter(); +bool CHDChain::IsCrypted() const +{ + return fCrypted; +} - CBitcoinExtPubKey b58extpubkey; - b58extpubkey.SetKey(extpubkey); - std::cout << "extended public masterkey: " << b58extpubkey.ToString().c_str() << std::endl; +void CHDChain::Debug(std::string strName) const +{ + DBG( + std::cout << __func__ << ": ---" << strName << "---" << std::endl; + if (fCrypted) { + std::cout << "mnemonic: ***CRYPTED***" << std::endl; + std::cout << "mnemonicpassphrase: ***CRYPTED***" << std::endl; + std::cout << "seed: ***CRYPTED***" << std::endl; + } else { + std::cout << "mnemonic: " << std::string(vchMnemonic.begin(), vchMnemonic.end()).c_str() << std::endl; + std::cout << "mnemonicpassphrase: " << std::string(vchMnemonicPassphrase.begin(), vchMnemonicPassphrase.end()).c_str() << std::endl; + std::cout << "seed: " << HexStr(vchSeed).c_str() << std::endl; + + CExtKey extkey; + extkey.SetMaster(&vchSeed[0], vchSeed.size()); + + CBitcoinExtKey b58extkey; + b58extkey.SetKey(extkey); + std::cout << "extended private masterkey: " << b58extkey.ToString().c_str() << std::endl; + + CExtPubKey extpubkey; + extpubkey = extkey.Neuter(); + + CBitcoinExtPubKey b58extpubkey; + b58extpubkey.SetKey(extpubkey); + std::cout << "extended public masterkey: " << b58extpubkey.ToString().c_str() << std::endl; + } ); } @@ -82,8 +98,6 @@ bool CHDChain::SetMnemonic(const std::vector& vchMnemonicIn, cons vchMnemonic = vchMnemonicTmp; vchMnemonicPassphrase = vchMnemonicPassphraseIn; - Debug(); - return !IsNull(); } @@ -118,8 +132,6 @@ bool CHDChain::SetSeed(const std::vector& vchSeedIn, bool fUpdate id = GetSeedHash(); } - Debug(); - return !IsNull(); } diff --git a/src/hdchain.h b/src/hdchain.h index aaae13c22bd0..e14b23752995 100644 --- a/src/hdchain.h +++ b/src/hdchain.h @@ -13,7 +13,7 @@ class CHDChain std::vector vchMnemonic; std::vector vchMnemonicPassphrase; - void Debug(); + bool fCrypted; public: static const int CURRENT_VERSION = 1; @@ -22,7 +22,7 @@ class CHDChain uint32_t nExternalChainCounter; uint32_t nInternalChainCounter; - CHDChain() : nVersion(CHDChain::CURRENT_VERSION), id(uint256()), nExternalChainCounter(0) { SetNull(); } + CHDChain() : nVersion(CHDChain::CURRENT_VERSION) { SetNull(); } ADD_SERIALIZE_METHODS; template @@ -36,11 +36,17 @@ class CHDChain READWRITE(id); READWRITE(nExternalChainCounter); READWRITE(nInternalChainCounter); + READWRITE(fCrypted); } bool SetNull(); bool IsNull() const; + void SetCrypted(bool fCryptedIn); + bool IsCrypted() const; + + void Debug(std::string strName) const; + bool SetMnemonic(const std::vector& vchMnemonicIn, const std::vector& vchMnemonicPassphraseIn, bool fUpdateID); bool GetMnemonic(std::vector& vchMnemonicRet, std::vector& vchMnemonicPassphraseRet) const; bool GetMnemonic(std::string& strMnemonicRet, std::string& strMnemonicPassphraseRet) const; diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index 5cd616575125..b33d4ef2c550 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -425,6 +425,9 @@ bool CCryptoKeyStore::EncryptHDChain(const CKeyingMaterial& vMasterKeyIn) if (!cryptedHDChain.IsNull()) return true; + if (cryptedHDChain.IsCrypted()) + return true; + // make sure seed matches this chain if (hdChain.id != hdChain.GetSeedHash()) return false; @@ -433,7 +436,10 @@ bool CCryptoKeyStore::EncryptHDChain(const CKeyingMaterial& vMasterKeyIn) if (!EncryptVector(vMasterKeyIn, hdChain.GetSeed(), hdChain.id, vchCryptedSeed)) return false; + hdChain.Debug(__func__); cryptedHDChain = hdChain; + cryptedHDChain.SetCrypted(true); + if (!cryptedHDChain.SetSeed(vchCryptedSeed, false)) return false; @@ -445,9 +451,9 @@ bool CCryptoKeyStore::EncryptHDChain(const CKeyingMaterial& vMasterKeyIn) std::vector vchCryptedMnemonic; std::vector vchCryptedMnemonicPassphrase; - if (!EncryptVector(vMasterKeyIn, vchMnemonic, hdChain.id, vchCryptedMnemonic)) + if (!vchMnemonic.empty() && !EncryptVector(vMasterKeyIn, vchMnemonic, hdChain.id, vchCryptedMnemonic)) return false; - if (!EncryptVector(vMasterKeyIn, vchMnemonicPassphrase, hdChain.id, vchCryptedMnemonicPassphrase)) + if (!vchMnemonicPassphrase.empty() && !EncryptVector(vMasterKeyIn, vchMnemonicPassphrase, hdChain.id, vchCryptedMnemonicPassphrase)) return false; if (!cryptedHDChain.SetMnemonic(vchCryptedMnemonic, vchCryptedMnemonicPassphrase, false)) @@ -468,12 +474,16 @@ bool CCryptoKeyStore::DecryptHDChain(CHDChain& hdChainRet) const if (cryptedHDChain.IsNull()) return false; + if (!cryptedHDChain.IsCrypted()) + return false; + std::vector vchSeed; if (!DecryptVector(vMasterKey, cryptedHDChain.GetSeed(), cryptedHDChain.id, vchSeed)) return false; hdChainRet = cryptedHDChain; - hdChainRet.SetSeed(vchSeed, false); + if (!hdChainRet.SetSeed(vchSeed, false)) + return false; // hash of decrypted seed must match chain id if (hdChainRet.GetSeedHash() != cryptedHDChain.id) @@ -487,15 +497,18 @@ bool CCryptoKeyStore::DecryptHDChain(CHDChain& hdChainRet) const std::vector vchMnemonic; std::vector vchMnemonicPassphrase; - if (!DecryptVector(vMasterKey, vchCryptedMnemonic, cryptedHDChain.id, vchMnemonic)) + if (!vchCryptedMnemonic.empty() && !DecryptVector(vMasterKey, vchCryptedMnemonic, cryptedHDChain.id, vchMnemonic)) return false; - if (!DecryptVector(vMasterKey, vchCryptedMnemonicPassphrase, cryptedHDChain.id, vchMnemonicPassphrase)) + if (!vchCryptedMnemonicPassphrase.empty() && !DecryptVector(vMasterKey, vchCryptedMnemonicPassphrase, cryptedHDChain.id, vchMnemonicPassphrase)) return false; if (!hdChainRet.SetMnemonic(vchMnemonic, vchMnemonicPassphrase, false)) return false; } + hdChainRet.SetCrypted(false); + hdChainRet.Debug(__func__); + return true; } @@ -504,6 +517,9 @@ bool CCryptoKeyStore::SetHDChain(const CHDChain& chain) if (IsCrypted()) return false; + if (chain.IsCrypted()) + return false; + hdChain = chain; return true; } @@ -513,6 +529,9 @@ bool CCryptoKeyStore::SetCryptedHDChain(const CHDChain& chain) if (!SetCrypted()) return false; + if (!chain.IsCrypted()) + return false; + cryptedHDChain = chain; return true; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 4036aa7d038e..0add39a15714 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1402,6 +1402,7 @@ void CWallet::GenerateNewHDChain() if (!newHdChain.SetMnemonic(vchMnemonic, vchMnemonicPassphrase, true)) throw std::runtime_error(std::string(__func__) + ": SetMnemonic failed"); } + newHdChain.Debug(__func__); if (!SetHDChain(newHdChain, false)) throw std::runtime_error(std::string(__func__) + ": SetHDChain failed"); From 24c750ea5b3f41b1d0a88b1c5f9985a594b3fd06 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Tue, 9 May 2017 21:11:29 +0300 Subject: [PATCH 17/31] prepare internal structures for multiple HD accounts (plus some code cleanup) --- src/hdchain.cpp | 51 +++++++++++++++---- src/hdchain.h | 96 ++++++++++++++++++++++++++++------- src/rpcmisc.cpp | 2 +- src/test/rpc_wallet_tests.cpp | 4 +- src/wallet/crypter.cpp | 18 +++---- src/wallet/rpcdump.cpp | 12 ++++- src/wallet/rpcwallet.cpp | 31 +++++++++-- src/wallet/wallet.cpp | 49 ++++++++++-------- src/wallet/wallet.h | 4 +- 9 files changed, 200 insertions(+), 67 deletions(-) diff --git a/src/hdchain.cpp b/src/hdchain.cpp index fa7b7cec37a2..740c06ef6393 100644 --- a/src/hdchain.cpp +++ b/src/hdchain.cpp @@ -11,13 +11,16 @@ bool CHDChain::SetNull() { + LOCK(cs_accounts); + nVersion = CURRENT_VERSION; + id = uint256(); + fCrypted = false; vchSeed.clear(); vchMnemonic.clear(); vchMnemonicPassphrase.clear(); - fCrypted = false; - id = uint256(); - nExternalChainCounter = 0; - nInternalChainCounter = 0; + mapAccounts.clear(); + // default blank account + mapAccounts.insert(std::pair(0, CHDAccount())); return IsNull(); } @@ -145,7 +148,7 @@ uint256 CHDChain::GetSeedHash() return Hash(vchSeed.begin(), vchSeed.end()); } -void CHDChain::DeriveChildExtKey(uint32_t childIndex, CExtKey& extKeyRet, bool fInternal) +void CHDChain::DeriveChildExtKey(uint32_t nAccountIndex, bool fInternal, uint32_t nChildIndex, CExtKey& extKeyRet) { // Use BIP44 keypath scheme i.e. m / purpose' / coin_type' / account' / change / address_index CExtKey masterKey; //hd master key @@ -159,21 +162,51 @@ void CHDChain::DeriveChildExtKey(uint32_t childIndex, CExtKey& extKeyRet, bool f // Use hardened derivation for purpose, coin_type and account // (keys >= 0x80000000 are hardened after bip32) - // TODO: support multiple accounts, external/internal addresses, and multiple index per each // derive m/purpose' masterKey.Derive(purposeKey, 44 | 0x80000000); // derive m/purpose'/coin_type' purposeKey.Derive(cointypeKey, Params().ExtCoinType() | 0x80000000); // derive m/purpose'/coin_type'/account' - cointypeKey.Derive(accountKey, 0x80000000); + cointypeKey.Derive(accountKey, nAccountIndex | 0x80000000); // derive m/purpose'/coin_type'/account/change accountKey.Derive(changeKey, fInternal ? 1 : 0); // derive m/purpose'/coin_type'/account/change/address_index - changeKey.Derive(extKeyRet, childIndex); + changeKey.Derive(extKeyRet, nChildIndex); +} + +void CHDChain::AddAccount() +{ + LOCK(cs_accounts); + mapAccounts.insert(std::pair(mapAccounts.size(), CHDAccount())); +} + +bool CHDChain::GetAccount(uint32_t nAccountIndex, CHDAccount& hdAccountRet) +{ + LOCK(cs_accounts); + if (nAccountIndex > mapAccounts.size() - 1) + return false; + hdAccountRet = mapAccounts[nAccountIndex]; + return true; +} + +bool CHDChain::SetAccount(uint32_t nAccountIndex, const CHDAccount& hdAccount) +{ + LOCK(cs_accounts); + // can only replace existing accounts + if (nAccountIndex > mapAccounts.size() - 1) + return false; + mapAccounts[nAccountIndex] = hdAccount; + return true; +} + +size_t CHDChain::CountAccounts() +{ + LOCK(cs_accounts); + return mapAccounts.size(); } std::string CHDPubKey::GetKeyPath() const { - return strprintf("m/44'/%d'/%d'/%d/%d", Params().ExtCoinType(), nAccount, nChange, extPubKey.nChild); + return strprintf("m/44'/%d'/%d'/%d/%d", Params().ExtCoinType(), nAccountIndex, nChangeIndex, extPubKey.nChild); } diff --git a/src/hdchain.h b/src/hdchain.h index e14b23752995..0eb50224e4bf 100644 --- a/src/hdchain.h +++ b/src/hdchain.h @@ -4,39 +4,92 @@ #define DASH_HDCHAIN_H #include "key.h" +#include "sync.h" + +/* hd account data model */ +class CHDAccount +{ +public: + uint32_t nExternalChainCounter; + uint32_t nInternalChainCounter; + + CHDAccount() : nExternalChainCounter(0), nInternalChainCounter(0) {} + + ADD_SERIALIZE_METHODS; + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) + { + READWRITE(nExternalChainCounter); + READWRITE(nInternalChainCounter); + } +}; /* simple HD chain data model */ class CHDChain { private: + static const int CURRENT_VERSION = 1; + int nVersion; + + uint256 id; + + bool fCrypted; + std::vector vchSeed; std::vector vchMnemonic; std::vector vchMnemonicPassphrase; - bool fCrypted; + std::map mapAccounts; + // critical section to protect mapAccounts + mutable CCriticalSection cs_accounts; public: - static const int CURRENT_VERSION = 1; - int nVersion; - uint256 id; - uint32_t nExternalChainCounter; - uint32_t nInternalChainCounter; CHDChain() : nVersion(CHDChain::CURRENT_VERSION) { SetNull(); } + CHDChain(const CHDChain& other) : + nVersion(other.nVersion), + id(other.id), + fCrypted(other.fCrypted), + vchSeed(other.vchSeed), + vchMnemonic(other.vchMnemonic), + vchMnemonicPassphrase(other.vchMnemonicPassphrase), + mapAccounts(other.mapAccounts) + {} ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { + LOCK(cs_accounts); READWRITE(this->nVersion); nVersion = this->nVersion; + READWRITE(id); + READWRITE(fCrypted); READWRITE(vchSeed); READWRITE(vchMnemonic); READWRITE(vchMnemonicPassphrase); - READWRITE(id); - READWRITE(nExternalChainCounter); - READWRITE(nInternalChainCounter); - READWRITE(fCrypted); + READWRITE(mapAccounts); + } + + void swap(CHDChain& first, CHDChain& second) // nothrow + { + // enable ADL (not necessary in our case, but good practice) + using std::swap; + + // by swapping the members of two classes, + // the two classes are effectively swapped + swap(first.nVersion, second.nVersion); + swap(first.id, second.id); + swap(first.fCrypted, second.fCrypted); + swap(first.vchSeed, second.vchSeed); + swap(first.vchMnemonic, second.vchMnemonic); + swap(first.vchMnemonicPassphrase, second.vchMnemonicPassphrase); + swap(first.mapAccounts, second.mapAccounts); + } + CHDChain& operator=(CHDChain from) + { + swap(*this, from); + return *this; } bool SetNull(); @@ -54,22 +107,31 @@ class CHDChain bool SetSeed(const std::vector& vchSeedIn, bool fUpdateID); std::vector GetSeed() const; + uint256 GetID() const { return id; } + uint256 GetSeedHash(); - void DeriveChildExtKey(uint32_t childIndex, CExtKey& extKeyRet, bool fInternal); + void DeriveChildExtKey(uint32_t nAccountIndex, bool fInternal, uint32_t nChildIndex, CExtKey& extKeyRet); + + void AddAccount(); + bool GetAccount(uint32_t nAccountIndex, CHDAccount& hdAccountRet); + bool SetAccount(uint32_t nAccountIndex, const CHDAccount& hdAccount); + size_t CountAccounts(); }; /* hd pubkey data model */ class CHDPubKey { -public: +private: static const int CURRENT_VERSION = 1; int nVersion; + +public: CExtPubKey extPubKey; uint256 hdchainID; - unsigned int nAccount; - unsigned int nChange; + uint32_t nAccountIndex; + uint32_t nChangeIndex; - CHDPubKey() : nVersion(CHDPubKey::CURRENT_VERSION), nAccount(0), nChange(0) {} + CHDPubKey() : nVersion(CHDPubKey::CURRENT_VERSION), nAccountIndex(0), nChangeIndex(0) {} ADD_SERIALIZE_METHODS; template @@ -79,8 +141,8 @@ class CHDPubKey nVersion = this->nVersion; READWRITE(extPubKey); READWRITE(hdchainID); - READWRITE(nAccount); - READWRITE(nChange); + READWRITE(nAccountIndex); + READWRITE(nChangeIndex); } std::string GetKeyPath() const; diff --git a/src/rpcmisc.cpp b/src/rpcmisc.cpp index 1c72efdcc22b..bcf522b9ae6e 100644 --- a/src/rpcmisc.cpp +++ b/src/rpcmisc.cpp @@ -321,7 +321,7 @@ UniValue validateaddress(const UniValue& params, bool fHelp) if (pwalletMain && address.GetKeyID(keyID) && pwalletMain->mapHdPubKeys.count(keyID) && pwalletMain->GetHDChain(hdChainCurrent)) { ret.push_back(Pair("hdkeypath", pwalletMain->mapHdPubKeys[keyID].GetKeyPath())); - ret.push_back(Pair("hdchainid", hdChainCurrent.id.GetHex())); + ret.push_back(Pair("hdchainid", hdChainCurrent.GetID().GetHex())); } #endif } diff --git a/src/test/rpc_wallet_tests.cpp b/src/test/rpc_wallet_tests.cpp index 51b3e1386a31..9997b02ccc0b 100644 --- a/src/test/rpc_wallet_tests.cpp +++ b/src/test/rpc_wallet_tests.cpp @@ -74,7 +74,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet) { LOCK(pwalletMain->cs_wallet); - demoPubkey = pwalletMain->GenerateNewKey(false); + demoPubkey = pwalletMain->GenerateNewKey(0, false); demoAddress = CBitcoinAddress(CTxDestination(demoPubkey.GetID())); string strPurpose = "receive"; BOOST_CHECK_NO_THROW({ /*Initialize Wallet with an account */ @@ -85,7 +85,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet) walletdb.WriteAccount(strAccount, account); }); - CPubKey setaccountDemoPubkey = pwalletMain->GenerateNewKey(false); + CPubKey setaccountDemoPubkey = pwalletMain->GenerateNewKey(0, false); setaccountDemoAddress = CBitcoinAddress(CTxDestination(setaccountDemoPubkey.GetID())); } /********************************* diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index b33d4ef2c550..f382e70e5153 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -285,7 +285,7 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn, bool fForMixin CHDChain hdChainTmp; if (DecryptHDChain(hdChainTmp)) { // make sure seed matches this chain - chainPass = cryptedHDChain.id == hdChainTmp.GetSeedHash(); + chainPass = cryptedHDChain.GetID() == hdChainTmp.GetSeedHash(); } if (!chainPass) { vMasterKey.clear(); @@ -429,11 +429,11 @@ bool CCryptoKeyStore::EncryptHDChain(const CKeyingMaterial& vMasterKeyIn) return true; // make sure seed matches this chain - if (hdChain.id != hdChain.GetSeedHash()) + if (hdChain.GetID() != hdChain.GetSeedHash()) return false; std::vector vchCryptedSeed; - if (!EncryptVector(vMasterKeyIn, hdChain.GetSeed(), hdChain.id, vchCryptedSeed)) + if (!EncryptVector(vMasterKeyIn, hdChain.GetSeed(), hdChain.GetID(), vchCryptedSeed)) return false; hdChain.Debug(__func__); @@ -451,9 +451,9 @@ bool CCryptoKeyStore::EncryptHDChain(const CKeyingMaterial& vMasterKeyIn) std::vector vchCryptedMnemonic; std::vector vchCryptedMnemonicPassphrase; - if (!vchMnemonic.empty() && !EncryptVector(vMasterKeyIn, vchMnemonic, hdChain.id, vchCryptedMnemonic)) + if (!vchMnemonic.empty() && !EncryptVector(vMasterKeyIn, vchMnemonic, hdChain.GetID(), vchCryptedMnemonic)) return false; - if (!vchMnemonicPassphrase.empty() && !EncryptVector(vMasterKeyIn, vchMnemonicPassphrase, hdChain.id, vchCryptedMnemonicPassphrase)) + if (!vchMnemonicPassphrase.empty() && !EncryptVector(vMasterKeyIn, vchMnemonicPassphrase, hdChain.GetID(), vchCryptedMnemonicPassphrase)) return false; if (!cryptedHDChain.SetMnemonic(vchCryptedMnemonic, vchCryptedMnemonicPassphrase, false)) @@ -478,7 +478,7 @@ bool CCryptoKeyStore::DecryptHDChain(CHDChain& hdChainRet) const return false; std::vector vchSeed; - if (!DecryptVector(vMasterKey, cryptedHDChain.GetSeed(), cryptedHDChain.id, vchSeed)) + if (!DecryptVector(vMasterKey, cryptedHDChain.GetSeed(), cryptedHDChain.GetID(), vchSeed)) return false; hdChainRet = cryptedHDChain; @@ -486,7 +486,7 @@ bool CCryptoKeyStore::DecryptHDChain(CHDChain& hdChainRet) const return false; // hash of decrypted seed must match chain id - if (hdChainRet.GetSeedHash() != cryptedHDChain.id) + if (hdChainRet.GetSeedHash() != cryptedHDChain.GetID()) return false; std::vector vchCryptedMnemonic; @@ -497,9 +497,9 @@ bool CCryptoKeyStore::DecryptHDChain(CHDChain& hdChainRet) const std::vector vchMnemonic; std::vector vchMnemonicPassphrase; - if (!vchCryptedMnemonic.empty() && !DecryptVector(vMasterKey, vchCryptedMnemonic, cryptedHDChain.id, vchMnemonic)) + if (!vchCryptedMnemonic.empty() && !DecryptVector(vMasterKey, vchCryptedMnemonic, cryptedHDChain.GetID(), vchMnemonic)) return false; - if (!vchCryptedMnemonicPassphrase.empty() && !DecryptVector(vMasterKey, vchCryptedMnemonicPassphrase, cryptedHDChain.id, vchMnemonicPassphrase)) + if (!vchCryptedMnemonicPassphrase.empty() && !DecryptVector(vMasterKey, vchCryptedMnemonicPassphrase, cryptedHDChain.GetID(), vchMnemonicPassphrase)) return false; if (!hdChainRet.SetMnemonic(vchMnemonic, vchMnemonicPassphrase, false)) diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 60daebbe938f..6fd66191099a 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -703,8 +703,16 @@ UniValue dumpwallet(const UniValue& params, bool fHelp) b58extpubkey.SetKey(masterPubkey); file << "# extended public masterkey: " << b58extpubkey.ToString() << "\n\n"; - file << "# external chain counter: " << hdChainCurrent.nExternalChainCounter << "\n"; - file << "# internal chain counter: " << hdChainCurrent.nInternalChainCounter << "\n\n"; + for (int i = 0; i < hdChainCurrent.CountAccounts(); ++i) + { + CHDAccount acc; + if(hdChainCurrent.GetAccount(i, acc)) { + file << "# external chain counter: " << acc.nExternalChainCounter << "\n"; + file << "# internal chain counter: " << acc.nInternalChainCounter << "\n\n"; + } else { + file << "# WARNING: ACCOUNT " << i << " IS MISSING!" << "\n\n"; + } + } } for (std::vector >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) { diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 68613db56196..582aeb0bc6da 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2383,8 +2383,15 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp) " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n" " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n" " \"hdchainid\": \"\", (string) the ID of the HD chain\n" - " \"hdexternalkeyindex\": xxxx, (numeric) current external childkey index\n" - " \"hdinternalkeyindex\": xxxx, (numeric) current internal childkey index\n" + " \"hdaccountcount\": xxx, (numeric) how many accounts of the HD chain are in this wallet\n" + " [\n" + " {\n" + " \"hdaccountindex\": xxx, (numeric) the index of the account\n" + " \"hdexternalkeyindex\": xxxx, (numeric) current external childkey index\n" + " \"hdinternalkeyindex\": xxxx, (numeric) current internal childkey index\n" + " }\n" + " ,...\n" + " ]\n" "}\n" "\nExamples:\n" + HelpExampleCli("getwalletinfo", "") @@ -2411,9 +2418,23 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp) obj.push_back(Pair("unlocked_until", nWalletUnlockTime)); obj.push_back(Pair("paytxfee", ValueFromAmount(payTxFee.GetFeePerK()))); if (fHDEnabled) { - obj.push_back(Pair("hdchainid", hdChainCurrent.id.GetHex())); - obj.push_back(Pair("hdexternalkeyindex", (int64_t)hdChainCurrent.nExternalChainCounter)); - obj.push_back(Pair("hdinternalkeyindex", (int64_t)hdChainCurrent.nInternalChainCounter)); + obj.push_back(Pair("hdchainid", hdChainCurrent.GetID().GetHex())); + obj.push_back(Pair("hdaccountcount", (int64_t)hdChainCurrent.CountAccounts())); + UniValue accounts(UniValue::VARR); + for (int i = 0; i < hdChainCurrent.CountAccounts(); ++i) + { + CHDAccount acc; + UniValue account(UniValue::VOBJ); + account.push_back(Pair("hdaccountindex", (int64_t)i)); + if(hdChainCurrent.GetAccount(i, acc)) { + account.push_back(Pair("hdexternalkeyindex", (int64_t)acc.nExternalChainCounter)); + account.push_back(Pair("hdinternalkeyindex", (int64_t)acc.nInternalChainCounter)); + } else { + account.push_back(Pair("error", strprintf("account %d is missing", i))); + } + accounts.push_back(account); + } + obj.push_back(Pair("hdaccounts", accounts)); } return obj; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 0add39a15714..52d3857301e8 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -100,7 +100,7 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const return &(it->second); } -CPubKey CWallet::GenerateNewKey(bool fInternal) +CPubKey CWallet::GenerateNewKey(uint32_t nAccountIndex, bool fInternal) { AssertLockHeld(cs_wallet); // mapKeyMetadata bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets @@ -114,7 +114,7 @@ CPubKey CWallet::GenerateNewKey(bool fInternal) CPubKey pubkey; // use HD key derivation if HD was enabled during wallet creation if (IsHDEnabled()) { - DeriveNewChildKey(metadata, secret, fInternal); + DeriveNewChildKey(metadata, secret, nAccountIndex, fInternal); pubkey = secret.GetPubKey(); } else { secret.MakeNewKey(fCompressed); @@ -137,7 +137,7 @@ CPubKey CWallet::GenerateNewKey(bool fInternal) return pubkey; } -void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret, bool fInternal) +void CWallet::DeriveNewChildKey(const CKeyMetadata& metadata, CKey& secretRet, uint32_t nAccountIndex, bool fInternal) { CHDChain hdChainTmp; if (!GetHDChain(hdChainTmp)) { @@ -147,21 +147,25 @@ void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret, bool fInte if (!DecryptHDChain(hdChainTmp)) throw std::runtime_error(std::string(__func__) + ": DecryptHDChainSeed failed"); // make sure seed matches this chain - if (hdChainTmp.id != hdChainTmp.GetSeedHash()) + if (hdChainTmp.GetID() != hdChainTmp.GetSeedHash()) throw std::runtime_error(std::string(__func__) + ": Wrong HD chain!"); + CHDAccount acc; + if (!hdChainTmp.GetAccount(nAccountIndex, acc)) + throw std::runtime_error(std::string(__func__) + ": Wrong HD account!"); + // derive child key at next index, skip keys already known to the wallet CExtKey childKey; - uint32_t childIndex = fInternal ? hdChainTmp.nInternalChainCounter : hdChainTmp.nExternalChainCounter; + uint32_t nChildIndex = fInternal ? acc.nInternalChainCounter : acc.nExternalChainCounter; do { - hdChainTmp.DeriveChildExtKey(childIndex, childKey, fInternal); + hdChainTmp.DeriveChildExtKey(nAccountIndex, fInternal, nChildIndex, childKey); // increment childkey index - childIndex++; + nChildIndex++; } while (HaveKey(childKey.key.GetPubKey().GetID())); - secret = childKey.key; + secretRet = childKey.key; - CPubKey pubkey = secret.GetPubKey(); - assert(secret.VerifyPubKey(pubkey)); + CPubKey pubkey = secretRet.GetPubKey(); + assert(secretRet.VerifyPubKey(pubkey)); // store metadata mapKeyMetadata[pubkey.GetID()] = metadata; @@ -173,12 +177,15 @@ void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret, bool fInte GetHDChain(hdChainCurrent); if (fInternal) { - hdChainCurrent.nInternalChainCounter = childIndex; + acc.nInternalChainCounter = nChildIndex; } else { - hdChainCurrent.nExternalChainCounter = childIndex; + acc.nExternalChainCounter = nChildIndex; } + if (!hdChainCurrent.SetAccount(nAccountIndex, acc)) + throw std::runtime_error(std::string(__func__) + ": SetAccount failed"); + if (IsCrypted()) { if (!SetCryptedHDChain(hdChainCurrent, false)) throw std::runtime_error(std::string(__func__) + ": SetCryptedHDChain failed"); @@ -220,11 +227,11 @@ bool CWallet::GetKey(const CKeyID &address, CKey& keyOut) const if (!DecryptHDChain(hdChainCurrent)) throw std::runtime_error(std::string(__func__) + ": DecryptHDChainSeed failed"); // make sure seed matches this chain - if (hdChainCurrent.id != hdChainCurrent.GetSeedHash()) + if (hdChainCurrent.GetID() != hdChainCurrent.GetSeedHash()) throw std::runtime_error(std::string(__func__) + ": Wrong HD chain!"); CExtKey extkey; - hdChainCurrent.DeriveChildExtKey(hdPubKey.extPubKey.nChild, extkey, hdPubKey.nChange != 0); + hdChainCurrent.DeriveChildExtKey(hdPubKey.nAccountIndex, hdPubKey.nChangeIndex != 0, hdPubKey.extPubKey.nChild, extkey); keyOut = extkey.key; return true; @@ -259,8 +266,8 @@ bool CWallet::AddHDPubKey(const CExtPubKey &extPubKey, bool fInternal) CHDPubKey hdPubKey; hdPubKey.extPubKey = extPubKey; - hdPubKey.hdchainID = hdChainCurrent.id; - hdPubKey.nChange = fInternal ? 1 : 0; + hdPubKey.hdchainID = hdChainCurrent.GetID(); + hdPubKey.nChangeIndex = fInternal ? 1 : 0; mapHdPubKeys[extPubKey.pubkey.GetID()] = hdPubKey; // check if we need to remove from watch-only @@ -790,7 +797,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) ); // ids should match, seed hashes should not - assert(hdChainCurrent.id == hdChainCrypted.id); + assert(hdChainCurrent.GetID() == hdChainCrypted.GetID()); assert(hdChainCurrent.GetSeedHash() != hdChainCrypted.GetSeedHash()); assert(SetCryptedHDChain(hdChainCrypted, false)); @@ -1456,7 +1463,7 @@ bool CWallet::GetDecryptedHDChain(CHDChain& hdChainRet) return false; // make sure seed matches this chain - if (hdChainTmp.id != hdChainTmp.GetSeedHash()) + if (hdChainTmp.GetID() != hdChainTmp.GetSeedHash()) return false; hdChainRet = hdChainTmp; @@ -3795,7 +3802,8 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize) if (!setExternalKeyPool.empty()) { nEnd = std::max(nEnd, *(--setExternalKeyPool.end()) + 1); } - if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey(fInternal), fInternal))) + // TODO: implement keypools for all accounts? + if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey(0, fInternal), fInternal))) throw runtime_error("TopUpKeyPool(): writing generated key failed"); if (fInternal) { @@ -3885,7 +3893,8 @@ bool CWallet::GetKeyFromPool(CPubKey& result, bool fInternal) if (nIndex == -1) { if (IsLocked(true)) return false; - result = GenerateNewKey(fInternal); + // TODO: implement keypool for all accouts? + result = GenerateNewKey(0, fInternal); return true; } KeepKey(nIndex); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index db663f316dbc..ba2f8e2ead62 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -653,7 +653,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface void SyncMetaData(std::pair); /* HD derive new child key (on internal or external chain) */ - void DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret, bool fInternal /*= false*/); + void DeriveNewChildKey(const CKeyMetadata& metadata, CKey& secretRet, uint32_t nAccountIndex, bool fInternal /*= false*/); public: /* @@ -802,7 +802,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface * keystore implementation * Generate a new key */ - CPubKey GenerateNewKey(bool fInternal /*= false*/); + CPubKey GenerateNewKey(uint32_t nAccountIndex, bool fInternal /*= false*/); //! HaveKey implementation that also checks the mapHdPubKeys bool HaveKey(const CKeyID &address) const; //! GetPubKey implementation that also checks the mapHdPubKeys From 35ab9079b8eaea6ec9066e69dc3b65f20cfd52e7 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Thu, 11 May 2017 21:58:04 +0300 Subject: [PATCH 18/31] use secure allocator for storing sensitive HD data --- src/hdchain.cpp | 14 ++++----- src/hdchain.h | 16 +++++----- src/wallet/crypter.cpp | 66 +++++++++++++++++------------------------- src/wallet/rpcdump.cpp | 2 +- src/wallet/wallet.cpp | 6 ++-- 5 files changed, 46 insertions(+), 58 deletions(-) diff --git a/src/hdchain.cpp b/src/hdchain.cpp index 740c06ef6393..f009c2736b66 100644 --- a/src/hdchain.cpp +++ b/src/hdchain.cpp @@ -69,9 +69,9 @@ void CHDChain::Debug(std::string strName) const ); } -bool CHDChain::SetMnemonic(const std::vector& vchMnemonicIn, const std::vector& vchMnemonicPassphraseIn, bool fUpdateID) +bool CHDChain::SetMnemonic(const CSecureVector& vchMnemonicIn, const CSecureVector& vchMnemonicPassphraseIn, bool fUpdateID) { - std::vector vchMnemonicTmp = vchMnemonicIn; + CSecureVector vchMnemonicTmp = vchMnemonicIn; if (fUpdateID) { // can't (re)set mnemonic if seed was already set @@ -84,7 +84,7 @@ bool CHDChain::SetMnemonic(const std::vector& vchMnemonicIn, cons // empty mnemonic i.e. "generate a new one" if (vchMnemonicIn.empty()) { strMnemonic = mnemonic_generate(128); - vchMnemonicTmp = std::vector(strMnemonic.begin(), strMnemonic.end()); + vchMnemonicTmp = CSecureVector(strMnemonic.begin(), strMnemonic.end()); } // NOTE: default mnemonic passphrase is an empty string @@ -94,7 +94,7 @@ bool CHDChain::SetMnemonic(const std::vector& vchMnemonicIn, cons uint8_t seed[64]; mnemonic_to_seed(strMnemonic.c_str(), strMnemonicPassphrase.c_str(), seed, 0); - vchSeed = std::vector(seed, seed + 64); + vchSeed = CSecureVector(seed, seed + 64); id = GetSeedHash(); } @@ -104,7 +104,7 @@ bool CHDChain::SetMnemonic(const std::vector& vchMnemonicIn, cons return !IsNull(); } -bool CHDChain::GetMnemonic(std::vector& vchMnemonicRet, std::vector& vchMnemonicPassphraseRet) const +bool CHDChain::GetMnemonic(CSecureVector& vchMnemonicRet, CSecureVector& vchMnemonicPassphraseRet) const { // mnemonic was not set, fail if (vchMnemonic.empty()) @@ -127,7 +127,7 @@ bool CHDChain::GetMnemonic(std::string& strMnemonicRet, std::string& strMnemonic return true; } -bool CHDChain::SetSeed(const std::vector& vchSeedIn, bool fUpdateID) +bool CHDChain::SetSeed(const CSecureVector& vchSeedIn, bool fUpdateID) { vchSeed = vchSeedIn; @@ -138,7 +138,7 @@ bool CHDChain::SetSeed(const std::vector& vchSeedIn, bool fUpdate return !IsNull(); } -std::vector CHDChain::GetSeed() const +CSecureVector CHDChain::GetSeed() const { return vchSeed; } diff --git a/src/hdchain.h b/src/hdchain.h index 0eb50224e4bf..5381131d8b10 100644 --- a/src/hdchain.h +++ b/src/hdchain.h @@ -24,6 +24,8 @@ class CHDAccount } }; +typedef std::vector > CSecureVector; + /* simple HD chain data model */ class CHDChain { @@ -35,9 +37,9 @@ class CHDChain bool fCrypted; - std::vector vchSeed; - std::vector vchMnemonic; - std::vector vchMnemonicPassphrase; + CSecureVector vchSeed; + CSecureVector vchMnemonic; + CSecureVector vchMnemonicPassphrase; std::map mapAccounts; // critical section to protect mapAccounts @@ -100,12 +102,12 @@ class CHDChain void Debug(std::string strName) const; - bool SetMnemonic(const std::vector& vchMnemonicIn, const std::vector& vchMnemonicPassphraseIn, bool fUpdateID); - bool GetMnemonic(std::vector& vchMnemonicRet, std::vector& vchMnemonicPassphraseRet) const; + bool SetMnemonic(const CSecureVector& vchMnemonicIn, const CSecureVector& vchMnemonicPassphraseIn, bool fUpdateID); + bool GetMnemonic(CSecureVector& vchMnemonicRet, CSecureVector& vchMnemonicPassphraseRet) const; bool GetMnemonic(std::string& strMnemonicRet, std::string& strMnemonicPassphraseRet) const; - bool SetSeed(const std::vector& vchSeedIn, bool fUpdateID); - std::vector GetSeed() const; + bool SetSeed(const CSecureVector& vchSeedIn, bool fUpdateID); + CSecureVector GetSeed() const; uint256 GetID() const { return id; } diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index f382e70e5153..d057d4370aee 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -394,28 +394,6 @@ bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn) return true; } -bool EncryptVector(const CKeyingMaterial& vMasterKeyIn, const std::vector &vchSecretIn, const uint256& nIV, std::vector &vchCryptedSecretRet) -{ - uint8_t secret[vchSecretIn.size()]; - memcpy(&secret[0], (unsigned char*)&(vchSecretIn.begin())[0], vchSecretIn.size()); - CKeyingMaterial vchSecret(secret, secret + vchSecretIn.size()); - - return EncryptSecret(vMasterKeyIn, vchSecret, nIV, vchCryptedSecretRet); -} - -bool DecryptVector(const CKeyingMaterial& vMasterKeyIn, const std::vector &vchCryptedSecretIn, const uint256& nIV, std::vector &vchSecretRet) -{ - CKeyingMaterial vchSecret; - if(!DecryptSecret(vMasterKeyIn, vchCryptedSecretIn, nIV, vchSecret)) - return false; - - uint8_t secret[vchSecret.size()]; - memcpy(&secret[0], (unsigned char*)&(vchSecret.begin())[0], vchSecret.size()); - vchSecretRet = std::vector(secret, secret + vchSecret.size()); - - return true; -} - bool CCryptoKeyStore::EncryptHDChain(const CKeyingMaterial& vMasterKeyIn) { // should call EncryptKeys first @@ -433,30 +411,33 @@ bool CCryptoKeyStore::EncryptHDChain(const CKeyingMaterial& vMasterKeyIn) return false; std::vector vchCryptedSeed; - if (!EncryptVector(vMasterKeyIn, hdChain.GetSeed(), hdChain.GetID(), vchCryptedSeed)) + if (!EncryptSecret(vMasterKeyIn, hdChain.GetSeed(), hdChain.GetID(), vchCryptedSeed)) return false; hdChain.Debug(__func__); cryptedHDChain = hdChain; cryptedHDChain.SetCrypted(true); - if (!cryptedHDChain.SetSeed(vchCryptedSeed, false)) + CSecureVector vchSecureCryptedSeed(vchCryptedSeed.begin(), vchCryptedSeed.end()); + if (!cryptedHDChain.SetSeed(vchSecureCryptedSeed, false)) return false; - std::vector vchMnemonic; - std::vector vchMnemonicPassphrase; + CSecureVector vchMnemonic; + CSecureVector vchMnemonicPassphrase; // it's ok to have no mnemonic if wallet was initialized via hdseed if (hdChain.GetMnemonic(vchMnemonic, vchMnemonicPassphrase)) { std::vector vchCryptedMnemonic; std::vector vchCryptedMnemonicPassphrase; - if (!vchMnemonic.empty() && !EncryptVector(vMasterKeyIn, vchMnemonic, hdChain.GetID(), vchCryptedMnemonic)) + if (!vchMnemonic.empty() && !EncryptSecret(vMasterKeyIn, vchMnemonic, hdChain.GetID(), vchCryptedMnemonic)) return false; - if (!vchMnemonicPassphrase.empty() && !EncryptVector(vMasterKeyIn, vchMnemonicPassphrase, hdChain.GetID(), vchCryptedMnemonicPassphrase)) + if (!vchMnemonicPassphrase.empty() && !EncryptSecret(vMasterKeyIn, vchMnemonicPassphrase, hdChain.GetID(), vchCryptedMnemonicPassphrase)) return false; - if (!cryptedHDChain.SetMnemonic(vchCryptedMnemonic, vchCryptedMnemonicPassphrase, false)) + CSecureVector vchSecureCryptedMnemonic(vchCryptedMnemonic.begin(), vchCryptedMnemonic.end()); + CSecureVector vchSecureCryptedMnemonicPassphrase(vchCryptedMnemonicPassphrase.begin(), vchCryptedMnemonicPassphrase.end()); + if (!cryptedHDChain.SetMnemonic(vchSecureCryptedMnemonic, vchSecureCryptedMnemonicPassphrase, false)) return false; } @@ -477,32 +458,37 @@ bool CCryptoKeyStore::DecryptHDChain(CHDChain& hdChainRet) const if (!cryptedHDChain.IsCrypted()) return false; - std::vector vchSeed; - if (!DecryptVector(vMasterKey, cryptedHDChain.GetSeed(), cryptedHDChain.GetID(), vchSeed)) + CSecureVector vchSecureSeed; + CSecureVector vchSecureCryptedSeed = cryptedHDChain.GetSeed(); + std::vector vchCryptedSeed(vchSecureCryptedSeed.begin(), vchSecureCryptedSeed.end()); + if (!DecryptSecret(vMasterKey, vchCryptedSeed, cryptedHDChain.GetID(), vchSecureSeed)) return false; hdChainRet = cryptedHDChain; - if (!hdChainRet.SetSeed(vchSeed, false)) + if (!hdChainRet.SetSeed(vchSecureSeed, false)) return false; // hash of decrypted seed must match chain id if (hdChainRet.GetSeedHash() != cryptedHDChain.GetID()) return false; - std::vector vchCryptedMnemonic; - std::vector vchCryptedMnemonicPassphrase; + CSecureVector vchSecureCryptedMnemonic; + CSecureVector vchSecureCryptedMnemonicPassphrase; // it's ok to have no mnemonic if wallet was initialized via hdseed - if (cryptedHDChain.GetMnemonic(vchCryptedMnemonic, vchCryptedMnemonicPassphrase)) { - std::vector vchMnemonic; - std::vector vchMnemonicPassphrase; + if (cryptedHDChain.GetMnemonic(vchSecureCryptedMnemonic, vchSecureCryptedMnemonicPassphrase)) { + CSecureVector vchSecureMnemonic; + CSecureVector vchSecureMnemonicPassphrase; + + std::vector vchCryptedMnemonic(vchSecureCryptedMnemonic.begin(), vchSecureCryptedMnemonic.end()); + std::vector vchCryptedMnemonicPassphrase(vchSecureCryptedMnemonicPassphrase.begin(), vchSecureCryptedMnemonicPassphrase.end()); - if (!vchCryptedMnemonic.empty() && !DecryptVector(vMasterKey, vchCryptedMnemonic, cryptedHDChain.GetID(), vchMnemonic)) + if (!vchCryptedMnemonic.empty() && !DecryptSecret(vMasterKey, vchCryptedMnemonic, cryptedHDChain.GetID(), vchSecureMnemonic)) return false; - if (!vchCryptedMnemonicPassphrase.empty() && !DecryptVector(vMasterKey, vchCryptedMnemonicPassphrase, cryptedHDChain.GetID(), vchMnemonicPassphrase)) + if (!vchCryptedMnemonicPassphrase.empty() && !DecryptSecret(vMasterKey, vchCryptedMnemonicPassphrase, cryptedHDChain.GetID(), vchSecureMnemonicPassphrase)) return false; - if (!hdChainRet.SetMnemonic(vchMnemonic, vchMnemonicPassphrase, false)) + if (!hdChainRet.SetMnemonic(vchSecureMnemonic, vchSecureMnemonicPassphrase, false)) return false; } diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 6fd66191099a..b027d76e8e9e 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -685,7 +685,7 @@ UniValue dumpwallet(const UniValue& params, bool fHelp) file << "# mnemonic: " << strMnemonic << "\n"; file << "# mnemonic passphrase: " << strMnemonicPassphrase << "\n\n"; - std::vector vchSeed = hdChainCurrent.GetSeed(); + CSecureVector vchSeed = hdChainCurrent.GetSeed(); file << "# HD seed: " << HexStr(vchSeed) << "\n\n"; CExtKey masterKey; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 52d3857301e8..56b44650626a 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1391,7 +1391,7 @@ void CWallet::GenerateNewHDChain() if(mapArgs.count("-hdseed") && IsHex(strSeed)) { std::vector vchSeed = ParseHex(strSeed); - if (!newHdChain.SetSeed(vchSeed, true)) + if (!newHdChain.SetSeed(CSecureVector(vchSeed.begin(), vchSeed.end()), true)) throw std::runtime_error(std::string(__func__) + ": SetSeed failed"); } else { @@ -1403,8 +1403,8 @@ void CWallet::GenerateNewHDChain() // NOTE: default mnemonic passphrase is an empty string std::string strMnemonicPassphrase = GetArg("-mnemonicpassphrase", ""); - std::vector vchMnemonic(strMnemonic.begin(), strMnemonic.end()); - std::vector vchMnemonicPassphrase(strMnemonicPassphrase.begin(), strMnemonicPassphrase.end()); + CSecureVector vchMnemonic(strMnemonic.begin(), strMnemonic.end()); + CSecureVector vchMnemonicPassphrase(strMnemonicPassphrase.begin(), strMnemonicPassphrase.end()); if (!newHdChain.SetMnemonic(vchMnemonic, vchMnemonicPassphrase, true)) throw std::runtime_error(std::string(__func__) + ": SetMnemonic failed"); From 72d4c45880cbcd9aaf3f6b0bf5c9b967e94832b8 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Sat, 13 May 2017 19:09:03 +0300 Subject: [PATCH 19/31] use secure strings for mnemonic(passphrase) --- src/hdchain.cpp | 6 +++--- src/hdchain.h | 2 +- src/wallet/rpcdump.cpp | 20 ++++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/hdchain.cpp b/src/hdchain.cpp index f009c2736b66..62e6a56b04af 100644 --- a/src/hdchain.cpp +++ b/src/hdchain.cpp @@ -115,14 +115,14 @@ bool CHDChain::GetMnemonic(CSecureVector& vchMnemonicRet, CSecureVector& vchMnem return true; } -bool CHDChain::GetMnemonic(std::string& strMnemonicRet, std::string& strMnemonicPassphraseRet) const +bool CHDChain::GetMnemonic(SecureString& ssMnemonicRet, SecureString& ssMnemonicPassphraseRet) const { // mnemonic was not set, fail if (vchMnemonic.empty()) return false; - strMnemonicRet = std::string(vchMnemonic.begin(), vchMnemonic.end()); - strMnemonicPassphraseRet = std::string(vchMnemonicPassphrase.begin(), vchMnemonicPassphrase.end()); + ssMnemonicRet = SecureString(vchMnemonic.begin(), vchMnemonic.end()); + ssMnemonicPassphraseRet = SecureString(vchMnemonicPassphrase.begin(), vchMnemonicPassphrase.end()); return true; } diff --git a/src/hdchain.h b/src/hdchain.h index 5381131d8b10..6a56e74bc9c4 100644 --- a/src/hdchain.h +++ b/src/hdchain.h @@ -104,7 +104,7 @@ class CHDChain bool SetMnemonic(const CSecureVector& vchMnemonicIn, const CSecureVector& vchMnemonicPassphraseIn, bool fUpdateID); bool GetMnemonic(CSecureVector& vchMnemonicRet, CSecureVector& vchMnemonicPassphraseRet) const; - bool GetMnemonic(std::string& strMnemonicRet, std::string& strMnemonicPassphraseRet) const; + bool GetMnemonic(SecureString& ssMnemonicRet, SecureString& ssMnemonicPassphraseRet) const; bool SetSeed(const CSecureVector& vchSeedIn, bool fUpdateID); CSecureVector GetSeed() const; diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index b027d76e8e9e..c9b4834a7d18 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -611,14 +611,14 @@ UniValue dumphdinfo(const UniValue& params, bool fHelp) if (!pwalletMain->GetDecryptedHDChain(hdChainCurrent)) throw JSONRPCError(RPC_INTERNAL_ERROR, "Cannot decrypt HD seed"); - std::string strMnemonic; - std::string strMnemonicPassphrase; - hdChainCurrent.GetMnemonic(strMnemonic, strMnemonicPassphrase); + SecureString ssMnemonic; + SecureString ssMnemonicPassphrase; + hdChainCurrent.GetMnemonic(ssMnemonic, ssMnemonicPassphrase); UniValue obj(UniValue::VOBJ); obj.push_back(Pair("hdseed", HexStr(hdChainCurrent.GetSeed()))); - obj.push_back(Pair("mnemonic", strMnemonic)); - obj.push_back(Pair("mnemonicpassphrase", strMnemonicPassphrase)); + obj.push_back(Pair("mnemonic", std::string(ssMnemonic.begin(), ssMnemonic.end()))); + obj.push_back(Pair("mnemonicpassphrase", std::string(ssMnemonicPassphrase.begin(), ssMnemonicPassphrase.end()))); return obj; } @@ -679,11 +679,11 @@ UniValue dumpwallet(const UniValue& params, bool fHelp) if (!pwalletMain->GetDecryptedHDChain(hdChainCurrent)) throw JSONRPCError(RPC_INTERNAL_ERROR, "Cannot decrypt HD chain"); - std::string strMnemonic; - std::string strMnemonicPassphrase; - hdChainCurrent.GetMnemonic(strMnemonic, strMnemonicPassphrase); - file << "# mnemonic: " << strMnemonic << "\n"; - file << "# mnemonic passphrase: " << strMnemonicPassphrase << "\n\n"; + SecureString ssMnemonic; + SecureString ssMnemonicPassphrase; + hdChainCurrent.GetMnemonic(ssMnemonic, ssMnemonicPassphrase); + file << "# mnemonic: " << std::string(ssMnemonic.begin(), ssMnemonic.end()) << "\n"; + file << "# mnemonic passphrase: " << std::string(ssMnemonicPassphrase.begin(), ssMnemonicPassphrase.end()) << "\n\n"; CSecureVector vchSeed = hdChainCurrent.GetSeed(); file << "# HD seed: " << HexStr(vchSeed) << "\n\n"; From 9e1bcb17a3df8b5ecc990d38c5fe9827dbf8340d Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Sat, 13 May 2017 19:10:09 +0300 Subject: [PATCH 20/31] small fix in GenerateNewHDChain --- src/wallet/wallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 56b44650626a..7a9fa5324be0 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1395,7 +1395,7 @@ void CWallet::GenerateNewHDChain() throw std::runtime_error(std::string(__func__) + ": SetSeed failed"); } else { - if (mapArgs.count("-hdseed") && !IsHex(GetArg("-hdseed", ""))) + if (mapArgs.count("-hdseed") && !IsHex(strSeed)) LogPrintf("CWallet::GenerateNewHDChain -- Incorrect seed, generating random one instead\n"); // NOTE: empty mnemonic means "generate a new one for me" From 396155800321cf3eb22dfc8a6b2e0e9b7d6b98ec Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Wed, 17 May 2017 03:27:33 +0300 Subject: [PATCH 21/31] use 24 words for mnemonic by default --- src/hdchain.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hdchain.cpp b/src/hdchain.cpp index 62e6a56b04af..4d7dd1037fe2 100644 --- a/src/hdchain.cpp +++ b/src/hdchain.cpp @@ -83,7 +83,7 @@ bool CHDChain::SetMnemonic(const CSecureVector& vchMnemonicIn, const CSecureVect // empty mnemonic i.e. "generate a new one" if (vchMnemonicIn.empty()) { - strMnemonic = mnemonic_generate(128); + strMnemonic = mnemonic_generate(256); vchMnemonicTmp = CSecureVector(strMnemonic.begin(), strMnemonic.end()); } // NOTE: default mnemonic passphrase is an empty string From 448751c9c0e1dbd8273cc002831569db550fbde0 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Wed, 17 May 2017 03:28:13 +0300 Subject: [PATCH 22/31] make sure mnemonic passphrase provided by user does not exceed 256 symbols --- src/init.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/init.cpp b/src/init.cpp index 6870f9d98a56..e01c1792345b 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1672,6 +1672,8 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) RandAddSeedPerfmon(); if (GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET) && !pwalletMain->IsHDEnabled()) { + if (GetArg("-mnemonicpassphrase", "").size() > 256) + return InitError(_("Mnemonic passphrase is too long, must be at most 256 characters")); // generate a new master key pwalletMain->GenerateNewHDChain(); } From 87308fefc81ce5b703ac800faf5740ce54b0e9f0 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Mon, 22 May 2017 14:47:05 +0300 Subject: [PATCH 23/31] more usage of secure allocators and memory_cleanse --- src/bip39.cpp | 76 ++++++++++++++------------------- src/bip39.h | 14 +++--- src/hdchain.cpp | 30 ++++++------- src/hdchain.h | 5 +-- src/support/allocators/secure.h | 2 + src/test/bip39_tests.cpp | 19 +++++---- src/wallet/wallet.cpp | 5 +++ 7 files changed, 70 insertions(+), 81 deletions(-) diff --git a/src/bip39.cpp b/src/bip39.cpp index 41d2f0362dba..18ba4130fdb6 100644 --- a/src/bip39.cpp +++ b/src/bip39.cpp @@ -24,41 +24,33 @@ // Source: // https://github.com/trezor/trezor-crypto -#include -#include - #include "bip39.h" -// #include "hmac.h" -// #include "rand.h" -// #include "sha2.h" -// #include "pbkdf2.h" #include "bip39_english.h" -// #include "options.h" #include "crypto/sha256.h" #include "random.h" #include -const char *mnemonic_generate(int strength) +SecureString mnemonic_generate(int strength) { if (strength % 32 || strength < 128 || strength > 256) { - return 0; + return SecureString(); } uint8_t data[32]; - // random_buffer(data, 32); GetRandBytes(data, 32); - return mnemonic_from_data(data, strength / 8); + SecureString mnemonic = mnemonic_from_data(data, strength / 8); + memory_cleanse(data, sizeof(data)); + return mnemonic; } -const char *mnemonic_from_data(const uint8_t *data, int len) +SecureString mnemonic_from_data(const uint8_t *data, int len) { if (len % 4 || len < 16 || len > 32) { - return 0; + return SecureString(); } uint8_t bits[32 + 1]; - // sha256_Raw(data, len, bits); CSHA256().Write(data, len).Finalize(bits); // checksum bits[len] = bits[0]; @@ -66,34 +58,33 @@ const char *mnemonic_from_data(const uint8_t *data, int len) memcpy(bits, data, len); int mlen = len * 3 / 4; - static char mnemo[24 * 10]; + SecureString mnemonic; int i, j, idx; - char *p = mnemo; for (i = 0; i < mlen; i++) { idx = 0; for (j = 0; j < 11; j++) { idx <<= 1; idx += (bits[(i * 11 + j) / 8] & (1 << (7 - ((i * 11 + j) % 8)))) > 0; } - strcpy(p, wordlist[idx]); - p += strlen(wordlist[idx]); - *p = (i < mlen - 1) ? ' ' : 0; - p++; + mnemonic.append(wordlist[idx]); + if (i < mlen - 1) { + mnemonic += ' '; + } } + memory_cleanse(bits, sizeof(bits)); - return mnemo; + return mnemonic; } -int mnemonic_check(const char *mnemonic) +int mnemonic_check(SecureString mnemonic) { - if (!mnemonic) { + if (mnemonic.empty()) { return 0; } - uint32_t i, n; + uint32_t i{}, n{}; - i = 0; n = 0; while (mnemonic[i]) { if (mnemonic[i] == ' ') { n++; @@ -108,8 +99,7 @@ int mnemonic_check(const char *mnemonic) char current_word[10]; uint32_t j, k, ki, bi; - uint8_t bits[32 + 1]; - memset(bits, 0, sizeof(bits)); + uint8_t bits[32 + 1]{}; i = 0; bi = 0; while (mnemonic[i]) { j = 0; @@ -143,37 +133,33 @@ int mnemonic_check(const char *mnemonic) return 0; } bits[32] = bits[n * 4 / 3]; - // sha256_Raw(bits, n * 4 / 3, bits); CSHA256().Write(bits, n * 4 / 3).Finalize(bits); + int result = 0; if (n == 12) { - return (bits[0] & 0xF0) == (bits[32] & 0xF0); // compare first 4 bits + result = (bits[0] & 0xF0) == (bits[32] & 0xF0); // compare first 4 bits } else if (n == 18) { - return (bits[0] & 0xFC) == (bits[32] & 0xFC); // compare first 6 bits + result = (bits[0] & 0xFC) == (bits[32] & 0xFC); // compare first 6 bits } else if (n == 24) { - return bits[0] == bits[32]; // compare 8 bits + result = bits[0] == bits[32]; // compare 8 bits } - return 0; + memory_cleanse(bits, sizeof(bits)); + return result; } // passphrase must be at most 256 characters or code may crash -void mnemonic_to_seed(const char *mnemonic, const char *passphrase, uint8_t seed[512 / 8], void (*progress_callback)(uint32_t current, uint32_t total)) +void mnemonic_to_seed(SecureString mnemonic, SecureString passphrase, CSecureVector& seedRet) { - int passphraselen = strlen(passphrase); - uint8_t salt[8 + 256 + 4]; - memcpy(salt, "mnemonic", 8); - memcpy(salt + 8, passphrase, passphraselen); - // pbkdf2_hmac_sha512((const uint8_t *)mnemonic, strlen(mnemonic), salt, passphraselen + 8, BIP39_PBKDF2_ROUNDS, seed, 512 / 8, progress_callback); + SecureString ssSalt = SecureString("mnemonic") + passphrase; + CSecureVector vchSalt(ssSalt.begin(), ssSalt.end()); // int PKCS5_PBKDF2_HMAC(const char *pass, int passlen, // const unsigned char *salt, int saltlen, int iter, // const EVP_MD *digest, // int keylen, unsigned char *out); - PKCS5_PBKDF2_HMAC(mnemonic, strlen(mnemonic), salt, passphraselen + 8, 2048, EVP_sha512(), 64, seed); -} - -const char * const *mnemonic_wordlist(void) -{ - return wordlist; + uint8_t seed[64]; + PKCS5_PBKDF2_HMAC(mnemonic.c_str(), mnemonic.size(), &vchSalt[0], vchSalt.size(), 2048, EVP_sha512(), 64, seed); + seedRet = CSecureVector(seed, seed + 64); + memory_cleanse(seed, sizeof(seed)); } diff --git a/src/bip39.h b/src/bip39.h index 1c368238422c..5b22a786920f 100644 --- a/src/bip39.h +++ b/src/bip39.h @@ -24,19 +24,15 @@ #ifndef __BIP39_H__ #define __BIP39_H__ -#include +#include "support/allocators/secure.h" -#define BIP39_PBKDF2_ROUNDS 2048 +SecureString mnemonic_generate(int strength); // strength in bits -const char *mnemonic_generate(int strength); // strength in bits +SecureString mnemonic_from_data(const uint8_t *data, int len); -const char *mnemonic_from_data(const uint8_t *data, int len); - -int mnemonic_check(const char *mnemonic); +int mnemonic_check(SecureString mnemonic); // passphrase must be at most 256 characters or code may crash -void mnemonic_to_seed(const char *mnemonic, const char *passphrase, uint8_t seed[512 / 8], void (*progress_callback)(uint32_t current, uint32_t total)); - -const char * const *mnemonic_wordlist(void); +void mnemonic_to_seed(SecureString mnemonic, SecureString passphrase, CSecureVector& seedRet); #endif diff --git a/src/hdchain.cpp b/src/hdchain.cpp index 4d7dd1037fe2..b3875b2557df 100644 --- a/src/hdchain.cpp +++ b/src/hdchain.cpp @@ -69,37 +69,37 @@ void CHDChain::Debug(std::string strName) const ); } -bool CHDChain::SetMnemonic(const CSecureVector& vchMnemonicIn, const CSecureVector& vchMnemonicPassphraseIn, bool fUpdateID) +bool CHDChain::SetMnemonic(const CSecureVector& vchMnemonic, const CSecureVector& vchMnemonicPassphrase, bool fUpdateID) { - CSecureVector vchMnemonicTmp = vchMnemonicIn; + return SetMnemonic(SecureString(vchMnemonic.begin(), vchMnemonic.end()), SecureString(vchMnemonicPassphrase.begin(), vchMnemonicPassphrase.end()), fUpdateID); +} + +bool CHDChain::SetMnemonic(const SecureString& ssMnemonic, const SecureString& ssMnemonicPassphrase, bool fUpdateID) +{ + SecureString ssMnemonicTmp = ssMnemonic; if (fUpdateID) { // can't (re)set mnemonic if seed was already set if (!IsNull()) return false; - std::string strMnemonic(vchMnemonicIn.begin(), vchMnemonicIn.end()); - std::string strMnemonicPassphrase(vchMnemonicPassphraseIn.begin(), vchMnemonicPassphraseIn.end()); - // empty mnemonic i.e. "generate a new one" - if (vchMnemonicIn.empty()) { - strMnemonic = mnemonic_generate(256); - vchMnemonicTmp = CSecureVector(strMnemonic.begin(), strMnemonic.end()); + if (ssMnemonic.empty()) { + ssMnemonicTmp = mnemonic_generate(256); } // NOTE: default mnemonic passphrase is an empty string - if (!mnemonic_check(strMnemonic.c_str())) { - throw std::runtime_error(std::string(__func__) + ": invalid mnemonic: `" + strMnemonic + "`"); + // printf("mnemonic: %s\n", ssMnemonicTmp.c_str()); + if (!mnemonic_check(ssMnemonicTmp)) { + throw std::runtime_error(std::string(__func__) + ": invalid mnemonic: `" + std::string(ssMnemonicTmp.c_str()) + "`"); } - uint8_t seed[64]; - mnemonic_to_seed(strMnemonic.c_str(), strMnemonicPassphrase.c_str(), seed, 0); - vchSeed = CSecureVector(seed, seed + 64); + mnemonic_to_seed(ssMnemonicTmp, ssMnemonicPassphrase, vchSeed); id = GetSeedHash(); } - vchMnemonic = vchMnemonicTmp; - vchMnemonicPassphrase = vchMnemonicPassphraseIn; + vchMnemonic = CSecureVector(ssMnemonicTmp.begin(), ssMnemonicTmp.end()); + vchMnemonicPassphrase = CSecureVector(ssMnemonicPassphrase.begin(), ssMnemonicPassphrase.end()); return !IsNull(); } diff --git a/src/hdchain.h b/src/hdchain.h index 6a56e74bc9c4..c59fc09d379e 100644 --- a/src/hdchain.h +++ b/src/hdchain.h @@ -24,8 +24,6 @@ class CHDAccount } }; -typedef std::vector > CSecureVector; - /* simple HD chain data model */ class CHDChain { @@ -102,7 +100,8 @@ class CHDChain void Debug(std::string strName) const; - bool SetMnemonic(const CSecureVector& vchMnemonicIn, const CSecureVector& vchMnemonicPassphraseIn, bool fUpdateID); + bool SetMnemonic(const CSecureVector& vchMnemonic, const CSecureVector& vchMnemonicPassphrase, bool fUpdateID); + bool SetMnemonic(const SecureString& ssMnemonic, const SecureString& ssMnemonicPassphrase, bool fUpdateID); bool GetMnemonic(CSecureVector& vchMnemonicRet, CSecureVector& vchMnemonicPassphraseRet) const; bool GetMnemonic(SecureString& ssMnemonicRet, SecureString& ssMnemonicPassphraseRet) const; diff --git a/src/support/allocators/secure.h b/src/support/allocators/secure.h index 1ec40fe83003..40fca7bf6a11 100644 --- a/src/support/allocators/secure.h +++ b/src/support/allocators/secure.h @@ -59,4 +59,6 @@ struct secure_allocator : public std::allocator { // This is exactly like std::string, but with a custom allocator. typedef std::basic_string, secure_allocator > SecureString; +typedef std::vector > CSecureVector; + #endif // BITCOIN_SUPPORT_ALLOCATORS_SECURE_H diff --git a/src/test/bip39_tests.cpp b/src/test/bip39_tests.cpp index e78d70540425..fcd149a41354 100644 --- a/src/test/bip39_tests.cpp +++ b/src/test/bip39_tests.cpp @@ -36,24 +36,25 @@ BOOST_AUTO_TEST_CASE(bip39_vectors) std::vector vData = ParseHex(test[0].get_str()); - std::string m = mnemonic_from_data(&vData[0], vData.size()); - std::string mnemonic = test[1].get_str(); + SecureString m = mnemonic_from_data(&vData[0], vData.size()); + std::string strMnemonic = test[1].get_str(); + SecureString mnemonic(strMnemonic.begin(), strMnemonic.end()); // printf("%s\n%s\n", m.c_str(), mnemonic.c_str()); BOOST_CHECK(m == mnemonic); - BOOST_CHECK(mnemonic_check(mnemonic.c_str())); + BOOST_CHECK(mnemonic_check(mnemonic)); - uint8_t seed[64]; - const char *passphrase = "TREZOR"; + CSecureVector seed; + SecureString passphrase = SecureString("TREZOR"); - mnemonic_to_seed(mnemonic.c_str(), passphrase, seed, 0); - // printf("seed: %s\n", HexStr(seed, seed + 64).c_str()); - BOOST_CHECK(HexStr(seed, seed + 64) == test[2].get_str()); + mnemonic_to_seed(mnemonic, passphrase, seed); + // printf("seed: %s\n", HexStr(std::string(seed.begin(), seed.end())).c_str()); + BOOST_CHECK(HexStr(std::string(seed.begin(), seed.end())) == test[2].get_str()); CExtKey key; CExtPubKey pubkey; - key.SetMaster(seed, 64); + key.SetMaster(&seed[0], 64); pubkey = key.Neuter(); CBitcoinExtKey b58key; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 7a9fa5324be0..bcb8dc2a5422 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1413,6 +1413,11 @@ void CWallet::GenerateNewHDChain() if (!SetHDChain(newHdChain, false)) throw std::runtime_error(std::string(__func__) + ": SetHDChain failed"); + + // clean up + mapArgs.erase("-hdseed"); + mapArgs.erase("-mnemonic"); + mapArgs.erase("-mnemonicpassphrase"); } bool CWallet::SetHDChain(const CHDChain& chain, bool memonly) From 1890734bf5cb18d0139625c35303ec726a07c6b1 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Mon, 22 May 2017 15:52:59 +0300 Subject: [PATCH 24/31] code cleanup --- src/keystore.h | 1 - src/wallet/rpcdump.cpp | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/keystore.h b/src/keystore.h index 6fee25c837af..ed103659ecfe 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -110,7 +110,6 @@ class CBasicKeyStore : public CKeyStore virtual bool HaveWatchOnly(const CScript &dest) const; virtual bool HaveWatchOnly() const; - bool AddHDChain(const CHDChain& hdChainIn); bool GetHDChain(CHDChain& hdChainRet) const; }; diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index c9b4834a7d18..91819dff17a7 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -617,8 +617,8 @@ UniValue dumphdinfo(const UniValue& params, bool fHelp) UniValue obj(UniValue::VOBJ); obj.push_back(Pair("hdseed", HexStr(hdChainCurrent.GetSeed()))); - obj.push_back(Pair("mnemonic", std::string(ssMnemonic.begin(), ssMnemonic.end()))); - obj.push_back(Pair("mnemonicpassphrase", std::string(ssMnemonicPassphrase.begin(), ssMnemonicPassphrase.end()))); + obj.push_back(Pair("mnemonic", ssMnemonic.c_str())); + obj.push_back(Pair("mnemonicpassphrase", ssMnemonicPassphrase.c_str())); return obj; } @@ -682,8 +682,8 @@ UniValue dumpwallet(const UniValue& params, bool fHelp) SecureString ssMnemonic; SecureString ssMnemonicPassphrase; hdChainCurrent.GetMnemonic(ssMnemonic, ssMnemonicPassphrase); - file << "# mnemonic: " << std::string(ssMnemonic.begin(), ssMnemonic.end()) << "\n"; - file << "# mnemonic passphrase: " << std::string(ssMnemonicPassphrase.begin(), ssMnemonicPassphrase.end()) << "\n\n"; + file << "# mnemonic: " << ssMnemonic << "\n"; + file << "# mnemonic passphrase: " << ssMnemonicPassphrase << "\n\n"; CSecureVector vchSeed = hdChainCurrent.GetSeed(); file << "# HD seed: " << HexStr(vchSeed) << "\n\n"; From df6009e77ffc4805f236782ac5a1807af82ff0c8 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Mon, 22 May 2017 15:55:34 +0300 Subject: [PATCH 25/31] rename: CSecureVector -> SecureVector --- src/bip39.cpp | 6 +++--- src/bip39.h | 2 +- src/hdchain.cpp | 12 ++++++------ src/hdchain.h | 14 +++++++------- src/support/allocators/secure.h | 2 +- src/test/bip39_tests.cpp | 2 +- src/wallet/crypter.cpp | 22 +++++++++++----------- src/wallet/rpcdump.cpp | 2 +- src/wallet/wallet.cpp | 6 +++--- 9 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/bip39.cpp b/src/bip39.cpp index 18ba4130fdb6..7a93bd86522b 100644 --- a/src/bip39.cpp +++ b/src/bip39.cpp @@ -150,16 +150,16 @@ int mnemonic_check(SecureString mnemonic) } // passphrase must be at most 256 characters or code may crash -void mnemonic_to_seed(SecureString mnemonic, SecureString passphrase, CSecureVector& seedRet) +void mnemonic_to_seed(SecureString mnemonic, SecureString passphrase, SecureVector& seedRet) { SecureString ssSalt = SecureString("mnemonic") + passphrase; - CSecureVector vchSalt(ssSalt.begin(), ssSalt.end()); + SecureVector vchSalt(ssSalt.begin(), ssSalt.end()); // int PKCS5_PBKDF2_HMAC(const char *pass, int passlen, // const unsigned char *salt, int saltlen, int iter, // const EVP_MD *digest, // int keylen, unsigned char *out); uint8_t seed[64]; PKCS5_PBKDF2_HMAC(mnemonic.c_str(), mnemonic.size(), &vchSalt[0], vchSalt.size(), 2048, EVP_sha512(), 64, seed); - seedRet = CSecureVector(seed, seed + 64); + seedRet = SecureVector(seed, seed + 64); memory_cleanse(seed, sizeof(seed)); } diff --git a/src/bip39.h b/src/bip39.h index 5b22a786920f..7639f42e4071 100644 --- a/src/bip39.h +++ b/src/bip39.h @@ -33,6 +33,6 @@ SecureString mnemonic_from_data(const uint8_t *data, int len); int mnemonic_check(SecureString mnemonic); // passphrase must be at most 256 characters or code may crash -void mnemonic_to_seed(SecureString mnemonic, SecureString passphrase, CSecureVector& seedRet); +void mnemonic_to_seed(SecureString mnemonic, SecureString passphrase, SecureVector& seedRet); #endif diff --git a/src/hdchain.cpp b/src/hdchain.cpp index b3875b2557df..554ba0ca03ab 100644 --- a/src/hdchain.cpp +++ b/src/hdchain.cpp @@ -69,7 +69,7 @@ void CHDChain::Debug(std::string strName) const ); } -bool CHDChain::SetMnemonic(const CSecureVector& vchMnemonic, const CSecureVector& vchMnemonicPassphrase, bool fUpdateID) +bool CHDChain::SetMnemonic(const SecureVector& vchMnemonic, const SecureVector& vchMnemonicPassphrase, bool fUpdateID) { return SetMnemonic(SecureString(vchMnemonic.begin(), vchMnemonic.end()), SecureString(vchMnemonicPassphrase.begin(), vchMnemonicPassphrase.end()), fUpdateID); } @@ -98,13 +98,13 @@ bool CHDChain::SetMnemonic(const SecureString& ssMnemonic, const SecureString& s id = GetSeedHash(); } - vchMnemonic = CSecureVector(ssMnemonicTmp.begin(), ssMnemonicTmp.end()); - vchMnemonicPassphrase = CSecureVector(ssMnemonicPassphrase.begin(), ssMnemonicPassphrase.end()); + vchMnemonic = SecureVector(ssMnemonicTmp.begin(), ssMnemonicTmp.end()); + vchMnemonicPassphrase = SecureVector(ssMnemonicPassphrase.begin(), ssMnemonicPassphrase.end()); return !IsNull(); } -bool CHDChain::GetMnemonic(CSecureVector& vchMnemonicRet, CSecureVector& vchMnemonicPassphraseRet) const +bool CHDChain::GetMnemonic(SecureVector& vchMnemonicRet, SecureVector& vchMnemonicPassphraseRet) const { // mnemonic was not set, fail if (vchMnemonic.empty()) @@ -127,7 +127,7 @@ bool CHDChain::GetMnemonic(SecureString& ssMnemonicRet, SecureString& ssMnemonic return true; } -bool CHDChain::SetSeed(const CSecureVector& vchSeedIn, bool fUpdateID) +bool CHDChain::SetSeed(const SecureVector& vchSeedIn, bool fUpdateID) { vchSeed = vchSeedIn; @@ -138,7 +138,7 @@ bool CHDChain::SetSeed(const CSecureVector& vchSeedIn, bool fUpdateID) return !IsNull(); } -CSecureVector CHDChain::GetSeed() const +SecureVector CHDChain::GetSeed() const { return vchSeed; } diff --git a/src/hdchain.h b/src/hdchain.h index c59fc09d379e..ab3ee6ed8edc 100644 --- a/src/hdchain.h +++ b/src/hdchain.h @@ -35,9 +35,9 @@ class CHDChain bool fCrypted; - CSecureVector vchSeed; - CSecureVector vchMnemonic; - CSecureVector vchMnemonicPassphrase; + SecureVector vchSeed; + SecureVector vchMnemonic; + SecureVector vchMnemonicPassphrase; std::map mapAccounts; // critical section to protect mapAccounts @@ -100,13 +100,13 @@ class CHDChain void Debug(std::string strName) const; - bool SetMnemonic(const CSecureVector& vchMnemonic, const CSecureVector& vchMnemonicPassphrase, bool fUpdateID); + bool SetMnemonic(const SecureVector& vchMnemonic, const SecureVector& vchMnemonicPassphrase, bool fUpdateID); bool SetMnemonic(const SecureString& ssMnemonic, const SecureString& ssMnemonicPassphrase, bool fUpdateID); - bool GetMnemonic(CSecureVector& vchMnemonicRet, CSecureVector& vchMnemonicPassphraseRet) const; + bool GetMnemonic(SecureVector& vchMnemonicRet, SecureVector& vchMnemonicPassphraseRet) const; bool GetMnemonic(SecureString& ssMnemonicRet, SecureString& ssMnemonicPassphraseRet) const; - bool SetSeed(const CSecureVector& vchSeedIn, bool fUpdateID); - CSecureVector GetSeed() const; + bool SetSeed(const SecureVector& vchSeedIn, bool fUpdateID); + SecureVector GetSeed() const; uint256 GetID() const { return id; } diff --git a/src/support/allocators/secure.h b/src/support/allocators/secure.h index 40fca7bf6a11..6cf3f4d5a28a 100644 --- a/src/support/allocators/secure.h +++ b/src/support/allocators/secure.h @@ -59,6 +59,6 @@ struct secure_allocator : public std::allocator { // This is exactly like std::string, but with a custom allocator. typedef std::basic_string, secure_allocator > SecureString; -typedef std::vector > CSecureVector; +typedef std::vector > SecureVector; #endif // BITCOIN_SUPPORT_ALLOCATORS_SECURE_H diff --git a/src/test/bip39_tests.cpp b/src/test/bip39_tests.cpp index fcd149a41354..de3e02af9fa5 100644 --- a/src/test/bip39_tests.cpp +++ b/src/test/bip39_tests.cpp @@ -44,7 +44,7 @@ BOOST_AUTO_TEST_CASE(bip39_vectors) BOOST_CHECK(m == mnemonic); BOOST_CHECK(mnemonic_check(mnemonic)); - CSecureVector seed; + SecureVector seed; SecureString passphrase = SecureString("TREZOR"); mnemonic_to_seed(mnemonic, passphrase, seed); diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index d057d4370aee..d3d42c20284a 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -418,12 +418,12 @@ bool CCryptoKeyStore::EncryptHDChain(const CKeyingMaterial& vMasterKeyIn) cryptedHDChain = hdChain; cryptedHDChain.SetCrypted(true); - CSecureVector vchSecureCryptedSeed(vchCryptedSeed.begin(), vchCryptedSeed.end()); + SecureVector vchSecureCryptedSeed(vchCryptedSeed.begin(), vchCryptedSeed.end()); if (!cryptedHDChain.SetSeed(vchSecureCryptedSeed, false)) return false; - CSecureVector vchMnemonic; - CSecureVector vchMnemonicPassphrase; + SecureVector vchMnemonic; + SecureVector vchMnemonicPassphrase; // it's ok to have no mnemonic if wallet was initialized via hdseed if (hdChain.GetMnemonic(vchMnemonic, vchMnemonicPassphrase)) { @@ -435,8 +435,8 @@ bool CCryptoKeyStore::EncryptHDChain(const CKeyingMaterial& vMasterKeyIn) if (!vchMnemonicPassphrase.empty() && !EncryptSecret(vMasterKeyIn, vchMnemonicPassphrase, hdChain.GetID(), vchCryptedMnemonicPassphrase)) return false; - CSecureVector vchSecureCryptedMnemonic(vchCryptedMnemonic.begin(), vchCryptedMnemonic.end()); - CSecureVector vchSecureCryptedMnemonicPassphrase(vchCryptedMnemonicPassphrase.begin(), vchCryptedMnemonicPassphrase.end()); + SecureVector vchSecureCryptedMnemonic(vchCryptedMnemonic.begin(), vchCryptedMnemonic.end()); + SecureVector vchSecureCryptedMnemonicPassphrase(vchCryptedMnemonicPassphrase.begin(), vchCryptedMnemonicPassphrase.end()); if (!cryptedHDChain.SetMnemonic(vchSecureCryptedMnemonic, vchSecureCryptedMnemonicPassphrase, false)) return false; } @@ -458,8 +458,8 @@ bool CCryptoKeyStore::DecryptHDChain(CHDChain& hdChainRet) const if (!cryptedHDChain.IsCrypted()) return false; - CSecureVector vchSecureSeed; - CSecureVector vchSecureCryptedSeed = cryptedHDChain.GetSeed(); + SecureVector vchSecureSeed; + SecureVector vchSecureCryptedSeed = cryptedHDChain.GetSeed(); std::vector vchCryptedSeed(vchSecureCryptedSeed.begin(), vchSecureCryptedSeed.end()); if (!DecryptSecret(vMasterKey, vchCryptedSeed, cryptedHDChain.GetID(), vchSecureSeed)) return false; @@ -472,13 +472,13 @@ bool CCryptoKeyStore::DecryptHDChain(CHDChain& hdChainRet) const if (hdChainRet.GetSeedHash() != cryptedHDChain.GetID()) return false; - CSecureVector vchSecureCryptedMnemonic; - CSecureVector vchSecureCryptedMnemonicPassphrase; + SecureVector vchSecureCryptedMnemonic; + SecureVector vchSecureCryptedMnemonicPassphrase; // it's ok to have no mnemonic if wallet was initialized via hdseed if (cryptedHDChain.GetMnemonic(vchSecureCryptedMnemonic, vchSecureCryptedMnemonicPassphrase)) { - CSecureVector vchSecureMnemonic; - CSecureVector vchSecureMnemonicPassphrase; + SecureVector vchSecureMnemonic; + SecureVector vchSecureMnemonicPassphrase; std::vector vchCryptedMnemonic(vchSecureCryptedMnemonic.begin(), vchSecureCryptedMnemonic.end()); std::vector vchCryptedMnemonicPassphrase(vchSecureCryptedMnemonicPassphrase.begin(), vchSecureCryptedMnemonicPassphrase.end()); diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 91819dff17a7..f2cfba770ee9 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -685,7 +685,7 @@ UniValue dumpwallet(const UniValue& params, bool fHelp) file << "# mnemonic: " << ssMnemonic << "\n"; file << "# mnemonic passphrase: " << ssMnemonicPassphrase << "\n\n"; - CSecureVector vchSeed = hdChainCurrent.GetSeed(); + SecureVector vchSeed = hdChainCurrent.GetSeed(); file << "# HD seed: " << HexStr(vchSeed) << "\n\n"; CExtKey masterKey; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index bcb8dc2a5422..92b6fa38bc90 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1391,7 +1391,7 @@ void CWallet::GenerateNewHDChain() if(mapArgs.count("-hdseed") && IsHex(strSeed)) { std::vector vchSeed = ParseHex(strSeed); - if (!newHdChain.SetSeed(CSecureVector(vchSeed.begin(), vchSeed.end()), true)) + if (!newHdChain.SetSeed(SecureVector(vchSeed.begin(), vchSeed.end()), true)) throw std::runtime_error(std::string(__func__) + ": SetSeed failed"); } else { @@ -1403,8 +1403,8 @@ void CWallet::GenerateNewHDChain() // NOTE: default mnemonic passphrase is an empty string std::string strMnemonicPassphrase = GetArg("-mnemonicpassphrase", ""); - CSecureVector vchMnemonic(strMnemonic.begin(), strMnemonic.end()); - CSecureVector vchMnemonicPassphrase(strMnemonicPassphrase.begin(), strMnemonicPassphrase.end()); + SecureVector vchMnemonic(strMnemonic.begin(), strMnemonic.end()); + SecureVector vchMnemonicPassphrase(strMnemonicPassphrase.begin(), strMnemonicPassphrase.end()); if (!newHdChain.SetMnemonic(vchMnemonic, vchMnemonicPassphrase, true)) throw std::runtime_error(std::string(__func__) + ": SetMnemonic failed"); From 6aad86ee6ee7be1d3e98e9ca1006d27380ef0906 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Mon, 22 May 2017 21:25:13 +0300 Subject: [PATCH 26/31] add missing include --- src/support/allocators/secure.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/support/allocators/secure.h b/src/support/allocators/secure.h index 6cf3f4d5a28a..c57000e44d9c 100644 --- a/src/support/allocators/secure.h +++ b/src/support/allocators/secure.h @@ -9,6 +9,7 @@ #include "support/pagelocker.h" #include +#include // // Allocator that locks its contents from being paged From 8bd79138229cc416030ba2d19d08008a08a64f43 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Mon, 22 May 2017 21:25:40 +0300 Subject: [PATCH 27/31] fix warning in rpcdump.cpp --- src/wallet/rpcdump.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index f2cfba770ee9..e2685c751d56 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -703,7 +703,7 @@ UniValue dumpwallet(const UniValue& params, bool fHelp) b58extpubkey.SetKey(masterPubkey); file << "# extended public masterkey: " << b58extpubkey.ToString() << "\n\n"; - for (int i = 0; i < hdChainCurrent.CountAccounts(); ++i) + for (size_t i = 0; i < hdChainCurrent.CountAccounts(); ++i) { CHDAccount acc; if(hdChainCurrent.GetAccount(i, acc)) { From a44db94ecbadabd2dfb1d4a47359c1e2e49e67c7 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Mon, 22 May 2017 21:26:22 +0300 Subject: [PATCH 28/31] refactor mnemonic_check (also fix a bug) --- src/bip39.cpp | 68 +++++++++++++++++++--------------------- src/test/bip39_tests.cpp | 3 +- 2 files changed, 34 insertions(+), 37 deletions(-) diff --git a/src/bip39.cpp b/src/bip39.cpp index 7a93bd86522b..c6dea035fdd2 100644 --- a/src/bip39.cpp +++ b/src/bip39.cpp @@ -83,70 +83,68 @@ int mnemonic_check(SecureString mnemonic) return 0; } - uint32_t i{}, n{}; + uint32_t nWordCount{}; - while (mnemonic[i]) { + for (size_t i = 0; i < mnemonic.size(); ++i) { if (mnemonic[i] == ' ') { - n++; + nWordCount++; } - i++; } - n++; + nWordCount++; // check number of words - if (n != 12 && n != 18 && n != 24) { + if (nWordCount != 12 && nWordCount != 18 && nWordCount != 24) { return 0; } - char current_word[10]; - uint32_t j, k, ki, bi; + SecureString ssCurrentWord; + uint32_t nWordLetterIndex{}, nWordIndex, ki, nBitsCount{}; uint8_t bits[32 + 1]{}; - i = 0; bi = 0; - while (mnemonic[i]) { - j = 0; - while (mnemonic[i] != ' ' && mnemonic[i] != 0) { - if (j >= sizeof(current_word) - 1) { + for (size_t i = 0; i < mnemonic.size(); ++i) + { + nWordLetterIndex = 0; + ssCurrentWord = ""; + while (i + ssCurrentWord.size() < mnemonic.size() && mnemonic[i + ssCurrentWord.size()] != ' ') { + if (ssCurrentWord.size() >= 9) { return 0; } - current_word[j] = mnemonic[i]; - i++; j++; + ssCurrentWord += mnemonic[i + ssCurrentWord.size()]; } - current_word[j] = 0; - if (mnemonic[i] != 0) i++; - k = 0; + i += ssCurrentWord.size(); + nWordIndex = 0; for (;;) { - if (!wordlist[k]) { // word not found + if (!wordlist[nWordIndex]) { // word not found return 0; } - if (strcmp(current_word, wordlist[k]) == 0) { // word found on index k + if (strcmp(ssCurrentWord.c_str(), wordlist[nWordIndex]) == 0) { // word found on index nWordIndex for (ki = 0; ki < 11; ki++) { - if (k & (1 << (10 - ki))) { - bits[bi / 8] |= 1 << (7 - (bi % 8)); + if (nWordIndex & (1 << (10 - ki))) { + bits[nBitsCount / 8] |= 1 << (7 - (nBitsCount % 8)); } - bi++; + nBitsCount++; } break; } - k++; + nWordIndex++; } } - if (bi != n * 11) { + if (nBitsCount != nWordCount * 11) { return 0; } - bits[32] = bits[n * 4 / 3]; - CSHA256().Write(bits, n * 4 / 3).Finalize(bits); + bits[32] = bits[nWordCount * 4 / 3]; + CSHA256().Write(bits, nWordCount * 4 / 3).Finalize(bits); - int result = 0; - if (n == 12) { - result = (bits[0] & 0xF0) == (bits[32] & 0xF0); // compare first 4 bits + int nResult = 0; + if (nWordCount == 12) { + nResult = (bits[0] & 0xF0) == (bits[32] & 0xF0); // compare first 4 bits } else - if (n == 18) { - result = (bits[0] & 0xFC) == (bits[32] & 0xFC); // compare first 6 bits + if (nWordCount == 18) { + nResult = (bits[0] & 0xFC) == (bits[32] & 0xFC); // compare first 6 bits } else - if (n == 24) { - result = bits[0] == bits[32]; // compare 8 bits + if (nWordCount == 24) { + nResult = bits[0] == bits[32]; // compare 8 bits } memory_cleanse(bits, sizeof(bits)); - return result; + return nResult; } // passphrase must be at most 256 characters or code may crash diff --git a/src/test/bip39_tests.cpp b/src/test/bip39_tests.cpp index de3e02af9fa5..a4af751e9a90 100644 --- a/src/test/bip39_tests.cpp +++ b/src/test/bip39_tests.cpp @@ -45,8 +45,7 @@ BOOST_AUTO_TEST_CASE(bip39_vectors) BOOST_CHECK(mnemonic_check(mnemonic)); SecureVector seed; - SecureString passphrase = SecureString("TREZOR"); - + SecureString passphrase("TREZOR"); mnemonic_to_seed(mnemonic, passphrase, seed); // printf("seed: %s\n", HexStr(std::string(seed.begin(), seed.end())).c_str()); BOOST_CHECK(HexStr(std::string(seed.begin(), seed.end())) == test[2].get_str()); From 20746f0e88a1f06ad172f29c7ab87b5fc0ac4e8e Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Tue, 23 May 2017 03:42:09 +0300 Subject: [PATCH 29/31] move bip39 functions to CMnemonic --- src/bip39.cpp | 10 +++++----- src/bip39.h | 21 +++++++++++---------- src/hdchain.cpp | 6 +++--- src/test/bip39_tests.cpp | 6 +++--- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/bip39.cpp b/src/bip39.cpp index c6dea035fdd2..b22ef6c3535e 100644 --- a/src/bip39.cpp +++ b/src/bip39.cpp @@ -31,19 +31,19 @@ #include -SecureString mnemonic_generate(int strength) +SecureString CMnemonic::Generate(int strength) { if (strength % 32 || strength < 128 || strength > 256) { return SecureString(); } uint8_t data[32]; GetRandBytes(data, 32); - SecureString mnemonic = mnemonic_from_data(data, strength / 8); + SecureString mnemonic = FromData(data, strength / 8); memory_cleanse(data, sizeof(data)); return mnemonic; } -SecureString mnemonic_from_data(const uint8_t *data, int len) +SecureString CMnemonic::FromData(const uint8_t *data, int len) { if (len % 4 || len < 16 || len > 32) { return SecureString(); @@ -77,7 +77,7 @@ SecureString mnemonic_from_data(const uint8_t *data, int len) return mnemonic; } -int mnemonic_check(SecureString mnemonic) +int CMnemonic::Check(SecureString mnemonic) { if (mnemonic.empty()) { return 0; @@ -148,7 +148,7 @@ int mnemonic_check(SecureString mnemonic) } // passphrase must be at most 256 characters or code may crash -void mnemonic_to_seed(SecureString mnemonic, SecureString passphrase, SecureVector& seedRet) +void CMnemonic::ToSeed(SecureString mnemonic, SecureString passphrase, SecureVector& seedRet) { SecureString ssSalt = SecureString("mnemonic") + passphrase; SecureVector vchSalt(ssSalt.begin(), ssSalt.end()); diff --git a/src/bip39.h b/src/bip39.h index 7639f42e4071..60a167371098 100644 --- a/src/bip39.h +++ b/src/bip39.h @@ -21,18 +21,19 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef __BIP39_H__ -#define __BIP39_H__ +#ifndef DASH_BIP39_H +#define DASH_BIP39_H #include "support/allocators/secure.h" -SecureString mnemonic_generate(int strength); // strength in bits - -SecureString mnemonic_from_data(const uint8_t *data, int len); - -int mnemonic_check(SecureString mnemonic); - -// passphrase must be at most 256 characters or code may crash -void mnemonic_to_seed(SecureString mnemonic, SecureString passphrase, SecureVector& seedRet); +class CMnemonic +{ +public: + static SecureString Generate(int strength); // strength in bits + static SecureString FromData(const uint8_t *data, int len); + static int Check(SecureString mnemonic); + // passphrase must be at most 256 characters or code may crash + static void ToSeed(SecureString mnemonic, SecureString passphrase, SecureVector& seedRet); +}; #endif diff --git a/src/hdchain.cpp b/src/hdchain.cpp index 554ba0ca03ab..e861e5bba7c9 100644 --- a/src/hdchain.cpp +++ b/src/hdchain.cpp @@ -85,16 +85,16 @@ bool CHDChain::SetMnemonic(const SecureString& ssMnemonic, const SecureString& s // empty mnemonic i.e. "generate a new one" if (ssMnemonic.empty()) { - ssMnemonicTmp = mnemonic_generate(256); + ssMnemonicTmp = CMnemonic::Generate(256); } // NOTE: default mnemonic passphrase is an empty string // printf("mnemonic: %s\n", ssMnemonicTmp.c_str()); - if (!mnemonic_check(ssMnemonicTmp)) { + if (!CMnemonic::Check(ssMnemonicTmp)) { throw std::runtime_error(std::string(__func__) + ": invalid mnemonic: `" + std::string(ssMnemonicTmp.c_str()) + "`"); } - mnemonic_to_seed(ssMnemonicTmp, ssMnemonicPassphrase, vchSeed); + CMnemonic::ToSeed(ssMnemonicTmp, ssMnemonicPassphrase, vchSeed); id = GetSeedHash(); } diff --git a/src/test/bip39_tests.cpp b/src/test/bip39_tests.cpp index a4af751e9a90..b643b81e10b5 100644 --- a/src/test/bip39_tests.cpp +++ b/src/test/bip39_tests.cpp @@ -36,17 +36,17 @@ BOOST_AUTO_TEST_CASE(bip39_vectors) std::vector vData = ParseHex(test[0].get_str()); - SecureString m = mnemonic_from_data(&vData[0], vData.size()); + SecureString m = CMnemonic::FromData(&vData[0], vData.size()); std::string strMnemonic = test[1].get_str(); SecureString mnemonic(strMnemonic.begin(), strMnemonic.end()); // printf("%s\n%s\n", m.c_str(), mnemonic.c_str()); BOOST_CHECK(m == mnemonic); - BOOST_CHECK(mnemonic_check(mnemonic)); + BOOST_CHECK(CMnemonic::Check(mnemonic)); SecureVector seed; SecureString passphrase("TREZOR"); - mnemonic_to_seed(mnemonic, passphrase, seed); + CMnemonic::ToSeed(mnemonic, passphrase, seed); // printf("seed: %s\n", HexStr(std::string(seed.begin(), seed.end())).c_str()); BOOST_CHECK(HexStr(std::string(seed.begin(), seed.end())) == test[2].get_str()); From 4301b3649fd30491a38fb1bfc761254755381c4a Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Fri, 26 May 2017 18:56:12 +0300 Subject: [PATCH 30/31] Few fixes for CMnemonic: - use `SecureVector` for data, bits, seed - `Check` should return bool --- src/bip39.cpp | 65 +++++++++++++++++++++------------------- src/bip39.h | 4 +-- src/test/bip39_tests.cpp | 3 +- 3 files changed, 38 insertions(+), 34 deletions(-) diff --git a/src/bip39.cpp b/src/bip39.cpp index b22ef6c3535e..21e34f5a1799 100644 --- a/src/bip39.cpp +++ b/src/bip39.cpp @@ -36,26 +36,30 @@ SecureString CMnemonic::Generate(int strength) if (strength % 32 || strength < 128 || strength > 256) { return SecureString(); } - uint8_t data[32]; - GetRandBytes(data, 32); + SecureVector data; + data.resize(32); + GetRandBytes(&data[0], 32); SecureString mnemonic = FromData(data, strength / 8); - memory_cleanse(data, sizeof(data)); return mnemonic; } -SecureString CMnemonic::FromData(const uint8_t *data, int len) +// SecureString CMnemonic::FromData(const uint8_t *data, int len) +SecureString CMnemonic::FromData(const SecureVector& data, int len) { if (len % 4 || len < 16 || len > 32) { return SecureString(); } - uint8_t bits[32 + 1]; + SecureVector checksum; + checksum.resize(32); + CSHA256().Write(&data[0], len).Finalize(&checksum[0]); - CSHA256().Write(data, len).Finalize(bits); - // checksum - bits[len] = bits[0]; // data - memcpy(bits, data, len); + SecureVector bits; + bits.resize(len); + memcpy(&bits[0], &data[0], len); + // checksum + bits.push_back(checksum[0]); int mlen = len * 3 / 4; SecureString mnemonic; @@ -72,15 +76,14 @@ SecureString CMnemonic::FromData(const uint8_t *data, int len) mnemonic += ' '; } } - memory_cleanse(bits, sizeof(bits)); return mnemonic; } -int CMnemonic::Check(SecureString mnemonic) +bool CMnemonic::Check(SecureString mnemonic) { if (mnemonic.empty()) { - return 0; + return false; } uint32_t nWordCount{}; @@ -93,19 +96,21 @@ int CMnemonic::Check(SecureString mnemonic) nWordCount++; // check number of words if (nWordCount != 12 && nWordCount != 18 && nWordCount != 24) { - return 0; + return false; } SecureString ssCurrentWord; - uint32_t nWordLetterIndex{}, nWordIndex, ki, nBitsCount{}; - uint8_t bits[32 + 1]{}; + SecureVector bits; + bits.resize(32 + 1); + + uint32_t nWordIndex, ki, nBitsCount{}; + for (size_t i = 0; i < mnemonic.size(); ++i) { - nWordLetterIndex = 0; ssCurrentWord = ""; while (i + ssCurrentWord.size() < mnemonic.size() && mnemonic[i + ssCurrentWord.size()] != ' ') { if (ssCurrentWord.size() >= 9) { - return 0; + return false; } ssCurrentWord += mnemonic[i + ssCurrentWord.size()]; } @@ -113,9 +118,9 @@ int CMnemonic::Check(SecureString mnemonic) nWordIndex = 0; for (;;) { if (!wordlist[nWordIndex]) { // word not found - return 0; + return false; } - if (strcmp(ssCurrentWord.c_str(), wordlist[nWordIndex]) == 0) { // word found on index nWordIndex + if (ssCurrentWord == wordlist[nWordIndex]) { // word found on index nWordIndex for (ki = 0; ki < 11; ki++) { if (nWordIndex & (1 << (10 - ki))) { bits[nBitsCount / 8] |= 1 << (7 - (nBitsCount % 8)); @@ -128,23 +133,23 @@ int CMnemonic::Check(SecureString mnemonic) } } if (nBitsCount != nWordCount * 11) { - return 0; + return false; } bits[32] = bits[nWordCount * 4 / 3]; - CSHA256().Write(bits, nWordCount * 4 / 3).Finalize(bits); + CSHA256().Write(&bits[0], nWordCount * 4 / 3).Finalize(&bits[0]); - int nResult = 0; + bool fResult = 0; if (nWordCount == 12) { - nResult = (bits[0] & 0xF0) == (bits[32] & 0xF0); // compare first 4 bits + fResult = (bits[0] & 0xF0) == (bits[32] & 0xF0); // compare first 4 bits } else if (nWordCount == 18) { - nResult = (bits[0] & 0xFC) == (bits[32] & 0xFC); // compare first 6 bits + fResult = (bits[0] & 0xFC) == (bits[32] & 0xFC); // compare first 6 bits } else if (nWordCount == 24) { - nResult = bits[0] == bits[32]; // compare 8 bits + fResult = bits[0] == bits[32]; // compare 8 bits } - memory_cleanse(bits, sizeof(bits)); - return nResult; + + return fResult; } // passphrase must be at most 256 characters or code may crash @@ -152,12 +157,10 @@ void CMnemonic::ToSeed(SecureString mnemonic, SecureString passphrase, SecureVec { SecureString ssSalt = SecureString("mnemonic") + passphrase; SecureVector vchSalt(ssSalt.begin(), ssSalt.end()); + seedRet.resize(64); // int PKCS5_PBKDF2_HMAC(const char *pass, int passlen, // const unsigned char *salt, int saltlen, int iter, // const EVP_MD *digest, // int keylen, unsigned char *out); - uint8_t seed[64]; - PKCS5_PBKDF2_HMAC(mnemonic.c_str(), mnemonic.size(), &vchSalt[0], vchSalt.size(), 2048, EVP_sha512(), 64, seed); - seedRet = SecureVector(seed, seed + 64); - memory_cleanse(seed, sizeof(seed)); + PKCS5_PBKDF2_HMAC(mnemonic.c_str(), mnemonic.size(), &vchSalt[0], vchSalt.size(), 2048, EVP_sha512(), 64, &seedRet[0]); } diff --git a/src/bip39.h b/src/bip39.h index 60a167371098..4d74383817ee 100644 --- a/src/bip39.h +++ b/src/bip39.h @@ -30,8 +30,8 @@ class CMnemonic { public: static SecureString Generate(int strength); // strength in bits - static SecureString FromData(const uint8_t *data, int len); - static int Check(SecureString mnemonic); + static SecureString FromData(const SecureVector& data, int len); + static bool Check(SecureString mnemonic); // passphrase must be at most 256 characters or code may crash static void ToSeed(SecureString mnemonic, SecureString passphrase, SecureVector& seedRet); }; diff --git a/src/test/bip39_tests.cpp b/src/test/bip39_tests.cpp index b643b81e10b5..79c9ba464839 100644 --- a/src/test/bip39_tests.cpp +++ b/src/test/bip39_tests.cpp @@ -35,8 +35,9 @@ BOOST_AUTO_TEST_CASE(bip39_vectors) } std::vector vData = ParseHex(test[0].get_str()); + SecureVector data(vData.begin(), vData.end()); - SecureString m = CMnemonic::FromData(&vData[0], vData.size()); + SecureString m = CMnemonic::FromData(data, data.size()); std::string strMnemonic = test[1].get_str(); SecureString mnemonic(strMnemonic.begin(), strMnemonic.end()); From c209dc8f9c012e87e39d8f356d215265cf85c76e Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Fri, 26 May 2017 19:41:19 +0300 Subject: [PATCH 31/31] init vectors with desired size where possible --- src/bip39.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/bip39.cpp b/src/bip39.cpp index 21e34f5a1799..608d4d9433c4 100644 --- a/src/bip39.cpp +++ b/src/bip39.cpp @@ -36,8 +36,7 @@ SecureString CMnemonic::Generate(int strength) if (strength % 32 || strength < 128 || strength > 256) { return SecureString(); } - SecureVector data; - data.resize(32); + SecureVector data(32); GetRandBytes(&data[0], 32); SecureString mnemonic = FromData(data, strength / 8); return mnemonic; @@ -50,13 +49,11 @@ SecureString CMnemonic::FromData(const SecureVector& data, int len) return SecureString(); } - SecureVector checksum; - checksum.resize(32); + SecureVector checksum(32); CSHA256().Write(&data[0], len).Finalize(&checksum[0]); // data - SecureVector bits; - bits.resize(len); + SecureVector bits(len); memcpy(&bits[0], &data[0], len); // checksum bits.push_back(checksum[0]); @@ -100,8 +97,7 @@ bool CMnemonic::Check(SecureString mnemonic) } SecureString ssCurrentWord; - SecureVector bits; - bits.resize(32 + 1); + SecureVector bits(32 + 1); uint32_t nWordIndex, ki, nBitsCount{};