Skip to documentation
SLOP

tiny.reticulum.identity.known

Reference tiny.reticulum identity known

Defined in identity.

API (38)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

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

Source

Called byCallsNo direct callstest sourcelib.reticulum.src.identity.known.test_Reticul...py:101-160 recalls and replaces ident...test sourcelib.reticulum.src.identity.knowntest: known identities admit maximum ...node.Nodeactivateidentity.known.Identitiesactivate
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.reticulum.src.identity.known.test_Reticul...py:101-160 recalls and replaces ident...test sourcelib.reticulum.src.identity.knowntest: known identities admit maximum ...identity.known.Identitiescount
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.reticulum.src.identity.known.test_Reticul...py:101-160 recalls and replaces ident...test sourcelib.reticulum.src.identity.knowntest: known identities admit maximum ...node.Nodedeinitidentity.known.Identitiesdeinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.reticulum.src.identity.known.test_Reticul...py:101-160 recalls and replaces ident...test sourcelib.reticulum.src.identity.knowntest: known identities admit maximum ...private sourcelib.reticulum.src.node.ownerinitSubownersidentity.known.Identitiesinit
Static calls · unresolved targets: 1 · external targets: 0.
Called byCallsNo direct callstest sourcelib.reticulum.src.identity.known.test_Reticul...py:101-160 recalls and replaces ident...test sourcelib.reticulum.src.identity.knowntest: known identities admit maximum ...identity.known.Identitiesrecall
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.reticulum.src.identity.known.test_Reticul...py:101-160 recalls and replaces ident...identity.known.IdentitiesrecallByIdentityHash
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallstest sourcelib.reticulum.src.identity.known.test_Reticul...py:101-160 recalls and replaces ident...test sourcelib.reticulum.src.identity.knowntest: known identities admit maximum ...private sourcelib.reticulum.src.identity.known.Identitiesmutableprivate sourcelib.reticulum.src.identity.known.Identitiesreplacementidentity.known.Identitiesremember
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.reticulum.src.identity.known.test_Reticul...py:69,463,494 expires peer ratchets e...test sourcelib.reticulum.src.identity.knowntest: known ratchets admit maximum an...node.Nodeactivateidentity.known.Ratchetsactivate
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.reticulum.src.identity.known.test_Reticul...py:69,463,494 expires peer ratchets e...private sourcelib.reticulum.src.identity.knownexpiredidentity.known.Ratchetsclean
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.reticulum.src.identity.known.test_Reticul...py:69,463,494 expires peer ratchets e...test sourcelib.reticulum.src.identity.knowntest: known ratchets admit maximum an...identity.known.Ratchetscount
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.reticulum.src.identity.known.test_Reticul...py:69,463,494 expires peer ratchets e...test sourcelib.reticulum.src.identity.knowntest: known ratchets admit maximum an...node.Nodedeinitidentity.known.Ratchetsdeinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.reticulum.src.identity.known.test_Reticul...py:69,463,494 expires peer ratchets e...test sourcelib.reticulum.src.identity.knowntest: known ratchets admit maximum an...private sourcelib.reticulum.src.identity.knownexpiredidentity.known.Ratchetsget
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.reticulum.src.identity.known.test_Reticul...py:69,463,494 expires peer ratchets e...test sourcelib.reticulum.src.identity.knowntest: known ratchets admit maximum an...private sourcelib.reticulum.src.node.ownerinitSubownersidentity.known.Ratchetsinit
Static calls · unresolved targets: 1 · external targets: 0.
Called byCallstest sourcelib.reticulum.src.identity.known.test_Reticul...py:69,463,494 expires peer ratchets e...test sourcelib.reticulum.src.identity.knowntest: known ratchets admit maximum an...private sourcelib.reticulum.src.identity.known.Ratchetsmutableprivate sourcelib.reticulum.src.identity.known.Ratchetsreplacementidentity.known.Ratchetsremember
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/reticulum/src/identity/known.zig

zig
const std = @import("std");const alloc_phase = @import("alloc_phase");const reticulum = @import("../root.zig");const destination = reticulum.destination;const packet = reticulum.packet;/// 333 bytes: the most application data one remembered announce keeps, the room/// a 19-byte-header announce leaves for it, so a caller sizes that application/// data, following Reticulum@1.5.0 RNS/Identity.py:511-578.pub const app_bytes_max: usize = destination.announce.received_app_bytes_max;/// 30 days in seconds: after that long a peer's rotating key stops being handed/// out, so a caller works out how long that key stays usable, following/// Reticulum@1.5.0 RNS/Identity.py:69.pub const ratchet_expiry: u64 = 60 * 60 * 24 * 30;pub const IdentityEntry = struct {    destination_hash: [16]u8,    public_key: reticulum.identity.KeyBytes,    announce_packet_hash: packet.Hash,    received: u64,    app_len: u16,    app_data: [app_bytes_max]u8,    pub fn applicationData(self: *const IdentityEntry) []const u8 {        std.debug.assert(self.app_len <= app_bytes_max);        return self.app_data[0..self.app_len];    }    pub fn public(self: *const IdentityEntry) reticulum.identity.Public {        return reticulum.identity.Public.fromBytes(self.public_key);    }};const IdentityLimits = struct {    identities_max: usize,};const IdentityCapacity = struct {    identities_max: usize,    storage_bytes: usize,    pub const DeriveError = error{ InvalidLimit, CapacityOverflow };    pub fn derive(limits: IdentityLimits) DeriveError!IdentityCapacity {        if (limits.identities_max == 0) return error.InvalidLimit;        const identities_max = limits.identities_max;        const storage_bytes = alloc_phase.capacity.mul(            usize,            identities_max,            @sizeOf(IdentityEntry),        ) catch return error.CapacityOverflow;        return .{ .identities_max = identities_max, .storage_bytes = storage_bytes };    }};pub const RememberIdentity = struct {    destination_hash: [16]u8,    public_key: reticulum.identity.KeyBytes,    announce_packet_hash: packet.Hash,    received: u64,    app_data: []const u8 = &.{},};/// The bounded store of what the node learned about other destinations from/// their announcements, so a node looks a peer up here before it encrypts to/// that peer or checks a signature from it, following Reticulum@1.5.0/// RNS/Identity.py:101-160. One entry holds the destination hash, the 64 public/// bytes of the identity, the hash of the announce it came from, the second it/// arrived, and that announce's application data. An announce for a destination/// already stored overwrites its entry, and once the store is full the entry/// that arrived earliest is the one overwritten. Remembering application data/// longer than 333 bytes returns `error.AppDataTooLong` and leaves the store as/// it was.pub const Identities = struct {    phase: alloc_phase.capacity.Phase,    capacity: Capacity,    storage: Storage,    entries: []IdentityEntry,    len: usize = 0,    pub const storage_alignment: usize = 8;    pub const Storage = []align(storage_alignment) u8;    pub const Limits: type = IdentityLimits;    pub const Capacity: type = IdentityCapacity;    pub const InitError = Capacity.DeriveError || error{StorageLengthMismatch};    pub const RememberError = error{AppDataTooLong};    pub const work_limits: alloc_phase.capacity.WorkLimits = .{        .transition_steps_max = 65_536,        .cleanup_steps_per_call_max = 0,        .cleanup_calls_at_capacity_max = 0,    };    pub const claim: alloc_phase.capacity.Declaration = .{        .source = .{            .id = "reticulum.known_identities",            .kind = .phase_static,            .limit_source = .caller,            .storage = .{                .covered = &.{.{                    .id = "caller_known_identity_table",                    .lifetime = .transferred,                    .detail = "caller storage for known peer identities and announce data",                }},                .excluded = &.{                    "borrowed lookup hashes",                    "persistent identity records and storage effects",                },            },            .capacity = .{                .inputs = &.{alloc_phase.capacity.bindInput(                    IdentityLimits,                    "identities_max",                    "identities_max",                )},                .type_selectors = &.{alloc_phase.capacity.bindType(                    IdentityEntry,                    "identity",                )},                .nodes = &.{                    .{ .input = 0 },                    .{ .scale = .{                        .node = 0,                        .coefficient = .{ .size_of_concrete_type = 0 },                    } },                },                .assertions = &.{.{                    .scope = .closure_total,                    .measure = .retained,                    .relation = .exact,                    .expression = 1,                }},            },            .overload = .{                .kind = .not_applicable,                .detail = "a new identity replaces the oldest identity when full",            },            .risks = .{                .transitive = .{                    .status = .excluded,                    .detail = "known identity operations call no allocating owner",                },                .foreign = .{                    .status = .excluded,                    .detail = "known identity storage crosses no foreign boundary",                },            },            .work = .{ .equation = "operations scan at most identities_max entries" },            .obligations = &.{                .{ .key = "reticulum_known_identities_capacity", .role = .capacity_model },                .{ .key = "reticulum_known_identities_replace", .role = .overload },                .{ .key = "reticulum_known_identities_work", .role = .work_bound },            },        },        .bindings = .{            .owner = @This(),            .seal = .{                .family = alloc_phase.capacity.selector(@This().activate),                .premise = .{ .class = .checked_semantic_fact, .authority = .checker },            },            .teardown = .{                .family = alloc_phase.capacity.selector(@This().deinit),                .premise = .{ .class = .checked_semantic_fact, .authority = .checker },            },        },    };    pub fn init(storage: Storage, limits: Limits) InitError!Identities {        const capacity = try Capacity.derive(limits);        if (storage.len != capacity.storage_bytes) return error.StorageLengthMismatch;        return .{            .phase = .initialization,            .capacity = capacity,            .storage = storage,            .entries = std.mem.bytesAsSlice(IdentityEntry, storage),        };    }    pub fn activate(self: *Identities) void {        std.debug.assert(self.phase == .initialization);        std.debug.assert(self.len == 0);        self.phase = .steady;    }    pub fn remember(self: *Identities, value: RememberIdentity) RememberError!void {        std.debug.assert(self.phase == .steady);        if (value.app_data.len > app_bytes_max) return error.AppDataTooLong;        const target = self.mutable(value.destination_hash) orelse self.replacement();        target.* = .{            .destination_hash = value.destination_hash,            .public_key = value.public_key,            .announce_packet_hash = value.announce_packet_hash,            .received = value.received,            .app_len = @intCast(value.app_data.len),            .app_data = @splat(0),        };        @memcpy(target.app_data[0..value.app_data.len], value.app_data);    }    pub fn recall(self: *const Identities, hash: [16]u8) ?*const IdentityEntry {        std.debug.assert(self.phase == .steady);        for (self.entries[0..self.len]) |*entry| {            if (std.mem.eql(u8, &entry.destination_hash, &hash)) return entry;        }        return null;    }    /// Returns the stored entry whose identity hashes to the given identity    /// hash, so a caller holding an identity hash finds the entry it names,    /// following Reticulum@1.5.0 RNS/Identity.py:129-141. The call hashes each    /// stored key in turn, so it costs one pass over the store. The call    /// returns null when no entry matches.    pub fn recallByIdentityHash(        self: *const Identities,        identity_hash: [16]u8,    ) ?*const IdentityEntry {        std.debug.assert(self.phase == .steady);        for (self.entries[0..self.len]) |*entry| {            const candidate = reticulum.hash.truncated(&entry.public_key);            if (std.mem.eql(u8, &candidate, &identity_hash)) return entry;        }        return null;    }    pub fn count(self: *const Identities) usize {        std.debug.assert(self.phase == .steady);        return self.len;    }    pub fn deinit(self: *Identities) Storage {        std.debug.assert(self.phase == .steady);        self.phase = .teardown;        const storage = self.storage;        self.* = undefined;        return storage;    }    fn mutable(self: *Identities, hash: [16]u8) ?*IdentityEntry {        for (self.entries[0..self.len]) |*entry| {            if (std.mem.eql(u8, &entry.destination_hash, &hash)) return entry;        }        return null;    }    fn replacement(self: *Identities) *IdentityEntry {        if (self.len < self.capacity.identities_max) {            const target = &self.entries[self.len];            self.len += 1;            return target;        }        var oldest: usize = 0;        for (1..self.len) |index| {            if (self.entries[index].received < self.entries[oldest].received) oldest = index;        }        return &self.entries[oldest];    }};pub const RatchetEntry = struct {    destination_hash: [16]u8,    public_key: [32]u8,    received: u64,};const RatchetLimits = struct {    ratchets_max: usize,};const RatchetCapacity = struct {    ratchets_max: usize,    storage_bytes: usize,    pub const DeriveError = error{ InvalidLimit, CapacityOverflow };    pub fn derive(limits: RatchetLimits) DeriveError!RatchetCapacity {        if (limits.ratchets_max == 0) return error.InvalidLimit;        const ratchets_max = limits.ratchets_max;        const storage_bytes = alloc_phase.capacity.mul(            usize,            ratchets_max,            @sizeOf(RatchetEntry),        ) catch return error.CapacityOverflow;        return .{ .ratchets_max = ratchets_max, .storage_bytes = storage_bytes };    }};pub const Ratchets = struct {    phase: alloc_phase.capacity.Phase,    capacity: Capacity,    storage: Storage,    entries: []RatchetEntry,    len: usize = 0,    pub const storage_alignment: usize = 8;    pub const Storage = []align(storage_alignment) u8;    pub const Limits: type = RatchetLimits;    pub const Capacity: type = RatchetCapacity;    pub const InitError = Capacity.DeriveError || error{StorageLengthMismatch};    pub const work_limits: alloc_phase.capacity.WorkLimits = .{        .transition_steps_max = 65_536,        .cleanup_steps_per_call_max = 65_536,        .cleanup_calls_at_capacity_max = 1,    };    pub const claim: alloc_phase.capacity.Declaration = .{        .source = .{            .id = "reticulum.known_ratchets",            .kind = .phase_static,            .limit_source = .caller,            .storage = .{                .covered = &.{.{                    .id = "caller_known_ratchet_table",                    .lifetime = .transferred,                    .detail = "caller storage for known peer rotating public keys",                }},                .excluded = &.{                    "borrowed destination hashes",                    "persistent rotating-key records and storage effects",                },            },            .capacity = .{                .inputs = &.{alloc_phase.capacity.bindInput(                    RatchetLimits,                    "ratchets_max",                    "ratchets_max",                )},                .type_selectors = &.{alloc_phase.capacity.bindType(                    RatchetEntry,                    "ratchet",                )},                .nodes = &.{                    .{ .input = 0 },                    .{ .scale = .{                        .node = 0,                        .coefficient = .{ .size_of_concrete_type = 0 },                    } },                },                .assertions = &.{.{                    .scope = .closure_total,                    .measure = .retained,                    .relation = .exact,                    .expression = 1,                }},            },            .overload = .{                .kind = .not_applicable,                .detail = "a new peer key replaces the oldest peer key when full",            },            .risks = .{                .transitive = .{                    .status = .excluded,                    .detail = "known rotating-key operations call no allocating owner",                },                .foreign = .{                    .status = .excluded,                    .detail = "known rotating-key storage crosses no foreign boundary",                },            },            .work = .{ .equation = "operations scan at most ratchets_max entries" },            .obligations = &.{                .{ .key = "reticulum_known_ratchets_capacity", .role = .capacity_model },                .{ .key = "reticulum_known_ratchets_replace", .role = .overload },                .{ .key = "reticulum_known_ratchets_work", .role = .work_bound },            },        },        .bindings = .{            .owner = @This(),            .seal = .{                .family = alloc_phase.capacity.selector(@This().activate),                .premise = .{ .class = .checked_semantic_fact, .authority = .checker },            },            .teardown = .{                .family = alloc_phase.capacity.selector(@This().deinit),                .premise = .{ .class = .checked_semantic_fact, .authority = .checker },            },        },    };    pub fn init(storage: Storage, limits: Limits) InitError!Ratchets {        const capacity = try Capacity.derive(limits);        if (storage.len != capacity.storage_bytes) return error.StorageLengthMismatch;        return .{            .phase = .initialization,            .capacity = capacity,            .storage = storage,            .entries = std.mem.bytesAsSlice(RatchetEntry, storage),        };    }    pub fn activate(self: *Ratchets) void {        std.debug.assert(self.phase == .initialization);        std.debug.assert(self.len == 0);        self.phase = .steady;    }    /// Stores a peer's rotating key against its destination hash, so a node    /// records the rotating key it read out of a peer's announce, following    /// Reticulum@1.5.0 RNS/Identity.py:410-425. A key already stored for that    /// destination is overwritten, and once the store is full the key that    /// arrived earliest is the one overwritten.    pub fn remember(self: *Ratchets, value: RatchetEntry) void {        std.debug.assert(self.phase == .steady);        const target = self.mutable(value.destination_hash) orelse self.replacement();        target.* = value;    }    /// Returns a peer's rotating key against its destination hash when one is    /// stored and has yet to expire, so a sender asks whether it has a rotating    /// key fresh enough to encrypt to, following Reticulum@1.5.0    /// RNS/Identity.py:485-508. A key that arrived more than 30 days before the    /// given second gives null. The call returns null when no key is stored for    /// that destination.    pub fn get(self: *const Ratchets, hash: [16]u8, now: u64) ?[32]u8 {        std.debug.assert(self.phase == .steady);        for (self.entries[0..self.len]) |entry| {            if (!std.mem.eql(u8, &entry.destination_hash, &hash)) continue;            if (expired(entry.received, now)) return null;            return entry.public_key;        }        return null;    }    pub fn clean(self: *Ratchets, now: u64) void {        std.debug.assert(self.phase == .steady);        var index: usize = 0;        for (0..self.capacity.ratchets_max) |_| {            if (index == self.len) return;            if (!expired(self.entries[index].received, now)) {                index += 1;                continue;            }            std.mem.copyForwards(                RatchetEntry,                self.entries[index .. self.len - 1],                self.entries[index + 1 .. self.len],            );            self.len -= 1;        }    }    pub fn count(self: *const Ratchets) usize {        std.debug.assert(self.phase == .steady);        return self.len;    }    pub fn deinit(self: *Ratchets) Storage {        std.debug.assert(self.phase == .steady);        self.phase = .teardown;        const storage = self.storage;        self.* = undefined;        return storage;    }    fn mutable(self: *Ratchets, hash: [16]u8) ?*RatchetEntry {        for (self.entries[0..self.len]) |*entry| {            if (std.mem.eql(u8, &entry.destination_hash, &hash)) return entry;        }        return null;    }    fn replacement(self: *Ratchets) *RatchetEntry {        if (self.len < self.capacity.ratchets_max) {            const target = &self.entries[self.len];            self.len += 1;            return target;        }        var oldest: usize = 0;        for (1..self.len) |index| {            if (self.entries[index].received < self.entries[oldest].received) oldest = index;        }        return &self.entries[oldest];    }};fn expired(received: u64, now: u64) bool {    if (now < received) return false;    return now - received >= ratchet_expiry;}comptime {    alloc_phase.capacity.requireProvisionedExactOwnerShape(Identities);}comptime {    alloc_phase.capacity.requireProvisionedExactOwnerShape(Ratchets);}fn identityValue(value: u8, received: u64, app_data: []const u8) RememberIdentity {    return .{        .destination_hash = @splat(value),        .public_key = @splat(value + 1),        .announce_packet_hash = @splat(value + 2),        .received = received,        .app_data = app_data,    };}test "known identities admit maximum and evict oldest at maximum plus one" {    comptime {        @stardustClaim(alloc_phase.capacity.witness(            Identities,            "reticulum_known_identities_capacity",        ), null, null, null, null, null, null);        @stardustClaim(alloc_phase.capacity.witness(            Identities,            "reticulum_known_identities_replace",        ), null, null, null, null, null, null);        @stardustClaim(alloc_phase.capacity.witness(            Identities,            "reticulum_known_identities_work",        ), null, null, null, null, null, null);    }    const capacity = comptime IdentityCapacity.derive(.{ .identities_max = 3 }) catch unreachable;    var bytes: [capacity.storage_bytes]u8 align(Identities.storage_alignment) = undefined;    var table = try Identities.init(&bytes, .{ .identities_max = 3 });    table.activate();    defer _ = table.deinit();    try table.remember(identityValue(1, 10, "one"));    try table.remember(identityValue(2, 20, "two"));    try table.remember(identityValue(3, 30, "three"));    try table.remember(identityValue(4, 40, "four"));    try std.testing.expect(table.recall(@splat(1)) == null);    try std.testing.expectEqual(@as(usize, 3), table.count());}test "Reticulum@1.5.0 RNS/Identity.py:101-160 recalls and replaces identities" {    const capacity = comptime IdentityCapacity.derive(.{ .identities_max = 3 }) catch unreachable;    var bytes: [capacity.storage_bytes]u8 align(Identities.storage_alignment) = undefined;    var table = try Identities.init(&bytes, .{ .identities_max = 3 });    table.activate();    defer _ = table.deinit();    try table.remember(identityValue(1, 10, "old"));    const original = table.recall(@splat(1)).?;    const identity_hash = reticulum.hash.truncated(&original.public_key);    try std.testing.expect(table.recallByIdentityHash(identity_hash) != null);    try table.remember(identityValue(1, 20, "new"));    try std.testing.expectEqualSlices(u8, "new", table.recall(@splat(1)).?.applicationData());    try std.testing.expectEqual(@as(usize, 1), table.count());}test "known ratchets admit maximum and evict oldest at maximum plus one" {    comptime {        @stardustClaim(alloc_phase.capacity.witness(            Ratchets,            "reticulum_known_ratchets_capacity",        ), null, null, null, null, null, null);        @stardustClaim(alloc_phase.capacity.witness(            Ratchets,            "reticulum_known_ratchets_replace",        ), null, null, null, null, null, null);        @stardustClaim(alloc_phase.capacity.witness(            Ratchets,            "reticulum_known_ratchets_work",        ), null, null, null, null, null, null);    }    const capacity = comptime RatchetCapacity.derive(.{ .ratchets_max = 3 }) catch unreachable;    var bytes: [capacity.storage_bytes]u8 align(Ratchets.storage_alignment) = undefined;    var table = try Ratchets.init(&bytes, .{ .ratchets_max = 3 });    table.activate();    defer _ = table.deinit();    for (1..5) |value| table.remember(.{        .destination_hash = @splat(@as(u8, @intCast(value))),        .public_key = @splat(@as(u8, @intCast(value + 10))),        .received = value,    });    try std.testing.expect(table.get(@splat(1), 4) == null);    try std.testing.expectEqual(@as(usize, 3), table.count());}test "Reticulum@1.5.0 RNS/Identity.py:69,463,494 expires peer ratchets exactly" {    const capacity = comptime RatchetCapacity.derive(.{ .ratchets_max = 3 }) catch unreachable;    var bytes: [capacity.storage_bytes]u8 align(Ratchets.storage_alignment) = undefined;    var table = try Ratchets.init(&bytes, .{ .ratchets_max = 3 });    table.activate();    defer _ = table.deinit();    table.remember(.{ .destination_hash = @splat(1), .public_key = @splat(2), .received = 10 });    try std.testing.expect(table.get(@splat(1), 10 + ratchet_expiry - 1) != null);    try std.testing.expect(table.get(@splat(1), 10 + ratchet_expiry) == null);    table.clean(10 + ratchet_expiry);    try std.testing.expectEqual(@as(usize, 0), table.count());}

Source: lib/reticulum/src/identity/root.zig:59

zig
pub const known = @import("known.zig");

Audit

Definitions39
Public names39
Members25
Version26.7.0
Revisiondaab053ee433