Trustless

Trustless-logo-dark       AppStore     Wallet Scrutiny    X-logo

Intro

Hi! Trustless is a fully open-source, non-custodial (only you have access to your keys), privacy-focused, Bitcoin-only mobile wallet. It is very minimalist and easy to use, yet very functional.

Table of contents

Main features

Getting started

To run the project in development mode, you need Node.js 22.14.0 and a setup for ios (xcode) or android (android studio). The required Node version is also recorded in .nvmrc and package.json.

  1. Clone the repository:
    git clone https://github.com/trustlesswallet/trustless.git
    cd trustless
    
  2. Use Node.js 22.14.0:
    node --version
    

    The command must print v22.14.0 before installing dependencies.

  3. Install dependencies:
    npm ci --legacy-peer-deps
    
  4. Run on device / simulator:

    • ios (mac only):
        npm run ios
      

      To run on a physical device, add the -- --device flag and ensure your iphone is connected.

    • android:
        npm run android
      

      Make sure you have an android emulator running or a physical device connected.

    • Manual build (advanced): If the automated commands fail, you can generate the native directories and build manually:
        npx expo prebuild --clean
        cd ios && pod install && cd ..
      

      Then open a trustless workspace file in your ios folder to open the project in xcode. From there you can install the app manually.

Making lightning work

Trustless uses Breez SDK to handle lightning operations. SDK requires an API key to work. For obvious reasons we don’t push the .env file with the key to the GitHub repo. To make lightning work you will have to create your own .env file in the root of the directory and add your own Breez API key there like this: EXPO_PUBLIC_BREEZ_API_KEY=your_api_key . To get the API key just fill out the form on Breez’s website. You will receive a key to your email address. This is completely free.

Reproducible build instructions

Maintainer release checklist 1. Ensure the working tree is completely clean to avoid the dirty flag. 2. **Bump `android.versionCode` in `app.json`.** It must strictly increase on every release regardless of the version name — check the previous release's value and increment by 1. This is a manual step; nothing enforces it automatically. 3. Bump `expo.version` in `app.json` to the new version name (e.g. `3.2.3`). 4. Create and push the new tag: `git tag -a 3.2.3 -m "release 3.2.3"` `git push origin 3.2.3` 5. Go to GitHub Actions → "Reproducible release build" → Run workflow → enter the tag. 6. Wait for the build to complete and download the unsigned APK artifact (`trustless-unsigned-apk`). 7. **Sign with the existing release keystore. Do not generate a new one** unless you are certain the original is permanently lost (see the recovery note below) — every release must share the same signer so users can install updates in place and verifiers can confirm releases come from the same source. `apksigner_path=$(find ~/Library/Android/sdk/build-tools -name "apksigner" | sort -r | head -n 1)` `$apksigner_path sign --ks trustless-release.keystore --ks-key-alias trustless-alias --out trustless-v3.2.3-release.apk app-release-unsigned.apk` 8. Generate the official hash: `shasum -a 256 trustless-v3.2.3-release.apk` 9. Create the GitHub release. Upload the signed package and paste the hash into the release notes. 10. **Verify the signer matches the previous release** before publishing: `$apksigner_path verify --print-certs trustless-v3.2.3-release.apk` Compare the SHA-256 cert fingerprint against the last release. If it doesn't match, stop, something is wrong with step 7. **If the keystore is genuinely lost** (not just misplaced, actually unrecoverable from any backup): generating a replacement is a last resort, not a routine step. It breaks in-place updates for every existing install and resets the signer identity that past releases have been verified against. Only do this if there is truly no way to recover the original file. ```bash keytool -genkeypair -v -keystore trustless-release.keystore \ -alias trustless-alias -keyalg RSA -keysize 2048 -validity 10000 ``` Immediately back the new file up in at least two separate secure locations before using it, so this is never needed a second time. Note the regeneration in the release notes so users and verifiers understand why the signer changed. **Build environment:** Ubuntu (linux/amd64), `reactnativecommunity/react-native-android@sha256:88d93a9282e0f54f84cec7b979da6c5e3f20d87f5be246b75c231838be852fec`, Node.js 22.14.0, NDK 27.1.12297006.

To verify that the official binary was built exactly from the published source code, follow the steps below. This process compares the internal contents of the official signed package (published on GitHub) against a locally built unsigned package (your local build).

The official APK is built on Linux (amd64) inside a pinned Docker container. To get a matching result, your local build must use the same environment.

  1. Download the signed release: Download the official signed file from the GitHub releases page into a new empty directory. (Replace the version number with the one you are testing).
    curl -L -o trustless-release.apk https://github.com/trustlesswallet/trustless/releases/download/3.2.3/trustless-v3.2.3-release.apk
    
  2. Verify the hash matches the one listed on GitHub:
    shasum -a 256 trustless-release.apk
    
  3. Clone the repository and check out the release tag:
    git clone https://github.com/trustlesswallet/trustless.git
    cd trustless
    git checkout 3.2.3
    
  4. Build the local unsigned package: The official APK is built inside GitHub Actions, whose internal workspace path is /__w/Trustless/Trustless. Some native modules embed this absolute build path into their compiled .so libraries, so your local build must be mounted at that exact container path to match, regardless of what your local folder is actually named or where it lives on disk.
    docker run --rm --platform linux/amd64 \
      -v "$(pwd):/__w/Trustless/Trustless" -w /__w/Trustless/Trustless \
      reactnativecommunity/react-native-android@sha256:88d93a9282e0f54f84cec7b979da6c5e3f20d87f5be246b75c231838be852fec \
      bash -c "
      curl -fsSL -o /tmp/node.tar.gz https://nodejs.org/dist/v22.14.0/node-v22.14.0-linux-x64.tar.gz && \
      tar -xzf /tmp/node.tar.gz -C /usr/local --strip-components=1 && \
      rm /tmp/node.tar.gz && \
      node --version && \
      bash reproducibility.sh
      "
    

    Note: on macOS this runs under emulation and will be slow. A native Linux machine is recommended for faster verification.

    If the build fails with a DNS-related error (e.g. Temporary failure in name resolution) while Gradle is downloading dependencies, retry with an explicit DNS server:

    docker run --rm --platform linux/amd64 --dns 8.8.8.8 \
      -v "$(pwd):/__w/Trustless/Trustless" -w /__w/Trustless/Trustless \
      ...
    
  5. Unpack both packages: Android packages are zip archives. Extract both into separate directories for comparison.
    cd ..
    mkdir unpacked-signed unpacked-unsigned
    unzip -q -o trustless-release.apk -d unpacked-signed
    unzip -q -o trustless/android/app/build/outputs/apk/release/app-release-unsigned.apk -d unpacked-unsigned
    
  6. Strip metadata and compare: Remove the META-INF directory from both folders. This directory contains the cryptographic developer signature and timestamps that will never match between a signed and unsigned package. Compare everything else.
    rm -rf unpacked-signed/META-INF unpacked-unsigned/META-INF
    diff -r unpacked-signed unpacked-unsigned
    

    If the diff command returns empty output, the contents are identical bit-for-bit. The build is reproducible.

Important note

For now Android APKs are used exclusively to prove reproducibility. There is no published app on Google Play right now. We don’t recommend sideloading the APK to Android devices, since the app hasn’t been properly tested there.

Lightning reproducibility implications

Trustless uses Breez SDK to handle lightning operations. The SDK requires an API key, which is not committed to the repository. Therefore, proper code reproduction is only possible without an API key, meaning lightning won’t work in a locally built version. With that said, even without the .env file, all lightning-related code is still compiled into the build, guaranteeing code integrity.

Contributing

We welcome contributions to Trustless! Please follow the standard fork-and-pull request workflow.

Contribution process

  1. Fork the repository to your own github account.
  2. Clone your fork to your local machine.
  3. Create a new branch for your feature or fix.
  4. Make your changes and commit them.
  5. Push your branch to your fork.
  6. Open a pull request against the main branch.

Guidelines

License

This project is licensed under the GNU General Public License v3.0 (GPLv3). See the LICENSE file for details.