/* * test_crypto.cpp — тесты криптографических функций (Crypto::) */ #include #include #include #include #include #include #include #include "../transport/crypto.h" class TestCrypto : public QObject { Q_OBJECT private slots: void initTestCase(); void test_derive_key_sizes(); void test_derive_priv_not_empty(); void test_derive_pub_not_empty(); void test_priv_ne_pub(); void test_sign_verify_roundtrip(); void test_sign_verify_empty(); void test_verify_wrong_sig(); void test_verify_wrong_key(); void test_sign_empty_key(); void test_sign_invalid_key_size(); void test_signFile_verifyFile(); void test_signFile_nonexistent(); void test_sign_verify_100mb_parallel(); private: void writeTestFile(const QString& path, unsigned char pattern, size_t fileSize); private: QByteArray m_x25519Priv; // 32 random bytes QByteArray m_edPriv; // derived Ed25519 private key QByteArray m_edPub; // derived Ed25519 public key QByteArray m_message; }; void TestCrypto::initTestCase() { m_x25519Priv = QByteArray(Crypto::kKeySize, 0); for (int i = 0; i < static_cast(Crypto::kKeySize); i++) m_x25519Priv[i] = static_cast(0x42 + i); /* детерминированное значение */ m_edPriv = Crypto::deriveEd25519Privkey(m_x25519Priv); m_edPub = Crypto::deriveEd25519Pubkey(m_x25519Priv); m_message = QByteArray("hello world"); } void TestCrypto::test_derive_key_sizes() { QCOMPARE(m_edPriv.size(), static_cast(Crypto::kKeySize)); QCOMPARE(m_edPub.size(), static_cast(Crypto::kKeySize)); } void TestCrypto::test_derive_priv_not_empty() { QVERIFY(!m_edPriv.isEmpty()); } void TestCrypto::test_derive_pub_not_empty() { QVERIFY(!m_edPub.isEmpty()); } void TestCrypto::test_priv_ne_pub() { QVERIFY(m_edPriv != m_edPub); } void TestCrypto::test_sign_verify_roundtrip() { QByteArray sig = Crypto::sign(m_edPriv, m_message); QCOMPARE(sig.size(), static_cast(Crypto::kSignSize)); QVERIFY(Crypto::verify(m_edPub, m_message, sig)); } void TestCrypto::test_sign_verify_empty() { QByteArray empty; QByteArray sig = Crypto::sign(m_edPriv, empty); QCOMPARE(sig.size(), static_cast(Crypto::kSignSize)); QVERIFY(Crypto::verify(m_edPub, empty, sig)); } void TestCrypto::test_verify_wrong_sig() { QByteArray sig = Crypto::sign(m_edPriv, m_message); QVERIFY(!sig.isEmpty()); sig[8] = static_cast(sig[8] ^ 1); /* портим подпись */ QVERIFY(!Crypto::verify(m_edPub, m_message, sig)); } void TestCrypto::test_verify_wrong_key() { QByteArray sig = Crypto::sign(m_edPriv, m_message); /* другой ключ */ QByteArray otherPriv(Crypto::kKeySize, 0); for (int i = 0; i < static_cast(Crypto::kKeySize); i++) otherPriv[i] = static_cast(0x77 + i); QByteArray otherPub = Crypto::deriveEd25519Pubkey(otherPriv); QVERIFY(!otherPub.isEmpty()); QVERIFY(!Crypto::verify(otherPub, m_message, sig)); } void TestCrypto::test_sign_empty_key() { QByteArray sig = Crypto::sign(QByteArray(), m_message); QVERIFY(sig.isEmpty()); QVERIFY(!Crypto::verify(QByteArray(), m_message, sig)); } void TestCrypto::test_sign_invalid_key_size() { QByteArray badKey(16, 0x42); QByteArray sig = Crypto::sign(badKey, m_message); QVERIFY(sig.isEmpty()); } void TestCrypto::test_signFile_verifyFile() { QTemporaryFile tmp; QVERIFY(tmp.open()); QByteArray content("file content for signing test"); tmp.write(content); tmp.close(); QByteArray sig; QEventLoop loop; Crypto::signFile(tmp.fileName(), m_edPriv, [&](const Crypto::SignResult& r) { sig = r.signature; QVERIFY(r.ok); QVERIFY(!sig.isEmpty()); loop.quit(); }); loop.exec(); QEventLoop loop2; Crypto::verifyFile(tmp.fileName(), m_edPub, sig, [&](const Crypto::SignResult& r) { QVERIFY(r.ok); loop2.quit(); }); loop2.exec(); } void TestCrypto::test_signFile_nonexistent() { QEventLoop loop; Crypto::signFile("/nonexistent/path/test", m_edPriv, [&](const Crypto::SignResult& r) { QVERIFY(!r.ok); QVERIFY(!r.error.isEmpty()); loop.quit(); }); loop.exec(); } void TestCrypto::writeTestFile(const QString& path, unsigned char pattern, size_t fileSize) { QFile f(path); QVERIFY(f.open(QIODevice::WriteOnly)); constexpr size_t kChunkSize = 65536; QByteArray chunk(static_cast(kChunkSize), Qt::Uninitialized); memset(chunk.data(), pattern, kChunkSize); size_t remaining = fileSize; while (remaining >= kChunkSize) { QCOMPARE(f.write(chunk), static_cast(kChunkSize)); remaining -= kChunkSize; } if (remaining > 0) f.write(chunk.constData(), static_cast(remaining)); f.close(); } void TestCrypto::test_sign_verify_100mb_parallel() { constexpr size_t kFileSize = 100ULL * 1024 * 1024; constexpr int kNumFiles = 3; QString paths[kNumFiles]; QTemporaryFile tmp[kNumFiles]; for (int i = 0; i < kNumFiles; i++) { QVERIFY(tmp[i].open()); paths[i] = tmp[i].fileName(); tmp[i].close(); writeTestFile(paths[i], static_cast(0xAA + i), kFileSize); } /* ------- Sign (параллельно) ------- */ QByteArray sigs[kNumFiles]; qint64 signTimesMs[kNumFiles] = {}; QElapsedTimer totalSignTimer; QAtomicInt signDone{0}; QEventLoop signLoop; totalSignTimer.start(); for (int i = 0; i < kNumFiles; i++) { QElapsedTimer t; t.start(); Crypto::signFile(paths[i], m_edPriv, [&, i, t](const Crypto::SignResult& r) mutable { QVERIFY(r.ok); QVERIFY(!r.signature.isEmpty()); sigs[i] = r.signature; signTimesMs[i] = t.elapsed(); if (signDone.fetchAndAddRelaxed(1) == kNumFiles - 1) signLoop.quit(); }); } signLoop.exec(); qint64 totalSignMs = totalSignTimer.elapsed(); /* ------- Verify (параллельно) ------- */ qint64 verifyTimesMs[kNumFiles] = {}; QElapsedTimer totalVerifyTimer; QAtomicInt verifyDone{0}; QEventLoop verifyLoop; totalVerifyTimer.start(); for (int i = 0; i < kNumFiles; i++) { QElapsedTimer t; t.start(); Crypto::verifyFile(paths[i], m_edPub, sigs[i], [&, i, t](const Crypto::SignResult& r) mutable { QVERIFY(r.ok); verifyTimesMs[i] = t.elapsed(); if (verifyDone.fetchAndAddRelaxed(1) == kNumFiles - 1) verifyLoop.quit(); }); } verifyLoop.exec(); qint64 totalVerifyMs = totalVerifyTimer.elapsed(); /* ------- Профилирование ------- */ qint64 signSum = 0, verifySum = 0; for (int i = 0; i < kNumFiles; i++) { signSum += signTimesMs[i]; verifySum += verifyTimesMs[i]; } qDebug() << "\n======= test_sign_verify_100mb_parallel ======="; for (int i = 0; i < kNumFiles; i++) qDebug().nospace() << " File " << i << ": sign=" << signTimesMs[i] << "ms | verify=" << verifyTimesMs[i] << "ms"; qDebug().nospace() << "Phase sign: " << totalSignMs << "ms total" << " (sum=" << signSum << "ms, speedup=" << (double)signSum / totalSignMs << "x)"; qDebug().nospace() << "Phase verify: " << totalVerifyMs << "ms total" << " (sum=" << verifySum << "ms, speedup=" << (double)verifySum / totalVerifyMs << "x)"; /* Вычищаем временные файлы */ for (int i = 0; i < kNumFiles; i++) QFile::remove(paths[i]); } QTEST_MAIN(TestCrypto) #include "test_crypto.moc"