Signal 如何加入免電話號碼註冊選項,以及零知識證明實際保護甚麼
How Signal Made the Phone Number Optional, and What the Zero-Knowledge Proof Actually Protects
Signal 在 Android 8.28.0 beta 加入可選的免電話號碼註冊,並以零知識證明避免付款與帳戶連結。功能仍處於 Android beta,美國一次性付款為 3 USD;零知識證明不驗證用戶身分或電話號碼,付費才是防垃圾帳戶措施。文章亦介紹自建服務可用匿名憑證處理邀請、會員資格或限流驗證,並提醒憑證竊取、實作錯誤和中繼資料洩漏等風險。
Signal has long had a privacy contradiction at its core: the most secure mainstream messenger still required a phone number to sign up. That number became a de facto identity anchor, linkable to you even if you never shared it with contacts. Signal has now added an optional way around that, and zero-knowledge proofs (ZKPs) are part of it, though not in the way most coverage suggests. Phone-numberless registration landed in the Signal Android 8.28.0 beta on 16 September 2026. It is optional, Android only, and it costs a one-time in-app purchase: 3 USD in the United States, with per-country differences, checked 29 September 2026. The zero-knowledge proof is not what replaces the phone number. It is what stops Signal from linking that purchase to the account it creates. This post breaks down what Signal actually built, and what self-hosters can and cannot take from it.
💡 Why This Matters
Phone numbers are lousy identifiers for a private messaging app. They are tied to real-world identity through carriers, they can be ported or spoofed, and they hand a centralized third party leverage over who can register. Signal has been aware of this tension for years, and their community forums have tracked the demand for phone-number-free registration since at least 2018.
What shipped is narrower than proving eligibility with a zero-knowledge proof. A numberless account is identified by an Account ID and an Account Key, which Signal Android lead Greyson Parrelli describes as "a username and password." Nothing is proved about the person at all. The anti-spam control is the fee, and the zero-knowledge proof exists so that the payment cannot be tied back to the account. That is still a real change to the trust model, but it is worth being precise about which part the cryptography is doing.
🔍 What Zero-Knowledge Proofs Actually Do Here
A zero-knowledge proof lets one party (the prover) convince another party (the verifier) that a statement is true, without revealing anything beyond the truth of that statement itself. In Signal's registration flow, the proof is not about you. It is about the payment. Signal's own description, from Parrelli's 16 September 2026 post on the Signal Community forum: "The payment uses the same zero-knowledge proofs our donation system uses so that there is no link between your payment and your account."
The machinery being reused is the anonymous credential code behind Signal's donation badges, which lives in the zkgroup and zkcredential crates inside libsignal. Note what is not happening: nothing verifies a phone number, and nothing verifies a username either. Registration simply mints an Account ID and Account Key. The credential work is confined to the purchase, so that redeeming it proves a payment was made without identifying which payment.
Concretely: you never give Signal a phone number in this flow, because no number is involved at any point. One caveat that the cryptography does not address. The purchase currently has to go through a Play Store in-app payment, so Google still sees a Signal purchase on your account even though Signal cannot link it to the Signal account. Signal says more payment methods are planned, and that a device without Play Services cannot buy an account today.
🏗️ The Architecture in Plain Terms
Here is the rough flow, based on how ZKP-based credential systems like this typically work:
- Credential issuance: A trusted issuer issues a blinded credential to the user. The issuer signs it without seeing the full content. Signal is not federated, so in Signal's case the issuer is Signal.
- Proof generation: The client uses the credential to generate a zero-knowledge proof that says "I hold a valid credential issued by this authority" without revealing the credential itself.
- Server-side verification: Signal's registration endpoint verifies the proof cryptographically. If it checks out, registration proceeds. No phone number is logged.
- One-time use: The proof is typically bound to the session or includes a nullifier so the same credential cannot register multiple accounts.
One-time redemption matters for abuse prevention in any scheme like this. Read the four bullets above as a description of the general pattern rather than of Signal's registration endpoint, because in Signal's case the abuse control is the price, not the cryptography. Parrelli's stated reason for charging anything at all: "Spammers, unfortunately. If it was free, spammers would acquire massive numbers of these accounts and ruin our network." Signal has also said it may raise the price if numberless accounts turn into a spam vector.
📋 Status as of 29 September 2026
What is actually shipping, from Signal's own release notes and forum posts rather than press coverage:
- Platform: Android only, and still in beta. It arrived in the 8.28.0 beta on 16 September 2026, and the 8.29 beta notes on 23 September 2026 say Signal is "keeping phoneless registration in beta for another week to get more testing." Signal iOS release notes through 8.30.0 do not mention it, and Signal Desktop has no standalone registration at all.
- Cost: a one-time in-app purchase, 3 USD in the United States, with prices differing by country. Play Store in-app payment is the only method at the moment, so a device without Play Services cannot buy an account.
- Recovery: none. The Account ID and Account Key are the only credentials, and Signal states plainly that there is no other recovery mechanism if you lose them. These accounts have no PIN, because the Account Key replaces it.
- Reachability: a username is optional, and without one the account is, in Signal's words, "completely unreachable" except in conversations you start yourself.
- Existing accounts: you cannot remove the phone number from an account you already have. Signal says that may become possible later.
- Two-factor: TOTP now, with passkeys and hardware keys promised. It is not yet available on phone number accounts.
🧰 What Self-Hosters Can Learn From This Pattern
You do not need to be Signal to apply this pattern. The ZKP credential model is useful any time you want to gate access to a service without storing identifying data about your users. Here are a few real scenarios where this architecture applies:
- Invite-only self-hosted apps: Issue blinded invite tokens. Users redeem them with a ZKP so you know the token is valid but cannot trace which token a specific account used.
- Age or membership verification: Prove "user is over 18" or "user is a paid member" without storing birthdate or subscription ID server-side.
- Rate limiting without tracking: Issue anonymous rate-limit tokens. Each token proves the holder is allowed N requests without tying requests back to an IP or user ID.
Libraries worth looking at if you want to experiment with this pattern include zk-kit (JavaScript/TypeScript), bellman and arkworks (Rust), and snarkjs for browser-compatible proof generation. For the credential-issuance side, Signal's own zkgroup is open source and worth reading even if you do not use it directly. Check each project's documentation for current API stability before building on them.
🛡️ Security Considerations
ZKPs are not magic. There are real failure modes to understand before you rely on this pattern.
- Trusted setup: Some ZKP systems (like Groth16) require a trusted setup ceremony. If that ceremony is compromised, proofs can be forged. Systems using transparent setups (STARKs, or PLONK with a universal SRS) reduce this risk.
- Credential theft: If an attacker steals a user's credential before it is blinded, the ZKP does not help. The issuance step still needs to be secured.
- Implementation bugs: A ZKP circuit that does not correctly encode the constraint you think it does will verify invalid proofs. Formal audits of ZKP circuits are expensive and specialized. Do not roll your own for production without one.
- Metadata leakage: Even with a valid ZKP, timing, IP addresses, and request patterns can still de-anonymize users. Layer your ZKP registration behind Tor or a VPN if anonymity is the goal.
🔧 Practical Next Steps If You Want to Explore This
If you want to get hands-on with ZKP-based authentication, here is a reasonable starting path:
Step 1: Read Signal's anonymous credential code. The standalone signalapp/zkgroup repository is archived and GitHub now points it at signalapp/libsignal, so start there. The rust/zkgroup and rust/zkcredential crates are where credential issuance and verification live. Checked 29 September 2026.
Step 2: Run through the snarkjs tutorial to build a trivial ZKP circuit locally. It uses Circom for the circuit definition and outputs a Solidity or JavaScript verifier. The two tools do not install the same way:
# snarkjs does ship on npm
npm install -g snarkjs
# circom does not. The "circom" package on npm is circom 1,
# which upstream marks deprecated. circom 2 is a Rust
# compiler you build from source:
# https://github.com/iden3/circom
Step 3: Design your constraint. Write out in plain English what your proof needs to show, for example "the prover knows a value x such that hash(x) equals a public commitment." Translating that into a Circom circuit is the hard part, and the circom documentation is the right reference. Use whichever documentation link the iden3/circom README currently gives, because docs.circom.io failed to resolve from two separate clients when this was checked on 29 September 2026.
Step 4: Wire up your verifier to your registration or access endpoint. The verifier function takes a proof and public inputs and returns true or false. That boolean is your gate:
// Pseudocode for a Node.js endpoint
const { groth16 } = require("snarkjs");
app.post("/register", async (req, res) => {
const { proof, publicSignals } = req.body;
const vKey = require("./verification_key.json");
const valid = await groth16.verify(vKey, publicSignals, proof);
if (!valid) return res.status(403).json({ error: "Invalid proof" });
// Proceed with account creation
});
Step 5: Add nullifier tracking. Store the nullifier from each proof in a database table. On each registration attempt, check that the nullifier has not been used before. This prevents credential reuse without revealing who the credential belongs to:
-- PostgreSQL example
CREATE TABLE used_nullifiers (
nullifier TEXT PRIMARY KEY,
used_at TIMESTAMPTZ DEFAULT now()
);
✅ Wrap Up
Signal has not dropped the phone number requirement. It has added an optional, paid, Android-beta alternative to it, and free phone number registration remains the default path. What the feature does demonstrate is narrower and still useful: an anonymous credential can sit between a payment and an account so that the two cannot be correlated, even by the service that issued both. That is the principle worth borrowing, wherever you currently store an identifier only to prove that someone paid or was invited.
The tooling is approachable enough that a small homelab project or self-hosted app can implement a basic ZKP gate without a cryptography PhD. Start with snarkjs, read the zkgroup source for inspiration, and design your constraints carefully. The hardest part is not the math, it is being precise about exactly what you need to prove.
Related from the lab
- The Design of Everyday Cryptography: A Homelab Primer
- Reclaim Your Phone: Self-Hosting Android Alternatives for Real Ownership
- You Should Self-Host Your Email (Yes, Even in 2026)
Originally published at codewithromi.com.
來源:DEV Community · dev.to