security/test_node_identity.cpp¶
Unit tests for NodeIdentity — key generation, sign/verify, encrypted save/load. More...
Functions¶
| Name | |
|---|---|
| TEST(NodeIdentity , DeterministicSignature ) | |
| TEST(NodeIdentity , DifferentMessagesDifferentSignatures ) | |
| TEST(NodeIdentity , SignAndVerifyRoundtrip ) | |
| TEST(NodeIdentity , SaveEncryptedLoadEncryptedRoundtrip ) | |
| TEST(NodeIdentity , LoadEncryptedWrongPassphrase ) | |
| TEST(NodeIdentity , LoadEncryptedTamperedFile ) | |
| TEST(NodeIdentity , SaveEncryptedWithoutKey ) | |
| TEST(NodeIdentity , LoadEncryptedNonexistentFile ) | |
| TEST(NodeIdentity , SaveEncryptedOverwrite ) | |
| TEST(NodeIdentity , PeerId_ConsistentForSameKey ) | |
| TEST(NodeIdentity , LoadEncrypted_TruncatedFile_ReturnsError ) | |
| TEST(NodeIdentity , PeerId_WithoutKey_ReturnsEmpty ) | |
| TEST(NodeIdentity , LoadFromFile_EmptyPath_ReturnsError ) | |
| TEST(NodeIdentity , SaveToFile_WithoutKey_ReturnsError ) | |
| TEST(NodeIdentity , GetPrivateKey_AfterGenerate_Returns32Bytes ) | |
| TEST(NodeIdentity , GetPrivateKey_AfterLoadFromFile_PreservesKey ) | |
| TEST(NodeIdentity , GetPrivateKey_AfterLoadEncrypted_PreservesKey ) | |
| TEST(NodeIdentity , PrivateKeyHexEncoding_Is66CharsWith0xPrefix ) |
Detailed Description¶
Unit tests for NodeIdentity — key generation, sign/verify, encrypted save/load.
Date: 2026-05-28 GSD Executor
Functions Documentation¶
function TEST¶
function TEST¶
function TEST¶
function TEST¶
function TEST¶
function TEST¶
function TEST¶
function TEST¶
function TEST¶
function TEST¶
function TEST¶
function TEST¶
function TEST¶
function TEST¶
function TEST¶
function TEST¶
function TEST¶
function TEST¶
Source code¶
#include "security/node_identity.hpp"
#include <gtest/gtest.h>
#include <cstdio>
#include <fstream>
#include <iomanip>
#include <sstream>
#include <vector>
using namespace sgns::neoswarm;
using namespace sgns::neoswarm::security;
namespace
{
const std::string kTestKeyPath = "/tmp/gnus_test_node.key";
const std::string kTestPass = "test123";
const std::string kWrongPass = "wrong456";
void RemoveTestFile()
{
std::remove( kTestKeyPath.c_str() );
}
} // namespace
// =======================================================================
// Key Generation & Identity
// =======================================================================
TEST( NodeIdentity, DeterministicSignature )
{
NodeIdentity ident;
ASSERT_TRUE( ident.Generate().has_value() );
ASSERT_TRUE( ident.IsLoaded() );
std::vector<uint8_t> msg1 = { 0x01, 0x02, 0x03, 0x04 };
std::vector<uint8_t> msg2 = { 0x01, 0x02, 0x03, 0x04 };
auto sig1 = ident.Sign( msg1 );
auto sig2 = ident.Sign( msg2 );
ASSERT_TRUE( sig1.has_value() );
ASSERT_TRUE( sig2.has_value() );
EXPECT_EQ( sig1.value().size(), sig2.value().size() );
EXPECT_EQ( sig1.value(), sig2.value() );
}
TEST( NodeIdentity, DifferentMessagesDifferentSignatures )
{
NodeIdentity ident;
ASSERT_TRUE( ident.Generate().has_value() );
std::vector<uint8_t> msgA = { 0xAA };
std::vector<uint8_t> msgB = { 0xBB };
auto sigA = ident.Sign( msgA );
auto sigB = ident.Sign( msgB );
ASSERT_TRUE( sigA.has_value() );
ASSERT_TRUE( sigB.has_value() );
EXPECT_NE( sigA.value(), sigB.value() );
}
TEST( NodeIdentity, SignAndVerifyRoundtrip )
{
NodeIdentity ident;
ASSERT_TRUE( ident.Generate().has_value() );
std::vector<uint8_t> msg = { 0x01, 0x02, 0x03, 0x04, 0x05 };
auto sig = ident.Sign( msg );
ASSERT_TRUE( sig.has_value() );
EXPECT_TRUE( ident.Verify( msg, sig.value() ) );
}
// =======================================================================
// AES-256-GCM Encrypted Key Storage
// =======================================================================
TEST( NodeIdentity, SaveEncryptedLoadEncryptedRoundtrip )
{
RemoveTestFile();
NodeIdentity ident1;
ASSERT_TRUE( ident1.Generate().has_value() );
ASSERT_TRUE( ident1.IsLoaded() );
auto saveResult = ident1.SaveEncrypted( kTestKeyPath, kTestPass );
ASSERT_TRUE( saveResult.has_value() );
NodeIdentity ident2;
auto loadResult = ident2.LoadEncrypted( kTestKeyPath, kTestPass );
ASSERT_TRUE( loadResult.has_value() );
ASSERT_TRUE( ident2.IsLoaded() );
EXPECT_EQ( ident1.GetPeerId(), ident2.GetPeerId() );
RemoveTestFile();
}
TEST( NodeIdentity, LoadEncryptedWrongPassphrase )
{
RemoveTestFile();
NodeIdentity ident1;
ASSERT_TRUE( ident1.Generate().has_value() );
ASSERT_TRUE( ident1.SaveEncrypted( kTestKeyPath, kTestPass ).has_value() );
NodeIdentity ident2;
auto result = ident2.LoadEncrypted( kTestKeyPath, kWrongPass );
EXPECT_FALSE( result.has_value() );
EXPECT_EQ( result.error(), Error::IdentityError );
RemoveTestFile();
}
TEST( NodeIdentity, LoadEncryptedTamperedFile )
{
RemoveTestFile();
NodeIdentity ident1;
ASSERT_TRUE( ident1.Generate().has_value() );
ASSERT_TRUE( ident1.SaveEncrypted( kTestKeyPath, kTestPass ).has_value() );
{
std::fstream f( kTestKeyPath, std::ios::binary | std::ios::in | std::ios::out );
ASSERT_TRUE( f.is_open() );
f.seekp( 48, std::ios::beg );
char c = 0;
f.get( c );
f.seekp( 48, std::ios::beg );
f.put( static_cast<char>( c ^ 0xFF ) );
f.close();
}
NodeIdentity ident2;
auto result = ident2.LoadEncrypted( kTestKeyPath, kTestPass );
EXPECT_FALSE( result.has_value() );
EXPECT_EQ( result.error(), Error::IdentityError );
RemoveTestFile();
}
TEST( NodeIdentity, SaveEncryptedWithoutKey )
{
RemoveTestFile();
NodeIdentity ident;
ASSERT_FALSE( ident.IsLoaded() );
auto result = ident.SaveEncrypted( kTestKeyPath, kTestPass );
EXPECT_FALSE( result.has_value() );
EXPECT_EQ( result.error(), Error::IdentityError );
RemoveTestFile();
}
TEST( NodeIdentity, LoadEncryptedNonexistentFile )
{
RemoveTestFile();
NodeIdentity ident;
auto result = ident.LoadEncrypted( kTestKeyPath, kTestPass );
EXPECT_FALSE( result.has_value() );
EXPECT_EQ( result.error(), Error::IdentityError );
}
TEST( NodeIdentity, SaveEncryptedOverwrite )
{
RemoveTestFile();
NodeIdentity ident1;
ASSERT_TRUE( ident1.Generate().has_value() );
ASSERT_TRUE( ident1.SaveEncrypted( kTestKeyPath, kTestPass ).has_value() );
ASSERT_TRUE( ident1.SaveEncrypted( kTestKeyPath, kTestPass ).has_value() );
NodeIdentity ident2;
ASSERT_TRUE( ident2.LoadEncrypted( kTestKeyPath, kTestPass ).has_value() );
EXPECT_EQ( ident1.GetPeerId(), ident2.GetPeerId() );
RemoveTestFile();
}
TEST( NodeIdentity, PeerId_ConsistentForSameKey )
{
RemoveTestFile();
NodeIdentity ident1;
ASSERT_TRUE( ident1.Generate().has_value() );
ASSERT_TRUE( ident1.SaveToFile( kTestKeyPath ).has_value() );
std::string peerId1 = ident1.GetPeerId();
NodeIdentity ident2;
ASSERT_TRUE( ident2.LoadFromFile( kTestKeyPath ).has_value() );
std::string peerId2 = ident2.GetPeerId();
EXPECT_EQ( peerId1, peerId2 );
EXPECT_FALSE( peerId1.empty() );
RemoveTestFile();
}
TEST( NodeIdentity, LoadEncrypted_TruncatedFile_ReturnsError )
{
RemoveTestFile();
NodeIdentity ident;
ASSERT_TRUE( ident.Generate().has_value() );
ASSERT_TRUE( ident.SaveEncrypted( kTestKeyPath, kTestPass ).has_value() );
{
std::ofstream f( kTestKeyPath, std::ios::trunc | std::ios::binary );
ASSERT_TRUE( f.is_open() );
f.write( "short", 5 );
f.close();
}
NodeIdentity ident2;
auto result = ident2.LoadEncrypted( kTestKeyPath, kTestPass );
EXPECT_FALSE( result.has_value() );
RemoveTestFile();
}
TEST( NodeIdentity, PeerId_WithoutKey_ReturnsEmpty )
{
NodeIdentity ident;
EXPECT_TRUE( ident.GetPeerId().empty() );
}
TEST( NodeIdentity, LoadFromFile_EmptyPath_ReturnsError )
{
NodeIdentity ident;
auto result = ident.LoadFromFile( "" );
EXPECT_FALSE( result.has_value() );
}
TEST( NodeIdentity, SaveToFile_WithoutKey_ReturnsError )
{
NodeIdentity ident;
auto result = ident.SaveToFile( kTestKeyPath );
EXPECT_FALSE( result.has_value() );
}
// =======================================================================
// GetPrivateKey — Phase 2, Plan 05 (SDK wiring)
// =======================================================================
TEST( NodeIdentity, GetPrivateKey_AfterGenerate_Returns32Bytes )
{
NodeIdentity ident;
ASSERT_TRUE( ident.Generate().has_value() );
ASSERT_TRUE( ident.IsLoaded() );
const auto& privKey = ident.GetPrivateKey();
EXPECT_EQ( privKey.size(), NodeIdentity::kPrivKeySize );
// Key must not be all zeros — verify at least one non-zero byte
bool hasNonZero = false;
for ( const auto byte : privKey )
{
if ( byte != 0 )
{
hasNonZero = true;
break;
}
}
EXPECT_TRUE( hasNonZero );
}
TEST( NodeIdentity, GetPrivateKey_AfterLoadFromFile_PreservesKey )
{
RemoveTestFile();
NodeIdentity ident1;
ASSERT_TRUE( ident1.Generate().has_value() );
ASSERT_TRUE( ident1.SaveToFile( kTestKeyPath ).has_value() );
const auto& privKey1 = ident1.GetPrivateKey();
NodeIdentity ident2;
ASSERT_TRUE( ident2.LoadFromFile( kTestKeyPath ).has_value() );
ASSERT_TRUE( ident2.IsLoaded() );
const auto& privKey2 = ident2.GetPrivateKey();
EXPECT_EQ( privKey1, privKey2 );
EXPECT_EQ( ident1.GetPeerId(), ident2.GetPeerId() );
RemoveTestFile();
}
TEST( NodeIdentity, GetPrivateKey_AfterLoadEncrypted_PreservesKey )
{
RemoveTestFile();
NodeIdentity ident1;
ASSERT_TRUE( ident1.Generate().has_value() );
ASSERT_TRUE( ident1.SaveEncrypted( kTestKeyPath, kTestPass ).has_value() );
const auto& privKey1 = ident1.GetPrivateKey();
NodeIdentity ident2;
ASSERT_TRUE( ident2.LoadEncrypted( kTestKeyPath, kTestPass ).has_value() );
ASSERT_TRUE( ident2.IsLoaded() );
const auto& privKey2 = ident2.GetPrivateKey();
EXPECT_EQ( privKey1, privKey2 );
EXPECT_EQ( ident1.GetPeerId(), ident2.GetPeerId() );
RemoveTestFile();
}
// =======================================================================
// Private key hex encoding — Phase 2, Plan 05 (GeniusSDK init)
// =======================================================================
TEST( NodeIdentity, PrivateKeyHexEncoding_Is66CharsWith0xPrefix )
{
NodeIdentity ident;
ASSERT_TRUE( ident.Generate().has_value() );
const auto& privKey = ident.GetPrivateKey();
EXPECT_EQ( privKey.size(), NodeIdentity::kPrivKeySize );
// Simulate the hex encoding used by SGClient::Initialize()
// Must produce "0x" + 64 hex chars (total 66) for GeniusSDKInitWithKey()
std::ostringstream hexStream;
hexStream << "0x";
hexStream << std::hex << std::setfill( '0' );
for ( const auto byte : privKey )
{
hexStream << std::setw( 2 ) << static_cast<int>( byte );
}
std::string hexKey = hexStream.str();
EXPECT_EQ( hexKey.size(), 66U );
EXPECT_EQ( hexKey.substr( 0, 2 ), "0x" );
// All chars after prefix must be valid hex
for ( size_t i = 2; i < hexKey.size(); ++i )
{
EXPECT_TRUE( std::isxdigit( static_cast<unsigned char>( hexKey[i] ) ) );
}
}
Updated on 2026-07-25 at 22:56:57 +0000