// the specification

O protocolo v1, exatamente.

Tudo abaixo está escrito com base em código congelado e publicado. Cada constante pode ser verificada em o repositório, e os vetores de teste entre implementações fixam o comportamento em nível de byte. Se esta página e o código um dia divergirem, o código é a verdade e a divergência é um bug que vale a pena relatar.

A versão curta

  • O texto da nota é criptografado no dispositivo do remetente. Nenhum servidor jamais o vê.
  • A chave de descriptografia viaja apenas no fragmento do link, a parte após #, que os navegadores nunca enviam a nenhum servidor.
  • A descriptografia acontece no navegador do destinatário, em JavaScript que qualquer um pode ler com Ver código-fonte.
  • O token de gerenciamento por trás de queimar, expiração e status pode controlar uma nota mas nunca descriptografá-la. O servidor armazena apenas seu hash SHA-256.
  • O link completo não pode ser reconstruído após a tela de nota criada fechar. O app mantém apenas o id da nota e o token, nunca a chave.

O link

burnpony.app/n/{id}#{key}{flags}. O id tem 22 caracteres base64url cunhados pelo servidor a partir de 16 bytes aleatórios. A key tem 43 caracteres de base64url sem preenchimento codificando exatamente 32 bytes aleatórios, gerados no dispositivo do remetente. O sufixo opcional flags é um ponto mais letras: p significa que uma frase secreta é necessária, r que o remetente ativou uma confirmação de leitura, para que a página possa lhe dizer ambas as coisas antes da revelação que consome visualização. Tudo após # é um fragmento de URL, e os navegadores não enviam fragmentos nas requisições. A chave nunca chega a nenhum servidor, não aparece em nenhum log e não faz parte da nota armazenada. Essa única propriedade sustenta todo o design.

O envelope

O que o servidor armazena é uma string JSON opaca: {"v":1,"pw":false,"salt":"…","nonce":"…","ct":"…"} — uma versão de formato, o sinalizador de frase secreta, um sal de 16 bytes, um nonce de 12 bytes e o texto cifrado AES-256-GCM com sua tag de autenticação de 16 bytes anexada, tudo em base64 padrão. A ordem das chaves no envelope não é significativa; ele é analisado como JSON.

O payload, byte a byte

O texto claro sob a criptografia é {"v":1,"t":"<text>","ah":<seconds>} — a nota (até 50.000 caracteres) e o temporizador de ocultar automaticamente. Sua serialização é determinística em toda implementação: chaves exatamente nessa ordem, sem espaços, escape limitado a \" \\ \n \r \t \b \f mais \u00XX para os caracteres de controle restantes, todo o resto emitido literalmente como UTF-8. As implementações de referência em Swift, WebCrypto e Python produzem bytes idênticos, e os vetores compartilhados o impõem.

Derivação de chave

A chave de criptografia é HKDF-SHA256(ikm, salt, info = "BurnPony-v1-key", 32 bytes). Sem frase secreta, ikm é a chave do fragmento. Com uma, a frase é reforçada por PBKDF2-HMAC-SHA256 a 600.000 iterações sobre o mesmo sal e anexada primeiro à chave do fragmento. As frases são canonicalizadas antes do reforço em toda implementação: normalização Unicode NFC, depois corte de espaços iniciais e finais incluindo U+FEFF. Um espaço de teclado perdido ou entrada Unicode decomposta é perdoado; mudanças de maiúsculas e de espaçamento interno, deliberadamente não. Uma frase errada e um texto cifrado adulterado ambos simplesmente falham na autenticação GCM; nada os distingue.

A API

Seis operações sob /api, todas JSON. POST /notes armazena um envelope com um teto de visualizações (1–100), uma expiração (300 a 2.592.000 segundos) e o sinalizador de confirmação, retornando o id da nota e um token de gerenciamento de uso único de 64 hex. GET /notes/{id} consome uma visualização dentro de uma transação e anula o texto cifrado quando o teto é atingido. Com o token de gerenciamento: GET …/status reporta contadores e horários de confirmação, DELETE queima imediatamente, PATCH re-temporiza uma nota viva contada a partir de agora, e POST /notes/{id}/push registra até cinco tokens APNs ou FCM para entrega de confirmações. POST /report/{id} registra uma denúncia de abuso sem consumir uma visualização. O token é armazenado no servidor apenas como hash SHA-256, e toda falha que pudesse distinguir um token errado de uma nota queimada de uma nota que nunca existiu retorna o {"error":"not_found"} idêntico byte a byte — a constância é deliberada.

O que o servidor armazena

Texto cifrado (anulado ao queimar ou esgotar), contadores de visualizações, carimbos de data/hora, o hash do token, carimbos de confirmação, tokens push para notas com confirmação, contadores de limitação de taxa com hash e um segredo local, e denúncias de abuso. Sem contas, sem e-mails, sem endereços IP brutos, sem analítica. Um varredor apaga as linhas expiradas, mas a expiração também é aplicada no momento da leitura, então um cron morto nunca prolonga a vida de uma nota.

Confirmações de leitura, mecanicamente

As confirmações são divulgadas ao destinatário antes de ele revelar — é para isso que o sinalizador r existe. O payload do push é propositalmente genérico: o alerta diz apenas que uma nota foi aberta, e a única chave personalizada é o id da nota. O próprio dispositivo do remetente reescreve a notificação localmente usando um rótulo que não existe em lugar algum além daquele dispositivo; o servidor nunca conhece nenhum rótulo.

Verifique você mesmo

Três verificações, sem confiança necessária. Uma: a página do destinatário é reproduzível — scripts/assemble_viewer.py --verify no repositório prova que a viewer.html publicada é idêntica byte a byte às suas três partes auditáveis, e Ver código-fonte em qualquer nota viva permite comparar com o arquivo publicado. Duas: a criptografia é fixada — shared/burnpony_vectors.json guarda a verdade de referência de uma implementação independente, e tanto a suíte de testes Swift quanto o núcleo WebCrypto embutido reproduzem cada texto cifrado exatamente. Três: a pobreza do servidor é legível — server/schema.sql e o roteador da API são curtos o bastante para ler de uma vez, e não há nada neles para vazar. Os limites honestos permanecem de qualquer forma: uma nota revelada sempre pode ser copiada ou fotografada, e o operador poderia servir código diferente do publicado — que é precisamente por isso que a página é reproduzível e esta especificação existe, para que a substituição seja detectável.

O passeio mais amigável pela mesma maquinaria está na documentação; o modelo de segurança e o caminho de relato estão na página de segurança.