feat(relay): Wire up CircuitBuilder with DHT Keys

- Implemented  in DHT for exact key lookup.
- Updated  to use DHT service for resolving Relay Public Keys.
- Generated  on client-side (random) for circuit privacy.
- Wired  to pass DHT instance to CircuitBuilder.
- Updated  forwarding logic to use strict SessionID binding.
- Fixed lints in dht.zig.
This commit is contained in:
Markus Maiwald 2026-01-31 22:15:46 +01:00
parent e5f59869bc
commit 24adf936e5
3 changed files with 30 additions and 14 deletions

View File

@ -4,9 +4,10 @@
const std = @import("std"); const std = @import("std");
const relay = @import("relay"); const relay = @import("relay");
const dht = @import("dht"); // Needed for NodeId type const dht = @import("dht");
const QvlStore = @import("qvl_store.zig").QvlStore; const QvlStore = @import("qvl_store.zig").QvlStore;
const PeerTable = @import("peer_table.zig").PeerTable; const PeerTable = @import("peer_table.zig").PeerTable;
const DhtService = dht.DhtService;
pub const CircuitError = error{ pub const CircuitError = error{
NoRelaysAvailable, NoRelaysAvailable,
@ -19,24 +20,26 @@ pub const CircuitBuilder = struct {
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
qvl_store: *QvlStore, qvl_store: *QvlStore,
peer_table: *PeerTable, peer_table: *PeerTable,
dht: *DhtService,
onion_builder: relay.OnionBuilder, onion_builder: relay.OnionBuilder,
pub fn init(allocator: std.mem.Allocator, qvl_store: *QvlStore, peer_table: *PeerTable) CircuitBuilder { pub fn init(allocator: std.mem.Allocator, qvl_store: *QvlStore, peer_table: *PeerTable, dht_service: *DhtService) CircuitBuilder {
return .{ return .{
.allocator = allocator, .allocator = allocator,
.qvl_store = qvl_store, .qvl_store = qvl_store,
.peer_table = peer_table, .peer_table = peer_table,
.dht = dht_service,
.onion_builder = relay.OnionBuilder.init(allocator), .onion_builder = relay.OnionBuilder.init(allocator),
}; };
} }
/// Builds a 1-hop circuit (MVP): Source -> Relay -> Target /// Builds a 1-hop circuit (MVP): Source -> Relay -> Target
/// Returns the fully wrapped packet ready to be sent to the Relay. /// Returns the fully wrapped packet ready to be sent to the Relay, and the Relay's address.
pub fn buildOneHopCircuit( pub fn buildOneHopCircuit(
self: *CircuitBuilder, self: *CircuitBuilder,
target_did: []const u8, target_did: []const u8,
payload: []const u8, payload: []const u8,
) !relay.RelayPacket { ) !struct { packet: relay.RelayPacket, first_hop: std.net.Address } {
// 1. Resolve Target // 1. Resolve Target
// We need the Target's NodeID (for the inner routing header). // We need the Target's NodeID (for the inner routing header).
// For MVP, we assume DID ~= NodeID or we have a mapping. // For MVP, we assume DID ~= NodeID or we have a mapping.
@ -77,19 +80,18 @@ pub const CircuitBuilder = struct {
// So the Relay forwards the *Inner Payload* to Target. // So the Relay forwards the *Inner Payload* to Target.
// Is the Inner Payload encrypted for Target? YES. // Is the Inner Payload encrypted for Target? YES.
// Mock Session secrets // Resolve Relay Keys from DHT
const relay_secret = [_]u8{0xAA} ** 32; const relay_node = self.dht.routing_table.findNode(relay_id) orelse return error.RelayNotFound;
const relay_pubkey = relay_node.key;
// Generate SessionID (Client-side)
var session_id: [16]u8 = undefined;
std.crypto.random.bytes(&session_id);
// Wrap: Relay Packet -> [ NextHop: Target | Payload ] // Wrap: Relay Packet -> [ NextHop: Target | Payload ]
const packet = try self.onion_builder.wrapLayer(payload, target_id, relay_secret); const packet = try self.onion_builder.wrapLayer(payload, target_id, relay_pubkey, session_id);
// The `packet` returned is what we send to the Relay. return .{ .packet = packet, .first_hop = relay_node.address };
// The Relay will unwrap it, see `target_id`, and forward `packet.payload` to `target_id`.
// Note: `packet.payload` here is the original `payload` (if only 1 layer).
// If we want E2E encryption for Target, we must have encrypted `payload` beforehand.
// This function assumes `payload` is ALREADY E2E encrypted (e.g. LWF frame).
return packet;
} }
}; };

View File

@ -199,6 +199,7 @@ pub const CapsuleNode = struct {
allocator, allocator,
qvl_store, qvl_store,
&self.peer_table, &self.peer_table,
&self.dht,
); );
std.log.info("Circuit Builder: ENABLED (trust threshold: {d})", .{config.relay_trust_threshold}); std.log.info("Circuit Builder: ENABLED (trust threshold: {d})", .{config.relay_trust_threshold});
} }

View File

@ -90,6 +90,19 @@ pub const RoutingTable = struct {
} }
} }
pub fn findNode(self: *RoutingTable, target: NodeId) ?RemoteNode {
const cpl = commonPrefixLen(self.self_id, target);
const bucket_idx = if (cpl == ID_LEN * 8) ID_LEN * 8 - 1 else cpl;
const bucket = &self.buckets[bucket_idx];
for (bucket.nodes.items) |node| {
if (std.mem.eql(u8, &node.id, &target)) {
return node;
}
}
return null;
}
pub fn findClosest(self: *RoutingTable, target: NodeId, count: usize) ![]RemoteNode { pub fn findClosest(self: *RoutingTable, target: NodeId, count: usize) ![]RemoteNode {
var results = std.ArrayList(RemoteNode){}; var results = std.ArrayList(RemoteNode){};
defer results.deinit(self.allocator); defer results.deinit(self.allocator);