Vauchi Documentation

Privacy-focused updatable contact cards via in-person exchange. End-to-end encrypted, decentralized.


What is Vauchi?

Vauchi is a contact card that updates automatically. When you change your phone number, everyone who has your card sees the change.

  • No accounts — Your device is your identity
  • No phone number required — Exchange contact cards in person
  • End-to-end encrypted — Only you and your contacts can read your data
  • Open source — Verify every claim yourself
👤 Getting StartedSet up Vauchi and exchange your first contact
FAQAnswers to common questions
🔒 SecurityHow we protect your data
💜 Our PrinciplesWhy we built Vauchi the way we did

For Developers

Get the App

  • Desktop: Coming soon (macOS, Windows, Linux)
  • CLI/TUI: Coming soon
  • iOS: Coming soon
  • Android: Coming soon

Source Code

GitLab · GitHub Mirror


Your privacy matters. Vauchi is built to prove it.

For Users

Welcome to Vauchi! This section contains everything you need to get started and make the most of the app.

Getting Started

New to Vauchi? Start here:

Features

Learn what Vauchi can do:

FeatureDescription
Contact ExchangeExchange contact cards in person via QR code
Privacy ControlsControl what each contact can see
Multi-Device SyncUse Vauchi on multiple devices
Auto UpdatesYour contacts always have your latest info
Backup & RecoveryProtect and restore your identity
EncryptionHow your data stays private

How-To Guides

Step-by-step instructions:

GuideWhat You'll Learn
Exchange ContactsHow to add someone using QR codes
Set Up Multi-DeviceLink Vauchi to another device
Recover Your AccountRestore access after losing a device
Manage VisibilityControl who sees what

Need Help?

  • Check the FAQ for common questions
  • Report issues at GitLab
  • Email: support@vauchi.app

Getting Started

Welcome to Vauchi! This guide will help you set up your identity and exchange your first contact.


Creating Your Identity

When you first open Vauchi, you'll be asked to create your identity:

  1. Enter your display name — This is how contacts will see you
  2. Tap "Create Identity" — Vauchi generates your unique cryptographic identity

Your identity includes:

  • A unique public ID (like a fingerprint for your identity)
  • Cryptographic keys for secure communication
  • Your display name

Important

Your identity keys are stored only on your device. There's no account to log into — your device IS your account.


Understanding the Home Screen

After creating your identity, you'll see:

  • Your Card — The contact information you've added
  • Fields — Individual pieces of contact info (email, phone, etc.)
  • Navigation — Access to Contacts, Exchange, and Settings

Adding Your Contact Information

Your contact card contains fields — pieces of information you want to share with contacts.

To Add a Field

  1. Tap the + button on the home screen
  2. Select a field type:
    • Email — Email addresses
    • Phone — Phone numbers
    • Website — URLs and websites
    • Address — Physical addresses
    • Social — Social media profiles
    • Custom — Any other information
  3. Enter a label (e.g., "Work", "Personal")
  4. Enter the value (e.g., "john@example.com")
  5. Tap Add

Your First Exchange

Ready to exchange contacts? Here's the quick version:

  1. Meet someone in person
  2. Open the Exchange tab
  3. Show them your QR code
  4. Scan their QR code
  5. Done — you're connected!

For detailed instructions, see the Exchange Contacts guide.


What's Next?

Now that you're set up:


Tips for New Users

Security Tips

  1. Create a backup as soon as you set up
  2. Verify important contacts in person
  3. Use a strong backup password (passphrase recommended)

Privacy Tips

  1. Review visibility settings when adding new fields
  2. Use specific labels to control field visibility precisely
  3. Check what contacts can see periodically

Organization Tips

  1. Use descriptive labels (e.g., "Work Email", "Personal Cell")
  2. Update outdated information promptly

Need Help?

  • Check the FAQ for common questions
  • See features for detailed explanations
  • Read the how-to guides for step-by-step instructions

Frequently Asked Questions

Answers to common questions about Vauchi.


Privacy & Security

Is my data encrypted?

Yes, comprehensively:

  • At rest: All data on your device is encrypted with XChaCha20-Poly1305 using a key stored in your device's secure hardware (iOS Keychain / Android KeyStore)
  • In transit: All communication uses end-to-end encryption (X25519 + XChaCha20-Poly1305)
  • Backups: Protected with Argon2id key derivation and XChaCha20-Poly1305

Can the relay server read my contacts?

No. The relay server only sees encrypted message envelopes. It cannot:

  • Decrypt any message content
  • See your contact list
  • Read your contact card fields
  • Associate your identity with your data

The relay is essentially a "dumb pipe" that passes encrypted blobs between devices.

What data does the relay server store?

Only:

  • Encrypted message envelopes (deleted after delivery or 30 days)
  • Connection metadata for rate limiting (IP address, timestamps — deleted after 24 hours)

Is Vauchi truly private?

Yes. Vauchi is designed with privacy as the core principle:

  • Local-first architecture (your data lives on your device)
  • End-to-end encryption (we can't read your data even if we wanted to)
  • No analytics or tracking
  • No cloud accounts
  • Open source (you can verify our claims)

Identity & Account

What happens if I lose my device?

You have several options:

  1. Another linked device: Continue using Vauchi normally
  2. Backup: Restore your identity from an encrypted backup
  3. Social recovery: Get vouchers from contacts who can verify your identity
  4. Start fresh: Create a new identity and re-exchange with contacts

How does social recovery work?

Social recovery uses your real-world relationships to verify your identity:

  1. You create a "recovery claim" on a new device
  2. You share this claim with trusted contacts
  3. Each contact creates a "voucher" confirming they recognize you
  4. After collecting enough vouchers (typically 3), your identity is restored

This prevents both:

  • You being locked out (contacts can vouch for you)
  • Someone else stealing your identity (they'd need to fool multiple contacts)

Can I change my display name?

Yes, go to Settings and edit your display name. The change syncs to all your devices and contacts see the update.

Do I need an account?

No. Your identity is created on your device. There's nothing to sign up for.


Contacts & Exchange

How do I exchange contacts?

  1. Meet the person in real life
  2. Open the Exchange screen
  3. Show them your QR code to scan
  4. Scan their QR code
  5. Done! You're now contacts

Why do I need to meet in person?

In-person exchange ensures:

  • You're connecting with who you think you are
  • No man-in-the-middle can intercept the exchange
  • The trust relationship is established in the real world

Can I exchange contacts remotely?

Not currently. Exchange requires physical proximity — both people must be in the same location so the devices can verify co-presence. This is a deliberate security design to prevent man-in-the-middle attacks.

Can I remove a contact?

Yes:

  1. Go to Contacts
  2. Select the contact
  3. Tap Delete/Remove
  4. Confirm

This removes them from your device. They still have your data (whatever was visible to them), but won't receive future updates.


Multi-Device

Can I use Vauchi on multiple devices?

Yes! Vauchi supports multi-device sync:

  1. Set up Vauchi on your first device
  2. Go to Settings, open the Devices screen, and generate a device link
  3. Follow the linking process on your second device
  4. Both devices now share the same identity

Up to 10 devices can be linked to one identity.

How do I migrate to a new phone?

Method 1: Device Linking

  1. On old phone: Go to Settings, then open the Devices screen and generate a device link
  2. On new phone: Install Vauchi and join existing identity
  3. Once synced, you can uninstall from old phone

Method 2: Backup & Restore

  1. On old phone: Create an encrypted backup
  2. On new phone: Install Vauchi and restore from backup

Backup & Restore

How do backups work?

  1. You create a backup with a password you choose
  2. Vauchi encrypts all your data using that password
  3. You receive a backup file or code (format varies by platform)
  4. To restore: backup data + password = your identity

What's included in a backup?

  • Your identity (cryptographic keys)
  • Your contact card (all fields)
  • All contacts and their cards
  • Visibility settings
  • Device information

I forgot my backup password. Can you recover it?

No. The encryption is designed so that only you can decrypt your backup. This is a security feature, not a bug. Without the password, the backup cannot be decrypted by anyone, including us.


Visibility & Sharing

How do I control what each contact sees?

  1. Open a contact's detail page
  2. Scroll to "What They Can See"
  3. Toggle individual fields on/off

Do contacts know when I hide fields?

They see fields disappear from your card, but don't receive a notification. It appears as if you removed the field.

Can I share different info with different contacts?

Yes! That's the core feature:

  • Work contacts: Show work email, hide personal phone
  • Family: Show everything
  • Acquaintances: Show only basic info

Technical

What's the relay server for?

The relay server:

  • Routes encrypted messages between your devices
  • Enables real-time sync
  • Stores messages temporarily if a device is offline
  • Cannot read any message content

Think of it like a post office that handles sealed envelopes.

Does Vauchi work offline?

Partially:

  • You can view all your data offline
  • You can make changes offline
  • Changes sync when you're back online
  • You cannot exchange contacts offline (needs camera + network)

What encryption does Vauchi use?

  • Signing: Ed25519
  • Key Exchange: X25519 (Curve25519)
  • Symmetric Encryption: XChaCha20-Poly1305
  • Key Derivation: Argon2id (for passwords)
  • Forward Secrecy: Double Ratchet protocol

All cryptography uses audited libraries (ed25519-dalek, x25519-dalek, chacha20poly1305, argon2).

Is Vauchi open source?

Yes! The complete source code is available at: https://gitlab.com/vauchi

You can:

  • Inspect how your data is handled
  • Verify our security claims
  • Contribute improvements
  • Run your own relay server

Troubleshooting

My contacts don't see my updates

  1. Check your internet connection
  2. Ensure sync is working (Settings > check last sync time)
  3. Verify the field is visible to that contact
  4. Ask them to manually refresh

The QR scanner doesn't work

  1. Check camera permissions
  2. Ensure adequate lighting
  3. Clean your camera lens
  4. Try adjusting distance to the QR code
  5. Restart the app

Sync seems stuck

  1. Check internet connectivity
  2. Try manual sync (pull to refresh or Settings > Sync)
  3. Check if the relay server is reachable
  4. Restart the app

Still Have Questions?

Contact Exchange

Exchange contact cards securely by scanning QR codes in person.


How It Works

Vauchi uses in-person exchange to establish contact relationships. Both parties must be physically present to complete an exchange.

sequenceDiagram
    participant A as You
    participant B as Contact

    Note over A,B: Meet in person

    A->>B: Show QR code
    B->>A: Scan QR code
    Note over A,B: Proximity verified
    A->>B: Scan their QR code

    Note over A,B: Exchange complete!
    Note over A,B: Both have each other's cards

Why In-Person?

The in-person requirement is a privacy and security feature:

ThreatHow In-Person Prevents It
SpamCan't be added by strangers
ImpersonationYou verify identity yourself
Man-in-the-middleDirect device communication
Screenshot attacksProximity verification

Exchange Methods

QR Code (Primary)

The main method for exchanging contacts:

  1. Open the Exchange tab
  2. Show your QR code
  3. Have the other person scan it
  4. Scan their QR code
  5. Exchange complete

QR codes expire after 5 minutes for security.

Proximity Verification

On iOS, Vauchi verifies physical proximity using ultrasonic audio:

  • Both phones emit and listen for an audio handshake (18-20 kHz)
  • Range: approximately 3 meters
  • If verification fails, exchange falls back to manual confirmation
  • This prevents screenshot attacks

Android proximity verification is planned.

Troubleshooting Proximity (iOS)

If proximity verification fails:

  1. Ensure both phones have working speakers/microphones
  2. Move closer together (within 2-3 meters)
  3. Reduce background noise
  4. Disable any audio-blocking apps
  5. Try again — or confirm manually when prompted

On desktop and CLI/TUI, proximity verification is not available — manual confirmation is required instead.

After Exchange

Once exchange completes:

  • The new contact appears in your Contacts list
  • You can see their contact card (fields they've shared)
  • They can see your contact card (fields you've shared)
  • Future updates sync automatically

Security Properties

PropertyMechanism
Proximity requiredUltrasonic audio handshake (iOS); manual confirmation (other platforms)
No man-in-the-middleX3DH key agreement with identity keys
Forward secrecyEphemeral keys discarded after exchange
Replay preventionOne-time token, 5-minute expiry
Card authenticityEd25519 signature on contact card

Auto Updates

Your contacts always have your latest information.


How It Works

When you update your contact card, everyone who has your card automatically sees the change. No need to send them the new info — it just appears.

sequenceDiagram
    participant Y as You
    participant R as Relay
    participant C1 as Contact 1
    participant C2 as Contact 2

    Y->>Y: Change phone number

    Y->>R: Send encrypted update
    R->>R: Store for offline contacts

    R->>C1: Deliver when online
    C1->>C1: Decrypt, update your card

    Note over C2: Offline

    C2->>R: Come online
    R->>C2: Deliver pending update
    C2->>C2: Decrypt, update your card

    Note over C1,C2: Both see your new number

What Updates

When you change your contact card:

ActionWhat Happens
Add a fieldContacts who can see it get notified
Edit a fieldContacts see the new value
Remove a fieldContacts see it disappear
Change visibilityField appears/disappears for that contact

Update Timing

When Online

  • Updates deliver within seconds
  • Contacts see changes when they open the app
  • Real-time sync when both are active

When Offline

  • Updates queue on the relay server
  • Delivered when the contact comes online
  • Messages kept for up to 30 days

Manual Refresh

Contacts can always:

  • Pull to refresh their contact list
  • Go to Settings > Sync Now

Privacy of Updates

Updates are end-to-end encrypted:

  • The relay server cannot read update content
  • Each contact receives updates encrypted with their unique key
  • Different contacts may see different fields (per visibility settings)

What the Relay Sees

SeesDoesn't See
Encrypted blobField names
Recipient IDField values
TimestampWho you are
Message size (padded)What changed

Visibility and Updates

Updates respect your visibility settings:

  • If you hide a field from someone, they don't receive updates for it
  • If you show a field to someone, they start receiving updates
  • Changes are per-contact, not global

Example

You change your phone number:

ContactVisibilityWhat They See
FamilyPhone visibleNew number
WorkPhone hiddenNothing
FriendPhone visibleNew number

Forward Secrecy

Each update uses a unique encryption key:

  • Keys are derived via Double Ratchet
  • Even if one key is compromised, other updates stay secure
  • Past messages can't be decrypted with current keys

Troubleshooting

Contact Doesn't See My Update

  1. Check visibility — Is the field visible to them?
  2. Check your connection — Are you online?
  3. Wait a moment — Updates may take a few seconds
  4. Ask them to refresh — Pull to refresh or manual sync

Updates Seem Slow

  1. Check both connections — You and the contact need internet
  2. Check relay status — Rare server issues may delay delivery
  3. Try manual sync — Settings > Sync Now

Update Stuck

If an update seems stuck:

  1. Close and reopen the app
  2. Check internet connectivity
  3. Try editing and saving the field again

Privacy Controls

Control exactly what each contact can see on your card.


How It Works

Every field on your contact card can be shown to or hidden from each contact. This gives you fine-grained control over your personal information.

Your Contact Card
┌────────────────────────────────────────────┐
│  Name: Alice Smith                         │
│                                            │
│  ┌─────────────────┬─────────┬─────────┐   │
│  │ Field           │ Family  │ Work    │   │
│  ├─────────────────┼─────────┼─────────┤   │
│  │ Personal Email  │   ✓     │   ✗     │   │
│  │ Work Email      │   ✓     │   ✓     │   │
│  │ Personal Phone  │   ✓     │   ✗     │   │
│  │ Work Phone      │   ✓     │   ✓     │   │
│  │ Home Address    │   ✓     │   ✗     │   │
│  └─────────────────┴─────────┴─────────┘   │
│                                            │
│  Family sees 5 fields, Work sees 2 fields  │
└────────────────────────────────────────────┘

Per-Contact Visibility

You control what each individual contact can see.

To Change What Someone Sees

  1. Go to Contacts
  2. Select a contact
  3. Scroll to "What They Can See"
  4. Toggle fields on/off

Visibility Options

For each field, you can set:

  • Visible — The contact can see this field
  • Hidden — The contact cannot see this field

Default Visibility

New fields are visible to everyone by default. You can change this immediately after adding or later.

Labels

Labels help you manage visibility for groups of contacts instead of individuals.

How Labels Work

  1. Create labels like "Family", "Work", "Friends"
  2. Assign contacts to labels
  3. Control visibility per label
  4. Contacts in multiple labels see the union of visible fields

Example Labels

LabelWhat They See
FamilyEverything
WorkWork email, work phone
FriendsPersonal email, personal phone
AcquaintancesJust name

Bulk Changes

On the home screen, tap the visibility button next to any field to:

  • Show to all — Make visible to all contacts
  • Hide from all — Hide from all contacts
  • Customize — Toggle individual contacts

Privacy Notes

  • They don't see changes in real-time — Updates sync when they open the app
  • No notifications — Contacts aren't notified when you hide fields
  • Looks like removal — Hidden fields appear as if you removed them
  • History isn't shared — They only see your current card, not past versions

Common Scenarios

Sharing Business Info

  1. Create a "Business" label
  2. Assign professional contacts
  3. Show: Work email, work phone, LinkedIn
  4. Hide: Personal phone, home address

Close Friends Only

  1. Create a "Close Friends" label
  2. Assign trusted contacts
  3. Show: Everything including personal details
  4. Everyone else sees less

Temporary Sharing

  1. Share field with a contact
  2. Complete your task
  3. Hide the field again
  4. They lose access immediately

Encryption

How Vauchi protects your data.


Overview

Everything in Vauchi is end-to-end encrypted. Only you and your contacts can read your data — not us, not the relay server, not anyone else.

What's Encrypted

DataEncrypted?Who Can Read
Your contact cardYesYou + your contacts
Messages between devicesYesYour devices only
BackupYesYou only (with password)
Data at rest (on device)YesYou only
Data in transitYesYou + recipient only

How It Works

Your Identity

When you create your identity, Vauchi generates:

  • A master seed (256 random bits) — the root of all your keys
  • A signing key (Ed25519) — proves messages are from you
  • An exchange key (X25519) — establishes shared secrets with contacts

These keys never leave your device unencrypted.

Exchanging Contacts

When you exchange with someone:

  1. You scan their QR code (contains their public key)
  2. Both devices perform X3DH key agreement
  3. A shared secret is established that only you two know
  4. All future communication is encrypted with this secret
Your Keys          Shared Secret          Their Keys
    ↘                   ↓                    ↙
     └───── X3DH Key Agreement ─────┘
                    ↓
          Unique encryption key
          (known only to you two)

Updates Between Contacts

When you update your card:

  1. The update is encrypted with the shared key for each contact
  2. Different contacts may receive different updates (per visibility)
  3. Each message uses a unique key (forward secrecy)
  4. The relay only sees encrypted blobs

Forward Secrecy

Vauchi uses the Double Ratchet protocol (same as Signal):

  • Each message uses a unique encryption key
  • Keys are derived, used once, then deleted
  • Even if one key is compromised, other messages stay secure
  • Past messages can't be decrypted with current keys

Encryption Algorithms

PurposeAlgorithmNotes
SigningEd25519Proves identity and authenticity
Key exchangeX25519Establishes shared secrets
Symmetric encryptionXChaCha20-Poly1305Encrypts all data
Key derivationHKDF-SHA256Derives keys from seeds
Password KDFArgon2idProtects backups

All cryptography uses audited libraries (ed25519-dalek, x25519-dalek, chacha20poly1305, argon2).

What the Relay Server Sees

The relay server routes messages but cannot read them:

Relay SeesRelay Cannot See
Encrypted blobsMessage content
Recipient IDYour identity
TimestampsWhat you changed
Message size (padded)Who you are

Messages are padded to fixed sizes to prevent size-based analysis.

Device Security

Your data is protected on your device:

PlatformKey StorageProtection
iOSSecure EnclaveHardware-backed, biometric
AndroidHardware KeyStoreHardware-backed, biometric
macOSKeychainOS-protected
WindowsCredential ManagerOS-protected
LinuxSecret ServiceIf available

Backup Security

Backups are encrypted with your password:

  1. Key derivation: Argon2id (memory-hard, resistant to brute force)
  2. Encryption: XChaCha20-Poly1305
  3. Result: Without your password, the backup is useless

We recommend passphrases (4+ random words) for memorable yet secure passwords.

Security Properties

PropertyHow Vauchi Achieves It
ConfidentialityXChaCha20-Poly1305 encryption
IntegrityAEAD authentication tags
AuthenticityEd25519 signatures
Forward secrecyDouble Ratchet, one-time keys
Break-in recoveryDH ratchet with ephemeral keys
Replay preventionMessage counters
Traffic analysis preventionMessage padding

Open Source

All Vauchi code is open source:

  • Inspect the encryption implementation yourself
  • Verify our security claims
  • Report vulnerabilities responsibly

Source: https://gitlab.com/vauchi

Limitations

What encryption doesn't protect:

  • Metadata you share: Your name, fields you make visible
  • Physical access: Someone with your unlocked device
  • Screenshots: If a contact screenshots your card
  • Deleted data: Until secure delete completes

Backup & Recovery

Protect your identity and recover access if something goes wrong.


Overview

Vauchi offers two ways to recover your identity:

MethodWhen to UseRequires
Encrypted BackupPlanned recovery, new deviceBackup code + password
Social RecoveryLost all devices and backup3+ contacts to vouch for you

Encrypted Backup

Creating a Backup

  1. Go to Settings > Backup
  2. Tap Export Backup
  3. Enter a strong password (must pass strength check)
  4. Confirm the password
  5. Copy or save the backup code

Important

  • Store your backup code securely (password manager, printed copy)
  • Remember your backup password — it cannot be recovered
  • The backup code + password = your entire identity

What's Included

DataIncluded?
Your identity (keys)Yes
Your display nameYes
Device informationYes
ContactsNo*

*Contact relationships are re-established through the relay when you restore.

Restoring from Backup

  1. Install Vauchi on a new device
  2. Choose Restore from Backup
  3. Paste your backup code
  4. Enter your backup password
  5. Your identity is restored

After restoration:

  • Your identity is fully restored
  • Contacts sync automatically via relay
  • You can link additional devices

Backup Security

  • Encryption: XChaCha20-Poly1305
  • Key derivation: Argon2id (resistant to brute force)
  • Without the password: Backup is useless

We recommend passphrases (4+ random words) for memorable yet secure passwords.

Social Recovery

If you lose access to all devices AND don't have a backup, social recovery lets trusted contacts help restore your identity.

How It Works

sequenceDiagram
    participant Y as You (New Device)
    participant C1 as Contact 1
    participant C2 as Contact 2
    participant C3 as Contact 3
    participant R as Relay

    Y->>Y: Create recovery claim
    Y->>C1: Meet in person, share claim
    C1->>C1: Verify it's really you
    C1->>Y: Create voucher

    Y->>C2: Meet in person, share claim
    C2->>Y: Create voucher

    Y->>C3: Meet in person, share claim
    C3->>Y: Create voucher

    Y->>R: Submit recovery proof (3 vouchers)
    R->>R: Verify vouchers

    Note over Y: Identity restored!

Starting Recovery

  1. Install Vauchi on a new device
  2. Create a new identity
  3. Go to Settings > Recovery
  4. Tap Recover Old Identity
  5. Enter your old public ID
  6. A recovery claim is generated

Getting Vouchers

For each voucher:

  1. Meet the contact in person
  2. Share your recovery claim with them
  3. They verify it's really you (visual recognition)
  4. They create a voucher in their app
  5. They share the voucher with you

Requirements

  • You need vouchers from 3 or more contacts
  • Each contact must have previously exchanged with your old identity
  • This proves your social network recognizes the recovery request

Completing Recovery

Once you have enough vouchers:

  1. Import all vouchers into your app
  2. Vauchi submits the recovery proof
  3. Other contacts verify via mutual connections
  4. Your identity transitions to the new device

Helping Others Recover

If a contact asks you to vouch for their recovery:

  1. Go to Settings > Recovery
  2. Tap Help Someone Recover
  3. Paste their recovery claim
  4. Verify their identity (call them, meet in person)
  5. Create a voucher
  6. Share the voucher with them

Warning

Only vouch if you're certain of their identity. This prevents identity theft.

Recovery Best Practices

Before You Need It

  1. Create a backup as soon as you set up
  2. Store backup securely (password manager, safe)
  3. Use a memorable passphrase for the password
  4. Have 5+ contacts in case some are unavailable

When You Need It

  1. Try backup restore first (faster, simpler)
  2. Use social recovery only if backup unavailable
  3. Meet contacts in person for vouching
  4. Don't rush — verify everything carefully

Troubleshooting

Forgot Backup Password

Unfortunately, backup passwords cannot be recovered. The encryption is designed so only you can decrypt your backup. Options:

  1. Use social recovery if available
  2. Create a new identity and re-exchange with contacts
  3. Check if you have another linked device still accessible

Not Enough Vouchers

If you can't reach 3 contacts:

  1. Check if old contacts are still available
  2. Wait if contacts are temporarily unavailable
  3. Consider creating a new identity as last resort

Voucher Rejected

Vouchers may be rejected if:

  • The voucher is for a different identity
  • The voucher is corrupted
  • The voucher has expired (90 days)

Ask the contact to create a new voucher.

Multi-Device Sync

Use Vauchi on multiple devices with the same identity.


How It Works

All your devices share the same identity and stay in sync. Changes made on one device appear on all others.

graph TB
    subgraph "Your Identity"
        ID[Master Seed]
    end

    subgraph "Devices"
        D1[Phone]
        D2[Tablet]
        D3[Desktop]
    end

    ID --> D1
    ID --> D2
    ID --> D3

    D1 <--> |Encrypted Sync| R[Relay Server]
    D2 <--> R
    D3 <--> R

Linking a New Device

Prerequisites

  • Your existing device with Vauchi set up
  • The new device with Vauchi installed
  • Both devices online

Steps

  1. On your existing device, go to Settings > Devices
  2. Tap Link New Device
  3. A QR code appears (valid for 5 minutes)
  4. On your new device, install Vauchi
  5. Choose Join Existing Identity
  6. Scan the QR code (or paste the data string on desktop/CLI)
  7. Verify the confirmation code matches on both devices
  8. Confirm to complete linking

Both devices now share your identity and sync automatically.

Confirmation Code

When linking, both devices display a 6-digit code (e.g., 123-456). This code is derived cryptographically from the shared link data — only the two devices involved can compute it. If the codes match, you know the link is authentic.

Device Limits

  • Maximum: 10 devices per identity
  • Minimum: 1 device (your primary)

If you need to add an 11th device, revoke an existing one first.

Platform Support

PlatformLink (Generate)Join (Scan/Paste)Manage Devices
iOSPlannedPlannedPlanned
AndroidPlannedPlannedPlanned
DesktopYesYes (paste)Yes
TUIYesPlannedYes
CLIYesYesYes

Managing Devices

Viewing Linked Devices

  1. Go to Settings > Devices
  2. See all linked devices
  3. Your current device is marked

Each device shows:

  • Device name
  • Platform (iOS, Android, Desktop, CLI, TUI)
  • Status (active, revoked)

Revoking a Device

If a device is lost, stolen, or no longer needed:

  1. Go to Settings > Devices on another device
  2. Find the device to revoke
  3. Tap Revoke
  4. Confirm the action

Warning

You cannot revoke your current device. Use another linked device to revoke a lost one.

A revoked device:

  • Loses access to your identity immediately
  • Cannot send or receive updates
  • Cannot be re-linked without starting fresh

How Sync Works

  • Changes sync automatically when online
  • Sync uses end-to-end encryption
  • The relay server cannot read your data
  • Offline changes sync when connectivity returns

What Syncs

DataSyncs?
Your contact cardYes
Your contactsYes
Visibility settingsYes
App preferencesYes
Device-specific settingsNo

Sync Frequency

  • Real-time: When both devices are online
  • On app open: Pulls any pending changes
  • Manual: Pull to refresh or Settings > Sync Now

Migration

Moving to a New Phone

Option 1: Device Linking (Recommended)

  1. On old phone: Link the new phone as a device
  2. Wait for sync to complete
  3. On old phone: Revoke the old phone (optional)

Option 2: Backup & Restore

  1. On old phone: Create an encrypted backup
  2. On new phone: Restore from backup

Device linking is preferred because it preserves device-specific keys and ensures a clean handoff.

Troubleshooting

Sync Not Working

  1. Check internet connectivity on both devices
  2. Ensure both devices have the app open
  3. Try manual sync (Settings > Sync Now)
  4. Check that the device hasn't been revoked

Device Not Appearing

  1. Wait a few minutes for sync
  2. Restart the app on both devices
  3. Check the link code hasn't expired (5 minutes)
  4. Try generating a new link code

Security

  • Each device has its own keys derived from your master seed
  • Revoking a device invalidates its keys immediately
  • The relay server never sees plaintext data
  • Device-to-device communication is end-to-end encrypted
  • Confirmation codes prevent man-in-the-middle attacks during linking

How to Exchange Contacts

Step-by-step guide for exchanging contact cards with other Vauchi users.


Prerequisites

  • Both you and the other person have Vauchi installed
  • You're physically together (proximity verification required)
  • Both devices have working cameras

QR Code Exchange

Both people show and scan each other's QR codes. This ensures fresh encryption keys are used for every exchange (forward secrecy).

Step 1: Open Exchange

  1. Open Vauchi
  2. Tap the Exchange tab at the bottom

Step 2: Show Your QR Code

  1. Tap Show My QR Code
  2. A QR code appears on your screen
  3. Show it to the other person

Tip

Keep your screen brightness up and hold steady for easier scanning.

Step 3: They Scan Your Code

  1. The other person points their camera at your QR code
  2. Their device confirms a successful scan

Step 4: Scan Their Code

  1. Tap Scan QR Code
  2. Point your camera at their QR code
  3. Wait for the scan to complete

Step 5: Confirm

Both devices show "Exchange Successful"

You now have each other's contact cards.


Troubleshooting

QR Code Won't Scan

  1. Lighting: Make sure the QR code is well-lit
  2. Stability: Hold both devices steady
  3. Distance: Try moving closer or farther
  4. Clean lens: Wipe your camera lens
  5. Refresh: Generate a new QR code (they expire after 5 minutes)

Exchange Keeps Failing

  1. Check internet connectivity on both devices
  2. Ensure the QR code hasn't expired (5-minute limit)
  3. Restart the app on both devices
  4. Try a fresh QR code

After Exchange

Once exchange completes:

  • They appear in your Contacts list
  • You can see their card (fields they've shared)
  • They can see your card (fields you've shared)
  • Future changes sync automatically

Next Steps


Security Notes

  • QR codes expire after 5 minutes (replay protection)
  • Both parties must scan each other's QR codes (mutual verification)
  • Each exchange uses fresh ephemeral keys (forward secrecy)
  • Exchange uses encrypted key agreement
  • The relay never sees unencrypted data

For more on security, see Encryption.

How to Manage Visibility

Step-by-step guide for controlling what each contact can see.


Overview

Visibility lets you control who sees what on your contact card. Show your work email to colleagues, your personal phone to friends, and hide your home address from everyone else.


Change What One Contact Sees

Step 1: Open Contact

  1. Go to Contacts
  2. Tap on the contact you want to modify

Step 2: Find Visibility Settings

  1. Scroll down to "What They Can See"
  2. You'll see a list of all your fields

Step 3: Toggle Fields

  • Enabled (green): They can see this field
  • Disabled (gray): They cannot see this field

Tap any field to toggle it.

Step 4: Confirm

Changes apply immediately. The contact will see the update next time they sync.


Show/Hide a Field for Everyone

Step 1: Go to Your Card

  1. Go to Home
  2. Find the field you want to change

Step 2: Open Visibility Menu

  1. Tap the visibility icon (eye) next to the field
  2. A menu appears

Step 3: Choose Option

  • Show to all: Makes the field visible to all contacts
  • Hide from all: Hides the field from all contacts
  • Customize: Opens per-contact toggles

Using Labels

Labels help you manage visibility for groups instead of individuals.

Creating a Label

  1. Go to Settings > Labels
  2. Tap Add Label
  3. Enter a name (e.g., "Work", "Family", "Friends")
  4. Tap Create

Assigning Contacts to Labels

  1. Open a contact
  2. Scroll to Labels
  3. Tap to assign/unassign labels

Setting Visibility by Label

  1. Go to Home
  2. Tap the visibility icon next to a field
  3. Tap Customize
  4. Switch to the Labels tab
  5. Toggle labels on/off

Example: Enable "Family" and "Friends", disable "Work" for your personal phone.


Common Configurations

Business Card Mode

Share only professional information:

FieldVisibility
Work EmailAll
Work PhoneAll
Personal EmailNone
Personal PhoneNone
Home AddressNone

Close Friends

Share everything with trusted contacts:

  1. Create a "Close Friends" label
  2. Assign trusted contacts
  3. Show all fields to "Close Friends"
  4. Restrict fields for everyone else

Temporary Sharing

Share a field temporarily:

  1. Show the field to a specific contact
  2. Complete whatever you needed
  3. Hide the field again

They lose access immediately when you hide it.


Checking What Someone Sees

Step 1: Open Contact

  1. Go to Contacts
  2. Tap on the contact

Step 2: Review Visible Fields

Look at "What They Can See":

  • Enabled fields = they see these
  • Disabled fields = they don't see these

Summary View

At the bottom of the contact, you'll see:

"Alice can see 3 of your 7 fields"


Default Visibility

For New Fields

When you add a new field, it's visible to everyone by default.

To change this:

  1. Add the field
  2. Immediately tap the visibility icon
  3. Adjust as needed

For New Contacts

When you exchange with someone new, they see all fields that are currently visible to "everyone".


Troubleshooting

Contact Still Sees Hidden Field

  1. Changes sync when they open the app
  2. Ask them to refresh their contacts
  3. Wait a few minutes for sync

Can't Find Visibility Options

  1. Make sure you're on the contact's detail page
  2. Scroll down — visibility is below their card info
  3. If missing, update the app

Label Changes Not Applying

  1. Make sure contacts are assigned to the label
  2. Check the label visibility settings
  3. Try removing and re-adding the label

Tips

Be Intentional

  • Review visibility when adding new fields
  • Periodically audit what each contact sees
  • Use labels to stay organized

Think in Categories

Group contacts by relationship type:

  • Work: Professional info only
  • Family: Everything
  • Acquaintances: Name and email only

Start Restrictive

It's easier to show more later than to hide after sharing.


Privacy Notes

  • Hidden fields disappear from their view
  • They aren't notified when you hide fields
  • They can't see your visibility settings
  • Hiding is per-contact — it doesn't delete the field

For more on privacy, see Privacy Controls.

How to Recover Your Account

Step-by-step guide for restoring access to your Vauchi identity.


Choose Your Recovery Method

SituationMethodTime Required
Have backup code + passwordBackup Restore5 minutes
Have another linked deviceDevice Link5 minutes
Lost everythingSocial RecoveryHours to days

Backup Restore

If you have your encrypted backup code and password:

Step 1: Start Fresh

  1. Install Vauchi on your new device
  2. On the welcome screen, tap Restore from Backup

Step 2: Enter Backup

  1. Paste your backup code (the long string of characters)
  2. Tap Next

Step 3: Enter Password

  1. Enter your backup password
  2. Tap Restore

Step 4: Wait for Sync

  1. Vauchi restores your identity
  2. Your contacts sync automatically via the relay
  3. Within minutes, you should see your contacts

Success

You're back! Your identity is fully restored.


If you have another device still logged in:

Step 1: On Your Working Device

  1. Open Vauchi
  2. Go to Settings > Devices
  3. Tap Link New Device
  4. A QR code appears

Step 2: On Your New Device

  1. Install Vauchi
  2. Tap Join Existing Identity
  3. Scan the QR code

Step 3: Revoke Lost Device (Optional)

If your old device was lost or stolen:

  1. On your working device, go to Settings > Devices
  2. Find the lost device
  3. Tap Revoke

Success

You're back! Your new device is now synced.


Social Recovery

If you've lost all devices and don't have a backup:

Overview

Social recovery uses your real-world relationships to verify your identity. You need vouchers from 3 or more contacts who have previously exchanged with you.

Step 1: Create New Identity

  1. Install Vauchi on a new device
  2. Create a new identity (fresh start)
  3. This gives you a new device to work from

Step 2: Start Recovery

  1. Go to Settings > Recovery
  2. Tap Recover Old Identity
  3. Enter your old public ID (if you know it)
    • If you don't know it, ask a contact — they can find it in your contact details
  4. A recovery claim is generated (valid for 48 hours)

Step 3: Collect Vouchers

For each voucher, you need to meet a contact in person:

  1. Meet in person (physical presence required)
  2. Show them your recovery claim
  3. They open Settings > Recovery > Help Someone Recover
  4. They paste your claim
  5. They verify it's really you
  6. They tap Create Voucher
  7. They share the voucher with you

Repeat until you have 3 or more vouchers.

Tip

Ask contacts you've met in person and who will recognize you immediately.

Step 4: Submit Recovery

  1. Go to Settings > Recovery
  2. Import each voucher you received
  3. Once you have 3+, tap Complete Recovery
  4. Vauchi submits your recovery proof

Step 5: Wait for Verification

  1. The relay verifies your vouchers
  2. Other contacts may verify via mutual connections
  3. Once verified, your identity transitions

Step 6: Re-Exchange (If Needed)

Some contacts may need to re-verify you:

  1. They'll see a notification about your recovery
  2. Meet them in person to confirm
  3. Your relationship continues

Success

You're back! Your identity has been recovered.


Helping Someone Else Recover

If a contact asks you to vouch for their recovery:

Step 1: Verify Their Identity

Before creating a voucher:

  • Meet in person if possible
  • Confirm they are who they claim to be
  • Be suspicious of unusual requests

Warning

Only vouch if you're certain. False vouching enables identity theft.

Step 2: Create Voucher

  1. Go to Settings > Recovery
  2. Tap Help Someone Recover
  3. Paste their recovery claim
  4. Tap Create Voucher

Step 3: Share Voucher

  1. Copy the voucher
  2. Send it to them (AirDrop, messaging, etc.)

Troubleshooting

Backup Restore: "Invalid Password"

  • Check for typos
  • Passwords are case-sensitive
  • Try any variations you might have used

If you truly can't remember the password, you'll need to use social recovery.

Backup Restore: "Invalid Backup Code"

  • Make sure you copied the entire code
  • Check for extra spaces or line breaks
  • Try copying again from the original source

Social Recovery: "Not Enough Vouchers"

  • You need at least 3 vouchers
  • Contact more people who have exchanged with your old identity
  • Vouchers must be from different contacts

Social Recovery: "Voucher Rejected"

  • The voucher may be for a different identity
  • The voucher may have expired (90 days)
  • Ask the contact to create a fresh voucher

Can't Remember Old Public ID

  • Ask any contact who had your old card
  • They can find your ID in your contact details
  • Look through old screenshots or notes

Prevention Tips

To avoid needing recovery:

  1. Create a backup as soon as you set up
  2. Store backup securely (password manager, safe)
  3. Link multiple devices (phone + tablet/desktop)
  4. Remember your password (use a passphrase)
  5. Have 5+ contacts who could vouch for you

Security Notes

  • Social recovery requires in-person verification
  • 3 vouchers prevent single-point-of-failure attacks
  • Vouchers expire after 90 days
  • Recovery is logged for transparency

For more on security, see Backup & Recovery Feature.

How to Set Up Multi-Device

Step-by-step guide for using Vauchi on multiple devices.


Prerequisites

  • Your existing device with Vauchi set up
  • A new device with Vauchi installed (but not set up)
  • Both devices have internet connectivity

Linking a New Device

On your existing device:

Mobile (iOS/Android):

  1. Open Vauchi
  2. Go to Settings (gear icon)
  3. Tap Devices
  4. Tap Link New Device
  5. A QR code appears (valid for 5 minutes)

Desktop:

  1. Open Vauchi Desktop
  2. Go to Devices (from the sidebar)
  3. Click Link New Device
  4. A QR code and data string appear (valid for 5 minutes)

TUI:

  1. Open Vauchi TUI
  2. Press d to go to Devices
  3. Press l to generate a link
  4. A QR code and data string appear in an overlay

CLI:

vauchi device link

A QR code and data string are displayed in the terminal.

Step 2: Join on New Device

On your new device:

Mobile (iOS/Android):

  1. Open Vauchi
  2. On the welcome screen, tap Join Existing Identity
  3. Point your camera at the QR code from Step 1
  4. Verify the confirmation code matches on both devices
  5. Wait for the linking to complete

Desktop:

  1. Open Vauchi Desktop
  2. On the setup screen, click Join Existing Identity
  3. Paste the data string from the existing device
  4. Verify the confirmation code matches on both devices
  5. Click Confirm to complete linking

CLI:

vauchi device join <data-string>

Then verify the confirmation code and run:

vauchi device complete <confirmation-code>

Step 3: Confirm

Both devices should show:

  • Your existing device: "Device linked successfully"
  • Your new device: "Welcome back, [Your Name]"

Your new device is now synced with your identity.


Verifying Setup

After linking:

On New Device

  1. Go to Contacts — your contacts should appear
  2. Go to Home — your contact card should appear
  3. Go to Settings > Devices — both devices should be listed

On Existing Device

  1. Go to Settings > Devices
  2. You should see both devices listed
  3. Your new device shows its platform and last sync time

Syncing Data

Data syncs automatically:

  • Immediately: When both devices are online
  • On app open: When you open the app
  • Manual: Pull to refresh or Settings > Sync Now

What Syncs

DataSyncs?
Your contact cardYes
Your contactsYes
Visibility settingsYes
App preferencesYes

Managing Devices

Viewing All Devices

Mobile/Desktop: Go to Settings > Devices to see all linked devices. Your current device is marked.

TUI: Press d to open the Devices screen. Navigate with j/k or arrow keys. Current device is marked [this device].

CLI:

vauchi device list

Revoking a Device

If a device is lost, stolen, or no longer needed:

Mobile/Desktop:

  1. Go to Settings > Devices on another device
  2. Find the device to revoke
  3. Tap Revoke
  4. Confirm by tapping Revoke Device

TUI:

  1. Press d to open Devices
  2. Navigate to the device with j/k
  3. Press r to revoke
  4. Press y to confirm

CLI:

vauchi device revoke <device-index>

Warning

You cannot revoke your current device. Use another linked device.

Danger

Revocation is immediate and permanent. The revoked device loses all access.


Troubleshooting

QR codes are valid for 5 minutes. If expired:

  1. On your existing device, generate a new link code
  2. Scan or paste the new code quickly

New Device Not Syncing

  1. Check internet on both devices
  2. Wait a few minutes for initial sync
  3. Pull to refresh on the new device
  4. Check Settings > Sync for last sync time

"Too Many Devices" Error

You can have up to 10 devices. To add another:

  1. Go to Settings > Devices
  2. Revoke a device you no longer use
  3. Try linking the new device again

Migrating to a New Phone

  1. Keep your old phone accessible
  2. Follow the steps above to link your new phone
  3. Wait for sync to complete
  4. Optionally, revoke your old phone

This is the cleanest migration path.

Option 2: Backup & Restore

If you can't access your old phone:

  1. Restore from an encrypted backup
  2. See How to Recover Your Account

Security Notes

  • Each device has its own derived keys
  • Revoking a device invalidates its keys immediately
  • The relay never sees plaintext data
  • Link codes expire after 5 minutes
  • A 6-digit confirmation code ensures you're linking the right devices

For more on security, see Multi-Device Feature.

About Vauchi

Privacy-focused updatable contact cards via in-person exchange.


What We're Building

Vauchi is a contact card that updates automatically. When you change your phone number, everyone who has your card sees the change — no need to notify them manually.

Unlike traditional contact apps:

  • No accounts — Your device is your identity
  • No phone number required — Exchange cards in person
  • End-to-end encrypted — Only you and your contacts can read your data
  • Open source — Verify every claim yourself

Why We Built It

Contact information goes stale. You change jobs, move cities, get a new number — and suddenly half your contacts have outdated info. The usual solution is to use a platform (social network, messaging app) as the source of truth, but that means trusting a company with your data.

We believe there's a better way: updatable contact cards that stay private.

Learn More

Open Source

Vauchi is open source under GPL-3.0-or-later. You can:

  • Inspect the code
  • Verify security claims
  • Contribute improvements
  • Run your own relay server

GitLab: https://gitlab.com/vauchi GitHub Mirror: https://github.com/vauchi

Contact

  • Issues: GitLab Issues
  • Email: hello@vauchi.app
  • Security: security@vauchi.app

Vauchi Principles

The single source of truth for core principles and philosophy.

All solutions must be validated against these principles before implementation.


Core Value Statement

Vauchi is built on five interlocking commitments:

1. Privacy is a right, not an option

All design starts with: "How would we build this if users were our only concern?"

  • E2E encryption for all communications
  • Zero-knowledge relay (sees only encrypted blobs)
  • No tracking, analytics, or telemetry
  • User owns and controls their data

2. Trust is earned in person

Human recognition is the security anchor, not passwords or platforms.

  • QR exchange with physical proximity verification for full trust; opt-in remote discovery at reduced trust
  • No accounts or registration (device IS the identity)
  • Social vouching for recovery (people you've actually met)
  • No trust-on-first-use, no platform-mediated relationships

3. Quality comes from rigorous methodology

Confidence through discipline, not hope.

  • Test-Driven Development (TDD) is mandatory
  • Problem-first workflow with full traceability
  • Threat modeling drives security decisions
  • No hacks, no tech debt, no ignored tests

4. Simplicity serves the user

Vauchi respects your time and attention — it does one thing well and stays out of your way.

  • No engagement tricks, no notifications designed to pull you back
  • Clear, minimal interface — useful without a learning curve
  • Features earn their place by solving real problems, not adding complexity
  • The app is a tool, not a destination

5. Beauty adapts to the user

Simplicity and beauty go hand in hand — and beauty is personal.

  • Design that feels good without demanding attention
  • Theming and customisation let users make it their own
  • Aesthetic choices serve clarity, never compete with it
  • A beautiful tool is one that fits the person using it

Principle Categories

Privacy Principles

PrincipleStatement
User OwnershipAll data stored locally on device, encrypted, under user control
Zero KnowledgeRelay cannot decrypt content; sees only encrypted blobs and metadata
No HarvestingNo analytics, telemetry, location tracking, or advertising IDs
No SharingUser data never shared with third parties
Selective VisibilityUsers control per-field, per-contact visibility

Security Principles

PrincipleStatement
Proximity Anchors Full TrustQR + BLE/ultrasonic required for full trust; opt-in remote contact at restricted visibility, no recovery/introduction privileges
Audited Crypto OnlyRustCrypto audited crates primary (ed25519-dalek, x25519-dalek: Trail of Bits; sha2, hmac, hkdf: RustCrypto); chacha20poly1305 and argon2 spec-mandated; aws-lc-rs retained for TLS only (via rustls); no custom cryptography
Forward SecrecyDouble Ratchet ensures past messages safe if keys compromised
Memory SafetyRust enforces safety; no unsafe in crypto paths
Defense in DepthMultiple layers: encryption, signing, verification

Technical Principles

PrincipleStatement
TDD MandatoryTidy→Red→Green→Refactor. Tidy first, test first. No exceptions
90%+ CoverageFor vauchi-core; real crypto in tests (no mocking)
Rust CoreMemory safety, no GC, cross-platform compilation
Clean Dependenciesvauchi-core standalone; downstream repos use git deps
Gherkin TraceabilityFeatures in features/*.feature drive test writing

UX Principles

PrincipleStatement
Complexity HiddenUsers see "scan QR, contact added"; encryption invisible
In-Person TrustHuman recognition is the security anchor
Offline-FirstQR exchange works without connectivity
Portable IdentityNo vendor lock-in; restore from backup, switch devices
Cross-Platform ConsistencySame experience on iOS, Android, desktop

Process Principles

PrincipleStatement
Problem-FirstEvery task starts as a problem; ideas restated as problems
Artifacts AccumulateInvestigation, rejected solutions, retrospectives attached to problems
No Wasted RejectionsArchive rejected solutions with reasoning
Small Atomic CommitsAfter each green, after each refactor
Retrospective RequiredLearn from every completed problem

Using These Principles

For Solution Validation

When evaluating a proposed solution, check:

  1. Does it align with Core Principles? (Privacy, Trust, Quality, Simplicity, Beauty)
  2. Does it fit the Culture? (Process Principles)
  3. Is it compatible with Current Implementation? (Technical Principles)
  4. Does it support existing Features? (UX Principles)

If a solution conflicts with any principle, it must be rejected with documented reasoning.

For Decision Making

When facing a design decision:

  1. Start with the user's perspective
  2. Assume adversarial conditions (what could go wrong?)
  3. Choose the option that best upholds all five core values
  4. Document the decision and rationale

For New Contributors

Read these principles before contributing. They are non-negotiable. If you disagree with a principle, open a problem record to discuss changing it—don't ignore it.


Amending Principles

Principles can be amended, but only through the Problem Workflow:

  1. Create a problem record explaining why the principle should change
  2. Investigate impact across codebase and documentation
  3. Validate the change against remaining principles
  4. Implement with full retrospective

Principles are not immutable, but changes must be deliberate and documented.

Security

How Vauchi protects your data.


Security Model

Vauchi is designed with the assumption that everything outside your device is hostile:

  • Relay server: Assumed compromised
  • Network: Assumed monitored
  • Other devices: Verified through in-person exchange

Despite these assumptions, your data stays private because of end-to-end encryption.

How We Protect You

End-to-End Encryption

All communication is encrypted so only you and your contacts can read it:

DataEncryption
Contact cardsXChaCha20-Poly1305
MessagesXChaCha20-Poly1305 with Double Ratchet
BackupsXChaCha20-Poly1305 with Argon2id KDF
Local storageXChaCha20-Poly1305

The relay server only sees encrypted blobs. It cannot:

  • Read your contacts
  • See your card fields
  • Decrypt any messages
  • Link your identity to your data

In-Person Verification

Contact exchange requires physical presence:

  • QR codes contain cryptographic identity
  • Proximity verification via ultrasonic audio
  • No trust-on-first-use — you verify who you're connecting with

This prevents spam, impersonation, and man-in-the-middle attacks.

Modern Cryptography

Vauchi uses battle-tested cryptographic libraries:

PurposeAlgorithmLibrary
SigningEd25519ed25519-dalek
Key exchangeX25519x25519-dalek
Symmetric encryptionXChaCha20-Poly1305chacha20poly1305
Password KDFArgon2idargon2
Key derivationHKDF-SHA256hkdf

All libraries are:

  • Written in Rust (memory-safe)
  • Well-audited
  • Widely used in production

Forward Secrecy

Each message uses a unique key derived via Double Ratchet:

  • Keys are used once then deleted
  • Even if one key is compromised, other messages stay safe
  • Past messages can't be decrypted with current keys

Threat Model

ThreatMitigation
Server compromiseE2E encryption — server can't read data
Network surveillanceTLS + Noise NK + E2E encryption — traffic is encrypted three layers deep
Man-in-the-middleIn-person verification — you verify identity yourself
Spam/harvestingProximity required — can't be added remotely
Device theftHardware-backed key storage, optional biometrics
Lost deviceSocial recovery + encrypted backups
Traffic analysisMessage padding to fixed sizes
Replay attacksOne-time tokens, message counters

Best Practices

For Users

  1. Create a backup — Protect against device loss
  2. Use a strong backup password — A passphrase (4+ words) is recommended. Store it somewhere safe, separate from your devices
  3. Verify important contacts — Compare fingerprints in person
  4. Revoke lost devices immediately — Prevent unauthorized access
  5. Keep your device secure — Enable lock screen, update OS
  6. Only link devices you physically control — Each linked device has full access to your identity

For Privacy

  1. Review visibility settings — Control what each contact sees
  2. Limit field sharing — Only share what's needed
  3. Remove old contacts — They keep seeing updates otherwise

For Recovery

Set up social recovery to protect against total device loss:

  1. Choose diverse guardians — Spread across different social circles (e.g., one family member, one friend, one colleague)
  2. Don't rely on one group — If all guardians are family, a single household event could make recovery impossible
  3. Set threshold to at least 3 — Higher thresholds are more secure
  4. Update guardians when relationships change — Remove guardians you've lost touch with and add new ones
  5. Review periodically — Check your guardian list once a year

For Backups

  1. Use a strong passphrase — At least 4 random words or equivalent strength
  2. Store backups securely — On a USB drive, external storage, or a secure location separate from your devices
  3. Don't store on cloud services — Backup files are encrypted, but keeping them local is more private
  4. Create fresh backups — After adding new contacts or linking devices

Security Reporting

Found a security issue? Please report it responsibly:

Email: security@vauchi.app

We will:

  • Acknowledge within 48 hours
  • Investigate and fix verified issues
  • Credit reporters (unless they prefer anonymity)
  • Not pursue legal action against good-faith researchers

Open Source

All code is open source and available for inspection:

We welcome security reviews and contributions.

Technical Details

For cryptographic implementation details, see:

Community

Join the Vauchi community.


Get Involved

Report Issues

Found a bug or have a feature request?

Contribute Code

We welcome contributions! See our Contributing Guide for:

  • Development setup
  • Code guidelines
  • Merge request process

Translations

Help translate Vauchi to your language:

Discussions

Have questions or ideas?


Code of Conduct

Our Commitment

Vauchi is built on trust earned in person. We extend that same spirit to our community: treat others as you would someone you've just met face-to-face.

Expected Behavior

  • Be respectful and considerate
  • Give and accept constructive feedback graciously
  • Focus on what's best for the project and community
  • Assume good intent; ask for clarification before assuming malice

Unacceptable Behavior

  • Harassment, insults, or personal attacks
  • Trolling or inflammatory comments
  • Publishing others' private information
  • Conduct that would be inappropriate in a professional setting

Scope

This applies to all project spaces: issues, merge requests, discussions, and any public space where you represent Vauchi.

Enforcement

Instances of unacceptable behavior may be reported to: conduct@vauchi.app

Maintainers will review and respond to all complaints. Responses may include:

  • Warning
  • Temporary ban
  • Permanent ban

Attribution

Adapted from the Contributor Covenant, version 2.1.


Contact

  • General: hello@vauchi.app
  • Security: security@vauchi.app
  • Conduct: conduct@vauchi.app

Supporters

Thank you to everyone who supports Vauchi's mission to build privacy-respecting software.


Platinum Sponsors

Become our first Platinum sponsor — GitHub Sponsors

Gold Sponsors

Become our first Gold sponsor — GitHub Sponsors

Silver Sponsors

Become our first Silver sponsor — GitHub Sponsors

Bronze Sponsors

Become our first Bronze sponsor — GitHub Sponsors

Backers

Your name could be here — GitHub Sponsors

Supporters

Your name could be here — GitHub Sponsors


How to Support

Every contribution helps us build privacy-respecting software without compromising on principles.

Where Funds Go

CategoryPurpose
HardwareDevelopment machines, mobile test devices
InfrastructureRelay server hosting, domain costs
SecurityExternal security audits
DevelopmentFull-time development toward v1.0

Thank you for believing in privacy-first software.

Privacy Policy

Last Updated: February 2026

Overview

Vauchi is a privacy-focused contact card exchange application. This privacy policy explains how we handle your data. The short version: your data stays on your devices, encrypted, and under your control.

Data Collection

What We Collect

On Your Device (Local Storage):

  • Your identity (cryptographic keypair, display name)
  • Your contact card (fields you choose to add: email, phone, etc.)
  • Contacts you've exchanged with (their public cards)
  • Visibility rules (which contacts can see which fields)
  • Device registry (for multi-device sync)

On Our Relay Server:

  • Temporary encrypted envelopes containing contact card updates (deleted after delivery or 30 days)
  • Connection metadata (IP address, connection timestamps) for rate limiting
  • No envelope content is ever readable by the server

What We Don't Collect

  • We do not collect analytics or telemetry
  • We do not track your location
  • We do not access your device contacts, photos, or other apps
  • We do not use advertising identifiers
  • We do not sell or share your data with third parties

Data Storage

Local-First Architecture

All your personal data is stored locally on your device:

  • Encryption at Rest: Your data is encrypted using XChaCha20-Poly1305 with a key stored in your device's secure enclave (iOS Keychain / Android KeyStore)
  • No Cloud Backup by Default: Your data is not automatically backed up to any cloud service
  • You Control Exports: You can create encrypted backups manually, protected by a password you choose

Relay Server

The relay server delivers encrypted contact card updates between your devices and your contacts. It operates as a store-and-forward broker:

  • Contact card updates are end-to-end encrypted before leaving your device
  • The server cannot decrypt any envelope content
  • Envelopes are deleted immediately after delivery or after 30 days if undelivered
  • Server logs contain only connection metadata, not contact card content
  • Rate limiting data (IP-based) is retained for 24 hours

Data Sharing

With Your Contacts

When you exchange contact cards with someone:

  • You explicitly choose which fields they can see
  • You can change visibility settings at any time
  • Changes sync automatically to their device

With Third Parties

We do not share your data with any third parties. Period.

With Law Enforcement

If required by law, we can only provide:

  • Connection metadata (IP addresses, timestamps)
  • Encrypted envelopes (which we cannot decrypt)

We cannot provide your contact card content, contact list, or any decrypted data because we do not have access to it.

Data Security

Cryptographic Protections

  • Identity Keys: Ed25519 signing keys, never leave your device
  • Encryption: X25519 key exchange + XChaCha20-Poly1305 for all contact card updates
  • Key Derivation: Argon2id for password-based encryption (backups)
  • Forward Secrecy: Double Ratchet protocol ensures each contact card update uses a unique encryption key

Platform Security

  • iOS: Keys stored in Secure Enclave via Keychain
  • Android: Keys stored in Hardware-backed KeyStore
  • Desktop: Keys encrypted with OS-level secure storage

Certificate Pinning

Mobile apps use certificate pinning to prevent man-in-the-middle attacks against the relay server connection.

Your Rights

Access Your Data

All your data is stored locally on your device. You can view it directly in the app.

Export Your Data

You can export an encrypted backup of all your data at any time from Settings > Backup.

Delete Your Data

  • Account Deletion: Use Settings > Delete Account to initiate deletion. A 7-day grace period allows you to cancel. After 7 days, the app sends a revocation signal to all your contacts (authenticated with your cryptographic identity), requests the relay server to purge all stored data for your account, and permanently deletes all local data (database, keys, and secure storage entries). Your contacts' apps will automatically delete your card upon receiving the revocation.
  • Single Contact Removal: You can remove any contact, which deletes their data from your device
  • Multi-Device: Account deletion is synchronized across all your linked devices. Initiating deletion on one device starts the grace period on all devices; cancellation from any device cancels on all devices.

Data Portability

Encrypted backups can be imported on any device where you install Vauchi.

Account Recovery

Vauchi has no central account or "forgot password" mechanism. Recovery depends on your situation:

  • Linked devices (primary method): If you have multiple linked devices and at least one remains accessible, your identity and all data are already synchronized. No recovery process is needed.
  • Social recovery (all devices lost): If all your devices are lost, trusted contacts you previously designated can vouch for your identity, allowing you to restore your cryptographic identity and contact list on a new device. Social recovery restores contacts and their card data, but trusted contact designations and per-contact visibility labels may not survive recovery and will need to be reconfigured.

Children's Privacy

Vauchi is not intended for children under 13. We do not knowingly collect data from children under 13. If you believe a child has provided us with data, please contact us.

Changes to This Policy

We may update this privacy policy from time to time. We will notify you of significant changes through the app or our website. Continued use of Vauchi after changes constitutes acceptance of the updated policy.

Open Source

Vauchi is open source software. You can inspect exactly how your data is handled by reviewing our source code at: gitlab.com/vauchi

Contact Us

For privacy-related questions or concerns:


Summary

QuestionAnswer
Do you store my contacts on your servers?No, only on your device
Can you read my contact card updates?No, they're end-to-end encrypted
Do you sell my data?No, never
Do you use tracking/analytics?No
Can I delete my data?Yes, use Settings > Delete Account (with 7-day grace period)
Is my data backed up automatically?No, you control backups
What if I lose my device?Use a linked device, or social recovery if all devices are lost

For Developers

Welcome to Vauchi development! This section contains everything you need to contribute.


Getting Started

New to the project? Start here:

  1. Contributing Guide — Set up your environment and learn the workflow
  2. Architecture Overview — Understand how the system works
  3. GUI Guidelines & UX Guidelines — Design rules for all platforms
  4. Cryptography Reference — Deep dive into encryption

Documentation

DocumentDescription
ContributingDevelopment workflow, code guidelines, PR process
GUI GuidelinesComponent-level design rules — toasts, inline editing, confirmations
UX Interaction GuidelinesInteraction philosophy — physical-first, offline-first, flow design
ArchitectureSystem overview, components, data flow
CryptographyEncryption algorithms, key management, protocols
Tech StackLanguages, frameworks, libraries
DiagramsSequence diagrams for core flows

Repository Structure

Vauchi is a multi-repo project under the vauchi GitLab group:

RepositoryPurpose
vauchi/Orchestrator repo (this documentation)
core/Rust workspace: vauchi-core + UniFFI bindings
relay/WebSocket relay server
linux-gtk/GTK4 Linux desktop app
linux-qt/Qt6/QML Linux desktop app
macos/macOS native app (SwiftUI)
windows/Windows native app (WinUI3)
ios/SwiftUI app
android/Kotlin/Compose app
features/Gherkin specs
locales/i18n JSON files

Quick Commands

# Clone and setup workspace
git clone git@gitlab.com:vauchi/vauchi.git
cd vauchi
just setup

# Build everything
just build

# Run all checks
just check

# Run tests
just test

# Show all commands
just help

Key Principles

All development follows our core principles:

  • TDD mandatory — Red → Green → Refactor
  • 90%+ coverage — For vauchi-core
  • Real crypto in tests — No mocking
  • Problem-first — Every task starts as a problem record

Getting Help

Architecture Overview

Vauchi is a privacy-focused contact card system. Users exchange contact cards in person via QR code (with NFC and Bluetooth as additional transport options). After exchange, cards update automatically — when you change your phone number, everyone who has your card sees the change.

System Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│                              VAUCHI SYSTEM                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │                           CLIENTS                                    │   │
│  │                                                                      │   │
│  │   ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐    │   │
│  │   │   iOS   │  │ Android │  │ Desktop │  │   CLI   │  │   TUI   │    │   │
│  │   │ SwiftUI │  │ Compose │  │ Native  │  │  Rust   │  │  Rust   │    │   │
│  │   └────┬────┘  └────┬────┘  └────┬────┘  └────┬────┘  └────┬────┘    │   │
│  │        │            │            │            │            │         │   │
│  │        └────────────┴─────┬──────┴────────────┴────────────┘         │   │
│  │                           │                                          │   │
│  │                    ┌──────▼──────┐                                   │   │
│  │                    │ vauchi-core │   Rust core library               │   │
│  │                    │  (UniFFI)   │   Crypto, storage, protocol       │   │
│  │                    └──────┬──────┘                                   │   │
│  │                           │                                          │   │
│  └───────────────────────────┼──────────────────────────────────────────┘   │
│                              │                                              │
│                              │ WebSocket (TLS)                              │
│                              │                                              │
│  ┌───────────────────────────▼──────────────────────────────────────────┐   │
│  │                         RELAY SERVER                                 │   │
│  │                                                                      │   │
│  │   ┌────────────────┐   ┌────────────────┐   ┌────────────────┐       │   │
│  │   │ Blob Storage   │   │ Device Sync    │   │ Recovery Store │       │   │
│  │   │ (encrypted)    │   │ (per-device)   │   │ (90-day TTL)   │       │   │
│  │   └────────────────┘   └────────────────┘   └────────────────┘       │   │
│  │                                                                      │   │
│  │   • Store-and-forward encrypted messages                             │   │
│  │   • No access to plaintext (zero-knowledge)                          │   │
│  │   • Rate limiting, quotas, GDPR purge                                │   │
│  │                                                                      │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Core Components

vauchi-core

The Rust core library provides all cryptographic and protocol functionality:

ModulePurposeKey Files
crypto/Encryption, signing, key derivationencryption.rs, signing.rs, ratchet.rs
exchange/Contact exchange protocolsession.rs, qr.rs, x3dh.rs
sync/Update propagationdevice_sync.rs, delta.rs
recovery/Social recoverymod.rs
storage/Local encrypted databasecontacts.rs, identity.rs, secure.rs
network/Relay communicationconnection.rs, protocol.rs
ui/Core-driven UI types and workflow enginesscreen.rs, component.rs, action.rs, engine.rs
i18nInternationalizationi18n.rs (runtime loading)

vauchi-protocol

Shared protocol message types used by both vauchi-core and the relay:

  • Serde-only crate (no crypto, no I/O)
  • Defines MessageEnvelope, MessagePayload, and all variant structs
  • Provides framing helpers (encode_message/decode_message)
  • Ensures wire format consistency between clients and relay

Relay Server

Rust server for message routing (depends on vauchi-protocol for shared types):

  • WebSocket-based store-and-forward
  • TLS required in production
  • No user accounts — just encrypted blobs
  • Background cleanup tasks (hourly)

Client Applications

PlatformStackBinding
iOSSwiftUIvauchi-platform-swift (SPM)
AndroidKotlin/Composevauchi-platform-kotlin (Gradle)
Linux (GTK)GTK4 (gtk4-rs)Direct Rust linkage
Linux (Qt)Qt6/QMLcbindgen C FFI
macOSSwiftUIUniFFI (shared with iOS)
WindowsWinUI3 (C# .NET 8)C ABI (vauchi-cabi)
CLIRustDirect library use
TUIRust (ratatui)Direct library use

Core-Driven UI

Core defines what to show; frontends only decide how to render natively. New workflows are pure Rust — zero frontend code unless a new component type is needed.

┌─────────────────────────────────────────────────────────┐
│                      Core (Rust)                         │
│                                                         │
│  WorkflowEngine trait                                   │
│    ├─ current_screen() → ScreenModel                    │
│    └─ handle_action(UserAction) → ActionResult           │
│                                                         │
│  ScreenModel  { title, subtitle, components, actions,   │
│                 progress }                               │
│  Component    { TextInput, ToggleList, FieldList,       │
│                 CardPreview, InfoPanel, Text, Divider }  │
│  UserAction   { TextChanged, ItemToggled,               │
│                 ActionPressed, ... }                     │
│  ActionResult { UpdateScreen, NavigateTo,               │
│                 ValidationError, Complete }              │
└────────────────────────┬────────────────────────────────┘
                         │  ScreenModel (JSON or direct)
                         │  UserAction (JSON or direct)
┌────────────────────────▼────────────────────────────────┐
│                Frontend (per platform)                    │
│                                                         │
│  Component Library (one native widget per Component)    │
│    TextInput → TextField / OutlinedTextField / <input>  │
│    ToggleList → Toggle list / Checkboxes / [x]/[ ]      │
│    ...                                                  │
│                                                         │
│  ScreenRenderer                                         │
│    Maps ScreenModel → native UI                         │
│    Sends UserAction back to core                        │
└─────────────────────────────────────────────────────────┘

Each frontend implements a component library (one native component per Component variant) and a ScreenRenderer that maps ScreenModel to native UI. The component library is built once and reused across all workflows.

ComponentLinux GTK4Linux Qt/QMLmacOS/iOS (SwiftUI)Android (Compose)Windows (WinUI3)TUI (Ratatui)CLI
TextInputgtk::EntryTextFieldTextFieldOutlinedTextFieldTextBoxInput widgetstdin prompt
ToggleListgtk::CheckButtonCheckBoxList + ToggleLazyColumn + CheckboxToggleSwitch[x]/[ ] listnumbered choice
FieldListgtk::ListBoxListViewList + chipsLazyColumn + chipsListViewTable rowsformatted output
CardPreviewgtk::FrameFrameCard viewCard composableBorderBox rendertext output
InfoPanelgtk::BoxColumnLayoutVStackColumnStackPanelBlockprintln sections

Transport: Rust clients (CLI, TUI, Desktop) call WorkflowEngine directly. Mobile clients (iOS, Android) use JSON over UniFFI.

Adding workflows: Implement a new WorkflowEngine in core. All frontends render it automatically via the existing component library — no frontend changes needed.

Adding component types: Define a new Component variant in core, then implement the corresponding native widget in each frontend's component library. This is rare — the vocabulary stabilizes quickly.

Data Flow

1. Contact Exchange (In-Person)

Alice                                 Bob
  │                                    │
  │─── Display QR (identity + key) ────│
  │                                    │
  │◄─── Scan QR, verify proximity ─────│
  │                                    │
  │─── X3DH key agreement ─────────────│
  │                                    │
  │◄─── Exchange encrypted cards ──────│
  │                                    │
  │ Both now have each other's cards   │

2. Card Updates (Remote via Relay)

Alice updates phone number
         │
         ▼
┌─────────────────┐
│ Encrypt delta   │  Per-contact shared key
│ with CEK        │  (Double Ratchet)
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ Send to relay   │  WebSocket
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ Relay stores    │  Indexed by recipient_id
│ encrypted blob  │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ Bob connects    │  Receives pending messages
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ Decrypt delta   │  Update Alice's card locally
└─────────────────┘

3. Multi-Device Sync

All devices under one identity share the same master seed. Device-specific keys are derived via HKDF:

Master Seed
    ├── Device 1 keys (HKDF + device_index)
    ├── Device 2 keys (HKDF + device_index)
    └── Device 3 keys (HKDF + device_index)

Device linking uses QR code scan with time-limited token.

4. Recovery (Social Vouching)

When all devices are lost:

  1. Create new identity
  2. Generate recovery claim (old_pk → new_pk)
  3. Meet contacts in person, collect signed vouchers
  4. When threshold (3) met, upload proof to relay
  5. Other contacts discover proof, verify via mutual contacts
  6. Accept/reject identity transition

Security Model

End-to-End Encryption

  • All card data encrypted with XChaCha20-Poly1305
  • Per-contact keys derived via X3DH + Double Ratchet
  • Forward secrecy: each message uses unique key
  • Relay sees only encrypted blobs

Key Hierarchy

Master Seed (256-bit, generated at identity creation)
├── Identity Signing Key (Ed25519, raw seed)
├── Exchange Key (X25519, HKDF derived)
└── SMK (Shredding Master Key, HKDF derived)
    ├── SEK (Storage Encryption Key)
    ├── FKEK (File Key Encryption Key)
    └── Per-Contact CEK (random 256-bit)

Physical Verification

Contact exchange requires in-person presence:

  • QR + ultrasonic audio verification (18-20 kHz) — implemented on iOS, planned for Android
  • NFC Active tap (planned — centimeters range)
  • BLE with RSSI proximity check (planned — GATT transport)

Repository Structure

vauchi/                    ← Orchestrator repo
├── core/                  ← vauchi-core + vauchi-platform + vauchi-protocol
├── relay/                 ← WebSocket relay server (uses vauchi-protocol)
├── linux-gtk/             ← GTK4 Linux desktop app
├── linux-qt/              ← Qt6/QML Linux desktop app
├── macos/                 ← macOS native app (SwiftUI)
├── windows/               ← Windows native app (WinUI3)
├── ios/                   ← SwiftUI app
├── android/               ← Kotlin/Compose app
├── cli/                   ← Command-line interface
├── tui/                   ← Terminal UI
├── features/              ← Gherkin specs
├── locales/               ← i18n JSON files
├── e2e/                   ← End-to-end tests
└── docs/                  ← Documentation

Cryptography Reference

Concise reference for all cryptographic operations in Vauchi.

Algorithms

PurposeAlgorithmLibraryNotes
SigningEd25519ed25519-dalekIdentity, device registry, revocation
Key ExchangeX25519x25519-dalekX3DH with identity binding for in-person exchange
Symmetric EncryptionXChaCha20-Poly1305chacha20poly1305Primary cipher (192-bit nonce)
Forward SecrecyDouble Ratchethkdf + hmacHKDF-SHA256 + HMAC-SHA256, chain limit 2000
Key DerivationHKDF-SHA256hkdfRFC 5869, domain-separated
Password KDFArgon2idargon2m=64MB, t=3, p=4 (OWASP)
CSPRNGOsRngrandOS-provided entropy via rand::rngs::OsRng
TLSTLS 1.2/1.3rustls (aws-lc-rs backend)Relay connections only

Key Types

Identity Keys

KeyTypeSizePurpose
Master SeedSymmetric256-bitRoot of all keys
Signing KeyEd2551932+64 bytesIdentity, signatures
Exchange KeyX2551932 bytesKey agreement

Storage Keys (Shredding Hierarchy)

Master Seed (256-bit)
├── Identity Signing Key       — raw seed (Ed25519 requirement)
├── Exchange Key               — HKDF(seed, "Vauchi_Exchange_Seed_v2")
└── SMK (Shredding Master Key) — HKDF(seed, "Vauchi_Shred_Key_v2")
    ├── SEK (Storage Encryption Key) — HKDF(SMK, "Vauchi_Storage_Key_v2")
    │   └── encrypts all local SQLite data
    ├── FKEK (File Key Encryption Key) — HKDF(SMK, "Vauchi_FileKey_Key_v2")
    │   └── encrypts file key storage
    └── Per-Contact CEK — random 256-bit per contact
        └── encrypts individual contact's card data

HKDF Convention: Master seed as IKM, no salt, domain string as info. All derivations use HKDF::derive_key(None, &seed, info).

HKDF Context Strings:

ContextUsage
Vauchi_Exchange_Seed_v2Exchange key derivation from master seed
Vauchi_Shred_Key_v2SMK derivation from master seed
Vauchi_Storage_Key_v2SEK derivation from SMK
Vauchi_FileKey_Key_v2FKEK derivation from SMK
vauchi-x3dh-symmetric-v2X3DH transcript binding (4-key HKDF info)
vauchi-x3dh-key-v2X3DH key agreement derivation
Vauchi_Root_RatchetDH ratchet root key step
Vauchi_Message_KeySymmetric ratchet message key
Vauchi_Chain_KeySymmetric ratchet chain key advance
Vauchi_AnonymousSender_v2Anonymous sender ID derivation
Vauchi_Mailbox_v1Contact mailbox token (daily rotation, SP-33)
Vauchi_DeviceSync_v1Device sync self-token (daily rotation, SP-33)

Ratchet Keys

KeyTypeLifecycle
Root Key32 bytesUpdated on DH ratchet
Chain Key32 bytesAdvances with each message
Message Key32 bytesSingle-use, deleted after

Ciphertext Format

algorithm_tag (1 byte) || nonce || ciphertext || tag
TagAlgorithmNonceNotes
0x01AES-256-GCM12 bytesRemoved — no longer supported
0x02XChaCha20-Poly130524 bytesDefault since v0.1.2
0x03XChaCha20-Poly1305 + AD24 bytesDouble Ratchet (header-bound)

Tag 0x03 binds message header as AEAD associated data to prevent relay manipulation.

Message Padding

All messages padded to fixed buckets before encryption:

BucketSizeTypical Content
Small256 BACK, presence, revocation
Medium1 KBCard deltas, small updates
Large4 KBMedia references, large payloads

Messages > 4 KB: rounded to next 256-byte boundary.

Format: [4-byte BE length prefix] [plaintext] [random padding]

X3DH Key Agreement

Full X3DH with identity binding (no signed pre-keys):

QR / Mutual Exchange (Symmetric)

Both sides:
  ephemeral ← generate X25519 keypair
  shared_bytes ← DH(our_ephemeral_secret, their_ephemeral_public)

  // Transcript binding: all four public keys sorted lexicographically
  // and appended to info, preventing identity misbinding attacks
  info ← "vauchi-x3dh-symmetric-v2" || sort(id_lo, id_hi) || sort(eph_lo, eph_hi)
  shared ← HKDF(ikm=shared_bytes, salt=None, info=info)

NFC/BLE Exchange

Same as Mutual QR — fresh ephemeral keys on both sides, HKDF-derived shared secret.

Double Ratchet

┌─────────────────────────────────────────────────────────────────┐
│                         DOUBLE RATCHET                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │                        DH RATCHET                         │  │
│  │                                                           │  │
│  │    our_dh_secret × their_dh_public                        │  │
│  │              ↓                                            │  │
│  │    HKDF(root_key, shared_secret, "Vauchi_Root_Ratchet")   │  │
│  │              ↓                                            │  │
│  │    [new_root_key, new_chain_key]                          │  │
│  │                                                           │  │
│  └───────────────────────────────────────────────────────────┘  │
│                              ↓                                  │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │                   SYMMETRIC RATCHET                       │  │
│  │                                                           │  │
│  │    chain_key                                              │  │
│  │        ↓                                                  │  │
│  │    HKDF(chain_key, "Vauchi_Message_Key")                  │  │
│  │        → message_key (single use)                         │  │
│  │        ↓                                                  │  │
│  │    HKDF(chain_key, "Vauchi_Chain_Key")                    │  │
│  │        → next_chain_key                                   │  │
│  │                                                           │  │
│  └───────────────────────────────────────────────────────────┘  │
│                                                                 │
│  Limits:                                                        │
│    • Max chain generations: 2000                                │
│    • Max skipped keys stored: 1000                              │
│    • Message key deleted immediately after use                  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Ratchet Message (Authenticated, Not Encrypted Header)

#![allow(unused)]
fn main() {
RatchetMessage {
    dh_public: [u8; 32],      // Current DH public key
    dh_generation: u32,       // DH ratchet step counter
    message_index: u32,       // Message index in current chain
    previous_chain_length: u32, // Messages sent in previous chain
    ciphertext: Vec<u8>,      // Encrypted payload
}
}

Header (44 bytes) bound as AEAD associated data (tag 0x03).

Backup Format

v2 (Current)

[0x02] || salt(16) || ciphertext
  • Key derivation: Argon2id (m=64MB, t=3, p=4)
  • Cipher: XChaCha20-Poly1305
  • Plaintext: display_name_len(4) || display_name || master_seed(32) || device_index(4) || device_name_len(4) || device_name

v1 (Removed)

salt(16) || nonce(12) || ciphertext || tag(16)
  • Key derivation: PBKDF2-HMAC-SHA256
  • Cipher: AES-256-GCM
  • Status: Removed from codebase. Documented for format reference only.

Transport Encryption (Noise NK)

Client-to-relay communication uses a Noise NK inner transport layer as defense-in-depth inside TLS.

Pattern

Noise_NK_25519_ChaChaPoly_BLAKE2s

NK means the relay's static public key is known to the client before the handshake (distributed via the /info HTTP endpoint as base64url). The client does not authenticate to the relay (anonymous initiator).

Handshake

Pre-message:  <- s   (relay's static public key, known to client)
Message 1:    -> e, es   (client sends ephemeral, DH with relay static)
Message 2:    <- e, ee   (relay sends ephemeral, DH between ephemerals)

After Message 2, both sides derive symmetric keys for bidirectional encryption.

v2 Framing

v2 (Noise-encrypted) connections are identified by a 3-byte magic prefix:

0x00 'V' '2' || 48-byte NK handshake message

All connections use the 3-byte 0x00 V 2 prefix followed by the NK handshake. After the handshake completes, all subsequent WebSocket frames are Noise-encrypted.

Why NK?

PropertyBenefit
No client authenticationPreserves anonymity — relay cannot link connections to identities
Forward secrecyEphemeral DH keys ensure past sessions can't be decrypted
Relay authenticationClient verifies the relay's identity via its static key
Defense-in-depthIf TLS is compromised, routing metadata (recipient IDs, message types) stays encrypted

Configuration

VariableDefaultDescription
RELAY_REQUIRE_NOISEfalseRemoved in v0.1 — Noise NK is always mandatory

The relay's Noise keypair is auto-generated on first start and persisted to {data_dir}/relay_noise_key.bin.

Security Properties

PropertyMechanism
ConfidentialityXChaCha20-Poly1305 encryption
IntegrityAEAD authentication tag
AuthenticityEd25519 signatures
Forward SecrecyDouble Ratchet, message keys deleted
Break-in RecoveryDH ratchet with ephemeral keys
No Nonce ReuseRandom 24-byte nonces
Memory Safetyzeroize on drop for all keys
Traffic Analysis PreventionFixed-size message padding
Replay PreventionDouble Ratchet counters
Transport EncryptionNoise NK inside TLS (defense-in-depth)

Source Files

ModulePath
Key Derivationcore/vauchi-core/src/crypto/kdf.rs
Signingcore/vauchi-core/src/crypto/signing.rs
Encryptioncore/vauchi-core/src/crypto/encryption.rs
Double Ratchetcore/vauchi-core/src/crypto/ratchet.rs
Chain Keycore/vauchi-core/src/crypto/chain.rs
CEKcore/vauchi-core/src/crypto/cek.rs
Shreddingcore/vauchi-core/src/crypto/shredding.rs
Password KDFcore/vauchi-core/src/crypto/password_kdf.rs
X3DHcore/vauchi-core/src/exchange/x3dh.rs
X3DH Session (Symmetric)core/vauchi-core/src/exchange/session.rs
Paddingcore/vauchi-core/src/crypto/padding.rs

Threat Model

Formal threat analysis of the Vauchi system using the STRIDE framework.


System Context

Vauchi is a contact card exchange system, not a messaging app:

  • Traffic pattern: Generated when users physically meet (exchange) or update contact info (small delta to all contacts)
  • Data type: Contact cards (name, phone, email, address, social handles)
  • Exchange model: In-person only via QR code, NFC, or BLE
  • Update frequency: Infrequent (users rarely change phone numbers or emails)
  • Update size: Small (delta of changed fields, typically < 1 KB)

This context shapes the threat model: low traffic volume and infrequent updates reduce the value of traffic analysis compared to messaging apps.

Trust Boundaries

┌─────────────────────────────────────────────────────────────┐
│                        CLIENT DEVICE                         │
│                                                              │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐   │
│  │ vauchi-core  │    │  Local DB    │    │  Platform    │   │
│  │ (crypto,     │    │ (encrypted   │    │  Keychain    │   │
│  │  protocol)   │    │  at rest)    │    │              │   │
│  └──────┬───────┘    └──────────────┘    └──────────────┘   │
│         │                                                    │
└─────────┼────────────────────────────────────────────────────┘
          │ TLS + E2E encrypted
          │
┌─────────▼────────────────────────────────────────────────────┐
│                      RELAY SERVER                             │
│                                                               │
│  • Assumed compromised (zero-knowledge design)               │
│  • Sees only encrypted blobs                                 │
│  • No user accounts, no decryption keys                      │
│  • Store-and-forward with TTL                                │
│                                                               │
└───────────────────────────────────────────────────────────────┘
BoundaryProtectionTrust Level
Client ↔ RelayTLS + authenticated WebSocketRelay is untrusted
Client ↔ ClientE2E encrypted (X3DH + Double Ratchet)Verified in person
Device ↔ DeviceSame identity, HKDF-derived device keysTrusted (same master seed)
Contact ↔ ContactPer-contact CEK, forward secrecyVerified at exchange time

Assets

AssetSensitivityDescription
Contact card dataHighPersonal info (phone, email, address)
Identity keysCriticalLong-term Ed25519 signing and X25519 exchange keys
Master seedCritical256-bit root secret for all key derivation
Social graphHighWho knows whom (contact relationships)
Update metadataMediumWhen someone changed their info

Adversary Model

AdversaryCapabilityMotivation
Passive network observerSees encrypted trafficSurveillance, profiling
Malicious relay operatorRuns a relay nodeData harvesting, traffic analysis
Compromised deviceFull access to one deviceTargeted attack, theft
Physical attackerSteals or seizes deviceLaw enforcement, theft
Malicious contactLegitimate contactSocial engineering, stalking

STRIDE Analysis

Spoofing

Threat: Attacker impersonates a contact or creates a fake identity.

Mitigations:

  • Ed25519 identity keys bound to each user
  • Full-trust contact exchange requires physical presence (QR, NFC, or BLE with proximity check)
  • Opt-in remote discovery establishes reduced-trust contacts (Tier 0/1) with restricted visibility
  • No trust-on-first-use for full trust: you verify who you connect with in person
  • Device registry is cryptographically signed; unauthorized devices cannot be added

Residual risk: Social engineering (convincing someone to scan a QR code under false pretenses).

Tampering

Threat: Relay or network attacker modifies messages in transit.

Mitigations:

  • AEAD encryption (XChaCha20-Poly1305) detects any modification via authentication tag
  • Ed25519 signatures verify sender authenticity
  • Double Ratchet message counters detect replay and reordering
  • Device registry version numbers prevent rollback

Residual risk: None. Tampering is cryptographically detected and rejected.

Repudiation

Threat: User denies having sent an update.

Design decision: Repudiation is a privacy feature, not a bug. Vauchi intentionally does not provide non-repudiation for contact card updates. Users should be able to update or remove their information without permanent proof of past states.

Information Disclosure

Threat: Unauthorized access to contact card data.

Mitigations:

  • All data E2E encrypted with XChaCha20-Poly1305 before leaving the device
  • Per-contact encryption keys (CEK) derived via X3DH + Double Ratchet
  • Forward secrecy: each message uses a unique key, deleted after use
  • Relay stores only encrypted blobs (zero-knowledge)
  • Local storage encrypted with device-derived keys (SEK from HKDF key hierarchy)
  • Sensitive key material zeroized on drop (zeroize crate)
  • Message padding to fixed buckets (256 B, 1 KB, 4 KB) prevents size-based inference

Residual risk: Metadata (connection timing, recipient pseudonyms) visible to relay. Mitigated by routing tokens, suppress_presence, and optional Tor support.

Denial of Service

Threat: Attacker disrupts relay availability.

Mitigations:

  • Token-bucket rate limiting (60 msgs/min per client, configurable)
  • Stricter recovery rate limit (10 queries/min, anti-enumeration)
  • Connection limit (max 1000 concurrent, RAII guard)
  • Multi-layer timeout protection: handshake timeout, idle timeout (5 min)
  • Message size limit (1 MB)
  • Automatic message expiration (30-day TTL; recovery store: 90-day TTL)
  • Federation support for relay redundancy

Residual risk: Sustained DDoS from many source IPs can overwhelm a single relay.

Elevation of Privilege

Threat: Attacker gains unauthorized capabilities.

Mitigations:

  • No user accounts on relay: no admin interface, no privilege levels
  • Client capabilities limited to own identity (can only decrypt own messages)
  • Master seed required for all identity operations
  • Device linking requires physical QR scan + identity key signature verification

Residual risk: Compromised device with master seed has full identity control. Mitigated by device revocation broadcast.

Key Security Properties

PropertyMechanism
ConfidentialityXChaCha20-Poly1305 E2E encryption
IntegrityAEAD authentication tag + Ed25519 signatures
Forward secrecyDouble Ratchet with ephemeral DH keys
Break-in recoveryDH ratchet step generates new key material
Zero-knowledge relayRelay sees only encrypted blobs, no decryption keys
Physical verificationQR + ultrasonic audio / NFC / BLE proximity (full trust); SAS + liveness (video, intermediate trust)
Traffic analysis resistanceFixed-size message padding + routing tokens
Memory safetyRust (no unsafe in crypto paths) + zeroize on drop
Replay preventionDouble Ratchet counters + version numbers

Attack Scenarios

Relay Compromise

If an attacker gains full control of a relay:

  • Cannot read contact card data (E2E encrypted)
  • Cannot forge messages (AEAD + signatures)
  • Can see pseudonymous recipient IDs and message timing
  • Can drop or delay messages (detected by delivery acknowledgments)
  • Can observe connection patterns (mitigated by Tor support)

Device Theft

If an attacker steals a device:

  • Master seed encrypted with user password via Argon2id (m=64 MB, t=3, p=4)
  • Platform-native key storage (macOS Keychain, iOS Keychain, Android KeyStore)
  • All key material zeroized on drop
  • Weak passwords can be brute-forced (Argon2id raises cost significantly)
  • Recommendation: Use a strong backup password, enable OS-level device encryption

Recovery Impersonation

If an attacker tries to abuse social recovery:

  • Must meet K contacts in person (default threshold: 3)
  • Each voucher signs with their identity key
  • Vouchers validated against victim's known contact list
  • Conflicting claims detected and flagged by relay
  • Voucher timestamps prevent replay (48-hour window)

Federation Security

When relays federate (forward blobs to peer relays for redundancy), additional trust boundaries apply.

What a federated relay CAN see:

  • Recipient routing IDs (pseudonymous public key hashes)
  • Blob sizes (padded to fixed buckets: 256, 512, 1024, 4096 bytes)
  • Message creation timestamps and hop count
  • Network topology via gossip protocol

What a federated relay CANNOT see:

  • Message content (E2E encrypted)
  • Sender identity (not included in federation protocol)
  • Real identity behind routing IDs
  • Client IP addresses (federation is relay-to-relay)

Guarantees:

  • mTLS authentication prevents unauthorized peers
  • Hop count limit prevents amplification attacks
  • SHA-256 integrity verification on all federated blobs
  • DNS rebinding protection: explicit resolution + SSRF validation before connecting to peer relays

Device Linking (STRIDE)

CategoryThreatMitigation
SpoofingAttacker scans device link QRQR expires in 5 min, physical proximity required
TamperingModified QR codeQR is signed by identity key
Information DisclosureMaster seed intercepted during transferEncrypted with ephemeral link key (XChaCha20-Poly1305)
Denial of ServiceExcessive device linkingMaximum 10 devices per identity
Elevation of PrivilegeUnauthorized device added to registryRegistry is cryptographically signed, version counter prevents rollback

Known limitation: Currently, the full master seed is shared during device linking (not per-device subkeys). A compromised linked device gains full identity control. Per-device subkey isolation is planned for 1.0 release.

Remote Discovery (Opt-In)

When remote discovery is enabled, users can generate discovery tokens that allow contacts to be established without physical proximity. This introduces a new attack surface that is mitigated by graduated trust tiers.

Trust Tiers

TierNameEstablished ViaCapabilities
0PendingToken-based contact requestView-only, expires after 30 days
1AcceptedRecipient accepts requestEveryone-visibility fields only
2VideoVerifiedSAS + liveness over video callLabel-based visibility (configurable)
3InPersonPhysical QR/NFC/BLE exchangeFull access, recovery, introductions

Recovery and facilitated introductions remain gated to Tier 3 (InPerson) only.

Attack Surface

ThreatMitigation
Token spam/harvestingToken expiry timestamps, one-time use flag, relay rate limiting (existing)
Phishing via discovery tokenTokens establish Tier 0 (Pending) only — no sensitive fields visible until manual acceptance
Social engineering to Tier 1Tier 1 sees only Everyone fields; cannot access recovery, introductions, or restricted labels
Video verification MITMSAS (Short Authentication String) mismatch detection — 6-digit code read aloud
Video deepfake/replayShared-secret-derived finger-count liveness challenge (not automatable without real-time interaction)
Sender identity leakage to relaySealed sender: ephemeral session IDs, no identity key in handshake
Recipient identity leakage to relayMailbox tokens: HKDF-derived, hourly rotation, opaque to relay
Token replayEd25519 signature + expiry timestamp + optional one-time use flag
Metadata correlation via mailbox tokensHourly rotation (epoch = unix_timestamp / 3600), derived per-contact pair

Sealed Sender Properties

Sealed sender is always enabled — it improves metadata protection for all users, not just remote contacts. With sealed sender delivery, the relay sees:

  • Session ID: Ephemeral, per-connection (no identity key)
  • Mailbox token: Opaque, hourly rotation, derived from shared key
  • Encrypted blob: AEAD ciphertext, padded to fixed buckets

The relay cannot determine: who sent a message, who the real recipient is (beyond the opaque token), or whether two sessions belong to the same user across reconnections.

Residual Risks

RiskSeverityNotes
Remote contacts with weak trustLowMitigated by tier gating — Tier 1 cannot exceed Everyone visibility
Token distribution on untrusted channelsMediumUser responsibility; tokens are self-generated and self-published
Video verification over compromised platformLowSAS provides cryptographic binding independent of video platform security
Correlation of mailbox tokens across epoch boundariesLowBrief overlap window during rotation; complementary to Tor support

Known Limitations

LimitationImpactMitigation Path
Relay sees connection metadataSocial graph inference possibleSealed sender (ephemeral sessions + mailbox tokens), Tor support
Device compromise exposes master seedAll-or-nothing with master seedPer-device subkeys (planned), strong passwords
Linked device receives full seedCompromised device = full identityPer-device subkeys (planned)
Single relay is availability SPOFUsers can't sync if relay is downFederation, multi-relay failover (planned)
Blocked contacts retain old dataCannot "unsend" previously shared infoBy design (accept tradeoff)
Recovery reveals voucher public keysPartial social graph leakageAccepted tradeoff for recovery
No guardian diversity enforcementAll guardians from same circleUX warnings (planned)
No key transparency post-exchangeKey history not auditableLightweight signed key log (future)
Remote contacts have weaker trustTier 1/2 lack in-person verificationGraduated trust tiers gate capabilities; upgrade via in-person meeting
Push notifications would leak metadataAPNs/FCM see delivery timingEmpty push + app-side fetch (required design)

Core-UI Trust Boundary

The vauchi-core library is consumed by multiple UI layers (macOS/SwiftUI, Linux-GTK, Linux-Qt, CLI, TUI, mobile Swift/Kotlin via UniFFI). The trust relationship between core and its callers:

What core trusts from UI:

  • UI provides correct file paths for storage
  • UI invokes lifecycle methods in the correct order (init before use)
  • UI does not hold references to sensitive data after core has zeroized them

What core does NOT trust from UI:

  • Input lengths: Core enforces maximum lengths at its API boundary (display name: 100 chars, field value: 1000 chars, field label: 64 chars, card size: 64 KB, avatar: 256 KB)
  • Field values: Core validates phone/email format, URL safety, and rejects malformed data
  • Contact IDs: Core verifies contact existence before processing updates
  • Ratchet messages: Core validates signatures, AEAD tags, and replay nonces before accepting peer data
  • File content: Core verifies checksums on all content fetched from remote servers

A compromised or buggy UI layer cannot cause vauchi-core to:

  • Decrypt data for an unauthorized recipient
  • Produce unsigned or weakly signed messages
  • Skip replay detection or signature verification
  • Bypass visibility label enforcement

UniFFI surface note: The UniFFI binding layer does not add rate limiting at the API boundary. Rate limiting for relay operations is enforced server-side. UI layers should implement their own rate limiting for user-facing operations to prevent accidental rapid-fire calls.

Relay Metadata Exposure Analysis

While the relay sees only encrypted blobs, it observes metadata that can reveal the social graph:

What the relay sees:

DatumVisibilityExample
Sender pseudonym (session ID)Per-connectionEphemeral, no identity key (sealed sender)
Recipient pseudonym (mailbox token)Hourly rotationHKDF-derived, opaque to relay
Message timingExactTimestamp of send/receive
Message sizeApproximateEncrypted blob size (padded to buckets)
Connection frequencyExactHow often a client connects

What the relay CANNOT see:

  • Message content (E2E encrypted, AEAD)
  • Contact card fields (encrypted under per-contact CEK)
  • Sender identity beyond pseudonymous routing ID
  • Which specific contact fields changed

Risk assessment for Vauchi:

The social graph inference risk is lower than for messaging apps because:

  1. Updates are infrequent (users rarely change contact info)
  2. Updates are small and padded to fixed buckets (256 B, 512 B, 1 KB, 4 KB)
  3. All locale files are downloaded in bulk to prevent language inference
  4. Routing IDs are pseudonymous and session-scoped

Current mitigations: Routing token rotation, suppress_presence flag, optional Tor support, fixed-size message padding.

Future considerations: Mixnet-based relay routing or Private Information Retrieval (PIR) could eliminate recipient pseudonym visibility entirely. These are not currently prioritized given Vauchi's low-frequency traffic pattern, but remain on the architectural roadmap for high-threat deployments.

Key Transparency

Current Model

Vauchi uses a trust-on-exchange model: during the initial in-person exchange (QR/NFC/BLE), both parties verify each other's Ed25519 identity keys directly. This provides strong initial authentication.

After the exchange, contacts receive updates via the Double Ratchet:

  • If a user adds a new device, it derives keys from the same master seed
  • Contacts receive a DeviceRegistry update signed by the existing identity key
  • The ratchet provides forward secrecy and break-in recovery

Gap

There is no append-only, auditable log of a user's key history. A sophisticated attacker who compromises both a relay and a user's device could theoretically:

  1. Generate a new identity key for the victim
  2. Distribute it to the victim's contacts via the compromised relay
  3. Contacts would have no way to verify this is consistent with the victim's key history

Accepted Tradeoff

This attack requires simultaneous compromise of both the relay and the target device, which is beyond Vauchi's primary threat model (relay-only compromise). The in-person exchange provides a strong root of trust that subsequent key changes cannot easily override without raising suspicion (contacts would see unexpected re-exchange requests).

Future Direction

A lightweight key transparency mechanism could provide additional assurance:

  • Signed key history: Each user maintains a signed append-only log of their key changes. Contacts can audit this log to detect unauthorized key modifications.
  • Cross-contact verification: Contacts can compare their view of a user's key history with other contacts to detect divergence (split-world attack detection).

This is not currently implemented but is documented as a future enhancement for high-assurance deployments. A full CONIKS-style transparency log is not necessary given Vauchi's decentralized architecture and low update frequency.

Push Notification Constraints

Push notifications (APNs for iOS, FCM for Android) are not currently implemented. If they are added in the future, the following constraint is mandatory:

Threat: Naive push notifications would expose message receipt timing to Apple/Google. Even though the relay sees only encrypted blobs, a push notification would link a specific device token (tied to a real Apple/Google account) to the exact moment a Vauchi message arrives. This undermines the zero-knowledge relay design.

Required pattern: Empty push + app-side fetch.

  1. The relay sends a push notification with no payload and no sender information — only a "wake up" signal
  2. The app receives the push, connects to the relay independently over TLS
  3. The app fetches pending messages using its normal encrypted channel
  4. Apple/Google learn only that the app was woken up, not who sent a message or what it contains

This pattern is used by Signal and other privacy-focused applications. Any implementation of push notifications in Vauchi must follow this pattern. Direct payload delivery via push is explicitly prohibited.

Cryptographic Primitives

PurposeAlgorithmLibrary
SigningEd25519ed25519-dalek
Key exchangeX25519x25519-dalek
Symmetric encryptionXChaCha20-Poly1305chacha20poly1305
Password KDFArgon2idargon2
Key derivationHKDF-SHA256hkdf
CSPRNGOsRngrand
TLSTLS 1.2/1.3rustls (aws-lc-rs backend)

For the full cryptographic specification, see the Cryptography Reference.

Comparison with Messaging Apps

AspectVauchiMessaging Apps
Traffic volumeVery low (rare updates)High (continuous)
Timing analysis valueLowHigh
Social graph valueMediumHigh
Metadata exposureRecipient ID onlySender + recipient
Forward secrecyYes (Double Ratchet)Varies
Relay knowledgeEncrypted blobs onlyOften plaintext
Recovery modelSocial vouching (K contacts, in person)Phone number / cloud backup

Vauchi's infrequent, small updates significantly reduce the value of traffic analysis compared to messaging apps.

Security Reporting

Found a vulnerability? Please report it responsibly:

Email: security@vauchi.app

We acknowledge reports within 48 hours and do not pursue legal action against good-faith researchers.

Contributing to Vauchi

Quick Start

# Clone and setup workspace
git clone git@gitlab.com:vauchi/vauchi.git
cd vauchi
just setup

# Build and test
just build
just check

Development Workflow

All repos have protected main branches — no direct pushes, merge via MR only, force push disabled.

Step 1: Create a branch

git checkout -b feature/my-feature

Branch naming: {type}/{short-description}

TypeUse Case
feature/New functionality
bugfix/Bug fixes
refactor/Code restructuring
tidy/Structural-only cleanup (Tidy First)
investigation/Research/exploration

For multi-repo features, use the same branch name in every affected repo:

just git branch feature/remote-content-updates features core docs

Step 2: Do the work — commit often

  • If changing code: strict TDD. Tidy → Red → Green → Refactor. No exceptions.
    1. Tidy first — small structural improvement if the code is hard to change (own tidy: commit)
    2. Write failing test first (Red)
    3. Write minimal code to pass (Green)
    4. Refactor
    5. Tests must trace to features/*.feature scenarios
  • Commit atomically — each commit should be a single logical change.
  • Run just check before pushing (formats, lints, tests).
  • See Principles for core values.

Commit message format:

{type}: {Short description}

{Longer description if needed}

Types: feat, fix, refactor, tidy, docs, test, chore

Use imperative mood: "Add feature" not "Added feature". Keep first line under 72 characters.

Use just commit for interactive commits across all repos with changes.

Step 3: Create a Merge Request

# Push all repos with changes (runs pre-push checks)
just git push-all

# Create MR for a specific repo
just gitlab mr-create core

CI must pass (security scans + tests) before merge.

If the work spans multiple repos, each MR must list the related MRs it depends on:

## Summary
- Bullet points of changes

## Related MRs
- vauchi/features!42 - Gherkin specs
- vauchi/core!78 - Implementation

## Test Plan
- [ ] Tests pass
- [ ] Manual testing done

## Checklist
- [ ] Follows TDD
- [ ] Documentation updated
- [ ] Feature file updated (if applicable)
- [ ] Follows [GUI Guidelines](gui-guidelines.md) and [UX Guidelines](ux-guidelines.md) (if UI changes)

Use GitLab MR reference format: {group}/{project}!{mr_number}

Code Guidelines

Rust

  • Use RustCrypto audited crates (ed25519-dalek, x25519-dalek, sha2, hmac, hkdf, chacha20poly1305, argon2); aws-lc-rs for TLS only (via rustls)
  • Never mock crypto in tests
  • 90%+ test coverage for vauchi-core
  • Use Result/Option, fail fast

Structure

crate/
├── src/      # Production code only
└── tests/    # Integration tests only

Mobile Bindings Workflow

UniFFI generates Swift/Kotlin bindings from Rust. When modifying vauchi-platform or vauchi-core:

When to Regenerate Bindings

Regenerate bindings when you:

  • Add/remove/rename exported types or functions
  • Change function signatures
  • Add new #[uniffi::export] or #[derive(uniffi::*)] annotations

How to Regenerate

cd core

# IMPORTANT: Build without symbol stripping to preserve metadata
RUSTFLAGS="-Cstrip=none" cargo build -p vauchi-platform --release

# Regenerate for both platforms (macOS required for iOS)
./scripts/build-bindings.sh

# Or regenerate Android only (works on Linux)
./scripts/build-bindings.sh --android

# Validate bindings have all expected types
./scripts/validate-bindings.sh

Common Issues

Empty or incomplete bindings:

  • Cause: Library built with symbol stripping (default in release)
  • Fix: Use RUSTFLAGS="-Cstrip=none" when building

Missing types in generated code:

  • Run validate-bindings.sh to check expected types
  • Regenerate with the steps above

Repository Organisation

This is a multi-repo project under the vauchi GitLab group. The root repo (vauchi/vauchi) is the workspace orchestrator — just setup clones all sub-repos as sibling directories. Each subdirectory is its own Git repo at gitlab.com/vauchi/<name>.

vauchi/                          ← root repo (justfile, CI config)
│
├── core/                        ← Rust workspace: vauchi-core + vauchi-platform (UniFFI)
├── relay/                       ← WebSocket relay server (standalone Rust)
├── cli/                         ← Command-line interface
├── tui/                         ← Terminal user interface
│
├── android/                     ← Kotlin/Compose native app
├── ios/                         ← SwiftUI native app
├── macos/                       ← SwiftUI macOS app
├── linux-gtk/                   ← GTK4 + libadwaita Linux app
├── linux-qt/                    ← Qt6 Linux app
├── windows/                     ← WinUI 3 Windows app
├── web-demo/                    ← SolidJS + WASM demo app
├── vauchi-platform-kotlin/      ← Generated Kotlin bindings + JNI libs (Gradle distribution)
├── vauchi-platform-swift/       ← Generated Swift bindings + XCFramework (SPM distribution)
│
├── e2e/                         ← End-to-end tests
├── features/                    ← Gherkin specs (shared across all platforms)
│
├── docs/                        ← Public documentation (this site)
├── scripts/                     ← Dev tools, hooks, utilities
├── website/                     ← Landing page source
└── assets/                      ← Brand assets, logos

Platform bindings (vauchi-platform-swift/, vauchi-platform-kotlin/) are not manually editedcore/ CI generates UniFFI bindings and pushes artifacts to these repos when merging to main.

Release Workflow

Vauchi uses a 3-tier versioning system. Each tier triggers a different CI pipeline scope:

TierTag FormatWhat RunsPublishes?
Devv0.2.3-dev.NLint + test onlyNo
RCv0.2.3-rc.NLint + test + coverage + mutation + securityNo
PRODv0.2.3Full release: build, package, publish, deploy, trigger mobile bindingsYes

Creating releases

just release-dev [repo]      # Fast feedback — default: core. E.g., just release-dev cli
just release-rc [repo]       # Full quality gate. E.g., just release-rc relay
just release-prod [repo]     # Full release. E.g., just release-prod core
just release-history [repo]  # Show promotion chain. E.g., just release-history tui

Typical flow

  1. Merge feature MRs to main
  2. just release-dev — verify basic CI passes (~2 min)
  3. just release-rc — full quality gate with coverage + mutation (~15 min)
  4. just release-prod — publish to package registry, trigger mobile binding distribution

Dev and RC tags never publish artifacts, trigger mobile repos, or create GitLab releases. Only PROD tags do.

Useful Commands

just help              # Show all commands
just check-annotations # Check test coverage vs features
just relay             # Start local relay for testing
just run cli           # Run CLI
just git sync          # Fetch all + pull where on main

Getting Help

License

By contributing, you agree that your contributions will be licensed under GPL-3.0-or-later.

Technology Stack

Core Library (Shared)

ComponentTechnologyNotes
LanguageRustMemory safety, cross-platform
Cryptoed25519-dalek, x25519-dalek, chacha20poly1305, argon2RustCrypto audited crates
StorageSQLiteEncrypted with XChaCha20-Poly1305
Serializationserde + JSONProtocol messages
FFIUniFFISwift/Kotlin bindings

Mobile Apps

iOS

ComponentTechnology
UI FrameworkSwiftUI
LanguageSwift
BindingsUniFFI (SPM package)
Min iOS15.0

Android

ComponentTechnology
UI FrameworkJetpack Compose
LanguageKotlin
BindingsUniFFI (Gradle dependency)
Min SDK26 (Android 8.0)

Desktop Apps (Native)

PlatformFrameworkLanguageBindings
macOSSwiftUISwiftUniFFI (SPM)
Linux (GTK)GTK4 + libadwaitaRustDirect (same process)
Linux (Qt)Qt 6C++C ABI (vauchi-cabi)
WindowsWinUI 3C# (.NET 8)C ABI (vauchi-cabi)

Web Demo

ComponentTechnologyNotes
FrameworkSolidJSTypeScript, WASM bridge
Corevauchi-core (WASM)wasm32-unknown-unknown target
CryptoPure RustCrypto (WASM)No WebCrypto bridge needed (SP-30)

CLI & TUI

ComponentTechnology
CLIRust (clap)
TUIRust (ratatui)

Relay Server

ComponentTechnologyNotes
LanguageRustStandalone binary
WebSockettokio-tungsteniteAsync runtime
TLSrustlsCertificate handling
StorageIn-memory + diskEncrypted blobs only

Development Tools

ToolPurpose
JustTask runner
CargoRust package manager
npm/pnpmJavaScript dependencies
DockerContainerization
GitLab CIContinuous integration

Performance Targets

OperationTarget
Contact exchange< 3 seconds
Update propagation< 30 seconds (when online)
Local operations< 100ms
App startup< 2 seconds

Data Limits

LimitValue
Max contact card size64KB (encrypted)
Max contacts per user10,000
Max fields per card100
Max linked devices10

Repository Dependencies

vauchi-core (standalone, no workspace deps)
    ↑ (git dependency)
cli/, tui/, e2e/, macos/, windows/, linux-gtk/, linux-qt/, web-demo/

vauchi-platform (UniFFI bindings, in core/ workspace)
    ↑ (via generated binding repos)
android/ ← vauchi-platform-kotlin (Gradle)
ios/     ← vauchi-platform-swift (SPM)

vauchi-cabi (C ABI exports, in core/ workspace)
    ↑ (cbindgen)
linux-qt/, windows/

relay/ (standalone, uses vauchi-protocol for shared types only)

Downstream repos use git dependencies with branch-based pinning (branch = "main"). Local development uses .cargo/config.toml path overrides.

TDD Rules

Three Laws

  1. No production code without a failing test
  2. Write only enough test to fail
  3. Write only enough code to pass

Tidy-Red-Green-Refactor-Commit

TIDY     → Small structural improvement  → COMMIT (no behavior change)
RED      → Write failing test
GREEN    → Minimal code to pass          → COMMIT (tests green)
REFACTOR → Improve design                → COMMIT (tests still green)

Inspired by Kent Beck's Tidy First?: make the change easy, then make the easy change.

TIDY is an optional pre-step before starting a Red-Green cycle. A tidying is a small, structural-only change that makes the upcoming work easier — guard clauses, extract helper, rename for clarity, reorder for readability, delete dead code. Tidyings never change behavior and always get their own commit (tidy: type).

When to tidy:

TimingGuidance
Tidy FirstDefault. The code you're about to change is hard to read or extend
Tidy AfterYou understand the shape of the change better after implementing
Tidy LaterBatch structural cleanup into a separate branch/MR
NeverCode works, won't change again, and is readable enough

Commit early, commit often:

  • Commit tidyings separately before the RED step (tidy: commit)
  • Commit immediately after GREEN (tests pass for the first time)
  • Commit after each REFACTOR cycle (if tests still pass)
  • Small, atomic commits make rollback and review easier
  • Never commit with failing tests

Tidying Catalog

Small, safe, structural-only changes (from Tidy First?):

TidyingWhat it does
Guard ClausesReplace nested if/match with early returns
Dead CodeDelete unreachable or unused code
Normalize SymmetriesMake similar code use consistent patterns
New Interface, Old ImplementationWrap before replacing internals
Reading OrderReorder declarations top-down
Cohesion OrderGroup related items together
Move Declaration and Initialization TogetherClose the gap between let and first use
Explaining VariablesName intermediate results
Explaining ConstantsReplace magic numbers with named constants
Explicit ParametersPass values instead of relying on ambient state
Chunk StatementsAdd blank lines between logical blocks
Extract HelperPull reusable logic into a function
One PileInline before re-extracting with better structure
Explaining CommentsAdd "why" comments where intent isn't obvious
Delete Redundant CommentsRemove comments that repeat the code

Test Types

TypeScopeSpeedCoverage
UnitSingle function< 100ms90% min
IntegrationMultiple components< 5sCritical paths
E2EFull system< 60sAll Gherkin scenarios

Naming

test_<function>_<scenario>_<expected>

Examples:
- test_encrypt_valid_key_returns_ciphertext
- test_decrypt_wrong_key_fails

Critical Rules

Crypto - Never mock. Test with real crypto:

  • Roundtrip (encrypt/decrypt, sign/verify)
  • Wrong key rejection
  • Tampered data rejection

Gherkin - Every scenario in features/ must have a test.

Coverage - 90% minimum for vauchi-core.

Forbidden

  • Writing code before tests
  • Mocking crypto operations
  • #[ignore] without tracking issue
  • Flaky/non-deterministic tests
  • Hardcoded test secrets

Mocking Strategy

ComponentApproach
CryptoReal (never mock)
NetworkMock transport
StorageIn-memory DB
TimeMockable clock

PR Checklist

  • Structural tidyings in separate tidy: commits (if any)
  • Tests written before code
  • All Gherkin scenarios covered
  • No ignored tests
  • Coverage ≥ 90%
  • Crypto has security tests

GUI Design Guidelines

Cross-platform design rules for all vauchi client interfaces — smartphones, dumb-phones, smartwatches, tablets, laptops, desktops (Windows, macOS, Linux, Android, iOS).

These rules enforce Principle 4: Simplicity serves the user — vauchi stays out of your way. All GUI contributions must follow these guidelines. For interaction-level guidelines (flows, physical device usage, navigation philosophy), see the sibling document UX Interaction Guidelines.


Problem Statement

Modal dialogs and confirmation popups interrupt user flow. Every "Are you sure?" dialog forces the user to stop, read, and click — even for routine, reversible actions like deleting a contact or hiding a field. Users develop dialog fatigue: they stop reading and click through reflexively, which defeats the safety purpose entirely.

The fix is not fewer safety nets — it's better ones. Modern interfaces (Gmail, Slack, iOS Mail) prove that act-then-undo is both safer and faster than ask-then-act. Users stay in flow, and recovery from mistakes is immediate.


The Rules

UI-01: No Dialogs for Reversible Actions

If an action can be undone, do it immediately. Show a non-blocking toast/snackbar with an Undo button (5-second window). No confirmation dialog.

Examples:

ActionWrongRight
Delete contact"Are you sure?" modalContact disappears, toast: "Contact deleted. Undo"
Hide fieldConfirmation dialogField hides, toast: "Field hidden. Undo"
Unlink device"Confirm unlink?" popupDevice unlinks, toast: "Device unlinked. Undo"

Why: Users perform reversible actions frequently. Interrupting each one trains them to click "OK" without reading — making real confirmations invisible.


UI-02: Confirm Only Irrevocable Actions

Reserve confirmation for actions that cannot be undone: permanent identity wipe, recovery phrase reset, key deletion. These are rare by design.

When confirmation is needed, use inline confirmation — expand the action area in place with a clear warning and explicit confirm/cancel buttons. Do not launch a modal overlay.

[ Delete Identity ]
     ↓ click
┌─────────────────────────────────────────┐
│ This permanently deletes your identity  │
│ and all contacts. This cannot be undone.│
│                                         │
│         [ Cancel ]  [ Delete Forever ]  │
└─────────────────────────────────────────┘

Why: Inline confirmation keeps context visible. Modal dialogs obscure what the user was looking at, forcing them to hold the context in memory.


UI-03: Inline Over Overlay

Editing, status messages, errors, and form validation appear inline — in the context where the action happened. Never launch a modal to show a single text field, a status message, or a validation error.

Use caseWrongRight
Edit contact nameModal with text inputName becomes editable in place
Validation errorAlert popup: "Name required"Red border + inline message below the field
Success message"Saved!" modal with OK buttonBrief inline indicator or toast
Error messageError dialogInline error banner at the relevant location

Why: Overlays break spatial context. Users lose their place and must re-orient after dismissing the dialog.


UI-04: Progressive Disclosure

Show only what the user needs now. Advanced options, secondary actions, and details expand in-place on demand.

  • Default views show primary content only
  • "Show more", accordions, and expandable sections reveal detail
  • Settings pages use sections, not nested dialogs
  • Help text appears on hover/focus, not in separate windows

Why: Front-loading complexity overwhelms users and obscures the primary action. Let them drill in when they choose to.


UI-05: Follow Platform Conventions

Each platform adapts these rules using native idioms. Users expect their platform's patterns — don't invent new ones.

ConceptLinux GTK4Linux Qt/QMLWindows (WinUI3)macOS (SwiftUI)Android (Compose)iOS (SwiftUI)watchOS / Wear OSKaiOS (Web)
Toast/Undoadw::ToastCustom QML popupInfoBarCustom overlaySnackbarHostCustom overlayHaptic + brief textSoft-key toast
Inline confirmIn-place gtk::BoxInline RowInline StackPanelInline VStackInline RowSwipe + confirmCrown press-holdConfirm soft-key
Inline editgtk::Entry swapTextInput swapTextBox swapTextField swapTextField swapTextField swapVoice or companionD-pad select
Navigationadw::NavigationViewStackViewNavigationViewNavigationStackNavHost / M3NavigationStackVertical page listSoft-key tabs
Loadinggtk::SpinnerBusyIndicatorProgressRingProgressViewCircularProgressProgressViewDots animationInline text

When platform convention conflicts with these rules: Platform convention wins for interaction patterns (gestures, navigation, system dialogs). These rules win for information architecture (what triggers a dialog vs. inline action).


UI-06: One Primary Action per Screen

Each screen has one primary action, visually dominant. Secondary actions are visually subordinate (smaller, muted, or behind a menu).

  • Primary action: filled/accent button, prominent placement
  • Secondary actions: outlined or text buttons, grouped away from primary
  • Destructive actions: never the primary visual element — require deliberate reach (end of list, behind a menu, or expandable section)

Why: When everything is prominent, nothing is. Users hesitate when faced with multiple equally weighted choices.


UI-07: Immediate Feedback

Every user action gets visible feedback within 100ms.

  • Tap/click: visual state change (pressed state, color shift)
  • Submit: loading indicator or optimistic UI update
  • Error: inline message at the point of failure
  • Success: brief visual confirmation (checkmark, state change) — not a dialog

If an operation takes longer than 300ms, show a loading state. If longer than 2 seconds, show a progress indicator with context ("Syncing contacts...").

Why: Delayed feedback makes users tap again, double-submit, or assume the app is broken.


UI-08: Escape Hatches Are Visible

Every state must have a clear, visible way back.

  • Back buttons are always present in navigation stacks
  • Cancel is always available during multi-step flows
  • Undo appears immediately after destructive actions (see UI-01)
  • No state requires a gesture (swipe, long-press) as the only way out — always provide a visible alternative

Why: Hidden escape hatches create anxiety. Users avoid taking actions when they're unsure they can get back.


Applying the Rules

For New Screens

Before implementing a new screen, answer:

  1. What is the one primary action on this screen? (UI-06)
  2. Are any actions truly irrevocable? List them. Everything else gets undo. (UI-01, UI-02)
  3. Can all editing happen inline? (UI-03)
  4. What is the minimum the user needs to see on first load? (UI-04)

For Existing Screens

When modifying an existing screen, check whether it violates any rule. If it does, fix the violation in a separate commit — don't mix guideline fixes with feature work.

For Code Review

Reviewers should check:

  • No new modal dialogs for reversible actions
  • Confirmation only for irrevocable actions, done inline
  • Error and status messages appear inline
  • Primary action is visually clear
  • Every action has visible feedback
  • Undo is available for destructive but reversible actions

Decision Record

These guidelines were adopted on 2026-02-24 based on:

UX Interaction Guidelines

Cross-platform interaction rules for all vauchi clients — smartphones, dumb-phones, smartwatches, tablets, laptops, desktops (Windows, macOS, Linux, Android, iOS).

These rules enforce Principle 4: Simplicity serves the user and Principle 2: Trust is earned in person. For component-level behavior (toasts, inline editing, confirmations), see the sibling document GUI Design Guidelines.


Philosophy

Four commitments shape every interaction in vauchi:

  1. Don't make me think — Every screen is self-explanatory. If a user pauses to figure out what to do, the design has failed.
  2. Keep the user informed — Always show what's happening, what just happened, and what comes next.
  3. Success paths are straight lines — Primary flows have no forks, no optional detours, no "you can also..." on the main screen.
  4. Simple views, few transitions — Each view does one thing well. Don't bounce users between screens when content can update in place.

These are not aspirations — they are constraints. Designs that violate them must be reworked.


The Rules

UX-01: Physical Device First

Prefer real-world device interaction over typing, pasting, or link sharing. Vauchi's trust model is built on physical proximity — the UX must reflect that.

ScenarioWrongRight
Contact exchange (phone ↔ phone)Copy a code, paste in appHold phones together, scan QR
Contact exchange (phone ↔ laptop)Type a code on laptopPoint phone camera at laptop screen QR
NFC-capable devicesShare a linkTap devices together
BLE proximityManual pairing flowAutomatic discovery, confirm on both devices

On devices without cameras or NFC (some desktops, dumb-phones without camera, CLI): fall back to the simplest available method (display QR for the other device to scan, or paste a one-time code). The device with more hardware capabilities drives the interaction.

On smartwatches: the watch displays a QR code; the other person's phone scans it. Watches don't drive complex flows — they companion with the paired phone for setup and editing.

Why: Physical actions are faster, harder to phish, and align with Principle 2 (trust is earned in person). Copy-paste is error-prone and trains users to move secrets through clipboards.


UX-02: Zero-Instruction Screens

If a screen needs written instructions to be understood, redesign it. Labels, icons, layout, and context must be self-evident.

  • Button labels state the action: "Add Contact", not "Submit"
  • Icons have text labels on first encounter (icon-only after familiarity)
  • Empty states explain what goes here and how to start: "No contacts yet. Scan a QR code to add one."
  • Error states say what went wrong and what to do (see UX-07)

Exceptions: Security-critical screens (identity wipe, recovery) may include a short warning. This is a safety message, not an instruction.

Why: Users scan, they don't read. Instructions are ignored or cause hesitation. Self-evident design eliminates both problems.


UX-03: Show State, Not Chrome

Screen real estate belongs to the user's data and current status. Decorative elements, branding, and navigation chrome are subordinate.

  • Contact list shows contacts, not app branding
  • Exchange screen shows the camera viewfinder or QR code — not a toolbar and a sidebar
  • Status indicators (syncing, connected, offline) are compact and contextual
  • On small screens (phones, watches), chrome compresses or hides entirely during focused tasks

Why: Users open vauchi to do something with their contacts, not to admire the app. Every pixel of chrome competes with the content they came for.


UX-04: One Happy Path

Primary flows (onboarding, exchange, editing a contact) have exactly one path forward. Alternatives, advanced options, and edge cases are hidden until needed.

  • Onboarding: one screen at a time, one action per screen, linear progression
  • Exchange: scan → confirm → done. No "choose your method" screen unless the device supports multiple methods
  • Settings: grouped by topic, expanded on demand (progressive disclosure per UI-04)

When multiple hardware methods exist (QR + NFC + BLE on a phone): auto-select the best available method. Show alternatives only on failure or explicit user request — not as a decision tree at the start.

Why: Every fork in the path is a decision point. Decisions cost attention. One clear path is faster and less stressful than three options with no clear winner.


UX-05: Progress Is Always Visible

Multi-step flows show where the user is, where they've been, and how many steps remain.

  • Step indicators: "Step 2 of 4" or a progress bar — not just the current screen
  • Completed steps show a checkmark or similar confirmation
  • The final step clearly signals completion ("Your identity is ready", not just returning to a blank home screen)
  • Long operations (sync, key generation) show a progress indicator with context: "Generating keys..." not just a spinner

On desktop and laptop: larger screens can show a step sidebar or breadcrumb trail. On phones, a compact progress bar or step counter at the top suffices.

Why: Users abandon flows when they can't tell how much is left. Visible progress reduces anxiety and drop-off.


UX-06: Transitions Are Earned

Don't navigate to a new screen when content can update in place. Screen changes are only for genuinely new contexts.

SituationWrongRight
Edit a contact fieldNavigate to edit screenField becomes editable in place (UI-03)
Show exchange resultNew "success" screenContact appears in list, toast confirms (UI-01)
Toggle a settingNavigate to sub-pageToggle updates in place
View contact detailsNew screenExpand contact card in list (phone), or side panel (desktop/laptop)

When a transition is appropriate: navigating to a genuinely different context (contact list → exchange camera), entering a multi-step flow (settings → identity wipe confirmation), or switching between top-level sections.

Desktop/laptop consideration: larger screens should use split views, side panels, and in-place expansion more aggressively. A phone might navigate to a contact detail screen; a laptop should show it alongside the list.

Why: Every screen transition resets the user's spatial context. Frequent transitions create disorientation and make the app feel heavier than it is.


UX-07: Errors Name the Fix

Every error message tells the user what happened and what they can do about it. Generic errors are forbidden.

WrongRight
"Something went wrong""Camera permission denied. Open Settings → Privacy → Camera to allow vauchi."
"Network error""Can't reach the relay. Your changes are saved and will sync when you're back online."
"Invalid QR""This QR code isn't a vauchi contact card. Ask the other person to open vauchi and show their code."
"Exchange failed""Couldn't complete the exchange. Move closer and try again."

Structure: [What happened]. [What to do about it]. — two sentences, no jargon.

Offline context: When offline, never show errors for things that will work later. Instead, show status: "Saved locally. Will sync when connected."

Why: An error without a fix is a dead end. Users can't solve "something went wrong" — they can solve "move closer and try again."


UX-08: Offline and In-Person First

QR exchange, contact viewing, and card editing work without network connectivity. The app never blocks on a network call for local operations.

  • Exchange: QR generation and scanning are fully local. BLE/NFC are local. No server round-trip needed.
  • Contact list and card details: always available from local storage
  • Editing own card: immediate, local. Sync happens when connectivity returns.
  • Sync status: shown as a subtle indicator, never as a blocking state

What requires connectivity: relay sync (pushing updates to contacts), recovery voucher upload, relay registration. These are background operations — never in the critical path of a user action.

On laptops and desktops: the same rules apply. A desktop user editing their card on a train without WiFi should have the same experience as someone on a connected phone.

Why: Vauchi's trust model is in-person. Two people standing next to each other should never see "Connecting..." when exchanging contacts. Local-first is both a UX and a security principle.


UX-09: Hardware Guides the Flow

When the device's hardware is active (camera, NFC, BLE), the hardware's output IS the primary UI. Don't cover it with chrome.

  • Camera (QR scan): viewfinder fills the available space. A subtle frame or overlay guides alignment — nothing more.
  • NFC: the system's NFC animation (iOS tap indicator, Android NFC dialog) is the feedback. The app shows a brief "Hold near the other device" prompt, then gets out of the way.
  • BLE discovery: show a compact list of nearby devices as they appear. No full-screen takeover.

On devices without hardware (desktop without camera, CLI): clearly communicate the fallback. "Display this QR code on your screen — the other person scans it with their phone."

Desktop with camera: the same camera-fills-the-space rule applies. A laptop scanning a phone's QR should show the webcam feed prominently, not buried in a small widget.

Why: Hardware feedback is immediate and real. Overlaying it with app UI creates competition for attention. Let the camera be the camera.


UX-10: Reachability Drives Layout

Primary actions sit where the user can reach them without effort. On touch devices, this is the thumb zone. On desktop and laptop, this is the main content area and keyboard shortcuts.

Smartphones and tablets:

  • Primary actions: bottom of screen, center or dominant-hand side
  • Navigation: bottom tab bar or swipe gestures
  • Destructive/rare actions: top of screen or behind a menu — require deliberate reach
  • Minimum tap targets: 44×44pt (iOS) / 48×48dp (Android)

Smartwatches:

  • One primary action per screen — crown or single tap to confirm
  • Scrollable vertical list for navigation — no side menus or tabs
  • Minimal text — icons and short labels only
  • Destructive actions require crown press-and-hold or companion app

Dumb-phones (KaiOS):

  • D-pad navigation — primary action on center/select key
  • Soft keys at bottom for contextual actions (left = back, right = options)
  • No gestures — every action reachable via key presses

Desktop and laptop (Windows, macOS, Linux):

  • Primary actions: prominent buttons in the main content area, keyboard shortcuts for frequent actions
  • Navigation: sidebar or top bar — persistent, not hidden behind a hamburger menu
  • Destructive/rare actions: behind a menu or at the end of a settings list
  • Keyboard accessibility: every action reachable without a mouse

Why: Frequent actions that are hard to reach feel heavy. Destructive actions that are easy to reach cause accidents. Layout should match action frequency and risk.


Applying the Rules

For New Flows

Before designing a new user flow, answer:

  1. Can this be done with a physical device action instead of typing? (UX-01)
  2. Does every screen explain itself without instructions? (UX-02)
  3. Is there one clear path forward? (UX-04)
  4. Does the user always know where they are in the flow? (UX-05)
  5. Can any screen transition be replaced with an in-place update? (UX-06)
  6. Does it work offline? If not, why not? (UX-08)

For Existing Flows

When modifying an existing flow, check whether it violates any rule. Fix violations in a separate commit — don't mix UX fixes with feature work (same as GUI guidelines).

For Code Review

Reviewers should check:

  • Physical interaction preferred over manual input where hardware allows
  • No screens require reading instructions to understand
  • Primary flow has one path, no unnecessary forks
  • Multi-step flows show progress
  • Screen transitions only for genuinely new contexts
  • Errors include what happened and what to do
  • Core operations work offline
  • Hardware UI (camera, NFC) not obscured by app chrome
  • Primary actions in easy-reach zones per platform

Platform Adaptation

These rules apply to all platforms, but implementation adapts:

ConceptSmartphoneDumb-phone (KaiOS)SmartwatchTabletLaptop/DesktopCLI/TUI
QR exchangeCamera viewfinderCamera viewfinderDisplay QR (no camera)Camera viewfinderWebcam or display QR for phone to scanDisplay QR in terminal (ASCII/sixel)
NFC/BLENative hardwareNFC if availableNFC tapNative hardwareUSB NFC reader (if available)Not applicable — QR fallback
ProgressTop progress barStep counterMinimal step dotsTop progress barStep sidebar or breadcrumbStep counter: [2/4]
ReachabilityThumb zone (bottom)D-pad center/selectCrown/single buttonThumb zone (bottom)Main content area + shortcutsCommand-line arguments
Inline editingTap to editSelect to editNot applicable — voice or companion appTap to editClick to edit, Enter to saveNot applicable — command-based
Split viewsFull-screen navigationFull-screen navigationSingle view onlySide panel + listSide panel + listNot applicable

Relationship to Other Documents

  • GUI Design Guidelines: Component-level behavior — how toasts, inline confirmations, and inline editing work. UX guidelines say when to use them; GUI guidelines say how they behave.
  • Principles: Philosophical foundation. UX guidelines are the practical application of Principles 2 (trust in person) and 4 (simplicity serves the user).
  • ADR-022 (Core-driven UI): UX guidelines inform what WorkflowEngine implementations should produce; ADR-022 defines the mechanism. See the internal Architecture Decision Records for details.

Decision Record

These guidelines were adopted on 2026-03-10 based on:

Diagrams

Sequence diagrams for core Vauchi flows.


Available Diagrams

DiagramDescription
Contact ExchangeIn-person QR code exchange
Device LinkingMulti-device setup
Sync UpdatesHow card updates propagate
Contact RecoverySocial recovery flow
Message DeliveryEnd-to-end message delivery flow
Crypto HierarchyKey derivation and storage hierarchy

Reading These Diagrams

All diagrams use Mermaid sequence diagram notation:

  • Solid arrows (->>) = Synchronous request
  • Dashed arrows (-->>) = Asynchronous/response
  • Notes = Context or explanation
  • Participants = Entities involved in the flow

Interaction Types

Each diagram indicates the interaction type:

IconTypeMeaning
🤝IN-PERSONPhysical proximity required
☁️REMOTEVia relay server
🔒ENCRYPTEDEnd-to-end encrypted

Contact Exchange Sequence

Interaction Type: 🤝 IN-PERSON (Proximity Required)

Two users exchange contact cards by scanning QR codes while physically present together. Proximity is verified via ultrasonic audio handshake to prevent remote/screenshot attacks.

Participants

  • Alice - User initiating exchange (displays QR)
  • Alice's Device - Mobile/Desktop running Vauchi
  • Bob - User completing exchange (scans QR)
  • Bob's Device - Mobile/Desktop running Vauchi
  • Relay - WebSocket relay server (fallback only)

Sequence Diagram

    accTitle: Contact Exchange Sequence
    accDescr: Shows how two users exchange contact cards via QR code with proximity verification
sequenceDiagram
    autonumber
    participant A as Alice
    participant AD as Alice's Device
    participant BD as Bob's Device
    participant B as Bob
    participant R as Relay

    Note over A,B: IN-PERSON: Alice and Bob meet physically

    %% QR Generation
    A->>AD: Tap "Share Contact"
    activate AD
    AD->>AD: Generate ephemeral X25519 keypair
    AD->>AD: Create exchange token (expires 5 min)
    AD->>AD: Generate audio challenge seed
    AD->>AD: Encode QR: [public_key, token, audio_challenge]
    AD->>A: Display QR code
    deactivate AD

    %% QR Scanning
    B->>BD: Open camera, scan QR
    activate BD
    BD->>BD: Decode QR data
    BD->>BD: Validate token not expired
    BD->>BD: Extract Alice's public key
    deactivate BD

    Note over AD,BD: PROXIMITY VERIFICATION (Ultrasonic Audio)

    %% Proximity Verification
    activate AD
    activate BD
    AD->>AD: Emit ultrasonic challenge (18-20 kHz)
    BD->>BD: Detect ultrasonic challenge
    BD->>BD: Sign challenge with Bob's key
    BD->>BD: Emit ultrasonic response
    AD->>AD: Detect and verify response
    AD->>AD: Confirm proximity
    BD->>BD: Confirm proximity
    deactivate AD
    deactivate BD

    Note over AD,BD: X3DH KEY AGREEMENT

    %% Key Exchange
    activate AD
    activate BD
    BD->>BD: Generate ephemeral X25519 keypair
    BD->>AD: Send: [Bob's identity key, ephemeral key]
    AD->>AD: X3DH: Derive shared secret
    AD->>BD: Send: [Alice's identity key, ephemeral key]
    BD->>BD: X3DH: Derive shared secret
    Note over AD,BD: Both have identical shared secret
    deactivate AD
    deactivate BD

    Note over AD,BD: CONTACT CARD EXCHANGE

    %% Card Exchange
    activate AD
    activate BD
    AD->>AD: Encrypt Alice's card with shared secret
    AD->>BD: Send encrypted contact card
    BD->>BD: Decrypt Alice's card
    BD->>BD: Store Alice as contact

    BD->>BD: Encrypt Bob's card with shared secret
    BD->>AD: Send encrypted contact card
    AD->>AD: Decrypt Bob's card
    AD->>AD: Store Bob as contact
    deactivate AD
    deactivate BD

    %% Success
    AD->>A: "Exchange Successful"
    BD->>B: "Exchange Successful"

    Note over A,B: Alice and Bob now have each other's contact cards

Data Exchanged

QR Code Contents

Binary format with WBEX magic bytes:

WBEX (4 bytes magic)
version (1 byte)
Alice's X25519 public key (32 bytes)
exchange token (32 bytes)
audio_challenge seed (32 bytes)
expiry timestamp (8 bytes)

Contact Card (Encrypted)

{
  "display_name": "Alice Smith",
  "fields": [
    {"type": "phone", "label": "Mobile", "value": "+1-555-1234"},
    {"type": "email", "label": "Personal", "value": "alice@example.com"}
  ],
  "signature": "Ed25519 signature of card"
}

Security Properties

PropertyMechanism
Proximity RequiredUltrasonic audio handshake (18-20 kHz)
No Man-in-the-MiddleX3DH key agreement with identity keys
Forward SecrecyEphemeral keys discarded after exchange
Replay PreventionOne-time token, 5-minute expiry
Card AuthenticityEd25519 signature on contact card

Failure Scenarios

Proximity Verification Fails

    accTitle: Contact Exchange Sequence (2)
    accDescr: Shows how two users exchange contact cards via QR code with proximity verification
sequenceDiagram
    participant AD as Alice's Device
    participant BD as Bob's Device

    Note over AD,BD: Bob scanning from screenshot (remote)
    AD->>AD: Emit ultrasonic challenge
    BD--xBD: No ultrasonic detected (too far)
    BD->>BD: Proximity verification FAILED
    BD->>BD: "Proximity verification failed"
    Note over AD,BD: Exchange blocked - no cards exchanged

QR Code Expired

    accTitle: Contact Exchange Sequence (3)
    accDescr: Shows how two users exchange contact cards via QR code with proximity verification
sequenceDiagram
    participant AD as Alice's Device
    participant BD as Bob's Device

    Note over AD,BD: QR scanned after 5 minutes
    BD->>BD: Decode QR, check expiry
    BD->>BD: Token expired
    BD->>BD: "QR code expired"
    Note over AD,BD: Alice must generate new QR

Platform Variations

PlatformProximity MethodFallback
iOS ↔ iOSUltrasonic audioManual confirmation
Android ↔ AndroidUltrasonic audioManual confirmation
iOS ↔ AndroidUltrasonic audioManual confirmation
Desktop ↔ MobileN/A (no mic)Manual confirmation required
Desktop ↔ DesktopN/AManual confirmation required

Sync Updates Sequence

Interaction Type: ☁️ REMOTE (Via Relay)

Contact card changes propagate automatically to contacts via the relay network. All data is end-to-end encrypted - relays only see encrypted blobs.

Participants

  • Alice - User updating their contact card
  • Alice's Device - Device where change is made
  • Alice's Other Device - Another linked device
  • Relay - WebSocket relay server
  • Bob's Device - Contact receiving the update
  • Bob - Contact who will see the update

Sequence Diagram

    accTitle: Sync Updates Sequence
    accDescr: Shows how contact card changes propagate via the relay network
sequenceDiagram
    autonumber
    participant A as Alice
    participant AD1 as Alice Device 1
    participant AD2 as Alice Device 2
    participant R as Relay
    participant BD as Bob's Device
    participant B as Bob

    Note over A,B: REMOTE: All communication via relay

    %% Alice makes a change
    A->>AD1: Update phone: "555-1111" → "555-2222"
    activate AD1
    AD1->>AD1: Update local contact card
    AD1->>AD1: Increment card version
    AD1->>AD1: Sign card with identity key
    deactivate AD1

    Note over AD1,R: PUSH TO OWN DEVICES

    %% Sync to own devices
    activate AD1
    AD1->>AD1: Encrypt update for Device 2
    AD1->>R: Push encrypted update (for AD2)
    R-->>AD2: Forward encrypted update
    activate AD2
    AD2->>AD2: Decrypt update
    AD2->>AD2: Apply change locally
    AD2->>AD2: Verify signature
    deactivate AD2
    deactivate AD1

    Note over AD1,R: PUSH TO CONTACTS

    %% Determine who can see the field
    activate AD1
    AD1->>AD1: Check visibility rules
    Note right of AD1: Phone visible to:<br/>- Bob<br/>- Carol<br/>- Dave (restricted)
    deactivate AD1

    %% Sync to Bob (online)
    activate AD1
    AD1->>AD1: Encrypt delta for Bob (shared key)
    Note right of AD1: Delta: {field: "phone", value: "555-2222"}
    AD1->>R: Push encrypted delta (for Bob)

    alt Bob is Online
        R-->>BD: Forward encrypted delta
        activate BD
        BD->>BD: Decrypt with Alice-Bob shared key
        BD->>BD: Verify signature
        BD->>BD: Update Alice's contact card locally
        BD->>B: Notification: "Alice updated contact info"
        deactivate BD
    else Bob is Offline
        R->>R: Queue message for Bob
        Note right of R: Queued until Bob connects
    end
    deactivate AD1

    %% Bob comes online later
    opt Bob was Offline
        BD->>R: Connect to relay
        R-->>BD: Deliver queued messages
        activate BD
        BD->>BD: Process Alice's update
        BD->>BD: Update local contact card
        BD->>B: "Alice updated contact info"
        deactivate BD
    end

    Note over A,B: Bob now sees Alice's new phone number

Visibility-Aware Sync

    accTitle: Sync Updates Sequence (2)
    accDescr: Shows how contact card changes propagate via the relay network
sequenceDiagram
    participant AD as Alice's Device
    participant R as Relay
    participant BD as Bob's Device
    participant CD as Carol's Device
    participant DD as Dave's Device

    Note over AD: Alice updates phone number

    AD->>AD: Check visibility rules

    Note right of AD: Visibility:<br/>Bob: [name, phone, email]<br/>Carol: [name, email]<br/>Dave: [name only]

    par Send to contacts based on visibility
        AD->>R: To Bob: {phone: "555-2222"}
        R-->>BD: Forward (Bob can see phone)
    and
        Note over AD,CD: Carol cannot see phone field
        AD--xCD: No update sent (field not visible)
    and
        Note over AD,DD: Dave cannot see phone field
        AD--xDD: No update sent (field not visible)
    end

Offline Queue Handling

    accTitle: Sync Updates Sequence (3)
    accDescr: Shows how contact card changes propagate via the relay network
sequenceDiagram
    participant AD as Alice's Device
    participant R as Relay
    participant BD as Bob's Device

    Note over BD: Bob is offline for 3 days

    AD->>AD: Update 1: Change phone
    AD->>R: Queue for Bob
    R->>R: Store encrypted message

    AD->>AD: Update 2: Change email
    AD->>R: Queue for Bob
    R->>R: Store encrypted message

    AD->>AD: Update 3: Change phone again
    AD->>R: Queue for Bob
    R->>R: Store encrypted message

    BD->>R: Connect (back online)
    R-->>BD: Deliver update 1
    R-->>BD: Deliver update 2
    R-->>BD: Deliver update 3

    BD->>BD: Apply all updates in order
    Note over BD: Bob sees latest values after applying all updates

Data Exchanged

Update Delta (Encrypted)

{
  "type": "card_update",
  "from": "Alice's public key",
  "version": 6,
  "timestamp": "2026-01-21T12:00:00Z",
  "changes": [
    {
      "op": "update",
      "path": "/fields/phone",
      "value": "555-2222"
    }
  ],
  "signature": "Ed25519 signature"
}

Security Properties

PropertyMechanism
End-to-End EncryptionUpdates encrypted with per-contact shared keys
Relay BlindnessRelay sees only encrypted blobs, no metadata
Update AuthenticityEd25519 signature on all updates
Replay PreventionMonotonic version numbers + timestamps
Visibility EnforcementOnly visible fields sent to each contact

Message Delivery Flow

Interaction Type: 🌐 REMOTE (Via Relay)

End-to-end message delivery from card update to acknowledgment.

Participants

  • Alice - User sending card update
  • Alice's Device - Source device
  • Relay - WebSocket relay server
  • Bob's Device - Recipient device
  • Bob - Contact receiving update

Message Sizes & Frequency

Message TypePayloadPadded SizeFrequency
Card delta50-200 B256 B1-5/month per user
Full card500 B - 2 KB1-4 KBInitial exchange only
Acknowledgment32-64 B256 BPer received message
Device sync100-500 B256 B - 1 KBReal-time when active

Complete Delivery Flow

    accTitle: Message Delivery Sequence
    accDescr: Shows end-to-end encrypted message delivery from card update to relay acknowledgment
sequenceDiagram
    autonumber
    participant A as Alice
    participant AD as Alice's Device 📱
    participant R as Relay 🖥️
    participant BD as Bob's Device 📱
    participant B as Bob

    Note over A,B: 🌐 REMOTE: All via relay

    %% Alice edits card
    A->>AD: Edit phone: "555-1111" → "555-2222"
    activate AD
    AD->>AD: Update local card
    AD->>AD: Create CardDelta
    Note right of AD: Delta: ~100 bytes<br/>{"field":"phone","value":"555-2222"}
    deactivate AD

    %% Encryption
    activate AD
    AD->>AD: Check visibility: Bob can see phone? ✓
    AD->>AD: Ratchet send chain forward
    Note right of AD: Chain gen 42 → 43<br/>Message key derived
    AD->>AD: Encrypt delta (XChaCha20-Poly1305)
    AD->>AD: Pad to 256 bytes (bucket)
    AD->>AD: Generate CEK, sign payload
    Note right of AD: Final: ~256 bytes<br/>(v0x02 format)
    deactivate AD

    %% Send to relay
    activate AD
    AD->>R: EncryptedUpdate(recipient=Bob, blob=...)
    Note over AD,R: WebSocket frame:<br/>4-byte length + JSON envelope
    deactivate AD

    %% Relay processing
    activate R
    R->>R: Validate recipient_id format
    R->>R: Check quota (blobs < 1000, storage < 50MB)
    R->>R: Store blob indexed by recipient_id
    R-->>AD: Acknowledgment(status=Stored)
    Note right of R: Blob stored with 30-day TTL
    deactivate R

    %% Delivery
    alt Bob is Online (Connected to Relay)
        activate R
        R-->>BD: Forward EncryptedUpdate
        deactivate R

        activate BD
        BD->>BD: Receive encrypted blob
        BD->>BD: Resolve anonymous sender ID
        Note right of BD: Try each contact's<br/>shared key against<br/>anonymous_id
        BD->>BD: Found: Alice
        BD->>BD: Derive message key (chain gen 43)
        BD->>BD: Decrypt payload
        BD->>BD: Remove padding
        BD->>BD: Verify Ed25519 signature
        BD->>BD: Update Alice's card locally
        deactivate BD

        BD->>B: "Alice updated contact info"

        %% Acknowledgment chain
        activate BD
        BD->>R: Acknowledgment(status=ReceivedByRecipient)
        deactivate BD

        activate R
        R-->>AD: Forward Acknowledgment
        Note over R,AD: Sender notified if<br/>suppress_presence=false
        deactivate R

        AD->>AD: Mark update as delivered

    else Bob is Offline
        Note over R,BD: Blob queued for later

        opt Bob comes online later
            BD->>R: Connect (Handshake)
            activate R
            R-->>BD: Deliver queued blobs
            deactivate R

            activate BD
            BD->>BD: Process all pending updates
            BD->>BD: Updates applied in order
            deactivate BD
        end
    end

    Note over A,B: Bob now sees Alice's new phone

Double Ratchet Message Flow

    accTitle: Message Delivery Sequence (2)
    accDescr: Shows end-to-end encrypted message delivery from card update to relay acknowledgment
sequenceDiagram
    participant AD as Alice's Ratchet
    participant BD as Bob's Ratchet

    Note over AD,BD: Initial state after X3DH

    rect rgb(240, 248, 255)
        Note over AD: SEND (Message 1)
        AD->>AD: Ratchet send chain: gen 0 → 1
        AD->>AD: Derive message key (gen 0)
        AD->>AD: Encrypt with message key
        AD->>AD: Delete message key
        AD->>BD: [DH_pub, gen=0, idx=0] + ciphertext
    end

    rect rgb(240, 255, 240)
        Note over BD: RECEIVE (Message 1)
        BD->>BD: Verify DH generation matches
        BD->>BD: Ratchet receive chain: gen 0 → 1
        BD->>BD: Derive message key (gen 0)
        BD->>BD: Decrypt
        BD->>BD: Delete message key
    end

    rect rgb(255, 248, 240)
        Note over BD: SEND REPLY (triggers DH ratchet)
        BD->>BD: Generate new ephemeral DH keypair
        BD->>BD: DH ratchet: compute new root key
        BD->>BD: Create new send chain
        BD->>BD: Encrypt with new chain's key
        BD->>AD: [NEW_DH_pub, gen=1, idx=0] + ciphertext
    end

    rect rgb(248, 240, 255)
        Note over AD: RECEIVE (triggers DH ratchet)
        AD->>AD: Detect new DH public key
        AD->>AD: DH ratchet: compute matching root key
        AD->>AD: Create new receive chain
        AD->>AD: Decrypt with new chain's key
    end

Out-of-Order Message Handling

    accTitle: Message Delivery Sequence (3)
    accDescr: Shows end-to-end encrypted message delivery from card update to relay acknowledgment
sequenceDiagram
    participant AD as Alice's Device
    participant R as Relay
    participant BD as Bob's Device

    Note over AD,BD: Messages may arrive out of order

    AD->>R: Message 1 (gen=0, idx=0)
    AD->>R: Message 2 (gen=0, idx=1)
    AD->>R: Message 3 (gen=0, idx=2)

    Note over R: Network delays

    R-->>BD: Message 3 arrives first
    activate BD
    BD->>BD: Expected idx=0, got idx=2
    BD->>BD: Skip chain to idx=2
    BD->>BD: Store skipped keys: [idx=0, idx=1]
    BD->>BD: Decrypt message 3
    deactivate BD

    R-->>BD: Message 1 arrives
    activate BD
    BD->>BD: Lookup skipped key for idx=0
    BD->>BD: Found! Decrypt message 1
    BD->>BD: Delete skipped key
    deactivate BD

    R-->>BD: Message 2 arrives
    activate BD
    BD->>BD: Lookup skipped key for idx=1
    BD->>BD: Found! Decrypt message 2
    BD->>BD: Delete skipped key
    deactivate BD

    Note over BD: All messages processed,<br/>skipped keys cleaned up

Relay Acknowledgment States

    accTitle: Message Delivery Sequence (4)
    accDescr: Shows end-to-end encrypted message delivery from card update to relay acknowledgment
stateDiagram-v2
    direction LR

    [*] --> Sent: Message created
    Sent --> Stored: Relay confirms storage
    Stored --> Delivered: Recipient connected
    Delivered --> ReceivedByRecipient: Recipient acks

    Stored --> Failed: Quota exceeded
    Delivered --> Failed: Decrypt error

    ReceivedByRecipient --> [*]
    Failed --> [*]

Wire Protocol

Envelope Format

┌─────────────────────────────────────────────────────────────┐
│                    MESSAGE ENVELOPE                          │
├─────────────────────────────────────────────────────────────┤
│  4 bytes: Length (big-endian)                               │
│  JSON payload:                                               │
│  {                                                           │
│    "version": 1,                                            │
│    "message_id": "uuid",                                    │
│    "timestamp": unix_secs,                                  │
│    "payload": { ... }                                       │
│  }                                                           │
└─────────────────────────────────────────────────────────────┘

EncryptedUpdate Payload

{
  "type": "EncryptedUpdate",
  "sender_id": "anonymous_id (rotating hourly)",
  "recipient_id": "Bob's public key hash",
  "blob": "base64(encrypted_delta)"
}

Acknowledgment Payload

{
  "type": "Acknowledgment",
  "message_id": "original message uuid",
  "status": "Stored|Delivered|ReceivedByRecipient|Failed",
  "error": null
}

Timing Estimates

PhaseDurationNotes
Encryption + padding1-5 msXChaCha20-Poly1305 is fast
Network latency50-200 msDepends on relay location
Relay storage1-10 msSQLite insert
Forward to recipient50-200 msIf online
Decryption + verify1-5 ms
Total (online)100-400 msEnd-to-end
Total (offline)< 30 daysUntil recipient connects

Device Linking Sequence

Interaction Type: 🤝 IN-PERSON (Proximity Required)

User links a new device to their existing identity. The new device receives the master seed and syncs all data. A confirmation code and proximity verification prevent unauthorized remote linking.

Participants

  • User - Person owning both devices
  • Device A (Primary) - Existing device with identity
  • Device B (New) - New device to be linked

Sequence Diagram

    accTitle: Device Linking Sequence
    accDescr: Shows how a user links a new device to their existing identity with proximity verification
sequenceDiagram
    autonumber
    participant U as User
    participant DA as Device A (Primary)
    participant DB as Device B (New)

    Note over U: IN-PERSON: User has both devices physically present

    %% Generate Link QR
    U->>DA: Settings > Devices > Link New Device
    activate DA
    DA->>DA: Generate ephemeral link_key (32 bytes)
    DA->>DA: Sign QR fields with identity Ed25519 key
    DA->>DA: Create QR: WBDL | version | identity_pubkey | link_key | timestamp | signature
    DA->>U: Display QR code (expires in 5 minutes)
    deactivate DA

    %% New Device Scans
    U->>DB: "Link to Existing Identity"
    activate DB
    DB->>DB: Scan QR code from Device A
    DB->>DB: Validate WBDL magic, version, signature, expiry
    DB->>DB: Create DeviceLinkRequest (device_name, random nonce, timestamp)
    DB->>DB: Encrypt request with link_key (ChaCha20-Poly1305)
    DB->>DA: Send encrypted request
    deactivate DB

    Note over DA,DB: CONFIRMATION & PROXIMITY VERIFICATION

    %% Confirmation Code
    activate DA
    DA->>DA: Decrypt request using link_key
    DA->>DA: Derive confirmation code: HMAC-SHA256(link_key, nonce) → XXX-XXX
    DA->>U: Show: "Link device 'Device B'? Code: XXX-XXX"
    activate DB
    DB->>DB: Derive same confirmation code from link_key + nonce
    DB->>U: Show: "Confirmation code: XXX-XXX"
    U->>U: Verify codes match on both screens
    U->>DA: Confirm link
    DA->>DA: Set proximity verified
    deactivate DB

    Note over DA,DB: IDENTITY TRANSFER

    %% Build and send response
    DA->>DA: Derive new device keys from master_seed + device_index
    DA->>DA: Add Device B to registry, re-sign
    DA->>DA: Build response: master_seed + display_name + device_index + registry + sync_payload
    DA->>DA: Encrypt response with link_key (ChaCha20-Poly1305)
    DA->>DB: Send encrypted response
    deactivate DA

    %% New device processes
    activate DB
    DB->>DB: Decrypt response
    DB->>DB: Extract master_seed, registry, sync_payload
    DB->>DB: Derive own device keys from master_seed + device_index
    DB->>DB: Store identity locally
    DB->>DB: Apply sync payload (contacts, card)
    deactivate DB

    %% Success
    DA->>U: "Device B linked successfully"
    DB->>U: "Welcome back, [Your Name]"

    Note over DA,DB: Both devices now share the same identity

Data Exchanged

Binary format with WBDL magic bytes, base64-encoded for QR:

WBDL              (4 bytes magic)
version           (1 byte, currently 1)
identity_pubkey   (32 bytes, Ed25519 public key)
link_key          (32 bytes, random ephemeral key)
timestamp         (8 bytes, big-endian u64 unix seconds)
signature         (64 bytes, Ed25519 over all preceding fields)
─────────────────
Total: 141 bytes  (before base64 encoding)

The QR expires after 300 seconds (5 minutes). Signature is verified by the new device using the embedded identity public key.

Confirmation Code

Derived independently by both devices:

HMAC-SHA256(link_key, request_nonce)
  → first 4 bytes as big-endian u32
  → modulo 1,000,000
  → formatted as XXX-XXX

Both devices display the same code. User verifies they match.

Proximity Challenge

For external proximity verification (NFC, ultrasonic, etc.):

HKDF(ikm=link_key, info="vauchi-device-link-proximity-v1", len=16)
  → 16-byte challenge

Both devices derive the same challenge from the shared link key.

DeviceLinkRequest (New → Existing)

Encrypted with ChaCha20-Poly1305 using link_key:

device_name_len   (4 bytes, little-endian u32)
device_name       (variable, UTF-8)
nonce             (32 bytes, random)
timestamp         (8 bytes, little-endian u64)

DeviceLinkResponse (Existing → New)

Encrypted with ChaCha20-Poly1305 using link_key:

master_seed       (32 bytes, zeroized after use)
display_name_len  (4 bytes, little-endian u32)
display_name      (variable, UTF-8)
device_index      (4 bytes, little-endian u32)
registry_json_len (4 bytes, little-endian u32)
registry_json     (variable, signed DeviceRegistry)
sync_payload_len  (4 bytes, little-endian u32)
sync_payload_json (variable, contacts + card)

Security Properties

PropertyMechanism
Seed EncryptionChaCha20-Poly1305 with ephemeral link_key
QR AuthenticationEd25519 signature over QR fields
Confirmation CodeHMAC-SHA256(link_key, nonce) displayed on both devices
Proximity VerificationHKDF-derived 16-byte challenge; enforced before confirm
Replay PreventionRandom 32-byte nonce in each request
Token ExpiryQR expires after 5 minutes
Registry IntegrityEd25519 signature over version + device list
Memory SafetyMaster seed zeroized on Drop
Device LimitMaximum 10 devices per identity

Numeric Code Fallback (No Camera)

    accTitle: Device Linking Sequence (2)
    accDescr: Shows how a user links a new device to their existing identity with proximity verification
sequenceDiagram
    participant U as User
    participant DA as Device A
    participant DB as Device B (Desktop/CLI)

    Note over U: Device B has no camera

    U->>DA: Generate link code
    DA->>U: Show QR code + data string

    U->>DB: "Link to Existing Identity"
    U->>DB: Paste data string from Device A
    DB->>DB: Parse WBDL data, validate signature + expiry

    Note over DA,DB: Same confirmation code flow as above
    DA->>U: "Code: XXX-XXX"
    DB->>U: "Code: XXX-XXX"
    U->>DA: Confirm

    DA->>DB: Encrypted identity bundle
    DB->>DB: Complete linking

Revoking a Device

    accTitle: Device Linking Sequence (3)
    accDescr: Shows how a user links a new device to their existing identity with proximity verification
sequenceDiagram
    participant U as User
    participant DA as Device A
    participant DB as Device B
    participant R as Relay

    Note over U: REMOTE: Can revoke from any device

    U->>DA: Settings > Devices > Revoke Device B
    U->>DA: Confirm revocation

    activate DA
    DA->>DA: Mark Device B as revoked in registry
    DA->>DA: Re-sign registry with identity key
    DA->>DA: Increment registry version
    DA->>R: Push updated registry (encrypted)
    R-->>DB: Forward revocation
    DB->>DB: Receive revocation notice
    DB->>DB: Wipe all identity data
    DB->>DB: Return to welcome screen
    deactivate DA

    DA->>U: "Device B revoked"
    DB->>U: "This device has been unlinked"

Platform Implementation Status

PlatformStatusNotes
Core APICompleteFull protocol with tests
CLIComplete7 commands: list, link, join, complete, finish, revoke, info
Desktop (native)CompleteNative UI (SwiftUI/GTK/Qt) with QR display, confirmation overlay
TUICompleteratatui UI with QR overlay, vim-style navigation
iOSPlannedAwaiting mobile bindings
AndroidPlannedAwaiting mobile bindings

Contact Recovery Sequence

Interaction Type: 🤝 + ☁️ MIXED (In-Person Vouching + Remote Distribution)

When a user loses all devices, they can recover their contact relationships through social vouching. Existing contacts vouch for the user in-person, and the recovery proof is distributed remotely via relay.

Participants

  • Alice - User who lost their device
  • Alice's New Device - Fresh install, new identity
  • Bob, Charlie, Betty - Alice's contacts who will vouch
  • John, David - Alice's contacts who will receive recovery proof
  • Relay - WebSocket relay server

Overview

┌────────────────────────────────────────────────────────────────────────────┐
│                         RECOVERY PROCESS                                   │
├────────────────────────────────────────────────────────────────────────────┤
│                                                                            │
│  PHASE 1: Vouching (In-Person)                                             │
│  ┌─────────┐    ┌─────────┐    ┌─────────┐                                 │
│  │   Bob   │    │ Charlie │    │  Betty  │                                 │
│  │  Vouch  │    │  Vouch  │    │  Vouch  │                                 │
│  └────┬────┘    └────┬────┘    └────┬────┘                                 │
│       │              │              │                                      │
│       └──────────────┼──────────────┘                                      │
│                      ▼                                                     │
│              ┌──────────────┐                                              │
│              │ Alice (new)  │  Threshold: 3 vouchers                       │
│              └──────┬───────┘                                              │
│                     │                                                      │
│  PHASE 2: Distribution (Remote)                                            │
│                     ▼                                                      │
│              ┌──────────────┐                                              │
│              │    Relay     │  Stores proof under hash(pk_old)             │
│              └──────┬───────┘                                              │
│                     │                                                      │
│       ┌─────────────┼─────────────┐                                        │
│       ▼             ▼             ▼                                        │
│  ┌─────────┐   ┌─────────┐   ┌─────────┐                                   │
│  │  John   │   │  David  │   │  Others │  Discover via relay query         │
│  │ Accept  │   │ Verify  │   │         │                                   │
│  └─────────┘   └─────────┘   └─────────┘                                   │
│                                                                            │
└────────────────────────────────────────────────────────────────────────────┘

Phase 1: In-Person Vouching

    accTitle: Contact Recovery Sequence
    accDescr: Shows how a user recovers contacts through social vouching after losing all devices
sequenceDiagram
    autonumber
    participant A as Alice (Lost Device)
    participant AN as Alice New Device
    participant BB as Bob's Device
    participant B as Bob

    Note over A,B: IN-PERSON: Alice meets Bob physically

    %% Alice creates new identity
    A->>AN: Install Vauchi on new device
    AN->>AN: Create new identity (pk_new)
    A->>AN: "I had identity pk_old"
    AN->>AN: Store recovery claim: pk_old → pk_new

    %% Generate recovery QR
    A->>AN: Generate recovery QR
    activate AN
    AN->>AN: Create recovery claim QR
    Note right of AN: QR contains:<br/>- type: recovery_claim<br/>- old_pk: pk_old<br/>- new_pk: pk_new<br/>- timestamp
    AN->>A: Display recovery QR
    deactivate AN

    %% Bob scans and vouches
    B->>BB: Scan Alice's recovery QR
    activate BB
    BB->>BB: Decode recovery claim
    BB->>BB: Lookup pk_old in contacts
    BB->>BB: Found: "Alice" with pk_old
    BB->>B: "Alice claims device loss"
    BB->>B: Show Alice's stored name & photo
    deactivate BB

    Note over BB,B: Bob verifies Alice is physically present

    B->>BB: "Yes, this is Alice, I confirm"
    activate BB
    BB->>BB: Create voucher
    Note right of BB: Voucher:<br/>- old_pk<br/>- new_pk<br/>- voucher_pk (Bob)<br/>- timestamp<br/>- Ed25519 signature
    BB->>AN: Send voucher to Alice
    deactivate BB

    AN->>AN: Store Bob's voucher (1 of 3)
    AN->>A: "Bob vouched for you (1/3)"

    Note over A,B: Alice now has 1 voucher, needs 2 more

Collecting Multiple Vouchers

    accTitle: Contact Recovery Sequence (2)
    accDescr: Shows how a user recovers contacts through social vouching after losing all devices
sequenceDiagram
    participant AN as Alice New Device
    participant CB as Charlie's Device
    participant BB as Betty's Device

    Note over AN,BB: IN-PERSON: Alice meets more contacts

    %% Charlie vouches
    rect rgb(240, 248, 255)
        Note over AN,CB: Alice meets Charlie
        AN->>CB: Show recovery QR
        CB->>CB: Verify pk_old is contact "Alice"
        CB->>AN: Send voucher
        AN->>AN: Store Charlie's voucher (2 of 3)
    end

    %% Betty vouches
    rect rgb(240, 255, 240)
        Note over AN,BB: Alice meets Betty
        AN->>BB: Show recovery QR
        BB->>BB: Verify pk_old is contact "Alice"
        BB->>AN: Send voucher
        AN->>AN: Store Betty's voucher (3 of 3)
    end

    Note over AN: THRESHOLD MET: 3 vouchers collected

    AN->>AN: Create recovery proof
    Note right of AN: Recovery Proof:<br/>- old_pk<br/>- new_pk<br/>- threshold: 3<br/>- vouchers: [Bob, Charlie, Betty]

Phase 2: Remote Distribution

    accTitle: Contact Recovery Sequence (3)
    accDescr: Shows how a user recovers contacts through social vouching after losing all devices
sequenceDiagram
    autonumber
    participant AN as Alice New Device
    participant R as Relay
    participant JD as John's Device
    participant DD as David's Device

    Note over AN,DD: REMOTE: Distribution via relay

    %% Upload proof
    AN->>R: Upload recovery proof
    R->>R: Store under key: hash(pk_old)
    R-->>AN: Stored

    Note over R: Proof stored for 90 days

    %% John discovers proof
    JD->>R: Batch query for contact recovery proofs
    Note right of JD: Query: [hash(pk1), hash(pk2), hash(pk_old), ...]
    R-->>JD: Found proof for hash(pk_old)

    activate JD
    JD->>JD: Decode recovery proof
    JD->>JD: Verify: pk_old is contact "Alice"
    JD->>JD: Check vouchers for mutual contacts

    alt Has Mutual Contacts (Bob, Charlie)
        JD->>JD: Bob is my contact
        JD->>JD: Charlie is my contact
        JD->>JD: 2 mutual vouchers ≥ threshold
        JD->>JD: "High confidence recovery"
    else No Mutual Contacts
        JD->>JD: No vouchers are my contacts
        JD->>JD: "Cannot verify - meet Alice in person"
    end
    deactivate JD

    %% David (isolated contact) case
    DD->>R: Query for recovery proofs
    R-->>DD: Found proof for Alice

    activate DD
    DD->>DD: Check vouchers: Bob, Charlie, Betty
    DD->>DD: None are David's contacts
    DD->>DD: "Warning: Unknown vouchers"
    DD->>DD: Options: Meet in person / Verify another way / Accept anyway
    deactivate DD

Data Structures

Recovery Claim QR

{
  "type": "recovery_claim",
  "old_pk": "Ed25519 public key (lost)",
  "new_pk": "Ed25519 public key (new)",
  "timestamp": "2026-01-21T10:00:00Z"
}

Voucher

{
  "old_pk": "Alice's old public key",
  "new_pk": "Alice's new public key",
  "voucher_pk": "Bob's public key",
  "timestamp": "2026-01-21T10:05:00Z",
  "signature": "Ed25519 signature of above fields"
}

Recovery Proof

{
  "old_pk": "Alice's old public key",
  "new_pk": "Alice's new public key",
  "threshold": 3,
  "vouchers": [
    { /* Bob's voucher */ },
    { /* Charlie's voucher */ },
    { /* Betty's voucher */ }
  ],
  "expires": "2026-04-21T10:00:00Z"
}

Security Properties

PropertyMechanism
In-Person VouchingVouchers must physically verify the person
Threshold SecurityRequires N vouchers (configurable, default 3)
Mutual Contact VerificationRecipients verify via contacts they trust
Relay PrivacyRelay stores proof under hash, learns nothing
Replay PreventionTimestamps, signatures, 90-day expiry
Attack DetectionConflicting claims trigger warnings

Crypto Key Hierarchy

Visual documentation of Vauchi's cryptographic key hierarchy and derivation paths.

Master Hierarchy

    accTitle: Cryptographic Key Hierarchy
    accDescr: Shows the derivation paths from master seed to all encryption and signing keys
flowchart TB
    subgraph Identity["Identity Creation"]
        SEED["Master Seed<br/>(256-bit, CSPRNG)"]
    end

    subgraph Signing["Signing Keys"]
        SIGN_SK["Identity Signing Key<br/>(Ed25519 secret)"]
        SIGN_PK["Identity Public Key<br/>(Ed25519 public)"]
    end

    subgraph Exchange["Exchange Keys"]
        EXCH_SK["Exchange Secret Key<br/>(X25519)"]
        EXCH_PK["Exchange Public Key<br/>(X25519)"]
    end

    subgraph Shredding["Shredding Hierarchy"]
        SMK["SMK<br/>(Shredding Master Key)"]
        SEK["SEK<br/>(Storage Encryption Key)"]
        FKEK["FKEK<br/>(File Key Encryption Key)"]
    end

    subgraph PerContact["Per-Contact Keys"]
        CEK1["CEK (Contact 1)<br/>random 256-bit"]
        CEK2["CEK (Contact 2)<br/>random 256-bit"]
        CEK3["CEK (Contact N)<br/>random 256-bit"]
    end

    SEED -->|"raw seed<br/>(Ed25519 requirement)"| SIGN_SK
    SIGN_SK --> SIGN_PK

    SEED -->|"HKDF<br/>info='Vauchi_Exchange_Seed'"| EXCH_SK
    EXCH_SK --> EXCH_PK

    SEED -->|"HKDF<br/>info='Vauchi_Shred_Key'"| SMK
    SMK -->|"HKDF<br/>info='Vauchi_Storage_Key'"| SEK
    SMK -->|"HKDF<br/>info='Vauchi_FileKey_Key'"| FKEK

    SEK -.->|"encrypts"| CEK1
    SEK -.->|"encrypts"| CEK2
    SEK -.->|"encrypts"| CEK3

    style SEED fill:#ff9,stroke:#333
    style SMK fill:#f99,stroke:#333
    style SEK fill:#f99,stroke:#333
    style FKEK fill:#f99,stroke:#333
    style CEK1 fill:#9f9,stroke:#333
    style CEK2 fill:#9f9,stroke:#333
    style CEK3 fill:#9f9,stroke:#333

Key Derivation Details

HKDF Convention

All HKDF derivations use standard RFC 5869 (documented as "DP-5"):

HKDF-SHA256:
  - salt: None (zeros per RFC 5869 §2.2)
  - ikm: master_seed (32 bytes, high-entropy input)
  - info: domain string (e.g., "Vauchi_Exchange_Seed_v2")
  - output: 32 bytes

This follows standard HKDF convention: high-entropy seed as IKM, no salt needed.

Key Sizes

KeySizeAlgorithm
Master Seed256 bitsCSPRNG
Identity Signing32 + 64 bytesEd25519 (seed + keypair)
Exchange32 bytesX25519
SMK256 bitsHKDF-SHA256
SEK256 bitsHKDF-SHA256
FKEK256 bitsHKDF-SHA256
CEK256 bitsCSPRNG

Double Ratchet Key Hierarchy

    accTitle: Cryptographic Key Hierarchy (2)
    accDescr: Shows the derivation paths from master seed to all encryption and signing keys
flowchart TB
    subgraph Initial["Initial Key Agreement (X3DH)"]
        X3DH["X3DH Shared Secret<br/>(32 bytes)"]
    end

    subgraph RootChain["Root Chain"]
        RK0["Root Key 0"]
        RK1["Root Key 1"]
        RK2["Root Key 2"]
        RKN["Root Key N"]
    end

    subgraph DHRatchet["DH Ratchet Steps"]
        DH1["DH(our_secret × their_pub)"]
        DH2["DH(our_secret × their_pub)"]
    end

    subgraph SendChain["Send Chain"]
        SCK0["Send Chain Key 0"]
        SCK1["Send Chain Key 1"]
        MK0["Message Key 0"]
        MK1["Message Key 1"]
    end

    subgraph RecvChain["Receive Chain"]
        RCK0["Recv Chain Key 0"]
        RCK1["Recv Chain Key 1"]
        RMK0["Message Key 0"]
        RMK1["Message Key 1"]
    end

    X3DH -->|"HKDF<br/>init"| RK0

    RK0 --> DH1
    DH1 -->|"HKDF"| RK1
    DH1 -->|"HKDF"| SCK0

    RK1 --> DH2
    DH2 -->|"HKDF"| RK2
    DH2 -->|"HKDF"| RCK0

    RK2 --> RKN

    SCK0 -->|"HKDF<br/>MESSAGE_KEY_INFO"| MK0
    SCK0 -->|"HKDF<br/>CHAIN_KEY_INFO"| SCK1
    SCK1 -->|"HKDF"| MK1

    RCK0 -->|"HKDF<br/>MESSAGE_KEY_INFO"| RMK0
    RCK0 -->|"HKDF<br/>CHAIN_KEY_INFO"| RCK1
    RCK1 -->|"HKDF"| RMK1

    style X3DH fill:#ff9,stroke:#333
    style RK0 fill:#f9f,stroke:#333
    style RK1 fill:#f9f,stroke:#333
    style RK2 fill:#f9f,stroke:#333
    style MK0 fill:#9ff,stroke:#333
    style MK1 fill:#9ff,stroke:#333
    style RMK0 fill:#9ff,stroke:#333
    style RMK1 fill:#9ff,stroke:#333

Device Key Derivation

    accTitle: Cryptographic Key Hierarchy (3)
    accDescr: Shows the derivation paths from master seed to all encryption and signing keys
flowchart TB
    subgraph Master["Master Identity"]
        SEED["Master Seed"]
        IDX0["Device Index 0<br/>(Primary)"]
        IDX1["Device Index 1"]
        IDX2["Device Index 2"]
    end

    subgraph Device0["Device 0 Keys"]
        D0_SIGN["Signing Key 0"]
        D0_EXCH["Exchange Key 0"]
    end

    subgraph Device1["Device 1 Keys"]
        D1_SIGN["Signing Key 1"]
        D1_EXCH["Exchange Key 1"]
    end

    subgraph Device2["Device 2 Keys"]
        D2_SIGN["Signing Key 2"]
        D2_EXCH["Exchange Key 2"]
    end

    SEED --> IDX0
    SEED --> IDX1
    SEED --> IDX2

    IDX0 -->|"HKDF(seed, device_index=0)"| D0_SIGN
    IDX0 --> D0_EXCH

    IDX1 -->|"HKDF(seed, device_index=1)"| D1_SIGN
    IDX1 --> D1_EXCH

    IDX2 -->|"HKDF(seed, device_index=2)"| D2_SIGN
    IDX2 --> D2_EXCH

    style SEED fill:#ff9,stroke:#333

Crypto-Shredding Paths

    accTitle: Cryptographic Key Hierarchy (4)
    accDescr: Shows the derivation paths from master seed to all encryption and signing keys
flowchart TB
    subgraph Destroy["Destruction Targets"]
        DSEED["Destroy Seed"]
        DSMK["Destroy SMK"]
        DCEK["Destroy CEK"]
    end

    subgraph Effect["Effect"]
        E_ALL["All data unreadable"]
        E_LOCAL["All local data unreadable"]
        E_CONTACT["Single contact unreadable"]
    end

    DSEED -->|"Complete identity destruction"| E_ALL
    DSMK -->|"Storage shredding"| E_LOCAL
    DCEK -->|"Per-contact shredding"| E_CONTACT

    style DSEED fill:#f99,stroke:#333
    style DSMK fill:#f99,stroke:#333
    style DCEK fill:#f99,stroke:#333

Key Storage Locations

    accTitle: Cryptographic Key Hierarchy (5)
    accDescr: Shows the derivation paths from master seed to all encryption and signing keys
flowchart LR
    subgraph Platform["Platform Keychain"]
        SMK_STORED["SMK<br/>(encrypted)"]
    end

    subgraph SQLite["SQLite Database"]
        SEK_DATA["Data encrypted<br/>with SEK"]
        CEK_STORED["CEK encrypted<br/>with SEK"]
        RATCHET["Ratchet state<br/>encrypted with SEK"]
    end

    subgraph Memory["Memory Only"]
        SEK_MEM["SEK (derived at boot)"]
        MK_MEM["Message keys<br/>(single use)"]
        CHAIN_MEM["Active chain keys"]
    end

    SMK_STORED -->|"derive on boot"| SEK_MEM
    SEK_MEM -->|"encrypt/decrypt"| SEK_DATA
    SEK_MEM -->|"encrypt/decrypt"| CEK_STORED
    SEK_MEM -->|"encrypt/decrypt"| RATCHET

    CHAIN_MEM -->|"derive"| MK_MEM
    MK_MEM -->|"delete after use"| MK_MEM

    style SMK_STORED fill:#f9f,stroke:#333
    style SEK_MEM fill:#9ff,stroke:#333
    style MK_MEM fill:#9f9,stroke:#333

Backup Key Derivation

    accTitle: Cryptographic Key Hierarchy (6)
    accDescr: Shows the derivation paths from master seed to all encryption and signing keys
flowchart TB
    subgraph Input["User Input"]
        PASSWORD["Password"]
        SALT["Random Salt<br/>(16 bytes)"]
    end

    subgraph KDF["Key Derivation"]
        ARGON["Argon2id<br/>m=64MB, t=3, p=4"]
    end

    subgraph Output["Output"]
        BACKUP_KEY["Backup Key<br/>(256 bits)"]
        ENCRYPTED["Encrypted Backup"]
    end

    subgraph Plaintext["Backup Contents"]
        NAME["Display Name"]
        SEED_PT["Master Seed"]
        DEV_IDX["Device Index"]
        DEV_NAME["Device Name"]
    end

    PASSWORD --> ARGON
    SALT --> ARGON
    ARGON --> BACKUP_KEY

    BACKUP_KEY -->|"XChaCha20-Poly1305"| ENCRYPTED

    NAME --> ENCRYPTED
    SEED_PT --> ENCRYPTED
    DEV_IDX --> ENCRYPTED
    DEV_NAME --> ENCRYPTED

    style PASSWORD fill:#ff9,stroke:#333
    style BACKUP_KEY fill:#9ff,stroke:#333

Security Properties by Key

KeyForward SecrecyBreak-in RecoveryZeroized on Drop
Master SeedN/ANoYes
Identity SigningNoNoYes
Exchange KeyNoNoYes
SMKNoNoYes
SEKNoNoYes (memory only)
CEKPer-contactN/AYes
Root KeyVia DH ratchetYesYes
Chain KeyVia symmetric ratchetN/AYes
Message KeySingle-use, deletedN/AYes