Budgeted cryptographic randomness
The configured RandomProvider implements the existing
latent:random/random@0.1.0 interface: bytes(length) and u64-value().
Both require an exact import, a current broker plan, the installed provider
identity and an operation grant with resource kind random. Guest input cannot
select a generator, source, seed or provider. An uninstalled import fails
preparation.
Composition and authority
An embedding constructs RandomProvider::system(&broker, epoch, limits) and
installs its returned Arc with ActivationCapabilityRuntime::install_random.
Its reference() supplies the provider profile, configuration digest and epoch
for the existing durable binding and plan compiler. Production uses the
system-random-v1 profile. Changing limits changes the configuration digest;
replacement follows normal provider retirement and binding revision rules.
This provider is available through the Rust embedding and the opt-in random
installation in standalone provider configuration.
Configuration installs the provider, not guest authority: exact host bindings,
deployment grants and current policy are still required.
The owner has fixed configuration and numeric counters. There is no service generator, entropy file descriptor, thread, task, timer, seed or guest instance. Prepared components can retain the shared owner; each invocation still opens a fresh broker session and Wasmtime Store. Sessions from another installation, tenant or activation cannot transfer permission or remaining allowance.
Source and bounds
On Linux the provider calls the kernel through rustix::rand::getrandom with
NONBLOCK, in chunks of at most 256 bytes. An unavailable entropy source,
interrupted syscall, unexpected short read or other error fails the whole call.
There is no retry loop, device-file fallback or insecure flag. See the
Linux getrandom contract
and rustix API.
Other targets use the OS source selected by
getrandom::fill.
The nonblocking Linux profile is not a hard scheduling guarantee for every OS.
Source failures never fall back to time, IDs or a predictable PRNG.
| Limit or charge | Contract |
|---|---|
| Per call | Default and hard maximum 4,096 bytes; configurable down to 8. |
| Per activation | Default and hard maximum 65,536 bytes; may be reduced to the per-call maximum. Shared by both methods. |
u64-value | Exactly 8 source bytes, interpreted in little-endian order. |
bytes(0) | Empty successful result with no entropy call; still requires a grant and host-call fuel. |
| CPU fuel | 100 + requested bytes, reserved through the original ledger before output allocation or entropy. Fuel is accounting work, not a time unit. |
| Output | Exact requested capacity, plus normal broker input/metadata/call reservations, acquired before allocation. |
| Other counters | No child call, outbound request, state, blob, log or effect charge. |
Pending calls reserve their aggregate allowance. Dropping a call before entropy starts releases that allowance. Once the source is attempted, its requested bytes remain spent even if the source fails or cancellation prevents delivery. The allowance lives in the original session, not the public activation ID or prepared cache. Reusing either starts a fresh allowance. Zero-length repetitions remain bounded by fuel and broker call/handle/metadata limits.
Success retains its original output owner through guest canonical lowering and Store destruction. Cancellation and errors wipe an unfinished host buffer before releasing its reservation. Guest-visible bytes belong to ordinary guest memory; the provider makes no promise that application copies are securely erased.
Outcomes and observability
invalid-length reports a request above the per-call limit. budget-exhausted
reports aggregate, fuel, buffer or broker capacity exhaustion. unavailable
reports a source or provider/authorization failure: the frozen 0.1.0 variant has
no separate permission-denied case. Cancellation, session closure and deadline
expiry are also checked at the host return boundary and stop guest execution.
No partial random result is returned after a failed source operation.
snapshot() exposes fixed counters for the two operations, generated byte count
and the three failure classes. Generated bytes need not have reached the guest;
these are diagnostic counters and confer no authority. Required capability audit
must admit a durable attempt before source access. Its request digest covers the
operation and requested length, never generated output. Outcomes use
HostCompleted or Rejected; terminal durability follows the existing
audit contract. Neither diagnostics nor audit contain
generated bytes, their digest, a seed, guest labels or provider error text.
The optional Cargo test-support feature exposes explicit trusted
RandomProvider::for_test construction under a distinct test-random-v1
identity. Node configuration cannot select it; system always uses the OS even
when that feature is built. Injected test code must return promptly. This seam
does not implement durable workflow replay or deterministic production entropy.
Verification
cargo test -p latent-wasmtime --test random --locked -- --test-threads=1
cargo test -p latent-capabilities --lib --locked
Executed canonical ABI guests cover both methods, zero/max/over length, shared aggregate exhaustion and reset on a reused cell, denied and retired authority, source failure, cancellation, policy revocation and required audit redaction. Ownership checks cover abandoned pending work, spent failed attempts, fuel prepayment and held results after session closure. A separate real OS source test checks bounded buffer completion. These tests make no statistical claim about cryptographic quality and require no load benchmark.