mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-03-27 12:44:13 +00:00
119 lines
3.9 KiB
Zig
119 lines
3.9 KiB
Zig
/// Periodic verification of share integrity across guardians.
|
|
///
|
|
/// Randomly selects shares and asks peers to confirm they have the same
|
|
/// commitment hash. Detects tampering and data corruption.
|
|
const std = @import("std");
|
|
const log = @import("../log.zig");
|
|
const protocol = @import("protocol.zig");
|
|
const node_list = @import("../membership/node_list.zig");
|
|
|
|
pub const VerifyResult = struct {
|
|
identity: []const u8,
|
|
peer_address: []const u8,
|
|
matches: bool,
|
|
peer_has_share: bool,
|
|
};
|
|
|
|
/// Send a verify request to a peer and wait for the response.
|
|
/// Returns null if the peer is unreachable or returns invalid data.
|
|
pub fn verifyWithPeer(
|
|
peer_address: []const u8,
|
|
peer_port: u16,
|
|
identity: []const u8,
|
|
) ?protocol.VerifyResponse {
|
|
const address = std.net.Address.parseIp4(peer_address, peer_port) catch return null;
|
|
const stream = std.net.tcpConnectToAddress(address) catch return null;
|
|
defer stream.close();
|
|
|
|
// Build request
|
|
var req = protocol.VerifyRequest{
|
|
.identity = .{0} ** 64,
|
|
.identity_len = @intCast(@min(identity.len, 64)),
|
|
};
|
|
@memcpy(req.identity[0..req.identity_len], identity[0..req.identity_len]);
|
|
|
|
const payload = protocol.encodeVerifyRequest(req);
|
|
const header = protocol.encodeHeader(.{
|
|
.version = protocol.PROTOCOL_VERSION,
|
|
.msg_type = .verify_request,
|
|
.payload_len = payload.len,
|
|
});
|
|
|
|
stream.writeAll(&header) catch return null;
|
|
stream.writeAll(&payload) catch return null;
|
|
|
|
// Read response header
|
|
var resp_header_buf: [protocol.HEADER_SIZE]u8 = undefined;
|
|
stream.readAll(&resp_header_buf) catch return null;
|
|
const resp_header = protocol.decodeHeader(resp_header_buf) orelse return null;
|
|
|
|
if (resp_header.msg_type != .verify_response) return null;
|
|
if (resp_header.payload_len < 98) return null;
|
|
|
|
// Read response payload
|
|
var resp_buf: [98]u8 = undefined;
|
|
stream.readAll(&resp_buf) catch return null;
|
|
|
|
return protocol.decodeVerifyResponse(&resp_buf);
|
|
}
|
|
|
|
/// Compare our local commitment with a peer's commitment.
|
|
pub fn compareCommitments(
|
|
local_root: [32]u8,
|
|
peer_response: protocol.VerifyResponse,
|
|
) VerifyResult {
|
|
return VerifyResult{
|
|
.identity = peer_response.identity[0..peer_response.identity_len],
|
|
.peer_address = "", // caller fills in
|
|
.peer_has_share = peer_response.has_share,
|
|
.matches = peer_response.has_share and
|
|
std.mem.eql(u8, &local_root, &peer_response.commitment_root),
|
|
};
|
|
}
|
|
|
|
// ── Tests ────────────────────────────────────────────────────────────────────
|
|
|
|
test "compareCommitments: matching roots" {
|
|
const root = [_]u8{0xAB} ** 32;
|
|
const resp = protocol.VerifyResponse{
|
|
.identity = .{0} ** 64,
|
|
.identity_len = 4,
|
|
.has_share = true,
|
|
.commitment_root = root,
|
|
};
|
|
|
|
const result = compareCommitments(root, resp);
|
|
try std.testing.expect(result.matches);
|
|
try std.testing.expect(result.peer_has_share);
|
|
}
|
|
|
|
test "compareCommitments: mismatched roots" {
|
|
const local_root = [_]u8{0xAB} ** 32;
|
|
var peer_root = [_]u8{0xAB} ** 32;
|
|
peer_root[0] = 0xCD; // tamper
|
|
|
|
const resp = protocol.VerifyResponse{
|
|
.identity = .{0} ** 64,
|
|
.identity_len = 4,
|
|
.has_share = true,
|
|
.commitment_root = peer_root,
|
|
};
|
|
|
|
const result = compareCommitments(local_root, resp);
|
|
try std.testing.expect(!result.matches);
|
|
}
|
|
|
|
test "compareCommitments: peer missing share" {
|
|
const root = [_]u8{0xAB} ** 32;
|
|
const resp = protocol.VerifyResponse{
|
|
.identity = .{0} ** 64,
|
|
.identity_len = 4,
|
|
.has_share = false,
|
|
.commitment_root = .{0} ** 32,
|
|
};
|
|
|
|
const result = compareCommitments(root, resp);
|
|
try std.testing.expect(!result.matches);
|
|
try std.testing.expect(!result.peer_has_share);
|
|
}
|