A self-contained web app demonstrating secure, end-to-end encryption for Ethereum wallets using X25519 keypairs, on-chain public-key registration, and off-chain message envelopes. This document provides a deep dive into the underlying algorithms, key management strategies, feature set, usage patterns, and security considerations.
- Algorithm Overview
- Keypair Generation & Recovery
- On-Chain Public Key Registry
- Hybrid Envelope Construction
- Browser App Features
- User Workflow & Usage
- Key Handling & Security
- Future Enhancements
-
X25519 Key Exchange
- Each user holds an X25519 keypair
(pk, sk). X25519 is a widely used elliptic-curve Diffie–Hellman (ECDH) scheme on Curve25519, chosen for performance and resilience.
- Each user holds an X25519 keypair
-
ECDH to Derive Symmetric Key
- To send a message to recipient with public key
pk_rec, the sender generates an ephemeral X25519 keypair(epk, esk). - Both parties compute a shared secret:
shared = X25519(esk, pk_rec) - The shared secret is hashed (SHA-256) to produce a 32-byte symmetric key
K.
- To send a message to recipient with public key
-
Symmetric Encryption (Secretbox)
- The plaintext is encrypted with
Kusing NaCl’ssecretbox(XSalsa20-Poly1305 AEAD). - A random nonce is prepended to the ciphertext; the result is Base64-encoded as the body cipher.
- The plaintext is encrypted with
-
Envelope for Key Transport
- The sender’s ephemeral public key
epkis prepended to the nonce and ciphertext to form the envelope for each recipient. - Recipients recover
KviaX25519(sk_rec, epk), then decrypt the body cipher.
- The sender’s ephemeral public key
-
Browser-Local Generation
- On first use, the app generates
nacl.box.keyPair(), yielding a 32-byte Curve25519 secret key and corresponding public key.
- On first use, the app generates
-
Wallet-Signed Binding
- To prove ownership of the on-chain identity, the app signs the Base64 public key with the user’s EOA via
signMessage(). - The signature is stored alongside the keypair in
localStorage, so one can audit that “EOA X registered pubkey Y.”
- To prove ownership of the on-chain identity, the app signs the Base64 public key with the user’s EOA via
-
Persistent Storage
- Keypairs are stored in
localStorageunder a map keyed by the lowercased wallet address. - Users may export their keypair to a JSON file (
<address>-enc-key.json) for offline backup or import it on another browser.
- Keypairs are stored in
-
Recovery on New Browser
- Import JSON: Load the backup file.
- LocalStorage Mapping: The app writes the imported keypair into
localStoragefor future automatic loading. - Signature Display: The stored signature re-affirms that the key belongs to the connected EOA.
A Solidity contract exposes:
function isPublicKeyRegistered(address user) view returns (bool);
function registerUserPublicKey(string b64PubKey) external;
function getUserPublicKey(address user) view returns (string);
event UserPublicKeyRegistered(address indexed user);-
Register / Update
- Users push their Base64 X25519 public key on-chain in a transaction.
- The UI listens for
UserPublicKeyRegisteredevents to build a local registry of address → public-key mappings.
-
Privacy
- Only public keys live on-chain; secrets and message data remain off-chain.
-
Body Cipher
- A single symmetric encryption of the plaintext under
K(derived from ephemeral & recipient keys).
- A single symmetric encryption of the plaintext under
-
Per-Recipient Envelope
- Each recipient gets an envelope containing:
- Ephemeral public key (
epk, 32 bytes) - Nonce (
24 bytes) - Secretbox ciphertext
- Ephemeral public key (
- The envelope is Base64-encoded and stored in a JSON map keyed by recipient address.
- Each recipient gets an envelope containing:
-
Multi-Recipient Efficiency
- The body cipher is computed once; recipients each get only the envelope containing their wrapped key.
-
Wallet Connect (Ethers.js)
- Detects
window.ethereum, requests accounts, and switches to the configured chain (Sonic Blaze Testnet,0xdede).
- Detects
-
Keypair UI
- Load Existing: Restores from
localStorageif present. - Generate New: Prompts overwrite if a key already exists.
- Copy to Clipboard: Public and secret keys.
- Save to File: Triggers JSON download of your keypair.
- Import from File: Loads a backup keypair JSON into
localStorage.
- Load Existing: Restores from
-
Registry Management
- Fetch past
UserPublicKeyRegisteredevents to populate the recipients list. - Register or update your on-chain public key with a single button click.
- Fetch past
-
Encrypt & Decrypt Workflows
- Encrypt: Type message, pick recipients (multi-select), click Encrypt, get a JSON map of envelopes.
- Decrypt: Paste that JSON, click Decrypt, and view your recovered plaintext automatically.
-
Per-Wallet Storage Isolation
- Under the hood, keys are stored at
enc_kp_map[<lowercase_address>]so multiple wallet users can share one browser without collisions.
- Under the hood, keys are stored at
- Serve Locally
git clone <repo> cd <repo> npx serve . # or any static server
- Open in Browser
- Connect Wallet → Approve in MetaMask (or any injected EVM wallet).
- Load vs Generate Keypair
- Click Load if you’ve used this page before or Import backup file.
- Otherwise, click Generate (overwriting only with confirmation).
- Register Public Key On-Chain → Sign & send transaction.
- Encrypt
- Enter plaintext
- Select registered recipients (always includes you)
- Click Encrypt → Copy the JSON envelopes
- Decrypt
- Paste the JSON envelopes
- Click Decrypt → Plaintext appears
-
Secret Key Confidentiality
- Never transmitted to any server.
- Only exposed in-browser when clicking Show and copied/exported explicitly.
-
LocalStorage Risks
- If an attacker gains filesystem or browser access, they can steal your secret key.
- Mitigation: Export and then clear your secret key from
localStoragewhen not in active use.
-
Signature Verification
- The on-chain contract does not enforce signature checks; the UI displays your wallet’s signature over your pubkey as an audit aid.
- For full security, clients should verify this signature off-chain or extend the contract to do so.
-
Envelope Replay & Integrity
- Each envelope is bound to a fresh ephemeral key; replaying old envelopes yields no new secret.
- Secretbox’s built-in MAC prevents tampering of ciphertext.
-
On-Chain Signature Enforcement
- Modify the registry contract to verify that only the legitimate wallet owner (via ECDSA recover) can register a pubkey.
-
Encrypted Message Storage
- Integrate with IPFS or decentralized storage to submit/retrieve encrypted data blobs.
-
Group Messaging & Key Rotation
- Support rotating user keypairs and retroactively re-encrypting for new keys.
-
UI Polish & UX
- Add countdown timers for auto-logout, secret key expiration, or “burn after reading.”
-
Interoperability
- Build NPM/ESM modules so this functionality can be integrated into larger dApps and frameworks.
With this foundation, you can build rich, private messaging or data-sharing apps on top of public blockchains—ensuring only authorized recipients can decrypt sensitive content, while leveraging on-chain identity and auditability.