Tons of updates and testing added

This commit is contained in:
kl3mta3
2025-01-28 11:03:22 -06:00
parent 085667b3ac
commit b1583a26bb
10 changed files with 518 additions and 292 deletions

View File

@@ -9,7 +9,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Packet", "Packet\Packet.csp
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Security", "Security\Security.csproj", "{312E9B69-6497-43ED-8760-F2A690A312FD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testing", "Testing\Testing.csproj", "{666EA51F-F44A-4F04-8E9C-09429D3EFDED}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Testing", "Testing\Testing.csproj", "{666EA51F-F44A-4F04-8E9C-09429D3EFDED}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedLibraries", "SharedLibraries\SharedLibraries.csproj", "{D7602CFF-949B-4B26-9438-9DAEB89F4B1B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -33,6 +35,10 @@ Global
{666EA51F-F44A-4F04-8E9C-09429D3EFDED}.Debug|Any CPU.Build.0 = Debug|Any CPU
{666EA51F-F44A-4F04-8E9C-09429D3EFDED}.Release|Any CPU.ActiveCfg = Release|Any CPU
{666EA51F-F44A-4F04-8E9C-09429D3EFDED}.Release|Any CPU.Build.0 = Release|Any CPU
{D7602CFF-949B-4B26-9438-9DAEB89F4B1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D7602CFF-949B-4B26-9438-9DAEB89F4B1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D7602CFF-949B-4B26-9438-9DAEB89F4B1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D7602CFF-949B-4B26-9438-9DAEB89F4B1B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -2,10 +2,13 @@
using System.Reflection;
using System.Text;
using System.Text.Json;
using SharedLibraries;
using SPHERE.Configure;
using SPHERE.Networking;
using SPHERE.PacketLib;
using SPHERE.Security;
using SharedLibraries;
using SPHERE.TestingLib;
namespace SPHERE.Blockchain
{
@@ -45,7 +48,7 @@ namespace SPHERE.Blockchain
Leech, // Does not store or support the chain in any way other than to look up info in the chain and return it. (Verification servers, and entities that dont need to store blocks, or are high risk for attacks)
}
public class Node
public class Node
{
public static readonly object stateLock = new object();
public const string DefaultPreviousHash = "UNKNOWN";
@@ -62,8 +65,7 @@ namespace SPHERE.Blockchain
public static Node CreateNode(Client client, NodeType nodeType)
{
// If not loaded load needed Libraries
DllLoader.LoadAllEmbeddedDlls();
//create a node.
Node node = new Node();
@@ -82,12 +84,12 @@ namespace SPHERE.Blockchain
try
{
// Initialize PeerHeader
Peer peer = new Peer
{
Node_Type = nodeType,
NodeId = AppIdentifier.GetOrCreateDHTNodeID(),
NodeId = ServiceAccountManager.GenerateKademliaId(),
NodeIP = client.clientIP.ToString(),
NodePort = client.clientListenerPort,
PreviousNodesHash = DefaultPreviousHash, // Placeholder value
@@ -161,16 +163,15 @@ namespace SPHERE.Blockchain
//This is used to Create the Node or Load one if it exists.
public static Node CreateNodeWithClientListenerUsingSTUN(NodeType nodeType)
{
// If not loaded load needed Libraries
DllLoader.LoadAllEmbeddedDlls();
//create a node.
Node node = new Node();
//Create a client get listeners using STUN.
Client client = new Client();
Client.StartClientListenerWithSTUNSync(client);
node.Client= client;
IKeyProvider keyProvider = new Testing();
Client client = new Client(keyProvider);
// Thread-safe key generation
lock (stateLock)
@@ -181,7 +182,8 @@ namespace SPHERE.Blockchain
KeyGenerator.GenerateNodeKeyPairs();
}
}
client.StartClientListenerWithSTUNSync(node, client);
node.Client = client;
try
{
@@ -189,7 +191,7 @@ namespace SPHERE.Blockchain
Peer peer = new Peer
{
Node_Type = nodeType,
NodeId = AppIdentifier.GetOrCreateDHTNodeID(),
NodeId = ServiceAccountManager.GenerateKademliaId(),
NodeIP = client.clientIP.ToString(),
NodePort = client.clientListenerPort,
PreviousNodesHash = DefaultPreviousHash, // Placeholder value
@@ -256,61 +258,98 @@ namespace SPHERE.Blockchain
//Sends a Bootstrap Request You will need to be provided the IP, Port and public communication Key of the Host. (It can be provided by any othter node on request. But is only good till they go off and back online and thier ip and port reset.
public async Task SendBootstrapRequest(string iPAddress, int port, string recipientsPublicComKey)
public async Task SendBootstrapRequest(string iPAddress, int port, string recipientsPublicEncryptKey)
{
Node node = this;
// Validate inputs
if (node == null)
try
{
throw new ArgumentNullException(nameof(node), "Node cannot be null.");
}
Console.WriteLine("Debug: Starting Bootstrap Request...");
if (string.IsNullOrWhiteSpace(iPAddress))
{
throw new ArgumentException("IP address cannot be null or empty.", nameof(iPAddress));
}
if (port <= 0 || port > 65535)
{
throw new ArgumentOutOfRangeException(nameof(port), "Port must be a valid number between 1 and 65535.");
}
if (string.IsNullOrWhiteSpace(recipientsPublicComKey))
{
throw new ArgumentException("Recipient's public communication key cannot be null or empty.", nameof(recipientsPublicComKey));
}
// Use RetryAsync to retry the operation on failure
await RetryAsync<bool>(async () =>
{
// Build the bootstrap request packet
Packet.PacketHeader header = Packet.PacketBuilder.BuildPacketHeader(Packet.PacketBuilder.PacketType.BootstrapRequest,node.Peer.NodeId, node.Peer.PublicSignatureKey,node.Client.clientListenerPort, node.Client.clientIP.ToString(), 75);
Packet packet = Packet.PacketBuilder.BuildPacket(header, "BootstrapRequest");
// Serialize the packet into a byte array
byte[] data = Packet.PacketBuilder.SerializePacket(packet);
// Encrypt the packet using the recipient's public communication key
byte[] encryptedData = Encryption.EncryptWithPersonalKey(data, recipientsPublicComKey);
// Generate a signature for the encrypted data using the node's private key
string signature = SignatureGenerator.SignByteArray(encryptedData);
// Send the encrypted data and signature to the recipient
bool success = await Client.SendPacketToPeerAsync(iPAddress, port, encryptedData, signature);
// If the send operation fails, throw an exception to trigger a retry
if (!success)
// Validate inputs
if (node == null)
{
throw new Exception($"Failed to send bootstrap request to {iPAddress}:{port}.");
Console.WriteLine("Debug: Node is null.");
throw new ArgumentNullException(nameof(node), "Node cannot be null.");
}
// Log successful bootstrap request
Console.WriteLine($"Bootstrap request successfully sent to {iPAddress}:{port}.");
if (string.IsNullOrWhiteSpace(iPAddress))
{
Console.WriteLine("Debug: Invalid IP address input.");
throw new ArgumentException("IP address cannot be null or empty.", nameof(iPAddress));
}
return success; // Explicitly return the success status
});
if (port <= 0 || port > 65535)
{
Console.WriteLine($"Debug: Invalid port: {port}.");
throw new ArgumentOutOfRangeException(nameof(port), "Port must be a valid number between 1 and 65535.");
}
if (string.IsNullOrWhiteSpace(recipientsPublicEncryptKey))
{
Console.WriteLine("Debug: Recipient's public communication key is null or empty.");
throw new ArgumentException("Recipient's public communication key cannot be null or empty.", nameof(recipientsPublicEncryptKey));
}
Console.WriteLine($"Debug: Inputs validated. IP: {iPAddress}, Port: {port}, PublicComKey: {recipientsPublicEncryptKey}");
// Use RetryAsync to retry the operation on failure
await RetryAsync<bool>(async () =>
{
Console.WriteLine("Debug: Building bootstrap request packet...");
Packet.PacketHeader header = Packet.PacketBuilder.BuildPacketHeader(
Packet.PacketBuilder.PacketType.BootstrapRequest,
node.Peer.NodeId,
node.Peer.PublicSignatureKey,
node.Peer.PublicEncryptKey,
node.Client.clientListenerPort,
node.Client.clientIP.ToString(),
75
);
Packet packet = Packet.PacketBuilder.BuildPacket(header, "BootstrapRequest");
Console.WriteLine($"Debug: Packet built with NodeId: {node.Peer.NodeId}, IP: {node.Client.clientIP}, Port: {node.Client.clientListenerPort}");
// Serialize the packet into a byte array
Console.WriteLine("Debug: Serializing packet...");
byte[] data = Packet.PacketBuilder.SerializePacket(packet);
Console.WriteLine($"Debug: Packet serialized. Data Length: {data.Length} bytes");
// Encrypt the packet using the recipient's public communication key
Console.WriteLine("Debug: Encrypting packet...");
byte[] encryptedData = Encryption.EncryptWithPersonalKey(data, recipientsPublicEncryptKey);
Console.WriteLine($"Debug: Packet encrypted. Encrypted Data Length: {encryptedData.Length} bytes");
// Generate a signature for the encrypted data using the node's private key
Console.WriteLine("Debug: Generating signature...");
string signature = SignatureGenerator.SignByteArray(encryptedData);
Console.WriteLine($"Debug: Signature generated. Signature Length: {signature.Length} characters");
// Send the encrypted data and signature to the recipient
Console.WriteLine($"Debug: Sending packet to {iPAddress}:{port}...");
bool success = await Client.SendPacketToPeerAsync(iPAddress, port, encryptedData, signature);
// If the send operation fails, throw an exception to trigger a retry
if (!success)
{
Console.WriteLine($"Debug: Failed to send bootstrap request to {iPAddress}:{port}");
throw new Exception($"Failed to send bootstrap request to {iPAddress}:{port}.");
}
// Log successful bootstrap request
Console.WriteLine($"Debug: Bootstrap request successfully sent to {iPAddress}:{port}");
return success; // Explicitly return the success status
});
Console.WriteLine("Debug: Bootstrap Request process completed.");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
Console.WriteLine($"Debug Trace: {ex.StackTrace}");
throw;
}
}
//Process Bootstrap Response
@@ -320,52 +359,65 @@ namespace SPHERE.Blockchain
try
{
Console.WriteLine("Debug: Starting to process bootstrap response...");
// Verify Node isn't already Bootstrapped, prevents re-bootstrapping by accident
if (node.isBootstrapped)
{
Console.WriteLine("Node is already Bootstrapped. Ignoring the response.");
Console.WriteLine("Debug: Node is already bootstrapped. Ignoring the response.");
return;
}
// Validate the packet and extract the header details
if (packet == null || packet.Header == null)
{
Console.WriteLine("Invalid packet or missing header.");
Console.WriteLine("Debug: Invalid packet or missing header.");
return;
}
Console.WriteLine($"Debug: Processing packet from Node ID: {packet.Header.NodeId}, IP: {packet.Header.IPAddress}, Port: {packet.Header.Port}");
// Extract details from the packet header
string senderPublicKey = packet.Header.PublicSignatureKey;
string signature = packet.Signature;
byte[] encryptedData = Convert.FromBase64String(packet.Content);
Console.WriteLine($"Debug: Packet signature: {signature}. Content size: {encryptedData.Length} bytes");
// Verify the signature
Console.WriteLine("Debug: Verifying packet signature...");
bool isSignatureValid = SignatureGenerator.VerifyByteArray(encryptedData, signature, senderPublicKey);
if (!isSignatureValid)
{
Console.WriteLine("Invalid signature. Response has been tampered with.");
Console.WriteLine("Debug: Invalid signature. Response has been tampered with.");
return;
}
Console.WriteLine("Debug: Signature verification passed.");
// Decrypt the data
Console.WriteLine("Debug: Decrypting packet data...");
byte[] decryptedData = Encryption.DecryptWithPrivateKey(
encryptedData,
ServiceAccountManager.UseKeyInStorageContainer(KeyGenerator.KeyType.PrivateNodeEncryptionKey)
);
Console.WriteLine($"Debug: Decryption successful. Decrypted data size: {decryptedData.Length} bytes");
// Deserialize the response payload
Console.WriteLine("Debug: Deserializing bootstrap response payload...");
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var responsePayload = JsonSerializer.Deserialize<BootstrapResponsePayload>(decryptedData, options);
if (responsePayload == null)
{
Console.WriteLine("Failed to deserialize bootstrap response payload.");
Console.WriteLine("Debug: Failed to deserialize bootstrap response payload.");
return;
}
Console.WriteLine("Debug: Bootstrap response payload deserialized successfully.");
// Process the peer list
if (responsePayload.Peers != null)
{
Console.WriteLine($"Debug: Processing {responsePayload.Peers.Count} peers...");
lock (RoutingTable) // Ensure thread-safe access to the RoutingTable
{
foreach (var peer in responsePayload.Peers)
@@ -382,7 +434,7 @@ namespace SPHERE.Blockchain
// Add the peer to the RoutingTable (will handle duplicates automatically)
RoutingTable.AddPeer(newPeer);
Console.WriteLine($"Added or updated peer {peer.NodeId} in the routing table.");
Console.WriteLine($"Debug: Added or updated peer {peer.NodeId} in the routing table.");
}
}
}
@@ -390,6 +442,7 @@ namespace SPHERE.Blockchain
// Process the DHT state (if included)
if (responsePayload.DHT != null)
{
Console.WriteLine($"Debug: Processing {responsePayload.DHT.Count} DHT blocks...");
lock (stateLock) // Ensure thread-safe access to the DHT
{
foreach (var block in responsePayload.DHT)
@@ -398,129 +451,173 @@ namespace SPHERE.Blockchain
if (DHT.IsBlockValid(block))
{
node.DHT.AddBlock(block);
Console.WriteLine($"Added DHT block: {block.Header.BlockId}");
Console.WriteLine($"Debug: Added DHT block: {block.Header.BlockId}");
}
else
{
Console.WriteLine($"Invalid block {block.Header.BlockId}. Skipping.");
Console.WriteLine($"Debug: Invalid block {block.Header.BlockId}. Skipping.");
}
}
}
}
Console.WriteLine("Bootstrap response processed successfully.");
Console.WriteLine("Debug: Bootstrap response processed successfully.");
}
catch (Exception ex)
{
Console.WriteLine($"Error processing bootstrap response: {ex.Message}");
Console.WriteLine($"Error: {ex.Message}");
Console.WriteLine($"Debug Trace: {ex.StackTrace}");
}
}
// Sends a response to a request to Bootstrap. Sends a peer list and copy of DHT (Or shards at some point)
public async Task SendBootstrapResponse( Packet packet)
public async Task SendBootstrapResponse(Packet packet)
{
Node node = this;
string recipientsID = packet.Header.NodeId;
string recipientIPAddress = packet.Header.IPAddress;
int recipientPort = int.Parse(packet.Header.Port);
string recipientPublicComKey= packet.Header.PublicSignatureKey;
// Validate inputs
if (packet == null)
{
throw new ArgumentNullException(nameof(packet), "Packet cannot be null.");
}
if (node == null)
try
{
throw new ArgumentNullException(nameof(node), "The Node cannot be null.");
}
Console.WriteLine("Debug: Starting to send bootstrap response...");
if (string.IsNullOrWhiteSpace(recipientIPAddress))
{
throw new ArgumentException("Packet's IP address cannot be null or empty.", nameof(recipientIPAddress));
}
// Extract recipient details from the packet
string recipientsID = packet.Header.NodeId;
string recipientIPAddress = packet.Header.IPAddress;
int recipientPort = int.Parse(packet.Header.Port);
string recipientPublicEncryptKey = packet.Header.PublicSignatureKey;
if (recipientPort <= 0 || recipientPort > 65535)
{
throw new ArgumentOutOfRangeException(nameof(recipientPort), "Packet port must be a valid number between 1 and 65535.");
}
Console.WriteLine($"Debug: Recipient details - NodeId: {recipientsID}, IP: {recipientIPAddress}, Port: {recipientPort}, PublicComKey: {recipientPublicEncryptKey}");
if (string.IsNullOrWhiteSpace(recipientPublicComKey))
{
throw new ArgumentException("Recipient's public communication key cannot be null or empty.", nameof(recipientPublicComKey));
}
// Use RetryAsync to ensure the response is sent
await RetryAsync<bool>(async () =>
{
// Collect peers from the RoutingTable
List<Peer> peerList;
lock (node.RoutingTable)
// Validate inputs
if (packet == null)
{
if (!string.IsNullOrWhiteSpace(recipientsID))
{
peerList = node.RoutingTable.GetClosestPeers(recipientsID, 20); // Adjust '20' as needed
}
else
{
peerList= node.RoutingTable.GetAllPeers();
}
Console.WriteLine("Debug: Packet is null.");
throw new ArgumentNullException(nameof(packet), "Packet cannot be null.");
}
// Prepare a lightweight peer list for the response payload
var responsePeerList = peerList.Select(peer => new
if (node == null)
{
peer.NodeId,
peer.NodeIP,
peer.NodePort,
peer.PublicSignatureKey,
peer.PublicEncryptKey
}).ToList();
// Include DHT state (if necessary)
var dhtState = node.DHT.GetCurrentState();
// Build the response payload
var responsePayload = new
{
MessageType = "BootstrapResponse",
Peers = responsePeerList,
DHT = dhtState
};
// Serialize the response payload into a byte array
byte[] responseData = JsonSerializer.SerializeToUtf8Bytes(responsePayload);
// Encrypt the response data using the recipient's public communication key
byte[] encryptedResponseData = Encryption.EncryptWithPersonalKey(responseData, recipientPublicComKey);
// Generate a signature for the encrypted data using the node's private key
string responseSignature = SignatureGenerator.SignByteArray(encryptedResponseData);
// Send the encrypted response data and signature to the recipient
bool success = await Client.SendPacketToPeerAsync(recipientIPAddress, recipientPort, encryptedResponseData, responseSignature);
// If the send operation fails, throw an exception to trigger a retry
if (!success)
{
throw new Exception($"Failed to send bootstrap response to {recipientIPAddress}:{recipientPort}.");
Console.WriteLine("Debug: Node is null.");
throw new ArgumentNullException(nameof(node), "The Node cannot be null.");
}
// Reward the recipient with a trust score for a valid request
lock (node.RoutingTable)
if (string.IsNullOrWhiteSpace(recipientIPAddress))
{
var peer = node.RoutingTable.GetPeerByIPAddress(recipientIPAddress);
if (peer != null)
Console.WriteLine("Debug: Recipient IP address is invalid.");
throw new ArgumentException("Packet's IP address cannot be null or empty.", nameof(recipientIPAddress));
}
if (recipientPort <= 0 || recipientPort > 65535)
{
Console.WriteLine($"Debug: Invalid recipient port: {recipientPort}");
throw new ArgumentOutOfRangeException(nameof(recipientPort), "Packet port must be a valid number between 1 and 65535.");
}
if (string.IsNullOrWhiteSpace(recipientPublicEncryptKey))
{
Console.WriteLine("Debug: Recipient's public communication key is invalid.");
throw new ArgumentException("Recipient's public communication key cannot be null or empty.", nameof(recipientPublicEncryptKey));
}
Console.WriteLine("Debug: Inputs validated successfully.");
// Use RetryAsync to ensure the response is sent
await RetryAsync<bool>(async () =>
{
Console.WriteLine("Debug: Preparing peer list for response...");
List<Peer> peerList;
lock (node.RoutingTable)
{
peer.UpdateTrustScore(peer, +5); // Reward 5 points
if (!string.IsNullOrWhiteSpace(recipientsID))
{
peerList = node.RoutingTable.GetClosestPeers(recipientsID, 20); // Adjust '20' as needed
Console.WriteLine($"Debug: Retrieved {peerList.Count} closest peers for NodeId {recipientsID}.");
}
else
{
peerList = node.RoutingTable.GetAllPeers();
Console.WriteLine($"Debug: Retrieved all peers. Total: {peerList.Count}");
}
}
}
// Log successful bootstrap response
Console.WriteLine($"Bootstrap response successfully sent to {recipientIPAddress}:{recipientPort}.");
return success; // Explicitly return success
});
// Prepare a lightweight peer list for the response payload
var responsePeerList = peerList.Select(peer => new
{
peer.NodeId,
peer.NodeIP,
peer.NodePort,
peer.PublicSignatureKey,
peer.PublicEncryptKey
}).ToList();
Console.WriteLine($"Debug: Peer list prepared. Count: {responsePeerList.Count}");
// Include DHT state (if necessary)
Console.WriteLine("Debug: Retrieving DHT state...");
var dhtState = node.DHT.GetCurrentState();
Console.WriteLine($"Debug: Retrieved DHT state. Block count: {dhtState.Count}");
// Build the response payload
var responsePayload = new
{
MessageType = "BootstrapResponse",
Peers = responsePeerList,
DHT = dhtState
};
Console.WriteLine("Debug: Serializing response payload...");
byte[] responseData = JsonSerializer.SerializeToUtf8Bytes(responsePayload);
Console.WriteLine($"Debug: Serialized response payload. Size: {responseData.Length} bytes");
// Encrypt the response data using the recipient's public communication key
Console.WriteLine("Debug: Encrypting response data...");
byte[] encryptedResponseData = Encryption.EncryptWithPersonalKey(responseData, recipientPublicEncryptKey);
Console.WriteLine($"Debug: Encrypted response data. Encrypted size: {encryptedResponseData.Length} bytes");
// Generate a signature for the encrypted data using the node's private key
Console.WriteLine("Debug: Generating signature for response...");
string responseSignature = SignatureGenerator.SignByteArray(encryptedResponseData);
Console.WriteLine($"Debug: Signature generated. Length: {responseSignature.Length} characters");
// Send the encrypted response data and signature to the recipient
Console.WriteLine($"Debug: Sending response to {recipientIPAddress}:{recipientPort}...");
bool success = await Client.SendPacketToPeerAsync(recipientIPAddress, recipientPort, encryptedResponseData, responseSignature);
// If the send operation fails, throw an exception to trigger a retry
if (!success)
{
Console.WriteLine($"Debug: Failed to send bootstrap response to {recipientIPAddress}:{recipientPort}");
throw new Exception($"Failed to send bootstrap response to {recipientIPAddress}:{recipientPort}.");
}
// Reward the recipient with a trust score for a valid request
Console.WriteLine("Debug: Updating trust score for recipient...");
lock (node.RoutingTable)
{
var peer = node.RoutingTable.GetPeerByIPAddress(recipientIPAddress);
if (peer != null)
{
peer.UpdateTrustScore(peer, +5); // Reward 5 points
Console.WriteLine($"Debug: Trust score updated for peer {peer.NodeId}. New Trust Score: {peer.TrustScore}");
}
else
{
Console.WriteLine("Debug: Recipient peer not found in the routing table.");
}
}
// Log successful bootstrap response
Console.WriteLine($"Debug: Bootstrap response successfully sent to {recipientIPAddress}:{recipientPort}.");
return success; // Explicitly return success
});
Console.WriteLine("Debug: Bootstrap response process completed successfully.");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
Console.WriteLine($"Debug Trace: {ex.StackTrace}");
throw;
}
}
//Resets the Bootstrap Status to allow a corupted node to "Reset" it's peers and DHT.
@@ -571,58 +668,8 @@ namespace SPHERE.Blockchain
}
}
//// Evlauates a peer based on TrustScore and location to decide to keep a new Peer.
//public void EvaluateAndReplacePeer(Peer newPeer)
//{
// lock (Peers)
// {
// if (Peers.Values.Count < MaxPeers)
// {
// Peers[newPeer.NodeId] = newPeer;
// Console.WriteLine($"Added new peer: {newPeer.NodeId}");
// return;
// }
// // Find the weakest peer
// Peer weakestPeer = Peers.Values
// .OrderBy(peer => peer.CalculateProximity(peer))
// .ThenBy(peer => peer.TrustScore)
// .FirstOrDefault();
// if (weakestPeer != null &&
// (newPeer.CalculateProximity(newPeer) > weakestPeer.CalculateProximity(weakestPeer) ||
// newPeer.TrustScore > weakestPeer.TrustScore))
// {
// Peers.Remove(weakestPeer.NodeId);
// Peers[newPeer.NodeId] = newPeer;
// Console.WriteLine($"Replaced peer {weakestPeer.NodeId} with {newPeer.NodeId}");
// }
// }
//}
// Returns a peer from the Peerlist based on their IP
//public Peer GetPeerByIPAddress(string ipAddress)
//{
// lock (stateLock)
// {
// return Peers.Values.FirstOrDefault(peer => peer.NodeIP == ipAddress);
// }
//}
//Returns a Peer from the Peer List By ID.
//public Peer GetPeerByID(string peerID)
//{
// if (string.IsNullOrEmpty(peerID))
// {
// throw new ArgumentException("Node ID cannot be null or empty.", nameof(peerID));
// }
// lock (stateLock) // Ensures thread-safe access to the Peers dictionary
// {
// return Peers.ContainsKey(peerID) ? Peers[peerID] : null;
// }
//}
// Send response to a Ping request.
public async Task RespondToPingAsync(Packet packet)
{

View File

@@ -83,6 +83,13 @@ namespace SPHERE.Blockchain
}
}
}
public int GetTotalBlockCount()
{
lock (stateLock) // Ensure thread safety
{
return _blocks.Count;
}
}
public static string GetAppDataPath(string fileName)
{

View File

@@ -23,9 +23,18 @@ public class RoutingTable
{
lock (_lock)
{
int bucketIndex = GetBucketIndex(peer.NodeId);
var bucket = _buckets[bucketIndex];
bucket.AddPeer(peer, _bucketSize);
try
{
// Console.WriteLine($"Adding peer to bucket: NodeId={peer.NodeId}");
int bucketIndex = GetBucketIndex(peer.NodeId);
//Console.WriteLine($"Bucket index for NodeId {peer.NodeId}: {bucketIndex}");
_buckets[bucketIndex].AddPeer(peer, _bucketSize);
}
catch (Exception ex)
{
Console.WriteLine($"Error in AddPeer: {ex.Message}");
throw;
}
}
}
@@ -76,8 +85,34 @@ public class RoutingTable
}
}
public static bool IsHexString(string input)
{
if (string.IsNullOrWhiteSpace(input))
return false;
foreach (char c in input)
{
if (!Uri.IsHexDigit(c))
return false;
}
return true;
}
private int GetBucketIndex(string nodeId)
{
// Validate that the input is not null or empty
if (string.IsNullOrWhiteSpace(nodeId))
{
throw new ArgumentNullException(nameof(nodeId), "NodeId cannot be null or empty.");
}
// Validate that the input is a valid hexadecimal string
if (!IsHexString(nodeId))
{
throw new FormatException($"NodeId '{nodeId}' is not a valid hexadecimal string.");
}
string localNodeId = "0000000000000000000000000000000000000000000000000000000000000000"; // Example local node ID (256 bits in hex)
BigInteger distance = CalculateXorDistance(localNodeId, nodeId);
@@ -87,20 +122,45 @@ public class RoutingTable
return 255 - (int)Math.Log2((double)distance);
}
private static BigInteger CalculateXorDistance(string id1, string id2)
private static BigInteger CalculateXorDistance(string localNodeId, string nodeId)
{
byte[] id1Bytes = Convert.FromHexString(id1);
byte[] id2Bytes = Convert.FromHexString(id2);
byte[] xorResult = new byte[id1Bytes.Length];
for (int i = 0; i < id1Bytes.Length; i++)
if (string.IsNullOrWhiteSpace(localNodeId) || string.IsNullOrWhiteSpace(nodeId))
{
xorResult[i] = (byte)(id1Bytes[i] ^ id2Bytes[i]);
throw new ArgumentNullException("Both localNodeId and nodeId must be non-null and non-empty.");
}
return new BigInteger(xorResult.Reverse().ToArray());
if (localNodeId.Length != nodeId.Length)
{
throw new ArgumentException("localNodeId and nodeId must have the same length.");
}
byte[] localNodeBytes = HexStringToByteArray(localNodeId);
byte[] nodeBytes = HexStringToByteArray(nodeId);
BigInteger xorDistance = new BigInteger(localNodeBytes) ^ new BigInteger(nodeBytes);
if (xorDistance.Sign < 0)
{
xorDistance = BigInteger.Abs(xorDistance); // Ensure non-negative result
}
return xorDistance;
}
private static byte[] HexStringToByteArray(string hex)
{
if (hex.Length % 2 != 0)
{
throw new ArgumentException("Hex string must have an even length.");
}
byte[] bytes = new byte[hex.Length / 2];
for (int i = 0; i < hex.Length; i += 2)
{
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
}
return bytes;
}
public void DisplayTable()
{
lock (_lock)

View File

@@ -5,7 +5,10 @@ using static SPHERE.Blockchain.Node;
using SPHERE.PacketLib;
using SPHERE.Blockchain;
using static SPHERE.PacketLib.Packet;
using SharedLibraries;
using SPHERE.Configure;
using SPHERE.TestingLib;
using SPHERE.Security;
namespace SPHERE.Networking
@@ -35,8 +38,16 @@ namespace SPHERE.Networking
/// !!!
///
/// </summary>
public class Client
public class Client
{
public readonly IKeyProvider _keyProvider;
public Client(IKeyProvider keyProvider)
{
_keyProvider = keyProvider;
}
private const int StartPort = 5000; // Start of the port range
private const int EndPort = 6000; // End of the port range
public TcpClient client;
@@ -44,8 +55,8 @@ namespace SPHERE.Networking
public Packet.PacketBuilder packetBuilder;
public Packet.PacketReader packetReader;
public IPAddress clientIP ;
public int clientListenerPort=0; //Port for listening to incoming messages Should be static and provided to other clinets
public IPAddress clientIP;
public int clientListenerPort = 0; //Port for listening to incoming messages Should be static and provided to other clinets
public int clientCommunicationPort;
@@ -73,7 +84,7 @@ namespace SPHERE.Networking
}
}
private static byte[] CombineEncryptedDataAndSignature(byte[] encryptedData, byte[] signature)
{
// Create a byte array to hold the signature length, signature, and encrypted data
@@ -107,7 +118,7 @@ namespace SPHERE.Networking
{
var stun = new STUN();
var (PublicIP, PublicPort) = stun.GetPublicEndpoint();
client.clientIP= PublicIP;
client.clientIP = PublicIP;
client.clientListenerPort = PublicPort;
}
@@ -115,7 +126,7 @@ namespace SPHERE.Networking
{
for (int port = StartPort; port <= EndPort; port++)
{
if ( IsPortAvailableAsync(port))
if (IsPortAvailableAsync(port))
{
return port;
}
@@ -127,32 +138,32 @@ namespace SPHERE.Networking
private static bool IsPortAvailableAsync(int port)
{
try
try
{
using (var listener = new TcpListener(IPAddress.Any, port))
{
using (var listener = new TcpListener(IPAddress.Any, port))
{
listener.Start();
listener.Stop();
return true;
}
listener.Start();
listener.Stop();
return true;
}
catch (SocketException)
{
return false;
}
}
catch (SocketException)
{
return false;
}
}
public static async Task StartClientListenerAsync(Client client)
public async Task StartClientListenerAsync(Node node, Client client)
{
DllLoader.LoadAllEmbeddedDlls();
if (client.clientListenerPort==0)
// DllLoader.LoadAllEmbeddedDlls();
if (client.clientListenerPort == 0)
{
client.clientListenerPort = FindAvailablePort();
}
client.Listener = new TcpListener(IPAddress.Any, client.clientListenerPort);
client.Listener.Start();
Console.WriteLine($"Async server is listening on port {client.clientListenerPort}");
@@ -164,7 +175,7 @@ namespace SPHERE.Networking
client.client = await client.Listener.AcceptTcpClientAsync();
Console.WriteLine($"Connection established with {client.client.Client.RemoteEndPoint}");
_ = HandleClientAsync(client.client);
_ = HandleClientAsync(node, client.client);
}
catch (Exception ex)
{
@@ -173,11 +184,11 @@ namespace SPHERE.Networking
}
}
public static async Task StartClientListenerWithSTUNAsync(Client client)
public async Task StartClientListenerWithSTUNAsync(Node node, Client client)
{
try
{
DllLoader.LoadAllEmbeddedDlls();
// DllLoader.LoadAllEmbeddedDlls();
var stun = new STUN();
var (PublicIP, PublicPort) = stun.GetPublicEndpoint();
@@ -207,7 +218,7 @@ namespace SPHERE.Networking
client.client = await client.Listener.AcceptTcpClientAsync();
Console.WriteLine($"Connection established with {client.client.Client.RemoteEndPoint}");
_ = HandleClientAsync(client.client);
_ = HandleClientAsync(node, client.client);
}
catch (Exception ex)
{
@@ -225,25 +236,46 @@ namespace SPHERE.Networking
// Synchronous method to start the client listener using STUN
public static void StartClientListenerWithSTUNSync(Client client)
public void StartClientListenerWithSTUNSync(Node node, Client client)
{
var stun = new STUN();
var (PublicIP, PublicPort) = stun.GetPublicEndpoint();
if (PublicIP == null || PublicPort == 0)
try
{
Console.WriteLine("Failed to retrieve public endpoint. Exiting.");
throw new Exception("STUN failed to retrieve public endpoint.");
}
// Step 1: Retrieve public IP and port using STUN
var stun = new STUN();
var (PublicIP, PublicPort) = stun.GetPublicEndpoint();
client.clientListenerPort = PublicPort;
client.clientIP = PublicIP;
client.Listener = new TcpListener(client.clientIP, client.clientListenerPort);
client.Listener.Start();
Console.WriteLine($"Server is listening on port {client.clientListenerPort}");
if (PublicIP == null || PublicPort == 0)
{
Console.WriteLine("Failed to retrieve public endpoint. Exiting.");
throw new Exception("STUN failed to retrieve public endpoint.");
}
client.clientListenerPort = PublicPort;
client.clientIP = PublicIP;
client.Listener = new TcpListener(client.clientIP, client.clientListenerPort);
// Step 2: Start the listener
client.Listener.Start();
Console.WriteLine($"Server is listening on {client.clientIP}:{client.clientListenerPort}");
while (true)
{
// Step 3: Accept a new client connection
TcpClient incomingClient = client.Listener.AcceptTcpClient();
Console.WriteLine($"New client connected from {((IPEndPoint)incomingClient.Client.RemoteEndPoint).Address}");
// Step 4: Handle the client in a separate task
_ = Task.Run(() => HandleClientAsync(node, incomingClient));
}
}
catch (Exception ex)
{
Console.WriteLine($"Error starting client listener: {ex.Message}");
}
}
private static async Task HandleClientAsync(TcpClient client)
private async Task HandleClientAsync(Node node, TcpClient client)
{
try
{
@@ -256,11 +288,13 @@ namespace SPHERE.Networking
string message = await reader.ReadMessage();
Console.WriteLine($"Received message: {message}");
byte[] encryptedMessage = Convert.FromBase64String(message);
Console.WriteLine($"Debug: Encrypted Message Length: {encryptedMessage.Length} bytes");
ProcessIncomingPacket(node, encryptedMessage);
// message needs to be sent to node processed encrypted.
//(Missing Fucntionality) will add.
}
}
catch (Exception ex)
@@ -273,10 +307,20 @@ namespace SPHERE.Networking
}
}
public async Task ProcessIncomingPacket(Node node, byte[] packetData)
public async Task ProcessIncomingPacket(Node node, byte[] packetData, bool? testing=false)
{
try
{
if (testing == true)
{
string key = _keyProvider.GetPrivateKey(node.Peer.NodeId);
}
else
{
byte[] decryptedData = Encryption.DecryptWithPrivateKey(packetData, ServiceAccountManager.UseKeyInStorageContainer(KeyGenerator.KeyType.PrivateNodeEncryptionKey));
}
Packet packet = PacketBuilder.DeserializePacket(packetData);
PacketBuilder.PacketType type = ParsePacketType(packet.Header.Packet_Type);
@@ -306,8 +350,9 @@ namespace SPHERE.Networking
}
}
}
}
/// <summary>
/// The STUN server is used to get an IP and port that is open for listening for traffic without needing pinholes or portforwarding.
/// It needs to be ran evertime a listener is started. It reaches out to a list of known STUN servers and then parses the response it gets to get a valid usable IP an port.
@@ -435,5 +480,6 @@ namespace SPHERE.Networking
}
}

View File

@@ -9,30 +9,36 @@ namespace SPHERE.Configure
{
public static class DllLoader
{
public static void LoadAllEmbeddedDlls()
// Static constructor to handle DLL loading on first use of the library
static DllLoader()
{
var assembly = Assembly.GetExecutingAssembly();
var resourceNames = assembly.GetManifestResourceNames();
foreach (var resourceName in resourceNames)
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
if (resourceName.StartsWith("SPHERE.Libs.") && resourceName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
try
{
using (var stream = assembly.GetManifestResourceStream(resourceName))
string resourceName = args.Name.Split(',')[0] + ".dll";
var assembly = Assembly.GetExecutingAssembly();
using (var stream = assembly.GetManifestResourceStream($"SPHERE.Libs.{resourceName}"))
{
if (stream == null)
{
throw new InvalidOperationException($"Resource {resourceName} not found.");
}
if (stream == null) return null;
var buffer = new byte[stream.Length];
stream.Read(buffer, 0, buffer.Length);
Assembly.Load(buffer);
return Assembly.Load(buffer);
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Failed to resolve assembly: {args.Name}. Error: {ex.Message}");
return null;
}
};
}
Console.WriteLine("All DLLs in SPHERE/Libs loaded successfully.");
// Optional method to allow manual initialization, if necessary
public static void Initialize()
{
// This method can be empty; calling it ensures the static constructor runs
}
}
}

View File

@@ -5,31 +5,69 @@
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageId>SPHERE</PackageId>
<Version>1.1.6</Version>
<Authors>Kenneth Lasyone</Authors>
<Company>MonoBehaviour</Company>
<Description>SPHERE (Secure Peer-to-Peer Hosted Encryption Record Exchange) is a robust framework designed for secure, decentralized communication and data sharing. The framework prioritizes user privacy, data integrity, and seamless encryption management while allowing for versatile application development.</Description>
<Copyright>Copyright © 2025 Kenneth Lasyone</Copyright>
<PackageTags>Blockchain Decentralized Networking DHT</PackageTags>
<RepositoryUrl>https://github.com/kl3mta3/SPHERE</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-or-later</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
</PropertyGroup>
<PropertyGroup>
<!-- ILRepack Configuration -->
<ILRepackAssemblies>$(OutputPath)\Packet.dll;$(OutputPath)\Security.dll;$(OutputPath)\Testing.dll</ILRepackAssemblies>
<ILRepackOutput>$(OutputPath)\SPHERE.dll</ILRepackOutput>
<!--<ILRepackInternalize>true</ILRepackInternalize>-->
<ILRepackKeepClassNames>true</ILRepackKeepClassNames>
</PropertyGroup>
<ItemGroup>
<None Remove="Libs\Packet.dll" />
<None Remove="Libs\Security.dll" />
<None Remove="Libs\Testing.dll" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Libs\Packet.dll" />
<EmbeddedResource Include="Libs\Security.dll" />
<EmbeddedResource Include="Libs\Testing.dll" />
</ItemGroup>
<ItemGroup>
<!-- ILRepack Package -->
<PackageReference Include="ILRepack.Lib.MSBuild" Version="2.1.18" />
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="9.0.1" />
</ItemGroup>
<ItemGroup>
<Content Include="Libs\Packet.dll" CopyToOutputDirectory="Always" />
<Content Include="Libs\Security.dll" CopyToOutputDirectory="Always" />
<Content Include="Libs\Testing.dll" CopyToOutputDirectory="Always" />
</ItemGroup>
<ItemGroup>
<!-- DLL References -->
<Reference Include="Packet">
<HintPath>Libs\Packet.dll</HintPath>
</Reference>
<Reference Include="Security">
<HintPath>Libs\Security.dll</HintPath>
</Reference>
<Reference Include="Testing">
<HintPath>Libs\Testing.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
<ItemGroup>
<None Remove="$(OutputPath)\SPHERE.dll" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SharedLibraries\SharedLibraries.csproj" />
</ItemGroup>
<!-- Merge DLLs into SPHERE.dll -->
<Target Name="MergeAssemblies" AfterTargets="Build">
<!-- Trigger ILRepack during the build process -->
<MSBuild Projects="$(MSBuildProjectFullPath)" Targets="ILRepack" Properties="ILRepackAssemblies=$(ILRepackAssemblies);&#xD;&#xA; ILRepackOutput=$(ILRepackOutput);&#xD;&#xA; ILRepackInternalize=$(ILRepackInternalize);&#xD;&#xA; ILRepackKeepClassNames=$(ILRepackKeepClassNames)" />
</Target>
</Project>

View File

@@ -0,0 +1,7 @@
namespace SharedLibraries
{
public interface IKeyProvider
{
string GetPrivateKey(string nodeId);
}
}

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>