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 }