OpenZeppelin / OpenZeppelin/guardian
Encrypted opaque storage: a general primitive for private off-chain account data
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 10
- Forks
- 20
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 43
Description
Motivation
Guardian already coordinates per-account state and authenticates the account's cosigners. What it doesn't offer is a place to durably store arbitrary private data that has no on-chain representation and can't be rebuilt from chain — and that the operator must not be able to read.
Many integrations need this. A wallet needs to persist contacts, trusted dApps, settings, and portfolio history. A dApp or bridge integration may need to stash per-account preferences, session metadata, or app-specific state. Today each of these has to stand up its own backend, even though Guardian is already the per-account, authenticated service they talk to.
Rather than solve this once per integration, add a general storage primitive to Guardian: encrypted, opaque, per-account. Wallet metadata is the first concrete consumer, not the scope.
Proposal
A small opaque, namespaced blob store scoped per account. Clients store named, versioned blobs; Guardian only ever sees ciphertext. The client encrypts before PUT and decrypts after GET with a key it derives and holds locally — Guardian never receives it and never interprets the bytes.
This is the same architectural move as #266: after that change Guardian treats proposals as opaque blobs with no server-side parsing. Storage is opaque by design — Guardian never indexes, validates, or type-checks contents. It's a dumb, encrypted, per-account key-value store. (Related: #255 META - Privacy.)
Example use case — Bread Wallet private metadata. The wallet writes namespaces like contacts, trusted_apps, settings, portfolio_history, encrypted under a key derived from the user's recovery phrase, so a new device restores and decrypts them during recovery. Guardian sees only opaque blobs.
Proposed API
Reuses the existing auth headers (x-pubkey / x-signature / x-timestamp), validated against the account's authorized cosigner list — no new auth.
GET /storage -> [{ key, version }] # cheap sync check
GET /storage/:key -> { version, value } # value = opaque ciphertext
PUT /storage/:key -> body { value }, header If-Match: <version>
# 201 on write, 409 on version mismatch (CAS)
DELETE /storage/:key -> tombstone
Data model
One additive table, opaque to the server (mirrors states / deltas):
CREATE TABLE account_storage (
account_id VARCHAR(64) NOT NULL,
key VARCHAR(64) NOT NULL, -- caller-defined namespace
version BIGINT NOT NULL, -- monotonic per (account_id, key)
value BYTEA NOT NULL, -- opaque bytes; never read server-side
updated_at TIMESTAMPTZ NOT NULL,
PRIMARY KEY (account_id, key)
);
- Conflicts: optimistic concurrency via
If-Match: <version>— consistent with the nonce "higher wins" rule already used for deltas. - Encryption is the client's responsibility and out of scope for the server; Guardian stores whatever bytes it's handed. Documenting only that the server must remain content-agnostic.
What this needs from Guardian
- The
account_storagetable + the endpoints above, kept fully opaque (no parsing/validation of contents). Small extension to the existing storage backend. - SDK client methods to
get/put/list/deleteentries (analogous to the client-API work already discussed for delta application).
Deliberately low-lift: no policy engine, no server-side crypto — opaque storage plus CRUD.
Open questions
- Per-account quota / value-size cap, and whether storage shares the delta rate-limit / body-size budgets?
- On
switch_guardian, is copying entries to the new Guardian sufficient, or should the SDK orchestrate migration? - Latest-only, or keep a short version history for multi-device conflict forensics?
- Naming: is
account_storage//storagethe right primitive name, or something more specific (e.g./kv)?
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the existing states/deltas storage backend and the client API work for delta application, then review authentication via x-pubkey, x-signature, and x-timestamp. Done means resolving the quota, migration, history, and naming questions and implementing the opaque account_storage schema, storage endpoints, and SDK get/put/list/delete methods with CAS behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust, sql
- Domain
- backend-api-design, databases
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100