Assurance you can check
Most messaging infrastructure asks you to trust a security claim. Tacenta draws the line precisely: what is machine-checked, what is tested, and what is trusted. Each claim below is stated at the strength of its evidence and no stronger, and the tier it sits in tells you what that evidence is.
Proven
Each claim in this section is machine-checked in Lean. The ledger lists 230 theorems, and 80 of them carry no pinned axiom list, so a change to the axioms they rely on would not, by itself, fail the build. CI rebuilds the model, proofs, committed Rust-to-Lean translation and evidence checks on every push. The translation is attested in public CI; regenerating it from Rust is a separate verification step. Where a proof covers the specification but not yet the Rust, or where a part of the system is tested rather than proved, the entry below says so.
- The wire format round-trips
- Every message the encoder produces, the decoder reads back intact. Wrong version, unknown type, length mismatch, and trailing bytes are rejected by construction, so a malformed frame cannot be mistaken for a valid one.
- Delivery is exactly-once and in order
- The delivery state machine never replays, reorders, or loses a message, and an acknowledgement can only move forward. A message counts as delivered to a user precisely when every one of their devices has read past it, and once delivered it stays delivered. The shipped Rust inherits this: it is proven to refine the specification.
- The ratchet and the handshake match their specification
-
Tacenta's own protocol implementation is translated to Lean by Charon
and Aeneas. Named operations of the Double Ratchet, sparse
post-quantum ratchet, ML-KEM Braid and composed Triple Ratchet have
machine-checked functional-agreement theorems against executable
models, under their stated hypotheses. The classical Double Ratchet
and composed Triple Ratchet receive theorems cover successful Rust
results only: they do not prove that every model acceptance is
accepted by Rust, or relate Rust errors to model refusals and unchanged
state. Other named theorems have different scopes; the source ledger
states each direction and covered branch. The same work establishes
panic-freedom for named ratchet entry points under stated boundary,
room and counter preconditions. The refinement proofs stop at the
ratchet boundary. Separately,
Session::encrypt,Session::decryptand the two establishment entry points have panic-freedom theorems, each conditional on a contract record of 15 to 44 named hypotheses and on explicit headroom. Decrypt and the responder entry point also assume a model of thezeroizewrapper that holds derived keys. The ledger in the source gives the size of each record; the hypotheses themselves are the fields of Lean structures in the source. Thirteen primitive contracts have satisfiability witnesses, and no theorem shows that any of the four records can be satisfied as a whole. The core has also not yet linked satisfiability witnesses to every numeric size and counter precondition: by its own rough count there are some 140 bound hypotheses on some 95 theorems, in about 54 shapes. Session and prekey orchestration are otherwise modelled and tested, but not proved end to end. - Bounded session lifecycle observations hold in the model
- Three Lean theorems cover a bounded abstract lifecycle: a refusal and restore leave a later honest observation unchanged; a terminal failure stays failed; and a replay has no second acceptance. The model assumes its abstract cryptographic verdicts and a committed snapshot. These are narrow L2 evidence, not a new security property or an end-to-end refinement of session orchestration.
- Identity trust holds on first contact
- Whatever key a device first registers is the key it stays bound to. A different key cannot displace an existing binding, and only the holder of the currently bound key can rotate it. The trust decision is proven of the shipped Rust; the recovery path around it is proven of the specification.
Tested
Verified by running the code against adversarial inputs, not by proof. Strong evidence, a different kind: a test covers the cases it draws, where a proof covers all of them.
- The implementation is compared with the model on named paths
- Model-generated vectors run against the Rust implementation and against a reader written from the specification. The reader is project-controlled evidence: its isolation from the code rests on records written by its own author for passes 2 to 7, 12 and 13 and is unrecorded for pass 1, and passes 8 to 11 and later maintenance were done with the implementation in view. Generated sequences compare the two ratchets and the Triple Ratchet step by step, including their persisted bytes, import refusals and counter ceilings. The public ledger does not claim cross-implementation interoperability as evidence. A pinned libsignal comparison is planned at the prekey-bundle boundary, but no such run is recorded; Tacenta's message layer uses its own wire format and KDF labels.
- Decoders survive hostile input
- Six coverage-guided fuzz targets and property-based decoder tests exercise hostile input. The public CI gate runs the Rust test suite, decoder properties and constant-time assembly checks; fuzzing, timing measurements and the interoperability harness run outside that gate.
- Session and prekey operations are differential-tested
-
A project-controlled reader, which its author declared was written from
the specification alone, checks all nine agreed operation families
over 27 traces and 41 steps, including prekey lifecycle and replay,
establishment, authenticated send and receive, skipped-key recovery
and named refusals. Five controls are required to fail. The
declaration is kept outside the public repository, and the committed
reader has changed since it was delivered. This reached the declared
bounded L2 target. Two later changes, the
legacy_blockedmarker rules and identity-key admission, are not in the corpus, and the decision behind that target has not been reopened for them. The reader compares selected outcomes and stored state, not every possible session lifecycle or a full end-to-end refinement, and the signature, key-agreement and MAC verdicts in its traces are declared facts, not recomputed. - Authentication does not leak who exists
- A sign-in with a wrong password and a sign-in for an account that does not exist take the same order of time and return the same error, so timing does not reveal which accounts are real. This is code-level reasoning backed by an empirical timing check, not a constant-time proof.
- Each release is exercised against the hosted service
- On every release tag, the CLI binary that tag published is downloaded, checked against its checksum, and run through a full encrypted round trip against the live service, over TCP and again over the WebSocket carriage. Then each SDK head built from that tag runs the same conversation against the live service: two users sign up, message both ways and one resumes from saved state. TypeScript runs it over the carriage; the Swift Package on a Mac and the Kotlin bindings on the JVM run it over TCP and again over the carriage. The transcript of each run is kept at tacenta.com/dl/conformance/, one file per version, with the binary's hash and the service document the server answered with. It is a record of what ran, not a proof: the Kotlin head runs on the JVM rather than a device, and a pass says the shipped code did what the tests ask of it that day.
Assumed
The trusted base. This section grew in August 2026: Tacenta now implements the protocol itself rather than depending on libsignal, so more of the responsibility is ours than it used to be. That is a stronger claim to have to defend, not a weaker one.
- The primitives, in full
- Tacenta implements the protocol, including the handshake, the ratchets and the encodings, and takes its primitives from established libraries, with the exceptions listed under in-house cryptographic code below. X25519 and Ed25519 come from the dalek crates, ML-KEM-1024 from libcrux, and SHA-256, HMAC and AES from the RustCrypto crates. Their security is assumed, not established here.
- In-house cryptographic code
- XEdDSA, signing with the same key used for key agreement, is implemented in-house over the Dalek curve and signature crates. Tacenta also contains a hand-written MAC comparison in the Braid that is meant to be constant-time: an assembly check covers that property and no proof does, although the comparison's panic-freedom and agreement with the model are proved; a finite-field erasure codec used by the Braid; the rule that decides which identity keys are accepted; and the composition of AES-CBC with HMAC that seals each message. The primitive libraries supply the underlying curve, hash, MAC, cipher and ML-KEM operations. These areas remain appropriate targets for outside scrutiny.
- Development tools
- Natuvea engineers use automated and AI-assisted tools for code, specification text, tests and review preparation. The maintainer is accountable for accepting the work, and tool output is project-controlled evidence rather than independent review.
- Not yet independently audited
-
A review of the cryptography and code examined pinned commit
d2dc386and its report was received on 2026-09-12. The report and the reviewer's identity are not public, so this page does not count it as external assurance. It was a review, not an audit, and fixes from it are in the commit history of tacenta-core. No independent audit has issued a report, and the proof-ledger engagement is not underway, so nothing here rests on outside sign-off. The core's own readiness gates for that engagement are recorded as not met. The core records no review of the claims ledger, claim by claim, by a reader who did not write it, and the review comments on several recent normative changes were written by the account that authored them. - Published security advisories
- Four security advisories have been published for tacenta-core: GHSA-cgvw-9r5f-xrxp, GHSA-v95x-f6p3-4qxg, GHSA-9hv6-fr6w-9758 and GHSA-r8w8-4rg9-mxhm. All four fixes are in the tagged commit this page cites. Erasure of secret state and the rule for which identity keys are acceptable at all are tested, not proved. That rule is separate from the key-binding rule under Identity trust above.
- The proof tools and the compiler
- The theorems assume the Lean kernel is sound, that Charon and Aeneas model Rust faithfully, and that the Rust compiler is correct. Some proof paths also trust Lean's compiled evaluation. These load-bearing assumptions, and the checks that constrain them, are written down.
Check it yourself
The source and the proofs are public, under the Apache-2.0 license. tacenta-core is the cryptographic core: its CLAIMS.md states what each proof covers and what it does not, and REPRODUCING.md rebuilds them. Its assurance inventory records the current evidence and its open targets; the obligation inventory names the blocking work before the proof-ledger review. tacenta is the product around it, with its own claims ledger and a recipe to rerun its proofs and checks from a clean checkout. Where this page and those ledgers disagree, the ledgers are right.
Why the line matters
A security claim you cannot inspect is a claim you have to take on trust. The value here is not that every part is proven. It is that the boundary between proven, tested, and trusted is drawn in one place and never blurred, so you can decide for yourself whether it meets your bar. For a team that has to answer for the privacy of its users, that boundary is the product.
Assessing Tacenta for a team
If you are deciding whether this boundary meets your application's requirements, write to info@natuvea.com with what you are building and the evidence your review needs. The reply says what exists today, where to find it, and what does not exist yet.