Skip to content

About

No description, website, or topics provided.

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

wallet-core

Shared wallet packages for Skylight Wallet (Monero-only) and Spice Wallet (multi-coin).

This repo contains the shared components for both apps, which makes maintenance easier. Each app only pulls the specific pieces that they need.

Packages

packages/
  wallet_infra/      transport, storage, logging, at-rest crypto (no coin or app knowledge)
  wallet_domain/     CryptoWallet, WalletManager, seeds, stores, alias policy
  wallet_monero/     Monero: light-wallet server and full node; polyseed, BIP39, 25-word
  wallet_bitcoin/    Bitcoin and testnet: BIP84 over Electrum
  wallet_ethereum/   Ethereum, Sepolia and ERC-20: JSON-RPC plus Blockscout history
  wallet_openalias/  OpenAlias resolution over Tor, DNSSEC-validated (Rust FFI)
  wallet_fhse/       FHSE: security keys (FIDO2 hmac-secret) guarding the wallet password (C FFI)
  wallet_backup/     metadata backup: payment destinations, tx keys and contacts as sealed files (pure Dart)
  wallet_backup_platform/  the backup's iCloud location (Swift), Auto Backup folder, backup file, screens
  wallet_background/ background sync and incoming-transaction notifications, view-only
  wallet_fiat/       fiat exchange rates from Kraken
  wallet_ui/         shared Flutter widgets, the route policy and the payment-link gate
scripts/             reproducible-build tooling for monero_c and the Tor toolchain
Dockerfile.builder   pinned build image
test_fixtures/       manifest for the generated Monero test wallets

One package per capability, so an app compiles only what it uses.

Alias resolution is split in two. wallet_domain holds the policy: bounds, sanitization, per-coin address validation, and a Tor gate that fails closed. wallet_openalias holds the resolver, which is a Rust build and would otherwise drag a Rust toolchain into every consumer. AliasResolver is the seam; the app installs resolveOpenAlias in main().

Anything that runs without the user is view-only: a Periodic run and Android's Continuous Sync (the "Sync Frequency" setting) open Monero's view-only background copy (or ask its light-wallet server with the view key), a Bitcoin account xpub or an Ethereum address, never the wallet password, the seed or a spend key. When the spend wallet locks, the app's own wallets drop their spending keys the same way. packages/wallet_background/README.md has what each Sync Frequency runs, per platform and per coin, and the details.

Logging is a privacy surface. Seeds, keys and raw server payloads never reach a log; addresses and transaction ids only as salted fingerprints; amounts only as an order of magnitude. Redact in wallet_infra is the toolkit.

Getting started

Requires Flutter 3.47.7. The packages form a Dart pub workspace, so one command at the root resolves all of them.

flutter pub get
flutter analyze
dart format packages/*/lib packages/*/test packages/*/tool

Run the tests for one package from its own directory:

cd packages/wallet_domain && flutter test

Format the three directories listed above rather than .. dart format reads page_width from analysis_options.yaml but ignores its exclude:, so . rewrites the vendored cargokit build tool under wallet_openalias.

Working on an app and the core together

Add a gitignored pubspec_overrides.yaml to the app repo, pointing at your local clone:

dependency_overrides:
  # Every wallet-core package the app's pubspec.yaml pins (all twelve for
  # Spice; Skylight leaves out wallet_bitcoin and wallet_ethereum):
  wallet_infra:
    path: ../wallet-core/packages/wallet_infra
  wallet_domain:
    path: ../wallet-core/packages/wallet_domain
  wallet_monero:
    path: ../wallet-core/packages/wallet_monero
  wallet_openalias:
    path: ../wallet-core/packages/wallet_openalias
  wallet_bitcoin:
    path: ../wallet-core/packages/wallet_bitcoin
  wallet_ethereum:
    path: ../wallet-core/packages/wallet_ethereum
  wallet_fhse:
    path: ../wallet-core/packages/wallet_fhse
  wallet_backup:
    path: ../wallet-core/packages/wallet_backup
  wallet_backup_platform:
    path: ../wallet-core/packages/wallet_backup_platform
  wallet_background:
    path: ../wallet-core/packages/wallet_background
  wallet_fiat:
    path: ../wallet-core/packages/wallet_fiat
  wallet_ui:
    path: ../wallet-core/packages/wallet_ui
  # Plus every override from the app's own pubspec.yaml, copied verbatim:
  # hashlib, bip39, blockchain_utils, web3dart, intl. See the table below.

pubspec_overrides.yaml replaces pubspec.yaml's dependency_overrides rather than merging with them, so it has to restate every override the app already declares, not just the paths. Dropping one usually fails at resolution; dropping hashlib is worse, because it resolves fine and then fails as a compile error inside polyseed.

Consuming from an app

These packages are not published, so an app depends on them by git SHA with a path: into the repo.

  wallet_monero:
    git:
      url: https://github.com/MAGICGrants/wallet-core
      ref: <sha>
      path: packages/wallet_monero

Apps must repeat this repo's dependency_overrides in their own pubspec.yaml. Pub honours overrides only in the package it resolves from, so the ones here apply when you run flutter pub get in this repo and nowhere else. Which ones you need depends on the packages you take:

Override Needed when you depend on
hashlib: 1.19.2 wallet_domain or wallet_monero (both pull polyseed), so also wallet_backup
bip39 at the cypherstack/stack-bip39 SHA any coin package or wallet_domain
blockchain_utils at the cake-tech SHA wallet_bitcoin, wallet_ethereum or wallet_monero
web3dart at the cake-tech SHA wallet_ethereum
intl: 0.20.3 wallet_bitcoin (its bitcoin_base asks for intl: ^0.19.0, which it never imports)

Copy the values from the root pubspec.yaml.

Pins and reproducible builds

Everything is pinned to a commit to make builds verifiable.

  • monero_c: magicgrants/monero_c@757bf8e, a fork carrying Wallet_estimateTransactionFee.
  • tor_ffi_plugin: MAGICGrants/tor@72e455f, a fork of cypherstack/tor native-0.2.0 whose build hook sets a fixed SOURCE_DATE_EPOCH and remaps the cargo home, so the Tor library is reproducible.
  • bip39: cypherstack/stack-bip39@0cd6d54, which fixes an entropy bug upstream still has.

scripts/ holds the build tooling, since this repo owns the pins. Apps get it as a git submodule. The F-Droid recipe already sets submodules: true, so nothing changes there, and F-Droid still builds monero_c from source.

Two of the scripts look odd:

  • pin-tor-rust-toolchain.sh patches cargokit after it is fetched. cargokit hardcodes rustup run stable, a channel that moves over time, and its config accepts only a channel name rather than an exact version, so patching the fetched package is the only way to fix the compiler version. Since native-0.2.0 Tor no longer uses cargokit: its native-assets build hook builds with the toolchain pinned in the package's own rust-toolchain.toml (Rust 1.99.1) and cargo --locked, so the script now matters only for wallet_openalias, whose cargokit copy it patches the same way.
  • fix-linux-moneroc-execstack.sh clears the executable-stack flag on the Linux monero_c library. Without it, hardened kernels refuse to load the library.

Two more guard TLS:

  • check-moneroc-openssldir.sh fails when a monero_c library's OpenSSL would look for certificates or openssl.cnf under its build directory. CI builds in /tmp, so on a user's machine that path is one any local account can create.
  • update-cacert.sh refreshes the CA bundle (see below).

TLS to Monero servers

Every TLS connection to an LWS or a node is verified against one CA set, Mozilla's, shipped as packages/wallet_infra/assets/cacert.pem. The native library gets it as a file (CaBundle.path(), written into the app's own directory); Dart's requests to the same server use CaBundle.securityContext(). The library's OpenSSL cannot use the Android or iOS store, so this is the only way to verify there, and using it everywhere keeps the platforms alike.

Refresh the bundle at least once a year with scripts/update-cacert.sh; the nightly job fails once it is 400 days old.

package:wallet_monero/testing.dart holds the TLS checks: LWS and node, direct and through SOCKS, against loopback servers with a throwaway PKI (package:wallet_infra/testing.dart, regenerated by packages/wallet_infra/tool/make_test_pki.sh). They run against a host build here and against the shipped binaries in each app:

cd packages/wallet_monero
MONERO_LIB_PATH=/path/to/libwallet2_api_c.so flutter test test/native_tls_ffi_test.dart

Light-wallet sends

test/lwsf_send_ffi_test.dart has LWSF build, sign and submit real transactions through the wallet's backend, against a loopback light-wallet server (test/support/fake_lws.dart) that reports outputs the test wallet really owns. It covers a send with change from the first account, the same from a second account, and a sweep of each account back to itself; Monero refuses to build any of them if LWSF gets the change address wrong. It then reads the submitted transaction back and checks that every output pays who it should, and opens to the right amount, and that the amount the wallet reports sending, before and after the send, is the amount the transaction pays.

It needs a host build. The backend loads the library by name in each isolate it spawns, so its directory goes on the loader path too, as in native.yml:

cd packages/wallet_monero
MONERO_LIB_PATH=/path/to/monero_libwallet2_api_c.so LD_LIBRARY_PATH=/path/to \
  flutter test test/lwsf_send_ffi_test.dart

Monero test wallets

Ten prebuilt wallets that pin the seed-to-address derivation. Each is a real wallet directory in one state (restored from a polyseed, from a 25-word seed, node mode, a corrupt cache) plus the answer the code should give for it. A changed address means the derivation changed, which is otherwise silent: the wallet still opens, it just belongs to someone else.

Generated rather than committed; only the manifest is tracked. The seeds are published and the wallets are stagenet, so none can hold real value.

cd packages/wallet_monero
dart run tool/make_fixtures.dart            # generate
dart run tool/make_fixtures.dart --verify   # check against the tracked manifest

Seven of the ten need a real Monero library, so set MONERO_LIB_PATH to the built libwallet2_api_c. Without it the generator builds only the three that need no FFI, and rewrites the tracked manifest without the rest. CI is a fresh checkout, so that is harmless there. Locally, run git checkout -- test_fixtures/wallets/MANIFEST.json afterwards and do not commit it.

Recorded addresses are cross-checked against a second, independent Monero implementation, so this does not just check monero_c against itself.

CI

ci.yml runs on every push: analysis, formatting and the unit tests for every package (wallet_fhse's against its native library, built in the job), plus cargo test for the OpenAlias resolver and a native-crypto job. Everything there uses FakeMoneroBackend rather than a real Monero library.

native.yml builds monero_c and runs wallet_monero's tests against it, generating the test wallets on the way, and runs the TLS checks against the Windows build on Windows. It runs nightly rather than per-push, because building Monero from source takes tens of minutes. So between nightly runs a change to wallet_monero has only been tested against the fake.

Each app's native-tls.yml runs the same TLS checks inside the app on an Android emulator, an iOS simulator, Linux and Windows, against the binaries it ships. It runs on pull requests that change those binaries or the lockfile.

build-monero-c.yml is manual (workflow_dispatch). It builds the library for each target and uploads it as an artifact. Use it when you want a library to develop against locally.

About

No description, website, or topics provided.

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages