4fa30abb4b
Build All Platforms / test-linux-unit (push) Waiting to run
Build All Platforms / test-linux-sanitizers (push) Waiting to run
Build All Platforms / build-windows-qt (push) Waiting to run
Build All Platforms / build-windows-daemon (push) Waiting to run
Build All Platforms / build-linux-qt (push) Waiting to run
Build All Platforms / build-linux-daemon (push) Waiting to run
Build All Platforms / build-macos (push) Waiting to run
Build All Platforms / release (push) Blocked by required conditions
Build All Platforms / Trigger TRI-PI ARM64 Build (push) Blocked by required conditions
Pre-v5.10 the secure-messaging store was backed by LevelDB at
<datadir>/smsgDB/. Phase 3a switched it to RocksDB; existing nodes
upgrading to v5.10 would otherwise lose their pubkey cache and
inbox/outbox because RocksDB can't open a LevelDB tree.
Detection: presence of CURRENT without IDENTITY in smsgDB/. RocksDB
writes IDENTITY on first open; LevelDB never does.
Migration path:
1. Atomic rename smsgDB/ → smsgDB.leveldb-backup/
2. Open backup with leveldb::DB (read-only)
3. Open smsgDB/ with rocksdb::DB (create_if_missing)
4. Iterate every key, copy in 5000-entry batches
5. Leave the backup in place — never deleted by the migration code,
so the user can roll back manually if needed
Triggered lazily inside SecMsgDB::Open so no separate flag or RPC is
needed. Already-migrated nodes (IDENTITY present) skip the path. Once
all users are on v5.10+ the helper and the leveldb headers it pulls
in can be dropped.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4385 lines
128 KiB
C++
4385 lines
128 KiB
C++
// Copyright (c) 2014 The ShadowCoin developers
|
|
// Distributed under the MIT/X11 software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
/*
|
|
Notes:
|
|
Running with -debug could leave to and from address hashes and public keys in the log.
|
|
|
|
|
|
parameters:
|
|
-nosmsg Disable secure messaging (fNoSmsg)
|
|
-debugsmsg Show extra debug messages (fDebugSmsg)
|
|
-smsgscanchain Scan the block chain for public key addresses on startup
|
|
|
|
|
|
Wallet Locked
|
|
A copy of each incoming message is stored in bucket files ending in _wl.dat
|
|
wl (wallet locked) bucket files are deleted if they expire, like normal buckets
|
|
When the wallet is unlocked all the messages in wl files are scanned.
|
|
|
|
|
|
Address Whitelist
|
|
Owned Addresses are stored in smsgAddresses vector
|
|
Saved to smsg.ini
|
|
Modify options using the smsglocalkeys rpc command or edit the smsg.ini file (with client closed)
|
|
|
|
|
|
*/
|
|
|
|
#include "smessage.h"
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <cctype>
|
|
#include <stdint.h>
|
|
#include <time.h>
|
|
#include <limits>
|
|
#include <map>
|
|
#include <stdexcept>
|
|
#include <sstream>
|
|
#include <errno.h>
|
|
|
|
#include <openssl/crypto.h>
|
|
#include <openssl/ec.h>
|
|
#include <openssl/ecdh.h>
|
|
#include <openssl/sha.h>
|
|
#include <openssl/aes.h>
|
|
#include <openssl/evp.h>
|
|
#include <openssl/hmac.h>
|
|
|
|
#include <string>
|
|
#include <boost/algorithm/string/predicate.hpp>
|
|
|
|
|
|
#include "base58.h"
|
|
#include "db.h"
|
|
#include "init.h" // pwalletMain
|
|
#include "txdb.h"
|
|
|
|
|
|
#include "lz4/lz4.h"
|
|
|
|
// LevelDB headers retained solely for the one-shot smsgDB leveldb→rocksdb
|
|
// migration in MigrateSmsgDBLevelDbToRocksDb. Once all users have upgraded
|
|
// past v5.10 the migration helper (and these includes) can be dropped.
|
|
#include <leveldb/db.h>
|
|
#include <leveldb/iterator.h>
|
|
|
|
#include "xxhash/xxhash.h"
|
|
#include "xxhash/xxhash.c"
|
|
|
|
|
|
// On 64 bit system ld is 64bits
|
|
#ifdef IS_ARCH_64
|
|
#undef PRId64
|
|
#undef PRIu64
|
|
#undef PRIx64
|
|
#define PRId64 "ld"
|
|
#define PRIu64 "lu"
|
|
#define PRIx64 "lx"
|
|
#endif // IS_ARCH_64
|
|
|
|
|
|
// TODO: For buckets older than current, only need to store no. messages and hash in memory
|
|
|
|
CSignal<void(SecMsgStored&)> NotifySecMsgInboxChanged;
|
|
CSignal<void(SecMsgStored&)> NotifySecMsgOutboxChanged;
|
|
CSignal<void()> NotifySecMsgWalletUnlocked;
|
|
|
|
bool fSecMsgEnabled = false;
|
|
|
|
std::map<int64_t, SecMsgBucket> smsgBuckets;
|
|
std::vector<SecMsgAddress> smsgAddresses;
|
|
SecMsgOptions smsgOptions;
|
|
|
|
uint32_t nPeerIdCounter = 1;
|
|
|
|
|
|
|
|
CCriticalSection cs_smsg;
|
|
CCriticalSection cs_smsgDB;
|
|
|
|
rocksdb::DB *smsgDB = NULL;
|
|
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
namespace
|
|
{
|
|
|
|
// rocksdb::DB::Open shipped the raw DB** overload for years, then added a
|
|
// std::unique_ptr<DB>* form in 8.x and removed the raw form in newer
|
|
// releases (Homebrew's macOS rocksdb hits this path; Ubuntu 22.04 and
|
|
// MSYS2 still expose the DB** form). Pick whichever overload the linked
|
|
// rocksdb actually has via SFINAE — `int` parameter is preferred over
|
|
// `long`, so if DB** exists, the first overload wins; otherwise the
|
|
// fallback that wraps unique_ptr is used.
|
|
template<typename T>
|
|
inline auto OpenSmsgDBImpl(const rocksdb::Options& opts, const std::string& path,
|
|
T** dbptr, int)
|
|
-> decltype(rocksdb::DB::Open(opts, path, dbptr))
|
|
{
|
|
return rocksdb::DB::Open(opts, path, dbptr);
|
|
}
|
|
|
|
template<typename T>
|
|
inline rocksdb::Status OpenSmsgDBImpl(const rocksdb::Options& opts, const std::string& path,
|
|
T** dbptr, long)
|
|
{
|
|
std::unique_ptr<T> tmp;
|
|
auto s = rocksdb::DB::Open(opts, path, &tmp);
|
|
if (s.ok()) *dbptr = tmp.release();
|
|
return s;
|
|
}
|
|
|
|
inline rocksdb::Status OpenSmsgDB(const rocksdb::Options& opts,
|
|
const std::string& path,
|
|
rocksdb::DB** dbptr)
|
|
{
|
|
return OpenSmsgDBImpl(opts, path, dbptr, 0);
|
|
}
|
|
|
|
// ── smsgDB leveldb → rocksdb migration ──────────────────────────────────────
|
|
//
|
|
// Pre-v5.10 the smessage store was backed by LevelDB at <datadir>/smsgDB/.
|
|
// Phase 3a switched it to RocksDB. Existing installations need their pubkey
|
|
// cache and inbox/outbox to carry across. The migration is one-shot and
|
|
// runs lazily inside SecMsgDB::Open: detect the legacy format, rename the
|
|
// directory aside as a backup, copy every key into a fresh rocksdb tree,
|
|
// then proceed with the normal open path. The leveldb backup is preserved
|
|
// (never deleted) so the user can roll back by deleting smsgDB/ and
|
|
// renaming smsgDB.leveldb-backup/ back.
|
|
|
|
// LevelDB and RocksDB share several filenames (CURRENT, MANIFEST-*, LOG).
|
|
// RocksDB additionally writes IDENTITY and OPTIONS-* on first open;
|
|
// presence of CURRENT *without* IDENTITY indicates a legacy LevelDB tree.
|
|
inline bool IsLegacyLevelDbSmsgDir(const fs::path& dir)
|
|
{
|
|
if (!fs::exists(dir / "CURRENT"))
|
|
return false;
|
|
if (fs::exists(dir / "IDENTITY"))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
inline bool MigrateSmsgDBLevelDbToRocksDb(std::string& strError)
|
|
{
|
|
fs::path smsgDir = GetDataDir() / "smsgDB";
|
|
fs::path backupDir = GetDataDir() / "smsgDB.leveldb-backup";
|
|
|
|
if (fs::exists(backupDir)) {
|
|
strError = "smsgDB.leveldb-backup/ already present at " + backupDir.string()
|
|
+ " — manual cleanup required before retrying migration.";
|
|
return false;
|
|
}
|
|
|
|
printf("smessage: migrating LevelDB-format smsgDB to RocksDB...\n");
|
|
|
|
// Atomic rename so the legacy data is never deleted by this routine —
|
|
// worst case we leave the backup and bail. Filesystem rename within the
|
|
// same datadir is atomic on every supported platform.
|
|
std::error_code ec;
|
|
fs::rename(smsgDir, backupDir, ec);
|
|
if (ec) {
|
|
strError = "Failed to rename smsgDB to backup: " + ec.message();
|
|
return false;
|
|
}
|
|
|
|
// Open backup as leveldb (read-only) — create_if_missing left at default
|
|
// false so a malformed dir errors out cleanly.
|
|
leveldb::Options leveldbOpts;
|
|
leveldb::DB* oldDb = nullptr;
|
|
leveldb::Status ls = leveldb::DB::Open(leveldbOpts, backupDir.string(), &oldDb);
|
|
if (!ls.ok()) {
|
|
strError = "Failed to open legacy LevelDB smsgDB at "
|
|
+ backupDir.string() + ": " + ls.ToString();
|
|
return false;
|
|
}
|
|
|
|
// Open destination as fresh rocksdb.
|
|
rocksdb::Options rdbOpts;
|
|
rdbOpts.create_if_missing = true;
|
|
rocksdb::DB* newDb = nullptr;
|
|
rocksdb::Status rs = OpenSmsgDB(rdbOpts, smsgDir.string(), &newDb);
|
|
if (!rs.ok()) {
|
|
delete oldDb;
|
|
strError = "Failed to create new RocksDB smsgDB: " + rs.ToString();
|
|
return false;
|
|
}
|
|
|
|
// Copy every key in batches of 5000.
|
|
leveldb::Iterator* it = oldDb->NewIterator(leveldb::ReadOptions());
|
|
rocksdb::WriteBatch batch;
|
|
int nMigrated = 0;
|
|
int nBatch = 0;
|
|
bool fOk = true;
|
|
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
|
batch.Put(it->key().ToString(), it->value().ToString());
|
|
nBatch++;
|
|
nMigrated++;
|
|
if (nBatch >= 5000) {
|
|
rocksdb::Status ws = newDb->Write(rocksdb::WriteOptions(), &batch);
|
|
if (!ws.ok()) {
|
|
strError = "RocksDB batch write failed during migration: " + ws.ToString();
|
|
fOk = false;
|
|
break;
|
|
}
|
|
batch.Clear();
|
|
nBatch = 0;
|
|
}
|
|
}
|
|
if (fOk && nBatch > 0) {
|
|
rocksdb::Status ws = newDb->Write(rocksdb::WriteOptions(), &batch);
|
|
if (!ws.ok()) {
|
|
strError = "RocksDB final batch write failed: " + ws.ToString();
|
|
fOk = false;
|
|
}
|
|
}
|
|
if (fOk && !it->status().ok()) {
|
|
strError = "LevelDB iterator failed mid-migration: " + it->status().ToString();
|
|
fOk = false;
|
|
}
|
|
delete it;
|
|
delete oldDb;
|
|
delete newDb;
|
|
|
|
if (!fOk) {
|
|
// Leave smsgDB/ in a partially-written state but the backup is
|
|
// intact. The user can recover by removing smsgDB/ and renaming
|
|
// smsgDB.leveldb-backup/ → smsgDB/.
|
|
return false;
|
|
}
|
|
|
|
printf("smessage: migrated %d entries from LevelDB to RocksDB. "
|
|
"Original data preserved at %s\n",
|
|
nMigrated, backupDir.string().c_str());
|
|
return true;
|
|
}
|
|
|
|
const long int SMSG_BUCKET_FILE_SIZE_LIMIT = 0x70000000L;
|
|
const int64_t SMSG_THREAD_SHUTDOWN_WAIT_MS = 5000;
|
|
const int64_t SMSG_THREAD_SHUTDOWN_POLL_MS = 50;
|
|
|
|
std::atomic<int> nSecureMsgThreadsRunning(0);
|
|
|
|
class CSecureMsgThreadGuard
|
|
{
|
|
public:
|
|
CSecureMsgThreadGuard()
|
|
{
|
|
++nSecureMsgThreadsRunning;
|
|
};
|
|
|
|
~CSecureMsgThreadGuard()
|
|
{
|
|
--nSecureMsgThreadsRunning;
|
|
};
|
|
};
|
|
|
|
bool SecureMsgAllDigits(const std::string& value)
|
|
{
|
|
if (value.empty())
|
|
return false;
|
|
|
|
for (std::string::const_iterator it = value.begin(); it != value.end(); ++it)
|
|
{
|
|
if (!std::isdigit((unsigned char) *it))
|
|
return false;
|
|
};
|
|
|
|
return true;
|
|
};
|
|
|
|
bool SecureMsgParseBucketFilename(const std::string& fileName, int64_t& bucket, uint32_t& fileIndex, bool& fWalletLocked)
|
|
{
|
|
if (!boost::algorithm::ends_with(fileName, ".dat"))
|
|
return false;
|
|
|
|
std::string baseName = fileName.substr(0, fileName.size() - 4);
|
|
fWalletLocked = false;
|
|
if (boost::algorithm::ends_with(baseName, "_wl"))
|
|
{
|
|
fWalletLocked = true;
|
|
baseName.erase(baseName.size() - 3);
|
|
};
|
|
|
|
size_t sep = baseName.find_first_of("_");
|
|
if (sep == std::string::npos
|
|
|| sep == 0
|
|
|| sep + 1 >= baseName.size())
|
|
return false;
|
|
|
|
std::string sBucket = baseName.substr(0, sep);
|
|
std::string sIndex = baseName.substr(sep + 1);
|
|
|
|
if (!SecureMsgAllDigits(sBucket)
|
|
|| !SecureMsgAllDigits(sIndex))
|
|
return false;
|
|
|
|
try {
|
|
bucket = std::stoll(sBucket);
|
|
unsigned long nIndex = std::stoul(sIndex);
|
|
if (nIndex < 1
|
|
|| nIndex > (unsigned long) std::numeric_limits<uint32_t>::max())
|
|
return false;
|
|
|
|
fileIndex = (uint32_t) nIndex;
|
|
} catch (const std::exception&)
|
|
{
|
|
return false;
|
|
};
|
|
|
|
return true;
|
|
};
|
|
|
|
std::string SecureMsgBucketFilename(int64_t bucket, uint32_t fileIndex, bool fWalletLocked)
|
|
{
|
|
std::string sIndex = std::to_string(fileIndex);
|
|
if (fileIndex < 10)
|
|
sIndex.insert(0, "0");
|
|
|
|
return std::to_string(bucket) + "_" + sIndex + (fWalletLocked ? "_wl.dat" : ".dat");
|
|
};
|
|
|
|
void SecureMsgGetBucketFiles(const fs::path& pathSmsgDir, int64_t bucket, bool fWalletLocked, std::vector<std::pair<uint32_t, fs::path> >& bucketFiles)
|
|
{
|
|
bucketFiles.clear();
|
|
|
|
if (!fs::exists(pathSmsgDir)
|
|
|| !fs::is_directory(pathSmsgDir))
|
|
return;
|
|
|
|
fs::directory_iterator itend;
|
|
for (fs::directory_iterator itd(pathSmsgDir) ; itd != itend ; ++itd)
|
|
{
|
|
if (!fs::is_regular_file(itd->status()))
|
|
continue;
|
|
|
|
int64_t fileBucket;
|
|
uint32_t fileIndex;
|
|
bool fFileWalletLocked;
|
|
std::string fileName = (*itd).path().filename().string();
|
|
if (!SecureMsgParseBucketFilename(fileName, fileBucket, fileIndex, fFileWalletLocked))
|
|
continue;
|
|
|
|
if (fileBucket != bucket
|
|
|| fFileWalletLocked != fWalletLocked)
|
|
continue;
|
|
|
|
bucketFiles.push_back(std::make_pair(fileIndex, (*itd).path()));
|
|
};
|
|
|
|
std::sort(bucketFiles.begin(), bucketFiles.end(),
|
|
[](const std::pair<uint32_t, fs::path>& a, const std::pair<uint32_t, fs::path>& b)
|
|
{
|
|
return a.first < b.first;
|
|
});
|
|
};
|
|
|
|
void SecureMsgRemoveBucketFiles(const fs::path& pathSmsgDir, int64_t bucket, bool fWalletLocked)
|
|
{
|
|
std::vector<std::pair<uint32_t, fs::path> > bucketFiles;
|
|
SecureMsgGetBucketFiles(pathSmsgDir, bucket, fWalletLocked, bucketFiles);
|
|
|
|
for (std::vector<std::pair<uint32_t, fs::path> >::iterator it = bucketFiles.begin(); it != bucketFiles.end(); ++it)
|
|
{
|
|
try {
|
|
fs::remove(it->second);
|
|
} catch (const fs::filesystem_error& ex)
|
|
{
|
|
printf("Error removing %s file %s.\n", fWalletLocked ? "wallet locked" : "bucket", ex.what());
|
|
};
|
|
};
|
|
};
|
|
|
|
bool SecureMsgSelectBucketFile(const fs::path& pathSmsgDir, int64_t bucket, bool fWalletLocked, uint32_t nPayload, fs::path& fullPath, uint32_t& fileIndex)
|
|
{
|
|
std::vector<std::pair<uint32_t, fs::path> > bucketFiles;
|
|
SecureMsgGetBucketFiles(pathSmsgDir, bucket, fWalletLocked, bucketFiles);
|
|
|
|
fileIndex = 1;
|
|
if (!bucketFiles.empty())
|
|
{
|
|
fileIndex = bucketFiles.back().first;
|
|
|
|
try {
|
|
uintmax_t nFileSize = fs::file_size(bucketFiles.back().second);
|
|
if (nFileSize + SMSG_HDR_LEN + nPayload > (uintmax_t) SMSG_BUCKET_FILE_SIZE_LIMIT)
|
|
fileIndex++;
|
|
} catch (const fs::filesystem_error&)
|
|
{
|
|
fileIndex++;
|
|
};
|
|
};
|
|
|
|
fullPath = pathSmsgDir / SecureMsgBucketFilename(bucket, fileIndex, fWalletLocked);
|
|
return true;
|
|
};
|
|
|
|
bool SecureMsgWaitForThreadsToStop()
|
|
{
|
|
int64_t nDeadline = GetTimeMillis() + SMSG_THREAD_SHUTDOWN_WAIT_MS;
|
|
while (nSecureMsgThreadsRunning.load() > 0
|
|
&& GetTimeMillis() < nDeadline)
|
|
{
|
|
MilliSleep(SMSG_THREAD_SHUTDOWN_POLL_MS);
|
|
};
|
|
|
|
if (nSecureMsgThreadsRunning.load() > 0)
|
|
{
|
|
printf("Timed out waiting for secure messaging threads to stop (%d still running).\n", nSecureMsgThreadsRunning.load());
|
|
return false;
|
|
};
|
|
|
|
return true;
|
|
};
|
|
}
|
|
|
|
bool SecMsgCrypter::SetKey(const std::vector<unsigned char>& vchNewKey, unsigned char* chNewIV)
|
|
{
|
|
|
|
if (vchNewKey.size() < sizeof(chKey))
|
|
return false;
|
|
|
|
return SetKey(&vchNewKey[0], chNewIV);
|
|
};
|
|
|
|
bool SecMsgCrypter::SetKey(const unsigned char* chNewKey, unsigned char* chNewIV)
|
|
{
|
|
// -- for EVP_aes_256_cbc() key must be 256 bit, iv must be 128 bit.
|
|
memcpy(&chKey[0], chNewKey, sizeof(chKey));
|
|
memcpy(chIV, chNewIV, sizeof(chIV));
|
|
|
|
fKeySet = true;
|
|
return true;
|
|
};
|
|
|
|
bool SecMsgCrypter::Encrypt(unsigned char* chPlaintext, uint32_t nPlain, std::vector<unsigned char> &vchCiphertext)
|
|
{
|
|
if (!fKeySet)
|
|
return false;
|
|
|
|
// -- max ciphertext len for a n bytes of plaintext is n + AES_BLOCK_SIZE - 1 bytes
|
|
int nLen = nPlain;
|
|
|
|
int nCLen = nLen + AES_BLOCK_SIZE, nFLen = 0;
|
|
vchCiphertext = std::vector<unsigned char> (nCLen);
|
|
|
|
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
|
|
if (!ctx)
|
|
return false;
|
|
|
|
bool fOk = true;
|
|
|
|
if (fOk) fOk = EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, &chKey[0], &chIV[0]);
|
|
if (fOk) fOk = EVP_EncryptUpdate(ctx, &vchCiphertext[0], &nCLen, chPlaintext, nLen);
|
|
if (fOk) fOk = EVP_EncryptFinal_ex(ctx, (&vchCiphertext[0])+nCLen, &nFLen);
|
|
EVP_CIPHER_CTX_free(ctx);
|
|
|
|
if (!fOk)
|
|
return false;
|
|
|
|
vchCiphertext.resize(nCLen + nFLen);
|
|
|
|
return true;
|
|
};
|
|
|
|
bool SecMsgCrypter::Decrypt(unsigned char* chCiphertext, uint32_t nCipher, std::vector<unsigned char>& vchPlaintext)
|
|
{
|
|
if (!fKeySet)
|
|
return false;
|
|
|
|
// plaintext will always be equal to or lesser than length of ciphertext
|
|
int nPLen = nCipher, nFLen = 0;
|
|
|
|
vchPlaintext.resize(nCipher);
|
|
|
|
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
|
|
if (!ctx)
|
|
return false;
|
|
|
|
bool fOk = true;
|
|
|
|
if (fOk) fOk = EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, &chKey[0], &chIV[0]);
|
|
if (fOk) fOk = EVP_DecryptUpdate(ctx, &vchPlaintext[0], &nPLen, &chCiphertext[0], nCipher);
|
|
if (fOk) fOk = EVP_DecryptFinal_ex(ctx, (&vchPlaintext[0])+nPLen, &nFLen);
|
|
EVP_CIPHER_CTX_free(ctx);
|
|
|
|
if (!fOk)
|
|
return false;
|
|
|
|
vchPlaintext.resize(nPLen + nFLen);
|
|
|
|
return true;
|
|
};
|
|
|
|
void SecMsgBucket::hashBucket()
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("SecMsgBucket::hashBucket()\n");
|
|
|
|
timeChanged = GetTime();
|
|
|
|
std::set<SecMsgToken>::iterator it;
|
|
|
|
void* state = XXH32_init(1);
|
|
|
|
for (it = setTokens.begin(); it != setTokens.end(); ++it)
|
|
{
|
|
XXH32_update(state, it->sample, 8);
|
|
};
|
|
|
|
hash = XXH32_digest(state);
|
|
|
|
if (fDebugSmsg)
|
|
printf("Hashed %"PRIszu" messages, hash %u\n", setTokens.size(), hash);
|
|
};
|
|
|
|
|
|
bool SecMsgDB::Open(const char* pszMode)
|
|
{
|
|
if (smsgDB)
|
|
{
|
|
pdb = smsgDB;
|
|
return true;
|
|
};
|
|
|
|
bool fCreate = strchr(pszMode, 'c');
|
|
|
|
fs::path fullpath = GetDataDir() / "smsgDB";
|
|
|
|
if (!fCreate
|
|
&& (!fs::exists(fullpath)
|
|
|| !fs::is_directory(fullpath)))
|
|
{
|
|
printf("SecMsgDB::open() - DB does not exist.\n");
|
|
return false;
|
|
};
|
|
|
|
// One-shot migration: pre-v5.10 nodes have a LevelDB tree under smsgDB/.
|
|
// Detect that and convert to RocksDB before opening. The legacy data is
|
|
// renamed to smsgDB.leveldb-backup/ as a recovery option.
|
|
if (fs::is_directory(fullpath) && IsLegacyLevelDbSmsgDir(fullpath)) {
|
|
std::string migrateError;
|
|
if (!MigrateSmsgDBLevelDbToRocksDb(migrateError)) {
|
|
printf("SecMsgDB::open() - migration failed: %s\n", migrateError.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
rocksdb::Options options;
|
|
options.create_if_missing = fCreate;
|
|
rocksdb::Status s = OpenSmsgDB(options, fullpath.string(), &smsgDB);
|
|
|
|
if (!s.ok())
|
|
{
|
|
printf("SecMsgDB::open() - Error opening db: %s.\n", s.ToString().c_str());
|
|
return false;
|
|
};
|
|
|
|
pdb = smsgDB;
|
|
|
|
return true;
|
|
};
|
|
|
|
|
|
// When performing a read, if we have an active batch we need to check it first
|
|
// before reading from the database, as the rest of the code assumes that once
|
|
// a database transaction begins reads are consistent with it.
|
|
//
|
|
// Previously implemented via rocksdb::WriteBatch::Handler subclass, which fails
|
|
// to link against Ubuntu's librocksdb-dev (typeinfo for the Handler base class
|
|
// isn't exported there). The pendingBatch map is updated alongside every Put
|
|
// or Delete on activeBatch and answers ScanBatch queries directly.
|
|
bool SecMsgDB::ScanBatch(const CDataStream& key, std::string* value, bool* deleted) const
|
|
{
|
|
if (!activeBatch)
|
|
return false;
|
|
|
|
*deleted = false;
|
|
auto it = pendingBatch.find(key.str());
|
|
if (it == pendingBatch.end())
|
|
return false;
|
|
if (!it->second.has_value()) {
|
|
*deleted = true;
|
|
return true;
|
|
}
|
|
*value = *it->second;
|
|
return true;
|
|
}
|
|
|
|
bool SecMsgDB::TxnBegin()
|
|
{
|
|
if (activeBatch)
|
|
return true;
|
|
activeBatch = new rocksdb::WriteBatch();
|
|
pendingBatch.clear();
|
|
return true;
|
|
};
|
|
|
|
bool SecMsgDB::TxnCommit()
|
|
{
|
|
if (!activeBatch)
|
|
return false;
|
|
|
|
rocksdb::WriteOptions writeOptions;
|
|
writeOptions.sync = true;
|
|
rocksdb::Status status = pdb->Write(writeOptions, activeBatch);
|
|
delete activeBatch;
|
|
activeBatch = NULL;
|
|
pendingBatch.clear();
|
|
|
|
if (!status.ok())
|
|
{
|
|
printf("SecMsgDB batch commit failure: %s\n", status.ToString().c_str());
|
|
return false;
|
|
};
|
|
|
|
return true;
|
|
};
|
|
|
|
bool SecMsgDB::TxnAbort()
|
|
{
|
|
delete activeBatch;
|
|
activeBatch = NULL;
|
|
pendingBatch.clear();
|
|
return true;
|
|
};
|
|
|
|
bool SecMsgDB::ReadPK(CKeyID& addr, CPubKey& pubkey)
|
|
{
|
|
if (!pdb)
|
|
return false;
|
|
|
|
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
|
ssKey.reserve(sizeof(addr) + 2);
|
|
ssKey << 'p';
|
|
ssKey << 'k';
|
|
ssKey << addr;
|
|
std::string strValue;
|
|
|
|
bool readFromDb = true;
|
|
if (activeBatch)
|
|
{
|
|
// -- check activeBatch first
|
|
bool deleted = false;
|
|
readFromDb = ScanBatch(ssKey, &strValue, &deleted) == false;
|
|
if (deleted)
|
|
return false;
|
|
};
|
|
|
|
if (readFromDb)
|
|
{
|
|
rocksdb::Status s = pdb->Get(rocksdb::ReadOptions(), ssKey.str(), &strValue);
|
|
if (!s.ok())
|
|
{
|
|
if (s.IsNotFound())
|
|
return false;
|
|
printf("RocksDB read failure: %s\n", s.ToString().c_str());
|
|
return false;
|
|
};
|
|
};
|
|
|
|
try {
|
|
CDataStream ssValue(strValue.data(), strValue.data() + strValue.size(), SER_DISK, CLIENT_VERSION);
|
|
ssValue >> pubkey;
|
|
} catch (std::exception& e) {
|
|
printf("SecMsgDB::ReadPK() unserialize threw: %s.\n", e.what());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
bool SecMsgDB::WritePK(CKeyID& addr, CPubKey& pubkey)
|
|
{
|
|
if (!pdb)
|
|
return false;
|
|
|
|
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
|
ssKey.reserve(sizeof(addr) + 2);
|
|
ssKey << 'p';
|
|
ssKey << 'k';
|
|
ssKey << addr;
|
|
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
|
|
ssValue.reserve(sizeof(pubkey));
|
|
ssValue << pubkey;
|
|
|
|
if (activeBatch)
|
|
{
|
|
activeBatch->Put(ssKey.str(), ssValue.str());
|
|
pendingBatch[ssKey.str()] = ssValue.str();
|
|
return true;
|
|
};
|
|
|
|
rocksdb::WriteOptions writeOptions;
|
|
writeOptions.sync = true;
|
|
rocksdb::Status s = pdb->Put(writeOptions, ssKey.str(), ssValue.str());
|
|
if (!s.ok())
|
|
{
|
|
printf("SecMsgDB write failure: %s\n", s.ToString().c_str());
|
|
return false;
|
|
};
|
|
|
|
return true;
|
|
};
|
|
|
|
bool SecMsgDB::ExistsPK(CKeyID& addr)
|
|
{
|
|
if (!pdb)
|
|
return false;
|
|
|
|
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
|
ssKey.reserve(sizeof(addr)+2);
|
|
ssKey << 'p';
|
|
ssKey << 'k';
|
|
ssKey << addr;
|
|
std::string unused;
|
|
|
|
if (activeBatch)
|
|
{
|
|
bool deleted;
|
|
if (ScanBatch(ssKey, &unused, &deleted) && !deleted)
|
|
{
|
|
return true;
|
|
};
|
|
};
|
|
|
|
rocksdb::Status s = pdb->Get(rocksdb::ReadOptions(), ssKey.str(), &unused);
|
|
return s.IsNotFound() == false;
|
|
};
|
|
|
|
|
|
bool SecMsgDB::NextSmesg(rocksdb::Iterator* it, std::string& prefix, unsigned char* chKey, SecMsgStored& smsgStored)
|
|
{
|
|
if (!pdb)
|
|
return false;
|
|
|
|
if (!it->Valid()) // first run
|
|
it->Seek(prefix);
|
|
else
|
|
it->Next();
|
|
|
|
if (!(it->Valid()
|
|
&& it->key().size() == 18
|
|
&& memcmp(it->key().data(), prefix.data(), 2) == 0))
|
|
return false;
|
|
|
|
memcpy(chKey, it->key().data(), 18);
|
|
|
|
try {
|
|
CDataStream ssValue(it->value().data(), it->value().data() + it->value().size(), SER_DISK, CLIENT_VERSION);
|
|
ssValue >> smsgStored;
|
|
} catch (std::exception& e) {
|
|
printf("SecMsgDB::NextSmesg() unserialize threw: %s.\n", e.what());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
bool SecMsgDB::NextSmesgKey(rocksdb::Iterator* it, std::string& prefix, unsigned char* chKey)
|
|
{
|
|
if (!pdb)
|
|
return false;
|
|
|
|
if (!it->Valid()) // first run
|
|
it->Seek(prefix);
|
|
else
|
|
it->Next();
|
|
|
|
if (!(it->Valid()
|
|
&& it->key().size() == 18
|
|
&& memcmp(it->key().data(), prefix.data(), 2) == 0))
|
|
return false;
|
|
|
|
memcpy(chKey, it->key().data(), 18);
|
|
|
|
return true;
|
|
};
|
|
|
|
bool SecMsgDB::ReadSmesg(unsigned char* chKey, SecMsgStored& smsgStored)
|
|
{
|
|
if (!pdb)
|
|
return false;
|
|
|
|
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
|
ssKey.write((const char*)chKey, 18);
|
|
std::string strValue;
|
|
|
|
bool readFromDb = true;
|
|
if (activeBatch)
|
|
{
|
|
// -- check activeBatch first
|
|
bool deleted = false;
|
|
readFromDb = ScanBatch(ssKey, &strValue, &deleted) == false;
|
|
if (deleted)
|
|
return false;
|
|
};
|
|
|
|
if (readFromDb)
|
|
{
|
|
rocksdb::Status s = pdb->Get(rocksdb::ReadOptions(), ssKey.str(), &strValue);
|
|
if (!s.ok())
|
|
{
|
|
if (s.IsNotFound())
|
|
return false;
|
|
printf("RocksDB read failure: %s\n", s.ToString().c_str());
|
|
return false;
|
|
};
|
|
};
|
|
|
|
try {
|
|
CDataStream ssValue(strValue.data(), strValue.data() + strValue.size(), SER_DISK, CLIENT_VERSION);
|
|
ssValue >> smsgStored;
|
|
} catch (std::exception& e) {
|
|
printf("SecMsgDB::ReadSmesg() unserialize threw: %s.\n", e.what());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
bool SecMsgDB::WriteSmesg(unsigned char* chKey, SecMsgStored& smsgStored)
|
|
{
|
|
if (!pdb)
|
|
return false;
|
|
|
|
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
|
ssKey.write((const char*)chKey, 18);
|
|
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
|
|
ssValue << smsgStored;
|
|
|
|
if (activeBatch)
|
|
{
|
|
activeBatch->Put(ssKey.str(), ssValue.str());
|
|
pendingBatch[ssKey.str()] = ssValue.str();
|
|
return true;
|
|
};
|
|
|
|
rocksdb::WriteOptions writeOptions;
|
|
writeOptions.sync = true;
|
|
rocksdb::Status s = pdb->Put(writeOptions, ssKey.str(), ssValue.str());
|
|
if (!s.ok())
|
|
{
|
|
printf("SecMsgDB write failed: %s\n", s.ToString().c_str());
|
|
return false;
|
|
};
|
|
|
|
return true;
|
|
};
|
|
|
|
bool SecMsgDB::ExistsSmesg(unsigned char* chKey)
|
|
{
|
|
if (!pdb)
|
|
return false;
|
|
|
|
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
|
ssKey.write((const char*)chKey, 18);
|
|
std::string unused;
|
|
|
|
if (activeBatch)
|
|
{
|
|
bool deleted;
|
|
if (ScanBatch(ssKey, &unused, &deleted) && !deleted)
|
|
{
|
|
return true;
|
|
};
|
|
};
|
|
|
|
rocksdb::Status s = pdb->Get(rocksdb::ReadOptions(), ssKey.str(), &unused);
|
|
return s.IsNotFound() == false;
|
|
return true;
|
|
};
|
|
|
|
bool SecMsgDB::EraseSmesg(unsigned char* chKey)
|
|
{
|
|
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
|
ssKey.write((const char*)chKey, 18);
|
|
|
|
if (activeBatch)
|
|
{
|
|
activeBatch->Delete(ssKey.str());
|
|
pendingBatch[ssKey.str()] = std::nullopt;
|
|
return true;
|
|
};
|
|
|
|
rocksdb::WriteOptions writeOptions;
|
|
writeOptions.sync = true;
|
|
rocksdb::Status s = pdb->Delete(writeOptions, ssKey.str());
|
|
|
|
if (s.ok() || s.IsNotFound())
|
|
return true;
|
|
printf("SecMsgDB erase failed: %s\n", s.ToString().c_str());
|
|
return false;
|
|
};
|
|
|
|
void ThreadSecureMsg(void* parg)
|
|
{
|
|
// -- bucket management thread
|
|
RenameThread("shadowcoin-smsg"); // Make this thread recognisable
|
|
CSecureMsgThreadGuard threadGuard;
|
|
|
|
uint32_t delay = 0;
|
|
|
|
while (fSecMsgEnabled)
|
|
{
|
|
// shutdown thread waits 5 seconds, this should be less
|
|
MilliSleep(1000); // milliseconds
|
|
|
|
if (!fSecMsgEnabled) // check again after sleep
|
|
break;
|
|
|
|
delay++;
|
|
if (delay < SMSG_THREAD_DELAY) // check every SMSG_THREAD_DELAY seconds
|
|
continue;
|
|
delay = 0;
|
|
|
|
int64_t now = GetTime();
|
|
|
|
if (fDebugSmsg)
|
|
printf("SecureMsgThread %"PRId64" \n", now);
|
|
|
|
int64_t cutoffTime = now - SMSG_RETENTION;
|
|
|
|
{
|
|
LOCK(cs_smsg);
|
|
std::map<int64_t, SecMsgBucket>::iterator it;
|
|
it = smsgBuckets.begin();
|
|
|
|
while (it != smsgBuckets.end())
|
|
{
|
|
//if (fDebugSmsg)
|
|
// printf("Checking bucket %"PRId64", size %"PRIszu" \n", it->first, it->second.setTokens.size());
|
|
if (it->first < cutoffTime)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Removing bucket %"PRId64" \n", it->first);
|
|
fs::path pathSmsgDir = GetDataDir() / "smsgStore";
|
|
SecureMsgRemoveBucketFiles(pathSmsgDir, it->first, false);
|
|
SecureMsgRemoveBucketFiles(pathSmsgDir, it->first, true);
|
|
|
|
smsgBuckets.erase(it++);
|
|
} else
|
|
{
|
|
// -- tick down nLockCount, so will eventually expire if peer never sends data
|
|
if (it->second.nLockCount > 0)
|
|
{
|
|
it->second.nLockCount--;
|
|
|
|
if (it->second.nLockCount == 0) // lock timed out
|
|
{
|
|
uint32_t nPeerId = it->second.nLockPeerId;
|
|
int64_t ignoreUntil = GetTime() + SMSG_TIME_IGNORE;
|
|
|
|
if (fDebugSmsg)
|
|
printf("Lock on bucket %"PRId64" for peer %u timed out.\n", it->first, nPeerId);
|
|
// -- look through the nodes for the peer that locked this bucket
|
|
LOCK(cs_vNodes);
|
|
for (CNode* pnode : vNodes)
|
|
{
|
|
if (pnode->smsgData.nPeerId != nPeerId)
|
|
continue;
|
|
pnode->smsgData.ignoreUntil = ignoreUntil;
|
|
|
|
// -- alert peer that they are being ignored
|
|
std::vector<unsigned char> vchData;
|
|
vchData.resize(8);
|
|
memcpy(&vchData[0], &ignoreUntil, 8);
|
|
pnode->PushMessage("smsgIgnore", vchData);
|
|
|
|
if (fDebugSmsg)
|
|
printf("This node will ignore peer %u until %"PRId64".\n", nPeerId, ignoreUntil);
|
|
break;
|
|
};
|
|
it->second.nLockPeerId = 0;
|
|
}; // if (it->second.nLockCount == 0)
|
|
};
|
|
++it;
|
|
}; // ! if (it->first < cutoffTime)
|
|
};
|
|
}; // LOCK(cs_smsg);
|
|
};
|
|
|
|
printf("ThreadSecureMsg exited.\n");
|
|
};
|
|
|
|
void ThreadSecureMsgPow(void* parg)
|
|
{
|
|
// -- proof of work thread
|
|
RenameThread("shadowcoin-smsg-pow"); // Make this thread recognisable
|
|
CSecureMsgThreadGuard threadGuard;
|
|
|
|
int rv;
|
|
std::vector<unsigned char> vchKey;
|
|
SecMsgStored smsgStored;
|
|
|
|
std::string sPrefix("qm");
|
|
unsigned char chKey[18];
|
|
|
|
|
|
while (fSecMsgEnabled)
|
|
{
|
|
// -- sleep at end, then fSecMsgEnabled is tested on wake
|
|
|
|
SecMsgDB dbOutbox;
|
|
rocksdb::Iterator* it;
|
|
{
|
|
LOCK(cs_smsgDB);
|
|
|
|
if (!dbOutbox.Open("cr+"))
|
|
continue;
|
|
|
|
// -- fifo (smallest key first)
|
|
it = dbOutbox.pdb->NewIterator(rocksdb::ReadOptions());
|
|
}
|
|
// -- break up lock, SecureMsgSetHash will take long
|
|
|
|
for (;;)
|
|
{
|
|
{
|
|
LOCK(cs_smsgDB);
|
|
if (!dbOutbox.NextSmesg(it, sPrefix, chKey, smsgStored))
|
|
break;
|
|
}
|
|
|
|
unsigned char* pHeader = &smsgStored.vchMessage[0];
|
|
unsigned char* pPayload = &smsgStored.vchMessage[SMSG_HDR_LEN];
|
|
SecureMessage* psmsg = (SecureMessage*) pHeader;
|
|
|
|
// -- do proof of work
|
|
rv = SecureMsgSetHash(pHeader, pPayload, psmsg->nPayload);
|
|
if (rv == 2)
|
|
break; // /eave message in db, if terminated due to shutdown
|
|
|
|
// -- message is removed here, no matter what
|
|
{
|
|
LOCK(cs_smsgDB);
|
|
dbOutbox.EraseSmesg(chKey);
|
|
}
|
|
if (rv != 0)
|
|
{
|
|
printf("SecMsgPow: Could not get proof of work hash, message removed.\n");
|
|
continue;
|
|
};
|
|
|
|
// -- add to message store
|
|
{
|
|
LOCK(cs_smsg);
|
|
if (SecureMsgStore(pHeader, pPayload, psmsg->nPayload, true) != 0)
|
|
{
|
|
printf("SecMsgPow: Could not place message in buckets, message removed.\n");
|
|
continue;
|
|
};
|
|
}
|
|
|
|
// -- test if message was sent to self
|
|
if (SecureMsgScanMessage(pHeader, pPayload, psmsg->nPayload, true) != 0)
|
|
{
|
|
// message recipient is not this node (or failed)
|
|
};
|
|
};
|
|
|
|
{
|
|
LOCK(cs_smsg);
|
|
delete it;
|
|
}
|
|
|
|
// -- shutdown thread waits 5 seconds, this should be less
|
|
MilliSleep(1000); // milliseconds
|
|
};
|
|
|
|
printf("ThreadSecureMsgPow exited.\n");
|
|
};
|
|
|
|
|
|
std::string getTimeString(int64_t timestamp, char *buffer, size_t nBuffer)
|
|
{
|
|
struct tm* dt;
|
|
time_t t = timestamp;
|
|
dt = localtime(&t);
|
|
|
|
strftime(buffer, nBuffer, "%Y-%m-%d %H:%M:%S %z", dt); // %Z shows long strings on windows
|
|
return std::string(buffer); // copies the null-terminated character sequence
|
|
};
|
|
|
|
std::string fsReadable(uint64_t nBytes)
|
|
{
|
|
char buffer[128];
|
|
if (nBytes >= 1024ll*1024ll*1024ll*1024ll)
|
|
snprintf(buffer, sizeof(buffer), "%.2f TB", nBytes/1024.0/1024.0/1024.0/1024.0);
|
|
else
|
|
if (nBytes >= 1024*1024*1024)
|
|
snprintf(buffer, sizeof(buffer), "%.2f GB", nBytes/1024.0/1024.0/1024.0);
|
|
else
|
|
if (nBytes >= 1024*1024)
|
|
snprintf(buffer, sizeof(buffer), "%.2f MB", nBytes/1024.0/1024.0);
|
|
else
|
|
if (nBytes >= 1024)
|
|
snprintf(buffer, sizeof(buffer), "%.2f KB", nBytes/1024.0);
|
|
else
|
|
snprintf(buffer, sizeof(buffer), "%"PRIu64" bytes", nBytes);
|
|
return std::string(buffer);
|
|
};
|
|
|
|
int SecureMsgBuildBucketSet()
|
|
{
|
|
/*
|
|
Build the bucket set by scanning the files in the smsgStore dir.
|
|
|
|
smsgBuckets should be empty
|
|
*/
|
|
|
|
if (fDebugSmsg)
|
|
printf("SecureMsgBuildBucketSet()\n");
|
|
|
|
int64_t now = GetTime();
|
|
uint32_t nFiles = 0;
|
|
uint32_t nMessages = 0;
|
|
|
|
fs::path pathSmsgDir = GetDataDir() / "smsgStore";
|
|
fs::directory_iterator itend;
|
|
|
|
|
|
if (!fs::exists(pathSmsgDir)
|
|
|| !fs::is_directory(pathSmsgDir))
|
|
{
|
|
printf("Message store directory does not exist.\n");
|
|
return 0; // not an error
|
|
}
|
|
|
|
|
|
for (fs::directory_iterator itd(pathSmsgDir) ; itd != itend ; ++itd)
|
|
{
|
|
if (!fs::is_regular_file(itd->status()))
|
|
continue;
|
|
|
|
std::string fileType = (*itd).path().extension().string();
|
|
|
|
if (fileType.compare(".dat") != 0)
|
|
continue;
|
|
|
|
std::string fileName = (*itd).path().filename().string();
|
|
|
|
|
|
if (fDebugSmsg)
|
|
printf("Processing file: %s.\n", fileName.c_str());
|
|
|
|
nFiles++;
|
|
|
|
int64_t fileTime;
|
|
uint32_t fileIndex;
|
|
bool fWalletLocked;
|
|
if (!SecureMsgParseBucketFilename(fileName, fileTime, fileIndex, fWalletLocked))
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Skipping unrecognised bucket file: %s.\n", fileName.c_str());
|
|
continue;
|
|
};
|
|
|
|
if (fileTime < now - SMSG_RETENTION)
|
|
{
|
|
printf("Dropping file %s, expired.\n", fileName.c_str());
|
|
try {
|
|
fs::remove((*itd).path());
|
|
} catch (const fs::filesystem_error& ex)
|
|
{
|
|
printf("Error removing bucket file %s, %s.\n", fileName.c_str(), ex.what());
|
|
};
|
|
continue;
|
|
};
|
|
|
|
if (fWalletLocked)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Skipping wallet locked file: %s.\n", fileName.c_str());
|
|
continue;
|
|
};
|
|
|
|
|
|
SecureMessage smsg;
|
|
std::set<SecMsgToken>& tokenSet = smsgBuckets[fileTime].setTokens;
|
|
|
|
{
|
|
LOCK(cs_smsg);
|
|
FILE *fp;
|
|
|
|
if (!(fp = fopen((*itd).path().string().c_str(), "rb")))
|
|
{
|
|
printf("Error opening file: %s\n", strerror(errno));
|
|
continue;
|
|
};
|
|
|
|
for (;;)
|
|
{
|
|
long int ofs = ftell(fp);
|
|
SecMsgToken token;
|
|
token.fileIndex = fileIndex;
|
|
token.offset = ofs;
|
|
errno = 0;
|
|
if (fread(&smsg.hash[0], sizeof(unsigned char), SMSG_HDR_LEN, fp) != (size_t)SMSG_HDR_LEN)
|
|
{
|
|
if (errno != 0)
|
|
{
|
|
printf("fread header failed: %s\n", strerror(errno));
|
|
} else
|
|
{
|
|
//printf("End of file.\n");
|
|
};
|
|
break;
|
|
};
|
|
token.timestamp = smsg.timestamp;
|
|
|
|
if (smsg.nPayload < 8)
|
|
continue;
|
|
|
|
if (fread(token.sample, sizeof(unsigned char), 8, fp) != 8)
|
|
{
|
|
printf("fread data failed: %s\n", strerror(errno));
|
|
break;
|
|
};
|
|
|
|
if (fseek(fp, smsg.nPayload-8, SEEK_CUR) != 0)
|
|
{
|
|
printf("fseek, strerror: %s.\n", strerror(errno));
|
|
break;
|
|
};
|
|
|
|
if (tokenSet.insert(token).second)
|
|
nMessages++;
|
|
};
|
|
|
|
fclose(fp);
|
|
};
|
|
smsgBuckets[fileTime].hashBucket();
|
|
|
|
if (fDebugSmsg)
|
|
printf("Bucket %"PRId64" contains %"PRIszu" messages.\n", fileTime, tokenSet.size());
|
|
};
|
|
|
|
printf("Processed %u files, loaded %"PRIszu" buckets containing %u messages.\n", nFiles, smsgBuckets.size(), nMessages);
|
|
|
|
return 0;
|
|
};
|
|
|
|
int SecureMsgAddWalletAddresses()
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("SecureMsgAddWalletAddresses()\n");
|
|
|
|
uint32_t nAdded = 0;
|
|
for (const auto& entry : pwalletMain->mapAddressBook)
|
|
{
|
|
if (!IsMine(*pwalletMain, entry.first))
|
|
continue;
|
|
|
|
CTrianglesAddress coinAddress(entry.first);
|
|
if (!coinAddress.IsValid())
|
|
continue;
|
|
|
|
std::string address;
|
|
std::string strPublicKey;
|
|
address = coinAddress.ToString();
|
|
|
|
|
|
bool fExists = 0;
|
|
for (std::vector<SecMsgAddress>::iterator it = smsgAddresses.begin(); it != smsgAddresses.end(); ++it)
|
|
{
|
|
if (address != it->sAddress)
|
|
continue;
|
|
fExists = 1;
|
|
break;
|
|
};
|
|
|
|
if (fExists)
|
|
continue;
|
|
|
|
bool recvEnabled = 1;
|
|
bool recvAnon = 1;
|
|
|
|
smsgAddresses.push_back(SecMsgAddress(address, recvEnabled, recvAnon));
|
|
nAdded++;
|
|
};
|
|
|
|
if (fDebugSmsg)
|
|
printf("Added %u addresses to whitelist.\n", nAdded);
|
|
|
|
return 0;
|
|
};
|
|
|
|
|
|
int SecureMsgReadIni()
|
|
{
|
|
if (!fSecMsgEnabled)
|
|
return false;
|
|
|
|
if (fDebugSmsg)
|
|
printf("SecureMsgReadIni()\n");
|
|
|
|
fs::path fullpath = GetDataDir() / "smsg.ini";
|
|
|
|
|
|
FILE *fp;
|
|
errno = 0;
|
|
if (!(fp = fopen(fullpath.string().c_str(), "r")))
|
|
{
|
|
printf("Error opening file: %s\n", strerror(errno));
|
|
return 1;
|
|
};
|
|
|
|
char cLine[512];
|
|
char *pName, *pValue;
|
|
|
|
char cAddress[64];
|
|
int addrRecv, addrRecvAnon;
|
|
|
|
while (fgets(cLine, 512, fp))
|
|
{
|
|
cLine[strcspn(cLine, "\n")] = '\0';
|
|
cLine[strcspn(cLine, "\r")] = '\0';
|
|
cLine[511] = '\0'; // for safety
|
|
|
|
// -- check that line contains a name value pair and is not a comment, or section header
|
|
if (cLine[0] == '#' || cLine[0] == '[' || strcspn(cLine, "=") < 1)
|
|
continue;
|
|
|
|
if (!(pName = strtok(cLine, "="))
|
|
|| !(pValue = strtok(NULL, "=")))
|
|
continue;
|
|
|
|
if (strcmp(pName, "newAddressRecv") == 0)
|
|
{
|
|
smsgOptions.fNewAddressRecv = (strcmp(pValue, "true") == 0) ? true : false;
|
|
} else
|
|
if (strcmp(pName, "newAddressAnon") == 0)
|
|
{
|
|
smsgOptions.fNewAddressAnon = (strcmp(pValue, "true") == 0) ? true : false;
|
|
} else
|
|
if (strcmp(pName, "key") == 0)
|
|
{
|
|
int rv = sscanf(pValue, "%64[^|]|%d|%d", cAddress, &addrRecv, &addrRecvAnon);
|
|
if (rv == 3)
|
|
{
|
|
smsgAddresses.push_back(SecMsgAddress(std::string(cAddress), addrRecv, addrRecvAnon));
|
|
} else
|
|
{
|
|
printf("Could not parse key line %s, rv %d.\n", pValue, rv);
|
|
}
|
|
} else
|
|
{
|
|
printf("Unknown setting name: '%s'.", pName);
|
|
};
|
|
};
|
|
|
|
printf("Loaded %"PRIszu" addresses.\n", smsgAddresses.size());
|
|
|
|
fclose(fp);
|
|
|
|
return 0;
|
|
};
|
|
|
|
int SecureMsgWriteIni()
|
|
{
|
|
if (!fSecMsgEnabled)
|
|
return false;
|
|
|
|
if (fDebugSmsg)
|
|
printf("SecureMsgWriteIni()\n");
|
|
|
|
fs::path fullpath = GetDataDir() / "smsg.ini~";
|
|
|
|
FILE *fp;
|
|
errno = 0;
|
|
if (!(fp = fopen(fullpath.string().c_str(), "w")))
|
|
{
|
|
printf("Error opening file: %s\n", strerror(errno));
|
|
return 1;
|
|
};
|
|
|
|
if (fwrite("[Options]\n", sizeof(char), 10, fp) != 10)
|
|
{
|
|
printf("fwrite error: %s\n", strerror(errno));
|
|
fclose(fp);
|
|
return false;
|
|
};
|
|
|
|
if (fprintf(fp, "newAddressRecv=%s\n", smsgOptions.fNewAddressRecv ? "true" : "false") < 0
|
|
|| fprintf(fp, "newAddressAnon=%s\n", smsgOptions.fNewAddressAnon ? "true" : "false") < 0)
|
|
{
|
|
printf("fprintf error: %s\n", strerror(errno));
|
|
fclose(fp);
|
|
return false;
|
|
}
|
|
|
|
if (fwrite("\n[Keys]\n", sizeof(char), 8, fp) != 8)
|
|
{
|
|
printf("fwrite error: %s\n", strerror(errno));
|
|
fclose(fp);
|
|
return false;
|
|
};
|
|
for (std::vector<SecMsgAddress>::iterator it = smsgAddresses.begin(); it != smsgAddresses.end(); ++it)
|
|
{
|
|
errno = 0;
|
|
if (fprintf(fp, "key=%s|%d|%d\n", it->sAddress.c_str(), it->fReceiveEnabled, it->fReceiveAnon) < 0)
|
|
{
|
|
printf("fprintf error: %s\n", strerror(errno));
|
|
continue;
|
|
};
|
|
};
|
|
|
|
|
|
fclose(fp);
|
|
|
|
|
|
try {
|
|
fs::path finalpath = GetDataDir() / "smsg.ini";
|
|
fs::rename(fullpath, finalpath);
|
|
} catch (const fs::filesystem_error& ex)
|
|
{
|
|
printf("Error renaming file %s, %s.\n", fullpath.string().c_str(), ex.what());
|
|
};
|
|
return 0;
|
|
};
|
|
|
|
|
|
/** called from AppInit2() in init.cpp */
|
|
bool SecureMsgStart(bool fDontStart, bool fScanChain)
|
|
{
|
|
if (fDontStart)
|
|
{
|
|
printf("Secure messaging not started.\n");
|
|
return false;
|
|
};
|
|
|
|
printf("Secure messaging starting.\n");
|
|
|
|
fSecMsgEnabled = true;
|
|
|
|
if (SecureMsgReadIni() != 0)
|
|
printf("Failed to read smsg.ini\n");
|
|
|
|
if (smsgAddresses.size() < 1)
|
|
{
|
|
printf("No address keys loaded.\n");
|
|
if (SecureMsgAddWalletAddresses() != 0)
|
|
printf("Failed to load addresses from wallet.\n");
|
|
};
|
|
|
|
if (fScanChain)
|
|
{
|
|
SecureMsgScanBlockChain();
|
|
};
|
|
|
|
if (SecureMsgBuildBucketSet() != 0)
|
|
{
|
|
printf("SecureMsg could not load bucket sets, secure messaging disabled.\n");
|
|
fSecMsgEnabled = false;
|
|
return false;
|
|
};
|
|
|
|
// -- start threads
|
|
if (!NewThread(ThreadSecureMsg, NULL)
|
|
|| !NewThread(ThreadSecureMsgPow, NULL))
|
|
{
|
|
printf("SecureMsg could not start threads, secure messaging disabled.\n");
|
|
fSecMsgEnabled = false;
|
|
SecureMsgWaitForThreadsToStop();
|
|
return false;
|
|
};
|
|
|
|
return true;
|
|
};
|
|
|
|
/** called from Shutdown() in init.cpp */
|
|
bool SecureMsgShutdown()
|
|
{
|
|
if (!fSecMsgEnabled)
|
|
return false;
|
|
|
|
printf("Stopping secure messaging.\n");
|
|
|
|
|
|
if (SecureMsgWriteIni() != 0)
|
|
printf("Failed to save smsg.ini\n");
|
|
|
|
fSecMsgEnabled = false;
|
|
SecureMsgWaitForThreadsToStop();
|
|
|
|
if (smsgDB)
|
|
{
|
|
LOCK(cs_smsgDB);
|
|
delete smsgDB;
|
|
smsgDB = NULL;
|
|
};
|
|
|
|
// -- main program will wait 5 seconds for threads to terminate.
|
|
|
|
return true;
|
|
};
|
|
|
|
bool SecureMsgEnable()
|
|
{
|
|
// -- start secure messaging at runtime
|
|
if (fSecMsgEnabled)
|
|
{
|
|
printf("SecureMsgEnable: secure messaging is already enabled.\n");
|
|
return false;
|
|
};
|
|
|
|
{
|
|
LOCK(cs_smsg);
|
|
fSecMsgEnabled = true;
|
|
|
|
smsgAddresses.clear(); // should be empty already
|
|
if (SecureMsgReadIni() != 0)
|
|
printf("Failed to read smsg.ini\n");
|
|
|
|
if (smsgAddresses.size() < 1)
|
|
{
|
|
printf("No address keys loaded.\n");
|
|
if (SecureMsgAddWalletAddresses() != 0)
|
|
printf("Failed to load addresses from wallet.\n");
|
|
};
|
|
|
|
smsgBuckets.clear(); // should be empty already
|
|
|
|
if (SecureMsgBuildBucketSet() != 0)
|
|
{
|
|
printf("SecureMsgEnable: could not load bucket sets, secure messaging disabled.\n");
|
|
fSecMsgEnabled = false;
|
|
return false;
|
|
};
|
|
|
|
}; // LOCK(cs_smsg);
|
|
|
|
// -- start threads
|
|
if (!NewThread(ThreadSecureMsg, NULL)
|
|
|| !NewThread(ThreadSecureMsgPow, NULL))
|
|
{
|
|
printf("SecureMsgEnable could not start threads, secure messaging disabled.\n");
|
|
fSecMsgEnabled = false;
|
|
SecureMsgWaitForThreadsToStop();
|
|
return false;
|
|
};
|
|
|
|
// -- ping each peer, don't know which have messaging enabled
|
|
{
|
|
LOCK(cs_vNodes);
|
|
for (CNode* pnode : vNodes)
|
|
{
|
|
pnode->PushMessage("smsgPing");
|
|
pnode->PushMessage("smsgPong"); // Send pong as have missed initial ping sent by peer when it connected
|
|
};
|
|
}
|
|
|
|
printf("Secure messaging enabled.\n");
|
|
return true;
|
|
};
|
|
|
|
bool SecureMsgDisable()
|
|
{
|
|
// -- stop secure messaging at runtime
|
|
if (!fSecMsgEnabled)
|
|
{
|
|
printf("SecureMsgDisable: secure messaging is already disabled.\n");
|
|
return false;
|
|
};
|
|
|
|
{
|
|
LOCK(cs_smsg);
|
|
fSecMsgEnabled = false;
|
|
|
|
// -- clear smsgBuckets
|
|
std::map<int64_t, SecMsgBucket>::iterator it;
|
|
it = smsgBuckets.begin();
|
|
for (it = smsgBuckets.begin(); it != smsgBuckets.end(); ++it)
|
|
{
|
|
it->second.setTokens.clear();
|
|
};
|
|
smsgBuckets.clear();
|
|
|
|
// -- tell each smsg enabled peer that this node is disabling
|
|
{
|
|
LOCK(cs_vNodes);
|
|
for (CNode* pnode : vNodes)
|
|
{
|
|
if (!pnode->smsgData.fEnabled)
|
|
continue;
|
|
|
|
pnode->PushMessage("smsgDisabled");
|
|
pnode->smsgData.fEnabled = false;
|
|
};
|
|
}
|
|
|
|
if (SecureMsgWriteIni() != 0)
|
|
printf("Failed to save smsg.ini\n");
|
|
|
|
smsgAddresses.clear();
|
|
|
|
}; // LOCK(cs_smsg);
|
|
|
|
SecureMsgWaitForThreadsToStop();
|
|
|
|
if (smsgDB)
|
|
{
|
|
LOCK(cs_smsgDB);
|
|
delete smsgDB;
|
|
smsgDB = NULL;
|
|
};
|
|
|
|
|
|
printf("Secure messaging disabled.\n");
|
|
return true;
|
|
};
|
|
|
|
|
|
bool SecureMsgReceiveData(CNode* pfrom, std::string strCommand, CDataStream& vRecv)
|
|
{
|
|
/*
|
|
Called from ProcessMessage
|
|
Runs in ThreadMessageHandler2
|
|
*/
|
|
|
|
if (fDebugSmsg)
|
|
printf("SecureMsgReceiveData() %s %s.\n", pfrom->addrName.c_str(), strCommand.c_str());
|
|
|
|
{
|
|
// break up?
|
|
LOCK(cs_smsg);
|
|
|
|
if (strCommand == "smsgInv")
|
|
{
|
|
std::vector<unsigned char> vchData;
|
|
vRecv >> vchData;
|
|
|
|
if (vchData.size() < 4)
|
|
{
|
|
pfrom->Misbehaving(1);
|
|
return false; // not enough data received to be a valid smsgInv
|
|
};
|
|
|
|
int64_t now = GetTime();
|
|
|
|
if (now < pfrom->smsgData.ignoreUntil)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Node is ignoring peer %u until %"PRId64".\n", pfrom->smsgData.nPeerId, pfrom->smsgData.ignoreUntil);
|
|
return false;
|
|
};
|
|
|
|
uint32_t nBuckets = smsgBuckets.size();
|
|
uint32_t nLocked = 0; // no. of locked buckets on this node
|
|
uint32_t nInvBuckets; // no. of bucket headers sent by peer in smsgInv
|
|
memcpy(&nInvBuckets, &vchData[0], 4);
|
|
if (fDebugSmsg)
|
|
printf("Remote node sent %d bucket headers, this has %d.\n", nInvBuckets, nBuckets);
|
|
|
|
|
|
// -- Check no of buckets:
|
|
if (nInvBuckets > (SMSG_RETENTION / SMSG_BUCKET_LEN) + 1) // +1 for some leeway
|
|
{
|
|
printf("Peer sent more bucket headers than possible %u, %u.\n", nInvBuckets, (SMSG_RETENTION / SMSG_BUCKET_LEN));
|
|
pfrom->Misbehaving(1);
|
|
return false;
|
|
};
|
|
|
|
if (vchData.size() < 4 + nInvBuckets*16)
|
|
{
|
|
printf("Remote node did not send enough data.\n");
|
|
pfrom->Misbehaving(1);
|
|
return false;
|
|
};
|
|
|
|
std::vector<unsigned char> vchDataOut;
|
|
vchDataOut.reserve(4 + 8 * nInvBuckets); // reserve max possible size
|
|
vchDataOut.resize(4);
|
|
uint32_t nShowBuckets = 0;
|
|
|
|
|
|
unsigned char *p = &vchData[4];
|
|
for (uint32_t i = 0; i < nInvBuckets; ++i)
|
|
{
|
|
int64_t time;
|
|
uint32_t ncontent, hash;
|
|
memcpy(&time, p, 8);
|
|
memcpy(&ncontent, p+8, 4);
|
|
memcpy(&hash, p+12, 4);
|
|
|
|
p += 16;
|
|
|
|
// Check time valid:
|
|
if (time < now - SMSG_RETENTION)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Not interested in peer bucket %"PRId64", has expired.\n", time);
|
|
|
|
if (time < now - SMSG_RETENTION - SMSG_TIME_LEEWAY)
|
|
pfrom->Misbehaving(1);
|
|
continue;
|
|
};
|
|
if (time > now + SMSG_TIME_LEEWAY)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Not interested in peer bucket %"PRId64", in the future.\n", time);
|
|
pfrom->Misbehaving(1);
|
|
continue;
|
|
};
|
|
|
|
if (ncontent < 1)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Peer sent empty bucket, ignore %"PRId64" %u %u.\n", time, ncontent, hash);
|
|
continue;
|
|
};
|
|
|
|
if (fDebugSmsg)
|
|
{
|
|
printf("peer bucket %"PRId64" %u %u.\n", time, ncontent, hash);
|
|
printf("this bucket %"PRId64" %"PRIszu" %u.\n", time, smsgBuckets[time].setTokens.size(), smsgBuckets[time].hash);
|
|
};
|
|
|
|
if (smsgBuckets[time].nLockCount > 0)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Bucket is locked %u, waiting for peer %u to send data.\n", smsgBuckets[time].nLockCount, smsgBuckets[time].nLockPeerId);
|
|
nLocked++;
|
|
continue;
|
|
};
|
|
|
|
// -- if this node has more than the peer node, peer node will pull from this
|
|
// if then peer node has more this node will pull fom peer
|
|
if (smsgBuckets[time].setTokens.size() < ncontent
|
|
|| (smsgBuckets[time].setTokens.size() == ncontent
|
|
&& smsgBuckets[time].hash != hash)) // if same amount in buckets check hash
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Requesting contents of bucket %"PRId64".\n", time);
|
|
|
|
uint32_t sz = vchDataOut.size();
|
|
vchDataOut.resize(sz + 8);
|
|
memcpy(&vchDataOut[sz], &time, 8);
|
|
|
|
nShowBuckets++;
|
|
};
|
|
};
|
|
|
|
// TODO: should include hash?
|
|
memcpy(&vchDataOut[0], &nShowBuckets, 4);
|
|
if (vchDataOut.size() > 4)
|
|
{
|
|
pfrom->PushMessage("smsgShow", vchDataOut);
|
|
} else
|
|
if (nLocked < 1) // Don't report buckets as matched if any are locked
|
|
{
|
|
// -- peer has no buckets we want, don't send them again until something changes
|
|
// peer will still request buckets from this node if needed (< ncontent)
|
|
vchDataOut.resize(8);
|
|
memcpy(&vchDataOut[0], &now, 8);
|
|
pfrom->PushMessage("smsgMatch", vchDataOut);
|
|
if (fDebugSmsg)
|
|
printf("Sending smsgMatch, %"PRId64".\n", now);
|
|
};
|
|
|
|
} else
|
|
if (strCommand == "smsgShow")
|
|
{
|
|
std::vector<unsigned char> vchData;
|
|
vRecv >> vchData;
|
|
|
|
if (vchData.size() < 4)
|
|
return false;
|
|
|
|
uint32_t nBuckets;
|
|
memcpy(&nBuckets, &vchData[0], 4);
|
|
|
|
if (vchData.size() < 4 + nBuckets * 8)
|
|
return false;
|
|
|
|
if (fDebugSmsg)
|
|
printf("smsgShow: peer wants to see content of %u buckets.\n", nBuckets);
|
|
|
|
std::map<int64_t, SecMsgBucket>::iterator itb;
|
|
std::set<SecMsgToken>::iterator it;
|
|
|
|
std::vector<unsigned char> vchDataOut;
|
|
int64_t time;
|
|
unsigned char* pIn = &vchData[4];
|
|
for (uint32_t i = 0; i < nBuckets; ++i, pIn += 8)
|
|
{
|
|
memcpy(&time, pIn, 8);
|
|
|
|
itb = smsgBuckets.find(time);
|
|
if (itb == smsgBuckets.end())
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Don't have bucket %"PRId64".\n", time);
|
|
continue;
|
|
};
|
|
|
|
std::set<SecMsgToken>& tokenSet = (*itb).second.setTokens;
|
|
|
|
try {
|
|
vchDataOut.resize(8 + 16 * tokenSet.size());
|
|
} catch (std::exception& e) {
|
|
printf("vchDataOut.resize %"PRIszu" threw: %s.\n", 8 + 16 * tokenSet.size(), e.what());
|
|
continue;
|
|
};
|
|
memcpy(&vchDataOut[0], &time, 8);
|
|
|
|
unsigned char* p = &vchDataOut[8];
|
|
for (it = tokenSet.begin(); it != tokenSet.end(); ++it)
|
|
{
|
|
memcpy(p, &it->timestamp, 8);
|
|
memcpy(p+8, &it->sample, 8);
|
|
|
|
p += 16;
|
|
};
|
|
pfrom->PushMessage("smsgHave", vchDataOut);
|
|
};
|
|
|
|
|
|
} else
|
|
if (strCommand == "smsgHave")
|
|
{
|
|
// -- peer has these messages in bucket
|
|
std::vector<unsigned char> vchData;
|
|
vRecv >> vchData;
|
|
|
|
if (vchData.size() < 8)
|
|
return false;
|
|
|
|
int n = (vchData.size() - 8) / 16;
|
|
|
|
int64_t time;
|
|
memcpy(&time, &vchData[0], 8);
|
|
|
|
// -- Check time valid:
|
|
int64_t now = GetTime();
|
|
if (time < now - SMSG_RETENTION)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Not interested in peer bucket %"PRId64", has expired.\n", time);
|
|
return false;
|
|
};
|
|
if (time > now + SMSG_TIME_LEEWAY)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Not interested in peer bucket %"PRId64", in the future.\n", time);
|
|
pfrom->Misbehaving(1);
|
|
return false;
|
|
};
|
|
|
|
if (smsgBuckets[time].nLockCount > 0)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Bucket %"PRId64" lock count %u, waiting for message data from peer %u.\n", time, smsgBuckets[time].nLockCount, smsgBuckets[time].nLockPeerId);
|
|
return false;
|
|
};
|
|
|
|
if (fDebugSmsg)
|
|
printf("Sifting through bucket %"PRId64".\n", time);
|
|
|
|
std::vector<unsigned char> vchDataOut;
|
|
vchDataOut.resize(8);
|
|
memcpy(&vchDataOut[0], &vchData[0], 8);
|
|
|
|
std::set<SecMsgToken>& tokenSet = smsgBuckets[time].setTokens;
|
|
std::set<SecMsgToken>::iterator it;
|
|
SecMsgToken token;
|
|
unsigned char* p = &vchData[8];
|
|
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
memcpy(&token.timestamp, p, 8);
|
|
memcpy(&token.sample, p+8, 8);
|
|
|
|
it = tokenSet.find(token);
|
|
if (it == tokenSet.end())
|
|
{
|
|
int nd = vchDataOut.size();
|
|
try {
|
|
vchDataOut.resize(nd + 16);
|
|
} catch (std::exception& e) {
|
|
printf("vchDataOut.resize %d threw: %s.\n", nd + 16, e.what());
|
|
continue;
|
|
};
|
|
|
|
memcpy(&vchDataOut[nd], p, 16);
|
|
};
|
|
|
|
p += 16;
|
|
};
|
|
|
|
if (vchDataOut.size() > 8)
|
|
{
|
|
if (fDebugSmsg)
|
|
{
|
|
printf("Asking peer for %"PRIszu" messages.\n", (vchDataOut.size() - 8) / 16);
|
|
printf("Locking bucket %"PRIszu" for peer %u.\n", time, pfrom->smsgData.nPeerId);
|
|
};
|
|
smsgBuckets[time].nLockCount = 3; // lock this bucket for at most 3 * SMSG_THREAD_DELAY seconds, unset when peer sends smsgMsg
|
|
smsgBuckets[time].nLockPeerId = pfrom->smsgData.nPeerId;
|
|
pfrom->PushMessage("smsgWant", vchDataOut);
|
|
};
|
|
} else
|
|
if (strCommand == "smsgWant")
|
|
{
|
|
std::vector<unsigned char> vchData;
|
|
vRecv >> vchData;
|
|
|
|
if (vchData.size() < 8)
|
|
return false;
|
|
|
|
std::vector<unsigned char> vchOne;
|
|
std::vector<unsigned char> vchBunch;
|
|
|
|
vchBunch.resize(4+8); // nmessages + bucketTime
|
|
|
|
int n = (vchData.size() - 8) / 16;
|
|
|
|
int64_t time;
|
|
uint32_t nBunch = 0;
|
|
memcpy(&time, &vchData[0], 8);
|
|
|
|
std::map<int64_t, SecMsgBucket>::iterator itb;
|
|
itb = smsgBuckets.find(time);
|
|
if (itb == smsgBuckets.end())
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Don't have bucket %"PRId64".\n", time);
|
|
return false;
|
|
};
|
|
|
|
std::set<SecMsgToken>& tokenSet = itb->second.setTokens;
|
|
std::set<SecMsgToken>::iterator it;
|
|
SecMsgToken token;
|
|
unsigned char* p = &vchData[8];
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
memcpy(&token.timestamp, p, 8);
|
|
memcpy(&token.sample, p+8, 8);
|
|
|
|
it = tokenSet.find(token);
|
|
if (it == tokenSet.end())
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Don't have wanted message %"PRId64".\n", token.timestamp);
|
|
} else
|
|
{
|
|
//printf("Have message at %"PRId64".\n", it->offset); // DEBUG
|
|
token.fileIndex = it->fileIndex;
|
|
token.offset = it->offset;
|
|
//printf("winb before SecureMsgRetrieve %"PRId64".\n", token.timestamp);
|
|
|
|
// -- place in vchOne so if SecureMsgRetrieve fails it won't corrupt vchBunch
|
|
if (SecureMsgRetrieve(token, vchOne) == 0)
|
|
{
|
|
nBunch++;
|
|
vchBunch.insert(vchBunch.end(), vchOne.begin(), vchOne.end()); // append
|
|
} else
|
|
{
|
|
printf("SecureMsgRetrieve failed %"PRId64".\n", token.timestamp);
|
|
};
|
|
|
|
if (nBunch >= 500
|
|
|| vchBunch.size() >= 96000)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Break bunch %u, %"PRIszu".\n", nBunch, vchBunch.size());
|
|
break; // end here, peer will send more want messages if needed.
|
|
};
|
|
};
|
|
p += 16;
|
|
};
|
|
|
|
if (nBunch > 0)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Sending block of %u messages for bucket %"PRId64".\n", nBunch, time);
|
|
|
|
memcpy(&vchBunch[0], &nBunch, 4);
|
|
memcpy(&vchBunch[4], &time, 8);
|
|
pfrom->PushMessage("smsgMsg", vchBunch);
|
|
};
|
|
} else
|
|
if (strCommand == "smsgMsg")
|
|
{
|
|
std::vector<unsigned char> vchData;
|
|
vRecv >> vchData;
|
|
|
|
if (fDebugSmsg)
|
|
printf("smsgMsg vchData.size() %"PRIszu".\n", vchData.size());
|
|
|
|
SecureMsgReceive(pfrom, vchData);
|
|
} else
|
|
if (strCommand == "smsgMatch")
|
|
{
|
|
std::vector<unsigned char> vchData;
|
|
vRecv >> vchData;
|
|
|
|
|
|
if (vchData.size() < 8)
|
|
{
|
|
printf("smsgMatch, not enough data %"PRIszu".\n", vchData.size());
|
|
pfrom->Misbehaving(1);
|
|
return false;
|
|
};
|
|
|
|
int64_t time;
|
|
memcpy(&time, &vchData[0], 8);
|
|
|
|
int64_t now = GetTime();
|
|
if (time > now + SMSG_TIME_LEEWAY)
|
|
{
|
|
printf("Warning: Peer buckets matched in the future: %"PRId64".\nEither this node or the peer node has the incorrect time set.\n", time);
|
|
if (fDebugSmsg)
|
|
printf("Peer match time set to now.\n");
|
|
time = now;
|
|
};
|
|
|
|
pfrom->smsgData.lastMatched = time;
|
|
|
|
if (fDebugSmsg)
|
|
printf("Peer buckets matched at %"PRId64".\n", time);
|
|
|
|
} else
|
|
if (strCommand == "smsgPing")
|
|
{
|
|
// -- smsgPing is the initial message, send reply
|
|
pfrom->PushMessage("smsgPong");
|
|
} else
|
|
if (strCommand == "smsgPong")
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Peer replied, secure messaging enabled.\n");
|
|
|
|
pfrom->smsgData.fEnabled = true;
|
|
} else
|
|
if (strCommand == "smsgDisabled")
|
|
{
|
|
// -- peer has disabled secure messaging.
|
|
|
|
pfrom->smsgData.fEnabled = false;
|
|
|
|
if (fDebugSmsg)
|
|
printf("Peer %u has disabled secure messaging.\n", pfrom->smsgData.nPeerId);
|
|
|
|
} else
|
|
if (strCommand == "smsgIgnore")
|
|
{
|
|
// -- peer is reporting that it will ignore this node until time.
|
|
// Ignore peer too
|
|
std::vector<unsigned char> vchData;
|
|
vRecv >> vchData;
|
|
|
|
if (vchData.size() < 8)
|
|
{
|
|
printf("smsgIgnore, not enough data %"PRIszu".\n", vchData.size());
|
|
pfrom->Misbehaving(1);
|
|
return false;
|
|
};
|
|
|
|
int64_t time;
|
|
memcpy(&time, &vchData[0], 8);
|
|
|
|
pfrom->smsgData.ignoreUntil = time;
|
|
|
|
if (fDebugSmsg)
|
|
printf("Peer %u is ignoring this node until %"PRId64", ignore peer too.\n", pfrom->smsgData.nPeerId, time);
|
|
} else
|
|
{
|
|
// Unknown message
|
|
};
|
|
|
|
}; // LOCK(cs_smsg);
|
|
|
|
return true;
|
|
};
|
|
|
|
bool SecureMsgSendData(CNode* pto, bool fSendTrickle)
|
|
{
|
|
/*
|
|
Called from ProcessMessage
|
|
Runs in ThreadMessageHandler2
|
|
*/
|
|
|
|
//printf("SecureMsgSendData() %s.\n", pto->addrName.c_str());
|
|
|
|
|
|
int64_t now = GetTime();
|
|
|
|
if (pto->smsgData.lastSeen == 0)
|
|
{
|
|
// -- first contact
|
|
pto->smsgData.nPeerId = nPeerIdCounter++;
|
|
if (fDebugSmsg)
|
|
printf("SecureMsgSendData() new node %s, peer id %u.\n", pto->addrName.c_str(), pto->smsgData.nPeerId);
|
|
// -- Send smsgPing once, do nothing until receive 1st smsgPong (then set fEnabled)
|
|
pto->PushMessage("smsgPing");
|
|
pto->smsgData.lastSeen = GetTime();
|
|
return true;
|
|
} else
|
|
if (!pto->smsgData.fEnabled
|
|
|| now - pto->smsgData.lastSeen < SMSG_SEND_DELAY
|
|
|| now < pto->smsgData.ignoreUntil)
|
|
{
|
|
return true;
|
|
};
|
|
|
|
// -- When nWakeCounter == 0, resend bucket inventory.
|
|
if (pto->smsgData.nWakeCounter < 1)
|
|
{
|
|
pto->smsgData.lastMatched = 0;
|
|
pto->smsgData.nWakeCounter = 10 + GetRandInt(300); // set to a random time between [10, 300] * SMSG_SEND_DELAY seconds
|
|
|
|
if (fDebugSmsg)
|
|
printf("SecureMsgSendData(): nWakeCounter expired, sending bucket inventory to %s.\n"
|
|
"Now %"PRId64" next wake counter %u\n", pto->addrName.c_str(), now, pto->smsgData.nWakeCounter);
|
|
};
|
|
pto->smsgData.nWakeCounter--;
|
|
|
|
{
|
|
LOCK(cs_smsg);
|
|
std::map<int64_t, SecMsgBucket>::iterator it;
|
|
|
|
uint32_t nBuckets = smsgBuckets.size();
|
|
if (nBuckets > 0) // no need to send keep alive pkts, coin messages already do that
|
|
{
|
|
std::vector<unsigned char> vchData;
|
|
// should reserve?
|
|
vchData.reserve(4 + nBuckets*16); // timestamp + size + hash
|
|
|
|
uint32_t nBucketsShown = 0;
|
|
vchData.resize(4);
|
|
|
|
unsigned char* p = &vchData[4];
|
|
for (it = smsgBuckets.begin(); it != smsgBuckets.end(); ++it)
|
|
{
|
|
SecMsgBucket &bkt = it->second;
|
|
|
|
uint32_t nMessages = bkt.setTokens.size();
|
|
|
|
if (bkt.timeChanged < pto->smsgData.lastMatched // peer has this bucket
|
|
|| nMessages < 1) // this bucket is empty
|
|
continue;
|
|
|
|
|
|
uint32_t hash = bkt.hash;
|
|
|
|
try {
|
|
vchData.resize(vchData.size() + 16);
|
|
} catch (std::exception& e) {
|
|
printf("vchData.resize %"PRIszu" threw: %s.\n", vchData.size() + 16, e.what());
|
|
continue;
|
|
};
|
|
memcpy(p, &it->first, 8);
|
|
memcpy(p+8, &nMessages, 4);
|
|
memcpy(p+12, &hash, 4);
|
|
|
|
p += 16;
|
|
nBucketsShown++;
|
|
//if (fDebug)
|
|
// printf("Sending bucket %"PRId64", size %d \n", it->first, it->second.size());
|
|
};
|
|
|
|
if (vchData.size() > 4)
|
|
{
|
|
memcpy(&vchData[0], &nBucketsShown, 4);
|
|
if (fDebugSmsg)
|
|
printf("Sending %d bucket headers.\n", nBucketsShown);
|
|
|
|
pto->PushMessage("smsgInv", vchData);
|
|
};
|
|
};
|
|
}
|
|
|
|
pto->smsgData.lastSeen = GetTime();
|
|
|
|
return true;
|
|
};
|
|
|
|
|
|
static int SecureMsgInsertAddress(CKeyID& hashKey, CPubKey& pubKey, SecMsgDB& addrpkdb)
|
|
{
|
|
/* insert key hash and public key to addressdb
|
|
|
|
should have LOCK(cs_smsg) where db is opened
|
|
|
|
returns
|
|
0 success
|
|
1 error
|
|
4 address is already in db
|
|
*/
|
|
|
|
|
|
if (addrpkdb.ExistsPK(hashKey))
|
|
{
|
|
//printf("DB already contains public key for address.\n");
|
|
CPubKey cpkCheck;
|
|
if (!addrpkdb.ReadPK(hashKey, cpkCheck))
|
|
{
|
|
printf("addrpkdb.Read failed.\n");
|
|
} else
|
|
{
|
|
if (cpkCheck != pubKey)
|
|
printf("DB already contains existing public key that does not match .\n");
|
|
};
|
|
return 4;
|
|
};
|
|
|
|
if (!addrpkdb.WritePK(hashKey, pubKey))
|
|
{
|
|
printf("Write pair failed.\n");
|
|
return 1;
|
|
};
|
|
|
|
return 0;
|
|
};
|
|
|
|
int SecureMsgInsertAddress(CKeyID& hashKey, CPubKey& pubKey)
|
|
{
|
|
int rv;
|
|
{
|
|
LOCK(cs_smsgDB);
|
|
SecMsgDB addrpkdb;
|
|
|
|
if (!addrpkdb.Open("cr+"))
|
|
return 1;
|
|
|
|
rv = SecureMsgInsertAddress(hashKey, pubKey, addrpkdb);
|
|
}
|
|
return rv;
|
|
};
|
|
|
|
|
|
static bool ScanBlock(CBlock& block, CTxDBBase& txdb, SecMsgDB& addrpkdb,
|
|
uint32_t& nTransactions, uint32_t& nInputs, uint32_t& nPubkeys, uint32_t& nDuplicates)
|
|
{
|
|
// -- should have LOCK(cs_smsg) where db is opened
|
|
for (CTransaction& tx : block.vtx)
|
|
{
|
|
if (!tx.IsStandard())
|
|
continue; // leave out coinbase and others
|
|
|
|
/*
|
|
Look at the inputs of every tx.
|
|
If the inputs are standard, get the pubkey from scriptsig and
|
|
look for the corresponding output (the input(output of other tx) to the input of this tx)
|
|
get the address from scriptPubKey
|
|
add to db if address is unique.
|
|
|
|
Would make more sense to do this the other way around, get address first for early out.
|
|
|
|
*/
|
|
|
|
for (unsigned int i = 0; i < tx.vin.size(); i++)
|
|
{
|
|
CScript *script = &tx.vin[i].scriptSig;
|
|
|
|
opcodetype opcode;
|
|
valtype vch;
|
|
CScript::const_iterator pc = script->begin();
|
|
CScript::const_iterator pend = script->end();
|
|
|
|
uint256 prevoutHash;
|
|
CKey key;
|
|
|
|
// -- matching address is in scriptPubKey of previous tx output
|
|
while (pc < pend)
|
|
{
|
|
if (!script->GetOp(pc, opcode, vch))
|
|
break;
|
|
// -- opcode is the length of the following data, compressed public key is always 33
|
|
if (opcode == 33)
|
|
{
|
|
key.SetPubKey(vch);
|
|
|
|
key.SetCompressedPubKey(); // ensure key is compressed
|
|
CPubKey pubKey = key.GetPubKey();
|
|
|
|
if (!pubKey.IsValid()
|
|
|| !pubKey.IsCompressed())
|
|
{
|
|
printf("Public key is invalid %s.\n", ValueString(pubKey.Raw()).c_str());
|
|
continue;
|
|
};
|
|
|
|
prevoutHash = tx.vin[i].prevout.hash;
|
|
CTransaction txOfPrevOutput;
|
|
if (!txdb.ReadDiskTx(prevoutHash, txOfPrevOutput))
|
|
{
|
|
printf("Could not get transaction for hash: %s.\n", prevoutHash.ToString().c_str());
|
|
continue;
|
|
};
|
|
|
|
unsigned int nOut = tx.vin[i].prevout.n;
|
|
if (nOut >= txOfPrevOutput.vout.size())
|
|
{
|
|
printf("Output %u, not in transaction: %s.\n", nOut, prevoutHash.ToString().c_str());
|
|
continue;
|
|
};
|
|
|
|
CTxOut *txOut = &txOfPrevOutput.vout[nOut];
|
|
|
|
CTxDestination addressRet;
|
|
if (!ExtractDestination(txOut->scriptPubKey, addressRet))
|
|
{
|
|
printf("ExtractDestination failed: %s.\n", prevoutHash.ToString().c_str());
|
|
break;
|
|
};
|
|
|
|
|
|
CTrianglesAddress coinAddress(addressRet);
|
|
CKeyID hashKey;
|
|
if (!coinAddress.GetKeyID(hashKey))
|
|
{
|
|
printf("coinAddress.GetKeyID failed: %s.\n", coinAddress.ToString().c_str());
|
|
break;
|
|
};
|
|
|
|
int rv = SecureMsgInsertAddress(hashKey, pubKey, addrpkdb);
|
|
if (rv != 0)
|
|
{
|
|
if (rv == 4)
|
|
nDuplicates++;
|
|
break;
|
|
};
|
|
nPubkeys++;
|
|
break;
|
|
};
|
|
|
|
//printf("opcode %d, %s, value %s.\n", opcode, GetOpName(opcode), ValueString(vch).c_str());
|
|
};
|
|
nInputs++;
|
|
};
|
|
nTransactions++;
|
|
|
|
if (nTransactions % 10000 == 0) // for ScanChainForPublicKeys
|
|
{
|
|
printf("Scanning transaction no. %u.\n", nTransactions);
|
|
};
|
|
};
|
|
return true;
|
|
};
|
|
|
|
|
|
bool SecureMsgScanBlock(CBlock& block)
|
|
{
|
|
/*
|
|
scan block for public key addresses
|
|
called from ProcessMessage() in main where strCommand == "block"
|
|
*/
|
|
|
|
if (fDebugSmsg)
|
|
printf("SecureMsgScanBlock().\n");
|
|
|
|
uint32_t nTransactions = 0;
|
|
uint32_t nInputs = 0;
|
|
uint32_t nPubkeys = 0;
|
|
uint32_t nDuplicates = 0;
|
|
|
|
{
|
|
LOCK(cs_smsgDB);
|
|
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
|
|
|
|
SecMsgDB addrpkdb;
|
|
if (!addrpkdb.Open("cw")
|
|
|| !addrpkdb.TxnBegin())
|
|
return false;
|
|
|
|
ScanBlock(block, txdb, addrpkdb,
|
|
nTransactions, nInputs, nPubkeys, nDuplicates);
|
|
|
|
addrpkdb.TxnCommit();
|
|
}
|
|
|
|
if (fDebugSmsg)
|
|
printf("Found %u transactions, %u inputs, %u new public keys, %u duplicates.\n", nTransactions, nInputs, nPubkeys, nDuplicates);
|
|
|
|
return true;
|
|
};
|
|
|
|
bool ScanChainForPublicKeys(CBlockIndex* pindexStart)
|
|
{
|
|
printf("Scanning block chain for public keys.\n");
|
|
int64_t nStart = GetTimeMillis();
|
|
|
|
if (fDebugSmsg)
|
|
printf("From height %u.\n", pindexStart->nHeight);
|
|
|
|
// -- public keys are in txin.scriptSig
|
|
// matching addresses are in scriptPubKey of txin's referenced output
|
|
|
|
uint32_t nBlocks = 0;
|
|
uint32_t nTransactions = 0;
|
|
uint32_t nInputs = 0;
|
|
uint32_t nPubkeys = 0;
|
|
uint32_t nDuplicates = 0;
|
|
|
|
{
|
|
LOCK(cs_smsgDB);
|
|
|
|
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
|
|
|
|
SecMsgDB addrpkdb;
|
|
if (!addrpkdb.Open("cw")
|
|
|| !addrpkdb.TxnBegin())
|
|
return false;
|
|
|
|
CBlockIndex* pindex = pindexStart;
|
|
while (pindex)
|
|
{
|
|
nBlocks++;
|
|
CBlock block;
|
|
block.ReadFromDisk(pindex, true);
|
|
|
|
ScanBlock(block, txdb, addrpkdb,
|
|
nTransactions, nInputs, nPubkeys, nDuplicates);
|
|
|
|
pindex = pindex->pnext;
|
|
};
|
|
|
|
addrpkdb.TxnCommit();
|
|
};
|
|
|
|
printf("Scanned %u blocks, %u transactions, %u inputs\n", nBlocks, nTransactions, nInputs);
|
|
printf("Found %u public keys, %u duplicates.\n", nPubkeys, nDuplicates);
|
|
printf("Took %"PRId64" ms\n", GetTimeMillis() - nStart);
|
|
|
|
return true;
|
|
};
|
|
|
|
bool SecureMsgScanBlockChain()
|
|
{
|
|
TRY_LOCK(cs_main, lockMain);
|
|
if (lockMain)
|
|
{
|
|
CBlockIndex *pindexScan = pindexGenesisBlock;
|
|
if (pindexScan == NULL)
|
|
{
|
|
printf("Error: pindexGenesisBlock not set.\n");
|
|
return false;
|
|
};
|
|
|
|
|
|
try { // -- in try to catch errors opening db,
|
|
if (!ScanChainForPublicKeys(pindexScan))
|
|
return false;
|
|
} catch (std::exception& e)
|
|
{
|
|
printf("ScanChainForPublicKeys() threw: %s.\n", e.what());
|
|
return false;
|
|
};
|
|
} else
|
|
{
|
|
printf("ScanChainForPublicKeys() Could not lock main.\n");
|
|
return false;
|
|
};
|
|
|
|
return true;
|
|
};
|
|
|
|
bool SecureMsgScanBuckets()
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("SecureMsgScanBuckets()\n");
|
|
|
|
if (!fSecMsgEnabled
|
|
|| pwalletMain->IsLocked())
|
|
return false;
|
|
|
|
int64_t mStart = GetTimeMillis();
|
|
int64_t now = GetTime();
|
|
uint32_t nFiles = 0;
|
|
uint32_t nMessages = 0;
|
|
uint32_t nFoundMessages = 0;
|
|
|
|
fs::path pathSmsgDir = GetDataDir() / "smsgStore";
|
|
fs::directory_iterator itend;
|
|
|
|
if (!fs::exists(pathSmsgDir)
|
|
|| !fs::is_directory(pathSmsgDir))
|
|
{
|
|
printf("Message store directory does not exist.\n");
|
|
return 0; // not an error
|
|
};
|
|
|
|
SecureMessage smsg;
|
|
std::vector<unsigned char> vchData;
|
|
|
|
for (fs::directory_iterator itd(pathSmsgDir) ; itd != itend ; ++itd)
|
|
{
|
|
if (!fs::is_regular_file(itd->status()))
|
|
continue;
|
|
|
|
std::string fileType = (*itd).path().extension().string();
|
|
|
|
if (fileType.compare(".dat") != 0)
|
|
continue;
|
|
|
|
std::string fileName = (*itd).path().filename().string();
|
|
|
|
|
|
if (fDebugSmsg)
|
|
printf("Processing file: %s.\n", fileName.c_str());
|
|
|
|
nFiles++;
|
|
|
|
int64_t fileTime;
|
|
uint32_t fileIndex;
|
|
bool fWalletLocked;
|
|
if (!SecureMsgParseBucketFilename(fileName, fileTime, fileIndex, fWalletLocked))
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Skipping unrecognised bucket file: %s.\n", fileName.c_str());
|
|
continue;
|
|
};
|
|
|
|
if (fileTime < now - SMSG_RETENTION)
|
|
{
|
|
printf("Dropping file %s, expired.\n", fileName.c_str());
|
|
try {
|
|
fs::remove((*itd).path());
|
|
} catch (const fs::filesystem_error& ex)
|
|
{
|
|
printf("Error removing bucket file %s, %s.\n", fileName.c_str(), ex.what());
|
|
};
|
|
continue;
|
|
};
|
|
|
|
if (fWalletLocked)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Skipping wallet locked file: %s.\n", fileName.c_str());
|
|
continue;
|
|
};
|
|
|
|
{
|
|
LOCK(cs_smsg);
|
|
FILE *fp;
|
|
errno = 0;
|
|
if (!(fp = fopen((*itd).path().string().c_str(), "rb")))
|
|
{
|
|
printf("Error opening file: %s\n", strerror(errno));
|
|
continue;
|
|
};
|
|
|
|
for (;;)
|
|
{
|
|
errno = 0;
|
|
if (fread(&smsg.hash[0], sizeof(unsigned char), SMSG_HDR_LEN, fp) != (size_t)SMSG_HDR_LEN)
|
|
{
|
|
if (errno != 0)
|
|
{
|
|
printf("fread header failed: %s\n", strerror(errno));
|
|
} else
|
|
{
|
|
//printf("End of file.\n");
|
|
};
|
|
break;
|
|
};
|
|
|
|
try {
|
|
vchData.resize(smsg.nPayload);
|
|
} catch (std::exception& e)
|
|
{
|
|
printf("SecureMsgWalletUnlocked(): Could not resize vchData, %u, %s\n", smsg.nPayload, e.what());
|
|
fclose(fp);
|
|
return 1;
|
|
};
|
|
|
|
if (fread(&vchData[0], sizeof(unsigned char), smsg.nPayload, fp) != smsg.nPayload)
|
|
{
|
|
printf("fread data failed: %s\n", strerror(errno));
|
|
break;
|
|
};
|
|
|
|
// -- don't report to gui,
|
|
int rv = SecureMsgScanMessage(&smsg.hash[0], &vchData[0], smsg.nPayload, false);
|
|
|
|
if (rv == 0)
|
|
{
|
|
nFoundMessages++;
|
|
} else
|
|
if (rv != 0)
|
|
{
|
|
// SecureMsgScanMessage failed
|
|
};
|
|
|
|
nMessages ++;
|
|
};
|
|
|
|
fclose(fp);
|
|
|
|
// -- remove wl file when scanned
|
|
try {
|
|
fs::remove((*itd).path());
|
|
} catch (const std::filesystem::filesystem_error& ex)
|
|
{
|
|
printf("Error removing wl file %s - %s\n", fileName.c_str(), ex.what());
|
|
return 1;
|
|
};
|
|
};
|
|
};
|
|
|
|
printf("Processed %u files, scanned %u messages, received %u messages.\n", nFiles, nMessages, nFoundMessages);
|
|
printf("Took %"PRId64" ms\n", GetTimeMillis() - mStart);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
int SecureMsgWalletUnlocked()
|
|
{
|
|
/*
|
|
When the wallet is unlocked scan messages received while wallet was locked.
|
|
*/
|
|
if (!fSecMsgEnabled)
|
|
return 0;
|
|
|
|
|
|
printf("SecureMsgWalletUnlocked()\n");
|
|
|
|
if (pwalletMain->IsLocked())
|
|
{
|
|
printf("Error: Wallet is locked.\n");
|
|
return 1;
|
|
};
|
|
|
|
int64_t now = GetTime();
|
|
uint32_t nFiles = 0;
|
|
uint32_t nMessages = 0;
|
|
uint32_t nFoundMessages = 0;
|
|
|
|
fs::path pathSmsgDir = GetDataDir() / "smsgStore";
|
|
fs::directory_iterator itend;
|
|
|
|
if (!fs::exists(pathSmsgDir)
|
|
|| !fs::is_directory(pathSmsgDir))
|
|
{
|
|
printf("Message store directory does not exist.\n");
|
|
return 0; // not an error
|
|
};
|
|
|
|
SecureMessage smsg;
|
|
std::vector<unsigned char> vchData;
|
|
|
|
for (fs::directory_iterator itd(pathSmsgDir) ; itd != itend ; ++itd)
|
|
{
|
|
if (!fs::is_regular_file(itd->status()))
|
|
continue;
|
|
|
|
std::string fileName = (*itd).path().filename().string();
|
|
|
|
int64_t fileTime;
|
|
uint32_t fileIndex;
|
|
bool fWalletLocked;
|
|
if (!SecureMsgParseBucketFilename(fileName, fileTime, fileIndex, fWalletLocked))
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Skipping unrecognised bucket file: %s.\n", fileName.c_str());
|
|
continue;
|
|
};
|
|
|
|
if (!fWalletLocked)
|
|
continue;
|
|
|
|
if (fDebugSmsg)
|
|
printf("Processing file: %s.\n", fileName.c_str());
|
|
|
|
nFiles++;
|
|
|
|
if (fileTime < now - SMSG_RETENTION)
|
|
{
|
|
printf("Dropping wallet locked file %s, expired.\n", fileName.c_str());
|
|
try {
|
|
fs::remove((*itd).path());
|
|
} catch (const std::filesystem::filesystem_error& ex)
|
|
{
|
|
printf("Error removing wl file %s - %s\n", fileName.c_str(), ex.what());
|
|
return 1;
|
|
};
|
|
continue;
|
|
};
|
|
|
|
{
|
|
LOCK(cs_smsg);
|
|
FILE *fp;
|
|
errno = 0;
|
|
if (!(fp = fopen((*itd).path().string().c_str(), "rb")))
|
|
{
|
|
printf("Error opening file: %s\n", strerror(errno));
|
|
continue;
|
|
};
|
|
|
|
for (;;)
|
|
{
|
|
errno = 0;
|
|
if (fread(&smsg.hash[0], sizeof(unsigned char), SMSG_HDR_LEN, fp) != (size_t)SMSG_HDR_LEN)
|
|
{
|
|
if (errno != 0)
|
|
{
|
|
printf("fread header failed: %s\n", strerror(errno));
|
|
} else
|
|
{
|
|
//printf("End of file.\n");
|
|
};
|
|
break;
|
|
};
|
|
|
|
try {
|
|
vchData.resize(smsg.nPayload);
|
|
} catch (std::exception& e)
|
|
{
|
|
printf("SecureMsgWalletUnlocked(): Could not resize vchData, %u, %s\n", smsg.nPayload, e.what());
|
|
fclose(fp);
|
|
return 1;
|
|
};
|
|
|
|
if (fread(&vchData[0], sizeof(unsigned char), smsg.nPayload, fp) != smsg.nPayload)
|
|
{
|
|
printf("fread data failed: %s\n", strerror(errno));
|
|
break;
|
|
};
|
|
|
|
// -- don't report to gui,
|
|
int rv = SecureMsgScanMessage(&smsg.hash[0], &vchData[0], smsg.nPayload, false);
|
|
|
|
if (rv == 0)
|
|
{
|
|
nFoundMessages++;
|
|
} else
|
|
if (rv != 0)
|
|
{
|
|
// SecureMsgScanMessage failed
|
|
};
|
|
|
|
nMessages ++;
|
|
};
|
|
|
|
fclose(fp);
|
|
|
|
// -- remove wl file when scanned
|
|
try {
|
|
fs::remove((*itd).path());
|
|
} catch (const std::filesystem::filesystem_error& ex)
|
|
{
|
|
printf("Error removing wl file %s - %s\n", fileName.c_str(), ex.what());
|
|
return 1;
|
|
};
|
|
};
|
|
};
|
|
|
|
printf("Processed %u files, scanned %u messages, received %u messages.\n", nFiles, nMessages, nFoundMessages);
|
|
|
|
// -- notify gui
|
|
NotifySecMsgWalletUnlocked();
|
|
|
|
return 0;
|
|
};
|
|
|
|
int SecureMsgWalletKeyChanged(std::string sAddress, std::string sLabel, ChangeType mode)
|
|
{
|
|
if (!fSecMsgEnabled)
|
|
return 0;
|
|
|
|
printf("SecureMsgWalletKeyChanged()\n");
|
|
|
|
// TODO: default recv and recvAnon
|
|
|
|
{
|
|
LOCK(cs_smsg);
|
|
|
|
switch(mode)
|
|
{
|
|
case CT_NEW:
|
|
smsgAddresses.push_back(SecMsgAddress(sAddress, smsgOptions.fNewAddressRecv, smsgOptions.fNewAddressAnon));
|
|
break;
|
|
case CT_DELETED:
|
|
for (std::vector<SecMsgAddress>::iterator it = smsgAddresses.begin(); it != smsgAddresses.end(); ++it)
|
|
{
|
|
if (sAddress != it->sAddress)
|
|
continue;
|
|
smsgAddresses.erase(it);
|
|
break;
|
|
};
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
}; // LOCK(cs_smsg);
|
|
|
|
|
|
return 0;
|
|
};
|
|
|
|
int SecureMsgScanMessage(unsigned char *pHeader, unsigned char *pPayload, uint32_t nPayload, bool reportToGui)
|
|
{
|
|
/*
|
|
Check if message belongs to this node.
|
|
If so add to inbox db.
|
|
|
|
if !reportToGui don't fire NotifySecMsgInboxChanged
|
|
- loads messages received when wallet locked in bulk.
|
|
|
|
returns
|
|
0 success,
|
|
1 error
|
|
2 no match
|
|
3 wallet is locked - message stored for scanning later.
|
|
*/
|
|
|
|
if (fDebugSmsg)
|
|
printf("SecureMsgScanMessage()\n");
|
|
|
|
if (pwalletMain->IsLocked())
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("ScanMessage: Wallet is locked, storing message to scan later.\n");
|
|
|
|
int rv;
|
|
if ((rv = SecureMsgStoreUnscanned(pHeader, pPayload, nPayload)) != 0)
|
|
return 1;
|
|
|
|
return 3;
|
|
};
|
|
|
|
std::string addressTo;
|
|
MessageData msg; // placeholder
|
|
bool fOwnMessage = false;
|
|
|
|
for (std::vector<SecMsgAddress>::iterator it = smsgAddresses.begin(); it != smsgAddresses.end(); ++it)
|
|
{
|
|
if (!it->fReceiveEnabled)
|
|
continue;
|
|
|
|
CTrianglesAddress coinAddress(it->sAddress);
|
|
addressTo = coinAddress.ToString();
|
|
|
|
if (!it->fReceiveAnon)
|
|
{
|
|
// -- have to do full decrypt to see address from
|
|
if (SecureMsgDecrypt(false, addressTo, pHeader, pPayload, nPayload, msg) == 0)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Decrypted message with %s.\n", addressTo.c_str());
|
|
|
|
if (msg.sFromAddress.compare("anon") != 0)
|
|
fOwnMessage = true;
|
|
break;
|
|
};
|
|
} else
|
|
{
|
|
|
|
if (SecureMsgDecrypt(true, addressTo, pHeader, pPayload, nPayload, msg) == 0)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Decrypted message with %s.\n", addressTo.c_str());
|
|
|
|
fOwnMessage = true;
|
|
break;
|
|
};
|
|
}
|
|
};
|
|
|
|
if (fOwnMessage)
|
|
{
|
|
// -- save to inbox
|
|
SecureMessage* psmsg = (SecureMessage*) pHeader;
|
|
std::string sPrefix("im");
|
|
unsigned char chKey[18];
|
|
memcpy(&chKey[0], sPrefix.data(), 2);
|
|
memcpy(&chKey[2], &psmsg->timestamp, 8);
|
|
memcpy(&chKey[10], pPayload, 8);
|
|
|
|
SecMsgStored smsgInbox;
|
|
smsgInbox.timeReceived = GetTime();
|
|
smsgInbox.status = (SMSG_MASK_UNREAD) & 0xFF;
|
|
smsgInbox.sAddrTo = addressTo;
|
|
|
|
// -- data may not be contiguous
|
|
try {
|
|
smsgInbox.vchMessage.resize(SMSG_HDR_LEN + nPayload);
|
|
} catch (std::exception& e) {
|
|
printf("SecureMsgScanMessage(): Could not resize vchData, %u, %s\n", SMSG_HDR_LEN + nPayload, e.what());
|
|
return 1;
|
|
};
|
|
memcpy(&smsgInbox.vchMessage[0], pHeader, SMSG_HDR_LEN);
|
|
memcpy(&smsgInbox.vchMessage[SMSG_HDR_LEN], pPayload, nPayload);
|
|
|
|
{
|
|
LOCK(cs_smsgDB);
|
|
SecMsgDB dbInbox;
|
|
|
|
if (dbInbox.Open("cw"))
|
|
{
|
|
if (dbInbox.ExistsSmesg(chKey))
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Message already exists in inbox db.\n");
|
|
} else
|
|
{
|
|
dbInbox.WriteSmesg(chKey, smsgInbox);
|
|
|
|
if (reportToGui)
|
|
NotifySecMsgInboxChanged(smsgInbox);
|
|
printf("SecureMsg saved to inbox, received with %s.\n", addressTo.c_str());
|
|
};
|
|
};
|
|
}
|
|
};
|
|
|
|
return 0;
|
|
};
|
|
|
|
int SecureMsgGetLocalKey(CKeyID& ckid, CPubKey& cpkOut)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("SecureMsgGetLocalKey()\n");
|
|
|
|
CKey key;
|
|
if (!pwalletMain->GetKey(ckid, key))
|
|
return 4;
|
|
|
|
key.SetCompressedPubKey(); // make sure key is compressed
|
|
|
|
cpkOut = key.GetPubKey();
|
|
if (!cpkOut.IsValid()
|
|
|| !cpkOut.IsCompressed())
|
|
{
|
|
printf("Public key is invalid %s.\n", ValueString(cpkOut.Raw()).c_str());
|
|
return 1;
|
|
};
|
|
|
|
return 0;
|
|
};
|
|
|
|
int SecureMsgGetLocalPublicKey(std::string& strAddress, std::string& strPublicKey)
|
|
{
|
|
/* returns
|
|
0 success,
|
|
1 error
|
|
2 invalid address
|
|
3 address does not refer to a key
|
|
4 address not in wallet
|
|
*/
|
|
|
|
CTrianglesAddress address;
|
|
if (!address.SetString(strAddress))
|
|
return 2; // Invalid coin address
|
|
|
|
CKeyID keyID;
|
|
if (!address.GetKeyID(keyID))
|
|
return 3;
|
|
|
|
int rv;
|
|
CPubKey pubKey;
|
|
if ((rv = SecureMsgGetLocalKey(keyID, pubKey)) != 0)
|
|
return rv;
|
|
|
|
strPublicKey = EncodeBase58(pubKey.Raw());
|
|
|
|
return 0;
|
|
};
|
|
|
|
int SecureMsgGetStoredKey(CKeyID& ckid, CPubKey& cpkOut)
|
|
{
|
|
/* returns
|
|
0 success,
|
|
1 error
|
|
2 public key not in database
|
|
*/
|
|
if (fDebugSmsg)
|
|
printf("SecureMsgGetStoredKey().\n");
|
|
|
|
{
|
|
LOCK(cs_smsgDB);
|
|
SecMsgDB addrpkdb;
|
|
|
|
if (!addrpkdb.Open("r"))
|
|
return 1;
|
|
|
|
if (!addrpkdb.ReadPK(ckid, cpkOut))
|
|
{
|
|
//printf("addrpkdb.Read failed: %s.\n", coinAddress.ToString().c_str());
|
|
return 2;
|
|
};
|
|
}
|
|
|
|
return 0;
|
|
};
|
|
|
|
int SecureMsgAddAddress(std::string& address, std::string& publicKey)
|
|
{
|
|
/*
|
|
Add address and matching public key to the database
|
|
address and publicKey are in base58
|
|
|
|
returns
|
|
0 success
|
|
1 error
|
|
2 publicKey is invalid
|
|
3 publicKey != address
|
|
4 address is already in db
|
|
5 address is invalid
|
|
*/
|
|
|
|
CTrianglesAddress coinAddress(address);
|
|
|
|
if (!coinAddress.IsValid())
|
|
{
|
|
printf("Address is not valid: %s.\n", address.c_str());
|
|
return 5;
|
|
};
|
|
|
|
CKeyID hashKey;
|
|
|
|
if (!coinAddress.GetKeyID(hashKey))
|
|
{
|
|
printf("coinAddress.GetKeyID failed: %s.\n", coinAddress.ToString().c_str());
|
|
return 5;
|
|
};
|
|
|
|
std::vector<unsigned char> vchTest;
|
|
DecodeBase58(publicKey, vchTest);
|
|
CPubKey pubKey(vchTest);
|
|
|
|
// -- check that public key matches address hash
|
|
CKey keyT;
|
|
if (!keyT.SetPubKey(pubKey))
|
|
{
|
|
printf("SetPubKey failed.\n");
|
|
return 2;
|
|
};
|
|
|
|
keyT.SetCompressedPubKey();
|
|
CPubKey pubKeyT = keyT.GetPubKey();
|
|
|
|
CTrianglesAddress addressT(address);
|
|
|
|
if (addressT.ToString().compare(address) != 0)
|
|
{
|
|
printf("Public key does not hash to address, addressT %s.\n", addressT.ToString().c_str());
|
|
return 3;
|
|
};
|
|
|
|
return SecureMsgInsertAddress(hashKey, pubKey);
|
|
};
|
|
|
|
int SecureMsgRetrieve(SecMsgToken &token, std::vector<unsigned char>& vchData)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("SecureMsgRetrieve() %"PRId64".\n", token.timestamp);
|
|
|
|
// -- has cs_smsg lock from SecureMsgReceiveData
|
|
|
|
fs::path pathSmsgDir = GetDataDir() / "smsgStore";
|
|
|
|
//printf("token.offset %"PRId64".\n", token.offset); // DEBUG
|
|
int64_t bucket = token.timestamp - (token.timestamp % SMSG_BUCKET_LEN);
|
|
std::string fileName = SecureMsgBucketFilename(bucket, token.fileIndex, false);
|
|
fs::path fullpath = pathSmsgDir / fileName;
|
|
|
|
//printf("bucket %"PRId64".\n", bucket);
|
|
//printf("bucket lld %lld.\n", bucket);
|
|
//printf("fileName %s.\n", fileName.c_str());
|
|
|
|
FILE *fp;
|
|
errno = 0;
|
|
if (!(fp = fopen(fullpath.string().c_str(), "rb")))
|
|
{
|
|
printf("Error opening file: %s\nPath %s\n", strerror(errno), fullpath.string().c_str());
|
|
return 1;
|
|
};
|
|
|
|
errno = 0;
|
|
if (fseek(fp, token.offset, SEEK_SET) != 0)
|
|
{
|
|
printf("fseek, strerror: %s.\n", strerror(errno));
|
|
fclose(fp);
|
|
return 1;
|
|
};
|
|
|
|
SecureMessage smsg;
|
|
errno = 0;
|
|
if (fread(&smsg.hash[0], sizeof(unsigned char), SMSG_HDR_LEN, fp) != (size_t)SMSG_HDR_LEN)
|
|
{
|
|
printf("fread header failed: %s\n", strerror(errno));
|
|
fclose(fp);
|
|
return 1;
|
|
};
|
|
|
|
try {
|
|
vchData.resize(SMSG_HDR_LEN + smsg.nPayload);
|
|
} catch (std::exception& e) {
|
|
printf("SecureMsgRetrieve(): Could not resize vchData, %u, %s\n", SMSG_HDR_LEN + smsg.nPayload, e.what());
|
|
return 1;
|
|
};
|
|
|
|
memcpy(&vchData[0], &smsg.hash[0], SMSG_HDR_LEN);
|
|
errno = 0;
|
|
if (fread(&vchData[SMSG_HDR_LEN], sizeof(unsigned char), smsg.nPayload, fp) != smsg.nPayload)
|
|
{
|
|
printf("fread data failed: %s. Wanted %u bytes.\n", strerror(errno), smsg.nPayload);
|
|
fclose(fp);
|
|
return 1;
|
|
};
|
|
|
|
|
|
fclose(fp);
|
|
|
|
return 0;
|
|
};
|
|
|
|
int SecureMsgReceive(CNode* pfrom, std::vector<unsigned char>& vchData)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("SecureMsgReceive().\n");
|
|
|
|
if (vchData.size() < 12) // nBunch4 + timestamp8
|
|
{
|
|
printf("Error: not enough data.\n");
|
|
return 1;
|
|
};
|
|
|
|
uint32_t nBunch;
|
|
int64_t bktTime;
|
|
|
|
memcpy(&nBunch, &vchData[0], 4);
|
|
memcpy(&bktTime, &vchData[4], 8);
|
|
|
|
|
|
// -- check bktTime ()
|
|
// bucket may not exist yet - will be created when messages are added
|
|
int64_t now = GetTime();
|
|
if (bktTime > now + SMSG_TIME_LEEWAY)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("bktTime > now.\n");
|
|
// misbehave?
|
|
return 1;
|
|
} else
|
|
if (bktTime < now - SMSG_RETENTION)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("bktTime < now - SMSG_RETENTION.\n");
|
|
// misbehave?
|
|
return 1;
|
|
};
|
|
|
|
std::map<int64_t, SecMsgBucket>::iterator itb;
|
|
|
|
if (nBunch == 0 || nBunch > 500)
|
|
{
|
|
printf("Error: Invalid no. messages received in bunch %u, for bucket %"PRId64".\n", nBunch, bktTime);
|
|
pfrom->Misbehaving(1);
|
|
|
|
// -- release lock on bucket if it exists
|
|
itb = smsgBuckets.find(bktTime);
|
|
if (itb != smsgBuckets.end())
|
|
itb->second.nLockCount = 0;
|
|
return 1;
|
|
};
|
|
|
|
uint32_t n = 12;
|
|
|
|
for (uint32_t i = 0; i < nBunch; ++i)
|
|
{
|
|
if (vchData.size() - n < SMSG_HDR_LEN)
|
|
{
|
|
printf("Error: not enough data sent, n = %u.\n", n);
|
|
break;
|
|
};
|
|
|
|
SecureMessage* psmsg = (SecureMessage*) &vchData[n];
|
|
|
|
int rv;
|
|
if ((rv = SecureMsgValidate(&vchData[n], &vchData[n + SMSG_HDR_LEN], psmsg->nPayload)) != 0)
|
|
{
|
|
// message dropped
|
|
if (rv == 2) // invalid proof of work
|
|
{
|
|
pfrom->Misbehaving(10);
|
|
} else
|
|
{
|
|
pfrom->Misbehaving(1);
|
|
};
|
|
continue;
|
|
};
|
|
|
|
// -- store message, but don't hash bucket
|
|
if (SecureMsgStore(&vchData[n], &vchData[n + SMSG_HDR_LEN], psmsg->nPayload, false) != 0)
|
|
{
|
|
// message dropped
|
|
break; // continue?
|
|
};
|
|
|
|
if (SecureMsgScanMessage(&vchData[n], &vchData[n + SMSG_HDR_LEN], psmsg->nPayload, true) != 0)
|
|
{
|
|
// message recipient is not this node (or failed)
|
|
};
|
|
|
|
n += SMSG_HDR_LEN + psmsg->nPayload;
|
|
};
|
|
|
|
// -- if messages have been added, bucket must exist now
|
|
itb = smsgBuckets.find(bktTime);
|
|
if (itb == smsgBuckets.end())
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Don't have bucket %"PRId64".\n", bktTime);
|
|
return 1;
|
|
};
|
|
|
|
itb->second.nLockCount = 0; // this node has received data from peer, release lock
|
|
itb->second.nLockPeerId = 0;
|
|
itb->second.hashBucket();
|
|
|
|
return 0;
|
|
};
|
|
|
|
int SecureMsgStoreUnscanned(unsigned char *pHeader, unsigned char *pPayload, uint32_t nPayload)
|
|
{
|
|
/*
|
|
When the wallet is locked a copy of each received message is stored to be scanned later if wallet is unlocked
|
|
*/
|
|
|
|
if (fDebugSmsg)
|
|
printf("SecureMsgStoreUnscanned()\n");
|
|
|
|
if (!pHeader
|
|
|| !pPayload)
|
|
{
|
|
printf("Error: null pointer to header or payload.\n");
|
|
return 1;
|
|
};
|
|
|
|
SecureMessage* psmsg = (SecureMessage*) pHeader;
|
|
|
|
fs::path pathSmsgDir;
|
|
try {
|
|
pathSmsgDir = GetDataDir() / "smsgStore";
|
|
fs::create_directory(pathSmsgDir);
|
|
} catch (const std::filesystem::filesystem_error& ex)
|
|
{
|
|
printf("Error: Failed to create directory %s - %s\n", pathSmsgDir.string().c_str(), ex.what());
|
|
return 1;
|
|
};
|
|
|
|
int64_t now = GetTime();
|
|
if (psmsg->timestamp > now + SMSG_TIME_LEEWAY)
|
|
{
|
|
printf("Message > now.\n");
|
|
return 1;
|
|
} else
|
|
if (psmsg->timestamp < now - SMSG_RETENTION)
|
|
{
|
|
printf("Message < SMSG_RETENTION.\n");
|
|
return 1;
|
|
};
|
|
|
|
int64_t bucket = psmsg->timestamp - (psmsg->timestamp % SMSG_BUCKET_LEN);
|
|
fs::path fullpath;
|
|
uint32_t fileIndex;
|
|
SecureMsgSelectBucketFile(pathSmsgDir, bucket, true, nPayload, fullpath, fileIndex);
|
|
|
|
FILE *fp;
|
|
errno = 0;
|
|
if (!(fp = fopen(fullpath.string().c_str(), "ab")))
|
|
{
|
|
printf("Error opening file: %s\n", strerror(errno));
|
|
return 1;
|
|
};
|
|
|
|
errno = 0;
|
|
if (fseek(fp, 0, SEEK_END) != 0)
|
|
{
|
|
printf("Error fseek failed: %s\n", strerror(errno));
|
|
fclose(fp);
|
|
return 1;
|
|
};
|
|
|
|
long int ofs = ftell(fp);
|
|
long int nRecordSize = SMSG_HDR_LEN + nPayload;
|
|
if (ofs > 0
|
|
&& ofs > SMSG_BUCKET_FILE_SIZE_LIMIT - nRecordSize)
|
|
{
|
|
fclose(fp);
|
|
|
|
fileIndex++;
|
|
fullpath = pathSmsgDir / SecureMsgBucketFilename(bucket, fileIndex, true);
|
|
errno = 0;
|
|
if (!(fp = fopen(fullpath.string().c_str(), "ab")))
|
|
{
|
|
printf("Error opening file: %s\n", strerror(errno));
|
|
return 1;
|
|
};
|
|
};
|
|
|
|
if (fwrite(pHeader, sizeof(unsigned char), SMSG_HDR_LEN, fp) != (size_t)SMSG_HDR_LEN
|
|
|| fwrite(pPayload, sizeof(unsigned char), nPayload, fp) != nPayload)
|
|
{
|
|
printf("fwrite failed: %s\n", strerror(errno));
|
|
fclose(fp);
|
|
return 1;
|
|
};
|
|
|
|
fclose(fp);
|
|
|
|
return 0;
|
|
};
|
|
|
|
|
|
int SecureMsgStore(unsigned char *pHeader, unsigned char *pPayload, uint32_t nPayload, bool fUpdateBucket)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("SecureMsgStore()\n");
|
|
|
|
if (!pHeader
|
|
|| !pPayload)
|
|
{
|
|
printf("Error: null pointer to header or payload.\n");
|
|
return 1;
|
|
};
|
|
|
|
SecureMessage* psmsg = (SecureMessage*) pHeader;
|
|
|
|
|
|
long int ofs;
|
|
fs::path pathSmsgDir;
|
|
try {
|
|
pathSmsgDir = GetDataDir() / "smsgStore";
|
|
fs::create_directory(pathSmsgDir);
|
|
} catch (const std::filesystem::filesystem_error& ex)
|
|
{
|
|
printf("Error: Failed to create directory %s - %s\n", pathSmsgDir.string().c_str(), ex.what());
|
|
return 1;
|
|
};
|
|
|
|
int64_t now = GetTime();
|
|
if (psmsg->timestamp > now + SMSG_TIME_LEEWAY)
|
|
{
|
|
printf("Message > now.\n");
|
|
return 1;
|
|
} else
|
|
if (psmsg->timestamp < now - SMSG_RETENTION)
|
|
{
|
|
printf("Message < SMSG_RETENTION.\n");
|
|
return 1;
|
|
};
|
|
|
|
int64_t bucket = psmsg->timestamp - (psmsg->timestamp % SMSG_BUCKET_LEN);
|
|
|
|
{
|
|
// -- must lock cs_smsg before calling
|
|
//LOCK(cs_smsg);
|
|
|
|
SecMsgToken token(psmsg->timestamp, pPayload, nPayload, 0);
|
|
|
|
std::set<SecMsgToken>& tokenSet = smsgBuckets[bucket].setTokens;
|
|
std::set<SecMsgToken>::iterator it;
|
|
it = tokenSet.find(token);
|
|
if (it != tokenSet.end())
|
|
{
|
|
printf("Already have message.\n");
|
|
if (fDebugSmsg)
|
|
{
|
|
printf("nPayload: %u\n", nPayload);
|
|
printf("bucket: %"PRId64"\n", bucket);
|
|
|
|
printf("message ts: %"PRId64, token.timestamp);
|
|
std::vector<unsigned char> vchShow;
|
|
vchShow.resize(8);
|
|
memcpy(&vchShow[0], token.sample, 8);
|
|
printf(" sample %s\n", ValueString(vchShow).c_str());
|
|
/*
|
|
printf("\nmessages in bucket:\n");
|
|
for (it = tokenSet.begin(); it != tokenSet.end(); ++it)
|
|
{
|
|
printf("message ts: %"PRId64, (*it).timestamp);
|
|
vchShow.resize(8);
|
|
memcpy(&vchShow[0], (*it).sample, 8);
|
|
printf(" sample %s\n", ValueString(vchShow).c_str());
|
|
};
|
|
*/
|
|
};
|
|
return 1;
|
|
};
|
|
|
|
fs::path fullpath;
|
|
uint32_t fileIndex;
|
|
SecureMsgSelectBucketFile(pathSmsgDir, bucket, false, nPayload, fullpath, fileIndex);
|
|
|
|
FILE *fp;
|
|
errno = 0;
|
|
if (!(fp = fopen(fullpath.string().c_str(), "ab")))
|
|
{
|
|
printf("Error opening file: %s\n", strerror(errno));
|
|
return 1;
|
|
};
|
|
|
|
// -- on windows ftell will always return 0 after fopen(ab), call fseek to set.
|
|
errno = 0;
|
|
if (fseek(fp, 0, SEEK_END) != 0)
|
|
{
|
|
printf("Error fseek failed: %s\n", strerror(errno));
|
|
fclose(fp);
|
|
return 1;
|
|
};
|
|
|
|
|
|
ofs = ftell(fp);
|
|
long int nRecordSize = SMSG_HDR_LEN + nPayload;
|
|
if (ofs > 0
|
|
&& ofs > SMSG_BUCKET_FILE_SIZE_LIMIT - nRecordSize)
|
|
{
|
|
fclose(fp);
|
|
|
|
fileIndex++;
|
|
fullpath = pathSmsgDir / SecureMsgBucketFilename(bucket, fileIndex, false);
|
|
errno = 0;
|
|
if (!(fp = fopen(fullpath.string().c_str(), "ab")))
|
|
{
|
|
printf("Error opening file: %s\n", strerror(errno));
|
|
return 1;
|
|
};
|
|
|
|
errno = 0;
|
|
if (fseek(fp, 0, SEEK_END) != 0)
|
|
{
|
|
printf("Error fseek failed: %s\n", strerror(errno));
|
|
fclose(fp);
|
|
return 1;
|
|
};
|
|
|
|
ofs = ftell(fp);
|
|
};
|
|
|
|
if (fwrite(pHeader, sizeof(unsigned char), SMSG_HDR_LEN, fp) != (size_t)SMSG_HDR_LEN
|
|
|| fwrite(pPayload, sizeof(unsigned char), nPayload, fp) != nPayload)
|
|
{
|
|
printf("fwrite failed: %s\n", strerror(errno));
|
|
fclose(fp);
|
|
return 1;
|
|
};
|
|
|
|
fclose(fp);
|
|
|
|
token.fileIndex = fileIndex;
|
|
token.offset = ofs;
|
|
|
|
//printf("token.offset: %"PRId64"\n", token.offset); // DEBUG
|
|
tokenSet.insert(token);
|
|
|
|
if (fUpdateBucket)
|
|
smsgBuckets[bucket].hashBucket();
|
|
};
|
|
|
|
//if (fDebugSmsg)
|
|
printf("SecureMsg added to bucket %"PRId64".\n", bucket);
|
|
return 0;
|
|
};
|
|
|
|
int SecureMsgStore(SecureMessage& smsg, bool fUpdateBucket)
|
|
{
|
|
return SecureMsgStore(&smsg.hash[0], smsg.pPayload, smsg.nPayload, fUpdateBucket);
|
|
};
|
|
|
|
int SecureMsgValidate(unsigned char *pHeader, unsigned char *pPayload, uint32_t nPayload)
|
|
{
|
|
/*
|
|
returns
|
|
0 success
|
|
1 error
|
|
2 invalid hash
|
|
3 checksum mismatch
|
|
4 invalid version
|
|
5 payload is too large
|
|
*/
|
|
SecureMessage* psmsg = (SecureMessage*) pHeader;
|
|
|
|
if (psmsg->version[0] != 1)
|
|
return 4;
|
|
|
|
if (nPayload > SMSG_MAX_MSG_WORST)
|
|
return 5;
|
|
|
|
unsigned char civ[32];
|
|
unsigned char sha256Hash[32];
|
|
int rv = 2; // invalid
|
|
|
|
uint32_t nonse;
|
|
memcpy(&nonse, &psmsg->nonse[0], 4);
|
|
|
|
if (fDebugSmsg)
|
|
printf("SecureMsgValidate() nonse %u.\n", nonse);
|
|
|
|
for (int i = 0; i < 32; i+=4)
|
|
memcpy(civ+i, &nonse, 4);
|
|
|
|
HMAC_CTX *ctx = HMAC_CTX_new();
|
|
|
|
unsigned int nBytes;
|
|
if (!HMAC_Init_ex(ctx, &civ[0], 32, EVP_sha256(), NULL)
|
|
|| !HMAC_Update(ctx, (unsigned char*) pHeader+4, SMSG_HDR_LEN-4)
|
|
|| !HMAC_Update(ctx, (unsigned char*) pPayload, nPayload)
|
|
|| !HMAC_Update(ctx, pPayload, nPayload)
|
|
|| !HMAC_Final(ctx, sha256Hash, &nBytes)
|
|
|| nBytes != 32)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("HMAC error.\n");
|
|
rv = 1; // error
|
|
} else
|
|
{
|
|
if (sha256Hash[31] == 0
|
|
&& sha256Hash[30] == 0
|
|
&& (~(sha256Hash[29]) & ((1<<0) || (1<<1) || (1<<2)) ))
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Hash Valid.\n");
|
|
rv = 0; // smsg is valid
|
|
};
|
|
|
|
if (memcmp(psmsg->hash, sha256Hash, 4) != 0)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Checksum mismatch.\n");
|
|
rv = 3; // checksum mismatch
|
|
}
|
|
}
|
|
HMAC_CTX_free(ctx);
|
|
|
|
return rv;
|
|
};
|
|
|
|
int SecureMsgSetHash(unsigned char *pHeader, unsigned char *pPayload, uint32_t nPayload)
|
|
{
|
|
/* proof of work and checksum
|
|
|
|
May run in a thread, if shutdown detected, return.
|
|
|
|
returns:
|
|
0 success
|
|
1 error
|
|
2 stopped due to node shutdown
|
|
|
|
*/
|
|
|
|
SecureMessage* psmsg = (SecureMessage*) pHeader;
|
|
|
|
int64_t nStart = GetTimeMillis();
|
|
unsigned char civ[32];
|
|
unsigned char sha256Hash[32];
|
|
|
|
//std::vector<unsigned char> vchHash;
|
|
//vchHash.resize(32);
|
|
|
|
bool found = false;
|
|
HMAC_CTX *ctx = HMAC_CTX_new();
|
|
|
|
uint32_t nonse = 0;
|
|
|
|
//CBigNum bnTarget(2);
|
|
//bnTarget = bnTarget.pow(256 - 40);
|
|
|
|
// -- break for HMAC_CTX_free
|
|
for (;;)
|
|
{
|
|
if (!fSecMsgEnabled)
|
|
break;
|
|
|
|
//psmsg->timestamp = GetTime();
|
|
//memcpy(&psmsg->timestamp, &now, 8);
|
|
memcpy(&psmsg->nonse[0], &nonse, 4);
|
|
|
|
for (int i = 0; i < 32; i+=4)
|
|
memcpy(civ+i, &nonse, 4);
|
|
|
|
unsigned int nBytes;
|
|
if (!HMAC_Init_ex(ctx, &civ[0], 32, EVP_sha256(), NULL)
|
|
|| !HMAC_Update(ctx, (unsigned char*) pHeader+4, SMSG_HDR_LEN-4)
|
|
|| !HMAC_Update(ctx, (unsigned char*) pPayload, nPayload)
|
|
|| !HMAC_Update(ctx, pPayload, nPayload)
|
|
|| !HMAC_Final(ctx, sha256Hash, &nBytes)
|
|
//|| !HMAC_Final(ctx, &vchHash[0], &nBytes)
|
|
|| nBytes != 32)
|
|
break;
|
|
|
|
/*
|
|
if (CBigNum(vchHash) <= bnTarget)
|
|
{
|
|
found = true;
|
|
if (fDebugSmsg)
|
|
printf("Match %u\n", nonse);
|
|
break;
|
|
};
|
|
*/
|
|
|
|
if (sha256Hash[31] == 0
|
|
&& sha256Hash[30] == 0
|
|
&& (~(sha256Hash[29]) & ((1<<0) || (1<<1) || (1<<2)) ))
|
|
// && sha256Hash[29] == 0)
|
|
{
|
|
found = true;
|
|
//if (fDebugSmsg)
|
|
// printf("Match %u\n", nonse);
|
|
break;
|
|
}
|
|
|
|
//if (nonse >= UINT32_MAX)
|
|
if (nonse >= 4294967295U)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("No match %u\n", nonse);
|
|
break;
|
|
//return 1;
|
|
}
|
|
nonse++;
|
|
};
|
|
|
|
HMAC_CTX_free(ctx);
|
|
|
|
if (!fSecMsgEnabled)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("SecureMsgSetHash() stopped, shutdown detected.\n");
|
|
return 2;
|
|
};
|
|
|
|
if (!found)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("SecureMsgSetHash() failed, took %"PRId64" ms, nonse %u\n", GetTimeMillis() - nStart, nonse);
|
|
return 1;
|
|
};
|
|
|
|
memcpy(psmsg->hash, sha256Hash, 4);
|
|
//memcpy(psmsg->hash, &vchHash[0], 4);
|
|
|
|
if (fDebugSmsg)
|
|
printf("SecureMsgSetHash() took %"PRId64" ms, nonse %u\n", GetTimeMillis() - nStart, nonse);
|
|
|
|
return 0;
|
|
};
|
|
|
|
int SecureMsgEncrypt(SecureMessage& smsg, std::string& addressFrom, std::string& addressTo, std::string& message)
|
|
{
|
|
/* Create a secure message
|
|
|
|
Using similar method to bitmessage.
|
|
If bitmessage is secure this should be too.
|
|
https://bitmessage.org/wiki/Encryption
|
|
|
|
Some differences:
|
|
bitmessage seems to use curve sect283r1
|
|
*coin addresses use secp256k1
|
|
|
|
returns
|
|
2 message is too long.
|
|
3 addressFrom is invalid.
|
|
4 addressTo is invalid.
|
|
5 Could not get public key for addressTo.
|
|
6 ECDH_compute_key failed
|
|
7 Could not get private key for addressFrom.
|
|
8 Could not allocate memory.
|
|
9 Could not compress message data.
|
|
10 Could not generate MAC.
|
|
11 Encrypt failed.
|
|
*/
|
|
|
|
if (fDebugSmsg)
|
|
printf("SecureMsgEncrypt(%s, %s, ...)\n", addressFrom.c_str(), addressTo.c_str());
|
|
|
|
|
|
if (message.size() > SMSG_MAX_MSG_BYTES)
|
|
{
|
|
printf("Message is too long, %"PRIszu".\n", message.size());
|
|
return 2;
|
|
};
|
|
|
|
smsg.version[0] = 1;
|
|
smsg.version[1] = 1;
|
|
smsg.timestamp = GetTime();
|
|
|
|
|
|
bool fSendAnonymous;
|
|
CTrianglesAddress coinAddrFrom;
|
|
CKeyID ckidFrom;
|
|
CKey keyFrom;
|
|
|
|
if (addressFrom.compare("anon") == 0)
|
|
{
|
|
fSendAnonymous = true;
|
|
|
|
} else
|
|
{
|
|
fSendAnonymous = false;
|
|
|
|
if (!coinAddrFrom.SetString(addressFrom))
|
|
{
|
|
printf("addressFrom is not valid.\n");
|
|
return 3;
|
|
};
|
|
|
|
if (!coinAddrFrom.GetKeyID(ckidFrom))
|
|
{
|
|
printf("coinAddrFrom.GetKeyID failed: %s.\n", coinAddrFrom.ToString().c_str());
|
|
return 3;
|
|
};
|
|
};
|
|
|
|
|
|
CTrianglesAddress coinAddrDest;
|
|
CKeyID ckidDest;
|
|
|
|
if (!coinAddrDest.SetString(addressTo))
|
|
{
|
|
printf("addressTo is not valid.\n");
|
|
return 4;
|
|
};
|
|
|
|
if (!coinAddrDest.GetKeyID(ckidDest))
|
|
{
|
|
printf("coinAddrDest.GetKeyID failed: %s.\n", coinAddrDest.ToString().c_str());
|
|
return 4;
|
|
};
|
|
|
|
// -- public key K is the destination address
|
|
CPubKey cpkDestK;
|
|
if (SecureMsgGetStoredKey(ckidDest, cpkDestK) != 0
|
|
&& SecureMsgGetLocalKey(ckidDest, cpkDestK) != 0) // maybe it's a local key (outbox?)
|
|
{
|
|
printf("Could not get public key for destination address.\n");
|
|
return 5;
|
|
};
|
|
|
|
|
|
// -- Generate 16 random bytes as IV.
|
|
RandAddSeedPerfmon();
|
|
RAND_bytes(&smsg.iv[0], 16);
|
|
|
|
|
|
// -- Generate a new random EC key pair with private key called r and public key called R.
|
|
CKey keyR;
|
|
keyR.MakeNewKey(true); // make compressed key
|
|
|
|
|
|
// -- Do an EC point multiply with public key K and private key r. This gives you public key P.
|
|
CKey keyK;
|
|
if (!keyK.SetPubKey(cpkDestK))
|
|
{
|
|
printf("Could not set pubkey for K: %s.\n", ValueString(cpkDestK.Raw()).c_str());
|
|
return 4; // address to is invalid
|
|
};
|
|
|
|
std::vector<unsigned char> vchP;
|
|
vchP.resize(32);
|
|
EC_KEY* pkeyr = keyR.GetECKey();
|
|
EC_KEY* pkeyK = keyK.GetECKey();
|
|
|
|
// always seems to be 32, worth checking?
|
|
//int field_size = EC_GROUP_get_degree(EC_KEY_get0_group(pkeyr));
|
|
//int secret_len = (field_size+7)/8;
|
|
//printf("secret_len %d.\n", secret_len);
|
|
|
|
// -- ECDH_compute_key returns the same P if fed compressed or uncompressed public keys
|
|
int lenP = ECDH_compute_key(&vchP[0], 32, EC_KEY_get0_public_key(pkeyK), pkeyr, NULL);
|
|
|
|
if (lenP != 32)
|
|
{
|
|
printf("ECDH_compute_key failed, lenP: %d.\n", lenP);
|
|
return 6;
|
|
};
|
|
|
|
CPubKey cpkR = keyR.GetPubKey();
|
|
if (!cpkR.IsValid()
|
|
|| !cpkR.IsCompressed())
|
|
{
|
|
printf("Could not get public key for key R.\n");
|
|
return 1;
|
|
};
|
|
|
|
memcpy(smsg.cpkR, &cpkR.Raw()[0], 33);
|
|
|
|
|
|
// -- 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<unsigned char> vchHashed;
|
|
vchHashed.resize(64); // 512
|
|
SHA512(&vchP[0], vchP.size(), (unsigned char*)&vchHashed[0]);
|
|
std::vector<unsigned char> key_e(&vchHashed[0], &vchHashed[0]+32);
|
|
std::vector<unsigned char> key_m(&vchHashed[32], &vchHashed[32]+32);
|
|
|
|
|
|
std::vector<unsigned char> vchPayload;
|
|
std::vector<unsigned char> vchCompressed;
|
|
unsigned char* pMsgData;
|
|
uint32_t lenMsgData;
|
|
|
|
uint32_t lenMsg = message.size();
|
|
if (lenMsg > 128)
|
|
{
|
|
// -- only compress if over 128 bytes
|
|
int worstCase = LZ4_compressBound(message.size());
|
|
try {
|
|
vchCompressed.resize(worstCase);
|
|
} catch (std::exception& e) {
|
|
printf("vchCompressed.resize %u threw: %s.\n", worstCase, e.what());
|
|
return 8;
|
|
};
|
|
|
|
int lenComp = LZ4_compress_default((char*)message.c_str(), (char*)&vchCompressed[0], lenMsg, worstCase);
|
|
if (lenComp < 1)
|
|
{
|
|
printf("Could not compress message data.\n");
|
|
return 9;
|
|
};
|
|
|
|
pMsgData = &vchCompressed[0];
|
|
lenMsgData = lenComp;
|
|
|
|
} else
|
|
{
|
|
// -- no compression
|
|
pMsgData = (unsigned char*)message.c_str();
|
|
lenMsgData = lenMsg;
|
|
};
|
|
|
|
if (fSendAnonymous)
|
|
{
|
|
try {
|
|
vchPayload.resize(9 + lenMsgData);
|
|
} catch (std::exception& e) {
|
|
printf("vchPayload.resize %u threw: %s.\n", 9 + lenMsgData, e.what());
|
|
return 8;
|
|
};
|
|
|
|
memcpy(&vchPayload[9], pMsgData, lenMsgData);
|
|
|
|
vchPayload[0] = 250; // id as anonymous message
|
|
// -- next 4 bytes are unused - there to ensure encrypted payload always > 8 bytes
|
|
memcpy(&vchPayload[5], &lenMsg, 4); // length of uncompressed plain text
|
|
} else
|
|
{
|
|
try {
|
|
vchPayload.resize(SMSG_PL_HDR_LEN + lenMsgData);
|
|
} catch (std::exception& e) {
|
|
printf("vchPayload.resize %u threw: %s.\n", SMSG_PL_HDR_LEN + lenMsgData, e.what());
|
|
return 8;
|
|
};
|
|
memcpy(&vchPayload[SMSG_PL_HDR_LEN], pMsgData, lenMsgData);
|
|
// -- compact signature proves ownership of from address and allows the public key to be recovered, recipient can always reply.
|
|
if (!pwalletMain->GetKey(ckidFrom, keyFrom))
|
|
{
|
|
printf("Could not get private key for addressFrom.\n");
|
|
return 7;
|
|
};
|
|
|
|
// -- sign the plaintext
|
|
std::vector<unsigned char> vchSignature;
|
|
vchSignature.resize(65);
|
|
keyFrom.SignCompact(Hash(message.begin(), message.end()), vchSignature);
|
|
|
|
// -- Save some bytes by sending address raw
|
|
vchPayload[0] = (static_cast<CTrianglesAddress_B*>(&coinAddrFrom))->getVersion(); // vchPayload[0] = coinAddrDest.nVersion;
|
|
memcpy(&vchPayload[1], (static_cast<CKeyID_B*>(&ckidFrom))->GetPPN(), 20); // memcpy(&vchPayload[1], ckidDest.pn, 20);
|
|
|
|
memcpy(&vchPayload[1+20], &vchSignature[0], vchSignature.size());
|
|
memcpy(&vchPayload[1+20+65], &lenMsg, 4); // length of uncompressed plain text
|
|
};
|
|
|
|
|
|
SecMsgCrypter crypter;
|
|
crypter.SetKey(key_e, smsg.iv);
|
|
std::vector<unsigned char> vchCiphertext;
|
|
|
|
if (!crypter.Encrypt(&vchPayload[0], vchPayload.size(), vchCiphertext))
|
|
{
|
|
printf("crypter.Encrypt failed.\n");
|
|
return 11;
|
|
};
|
|
|
|
try {
|
|
smsg.pPayload = new unsigned char[vchCiphertext.size()];
|
|
} catch (std::exception& e)
|
|
{
|
|
printf("Could not allocate pPayload, exception: %s.\n", e.what());
|
|
return 8;
|
|
};
|
|
|
|
memcpy(smsg.pPayload, &vchCiphertext[0], vchCiphertext.size());
|
|
smsg.nPayload = vchCiphertext.size();
|
|
|
|
|
|
// -- Calculate a 32 byte MAC with HMACSHA256, using key_m as salt
|
|
// Message authentication code, (hash of timestamp + destination + payload)
|
|
bool fHmacOk = true;
|
|
unsigned int nBytes = 32;
|
|
HMAC_CTX *ctx = HMAC_CTX_new();
|
|
|
|
if (!HMAC_Init_ex(ctx, &key_m[0], 32, EVP_sha256(), NULL)
|
|
|| !HMAC_Update(ctx, (unsigned char*) &smsg.timestamp, sizeof(smsg.timestamp))
|
|
|| !HMAC_Update(ctx, &vchCiphertext[0], vchCiphertext.size())
|
|
|| !HMAC_Final(ctx, smsg.mac, &nBytes)
|
|
|| nBytes != 32)
|
|
fHmacOk = false;
|
|
|
|
HMAC_CTX_free(ctx);
|
|
|
|
if (!fHmacOk)
|
|
{
|
|
printf("Could not generate MAC.\n");
|
|
return 10;
|
|
};
|
|
|
|
|
|
return 0;
|
|
};
|
|
|
|
int SecureMsgSend(std::string& addressFrom, std::string& addressTo, std::string& message, std::string& sError)
|
|
{
|
|
/* Encrypt secure message, and place it on the network
|
|
Make a copy of the message to sender's first address and place in send queue db
|
|
proof of work thread will pick up messages from send queue db
|
|
|
|
*/
|
|
|
|
if (fDebugSmsg)
|
|
printf("SecureMsgSend(%s, %s, ...)\n", addressFrom.c_str(), addressTo.c_str());
|
|
|
|
if (pwalletMain->IsLocked())
|
|
{
|
|
sError = "Wallet is locked, wallet must be unlocked to send and recieve messages.";
|
|
printf("Wallet is locked, wallet must be unlocked to send and recieve messages.\n");
|
|
return 1;
|
|
};
|
|
|
|
if (message.size() > SMSG_MAX_MSG_BYTES)
|
|
{
|
|
std::ostringstream oss;
|
|
oss << message.size() << " > " << SMSG_MAX_MSG_BYTES;
|
|
sError = "Message is too long, " + oss.str();
|
|
printf("Message is too long, %"PRIszu".\n", message.size());
|
|
return 1;
|
|
};
|
|
|
|
|
|
int rv;
|
|
SecureMessage smsg;
|
|
|
|
if ((rv = SecureMsgEncrypt(smsg, addressFrom, addressTo, message)) != 0)
|
|
{
|
|
printf("SecureMsgSend(), encrypt for recipient failed.\n");
|
|
|
|
switch(rv)
|
|
{
|
|
case 2: sError = "Message is too long."; break;
|
|
case 3: sError = "Invalid addressFrom."; break;
|
|
case 4: sError = "Invalid addressTo."; break;
|
|
case 5: sError = "Could not get public key for addressTo."; break;
|
|
case 6: sError = "ECDH_compute_key failed."; break;
|
|
case 7: sError = "Could not get private key for addressFrom."; break;
|
|
case 8: sError = "Could not allocate memory."; break;
|
|
case 9: sError = "Could not compress message data."; break;
|
|
case 10: sError = "Could not generate MAC."; break;
|
|
case 11: sError = "Encrypt failed."; break;
|
|
default: sError = "Unspecified Error."; break;
|
|
};
|
|
|
|
return rv;
|
|
};
|
|
|
|
|
|
// -- Place message in send queue, proof of work will happen in a thread.
|
|
std::string sPrefix("qm");
|
|
unsigned char chKey[18];
|
|
memcpy(&chKey[0], sPrefix.data(), 2);
|
|
memcpy(&chKey[2], &smsg.timestamp, 8);
|
|
memcpy(&chKey[10], &smsg.pPayload, 8);
|
|
|
|
SecMsgStored smsgSQ;
|
|
|
|
smsgSQ.timeReceived = GetTime();
|
|
smsgSQ.sAddrTo = addressTo;
|
|
|
|
try {
|
|
smsgSQ.vchMessage.resize(SMSG_HDR_LEN + smsg.nPayload);
|
|
} catch (std::exception& e) {
|
|
printf("smsgSQ.vchMessage.resize %u threw: %s.\n", SMSG_HDR_LEN + smsg.nPayload, e.what());
|
|
sError = "Could not allocate memory.";
|
|
return 8;
|
|
};
|
|
|
|
memcpy(&smsgSQ.vchMessage[0], &smsg.hash[0], SMSG_HDR_LEN);
|
|
memcpy(&smsgSQ.vchMessage[SMSG_HDR_LEN], smsg.pPayload, smsg.nPayload);
|
|
|
|
{
|
|
LOCK(cs_smsgDB);
|
|
SecMsgDB dbSendQueue;
|
|
if (dbSendQueue.Open("cw"))
|
|
{
|
|
dbSendQueue.WriteSmesg(chKey, smsgSQ);
|
|
//NotifySecMsgSendQueueChanged(smsgOutbox);
|
|
};
|
|
}
|
|
|
|
// TODO: only update outbox when proof of work thread is done.
|
|
|
|
// -- for outbox create a copy encrypted for owned address
|
|
// if the wallet is encrypted private key needed to decrypt will be unavailable
|
|
|
|
if (fDebugSmsg)
|
|
printf("Encrypting message for outbox.\n");
|
|
|
|
std::string addressOutbox = "None";
|
|
CTrianglesAddress coinAddrOutbox;
|
|
|
|
for (const auto& entry : pwalletMain->mapAddressBook)
|
|
{
|
|
// -- get first owned address
|
|
if (!IsMine(*pwalletMain, entry.first))
|
|
continue;
|
|
|
|
const CTrianglesAddress& address = entry.first;
|
|
|
|
addressOutbox = address.ToString();
|
|
if (!coinAddrOutbox.SetString(addressOutbox)) // test valid
|
|
continue;
|
|
break;
|
|
};
|
|
|
|
if (addressOutbox == "None")
|
|
{
|
|
printf("Warning: SecureMsgSend() could not find an address to encrypt outbox message with.\n");
|
|
} else
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("Encrypting a copy for outbox, using address %s\n", addressOutbox.c_str());
|
|
|
|
SecureMessage smsgForOutbox;
|
|
if ((rv = SecureMsgEncrypt(smsgForOutbox, addressFrom, addressOutbox, message)) != 0)
|
|
{
|
|
printf("SecureMsgSend(), encrypt for outbox failed, %d.\n", rv);
|
|
} else
|
|
{
|
|
// -- save sent message to db
|
|
std::string sPrefix("sm");
|
|
unsigned char chKey[18];
|
|
memcpy(&chKey[0], sPrefix.data(), 2);
|
|
memcpy(&chKey[2], &smsgForOutbox.timestamp, 8);
|
|
memcpy(&chKey[10], &smsgForOutbox.pPayload, 8); // sample
|
|
|
|
SecMsgStored smsgOutbox;
|
|
|
|
smsgOutbox.timeReceived = GetTime();
|
|
smsgOutbox.sAddrTo = addressTo;
|
|
smsgOutbox.sAddrOutbox = addressOutbox;
|
|
|
|
try {
|
|
smsgOutbox.vchMessage.resize(SMSG_HDR_LEN + smsgForOutbox.nPayload);
|
|
} catch (std::exception& e) {
|
|
printf("smsgOutbox.vchMessage.resize %u threw: %s.\n", SMSG_HDR_LEN + smsgForOutbox.nPayload, e.what());
|
|
sError = "Could not allocate memory.";
|
|
return 8;
|
|
};
|
|
memcpy(&smsgOutbox.vchMessage[0], &smsgForOutbox.hash[0], SMSG_HDR_LEN);
|
|
memcpy(&smsgOutbox.vchMessage[SMSG_HDR_LEN], smsgForOutbox.pPayload, smsgForOutbox.nPayload);
|
|
|
|
|
|
{
|
|
LOCK(cs_smsgDB);
|
|
SecMsgDB dbSent;
|
|
|
|
if (dbSent.Open("cw"))
|
|
{
|
|
dbSent.WriteSmesg(chKey, smsgOutbox);
|
|
NotifySecMsgOutboxChanged(smsgOutbox);
|
|
};
|
|
}
|
|
};
|
|
};
|
|
|
|
if (fDebugSmsg)
|
|
printf("Secure message queued for sending to %s.\n", addressTo.c_str());
|
|
|
|
return 0;
|
|
};
|
|
|
|
|
|
int SecureMsgDecrypt(bool fTestOnly, std::string& address, unsigned char *pHeader, unsigned char *pPayload, uint32_t nPayload, MessageData& msg)
|
|
{
|
|
/* Decrypt secure message
|
|
|
|
address is the owned address to decrypt with.
|
|
|
|
validate first in SecureMsgValidate
|
|
|
|
returns
|
|
1 Error
|
|
2 Unknown version number
|
|
3 Decrypt address is not valid.
|
|
8 Could not allocate memory
|
|
*/
|
|
|
|
if (fDebugSmsg)
|
|
printf("SecureMsgDecrypt(), using %s, testonly %d.\n", address.c_str(), fTestOnly);
|
|
|
|
if (!pHeader
|
|
|| !pPayload)
|
|
{
|
|
printf("Error: null pointer to header or payload.\n");
|
|
return 1;
|
|
};
|
|
|
|
SecureMessage* psmsg = (SecureMessage*) pHeader;
|
|
|
|
|
|
if (psmsg->version[0] != 1)
|
|
{
|
|
printf("Unknown version number.\n");
|
|
return 2;
|
|
};
|
|
|
|
|
|
|
|
// -- Fetch private key k, used to decrypt
|
|
CTrianglesAddress coinAddrDest;
|
|
CKeyID ckidDest;
|
|
CKey keyDest;
|
|
if (!coinAddrDest.SetString(address))
|
|
{
|
|
printf("Address is not valid.\n");
|
|
return 3;
|
|
};
|
|
if (!coinAddrDest.GetKeyID(ckidDest))
|
|
{
|
|
printf("coinAddrDest.GetKeyID failed: %s.\n", coinAddrDest.ToString().c_str());
|
|
return 3;
|
|
};
|
|
if (!pwalletMain->GetKey(ckidDest, keyDest))
|
|
{
|
|
printf("Could not get private key for addressDest.\n");
|
|
return 3;
|
|
};
|
|
|
|
|
|
|
|
CKey keyR;
|
|
std::vector<unsigned char> vchR(psmsg->cpkR, psmsg->cpkR+33); // would be neater to override CPubKey() instead
|
|
CPubKey cpkR(vchR);
|
|
if (!cpkR.IsValid())
|
|
{
|
|
printf("Could not get public key for key R.\n");
|
|
return 1;
|
|
};
|
|
if (!keyR.SetPubKey(cpkR))
|
|
{
|
|
printf("Could not set pubkey for R: %s.\n", ValueString(cpkR.Raw()).c_str());
|
|
return 1;
|
|
};
|
|
|
|
cpkR = keyR.GetPubKey();
|
|
if (!cpkR.IsValid()
|
|
|| !cpkR.IsCompressed())
|
|
{
|
|
printf("Could not get compressed public key for key R.\n");
|
|
return 1;
|
|
};
|
|
|
|
|
|
// -- Do an EC point multiply with private key k and public key R. This gives you public key P.
|
|
std::vector<unsigned char> vchP;
|
|
vchP.resize(32);
|
|
EC_KEY* pkeyk = keyDest.GetECKey();
|
|
EC_KEY* pkeyR = keyR.GetECKey();
|
|
|
|
int lenPdec = ECDH_compute_key(&vchP[0], 32, EC_KEY_get0_public_key(pkeyR), pkeyk, NULL);
|
|
|
|
if (lenPdec != 32)
|
|
{
|
|
printf("ECDH_compute_key failed, lenPdec: %d.\n", lenPdec);
|
|
return 1;
|
|
};
|
|
|
|
|
|
// -- 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<unsigned char> vchHashedDec;
|
|
vchHashedDec.resize(64); // 512 bits
|
|
SHA512(&vchP[0], vchP.size(), (unsigned char*)&vchHashedDec[0]);
|
|
std::vector<unsigned char> key_e(&vchHashedDec[0], &vchHashedDec[0]+32);
|
|
std::vector<unsigned char> key_m(&vchHashedDec[32], &vchHashedDec[32]+32);
|
|
|
|
|
|
// -- Message authentication code, (hash of timestamp + destination + payload)
|
|
unsigned char MAC[32];
|
|
bool fHmacOk = true;
|
|
unsigned int nBytes = 32;
|
|
HMAC_CTX *ctx = HMAC_CTX_new();
|
|
|
|
if (!HMAC_Init_ex(ctx, &key_m[0], 32, EVP_sha256(), NULL)
|
|
|| !HMAC_Update(ctx, (unsigned char*) &psmsg->timestamp, sizeof(psmsg->timestamp))
|
|
|| !HMAC_Update(ctx, pPayload, nPayload)
|
|
|| !HMAC_Final(ctx, MAC, &nBytes)
|
|
|| nBytes != 32)
|
|
fHmacOk = false;
|
|
|
|
HMAC_CTX_free(ctx);
|
|
|
|
if (!fHmacOk)
|
|
{
|
|
printf("Could not generate MAC.\n");
|
|
return 1;
|
|
};
|
|
|
|
if (memcmp(MAC, psmsg->mac, 32) != 0)
|
|
{
|
|
if (fDebugSmsg)
|
|
printf("MAC does not match.\n"); // expected if message is not to address on node
|
|
|
|
return 1;
|
|
};
|
|
|
|
if (fTestOnly)
|
|
return 0;
|
|
|
|
SecMsgCrypter crypter;
|
|
crypter.SetKey(key_e, psmsg->iv);
|
|
std::vector<unsigned char> vchPayload;
|
|
if (!crypter.Decrypt(pPayload, nPayload, vchPayload))
|
|
{
|
|
printf("Decrypt failed.\n");
|
|
return 1;
|
|
};
|
|
|
|
msg.timestamp = psmsg->timestamp;
|
|
uint32_t lenData;
|
|
uint32_t lenPlain;
|
|
|
|
unsigned char* pMsgData;
|
|
bool fFromAnonymous;
|
|
if ((uint32_t)vchPayload[0] == 250)
|
|
{
|
|
fFromAnonymous = true;
|
|
lenData = vchPayload.size() - (9);
|
|
memcpy(&lenPlain, &vchPayload[5], 4);
|
|
pMsgData = &vchPayload[9];
|
|
} else
|
|
{
|
|
fFromAnonymous = false;
|
|
lenData = vchPayload.size() - (SMSG_PL_HDR_LEN);
|
|
memcpy(&lenPlain, &vchPayload[1+20+65], 4);
|
|
pMsgData = &vchPayload[SMSG_PL_HDR_LEN];
|
|
};
|
|
|
|
try {
|
|
msg.vchMessage.resize(lenPlain + 1);
|
|
} catch (std::exception& e) {
|
|
printf("msg.vchMessage.resize %u threw: %s.\n", lenPlain + 1, e.what());
|
|
return 8;
|
|
};
|
|
|
|
|
|
if (lenPlain > 128)
|
|
{
|
|
// -- decompress
|
|
if (LZ4_decompress_safe((char*) pMsgData, (char*) &msg.vchMessage[0], lenData, lenPlain) != (int) lenPlain)
|
|
{
|
|
printf("Could not decompress message data.\n");
|
|
return 1;
|
|
};
|
|
} else
|
|
{
|
|
// -- plaintext
|
|
memcpy(&msg.vchMessage[0], pMsgData, lenPlain);
|
|
};
|
|
|
|
msg.vchMessage[lenPlain] = '\0';
|
|
|
|
if (fFromAnonymous)
|
|
{
|
|
// -- Anonymous sender
|
|
msg.sFromAddress = "anon";
|
|
} else
|
|
{
|
|
std::vector<unsigned char> vchUint160;
|
|
vchUint160.resize(20);
|
|
|
|
memcpy(&vchUint160[0], &vchPayload[1], 20);
|
|
|
|
uint160 ui160(vchUint160);
|
|
CKeyID ckidFrom(ui160);
|
|
|
|
CTrianglesAddress coinAddrFrom;
|
|
coinAddrFrom.Set(ckidFrom);
|
|
if (!coinAddrFrom.IsValid())
|
|
{
|
|
printf("From Addess is invalid.\n");
|
|
return 1;
|
|
};
|
|
|
|
std::vector<unsigned char> vchSig;
|
|
vchSig.resize(65);
|
|
|
|
memcpy(&vchSig[0], &vchPayload[1+20], 65);
|
|
|
|
CKey keyFrom;
|
|
keyFrom.SetCompactSignature(Hash(msg.vchMessage.begin(), msg.vchMessage.end()-1), vchSig);
|
|
CPubKey cpkFromSig = keyFrom.GetPubKey();
|
|
if (!cpkFromSig.IsValid())
|
|
{
|
|
printf("Signature validation failed.\n");
|
|
return 1;
|
|
};
|
|
|
|
// -- get address for the compressed public key
|
|
CTrianglesAddress coinAddrFromSig;
|
|
coinAddrFromSig.Set(cpkFromSig.GetID());
|
|
|
|
if (!(coinAddrFrom == coinAddrFromSig))
|
|
{
|
|
printf("Signature validation failed.\n");
|
|
return 1;
|
|
};
|
|
|
|
cpkFromSig = keyFrom.GetPubKey();
|
|
|
|
int rv = 5;
|
|
try {
|
|
rv = SecureMsgInsertAddress(ckidFrom, cpkFromSig);
|
|
} catch (std::exception& e) {
|
|
printf("SecureMsgInsertAddress(), exception: %s.\n", e.what());
|
|
//return 1;
|
|
};
|
|
|
|
switch(rv)
|
|
{
|
|
case 0:
|
|
printf("Sender public key added to db.\n");
|
|
break;
|
|
case 4:
|
|
printf("Sender public key already in db.\n");
|
|
break;
|
|
default:
|
|
printf("Error adding sender public key to db.\n");
|
|
break;
|
|
};
|
|
|
|
msg.sFromAddress = coinAddrFrom.ToString();
|
|
};
|
|
|
|
if (fDebugSmsg)
|
|
printf("Decrypted message for %s.\n", address.c_str());
|
|
|
|
return 0;
|
|
};
|
|
|
|
int SecureMsgDecrypt(bool fTestOnly, std::string& address, SecureMessage& smsg, MessageData& msg)
|
|
{
|
|
return SecureMsgDecrypt(fTestOnly, address, &smsg.hash[0], smsg.pPayload, smsg.nPayload, msg);
|
|
};
|
|
|