upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/script.cpp
diff options
context:
space:
mode:
authorBitflash <btf01@proton.me>2026-08-04 10:01:36 -0300
committerGitHub <noreply@github.com>2026-08-04 10:01:36 -0300
commit1db5f372c36eb701602a70a358de2344726ed734 (patch)
tree9d028ed3097ac51368ca312ff439664c76f439b7 /src/script.cpp
parent9176d5b3f32b907e4bafde5152828b2ae04ae8a1 (diff)
wallet encryption: the stack from #130–#133, with the two blockers fixed (#134)
* wallet: refuse future encrypted wallet formats * wallet: add encrypted key unlock core * wallet: add encryptwallet rewrite command * wallet: read passphrases from stdin or files * wallet: keep the derivation across encryption, and take the logs with it Two things stopped the encryption stack from being safe to merge. Both were found against a wallet holding real coin, not in the self-tests. 1. Encrypting turned a BIP44 wallet into a legacy one. LoadWallet() ends by resetting the schema and the derivation counters when no seed is in memory. That was written when "no seed after loading" could only mean the wallet had none. An encrypted wallet has a seed -- it just has not been decrypted yet -- so the reset threw away metadata that had been read correctly a moment earlier. Unlocking later restores the seed and nothing restores the rest. Measured on the wallet from the #124 proof: after encrypting, index 1 moved off BAtgSgra2X8Dqo5PJzPCLJhPwrrC2thYVJ, the address actually holding 1.00 BTF, and the audit went from "1.00 covered by the phrase" to "0.00 covered, 1.00 wallet.dat-only". Encrypt your wallet and the program told you your recovery phrase no longer covered your money. Now the condition also accepts a wallet whose seed is present but encrypted. Same wallet, before and after: schema bip44, same four derived addresses, 1.00 BTF still reported as covered. 2. The plaintext keys stayed behind in the Berkeley DB logs. wallet.dat comes out clean -- verified by scanning the raw file for the 118 bytes of a known private key. database/log.0000000002 did not: it held the whole key, and still did after a full node restart, because every record this wallet ever wrote passed through the log. That defeats the feature for the case it exists for, since our own backup advice is to copy the whole directory rather than wallet.dat alone. log_archive() cannot fix it: it only drops logs Berkeley DB considers obsolete and never the one it is writing to, and with one 10 MB log per directory that is precisely where the keys are. They also did not get there at encryption time -- they accumulated over the wallet's whole life -- so nothing done during the rewrite can archive them out. So the encrypt command now closes the environment and deletes the logs on its way out, after the flush that makes every .dat self-contained. This is not secure erasure; the blocks are unlinked, not overwritten. It closes the case of someone reading a copied data directory. Getting there uncovered a third bug, older than both. DBFlush(true) is not safe to call twice. The first call closes the environment, and txn_checkpoint() on a closed environment segfaults instead of returning an error, so the try/catch around it never sees anything. EncryptWallet() already flushes before renaming the wallet, and the command flushes again on the way out -- that pair crashed every single time. Guarded now. The same function has been calling log_archive() with the address of an uninitialised pointer since 0.1.0, on every clean shutdown, in a path whose catch(...) cannot catch a structured fault on Windows. DB_ARCH_REMOVE requires that argument to be NULL. Fixed while here, because the encryption cleanup depends on that call not being a coin flip. --------- Co-authored-by: Gilcimar Duarte <ti@gpm.infra>
Diffstat (limited to 'src/script.cpp')
-rw-r--r--src/script.cpp89
1 files changed, 50 insertions, 39 deletions
diff --git a/src/script.cpp b/src/script.cpp
index 6d4fb34..1e6e1ca 100644
--- a/src/script.cpp
+++ b/src/script.cpp
@@ -1020,43 +1020,54 @@ bool Solver(const CScript& scriptPubKey, uint256 hash, int nHashType, CScript& s
1020 if (!Solver(scriptPubKey, vSolution)) 1020 if (!Solver(scriptPubKey, vSolution))
1021 return false; 1021 return false;
1022 1022
1023 // Compile solution 1023 // Compile solution. Public keys stay available for encrypted wallets so the
1024 CRITICAL_BLOCK(cs_mapKeys) 1024 // wallet can recognize its own outputs while locked; private keys are
1025 // fetched only for an actual signature.
1026 foreach(PAIRTYPE(opcodetype, valtype)& item, vSolution)
1025 { 1027 {
1026 foreach(PAIRTYPE(opcodetype, valtype)& item, vSolution) 1028 if (item.first == OP_PUBKEY)
1027 { 1029 {
1028 if (item.first == OP_PUBKEY) 1030 const valtype& vchPubKey = item.second;
1031 if (!WalletCanSpendKey(vchPubKey))
1032 return false;
1033 if (hash != 0)
1029 { 1034 {
1030 // Sign 1035 CPrivKey vchPrivKey;
1031 const valtype& vchPubKey = item.second; 1036 string strError;
1032 if (!mapKeys.count(vchPubKey)) 1037 if (!GetWalletPrivKey(vchPubKey, vchPrivKey, strError))
1038 return false;
1039 vector<unsigned char> vchSig;
1040 if (!CKey::Sign(vchPrivKey, hash, vchSig))
1033 return false; 1041 return false;
1034 if (hash != 0) 1042 vchSig.push_back((unsigned char)nHashType);
1043 scriptSigRet << vchSig;
1044 }
1045 }
1046 else if (item.first == OP_PUBKEYHASH)
1047 {
1048 vector<unsigned char> vchPubKey;
1049 {
1050 CRITICAL_BLOCK(cs_mapKeys)
1035 { 1051 {
1036 vector<unsigned char> vchSig; 1052 map<uint160, valtype>::iterator mi = mapPubKeys.find(uint160(item.second));
1037 if (!CKey::Sign(mapKeys[vchPubKey], hash, vchSig)) 1053 if (mi == mapPubKeys.end())
1038 return false; 1054 return false;
1039 vchSig.push_back((unsigned char)nHashType); 1055 vchPubKey = (*mi).second;
1040 scriptSigRet << vchSig;
1041 } 1056 }
1042 } 1057 }
1043 else if (item.first == OP_PUBKEYHASH) 1058 if (!WalletCanSpendKey(vchPubKey))
1059 return false;
1060 if (hash != 0)
1044 { 1061 {
1045 // Sign and give pubkey 1062 CPrivKey vchPrivKey;
1046 map<uint160, valtype>::iterator mi = mapPubKeys.find(uint160(item.second)); 1063 string strError;
1047 if (mi == mapPubKeys.end()) 1064 if (!GetWalletPrivKey(vchPubKey, vchPrivKey, strError))
1048 return false; 1065 return false;
1049 const vector<unsigned char>& vchPubKey = (*mi).second; 1066 vector<unsigned char> vchSig;
1050 if (!mapKeys.count(vchPubKey)) 1067 if (!CKey::Sign(vchPrivKey, hash, vchSig))
1051 return false; 1068 return false;
1052 if (hash != 0) 1069 vchSig.push_back((unsigned char)nHashType);
1053 { 1070 scriptSigRet << vchSig << vchPubKey;
1054 vector<unsigned char> vchSig;
1055 if (!CKey::Sign(mapKeys[vchPubKey], hash, vchSig))
1056 return false;
1057 vchSig.push_back((unsigned char)nHashType);
1058 scriptSigRet << vchSig << vchPubKey;
1059 }
1060 } 1071 }
1061 } 1072 }
1062 } 1073 }
@@ -1080,27 +1091,27 @@ bool ExtractPubKey(const CScript& scriptPubKey, bool fMineOnly, vector<unsigned
1080 if (!Solver(scriptPubKey, vSolution)) 1091 if (!Solver(scriptPubKey, vSolution))
1081 return false; 1092 return false;
1082 1093
1083 CRITICAL_BLOCK(cs_mapKeys) 1094 foreach(PAIRTYPE(opcodetype, valtype)& item, vSolution)
1084 { 1095 {
1085 foreach(PAIRTYPE(opcodetype, valtype)& item, vSolution) 1096 valtype vchPubKey;
1097 if (item.first == OP_PUBKEY)
1086 { 1098 {
1087 valtype vchPubKey; 1099 vchPubKey = item.second;
1088 if (item.first == OP_PUBKEY) 1100 }
1089 { 1101 else if (item.first == OP_PUBKEYHASH)
1090 vchPubKey = item.second; 1102 {
1091 } 1103 CRITICAL_BLOCK(cs_mapKeys)
1092 else if (item.first == OP_PUBKEYHASH)
1093 { 1104 {
1094 map<uint160, valtype>::iterator mi = mapPubKeys.find(uint160(item.second)); 1105 map<uint160, valtype>::iterator mi = mapPubKeys.find(uint160(item.second));
1095 if (mi == mapPubKeys.end()) 1106 if (mi == mapPubKeys.end())
1096 continue; 1107 continue;
1097 vchPubKey = (*mi).second; 1108 vchPubKey = (*mi).second;
1098 } 1109 }
1099 if (!fMineOnly || mapKeys.count(vchPubKey)) 1110 }
1100 { 1111 if (!fMineOnly || WalletCanSpendKey(vchPubKey))
1101 vchPubKeyRet = vchPubKey; 1112 {
1102 return true; 1113 vchPubKeyRet = vchPubKey;
1103 } 1114 return true;
1104 } 1115 }
1105 } 1116 }
1106 return false; 1117 return false;