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 }