Lattice Network · Android

Scarica Lattice Pulse

Messaggi, foto, note vocali e chiamate cifrati end-to-end con crittografia post-quantistica ibrida (ML-KEM-768 + X25519, triplo ratchet). Il nostro server vede solo buste opache: nessun contenuto, nessun indirizzo IP conservato. L'app non è sul Play Store per scelta: l'APK si scarica da qui e si verifica da soli con l'impronta SHA-256.

Ultima versione
v1.1.1
⬇ Scarica l'APK · 61 MB
Versione1.1.1 (build 312)
RequisitiAndroid 8 o successivo
Impronta SHA-256af124b1286d25b74bbd18ae18e26a313034d7f54067a37376587fff7e855e2d8

Versione precedente, se ti serve tornare indietro: lattice-pulse-1.1.0.apk

Storico delle versioni e impronte

Ogni impronta qui sotto si rifà in un comando: sha256sum lattice-pulse-<versione>.apk. Dalla v6.6.4 ogni rilascio porta anche la firma ML-DSA-65 del suo manifest (latest.json): è la firma, non la nostra parola, a dire che quell'impronta l'abbiamo dichiarata noi. Le versioni precedenti restano scaricabili con l'impronta calcolata sul file: archivio, non certificato — e lo scriviamo invece di far finta.

VersioneImpronta SHA-256DimensioneDataCertificazione
v8.2.3 · build 3091fde3023ed158698278a1b6cd9ed2834eb27ca8995329bc73f875e4c350827a660.8 MB2026-09-28firmata ML-DSA-65
v8.2.2 · build 3081c216b0444296777d9625b5cd20b7ca1f03ef1d719c08a234e025f668b3aa9d860.8 MB2026-09-27firmata ML-DSA-65
v8.1.6 · build 306f36e09398015b07219011cf01c02cbbaa00f4c1b9be26af9dd872223e1090d8c60.8 MB2026-09-27firmata ML-DSA-65
v8.1.5 · build 305ebad8e18a679d48b4d54be68e4e8939a1c63b054983ceafd949d3c5147abd15b60.8 MB2026-09-27firmata ML-DSA-65
v8.1.4 · build 304a170eb98da088160e04899db382f0d58522b0a241192ae66c66cfef3989172f360.8 MB2026-09-27firmata ML-DSA-65
v8.1.3 · build 303313ec857b8a0e9c3e05c113b58e2efbc5a435d959df45ac85efbbd93a83ae2f260.8 MB2026-09-27firmata ML-DSA-65
v8.1.2 · build 302cc08b85a2223d2fc278fc2389ef39add28b839495623dafb235fb9f16b01699160.7 MB2026-09-26firmata ML-DSA-65
v8.1.0 · build 301262c872f6eb64e1f6a9ae2452dd3360a93028dc04ec9178f05fd46a28acffd5a60.7 MB2026-09-26firmata ML-DSA-65
v8.0.7 · build 300d2e1da9bd4d085445423d4af75056a8e77886412c4b8cf5b8c49f7c8b3e7cdbb60.6 MB2026-09-26firmata ML-DSA-65
v8.0.5 · build 2999e3de5e19feb03735f261fb455ef87ab2ef4a965499c15e7d2712fdd920bafc360.6 MB2026-09-25firmata ML-DSA-65
v7.9.6 · build 29801292df06d0aec76e9434e84634a24e0353821dd92795e6c7cd0636ec5cc214060.6 MB2026-09-25firmata ML-DSA-65
v7.9.0 · build 297a62d218076ac8b4d5e7aa9442875e63083463388a4b2992a786aa8e5a6b0203260.6 MB2026-09-25firmata ML-DSA-65
v7.8.0 · build 296f6b90aceae41bad4ca20670cebbfb9e5a393dd62f3ee950c3ea20584cd35249a60.6 MB2026-09-24firmata ML-DSA-65
v7.7.0 · build 2952a2a7d091711f2a01fe6d615fba77ef1a0bed79b14cb6fd7410569a62c870ece60.6 MB2026-09-24firmata ML-DSA-65
v7.6.0 · build 29496b09c6b9867586eb9085a4e697c593039b0199532058657591116d3b8a33bf960.6 MB2026-09-24firmata ML-DSA-65
v7.5.5 · build 291865849fe8cc213bd801aef1a1da995ffe2e56ac68797c91dc4070773f52ced0460.6 MB2026-09-24firmata ML-DSA-65
v7.5.4 · build 289f0d094f7caa7c0d93bb924356d4a61a6ac27865d238dd6e514af19a128e5c77160.6 MB2026-09-22firmata ML-DSA-65
v7.5.2 · build 28728164eba7bb0896c60a6e3d954386b2edadca18c76ed07f4bc508f66178c91d260.6 MB2026-09-22firmata ML-DSA-65
v7.5.0 · build 285a5fdc42eb90df5a6a2adcac491adddd8ced7577cf2b1eb6d82d33703f58ce7a360.6 MB2026-09-22firmata ML-DSA-65
v7.3.2 · build 283c811ddc27af4d4f7c1d972191cd9caff33b599603b4248d3e6b18cf507081b5f60.6 MB2026-09-22firmata ML-DSA-65
v7.3.1 · build 28278ddbefe13fd37b0d8b90fc766e78fd66f87be041c1aaa51d7df143a08d40d3560.6 MB2026-09-22firmata ML-DSA-65
v7.3.0 · build 281a9549edcee4072dfbef356234a76dfd9475bbcb4422218766b8b4eff284c46db60.6 MB2026-09-21firmata ML-DSA-65
v7.2.2 · build 280a1c730f6324739659d80603e9c85172ffe02b2edb5e0ae55195b6228fed6584360.6 MB2026-09-21firmata ML-DSA-65
v7.2.1 · build 2794bf0583d1cf0c86656effa181e34d722185370466e01b02d292c5229ec009fc261.5 MB2026-09-21firmata ML-DSA-65
v7.2.0 · build 2785ff50b7cfe949e224b16e3d1e148e33f7f16e9eabccc1128037d50e9eeeb241b61.5 MB2026-09-21firmata ML-DSA-65
v7.1.0 · build 277b2a596335083552107ee4c964e729f93167b5479a4c4e2966a76763b4a1a92bd61.8 MB2026-09-20firmata ML-DSA-65
v7.0.3 · build 276daf243a9cc33775ee57228faa6a0c892435a53d4fc64d3132fb1ed72fbfbe7fb61.8 MB2026-09-20firmata ML-DSA-65
v7.0.2 · build 275aec32e0dfc7aa403fea5f100160ef2bc9cf4cdb7cd8ff31ddbbe623c4d913acc61.8 MB2026-09-20firmata ML-DSA-65
v7.0.0 · build 2742e79365ae5bcfccee5cfbe79830e29bb6aee228308619b1434a37c3c00348f2761.5 MB2026-09-20firmata ML-DSA-65
v6.9.2 · build 2724316b235aafced587b2acd576e206ff35c6ae5d46dc003ac969ac1e08b81aaf361.5 MB2026-09-19firmata ML-DSA-65
v6.9.1 · build 27196d31785efcd052e36f26f6d3c9e02a6e2313a43f4e7e51d1e34517cf50b704f61.4 MB2026-09-18firmata ML-DSA-65
v6.9.0 · build 2700de4bf23146928f1d8261328896dff9784694c6395943c8b813754b053e96ee261.4 MB2026-09-17firmata ML-DSA-65
v6.8.0 · build 269dbd67ae1b6c62f0658a490c2160e45d10dd9c4edabb0efeb643c4d65397b2ce861.4 MB2026-09-16firmata ML-DSA-65
v6.7.1 · build 26832a9b3b8624edf28f3b5ec350ab8b5b6da4141bf8fab5bfa82b398e6cea4fd4761.4 MB2026-09-16firmata ML-DSA-65
v6.7.0 · build 267f0119d0cadd0a6d335e893335bfea7971c45e5811915ab7d25b568056b7313b761.4 MB2026-09-16firmata ML-DSA-65
v6.6.4 · build 2663db33d64f57536a9cda1c61bf95c5b054f097169185a8de3eca07e93fed25b3761.4 MB2026-09-16firmata ML-DSA-65
v6.6.3751ab10682235da8c453d59246fc3688127e83d6b0533020444d6f8eb98e111361.4 MB2026-09-16archivio · impronta calcolata
v6.6.2c956457aba609f66d7e90194f7d2d00c058db457b6eed12f184c2aaecbd60b0161.4 MB2026-09-16archivio · impronta calcolata
v6.6.1ed9b1a58c042e60281c6b46d60e45c9f6d602a457c9c1ff7b393ad327a1e09ad61.4 MB2026-09-16archivio · impronta calcolata
v6.6.041008cd3f22ff680154edf96618a5a7393bba877229c37352984f5e304bbc66061.4 MB2026-09-16archivio · impronta calcolata
v6.5.13c7ee31c87d446b643d028d51f14c03a230286b0fff38c8630a3d09fa4531ea361.4 MB2026-09-16archivio · impronta calcolata
v6.5.077ed6e13072a672845dabb43127f0b313220ea67dd10cecce6fea8d28fdfed2861.4 MB2026-09-15archivio · impronta calcolata
v5.1.0b4d4297e98b22204e9dee0eff795bec6923826d59fda761fb5fcd600acbdde5c61.3 MB2026-09-13archivio · impronta calcolata
v5.0.1a1c6a3cc32fdcb379477046c18df4d1e0cbc7f16314e0c9320193bd9d4fc287661.3 MB2026-09-13archivio · impronta calcolata
v5.0.0f3ea414e65160640c9839b5ec597228a671f1961ee224c87a4f6a7c069d7355961.3 MB2026-09-13archivio · impronta calcolata
v4.5.3d6ea9b1c5194b33f8bad83a64e50c33b0d3905d08cea5c00e90c4f485b1d5b8461.3 MB2026-09-13archivio · impronta calcolata
v4.5.23ecf6784ff97bed7975bc104be9dcc7311de2c457c6db138e741876fb49a2da061.2 MB2026-09-13archivio · impronta calcolata
v4.5.143c6e6dae764a03a6b397f7bfabf391d6be65be0d9663a09983c54c619c6efb261.2 MB2026-09-13archivio · impronta calcolata
v4.5.084abf3417e8ed2cc09032089bf2640a6a7491db980845b4018b5649b5c75665061.2 MB2026-09-13archivio · impronta calcolata
v4.4.9c1485e1a90b8280047b3b90c86d7cf50ea219cbde313795bb0f0780604247baf61.2 MB2026-09-13archivio · impronta calcolata
v4.4.80b1c87d0e062fe0f5487ce20e464b0ce471f1fb3e81717204a902b76b742def261.2 MB2026-09-13archivio · impronta calcolata
v4.4.708ca663b2bb9193d55f1960249c55f0f0d45098997792b55a7537be8163bc9a061.2 MB2026-09-13archivio · impronta calcolata
v4.4.6ca85fbe7911400489040dcaf14acaab8b21c17d3972fd85c43c3b91289e0084d61.2 MB2026-09-13archivio · impronta calcolata
v4.4.535df0c733f2c860f640524aa065289b06709a146f3fb85f48a2bedd54b7017a461.2 MB2026-09-13archivio · impronta calcolata
v4.4.477e2798a21af740ac15bb9dfa886b97419ad70f2e012794a0f678cf3ecb664ba61.1 MB2026-09-13archivio · impronta calcolata
v4.4.313a1e8d62aeca2fc396ee4fc171a1ad9b6ad4c90f4839f446df170aba0b956b561.1 MB2026-09-13archivio · impronta calcolata
v1.1.1 · build 312af124b1286d25b74bbd18ae18e26a313034d7f54067a37376587fff7e855e2d860.9 MB2026-09-28firmata ML-DSA-65
v1.1.0 · build 3103cf954acb8eb7c2edf37f8cadb9c2b9e1a631ee1d7d3c99f73de18ef17811adc60.8 MB2026-09-28archivio · impronta calcolata

Elenco leggibile da una macchina: storico.json

Certificato di firma ML-DSA-65 · v1.1.1

Questa è la firma post-quantistica del manifest dell'ultima versione. Verificarla a mano richiede tre cose e nessuna fiducia: il manifest, la chiave pubblica del progetto e un comando.

Algoritmoml-dsa-65
Versione firmatav1.1.1 · build 312
Impronta dell'APKaf124b1286d25b74bbd18ae18e26a313034d7f54067a37376587fff7e855e2d8
# 1 · il manifest firmato e la chiave pubblica del sito
curl -s https://lattice-network.it/downloads/latest.json -o latest.json
curl -sO https://lattice-network.it/downloads/verify-apk.mjs
curl -s https://lattice-network.it/web-manifest.json | python3 -c "import json,sys;print(json.load(sys.stdin)['pk'])" > pk.hex

# 2 · l'impronta dell'APK che hai scaricato deve coincidere
sha256sum lattice-pulse.apk

# 3 · la firma copre versione, build, indirizzo, impronta e dimensione, in quest'ordine
#     lattice-apk-v1|<versione>|<build>|<url>|<sha256>|<byte>
node verify-apk.mjs latest.json pk.hex lattice-pulse.apk

Lo strumento: verify-apk.mjs (Node 20+, una sola dipendenza: @noble/post-quantum). Controlla la firma del manifest e poi l'impronta del file che hai scaricato. Se prendi la chiave pubblica da un canale diverso dal nostro sito, la verifica smette di dipendere dal nostro sito: è esattamente il punto.

Firma (ML-DSA-65, 3309 byte):

5272ae56b22944e3a2f6e8f680ab6f0c2919d7497d064c28bd828b926c2d73184adc59f59408fdabf6391ae5e336f806e160e1cb3c16880f13379b122ca2ae8b09615a2ec8d175ce6bf578f7f74f65bb1f82aa3e8419077743c7a62bb8895f704327b56d1bb93d7c8045652897591477ef6779232ed8941fae81c8c7705a5610b4e39c1ee58f19d71aa1c81c52a8ce759fe546fe7d635ac932b20dc370735b26750a8b7b3ff9fc8bb5382907483f2ed065afe4d34fe60b33389c7147b32b146b4b7cbff857cccbff766ed945d6745b69c4ca2559ba45aa843c908d0b4a32f17a3367cd9771da6ae089be7093f263ce4a017989166e03c65530d32a15f38cb59e…

Attestazione di indipendenza della rete · PDF firmato

Un foglio solo, da allegare a una gara o da girare a un ufficio acquisti: dice con dei numeri — non con una promessa — che nei nostri server non c'è e non può entrare nulla di Google. I numeri non li scriviamo a mano: il server apre il proprio binario in esecuzione e conta quante volte compaiono gli indirizzi dei servizi di Google. Zero significa che quel codice non esiste, non che è disattivato.

Scarica l'attestazione (PDF, 8 KB)
Retev1.1.1
NodiIT 1.1.1 · DE 1.1.1 · FI 1.1.1
EsitoASSOLUTO · nessuna traccia di Google
Reti di Google vietate nel kernel77
Rilevato il2026-09-28 14:20:11 UTC
Impronta SHA3-256 del PDF0c9fb7af258377fafc283e81d862efc08adb39ddcf5558c6928f4d164a557431
FirmaML-DSA-65 (la stessa chiave che firma gli aggiornamenti)

Verificabile da soli, senza fidarsi di noi:

curl -s https://lattice-network.it/api/public/zero-google/nodo
curl -s https://lattice-network.it/downloads/zerogoogle-nodo.json
curl -s https://lattice-network.it/downloads/attestato-nodo.pdf.sig

La pagina della prova, aggiornata dal vivo → · referto JSON · firma del PDF · versione della rete

Referto di audit tecnico v7.9.0 · PDF firmato

Due fogli con misure prese sul sistema in funzione, non dichiarazioni: quanto ci mette un messaggio ad arrivare, quanto ci mette una chiamata ad aprire il canale sul relay, che cosa succede a chi prova a usare il relay con credenziali scadute o manomesse, e l'esito «Zero Google» contato dentro il binario in esecuzione. In fondo, il comando per rifare ogni singola misura.

messaggio consegnato 10 ms (p95 88) · apertura chat 6 ms · biglietto del relay 9 ms · canale sul relay IT 2 ms · canale su TLS/443 9 ms
tre relay sovrani: IT · DE · FI, ognuno su 3478 UDP/TCP, 5349 TLS e 443 TLS · credenziali valide 600 s
regressione: 551 superati, 0 falliti · esito: versione sigillata
impronta SHA3-256 del PDF: de66fb0843da0d422aa1d088efe7a14083c5189045b63bff…

Scarica il referto di audit · PDF   firma ML-DSA-65   misure in JSON

Se venite dalla v2.1.0 o precedenti: va disinstallata

Fino alla v2.1.0 l'APK era firmato con la chiave di debug di Android, la cui password è pubblica: il file della chiave era l'unico segreto, ed è un modo fragile di firmare un'app che parla di privacy. Dalla v2.2.0 c'è una chiave di release RSA 4096 generata sul nostro server, che da lì non esce.

Il prezzo lo pagate voi, una volta sola: Android — giustamente — non accetta un aggiornamento firmato con una chiave diversa. Quindi: esportate il vostro file chiave (Impostazioni → Copia di sicurezza) se non l'avete già, disinstallate Lattice Pulse, installate questa versione, rientrate col file chiave.

Chi ha già la v2.2.0 non deve fare niente di tutto questo: la chiave di firma è la stessa, la v2.3.0 si installa sopra come un normale aggiornamento.

Lo scriviamo grosso e in cima perché è un fastidio reale causato da un errore reale. Nasconderlo in fondo alla pagina sarebbe stato peggio del fastidio.

Come si installa (2 minuti)

  1. Tocca "Scarica l'APK" qui sopra dal telefono.
  2. Android chiede il permesso di installare app da questa fonte: consenti (è una tutela di Android, non un avviso di virus).
  3. Apri il file scaricato e conferma l'installazione.
  4. Al primo avvio entri con il tuo file chiave, come sempre.

Aggiorna sempre da questa pagina se l’app non si apre o si chiude da sola: l’impronta SHA-256 qui sopra è la prova che il file è quello firmato da noi. Nelle versioni fino alla 1.3.7 il banner di aggiornamento dentro l’app aveva un difetto che la chiudeva: da quelle versioni bisogna passare da qui una volta.

Verificare l'APK da soli (facoltativo)

Il codice di questa app è pubblico →

Non chiediamo di crederci sulla parola: il sorgente del client Android è in rete, e a ogni pubblicazione dell'APK viene riallineato automaticamente, così quello che leggi corrisponde alla versione che hai in mano.

Attenzione a come si legge la barra dei linguaggi di GitHub: quel 93% di JavaScript riguarda il client Android. Backend, nodi, consenso e ledger sono ~20.000 righe di Rust (49 moduli, framework Axum) e non sono pubblicati: sono la parte che non deve essere clonata, si installano come immagine firmata e la loro matematica è dimostrata a parte in Dimostrazione QBFT 7 su 10. Le primitive crittografiche di quel Rust sono però pubbliche e si provano da sole (cargo test, 28 prove): github.com/Jabo86/lattice-crypto. Il tunnel di resistenza alla censura è in Go (libXray). La crittografia che protegge i messaggi gira sul dispositivo, quindi sta nel repository che puoi leggere.

Cosa non c'è, e per scelta: le chiavi di firma, le credenziali dei servizi di notifica e il codice del server. L'assenza di quei file non impedisce la verifica, perché il confronto riguarda il contenuto dell'APK, non la firma.

Security & Transparency Report →

Quante richieste di dati abbiamo ricevuto, cosa il server può e non può vedere, il canarino firmato, e la cronologia completa della verifica riproducibile (11 → 3 → 0 voci diverse). Una pagina di numeri e di cose controllabili, non di aggettivi.

Il canarino è sorvegliato ogni giorno: un controllo automatico verifica la firma e la data e scrive canary-status.json, che l'app legge e mostra nella sezione Sicurezza come VERIFICATO o ALLARME. L'app calcola la scadenza da sola: se la dichiarazione non viene rifirmata, lo dice senza che il server debba collaborare — che è l'unico modo in cui un canarino ha senso.

Ricompilarlo da soli e ottenere lo stesso file

La firma prova che l'APK viene da noi. Non prova cosa c'è dentro. L'unica cosa che lo prova è ricompilare il sorgente e ritrovarsi lo stesso binario. Perché sia possibile servono tre pezzi, e diciamo dove siamo su ognuno:

Due impronte, e vanno tenute distinte:

La via breve: un comando. Un'immagine che contiene già tutto — JDK 17, SDK e NDK, Go, Node, yarn — scarica l'APK pubblicato, controlla che sia quello dichiarato nel manifest, ricompila tutto dal sorgente (nucleo del trasporto compreso) e confronta voce per voce. Non installa niente sul vostro computer.

curl -O https://lattice-network.it/downloads/Dockerfile.verify
curl -O https://lattice-network.it/downloads/verify-apk.sh

docker build -t lattice-verify -f Dockerfile.verify .
docker run --rm --privileged lattice-verify 2.3.2

Serve --privileged per un motivo preciso e non negoziabile: i compilatori nativi incorporano il percorso assoluto dei sorgenti dentro le librerie .so, quindi la compilazione avviene da un percorso canonico montato con un bind mount. Senza quello il codice sarebbe identico e le librerie diverse.

La via lunga, a mano. Servono JDK 17, Android SDK con NDK, Go 1.26+, Node 20+, yarn, e privilegi di root per il percorso canonico:

curl -O https://lattice-network.it/downloads/lattice-pulse-src-2.3.2.tgz
curl -O https://lattice-network.it/downloads/repro-2.3.2.json
tar xzf lattice-pulse-src-2.3.2.tgz && cd lattice-pulse-2.3.2
yarn install --frozen-lockfile
echo "sdk.dir=$ANDROID_HOME" > android/local.properties

# la nostra chiave di firma non è nell'archivio, e non ci sarà: generate la vostra.
# la firma è esclusa dal confronto, quindi non cambia il risultato.
keytool -genkeypair -keystore android/app/debug.keystore -storepass android \
  -keypass android -alias androiddebugkey -keyalg RSA -keysize 2048 \
  -validity 10000 -dname "CN=Android Debug,O=Android,C=US"

bash tools/repro-build.sh /tmp/mio.apk
python3 tools/apkrepro.py check /tmp/mio.apk ../repro-2.3.2.json

Il passo a passo completo è dentro l'archivio, in README-VERIFICA.md.

Lo strumento elenca quali voci non combaciano, non solo che qualcosa non torna: se una differenza esiste, si vede dove. Le build sono deterministiche perché fissiamo tutto ciò che normalmente cambia da una volta all'altra — fuso orario, lingua di sistema, la data incorporata (SOURCE_DATE_EPOCH, pubblicata nel manifest), le cache di compilazione, che vengono buttate.

La prova, e vi raccontiamo anche come è andata, perché il modo in cui è andata è la parte utile. Ricompilare due volte nella nostra cartella dava subito 1.663 voci su 1.663 identiche: un risultato che sembra una vittoria e non vale niente, perché nessuno verifica dalla nostra cartella. La prova vera è estrarre l'archivio pubblicato altrove, con un'altra chiave di firma, e confrontare. È andata così:

1° tentativo   11 voci diverse su 1663   librerie native e dex
2° tentativo    3 voci diverse su 1663   dex e profili ART
3° tentativo    0 voci diverse su 1663   RIPRODUCIBILE          (v2.1.0)
v2.2.0          0 voci diverse su 1662   RIPRODUCIBILE, nucleo Xray compreso
v2.3.0 build 157   1 voce diversa su 1662   resources.arsc: dentro c'era l'IP
                                            della macchina che compila
v2.3.0 build 158   0 voci diverse su 1662   RIPRODUCIBILE da un
                                            container pulito, non dalla nostra cartella

Le prime undici erano i compilatori nativi che incorporano il percorso assoluto dei sorgenti dentro le .so: codice identico, librerie diverse. Risolto compilando sempre da un percorso canonico — /lattice-src, un bind mount — sia noi sia chi verifica; ed è per questo che servono i privilegi di root. Le ultime tre erano un buco nell'archivio: mancava modules/app-disguise, un modulo nativo locale che sta fuori da src/ e android/. Senza di esso il progetto compila comunque — semplicemente senza quel modulo, nove classi in meno nel dex. Un difetto che nessuna dichiarazione avrebbe trovato: l'ha trovato la verifica, fatta per davvero.

Il risultato finale, ripetibile da chiunque: un APK compilato solo partendo dall'archivio pubblicato, in un'altra cartella e con un'altra chiave, ha 1.663 voci su 1.663 identiche byte per byte a quello che scaricate da qui.

Le impronte della v2.1.0:

content_sha256    8cb4f342092d81a2039002ba0ca072fc6457bd06faa22e544bb588fcde0e794d
apk_sha256        48f465a7f5c9b77e53ed33b2eb6e4f9cbef4db9904533bf99a818c27d7172159
src_sha256        5aaf50101455645c11cbf68bbe3e00461e88a1548d7d071d7fc183cb855a2dc6
libv2ray.aar      ba84bad0493194eefe8c53df8085bf7e34e260483b4d92772a873cd82c07ae33   (compilato da noi)
firma APK         RSA 4096 · SHA-256 del certificato
                  45:11:53:AA:02:9A:67:B9:61:29:DA:64:76:27:3B:3D:9E:6F:A4:34:25:2F:CD:2C:8E:17:2B:9D:8D:AF:F1:C3
SOURCE_DATE_EPOCH 1757000000

La build di pubblicazione è la stessa che pubblichiamo: rebuild-apk.sh passa da repro-build.sh, non da un comando a mano. Se fossero due catene diverse, il manifest sarebbe carta straccia. Dalla v2.1.0 il manifest di riproducibilità viene pubblicato accanto a ogni versione, come repro-<versione>.json, con l'elenco completo delle impronte voce per voce; il riassunto in chiaro sta in repro.txt. Per la v2.0.0 e le precedenti non c'è, perché sono state compilate prima della catena deterministica: un manifest che nessuno può verificare è una promessa vuota.

Il buco che avevamo dichiarato qui — libv2ray.aar, il nucleo Xray del trasporto offuscato, un binario compilato da qualcun altro e messo dentro l'APK così com'era — dalla v2.2.0 non c'è più. Quel nucleo si compila qui, da sorgente, con Go e gomobile, a ogni build. Non è più un file versionato: è un prodotto della compilazione, e infatti è scomparso dall'archivio del sorgente. Nella stessa verifica che ha confrontato l'APK, il nucleo è stato ricompilato dall'archivio pubblicato e ha dato lo stesso .aar, byte per byte. Da qui in avanti ogni byte eseguibile dell'APK viene da un compilatore che gira sul nostro server, su codice che pubblichiamo.

Resta la fiducia negli strumenti: il compilatore Go, il JDK, l'SDK e l'NDK di Android non li scriviamo noi. Ogni build del mondo ha questo limite. Dirlo è l'unica cosa che possiamo fare in più degli altri.

Onestà: la riproducibilità dimostra che quel binario viene da quel sorgente. Non dimostra che il sorgente non abbia difetti — per quello serve un audit esterno, che non abbiamo ancora e che continuiamo a dire di non avere.


Documentazione · 1 / 3

L'ecosistema Lattice

Lattice non è una singola app: è una rete con quattro pezzi, di cui solo uno è nostro e quell'uno è progettato per essere il meno informato di tutti. Qui c'è tutto quello che fa, senza sconti: se un pezzo ha un limite, il limite è scritto.

I quattro pezzi

PezzoChe cos'èChi lo controlla
Lattice Pulse L'app Android che scarichi qui. Dentro c'è tutta la crittografia: chiavi, cifratura, decifratura, firma. Il testo in chiaro non esce mai da questo perimetro. Il tuo telefono
Il relay Il nostro server (Rust/Axum + MongoDB). Fa il postino di buste chiuse e il tabellone dove depositare la propria chiave pubblica. Non ha le chiavi private di nessuno. Noi — ma cieco
Nodo Sovrano La rete mesh fra telefoni vicini: Wi-Fi, Wi-Fi Direct e Bluetooth LE. Instrada a cipolla su 4 salti ibridi (ML-KEM-768 + X25519 per strato), con pacchetti tutti della stessa dimensione, e dentro trasporta le stesse buste del triplo ratchet. Funziona a internet spento. sperimentale Nessuno: sono i telefoni
Canale on-premise Per le aziende: lo stesso relay installato sui server del cliente, così le buste non passano nemmeno da noi. Il cliente

Cosa sa fare l'app

FunzioneCome è protetta
Chat 1 a 1Cifratura end-to-end con triplo ratchet ibrido: una chiave nuova per ogni messaggio, una chiave nuova su curva X25519 a ogni cambio di turno e una radice nuova ML-KEM-768 ogni pochi turni. Per leggere una riga servono rotte entrambe le matematiche. Chi copia il tuo telefono viene tagliato fuori dopo un solo scambio.
GruppiUna busta cifrata per ciascun destinatario (nessuna chiave di gruppo condivisa da revocare): chi esce dal gruppo non riceve più nulla, senza rinegoziare niente.
Canali broadcastChiave di canale AES-256 imbustata con ML-KEM-768 per ogni iscritto. Il server distribuisce il testo cifrato e non conosce la chiave.
Foto, file, note vocaliCifrati sul telefono con AES-256-GCM prima del caricamento. Il server conserva un blocco opaco: non può generare anteprime né vedere il tipo di contenuto.
Chiamate audio/videoWebRTC punto-a-punto con DTLS-SRTP. Le offerte di sessione (SDP) sono firmate con ML-DSA-65, quindi il server non può inserirsi come terzo ascoltatore. Se la rete impone un relay, il TURN vede pacchetti cifrati.
Verifica del contattoQR code e numero di sicurezza a 60 cifre derivato dalle impronte delle due identità: se i numeri combaciano, nessuno si è messo in mezzo.
PIN, PIN di panico, autodistruzioneIl database locale è cifrato con una chiave legata al dispositivo. Il PIN di panico sblocca un profilo innocuo e cancella il vero. Con l'autodistruzione le chiavi vengono azzerate e i dispositivi revocati sul server.
Backup cifratoArchivio esportabile, chiave derivata dalla tua password con scrypt (N=2¹⁴, r=8, p=1) e contenuto in AES-256-GCM. La password non lascia il telefono e non esiste alcun recupero: se la perdi, il backup è definitivamente illeggibile.
Nodo Sovrano, Ponte, CorriereChat sulla mesh senza internet, con lo stesso triplo ratchet delle chat via server: una chiave nuova per ogni messaggio anche qui. Se un vicino ha rete la tua busta esce da lui (Ponte); se nessuno ce l'ha, un vicino la porta con sé sigillata e la consegna quando ritrova la rete (Corriere).
AggiornamentiNessun Play Store. Il manifest latest.json è firmato con ML-DSA-65: l'app rifiuta ogni APK la cui firma o impronta SHA-256 non combacia.

Documentazione · 2 / 3

Cosa protegge, e cosa no

Un modello di minaccia serve a poco se elenca solo le vittorie. Questa tabella dice, per ogni avversario, se Lattice regge ✔, se regge in parte ~ o se non vi protegge ✘.

AvversarioEsitoPerché
Chi ascolta la rete
(Wi-Fi, operatore, ISP)
✔ Tutto è dentro TLS e, sotto, già cifrato end-to-end. Il traffico è indistinguibile dal rumore e le dimensioni sono normalizzate.
Il nostro server,
o chi ce lo sequestra
✔ Il server non ha chiavi private: conserva buste opache e chiavi pubbliche. Nessun IP registrato, nessun log dei contenuti, nessuna rubrica caricata, nessun hash di password da rubare. Quello che sa lo trovate due righe più sotto, alla voce grafo sociale.
Un computer quantistico
(anche fra 15 anni)
✔ Nessuna curva ellittica nella riservatezza dei messaggi: chiavi e sessioni usano ML-KEM-768, le firme ML-DSA-65. Chi registra oggi il traffico per decifrarlo domani non ci riesce.
Uno che si mette in mezzo
(MITM sulle chiavi)
~ Le chiavi pubbliche arrivano dal nostro tabellone: se il server mentisse, potrebbe darvi una chiave sbagliata. È esattamente per questo che esiste il numero di sicurezza: verificate il contatto una volta e l'inganno diventa impossibile.
Chi ti prende il telefono
sbloccato in mano
✘ Nessuna crittografia salva un dispositivo già aperto. Mitigazioni: PIN, PIN di panico, blocco automatico, autodistruzione.
Chi vuole ricostruire
il tuo grafo sociale
~ Per consegnare un messaggio il relay deve sapere a chi consegnarlo: nei record nuovi l'indirizzo di chi scrive non c'è più (resta solo un indice fra i partecipanti), ma la coppia che conversa il server la conosce. Non i contenuti, non i nomi dei gruppi, non gli allegati: chi parla con chi. Chi non vuole nemmeno questo ha tre strade reali, tutte già nel prodotto: la cassetta postale anonima (qui sotto), il canale on-premise dove il relay è vostro, e il Nodo Sovrano dove non c'è nessun relay. Lo scriviamo perché è l'unica cosa che il nostro server sa di voi.
Chi vuole sapere che
usi Lattice
✘ Il tuo indirizzo IP contatta un nostro server: chi osserva la tua linea vede che parli con lattice-network.it. Il contenuto no, il fatto sì. Il tunnel offuscato riduce il problema ma non lo elimina.
Analisi dei tempi
sulla mesh locale
~ Su una Wi-Fi condivisa un osservatore può seguire in parte le cadenze degli inoltri. Le abbiamo rese casuali e aggiungiamo traffico di copertura, e da questa versione nemmeno la destinazione di uno strato è più leggibile senza rompere due matematiche — ma il problema dei tempi non è risolto: per questo il Nodo Sovrano resta sperimentale.
Chi ti costringe
a consegnare la password
✘ Nessun software risolve la coercizione. Il PIN di panico è una risposta parziale e va preparata prima, non dopo.

Cosa non promettiamo: non abbiamo ancora un audit esterno indipendente pubblicato, e la nostra crittografia non è ibrida (non affianca cioè una curva classica a ML-KEM). Sono le due cose su cui stiamo lavorando, e le trovate scritte anche nella roadmap qui sotto.

Documentazione · 3 / 3

Specifiche del protocollo

Questa è la specifica reale, quella che corrisponde al codice pubblicato in questa versione. Ogni numero qui sotto è verificabile: sono le stesse costanti che trovate nei moduli crypto.js, ratchetCore.js e mesh/onion.js dentro l'APK.

Primitive crittografiche

ScopoAlgoritmoParametri
Apertura sessioneML-KEM-1024 (FIPS 203) pk 1568 B · ct 1568 B — NIST livello 5, il massimo previsto dallo standard. Protegge la radice, cioè il segreto più longevo: si paga una volta per sessione
Passo del ratchetML-KEM-768 (FIPS 203) pk 1184 B · ct 1088 B · segreto 32 B — NIST livello 3, rifatto ogni pochi turni: qui il parametro alto sarebbe banda buttata
FirmeML-DSA-65 (FIPS 204) pk 1952 B · firma 3309 B — NIST livello 3
Cifratura autenticataAES-256-GCMIV 96 bit casuale · tag 128 bit · AAD sull'intestazione
Derivazione di chiaviHKDF-SHA-256etichette di dominio separate per radice, catena e aggancio
Impronte e IDSHA-256 / SHA-512ID nodo mesh = primi 16 B di SHA-256(pk)
Password del backupscryptN=2¹⁴, r=8, p=1 → 32 B

Nessun RSA, nessun Diffie-Hellman su curve nella riservatezza dei messaggi. La libreria è @noble/post-quantum, implementazione pura in JavaScript senza codice nativo opaco.

Identità e accesso

L'identità di un utente è una coppia di chiavi ML-DSA-65 generata sul telefono: la privata non viene mai trasmessa, e sta nel file chiave che l'utente custodisce. L'accesso non usa password: il server manda un nonce casuale, il telefono lo firma, il server verifica la firma con la chiave pubblica che ha in archivio. Non esiste quindi alcun hash di password da rubare, e un archivio del server compromesso non permette a nessuno di autenticarsi come voi.

Ogni telefono registra inoltre una propria chiave di dispositivo ML-KEM-768 più un fondo di chiavi usa-e-getta. Un dispositivo revocato o inattivo viene ripulito automaticamente insieme alle sue chiavi orfane.

Handshake ibrido (in stile PQXDH)

L'apertura di una sessione mescola due segreti di natura diversa: un'incapsulazione ML-KEM-768 sulla chiave d'identità post-quantistica del destinatario e un Diffie-Hellman X25519 effimero contro la sua chiave d'identità su curva. I due segreti vengono concatenati e passati a HKDF: per ricostruire la radice bisogna rompere entrambi i mondi, i reticoli e le curve ellittiche. È la ragione per cui esiste PQXDH, ed è la ragione per cui l'abbiamo adottato: ML-KEM è una primitiva del 2024, e non riteniamo prudente appoggiarci solo a lei.

La radice iniziale è inoltre legata alla trascrizione: dipende dall'identità di chi riceve, dall'identificativo della chiave usa-e-getta consumata, dai cifrati e dal dispositivo mittente. Un incapsulamento catturato non è riutilizzabile in nessun altro contesto — niente unknown key-share, niente riuso in una sessione diversa.

La chiave usa-e-getta viene distrutta nell'istante in cui ha aperto la sessione, non dopo giorni. È la differenza fra forward secrecy adesso e forward secrecy fra una settimana.

E l'apertura usa ML-KEM-1024, il livello NIST 5: il parametro più alto che lo standard definisce. Non è simmetria estetica, è dove va messo il margine — la radice sopravvive a tutta la conversazione, mentre un passo del ratchet vive pochi turni e lì il livello 3 è la scelta giusta. La chiave di livello 5 nasce dallo stesso segreto d'identità con un'etichetta diversa: niente in più da custodire, e il numero di sicurezza non cambia, così chi si è già verificato non vede falsi allarmi. Il livello dichiarato è coperto dal dato autenticato della busta: farlo scendere per strada fa fallire la decifratura, non declassa la sessione in silenzio. Con chi ha una versione precedente si resta a 768, come prima.

Apertura della sessione
  ss1   = ML-KEM-1024.Encaps(IK_kem_destinatario)        -> ct1   (livello NIST 5)
  ss2   = X25519(eph_mittente, IK_curva_destinatario)    -> epk
  tr    = "…init-v2" | H(IK_kem_dest ‖ IK_curva_dest) | pk_id | ct1 | epk | dev_mittente
  rk    = HKDF-SHA256(ikm = ss1 ‖ ss2, info = tr)

Triplo ratchet ibrido

Tre catene lavorano insieme, e ognuna risolve un problema diverso.

CatenaCadenzaChe cosa garantisce
Simmetrica
HKDF-SHA-256
ogni messaggio Forward secrecy. La chiave del messaggio viene usata una volta e cancellata: quella di ieri non esiste più da nessuna parte.
Su curva
X25519
ogni cambio di turno Post-compromise security continua. Chi ha copiato il telefono viene tagliato fuori dopo un solo scambio. Costa 32 byte: si può fare sempre.
Post-quantistica
ML-KEM-768
ogni pochi turni Rinnovo post-quantistico. Costa 2,6 KB fra chiave e cifrato: farlo per scrivere «ok» sarebbe uno spreco. La sua eredità resta però dentro la radice per sempre.
Turno nuovo (a ogni cambio di direzione)
  (xpk, xsk)  = X25519.KeyGen()                          -> xpk nell'intestazione (32 B)
  ssX         = X25519(xsk, xpk_altro)
  ssKem       = ML-KEM-768.Encaps(pk_altro)              -> solo nei turni post-quantistici
  rk' , ck    = HKDF-SHA256(ikm = ssKem ‖ ssX, salt = rk, info = "…root-v2", 64 B)

Ogni messaggio
  ck' , mk    = HKDF-SHA256(ck, info = "…chain-v2", 64 B)
  busta       = AES-256-GCM(mk, iv, AAD = intera intestazione).Encrypt(testo)
  mk viene usata una volta sola e cancellata

Una precisazione che conta, e la diciamo per non essere fraintesi: la riservatezza è sempre ibrida, anche nei turni in cui il passo ML-KEM non viene rifatto, perché il suo segreto è già cotto dentro la radice e la radice non dimentica nulla. Quello che è rado è soltanto la velocità di rinnovo post-quantistica, cioè quanto tempo un intruso post-quantistico resta dentro dopo aver copiato il telefono: al massimo tre turni o venti minuti, non uno solo. Contro un intruso classico il taglio resta immediato, a ogni singolo turno.

Intestazione: perché i byte sono un problema di sicurezza

Una chiave pubblica ML-KEM-768 pesa 1184 byte, un cifrato 1088. Trasmetterli in ogni messaggio, in esadecimale, portava una riga di testo a 11.477 byte. Quando una busta del genere deve viaggiare sulla mesh — cioè con il Ponte o il Corriere, dove un pacchetto è di 8192 byte fissi con 3652 byte utili — significava spezzare un singolo messaggio in quattro pacchetti, ognuno con quattro salti: sedici trasmissioni radio per dire «arrivo». Più frammenti, più probabilità di perderne uno, più occasioni per un osservatore.

Messaggio di testoPesoPacchetti mesh
Ratchet precedente (v1)11.477 byte4
Triplo ratchet, turno post-quantistico3.259 byte1
Triplo ratchet, turno normale191 byte1

Come: la chiave ML-KEM non viaggia più in ogni messaggio ma si richiama con un'impronta da 8 byte, e tutto passa da esadecimale a base64. E una proprietà che ci teniamo stretta: ogni messaggio porta con sé tutto il necessario per aprirsi. Non esiste un «messaggio chiave» che, perdendosi, blocca la catena — i messaggi possono arrivare in qualunque ordine, che è esattamente quello che succede su una radio.

Fuori ordine e perdite: fino a 300 messaggi possono essere scavalcati in un colpo e 600 chiavi restano da parte per i ritardatari. Una sessione desincronizzata viene riagganciata automaticamente dalla catena di guarigione, invece di lasciare messaggi illeggibili.

Compatibilità fra versioni

Ogni dispositivo annuncia le proprie capacità. Se il telefono dell'altro sa fare l'ibrido e la sua chiave su curva è disponibile, si usa il triplo ratchet (rat2). Altrimenti si scende al ratchet post-quantistico puro (rat1), che è ciò che c'era prima — mai a nulla di più debole, e mai al silenzio. Chi ha una versione molto vecchia riceve ancora le buste a chiave usa-e-getta. Il livello effettivo di ogni chat è scritto nella schermata di verifica del contatto, dentro l'app: non ve lo chiediamo per fede.

Gruppi e canali

I gruppi non usano una chiave condivisa: ogni messaggio viene cifrato una volta per ciascun destinatario con la sua sessione a doppio ratchet. Costa più banda, ma dà due proprietà che una chiave di gruppo non può dare: chi esce non può leggere il futuro senza che si debba rinegoziare nulla, e la compromissione di un membro non espone gli altri. I canali broadcast, dove i destinatari sono migliaia, usano invece una chiave AES-256 di canale imbustata con ML-KEM-768 per ogni iscritto.

Mesh: instradamento a cipolla ibrida (v3)

Con il Nodo Sovrano attivo i telefoni si trovano da soli su Wi-Fi (UDP broadcast), Wi-Fi Direct e Bluetooth LE, e si scambiano pacchetti senza alcun server. Le chiavi mesh — sia quella ML-KEM sia quella su curva — sono derivate dall'identità: nessuna chiave in più da custodire. L'ID pubblico di un nodo resta un'impronta a 16 byte della sola chiave ML-KEM, così un telefono aggiornato e uno vecchio continuano a riconoscersi e la rete non si spacca in due.

Formato del pacchetto — invariabile
  dimensione        8192 byte, sempre, per ogni tratta
  salti             4 (3 relay + il nodo che consegna)
  per strato        ct ML-KEM-768 1088 B + curva effimera X25519 32 B + IV 12 B
                    cifrato: next(16) + flag(1) + len(2) + tag(16)
  chiave di strato  HKDF-SHA256( ss_MLKEM ‖ ss_X25519 , info = "…mesh-onion-v3" )
  contenuto utile   3524 byte, sempre a lunghezza fissa
  riempimento       byte casuali, indistinguibili dal cifrato

Ogni relay sa esattamente due cose: da chi ha ricevuto lo strato e a quale ID consegnarlo. Non sa quanti salti sono passati, quanti ne restano, quanto è lungo il messaggio, né chi sia il mittente. E da questa versione, per sapere anche solo a chi è diretto uno strato servono rotte entrambe le matematiche: chi registra oggi il traffico radio per ricostruirlo domani con un computer quantistico deve anche rompere le curve ellittiche, e viceversa. Il prezzo è 32 byte per strato — 128 su 8192, cioè 128 byte di contenuto utile in meno. Le cadenze di inoltro sono randomizzate e viene generato traffico di copertura per attenuare l'analisi dei tempi.

Se anche un solo salto del percorso ha una versione precedente dell'app, l'intero pacchetto scende alla cipolla post-quantistica pura: un percorso a geometria mista non esiste, sarebbe un pacchetto con due forme diverse. Un nodo aggiornato apre comunque anche gli strati vecchio stile.

Il triplo ratchet viaggia anche sulla mesh

Fino alla versione 1.8.0 questo era il punto più debole di tutto il sistema, e lo scrivevamo: sulla rete locale il testo era protetto soltanto dalla chiave mesh del destinatario, che nasce dalla sua identità e non ruota mai. Nessuna forward secrecy, nessuna auto-guarigione: chi avesse copiato un telefono avrebbe letto tutto il traffico mesh passato e futuro.

Adesso dentro la cipolla c'è la stessa busta del triplo ratchet ibrido che passerebbe dal server. Una chiave nuova per ogni messaggio, una curva nuova a ogni turno, una radice post-quantistica rinnovata. È diventato possibile solo grazie all'intestazione snella della 1.8.0: una busta a regime pesa ~200 byte invece di 11,5 KB, e in un pacchetto mesh ci sta. Prima non ci sarebbe entrata.

Quando la busta non ci sta — succede al primo messaggio, quello che porta l'aggancio della sessione, perché contiene tre pezzi di materiale crittografico — viene spezzata in pezzi, ognuno dentro la sua cipolla e per la sua strada, e ricomposta a destinazione. I pezzi vivono solo in memoria e scadono in cinque minuti: sul telefono non resta nulla. Per i relay sono pacchetti da 8192 byte come tutti gli altri.

Cosa resta fuori, e lo diciamo: sulla mesh viaggia il testo. Foto, file e note vocali richiedono ancora il server. E se una sessione ratchet non esiste ancora — un contatto con cui non si è mai scambiato niente, e nessuna rete per aprirla — il messaggio parte comunque col formato precedente invece di restare a terra: mai il silenzio, mai qualcosa di più debole di quello che c'era prima.

Ponte Internet e Corriere Offline

Servono a un caso preciso: tu non hai rete, il destinatario è lontano. La busta è già cifrata punto-a-punto, quindi può viaggiare in mano a chiunque.

SituazioneCosa accade
Ponte
un vicino ha internet
La busta parte sulla mesh come cipolla a 4 salti verso quel vicino, che la spinge al relay con un ticket anonimo prenotato in precedenza dal mittente. Consegna immediata.
Corriere
nessuno ha internet
La busta viene affidata a un vicino qualsiasi, che la prende in custodia sigillata e la consegna appena ritrova una connessione: anche ore dopo, anche chilometri più in là.

Cosa non può fare chi vi trasporta: leggere il messaggio (è cifrato per il destinatario), sapere chi sia il mittente o il destinatario (non appaiono nel pacchetto), alterare la busta (l'AAD del ratchet non tornerebbe), o fingere di aver consegnato. Nel suo telefono resta solo un numero: quanti pacchetti sta portando. Il ticket è un gettone di spedizione anonimo che non contiene l'identità di chi lo ha chiesto. Limiti volutamente severi perché nessuno possa usare il vostro telefono come deposito: massimo 20 pacchetti in custodia, scadenza a 24 ore, poi vengono buttati. Gli allegati non passano da qui: restano al server, la mesh trasporta solo testo.

Sigillo del mittente: la cassetta postale anonima

Alla voce «grafo sociale» qui sopra abbiamo scritto che il relay conosce la coppia che conversa. Questa è la risposta, e non è una promessa: è una modalità che si accende.

Con la cassetta anonima i messaggi non partono più a nome del tuo account. Vengono depositati a un indirizzo casuale — una cassetta che non è collegata alla tua identità — usando gettoni monouso che il tuo contatto ti consegna dentro la chat già cifrata. Il deposito avviene senza autenticarsi: il server non ha nulla a cui associarlo.

Cosa il server vede di un deposito
Il testo del messaggio✘ mai
L'indirizzo Lattice di chi scrive✘ mai
L'indirizzo Lattice di chi riceve✘ mai
L'identificativo della conversazione✘ mai
L'identificativo del dispositivo✘ mai
Che due account Lattice si stiano parlando✘ mai
Un gettone monouso e un blocco opaco✔ questo, e nient'altro

Due depositi consecutivi non hanno un solo byte in comune: gettone diverso, incapsulazione diversa, cifrato diverso, e nessun campo che permetta di metterli in fila. Un gettone speso non si può riusare. Le notifiche sono agganciate alla cassetta e non alla tua identità, quindi il telefono si sveglia senza che il server sappia di chi è l'account.

Fino alla versione 2.0.0 questa modalità aveva un prezzo che abbiamo sempre dichiarato: dentro la busta anonima viaggiava la cifratura a colpo singolo, quindi accenderla significava rinunciare al ratchet. Adesso dentro il deposito c'è la stessa busta del triplo ratchet ibrido di tutte le altre chat: chiave nuova per ogni messaggio, curva nuova a ogni turno, auto-guarigione. Accenderla non costa più nulla in robustezza.

Cosa resta visibile, perché va detto: il primo scambio di gettoni passa dalla chat normale. Il server vede una volta che vi siete scritti, e da quel momento in poi la conversazione diventa opaca e ogni messaggio successivo è inaccostabile agli altri. È una modalità da accendere su entrambi i telefoni, in «PIN app e autodistruzione»: la teniamo volontaria perché cambia il modo in cui i messaggi vengono consegnati, e una cosa così non si accende alle spalle di chi la usa.

Rotazione delle chiavi d'aggancio

Il problema: la chiave con cui gli altri aprono una conversazione nuova con voi protegge la radice di quella conversazione, cioè il segreto più longevo che esiste, e resta esposta su un tabellone pubblico per tutta la sua vita. Ma non si può cambiare l'identità per ricambiarla: il numero di sicurezza cambierebbe, e ogni contatto che vi ha verificato vedrebbe un allarme. È il motivo per cui, in pratica, nessuno la ruota mai.

La soluzione è separare le due cose. L'identità — ML-DSA-65 per le firme, la ML-KEM-768 e la curva X25519 su cui si calcolano numero di sicurezza e codice QR — resta ferma alla generazione 0 per sempre. Accanto, si pubblica una coppia di chiavi d'aggancio (X25519 + ML-KEM-1024) che porta un contatore di generazione e si ricambia da sola ogni trenta giorni, oppure a mano quando volete, da «Chiavi d'aggancio» nelle impostazioni.

Ogni generazione è derivata dal segreto d'identità con una separazione di dominio sul numero di generazione: non se ne conserva nessuna, si rifanno quando servono. Sul filo non viaggia quale generazione è stata usata: chi riceve prova la corrente e le sei precedenti (circa mezzo anno per chi è stato offline) e vince quella che apre. Serve una nota, perché è un errore facile: ML-KEM non fallisce mai la decapsulazione — con la chiave sbagliata restituisce un segreto pseudocasuale, senza dire niente. Quindi la generazione giusta non si riconosce dalla decapsulazione, ma solo aprendo per davvero il primo messaggio: il tentativo si fa su una copia dello stato, e nessun campo in più finisce nella busta. Un tentativo in più su una conversazione nuova, zero informazioni regalate a chi guarda.

Il limite, detto chiaro: siccome le generazioni sono derivate dal segreto d'identità, chi rubasse il vostro file chiave otterrebbe tutte le generazioni, passate e future. La rotazione accorcia la vita di una chiave pubblicata e riduce quanto vale registrare traffico oggi sperando di romperlo domani; non sostituisce la custodia del file chiave, e non difende un telefono già compromesso. Contro quello lavora il ratchet, che ricambia le chiavi a ogni turno.

Gruppi sulla mesh

Il problema, in un gruppo offline, è banale e fastidioso: cinque membri, quattro a portata di radio e uno lontano. Prima non partiva niente per nessuno — la consegna era tutto-o-niente, e un solo membro assente bloccava il messaggio anche per chi era nella stessa stanza.

Adesso la consegna è per destinatario. Chi si è presentato sulla mesh riceve subito. Chi non si è mai presentato torna al ponte o alla coda, ma con solo le sue buste: ogni busta porta scritto per chi è, quindi ridurre l'elenco è esatto e nessuno riceve il messaggio due volte.

E poi il passaparola. La copia destinata a un membro che non si vede adesso viene affidata a un membro che si vede: appena quello incrocia il destinatario, gliela consegna. Non è una funzione nuova del protocollo, è la custodia del corriere applicata dentro un gruppo. Chi fa da corriere vede il nome del destinatario — ma è nel gruppo, quindi lo sapeva già — e un blocco di byte che non può leggere (il contenuto è cifrato col ratchet fra mittente e destinatario) né modificare (il sigillo è calcolato col segreto mesh di quella coppia, che lui non ha). Se la stessa copia arriva sia per via diretta sia per passaparola, il destinatario ne tiene una: il doppione si riconosce dal sigillo, identico. La custodia ha un tetto di 40 copie e scade dopo 12 ore, così nessuno può usare il telefono di un altro come magazzino.

Un dettaglio che il banco di prova ha scovato e che vale la pena raccontare, perché è il tipo di errore che si scrive senza accorgersene: il passaparola, così com'era, avrebbe potuto trasportare anche il formato precedente (v1), il cui corpo è protetto dai soli strati della cipolla. Ma il membro-corriere sbuccia il suo strato — quindi lo avrebbe letto. Adesso la custodia si affida solo alle buste del ratchet. Un messaggio in chiaro non si dà a nessuno, nemmeno a un membro del gruppo: meglio che resti in coda.

Dopo la dogana

Il caso non è ipotetico: il telefono è stato fuori dalle vostre mani. Un controllo alla frontiera, un fermo, un albergo. Chi l'ha avuto in mano può aver copiato dei file, e i file si copiano. Due cose, in particolare, valgono la fatica: lo stato delle sessioni e i segreti delle chiavi usa-e-getta non ancora consumate, che stanno sul telefono per definizione.

Un tocco, sei passaggi, in quest'ordine — e l'ordine non è arbitrario:

  1. Usa-e-getta bruciate. Prima di tutto, perché sono la cosa più preziosa da copiare: il telefono dichiara al server di non possederne più nessuna, il server butta le pubbliche, e ne viene pubblicato un lotto nuovo.
  2. Chiavi d'aggancio ruotate. Generazione nuova, X25519 + ML-KEM-1024. Prima di azzerare le sessioni, così le sessioni nuove nascono già sulle chiavi nuove.
  3. Sessioni azzerate. Ogni conversazione riparte da una radice nuova: lo stato copiato non apre più niente da adesso in avanti.
  4. Chiavi dei contatti buttate dalla cassaforte locale e riscaricate. Va insieme al punto precedente: se si buttassero le sessioni tenendo le chiavi in cassaforte, una chiave falsa infilata mentre il telefono era via resterebbe lì a fare danno.
  5. Contatti verificati ricontrollati. È il passaggio che conta davvero, e viene per forza dopo il punto 4: le impronte vengono ricalcolate su chiavi appena scaricate, e ogni contatto che era verificato e ora non combacia più viene nominato.
  6. File decifrati nella cache cancellati: foto, video, documenti aperti, esportazioni.

Cronologia, identità e numero di sicurezza non si toccano. Un passaggio che fallisce (niente rete) non ferma gli altri e finisce nella ricevuta col suo nome, invece di sparire.

Il limite, dichiarato dentro la schermata stessa: l'identità ML-DSA-65 non si rifà, perché cambiarla vorrebbe dire cambiare numero di sicurezza e far scattare un allarme a ogni contatto verificato. Quindi se il sospetto è che vi abbiano copiato IL FILE CHIAVE, o che il telefono sia stato modificato a livello di sistema, quel pulsante non basta: serve l'autodistruzione e un'identità nuova su un telefono diverso.

Aggiornamenti verificabili

L'APK non passa da nessuno store. Il manifest degli aggiornamenti contiene versione, dimensione e impronta SHA-256, ed è firmato con ML-DSA-65 dalla chiave di produzione: l'app scarica, ricalcola l'impronta e verifica la firma, e rifiuta il file se una delle due non combacia. Potete rifare la stessa verifica a mano con sha256sum, ed è il motivo per cui l'impronta sta scritta in cima a questa pagina.

Roadmap crittografica — cosa manca ancora

VoceStatoCosa cambierebbe
Ratchet ibrido
(curva + post-quantistico)
fatto · v1.8.0 X25519 accanto a ML-KEM in handshake e a ogni turno: per leggervi servono rotti entrambi.
Aggancio legato
alla trascrizione
fatto · v1.8.0 La radice iniziale dipende da identità, chiave usa-e-getta e cifrati. La usa-e-getta viene distrutta all'istante, non dopo giorni.
Passo post-quantistico
rado
fatto · v1.8.0 Il costo ML-KEM si paga ogni pochi turni invece che a ogni messaggio: 191 byte per una riga di testo invece di 11.477.
Cipolla mesh ibrida (v3)fatto · v1.9.0 ML-KEM-768 + X25519 in ognuno dei quattro strati. Costo reale: 128 byte su 8192.
Ratchet sulla chat meshfatto · v1.9.0 Era l'ultimo punto in cui la chiave non ruotava mai. Adesso sulla mesh viaggia la stessa busta del triplo ratchet, con frammentazione quando serve.
Allegati sulla meshin valutazione Note vocali e immagini spezzate in pacchetti mesh. La frammentazione ora esiste: è il pezzo che mancava.
Rotazione delle chiavi
d'aggancio
fatto · v2.1.0 Curva X25519 e ML-KEM-1024 d'aggancio ruotano ogni trenta giorni, o a mano. Identità, numero di sicurezza e codice QR non si muovono: chi vi ha verificato non vede allarmi.
ML-KEM-1024
nell'aggancio
fatto · v2.0.0 Livello NIST 5 dove protegge la radice. Numero di sicurezza invariato, nessun declassamento possibile per strada.
Sigillo mittente
col ratchet dentro
fatto · v2.0.0 La cassetta anonima ora trasporta il triplo ratchet invece della cifratura a colpo singolo: accenderla non costa più robustezza.
Sigillo mittente
acceso di serie
dopo le prove sul campo Cambia il modo in cui i messaggi vengono consegnati: prima va provato in mano alle persone, poi si valuta se diventi il modo normale di funzionare.
Chat di gruppo
sulla mesh
fatto · v2.2.0 Consegna per destinatario invece di tutto-o-niente, e passaparola: la copia di chi non si vede la porta un membro che si vede. Sulla mesh passa solo testo, per scelta: allegati e vocali hanno bisogno dei blob sul server, e una radio locale non è il posto dove spingere megabyte. Il P2P serve a far arrivare le parole quando non c'è altro.
Compilare libv2ray.aar
dal sorgente
fatto · v2.2.0 Il nucleo Xray si compila da sorgente con Go e gomobile a ogni build, e due compilazioni danno lo stesso file. Zero binari importati dentro l'APK.
Build riproducibilifatto · v2.1.0 Due compilazioni indipendenti dello stesso sorgente danno 1.663 voci identiche byte per byte. Si pubblica il content_sha256 di ogni versione e lo strumento che dice quale voce non combacia. La differenza fra fidarsi e verificare.
Audit esterno
indipendente
non ancora Finché non c'è, l'unica garanzia che possiamo darvi è il codice verificabile e le prove automatiche che pubblichiamo. Lo diciamo, invece di far finta.
← Torna al sito Sicurezza e audit Trasparenza Regole