lib/reticulum/src/properties/cipher.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

 1 const std = @import("std");
 2 const hypothesis = @import("hypothesis");
 3 const reticulum = @import("reticulum");
 4 
 5 const identity = reticulum.identity;
 6 const seed: u64 = 0x5234_4349_5048_4552;
 7 
 8 fn roundTrip(
 9     private: *const identity.Private,
10     ratchet: ?*const identity.Ratchet,
11     ephemeral_private: *const [32]u8,
12     iv: [16]u8,
13     plaintext: []const u8,
14 ) !void {
15     var public = private.public();
16     defer public.zero();
17     var ratchet_public: [32]u8 = undefined;
18     const selected: ?*const [32]u8 = if (ratchet) |value| blk: {
19         ratchet_public = value.publicBytes();
20         break :blk &ratchet_public;
21     } else null;
22     var ciphertext_storage: [480]u8 = undefined;
23     const ciphertext = try identity.cipher.encrypt(
24         &public,
25         selected,
26         ephemeral_private,
27         iv,
28         plaintext,
29         &ciphertext_storage,
30     );
31     const retained: []const identity.Ratchet = if (ratchet) |value| value[0..1] else &.{};
32     var plaintext_storage: [384]u8 = undefined;
33     const decrypted = try identity.cipher.decrypt(
34         private,
35         retained,
36         false,
37         ciphertext,
38         &plaintext_storage,
39     );
40     try std.testing.expectEqualSlices(u8, plaintext, decrypted.plaintext);
41     const expected_id: ?[10]u8 = if (ratchet) |value| value.id() else null;
42     try std.testing.expectEqual(expected_id, decrypted.ratchet_id);
43 }
44 
45 const CipherRoundTrip = struct {
46     pub fn property(data: *hypothesis.ConjectureData, _: std.mem.Allocator) !void {
47         const private_input = try data.drawBytes(identity.key_bytes, identity.key_bytes);
48         const ephemeral_input = try data.drawBytes(32, 32);
49         const iv_input = try data.drawBytes(16, 16);
50         const ratchet_input = try data.drawBytes(32, 32);
51         const plaintext_length: usize = @intCast(try data.drawInteger(0, 383, 0));
52         const plaintext = try data.drawBytes(plaintext_length, plaintext_length);
53         var private = identity.Private.fromBytes(private_input[0..identity.key_bytes].*);
54         defer private.zero();
55         var ratchet = identity.Ratchet.fromBytes(ratchet_input[0..32].*);
56         defer ratchet.zero();
57         const ephemeral_private = ephemeral_input[0..32].*;
58         const iv = iv_input[0..16].*;
59         try roundTrip(&private, null, &ephemeral_private, iv, plaintext);
60         try roundTrip(&private, &ratchet, &ephemeral_private, iv, plaintext);
61     }
62 };
63 
64 const ArbitraryCiphertext = struct {
65     pub fn property(data: *hypothesis.ConjectureData, _: std.mem.Allocator) !void {
66         const private_input = try data.drawBytes(identity.key_bytes, identity.key_bytes);
67         const ratchet_input = try data.drawBytes(32, 32);
68         const ciphertext = try data.drawBytes(0, 500);
69         const enforce = try data.drawBoolean();
70         var private = identity.Private.fromBytes(private_input[0..identity.key_bytes].*);
71         defer private.zero();
72         var ratchet = identity.Ratchet.fromBytes(ratchet_input[0..32].*);
73         defer ratchet.zero();
74         var plaintext: [500]u8 = undefined;
75         _ = identity.cipher.decrypt(
76             &private,
77             (&ratchet)[0..1],
78             enforce,
79             ciphertext,
80             &plaintext,
81         ) catch return;
82     }
83 };
84 
85 test "property: Reticulum@1.5.0 RNS/Identity.py:804-907 cipher round trips" {
86     const settings = hypothesis.Settings.quick()
87         .withSeed(seed)
88         .withDatabase("zig-out/hypothesis-failures/reticulum");
89     try hypothesis.checkNamed(CipherRoundTrip, "reticulum-cipher-round-trip", settings);
90 }
91 
92 test "property: Reticulum identity decrypt is total on arbitrary bytes" {
93     const settings = hypothesis.Settings.quick()
94         .withSeed(seed ^ 0xffff_ffff_ffff_ffff)
95         .withDatabase("zig-out/hypothesis-failures/reticulum");
96     try hypothesis.checkNamed(ArbitraryCiphertext, "reticulum-cipher-total", settings);
97 }