Não peça para confiar na nossa auditoria.
Confira.
O verificador abaixo é Rust compilado para WebAssembly. Ele roda no seu navegador, contra uma chave pública que qualquer um pode ler, e não fala com nenhum servidor nosso.
Log editável não é evidência
Toda plataforma tem um log. E todo log pertence a quem o guarda.
Se o registro de que uma mensagem tinha consentimento mora numa tabela que o fornecedor administra, o que você tem não é prova — é a palavra dele, escrita num lugar que ele pode reescrever. Isso basta na maior parte dos dias, e deixa de bastar exatamente no dia em que alguém precisa da prova.
A pergunta que separa as duas coisas é simples e desconfortável:se o registro tivesse sido alterado, você teria como saber? Um log, sozinho, responde não. É por isso que o produto não se apoia num log — se apoia numa cadeia encadeada, numa árvore e numa assinatura, que é o que a próxima seção descreve.
Todas as plataformas registram. Nenhuma prova.
Da ação à raiz assinada, em cinco passos
Cada passo consome o anterior. É por isso que mexer em qualquer um deles aparece.
A ação vira um evento canônico
O que aconteceu é serializado com as chaves em ordem e sem espaço. Duas máquinas que serializem o mesmo evento produzem os mesmos bytes — sem isso, nada depois é comparável.
O evento vira um hash
SHA-256 daqueles bytes. É esse resumo que o banco guarda ao lado do evento, e é ele que o verificador recalcula: quem confere não acredita no hash declarado, refaz a conta.
O hash entra numa cadeia
Cada evento carrega o hash do anterior. O primeiro aponta para o genesis — trinta e dois bytes zero. Reescrever um evento antigo quebra todos os elos seguintes de uma vez.
A janela vira uma árvore
Periodicamente, os eventos de uma janela viram uma árvore de Merkle. Cada folha e cada nó carregam um prefixo de domínio distinto, como manda a RFC 6962 — é o que impede alguém forjar uma folha que colida com um nó interno.
A raiz é assinada
A raiz da janela recebe uma assinatura ECDSA-P256. Ela é o único ponto em que a nossa palavra entra — e é justamente o ponto que a chave pública torna conferível por terceiro.
O verificador
A prova de exemplo, e a sua.
Abaixo, uma prova de exemplo: cinco eventos sintéticos, com hashes, árvore e assinatura de verdade. Clique em verificar e o cálculo roda aqui. Clique em adulterar e ele falha — e o modo como ele falha é o que interessa.
Este registro tem uma prova. Confira você mesmo.
O verificador roda no seu navegador, em WebAssembly — com o JavaScript desligado ele não tem como rodar. A anatomia acima continua valendo.
Ou confira a SUA prova
Se você já é cliente, exporte a prova de um registro e solte o arquivo aqui. Ele não sai do seu computador: o navegador o lê, o WebAssembly o confere, e nenhuma requisição é feita.
O formato é o mesmo do exemplo acima — a página o descreve campo a campo em Como a prova é montada.
Este cálculo rodou no seu computador. Nenhum dado foi enviado para nós — o binário é baixado uma vez e roda no seu navegador.
O botão de exportar ainda não existe no painel. O formato abaixo é o que o produto vai emitir, e é o que este verificador já aceita — está escrito aqui antes de existir para que ninguém precise adivinhá-lo depois.
Como a prova é montada
Oito campos. Nenhum deles é opcional, e o verificador diz qual falta.
| Campo | Tipo | O que ele é |
|---|---|---|
| payloadHashes | lista de hexadecimais de 64 dígitos | o hash de cada evento da cadeia, em ordem cronológica |
| prevHashes | lista, do mesmo tamanho | o encadeamento declarado. O primeiro tem de ser o genesis; cada seguinte, o hash do evento anterior |
| provado | número | qual evento da cadeia esta prova demonstra pertencer à janela |
| caminho | lista de hexadecimais | os nós irmãos que levam a folha até a raiz, da base para o topo |
| caminhoNaDireita | lista de booleanos, do mesmo tamanho | para cada irmão, se ele fica à direita. O lado é informação: trocá-lo faz o caminho não fechar |
| raiz | hexadecimal de 64 dígitos | a raiz da janela. O verificador a RE-DERIVA do caminho e compara — não confia nesta |
| assinaturaDer | hexadecimal | a assinatura ECDSA-P256 da raiz, em DER |
| chavePublicaPem | texto em PEM | a chave que confere a assinatura |
O verificador recusa um arquivo mal formado dizendo qual campo falta, e nunca chama isso de falha de verificação. A distinção não é preciosismo: “falhou” é uma acusação de adulteração, e um verificador que a usa para dedo trocado não serve para nada sério.
A chave pública
É o único ponto em que a nossa palavra entra — e é por isso que ela é pública.
A assinatura da raiz é feita com uma chave privada que só o nosso processo de auditoria tem. A metade pública dela é publicada, e é com ela que o seu navegador confere a assinatura. Se as duas metades não casarem, a verificação falha — e é assim que deve ser.
Há duas chaves nesta página, e elas não são a mesma. A de produção é a abaixo, e é a única que vale para conferir uma prova de verdade. A prova de exemplo logo acima carrega outra, gerada e descartada no momento em que ela foi montada: essa prova que o verificador funciona, e não prova nada sobre o produto.
Como conferir que é esta a chave
Ela é servida em https://flow.fieli.app/.well-known/audit-pubkey.json, com o identificador v_64v-D_WwxUYkq-. Compare o que está lá com o que a sua prova carrega: se divergirem, alguma das duas não é a nossa.
-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEi+z95BlJiLFJEfwIsa24fKxrPA1q 3icwQ8zUOtjErHvISnOkKDi0d8ui430xo3cfQ4FhSkOKfuk/FmJ5N8eiCQ== -----END PUBLIC KEY-----
Por que assim, e não de outro jeito
Três decisões que parecem detalhe e não são.
Prefixo de domínio nas folhas e nos nós
A folha é o hash de um byte zero seguido do conteúdo; o nó interno, de um byte um seguido dos dois filhos. Sem essa separação, alguém pode forjar uma folha cujo conteúdo coincide com o hash de um nó interno e provar inclusão de algo que nunca esteve na árvore. É a mesma construção dos registros de transparência de certificados.
A raiz é re-derivada, nunca aceita
O verificador não confere a raiz que veio no arquivo contra a assinatura e para por aí. Ele reconstrói a raiz a partir da folha e do caminho de irmãos, e só então compara. Uma raiz forjada casaria com a própria assinatura e mesmo assim seria recusada.
A cadeia existe além da árvore
A árvore prova que um evento pertence a uma janela. A cadeia prova que os eventos vieram na ordem em que dizem ter vindo. São garantias diferentes, e o verificador confere as duas — remover um evento do meio passaria pela primeira e não passa pela segunda.
Roda no seu computador, e você pode conferir isso também
Nenhuma requisição sai da sua máquina durante a verificação.
O verificador é um binário de pouco mais de oitenta kilobytes, compilado de Rust para WebAssembly. Ele é baixado uma vez, quando você clica, e a partir daí o cálculo é local. Abra as ferramentas de desenvolvedor do seu navegador, vá à aba de rede e clique em verificar de novo: você não vai ver requisição nenhuma.
O binário é o mesmo que o painel do produto usa, e a procedência dele está versionada neste repositório junto com o comando que o compila — se algum dia ele for trocado, o nosso próprio processo de publicação reprova antes de ir ao ar.
O que isso muda numa fiscalização
Em linguagem de quem vai responder, e não de quem escreveu o código.
Numa fiscalização, a pergunta que chega não é “vocês têm log?”. É alguma variação de mostre que esta mensagem específica tinha base legal, e que este registro não foi produzido depois do fato.
Com um log comum, a resposta é uma exportação que o próprio fornecedor gerou. Ela é aceita ou não conforme a confiança que a autoridade deposite em quem a gerou — e essa confiança não é sua, é dele.
Com uma prova, a resposta é um arquivo que qualquer um verifica sem pedir nada a ninguém, incluindo o fiscal, incluindo o juiz, incluindo o perito da outra parte. O que muda não é o rigor do seu processo: é quem precisa acreditar em quem.
Log imutável é promessa. Prova de Merkle é fato.