Skip to documentation
SLOP

tiny.reticulum.identity.ratchet

Reference tiny.reticulum identity ratchet

Defined in identity.

API (31)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

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

Source

Called byCallstest sourcelib.reticulum.src.identity.testtest: rotating-key ring proves caller...private sourcelib.ui.src.tree.capacitymulidentity.ratchet.Capacityderive
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsidentity.Ratchetgenerateidentity.RatchetfromBytes
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersidentity.RatchetfromBytesidentity.Ratchetgenerate
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersidentity.RatchetpublicBytesidentity.Ratchetid
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callsidentity.Ratchetididentity.RatchetpublicBytes
Static calls · unresolved targets: 1 · external targets: 0.
Called byCallsNo direct callersprivate sourcelib.reticulum.src.identity.ratchet.Ringslotsidentity.Ringall
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsidentity.RinglatestIdprivate sourcelib.reticulum.src.identity.ratchet.Ringslotsidentity.Ringlatest
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersidentity.Ringlatestidentity.RinglatestId
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callersprivate sourcelib.reticulum.src.identity.ratchet.RingprovisionedSlotsMutidentity.Ringrestore
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate sourcelib.reticulum.src.identity.ratchet.RingslotsMutidentity.Ringrotate
Static calls · unresolved targets: 0 · external targets: 0.

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

zig
const alloc_phase = @import("alloc_phase");const std = @import("std");const reticulum = @import("../root.zig");const capacity = alloc_phase.capacity;const X25519 = std.crypto.dh.X25519;pub const Seconds = u64;/// 512, the number of retained keys the reference gives a destination by/// default, following Reticulum@1.5.0 RNS/Destination.py:85.pub const reference_retained_max: u16 = 512;/// 1,800 seconds, the rotation interval the reference gives a destination by/// default, following Reticulum@1.5.0 RNS/Destination.py:90.pub const reference_interval: Seconds = 30 * 60;/// One retained X25519 private key. A destination publishes the public half in/// its announcements and holds the key afterward, so a message a sender/// addressed to a key it has since retired still opens.pub const Ratchet = struct {    bytes: [X25519.secret_length]u8,    /// Takes 32 bytes of the caller's entropy as a retained private key,    /// following Reticulum@1.5.0 RNS/Identity.py:404-408.    pub fn fromBytes(bytes: [X25519.secret_length]u8) Ratchet {        return .{ .bytes = bytes };    }    pub fn generate(entropy: [X25519.secret_length]u8) Ratchet {        return fromBytes(entropy);    }    pub fn toBytes(self: *const Ratchet) [X25519.secret_length]u8 {        return self.bytes;    }    /// Returns the X25519 public bytes a destination publishes for this    /// retained key, following Reticulum@1.5.0 RNS/Identity.py:400-401.    pub fn publicBytes(self: *const Ratchet) [X25519.public_length]u8 {        return X25519.recoverPublicKey(self.bytes) catch unreachable;    }    /// Returns the 10-byte name hash of this key's public bytes, which names    /// the key a message was addressed to, following Reticulum@1.5.0    /// RNS/Identity.py:396-397.    pub fn id(self: *const Ratchet) [reticulum.hash.name_bytes]u8 {        const public = self.publicBytes();        return reticulum.hash.name(&public);    }    pub fn zero(self: *Ratchet) void {        std.crypto.secureZero(u8, &self.bytes);    }};pub const Limits = struct {    retained_max: u16,};pub const Capacity = struct {    retained_max: u16,    storage_bytes: usize,    pub const DeriveError = error{        InvalidLimit,        CapacityOverflow,    };    pub fn derive(limits: Limits) DeriveError!Capacity {        if (limits.retained_max == 0) return error.InvalidLimit;        const retained: usize = limits.retained_max;        const storage_bytes = capacity.mul(usize, retained, @sizeOf(Ratchet)) catch            return error.CapacityOverflow;        return .{            .retained_max = limits.retained_max,            .storage_bytes = storage_bytes,        };    }};const RingLimits = Limits;const RingCapacity = Capacity;/// The caller's storage for a destination's retained keys, held newest first./// The caller fixes how many keys the ring holds, and the storage it hands over/// has to reach the byte count derived from that number. Taking the storage/// zeroes it, and handing it back zeroes it again. Restoring more keys than the/// ring holds returns `error.TooManyRatchets`.pub const Ring = struct {    phase: capacity.Phase,    capacity: RingCapacity,    storage: Storage,    count: u16,    latest_time: ?Seconds,    pub const storage_alignment: usize = @alignOf(Ratchet);    pub const Storage = []align(storage_alignment) u8;    pub const Limits: type = RingLimits;    pub const Capacity: type = RingCapacity;    pub const InitError = RingCapacity.DeriveError || error{StorageTooShort};    pub const RestoreError = error{TooManyRatchets};    pub const work_limits: capacity.WorkLimits = .{        .transition_steps_max = 1,        .cleanup_steps_per_call_max = 0,        .cleanup_calls_at_capacity_max = 0,    };    pub const claim: capacity.Declaration = .{        .source = .{            .id = "reticulum.ratchet_ring",            .kind = .phase_static,            .limit_source = .caller,            .storage = .{                .covered = &.{.{                    .id = "retained_private_keys",                    .lifetime = .transferred,                    .detail = "caller storage for the bounded newest-first private key ring",                }},                .excluded = &.{                    "ring count and latest wall-clock seconds",                    "caller-owned restore slices and returned storage",                },            },            .capacity = .{                .inputs = &.{capacity.bindInput(RingLimits, "retained_max", "retained_max")},                .type_selectors = &.{capacity.bindType(Ratchet, "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 = "rotation replaces the oldest slot inside the exact window",            },            .risks = .{                .transitive = .{                    .status = .witnessed,                    .detail = "the corpus checks fixed-value X25519 derivation and key identifiers",                },                .foreign = .{                    .status = .excluded,                    .detail = "ring operations cross no foreign or operating-system boundary",                },            },            .work = .{ .equation = "rotation copy steps <= retained_max" },            .obligations = &.{                .{ .key = "reticulum_ratchet_ring_capacity", .role = .capacity_model },                .{ .key = "reticulum_ratchet_ring_transitive", .role = .transitive_risk },                .{ .key = "reticulum_ratchet_ring_work", .role = .work_bound },            },        },        .bindings = .{            .owner = @This(),            .seal = .{                .family = capacity.selector(@This().activate),                .premise = .{ .class = .checked_semantic_fact, .authority = .checker },            },            .teardown = .{                .family = capacity.selector(@This().deinit),                .premise = .{ .class = .checked_semantic_fact, .authority = .checker },            },        },    };    pub fn init(storage: Storage, limits: RingLimits) InitError!Ring {        const derived = try RingCapacity.derive(limits);        if (storage.len < derived.storage_bytes) return error.StorageTooShort;        const owned = storage[0..derived.storage_bytes];        std.crypto.secureZero(u8, owned);        return .{            .phase = .initialization,            .capacity = derived,            .storage = owned,            .count = 0,            .latest_time = null,        };    }    pub fn activate(self: *Ring) void {        std.debug.assert(self.phase == .initialization);        std.debug.assert(self.storage.len == self.capacity.storage_bytes);        self.phase = .steady;    }    pub fn deinit(self: *Ring) Storage {        std.debug.assert(self.phase == .steady);        std.crypto.secureZero(u8, self.storage);        self.phase = .teardown;        const owned = self.storage;        self.* = undefined;        return owned;    }    fn slots(self: *const Ring) []const Ratchet {        std.debug.assert(self.phase == .steady);        return std.mem.bytesAsSlice(Ratchet, self.storage);    }    fn slotsMut(self: *Ring) []Ratchet {        std.debug.assert(self.phase == .steady);        return std.mem.bytesAsSlice(Ratchet, self.storage);    }    fn provisionedSlotsMut(self: *Ring) []Ratchet {        std.debug.assert(self.phase != .teardown);        return std.mem.bytesAsSlice(Ratchet, self.storage);    }    pub fn latest(self: *const Ring) ?*const Ratchet {        if (self.count == 0) return null;        return &self.slots()[0];    }    pub fn latestId(self: *const Ring) ?[reticulum.hash.name_bytes]u8 {        const value = self.latest() orelse return null;        return value.id();    }    pub fn all(self: *const Ring) []const Ratchet {        const count: usize = self.count;        return self.slots()[0..count];    }    pub fn restore(        self: *Ring,        ratchets: []const Ratchet,        latest_time: Seconds,    ) RestoreError!void {        const retained: usize = self.capacity.retained_max;        if (ratchets.len > retained) return error.TooManyRatchets;        const owned = self.provisionedSlotsMut();        @memmove(owned[0..ratchets.len], ratchets);        std.crypto.secureZero(u8, std.mem.sliceAsBytes(owned[ratchets.len..]));        self.count = @intCast(ratchets.len);        self.latest_time = if (ratchets.len == 0) null else latest_time;    }    /// Puts a fresh key at the front, pushes the rest back one slot, and    /// returns whether it rotated, following Reticulum@1.5.0    /// RNS/Destination.py:206-209,228-243. A ring holding no key yet rotates,    /// and so does one whose newest key is older than the interval the caller    /// passed. Once the ring is full the oldest key falls off the back.    pub fn rotate(self: *Ring, now: Seconds, interval: Seconds, fresh: Ratchet) bool {        const should_rotate = if (self.count == 0)            true        else if (self.latest_time) |latest_time|            now > (std.math.add(Seconds, latest_time, interval) catch                std.math.maxInt(Seconds))        else            true;        if (!should_rotate) return false;        const owned = self.slotsMut();        const retained: usize = self.capacity.retained_max;        var index = @min(@as(usize, self.count), retained - 1);        while (index > 0) : (index -= 1) owned[index] = owned[index - 1];        owned[0] = fresh;        if (self.count < self.capacity.retained_max) self.count += 1;        self.latest_time = now;        return true;    }};comptime {    alloc_phase.capacity.requireProvisionedExactOwnerShape(Ring);}

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

zig
pub const ratchet = ratchet_module;

Audit

Definitions32
Public names57
Members12
Version26.7.0
Revisiondaab053ee433