Wallet-Based Identity
How Zentalk uses cryptographic wallet addresses as the foundation for user identity.
Why Wallet-Based Identity?
Traditional messaging systems use problematic identity models. Zentalk takes a fundamentally different approach.
Problems with Username/Password
| Issue | Risk |
|---|---|
| Server stores credentials | Database breaches expose all users |
| Password recovery | Server can reset your identity |
| Centralized control | Service can lock you out |
| Phishing attacks | Users tricked into revealing passwords |
| Weak passwords | Most users choose guessable passwords |
The fundamental problem: Your identity exists on someone else’s server. They can impersonate you, lock you out, or hand over access to authorities.
Problems with Phone Number Identity
| Issue | Risk |
|---|---|
| SIM swap attacks | Attacker ports your number, steals identity |
| Carrier cooperation | Governments can request number transfers |
| Privacy leak | Phone number links to real-world identity |
| Recycled numbers | Previous owner receives your messages |
| Regional restrictions | Numbers tied to countries |
The fundamental problem: Your phone number is controlled by your carrier, not by you.
Benefits of Cryptographic Identity
| Property | Benefit |
|---|---|
| Self-sovereign | You control your identity completely |
| Portable | Move between devices and services |
| Decentralized | No single point of failure |
| Cryptographically secure | Cannot be guessed or brute-forced |
| Pseudonymous | No link to real-world identity required |
| Verifiable | Others can prove your identity mathematically |
The solution: Your identity is a cryptographic key pair. Only you possess the private key. Your identity cannot be seized, reset, or transferred without your explicit action.
How Wallet Identity Works
Wallet Address Derivation
Your Zentalk identity starts with key generation:
1. Generate random seed (256 bits of entropy)
2. Derive master key using BIP-39 mnemonic
3. Generate Ed25519 keypair from master key
4. Wallet address = encode(hash(public_key))Example address: zk1qxy7f8h9j2k3m4n5p6q7r8s9t0u1v2w3x4y5z6
The address is a compressed, human-readable representation of your public key.
Key Hierarchy
Zentalk uses a hierarchical key structure:
| Key | Derivation | Purpose |
|---|---|---|
| Master Seed | Random entropy | Root of all keys |
| Wallet Key | Derived from seed | Identity and authentication |
| Identity Key (IK) | Derived from wallet | Signing, X3DH identity |
| Device Keys | Per-device derivation | Multi-device support |
Wallet Key vs Identity Key
Are they the same key? No, but they are mathematically related.
| Aspect | Wallet Key | Identity Key |
|---|---|---|
| Algorithm | secp256k1 (Ethereum compatible) | Ed25519 |
| Purpose | Authentication, on-chain operations | Message signing, X3DH |
| Derivation | Direct from seed | Derived from wallet key |
| Exposure | Used for challenges | Published in key bundle |
Why separate keys?
- Algorithm optimization: Ed25519 is faster and simpler for signatures
- Protocol compatibility: X3DH requires Ed25519/X25519 keys
- Risk isolation: Compromising one key doesn’t compromise the other
- Blockchain compatibility: Wallet key works with Ethereum/EVM chains
Derivation relationship:
Master Seed
|
+-- Wallet Key (secp256k1)
| |
| +-- On-chain identity registration
| +-- Authentication challenges
|
+-- Identity Key (Ed25519)
|
+-- X3DH key exchange
+-- Message signing
+-- Key bundle signaturesBoth keys prove ownership of the same identity because they derive from the same seed.
Authentication Flow
Challenge-Response Protocol
When you connect to a Zentalk server, you prove identity ownership without revealing your private key.
┌─────────────┐ ┌─────────────┐
│ Client │ │ Server │
└──────┬──────┘ └──────┬──────┘
│ │
│ 1. Request authentication │
│ ─────────────────────────────────► │
│ { wallet_address } │
│ │
│ 2. Challenge │
│ ◄───────────────────────────────── │
│ { nonce, timestamp, server_id } │
│ │
│ 3. Signed response │
│ ─────────────────────────────────► │
│ { signature, wallet_address } │
│ │
│ 4. JWT token │
│ ◄───────────────────────────────── │
│ { token, expires_at } │
│ │Challenge Structure
The server generates a challenge containing:
{
"nonce": "a1b2c3d4e5f6...",
"timestamp": 1704067200,
"server_id": "node-eu-west-1",
"version": 1,
"action": "authenticate"
}| Field | Purpose |
|---|---|
| nonce | Random value (32 bytes), prevents replay |
| timestamp | Unix timestamp, expires in 60 seconds |
| server_id | Identifies the requesting server |
| version | Protocol version for future compatibility |
| action | What the signature authorizes |
Client Signature
The client signs the challenge:
message = canonical_json(challenge)
signature = secp256k1_sign(wallet_private_key, sha256(message))Why SHA-256 before signing? The message is hashed first to ensure constant-size input and prevent length extension attacks.
Replay Attack Prevention
Multiple mechanisms prevent replay attacks:
| Mechanism | Protection |
|---|---|
| Nonce | Each challenge unique, cannot reuse signature |
| Timestamp | Challenge expires after 60 seconds |
| Server ID | Signature only valid for specific server |
| Action field | Signature cannot be used for different operations |
Attack scenario prevented:
- Eve intercepts Alice’s authentication signature
- Eve tries to replay the signature to the server
- Server rejects: nonce already used OR timestamp expired
JWT Token Generation
After successful verification, the server issues a JWT:
{
"header": {
"alg": "ES256",
"typ": "JWT"
},
"payload": {
"sub": "zk1qxy7f8h9j2k3m4n5p6q7r8s9t0u1v2w3x4y5z6",
"iat": 1704067200,
"exp": 1704153600,
"iss": "zentalk-api",
"device_id": "device-abc123"
}
}| Field | Meaning |
|---|---|
| sub | Wallet address (subject) |
| iat | Issued at timestamp |
| exp | Expiration (24 hours default) |
| iss | Issuing service |
| device_id | Device that authenticated |
Token refresh: Before expiration, clients can request a new token using the existing valid token, without re-signing.
Identity Verification
Key Fingerprints
Users verify each other’s identity using fingerprints derived from identity keys:
fingerprint = SHA-256(identity_public_key)
display = format_as_blocks(fingerprint)Display format:
37291 84756 19283 04857
29184 73829 18374 02948Why numeric format?
- Easier to read aloud during verification
- Less prone to transcription errors than hex
- Can be verified over phone call
Out-of-Band Verification
Users should verify fingerprints through a separate channel:
| Method | Security Level | Use Case |
|---|---|---|
| In person | Highest | High-security contacts |
| Video call | High | Remote but visual confirmation |
| Phone call | Medium | Voice recognition adds assurance |
| Trusted third party | Medium | Mutual contact vouches |
| QR code scan | High | Quick in-person exchange |
Verification ceremony:
- Alice and Bob meet in person
- Each opens Zentalk settings and shows fingerprint
- Each reads their fingerprint aloud
- Both confirm the displayed fingerprint matches what they hear
- Mark contact as “verified” in the app
Trust On First Use (TOFU)
For casual conversations, Zentalk uses TOFU:
| Event | Behavior |
|---|---|
| First message | Accept identity key, store fingerprint |
| Subsequent messages | Verify against stored fingerprint |
| Key change detected | Warning displayed, require acknowledgment |
TOFU limitations:
- Does not protect against MITM on first contact
- User must verify for high-security conversations
- Key rotation requires re-verification
Safety Numbers
Similar to Signal, Zentalk generates safety numbers for each conversation:
safety_number = SHA-256(
sort(identity_key_A, identity_key_B) ||
"ZentalkSafetyNumber"
)Properties:
| Property | Meaning |
|---|---|
| Unique per pair | Different for Alice-Bob vs Alice-Carol |
| Order independent | Alice sees same number as Bob |
| Changes on key rotation | Alerts both parties |
When safety number changes:
Security alert:
Your safety number with Bob has changed.
This could mean:
- Bob reinstalled Zentalk
- Bob switched devices
- Someone is intercepting your messages
Verify Bob's new fingerprint before continuing.Account Creation
What Happens When You Create an Account
┌─────────────────────────────────────────────────────────────┐
│ Account Creation Flow │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. Generate entropy (256 bits from secure RNG) │
│ │ │
│ ▼ │
│ 2. Create BIP-39 mnemonic (12 or 24 words) │
│ │ │
│ ▼ │
│ 3. Derive master seed from mnemonic │
│ │ │
│ ▼ │
│ 4. Generate wallet keypair (secp256k1) │
│ │ │
│ ▼ │
│ 5. Generate identity keypair (Ed25519) │
│ │ │
│ ▼ │
│ 6. Generate initial pre-keys (X25519) │
│ │ │
│ ▼ │
│ 7. Sign pre-keys with identity key │
│ │ │
│ ▼ │
│ 8. Register on network (publish public keys) │
│ │ │
│ ▼ │
│ 9. Store private keys locally (encrypted) │
│ │
└─────────────────────────────────────────────────────────────┘Key Generation Sequence
Step-by-step breakdown:
// 1. Generate cryptographic entropy
const entropy = crypto.getRandomValues(new Uint8Array(32))
// 2. Create mnemonic phrase
const mnemonic = bip39.entropyToMnemonic(entropy)
// "apple banana cherry dragon eagle frog garden honey igloo jacket kite lemon"
// 3. Derive master seed
const seed = bip39.mnemonicToSeedSync(mnemonic)
// 4. Derive wallet key (secp256k1)
const walletKey = deriveKey(seed, "m/44'/60'/0'/0/0")
// 5. Derive identity key (Ed25519)
const identityKey = deriveEd25519(seed, "m/44'/1991'/0'/0/0")
// 6. Generate pre-keys
const signedPreKey = generateX25519Keypair()
const oneTimePreKeys = Array(100).fill().map(() => generateX25519Keypair())
// 7. Sign pre-keys
const spkSignature = ed25519.sign(signedPreKey.publicKey, identityKey.privateKey)Storage Distribution
| Data | Location | Encryption |
|---|---|---|
| Mnemonic phrase | User’s backup (paper/password manager) | None (user responsibility) |
| Private keys | Local device (IndexedDB) | AES-256-GCM + device key |
| Public identity key | Network (API servers) | None (public data) |
| Signed pre-key | Network (API servers) | None (public data) |
| One-time pre-keys | Network (API servers) | None (public data) |
| Wallet address | On-chain (optional) | None (public data) |
On-Chain Registration (Optional)
For additional verification, identity can be registered on-chain:
// Simplified identity contract
mapping(address => bytes32) public identityKeys;
function registerIdentity(bytes32 identityKeyHash) external {
require(identityKeys[msg.sender] == 0, "Already registered");
identityKeys[msg.sender] = identityKeyHash;
emit IdentityRegistered(msg.sender, identityKeyHash);
}Benefits of on-chain registration:
| Benefit | Description |
|---|---|
| Immutable record | Identity key change history is permanent |
| Decentralized lookup | Anyone can verify without trusting a server |
| Timestamped | Blockchain provides proof of registration time |
| Censorship resistant | Cannot be removed by any party |
Identity Portability
Moving to a New Device
Option 1: Recovery phrase
1. Install Zentalk on new device
2. Select "Restore identity"
3. Enter 12/24 word mnemonic
4. All keys are regenerated deterministically
5. Fetch message history from mesh storageOption 2: Device linking (existing device available)
1. Open Zentalk on both devices
2. New device displays QR code
3. Existing device scans QR code
4. Encrypted key transfer via secure channel
5. New device added to device listExporting Identity
Users can export their identity for backup:
Export contents:
- Encrypted private key bundle
- Identity key (encrypted)
- Wallet key (encrypted)
- Device key list
NOT exported:
- Message history (use mesh backup)
- Contact list (use mesh backup)
- Session state (regenerated)Export encryption:
1. User provides export password
2. salt = random(32 bytes)
3. key = PBKDF2(password, salt, 600000 iterations)
4. encrypted_bundle = AES-256-GCM(key, private_keys)
5. output = salt || nonce || encrypted_bundle || tagWhat Can and Cannot Be Transferred
| Transferable | Not Transferable |
|---|---|
| Identity (mnemonic/keys) | Active sessions |
| Contact list (from mesh) | Session keys |
| Message history (from mesh) | Ephemeral keys already used |
| Verified contacts | Device-specific keys |
| Profile data | Push notification tokens |
Why sessions cannot transfer:
Double Ratchet sessions contain ephemeral keys that change with every message. Transferring would require syncing state across devices in real-time, which Zentalk handles through the multi-device protocol instead.
Multi-Device Considerations
When identity exists on multiple devices:
| Aspect | Handling |
|---|---|
| Same identity key | All devices share identity |
| Separate device keys | Each device has unique sub-identity |
| Message sync | Mesh storage delivers to all devices |
| Key rotation | Coordinated across devices |
| Device revocation | Remove from authorized list |
Privacy Properties
Wallet Address as Pseudonym
Your wallet address is a pseudonym:
| Property | Privacy Impact |
|---|---|
| No personal data | Address contains no name, email, phone |
| Unlinkable to real identity | Unless you link it yourself |
| Multiple addresses possible | Create separate identities for different contexts |
| Deterministic | Same seed always produces same address |
Unlinkability Between Accounts
Users can maintain multiple unlinkable identities:
Seed A → Wallet Address A → Identity A
Seed B → Wallet Address B → Identity B
No mathematical relationship between A and BUse cases:
| Scenario | Why Separate Identity |
|---|---|
| Personal/Professional | Keep work and personal contacts separate |
| Activism | Protect real identity while organizing |
| Whistleblowing | Report wrongdoing without attribution |
| Compartmentalization | Different security levels |
Metadata Exposure
| Data | Who Can See | Mitigation |
|---|---|---|
| Wallet address | Anyone you message | Use different addresses |
| Public keys | Network servers | Required for encryption |
| Online status | Servers | Configurable, can be hidden |
| Message timing | Servers | Traffic padding |
| Contact graph | Servers | 3-hop relay hides connections |
| IP address | Servers | Use Tor or VPN |
What the Server Cannot Learn
| Data | Why Server Cannot Access |
|---|---|
| Message content | E2EE - only recipient has key |
| Contact names | Stored locally, encrypted |
| Conversation topics | Cannot decrypt messages |
| Private keys | Never leave device |
| Recovery phrase | Only user possesses |
Comparison: Identity Models
Zentalk vs Signal vs WhatsApp
| Aspect | Zentalk | Signal | |
|---|---|---|---|
| Identity Anchor | Wallet address | Phone number | Phone number |
| Registration | Key generation only | Phone verification | Phone verification |
| Server Knowledge | Public keys only | Phone number + keys | Phone + contacts + metadata |
| Identity Recovery | Mnemonic phrase | Phone number | Phone number + backup |
| Account Portability | Full (seed phrase) | Limited (phone) | Limited (phone + cloud) |
| Pseudonymity | Native | Requires burner | Requires burner |
| Multiple Accounts | Easy (multiple seeds) | Hard (multiple phones) | Hard (multiple phones) |
| Decentralized | Yes (mesh + optional chain) | No (central servers) | No (Meta servers) |
| SIM Swap Risk | None | High | High |
| Phone Privacy | No phone needed | Phone exposed | Phone exposed |
| Verification | Fingerprints | Safety numbers | QR code |
| Key Custody | Self-custody | Self-custody | Cloud backup (Meta access) |
Identity Attack Vectors
| Attack | Zentalk | Signal | |
|---|---|---|---|
| SIM Swap | Not possible | Identity theft | Identity theft |
| Server Compromise | Keys safe | Keys safe | Metadata exposed |
| Carrier Request | Not applicable | Account access | Account access |
| Subpoena | Encrypted blobs only | Phone number + metadata | Full metadata + contacts |
| Phishing | Seed phrase risk | Phone/PIN risk | Phone/PIN/2FA risk |
| Device Seizure | Encrypted local data | Encrypted local data | Encrypted + cloud backup |
Migration Comparison
| Scenario | Zentalk | Signal | |
|---|---|---|---|
| Lost phone | Recover with mnemonic | Re-register with phone | Re-register + cloud restore |
| New phone | Import seed | Transfer requires old phone | Cloud backup or transfer |
| Number change | Not applicable | Complex migration | Complex migration |
| Account deletion | Delete local keys | Request deletion | Request deletion (data retained) |
Security Recommendations
For All Users
| Action | Benefit |
|---|---|
| Backup recovery phrase securely | Can recover identity if device lost |
| Never share recovery phrase | Prevents identity theft |
| Verify important contacts | Prevents MITM attacks |
| Use unique identities for sensitive contexts | Compartmentalization |
| Enable device PIN/biometrics | Protects local keys |
For High-Risk Users
| Action | Benefit |
|---|---|
| Use hardware wallet for seed | Air-gapped key storage |
| Create identity on air-gapped device | Prevents key exposure |
| Use separate identity per context | Unlinkability |
| Regular key rotation | Limits compromise window |
| Verify all contacts in person | Maximum MITM protection |
Stealth Addresses
Stealth addresses provide an additional layer of privacy by generating one-time addresses for each transaction or message exchange, preventing address reuse from creating linkable patterns.
What Are Stealth Addresses?
The Problem: Public Address Visibility
In traditional cryptocurrency and messaging systems, wallet addresses are publicly visible on the blockchain or network. This creates significant privacy concerns.
| Problem | Description |
|---|---|
| Transaction tracking | Anyone can monitor all incoming transfers to a known address |
| Balance surveillance | Observers can calculate total holdings and spending patterns |
| Address clustering | Multiple transactions can be linked to a single entity |
| Social graph exposure | Communication patterns reveal relationships |
| Targeted attacks | High-value addresses become targets for phishing or coercion |
Example scenario:
Alice publishes her Zentalk address: zk1abc123...
Bob sends a message to Alice using this address.
Eve, monitoring the network, observes this.
Later, Carol also messages Alice at the same address.
Eve now knows: Alice communicates with both Bob and Carol.
Over time, Eve builds a complete map of Alice's contacts.The Solution: One-Time Addresses
Stealth addresses solve this by generating a unique, unlinkable address for each sender. Even though all payments/messages go to Alice, each appears to go to a completely different address.
| Property | Benefit |
|---|---|
| Unlinkability | No connection between stealth addresses and recipient’s main address |
| Sender privacy | Only the recipient knows who sent to each address |
| Recipient privacy | Observers cannot determine the true recipient |
| No coordination required | Sender generates address without recipient interaction |
How Stealth Addresses Work
Zentalk implements a dual-key stealth address protocol based on elliptic curve Diffie-Hellman (ECDH). This separates the ability to receive (scan) from the ability to spend (control).
Dual-Key Architecture
| Key Type | Name | Purpose | Security Level |
|---|---|---|---|
| View Key | Scan Key | Detect incoming messages/payments | Can be delegated |
| Spend Key | Spend Key | Actually access/read messages | Must remain secret |
Key Pair Notation
| Symbol | Meaning |
|---|---|
| (v, V) | Recipient’s view/scan keypair (v = private, V = public) |
| (s, S) | Recipient’s spend keypair (s = private, S = public) |
| (r, R) | Sender’s ephemeral keypair (r = private, R = public) |
| G | Elliptic curve generator point |
| H() | Cryptographic hash function (SHA-256) |
| · | Elliptic curve point multiplication |
Mathematical Foundation
The stealth address protocol relies on the ECDH key agreement property:
ECDH shared secret:
r · V = v · R
Where:
r = sender's ephemeral private key
V = v · G (recipient's public view key)
R = r · G (sender's ephemeral public key)This allows sender and recipient to derive the same shared secret independently:
- Sender computes: r · V
- Recipient computes: v · R
Both yield the same point on the elliptic curve.
Protocol Overview
┌─────────────────────────────────────────────────────────────┐
│ Stealth Address Protocol │
├─────────────────────────────────────────────────────────────┤
│ │
│ Recipient publishes: │
│ Meta-address = (V, S) = (v·G, s·G) │
│ │
│ Sender generates: │
│ 1. Fresh ephemeral keypair (r, R) │
│ 2. Shared secret: shared = H(r · V) │
│ 3. Stealth public key: P = S + shared · G │
│ 4. Sends message to P, attaches R │
│ │
│ Recipient scans: │
│ 1. For each R on network: shared = H(v · R) │
│ 2. Compute expected P = S + shared · G │
│ 3. If P matches, message is for recipient │
│ 4. Derive private key: p = s + shared │
│ │
└─────────────────────────────────────────────────────────────┘Stealth Address Generation
When a sender wants to contact a recipient using a stealth address, they perform the following steps.
Prerequisites
| Requirement | Description |
|---|---|
| Recipient meta-address | The tuple (V, S) published by recipient |
| Secure random source | For generating ephemeral keypair |
| Elliptic curve operations | Point multiplication and addition |
Step-by-Step Generation Process
| Step | Operation | Result |
|---|---|---|
| 1 | Generate ephemeral private key | r ← random(256 bits) |
| 2 | Compute ephemeral public key | R = r · G |
| 3 | Compute ECDH shared point | sharedPoint = r · V |
| 4 | Derive shared secret scalar | sharedSecret = H(sharedPoint ‖ R) |
| 5 | Compute stealth public key | P = S + sharedSecret · G |
| 6 | Derive stealth address | address = encode(H(P)) |
Pseudocode: Stealth Address Generation
function generateStealthAddress(recipientMetaAddress):
// Parse recipient's published meta-address
V = recipientMetaAddress.viewPublicKey
S = recipientMetaAddress.spendPublicKey
// Generate fresh ephemeral keypair
r = secureRandom(32) // 256 bits
R = scalarMultiply(G, r)
// Compute shared secret via ECDH
sharedPoint = scalarMultiply(V, r)
sharedSecret = sha256(sharedPoint || R)
// Derive the stealth public key
sharedSecretPoint = scalarMultiply(G, sharedSecret)
P = pointAdd(S, sharedSecretPoint)
// Generate final stealth address
stealthAddress = encodeAddress(sha256(P))
return {
stealthAddress: stealthAddress,
ephemeralPublicKey: R,
stealthPublicKey: P
}What Gets Transmitted
| Component | Included | Purpose |
|---|---|---|
| Stealth address P | Yes | Destination for message/payment |
| Ephemeral public key R | Yes | Enables recipient to derive shared secret |
| Ephemeral private key r | No | Discarded after generation |
| Shared secret | No | Derived independently by recipient |
Security Properties of Generation
| Property | Guarantee |
|---|---|
| Freshness | New ephemeral key per address ensures uniqueness |
| Unlinkability | Cannot link stealth addresses without view key |
| Forward secrecy | Ephemeral key discarded; past addresses remain private |
| Non-interactivity | No communication with recipient required |
Recipient Scanning
The recipient must scan the network to detect messages or payments addressed to them via stealth addresses.
Scanning Process
For each announcement (R, P) observed on the network:
| Step | Operation | Result |
|---|---|---|
| 1 | Retrieve ephemeral public key | R from announcement |
| 2 | Compute ECDH shared point | sharedPoint = v · R |
| 3 | Derive shared secret | sharedSecret = H(sharedPoint ‖ R) |
| 4 | Compute expected stealth key | P’ = S + sharedSecret · G |
| 5 | Compare with announced key | if P’ = P, message is for us |
| 6 | Derive private key (if match) | p = s + sharedSecret |
Pseudocode: Recipient Scanning
function scanForPayments(viewPrivateKey, spendPublicKey, announcements):
v = viewPrivateKey
S = spendPublicKey
myPayments = []
for each (R, P, payload) in announcements:
// Compute shared secret using our view key
sharedPoint = scalarMultiply(R, v)
sharedSecret = sha256(sharedPoint || R)
// Derive expected stealth public key
sharedSecretPoint = scalarMultiply(G, sharedSecret)
expectedP = pointAdd(S, sharedSecretPoint)
// Check if this payment is for us
if expectedP equals P:
myPayments.append({
ephemeralKey: R,
stealthKey: P,
sharedSecret: sharedSecret,
payload: payload
})
return myPaymentsDeriving the Spending Key
Once a matching stealth address is found, the recipient derives the private key:
function deriveSpendingKey(spendPrivateKey, sharedSecret):
s = spendPrivateKey
p = scalarAdd(s, sharedSecret) // modular addition
return p
// Verification: p · G should equal PScanning Efficiency Considerations
| Factor | Impact | Mitigation |
|---|---|---|
| Network volume | Each announcement requires one ECDH operation | Batch processing |
| ECDH cost | Point multiplication is computationally expensive | Dedicated scanning hardware |
| Storage | Must track all scanned announcements | Bloom filters for quick rejection |
| Latency | Scanning must be near real-time for messages | Parallel processing |
Performance Benchmarks
| Operation | Time (approx.) | Notes |
|---|---|---|
| ECDH point multiplication | 0.1-0.5 ms | Curve25519 optimized |
| SHA-256 hash | 0.001 ms | Negligible |
| Point addition | 0.01 ms | Fast operation |
| Full scan iteration | 0.2-0.6 ms | Per announcement |
Scalability Table
| Announcements/day | Scan time/day | CPU overhead |
|---|---|---|
| 1,000 | ~0.5 seconds | Negligible |
| 100,000 | ~50 seconds | Low |
| 1,000,000 | ~8 minutes | Moderate |
| 10,000,000 | ~80 minutes | Significant |
View Key Delegation
A powerful feature of the dual-key architecture is the ability to delegate scanning without compromising spending authority.
View Key Properties
| Capability | View Key | Spend Key |
|---|---|---|
| Detect incoming messages | Yes | No (needs view key) |
| Read message content | Yes (with decryption) | No |
| Send responses | No | Yes |
| Access funds | No | Yes |
| Prove ownership | No | Yes |
Delegation Use Cases
| Scenario | Implementation |
|---|---|
| Auditor access | Share view key for compliance monitoring |
| Watch-only wallet | Mobile device with view key only |
| Scanning service | Third-party scans on your behalf |
| Inheritance planning | Beneficiary can see activity before full access |
Pseudocode: Delegated Scanning Service
// User shares only their view key with scanning service
function delegatedScanning(userViewKey, userSpendPublicKey, announcements):
// Service can detect payments but cannot spend
matches = scanForPayments(userViewKey, userSpendPublicKey, announcements)
// Service notifies user of matches
for each match in matches:
notifyUser(match.ephemeralKey, match.stealthKey)
// User derives spending key locally (never shared)
// p = s + match.sharedSecretSecurity of View Key Sharing
| Risk | Severity | Mitigation |
|---|---|---|
| Pattern analysis | Medium | Trusted scanning services only |
| Notification delay | Low | Multiple scanning services |
| Service compromise | Medium | View key reveals timing, not funds |
| Privacy leak | Medium | Rotate view keys periodically |
When Stealth Addresses Are Used
Zentalk employs stealth addresses strategically to balance privacy and efficiency.
Per-Conversation vs Per-Message
| Mode | Description | Use Case |
|---|---|---|
| Per-conversation | Single stealth address for entire conversation | Long-term contacts, efficiency priority |
| Per-message | Fresh stealth address per message | Maximum privacy, one-time contacts |
| Hybrid | New address on significant events | Balance of privacy and efficiency |
Decision Matrix
| Scenario | Stealth Address Mode | Rationale |
|---|---|---|
| First contact from stranger | Per-message | Maximum unlinkability |
| Established trusted contact | Per-conversation | Efficiency, lower overhead |
| Anonymous tip submission | Per-message | No linkability required |
| Group chat | Per-group | All members share scanning |
| High-risk communication | Per-message | Compartmentalization |
First Contact Scenarios
When Alice initiates contact with Bob for the first time:
1. Alice obtains Bob's meta-address (V_B, S_B)
- From public directory
- From QR code
- From mutual contact
2. Alice generates stealth address for Bob
- Creates ephemeral keypair (r, R)
- Computes stealth address P
3. Alice sends message to stealth address P
- Includes ephemeral public key R
- Message encrypted with derived key
4. Bob scans and detects the message
- Computes shared secret with v_B · R
- Verifies P matches expected value
5. Subsequent messages
- Option A: Continue using same stealth address
- Option B: Generate new stealth address per messageGroup Chat Considerations
| Aspect | Handling |
|---|---|
| Stealth address per sender | Each member has unique stealth address for group |
| Scanning overhead | Multiplied by group size |
| Member addition | New stealth addresses distributed |
| Member removal | Rotate all stealth addresses |
| Anonymity within group | Sender identity revealed to group only |
Group Stealth Address Protocol
Group setup:
For each member M_i:
- All other members generate stealth address for M_i
- M_i scans for N-1 stealth addresses
Message to group:
Sender S generates stealth addresses for all recipients
Message sent to each stealth address
Efficiency:
O(N) stealth addresses per message
O(N²) total scanning for all membersPrivacy Properties
Stealth addresses provide strong privacy guarantees when properly implemented.
Unlinkability Analysis
| Observer | Can Determine | Cannot Determine |
|---|---|---|
| Network observer | That a message was sent | Sender or recipient identity |
| Sender | Their own sent messages | Other senders to same recipient |
| Recipient | All messages to them | Relationship between their addresses |
| Third party with view key | Incoming message timing | Spending capability |
What an Observer Sees
Network observation:
[R_1, P_1, encrypted_payload_1]
[R_2, P_2, encrypted_payload_2]
[R_3, P_3, encrypted_payload_3]
Observer's knowledge:
- Three messages were sent
- Each to a different address
- All payloads are encrypted
Observer cannot determine:
- If any messages share a recipient
- Who the senders are
- Who the recipients are
- Message contentsComparison: Regular vs Stealth Addresses
| Property | Regular Address | Stealth Address |
|---|---|---|
| Address reuse | Same address used repeatedly | Unique address per sender/message |
| Balance visibility | Total visible to all | Only recipient knows full balance |
| Transaction linking | Trivial to link | Computationally infeasible |
| Sender anonymity | Not provided | Preserved |
| Recipient anonymity | Not provided | Preserved |
| Scanning required | No | Yes |
Privacy Guarantees
| Guarantee | Description | Assumption |
|---|---|---|
| Recipient unlinkability | Stealth addresses are indistinguishable | ECDH is secure |
| Sender unlinkability | No link between sender and stealth address | Ephemeral key is random |
| Forward privacy | Past addresses remain unlinkable | Ephemeral keys discarded |
| Amount privacy | Combined with encryption | Encrypted payloads |
Attack Resistance
| Attack | Protection Level | Notes |
|---|---|---|
| Timing analysis | Partial | Traffic padding helps |
| Volume analysis | Partial | Dummy messages help |
| Intersection attack | Strong | Requires view key |
| Blockchain analysis | Strong | No on-chain link |
| Social engineering | Depends on user | View key compromise reveals timing |
Limitations
While stealth addresses provide strong privacy, they come with tradeoffs.
Scanning Overhead
| Limitation | Description | Impact |
|---|---|---|
| Computational cost | Every announcement requires ECDH operation | CPU/battery drain |
| Latency | Must scan before detecting messages | Delayed notifications |
| Scalability | Cost grows linearly with network activity | High-volume networks are expensive |
| Always-on requirement | Must scan regularly or miss messages | Not suitable for offline devices |
Storage Requirements
| Data | Storage Need | Growth Rate |
|---|---|---|
| Scanned announcements | Track to avoid re-scanning | Linear with network |
| Detected stealth addresses | Store for spending | Linear with received messages |
| Ephemeral keys (sender) | Temporary during generation | Constant (discarded) |
| Derived spending keys | Must persist | Linear with received messages |
Storage Comparison
| Wallet Type | Storage (1000 transactions) | Notes |
|---|---|---|
| Regular | ~50 KB | Just addresses and keys |
| Stealth (minimal) | ~100 KB | Additional ephemeral keys |
| Stealth (full scanning) | ~10 MB | Announcement cache |
Compatibility Considerations
| Issue | Description | Workaround |
|---|---|---|
| Existing wallets | May not support stealth addresses | Wrapper/proxy protocols |
| Light clients | Cannot scan efficiently | Scanning service delegation |
| Hardware wallets | Limited computational capacity | External scanning device |
| Exchange deposits | Require deterministic addresses | View key + scanning infrastructure |
Protocol Complexity
| Aspect | Complexity Added |
|---|---|
| Key management | Two keypairs instead of one |
| Address generation | Multi-step ECDH computation |
| Payment detection | Active scanning required |
| Recovery | Must backup both key pairs |
| Delegation | Additional security considerations |
Tradeoff Summary
| Factor | Without Stealth | With Stealth |
|---|---|---|
| Privacy | Low | High |
| Efficiency | High | Medium |
| Simplicity | High | Low |
| Mobile friendliness | High | Medium |
| Offline capability | High | Low |
| Scalability | High | Medium |
Mitigation Strategies
| Limitation | Mitigation |
|---|---|
| Scanning cost | Delegated scanning services |
| Latency | Background scanning + push notifications |
| Storage | Bloom filters, announcement pruning |
| Complexity | Abstraction in wallet software |
| Mobile drain | Server-side scanning with view key |
Related Documentation
- Cryptography Fundamentals - Underlying primitives
- Protocol Specification - X3DH and Double Ratchet details
- Multi-Device Support - Managing multiple devices
- Threat Model - Security assumptions and attacks