Encrypted Profile Storage Blueprint
Versioned, client-encrypted profile storage with avatar upload credential issuance and zero-knowledge profile key credential system
| Feature | encrypted-profile-storage |
| Category | Auth |
| Version | 1.0.0 |
| Tags | encryption, profile, zero-knowledge, avatar, versioning, privacy |
| YAML Source | View on GitHub |
| JSON API | encrypted-profile-storage.json |
Fields
| Name | Type | Required | Label | Description |
|---|---|---|---|---|
profile_version | text | Yes | Profile Version | |
commitment | token | Yes | Profile Key Commitment | |
name | token | No | Encrypted Name | |
about | token | No | Encrypted About | |
about_emoji | token | No | Encrypted About Emoji | |
payment_address | token | No | Encrypted Payment Address | |
phone_number_sharing | token | No | Encrypted Phone Number Sharing | |
has_avatar | boolean | No | Has Avatar | |
same_avatar | boolean | No | Same Avatar | |
credential_request | token | No | Credential Request | |
credential_type | select | No | Credential Type |
Rules
- encryption:
- client_side_only: true
- note: All profile field values (name, about, about_emoji, payment_address, phone_number_sharing) are encrypted on the client before upload; the server stores opaque ciphertext and never accesses plaintext
- padding_required: true
- padding_purpose: Fixed-size padding prevents traffic-analysis leakage of plaintext length
- versioning:
- key: account_identifier_plus_version
- multi_version: true
- note: Each profile version is keyed by both the account identifier and the version string; multiple versions may coexist per account
- commitment_immutable: true
- commitment_note: The profile key commitment is written once per version using a conditional update; it cannot be overwritten after creation
- payment_address:
- scope: current_version_only
- note: Payment addresses are only returned when the requested version matches the account’s current profile version
- region_blocking: configurable
- region_note: Payment addresses are blocked for phone-number prefixes on a configurable disallowed-regions list, unless a payment address was already set
- avatar:
- max_size_bytes: 10485760
- storage: object_storage_profiles_prefix
- upload: presigned_post_policy
- note: Avatar uploads require pre-signed form credentials; the server generates an object name from random bytes and signs a timed upload form
- cleanup: Previous avatar is deleted when an account sets a new avatar
- access_control:
- methods: authenticated, unidentified_access_key, group_send_token
- note: Profile retrieval requires authenticated access, a valid unidentified-access key, or a group send token (unversioned endpoint only)
- rate_limiting: per_authenticated_account
- credentials:
- type: expiring_profile_key
- validity_days: 7
- note: ZK expiring profile key credentials are valid for 7 days from issuance
- deletion:
- on_account_delete: all_versions_deleted
- avatar_on_explicit_delete: deleted
- avatar_on_reimplicit_register: preserved_for_pin_recovery
Outcomes
Rate_limited (Priority: 1) — Error: PROFILE_RATE_LIMITED
Given:
requester_uuid(session) exists- Profile read rate limit is exceeded for this account
Then:
- emit_event event:
profile.rate_limited
Result: Request rejected with rate-limit error
Unauthorized_access (Priority: 2) — Error: PROFILE_UNAUTHORIZED
Given:
- Caller provides neither valid authentication nor a valid unidentified-access key nor a valid group send token
Then:
- emit_event event:
profile.access_denied
Result: Request rejected as unauthorized
Payment_address_region_blocked (Priority: 3) — Error: PROFILE_PAYMENT_ADDRESS_REGION_BLOCKED
Given:
- Caller is authenticated and is updating their own profile
payment_address(input) exists- Account phone number matches a blocked-region prefix and no payment address exists yet for this version
Result: Profile update rejected because payment addresses are not supported in this region
Profile_set_new_avatar (Priority: 4)
Given:
- Caller is authenticated
commitment(input) existshas_avatar(input) eqtruesame_avatar(input) eqfalse
Then:
- create_record — Persist encrypted profile fields and new avatar path
- delete_record — Remove old avatar from object storage
- set_field target:
current_profile_versionvalue:profile_version - emit_event event:
profile.updated
Result: Profile stored; response contains pre-signed avatar upload form credentials
Profile_set_clear_avatar (Priority: 5)
Given:
- Caller is authenticated
commitment(input) existshas_avatar(input) eqfalse
Then:
- create_record — Persist encrypted profile fields without avatar
- delete_record — Remove old avatar from object storage
- set_field target:
current_profile_versionvalue:profile_version - emit_event event:
profile.updated
Result: Profile stored with no avatar; empty 200 response
Profile_set_unchanged_avatar (Priority: 6)
Given:
- Caller is authenticated
commitment(input) existssame_avatar(input) eqtrue
Then:
- create_record — Persist encrypted profile fields reusing existing avatar path
- set_field target:
current_profile_versionvalue:profile_version - emit_event event:
profile.updated
Result: Profile stored; existing avatar object reused; empty 200 response
Versioned_profile_retrieved (Priority: 7)
Given:
- Caller provides valid authentication or unidentified-access key
profile_version(input) exists
Then:
- emit_event event:
profile.accessed
Result: Encrypted profile fields returned (name, about, about_emoji, avatar path, payment_address, phone_number_sharing); fields absent if version not found
Profile_key_credential_issued (Priority: 8)
Given:
- Caller provides valid authentication or unidentified-access key
credential_request(input) existscredential_type(input) eqexpiringProfileKey- Profile version exists in storage
Then:
- emit_event event:
profile.credential_issued
Result: Versioned profile fields returned alongside an expiring ZK profile key credential valid for 7 days
Batch_identity_check_mismatch (Priority: 9)
Given:
- Unauthenticated batch identity check request submitted
- At least one element 4-byte SHA-256 fingerprint does not match the stored identity key
Then:
- emit_event event:
profile.identity_mismatch
Result: Response contains only the elements whose fingerprints differ from stored identity keys
Errors
| Code | Status | Message | Retry |
|---|---|---|---|
PROFILE_RATE_LIMITED | 429 | Too many profile requests. Please wait before trying again. | Yes |
PROFILE_UNAUTHORIZED | 401 | Not authorized to access this profile. | No |
PROFILE_NOT_FOUND | 404 | The requested profile or account was not found. | No |
PROFILE_INVALID_REQUEST | 400 | Invalid profile request. Check field sizes and formats. | No |
PROFILE_PAYMENT_ADDRESS_REGION_BLOCKED | 403 | Payment addresses are not supported in your region. | No |
PROFILE_INVALID_CREDENTIAL_TYPE | 400 | Unsupported credential type requested. | No |
PROFILE_INVALID_CREDENTIAL_REQUEST | 400 | The credential request is invalid or does not match the stored commitment. | No |
Events
| Event | Description | Payload |
|---|---|---|
profile.updated | A versioned profile was created or replaced by its owner | account_id, profile_version, avatar_changed |
profile.accessed | A profile version was retrieved by a requester | target_account_id, profile_version, requester_type |
profile.credential_issued | An expiring ZK profile key credential was issued for a profile version | target_account_id, profile_version, expiry |
profile.rate_limited | A profile read request was rejected due to rate limiting | requester_uuid |
profile.access_denied | A profile read was denied due to missing or invalid credentials | target_account_id |
profile.identity_mismatch | One or more accounts returned mismatched identity key fingerprints in a batch check | mismatched_identifiers |
profile.deleted | All versioned profiles for an account were deleted on account deletion or re-registration | account_id, avatars_deleted |
Related Blueprints
| Feature | Relationship | Reason |
|---|---|---|
| e2e-key-exchange | required | Profile key commitment is derived from the user’s end-to-end encryption key material |
| device-management | required | Authenticated profile updates require a valid device session |
| login | required | Authenticated profile read/write endpoints require account authentication |
| encrypted-group-metadata | recommended | Group send tokens can gate unversioned profile retrieval for group members |
| multi-device-sync | recommended | Profile version changes should be synced across all devices belonging to the account |
AGI Readiness
Goals
Reliable Encrypted Profile Storage
Versioned, client-encrypted profile storage with avatar upload credential issuance and zero-knowledge profile key credential system
Success Metrics:
| Metric | Target | Measurement |
|---|---|---|
| unauthorized_access_rate | 0% | Failed authorization attempts that succeed |
| response_time_p95 | < 500ms | 95th percentile response time |
Constraints:
- security (non-negotiable): Follow OWASP security recommendations
- security (non-negotiable): Sensitive fields must be encrypted at rest and never logged in plaintext
Autonomy
Level: supervised
Human Checkpoints:
- before modifying sensitive data fields
Escalation Triggers:
error_rate > 5consecutive_failures > 3
Verification
Invariants:
- sensitive fields are never logged in plaintext
- all data access is authenticated and authorized
- error messages never expose internal system details
Tradeoffs
| Prefer | Over | Reason |
|---|---|---|
| security | performance | authentication must prioritize preventing unauthorized access |
Coordination
Protocol: request_response
Consumes:
| Capability | From | Fallback |
|---|---|---|
e2e_key_exchange | e2e-key-exchange | fail |
device_management | device-management | fail |
login | login | fail |
Safety
| Action | Permission | Cooldown | Max Auto |
|---|---|---|---|
| rate_limited | autonomous | - | - |
| unauthorized_access | autonomous | - | - |
| payment_address_region_blocked | human_required | - | - |
| profile_set_new_avatar | autonomous | - | - |
| profile_set_clear_avatar | autonomous | - | - |
| profile_set_unchanged_avatar | supervised | - | - |
| versioned_profile_retrieved | autonomous | - | - |
| profile_key_credential_issued | autonomous | - | - |
| batch_identity_check_mismatch | autonomous | - | - |