lib/reticulum/src/wire/hash.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const flags = @import("flags.zig");
3 const header = @import("header.zig");
4
5 const Sha256 = std.crypto.hash.sha2.Sha256;
6 const assert = std.debug.assert;
7
8 pub const HashError = error{PacketTooShort};
9
10 fn suffixOffset(bytes: []const u8) HashError!usize {
11 if (bytes.len < 1) return error.PacketTooShort;
12 const header_type = flags.Flags.decode(bytes[0]).header;
13 if (bytes.len < header.headerLength(header_type)) return error.PacketTooShort;
14 return switch (header_type) {
15 .one => 2,
16 .two => 18,
17 };
18 }
19
20 /// Returns how many bytes a packet hash covers: one for the masked flags byte
21 /// plus everything from the destination hash onward, so a caller can size a
22 /// buffer or check a bound before it hashes a packet, following Reticulum@1.5.0
23 /// RNS/Packet.py:353-357. The call returns `error.PacketTooShort` for an empty
24 /// input, and for bytes shorter than the header their flags byte claims.
25 pub fn hashableLength(bytes: []const u8) HashError!usize {
26 const offset = try suffixOffset(bytes);
27 assert(offset <= bytes.len);
28 return 1 + bytes.len - offset;
29 }
30
31 /// Returns the 32-byte SHA-256 digest that names one Reticulum datagram
32 /// (*packet*), so a caller can match the proof sent back for a packet to the
33 /// packet it answers or recognize one it has already handled. The digest covers
34 /// the low four bits of the packet's first byte followed by the destination
35 /// hash onward, which leaves out the hop count and the 16 bytes a carrying node
36 /// inserts, so the name holds still at every node the packet crosses. The two
37 /// pieces stream into the hash state where they lie, so the call copies the
38 /// hashable part nowhere. The call returns `error.PacketTooShort` for an empty
39 /// input, and for bytes shorter than the header their flags byte claims.
40 pub fn full(bytes: []const u8) HashError![32]u8 {
41 const offset = try suffixOffset(bytes);
42 assert(offset <= bytes.len);
43 const first = [1]u8{bytes[0] & 0x0f};
44 var state = Sha256.init(.{});
45 state.update(&first);
46 state.update(bytes[offset..]);
47 var result: [32]u8 = undefined;
48 state.final(&result);
49 return result;
50 }
51
52 /// Returns the first 16 bytes of the full hash of one Reticulum datagram
53 /// (*packet*), the width carried as the destination so a caller can address the
54 /// proof sent back for a packet, following Reticulum@1.5.0
55 /// RNS/Packet.py:349-351.
56 pub fn truncated(bytes: []const u8) HashError![16]u8 {
57 const result = try full(bytes);
58 return result[0..16].*;
59 }