Skip to documentation
SLOP

tiny.reticulum.interface.ifac

Reference tiny.reticulum interface ifac

Defined in interface.

API (17)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

No direct callersNo direct callsinterfaceifac
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallsNo direct callsprivate sourcelib.reticulum.src.interface.testexpectCasetest sourcelib.reticulum.src.interface.test.test_Reticul...py:152 IFAC size domain rejects boundsprivate sourcelib.reticulum.src.properties.ifacdrawSizeinterface.ifac.SizefromByte
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate sourcelib.reticulum.src.interface.testexpectCasetest sourcelib.reticulum.src.interface.test.test_Reticul...py:1250 IFAC admits MTU and rejects m...private sourcelib.reticulum.src.node.outboundprotectprivate sourcelib.reticulum.src.properties.ifac.RoundTrippropertyprivate sourcelib.reticulum.src.interface.ifacoverlapsinterface.ifacapply
Static calls · unresolved targets: 0 · external targets: 5.
Called byCallsNo direct callsprivate sourcelib.reticulum.src.interface.testexpectCasetest sourcelib.reticulum.src.interface.test.test_Reticul...py:989 IFAC needs network materialtest sourcelib.reticulum.src.interface.test.test_Reticul...py:1250 IFAC admits MTU and rejects m...private sourcelib.reticulum.src.node.fixture.testcodesinterface.ifacderive
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callsinterface.ifacstriptest sourcelib.reticulum.src.interface.test.test_Reticul...py:1693 IFAC flag detectiontest sourcelib.reticulum.src.node.fixture.test.test_Reti...py:1244-1276 relays every link frame ...test sourcelib.reticulum.src.node.fixture.test.test_Reti...py:1244-1276 relays the same frames b...private sourcelib.reticulum.src.node.inboundauthenticatedFrameinterface.ifachasFlag
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate sourcelib.reticulum.src.interface.testexpectStripprivate sourcelib.reticulum.src.node.fixture.testexpectFrameprivate sourcelib.reticulum.src.node.inboundauthenticatedFrameprivate sourcelib.reticulum.src.properties.ifac.RoundTrippropertyprivate sourcelib.reticulum.src.properties.ifac.TotalStrippropertyprivate sourcelib.reticulum.src.interface.ifacequalCodeinterface.ifachasFlagprivate sourcelib.reticulum.src.interface.ifacoverlapsinterface.ifacstrip
Static calls · unresolved targets: 0 · external targets: 5.

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

Definitions18
Public names18
Members75
Version26.7.0
Revisiondaab053ee433