diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f487596184a..575064f653ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -448,6 +448,7 @@ set(COMMON_SOURCES ./src/amount.cpp ./src/base58.cpp ./src/bip38.cpp + ./src/bip39.cpp ./src/chainparams.cpp ./src/coins.cpp ./src/compressor.cpp @@ -459,6 +460,7 @@ set(COMMON_SOURCES ./src/core_read.cpp ./src/core_write.cpp ./src/hash.cpp + ./src/hdchain.cpp ./src/invalid.cpp ./src/key.cpp ./src/keystore.cpp diff --git a/src/Makefile.am b/src/Makefile.am index 3c3d63644c62..594f157d212d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -82,6 +82,8 @@ BITCOIN_CORE_H = \ allocators.h \ amount.h \ base58.h \ + bip39.h \ + bip39_english.h \ bip38.h \ bloom.h \ blocksignature.h \ @@ -111,6 +113,7 @@ BITCOIN_CORE_H = \ obfuscation-relay.h \ wallet/db.h \ hash.h \ + hdchain.h \ httprpc.h \ httpserver.h \ init.h \ @@ -363,6 +366,7 @@ libbitcoin_common_a_SOURCES = \ allocators.cpp \ amount.cpp \ base58.cpp \ + bip39.cpp \ bip38.cpp \ chainparams.cpp \ coins.cpp \ @@ -375,6 +379,7 @@ libbitcoin_common_a_SOURCES = \ core_read.cpp \ core_write.cpp \ hash.cpp \ + hdchain.cpp \ invalid.cpp \ key.cpp \ keystore.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 3deabbe18ce8..44ac8fe7658f 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -13,6 +13,7 @@ JSON_TEST_FILES = \ test/data/sig_noncanonical.json \ test/data/base58_encode_decode.json \ test/data/base58_keys_invalid.json \ + test/data/bip39_vectors.json \ test/data/script_invalid.json \ test/data/tx_invalid.json \ test/data/tx_valid.json \ @@ -39,6 +40,7 @@ BITCOIN_TESTS =\ test/base32_tests.cpp \ test/base58_tests.cpp \ test/base64_tests.cpp \ + test/bip39_tests.cpp \ test/budget_tests.cpp \ test/checkblock_tests.cpp \ test/Checkpoints_tests.cpp \ diff --git a/src/allocators.h b/src/allocators.h index 068d356273d1..9f093c02229c 100644 --- a/src/allocators.h +++ b/src/allocators.h @@ -260,7 +260,7 @@ struct zero_after_free_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 > SecureVector; // Byte-vector that clears its contents before deletion. typedef std::vector > CSerializeData; diff --git a/src/bip39.cpp b/src/bip39.cpp new file mode 100644 index 000000000000..2557a694509f --- /dev/null +++ b/src/bip39.cpp @@ -0,0 +1,162 @@ +/** + * 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 "bip39.h" +#include "bip39_english.h" +#include "crypto/sha256.h" +#include "random.h" + +#include + +SecureString CMnemonic::Generate(int strength) +{ + if (strength % 32 || strength < 128 || strength > 256) { + return SecureString(); + } + SecureVector data(32); + GetStrongRandBytes(&data[0], 32); + SecureString mnemonic = FromData(data, strength / 8); + return mnemonic; +} + +// 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(); + } + + SecureVector checksum(32); + CSHA256().Write(&data[0], len).Finalize(&checksum[0]); + + // data + SecureVector bits(len); + memcpy(&bits[0], &data[0], len); + // checksum + bits.push_back(checksum[0]); + + int mlen = len * 3 / 4; + SecureString mnemonic; + + int i, j, idx; + 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; + } + mnemonic.append(wordlist[idx]); + if (i < mlen - 1) { + mnemonic += ' '; + } + } + + return mnemonic; +} + +bool CMnemonic::Check(SecureString mnemonic) +{ + if (mnemonic.empty()) { + return false; + } + + uint32_t nWordCount{}; + + for (size_t i = 0; i < mnemonic.size(); ++i) { + if (mnemonic[i] == ' ') { + nWordCount++; + } + } + nWordCount++; + // check number of words + if (nWordCount != 12 && nWordCount != 18 && nWordCount != 24) { + return false; + } + + SecureString ssCurrentWord; + SecureVector bits(32 + 1); + + uint32_t nWordIndex, ki, nBitsCount{}; + + for (size_t i = 0; i < mnemonic.size(); ++i) + { + ssCurrentWord = ""; + while (i + ssCurrentWord.size() < mnemonic.size() && mnemonic[i + ssCurrentWord.size()] != ' ') { + if (ssCurrentWord.size() >= 9) { + return false; + } + ssCurrentWord += mnemonic[i + ssCurrentWord.size()]; + } + i += ssCurrentWord.size(); + nWordIndex = 0; + for (;;) { + if (!wordlist[nWordIndex]) { // word not found + return false; + } + 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)); + } + nBitsCount++; + } + break; + } + nWordIndex++; + } + } + if (nBitsCount != nWordCount * 11) { + return false; + } + bits[32] = bits[nWordCount * 4 / 3]; + CSHA256().Write(&bits[0], nWordCount * 4 / 3).Finalize(&bits[0]); + + bool fResult = 0; + if (nWordCount == 12) { + fResult = (bits[0] & 0xF0) == (bits[32] & 0xF0); // compare first 4 bits + } else + if (nWordCount == 18) { + fResult = (bits[0] & 0xFC) == (bits[32] & 0xFC); // compare first 6 bits + } else + if (nWordCount == 24) { + fResult = bits[0] == bits[32]; // compare 8 bits + } + + return fResult; +} + +// passphrase must be at most 256 characters or code may crash +void CMnemonic::ToSeed(SecureString mnemonic, SecureString passphrase, SecureVector& seedRet) +{ + 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); + PKCS5_PBKDF2_HMAC(mnemonic.c_str(), mnemonic.size(), &vchSalt[0], vchSalt.size(), 2048, EVP_sha512(), 64, &seedRet[0]); +} \ No newline at end of file diff --git a/src/bip39.h b/src/bip39.h new file mode 100644 index 000000000000..bfe9b072b4c1 --- /dev/null +++ b/src/bip39.h @@ -0,0 +1,39 @@ +/** + * 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 PIVX_BIP39_H +#define PIVX_BIP39_H + +#include "allocators.h" + +class CMnemonic +{ +public: + static SecureString Generate(int strength); // strength in bits + 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); +}; + +#endif \ No newline at end of file diff --git a/src/bip39_english.h b/src/bip39_english.h new file mode 100644 index 000000000000..3f004d878a11 --- /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, +}; \ No newline at end of file diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 7f5d4ea71c8a..87b3fe4627c3 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -268,7 +268,7 @@ class CMainParams : public CChainParams base58Prefixes[EXT_PUBLIC_KEY] = boost::assign::list_of(0x02)(0x2D)(0x25)(0x33).convert_to_container >(); base58Prefixes[EXT_SECRET_KEY] = boost::assign::list_of(0x02)(0x21)(0x31)(0x2B).convert_to_container >(); // BIP44 coin type is from https://github.com/satoshilabs/slips/blob/master/slip-0044.md - base58Prefixes[EXT_COIN_TYPE] = boost::assign::list_of(0x80)(0x00)(0x00)(0x77).convert_to_container >(); + nExtCoinType = 119; convertSeed6(vFixedSeeds, pnSeed6_main, ARRAYLEN(pnSeed6_main)); @@ -396,7 +396,7 @@ class CTestNetParams : public CMainParams // Testnet pivx BIP32 prvkeys start with 'DRKP' base58Prefixes[EXT_SECRET_KEY] = boost::assign::list_of(0x3a)(0x80)(0x58)(0x37).convert_to_container >(); // Testnet pivx 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; convertSeed6(vFixedSeeds, pnSeed6_test, ARRAYLEN(pnSeed6_test)); @@ -489,7 +489,6 @@ class CRegTestParams : public CTestNetParams vFixedSeeds.clear(); //! Testnet mode doesn't have any fixed seeds. vSeeds.clear(); //! Testnet mode doesn't have any DNS seeds. - fMiningRequiresPeers = false; fAllowMinDifficultyBlocks = true; fDefaultConsistencyChecks = true; diff --git a/src/chainparams.h b/src/chainparams.h index c91a78d12169..ebc7b3fee7ea 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -40,7 +40,6 @@ class CChainParams SECRET_KEY, // BIP16 EXT_PUBLIC_KEY, // BIP32 EXT_SECRET_KEY, // BIP32 - EXT_COIN_TYPE, // BIP44 STAKING_ADDRESS, MAX_BASE58_TYPES @@ -103,6 +102,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; } virtual const Checkpoints::CCheckpointData& Checkpoints() const = 0; int PoolMaxTransactions() const { return nPoolMaxTransactions; } @@ -199,6 +199,7 @@ class CChainParams int nMinerThreads; std::vector vSeeds; std::vector base58Prefixes[MAX_BASE58_TYPES]; + int nExtCoinType; CBaseChainParams::Network networkID; std::string strNetworkID; CBlock genesis; diff --git a/src/crypter.cpp b/src/crypter.cpp index 9e81264a8c11..e99470b7e98b 100644 --- a/src/crypter.cpp +++ b/src/crypter.cpp @@ -248,9 +248,22 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn) LogPrintf("The wallet is probably corrupted: Some keys decrypt but not all."); throw std::runtime_error("Error unlocking wallet: some keys decrypt but not all. Your wallet file may be corrupt."); } - 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 + CHDChain hdChainTmp; + if (DecryptHDChain(hdChainTmp)) { + // make sure seed matches this chain + chainPass = cryptedHDChain.GetID() == hdChainTmp.GetSeedHash(); + } + if (!chainPass) { + vMasterKey.clear(); + return false; + } + } fDecryptionThoroughlyChecked = true; uint256 hashSeed; @@ -275,7 +288,144 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn) NotifyStatusChanged(this); return true; } +bool CCryptoKeyStore::EncryptHDChain(const CKeyingMaterial& vMasterKeyIn) +{ + // should call EncryptKeys first + if (!IsCrypted()) + return false; + + if (!cryptedHDChain.IsNull()) + return true; + + if (cryptedHDChain.IsCrypted()) + return true; + + // make sure seed matches this chain + if (hdChain.GetID() != hdChain.GetSeedHash()) + return false; + + std::vector vchCryptedSeed; + if (!EncryptSecret(vMasterKeyIn, hdChain.GetSeed(), hdChain.GetID(), vchCryptedSeed)) + return false; + + hdChain.Debug(__func__); + cryptedHDChain = hdChain; + cryptedHDChain.SetCrypted(true); + + SecureVector vchSecureCryptedSeed(vchCryptedSeed.begin(), vchCryptedSeed.end()); + if (!cryptedHDChain.SetSeed(vchSecureCryptedSeed, false)) + return false; + + SecureVector vchMnemonic; + SecureVector 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() && !EncryptSecret(vMasterKeyIn, vchMnemonic, hdChain.GetID(), vchCryptedMnemonic)) + return false; + if (!vchMnemonicPassphrase.empty() && !EncryptSecret(vMasterKeyIn, vchMnemonicPassphrase, hdChain.GetID(), vchCryptedMnemonicPassphrase)) + return false; + + SecureVector vchSecureCryptedMnemonic(vchCryptedMnemonic.begin(), vchCryptedMnemonic.end()); + SecureVector vchSecureCryptedMnemonicPassphrase(vchCryptedMnemonicPassphrase.begin(), vchCryptedMnemonicPassphrase.end()); + if (!cryptedHDChain.SetMnemonic(vchSecureCryptedMnemonic, vchSecureCryptedMnemonicPassphrase, false)) + return false; + } + + if (!hdChain.SetNull()) + return false; + + return true; +} + +bool CCryptoKeyStore::DecryptHDChain(CHDChain& hdChainRet) const +{ + if (!IsCrypted()) + return true; + + if (cryptedHDChain.IsNull()) + return false; + + if (!cryptedHDChain.IsCrypted()) + return false; + + SecureVector vchSecureSeed; + SecureVector vchSecureCryptedSeed = cryptedHDChain.GetSeed(); + std::vector vchCryptedSeed(vchSecureCryptedSeed.begin(), vchSecureCryptedSeed.end()); + if (!DecryptSecret(vMasterKey, vchCryptedSeed, cryptedHDChain.GetID(), vchSecureSeed)) + return false; + + hdChainRet = cryptedHDChain; + if (!hdChainRet.SetSeed(vchSecureSeed, false)) + return false; + + // hash of decrypted seed must match chain id + if (hdChainRet.GetSeedHash() != cryptedHDChain.GetID()) + return false; + + SecureVector vchSecureCryptedMnemonic; + SecureVector vchSecureCryptedMnemonicPassphrase; + + // it's ok to have no mnemonic if wallet was initialized via hdseed + if (cryptedHDChain.GetMnemonic(vchSecureCryptedMnemonic, vchSecureCryptedMnemonicPassphrase)) { + SecureVector vchSecureMnemonic; + SecureVector vchSecureMnemonicPassphrase; + + std::vector vchCryptedMnemonic(vchSecureCryptedMnemonic.begin(), vchSecureCryptedMnemonic.end()); + std::vector vchCryptedMnemonicPassphrase(vchSecureCryptedMnemonicPassphrase.begin(), vchSecureCryptedMnemonicPassphrase.end()); + + if (!vchCryptedMnemonic.empty() && !DecryptSecret(vMasterKey, vchCryptedMnemonic, cryptedHDChain.GetID(), vchSecureMnemonic)) + return false; + if (!vchCryptedMnemonicPassphrase.empty() && !DecryptSecret(vMasterKey, vchCryptedMnemonicPassphrase, cryptedHDChain.GetID(), vchSecureMnemonicPassphrase)) + return false; + + if (!hdChainRet.SetMnemonic(vchSecureMnemonic, vchSecureMnemonicPassphrase, false)) + return false; + } + + hdChainRet.SetCrypted(false); + hdChainRet.Debug(__func__); + + return true; +} + +bool CCryptoKeyStore::SetHDChain(const CHDChain& chain) +{ + if (IsCrypted()) + return false; + if (chain.IsCrypted()) + return false; + + hdChain = chain; + return true; +} + +bool CCryptoKeyStore::SetCryptedHDChain(const CHDChain& chain) +{ + if (!SetCrypted()) + return false; + + if (!chain.IsCrypted()) + return false; + + cryptedHDChain = chain; + return true; +} + +bool CCryptoKeyStore::GetHDChain(CHDChain& hdChainRet) const +{ + if(IsCrypted()) { + hdChainRet = cryptedHDChain; + return !cryptedHDChain.IsNull(); + } + + hdChainRet = hdChain; + return !hdChain.IsNull(); +} bool CCryptoKeyStore::AddKeyPubKey(const CKey& key, const CPubKey& pubkey) { { diff --git a/src/crypter.h b/src/crypter.h index e613bee31c11..eb23fe5d6699 100644 --- a/src/crypter.h +++ b/src/crypter.h @@ -122,6 +122,8 @@ bool DecryptAES256(const SecureString& sKey, const std::string& sCiphertext, con class CCryptoKeyStore : public CBasicKeyStore { private: + CHDChain cryptedHDChain; + CKeyingMaterial vMasterKey; //! if fUseCrypto is true, mapKeys must be empty @@ -136,7 +138,10 @@ class CCryptoKeyStore : public CBasicKeyStore //! will encrypt previously unencrypted keys bool EncryptKeys(CKeyingMaterial& vMasterKeyIn); - + bool EncryptHDChain(const CKeyingMaterial& vMasterKeyIn); + bool DecryptHDChain(CHDChain& hdChainRet) const; + bool SetHDChain(const CHDChain& chain); + bool SetCryptedHDChain(const CHDChain& chain); bool Unlock(const CKeyingMaterial& vMasterKeyIn); CryptedKeyMap mapCryptedKeys; @@ -195,6 +200,7 @@ class CCryptoKeyStore : public CBasicKeyStore bool GetDeterministicSeed(const uint256& hashSeed, uint256& seed); bool AddDeterministicSeed(const uint256& seed); + bool GetHDChain(CHDChain& hdChainRet) const; /** diff --git a/src/hdchain.cpp b/src/hdchain.cpp new file mode 100644 index 000000000000..c006f251e828 --- /dev/null +++ b/src/hdchain.cpp @@ -0,0 +1,211 @@ +// Copyright (c) 2014-2017 The Dash Core developers +// Distributed under the MIT software license, see the accompanying + +#include "base58.h" +#include "chainparams.h" +#include "hdchain.h" +#include "tinyformat.h" +#include "util.h" +#include "utilstrencodings.h" + +bool CHDChain::SetNull() +{ + LOCK(cs_accounts); + nVersion = CURRENT_VERSION; + id = uint256(); + fCrypted = false; + vchSeed.clear(); + vchMnemonic.clear(); + vchMnemonicPassphrase.clear(); + mapAccounts.clear(); + // default blank account + mapAccounts.insert(std::pair(0, CHDAccount())); + return IsNull(); +} + +bool CHDChain::IsNull() const +{ + return vchSeed.empty() || id == uint256(); +} + +void CHDChain::SetCrypted(bool fCryptedIn) +{ + fCrypted = fCryptedIn; +} + +bool CHDChain::IsCrypted() const +{ + return fCrypted; +} + +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; + } + ); +} + +bool CHDChain::SetMnemonic(const SecureVector& vchMnemonic, const SecureVector& vchMnemonicPassphrase, bool fUpdateID) +{ + 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; + + // empty mnemonic i.e. "generate a new one" + if (ssMnemonic.empty()) { + ssMnemonicTmp = CMnemonic::Generate(256); + } + // NOTE: default mnemonic passphrase is an empty string + + // printf("mnemonic: %s\n", ssMnemonicTmp.c_str()); + if (!CMnemonic::Check(ssMnemonicTmp)) { + throw std::runtime_error(std::string(__func__) + ": invalid mnemonic: `" + std::string(ssMnemonicTmp.c_str()) + "`"); + } + + CMnemonic::ToSeed(ssMnemonicTmp, ssMnemonicPassphrase, vchSeed); + id = GetSeedHash(); + } + + vchMnemonic = SecureVector(ssMnemonicTmp.begin(), ssMnemonicTmp.end()); + vchMnemonicPassphrase = SecureVector(ssMnemonicPassphrase.begin(), ssMnemonicPassphrase.end()); + + return !IsNull(); +} + +bool CHDChain::GetMnemonic(SecureVector& vchMnemonicRet, SecureVector& vchMnemonicPassphraseRet) const +{ + // mnemonic was not set, fail + if (vchMnemonic.empty()) + return false; + + vchMnemonicRet = vchMnemonic; + vchMnemonicPassphraseRet = vchMnemonicPassphrase; + return true; +} + +bool CHDChain::GetMnemonic(SecureString& ssMnemonicRet, SecureString& ssMnemonicPassphraseRet) const +{ + // mnemonic was not set, fail + if (vchMnemonic.empty()) + return false; + + ssMnemonicRet = SecureString(vchMnemonic.begin(), vchMnemonic.end()); + ssMnemonicPassphraseRet = SecureString(vchMnemonicPassphrase.begin(), vchMnemonicPassphrase.end()); + + return true; +} + +bool CHDChain::SetSeed(const SecureVector& vchSeedIn, bool fUpdateID) +{ + vchSeed = vchSeedIn; + + if (fUpdateID) { + id = GetSeedHash(); + } + + return !IsNull(); +} + +SecureVector CHDChain::GetSeed() const +{ + return vchSeed; +} + +uint256 CHDChain::GetSeedHash() +{ + return Hash(vchSeed.begin(), vchSeed.end()); +} + +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 + 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) + + // 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, 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, 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(), nAccountIndex, nChangeIndex, extPubKey.nChild); +} \ No newline at end of file diff --git a/src/hdchain.h b/src/hdchain.h new file mode 100644 index 000000000000..6a246c647a2b --- /dev/null +++ b/src/hdchain.h @@ -0,0 +1,153 @@ +// Copyright (c) 2014-2017 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +#ifndef PIVX_HDCHAIN_H +#define PIVX_HDCHAIN_H + +#include "key.h" +#include "sync.h" +#include "bip39.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; + + SecureVector vchSeed; + SecureVector vchMnemonic; + SecureVector vchMnemonicPassphrase; + + std::map mapAccounts; + // critical section to protect mapAccounts + mutable CCriticalSection cs_accounts; + +public: + + 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(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(); + bool IsNull() const; + + void SetCrypted(bool fCryptedIn); + bool IsCrypted() const; + + void Debug(std::string strName) const; + + bool SetMnemonic(const SecureVector& vchMnemonic, const SecureVector& vchMnemonicPassphrase, bool fUpdateID); + bool SetMnemonic(const SecureString& ssMnemonic, const SecureString& ssMnemonicPassphrase, bool fUpdateID); + bool GetMnemonic(SecureVector& vchMnemonicRet, SecureVector& vchMnemonicPassphraseRet) const; + bool GetMnemonic(SecureString& ssMnemonicRet, SecureString& ssMnemonicPassphraseRet) const; + + bool SetSeed(const SecureVector& vchSeedIn, bool fUpdateID); + SecureVector GetSeed() const; + + uint256 GetID() const { return id; } + + uint256 GetSeedHash(); + 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 +{ +private: + static const int CURRENT_VERSION = 1; + int nVersion; + +public: + CExtPubKey extPubKey; + uint256 hdchainID; + uint32_t nAccountIndex; + uint32_t nChangeIndex; + + CHDPubKey() : nVersion(CHDPubKey::CURRENT_VERSION), nAccountIndex(0), nChangeIndex(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(nAccountIndex); + READWRITE(nChangeIndex); + } + + std::string GetKeyPath() const; +}; + +#endif // PIVX_HDCHAIN_H \ No newline at end of file diff --git a/src/init.cpp b/src/init.cpp index 91ecb3f1e211..05cf784be93a 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -469,6 +469,10 @@ 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)"), 1)); strUsage += HelpMessageOpt("-maxtxfee=", strprintf(_("Maximum total fees to use in a single wallet transaction, setting too low may abort large transactions (default: %s)"), FormatMoney(maxTxFee))); + 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("-walletnotify=", _("Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)")); @@ -831,7 +835,11 @@ bool AppInit2() fPrintToConsole = GetBoolArg("-printtoconsole", false); fLogTimestamps = GetBoolArg("-logtimestamps", true); fLogIPs = GetBoolArg("-logips", false); - + 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 -hdseed\n", __func__); + } if (mapArgs.count("-bind") || mapArgs.count("-whitebind")) { // when specifying an explicit binding address, you want to listen on it // even when -connect or -proxy is specified @@ -1671,6 +1679,15 @@ bool AppInit2() if (fFirstRun) { // Create new keyUser and set as default key + 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(); + // ensure this wallet.dat can only be opened by clients supporting HD + pwalletMain->SetMinVersion(FEATURE_HD); + } + CPubKey newDefaultKey; // Top up the keypool if (!pwalletMain->TopUpKeyPool()) { @@ -1681,6 +1698,18 @@ bool AppInit2() pwalletMain->SetBestChain(chainActive.GetLocator()); } + 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)); + } + + // 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("Init errors: %s\n", strErrors.str()); LogPrintf("Wallet completed loading in %15dms\n", GetTimeMillis() - nWalletStartTime); @@ -1928,9 +1957,15 @@ bool AppInit2() 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); + 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)) diff --git a/src/key.h b/src/key.h index d56ff962844d..bf43ddd34773 100644 --- a/src/key.h +++ b/src/key.h @@ -170,6 +170,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 = BIP32_EXTKEY_SIZE; + ::WriteCompactSize(s, len); + unsigned char code[BIP32_EXTKEY_SIZE]; + 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[BIP32_EXTKEY_SIZE]; + 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/keystore.cpp b/src/keystore.cpp index d3466c48d4cf..a1e6b4a507b0 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -87,6 +87,12 @@ bool CBasicKeyStore::HaveWatchOnly() const return (!setWatchOnly.empty()); } +bool CBasicKeyStore::GetHDChain(CHDChain& hdChainRet) const +{ + hdChainRet = hdChain; + return !hdChain.IsNull(); +} + bool CBasicKeyStore::AddMultiSig(const CScript& dest) { LOCK(cs_KeyStore); diff --git a/src/keystore.h b/src/keystore.h index 4032e3904ce7..2e18d3a55418 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -10,6 +10,7 @@ #include "key.h" #include "pubkey.h" #include "sync.h" +#include "hdchain.h" #include @@ -65,6 +66,7 @@ class CBasicKeyStore : public CKeyStore KeyMap mapKeys; ScriptMap mapScripts; WatchOnlySet setWatchOnly; + CHDChain hdChain; /* the HD chain data model*/ MultiSigScriptSet setMultiSig; public: @@ -81,6 +83,7 @@ class CBasicKeyStore : public CKeyStore virtual bool RemoveWatchOnly(const CScript& dest); virtual bool HaveWatchOnly(const CScript& dest) const; virtual bool HaveWatchOnly() const; + bool GetHDChain(CHDChain& hdChainRet) const; virtual bool AddMultiSig(const CScript& dest); virtual bool RemoveMultiSig(const CScript& dest); diff --git a/src/miner.cpp b/src/miner.cpp index a990b1c236b6..615c606e4b71 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -532,8 +532,7 @@ CBlockTemplate* CreateNewBlockWithKey(CReserveKey& reservekey, CWallet* pwallet) static int nLastPOWBlock = Params().LAST_POW_BLOCK(); // If we're building a late PoW block, don't continue - // PoS blocks are built directly with CreateNewBlock - if ((nHeightNext > nLastPOWBlock)) { + if (nHeightNext > nLastPOWBlock) { LogPrintf("%s: Aborting PoW block creation during PoS phase\n", __func__); // sleep 1/2 a block time so we don't go into a tight loop. MilliSleep((Params().TargetSpacing() * 1000) >> 1); diff --git a/src/pubkey.cpp b/src/pubkey.cpp index a82f2b0c33aa..29657e066929 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -250,7 +250,7 @@ bool CPubKey::Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChi return true; } -void CExtPubKey::Encode(unsigned char code[74]) const +void CExtPubKey::Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const { code[0] = nDepth; memcpy(code+1, vchFingerprint, 4); @@ -261,7 +261,7 @@ void CExtPubKey::Encode(unsigned char code[74]) const memcpy(code+41, pubkey.begin(), CPubKey::COMPRESSED_PUBLIC_KEY_SIZE); } -void CExtPubKey::Decode(const unsigned char code[74]) +void CExtPubKey::Decode(const unsigned char code[BIP32_EXTKEY_SIZE]) { nDepth = code[0]; memcpy(vchFingerprint, code+1, 4); diff --git a/src/pubkey.h b/src/pubkey.h index 4eaa7470aef8..4f365b287aa4 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -225,7 +225,29 @@ struct CExtPubKey { void Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const; void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]); bool Derive(CExtPubKey& out, unsigned int nChild) const; - + unsigned int GetSerializeSize(int nType, int nVersion) const + { + return BIP32_EXTKEY_SIZE+1; //add one byte for the size (compact int) + } + template + void Serialize(Stream& s, int nType, int nVersion) const + { + unsigned int len = BIP32_EXTKEY_SIZE; + ::WriteCompactSize(s, len); + unsigned char code[BIP32_EXTKEY_SIZE]; + 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[BIP32_EXTKEY_SIZE]; + if (len != BIP32_EXTKEY_SIZE) + throw std::runtime_error("Invalid extended key size\n"); + s.read((char *)&code[0], len); + Decode(code); + } void Serialize(CSizeComputer& s) const { // Optimized implementation for ::GetSerializeSize that avoids copying. diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index f085299da66a..522aa0f02964 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -153,7 +153,7 @@ UniValue getinfo(const UniValue& params, bool fHelp) #ifdef ENABLE_WALLET if (pwalletMain) { 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 (pwalletMain && pwalletMain->IsCrypted()) obj.push_back(Pair("unlocked_until", nWalletUnlockTime)); @@ -366,6 +366,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" + " \"hdchainid\" : \"\" (string, optional) The ID of the HD chain\n" "}\n" "\nExamples:\n" + @@ -398,6 +400,13 @@ 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; + 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("hdchainid", hdChainCurrent.GetID().GetHex())); + } #endif } return ret; diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 02ba51a28849..4469df0db0dd 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -396,7 +396,9 @@ static const CRPCCommand vRPCCommands[] = {"wallet", "backupwallet", &backupwallet, true, false, true}, {"wallet", "delegatestake", &delegatestake, false, false, true}, {"wallet", "dumpprivkey", &dumpprivkey, true, false, true}, + {"wallet", "dumphdinfo", &dumphdinfo, true, false, true}, {"wallet", "dumpwallet", &dumpwallet, true, false, true}, + {"wallet", "dumpwallethd", &dumpwallethd, true, false, true}, {"wallet", "bip38encrypt", &bip38encrypt, true, false, true}, {"wallet", "bip38decrypt", &bip38decrypt, true, false, true}, {"wallet", "encryptwallet", &encryptwallet, true, false, true}, diff --git a/src/rpc/server.h b/src/rpc/server.h index e527b9a9e5fb..91105bb999f4 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -195,7 +195,9 @@ extern UniValue clearbanned(const UniValue& params, bool fHelp); extern UniValue dumpprivkey(const UniValue& params, bool fHelp); // in rpcdump.cpp extern UniValue importprivkey(const UniValue& params, bool fHelp); extern UniValue importaddress(const UniValue& params, bool fHelp); +extern UniValue dumphdinfo(const UniValue& params, bool fHelp); extern UniValue dumpwallet(const UniValue& params, bool fHelp); +extern UniValue dumpwallethd(const UniValue& params, bool fHelp); extern UniValue importwallet(const UniValue& params, bool fHelp); extern UniValue bip38encrypt(const UniValue& params, bool fHelp); extern UniValue bip38decrypt(const UniValue& params, bool fHelp); diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 9fb1066d46bc..e3a4a3fb9e31 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -331,6 +331,11 @@ CScript GetScriptForDestination(const CTxDestination& dest) return script; } +CScript GetScriptForRawPubKey(const CPubKey& pubKey) +{ + return CScript() << std::vector(pubKey.begin(), pubKey.end()) << OP_CHECKSIG; +} + CScript GetScriptForStakeDelegation(const CKeyID& stakingKey, const CKeyID& spendingKey) { CScript script; diff --git a/src/script/standard.h b/src/script/standard.h index ed898e591cdf..96a348c80359 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -93,6 +93,6 @@ bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std:: CScript GetScriptForDestination(const CTxDestination& dest); CScript GetScriptForMultisig(int nRequired, const std::vector& keys); +CScript GetScriptForRawPubKey(const CPubKey& pubKey); CScript GetScriptForStakeDelegation(const CKeyID& stakingKey, const CKeyID& spendingKey); - #endif // BITCOIN_SCRIPT_STANDARD_H diff --git a/src/test/bip32_tests.cpp b/src/test/bip32_tests.cpp index 911e3c15633d..ac3fa239bf93 100644 --- a/src/test/bip32_tests.cpp +++ b/src/test/bip32_tests.cpp @@ -106,6 +106,21 @@ void RunTest(const TestVector &test) { } key = keyNew; pubkey = pubkeyNew; + CDataStream ssPub(SER_DISK, CLIENT_VERSION); + ssPub << pubkeyNew; + BOOST_CHECK(ssPub.size() == BIP32_EXTKEY_SIZE+1); + + CDataStream ssPriv(SER_DISK, CLIENT_VERSION); + ssPriv << keyNew; + BOOST_CHECK(ssPriv.size() == BIP32_EXTKEY_SIZE+1); + + CExtPubKey pubCheck; + CExtKey privCheck; + ssPub >> pubCheck; + ssPriv >> privCheck; + + BOOST_CHECK(pubCheck == pubkeyNew); + BOOST_CHECK(privCheck == keyNew); } } diff --git a/src/test/bip39_tests.cpp b/src/test/bip39_tests.cpp new file mode 100644 index 000000000000..31e3d1f7cb9c --- /dev/null +++ b/src/test/bip39_tests.cpp @@ -0,0 +1,69 @@ +// 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_pivx.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; + } + + std::vector vData = ParseHex(test[0].get_str()); + SecureVector data(vData.begin(), vData.end()); + + SecureString m = CMnemonic::FromData(data, data.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(CMnemonic::Check(mnemonic)); + + SecureVector seed; + SecureString passphrase("TREZOR"); + 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()); + + CExtKey key; + CExtPubKey pubkey; + + key.SetMaster(&seed[0], 64); + pubkey = key.Neuter(); + + CBitcoinExtKey b58key; + b58key.SetKey(key); + printf("CBitcoinExtKey: %s\n", b58key.ToString().c_str()); + printf("testKey: %s\n", test[3].get_str().c_str()); + + BOOST_CHECK(b58key.ToString() == test[3].get_str()); + } +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index c58610ecbd50..90dd63975800 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -19,6 +19,7 @@ #include #include +#include BOOST_FIXTURE_TEST_SUITE(crypto_tests, BasicTestingSetup) @@ -263,7 +264,28 @@ BOOST_AUTO_TEST_CASE(hmac_sha512_testvectors) { "e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944" "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"); +} void TestRFC6979(const std::string& hexkey, const std::string& hexmsg, const std::vector& hexout) { std::vector key = ParseHex(hexkey); diff --git a/src/test/data/bip39_vectors.json b/src/test/data/bip39_vectors.json new file mode 100644 index 000000000000..6d753da878e1 --- /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", + "TDt9EWvD5T5T44hAatc3DkJpKCCPhGkyqPsjd5ruhbxZ5GUsrAVyEFcVZ4PYWUk2TqnMtPtgJD4jL53k1gcg8YkJL8opjx8RtDsoFUDo14EtGNC" + ], + [ + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + "legal winner thank year wave sausage worth useful legal winner thank yellow", + "2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6fa457fe1296106559a3c80937a1c1069be3a3a5bd381ee6260e8d9739fce1f607", + "TDt9EWvD5T5T44hAZsiW1o8UdttgDghQnDERfzifWyhuELHRhbArVSum3TEkdgJaKoQCw2saMAc3JSFWWJoVKSGEHzKqDDJaVS2ZEiTvkj4Trbr" + ], + [ + "80808080808080808080808080808080", + "letter advice cage absurd amount doctor acoustic avoid letter advice cage above", + "d71de856f81a8acc65e6fc851a38d4d7ec216fd0796d0a6827a3ad6ed5511a30fa280f12eb2e47ed2ac03b5c462a0358d18d69fe4f985ec81778c1b370b652a8", + "TDt9EWvD5T5T44hAa5G3PDiYh7DS9tXQcDxFR6ssHFfeEKpspoGkxyjwMB9hT3Z3PnTZ1h1MvU6G7b4iUjiKsJMEqJpxWja7jdLnMiAfcijc4Ca" + ], + [ + "ffffffffffffffffffffffffffffffff", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong", + "ac27495480225222079d7be181583751e86f571027b0497b5b5d11218e0a8a13332572917f0f8e5a589620c6f15b11c61dee327651a14c34e18231052e48c069", + "TDt9EWvD5T5T44hAZgdBp9eaUdCHnjy97mjaLkvwhQFjGXY4gXSRuXoepBPYbuBv3fkU3giDD3LBcTDByZ9cgCC6TtiThtiEsX15ntxDt7vQoib" + ], + [ + "000000000000000000000000000000000000000000000000", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent", + "035895f2f481b1b0f01fcf8c289c794660b289981a78f8106447707fdd9666ca06da5a9a565181599b79f53b844d8a71dd9f439c52a3d7b3e8a79c906ac845fa", + "TDt9EWvD5T5T44hAaxnbsBQqu6LTbYy6A1PN1bTnaFv5YGxkx5VBGfHmYUfsFGYbnRPAAjJUGRd32vV9SDyzuF4EfEVxmc5vWumjiMP2KnVNB8Z" + ], + [ + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will", + "f2b94508732bcbacbcc020faefecfc89feafa6649a5491b8c952cede496c214a0c7b3c392d168748f2d4a612bada0753b52a1c7ac53c1e93abd5c6320b9e95dd", + "TDt9EWvD5T5T44hAaYUXMkvxMADQ9zhjop6UbpwLLx1bbCjttMfGxHLDqRtMS27gqg451sXtNGzWQkPPGYFTE6TdsDvbQo8ULKWYzrprA4Qxs6u" + ], + [ + "808080808080808080808080808080808080808080808080", + "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter always", + "107d7c02a5aa6f38c58083ff74f04c607c2d2c0ecc55501dadd72d025b751bc27fe913ffb796f841c49b1d33b610cf0e91d3aa239027f5e99fe4ce9e5088cd65", + "TDt9EWvD5T5T44hAagwacABiEtBHEY8fCoDjUkVpUGZRXvE6o3WBjqJQnmJd2b2xXr7AA5q2fKzLVkjwRTFT5SUTvvYm69xCoQVieEstFRcLiT2" + ], + [ + "ffffffffffffffffffffffffffffffffffffffffffffffff", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when", + "0cd6e5d827bb62eb8fc1e262254223817fd068a74b5b449cc2f667c3f1f985a76379b43348d952e2265b4cd129090758b3e3c2c49103b5051aac2eaeb890a528", + "TDt9EWvD5T5T44hAaHjB5vseqkfrJK3nFwRDQbW5CughzCVk6f9pJz2XWrmnympKNzUH3ijoEKEf4ccRdFdZnusGNCx81jQsRRccQx3U5va4Bn8" + ], + [ + "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", + "TDt9EWvD5T5T44hAaEPZat4XWQtcRb15DSnP2FdSvDxLzmLjZ2m74uSmbT3qyvbRRG9EfUbUMJ61XpKzBUWdzB9dTcYFHqLFHuCCynoLpnz1AcX" + ], + [ + "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", + "TDt9EWvD5T5T44hAajaFdE7jFDNUvg1q6foTwavjAZgAEheBu7Jp1XMXCWe44BELBA3wgKXPGKnRQA5LvYcxpdaipKjvJh7xbbD3u9y4qcjjo24" + ], + [ + "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", + "TDt9EWvD5T5T44hAaQ1AQa8RPRFkFZ1M8LfbydsXg8rxwxi9fmu2wASVYPCnekGehh216yc6U4RWx9gUz7ro9T9RpVCQRFR4PEAFKpZfLe1Xmrb" + ], + [ + "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", + "TDt9EWvD5T5T44hAZhod1J7MRWSzHYW31yADNhBUcBrNY81NzLhEvkKrvDxz4C24kVQCQVWjtWQwfq8EiQbmYZkSHEFtEkVJdAD4ZmVFkE3gmvP" + ], + [ + "9e885d952ad362caeb4efe34a8e91bd2", + "ozone drill grab fiber curtain grace pudding thank cruise elder eight picnic", + "274ddc525802f7c828d8ef7ddbcdc5304e87ac3535913611fbbfa986d0c9e5476c91689f9c8a54fd55bd38606aa6a8595ad213d4c9c9f9aca3fb217069a41028", + "TDt9EWvD5T5T44hAa17Xt5mnAs197UbgtowcGXzCGuWUtKdDoNe7hGCDHwnqVjPX9CgtvGgXu6vcKxChUuPyoc15cNwNF1p2GUiE84wRKybTHuQ" + ], + [ + "6610b25967cdcca9d59875f5cb50b0ea75433311869e930b", + "gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow fog", + "628c3827a8823298ee685db84f55caa34b5cc195a778e52d45f59bcf75aba68e4d7590e101dc414bc1bbd5737666fbbef35d1f1903953b66624f910feef245ac", + "TDt9EWvD5T5T44hAb71WecQApoJYwVaZKEhAgpaqrRf4nkr4f1bkPF11wX3FDisTuRvdovj39U91fDx3wNXpMrity58VKPkk5pDtW621bvY3U5L" + ], + [ + "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", + "TDt9EWvD5T5T44hAZj1YiBTHAQoCMVjVAwvLjzJENDDkTaEnj6bnPUfL4PsbwjU5cvg9jeLpzkMfxuypVQjx8Hcsf8G5Dh5KBMKe2wYXb7EbojZ" + ], + [ + "c0ba5a8e914111210f2bd131f3d5e08d", + "scheme spot photo card baby mountain device kick cradle pact join borrow", + "ea725895aaae8d4c1cf682c1bfd2d358d52ed9f0f0591131b559e2724bb234fca05aa9c02c57407e04ee9dc3b454aa63fbff483a8b11de949624b9f1831a9612", + "TDt9EWvD5T5T44hAaTP2s9p3UygTLhKiN7pZYaXc3ZwieVchqQxEWrTmRyyGneg1JGnSsbfuTn4ANQWDdDmcNt4thjA2Pv9odnbB2cwenaLHk9c" + ], + [ + "6d9be1ee6ebd27a258115aad99b7317b9c8d28b6d76431c3", + "horn tenant knee talent sponsor spell gate clip pulse soap slush warm silver nephew swap uncle crack brave", + "fd579828af3da1d32544ce4db5c73d53fc8acc4ddb1e3b251a31179cdb71e853c56d2fcb11aed39898ce6c34b10b5382772db8796e52837b54468aeb312cfc3d", + "TDt9EWvD5T5T44hAacZpfh9ngwvBbwtZ2fiE3vAKnxBkeKpKyjv2GN2PtJgCvCTGJedkvPs13eihotf97Ti1Egn2VhexLtw9XU9dxwybxPRagzN" + ], + [ + "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", + "TDt9EWvD5T5T44hAZhwAKyU7GcxwDpCa1kKyZtgJtZoHQMtxDKmPsG4iBciaRUWFqFMXfUPK2gLNmmmi1WeVosqXuTNyGb7SsYtC9aUaMLNsMPd" + ], + [ + "23db8160a31d3e0dca3688ed941adbf3", + "cat swing flag economy stadium alone churn speed unique patch report train", + "deb5f45449e615feff5640f2e49f933ff51895de3b4381832b3139941c57b59205a42480c52175b6efcffaa58a2503887c1e8b363a707256bdd2b587b46541f5", + "TDt9EWvD5T5T44hAbTaWoxrb9jfPdPvp5QtT5YS5iF62TPUvTCY4VKJQr5bQwnTNEerKB1QazXrfVJhm9sMJH2qCAE7PCpNojvRVkPHcHMvBs8s" + ], + [ + "8197a4a47f0425faeaa69deebc05ca29c0a5b5cc76ceacc0", + "light rule cinnamon wrap drastic word pride squirrel upgrade then income fatal apart sustain crack supply proud access", + "4cbdff1ca2db800fd61cae72a57475fdc6bab03e441fd63f96dabd1f183ef5b782925f00105f318309a7e9c3ea6967c7801e46c8a58082674c860a37b93eda02", + "TDt9EWvD5T5T44hAb9TFvjiZNdza3URue2mYMMFEfAMZAYNqq1XLTkzm2Db9Xjn3xaV6Nm9Q5Q6pXLJYeH3XivpPTeU8TjcrU6L1piZyUCxXmze" + ], + [ + "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", + "TDt9EWvD5T5T44hAb3o3qdwHkcr5rS5JcCkREKxi3rQ1cdaf6Vz8P1Din9wSbqKWfnHKV5fpjKWm49kway9hNAx8sDRYY2z4nXYbPTMjiaZjoyX" + ], + [ + "f30f8c1da665478f49b001d94c5fc452", + "vessel ladder alter error federal sibling chat ability sun glass valve picture", + "2aaa9242daafcee6aa9d7269f17d4efe271e1b9a529178d7dc139cd18747090bf9d60295d0ce74309a78852a9caadf0af48aae1c6253839624076224374bc63f", + "TDt9EWvD5T5T44hAZc4s9iNMqzHgpKZ4QhkrjcuJrVKxWR6QGsKUVYECPQyJiwfj56duSpBxg39XJyc4JcCkWFzemWfiqzSv2XNZx5dZdjAHYjo" + ], + [ + "c10ec20dc3cd9f652c7fac2f1230f7a3c828389a14392f05", + "scissors invite lock maple supreme raw rapid void congress muscle digital elegant little brisk hair mango congress clump", + "7b4a10be9d98e6cba265566db7f136718e1398c71cb581e1b2f464cac1ceedf4f3e274dc270003c670ad8d02c4558b2f8e39edea2775c9e232c7cb798b069e88", + "TDt9EWvD5T5T44hAbmnoaEC4T8Vjj4ubQZEg4C7LAZjoZ3vjDhcBEjjuDegRSFnUGcmNcHTTd6R6DMhtTQWo1VaAsZDGT9jAeNiqeM2QBk2E4kr" + ], + [ + "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", + "TDt9EWvD5T5T44hAaMRAQWM2ALS6K6xi2gyS8Fri9JSJjTkqC5VTtxtdmCLd89G8EbPNKbSNH7g1J1hYCzZsbotdBAqGbkESHaH9y1narBksvjo" + ] + ] \ No newline at end of file diff --git a/src/test/rpc_wallet_tests.cpp b/src/test/rpc_wallet_tests.cpp index ec484c2dfc9a..7e062e286f46 100644 --- a/src/test/rpc_wallet_tests.cpp +++ b/src/test/rpc_wallet_tests.cpp @@ -70,7 +70,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet) LOCK2(cs_main, pwalletMain->cs_wallet); - CPubKey demoPubkey = pwalletMain->GenerateNewKey(); + CPubKey demoPubkey = pwalletMain->GenerateNewKey(0); CBitcoinAddress demoAddress = CBitcoinAddress(CTxDestination(demoPubkey.GetID())); UniValue retValue; std::string strAccount = "walletDemoAccount"; @@ -83,7 +83,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet) walletdb.WriteAccount(strAccount, account); }); - CPubKey setaccountDemoPubkey = pwalletMain->GenerateNewKey(); + CPubKey setaccountDemoPubkey = pwalletMain->GenerateNewKey(0); CBitcoinAddress setaccountDemoAddress = CBitcoinAddress(CTxDestination(setaccountDemoPubkey.GetID())); /********************************* diff --git a/src/util.h b/src/util.h index 00912870ec54..d5ab5759833b 100644 --- a/src/util.h +++ b/src/util.h @@ -29,7 +29,15 @@ #include #include #include // for boost::thread_interrupted - +// Debugging macros +// Uncomment the following line to enable debugging messages +// or enable on a per file basis prior to inclusion of util.h +//#define ENABLE_PIVX_DEBUG +#ifdef ENABLE_PIVX_DEBUG +#define DBG( x ) x +#else +#define DBG( x ) +#endif //PIVX only features extern bool fMasterNode; diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 248ef45bd5d4..efb40628abb0 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -363,6 +363,48 @@ UniValue dumpprivkey(const UniValue& params, bool fHelp) return CBitcoinSecret(vchSecret).ToString(); } +UniValue dumphdinfo(const UniValue& params, bool fHelp) +{ + if (pwalletMain == NULL) + return NullUniValue; + + if (fHelp || params.size() != 0) + throw std::runtime_error( + "dumphdinfo\n" + "Returns an object containing sensitive private info about this HD wallet.\n" + "\nResult:\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("dumphdinfo", "") + + HelpExampleRpc("dumphdinfo", "") + ); + + LOCK(pwalletMain->cs_wallet); + + EnsureWalletIsUnlocked(); + + CHDChain hdChainCurrent; + if (!pwalletMain->GetHDChain(hdChainCurrent)) + throw JSONRPCError(RPC_WALLET_ERROR, "This wallet is not a HD wallet."); + + if (!pwalletMain->GetDecryptedHDChain(hdChainCurrent)) + throw JSONRPCError(RPC_INTERNAL_ERROR, "Cannot decrypt HD seed"); + + 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", ssMnemonic.c_str())); + obj.push_back(Pair("mnemonicpassphrase", ssMnemonicPassphrase.c_str())); + + return obj; +} UniValue dumpwallet(const UniValue& params, bool fHelp) { @@ -434,6 +476,104 @@ UniValue dumpwallet(const UniValue& params, bool fHelp) return reply; } +UniValue dumpwallethd(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw std::runtime_error( + "dumpwallethd \"filename\"\n" + "\nDumps all wallet keys in a human-readable format.\n" + + HelpRequiringPassphrase() + "\n" + + "\nArguments:\n" + "1. \"filename\" (string, required) The filename\n" + + "\nExamples:\n" + + HelpExampleCli("dumpwallethd", "\"test\"") + HelpExampleRpc("dumpwallethd", "\"test\"")); + + LOCK2(cs_main, pwalletMain->cs_wallet); + EnsureWalletIsUnlocked(); + boost::filesystem::path filepath = params[0].get_str().c_str(); + filepath = boost::filesystem::absolute(filepath); + std::ofstream file; + file.open(params[0].get_str().c_str()); + if (!file.is_open()) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); + + std::map mapKeyBirth; + std::set setKeyPool; + pwalletMain->GetKeyBirthTimes(mapKeyBirth); + pwalletMain->GetAllReserveKeys(setKeyPool); + // sort time/key pairs + std::vector > vKeyBirth; + for (std::map::const_iterator it = mapKeyBirth.begin(); it != mapKeyBirth.end(); it++) { + vKeyBirth.push_back(std::make_pair(it->second, it->first)); + } + mapKeyBirth.clear(); + std::sort(vKeyBirth.begin(), vKeyBirth.end()); + // produce output + file << strprintf("# Wallet dump created by PIVX %s (%s)\n", CLIENT_BUILD, CLIENT_DATE); + file << strprintf("# * Created on %s\n", EncodeDumpTime(GetTime())); + 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 + CHDChain hdChainCurrent; + if (pwalletMain->GetHDChain(hdChainCurrent)) + { + if (!pwalletMain->GetDecryptedHDChain(hdChainCurrent)) + throw JSONRPCError(RPC_INTERNAL_ERROR, "Cannot decrypt HD chain"); + + SecureString ssMnemonic; + SecureString ssMnemonicPassphrase; + hdChainCurrent.GetMnemonic(ssMnemonic, ssMnemonicPassphrase); + file << "# mnemonic: " << ssMnemonic << "\n"; + file << "# mnemonic passphrase: " << ssMnemonicPassphrase << "\n\n"; + SecureVector vchSeed = hdChainCurrent.GetSeed(); + file << "# HD seed: " << HexStr(vchSeed) << "\n\n"; + CExtKey masterKey; + masterKey.SetMaster(&vchSeed[0], vchSeed.size()); + CBitcoinExtKey b58extkey; + b58extkey.SetKey(masterKey); + file << "# extended private masterkey: " << b58extkey.ToString() << "\n"; + CExtPubKey masterPubkey; + masterPubkey = masterKey.Neuter(); + CBitcoinExtPubKey b58extpubkey; + b58extpubkey.SetKey(masterPubkey); + file << "# extended public masterkey: " << b58extpubkey.ToString() << "\n\n"; + for (size_t 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++) { + 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("label=%s", EncodeDumpString(pwalletMain->mapAddressBook[keyid].name)); + } else if (setKeyPool.count(keyid)) { + file << "reserve=1"; + } else { + file << strprintf(" # addr=%s%s\n", strAddr, (pwalletMain->mapHdPubKeys.count(keyid) ? " hdkeypath="+pwalletMain->mapHdPubKeys[keyid].GetKeyPath() : "")); + } + } + } + file << "\n"; + file << "# End of dump\n"; + file.close(); + UniValue reply(UniValue::VOBJ); + reply.push_back(Pair("filename", filepath.string())); + return reply; +} UniValue bip38encrypt(const UniValue& params, bool fHelp) { if (fHelp || params.size() != 2) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index d4ae6ca55dc7..f9606795c9f1 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -371,7 +371,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(); @@ -2285,7 +2285,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; @@ -2505,7 +2505,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; pivx server stopping, restart to run with encrypted wallet. The keypool has been flushed, you need to make a new backup."; + return "Wallet encrypted; pivx 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) @@ -2718,7 +2718,17 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp) " \"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" " \"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 PIV/kB\n" + " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in PIV/kB\n" + " \"automintaddresses\": status (boolean) the status of automint addresses (true if enabled, false if disabled)\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" + @@ -2726,6 +2736,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()))); @@ -2737,10 +2749,33 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp) obj.push_back(Pair("immature_cold_staking_balance", ValueFromAmount(pwalletMain->GetImmatureColdStakingBalance()))); 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()))); + } + if (pwalletMain->IsCrypted()) obj.push_back(Pair("unlocked_until", nWalletUnlockTime)); obj.push_back(Pair("paytxfee", ValueFromAmount(payTxFee.GetFeePerK()))); + if (fHDEnabled) { + 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 27356a316960..6e742e26b8fa 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -15,6 +15,7 @@ #include "net.h" #include "primitives/transaction.h" #include "script/script.h" +#include "script/standard.h" #include "script/sign.h" #include "spork.h" #include "stakeinput.h" @@ -131,29 +132,40 @@ PairResult CWallet::getNewAddress(CBitcoinAddress& ret, const std::string addres return PairResult(true); } -CPubKey CWallet::GenerateNewKey() +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 CKey secret; - secret.MakeNewKey(fCompressed); - // Compressed public keys were introduced in version 0.6.0 - if (fCompressed) - SetMinVersion(FEATURE_COMPRPUBKEY); - - CPubKey pubkey = secret.GetPubKey(); - assert(secret.VerifyPubKey(pubkey)); // Create new metadata int64_t nCreationTime = GetTime(); - mapKeyMetadata[pubkey.GetID()] = CKeyMetadata(nCreationTime); - if (!nTimeFirstKey || nCreationTime < nTimeFirstKey) - nTimeFirstKey = nCreationTime; + CKeyMetadata metadata(nCreationTime); + + CPubKey pubkey; + // use HD key derivation if HD was enabled during wallet creation + if (IsHDEnabled()) { + DeriveNewChildKey(metadata, secret, nAccountIndex, fInternal); + pubkey = secret.GetPubKey(); + } else { + secret.MakeNewKey(fCompressed); - if (!AddKeyPubKey(secret, pubkey)) - throw std::runtime_error("CWallet::GenerateNewKey() : AddKey failed"); + // Compressed public keys were introduced in version 0.6.0 + if (fCompressed) + SetMinVersion(FEATURE_COMPRPUBKEY); + + pubkey = secret.GetPubKey(); + assert(secret.VerifyPubKey(pubkey)); + // Create new metadata + mapKeyMetadata[pubkey.GetID()] = metadata; + if (!nTimeFirstKey || nCreationTime < nTimeFirstKey) + nTimeFirstKey = nCreationTime; + + if (!AddKeyPubKey(secret, pubkey)) + throw std::runtime_error(std::string(__func__) + ": AddKey failed"); + } return pubkey; } @@ -174,6 +186,154 @@ int64_t CWallet::GetKeyCreationTime(const CBitcoinAddress& address) return 0; } +void CWallet::DeriveNewChildKey(const CKeyMetadata& metadata, CKey& secretRet, uint32_t nAccountIndex, bool fInternal) +{ + CHDChain hdChainTmp; + if (!GetHDChain(hdChainTmp)) { + throw std::runtime_error(std::string(__func__) + ": GetHDChain failed"); + } + + if (!DecryptHDChain(hdChainTmp)) + throw std::runtime_error(std::string(__func__) + ": DecryptHDChainSeed failed"); + // make sure seed matches this chain + 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 nChildIndex = fInternal ? acc.nInternalChainCounter : acc.nExternalChainCounter; + do { + hdChainTmp.DeriveChildExtKey(nAccountIndex, fInternal, nChildIndex, childKey); + // increment childkey index + nChildIndex++; + } while (HaveKey(childKey.key.GetPubKey().GetID())); + secretRet = childKey.key; + + CPubKey pubkey = secretRet.GetPubKey(); + assert(secretRet.VerifyPubKey(pubkey)); + + // store metadata + mapKeyMetadata[pubkey.GetID()] = metadata; + if (!nTimeFirstKey || metadata.nCreateTime < nTimeFirstKey) + nTimeFirstKey = metadata.nCreateTime; + + // update the chain model in the database + CHDChain hdChainCurrent; + GetHDChain(hdChainCurrent); + + if (fInternal) { + acc.nInternalChainCounter = nChildIndex; + } + else { + 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"); + } + else { + if (!SetHDChain(hdChainCurrent, false)) + throw std::runtime_error(std::string(__func__) + ": SetHDChain failed"); + } + + if (!AddHDPubKey(childKey.Neuter(), fInternal)) + 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()) + { + // if the key has been found in mapHdPubKeys, derive it on the fly + const CHDPubKey &hdPubKey = (*mi).second; + CHDChain hdChainCurrent; + if (!GetHDChain(hdChainCurrent)) + throw std::runtime_error(std::string(__func__) + ": GetHDChain failed"); + if (!DecryptHDChain(hdChainCurrent)) + throw std::runtime_error(std::string(__func__) + ": DecryptHDChainSeed failed"); + // make sure seed matches this chain + if (hdChainCurrent.GetID() != hdChainCurrent.GetSeedHash()) + throw std::runtime_error(std::string(__func__) + ": Wrong HD chain!"); + + CExtKey extkey; + hdChainCurrent.DeriveChildExtKey(hdPubKey.nAccountIndex, hdPubKey.nChangeIndex != 0, hdPubKey.extPubKey.nChild, extkey); + keyOut = extkey.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, bool fInternal) +{ + AssertLockHeld(cs_wallet); + + CHDChain hdChainCurrent; + GetHDChain(hdChainCurrent); + + CHDPubKey hdPubKey; + hdPubKey.extPubKey = extPubKey; + hdPubKey.hdchainID = hdChainCurrent.GetID(); + hdPubKey.nChangeIndex = fInternal ? 1 : 0; + 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) { AssertLockHeld(cs_wallet); // mapKeyMetadata @@ -643,6 +803,9 @@ 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) { @@ -653,7 +816,21 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) // die and let the user reload their unencrypted wallet. assert(false); } - + if (!hdChainCurrent.IsNull()) { + assert(EncryptHDChain(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.GetID() == hdChainCrypted.GetID()); + assert(hdChainCurrent.GetSeedHash() != hdChainCrypted.GetSeedHash()); + + assert(SetCryptedHDChain(hdChainCrypted, false)); + } // Encryption was introduced in version 0.4.0 SetMinVersion(FEATURE_WALLETCRYPT, pwalletdbEncryption, true); @@ -671,7 +848,13 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) Lock(); Unlock(strWalletPassphrase); - NewKeyPool(); + // if we are not using HD, generate new keypool + if(IsHDEnabled()) { + TopUpKeyPool(); + } + else { + NewKeyPool(); + } Lock(); // Need to completely rewrite the wallet file; if we don't, bdb might keep @@ -682,7 +865,105 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) return true; } +void CWallet::GenerateNewHDChain() +{ + CHDChain newHdChain; + + std::string strSeed = GetArg("-hdseed", "not hex"); + + if(mapArgs.count("-hdseed") && IsHex(strSeed)) { + std::vector vchSeed = ParseHex(strSeed); + if (!newHdChain.SetSeed(SecureVector(vchSeed.begin(), vchSeed.end()), true)) + throw std::runtime_error(std::string(__func__) + ": SetSeed failed"); + } + else { + 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" + std::string strMnemonic = GetArg("-mnemonic", ""); + // NOTE: default mnemonic passphrase is an empty string + std::string strMnemonicPassphrase = GetArg("-mnemonicpassphrase", ""); + + 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"); + } + newHdChain.Debug(__func__); + + 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) +{ + LOCK(cs_wallet); + 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::SetCryptedHDChain(const CHDChain& chain, bool memonly) +{ + LOCK(cs_wallet); + + 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::GetDecryptedHDChain(CHDChain& hdChainRet) +{ + LOCK(cs_wallet); + + CHDChain hdChainTmp; + if (!GetHDChain(hdChainTmp)) { + return false; + } + + if (!DecryptHDChain(hdChainTmp)) + return false; + + // make sure seed matches this chain + if (hdChainTmp.GetID() != hdChainTmp.GetSeedHash()) + return false; + + hdChainRet = hdChainTmp; + + return true; +} + +bool CWallet::IsHDEnabled() +{ + CHDChain hdChainCurrent; + return GetHDChain(hdChainCurrent); + +} int64_t CWallet::IncOrderPosNext(CWalletDB* pwalletdb) { AssertLockHeld(cs_wallet); // nOrderPosNext @@ -2479,7 +2760,7 @@ bool CWallet::CreateTransaction(const std::vector >& // Reserve a new key pair from key pool CPubKey vchPubKey; bool ret; - ret = reservekey.GetReservedKey(vchPubKey); + ret = reservekey.GetReservedKey(vchPubKey,true); assert(ret); // should never fail, as we just unlocked scriptChange = GetScriptForDestination(vchPubKey.GetID()); @@ -2821,8 +3102,8 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) if (nLoadWalletRet == DB_NEED_REWRITE) { if (CDB::Rewrite(strWalletFile, "\x04pool")) { LOCK(cs_wallet); - setKeyPool.clear(); - // Note: can't top-up keypool here, because wallet is locked. + setInternalKeyPool.clear(); + setExternalKeyPool.clear(); // Note: can't top-up keypool here, because wallet is locked. // User will be prompted to unlock wallet the next operation // the requires a new key. } @@ -2848,8 +3129,8 @@ DBErrors CWallet::ZapWalletTx(std::vector& vWtx) if (nZapWalletTxRet == DB_NEED_REWRITE) { if (CDB::Rewrite(strWalletFile, "\x04pool")) { LOCK(cs_wallet); - setKeyPool.clear(); - // Note: can't top-up keypool here, because wallet is locked. + setInternalKeyPool.clear(); + setExternalKeyPool.clear(); // Note: can't top-up keypool here, because wallet is locked. // User will be prompted to unlock wallet the next operation // that requires a new key. } @@ -2954,24 +3235,32 @@ bool CWallet::NewKeyPool() { LOCK(cs_wallet); CWalletDB walletdb(strWalletFile); - for (int64_t nIndex : setKeyPool) + for (int64_t nIndex : setInternalKeyPool){ walletdb.ErasePool(nIndex); - setKeyPool.clear(); - - if (IsLocked()) + } + setInternalKeyPool.clear(); + for (int64_t nIndex : setExternalKeyPool){ + walletdb.ErasePool(nIndex); + } + setExternalKeyPool.clear(); + if (!TopUpKeyPool()) return false; - int64_t nKeys = std::max(GetArg("-keypool", 1000), (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) { { @@ -2980,7 +3269,6 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize) if (IsLocked()) return false; - CWalletDB walletdb(strWalletFile); // Top up key pool unsigned int nTargetSize; @@ -2989,14 +3277,42 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize) else nTargetSize = std::max(GetArg("-keypool", 1000), (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); + } + // TODO: implement keypools for all accounts? + if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey(0, fInternal), fInternal))) throw std::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); @@ -3005,7 +3321,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(); @@ -3015,18 +3331,25 @@ void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool) if (!IsLocked()) 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 std::runtime_error("ReserveKeyFromKeyPool() : read failed"); - if (!HaveKey(keypool.vchPubKey.GetID())) - throw std::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); } @@ -3042,26 +3365,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()) return false; - result = GenerateNewKey(); + // TODO: implement keypool for all accouts? + result = GenerateNewKey(0, fInternal); return true; } KeepKey(nIndex); @@ -3069,16 +3397,34 @@ 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() @@ -3219,16 +3565,17 @@ std::set CWallet::GetAccountAddresses(std::string strAccount) co 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) - vchPubKey = keypool.vchPubKey; + pwallet->ReserveKeyFromKeyPool(nIndex, keypool, fInternalIn); + if (nIndex != -1) { + vchPubKey = keypool.vchPubKey;} else { return false; } + fInternal = keypool.fInternal; } assert(vchPubKey.IsValid()); pubkey = vchPubKey; @@ -3246,30 +3593,39 @@ void CReserveKey::KeepKey() void CReserveKey::ReturnKey() { if (nIndex != -1) - pwallet->ReturnKey(nIndex); + pwallet->ReturnKey(nIndex, fInternal); nIndex = -1; vchPubKey = CPubKey(); } -void CWallet::GetAllReserveKeys(std::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); for (const int64_t& id : setKeyPool) { CKeyPool keypool; if (!walletdb.ReadPool(id, keypool)) throw std::runtime_error("GetAllReserveKeyHashes() : read failed"); assert(keypool.vchPubKey.IsValid()); CKeyID keyID = keypool.vchPubKey.GetID(); - if (!HaveKey(keyID)) - throw std::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); + for (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) { { @@ -3700,10 +4056,11 @@ CKeyPool::CKeyPool() nTime = GetTime(); } -CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn) +CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool fInternalIn) { nTime = GetTime(); vchPubKey = vchPubKeyIn; + fInternal = fInternalIn; } CWalletKey::CWalletKey(int64_t nExpires) @@ -4969,8 +5326,8 @@ void CWallet::Inventory(const uint256& hash) unsigned int CWallet::GetKeyPoolSize() { - AssertLockHeld(cs_wallet); // setKeyPool - return setKeyPool.size(); + AssertLockHeld(cs_wallet); // set{Ex,In}ternalKeyPool + return setInternalKeyPool.size() + setExternalKeyPool.size(); } int CWallet::GetVersion() diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index bbba5de5d5a2..8d5c5018a0eb 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -61,6 +61,10 @@ static const CAmount nHighTransactionMaxFeeWarning = 100 * nHighTransactionFeeWa static const unsigned int MAX_FREE_TRANSACTION_CREATE_SIZE = 1000; //! -custombackupthreshold default static const int DEFAULT_CUSTOMBACKUPTHRESHOLD = 1; +//! -enableautoconvertaddress default +static const bool DEFAULT_AUTOCONVERTADDRESS = true; +//! if set, all keys will be derived by using BIP32 +static const bool DEFAULT_USE_HD_WALLET = true; class CAccountingEntry; class CCoinControl; @@ -75,7 +79,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,change this to 4000000 once version changes are made FEATURE_LATEST = 61000 }; @@ -125,9 +130,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; @@ -138,6 +144,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); + } } }; @@ -198,6 +217,8 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface void MarkConflicted(const uint256& hashBlock, const uint256& hashTx); void SyncMetaData(std::pair); + /* HD derive new child key (on internal or external chain) */ + void DeriveNewChildKey(const CKeyMetadata& metadata, CKey& secretRet, uint32_t nAccountIndex, bool fInternal = false); public: @@ -280,7 +301,24 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface bool fBackupMints; std::unique_ptr zpivTracker; - 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; @@ -330,6 +368,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface std::set setLockedCoins; int64_t nTimeFirstKey; + std::map mapHdPubKeys; //& setAddress) const; @@ -514,9 +565,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface bool DelAddressBook(const CTxDestination& address, const CChainParams::Base58Type addrType = CChainParams::PUBKEY_ADDRESS); bool HasAddressBook(const CTxDestination& address) const; bool HasDelegator(const CTxOut& out) const; - std::string purposeForAddress(const CTxDestination& address) const; - bool UpdatedTransaction(const uint256& hashTx); void Inventory(const uint256& hash); @@ -532,6 +581,18 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface //! get the current wallet format (the oldest client version guaranteed to understand this wallet) int GetVersion(); + /** + * HD Wallet Functions + */ + + /* 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); + bool SetCryptedHDChain(const CHDChain& chain, bool memonly); + bool GetDecryptedHDChain(CHDChain& hdChainRet); //! Get wallet transactions that conflict with given transaction (spend same outputs) std::set GetConflicts(const uint256& txid) const; @@ -574,12 +635,14 @@ class CReserveKey CWallet* pwallet; int64_t nIndex; CPubKey vchPubKey; + bool fInternal; public: CReserveKey(CWallet* pwalletIn) { nIndex = -1; pwallet = pwalletIn; + fInternal = false; } ~CReserveKey() @@ -588,7 +651,7 @@ class CReserveKey } void ReturnKey(); - bool GetReservedKey(CPubKey& pubkey); + bool GetReservedKey(CPubKey &pubkey, bool fInternalIn = false); void KeepKey(); }; diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 2146a1c6fee6..f48ef73c7b01 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -596,14 +596,7 @@ bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, CW 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") { ssValue >> wss.nFileVersion; if (wss.nFileVersion == 10300) @@ -658,6 +651,44 @@ bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, CW return false; } } + else if (strType == "hdchain" || strType == "chdchain") + { + CHDChain chain; + ssValue >> chain; + if(strType == "hdchain"){ + if (!pwallet->SetHDChain(chain, true)) + { + strErr = "Error reading wallet database: SetHDChain failed"; + return false; + } + } + else{ + if (!pwallet->SetCryptedHDChain(chain, true)) + { + strErr = "Error reading wallet database: SetHDCryptedChain failed"; + return false; + } + } + } + else if (strType == "hdpubkey") + { + CPubKey vchPubKey; + ssKey >> 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; } @@ -666,8 +697,8 @@ bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, CW static bool IsKeyType(std::string strType) { - return (strType == "key" || strType == "wkey" || - strType == "mkey" || strType == "ckey"); +return (strType == "mkey" || strType == "ckey" || + strType == "hdchain" || strType == "chdchain"); } DBErrors CWalletDB::LoadWallet(CWallet* pwallet) @@ -1096,7 +1127,7 @@ bool CWalletDB::Recover(CDBEnv& dbenv, std::string filename, bool fOnlyKeys) std::string strType, strErr; bool fReadOK = ReadKeyValue(&dummyWallet, ssKey, ssValue, wss, strType, strErr); - if (!IsKeyType(strType)) + if (!IsKeyType(strType) && strType != "hdpubkey") continue; if (!fReadOK) { LogPrintf("WARNING: CWalletDB::Recover skipping %s: %s\n", strType, strErr); @@ -1131,7 +1162,33 @@ 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); +} + +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++; + + 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); +} bool CWalletDB::WriteZerocoinSpendSerialEntry(const CZerocoinSpend& zerocoinSpend) { return Write(std::make_pair(std::string("zcserial"), zerocoinSpend.GetSerial()), zerocoinSpend, true); diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index fc09720a8436..4a3c1235f923 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -60,7 +60,7 @@ class CKeyMetadata } CKeyMetadata(int64_t nCreateTime_) { - nVersion = CKeyMetadata::CURRENT_VERSION; + SetNull(); nCreateTime = nCreateTime_; } @@ -152,6 +152,10 @@ class CWalletDB : public CDB static bool Recover(CDBEnv& dbenv, std::string filename, bool fOnlyKeys); static bool Recover(CDBEnv& dbenv, std::string filename); + //! 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); bool WriteDeterministicMint(const CDeterministicMint& dMint); bool ReadDeterministicMint(const uint256& hashPubcoin, CDeterministicMint& dMint); bool EraseDeterministicMint(const uint256& hashPubcoin); diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py index a9df8cbde0d9..9a429a9f4b11 100755 --- a/test/functional/rpc_fundrawtransaction.py +++ b/test/functional/rpc_fundrawtransaction.py @@ -486,6 +486,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) @@ -499,6 +500,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/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 0367b816b989..d7ad25550962 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -355,11 +355,12 @@ def disconnect_nodes(from_connection, node_num): wait_until(lambda: [peer['addr'] for peer in from_connection.getpeerinfo() if "testnode%d" % node_num in peer['subver']] == [], timeout=5) def connect_nodes(from_connection, node_num): - ip_port = "127.0.0.1:" + str(p2p_port(node_num)) + ip_port = "127.0.0.1:"+str(p2p_port(node_num)) from_connection.addnode(ip_port, "onetry") # poll until version handshake complete to avoid race conditions # with transaction relaying - wait_until(lambda: all(peer['version'] != 0 for peer in from_connection.getpeerinfo())) + while any(peer['version'] == 0 for peer in from_connection.getpeerinfo()): + time.sleep(0.1) def connect_nodes_bi(nodes, a, b): connect_nodes(nodes[a], b) @@ -374,28 +375,18 @@ def connect_nodes_clique(nodes): def sync_blocks(rpc_connections, *, wait=1, timeout=60): """ Wait until everybody has the same tip. - sync_blocks needs to be called with an rpc_connections set that has least one node already synced to the latest, stable tip, otherwise there's a chance it might return before all nodes are stably synced. """ - # Use getblockcount() instead of waitforblockheight() to determine the - # initial max height because the two RPCs look at different internal global - # variables (chainActive vs latestBlock) and the former gets updated - # earlier. - time.sleep(5) - maxheight = max(x.getblockcount() for x in rpc_connections) - start_time = cur_time = time.time() - while cur_time <= start_time + timeout: - tips = [r.waitforblockheight(maxheight, int(wait * 1000)) for r in rpc_connections] - if all(t["height"] == maxheight for t in tips): - if all(t["hash"] == tips[0]["hash"] for t in tips): - return - raise AssertionError("Block sync failed, mismatched block hashes:{}".format( - "".join("\n {!r}".format(tip) for tip in tips))) - cur_time = time.time() - raise AssertionError("Block sync to height {} timed out:{}".format( - maxheight, "".join("\n {!r}".format(tip) for tip in tips))) + stop_time = time.time() + timeout + while time.time() <= stop_time: + best_hash = [x.getbestblockhash() for x in rpc_connections] + if best_hash.count(best_hash[0]) == len(rpc_connections): + return + time.sleep(wait) + raise AssertionError("Block sync timed out:{}".format("".join("\n {!r}".format(b) for b in best_hash))) + def sync_chain(rpc_connections, *, wait=1, timeout=60): """ diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 4052302f91be..59b740118e33 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -87,6 +87,7 @@ 'wallet_encryption.py', # ~ 89 sec 'wallet_keypool.py', # ~ 88 sec 'wallet_dump.py', # ~ 83 sec + 'wallet_hd.py', 'rpc_net.py', # ~ 83 sec 'rpc_bip38.py', # ~ 82 sec 'interface_bitcoin_cli.py', # ~ 80 sec @@ -337,9 +338,9 @@ def pingTravis(): done_str = "{}/{} - {}{}{}".format(i + 1, test_count, BOLD[1], test_result.name, BOLD[0]) if test_result.status == "Passed": if stderr == "": - logging.debug("%s passed, Duration: %s s" % (done_str, test_result.time)) + logging.debug("%s passed, Duration: %s s\n" % (done_str, test_result.time)) else: - logging.debug("%s passed (with warnings), Duration: %s s" % (done_str, test_result.time)) + logging.debug("%s passed (with warnings), Duration: %s s\n" % (done_str, test_result.time)) print(BOLD[1] + 'stderr:\n' + BOLD[0] + stderr + '\n') elif test_result.status == "Skipped": logging.debug("%s skipped" % (done_str)) @@ -591,4 +592,4 @@ def _get_uncovered_rpc_commands(self): if __name__ == '__main__': - main() + main() \ No newline at end of file diff --git a/test/functional/wallet_backup.py b/test/functional/wallet_backup.py index 62d4ff731a3a..d556dbc3acf8 100755 --- a/test/functional/wallet_backup.py +++ b/test/functional/wallet_backup.py @@ -115,15 +115,6 @@ def run_test(self): for i in range(5): self.do_one_round() - self.log.info("Backing up") - tmpdir = self.options.tmpdir - self.nodes[0].backupwallet(tmpdir + "/node0/wallet.bak") - self.nodes[0].dumpwallet(tmpdir + "/node0/wallet.dump") - self.nodes[1].backupwallet(tmpdir + "/node1/wallet.bak") - self.nodes[1].dumpwallet(tmpdir + "/node1/wallet.dump") - self.nodes[2].backupwallet(tmpdir + "/node2/wallet.bak") - self.nodes[2].dumpwallet(tmpdir + "/node2/wallet.dump") - self.log.info("More transactions") for i in range(5): self.do_one_round() @@ -138,6 +129,15 @@ def run_test(self): balance3 = self.nodes[3].getbalance() total = balance0 + balance1 + balance2 + balance3 + self.log.info("Backing up") + tmpdir = self.options.tmpdir + self.nodes[0].backupwallet(tmpdir + "/node0/wallet.bak") + self.nodes[0].dumpwallet(tmpdir + "/node0/wallet.dump") + self.nodes[1].backupwallet(tmpdir + "/node1/wallet.bak") + self.nodes[1].dumpwallet(tmpdir + "/node1/wallet.dump") + self.nodes[2].backupwallet(tmpdir + "/node2/wallet.bak") + self.nodes[2].dumpwallet(tmpdir + "/node2/wallet.dump") + # At this point, there are 214 blocks (103 for setup, then 10 rounds, then 101.) # 114 are mature, so the sum of all wallets should be 114 * 250 = 28500. assert_equal(total, 28500) @@ -202,4 +202,4 @@ def run_test(self): if __name__ == '__main__': - WalletBackupTest().main() + WalletBackupTest().main() \ No newline at end of file diff --git a/test/functional/wallet_dump.py b/test/functional/wallet_dump.py index 8f361de9c8ae..8edd0fb326fb 100755 --- a/test/functional/wallet_dump.py +++ b/test/functional/wallet_dump.py @@ -73,7 +73,7 @@ def run_test (self): read_dump(tmpdir + "/node0/wallet.unencrypted.dump", addrs, None) assert_equal(found_addr, test_addr_count) # all keys must be in the dump assert_equal(found_addr_chg, 0) # 0 blocks where mined - assert_equal(found_addr_rsv, 90 + 1) # keypool size (TODO: fix off-by-one) + assert_equal(found_addr_rsv, 180) # keypool size (TODO: fix off-by-one) #encrypt wallet, restart, unlock and dump self.nodes[0].node_encrypt_wallet('test') @@ -86,8 +86,8 @@ def run_test (self): found_addr, found_addr_chg, found_addr_rsv, hd_master_addr_enc = \ read_dump(tmpdir + "/node0/wallet.encrypted.dump", addrs, hd_master_addr_unenc) assert_equal(found_addr, test_addr_count) - assert_equal(found_addr_chg, 90 + 1) # old reserve keys are marked as change now - assert_equal(found_addr_rsv, 90 + 1) # keypool size (TODO: fix off-by-one) + assert_equal(found_addr_chg, 0) # old reserve keys are marked as change now + assert_equal(found_addr_rsv, 180) # keypool size (TODO: fix off-by-one) if __name__ == '__main__': - WalletDumpTest().main () + WalletDumpTest().main () \ No newline at end of file diff --git a/test/functional/wallet_hd.py b/test/functional/wallet_hd.py new file mode 100755 index 000000000000..49a6dbdba2fd --- /dev/null +++ b/test/functional/wallet_hd.py @@ -0,0 +1,108 @@ +#!/usr/bin/env 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 PivxTestFramework +from test_framework.util import * +import shutil +class WalletHDTest(PivxTestFramework): + + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 2 + self.extra_args = [['-usehd=0'],['-usehd=1', '-keypool=0']] + + def setup_network(self): + self.add_nodes(self.num_nodes, self.extra_args) + self.start_node(0) + self.start_node(1) + self.is_network_split = False + connect_nodes_bi(self.nodes, 0, 1) + self.sync_all() + + + def run_test (self): + tmpdir = self.options.tmpdir + + # Make sure can't switch off usehd after wallet creation + self.stop_node(1) + self.start_node(1) + # self.assert_start_raises_init_error(1, self.options.tmpdir, ['-usehd=0'], 'already existing HD wallet') + 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) + + # 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)) + + # 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(120) + 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)) + assert_equal(hd_info["hdchainid"], chainid) + self.nodes[0].sendtoaddress(hd_add, 1) + + self.nodes[0].sendtoaddress(non_hd_add, 1) + self.nodes[0].generate(6) + + # 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) + + self.log.info("Restore backup ...") + self.stop_node(1) + os.remove(self.options.tmpdir + "/node1/regtest/wallet.dat") + shutil.copyfile(tmpdir + "/hd.bak", tmpdir + "/node1/regtest/wallet.dat") + self.start_node(1,['-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(_)) + assert_equal(hd_info_2["hdchainid"], chainid) + assert_equal(hd_add, hd_add_2) + + # Needs rescan + self.stop_node(1) + self.start_node(1,['-usehd=1', '-keypool=0', '-rescan']) + 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/test/functional/wallet_keypool.py b/test/functional/wallet_keypool.py index 2692554bee7b..b1576c8f9618 100755 --- a/test/functional/wallet_keypool.py +++ b/test/functional/wallet_keypool.py @@ -15,6 +15,8 @@ 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['hdchainid'] == wallet_info_old['hdchainid']) # Encrypt wallet and wait to terminate nodes[0].node_encrypt_wallet('test') @@ -23,15 +25,17 @@ def run_test(self): # Keep creating keys addr = nodes[0].getnewaddress() addr_data = nodes[0].validateaddress(addr) - assert_raises_rpc_error(-12, "Keypool ran out, please call keypoolrefill first, or unlock the wallet.", - nodes[0].getnewaddress) + wallet_info = nodes[0].getwalletinfo() + assert(addr_before_encrypting_data['hdchainid'] == wallet_info['hdchainid']) + assert(addr_data['hdchainid'] == wallet_info['hdchainid']) # 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(6) nodes[0].walletlock() wi = nodes[0].getwalletinfo() - assert_equal(wi['keypoolsize'], 7) + assert_equal(wi['keypoolsize_hd_internal'], 6) + assert_equal(wi['keypoolsize'], 6) # drain the internal keys nodes[0].getrawchangeaddress() @@ -40,10 +44,9 @@ def run_test(self): nodes[0].getrawchangeaddress() nodes[0].getrawchangeaddress() nodes[0].getrawchangeaddress() - nodes[0].getrawchangeaddress() addr = set() # the next one should fail - assert_raises_rpc_error(-12, "Keypool ran out", nodes[0].getrawchangeaddress) + assert_raises_rpc_error(-12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getrawchangeaddress) # drain the external keys #addr.add(nodes[0].getnewaddress()) @@ -74,7 +77,8 @@ def run_test(self): nodes[0].walletpassphrase('test', 100) nodes[0].keypoolrefill(100) wi = nodes[0].getwalletinfo() - assert_equal(wi['keypoolsize'], 101) + assert_equal(wi['keypoolsize_hd_internal'], 100) + assert_equal(wi['keypoolsize'], 100) if __name__ == '__main__': KeyPoolTest().main()