
Password Manager and Private Messenger
Note: KeyPears is a work-in-progress open-source password manager and cryptocurrency wallet. The design decisions described here represent our development approach and may evolve before our official release.
This week we shipped the key derivation system that powers KeyPears' upcoming Diffie-Hellman key exchange. It solves a fundamental problem: how can a server generate public keys for users while ensuring only the user can derive the corresponding private keys?
This post explains the mathematics, the implementation, and why this matters for federated secret sharing.
Imagine Alice wants to send an encrypted secret to Bob. She needs Bob's public key to establish a shared secret via Diffie-Hellman. But Bob is offline—maybe he's on a plane, or his phone is dead, or he simply hasn't opened the app in days.
In a centralized system, this is easy: the server stores Bob's public key and hands it to Alice. But KeyPears is designed so that servers never learn users' private keys. The server knows Bob's vault public key, but we don't want to expose that directly—doing so would enable correlation across all of Bob's relationships and create a single point of failure if that key were ever compromised.
We need the server to generate a fresh, unique public key for each relationship (Alice↔Bob, Carol↔Bob, etc.) while ensuring:
How do we square this circle?
The answer lies in a beautiful property of elliptic curve cryptography. On the
secp256k1 curve (the same one Bitcoin uses), there's a generator point G. Any
private key a has a corresponding public key A = a * G (scalar
multiplication).
Here's the critical insight:
If you add two private keys together, the result corresponds to adding their public keys together.
Given:
Private key a → Public key A (where A = a * G)
Private key b → Public key B (where B = b * G)
Then:
(a + b) * G = A + B
This means we can construct a derived keypair by addition:
The server knows the vault public key and can generate the derivation keypair. So the server can compute the derived public key. But only Bob knows his vault private key, so only Bob can compute the derived private key.
KeyPears combines three sources of entropy to generate derived keys:
1. Server Entropy (from DERIVATION_ENTROPY_N environment variables)
The server maintains one or more 32-byte entropy values, numbered sequentially.
The highest-numbered entropy is used for new keys; older entropy is retained for
re-derivation. This enables rotation: add DERIVATION_ENTROPY_2, and new keys
use it while old keys remain derivable.
2. DB Entropy (random 32 bytes per derived key)
Each derived key gets fresh random entropy, stored in the database. This ensures every key is unique, even for the same user. Compromise of one key's entropy doesn't help attack others.
3. Vault Key (user's master private key)
The user's 32-byte secp256k1 private key never leaves their device. It's the "secret ingredient" that only the client possesses. The server knows the corresponding public key but can't reverse it.
When Alice requests a key for Bob, Bob's server performs:
1. db_entropy = random(32 bytes)
2. derivation_privkey = HMAC-SHA256(key: server_entropy, data: db_entropy)
3. derivation_pubkey = derivation_privkey * G
4. derived_pubkey = vault_pubkey + derivation_pubkey
The server stores db_entropy and server_entropy_index in the database, then
returns derived_pubkey to Alice. Alice can now encrypt to Bob using this
public key.
When Bob comes online and needs to decrypt Alice's message:
1. Bob requests derivation_privkey from server
2. Server recomputes: derivation_privkey = HMAC-SHA256(server_entropy, db_entropy)
3. Server returns derivation_privkey to Bob
4. Bob computes: derived_privkey = vault_privkey + derivation_privkey
5. Bob verifies: derived_privkey * G == derived_pubkey
The verification step ensures nothing went wrong. If the derived public key matches, Bob knows he has the correct private key and can proceed with decryption.
KeyPears uses the @webbuf family of packages for cryptographic operations.
These are Rust implementations compiled to WebAssembly, providing both memory
safety and cross-platform consistency.
The server computes the derivation private key using HMAC-SHA256:
import { sha256Hmac } from "@webbuf/sha256";
import { FixedBuf } from "@webbuf/fixedbuf";
function deriveDerivationPrivKey(
serverEntropy: FixedBuf<32>,
dbEntropy: FixedBuf<32>,
): FixedBuf<32> {
return sha256Hmac(serverEntropy.buf, dbEntropy.buf);
}
HMAC provides domain separation: even if an attacker knew db_entropy, they
couldn't compute derivation_privkey without server_entropy.
The server derives the derivation public key and adds it to the vault public key:
import { publicKeyCreate, publicKeyAdd } from "@webbuf/secp256k1";
// derivation_pubkey = derivation_privkey * G
const derivationPubKey = publicKeyCreate(derivationPrivKey);
// derived_pubkey = vault_pubkey + derivation_pubkey
const derivedPubKey = publicKeyAdd(vaultPubKey, derivationPubKey);
When the user needs the derived private key:
import { privateKeyAdd, publicKeyCreate } from "@webbuf/secp256k1";
// derived_privkey = vault_privkey + derivation_privkey
const derivedPrivKey = privateKeyAdd(vaultPrivKey, derivationPrivKey);
// Verify: derived_privkey * G should equal derived_pubkey
const verifyPubKey = publicKeyCreate(derivedPrivKey);
if (verifyPubKey.toHex() !== derivedPubKey.toHex()) {
throw new Error("Derived key verification failed");
}
The verification step is crucial. It confirms that the addition was performed correctly and that all parties agree on the final keypair.
This key derivation system is the foundation for KeyPears' federated Diffie-Hellman key exchange. Here's how the pieces fit together:
Per-Relationship Keys: When Alice wants to communicate with Bob, Bob's server generates a derived key specifically for the Alice↔Bob relationship. This key is mathematically linked to Bob's vault key but reveals nothing about it.
Privacy Isolation: If an attacker compromises the Alice↔Bob derived key, they learn nothing about Bob's vault key or his keys for other relationships. Each relationship is cryptographically isolated.
Offline Operation: Alice can initiate contact with Bob even when Bob is offline. The server provides Bob's derived public key immediately. Bob derives the matching private key whenever he comes online.
Quantum Resistance: By never exposing the primary vault public key, we limit the attack surface. A quantum computer would need to target each derived key individually rather than compromising all relationships at once.
The full Diffie-Hellman protocol builds on this: Alice and Bob each get derived keys for their relationship, compute a shared secret via ECDH, and use that secret to encrypt communications. The servers coordinate but never see plaintext.
Server Never Learns Vault Private Key
The server knows vault_pubkey, derivation_privkey, and derived_pubkey. But
computing vault_privkey would require solving the discrete logarithm
problem—computationally infeasible on secp256k1.
Per-Key Entropy Isolation
Each derived key uses fresh db_entropy. Even if an attacker compromised the
database and obtained all db_entropy values, they'd still need server_entropy
to compute any derivation_privkey. And even with both, they'd need
vault_privkey to compute derived_privkey.
Entropy Rotation Limits Blast Radius
Server entropy rotates periodically (we recommend every 90 days). Each derived
key records which entropy index was used. If entropy N is somehow compromised,
only keys using index N are affected—and the attacker still needs per-key
db_entropy and user vault_privkey.
Verification Prevents Subtle Attacks
The client always verifies that derived_privkey * G == derived_pubkey. This
catches any corruption, miscalculation, or tampering. If verification fails, the
client rejects the key.
This week we implemented the complete key derivation system:
@keypears/lib: Exported privateKeyAdd, publicKeyAdd, and
deriveDerivationPrivKey functions@keypears/api-server: Created createDerivedKey, getDerivedKeys, and
getDerivationPrivKey procedures@keypears/tauri-ts: Built a "Keys" page where users can generate derived
keys and reveal private keys on demandThe infrastructure is in place. Next, we'll build the full Diffie-Hellman key
exchange protocol on top of it, enabling alice@example.com to securely share
secrets with bob@company.com across different domains and hosting providers.
The key derivation system demonstrates a pattern we use throughout KeyPears: servers coordinate without learning secrets. By exploiting the additive property of elliptic curve keys, we enable server-side public key generation while keeping private keys strictly client-side.
This isn't just a clever trick—it's the foundation for federated secret sharing. When you share a password with a colleague at a different company, both servers help coordinate the key exchange, but neither server can read the shared secret. That's the promise of KeyPears: the convenience of cloud sync with the security of self-custody.
The math is elegant. The implementation is straightforward. And the security properties are exactly what we need for a federated Diffie-Hellman key exchange system.
Next up: the full DH key exchange protocol for cross-user secret sharing. Stay tuned!