Relational state management on-chain for different types of queries
Cubane does not embed a SQL database in a blockchain. It treats relational semantics as a protocol-level abstraction over deterministic, versioned key-value state, and changes the execution strategy for each query. Ten example statements show how.
Not a database inside a chain
A common assumption about encrypted relational execution is that it means running a conventional database engine, such as PostgreSQL or MySQL, inside a blockchain node. That is not what Cubane does, and the difference matters for how state changes are managed.
We manage state changes on top of a relational abstraction, and the relational abstraction is not the same thing as relational database management. Tables, columns, constraints and joins exist as a contract that developers and the protocol share. Underneath, there is no database server. There is deterministic, versioned key-value state that every node holds and every node can replay.
Cubane does not put a conventional SQL database “inside the blockchain.” It makes relational semantics a protocol-level abstraction over deterministic versioned KV state, with the execution strategy changing according to the confidentiality and computational requirements of each query.
Abstraction versus management
A relational database management system is a piece of software that owns its storage, its locks, its query planner and its access control. It is operated by someone, and the people using it trust that operator.
A relational abstraction in Cubane is a set of rules for how relational statements compile into operations on shared state. The rules are part of the protocol. The resulting state transitions are verified the same way as any other transaction, and the storage beneath them is the same versioned key-value store the rest of the chain uses.Two different things
| Conventional RDBMS | Cubane relational abstraction | |
|---|---|---|
| Storage | Pages and files on one operator’s machine | Deterministic versioned KV state, committed per tick |
| Trust | The operator is trusted | Every node reaches the same result by replay |
| Concurrency | Locks or local MVCC | Version vectors (row, tick, branch) shared by all nodes |
| Execution | One engine for every query | Strategy chosen per query by confidentiality and compute need |
| Privacy | Access control around plaintext | Per-column scheme, mode and visibility |
| Deletion | Rows are removed | Tombstone version; erasure by key destruction |
State changes on the abstraction
Every relational statement, whether it reads, inserts, updates or deletes, becomes a set of versioned key-value operations. A read is a load of a specific version at a snapshot tick. A write creates a new version at the current tick. A delete creates a tombstone. Nothing is overwritten in place.
The layers that perform this compilation are strictly separated. Each consumes only the layer below and exposes a typed contract upward, and the bottom layer is required to work without knowing whether a value is plaintext or ciphertext.The abstraction stack
L1
Statement and canonical ASTDeterministic parse: sorted children, no floating point.
L2
Schema registry and column policyTables, constraints and a (scheme, mode, visibility) tuple per column.
L3
MVCC and conflict resolutionVersions identified by (row, tick, branch), independent of value.
L4
KV and index projectionRows to keys; four index kinds, each with a declared leakage class.
L5
Versioned KV state and commitmentsKey, value, version, tombstone. Merkle per tick, KZG per epoch.
One statement, text to commitment
The path a statement takes is the same for every query type. What changes is the plan produced at the policy step, and therefore which execution strategy runs.Statement lifecycle
01StatementParsed into a canonical syntax tree
02Policy lookupEach column’s scheme, mode and visibility
03PlanStrategy and fee quote fixed before execution
04Versioned opsLoad, Store, Delete, PrefixScan, QueryIndex
05CommitmentNew state root bound into the STU
How a strategy is chosen
Each column carries one validated tuple. The scheme says how the value is represented: plaintext, FHE ciphertext, ABE-encrypted plaintext or obfuscated plaintext. The mode says how it may be computed on: full FHE, hybrid, proof-only, or MPC and FHE combined. The visibility says who may see it.
When encrypted computation is needed, the compiler chooses a scheme from a versioned cost table. TFHE handles comparisons and branches. CKKS handles packed approximate arithmetic such as sums and averages. BGV and BFV handle exact integer arithmetic such as balances. The choice follows a fixed precedence of query pragma, then table, then namespace, then protocol default, so it is never decided at random at runtime.
The examples that follow use one illustrative schema and illustrative syntax. The comments under each statement show the plan the abstraction produces.Illustrative schema
CREATE TABLE accounts ( account_id BYTES POLICY (plaintext, hybrid, public), branch TEXT POLICY (plaintext, hybrid, public), balance INT64 POLICY (bgv, full_fhe, confidential), amount INT64 POLICY (ckks, full_fhe, confidential) ); CREATE TABLE customers ( customer_id BYTES POLICY (plaintext, hybrid, public), national_id TEXT POLICY (abe, proof_only, compliance), risk_score INT32 POLICY (tfhe, full_fhe, confidential) );
Example 1: public point read
The simplest case. Every column involved is public, so no encryption is needed. The index lookup and the load happen against a snapshot at a specific tick, which means every node returns the same value even while new versions are being written.Statement
SELECT branch FROM accounts WHERE account_id = :id; -- policy accounts.branch (plaintext, hybrid, public) -- index plaintext B-tree on account_id -- ops QueryIndex -> Load K @ snapshot tick T -- output plaintext value
Example 2: public aggregate
Counting rows by a public column is also a plaintext operation. The abstraction still requires a deterministic plan: the scan order is fixed and the grouping is computed with integer arithmetic, so the result can be reproduced on any node.Statement
SELECT branch, COUNT(*) FROM accounts GROUP BY branch; -- policy all referenced columns public -- strategy plaintext aggregation in the read path -- ops PrefixScan over accounts @ tick T -- output plaintext counts
Example 3: confidential aggregate
Here the query mixes visibilities. The planner splits it: the public filter runs as an ordinary predicate, and the sum runs homomorphically over encrypted amounts, packed so that many values are added in a single operation.
The result is ciphertext, accompanied by a public zero-knowledge label. Dashboards and indexes consume the label. The abstraction never decrypts to answer the query.Statement
SELECT SUM(amount) FROM accounts WHERE branch = 'MUM'; -- policy branch (plaintext, public) | amount (ckks, full_fhe) -- plan mixed-visibility split -- filter: plaintext predicate on branch -- sum: CKKS SIMD-packed addition over ciphertext -- output ciphertext result + public ZK label
Example 4: confidential filter
A comparison on an encrypted column is a boolean operation, so the compiler selects TFHE. Each row is evaluated under encryption. Which rows matched is itself sensitive, so the output is an encrypted set of flags rather than a plaintext list, released only to holders of the right key or policy.Statement
SELECT customer_id FROM customers WHERE risk_score > 70; -- policy risk_score (tfhe, full_fhe, confidential) -- strategy TFHE comparator per row -- output encrypted match flags + proof of correct evaluation
Example 5: exact state change
Balances must be exact, so the compiler selects BGV rather than CKKS. The update does not overwrite the old balance. It reads one version and writes a new one, identified by the row, the tick and the canonical branch.
If the table declares a CHECK constraint, it compiles to a circuit evaluated on the ciphertext. Conflict detection uses only the version identity, so two concurrent updates to the same account are ordered deterministically by the canonical order described in our article on DSPE.Statement
UPDATE accounts SET balance = balance - 250 WHERE account_id = :id; -- policy balance (bgv, full_fhe, confidential) -- strategy BGV exact integer subtraction over ciphertext -- MVCC read version (row, T, 0) -> write version (row, T', 0) -- ops Load K @ T -> Store K V' @ T' -- check CHECK (balance >= 0) compiled to a circuit
Example 6: insert with uniqueness
Uniqueness on an encrypted column cannot be checked by looking at the value. The namespace chooses one of two strategies in its schema. A salted-hash index is cheap but reveals when two values are equal. A zero-knowledge uniqueness proof per insert reveals nothing, at a higher cost. The registry will not let the leakier option be selected silently.Statement
INSERT INTO customers (customer_id, national_id, risk_score) VALUES (:cid, :enc_national_id, :enc_score); -- constraint UNIQUE (national_id) -- option A salted-hash index leakage: equality -- option B per-insert ZK proof leakage: none, higher cost -- ops Store K V @ T' + index update
Example 7: join across tables
Joins are where encrypted systems usually leak, so the abstraction offers three strategies and requires the leakage of each to be declared. A deterministic-encryption hash join is efficient and reveals how many rows match. An FHE merge join reveals less and costs more. A plan computed off-chain can be accepted if its result set is attested by proof. The plan itself, and what it reveals, must be disclosed.Statement
SELECT a.branch, SUM(t.amount) FROM transfers t JOIN accounts a ON t.account_id = a.account_id GROUP BY a.branch; -- strategy A hash join on deterministic-encryption keys -- leakage: join cardinality (declared) -- strategy B merge join via FHE comparison -- strategy C planned off-chain, ZK-attested result set -- aggregate CKKS over joined ciphertext
Example 8: attest without revealing
Some questions need an answer, not a value. A lender may need to know that an account holds at least a threshold, without learning the balance. In proof-only mode the abstraction produces a public result and a proof that it is correct with respect to committed state.Statement
SELECT PROVE(balance >= 10000) AS eligible FROM accounts WHERE account_id = :id; -- mode proof-only -- strategy ZK range proof over the committed balance -- output public boolean + proof; balance never revealed
Example 9: very large aggregate
At scale, the same aggregate is split across workers. Each computes packed partial results, and a recursive aggregation circuit combines them. The fan-out is priced deterministically when the plan is made, so the fee is known before execution, and the work can settle asynchronously off the critical path.Statement
SELECT branch, AVG(amount) FROM transfers GROUP BY branch; -- rows > 1M encrypted rows -- strategy map-reduce across FHE workers -- CKKS packed partial sums per shard -- recursive aggregation circuit -- pricing fan-out priced in the plan before execution
Example 10: erasure
Deletion is a new version, not an absence. For personal data, erasure is achieved by destroying the row’s data key: the encrypted blob may remain, but it can no longer be read. If a legal hold or retention minimum pins the row, key destruction waits until the pin is released, and a verifiable record of deletion is committed.Statement
DELETE FROM customers WHERE customer_id = :cid; -- right to erasure -- MVCC tombstone version (row, T', 0) -- erasure row data key destroyed; blob retained, unreadable -- gate CheckLegalHold(namespace, table, row) before key death -- record zeroisation record + proof of deletion
Strategies at a glance
Across the ten examples, the abstraction and the storage stay the same. Only the strategy changes, chosen by the confidentiality of the columns involved and the kind of computation the statement needs.Statement type to execution strategy
| Statement | Columns | Strategy | Leakage |
|---|---|---|---|
| Point read | Public | Index + snapshot load | Value, by policy |
| Aggregate | Public | Plaintext scan | Value, by policy |
| Aggregate | Mixed | Split plan, CKKS | None beyond public filter |
| Filter | Confidential | TFHE comparators | None |
| Update | Confidential | BGV exact arithmetic | None |
| Insert, UNIQUE | Confidential | Salted hash or ZK proof | Equality, or none |
| Join | Confidential | DET hash, FHE merge, attested plan | Declared per strategy |
| Attestation | Confidential | Proof-only | Result only |
| Large aggregate | Confidential | Map-reduce FHE workers | None |
| Erasure | Any | Tombstone + key destruction | Deletion event |
Leakage is declared
Every index kind and join strategy carries a leakage class in the schema registry. A schema whose leakage is not declared is invalid. This makes the trade between performance and privacy something that can be read and reviewed, rather than something discovered after deployment.
Leakage is declared, never discovered. Undeclared leakage makes a schema invalid.
Why build it this way
Treating relational semantics as a protocol abstraction, rather than embedding a database, gives three properties that a conventional engine cannot. Every state change is a deterministic, replayable function of committed state. The same storage serves public and encrypted data without knowing the difference. And each query gets the execution strategy its data requires, from a plaintext index lookup to a homomorphic join, instead of one engine and one privacy level for everything.