lib/reticulum/src/wire/proof.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 /// 32 bytes, the width of the packet hash a proof (the signed reply that tells
4 /// a sender its packet arrived) signs, for sizing the hash, following
5 /// Reticulum@1.5.0 RNS/Packet.py:403.
6 pub const packet_hash_bytes: u16 = 32;
7 /// 64 bytes, the width of the Ed25519 signature a proof carries, for sizing the
8 /// signature, following Reticulum@1.5.0 RNS/Packet.py:403-404.
9 pub const signature_bytes: u16 = 64;
10 /// 96 bytes, a proof payload that carries the proved packet's hash ahead of the
11 /// signature, for sizing or recognizing the longer of the two proof payloads,
12 /// following Reticulum@1.5.0 RNS/Packet.py:403.
13 pub const explicit_bytes: u16 = packet_hash_bytes + signature_bytes;
14 /// 64 bytes, a proof payload that carries the signature alone, which the
15 /// receiver matches against a packet it is already waiting on, for sizing or
16 /// recognizing the shorter of the two proof payloads, following Reticulum@1.5.0
17 /// RNS/Packet.py:404.
18 pub const implicit_bytes: u16 = signature_bytes;
19
20 pub const Explicit = struct {
21 packet_hash: [packet_hash_bytes]u8,
22 signature: [signature_bytes]u8,
23 };
24
25 pub const Implicit = struct {
26 signature: [signature_bytes]u8,
27 };
28
29 pub const Proof = union(enum) {
30 explicit: Explicit,
31 implicit: Implicit,
32 };
33
34 pub const DecodeError = error{InvalidLength};
35 pub const EncodeError = error{OutputTooSmall};
36
37 /// Splits a proof payload into its pieces, and its length decides the form: 96
38 /// bytes give a hash and a signature, and 64 give a signature, following
39 /// Reticulum@1.5.0 RNS/Packet.py:403-404. The call returns
40 /// `error.InvalidLength` for every other length.
41 pub fn decode(bytes: []const u8) DecodeError!Proof {
42 return switch (bytes.len) {
43 explicit_bytes => .{ .explicit = .{
44 .packet_hash = bytes[0..packet_hash_bytes].*,
45 .signature = bytes[packet_hash_bytes..explicit_bytes].*,
46 } },
47 implicit_bytes => .{ .implicit = .{
48 .signature = bytes[0..implicit_bytes].*,
49 } },
50 else => error.InvalidLength,
51 };
52 }
53
54 pub fn encodedLength(value: Proof) u16 {
55 return switch (value) {
56 .explicit => explicit_bytes,
57 .implicit => implicit_bytes,
58 };
59 }
60
61 /// Writes a built proof payload into `out` for sending and returns the bytes
62 /// written, following Reticulum@1.5.0 RNS/Packet.py:403-404. The call returns
63 /// `error.OutputTooSmall` when `out` is shorter than the payload.
64 pub fn encode(value: Proof, out: []u8) EncodeError![]u8 {
65 const length: usize = encodedLength(value);
66 if (out.len < length) return error.OutputTooSmall;
67 switch (value) {
68 .explicit => |explicit| {
69 std.mem.copyForwards(u8, out[0..packet_hash_bytes], &explicit.packet_hash);
70 std.mem.copyForwards(
71 u8,
72 out[packet_hash_bytes..explicit_bytes],
73 &explicit.signature,
74 );
75 },
76 .implicit => |implicit| {
77 std.mem.copyForwards(u8, out[0..implicit_bytes], &implicit.signature);
78 },
79 }
80 return out[0..length];
81 }
82
83 /// Returns the first 16 bytes of a proved packet's hash as the destination for
84 /// addressing the proof packet sent back, following Reticulum@1.5.0
85 /// RNS/Packet.py:381-384.
86 pub fn destinationHash(packet_hash: [packet_hash_bytes]u8) [16]u8 {
87 return packet_hash[0..16].*;
88 }
89
90 test "Reticulum@1.5.0 RNS/Packet.py:403-404 splits and joins both proof forms" {
91 var explicit_raw: [explicit_bytes]u8 = undefined;
92 for (&explicit_raw, 0..) |*byte, index| byte.* = @intCast(index);
93 const explicit = try decode(&explicit_raw);
94 var explicit_out: [explicit_bytes]u8 = undefined;
95 try std.testing.expectEqualSlices(u8, &explicit_raw, try encode(explicit, &explicit_out));
96
97 var implicit_raw: [implicit_bytes]u8 = undefined;
98 for (&implicit_raw, 0..) |*byte, index| byte.* = @intCast(index);
99 const implicit = try decode(&implicit_raw);
100 var implicit_out: [implicit_bytes]u8 = undefined;
101 try std.testing.expectEqualSlices(u8, &implicit_raw, try encode(implicit, &implicit_out));
102 }
103
104 test "Reticulum@1.5.0 RNS/Packet.py:381-384 truncates proof destinations" {
105 var packet_hash: [packet_hash_bytes]u8 = undefined;
106 for (&packet_hash, 0..) |*byte, index| byte.* = @intCast(index);
107 try std.testing.expectEqualSlices(u8, packet_hash[0..16], &destinationHash(packet_hash));
108 }
109
110 test "Reticulum@1.5.0 RNS/Packet.py:403-404 rejects other proof lengths" {
111 var bytes: [explicit_bytes + 1]u8 = @splat(0);
112 try std.testing.expectError(error.InvalidLength, decode(bytes[0 .. implicit_bytes - 1]));
113 try std.testing.expectError(error.InvalidLength, decode(bytes[0 .. implicit_bytes + 1]));
114 try std.testing.expectError(error.InvalidLength, decode(bytes[0 .. explicit_bytes - 1]));
115 try std.testing.expectError(error.InvalidLength, decode(&bytes));
116 }