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 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
- Private state management solution · Issue #3 · 0xMiden/miden-proposals · GitHub
- 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) ?
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, such as ECDH or ML-KEM. You may encrypt “to an address” with something like Nuke / ECIES Demo · GitLab 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. .
Protocol Outline (ZK Enforced)
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]
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
- 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
- PO produces a ZKP for each user with:
- Input = state_{user} and associated s\_key_{user}
- Hash the state to arrive at identical leaf hash (H_{user})
- Commit H_{user} in VE as “anchor” to T that contains H_{user}
- PO submits to DA:
- T
- ZKP of Verifiably Encrypted user states proving T as public “anchor” that all H_{user} reference,
- User obtains ZKP of their encrypted state data from PO (happy case) or DA (if P0 is misbehaving)
- User verifies ZKP to obtain $(encrypted(state),H\_{user})$
- User decrypts to obtain state_{user}
- 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, 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, 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) 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.
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
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
- Verifiable Encryption (VE) explainer & Private Blockspace Proxy Service
- Messaging Layer Security (MLS) - home, Rust impl
window ↩︎
This could maybe be relaxed with more thinking, possibly requires something like FHE/MPC to do
… Open to ideas! ↩︎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. ↩︎Important practical note: with 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 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. ↩︎