# \[Account | User\]-centric Private Blockspace

**URL:** <https://forum.celestia.org/t/account-user-centric-private-blockspace/2155>\
**Category:** Privacy\
**Created:** [August 20, 2025, 3:06pm UTC](https://forum.celestia.org/t/account-user-centric-private-blockspace/2155 "2025-08-20T15:06:13Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![nuke](https://forum.celestia.org/user_avatar/forum.celestia.org/nuke/32/1246_2.png) [@nuke](https://forum.celestia.org/u/nuke)\
**Post date:** [August 20, 2025, 3:06pm UTC](https://forum.celestia.org/t/account-user-centric-private-blockspace/2155/1 "2025-08-20T15:06:13Z")

</div>

This post outlines a model that addresses valid critiques around Private Blockspace’s present model that has Protocol Operators (POs) in control of encryption key management.  
Ultimately, the encryption keys have a DA problem: you must have robust solutions to prevent POs from withholding the data by withholding the keys.

## Why Private Blockspace?

The web3 industry is moving away from it’s original form where all state is public. We now are emerging into a world where state is mostly offchain and thus putting the responsibility of maintaining it on individuals a (exclusive) subsets of users, not the entire network.

If user state is never shared, or latter lost by all parties, it is likely that their account is “frozen” and irrecoverable, perhaps even as much as the protocol itself being unable to progress globally.  
A good solution to this is to _publicly_ ensure data availability (DA) over that data.

**I need DA, but what if we don’t want data to be publicly _readable_?**  
We need a way to obtain DA properties on private data.

## Goal

Enable user-specific encryption of their state, stored publicly, and **_always_** accessible to users and POs even under hostile conditions using Celestia for DA.

- Users **must** be able to determine if offchain global state for a protocol includes their account state.
- Users **must** be able to selectively disclose and make proofs about their latest state (and **may** be able to do so for historical state as well).
- Protocols **should** ensure user encrypted state is posted on Celestia before progressing global state.
- Users **should** be able to progress the protocol by proving a correct STF on their state onchain without any POs involvement - enabling “forced” state updates via proofs on their state (likely to “exit” the protocol, like taking tokens out, etc.).

Desired properties of encryption:

- Users **should** have the ultimate control of encryption keys used, rather than relying on POs to create, distribute, and maintain them.
- Encryption schemes **should** provide means to enable [forward secrecy](https://en.wikipedia.org/wiki/Forward_secrecy) and post-compromise security properties on state.
- Existing Key Management Systems (KMS) should be easy to integrate for POs and users.

### Initial use cases

A few related ideas:

- **Much more advanced ideas we can learn a lot from: [Keyhive: Local-first access control](https://www.inkandswitch.com/keyhive/notebook/#layers)**
- [Private state management solution · Issue #3 · 0xMiden/miden-proposals · GitHub](https://github.com/0xMiden/miden-proposals/issues/3#issuecomment-3013845161)
- CLOB DEX that has trusted POs progressing all state with ZKPs, and wants private user state backups and ways to force include transactions (later is out of scope, but should be kept in mind)

### Why not \<some other storage\>?

> Where should state be stored? IPFS? Matrix? Centralized cloud? All of them?

Some example use cases that need DA:

- Enforce that a protocol _cannot_ progress private user state data is not made available. Thus you ensure users can always exit, possibly further able to force include any transaction type into the protocol.  
They are guaranteed DA for a 30 day window \[1\] to be able to get the data needed to exit if the DEX is not cooperating and/or down.

- Users can make proofs about their private state, know to be publicly accessible where you would be able to build in protocol mechanisms to monitor for such a **censorship resistant message** on Celestia.  
Example: “I hold X tokens on this private DEX. Force exit these tokens from the protocol.”

### Why not just authenticated encryption ([AEAD](https://en.wikipedia.org/wiki/Authenticated_encryption)) ?

While you can prove that some public data was embedded into the encryption operation, you cannot constrain the plaintext at all.  
You must fully trust the publisher that some associated data like the hash of the plaintext didn’t lie in creating the ciphertext.  
It’s quite possible to commit a “hash” of _something_ into the AEAD object, but that hash is not required to be related to the plaintext in any way.

Verifiable Encryption (VE) enforces that some ciphertext _contains_ plaintext with specific properties (like the plaintext hash), and constrains the it to be constructed correctly (using a specific key, nonce, algorithm used, etc.)

## Assumptions

- **POs have a “god’s eye view”: they know all protocol state and also all per-user encryption keys.** \[2\]
- All user state is accounted for in a Merkle Tree (T) that uses some State Transition Function (STF) protocol that progresses T^n \rightarrow T^{n+1}.
- POs with global protocol state can _prove publicly_ that the STF indeed used the state (or state update) that is fully described by T and has root R.
  - Likely requires a ZKP of the STF.

- T has a Merkle Root (R) that any user can obtain a Merkle Proof (\gamma\_{user}) for that proves inclusion in T.
- Users coordinate with PO to establish a means of generating a user-specific symmetric key (s\_key\_{user}) that the PO stores for all users, and each user stores for themselves.
  - s\_key\_{user} may be generated via standard [key exchange protocols](https://en.wikipedia.org/wiki/Key_exchange), such as ECDH or ML-KEM. You may encrypt “to an address” with something like [Nuke / ECIES Demo · GitLab](https://gitlab.com/nuke-web3/ecies-demo/) or [GitHub - str4d/rage: A simple, secure and modern file encryption tool (and Rust library) with small explicit keys, no config options, and UNIX-style composability.](https://github.com/str4d/rage) .

## Protocol Outline (ZK Enforced)

```mermaid
flowchart TD
    subgraph MerkleHashes["Tree $$\ (T)$$"]
        Root["Root $$\ (R)$$"]
        L1["$$H(H_{1},H_{2})$$)"]
        L2["$$H(H_{3},H_{4})$$)"]
        LH1["$$H_{user=1}$$ = Leaf Hash"]
        LH2["$$H_{2}$$"]
        LH3["$$H_{3}$$"]
        LH4["$$H_{4}$$"]

        Root --> L1
        Root --> L2
        L1 --> LH1
        L1 --> LH2
        L2 --> LH3
        L2 --> LH4
    end

    subgraph POonly["PO known states<br>(NOT on Celestia)"]
        D1["$$state_{user=1}$$"]
        D2["$$state_2$$"]
        D3["$$state_3$$"]
        D4["$$state_4$$"]
    end

    LH1 --> D1
    LH2 --> D2
    LH3 --> D3
    LH4 --> D4

    PD1["$$ZKP(encrypted(state_1), H_1)$$"]
    PD2["$$ZKP(encrypted(state_2), H_2)$$"]
    PD3["$$ZKP(encrypted(state_3), H_3)$$"]
    PD4["$$ZKP(encrypted(state_4), H_4)$$"]

    D1 -- "$$VE(state_{user=1},s\_key_{user=1})$$" --> PD1
    D2 -- "$$VE(state_{2},s\_key_{2})$$" --> PD2
    D3 -- "$$VE(state_{3},s\_key_{3})$$" --> PD3
    D4 -- "$$VE(state_{4},s\_key_{4})$$" --> PD4

    subgraph EncryptedStates["VE of states (on Celestia)"]
        PD1
        PD2
        PD3
        PD4
    end

    PD1 .-> LH1

```

We publicly disclose the Merkle Tree T: All tree node _hashes_.  
We also only encrypt the leaf _values_ (user-specific state data) as we **assume there is no need to hide the remaining tree structure** as no state information is disclosed in it.\[3\]

```mermaid
flowchart TD
    User[/"User"\]
    DA[("Celestia Data Availability")]

    subgraph " "
        direction TB
        PO[/"Protocol Operator (PO)"\] -- "$$verifiably\_encrypt(state_{user}, s\_key_{user})$$" --> EncProofPerUser["User encrypted $$\ ZKP(state_{user}, H_{user})\ $$ packets"]
        PO -- "Tree $$\ T\ $$ of user states, leaves omitted" --> ZKP["$$T$$"]
    end

    ZKP --> DA
    EncProofPerUser --> DA

    subgraph " "
        direction TB
        User -- "$$verify(ZKP) \rightarrow H_{user} \ \ \&\& \ \ decrypt(state_{user}, s\_key_{user})$$" --> UserProof["$$(state_{user}, H_{user})$$"]
    end

    EncProofPerUser -- "Receive update <br/> (happy path)" --> User
    DA -- "Receive update <br/> (fallback)" --> User

```

1. PO _verifiably_ encrypts user’s state (state\_{user}) with user-specific symmetric key (s\_key\_{user}), and commits to it’s hash H\_{user} that is included in T with root R .
  - Verify ZKP so users know it’s unmodified upon decryption

2. PO produces a ZKP _for each user_ with:
  1. Input = state\_{user} and associated s\_key\_{user}
  2. Hash the state to arrive at identical leaf hash (H\_{user})
  3. Commit H\_{user} in VE as “anchor” to T that contains H\_{user}

3. PO submits to DA:
  - T
  - ZKP of Verifiably Encrypted user states proving T as public “anchor” that all H\_{user} reference,

4. User obtains ZKP of their encrypted state data from PO (happy case) or DA (if P0 is misbehaving)
5. User verifies ZKP to obtain $(encrypted(state),H\_{user})$
6. User decrypts to obtain state\_{user}
7. User knows state\_{user} is included in T with root R, thus affirming their state is in T that only POs know globally.

With [Blobstream](https://docs.celestia.org/how-to-guides/blobstream), we may enforce DA happens in protocol, ensuring state data was published on DA layer before progressing on any EVM chain.\[4\]

## User-Controlled Key Management Protocol

In the [original vision of VE protocol-centric models](https://github.com/celestiaorg/private-blockspace-proxy/blob/main/doc/verifiable_encryption.md), the POs would be the the only parties who had sole access of the symmetric keys to encrypt with, and thus undefined methods for key recovery (threshold decryption, MPC, etc.) was required.  
In user-centric models, users **must** know the keys for normal operations, and thus no key recovery mechanism is generally required as all the keys needed are already shared with parties that need them.

We can go further to enforce that encryption keys are generated and held by users in such a fashion that POs cannot progress without using the user’s defined key selection to encrypt with.

#### Implementation Proposal

With the goal to be easy to integrate, well supported cryptographic standards and protocols should be use.  
The [Messaging Layer Security (MLS)](https://messaginglayersecurity.rocks/) is a emerging standard that could be a great fit for use here.  
It defines how to couple protocols:

- Key exchange and management
- Authentication Services
- Delivery Delivery

MLS is a bit over complex for one-to-one session that will likely be the most common type of communication, but as implementations exist making using MLS relatively easy, and overhead is not imposed on anything we need to run the the zkVM, it’s a solid basis to expand into multi-party selective disclosure solutions latter on.

Below is a basic MLS workflow to establish a three participant group that includes a single user, a protocol operator, and an auditor.  
As an example, we use a Directory Service as some account based blockchain, mapping account addresses to their respective full public keys is needed. MLS Directory Service and Group Channel are represented as teh protocol’s settlement layer blockchain and Celestia, respectively. Specifics of what the Directory and Channels are could be adjusted to fit the use cases.

```mermaid
sequenceDiagram
    participant A as User [A]
    participant B as Protocol Operator [B]
    participant C as (Optional) Auditor [C]
    participant Dir as Directory (L1 PubKeys)
    participant DA as Group Channel (Celestia)

    A->>Dir: Upload KeyPackageA
    B->>Dir: Upload KeyPackageB
    C->>Dir: Upload KeyPackageC

```

> Clients A, B, and C publish KeyPackages to the directory

```mermaid
sequenceDiagram
  participant A as User [A]
  participant B as Protocol Operator [B]
  participant C as (Optional) Auditor [C]f
  participant Dir as Directory (L1 PubKeys)
  participant DA as Celestia (Group Channel)

  Dir->>A: download KeyPackageB, KeyPackageC
  A->>DA: Add(A→AB)<br/>Commit(Add)
  A->>B: Welcome(B)
  DA->>A: Add(AB→ABC)<br/>Commit(Add)
  A->>C: Welcome(C)
  DA->>A: Add(AB→ABC)<br/>Commit(Add)
  DA->>B: Add(AB→ABC)<br/>Commit(Add)

```

> Client A creates a group with clients B and C

## Next Steps

Gather feedback from the community on these ideas and plan for proof-of-concept implementation.

🙏 **Please comment on this post!**  
We are eager to understand what issues you spot, and even more so what use cases you would want to pursue with it!

### Further Reading

- [Living Research Document](https://hackmd.io/c3Fh06UcTK-ruElfdDn8Ug?both#User-Controlled-Key-Management-Protocol)
- [Verifiable Encryption (VE) explainer & Private Blockspace Proxy Service](https://github.com/celestiaorg/private-blockspace-proxy/blob/main/doc/verifiable_encryption.md)
- Messaging Layer Security (MLS) - [home](https://messaginglayersecurity.rocks/), [Rust impl](https://github.com/openmls/openmls)

* * *

1. window 

2. This could maybe be relaxed with more thinking, possibly requires something like FHE/MPC to do 🤔 … Open to ideas! 

3. The overhead of encrypting the full Merkle proofs is redundant and costly if there is no problem revealing all but the leaves publicly.  
(If that is an issue, replacing encrypt(\gamma\_{user}, s\_key\_{user}) with verifiably\_encrypt(\gamma\_{user}, s\_key\_{user})) in full would work too. 

4. Important practical note: with [Blobstream](https://docs.celestia.org/how-to-guides/blobstream) integration, POs _should_ never be able to leave any user in an irrecoverable state. BUT this may be impractical to block progress on, as encrypted user data packets need to be _simultaneously_ gathered and all verified. Tooling like the [equivalency service](https://github.com/celestiaorg/eq-service) could be adapted for that purpose: to prove a multi-blob, and even multi-block inclusion of all those packets into one succinct (i.e. groth16) verification on the EVM.

---

<div class="post-metadata">

**Author:** ![apstouka](https://forum.celestia.org/user_avatar/forum.celestia.org/apstouka/32/1537_2.png) [@apstouka](https://forum.celestia.org/u/apstouka)\
**Post date:** [December 4, 2025, 1:17pm UTC](https://forum.celestia.org/t/account-user-centric-private-blockspace/2155/2 "2025-12-04T13:17:26Z")

</div>

Thank you for your proposal! I have the following questions/ suggestions and I would be very happy to hear your thoughts:

- The PO proves for every user that they encrypted the same state whose hash appears in the Merkle tree. But do they also prove that they used the **correct** key—specifically, the user’s symmetric key—to perform this encryption? Such a guarantee would be useful, because, otherwise, a malicious PO could encrypt the state under an arbitrary key, preventing the user from decrypting it later and recovering their state. For the PO to prove that the correct keys were used, there would need to be some form of public commitment to those symmetric keys.

- If a user tries to decrypt their encrypted state and discovers that the PO did not use the correct key, can the user prove this claim _without_ revealing their secret symmetric key? Furthermore, if such a complaint arises—where a user claims that the PO used an incorrect key—can the system enforce accountability to determine whether the PO was actually malicious or whether the user falsely accused the PO?

---

<div class="post-metadata">

**Author:** ![nuke](https://forum.celestia.org/user_avatar/forum.celestia.org/nuke/32/1246_2.png) [@nuke](https://forum.celestia.org/u/nuke)\
**Post date:** [December 16, 2025, 9:34pm UTC](https://forum.celestia.org/t/account-user-centric-private-blockspace/2155/3 "2025-12-16T21:34:00Z")

</div>

Thanks so much for the interest & great questions!

1. Indeed you _can_ use Verifiable Encryption as an end user to publish a key with an “anchored” hash of the key itself, encrypted using _asymmetric_ encryption. \[1\] Thus _everyone_ can know that a specific key was declared by the user, and subsequently the PO can prove they used the same key in encrypting data for that user. A very rough idea on a protocol:
  1. PO generates keypair
  2. PO publishes pub\_key\_{protocol} onchain
  3. User generates a symmetric encryption key they want PO to use
  4. User VE’s their chosen encryption key, using a preestablished public key the POs control a private key for UserKeyProof:
    - VE\_{asymmetric}(pub\_key\_{protocol}, s\_key\_{user}) = Proof(hash(s\_key\_{user}), encrypted\_to\_pubkey(s\_{key}))

  5. User publishes UserKeyProof on settlement chain, verifier checks against pub\_key\_{protocol} for correct encryption from user-\>PO
  6. Onchain STF enforces that VE produced by POs _must_ use whatever each user set onchain \[2\]

Thus there should be _no way_ that incorrect encryption or using an malformed key is possible, keeping everyone fully constrained to use keys counter-parties declare ahead of time, and can update (ideally) at any time. (That would be the goal at least! We would surely need to refine the draft protocol here)

Please do let me know if you have any feedback on this, and/or any further questions @apstouka!

* * *

1. The caveat here being that VE as it’s presently implemented is symmetric only, so a new asymmetric encryption zkVM program or other solution would be needed targeting end user devices. This may be impractically heavy computation at this time… But that said, the user only needs to do this when they want to update a key, so could be as little as one time (but really should be done semi-regularly) 

2. I would think this would be a more natural fit for a contract where settlement is taking place - so user set key hashes can be directly compared to what the PO publishes & rejected if there is a mismatch on attempting an update, but this could also be some aggregate proof offchain, verified onchain too.

---

<div class="post-metadata">

**Author:** ![apstouka](https://forum.celestia.org/user_avatar/forum.celestia.org/apstouka/32/1537_2.png) [@apstouka](https://forum.celestia.org/u/apstouka)\
**Post date:** [January 9, 2026, 3:12pm UTC](https://forum.celestia.org/t/account-user-centric-private-blockspace/2155/4 "2026-01-09T15:12:55Z")

</div>

Thank you very much for your response! Using VE to ensure (i) that the user encrypted correctly the symmetric key and that (ii) PO encrypted the state of every user using the correct symmetric key seems to me a good direction in terms of security. One concern I retain is the efficiency of this approach, since in case (ii) the PO must use VE for every user and for every state update.

---

<div class="post-metadata">

**Author:** ![nuke](https://forum.celestia.org/user_avatar/forum.celestia.org/nuke/32/1246_2.png) [@nuke](https://forum.celestia.org/u/nuke)\
**Post date:** [January 9, 2026, 9:18pm UTC](https://forum.celestia.org/t/account-user-centric-private-blockspace/2155/5 "2026-01-09T21:18:53Z")

</div>

> [@apstouka](#):
>
> PO must use VE for every user and for every state update

Good observation! I think for the likely-to-be-infrequent updates from end-users on their keys, the proving time is less important, and we are only encrypting a key. For the bulk user data, I am confident there are ways to drastically reduce the overhead of proving for VE such that this would not be a bottleneck for realistic apps with ~seconds per state update, but in the present implementation using a zkVM to fully encrypt data, it likely is.

Some initial ideas:

- (minor proof cost reductions, better parallel processing) Avoiding overhead of per-user VE job: VE program modified for most optimal bulk encryption - ideally streaming with proof aggregation as needed. (for very rough benchmarks see the [initial testing](https://github.com/nuke-web3/risc0-chacha/issues/2) and [close-to-present SP1 program](https://github.com/nuke-web3/sp1-chacha/issues/5))
- (large proof cost reduction, increased complexity & perhaps trust) Rather than fully proving all encryption, you can prove a random subset of encryption on a larger batch of data is correct, and sample it. As ChaCha is a stream cypher, we can cheaply encrypt random segments, I would think a carefully crafted [Fiat–Shamir heuristic](https://en.wikipedia.org/wiki/Fiat%E2%80%93Shamir_heuristic) here could assist. Thus
- (radical new scheme, for “zero” overhead VE when posted to DA) Some preliminary ideas mentioned by [Lev Soukhanov](https://www.semanticscholar.org/author/Lev-Soukhanov/2261145694) {[telegram](https://t.me/levs57)} in the ZK Podcast chat ([here](https://t.me/c/1290037485/41974)) around using a [ZODA](https://baincapitalcrypto.com/zoda-zero-overhead-data-availability/)-like construction for “free” encryption on proof of correct encoding (that is far easier to prove)

> You encrypt RS encoding of your cleartext (padded with some blinding factors), with individual values being selectively decryptable.  
> And then you run normal FRI, with 1st round using this ciphertext instead of Merkle tree, and all the rest running normally.  
> So zk is from zk of the original construction. And encryption validity is also from original construction because it guarantees you are close to a codeword.  
> (selective decryption can be achieved in few ways, simplest one is computing pads that you use to respond to 1st round queries in a small accompanying zk argument)  
> So compared to naive version this has almost no overhead. It is almost like zoda’s “[accidental computer](https://baincapitalcrypto.com/the-accidental-computer-polynomial-commitments-from-data-availability/)”, but here we have “accidental encryption” at no overhead.

If there is some real demand for account-centric Private Blockspace, these directions are surely to be explored 😁

---

<div class="post-metadata">

**Author:** ![apstouka](https://forum.celestia.org/user_avatar/forum.celestia.org/apstouka/32/1537_2.png) [@apstouka](https://forum.celestia.org/u/apstouka)\
**Post date:** [January 19, 2026, 11:19am UTC](https://forum.celestia.org/t/account-user-centric-private-blockspace/2155/6 "2026-01-19T11:19:57Z")

</div>

Yes, I agree that efficiency optimisations are more important for the VE used by PO to prove that they used the correct key to encrypt the new state of the user, because this process must occur every time PO updates a user’s state, not only when the user needs to update their symmetric key (e.g., for security reasons), as in the first use case of VE 🙂 .
