// the specification

Il protocollo v1, esattamente.

Tutto ciò che segue è scritto sul codice congelato e pubblicato. Ogni costante può essere verificata in il repository, e i vettori di test tra implementazioni fissano il comportamento a livello di byte. Se questa pagina e il codice dovessero mai divergere, il codice è la verità e la divergenza è un bug che vale la pena segnalare.

La versione breve

  • Il testo della nota è cifrato sul dispositivo del mittente. Nessun server lo vede mai.
  • La chiave di decifratura viaggia solo nel frammento del link, la parte dopo #, che i browser non inviano mai a nessun server.
  • La decifratura avviene nel browser del destinatario, in JavaScript che chiunque può leggere con Visualizza sorgente.
  • Il token di gestione dietro brucia, scadenza e stato può controllare una nota ma non decifrarla mai. Il server memorizza solo il suo hash SHA-256.
  • Il link completo non può essere ricostruito dopo la chiusura della schermata di nota creata. L'app conserva solo l'id della nota e il token, mai la chiave.

Il link

burnpony.app/n/{id}#{key}{flags}. L'id è di 22 caratteri base64url coniati dal server da 16 byte casuali. La key è di 43 caratteri di base64url senza padding che codificano esattamente 32 byte casuali, generati sul dispositivo del mittente. Il suffisso facoltativo flags è un punto più lettere: p significa che serve una passphrase, r che il mittente ha attivato una conferma di lettura, così la pagina può dirti entrambe le cose prima della rivelazione che consuma una visualizzazione. Tutto dopo # è un frammento di URL, e i browser non inviano i frammenti nelle richieste. La chiave non raggiunge alcun server, non appare in alcun log e non fa parte della nota memorizzata. Quest'unica proprietà regge l'intero design.

L'envelope

Ciò che il server memorizza è una stringa JSON opaca: {"v":1,"pw":false,"salt":"…","nonce":"…","ct":"…"} — una versione di formato, il flag della passphrase, un salt di 16 byte, un nonce di 12 byte e il testo cifrato AES-256-GCM con il suo tag di autenticazione di 16 byte accodato, tutto in base64 standard. L'ordine delle chiavi nell'envelope non è significativo; viene analizzato come JSON.

Il payload, byte per byte

Il testo in chiaro sotto la cifratura è {"v":1,"t":"<text>","ah":<seconds>} — la nota (fino a 50.000 caratteri) e il timer di occultamento automatico. La sua serializzazione è deterministica in ogni implementazione: chiavi esattamente in quell'ordine, nessuno spazio, escaping limitato a \" \\ \n \r \t \b \f più \u00XX per i restanti caratteri di controllo, tutto il resto emesso letteralmente come UTF-8. Le implementazioni di riferimento Swift, WebCrypto e Python producono byte identici, e i vettori condivisi lo impongono.

Derivazione della chiave

La chiave di cifratura è HKDF-SHA256(ikm, salt, info = "BurnPony-v1-key", 32 bytes). Senza passphrase, ikm è la chiave del frammento. Con una, la passphrase viene rafforzata da PBKDF2-HMAC-SHA256 a 600.000 iterazioni sullo stesso salt e anteposta alla chiave del frammento. Le passphrase vengono canonicalizzate prima del rafforzamento in ogni implementazione: normalizzazione Unicode NFC, poi rimozione degli spazi iniziali e finali incluso U+FEFF. Uno spazio da tastiera di troppo o un input Unicode decomposto è perdonato; i cambi di maiuscole e di spaziatura interna, deliberatamente no. Una passphrase errata e un testo cifrato manomesso falliscono entrambi semplicemente l'autenticazione GCM; nulla distingue i due.

L'API

Sei operazioni sotto /api, tutte JSON. POST /notes memorizza un envelope con un limite di visualizzazioni (1–100), una scadenza (da 300 a 2.592.000 secondi) e il flag della ricevuta, restituendo l'id della nota e un token di gestione monouso di 64 esadecimali. GET /notes/{id} consuma una visualizzazione dentro una transazione e annulla il testo cifrato quando il limite è raggiunto. Con il token di gestione: GET …/status riporta i contatori e gli orari delle ricevute, DELETE brucia subito, PATCH ri-temporizza una nota viva contata da adesso, e POST /notes/{id}/push registra fino a cinque token APNs o FCM per la consegna delle ricevute. POST /report/{id} presenta una segnalazione di abuso senza consumare una visualizzazione. Il token è memorizzato lato server solo come hash SHA-256, e ogni errore che potrebbe distinguere un token errato da una nota bruciata da una nota mai esistita restituisce il {"error":"not_found"} identico al byte — la costanza è deliberata.

Cosa memorizza il server

Testo cifrato (annullato al brucia o all'esaurimento), contatori delle visualizzazioni, timestamp, l'hash del token, timestamp delle ricevute, token push per le note con ricevuta, contatori di limitazione del tasso con hash e un segreto locale, e segnalazioni di abuso. Nessun account, nessuna email, nessun indirizzo IP grezzo, nessuna analitica. Uno sweeper elimina le righe scadute, ma la scadenza è applicata anche in lettura, quindi un cron morto non estende mai la vita di una nota.

Conferme di lettura, meccanicamente

Le ricevute sono divulgate al destinatario prima che riveli — è a questo che serve il flag r. Il payload del push è deliberatamente generico: l'avviso dice solo che una nota è stata aperta, e l'unica chiave personalizzata è l'id della nota. Il dispositivo del mittente riscrive la notifica localmente usando un'etichetta che non esiste da nessuna parte tranne quel dispositivo; il server non conosce alcuna etichetta.

Verificalo tu stesso

Tre verifiche, nessuna fiducia richiesta. Uno: la pagina del destinatario è riproducibile — scripts/assemble_viewer.py --verify nel repository dimostra che la viewer.html distribuita è identica al byte alle sue tre parti verificabili, e Visualizza sorgente su qualsiasi nota viva permette il confronto con il file pubblicato. Due: la crittografia è fissata — shared/burnpony_vectors.json contiene la verità di riferimento da un'implementazione indipendente, e sia la suite di test Swift sia il core WebCrypto incorporato riproducono ogni testo cifrato esattamente. Tre: la povertà del server è leggibile — server/schema.sql e il router dell'API sono abbastanza brevi da leggere in una volta, e non c'è nulla in essi da trafugare. I limiti onesti reggono comunque: una nota rivelata può sempre essere copiata o fotografata, e l'operatore potrebbe servire codice diverso da quello pubblicato — ed è proprio per questo che la pagina è riproducibile e questa specifica esiste, così che la sostituzione sia rilevabile.

Il tour più amichevole dello stesso meccanismo è nella documentazione; il modello di sicurezza e la via di segnalazione sono sulla pagina sulla sicurezza.