Protocol Specification v1

ENC Protocol

Encode, Encrypt, Enclave — a formally verified protocol for building log-based, verifiable, sovereign data structures with role-based access control.

48FTA Theorems
83+Lean Proofs
0sorry
9API Endpoints
Theory

FTA: A Formal Theory of Applications

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.

§0 — Architectural Scope

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).

Safety invariant: If a bundle is accepted by 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.

§I — Primitives

Three foundations. Everything is what, who, or when.

τ

Values — what exists

Null | Bool | Int | Float | String | Bytes | [τ] | {k:τ} | τ|τ | κ→ν. The data universe.

K

Keys — who controls

Sym Secret | Asym Pub Priv | Shard Pub K N. Six operations: agree, kdf, derive, encrypt/decrypt, sign/verify, hash, split/combine.

T

Time — when it holds

Time = Nat. Window = Time × Time. valid(s,e) now ⟺ s ≤ now ≤ e.

FTA§I — Key operations
-- 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)

Laws

FTA§I — Seven primitive laws
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

Cryptographic Axioms

The theory rests on five named assumptions. All theorems involving keys, signatures, hashing, or encryption depend on one or more:

AxiomStatementENC Instantiation
CRHash is collision resistant (idealized as injective)SHA-256
EUF-CMASignatures existentially unforgeable under CMABIP-340 Schnorr
CDHComputational Diffie-Hellman hard on chosen groupsecp256k1
KDF-OWKey derivation is one-wayHKDF-SHA256
AEAD-SECIND-CPA + INT-CTAG secure authenticated encryptionXChaCha20Poly1305

Idempotency

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.

§II — Identity

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.

FTA§II — Identity & 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)

§III — Trust

Five concerns govern all data. Each composes the three primitives. Having one does not imply another. All five = full trust.

A

Access

Id × Resource × Schema → Bool
Keys govern permission.

O

Origin

τ × Origin → Bool
Keys prove authorship.

S

Secrecy

SymKey × τ → Cipher
Keys hide content.

T

Order

Log → Bool
Time sequences events.

P

Proof

Claim × Evidence × Digest → Bool
Hash verifies state.

Schema

Access rules: Schema = [Rule], Rule = (Resource, Role, [Op]). Set once at creation. Immutable. New schema = new data space.

Origin

FTA§III — Origin types
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

Envelope

Every stored datum: Envelope = (Visible τ, Hidden (Cipher | τ), Origin). Visible fields are queryable plaintext. Hidden content is opaque — may be ciphertext.

Log

FTA§III — Log well-formedness & consistency
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

§IV — Secrecy

agree + kdf + encrypt compose to build any encryption protocol. Transport controls distribution topology. Evolution controls key lifecycle. Time optionally bounds ciphertext and forces rotation.

FTA§IV — Scheme = Transport × Evolution × TTL × 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)

§V — State

Four components: Global (shared), Local (per view), Persist (across sessions), Binding (lifecycle). Optionally, Global can be Convergent (CRDT-compatible with semilattice merge).

FTA§V — State = G × L × P × B
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

Life

One lifecycle type for all bindings. Six states, defined transitions, mandatory timeout enforcement:

FTA§V — Life state machine
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

§VI — Storage

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.

FTA§VI — Storage & Store
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

§VII — Binding

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).

FTA§VII — Binding & pipeline
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]

§VIII — Transition

Three action types. reduce is a pure function of state and action — no external inputs. If access check fails, state is unchanged.

FTA§VIII — Action & reduce
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

§IX — View

render is a pure function of state. No external inputs. The virtual DOM is a tree of elements, text, and empty nodes.

FTA§IX — View
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

§X — Effect

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.

FTA§X — Effect descriptions
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

§XI — Application

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.

FTA§XI — App definition
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

§XIII — Threat Model

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.

AdversaryCapabilitiesDefense
Authority misbehaviorCensor, reorder, forkCT signed-head comparison detects forks
Network attackerEavesdrop, replay, MITMECDH + AEAD per query/response/push
Key compromise (client)Exfiltrate, impersonateEpoch rotation limits historical exposure
Key compromise (authority)Order manipulationNot content access (keys never shared)
ReplayResubmit valid commitsHash dedup + expiration windows

Trust Assumptions

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.

Out of Scope

§XIV — Architecture

FTA§XIV — Full architecture diagram
       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)

§XV — Example: Encrypted Group Chat

A complete group chat using all five trust concerns, both transport and content encryption, the full Life state machine, and App-as-Store composition.

FTA§XV — Chat pipeline (sending a message)
-- 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))
FTA§XV — Epoch transitions (MLS / RBAC sync)
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)
Summary: 3 primitives (Values, Keys, Time) · 3 functions (reduce, effect, render) · 48 theorems. This single example exercises 20 of the 48 theorems, all five trust concerns, both transport and content encryption, the full Life state machine, and App-as-Store composition.

Formal Verification

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.

Proof Architecture

ArchitectureTwo-layer proof structure
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.

Why Typeclasses

Lean 4 has no interfaces or inheritance. Three approaches exist for proving "ENC satisfies FTA":

ApproachMechanismTrade-off
TypeclassesDefine FTA as class, provide ENC instanceGeneric theorems auto-apply; compile-time enforcement
Structure witnessDefine FTA as structure, construct ENC termMore explicit but less composable
Theorem-levelProve each property individually on ENC typesWorks, but correspondence is implicit

CryptoSuite Instance

Lean 4FTA.CryptoSuite — abstract interface + ENC instance
-- 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

App Instance

Lean 4FTA.App — 5 components + theorem obligations
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

Proof Classification

Each of the 48 FTA theorems is witnessed by one of five proof kinds:

KindCountHow proven
Theorem10Machine-checked Lean proof (H_injective, dm_roundtrip, expired_always_rejected, etc.)
Axiom11Cryptographic assumption — the bridge to computational security (sha256_injective, enc_agree_sym, etc.)
Type sig7Encoded in Lean's type system (reduce : S → Action → S proves Preservation + Totality + Purity)
Def3True by definition (Window.valid, Life.canTransition, Persist.set/del)
Structural17Follows from architecture (validation pipeline, dual-sig constructor, CT consistency, etc.)

Full Theorem Mapping

MappingAll 48 FTA theorems → ENC proofs
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_injectiveH_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

Instantiation

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.

48 Theorems

FTA defines 48 theorems across 15 categories. ENC instantiates all of them — 83+ proven in Lean 4 with zero sorry.

SummaryFTA v2.3 — complete theorem coverage
 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

Composition Conditions

46 composition theorems across 4 reference apps (Timeline, Registry, DM, Chat) prove the system holds together.

ConditionCountWhat it proves
C117Every SDK write reduces to createCommit
C214DataView data bounded by schema (P or N)
C35Middleware roundtrip = identity (ECDH + MLS)
C44Every content type creatable by some role
C52Middleware isolation (no cross-contamination)
C64Components compose only from AppSDK

Appendix: Convergent State (CRDT-compatible)

If an application requires multi-writer convergence without a sequencer, G can be configured as Convergent τ Merge where Merge must satisfy full semilattice laws:

FTASemilattice laws for convergent state
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.


Protocol

Overview

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.

Architecture

E

Enclave

A log-based, verifiable, sovereign data structure. Maintains an append-only event log, SMT state, and CT proof tree.

N

Node

Hosts one or more enclaves. Stores data, serves queries, produces proofs, and sequences commits into events.

C

Client

Controls an identity key (id_priv). Produces signatures, encrypts/decrypts, and submits commits.

DV

DataView

Indexes, aggregates, and transforms enclave data for efficient querying. Trusted, not verifiable.

Trust Model

Two query paths exist with fundamentally different trust properties:

PathTrustVerificationUse Case
EnclaveSource of truthSMT + CT proofsDisputes, audits, high-value transactions
DataViewConvenienceNone (trusted)Routine queries, real-time apps

DataView receives data via three methods, each requiring a role assignment:

MethodPermissionDescription
QueryRDataView queries enclave directly as a member
PushPNode delivers full events proactively
NotifyNNode delivers lightweight notifications; DV fetches as needed

Sequencer Model

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.


Primitives

Cryptographic Primitives

Five cryptographic axioms underpin every theorem. Each is instantiated with a concrete algorithm and modeled as idealized for formal reasoning.

CR

Collision Resistance

SHA-256 (FIPS 180-4). Modeled as injective: H(a) = H(b) → a = b.

Sig

EUF-CMA

BIP-340 Schnorr over secp256k1. Deterministic signing with zero auxiliary randomness.

DH

CDH

secp256k1 ECDH. Computational Diffie-Hellman assumption on the curve.

KDF

KDF-OW

HKDF-SHA256. One-way key derivation: derive(k, l) does not reveal k.

AE

AEAD-SEC

XChaCha20Poly1305. IND-CPA + INT-CTAG secure authenticated encryption.

The Canonical Hash Function

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.

Lean 4The canonical hash function
-- 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

Hash Prefix Registry

All hash operations use a unique single-byte prefix to prevent cross-domain collisions. Signature contexts use string prefixes for domain separation.

PrefixDomainUsage
0x00CT LeafRFC 9162 leaf hash
0x01CT NodeRFC 9162 internal node
0x10CommitClient-generated commit hash
0x11EventSequencer-finalized event hash
0x12Enclave IDContent-addressed enclave identity
0x20SMT LeafSparse Merkle Tree leaf
0x21SMT NodeSparse Merkle Tree internal node
ContextString PrefixUsage
STH Signature"enc:sth:"56-byte message → SHA-256 → Schnorr
Session Token"enc:session:"Message → SHA-256 → Schnorr

Wire Format

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.


Identity

Identity

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.

Lean 4FTA.Identity
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 : IdNat
  | .anon       => 0    -- cannot create sessions
  | .hold _     => 1    -- can sign commits
  | .share _ _  => 0    -- single shard grants no capability
  | .attest _ _ => 2    -- proved via signature

Session Tokens

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.

ProtocolSession derivation (68 bytes)
-- 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

Per-Node Signer Derivation

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.

ProtocolSigner derivation
t           = sha256(session_pub || seq_pub || enclave)
signer_priv = session_priv + t  (mod n)
signer_pub  = session_pub  + t · G

Events

Event Lifecycle

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.

Hash Chain Construction

ProtocolCore hash chain
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)
Key insight: The event_id commits to both client intent (via sig inside event_hash) and node ordering (via seq_sig). Identical inputs produce identical commit hashes, enabling dedup. Two Manifests with identical content produce the same enclave_id — include a unique value in meta to differentiate.

Commit Structure

FieldTypeDescription
hashhex64Commitment hash of fields
enclavehex64Destination enclave
fromhex64Author identity key
typestringEvent type (schema-scoped)
contentstringUTF-8 payload (opaque to node)
expuintExpiration (Unix ms) — also acts as nonce
tagsarrayNode-level metadata (see Tags)
sighex128Schnorr signature over hash

Tags

Tags provide node-level processing instructions (not query indexes). Content is opaque to the node; tags are understood.

TagDescription
rReference — ["r", "event_id", "context"]. Context: target, reply, quote, thread
auto-deleteNode removes content after this Unix ms timestamp (trust-based, no SMT update)

Event Finalization

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.

Validation Pipeline

1. Lifecycle Gate 2. Expiration 3. Dedup 4. RBAC 5. Type-Specific Accept
Lean 4Validation — proven properties
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

Replay Protection

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.

Predefined Event Types

CategoryEventsModifies SMT
ACManifest, Grant, Grant_Push, Revoke, Revoke_Self, Move, Force_Move, Transfer_Owner, AC_Bundle✓ (RBAC)
MutationUpdate, Delete✓ (Status)
LifecyclePause, Resume, Terminate, Migrate
ContentEverything 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.

Update & Delete

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 ResultInterpretation
Non-membership (null)Active OR never existed (combine with CT proof to distinguish)
0x00 (1 byte)Deleted
32-byte IDUpdated to this event_id

Access Control

RBAC

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.

Operations

OpMeaningApplies to
CCreate — can submit commitsAll events
RRead — can query eventsAll events
UUpdate — can update eventsContent Events only
DDelete — can delete eventsContent Events only
PPush — receives full eventsAll events
NNotify — receives metadata onlyAll events

Reserved Roles

BitRoleStored in SMTDescription
0SelfNo (dynamic)Matches event.from — for U/D, matches original author
1OwnerYesMust be assigned in initial_state
2NodeNo (dynamic)Matches current sequencer
3AnyNo (implicit)Everyone. Cannot have P or N.
4–31ReservedFuture protocol use
32+CustomYesApp-defined, ordered by schema position

Schema Entry Structure

JSONSchema entry
{
  "event": "Grant",          // event type or "*" wildcard
  "role": "Admin",           // role name
  "ops": ["C"],              // permitted operations
  "target_roles": ["Member"] // privilege escalation bounds
}

Privilege Escalation Prevention

Lean 4RBAC — proven properties
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

AC Events

TypeEffectContent
GrantGrant role to identity (pull){ role, identity }
Grant_PushGrant role + register webhook{ role, identity, endpoint }
RevokeRevoke role from identity{ role, identity }
Revoke_SelfRemove own role (not Owner){ role }
MoveAtomically change bitmask{ identity, from, to }
Force_MoveBypass target_roles (no Owner){ identity, from, to }
Transfer_OwnerAtomic ownership transfer{ new_owner }
AC_BundleAtomic 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.

P (Push) & N (Notify)

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.

Enclave Lifecycle

StateConditionAcceptsReads
ActiveNo Terminate/Migrate; not pausedAll (per RBAC)Yes
PausedLast lifecycle = PauseResume, Terminate, MigrateYes
TerminatedTerminate existsNoneUntil deleted
MigratedMigrate existsNoneNo 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.


State

Enclave State (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).

PropertyValue
Total depth168 bits (21 bytes)
Namespace prefix8 bits (1 byte)
Entry path160 bits (20 bytes)
Hash functionSHA-256
Empty valuesha256("") = 0xe3b0c44...
Update cost168 hashes ≈ 170μs
Throughput~6,000 updates/sec

Key Construction

ProtocolSMT key derivation
-- 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

Leaf Values

NamespaceValueMeaning
RBAC256-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 Status0x00 (1 byte)Deleted
Event Status32-byte event_idUpdated to this event

Hash Construction

ProtocolSMT hashing
leaf_hash  = H(0x20, key, value)
node_hash  = H(0x21, left_child, right_child)
empty_hash = sha256("")    -- hardcoded constant

State Binding

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).


Proofs

Proof System

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.

SM

Sparse Merkle Tree

168-bit depth. RBAC (identity → bitmask) + Event Status (event → status). O(168) hashes per operation.

CT

Certificate Transparency

RFC 9162 append-only tree over bundles. Inclusion and consistency proofs detect forks.

Bundle Structure

ProtocolCT leaf construction
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 2

Full Event Proof (3 Layers)

1. Bundle Membership 2. CT Inclusion 3. SMT State
event_id ∈ events_root → leaf_hash ∈ CT tree → SMT.verify(key, value, state_hash)

Signed Tree Head (STH)

ProtocolSTH structure (56-byte message)
message = "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>" }

SMT Proof Wire Format

JSONSMT proof structure
{
  "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 root

Bundle Configuration

Set 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

Migration

Migration transfers an enclave to a new node while preserving the full event log and enclave identity. Only Owner can issue Migrate. Two modes:

P

Peaceful Handoff

Old node online. Owner submits Migrate → old node finalizes as last event → transfers log → new node verifies ct_root → continues sequencing.

F

Forced Takeover

Old node offline. Owner signs unfinalized Migrate → submits log + commit to new node → new node replays entire log → verifies everything → finalizes Migrate itself.

JSONMigrate event content
{
  "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"]}.


Secrecy

Secrecy & Encryption

Two layers: transport encryption (always on, ECDH + XChaCha20Poly1305) and content encryption (application-chosen middleware). The node is a blind relay for encrypted content.

Transport Encryption

ContextClient KeyNode KeyHKDF Label
Query requestsigner_privseq_pub"enc:query"
Query responsesigner_privseq_pub"enc:response"
Push deliveryto (recipient)seq_priv"enc:push"

Nonce: random 24 bytes, prepended to ciphertext. Wire format: nonce || ciphertext || tag (min 40 bytes).

Content Encryption (Middleware)

SchemeTransportEvolutionUse Case
PlaintextDirectStaticPublic timeline, registry
DH_SessionDirectEpochDM conversations
Group_LazyTree NEpoch (24h rotation)Casual group chat
Group_SecureTree NRatchetHigh-security groups
Lean 4ECDH roundtrip — proven
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

API

Node API

All communication uses a single endpoint (POST /) with request type detection, plus proof retrieval endpoints. Content-type: application/json.

Endpoints

MethodPathAuthDescription
POST/SigSubmit commit (detected by exp field)
POST/SessionQuery events (detected by type: "Query")
POST/SessionPull missed batches (detected by type: "Pull")
WS/SessionReal-time subscriptions
GET/:enc/sthPublicSigned Tree Head
GET/:enc/consistencyPublicCT consistency proof
POST/inclusionRCT inclusion proof
POST/bundleRBundle membership proof
POST/stateRSMT state proof

WebSocket Model

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.

DirectionTypeDescription
C → NQuerySubscribe (first valid Query)
C ← NEventStored + live events (encrypted)
C ← NEOSEEnd of stored events marker
C → NCommitWrite event → Receipt
C → NCloseUnsubscribe from sub_id
C ← NClosedSubscription terminated (with reason)

Filter

JSONQuery filter (all fields optional)
{
  "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
}

Webhook Delivery

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".

JSONPush payload (encrypted content)
{
  "push_seq": 130,
  "push":   { "enclaves": [{ "enclave": "...", "events": [...] }] },
  "notify": { "enclaves": [{ "enclave": "...", "seq": 150 }] }
}

Error Codes

CodeHTTPDescription
INVALID_COMMIT400Malformed commit structure
INVALID_HASH400Hash doesn't match CBOR encoding
INVALID_SIGNATURE400Signature verification failed
EXPIRED400Commit exp < current time
DUPLICATE409Commit hash already processed
UNAUTHORIZED403Insufficient RBAC permissions
ENCLAVE_PAUSED403Only Resume/Terminate/Migrate accepted
ENCLAVE_TERMINATED410Enclave is terminated
ENCLAVE_MIGRATED410Enclave has migrated
SESSION_EXPIRED401Session token expired
BITMASK_MISMATCH400Move/Force_Move from ≠ current state
OWNER_SELF_REVOKE_FORBIDDEN400Cannot self-revoke Owner
OWNER_BIT_PROTECTED400Force_Move cannot touch Owner bit

Registry

Registry

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.

EndpointReturns
GET /nodes/:seq_pubNode endpoints, protocols, enc_v
GET /enclaves/:enclave_idEnclave metadata, sequencer, creator
GET /resolve/:enclave_idCombined enclave + node lookup
GET /identity/:id_pubIdentity's registered enclaves map

Registration Flow

1. Node → Reg_Node 2. Client → Manifest 3. Client → Reg_Enclave 4. Client → Reg_Identity

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