ENGINEERING 30 OCT 2026 11 MIN READ

Relational state management on-chain for different types of queries

Cubane · SchematicRelational 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 RDBMSCubane relational abstraction
StoragePages and files on one operator’s machineDeterministic versioned KV state, committed per tick
TrustThe operator is trustedEvery node reaches the same result by replay
ConcurrencyLocks or local MVCCVersion vectors (row, tick, branch) shared by all nodes
ExecutionOne engine for every queryStrategy chosen per query by confidentiality and compute need
PrivacyAccess control around plaintextPer-column scheme, mode and visibility
DeletionRows are removedTombstone 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

StatementColumnsStrategyLeakage
Point readPublicIndex + snapshot loadValue, by policy
AggregatePublicPlaintext scanValue, by policy
AggregateMixedSplit plan, CKKSNone beyond public filter
FilterConfidentialTFHE comparatorsNone
UpdateConfidentialBGV exact arithmeticNone
Insert, UNIQUEConfidentialSalted hash or ZK proofEquality, or none
JoinConfidentialDET hash, FHE merge, attested planDeclared per strategy
AttestationConfidentialProof-onlyResult only
Large aggregateConfidentialMap-reduce FHE workersNone
ErasureAnyTombstone + key destructionDeletion 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.

KEEP READING

Scroll to Top