diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/db.cpp | 191 | ||||
| -rw-r--r-- | src/db.h | 7 | ||||
| -rw-r--r-- | src/main_gui.cpp | 33 |
3 files changed, 231 insertions, 0 deletions
| @@ -708,3 +708,194 @@ bool BackupWallet(const string& strDest) | |||
| 708 | printf("BackupWallet() : wrote %s\n", strDest.c_str()); | 708 | printf("BackupWallet() : wrote %s\n", strDest.c_str()); |
| 709 | return true; | 709 | return true; |
| 710 | } | 710 | } |
| 711 | |||
| 712 | |||
| 713 | // ---- plain-text key export/import ------------------------------------------ | ||
| 714 | // | ||
| 715 | // The last door out. Everything else in this file assumes Berkeley DB will | ||
| 716 | // cooperate; these two do not. A wallet.dat can be unreadable for reasons that | ||
| 717 | // have nothing to do with your keys being gone -- a version this build does not | ||
| 718 | // understand, a file truncated by a bad copy, an environment that cannot be | ||
| 719 | // recovered -- and until now that meant the coins were unreachable even though | ||
| 720 | // the secrets were sitting right there. Issue #40 is one person's account of | ||
| 721 | // exactly that. | ||
| 722 | // | ||
| 723 | // The format is deliberately dull: a text file, one key per line, no database, | ||
| 724 | // no environment, no framing. Anything that can read a line can recover from it. | ||
| 725 | |||
| 726 | static int HexDigitValue(char c) | ||
| 727 | { | ||
| 728 | if (c >= '0' && c <= '9') return c - '0'; | ||
| 729 | if (c >= 'a' && c <= 'f') return c - 'a' + 10; | ||
| 730 | if (c >= 'A' && c <= 'F') return c - 'A' + 10; | ||
| 731 | return -1; | ||
| 732 | } | ||
| 733 | |||
| 734 | static bool ParseHexInto(const string& str, vector<unsigned char>& vchRet) | ||
| 735 | { | ||
| 736 | vchRet.clear(); | ||
| 737 | if (str.empty() || (str.size() % 2) != 0) | ||
| 738 | return false; | ||
| 739 | for (size_t i = 0; i < str.size(); i += 2) | ||
| 740 | { | ||
| 741 | int hi = HexDigitValue(str[i]); | ||
| 742 | int lo = HexDigitValue(str[i + 1]); | ||
| 743 | if (hi < 0 || lo < 0) | ||
| 744 | return false; | ||
| 745 | vchRet.push_back((unsigned char)((hi << 4) | lo)); | ||
| 746 | } | ||
| 747 | return true; | ||
| 748 | } | ||
| 749 | |||
| 750 | bool DumpWallet(const string& strDest) | ||
| 751 | { | ||
| 752 | if (strDest.empty()) | ||
| 753 | return error("DumpWallet() : no destination given\n"); | ||
| 754 | |||
| 755 | // Never over an existing file. The whole point is to hold the only copy of | ||
| 756 | // something irreplaceable, and a mistyped path must not eat one. | ||
| 757 | if (FileExists(strDest.c_str())) | ||
| 758 | return error("DumpWallet() : %s already exists -- refusing to overwrite\n", | ||
| 759 | strDest.c_str()); | ||
| 760 | |||
| 761 | FILE* pf = fopen(strDest.c_str(), "w"); | ||
| 762 | if (!pf) | ||
| 763 | return error("DumpWallet() : cannot write %s\n", strDest.c_str()); | ||
| 764 | #ifndef _WIN32 | ||
| 765 | // Before a single key reaches it. | ||
| 766 | chmod(strDest.c_str(), S_IRUSR | S_IWUSR); | ||
| 767 | #endif | ||
| 768 | |||
| 769 | fprintf(pf, "# Bitflash wallet key dump\n"); | ||
| 770 | fprintf(pf, "# %s\n", DateTimeStr(GetTime()).c_str()); | ||
| 771 | fprintf(pf, "#\n"); | ||
| 772 | fprintf(pf, "# THIS FILE CONTAINS PRIVATE KEYS IN PLAIN TEXT.\n"); | ||
| 773 | fprintf(pf, "# Anyone who reads it can spend these coins. Treat it as cash.\n"); | ||
| 774 | fprintf(pf, "#\n"); | ||
| 775 | fprintf(pf, "# One key per line: <private-key-hex> <address> [label]\n"); | ||
| 776 | fprintf(pf, "# The key is the DER encoding, hex; restore with -importwallet=FILE.\n"); | ||
| 777 | fprintf(pf, "#\n"); | ||
| 778 | |||
| 779 | int nKeys = 0; | ||
| 780 | bool fOk = true; | ||
| 781 | CRITICAL_BLOCK(cs_mapKeys) | ||
| 782 | { | ||
| 783 | for (map<vector<unsigned char>, CPrivKey>::iterator mi = mapKeys.begin(); | ||
| 784 | mi != mapKeys.end(); ++mi) | ||
| 785 | { | ||
| 786 | const vector<unsigned char>& vchPubKey = (*mi).first; | ||
| 787 | const CPrivKey& vchPrivKey = (*mi).second; | ||
| 788 | |||
| 789 | string strAddress = PubKeyToAddress(vchPubKey); | ||
| 790 | string strLabel; | ||
| 791 | map<string, string>::iterator mia = mapAddressBook.find(strAddress); | ||
| 792 | if (mia != mapAddressBook.end()) | ||
| 793 | strLabel = (*mia).second; | ||
| 794 | |||
| 795 | // Labels are user text and could contain a newline, which would | ||
| 796 | // forge a second entry on read-back. | ||
| 797 | for (size_t i = 0; i < strLabel.size(); i++) | ||
| 798 | if (strLabel[i] == '\n' || strLabel[i] == '\r') | ||
| 799 | strLabel[i] = ' '; | ||
| 800 | |||
| 801 | if (fprintf(pf, "%s %s %s\n", | ||
| 802 | HexStr(vchPrivKey.begin(), vchPrivKey.end(), false).c_str(), | ||
| 803 | strAddress.c_str(), | ||
| 804 | strLabel.c_str()) < 0) | ||
| 805 | { fOk = false; break; } | ||
| 806 | nKeys++; | ||
| 807 | } | ||
| 808 | } | ||
| 809 | |||
| 810 | if (fclose(pf) != 0) | ||
| 811 | fOk = false; | ||
| 812 | if (!fOk) | ||
| 813 | { | ||
| 814 | remove(strDest.c_str()); | ||
| 815 | return error("DumpWallet() : writing %s failed\n", strDest.c_str()); | ||
| 816 | } | ||
| 817 | |||
| 818 | printf("DumpWallet() : wrote %d key(s) to %s\n", nKeys, strDest.c_str()); | ||
| 819 | return true; | ||
| 820 | } | ||
| 821 | |||
| 822 | bool ImportWallet(const string& strSrc, int& nAddedRet, int& nSkippedRet) | ||
| 823 | { | ||
| 824 | nAddedRet = 0; | ||
| 825 | nSkippedRet = 0; | ||
| 826 | |||
| 827 | FILE* pf = fopen(strSrc.c_str(), "r"); | ||
| 828 | if (!pf) | ||
| 829 | return error("ImportWallet() : cannot read %s\n", strSrc.c_str()); | ||
| 830 | |||
| 831 | char buf[8192]; | ||
| 832 | int nLine = 0; | ||
| 833 | int nBad = 0; | ||
| 834 | while (fgets(buf, sizeof(buf), pf)) | ||
| 835 | { | ||
| 836 | nLine++; | ||
| 837 | string strLine = buf; | ||
| 838 | while (!strLine.empty() && (strLine[strLine.size()-1] == '\n' || | ||
| 839 | strLine[strLine.size()-1] == '\r')) | ||
| 840 | strLine.resize(strLine.size() - 1); | ||
| 841 | if (strLine.empty() || strLine[0] == '#') | ||
| 842 | continue; | ||
| 843 | |||
| 844 | string strHex = strLine.substr(0, strLine.find(' ')); | ||
| 845 | vector<unsigned char> vchPrivKey; | ||
| 846 | if (!ParseHexInto(strHex, vchPrivKey)) | ||
| 847 | { | ||
| 848 | printf("ImportWallet() : line %d is not a hex key, skipped\n", nLine); | ||
| 849 | nBad++; | ||
| 850 | continue; | ||
| 851 | } | ||
| 852 | |||
| 853 | CKey key; | ||
| 854 | // A key this build cannot load is reported and stepped over rather than | ||
| 855 | // aborting the run: one damaged line in a salvage file must not cost | ||
| 856 | // the reader every other key in it. | ||
| 857 | try | ||
| 858 | { | ||
| 859 | CPrivKey vchSecure(vchPrivKey.begin(), vchPrivKey.end()); | ||
| 860 | if (!key.SetPrivKey(vchSecure)) | ||
| 861 | { | ||
| 862 | printf("ImportWallet() : line %d is not a usable key, skipped\n", nLine); | ||
| 863 | nBad++; | ||
| 864 | continue; | ||
| 865 | } | ||
| 866 | } | ||
| 867 | catch (const std::exception& e) | ||
| 868 | { | ||
| 869 | printf("ImportWallet() : line %d rejected (%s), skipped\n", nLine, e.what()); | ||
| 870 | nBad++; | ||
| 871 | continue; | ||
| 872 | } | ||
| 873 | |||
| 874 | bool fHave = false; | ||
| 875 | CRITICAL_BLOCK(cs_mapKeys) | ||
| 876 | fHave = mapKeys.count(key.GetPubKey()) > 0; | ||
| 877 | if (fHave) | ||
| 878 | { | ||
| 879 | nSkippedRet++; // already ours; importing twice is not an error | ||
| 880 | continue; | ||
| 881 | } | ||
| 882 | |||
| 883 | if (!AddKey(key)) | ||
| 884 | { | ||
| 885 | fclose(pf); | ||
| 886 | return error("ImportWallet() : failed to store key from line %d\n", nLine); | ||
| 887 | } | ||
| 888 | nAddedRet++; | ||
| 889 | } | ||
| 890 | fclose(pf); | ||
| 891 | |||
| 892 | printf("ImportWallet() : %d added, %d already present, %d unreadable\n", | ||
| 893 | nAddedRet, nSkippedRet, nBad); | ||
| 894 | |||
| 895 | // Imported keys are only useful once their transactions are noticed, and | ||
| 896 | // that scan is a full pass over the chain the node does at startup. | ||
| 897 | if (nAddedRet > 0) | ||
| 898 | printf("ImportWallet() : restart the node so it can find transactions " | ||
| 899 | "belonging to the new keys\n"); | ||
| 900 | return true; | ||
| 901 | } | ||
| @@ -409,6 +409,13 @@ bool LoadWallet(); | |||
| 409 | // address created after this call are not spendable from the file it writes. | 409 | // address created after this call are not spendable from the file it writes. |
| 410 | bool BackupWallet(const string& strDest); | 410 | bool BackupWallet(const string& strDest); |
| 411 | 411 | ||
| 412 | // Plain-text key export/import: no Berkeley DB, no environment, no version | ||
| 413 | // coupling. What you fall back on when wallet.dat itself will not cooperate. | ||
| 414 | // DumpWallet refuses to overwrite an existing file and writes owner-only on | ||
| 415 | // POSIX -- the output holds private keys in the clear. | ||
| 416 | bool DumpWallet(const string& strDest); | ||
| 417 | bool ImportWallet(const string& strSrc, int& nAddedRet, int& nSkippedRet); | ||
| 418 | |||
| 412 | inline bool SetAddressBookName(const string& strAddress, const string& strName) | 419 | inline bool SetAddressBookName(const string& strAddress, const string& strName) |
| 413 | { | 420 | { |
| 414 | return CWalletDB().WriteName(strAddress, strName); | 421 | return CWalletDB().WriteName(strAddress, strName); |
diff --git a/src/main_gui.cpp b/src/main_gui.cpp index 976cd42..2484592 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp | |||
| @@ -70,6 +70,9 @@ static void PrintUsage() | |||
| 70 | printf("Wallet:\n"); | 70 | printf("Wallet:\n"); |
| 71 | printf(" /backupwallet=FILE (write a wallet.dat that opens on its own,\n"); | 71 | printf(" /backupwallet=FILE (write a wallet.dat that opens on its own,\n"); |
| 72 | printf(" then exit; run it again after new addresses)\n"); | 72 | printf(" then exit; run it again after new addresses)\n"); |
| 73 | printf(" /dumpwallet=FILE (export private keys as text -- readable by\n"); | ||
| 74 | printf(" anyone, so guard it like cash)\n"); | ||
| 75 | printf(" /importwallet=FILE (load keys from such a file back in)\n"); | ||
| 73 | printf("\n"); | 76 | printf("\n"); |
| 74 | printf("Each option also accepts '-' instead of '/'.\n"); | 77 | printf("Each option also accepts '-' instead of '/'.\n"); |
| 75 | } | 78 | } |
| @@ -245,6 +248,36 @@ int main(int argc, char* argv[]) | |||
| 245 | return fOk ? 0 : 1; | 248 | return fOk ? 0 : 1; |
| 246 | } | 249 | } |
| 247 | 250 | ||
| 251 | string strDump = argval2(argc, argv, "/dumpwallet", "-dumpwallet"); | ||
| 252 | if (!strDump.empty()) | ||
| 253 | { | ||
| 254 | bool fOk = DumpWallet(strDump); | ||
| 255 | if (fOk) | ||
| 256 | { | ||
| 257 | printf("Wrote %s\n", strDump.c_str()); | ||
| 258 | printf("It holds your private keys as readable text. Anyone with the file " | ||
| 259 | "can spend these coins -- keep it off shared storage and delete it " | ||
| 260 | "once you have it somewhere safe.\n"); | ||
| 261 | } | ||
| 262 | else | ||
| 263 | fprintf(stderr, "Export failed -- nothing was written.\n"); | ||
| 264 | DBFlush(true); | ||
| 265 | return fOk ? 0 : 1; | ||
| 266 | } | ||
| 267 | |||
| 268 | string strImport = argval2(argc, argv, "/importwallet", "-importwallet"); | ||
| 269 | if (!strImport.empty()) | ||
| 270 | { | ||
| 271 | int nAdded = 0, nSkipped = 0; | ||
| 272 | bool fOk = ImportWallet(strImport, nAdded, nSkipped); | ||
| 273 | if (fOk) | ||
| 274 | printf("Imported %d key(s); %d were already here.\n", nAdded, nSkipped); | ||
| 275 | else | ||
| 276 | fprintf(stderr, "Import failed.\n"); | ||
| 277 | DBFlush(true); | ||
| 278 | return fOk ? 0 : 1; | ||
| 279 | } | ||
| 280 | |||
| 248 | ReacceptWalletTransactions(); | 281 | ReacceptWalletTransactions(); |
| 249 | 282 | ||
| 250 | if (!StartNode(strErrors)) { fprintf(stderr,"StartNode: %s\n",strErrors.c_str()); return 1; } | 283 | if (!StartNode(strErrors)) { fprintf(stderr,"StartNode: %s\n",strErrors.c_str()); return 1; } |