Encode, Encrypt, Enclave — a formally verified protocol for building log-based, verifiable, sovereign data structures with role-based access control.
FTA is the theoretical foundation underlying ENC. It defines a complete formal framework for building verifiable applications from three primitives, three functions, and 48 theorems. Everything in the protocol is an instantiation of this theory.
The theory assumes: a single ordering authority per data space (total order guaranteed), forks detectable but not prevented (transparency proofs), no multi-writer consensus (ordering is assigned, not agreed), schema immutability (new schema = new data space), and commit-hash idempotency (identical inputs → identical hash, no nonces).
reduce and all signatures verify, then every action in that bundle was produced by a key with strength ≥ the threshold required by the schema. This ties cryptography to state transitions in a single safety boundary.Effect/runtime separation: effect returns descriptions, not executions. Runtime execution may retry, partially fail, or execute out of order. Only reduce mutates S, and reduce is only called with verified Actions. This preserves referential transparency at the core.
Distribution is a detection problem (transparency log fork detection), not a consensus problem. This drastically simplifies the theory.
Three foundations. Everything is what, who, or when.
Null | Bool | Int | Float | String | Bytes | [τ] | {k:τ} | τ|τ | κ→ν. The data universe.
Sym Secret | Asym Pub Priv | Shard Pub K N. Six operations: agree, kdf, derive, encrypt/decrypt, sign/verify, hash, split/combine.
Time = Nat. Window = Time × Time. valid(s,e) now ⟺ s ≤ now ≤ e.
-- Asymmetric key agreement agree : PrivKey × PubKey → SharedSecret -- Key derivation kdf : SharedSecret × Context → SymKey derive : Key × Label → Key -- Symmetric authenticated encryption encrypt : SymKey × τ → Cipher decrypt : SymKey × Cipher → Option τ -- Digital signatures sign : PrivKey × τ → Sig verify : PubKey × τ × Sig → Bool -- Hashing & secret sharing hash : τ → Digest split : Key × K × N → [Key] combine : [Key] → Option Key -- Canonical asymmetric → symmetric path: symKey = kdf(agree(my_priv, their_pub), context)
agree_sym : agree(a_priv, B_pub) = agree(b_priv, A_pub) encrypt_rt : decrypt(k, encrypt(k, m)) = Some m hash_cr : collision resistant (idealized as injective) derive_oneway : derive(k, l) does not reveal k derive_sep : l ≠ l' → derive(k, l) ≠ derive(k, l') split_threshold : |keys| < k → combine keys = None split_sufficient : |keys| ≥ k → ∃ key, combine keys = Some key valid_iff : valid (s,e) now ⟺ s ≤ now ≤ e
The theory rests on five named assumptions. All theorems involving keys, signatures, hashing, or encryption depend on one or more:
| Axiom | Statement | ENC Instantiation |
|---|---|---|
CR | Hash is collision resistant (idealized as injective) | SHA-256 |
EUF-CMA | Signatures existentially unforgeable under CMA | BIP-340 Schnorr |
CDH | Computational Diffie-Hellman hard on chosen group | secp256k1 |
KDF-OW | Key derivation is one-way | HKDF-SHA256 |
AEAD-SEC | IND-CPA + INT-CTAG secure authenticated encryption | XChaCha20Poly1305 |
Commit-hash determinism is the replay protection mechanism. No nonces. The guarantee chain: (1) canonical encoding is deterministic and injective (CBOR RFC 8949 §4.2), (2) hash injectivity from CR, (3) identical inputs → identical commit hash, (4) ordering authority rejects duplicate hash. This makes idempotency a theorem, not a convention.
Identity is key possession. Four variants with cryptographic strength levels. A session is a derived key with a time window — not a new concept, just Key + Time. Anon cannot create sessions.
Id = Anon -- no key, strength 0 | Hold Public -- I have the private half, strength 1 | Share Public K -- I hold one shard, strength 0 | Attest Public Sig Nonce -- I just proved it, strength 2 session : Id → Label → Window → Option Session session Anon _ _ = None session (Hold k) label w = Some (derive(k, label), w) session (Share k _) label w = Some (derive(k, label), w) session (Attest k _ _) l w = Some (derive(k, l), w)
Five concerns govern all data. Each composes the three primitives. Having one does not imply another. All five = full trust.
Id × Resource × Schema → Bool
Keys govern permission.
τ × Origin → Bool
Keys prove authorship.
SymKey × τ → Cipher
Keys hide content.
Log → Bool
Time sequences events.
Claim × Evidence × Digest → Bool
Hash verifies state.
Access rules: Schema = [Rule], Rule = (Resource, Role, [Op]). Set once at creation. Immutable. New schema = new data space.
Origin = Unsigned | Signed Public Sig | Endorsed (Public, Sig) (Public, Sig, Nat, Time) | CoSigned [(Public, Sig)] K | Logged Digest BundleIndex -- Endorsed = author signs, endorser co-signs with seq + time -- Logged = included in append-only verifiable log -- Replay protection: commit hash is deterministic replay identifier -- Verifier checks: time ∈ skew window, hash not previously accepted, seq monotonic
Every stored datum: Envelope = (Visible τ, Hidden (Cipher | τ), Origin). Visible fields are queryable plaintext. Hidden content is opaque — may be ciphertext.
Entry = (τ, Nat, Time, Origin) -- (data, seq, time, origin) wellFormed : [Entry] → Bool wellFormed log = consecutive(seq) ∧ nonDecreasing(time) ∧ allVerified(origin) consistent : [Entry] → [Entry] → Bool consistent(l1, l2) = ∃ proof, verify_consistency(root(l1), root(l2), proof) = true -- l1's CT root is a prefix of l2's CT root Claim = Has Digest τ | Missing Digest | Included Entry Digest | Consistent Digest Digest
agree + kdf + encrypt compose to build any encryption protocol. Transport controls distribution topology. Evolution controls key lifecycle. Time optionally bounds ciphertext and forces rotation.
Transport = Direct -- 1:1, shared = agree(my, their) | Tree Nat -- 1:N, secret = root(tree), O(log N) update Evolution = Static -- key never changes | Epoch -- key per group change: keyₙ = derive(epoch, seq) | Ratchet -- key per use, forward only: -- keyₙ = derive(stateₙ, "use") -- stateₙ₊₁ = derive(stateₙ, "next"), delete stateₙ Scheme = (Transport, Evolution, Option Window, Option Window) -- ↑ TTL ↑ Rotation -- Instances: Plaintext = (Direct, Static, ∅, ∅) DH_Session = (Direct, Epoch, ∅, ∅) Ratchet_DM = (Direct, Ratchet, ∅, ∅) Group_Lazy = (Tree N, Epoch, 24h, 24h) Group_Secure = (Tree N, Ratchet, ∅, ∅) Group_Ephemeral = (Tree N, Epoch, 1h, 1h)
Four components: Global (shared), Local (per view), Persist (across sessions), Binding (lifecycle). Optionally, Global can be Convergent (CRDT-compatible with semilattice merge).
S = G × L × P × B G : { id : Id, schema : Schema, ... } -- global, shared L : Scope → τ -- local, per view P : String → τ -- persisted across sessions B : Binding → Life -- data binding lifecycle -- Persistence: get(k, p) : Option τ set(k, v, p) : P del(k, p) : P
One lifecycle type for all bindings. Six states, defined transitions, mandatory timeout enforcement:
Life = Idle | Pending Time -- waiting, with deadline | Active τ Time -- has data, received at | Failed Err Time -- error, occurred at | Stale τ Time -- data expired | Done Reason -- terminated Reason = Completed | Revoked | Expired | Closed | Timeout | Error Err -- Transitions: Idle --> Pending t Pending t --> Active (if now <= t) Pending t --> Failed (if now > t, MANDATORY -- Theorem 48) Active --> Stale --> Pending t --> Active Active | Pending | Failed | Stale --> Done
Storage is a set of capabilities and guarantees, not a category. Each store declares which operations it supports. Bindings are checked against the store's ops set.
Storage = { ops : [Op], -- {Get, Put, Del, Scan, Batch, ...} atomicity : None | Record | Global, consistency : Eventual | Strong | Causal, provable : Bool -- provides inclusion proofs? } Op = Read Query | Write Mutation Query = Select | Get | Scan | List Mutation = Insert | Put | Del | Batch Store = { storage : Storage, interface : [Interface], -- REST | GraphQL | WebSocket | Local auth : [Auth], -- None | Bearer | ApiKey | Sig | OAuth protect : Protection } Protection = (Scope, Scheme, Signing) Scope = Open | Content | Record | Field (Name → Scope) Signing = Unsigned | Signed Key | Multi [Key] K | Chain
A binding connects a Store to State. If the Store is another App, the binding is an inter-app protocol. Two modes: Pull (trigger → query → response) and Push (subscribe → stream).
Binding = { store : Store, -- App satisfies Store via : Interface ∈ store.interface, -- checked: Theorem 31 auth : Auth ∈ store.auth, -- checked: Theorem 32 mode : Pull Query Trigger | Push Query Session, parse : τ → Option τ, target : Path S, guard : Id × Schema → Bool } Trigger = Init | Tick Duration | On Event | Change Path Session = derive(id.key, label) + Window -- Every datum passes through trust: -- Inbound: guard → auth → request → [transport] → verify(origin) -- → verify(claim)? → decrypt(scheme) → parse → state -- Outbound: guard → sign → encrypt(scheme) → request → [transport]
Three action types. reduce is a pure function of state and action — no external inputs. If access check fails, state is unchanged.
Action = Set Path τ -- set value at path in S | Del Path -- delete value at path | Bind Binding Life -- update binding lifecycle reduce : S × Action → S reduce(s, a) = if access(s.id, a, s.schema) then apply(s, a) else s -- Theorem 4: Guard Event = Click | Input | Select | Submit | Nav | Scroll | Gesture | Custom String
render is a pure function of state. No external inputs. The virtual DOM is a tree of elements, text, and empty nodes.
V = El Tag [Attr] [V] | Text String | Empty Attr = Class String | On Event Action | Bind Path | Prop String τ | If (S → Bool) | Key τ render : S → V -- pure, total, no IO
effect is a pure function of state, action, and environment. Env makes external inputs (time) explicit parameters. execute performs I/O — outside the purity guarantee. Crypto operations are not separate effects — they are steps in the Binding pipeline.
Env = { time : Time } -- explicit external inputs Effect = Fetch Binding Op (τ → Action) -- read or write store | Open Binding (Life → Action) -- open push connection | Close Binding -- close connection | Route String -- navigate | Sync P -- persist | Batch [Effect] -- parallel | None effect : S × Action × Env → [Effect] -- pure execute : Effect → IO () -- impure, outside guarantee
Three functions. Two pure of state alone, one pure of state + environment. Everything else is structure. An App that exposes an Interface is a Store. Other Apps can bind to it. Composition is structural, not special-cased.
App = (S₀, B, reduce, effect, render) S₀ : S -- initial state B : [Binding] -- all data bindings reduce : S × Action → S -- pure state transition effect : S × Action × Env → [Effect] -- pure effect description render : S → V -- pure view function
The ordering authority is trusted to order and assign seq/time, but not trusted to read or forge client payloads. Content encryption keys are never shared with it. Compromised authority key allows ordering manipulation but not content forgery or decryption.
| Adversary | Capabilities | Defense |
|---|---|---|
| Authority misbehavior | Censor, reorder, fork | CT signed-head comparison detects forks |
| Network attacker | Eavesdrop, replay, MITM | ECDH + AEAD per query/response/push |
| Key compromise (client) | Exfiltrate, impersonate | Epoch rotation limits historical exposure |
| Key compromise (authority) | Order manipulation | Not content access (keys never shared) |
| Replay | Resubmit valid commits | Hash dedup + expiration windows |
Honest witness requirement: Forced migration security depends on at least one honest client retaining a previously observed signed tree head. Without this, colluding Owner + new authority can truncate history — the fundamental limit of any append-only log without external anchoring (equivalent to the CT trust model).
Notify delivery is best-effort (censorship undetectable). Guard failures must not leak store existence. Group key state must be rebuilt from event log on reconnection. Dedup prevents identical events, not identical intents.
Inbound Outbound
======= ========
+-------------------+
Store --[pull]------>| |--[write]--> Store
| access |
Store ==[push]======>| verify |--[route]--> URL
(stream) | | prove? |
| | decrypt |--[sync]---> Device
User --[event]---+-->| parse |
| reduce |
Key --[sign]------->| render |--[view]---> DOM
| effect |
| |
| G x L x P x B |
| proven |
+-------------------+
In: access -> verify -> prove? -> decrypt(scheme) -> parse
Out: sign -> encrypt(scheme) -> write(origin)A complete group chat using all five trust concerns, both transport and content encryption, the full Life state machine, and App-as-Store composition.
-- Outbound (alice sends): 1. guard: access(alice, Chat_Message, schema) -> true (Member, C) 2. sign: sig = schnorr(commit_hash, alice_priv) 3. encrypt: key = MLS.deriveMessageKey(epochSecret) cipher = encrypt(key, plaintext) 4. commit: hash = H(0x10, enclave, alice_pub, "Chat_Message", cipher, exp, tags) 5. transport: ECDH(signer_priv, seq_pub) + HKDF("enc:query") + XChaCha20 -- Inbound (bob receives via push): 1. transport: ECDH(seq_priv, bob_signer_pub) + HKDF("enc:push") 2. verify: check sig (alice) + seq_sig (node) 3. decrypt: MLS.decrypt(epochKey, cipher) -> plaintext 4. parse: parse(ChatMessage) -> Some msg 5. state: reduce(s, Set L.messages (append msg))
seq=0 Manifest -> epoch 0 (alice, bob initial members) seq=1 Grant Member carol -> MLS.addMember(carol) -> epoch 1 seq=2 Chat_Message (alice) -> encrypted with epoch 1 key seq=3 Revoke Member carol -> MLS.removeMember(carol) -> epoch 2 seq=4 Chat_Message (bob) -> encrypted with epoch 2 key carol CANNOT decrypt (forward secrecy)
Every FTA obligation is machine-checked in Lean 4. The abstract theory is defined as typeclasses — a contract with proof obligations. ENC provides concrete instances that fill every obligation. If any proof is missing, the specification does not compile.
Abstract theory Typeclasses class FTA.CryptoSuite 6 key theorems (7-12) as fields class FTA.App 48 theorem obligations as fields structure FTA.Compliant CryptoSuite + App = full compliance Concrete protocol Instances ENC types + axioms 83+ theorems proven FTA instantiation Maps each proof to its FTA obligation -- The existence of instances IS the proof. -- No sorry. No axiom escape. The kernel verifies the mapping.
Lean 4 has no interfaces or inheritance. Three approaches exist for proving "ENC satisfies FTA":
| Approach | Mechanism | Trade-off |
|---|---|---|
| Typeclasses ✓ | Define FTA as class, provide ENC instance | Generic theorems auto-apply; compile-time enforcement |
| Structure witness | Define FTA as structure, construct ENC term | More explicit but less composable |
| Theorem-level | Prove each property individually on ENC types | Works, but correspondence is implicit |
-- FTA defines the abstract interface class FTA.CryptoSuite (Hash Sig PubKey PrivKey SymKey Cipher : Type) where hash : ByteArray → Hash sign : PrivKey → ByteArray → Sig verify : PubKey → ByteArray → Sig → Bool agree : PrivKey → PubKey → SymKey encrypt : SymKey → ByteArray → Cipher decrypt : SymKey → Cipher → Option ByteArray -- Theorem obligations (FTA 7-12) agree_sym : ∀ a_priv b_priv a_pub b_pub, agree a_priv b_pub = agree b_priv a_pub encrypt_rt : ∀ k m, decrypt k (encrypt k m) = some m hash_injective : ∀ a b, hash a = hash b → a = b derive_sep : ∀ k l l', l ≠ l' → derive k l ≠ derive k l' -- ENC provides the concrete instance instance : FTA.CryptoSuite (ByteVec 32) (ByteVec 64) (ByteVec 32) (ByteVec 32) (ByteVec 32) ByteArray where hash := sha256 sign := schnorrSign verify := schnorrVerify agree := ecdh encrypt := xchacha20_encrypt decrypt := xchacha20_decrypt agree_sym := enc_agree_sym -- FTA 7 ← CDH axiom encrypt_rt := dm_roundtrip -- FTA 8 ← proven theorem hash_injective := sha256_injective -- FTA 9 ← CR axiom derive_sep := enc_derive_sep -- FTA 11 ← KDF-OW axiom
class FTA.App (S : Type) where -- Components S₀ : S bindings : List Binding schema : Schema reduce : S → Action → S -- pure (FTA 1-3 by type) effect : S → Action → Env → List Effect render : S → V -- total (FTA 45 by type) -- Theorem obligations guard : ¬access(id, a, schema) → reduce(s, a) = s -- FTA 4 no_escalation : strength(reduce(s,a)) ≤ strength(s) -- FTA 5 schema_fixed : getSchema(reduce(s,a)) = getSchema(s) -- FTA 6 session_finite : ∀ w, ∃ t, ¬valid(w, t) -- FTA 14 life_machine : transitions follow Life graph -- FTA 34 state_isolation: pa ≠ pb → set(pa, v) does not affect pb -- FTA 36 coverage : ∀ path ∈ S₀, ∃ b targeting path -- FTA 46 deadline : Pending t ∧ now > t → runtime emits Failed -- FTA 48 -- Combined witness: existence IS the proof structure FTA.Compliant (S Hash Sig PubKey PrivKey SymKey Cipher : Type) where crypto : FTA.CryptoSuite Hash Sig PubKey PrivKey SymKey Cipher app : FTA.App S
Each of the 48 FTA theorems is witnessed by one of five proof kinds:
| Kind | Count | How proven |
|---|---|---|
| Theorem | 10 | Machine-checked Lean proof (H_injective, dm_roundtrip, expired_always_rejected, etc.) |
| Axiom | 11 | Cryptographic assumption — the bridge to computational security (sha256_injective, enc_agree_sym, etc.) |
| Type sig | 7 | Encoded in Lean's type system (reduce : S → Action → S proves Preservation + Totality + Purity) |
| Def | 3 | True by definition (Window.valid, Life.canTransition, Persist.set/del) |
| Structural | 17 | Follows from architecture (validation pipeline, dual-sig constructor, CT consistency, etc.) |
FTA# Name ENC Proof Kind ---- ---- --------- ---- 1 Preservation reduce : S → Action → S Type sig 2 Totality total function, no partial Type sig 3 Purity no IO in return type Type sig 4 Guard expired_always_rejected Theorem terminated_rejects_all Theorem 5 No Escalation enc_no_escalation Theorem 6 Schema Fixed enc_schema_fixed Theorem 7 Agree Sym enc_agree_sym Axiom 8 Encrypt RT dm_roundtrip Theorem 9 Hash CR sha256_injective → H_injective Theorem 10 Derive Oneway enc_derive_oneway Axiom 11 Derive Sep enc_derive_sep Axiom 12 Threshold enc_split_threshold/sufficient Axiom 13 Window Window.valid definition Def 14 Session Finite enc_session_finite Theorem 15 Expiry Rejects expired_always_rejected Theorem 16 Verify validateCommit pipeline Structural 17 Endorse Event (sig + seq_sig) Structural 18 Append Only CT.inclusion_sound Axiom 19 Seq Strict Node assigns monotonic seq Structural 20 Time Weak Node assigns nondecr time Structural 21 Sound SMT.membership_sound Axiom 22 Non-Forgery SMT.nonmembership_sound Axiom 23 Direct Sym = Theorem 7 (ecdh) Axiom 24 Epoch Fresh mls_add/remove_advances Axiom 25 Ratchet Fwd mls_forward_secrecy Axiom 26 TTL auto-delete mechanism Structural 27 Rotation epoch rotation on stale Structural 28 Query Visible Filter on Envelope.visible Structural 29 Hidden Opaque AEAD-SEC axiom Axiom 30 Meta Queryable Envelope.visible = plaintext Structural 31 Interface Node declares [REST, WS] Structural 32 Auth Node declares [Sig, Session] Structural 33 Op Valid Schema validates ops Structural 34 Life Machine Life.canTransition Def 35 Revoke/Expire RBAC revoke → UNAUTHORIZED Structural 36 Isolation SMT namespace separation Structural 37 Persist Set KV store property Def 38 Persist Del KV store property Def 39 Hydration Event log replay Structural 40 Safe CBOR decode : Option τ Type sig 41 No Raw Typed structures only Type sig 42 Effect Only createCommit only write path Type sig 43 Sign First sign(hash) before encrypt Structural 44 Origin sig + from mandatory Structural 45 View Total render : S → V (total) Type sig 46 Coverage bindings cover all paths Structural 47 Isolation Composition theorems Theorem 48 Deadline enc_deadline_accepted Theorem
Any protocol can prove FTA compliance by defining concrete types, providing typeclass instances with proofs for each obligation, and verifying the result compiles with zero sorry.
FTA defines 48 theorems across 15 categories. ENC instantiates all of them — 83+ proven in Lean 4 with zero sorry.
1–3 Pure reduce is total, preserving, effect-free 4–6 Access Guard, No Escalation, Schema Fixed 7–12 Keys ECDH Symmetric, Encrypt RT, Hash CR, Derive OW, Derive Sep, Threshold 13–15 Time Window (MAX_EXP_WINDOW), Session (7200s max), Expiry (±60s skew) 16–18 Origin Verify (schnorr), Endorse (dual-sig), Append Only (CT) 19–20 Order Seq Strict (monotonic), Time Weak (non-decreasing) 21–22 Proof SMT Sound (membership + non-membership), Non-Forgery 23–27 Secrecy Direct Sym, Epoch Fresh, Ratchet Fwd, TTL, Rotation 28–30 Scope Query Visible, Hidden Opaque, Meta Queryable 31–35 Binding Interface, Auth, Op Valid, Life Machine, Revoke/Expire 36–39 State Isolation, Persist Set, Persist Del, Hydration 40–41 Parse Safe (CBOR + hash verify), No Raw (type system enforced) 42–44 Write Effect Only, Sign First, Origin 45–46 Complete View Total, Coverage (all content types creatable) 47 Compose Isolation (App-as-Store) 48 Deadline Pending timeout enforced by runtime
46 composition theorems across 4 reference apps (Timeline, Registry, DM, Chat) prove the system holds together.
| Condition | Count | What it proves |
|---|---|---|
| C1 | 17 | Every SDK write reduces to createCommit |
| C2 | 14 | DataView data bounded by schema (P or N) |
| C3 | 5 | Middleware roundtrip = identity (ECDH + MLS) |
| C4 | 4 | Every content type creatable by some role |
| C5 | 2 | Middleware isolation (no cross-contamination) |
| C6 | 4 | Components compose only from AppSDK |
If an application requires multi-writer convergence without a sequencer, G can be configured as Convergent τ Merge where Merge must satisfy full semilattice laws:
merge(a, b) = merge(b, a) -- commutativity merge(a, merge(b, c)) = merge(merge(a, b), c) -- associativity merge(a, a) = a -- idempotence
This is outside the core theory's single-sequencer model and not counted among the 48 theorems. Applications requiring convergent state should verify these properties independently.
ENC is a protocol for building append-only, cryptographically verifiable data structures. Every enclave is a sequenced event log with RBAC, Merkle proofs, and a single sequencer that orders and finalizes commits.
A log-based, verifiable, sovereign data structure. Maintains an append-only event log, SMT state, and CT proof tree.
Hosts one or more enclaves. Stores data, serves queries, produces proofs, and sequences commits into events.
Controls an identity key (id_priv). Produces signatures, encrypts/decrypts, and submits commits.
Indexes, aggregates, and transforms enclave data for efficient querying. Trusted, not verifiable.
Two query paths exist with fundamentally different trust properties:
| Path | Trust | Verification | Use Case |
|---|---|---|---|
| Enclave | Source of truth | SMT + CT proofs | Disputes, audits, high-value transactions |
| DataView | Convenience | None (trusted) | Routine queries, real-time apps |
DataView receives data via three methods, each requiring a role assignment:
| Method | Permission | Description |
|---|---|---|
| Query | R | DataView queries enclave directly as a member |
| Push | P | Node delivers full events proactively |
| Notify | N | Node delivers lightweight notifications; DV fetches as needed |
Each enclave has a single sequencer — the node that orders and finalizes commits. The node that accepts the Manifest event becomes the sequencer, and its identity key is recorded in the Manifest's sequencer field. Multi-sequencer consensus is out of scope for v1.
The sequencer accepts valid commits, assigns seq and timestamp, signs events with seq_sig, maintains the SMT and CT trees, and delivers P/N to registered recipients.
Five cryptographic axioms underpin every theorem. Each is instantiated with a concrete algorithm and modeled as idealized for formal reasoning.
SHA-256 (FIPS 180-4). Modeled as injective: H(a) = H(b) → a = b.
BIP-340 Schnorr over secp256k1. Deterministic signing with zero auxiliary randomness.
secp256k1 ECDH. Computational Diffie-Hellman assumption on the curve.
HKDF-SHA256. One-way key derivation: derive(k, l) does not reveal k.
XChaCha20Poly1305. IND-CPA + INT-CTAG secure authenticated encryption.
Every hash in ENC routes through H() — SHA-256 over deterministic CBOR encoding (RFC 8949 §4.2). CBOR is used only for hash computation, not transport. The v1 wire format is JSON.
-- H(fields...) = sha256(cborEncode(fields)) noncomputable def H (fields : List CborField) : Hash := sha256 (cborEncode fields) theorem H_injective : ∀ (a b : List CborField), H a = H b → a = b := by intro a b hab have hcbor := sha256_injective _ _ hab exact cborEncode_injective _ _ hcbor
All hash operations use a unique single-byte prefix to prevent cross-domain collisions. Signature contexts use string prefixes for domain separation.
| Prefix | Domain | Usage |
|---|---|---|
0x00 | CT Leaf | RFC 9162 leaf hash |
0x01 | CT Node | RFC 9162 internal node |
0x10 | Commit | Client-generated commit hash |
0x11 | Event | Sequencer-finalized event hash |
0x12 | Enclave ID | Content-addressed enclave identity |
0x20 | SMT Leaf | Sparse Merkle Tree leaf |
0x21 | SMT Node | Sparse Merkle Tree internal node |
| Context | String Prefix | Usage |
|---|---|---|
| STH Signature | "enc:sth:" | 56-byte message → SHA-256 → Schnorr |
| Session Token | "enc:session:" | Message → SHA-256 → Schnorr |
Transport uses JSON. Binary values are hex-encoded: hashes and public keys as 64-char hex (no prefix), signatures as 128-char hex, bitmasks as 0x-prefixed hex. CBOR encoding is used exclusively for hash pre-image computation.
An identity key is a 256-bit secp256k1 public key (id_pub). A client controls an identity by holding (or having authorized access to) the corresponding private key. The FTA formalizes four identity variants with cryptographic strength levels.
inductive Id where | anon -- no key, strength 0 | hold (pub : PubKey) -- controls keypair, strength 1 | share (pub : PubKey) (k : Nat) -- holds shard, strength 0 | attest (pub : PubKey) (sig : Signature) -- proved via sig, strength 2 def Id.strength : Id → Nat | .anon => 0 -- cannot create sessions | .hold _ => 1 -- can sign commits | .share _ _ => 0 -- single shard grants no capability | .attest _ _ => 2 -- proved via signature
Stateless query authentication. A 68-byte token (136 hex chars) derived from the identity key, verifiable in O(1) EC math — no signature verification needed. Maximum lifetime: 7200 seconds (2 hours). Clock skew tolerance: ±60 seconds.
-- Client creates: expires = now + duration -- max 7200 seconds message = "enc:session:" || be32(expires) sig = schnorr(sha256(message), id_priv) r, s = sig[0:32], sig[32:64] session_pub = s · G -- point on secp256k1 token = r || session_pub || be32(expires) -- Node verifies in O(1) EC math: t = sha256(session_pub || seq_pub || enclave) expected = r + sha256(r || from || message) · from check: session_pub == expected
Each session derives a per-node, per-enclave signer key for ECDH. This provides per-enclave key isolation: a compromised signer in Enclave A cannot decrypt messages for Enclave B.
t = sha256(session_pub || seq_pub || enclave) signer_priv = session_priv + t (mod n) signer_pub = session_pub + t · G
A Commit is a client proposal. An Event is the sequencer-endorsed result. A Receipt is the node-signed acknowledgment. The dual-signature chain binds author intent to sequencer ordering.
content_hash = sha256(utf8_bytes(content)) -- raw SHA-256, not H() commit_hash = H(0x10, enclave, from, type, content_hash, exp, tags) sig = schnorr(commit_hash, from_priv) event_hash = H(0x11, timestamp, seq, sequencer, sig) seq_sig = schnorr(event_hash, sequencer_priv) event_id = sha256(seq_sig) -- commits to both enclave_id = H(0x12, from, "Manifest", content_hash, tags)
meta to differentiate.| Field | Type | Description |
|---|---|---|
hash | hex64 | Commitment hash of fields |
enclave | hex64 | Destination enclave |
from | hex64 | Author identity key |
type | string | Event type (schema-scoped) |
content | string | UTF-8 payload (opaque to node) |
exp | uint | Expiration (Unix ms) — also acts as nonce |
tags | array | Node-level metadata (see Tags) |
sig | hex128 | Schnorr signature over hash |
Tags provide node-level processing instructions (not query indexes). Content is opaque to the node; tags are understood.
| Tag | Description |
|---|---|
r | Reference — ["r", "event_id", "context"]. Context: target, reply, quote, thread |
auto-delete | Node removes content after this Unix ms timestamp (trust-based, no SMT update) |
The node adds id, timestamp, sequencer, seq, seq_sig to produce the final event. For all events except Migrate (forced takeover), the sequencer field must match the current sequencer.
theorem terminated_rejects_all (commit : Commit) (t : Timestamp) (dup : Bool) (ctx : AuthContext) (s : Schema) : validateCommit commit t .terminated dup ctx s = .reject .enclaveTerminated theorem duplicate_rejected -- dedup gate ... (hNotExp) (hNotFar) : validateCommit commit t ls true ctx s = .reject .duplicateCommit
The node maintains a set of accepted commit hashes per enclave. Duplicate hashes are rejected. Hashes may be garbage-collected after exp + 60000 ms. Rejected commits are not added to the dedup set. Expiration window limit: MAX_EXP_WINDOW = 3600000 (1 hour). Clock skew tolerance: ±60 seconds.
| Category | Events | Modifies SMT |
|---|---|---|
| AC | Manifest, Grant, Grant_Push, Revoke, Revoke_Self, Move, Force_Move, Transfer_Owner, AC_Bundle | ✓ (RBAC) |
| Mutation | Update, Delete | ✓ (Status) |
| Lifecycle | Pause, Resume, Terminate, Migrate | — |
| Content | Everything else (app-defined, e.g. Chat_Message) | — |
Only Content Events can be Updated or Deleted. AC Events are irreversible state transitions. Update/Delete events cannot themselves be U/D'd.
U and D are logical operations implemented as new events referencing prior events via the r tag. The log remains append-only; originals are never mutated. Event status is tracked in the SMT Event Status namespace.
| SMT Proof Result | Interpretation |
|---|---|
| Non-membership (null) | Active OR never existed (combine with CT proof to distinguish) |
0x00 (1 byte) | Deleted |
| 32-byte ID | Updated to this event_id |
Role-Based Access Control consists of an immutable Schema (fixed at Manifest creation) and mutable State (tracked in the SMT as 256-bit bitmasks). Changing the schema requires creating a new enclave.
| Op | Meaning | Applies to |
|---|---|---|
C | Create — can submit commits | All events |
R | Read — can query events | All events |
U | Update — can update events | Content Events only |
D | Delete — can delete events | Content Events only |
P | Push — receives full events | All events |
N | Notify — receives metadata only | All events |
| Bit | Role | Stored in SMT | Description |
|---|---|---|---|
| 0 | Self | No (dynamic) | Matches event.from — for U/D, matches original author |
| 1 | Owner | Yes | Must be assigned in initial_state |
| 2 | Node | No (dynamic) | Matches current sequencer |
| 3 | Any | No (implicit) | Everyone. Cannot have P or N. |
| 4–31 | Reserved | — | Future protocol use |
| 32+ | Custom | Yes | App-defined, ordered by schema position |
{
"event": "Grant", // event type or "*" wildcard
"role": "Admin", // role name
"ops": ["C"], // permitted operations
"target_roles": ["Member"] // privilege escalation bounds
}theorem revokeSelf_rejects_owner (ctx) (schema) : validateRevokeSelf ctx schema "Owner" = false theorem forceMove_protects_owner_from (ctx) (schema) (fromBm toBm actual) (h : fromBm.hasBit bitOwner = true) : validateForceMove ctx schema fromBm toBm actual = false theorem transfer_requires_owner (ctx) (h : ctx.actorBitmask.isOwner = false) : validateTransferOwner ctx = false
| Type | Effect | Content |
|---|---|---|
Grant | Grant role to identity (pull) | { role, identity } |
Grant_Push | Grant role + register webhook | { role, identity, endpoint } |
Revoke | Revoke role from identity | { role, identity } |
Revoke_Self | Remove own role (not Owner) | { role } |
Move | Atomically change bitmask | { identity, from, to } |
Force_Move | Bypass target_roles (no Owner) | { identity, from, to } |
Transfer_Owner | Atomic ownership transfer | { new_owner } |
AC_Bundle | Atomic batch of AC ops | { operations: [...] } |
Grant and Grant_Push are idempotent. Revoke and Grant for roles already held/not held succeed as no-ops. AC_Bundle uses simulated sequential validation: each operation is authorized against state after all preceding ops. If any fails, the entire bundle is rejected.
Push delivers full events with at-least-once semantics (exponential backoff: 1s → 2x → max 5min, 10 retries). Notify delivers metadata only with best-effort. Each delivery is independent; per-type sequence counters (type_seq, 1-indexed, never resets) enable gap detection.
| State | Condition | Accepts | Reads |
|---|---|---|---|
| Active | No Terminate/Migrate; not paused | All (per RBAC) | Yes |
| Paused | Last lifecycle = Pause | Resume, Terminate, Migrate | Yes |
| Terminated | Terminate exists | None | Until deleted |
| Migrated | Migrate exists | None | No obligation |
Terminate and Migrate are mutually exclusive terminal states. The first terminal event wins. Lifecycle is derived from the event log, not stored in SMT.
A single Sparse Merkle Tree stores both RBAC state and event status. Two namespaces, 168-bit depth (21-byte keys), same collision safety as Ethereum addresses (~10⁻³⁰ at 1B entries).
| Property | Value |
|---|---|
| Total depth | 168 bits (21 bytes) |
| Namespace prefix | 8 bits (1 byte) |
| Entry path | 160 bits (20 bytes) |
| Hash function | SHA-256 |
| Empty value | sha256("") = 0xe3b0c44... |
| Update cost | 168 hashes ≈ 170μs |
| Throughput | ~6,000 updates/sec |
-- RBAC namespace (0x00): path = 0x00 || sha256(id_pub)[0:160 bits] -- 21 bytes -- Event Status namespace (0x01): path = 0x01 || sha256(event_id)[0:160 bits] -- 21 bytes
| Namespace | Value | Meaning |
|---|---|---|
| RBAC | 256-bit bitmask (32 bytes big-endian) | Role assignments |
| RBAC | (not in tree) | No roles (zero bitmask → leaf removed) |
| Event Status | (not in tree) | Active |
| Event Status | 0x00 (1 byte) | Deleted |
| Event Status | 32-byte event_id | Updated to this event |
leaf_hash = H(0x20, key, value) node_hash = H(0x21, left_child, right_child) empty_hash = sha256("") -- hardcoded constant
The SMT root (state_hash) is bound to the event log via CT leaf hashes. With bundling, state_hash is recorded per bundle. Two query modes: verified (CT-proven, may be stale) and current (fresh, no proof).
Two Merkle trees provide verifiable state and history. The CT root alone commits to the entire event log AND current state in a single 32-byte hash.
168-bit depth. RBAC (identity → bitmask) + Event Status (event → status). O(168) hashes per operation.
RFC 9162 append-only tree over bundles. Inclusion and consistency proofs detect forks.
bundle = {
events: [event_id_0, ..., event_id_N], -- ordered by seq
state_hash: SMT root after last event
}
events_root:
N=1 → event_ids[0] -- no tree needed
N>1 → binary Merkle tree, internal = H(0x01, L, R)
right-pad with last event_id to power of 2
leaf_hash = H(0x00, events_root, state_hash)
-- CT tree: bundles are leaves, RFC 9162 §2.1
-- Right-pad with last bundle's leaf_hash to power of 2message = "enc:sth:" || be64(t) || be64(ts) || r -- 8+8+8+32 = 56 bytes sig = schnorr(sha256(message), seq_priv) -- Wire format: { "t": 1706000000000, "ts": 1000, "r": "<hex64>", "sig": "<hex128>" }
{
"k": "<hex42>", // 21-byte SMT key
"v": "<hex|null>", // leaf value (null = non-membership)
"b": "<hex42>", // 168-bit bitmap (which siblings present)
"s": ["<hex64>"] // non-empty sibling hashes only
}
// Verify: compute leaf → walk 167..0 using bitmap → compare rootSet in Manifest (immutable). Close when size events accumulated OR timeout ms passed (whichever first). Timeout uses event timestamps, not wall clock. Default: size=256, timeout=5000. Set size=1 for per-event state verification.
Migration transfers an enclave to a new node while preserving the full event log and enclave identity. Only Owner can issue Migrate. Two modes:
Old node online. Owner submits Migrate → old node finalizes as last event → transfers log → new node verifies ct_root → continues sequencing.
Old node offline. Owner signs unfinalized Migrate → submits log + commit to new node → new node replays entire log → verifies everything → finalizes Migrate itself.
{
"new_sequencer": "<new_seq_pub>",
"prev_seq": 1234,
"ct_root": "<ct_root_at_prev_seq>"
}Forced takeover verification: (1) replay all events computing SMT state, (2) verify SMT root matches state_hash in final bundle, (3) verify Migrate sender has Owner bit, (4) recompute CT root — must match ct_root, (5) verify commit signature, (6) verify prev_seq matches last event's seq.
Migration barrier: Once accepted, Migrate is the final event. The node rejects all pending commits, closes the bundle immediately. The ct_root checkpoint proves both log and state.
Backup pattern: To enable forced takeover, ensure someone has the full log — either Owner stores locally, or assign a Backup role with {"event": "*", "role": "Backup", "ops": ["P"]}.
Two layers: transport encryption (always on, ECDH + XChaCha20Poly1305) and content encryption (application-chosen middleware). The node is a blind relay for encrypted content.
| Context | Client Key | Node Key | HKDF Label |
|---|---|---|---|
| Query request | signer_priv | seq_pub | "enc:query" |
| Query response | signer_priv | seq_pub | "enc:response" |
| Push delivery | to (recipient) | seq_priv | "enc:push" |
Nonce: random 24 bytes, prepended to ciphertext. Wire format: nonce || ciphertext || tag (min 40 bytes).
| Scheme | Transport | Evolution | Use Case |
|---|---|---|---|
Plaintext | Direct | Static | Public timeline, registry |
DH_Session | Direct | Epoch | DM conversations |
Group_Lazy | Tree N | Epoch (24h rotation) | Casual group chat |
Group_Secure | Tree N | Ratchet | High-security groups |
theorem dm_roundtrip (senderPriv recipientPriv : ByteVec 32) (senderPub recipientPub : PubKey) (hSender : senderPub = derivePubKey senderPriv) (hRecipient : recipientPub = derivePubKey recipientPriv) (plaintext : ByteArray) : decryptFromSender recipientPriv senderPub (encryptForRecipient senderPriv recipientPub plaintext) = some plaintext
All communication uses a single endpoint (POST /) with request type detection, plus proof retrieval endpoints. Content-type: application/json.
| Method | Path | Auth | Description |
|---|---|---|---|
POST | / | Sig | Submit commit (detected by exp field) |
POST | / | Session | Query events (detected by type: "Query") |
POST | / | Session | Pull missed batches (detected by type: "Pull") |
WS | / | Session | Real-time subscriptions |
GET | /:enc/sth | Public | Signed Tree Head |
GET | /:enc/consistency | Public | CT consistency proof |
POST | /inclusion | R | CT inclusion proof |
POST | /bundle | R | Bundle membership proof |
POST | /state | R | SMT state proof |
First valid Query creates a subscription (node assigns sub_id). Events stream as encrypted messages. One connection supports multiple identities, each with their own session.
| Direction | Type | Description |
|---|---|---|
| C → N | Query | Subscribe (first valid Query) |
| C ← N | Event | Stored + live events (encrypted) |
| C ← N | EOSE | End of stored events marker |
| C → N | Commit | Write event → Receipt |
| C → N | Close | Unsubscribe from sub_id |
| C ← N | Closed | Subscription terminated (with reason) |
{
"id": "<hex64> | [...]",
"seq": "<uint> | [...] | Range",
"type": "<string> | [...]",
"from": "<hex64> | [...]",
"tags": { "r": "abc..." }, // tag filter
"timestamp": { "start_at": N, "end_before": M },
"limit": 100, // max 1000
"reverse": false
}Node aggregates events into a single queue per (identity, url) pair across all enclaves. One push_seq counter for gap detection. Pull fallback via POST / with type: "Pull".
{
"push_seq": 130,
"push": { "enclaves": [{ "enclave": "...", "events": [...] }] },
"notify": { "enclaves": [{ "enclave": "...", "seq": 150 }] }
}| Code | HTTP | Description |
|---|---|---|
INVALID_COMMIT | 400 | Malformed commit structure |
INVALID_HASH | 400 | Hash doesn't match CBOR encoding |
INVALID_SIGNATURE | 400 | Signature verification failed |
EXPIRED | 400 | Commit exp < current time |
DUPLICATE | 409 | Commit hash already processed |
UNAUTHORIZED | 403 | Insufficient RBAC permissions |
ENCLAVE_PAUSED | 403 | Only Resume/Terminate/Migrate accepted |
ENCLAVE_TERMINATED | 410 | Enclave is terminated |
ENCLAVE_MIGRATED | 410 | Enclave has migrated |
SESSION_EXPIRED | 401 | Session token expired |
BITMASK_MISMATCH | 400 | Move/Force_Move from ≠ current state |
OWNER_SELF_REVOKE_FORBIDDEN | 400 | Cannot self-revoke Owner |
OWNER_BIT_PROTECTED | 400 | Force_Move cannot touch Owner bit |
The Registry is an enclave with a DataView server providing discovery. Three event types: Reg_Node, Reg_Enclave, Reg_Identity — all Content Events within the Registry enclave, updatable and deletable.
| Endpoint | Returns |
|---|---|
GET /nodes/:seq_pub | Node endpoints, protocols, enc_v |
GET /enclaves/:enclave_id | Enclave metadata, sequencer, creator |
GET /resolve/:enclave_id | Combined enclave + node lookup |
GET /identity/:id_pub | Identity's registered enclaves map |
Reg_Node: Signed by seq_pub. Contains endpoints (max 10) with priority, protocols, enc_v. Reg_Enclave: Contains the full finalized Manifest event. For post-Transfer_Owner updates, includes owner_proof (STH + CT proof + SMT proof of Owner bit). Reg_Identity: Maps id_pub → enclaves (max 256 entries).
New submissions supersede existing entries for the same key (implicit Update in SMT). First Reg_Enclave for an enclave_id wins; conflicts are errors, not updates.
ENC PROTOCOL · FTA v2.3 · 48 THEOREMS · 83+ LEAN PROOFS · 0 SORRY