lib/reticulum/src/packet/proof.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const reticulum = @import("../root.zig");
3
4 const identity = reticulum.identity;
5 const wire = reticulum.wire;
6
7 pub const BuildError = wire.proof.EncodeError;
8 /// True, so a proof carries the signature alone unless the caller asks for the
9 /// longer form, following Reticulum@1.5.0 RNS/Reticulum.py:262.
10 pub const default_implicit = true;
11
12 /// Signs the proved packet's hash with the answering identity's private key and
13 /// writes the proof payload into `out`, following Reticulum@1.5.0
14 /// RNS/Identity.py:943-953. An explicit proof carries that hash ahead of the
15 /// signature, and an implicit one carries the signature alone. The call returns
16 /// `error.OutputTooSmall` when `out` is shorter than the payload.
17 pub fn build(
18 packet_hash: [32]u8,
19 private: *const identity.Private,
20 implicit: bool,
21 out: []u8,
22 ) BuildError![]u8 {
23 const signature = private.sign(&packet_hash);
24 const value: wire.proof.Proof = if (implicit)
25 .{ .implicit = .{ .signature = signature } }
26 else
27 .{ .explicit = .{ .packet_hash = packet_hash, .signature = signature } };
28 return wire.proof.encode(value, out);
29 }
30
31 /// Returns the first 16 bytes of a proved packet's hash, the address a proof
32 /// packet carries, following Reticulum@1.5.0 RNS/Packet.py:381-384.
33 pub fn destinationHash(packet_hash: [32]u8) [16]u8 {
34 return wire.proof.destinationHash(packet_hash);
35 }
36
37 /// Builds the unencrypted HEADER_1 packet that carries a proof, addressed to
38 /// the first 16 bytes of the proved packet's hash and starting at zero hops,
39 /// following Reticulum@1.5.0 RNS/Packet.py:178-242.
40 pub fn makePacket(packet_hash: [32]u8, payload: []const u8) wire.Packet {
41 return .{
42 .ifac = 0,
43 .header = .one,
44 .context_flag = 0,
45 .transport = .broadcast,
46 .destination_type = .single,
47 .packet_type = .proof,
48 .hops = 0,
49 .transport_id = null,
50 .destination = destinationHash(packet_hash),
51 .context = .none,
52 .payload = payload,
53 };
54 }
55
56 test "Reticulum@1.5.0 RNS/Identity.py:943-953 builds both proof lengths" {
57 var key_bytes: identity.KeyBytes = undefined;
58 for (&key_bytes, 0..) |*byte, index| byte.* = @intCast(index + 1);
59 var private = identity.Private.fromBytes(key_bytes);
60 defer private.zero();
61 const packet_hash: [32]u8 = @splat(0xa5);
62 var out: [wire.proof.explicit_bytes]u8 = undefined;
63 const implicit = try build(packet_hash, &private, true, &out);
64 try std.testing.expectEqual(@as(usize, wire.proof.implicit_bytes), implicit.len);
65 try std.testing.expect((try wire.proof.decode(implicit)) == .implicit);
66 const explicit = try build(packet_hash, &private, false, &out);
67 try std.testing.expectEqual(@as(usize, wire.proof.explicit_bytes), explicit.len);
68 try std.testing.expect((try wire.proof.decode(explicit)) == .explicit);
69 }
70
71 test "Reticulum@1.5.0 RNS/Packet.py:485-520 validates built proof payloads" {
72 var key_bytes: identity.KeyBytes = undefined;
73 for (&key_bytes, 0..) |*byte, index| byte.* = @intCast(index + 1);
74 var private = identity.Private.fromBytes(key_bytes);
75 defer private.zero();
76 var public = private.public();
77 defer public.zero();
78 const packet_hash: [32]u8 = @splat(0x5a);
79 var out: [wire.proof.explicit_bytes]u8 = undefined;
80 var implicit_receipt = reticulum.packet.receipt.Receipt{
81 .hash = packet_hash,
82 .truncated = packet_hash[0..16].*,
83 .destination = @splat(1),
84 .sent_at = 1,
85 .timeout = 12,
86 };
87 const implicit = try build(packet_hash, &private, default_implicit, &out);
88 try std.testing.expect(implicit_receipt.validateProof(
89 try wire.proof.decode(implicit),
90 &public,
91 2,
92 ));
93 var explicit_receipt = implicit_receipt;
94 explicit_receipt.status = .sent;
95 const explicit = try build(packet_hash, &private, false, &out);
96 try std.testing.expect(explicit_receipt.validateProof(
97 try wire.proof.decode(explicit),
98 &public,
99 3,
100 ));
101 }
102
103 test "Reticulum@1.5.0 RNS/Packet.py:381-384 proof destination is the hash prefix" {
104 var packet_hash: [32]u8 = undefined;
105 for (&packet_hash, 0..) |*byte, index| byte.* = @intCast(index);
106 try std.testing.expectEqualSlices(u8, packet_hash[0..16], &destinationHash(packet_hash));
107 const value = makePacket(packet_hash, "proof");
108 try std.testing.expectEqual(wire.HeaderType.one, value.header);
109 try std.testing.expectEqual(wire.PacketType.proof, value.packet_type);
110 try std.testing.expectEqual(wire.Context.none, value.context);
111 try std.testing.expectEqual(@as(u8, 0), value.hops);
112 }