diff --git a/src/chainparamsbase.h b/src/chainparamsbase.h index ea933d1ca832..6c2c3b753b3b 100644 --- a/src/chainparamsbase.h +++ b/src/chainparamsbase.h @@ -9,6 +9,7 @@ #include #include +#include class ArgsManager; diff --git a/src/node/interface_ui.h b/src/node/interface_ui.h index f8a16d578aa1..8b77512f0897 100644 --- a/src/node/interface_ui.h +++ b/src/node/interface_ui.h @@ -9,6 +9,7 @@ #include #include #include +#include class CBlockIndex; enum class SynchronizationState; diff --git a/src/rpc/mnemonic.cpp b/src/rpc/mnemonic.cpp index 059231c72bec..f0d660f31e4b 100644 --- a/src/rpc/mnemonic.cpp +++ b/src/rpc/mnemonic.cpp @@ -50,7 +50,7 @@ static RPCHelpMan mnemonicrpc() RPCResult{RPCResult::Type::ANY, "", ""}, RPCExamples{ HelpExampleCli("mnemonic", "\"new\" \"my pass phrase\" french 64 true") + - HelpExampleRpc("smsgpurge", "\"new\", \"my pass phrase\", french, 64, true") + HelpExampleRpc("mnemonic", "\"new\", \"my pass phrase\", french, 64, true") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { diff --git a/src/smsg/crypter.cpp b/src/smsg/crypter.cpp index 3994d76386c5..0404ad104b8c 100644 --- a/src/smsg/crypter.cpp +++ b/src/smsg/crypter.cpp @@ -11,7 +11,7 @@ #include -bool SecMsgCrypter::SetKey(const std::vector &vchNewKey, const uint8_t *chNewIV) +bool SecMsgCrypter::SetKey(const std::vector> &vchNewKey, const uint8_t *chNewIV) { if (vchNewKey.size() != SMSG_CRYPTO_KEY_SIZE) { return false; diff --git a/src/smsg/crypter.h b/src/smsg/crypter.h index 3f9964af65fa..d2ddfe5b39f4 100644 --- a/src/smsg/crypter.h +++ b/src/smsg/crypter.h @@ -39,7 +39,7 @@ class SecMsgCrypter fKeySet = false; } - bool SetKey(const std::vector &vchNewKey, const uint8_t *chNewIV); + bool SetKey(const std::vector> &vchNewKey, const uint8_t *chNewIV); bool SetKey(const uint8_t *chNewKey, const uint8_t *chNewIV); bool Encrypt(const uint8_t *chPlaintext, uint32_t nPlain, std::vector &vchCiphertext); bool Decrypt(const uint8_t *chCiphertext, uint32_t nCipher, std::vector &vchPlaintext); diff --git a/src/smsg/rpcsmessage.cpp b/src/smsg/rpcsmessage.cpp index 33ad712b5503..d8fdff569481 100644 --- a/src/smsg/rpcsmessage.cpp +++ b/src/smsg/rpcsmessage.cpp @@ -2596,8 +2596,21 @@ static RPCHelpMan smsgimport() } std::vector vsmsg = ParseHex(str_msg.c_str()); + if (vsmsg.size() < smsg::SMSG_HDR_LEN) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "msg too short"); + } + smsg::SecureMessage smsg(vsmsg.data()); smsg.pPayload = vsmsg.data() + smsg::SMSG_HDR_LEN; + if (smsg.nPayload < smsg::SMSG_MIN_CIPERTEXT_SIZE) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Smsg payload size too low"); + } + if (smsg.nPayload > smsg::SMSG_MAX_MSG_WORST_PAID) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Smsg payload size too high"); + } + if (smsg.nPayload != vsmsg.size() - smsg::SMSG_HDR_LEN) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Mismatched smsg payload size"); + } UniValue result(UniValue::VOBJ); std::string str_error; diff --git a/src/smsg/securemessage.h b/src/smsg/securemessage.h index c73f9a5a6e87..057919898115 100644 --- a/src/smsg/securemessage.h +++ b/src/smsg/securemessage.h @@ -44,6 +44,11 @@ class SecureMessage return version[0] == 3; } + bool IsPurged() const + { + return version[0] == 0 && version[1] == 0; + } + void set(const uint8_t *data) { size_t ofs = 0; diff --git a/src/smsg/smessage.cpp b/src/smsg/smessage.cpp index 98b7fcfd0973..96ab6f52024d 100644 --- a/src/smsg/smessage.cpp +++ b/src/smsg/smessage.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2014-2016 The ShadowCoin developers -// Copyright (c) 2017-2025 The Particl Core developers +// Copyright (c) 2017-2026 The Particl Core developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -126,7 +126,7 @@ inline static void memput_int64_le(uint8_t *p, int64_t v) { memcpy(p, &v, 8); } -inline static uint32_t memget_int64_le(uint8_t *p) { +inline static int64_t memget_int64_le(uint8_t *p) { int64_t v = 0; memcpy(&v, p, 8); v = (int64_t) le64toh_internal((uint64_t) v); @@ -223,7 +223,7 @@ void ThreadSecureMsg(smsg::CSMSG *smsg_module) std::string fileName = util::ToString(it->first); - fs::path fullPath = gArgs.GetDataDirNet() / fs::PathFromString(STORE_DIR) / fs::PathFromString(fileName + "_01.dat"); + fs::path fullPath = smsg_module->m_smsg_storedir / fs::PathFromString(fileName + "_01.dat"); if (fs::exists(fullPath)) { try { fs::remove(fullPath); } catch (const fs::filesystem_error &ex) { @@ -234,7 +234,7 @@ void ThreadSecureMsg(smsg::CSMSG *smsg_module) } // Look for a wl file, it stores incoming messages when wallet is locked - fullPath = gArgs.GetDataDirNet() / fs::PathFromString(STORE_DIR) / fs::PathFromString(fileName + "_01_wl.dat"); + fullPath = smsg_module->m_smsg_storedir / fs::PathFromString(fileName + "_01_wl.dat"); if (fs::exists(fullPath)) { try { fs::remove(fullPath); } catch (const fs::filesystem_error &ex) { @@ -301,6 +301,7 @@ void ThreadSecureMsg(smsg::CSMSG *smsg_module) continue; } + smsg_module->SmsgMisbehaving(pnode, 5); LOCK(pnode->smsgData.cs_smsg_net); int64_t ignoreUntil = GetTime() + SMSG_TIME_IGNORE; pnode->smsgData.ignoreUntil = ignoreUntil; @@ -365,19 +366,36 @@ void ThreadSecureMsgPow(smsg::CSMSG *smsg_module) break; } } - + if (smsgStored.vchMessage.size() < smsg::SMSG_HDR_LEN) { + LogPrintf("%s: Invalid smsg in queue - header size\n", __func__); + { + LOCK(cs_smsgDB); + dbOutbox.EraseSmesg(chKey); + } + continue; + } uint8_t *pHeader = smsgStored.vchMessage.data(); - uint8_t *pPayload = &smsgStored.vchMessage[SMSG_HDR_LEN]; SecureMessage smsg(pHeader); + if (smsg.nPayload < (smsg.IsPaidVersion() ? 32 : 0) + smsg::SMSG_MIN_CIPERTEXT_SIZE || + smsg.nPayload > smsg::SMSG_MAX_MSG_WORST_PAID || + smsgStored.vchMessage.size() != smsg::SMSG_HDR_LEN + smsg.nPayload) { + LogPrintf("%s: Invalid smsg in queue - payload size\n", __func__); + { + LOCK(cs_smsgDB); + dbOutbox.EraseSmesg(chKey); + } + continue; + } + uint8_t *pPayload = &smsgStored.vchMessage[SMSG_HDR_LEN]; const int64_t FUND_TXN_TIMEOUT = 3600 * 48; int64_t now = GetTime(); if (smsg.IsPaidVersion()) { - if (smsgModule.CheckFundingTx(consensus_params, &smsg, pPayload) != SMSG_NO_ERROR) { + if (smsg_module->CheckFundingTx(consensus_params, &smsg, pPayload) != SMSG_NO_ERROR) { if (smsg.timestamp > now + FUND_TXN_TIMEOUT) { uint160 msgId; - smsgModule.HashMsg(smsg, pPayload, smsg.nPayload - 32, msgId); + smsg_module->HashMsg(smsg, pPayload, smsg.nPayload - 32, msgId); LogPrintf("%s: Funding txn timeout, dropping message %s\n", __func__, msgId.ToString()); LOCK(cs_smsgDB); dbOutbox.EraseSmesg(chKey); @@ -386,7 +404,7 @@ void ThreadSecureMsgPow(smsg::CSMSG *smsg_module) } } else { // Do proof of work - rv = smsgModule.SetHash(&smsg, pPayload, smsg.nPayload); + rv = smsg_module->SetHash(&smsg, pPayload, smsg.nPayload); if (rv == SMSG_SHUTDOWN_DETECTED) { break; // Leave message in db when terminated due to shutdown } @@ -515,16 +533,15 @@ int CSMSG::BuildBucketSet() uint32_t nMessages = 0; unsigned char header_buffer[SMSG_HDR_LEN]; - fs::path pathSmsgDir = gArgs.GetDataDirNet() / fs::PathFromString(STORE_DIR); fs::directory_iterator itend; - if (!fs::exists(pathSmsgDir) || - !fs::is_directory(pathSmsgDir)) { + if (!fs::exists(m_smsg_storedir) || + !fs::is_directory(m_smsg_storedir)) { LogPrintf("Message store directory does not exist.\n"); return SMSG_NO_ERROR; // not an error } - for (fs::directory_iterator itd(pathSmsgDir); itd != itend; ++itd) { + for (fs::directory_iterator itd(m_smsg_storedir); itd != itend; ++itd) { if (!fs::is_regular_file(itd->status())) { continue; } @@ -598,13 +615,17 @@ int CSMSG::BuildBucketSet() } smsg.set(header_buffer); token.timestamp = smsg.timestamp; - token.ttl = smsg.version[0] == 0 && smsg.version[1] == 0 ? 0 // Purged message header + token.ttl = smsg.IsPurged() ? 0 // Purged message header : smsg.m_ttl; token.m_changed = now - fileTime; if (smsg.m_ttl > 0 && (bucket.nLeastTTL == 0 || smsg.m_ttl < bucket.nLeastTTL)) { bucket.nLeastTTL = smsg.m_ttl; } if (smsg.nPayload < 8) { + if (smsg.nPayload && fseek(fp, smsg.nPayload, SEEK_CUR) != 0) { + LogPrintf("fseek failed: %s.\n", SysErrorString(errno)); + break; + } continue; } if (fread(token.sample, sizeof(uint8_t), 8, fp) != 8) { @@ -672,7 +693,7 @@ int CSMSG::AddWalletAddresses() LogPrint(BCLog::SMSG, "%s\n", __func__); #ifdef ENABLE_WALLET - if (!gArgs.GetBoolArg("-smsgsaddnewkeys", false)) { + if (!m_addnewkeys) { LogPrint(BCLog::SMSG, "%s smsgsaddnewkeys option is disabled.\n", __func__); return SMSG_NO_ERROR; } @@ -903,6 +924,10 @@ bool CSMSG::Start(std::shared_ptr pwalletIn, std::vectorsecond.nLockCount > 0) { LogPrint(BCLog::SMSG, "Bucket is locked %u, waiting for peer %u to send data.\n", it_lb->second.nLockCount, it_lb->second.nLockPeerId); - nLocked++; continue; } @@ -1505,7 +1528,12 @@ int CSMSG::ReceiveData(PeerManager *peerLogic, CNode *pfrom, const std::string & uint32_t nBuckets = memget_uint32_le(&vchData[0]); - if (vchData.size() < 4 + nBuckets * 8) { + if (nBuckets < 1 || nBuckets > SMSG_MAX_SHOW) { + SmsgMisbehaving(pfrom, 100); + return SMSG_GENERAL_ERROR; + } + if (vchData.size() < (uint64_t)4 + (uint64_t)nBuckets * 8) { + SmsgMisbehaving(pfrom, 10); return SMSG_GENERAL_ERROR; } @@ -1603,6 +1631,10 @@ int CSMSG::ReceiveData(PeerManager *peerLogic, CNode *pfrom, const std::string & peerLogic->MisbehavingById(pfrom->GetId(), 1, "smsg-time"); return SMSG_GENERAL_ERROR; } + if (time % SMSG_BUCKET_LEN) { + peerLogic->MisbehavingById(pfrom->GetId(), 10, "smsg-time"); + return SMSG_GENERAL_ERROR; + } std::vector vchDataOut; @@ -1660,6 +1692,10 @@ int CSMSG::ReceiveData(PeerManager *peerLogic, CNode *pfrom, const std::string & memcpy(&vchDataOut[nd], p, 16); } + size_t n_messages = (vchDataOut.size() - 8) / 16; + if (pfrom->smsgData.m_num_want_sent + n_messages >= MAX_WANT_SENT) { + break; + } } if (vchDataOut.size() > 8) { @@ -1688,6 +1724,10 @@ int CSMSG::ReceiveData(PeerManager *peerLogic, CNode *pfrom, const std::string & vchBunch.resize(4 + 8); // nMessages + bucketTime int n = (vchData.size() - 8) / 16; + if (n > MAX_WANT_SENT) { + peerLogic->MisbehavingById(pfrom->GetId(), 10, "overfilled-smsg-want"); + return SMSG_GENERAL_ERROR; + } int64_t time = memget_int64_le(&vchData[0]); uint32_t nBunch = 0; @@ -1800,7 +1840,15 @@ int CSMSG::ReceiveData(PeerManager *peerLogic, CNode *pfrom, const std::string & } int64_t time = memget_int64_le(&vchData[0]); - + int64_t now = m_node->chainman->GetAdjustedTimeInt(); + if (time < now) { + peerLogic->MisbehavingById(pfrom->GetId(), 1, "smsg-ignore-time-past"); + return SMSG_GENERAL_ERROR; + } + // Clamp max ignore time to bantime + if (time > now + SMSG_DEFAULT_BANTIME) { + time = now + SMSG_DEFAULT_BANTIME; + } { LOCK(pfrom->smsgData.cs_smsg_net); pfrom->smsgData.ignoreUntil = time; @@ -2223,11 +2271,8 @@ bool CSMSG::ScanBuckets(bool scan_all) uint32_t nFoundMessages = 0; unsigned char header_buffer[SMSG_HDR_LEN]; - fs::path pathSmsgDir = gArgs.GetDataDirNet() / fs::PathFromString(STORE_DIR); - fs::directory_iterator itend; - - if (!fs::exists(pathSmsgDir) || - !fs::is_directory(pathSmsgDir)) { + if (!fs::exists(m_smsg_storedir) || + !fs::is_directory(m_smsg_storedir)) { LogPrintf("Message store directory does not exist.\n"); return true; // not an error } @@ -2235,7 +2280,8 @@ bool CSMSG::ScanBuckets(bool scan_all) SecureMessage smsg; std::vector vchData; - for (fs::directory_iterator itd(pathSmsgDir); itd != itend; ++itd) { + fs::directory_iterator itend; + for (fs::directory_iterator itd(m_smsg_storedir); itd != itend; ++itd) { if (!fs::is_regular_file(itd->status())) { continue; } @@ -2306,10 +2352,14 @@ bool CSMSG::ScanBuckets(bool scan_all) } smsg.set(header_buffer); + if (smsg.nPayload < SMSG_MIN_CIPERTEXT_SIZE || smsg.nPayload > SMSG_MAX_MSG_WORST_PAID) { + LogPrintf("Invalid smsg payload size\n"); + break; + } + try { vchData.resize(smsg.nPayload); } catch (std::exception &e) { LogPrintf("SecureMsgWalletUnlocked(): Could not resize vchData, %u, %s\n", smsg.nPayload, e.what()); - fclose(fp); - return false; + break; } if (fread(vchData.data(), sizeof(uint8_t), smsg.nPayload, fp) != smsg.nPayload) { @@ -2405,11 +2455,10 @@ int CSMSG::WalletUnlocked(wallet::CWallet *pwallet) uint32_t nFoundMessages = 0; unsigned char header_buffer[SMSG_HDR_LEN]; - fs::path pathSmsgDir = gArgs.GetDataDirNet() / fs::PathFromString(STORE_DIR); fs::directory_iterator itend; - if (!fs::exists(pathSmsgDir) || - !fs::is_directory(pathSmsgDir)) { + if (!fs::exists(m_smsg_storedir) || + !fs::is_directory(m_smsg_storedir)) { LogPrintf("Message store directory does not exist.\n"); return SMSG_NO_ERROR; // not an error } @@ -2417,7 +2466,7 @@ int CSMSG::WalletUnlocked(wallet::CWallet *pwallet) SecureMessage smsg; std::vector vchData; - for (fs::directory_iterator itd(pathSmsgDir); itd != itend; ++itd) { + for (fs::directory_iterator itd(m_smsg_storedir); itd != itend; ++itd) { if (!fs::is_regular_file(itd->status())) { continue; } @@ -2477,6 +2526,10 @@ int CSMSG::WalletUnlocked(wallet::CWallet *pwallet) break; } smsg.set(header_buffer); + if (smsg.nPayload < SMSG_MIN_CIPERTEXT_SIZE || smsg.nPayload > SMSG_MAX_MSG_WORST_PAID) { + LogPrintf("Invalid smsg payload size\n"); + break; + } try { vchData.resize(smsg.nPayload); } catch (std::exception &e) { LogPrintf("%s: Could not resize vchData, %u, %s\n", __func__, smsg.nPayload, e.what()); @@ -2546,7 +2599,7 @@ int CSMSG::WalletKeyChanged(CKeyID &keyId, const std::string &sLabel, ChangeType LogPrintf("%s\n", __func__); - if (!gArgs.GetBoolArg("-smsgsaddnewkeys", false)) { + if (!m_addnewkeys) { LogPrint(BCLog::SMSG, "%s smsgsaddnewkeys option is disabled.\n", __func__); return SMSG_GENERAL_ERROR; } @@ -2567,7 +2620,7 @@ int CSMSG::ScanMessage(const uint8_t *pHeader, const uint8_t *pPayload, uint32_t fOwnMessage = false; MessageData msg; // placeholder CKeyID addressTo; - for (auto &p : smsgModule.keyStore.mapKeys) { + for (auto &p : keyStore.mapKeys) { auto &address = p.first; auto &key = p.second; @@ -2707,7 +2760,7 @@ int CSMSG::ScanMessage(const uint8_t *pHeader, const uint8_t *pPayload, uint32_t #if HAVE_SYSTEM if (!fExisted) { // notify an external script when a message comes in - std::string strCmd = gArgs.GetArg("-smsgnotify", ""); + std::string strCmd = m_notify_cmd; //TODO: Format message if (!strCmd.empty()) { @@ -3059,11 +3112,9 @@ int CSMSG::Retrieve(const SecMsgToken &token, std::vector &vchData) LogPrint(BCLog::SMSG, "%s: %d.\n", __func__, token.timestamp); AssertLockHeld(cs_smsg); - fs::path pathSmsgDir = gArgs.GetDataDirNet() / fs::PathFromString(STORE_DIR); - int64_t bucket = token.timestamp - (token.timestamp % SMSG_BUCKET_LEN); std::string fileName = util::ToString(bucket) + "_01.dat"; - fs::path fullpath = pathSmsgDir / fs::PathFromString(fileName); + fs::path fullpath = m_smsg_storedir / fs::PathFromString(fileName); FILE *fp; errno = 0; @@ -3110,11 +3161,9 @@ int CSMSG::Remove(const SecMsgToken &token) unsigned char header_buffer[SMSG_HDR_LEN]; - fs::path pathSmsgDir = gArgs.GetDataDirNet() / fs::PathFromString(STORE_DIR); - int64_t bucket = token.timestamp - (token.timestamp % SMSG_BUCKET_LEN); std::string fileName = util::ToString(bucket) + "_01.dat"; - fs::path fullpath = pathSmsgDir / fs::PathFromString(fileName); + fs::path fullpath = m_smsg_storedir / fs::PathFromString(fileName); FILE *fp; errno = 0; @@ -3136,8 +3185,8 @@ int CSMSG::Remove(const SecMsgToken &token) SecureMessage smsg(header_buffer); uint16_t z = 0; - if (0 != fseek(fp, token.offset + 4, SEEK_SET) - || 2 != fwrite(&z, 1, 2, fp)) { + if (0 != fseek(fp, token.offset + 8, SEEK_SET) || + 2 != fwrite(&z, 1, 2, fp)) { fclose(fp); return errorN(SMSG_GENERAL_ERROR, "%s - zero version error: %s.", __func__, SysErrorString(errno)); } @@ -3147,12 +3196,15 @@ int CSMSG::Remove(const SecMsgToken &token) return errorN(SMSG_GENERAL_ERROR, "%s - fseek, error: %s.", __func__, SysErrorString(errno)); } - size_t zlen = smsg.nPayload - 8; - std::vector zbuf(zlen); - memset(zbuf.data(), 0, zlen); - if (smsg.nPayload <= 8 || zlen != fwrite(zbuf.data(), 1, zlen, fp)) { - fclose(fp); - return errorN(SMSG_GENERAL_ERROR, "%s - fwrite, zlen %d, error: %s.", __func__, zlen, SysErrorString(errno)); + // Zero all except the message token + if (smsg.nPayload > 8) { + size_t zlen = smsg.nPayload - 8; + std::vector zbuf(zlen); + memset(zbuf.data(), 0, zlen); + if (zlen != fwrite(zbuf.data(), 1, zlen, fp)) { + fclose(fp); + return errorN(SMSG_GENERAL_ERROR, "%s - fwrite, zlen %d, error: %s.", __func__, zlen, SysErrorString(errno)); + } } fclose(fp); @@ -3167,7 +3219,7 @@ int CSMSG::SmsgMisbehaving(CNode *pfrom, uint8_t n) if (pfrom->smsgData.misbehaving > 100) { pfrom->smsgData.misbehaving = 0; - pfrom->smsgData.ignoreUntil = GetTime() + gArgs.GetIntArg("-smsgbantime", SMSG_DEFAULT_BANTIME); + pfrom->smsgData.ignoreUntil = GetTime() + m_bantime; LogPrintf("Node is ignoring peer %d until %d.\n", pfrom->GetId(), pfrom->smsgData.ignoreUntil); } @@ -3207,7 +3259,11 @@ int CSMSG::Receive(PeerManager *peerLogic, CNode *pfrom, std::vector &v LogPrintf("Error: Received unsolicited message bunch from peer %d: %d, %d.\n", pfrom->GetId(), nBunch, pfrom->smsgData.m_num_want_sent); SmsgMisbehaving(pfrom, 20); } - pfrom->smsgData.m_num_want_sent -= nBunch; + if (nBunch > (uint32_t)pfrom->smsgData.m_num_want_sent) { + pfrom->smsgData.m_num_want_sent = 0; + } else { + pfrom->smsgData.m_num_want_sent -= nBunch; + } if (nBunch == 0 || nBunch > MAX_BUNCH_MESSAGES || vchData.size() > MAX_BUNCH_BYTES) { LogPrintf("Error: Invalid message bunch received for bucket %d: %d, %d.\n", bktTime, nBunch, vchData.size()); @@ -3229,12 +3285,22 @@ int CSMSG::Receive(PeerManager *peerLogic, CNode *pfrom, std::vector &v for (uint32_t i = 0; i < nBunch; ++i) { if (vchData.size() - n < SMSG_HDR_LEN) { - LogPrintf("Error: not enough data sent, n = %u.\n", n); + LogPrintf("Error: Not enough data sent, n = %u.\n", n); break; } SecureMessage smsg(&vchData[n]); const uint8_t *pPayload = &vchData[n + SMSG_HDR_LEN]; + if (smsg.nPayload < SMSG_MIN_CIPERTEXT_SIZE || smsg.nPayload > SMSG_MAX_MSG_WORST_PAID) { + LogPrintf("Error: Message payload size invalid, n = %u, payload %u.\n", n, smsg.nPayload); + SmsgMisbehaving(pfrom, 100); + break; + } + if (vchData.size() - n < SMSG_HDR_LEN + smsg.nPayload) { + LogPrintf("Error: Not enough data sent, n = %u, payload %u.\n", n, smsg.nPayload); + SmsgMisbehaving(pfrom, 100); + break; + } if (!smsg.IsPaidVersion() && now - start_time > SMSG_BUCKET_LEN * 2) { // buckets should be fully matched after time if (smsg.timestamp < now - SMSG_BUCKET_LEN * 3) { @@ -3359,12 +3425,10 @@ int CSMSG::StoreUnscanned(const uint8_t *pHeader, const uint8_t *pPayload, uint3 return errorN(SMSG_PURGED_MSG, "%s: Purged message.", __func__); } - fs::path pathSmsgDir; try { - pathSmsgDir = gArgs.GetDataDirNet() / fs::PathFromString(STORE_DIR); - fs::create_directory(pathSmsgDir); + fs::create_directory(m_smsg_storedir); } catch (const fs::filesystem_error &ex) { - return errorN(SMSG_GENERAL_ERROR, "%s - Failed to create directory %s - %s.", __func__, fs::PathToString(pathSmsgDir), ex.what()); + return errorN(SMSG_GENERAL_ERROR, "%s - Failed to create directory %s - %s.", __func__, fs::PathToString(m_smsg_storedir), ex.what()); } int64_t now = m_node->chainman->GetAdjustedTimeInt(); @@ -3378,7 +3442,7 @@ int CSMSG::StoreUnscanned(const uint8_t *pHeader, const uint8_t *pPayload, uint3 int64_t bucket = smsg.timestamp - (smsg.timestamp % SMSG_BUCKET_LEN); std::string fileName = util::ToString(bucket) + "_01_wl.dat"; - fs::path fullpath = pathSmsgDir / fs::PathFromString(fileName); + fs::path fullpath = m_smsg_storedir / fs::PathFromString(fileName); FILE *fp; errno = 0; @@ -3413,12 +3477,10 @@ int CSMSG::Store(const uint8_t *pHeader, const uint8_t *pPayload, uint32_t nPayl } long int ofs; - fs::path pathSmsgDir; try { - pathSmsgDir = gArgs.GetDataDirNet() / fs::PathFromString(STORE_DIR); - fs::create_directory(pathSmsgDir); + fs::create_directory(m_smsg_storedir); } catch (const fs::filesystem_error &ex) { - return errorN(SMSG_GENERAL_ERROR, "Failed to create directory %s - %s.", fs::PathToString(pathSmsgDir), ex.what()); + return errorN(SMSG_GENERAL_ERROR, "Failed to create directory %s - %s.", fs::PathToString(m_smsg_storedir), ex.what()); } int64_t now = m_node->chainman->GetAdjustedTimeInt(); @@ -3446,7 +3508,7 @@ int CSMSG::Store(const uint8_t *pHeader, const uint8_t *pPayload, uint32_t nPayl } std::string fileName = util::ToString(bucketTime) + "_01.dat"; - fs::path fullpath = pathSmsgDir / fs::PathFromString(fileName); + fs::path fullpath = m_smsg_storedir / fs::PathFromString(fileName); FILE *fp; errno = 0; @@ -3551,7 +3613,8 @@ int CSMSG::Purge(std::vector &vMsgId, std::string &sError) break; } - chKey[0] = 'p'; + chKey[0] = DBK_PURGED_TOKEN[0]; + chKey[1] = DBK_PURGED_TOKEN[1]; db.WritePurged(chKey, purged); setPurged.insert(purged); @@ -3845,7 +3908,7 @@ int CSMSG::ClearBestBlock() int CSMSG::Validate(const SecureMessage *psmsg, const uint8_t *pPayload, uint32_t nPayload) { if (psmsg->IsPaidVersion()) { - if (nPayload > SMSG_MAX_MSG_BYTES_PAID) { + if (nPayload > SMSG_MAX_MSG_WORST_PAID) { return SMSG_PAYLOAD_OVER_SIZE; } } else @@ -4071,11 +4134,11 @@ int CSMSG::Encrypt(SecureMessage &smsg, const CKeyID &addressFrom, const CKeyID // Use public key P and calculate the SHA512 hash H. // The first 32 bytes of H are called key_e and the last 32 bytes are called key_m. - std::vector vchHashed(64); // 512 + std::vector> vchHashed(64); // 512 memset(vchHashed.data(), 0, 64); CSHA512().Write(P.begin(), 32).Finalize(&vchHashed[0]); - std::vector key_e(&vchHashed[0], &vchHashed[0]+32); - std::vector key_m(&vchHashed[32], &vchHashed[32]+32); + std::vector> key_e(&vchHashed[0], &vchHashed[0]+32); + std::vector> key_m(&vchHashed[32], &vchHashed[32]+32); std::vector vchPayload, vchCompressed; uint8_t *pMsgData; @@ -4190,7 +4253,8 @@ int CSMSG::Encrypt(SecureMessage &smsg, const CKeyID &addressFrom, const CKeyID int CSMSG::Import(SecureMessage *psmsg, std::string &sError, bool setread, bool submitmsg, bool rehashmsg) { - if (psmsg->IsPaidVersion() && psmsg->nPayload < 33) { + if ((psmsg->IsPaidVersion() && psmsg->nPayload < 32 + SMSG_MIN_CIPERTEXT_SIZE) || + psmsg->nPayload < SMSG_MIN_CIPERTEXT_SIZE) { sError = "Payload too short."; return SMSG_GENERAL_ERROR; } @@ -4674,11 +4738,17 @@ int CSMSG::Decrypt(bool fTestOnly, const CKey &keyDest, const CKeyID &address, c SecureMessage smsg(pHeader); if (smsg.IsPaidVersion()) { + if (nPayload < 32 + SMSG_MIN_CIPERTEXT_SIZE) { + return errorN(SMSG_GENERAL_ERROR, "%s: Paid smsg payload too small.", __func__); + } nPayload -= 32; // Exclude funding txid } else if (smsg.version[0] != 2) { return errorN(SMSG_UNKNOWN_VERSION, "%s: Unknown version number.", __func__); } + if (nPayload < SMSG_MIN_CIPERTEXT_SIZE) { + return errorN(SMSG_GENERAL_ERROR, "%s: Smsg payload too small.", __func__); + } // Do an EC point multiply with private key k and public key R. This gives you public key P. //CPubKey R(psmsg->cpkR, psmsg->cpkR+33); @@ -4695,11 +4765,11 @@ int CSMSG::Decrypt(bool fTestOnly, const CKey &keyDest, const CKeyID &address, c // Use public key P to calculate the SHA512 hash H. // The first 32 bytes of H are called key_e and the last 32 bytes are called key_m. - std::vector vchHashedDec(64); // 512 bits + std::vector> vchHashedDec(64); // 512 bits memset(vchHashedDec.data(), 0, 64); CSHA512().Write(P.begin(), 32).Finalize(&vchHashedDec[0]); - std::vector key_e(&vchHashedDec[0], &vchHashedDec[0]+32); - std::vector key_m(&vchHashedDec[32], &vchHashedDec[32]+32); + std::vector> key_e(&vchHashedDec[0], &vchHashedDec[0]+32); + std::vector> key_m(&vchHashedDec[32], &vchHashedDec[32]+32); // Message authentication code, (hash of timestamp + iv + destination + payload) uint8_t MAC[32]; @@ -4726,6 +4796,9 @@ int CSMSG::Decrypt(bool fTestOnly, const CKey &keyDest, const CKeyID &address, c if (!crypter.Decrypt(pPayload, nPayload, vchPayload)) { return errorN(SMSG_GENERAL_ERROR, "%s: Decrypt failed.", __func__); } + if (vchPayload.size() < 9) { + return errorN(SMSG_GENERAL_ERROR, "%s: Message payload too short", __func__); + } msg.timestamp = smsg.timestamp; uint32_t lenData, lenPlain; @@ -4747,10 +4820,16 @@ int CSMSG::Decrypt(bool fTestOnly, const CKey &keyDest, const CKeyID &address, c compressed = vchPayload[1]; insert_null = false; start_offset = 1; + if (vchPayload.size() < SMSG_PL_HDR_LEN+1+start_offset) { + return errorN(SMSG_GENERAL_ERROR, "%s: Message payload too short", __func__); + } lenData = vchPayload.size() - (SMSG_PL_HDR_LEN+start_offset); lenPlain = memget_uint32_le(&vchPayload[1+20+65+start_offset]); pMsgData = &vchPayload[SMSG_PL_HDR_LEN+start_offset]; } else { + if (vchPayload.size() < SMSG_PL_HDR_LEN+1) { + return errorN(SMSG_GENERAL_ERROR, "%s: Message payload too short", __func__); + } lenData = vchPayload.size() - SMSG_PL_HDR_LEN; lenPlain = memget_uint32_le(&vchPayload[1+20+65]); pMsgData = &vchPayload[SMSG_PL_HDR_LEN]; @@ -4759,6 +4838,10 @@ int CSMSG::Decrypt(bool fTestOnly, const CKey &keyDest, const CKeyID &address, c msg_info->payload_version = payload_version; } + if (lenPlain > lenData) { + return errorN(SMSG_GENERAL_ERROR, "%s: Bad lenPlain", __func__); + } + try { msg.vchMessage.resize(lenPlain + (insert_null ? 1 : 0)); } catch (std::exception &e) { @@ -4859,7 +4942,7 @@ int CSMSG::Decrypt(bool fTestOnly, const CKeyID &address, const uint8_t *pHeader #ifdef ENABLE_WALLET if (!keyDest.IsValid()) { - for (const auto &pw : smsgModule.m_vpwallets) { + for (const auto &pw : m_vpwallets) { if (pw->IsLocked()) { if (pw->HaveKey(address)) { return SMSG_WALLET_LOCKED; diff --git a/src/smsg/smessage.h b/src/smsg/smessage.h index 982b2229c49f..118303ee8d15 100644 --- a/src/smsg/smessage.h +++ b/src/smsg/smessage.h @@ -1,5 +1,5 @@ // Copyright (c) 2014-2016 The ShadowCoin developers -// Copyright (c) 2017-2025 The Particl Core developers +// Copyright (c) 2017-2026 The Particl Core developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -80,6 +80,7 @@ enum SecureMessageCodes { const uint32_t SMSG_HDR_LEN = 108; // length of unencrypted header, 4 + 4 + 2 + 1 + 8 + 4 + 16 + 33 + 32 + 4 const uint32_t SMSG_PL_HDR_LEN = 1+20+65+4; // length of encrypted header in payload +const uint32_t SMSG_MIN_CIPERTEXT_SIZE = 16; extern uint32_t SMSG_BUCKET_LEN; // seconds extern uint32_t SMSG_SECONDS_IN_DAY; @@ -104,7 +105,8 @@ const uint32_t SMSG_MAX_MSG_BYTES_PAID = 512 * 1024; // the user input part ( // Max size of payload worst case compression const uint32_t SMSG_MAX_MSG_WORST = LZ4_COMPRESSBOUND(SMSG_MAX_MSG_BYTES+SMSG_PL_HDR_LEN); -const uint32_t SMSG_MAX_MSG_WORST_PAID = LZ4_COMPRESSBOUND(SMSG_MAX_MSG_BYTES_PAID+SMSG_PL_HDR_LEN); +const uint32_t SMSG_MAX_MSG_WORST_PAID = LZ4_COMPRESSBOUND(SMSG_MAX_MSG_BYTES_PAID+SMSG_PL_HDR_LEN) + 32; // +32 for funding txid +static_assert(SMSG_MAX_MSG_WORST_PAID >= SMSG_MAX_MSG_BYTES_PAID+SMSG_PL_HDR_LEN, "Bad SMSG_MAX_MSG_WORST_PAID size"); extern const std::string STORE_DIR; @@ -492,7 +494,12 @@ class CSMSG int64_t m_last_changed = 0; // Updated whenever a message is stored int64_t nLastProcessedPurged = 0; CAmount m_absurd_smsg_fee = 500 * COIN; - uint16_t m_smsg_max_receive_count = SMSG_DEFAULT_MAXRCV; + + uint16_t m_smsg_max_receive_count{SMSG_DEFAULT_MAXRCV}; + int64_t m_bantime{SMSG_DEFAULT_BANTIME}; + bool m_addnewkeys{false}; + std::string m_notify_cmd; + fs::path m_smsg_storedir; std::map m_show_requests; diff --git a/src/test/smsg_tests.cpp b/src/test/smsg_tests.cpp index e424b7dba506..c3d1af590e9d 100644 --- a/src/test/smsg_tests.cpp +++ b/src/test/smsg_tests.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2025 The Particl Core developers +// Copyright (c) 2017-2026 The Particl Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -9,6 +9,7 @@ #include #include +#include #ifdef ENABLE_WALLET #include #include @@ -43,6 +44,26 @@ BOOST_AUTO_TEST_CASE(smsg_test_ckeyId_inits_null) BOOST_CHECK(k.IsNull()); } +BOOST_AUTO_TEST_CASE(smsg_test_encryption) +{ + CKey key_e; + uint8_t iv[16]; + std::vector> key_data(32); + std::vector plaintext, ciphertext; + SecMsgCrypter crypter; + InsecureNewKey(key_e, true); + InsecureRandBytes(iv, sizeof(iv)); + memcpy(key_data.data(), key_e.data(), 32); + crypter.SetKey(key_data, iv); + plaintext.resize(1); + plaintext[0] = 'a'; + + crypter.Encrypt(plaintext.data(), plaintext.size(), ciphertext); + BOOST_CHECK(ciphertext.size() == smsg::SMSG_MIN_CIPERTEXT_SIZE); + crypter.Decrypt(ciphertext.data(), ciphertext.size(), plaintext); + BOOST_CHECK(plaintext.size() == 1); +} + #ifdef ENABLE_WALLET void CheckValid(smsg::SecureMessage &smsg, CKeyID &kFrom, CKeyID &kTo, bool expect_pass) diff --git a/test/functional/feature_part_smsgpaid.py b/test/functional/feature_part_smsgpaid.py index 84b64c37c0fb..3d11f35f723a 100755 --- a/test/functional/feature_part_smsgpaid.py +++ b/test/functional/feature_part_smsgpaid.py @@ -429,8 +429,11 @@ def run_test(self): self.sync_mempools([nodes[0], nodes[1]]) self.stakeBlocks(1, nStakeNode=1) + self.waitForSmsgExchange(20, 1, 0, count_purged=True) + ro = nodes[0].smsgbuckets() + assert (ro['total']['messages'] == 19) + assert (ro['total']['numpurged'] == 1) - self.waitForSmsgExchange(20, 1, 0) inb = nodes[1].smsginbox() assert (len(inb['messages']) == 13) diff --git a/test/functional/test_framework/test_particl.py b/test/functional/test_framework/test_particl.py index bef4d6a83f29..749f80389eb6 100644 --- a/test/functional/test_framework/test_particl.py +++ b/test/functional/test_framework/test_particl.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2017-2023 The Particl Core developers +# Copyright (c) 2017-2026 The Particl Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -131,14 +131,17 @@ def wait_for_wtx(self, node, txid, nTries=20): continue return False - def waitForSmsgExchange(self, nMessages, nodeA, nodeB): + def waitForSmsgExchange(self, nMessages, nodeA, nodeB, count_purged=False): nodes = self.nodes fPass = False for i in range(30): time.sleep(0.5) ro = nodes[nodeA].smsgbuckets() - if ro['total']['messages'] == nMessages: + total_messages = ro['total']['messages'] + if count_purged: + total_messages += ro['total'].get("numpurged", 0) + if total_messages == nMessages: fPass = True break assert (fPass) @@ -147,7 +150,10 @@ def waitForSmsgExchange(self, nMessages, nodeA, nodeB): for i in range(30): time.sleep(0.5) ro = nodes[nodeB].smsgbuckets() - if ro['total']['messages'] == nMessages: + total_messages = ro['total']['messages'] + if count_purged: + total_messages += ro['total'].get("numpurged", 0) + if total_messages == nMessages: fPass = True break assert (fPass)