tiny.reticulum.interface.ifac
Defined in interface.
API (17)
Actions
Public operations.
Size.byteSize.fromByteapply: Writes the authenticated frame intooutand returns it, so a node authenticates every frame it puts on a closed carrier, following Reticulum@1.5.0 RNS/Transport.py:1244-1276.derive: Returns the 64-byte interface key for a network name and a network key, so every node on a network derives the same key from that name, following Reticulum@1.5.0 RNS/Reticulum.py:989-1002.hasFlag: Returns whether a frame's first byte has its top bit set, which says the frame carries an access code, so a node sorts arriving frames by whether they carry a code before trying to check one, following Reticulum@1.5.0 RNS/Transport.py:1693-1694.strip: Writes the packet back intooutand returns it, once the code checks out, so a node checks every frame arriving on a closed carrier and recovers the packet inside it, following Reticulum@1.5.0 RNS/Transport.py:1648-1687.
Types and contracts
Public types and contracts.
ApplyErrorDeriveErrorKey: The 64 bytes that authenticate every frame on one carrier, so a caller holds one per closed carrier and passes it to both directions, following Reticulum@1.5.0 RNS/Reticulum.py:999-1002.Size: How many bytes of the signature a frame carries as its code, from one byte through a whole 64-byte Ed25519 signature, following Reticulum@1.5.0 RNS/Reticulum.py:152 and Reticulum@1.5.0 RNS/Transport.py:1247.StripError
Values and defaults
Public values and defaults.
default_size: 16 bytes, the code size the reference uses unless an operator sets another, and the size a caller gets when it states none, following Reticulum@1.5.0 RNS/Interfaces/Interface.py:96.key_bytesmax_frame_bytes: 564 bytes, the longest frame this scheme produces, a 500-byte packet plus a 64-byte code, so a caller sizes the buffer it writes an authenticated frame into, following Reticulum@1.5.0 RNS/Transport.py:1247-1250.max_size_bytesmin_size_bytessalt: The 32 fixed bytes that salt the derivation of an interface key, for a caller reproducing the derivation outside this package, following Reticulum@1.5.0 RNS/Reticulum.py:153.
Source
Source: lib/reticulum/src/interface/ifac.zig
zig
const std = @import("std");const reticulum = @import("../root.zig");/// The 64 bytes that authenticate every frame on one carrier, so a caller holds/// one per closed carrier and passes it to both directions, following/// Reticulum@1.5.0 RNS/Reticulum.py:999-1002. The same bytes both sign the code/// and key the mask.pub const Key = reticulum.identity.KeyBytes;pub const key_bytes: usize = reticulum.identity.key_bytes;pub const min_size_bytes: u7 = 1;pub const max_size_bytes: u7 = 64;/// How many bytes of the signature a frame carries as its code, from one byte/// through a whole 64-byte Ed25519 signature, following Reticulum@1.5.0/// RNS/Reticulum.py:152 and Reticulum@1.5.0 RNS/Transport.py:1247. A byte/// outside that range gives null.pub const Size = enum(u7) { bytes_1 = 1, bytes_2 = 2, bytes_3 = 3, bytes_4 = 4, bytes_5 = 5, bytes_6 = 6, bytes_7 = 7, bytes_8 = 8, bytes_9 = 9, bytes_10 = 10, bytes_11 = 11, bytes_12 = 12, bytes_13 = 13, bytes_14 = 14, bytes_15 = 15, bytes_16 = 16, bytes_17 = 17, bytes_18 = 18, bytes_19 = 19, bytes_20 = 20, bytes_21 = 21, bytes_22 = 22, bytes_23 = 23, bytes_24 = 24, bytes_25 = 25, bytes_26 = 26, bytes_27 = 27, bytes_28 = 28, bytes_29 = 29, bytes_30 = 30, bytes_31 = 31, bytes_32 = 32, bytes_33 = 33, bytes_34 = 34, bytes_35 = 35, bytes_36 = 36, bytes_37 = 37, bytes_38 = 38, bytes_39 = 39, bytes_40 = 40, bytes_41 = 41, bytes_42 = 42, bytes_43 = 43, bytes_44 = 44, bytes_45 = 45, bytes_46 = 46, bytes_47 = 47, bytes_48 = 48, bytes_49 = 49, bytes_50 = 50, bytes_51 = 51, bytes_52 = 52, bytes_53 = 53, bytes_54 = 54, bytes_55 = 55, bytes_56 = 56, bytes_57 = 57, bytes_58 = 58, bytes_59 = 59, bytes_60 = 60, bytes_61 = 61, bytes_62 = 62, bytes_63 = 63, bytes_64 = 64, pub fn fromByte(value: u8) ?Size { if (value < min_size_bytes) return null; if (value > max_size_bytes) return null; return std.enums.fromInt(Size, @as(u7, @intCast(value))); } pub fn byte(self: Size) u7 { return @backingInt(self); }};/// 16 bytes, the code size the reference uses unless an operator sets another,/// and the size a caller gets when it states none, following Reticulum@1.5.0/// RNS/Interfaces/Interface.py:96.pub const default_size = Size.bytes_16;/// The 32 fixed bytes that salt the derivation of an interface key, for a/// caller reproducing the derivation outside this package, following/// Reticulum@1.5.0 RNS/Reticulum.py:153.pub const salt: [32]u8 = .{ 0xad, 0xf5, 0x4d, 0x88, 0x2c, 0x9a, 0x9b, 0x80, 0x77, 0x1e, 0xb4, 0x99, 0x5d, 0x70, 0x2d, 0x4a, 0x3e, 0x73, 0x33, 0x91, 0xb2, 0xa0, 0xf5, 0x3f, 0x41, 0x6d, 0x9f, 0x90, 0x7e, 0x55, 0xcf, 0xf8,};/// 564 bytes, the longest frame this scheme produces, a 500-byte packet plus a/// 64-byte code, so a caller sizes the buffer it writes an authenticated frame/// into, following Reticulum@1.5.0 RNS/Transport.py:1247-1250.pub const max_frame_bytes: u16 = reticulum.wire.mtu + max_size_bytes;pub const DeriveError = error{NoNetwork};pub const ApplyError = error{ OutputTooSmall, OverlappingBuffers, PacketTooLarge, PacketTooShort,};pub const StripError = error{ InvalidCode, MissingFlag, OutputTooSmall, OverlappingBuffers, PacketTooLarge, Truncated,};/// Returns the 64-byte interface key for a network name and a network key, so/// every node on a network derives the same key from that name, following/// Reticulum@1.5.0 RNS/Reticulum.py:989-1002. The key derives from the SHA-256/// digest of each value the caller passed, joined in that order, hashed again,/// then stretched under the fixed salt. The call returns `error.NoNetwork` when/// the caller omits both the name and the key.pub fn derive(netname: ?[]const u8, netkey: ?[]const u8) DeriveError!Key { if (netname == null and netkey == null) return error.NoNetwork; var origin: [reticulum.hash.full_bytes * 2]u8 = undefined; var origin_length: usize = 0; if (netname) |value| { const digest = reticulum.hash.full(value); @memcpy(origin[origin_length..][0..digest.len], &digest); origin_length += digest.len; } if (netkey) |value| { const digest = reticulum.hash.full(value); @memcpy(origin[origin_length..][0..digest.len], &digest); origin_length += digest.len; } std.debug.assert(origin_length >= reticulum.hash.full_bytes); std.debug.assert(origin_length <= origin.len); const origin_hash = reticulum.hash.full(origin[0..origin_length]); var key: Key = undefined; _ = reticulum.crypto.hkdf.derive(key_bytes, &origin_hash, &salt, null, &key) catch unreachable; return key;}/// Writes the authenticated frame into `out` and returns it, so a node/// authenticates every frame it puts on a closed carrier, following/// Reticulum@1.5.0 RNS/Transport.py:1244-1276. The code is the tail of an/// Ed25519 signature over the packet, taken under the interface key, and it/// sits right after the frame's first two bytes. Every other byte is combined/// with a mask stretched from the interface key and seeded with that code. The/// frame's first byte goes out with its top bit set, so a receiver knows the/// frame carries a code. The signing copy of the key is erased before the call/// returns. The call returns `error.PacketTooShort` under two bytes,/// `error.PacketTooLarge` past 500 bytes, `error.OutputTooSmall` when `out` is/// shorter than the frame, and `error.OverlappingBuffers` when `out` overlaps/// the packet.pub fn apply( key: *const Key, size: Size, raw: []const u8, out: []u8,) ApplyError![]u8 { if (raw.len < 2) return error.PacketTooShort; if (raw.len > reticulum.wire.mtu) return error.PacketTooLarge; const code_length: usize = size.byte(); std.debug.assert(code_length >= min_size_bytes); std.debug.assert(code_length <= max_size_bytes); const frame_length = std.math.add(usize, raw.len, code_length) catch return error.PacketTooLarge; std.debug.assert(frame_length >= raw.len); std.debug.assert(frame_length <= max_frame_bytes); if (out.len < frame_length) return error.OutputTooSmall; std.debug.assert(out.len >= frame_length); const frame = out[0..frame_length]; if (overlaps(raw, frame)) return error.OverlappingBuffers; var signer = reticulum.identity.Private.fromBytes(key.*); defer signer.zero(); const signature = signer.sign(raw); const code = signature[signature.len - code_length ..]; var mask: [max_frame_bytes]u8 = undefined; _ = reticulum.crypto.hkdf.derive( @intCast(frame_length), code, key, null, &mask, ) catch unreachable; frame[0] = ((raw[0] | 0x80) ^ mask[0]) | 0x80; frame[1] = raw[1] ^ mask[1]; @memcpy(frame[2..][0..code_length], code); var raw_index: usize = 2; while (raw_index < raw.len) : (raw_index += 1) { const frame_index = raw_index + code_length; frame[frame_index] = raw[raw_index] ^ mask[frame_index]; } std.debug.assert(raw_index == raw.len); return frame;}/// Writes the packet back into `out` and returns it, once the code checks out,/// so a node checks every frame arriving on a closed carrier and recovers the/// packet inside it, following Reticulum@1.5.0 RNS/Transport.py:1648-1687. The/// code the frame carries seeds the same mask, which is undone to recover the/// packet, and the first byte comes back with its top bit cleared. The/// signature is recomputed over the recovered packet and compared against the/// carried code across a fixed 64 bytes, so the comparison takes the same time/// whatever the bytes are. The signing copy of the key is erased before the/// call returns. The call returns `error.Truncated` for an empty frame or a/// frame of at most two bytes plus the code, `error.MissingFlag` when the first/// byte's top bit is clear, `error.PacketTooLarge` past 500 bytes plus the/// code, `error.OutputTooSmall`, `error.OverlappingBuffers`, and/// `error.InvalidCode` when the recomputed signature disagrees.pub fn strip( key: *const Key, size: Size, masked: []const u8, out: []u8,) StripError![]u8 { if (masked.len == 0) return error.Truncated; if (!hasFlag(masked[0])) return error.MissingFlag; const code_length: usize = size.byte(); std.debug.assert(code_length >= min_size_bytes); std.debug.assert(code_length <= max_size_bytes); const prefix_length = 2 + code_length; if (masked.len <= prefix_length) return error.Truncated; if (masked.len > reticulum.wire.mtu + code_length) return error.PacketTooLarge; const raw_length = masked.len - code_length; std.debug.assert(raw_length > 2); std.debug.assert(raw_length <= reticulum.wire.mtu); if (out.len < raw_length) return error.OutputTooSmall; std.debug.assert(out.len >= raw_length); const raw = out[0..raw_length]; if (overlaps(masked, raw)) return error.OverlappingBuffers; var actual_code: [key_bytes]u8 = @splat(0); const actual_tail = actual_code[actual_code.len - code_length ..]; @memcpy(actual_tail, masked[2..][0..code_length]); var mask: [max_frame_bytes]u8 = undefined; _ = reticulum.crypto.hkdf.derive( @intCast(masked.len), actual_tail, key, null, &mask, ) catch unreachable; raw[0] = (masked[0] ^ mask[0]) & 0x7f; raw[1] = masked[1] ^ mask[1]; var masked_index: usize = prefix_length; while (masked_index < masked.len) : (masked_index += 1) { const raw_index = masked_index - code_length; raw[raw_index] = masked[masked_index] ^ mask[masked_index]; } std.debug.assert(masked_index == masked.len); var signer = reticulum.identity.Private.fromBytes(key.*); defer signer.zero(); const signature = signer.sign(raw); var expected_code: [key_bytes]u8 = @splat(0); const expected_tail = signature[signature.len - code_length ..]; @memcpy(expected_code[expected_code.len - code_length ..], expected_tail); if (!equalCode(&actual_code, &expected_code)) { return error.InvalidCode; } return raw;}/// Returns whether a frame's first byte has its top bit set, which says the/// frame carries an access code, so a node sorts arriving frames by whether/// they carry a code before trying to check one, following Reticulum@1.5.0/// RNS/Transport.py:1693-1694.pub fn hasFlag(first_byte: u8) bool { return first_byte & 0x80 == 0x80;}fn overlaps(input: []const u8, output: []u8) bool { if (input.len == 0) return false; const input_start = @intFromPtr(input.ptr); const output_start = @intFromPtr(output.ptr); const input_end = input_start + input.len; const output_end = output_start + output.len; return input_start < output_end and output_start < input_end;}fn equalCode(actual: *const [key_bytes]u8, expected: *const [key_bytes]u8) bool { var difference: u8 = 0; for (actual, expected) |actual_byte, expected_byte| { difference |= actual_byte ^ expected_byte; } return difference == 0;}comptime { std.debug.assert(@backingInt(Size.bytes_1) == min_size_bytes); std.debug.assert(@backingInt(Size.bytes_64) == max_size_bytes);}Source: lib/reticulum/src/interface/root.zig:39
zig
pub const ifac = @import("ifac.zig");Audit
| Definitions | 18 |
|---|---|
| Public names | 18 |
| Members | 75 |
| Version | 26.7.0 |
| Revision | daab053ee433 |