A desktop Monero (XMR) wallet, structured after Wasabi Wallet (.NET + Avalonia + MVVM). It is password-protected, encrypted at rest with AES-256-GCM, and supports a duress password that opens a decoy wallet (and can optionally wipe the real one).
This is a solid, reviewable foundation with a fully-implemented, unit-tested security core — but it has not been independently audited. Any wallet that holds meaningful money should be.
The single most dangerous thing a wallet author can do is hand-roll Monero's cryptography
(ring signatures, RingCT, bulletproofs, stealth addresses). This project deliberately does
not do that. Instead it drives the official monero-wallet-rpc binary — the same
crypto that Monero's own tools use — and focuses its own code on the part where correctness
is achievable and verifiable: the encrypted vault, the KDF, and the duress logic.
This is an educational, work-in-progress Monero wallet. It has not had a professional security audit. It may contain bugs that cause permanent, irreversible loss of funds — Monero transactions cannot be reversed or refunded.
- Do not store real (mainnet) XMR in it. Use testnet or stagenet only.
- Provided as-is, with no warranty of any kind (see LICENSE).
- The "wipe real wallet on duress" option is irreversible and can destroy your seed.
- Always keep an independent offline backup of your 25-word seed.
If you are looking for a wallet to actually hold Monero, use an established, audited one (e.g. the official Monero GUI/CLI, Feather, or Cake). Read SECURITY.md in full before doing anything with this project.
XaultWallet.Core ← no UI; unit-testable
├── Security/
│ ├── SecureBuffer pinned, zeroed memory for secrets
│ ├── VaultCrypto Argon2id KDF + AES-256-GCM (authenticated)
│ ├── VaultFile on-disk format: 2 equal-size, indistinguishable slots
│ ├── VaultManager create / unlock / change-password / duress policy
│ └── PasswordStrength conservative entropy estimate
├── Models/ WalletSecrets, ProfileKind (Real | Duress), network, …
└── Monero/
├── MoneroRpcClient JSON-RPC 2.0 over HTTP digest auth
├── MoneroProcessManager launches monero-wallet-rpc on a random localhost port
└── MoneroWalletService balance / send / receive / history
XaultWallet.Desktop ← Avalonia 11 app (MVVM via CommunityToolkit.Mvvm)
├── unlock / create-wallet / dashboard views
└── the UI is IDENTICAL whether the real or the duress wallet is opened
XaultWallet.Core.Tests ← xUnit tests for the crypto + duress behaviour
The vault file always contains exactly two equal-sized encrypted slots. One holds the
real wallet; the other holds either the decoy wallet or — if you never set a duress
password — uniform random bytes that are indistinguishable from an encrypted slot. When you
type a password, every slot is tried; whichever slot's AES-GCM authentication tag verifies
is the one that unlocks. There is no plaintext password comparison anywhere, and slot
position is randomised, so an attacker who seizes the file cannot prove a hidden wallet
exists. ProfileKind (Real vs Duress) lives inside the encrypted payload, so it is only
visible after a correct password decrypts a slot.
Two duress policies are configurable at creation time:
- Decoy (default): silently open the second wallet. Safest for your funds.
- Wipe: additionally overwrite the real slot so it can't be recovered from this device.
- .NET 8 SDK — https://dotnet.microsoft.com/download
- Official Monero CLI tools — download
monero-wallet-rpcfrom https://www.getmonero.org/downloads/ and either place it next to the built app or set its path inAppServices.WalletRpcBinaryPath. - A Monero daemon to sync against — a local
monerod(defaulthttp://127.0.0.1:18081) or a remote/public node.
The project targets .NET 8. With the .NET 8 SDK installed, one command builds and tests everything:
./build.sh # Linux/macOS (restore + build Release + unit tests)
./build.ps1 # Windows PowerShellOr run the steps manually:
dotnet restore
dotnet test # security-core + hardening unit tests
dotnet run --project src/XaultWallet.Desktop # launches the wallet- Requirements: Visual Studio 2022 17.8 or later with the .NET desktop development workload (this includes the .NET 8 SDK). Nothing else needs installing to build.
- Extract the download and double-click
XaultWallet.sln. XaultWallet.Desktopis listed first in the solution, so it is the default startup project — press F5 (debug) or Ctrl+F5 (run). The first build restores NuGet packages (Avalonia, CommunityToolkit.Mvvm, Konscious.Argon2), so it needs internet once.- Run the unit tests from Test Explorer (Test ▸ Run All Tests). The integration tests
are skipped unless the
XW_WALLET_RPC/XW_DAEMONenvironment variables are set — seeSTAGENET-TESTING.md.
At runtime the wallet drives the external monero-wallet-rpc binary from the official
Monero CLI tools — it is not compiled into this solution. Launch the app once, open
Settings, and point it at that binary (the Test binary button confirms it works).
On first launch, open Settings (top-right) to point XaultWallet at your
monero-wallet-rpc binary and daemon, and use the Test binary / Test daemon buttons
to confirm both work before creating a wallet. Settings are saved to
%APPDATA%/XaultWallet/settings.json (non-secret: just the binary path, default daemon,
network, and refresh interval).
For a full end-to-end walkthrough on Monero's test network, see
STAGENET-TESTING.md. Automated integration tests live in
tests/XaultWallet.IntegrationTests and are skipped unless XW_WALLET_RPC and XW_DAEMON
are set, so the default dotnet test stays green without a node.
The vault file is written to %APPDATA%/XaultWallet/vault.xv (Windows) or the platform
equivalent. It is the only thing that persists on disk — the Monero wallet files
themselves are restored from your seed into an ephemeral temp directory on unlock and
shredded on lock.
Complete and unit-tested: the vault format, Argon2id + AES-256-GCM, the dual-slot duress mechanism (decoy + wipe), password change, atomic file writes.
Seed generation & backup: new 25-word Monero seeds are generated by the official
monero-wallet-rpc (create_wallet + query_key) in a throwaway instance — never
hand-rolled. The create flow forces a backup step: you must either verify three random
words or download a plaintext backup file (with a prominent warning) before the vault is
sealed. Importing an existing seed is also supported, for both the real and decoy wallets.
Implemented but needs integration testing against a live daemon: the RPC client,
process manager, and send/receive/history flows. These depend on the monero-wallet-rpc
binary being present.
Intentionally out of scope for this foundation: hardware-wallet support, multisig, a bundled node, and a professional security audit.
This build has had a dedicated hardening pass. Notable behaviour:
- Process supervision: monero-wallet-rpc is launched with random localhost port + credentials; both stdout and stderr are drained (so the child can't block on a full pipe); early exits are detected and surfaced with the captured stderr tail; and the child process and its ephemeral temp dir are always cleaned up on failure, cancellation, or app close.
- Readiness is probed with
get_version, which responds with or without an open wallet, so neither restore nor generation hangs waiting on the wrong signal. - Imported seeds are validated (they must actually open a wallet) before they're sealed into the vault, so a typo can't produce an unopenable vault. If the RPC binary is missing, it falls back to a word-count check and tells you validation was skipped.
- The dashboard never blocks on a synchronous refresh: it opens the wallet, then polls balance/height/history on a background timer. A manual refresh is bounded by a timeout. Startup failures show a Retry button instead of a dead screen.
- Vault integrity: the file format is length- and header-checked, KDF parameters are bounds-checked on load (a tampered header can't force a multi-GB allocation), saves are written to a temp file, flushed to disk, then atomically swapped in.
- Graceful shutdown intercepts window close, tears the wallet down (killing the child and shredding temp files), then exits. Unhandled/background exceptions are logged, not fatal.
- Logging goes to
%APPDATA%/XaultWallet/logs/and deliberately never records seeds, passwords, keys, or RPC credentials.
This is at a beta-on-stagenet bar, not a "trust it with savings" bar. It was written carefully but, being a wallet, still needs the following before mainnet use:
- A clean
dotnet restore && dotnet testand manual compile on your machine. - End-to-end testing against stagenet: generate a wallet, receive, send, restart, restore,
and exercise the duress password — with a real
monero-wallet-rpcand daemon. - A professional third-party security audit. Do not skip this for a wallet.
The downloadable seed backup is intentionally plaintext (that's what a backup is); the in-app
warning says so. See SECURITY.md for the threat model and the honest limits of the
memory-hygiene and plausible-deniability guarantees.
This is a personal, educational project shared in the open. Review, issues, and pull requests are welcome — extra eyes on a self-custody wallet are exactly the point of open-sourcing it. For anything security-sensitive, please follow the private reporting process in SECURITY.md rather than opening a public issue.
Released under the MIT License — see LICENSE. Provided as-is, with no warranty.
Built on the official Monero tools (monerod, monero-wallet-rpc) — this project drives them
rather than reimplementing Monero's cryptography. UI built with Avalonia.
Brought to life in harmony — https://dboudreau.dev