Skip to documentation
SLOP

tiny.reticulum.identity.cipher

Reference tiny.reticulum identity cipher

Defined in identity.

API (8)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

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

Source

Called byCallsdestination.cipherdecryptprivate sourcelib.reticulum.src.identity.testcheckCipherVectortest sourcelib.reticulum.src.identity.testtest: identity decrypt falls back aft...test sourcelib.reticulum.src.identity.testtest: identity decrypt rejects a 32-b...test sourcelib.reticulum.src.identity.testtest: identity decrypt rejects one fl...+3 moreprivate sourcelib.reticulum.src.identity.cipherdecryptWithKeyprivate sourcelib.reticulum.src.identity.cipherdecryptWithRatchetprivate sourcelib.reticulum.src.identity.ciphertokenOutputLengthidentity.cipherdecrypt
Static calls · unresolved targets: 0 · external targets: 3.
Called byCallsdestination.cipherencryptprivate sourcelib.reticulum.src.identity.testcheckCipherVectorprivate sourcelib.reticulum.src.properties.cipherroundTripcrypto.hkdfderivecrypto.token.Tokenencryptcrypto.token.Tokeninitidentity.cipherencryptedLengthidentity.cipherencrypt
Static calls · unresolved targets: 2 · external targets: 2.
Called byCallsidentity.cipherencryptcrypto.tokenencryptedLengthidentity.cipherencryptedLength
Static calls · unresolved targets: 0 · external targets: 0.

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

zig
const std = @import("std");const reticulum = @import("../root.zig");const crypto = reticulum.crypto;const identity = reticulum.identity;const X25519 = std.crypto.dh.X25519;pub const ephemeral_bytes: u8 = X25519.public_length;pub const derived_bytes: u8 = 64;pub const EncryptError = crypto.token.TokenError || error{InvalidKey};pub const DecryptError = error{    Truncated,    RatchetRequired,    InvalidToken,    OutputTooSmall,};pub const Decrypted = struct {    plaintext: []u8,    ratchet_id: ?[reticulum.hash.name_bytes]u8,};pub fn encryptedLength(plaintext_length: usize) EncryptError!usize {    if (plaintext_length > crypto.token.max_plaintext_length) {        return error.PlaintextTooLong;    }    const token_bytes = try crypto.token.encryptedLength(@intCast(plaintext_length));    return @as(usize, ephemeral_bytes) + token_bytes;}/// Seals a payload for a recipient identity and writes the result into `out`,/// returning the bytes written, so a sender uses a key that only the holder of/// that identity can rebuild. What goes on the wire is the sender's 32-byte/// ephemeral public key followed by a token, following Reticulum@1.5.0/// RNS/Identity.py:804-836. The key both ends arrive at comes from the X25519/// product of the caller's ephemeral secret and either the recipient's rotating/// public key, when the caller passes one, or the recipient's identity key. The/// recipient's identity hash salts that product. The call then expands that/// product into the 64 token bytes through HKDF. The shared secret and the/// derived key are erased before the call returns. The call returns/// `error.PlaintextTooLong` past 65,471 bytes, `error.OutputTooSmall` when/// `out` is shorter than the result, and `error.InvalidKey` when the ephemeral/// secret or the target key forms no valid X25519 point.pub fn encrypt(    recipient: *const identity.Public,    ratchet_public: ?*const [X25519.public_length]u8,    ephemeral_private: *const [X25519.secret_length]u8,    iv: [crypto.token.iv_length]u8,    plaintext: []const u8,    out: []u8,) EncryptError![]u8 {    const result_length = try encryptedLength(plaintext.len);    if (out.len < result_length) return error.OutputTooSmall;    const ephemeral_secret = ephemeral_private.*;    const ephemeral_public = X25519.recoverPublicKey(ephemeral_secret) catch        return error.InvalidKey;    const recipient_bytes = recipient.toBytes();    const target = if (ratchet_public) |public|        public.*    else        recipient_bytes[0..X25519.public_length].*;    var shared = X25519.scalarmult(ephemeral_secret, target) catch        return error.InvalidKey;    defer std.crypto.secureZero(u8, &shared);    var derived: [derived_bytes]u8 = undefined;    defer std.crypto.secureZero(u8, &derived);    const salt = recipient.hash();    _ = crypto.hkdf.derive(derived_bytes, &shared, &salt, null, &derived) catch        unreachable;    const token = crypto.token.Token.init(&derived) catch unreachable;    const body = try token.encrypt(iv, plaintext, out[ephemeral_bytes..result_length]);    std.debug.assert(body.len + ephemeral_bytes == result_length);    out[0..ephemeral_bytes].* = ephemeral_public;    return out[0..result_length];}fn tokenOutputLength(token: []const u8) ?usize {    const minimum = crypto.token.overhead + crypto.cbc.block_length;    if (token.len < minimum) return null;    if (token.len > crypto.token.max_encrypted_length) return null;    const padded_length = token.len - crypto.token.overhead;    if (padded_length % crypto.cbc.block_length != 0) return null;    return padded_length;}fn decryptWithKey(    key: *const [X25519.secret_length]u8,    peer_public: [X25519.public_length]u8,    salt: [reticulum.hash.truncated_bytes]u8,    token_bytes: []const u8,    out: []u8,) ?[]u8 {    var shared = X25519.scalarmult(key.*, peer_public) catch return null;    defer std.crypto.secureZero(u8, &shared);    var derived: [derived_bytes]u8 = undefined;    defer std.crypto.secureZero(u8, &derived);    _ = crypto.hkdf.derive(derived_bytes, &shared, &salt, null, &derived) catch return null;    const token = crypto.token.Token.init(&derived) catch return null;    return token.decrypt(token_bytes, out) catch null;}fn decryptWithRatchet(    ratchet: *const identity.Ratchet,    peer_public: [X25519.public_length]u8,    salt: [reticulum.hash.truncated_bytes]u8,    token_bytes: []const u8,    out: []u8,) ?[]u8 {    var private_bytes = ratchet.toBytes();    defer std.crypto.secureZero(u8, &private_bytes);    return decryptWithKey(&private_bytes, peer_public, salt, token_bytes, out);}/// Opens a ciphertext into `out` and reports which retained key it took, so a/// receiver opens what arrived without being told which of its keys the sender/// chose, following Reticulum@1.5.0 RNS/Identity.py:849-907. The call tries/// each retained key in the order the caller gave and then falls back to the/// identity key. Enforcing rotating keys turns that fallback off, so a/// ciphertext no retained key opened returns `error.RatchetRequired`. The call/// returns `error.Truncated` when the ciphertext is as short as the ephemeral/// key or shorter, `error.OutputTooSmall` when `out` is shorter than the/// plaintext, and `error.InvalidToken` when no key opened it. Every retained/// private key the call copies and every key it derives is erased before it/// returns.pub fn decrypt(    private: *const identity.Private,    ratchets: []const identity.Ratchet,    enforce_ratchets: bool,    ciphertext: []const u8,    out: []u8,) DecryptError!Decrypted {    if (ciphertext.len <= ephemeral_bytes) return error.Truncated;    const peer_public = ciphertext[0..ephemeral_bytes].*;    const token_bytes = ciphertext[ephemeral_bytes..];    if (tokenOutputLength(token_bytes)) |needed| {        if (out.len < needed) return error.OutputTooSmall;    }    const salt = private.hash();    for (ratchets) |*ratchet| {        if (decryptWithRatchet(ratchet, peer_public, salt, token_bytes, out)) |plaintext| {            return .{ .plaintext = plaintext, .ratchet_id = ratchet.id() };        }    }    if (enforce_ratchets) return error.RatchetRequired;    var private_bytes = private.toBytes();    defer std.crypto.secureZero(u8, &private_bytes);    const key = private_bytes[0..X25519.secret_length];    const plaintext = decryptWithKey(key, peer_public, salt, token_bytes, out) orelse        return error.InvalidToken;    return .{ .plaintext = plaintext, .ratchet_id = null };}

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

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

Complete caller list for identity.cipher.decrypt

8 direct callers.

Audit

Definitions9
Public names9
Members6
Version26.7.0
Revisiondaab053ee433