Unofficial Signal client for Xous on Precursor.
Prototype. Not for production use.
Built on the Whisperfish Rust stack —
presage,
libsignal-service-rs,
and signalapp/libsignal — rather
than reimplementing the Signal protocol from primitives. The on-device binary
is named xas ("Xous app signal").
The driving project value is end-user verifiability: a user who buys a Precursor should be able to read every line of Rust that ends up on their device. The design therefore leans on upstream community-maintained code (reused as-is or with small reviewable patches) and minimizes bespoke Xous-specific glue.
Smartphones are the primary target for surveillance of journalists and human-rights workers. The Pegasus Project — coordinated by Forbidden Stories with forensic support from Amnesty International's Security Lab — documented commercial spyware on devices belonging to journalists, activists, and dissidents in over 50 countries. Zero-click exploits like BLASTPASS install spyware without the target tapping anything. End-to-end encryption in apps like Signal is necessary but insufficient when the host operating system itself is compromised — once Pegasus is on the phone, it can read messages and turn on the microphone and camera regardless of which app you used.
Precursor is an open-hardware mobile device built around the principle of evidence-based trust: every layer from the FPGA bitstream up through the Xous microkernel is inspectable and reproducible, and Precursor generates and seals its own keys without relying on factory secrets. Until now most Precursor applications have focused on credential storage (password managers, FIDO/U2F, cryptocurrency wallets). This project fills the missing piece — a hardened communications device — by porting Signal's secondary-device protocol to Xous so that messages a journalist or activist sends from their pocket are protected by hardware they can audit themselves.
Short-term goal: a hardened communications device for journalists and activists who already have access to Precursor.
Long-term goal: a cheaper successor (the Betrusted ASIC currently in development) that puts this same threat model in reach of users in the Global South, where surveillance pressure is highest and where activists and journalists most often lack resources to recover from compromise.
Scope tradeoff: Precursor's hardware constraints (16 MiB total RAM, no GPU, single-screen monochrome 336×536 display, no audio/video hardware) mean this client implements a deliberately minimal Signal feature set. The goal is "your most sensitive conversations on hardware you can audit," not "feature parity with the mobile app." The feature support matrix below makes the tradeoffs explicit.
Verified working on Precursor PVT2 hardware unless noted. Each capability that maps to a Signal app feature is listed; the third column says whether the gap is fundamental (hardware can't do it) or a roadmap item (planned but not yet built).
| Feature | Status | Note |
|---|---|---|
| Link as a secondary device (QR scan) | ✅ | Boot, PDDB unlock, QR scan, decrypt ProvisionEnvelope, register, persist |
| Receive 1:1 text messages | ✅ | Near-instant once linked. Verified on hardware (2026-05-11) receiving DMs from multiple distinct senders. |
| Send 1:1 text messages | ✅ | Latency 1–4 min — Signal edge-server WS rotation race; transport refactor on roadmap |
| Conversation list (Home) | ✅ | Per-thread last-message + relative timestamp + unread indicator |
| Per-thread message view | ✅ | Optimistic-render compose; auto-mark-read on thread open |
| Group chats (read or write) | ❌ | Roadmap. Adds ~1 MiB of state per group + UI surface |
| Disappearing messages | ❌ | Body still displays but no timer indicator; xas doesn't honor the expire_timer (no auto-delete). Roadmap |
| Typing indicators | ❌ | Roadmap. Low priority |
| Read receipts (sending) | ❌ | Auto-mark-read on thread open updates UI state only; xas doesn't call any send-receipt API. Roadmap |
| Stories | ❌ | Out of scope. Built around media |
| Feature | Status | Note |
|---|---|---|
| Image / video / file attachments (send or receive) | ❌ | Out of scope. Display + storage budget too small for media-first UX |
| Voice notes | ❌ | Hardware: no microphone codec wired through Xous |
| Stickers | ❌ | Out of scope. Display is monochrome |
| Emoji reactions | ❌ | Inbound reactions arrive as DataMessages with empty body and are silently dropped at process_received. No outbound UI either. Roadmap |
| Feature | Status | Note |
|---|---|---|
| Voice calls | ❌ | Hardware: no audio path on Precursor |
| Video calls | ❌ | Hardware: no camera, no codec |
| Feature | Status | Note |
|---|---|---|
| Display name + phone number on Profile | ✅ | Read from registration data |
| Profile editing (name / picture / about) | ❌ | Roadmap. Read-only today |
Username (@alice.42) on Profile |
❌ | No API to read one's own Signal username in our build (RegistrationData has no username field; Profile struct has no username field). The primary phone holds that state |
| Username lookup in "New chat" | ✅ | F1 → enter name.42 → presage's lookup_username resolves to ACI; UI opens a Thread |
| Phone-number lookup in "New chat" | ❌ | Needs CDSI which requires boring-sys (BoringSSL) — disabled in this build because it can't target rv32-xous |
| Logout | Stub today (tells the user to wipe PDDB manually); real implementation on roadmap | |
| Multiple linked accounts | ❌ | Single-account device by design |
| Primary registration (this device IS the primary) | ❌ | Out of scope. Secondary-device only — your phone stays primary |
| Feature | Status | Note |
|---|---|---|
| Wi-Fi (2.4 GHz only — Precursor is single-band) | ✅ | Configured via shellchat (wlan off; wlan on; ssid scan; wlan status); no in-app onboarding |
| Hardware-rooted key sealing (PDDB) | ✅ | Inherited from Xous; xas writes registration + sessions through presage-store-pddb |
| Sealed sender (Signal Protocol) | ✅ | Handled transparently by libsignal-service-rs |
| PQXDH (post-quantum key agreement) | ✅ | libsignal default; verified working on rv32 |
xas leverages the Signal Protocol implementation in
signalapp/libsignal
(via libsignal-service-rs
and presage). Cryptographic
primitives — Double Ratchet, X3DH/PQXDH, sealed sender, prekeys,
identity keys — are not reimplemented here. See
docs/ARCHITECTURE.md for what xas adds
on top of those libraries (mostly: Xous IPC plumbing, a
sync-blocking-on-async transport bridge, and the GAM-rendered
UI).
BUILDING.md— clone-to-running instructions for both hosted-mode emulator and Precursor hardware pathsdocs/ARCHITECTURE.md— how xas works, written for a Rust developer with passing crypto knowledgetests/README.md— overview of the four testing approaches (unit / hosted / Renode / Precursor), pros and cons, and the dev/main branch conventiontests/precursor/README.md— hardware-test workflow: build, flash, watch UART (read this BEFORE running any flash command — its "Brick prevention" section is non-negotiable)
End-to-end build instructions for both the hosted-mode
emulator (Linux x86_64, no hardware needed) and the Precursor
hardware path live in BUILDING.md. It's
written to be followed cold — clone, set up toolchain, build,
run, link to a Signal account.
For testing, see tests/README.md, which
compares the four available approaches (unit tests / hosted /
Renode / Precursor) with a pros-cons table so you can pick the
right one for the change at hand. Hardware-test scripts (build,
flash, watch UART) and the brick-prevention rules live in
tests/precursor/README.md — read
it before running any flash command.
xas depends on three upstream patches. All three have PRs filed
upstream and are in review; until they merge, the
xous-app-signal branch of tunnell/xous-core
and the vendored copy of libsignal-service-rs carry the fixes.
betrusted-io/xous-corenet-service encoding fix — betrusted-io/xous-core#877. The kernel writesNetErrorcodes at byte 4 of the response buffer; the Rust stdlib's Xous backend reads from byte 1 in the recv path. The mismatch madeErrorKind::TimedOutunreachable fromTcpStream::recv— fatal for any long-lived WS that usesset_read_timeoutto interleave reads and writes. The fix mirrors the code at byte 1 too.BUILDING.mdinstructs you to clone thexous-app-signalbranch oftunnell/xous-corewhich carries this (the branch is the upstream PR commit + the CNAME-chain DNS fix below + a small hosted-mode PDDB tweak).whisperfish/libsignal-service-rskeepalive tolerance — whisperfish/libsignal-service-rs#431 (draft). Upstream closes the WS the moment any keepalive is outstanding. Under any non-zero scheduling jitter (e.g. rv32) this races and closes healthy connections. The PR adds an opt-inwith_max_outstanding_keepalives(...)constructor so callers like xas can tolerate the race without changing default behavior for other consumers. The patch lives invendor/libsignal-service-rs/in this repo as a constantMAX_OUTSTANDING_KEEPALIVES = 3(semantically equivalent for our use); the vendored copy will be re-aligned to the builder shape after the upstream PR merges.rust-lang/rustXous std-side recv encoding — rust-lang/rust#156414 (draft). The long-arc fix that makes #1 unnecessary at the std level — change the recv decode to read byte 4 (matching the send decode). Rust toolchainr?cycles take weeks; the kernel-side mirror in #1 is the immediately-shippable workaround. Once #3 lands and propagates to a stable Rust release, the byte-1 mirror in #1 becomes belt-and-suspenders rather than load-bearing.
Once #1 merges, BUILDING.md will be updated to point at stock
betrusted-io/xous-core. PR #2's merge triggers a re-vendor of
libsignal-service-rs. PR #3 is asynchronous and does not
gate either of the above.
xous-app-signal/
├── crates/
│ ├── presage-store-pddb/ storage trait impls over PDDB
│ ├── xous-net-bridge/ sync TLS + WS pump + channel bridge
│ ├── xous-pddb-ipc/ hand-rolled PDDB IPC client
│ ├── xous-modals-ipc/ hand-rolled modals IPC client
│ ├── xous-signal-worker/ presage::Manager on worker thread + Cmd/Event channels
│ ├── xous-app-signal/ binary entry point (binary name: `xas`)
│ └── src/stdin_ui/ stdin-driven UI fallback for standalone runs (no Xous server)
├── docs/ ARCHITECTURE.md (reader's-eye-view of the codebase)
├── tests/ hosted-mode + Renode + precursor (hardware) test harnesses
└── vendor/ vendored forks of presage / libsignal-service-rs / curve25519-dalek
This project is dual-licensed under the terms of the AGPL 3.0 license, as
a derivative work; and under the terms of the Apache 2.0 license.
SPDX-License-Identifier: AGPL-3.0 OR Apache-2.0
You can choose between one of them if you use this work.
We have a desire to license xas under Apache-2.0 so that elements may be readily incorporated into other future Xous related projects. We are required to license any derivative works of libsignal under the AGPL-3.0 license.
Contributions are welcome via pull request against the dev
branch. See tests/README.md for the
branch convention and what release-cycle gates a PR has to
pass before it can land in main.
AI-assisted contributions are explicitly welcome, on one condition: disclose them. AI coding agents have been used in this codebase's development, and the project is honest about that — both because end-user verifiability is a stated value and because reviewers benefit from knowing where to look harder.
Concretely:
- If you used an AI agent to help write the diff, mention it in the PR description. A short note is fine — "drafted with an AI agent and reviewed line-by-line" or similar. No need to name the specific tool.
- The author of the commit is still you. AI agents are tools, not co-authors. Don't add AI-attribution trailers to commit messages.
- Apply the same review discipline you'd apply to any code: the PR is your work in the sense that you're vouching for it. Read every line you submit.
The reason for the disclosure norm is alignment with the project's threat model: users of this client need to be able to audit it. Knowing which sections were AI-assisted lets reviewers weight their attention.