Skip to content

add credential storage docs from notion#335

Open
lukejmann wants to merge 5 commits into
mainfrom
credential-storage-documentation
Open

add credential storage docs from notion#335
lukejmann wants to merge 5 commits into
mainfrom
credential-storage-documentation

Conversation

@lukejmann

@lukejmann lukejmann commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

Note

Low Risk
Comment and documentation changes only; no runtime or storage behavior is modified.

Overview
Adds module-level Rust docs across the credential storage layer, largely ported from internal Notion material, so hosts and reviewers can understand layout and behavior without digging through implementation.

The storage crate root (mod.rs) now documents the overall Credential Store model: vault vs cache DB roles, CredentialStore as the UniFFI facade, key usage via walletkit-db, on-disk layout under worldid/, and privacy notes (generic filenames, no RP/action leakage).

paths, traits, and keys docs clarify integration boundaries: what lives under StoragePaths vs the host AtomicBlobStore for account_keys.bin, per-platform keystore/blob expectations, and delegation of the K_deviceK_intermediate hierarchy to walletkit-db.

Cache docs in schema describe the unified cache_entries key-prefix scheme; nullifiers expands replay-guard semantics (NBF grace, TTL retention) and explicitly documents that replay detection is not atomic across concurrent proof flows (sequential reuse only unless callers serialize check-and-set).

types adds a short note that nullifier replay entries are TTL-bounded to avoid a permanent on-device interaction history.

Reviewed by Cursor Bugbot for commit 4f0858c. Bugbot is set up for automated code reviews on this repo. Configure here.

@paolodamico paolodamico force-pushed the credential-storage-documentation branch from 32e2538 to c96c89b Compare July 2, 2026 18:01
@paolodamico paolodamico marked this pull request as ready for review July 2, 2026 20:16
@paolodamico

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 837711ee4e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread walletkit-core/src/storage/paths.rs Outdated
Comment thread walletkit-core/src/storage/cache/nullifiers.rs Outdated
Comment on lines +7 to +9
//! - `0x01 || …` — Merkle inclusion proof; value is the proof bytes.
//! - `0x02 || oprf_seed` — session seed; value is the `session_id_r_seed`.
//! - `0x03 || nullifier` — replay guard; value is a presence marker.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we move

https://github.com/worldcoin/walletkit/blob/main/walletkit-core/src/storage/cache/util.rs#L38-L40

pub(super) const CACHE_KEY_PREFIX_MERKLE: u8 = 0x01;
pub(super) const CACHE_KEY_PREFIX_SESSION: u8 = 0x02;
pub(super) const CACHE_KEY_PREFIX_REPLAY_NULLIFIER: u8 = 0x03;

below this comment?

//! key/value/TTL structure. Entries are distinguished by a 1-byte key prefix
//! followed by type-specific key material (see `cache/util.rs`):
//!
//! - `0x01 || …` — Merkle inclusion proof; value is the proof bytes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//! - `0x01 || …` — Merkle inclusion proof; value is the proof bytes.
//! - `0x01` — Merkle inclusion proof; at most one entry; value is the proof bytes.

Comment on lines +3 to +5
//! All cacheable data lives in a single `cache_entries` table with a unified
//! key/value/TTL structure. Entries are distinguished by a 1-byte key prefix
//! followed by type-specific key material (see `cache/util.rs`):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//! All cacheable data lives in a single `cache_entries` table with a unified
//! key/value/TTL structure. Entries are distinguished by a 1-byte key prefix
//! followed by type-specific key material (see `cache/util.rs`):
//! A table for storing cachable data. Each row has a key (`key_bytes`),
//! value (`value_bytes`) and TTL columns (see `ensure_entries_schema` for details).
//!
//! The keys adhere to the following schema:

Comment on lines +35 to +37
///
/// Bounded rather than indefinite so the cache doesn't grow into a permanent
/// record.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
///
/// Bounded rather than indefinite so the cache doesn't grow into a permanent
/// record.

Comment on lines +7 to +15
//!
//! Each `replay_guard_set` insert is atomic and idempotent. Detection is *not*
//! atomic end-to-end, though: the proof flow calls `is_nullifier_replay` (a read),
//! generates the proof, then calls `replay_guard_set` (the insert) as separate
//! steps, holding no lock across them. Two concurrent requests for the same
//! nullifier can therefore both pass the check before either records its guard and
//! both disclose. This guard defends against sequential reuse, not a concurrent
//! race; callers that need strict single-use must serialize the check-and-set
//! themselves.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//!
//! Each `replay_guard_set` insert is atomic and idempotent. Detection is *not*
//! atomic end-to-end, though: the proof flow calls `is_nullifier_replay` (a read),
//! generates the proof, then calls `replay_guard_set` (the insert) as separate
//! steps, holding no lock across them. Two concurrent requests for the same
//! nullifier can therefore both pass the check before either records its guard and
//! both disclose. This guard defends against sequential reuse, not a concurrent
//! race; callers that need strict single-use must serialize the check-and-set
//! themselves.

that's just how db operations work - I think this is detailing too much

Comment on lines +56 to +61
///
/// `path` is a bare, storage-relative filename (e.g. `account_keys.bin`), not an
/// absolute path — the implementation resolves it against whatever root it chooses.
/// That root is independent of [`StoragePaths`], so the key envelope need not live
/// under `<root>/worldid/`; a host that backs up or deletes an account must persist
/// and remove these blobs separately from the `worldid/` files.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we even need to specify all this? Feels like it's all up to the foreign implementor

Suggested change
///
/// `path` is a bare, storage-relative filename (e.g. `account_keys.bin`), not an
/// absolute path — the implementation resolves it against whatever root it chooses.
/// That root is independent of [`StoragePaths`], so the key envelope need not live
/// under `<root>/worldid/`; a host that backs up or deletes an account must persist
/// and remove these blobs separately from the `worldid/` files.

Comment on lines +20 to +23
//!
//! Filenames are fixed and generic by design: none of them encode a relying-party
//! ID, action name, issuer name, or the account's leaf index, so directory listings
//! and backup archives don't leak which accounts or actions exist on a device.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//!
//! Filenames are fixed and generic by design: none of them encode a relying-party
//! ID, action name, issuer name, or the account's leaf index, so directory listings
//! and backup archives don't leak which accounts or actions exist on a device.

Comment on lines +5 to +8
//! The storage layer handles structured storage of all credentials and their
//! associated data (only storage, the semantics of the associated data is the
//! Issuer's responsibility). In addition the storage layer handles encryption
//! and clean up after expiration.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//! The storage layer handles structured storage of all credentials and their
//! associated data (only storage, the semantics of the associated data is the
//! Issuer's responsibility). In addition the storage layer handles encryption
//! and clean up after expiration.
//! The storage layer handles structured storage of all credentials and their
//! associated data. In addition the storage layer handles encryption
//! and clean up after expiration.

Comment on lines +45 to +49
//!
//! - No filesystem paths encode `leaf_index`, RP identifiers, issuer names, or action
//! name. See [`crate::storage::StoragePaths`].
//! - The vault (authoritative) holds the `leaf_index`, credentials, and blobs; the
//! cache DB holds only regenerable, TTL-bounded entries.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//!
//! - No filesystem paths encode `leaf_index`, RP identifiers, issuer names, or action
//! name. See [`crate::storage::StoragePaths`].
//! - The vault (authoritative) holds the `leaf_index`, credentials, and blobs; the
//! cache DB holds only regenerable, TTL-bounded entries.

Comment on lines +42 to +45
///
/// Replay guard entries keyed by a nullifier are intentionally short-lived
/// (bounded by TTL) to avoid accumulating a long-lived "interaction history"
/// on device.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
///
/// Replay guard entries keyed by a nullifier are intentionally short-lived
/// (bounded by TTL) to avoid accumulating a long-lived "interaction history"
/// on device.

I'd remove this - this feels like storage semantics of each nullifier, not enforced by the Nullifier type alias

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants