Skip to content

Cryptographic Boundary

The accepted standard-library boundary keeps only system cryptographic randomness in std.crypto.random. Secure memory erasure is the general memory operation std.mem.wipe. Algorithms, constructions, protocol formats, and algorithm-specific types belong to the independently released zynx-crypto package.

Migration status: this is the accepted RFC 0019 standard-library surface and RFC 0020 value contract. The current source inventory still contains the superseded batteries-included std.crypto and typed-value experiments while the external package and deletion migration are pending. Their presence is not a compatibility promise.

There is no public root std.crypto facade and no shared CryptoError. Import the leaf module directly:

Zynx
import std.crypto.random;

fn make_seed(out: &[u8]) throws(random.RandomError) {
    try random.fill(out);
}

Random Bytes

The complete leaf API is:

Zynx
export error RandomError {
    Unavailable
}

export fn fill(out: &[u8]) throws(RandomError);

random.fill:

  • fills the complete caller-owned output or throws RandomError.Unavailable;
  • sets every output byte to zero before returning an error;
  • allocates nothing and never exposes a partial byte count;
  • retries valid short and interrupted platform reads internally;
  • succeeds immediately for an empty output;
  • supports concurrent callers;
  • never falls back to deterministic, statistical, weak, or globally replaceable randomness.

The platform source is an implementation detail. A target without a usable cryptographically secure source reports Unavailable or requires an explicit platform runtime adapter; it never fabricates entropy.

random.uint32 and random.uniform are not part of the accepted surface. Sampling and distribution policy belongs in an external package built on fill.

Memory Erasure

Use std.mem.wipe for a caller-owned byte region:

Zynx
import std.mem;

fn discard_secret(secret: &[u8]) {
    mem.wipe(secret);
}

mem.wipe guarantees an exact-region overwrite that the compiler, optimizer, and linker cannot remove. It does not promise to remove previous copies, register contents, stack spills, swap, core dumps, or data outside the passed region. The overwrite pattern is not part of its contract. The stronger all-zero-on-error result of random.fill is specified separately.

There is no std.crypto.secure_zero alias and no generic wipe<T>.

Algorithms And Formats

The official external package uses direct construction namespaces:

Zynx
import zynx.crypto.aead.xchacha20poly1305;
import zynx.crypto.hash.sha256;
import zynx.crypto.sign.ed25519;

It does not expose a core global Algorithm, DigestAlgorithm, MacAlgorithm, string algorithm lookup, or default cipher. Dynamic suite IDs belong to the protocol or persistent format that serializes them.

Formats that do not carry a suite identifier use immutable versioned modules:

Zynx
import zynx.crypto.sealed.v1;

try sealed.v1.seal_into(key, plaintext, associated_data, &out);

Changing the construction, parameters, framing, domain separation, or nonce policy creates a new version; it never changes existing sealed.vN bytes.

Algorithm-specific typed values are owned by zynx-crypto. RFC 0020 specifies their contract without adding them to std.

Software Secrets

Each ordinary software SecretKey<A>, Seed<A>, SharedSecret<A>, and owned password is a construction-specific nominal owner parameterized by its concrete deallocation capability A. It privately owns ^A and one non-reallocating payload allocation whose length is fixed once at construction: by the nominal type for fixed-size secrets and by the validated runtime length, with capacity equal to length, for owned passwords. An ordinary move transfers ^A, its owner pointer, and the single cleanup/free obligation; it does not copy secret bytes.

Secret creation makes allocation visible in an *_alloc name and receives an owned allocator: ^A explicitly. The call consumes A, and the secret keeps it until matching wipe -> A.free; named non-Copy callers therefore write <-allocator. A secret has no default, sentinel, or uninitialized empty state and is not Copy or generally Clone. Zero-length password policy belongs to its construction. A construction may provide named fallible duplicate_alloc only when independent ownership is a real domain operation. Algorithms normally take shared loans:

Zynx
try xchacha20poly1305.seal_into(
    key,
    nonce,
    plaintext,
    associated_data,
    &output,
);

There is no ordinary public bytes() view. When raw export is genuinely needed, a fixed-size secret uses a signature such as export_into(output: &[u8; 32]) without an error edge. A runtime-size secret validates the exact &[u8] length before writing; a wrong length is a checked error, not a trap, leaves output unchanged, and never exposes a partial secret copy. Neither form allocates. After a successful export, the caller owns the additional copy and its wipe responsibility. Import does not silently wipe the caller's source buffer.

Secrets do not automatically participate in serialization, interpolation, format/debug content, hashing, ordering, or ordinary equality. A reviewed domain may expose named constant-time equal_secret; Tag has no ordinary equality, hash, or ordering, and authentication uses the construction's verify. Fixed-size tag comparison has no data-dependent early exit, without promising equal wall-clock or microarchitectural timing. Caller-controlled length, parse, allocation, randomness, and authentication failures are checked outcomes rather than traps.

Construction arms a fully initialized private allocation guard containing ^A immediately after a payload request succeeds and before another fallible operation. On a later checked failure it wipes the complete requested extent, frees it once through the same A, and drops A. On every defined lexical exit and successful replacement, the complete secret backing is likewise wiped at least once before exactly one free. Replacement first constructs the new value, so a checked construction error leaves the old secret intact. The guarantee does not cover traps, abort or process kill, nor caller/FFI copies, registers, spills, swap, core dumps, or microarchitectural state.

The allocator type is part of static identity: SecretKey<A> is Send exactly when A: Send and Sync exactly when A: Sync, through the defining package's audited conditional bridge for its private raw-pointer owner. No erased runtime allocator value or unconditional marker can change that result. HSM, keystore, enclave, and remote-provider keys use provider-specific nominal handle types and provider lifecycle/export rules.

There is no universal language Secret<T> or compiler erasure magic. Locked pages, guards, no-dump policy, and related hardened storage remain a future fallible pay-for-use allocator or type, not the default contract.

Public Cryptographic Values

Construction-specific Nonce, PublicKey, Signature, Digest, Salt, and Tag values are public data. Small fixed-size forms use inline storage and are Copy + Send + Sync; they may expose explicit byte encoding because their contents are not secret.

A Nonce type proves representation and size, not freshness or uniqueness. The caller, generator, and protocol remain responsible for the construction's nonce rule. A Tag is public, but authentication decisions still go through the construction's verify operation.

Allocation And Randomness Rules

Primitive package APIs are caller-buffer-first:

Zynx
try xchacha20poly1305.seal_into(key, nonce, plaintext, aad, &out);
try xchacha20poly1305.open_into(key, nonce, ciphertext, aad, &out);

An allocating helper must contain alloc in its name and accept an allocator explicitly. Neutral seal, open, hash, and sign operations do not hide allocation or randomness. An operation may obtain randomness only when its name says so, for example Key.generate, Nonce.generate, or seal_random_into.

Package Reproducibility

zynx-crypto is an ordinary external package. Applications select an exact source revision and content digest through the normal manifest and lockfile; updating the compiler or SDK does not silently change the selected crypto package. Published sealed.vN formats remain immutable, and security advisories can identify or withdraw affected exact revisions independently of the language release.

The secret/public value contract is fixed by RFC 0020. The security-support window, release cadence, audit policy, and emergency-update policy remain separate follow-up package-governance work.

Accepted Target API

ModuleAPIContract
std.crypto.randomRandomError.UnavailableSole random-source error.
std.crypto.randomfill(out: &[u8]) throws(RandomError)Complete secure fill or all-zero output plus Unavailable; no allocation or weak fallback.
std.memwipe(out: &[u8])Non-elidable exact-region overwrite; overwrite pattern unspecified.

Status

RFC 0019 and RFC 0020 are accepted. Runtime, std-source, external-package, typed-value, inventory, tests, and vendored-libsodium deletion work remain implementation milestones; this page does not claim that migration is complete.

Released under the MIT License.