The MPC Wallet has working cryptography (FROST 2-of-3, Ark integration, MutinyNet support) but significant gaps in security, reliability, and operations. This document captures every finding from a deep codebase audit, prioritized by severity.
- Server:
server/src/main.rs:174-178--Server::builder().serve(addr)with no TLS - Client:
ap/lib/services/mpc_service.dart:192,229,284--ChannelCredentials.insecure() - Impact: All traffic (auth signatures, key shares, signing data) in plaintext. MitM trivial.
- Note: If server runs inside an enclave that only accepts HTTP, TLS may be handled by the enclave's ingress layer.
- File:
server/src/wallet_service.rs:745-1150--dkg_step1/2/3have NOverify_auth()calls - Impact: Anyone can initiate key generation for arbitrary user IDs, create unbounded WASM instances, enumerate recovery IDs via
find_policy_by_recovery_id()(line 480-513)
- File:
server/src/wallet_service.rs:2624-2644-- no auth check - Impact: Any client can broadcast arbitrary transactions
- File:
server/src/wasm_manager.rs:123-165--HashMap<String, UserInstance>grows indefinitely - Impact: Each WASM Store allocates ~10+ MB. Combined with unauthenticated DKG, trivial memory exhaustion.
- File:
server/src/wallet_service.rs:410-412--user_id_hex()accepts arbitrary bytes - Impact: Invalid pubkeys (wrong length, bad prefix) accepted without validation. Potential crashes in crypto ops.
- File:
server/src/bitcoin/electrum.rs:52-90-- reader task ends silently when TCP drops - No reconnection logic after initial connect -- retry loop is startup-only
- No request timeout --
request()hangs forever if reader dies - No write flush --
write_all()never callsflush() - Impact: Server becomes silently broken after any Electrum disconnection. All UTXO queries and broadcasts hang indefinitely.
- No key share backup/export mechanism anywhere in the codebase
- If Sled DB is lost, all user wallets are permanently unrecoverable
- Impact: Single point of failure for all funds
- File:
server/src/wallet_service.rs-- 82 instances ofself.wasm_manager.lock().unwrap() - If ANY WASM operation panics, mutex is poisoned and ALL subsequent requests crash
- Same pattern in
auth/verifier.rs:96,111,122with RwLock
wallet_service.rs:773,886,909--user.dkg_session.unwrap()assumes session existswallet_service.rs:1278--user.round2_secret.take().unwrap()wallet_service.rs:1470,1518,1527--user.signing_session.unwrap()bitcoin/history.rs:178--duration_since()panics if system clock goes backwardsmain.rs:80--panic!("Unknown persistence backend")
ap/lib/services/mpc_service.dartnever passes aHiveCiphertoMpcClientorWalletStore- Signing secret stored in plaintext Hive box on device
- PIN-derived encryption exists but is NOT wired up
- In-flight DKG/signing sessions lost on kill
- No draining of active gRPC streams
- Zero rate limiting on any endpoint
- No
/healthor gRPC health check service
- Only WASM build in GitHub Actions. No cargo test, dart test, or E2E automation.
- Evicts arbitrarily instead of by timestamp expiration
- Combined with no TLS, gives attacker 300s to replay captured signatures
Items 1-5, 18, 21
Items 6, 8-13
Items 14-15, 7, 20, 17, 27-28