Nossas vidas digitais estão tão imersas em tecnologias complexas que as tomamos como certas, não entendemos como elas realmente funcionam ou às vezes nem percebemos sua existência. Um exemplo de tal tecnologia está por trás dos aplicativos de mensagens que são usados literalmente por todas as pessoas com um telefone neste planeta.
Vamos dar uma olhada na tecnologia que alimenta o mensageiro Status. Em artigos anteriores, analisamos como nosso mensageiro funciona no geral e comparamos com outros mensageiros populares. Nesse artigo, rastreamos a “vida de uma mensagem” quando Alice se comunica com Bob usando o mensageiro Status. No contexto de possibilitar a jornada dessa mensagem, nós descrevemos em detalhes os vários componentes da rede peer-to-peer (P2P) subjacente, protocolos de privacidade, algoritmos criptográficos e outras questões primitivas de envio de mensagens usados para arquitetar as propriedades de segurança e privacidade do Status Messenger.
Em um paradigma cliente-servidor — como usado em mensageiros populares incluindo Signal e Telegram — muitos dos aspectos acima são relativamente simples, porque os clientes falam com servidores que geralmente são confiáveis, disponíveis e conscientes de como encaminhar mensagens para outros clientes. No entanto, o Status Messenger é arquitetado para redes P2P descentralizadas que não têm o conceito de servidores e clientes.
O sistema descentralizado de mensagens P2P visa a remoção de intermediários centralizados que buscam renda, remoção de pontos únicos de falha e aumento da resistência à censura.
Cada nó é um par, às vezes com capacidades diferentes. As mensagens não tem apenas duas etapas — do cliente de origem ao servidor e então ao cliente destino — como é o caso em arquiteturas cliente-servidor. Em vez disso, elas passam por múltiplos pares e continuam passando mesmo depois de alcançar seu destinatário pretendido, porque os pares não sabem quem é o destinatário final.
Nós buscamos promover privacidade na camada de transporte onde os nós podem alcançar a negação plausível de terem recebido mensagens destinadas a eles. Nós também usamos chaves criptográficas como identificadores de contas ao invés dos números de telefone comumente usados por razões de privacidade e anonimato aprimorados (como descrito neste artigo). Esses aspectos naturalmente adicionam mais complexidade à arquitetura de nossa solução.
Simplificando, a viagem de uma mensagem de Alice para Bob começa com Alice conhecendo a chave Status de bate-papo de Bob e em seguida, inundando a rede P2P com sua mensagem criptografada. Isto é retransmitido entre os pares e eventualmente chega a Bob, que é o único que pode descriptografar a mensagem de Alice. Alice e Bob também sabem como “ouvir” as mensagens um do outro sem que ninguém mais na rede deduza que eles são os destinatários pretendidos das mensagens.
Isso, em resumo, é a vida privada de uma mensagem no Status Messenger.
É verdade que nossa implementação atual não é nem totalmente P2P nem completamente descentralizada, porque, por enquanto, faz certas suposições práticas simplificadoras, para entregar um produto mensageiro funcional. Por exemplo, partes de nossa implementação atual se assemelham a uma relação cliente-servidor e hoje o Status executa todos os nós de retransmissão/armazenamento de mensagens. Entretanto, é possível para qualquer pessoa executar qualquer tipo de nó em nossa arquitetura — especificações são públicas, todo o código é aberto (open-source) e a participação na rede não requer permissão. O que está faltando hoje são incentivos para executar os nós de pares para retransmissão e armazenar (temporariamente) mensagens para outros. Esta é uma área ativa de pesquisas na Status.
Este artigo, portanto, se concentra em descrever nossa arquitetura idealizada, para a qual estamos firmemente caminhando, ao invés da implementação atual que está constantemente evoluindo em direção a essa visão.
Descrevemos os blocos de construção arquitetônica do Status Messenger e ilustramos a jornada da mensagem de Alice até Bob através dessas diferentes camadas. Esse artigo tenta fornecer insights técnicos profundos em nossa pilha de protocolos ao consolidar aspectos chave de nossas várias especificações sobre cliente, conta, transporte seguro, payloads (carga de uma transmissão de dados), uso do Waku e MVDS, ao mesmo tempo em que toma emprestado especificações do Signal sobre X3DH e Double Ratchet.
Pilha de Protocolos
A ilustração acima mostra as cinco camadas da pilha de protocolos do Status Messenger com seus respectivos propósitos no centro e as tecnologias específicas que constituem essa camada à direita.
Sobreposição P2P
A camada de fundo é uma sobreposição P2P na rede TCP/IP. Essa camada implementa uma rede peer-to-peer pública e sem a necessidade de permissão, alimentada pelos protocolos de rede devp2p.
O Devp2p fornece protocolos para a detecção de nós onde os pares usam o protocolo de transporte RLPx baseado em TCP para se comunicarem uns com os outros. A equipe Vac da Status está atualmente trabalhando em um substituto a libp2p que irá suportar múltiplos transportes, melhor negociação de protocolos e NAT transversal, entre outros benefícios.
Tipos de nós: definimos um nó por seu conjunto de capacidades. Essas capacidades são: enviar mensagens, receber mensagens, retransmitir mensagens, armazenar mensagens antigas e inicializar outros nós.
Baseado nessas capacidades, nós definimos os seguintes tipos de nós:
- Nó light: possui apenas capacidade de enviar e receber. Normalmente são clientes móveis com o Status Messenger que possuem recursos limitados e, portanto, só podem enviar e receber suas próprias mensagens.
- Nó full: possui todas as capacidades de envio/recebimento/retransmissão/armazenamento/inicialização de outros nós. Esses nós enviam, recebem e retransmitem para outros nós na rede. Eles podem temporariamente armazenar mensagens destinadas a outros nós que estiverem atualmente offline. Eles também podem auxiliar os nós de bootstrap ajudando-os a descobrir e se conectar a outros nós na rede.
- Nó bootstrap: possui apenas a capacidade de inicializar nós. Esses nós apenas auxiliam os nós bootstrap ajudando-as a descobrir e se conectar a outros nós na rede.
Observe que esses recursos são configuráveis e podem mudar ao longo do tempo de acordo com a capacidade ou contexto do nó. Por exemplo: um cliente móvel pode se comportar como um Nó Light em redes de celular, mas mudar para um Nó Full em Wifi.
Detecção de nó: um nó Status deve descobrir outros pares ou já possui uma lista de pares para se conectar. A Status oferece suporte a uma combinação de protocolos Discovery v5 e Rendezvous com a opção de também utilizar nós estáticos.
O Discovery v5 usa nós bootstrap para descobrir outros pares. É um mecanismo de detecção baseado em Kademlia e pode consumir uma quantidade significativa (pelo menos em dispositivos móveis) de tráfego de rede para operar. Veja este artigo para mais detalhes.
O protocolo Rendezvous é um mecanismo de detecção de pares mais simples e compatível com dispositivos móveis. É um mecanismo de detecção do tipo solicitação-resposta (request-response) e utiliza Registros de Nó Ethereum (ENR) para reportar pares descobertos.
Como alternativa, um cliente pode contar exclusivamente com uma lista de pares estáticos. Embora essa possa ser a maneira mais eficiente, porque na realidade não há nenhuma detecção envolvida, a desvantagem é que esses pares podem estar offline ou indisponíveis. E sem um mecanismo de detecção, não será possível encontrar novos pares.
Privacidade de Transporte
A camada de privacidade de transporte fornece roteamento, proteção de metadados, multicast baseado em tópicos e algoritmos de criptografia para oferecer suporte a bate-papo assíncrono. Isso é implementado pelo Waku, que é nosso sucessor para o Whisper, conforme explicado neste artigo.
O Waku usa o conceito de tópicos para dividir suas mensagens. Os tópicos são strings derivadas de algoritmos especificados e são usados em “envelopes” Waku, onde os envelopes encapsulam mensagens criptografadas junto com este tópico e o tempo de vida (TTL).
Dado que em uma rede P2P, todas as mensagens são transmitidas para todos os nós, os nós têm que “escutar” certos tópicos de seu interesse. Embora em teoria todos possam se comunicar usando um único tópico, isso seria extremamente ineficiente, porque os nós tem que tentar descriptografar todas as mensagens - uma vez que qualquer pessoa pode receber envelopes Waku, ainda depende da capacidade de descriptografar as mensagens para decidir o destinatário correto. A outra extremidade do espectro usaria um tópico exclusivo para cada conversa, mas isso não fornece a privacidade necessária porque seria muito mais fácil detectar se e quando duas partes têm uma conversa ativa.
O Status Messenger atinge um equilíbrio para fornecer o nível desejado de privacidade de transporte usando três tipos de tópicos:
- Tópico de código de contato: Dado que o Status Messenger usa chaves criptográficas como identificadores de conta e que não há servidores centrais na rede P2P, os nós precisam de uma maneira de inicializar o material criptográfico necessário para iniciar uma conversa com outros nós. O tópico do código de contato facilita esse bootstrapping.
O tópico do código de contato é definido como "0x” + publicKey + "-contact-code", onde publicKey é o bate-papo público ou a chave de identidade de um nó. A chave pública de bate-papo é derivada da frase-semente BIP-39 do usuário no caminho m/43'/60'/1581'/0/0 conforme EIP-1581. Cada nó transmite um pacote criptográfico inicial (descrito na próxima seção) neste tópico.
Portanto, se Alice estiver interessada em falar com Bob, ela ouve este tópico com a chave de bate-papo pública de Bob (na equação acima), que presumimos que ela já saiba. Quando a mensagem transmitida por Bob (os nós continuam publicando isso periodicamente) sobre este tópico chega a Alice, ela tem o pacote criptográfico inicial de Bob necessário para iniciar um bate-papo com ele. - Tópico particionado: Alice agora precisa se comunicar com Bob para gerar chaves criptográficas específicas que serão usadas para criptografar mensagens futuras entre eles. O tópico particionado é usado para esta comunicação.
O tópico particionado é definido como "contact-discovery-" + mod (publicKey, 5000), onde é derivado da chave de bate-papo pública do nó dividida em 5000 partições. O número de partições afeta o equilíbrio entre eficiência e privacidade - mais partições fornecem menos privacidade com maior eficiência e vice-versa. Todos os nós continuam ouvindo em seus respectivos tópicos particionados para verificar se algum outro nó está interessado em se comunicar com eles.
Alice inicia a comunicação com Bob em seu tópico particionado, que é utilizado até que Alice e Bob gerem uma chave simétrica entre eles. - Tópico negociado: uma vez que uma chave criptográfica simétrica é gerada entre Alice e Bob, eles devem começar a se comunicar usando um tópico específico para este bate-papo 1:1. O tópico negociado é utilizado para essa comunicação posteriormente.
O tópico negociado é definido como “0x” + primeiros quatro bytes de keccak256(symmetricKey) + “-negotiated", onde symmetricKey é a chave criptográfica simétrica gerada entre Alice e Bob usando uma versão extendida do algoritmo Diffie-Hellman.
Portanto, para resumir, os tópicos usados no fluxo de mensagens entre Alice e Bob são os seguintes:
- Alice ouve o tópico de código de contato de Bob para obter seu pacote criptográfico.
- Alice envia uma mensagem para o tópico particionado de Bob. Alice e Bob geram uma chave simétrica usando uma versão extendida do algoritmo Diffie-Hellman (veja a próxima seção) utilizando os tópicos particionados.
- Alice e Bob começam a utilizar o tópico negociado derivado da chave simétrica compartilhada.
Se Alice não receber o pacote criptográfico de Bob enquanto escuta o tópico do código de contato de Bob (na etapa 1 acima), ela envia uma mensagem criptografada com seu pacote criptográfico para Bob em seu tópico particionado. Bob se envolve com o protocolo da etapa 2 em diante.
Para bate-papos de grupos públicos, o tópico são os primeiros quatro bytes do hash do nome do canal. Bate-papos em grupos privados não possuem um tópico dedicado — as mensagens são enviadas 1:1 para vários destinatários usando o código de contato, tópicos negociados e particionados similares a mensagens privadas 1:1.
O Waku também adiciona uma camada de criptografia ao criptografar mensagens com a chave de identidade do destinatário (no caso de mensagens 1:1 ou grupos de bate-papo privados) ou com uma chave simétrica no caso de mensagens públicas.
Transporte Seguro
Esta camada é responsável por fornecer garantias criptográficas de confidencialidade, integridade, autenticidade e sigilo de envio. É independente de transporte e funciona em redes assíncronas, ou seja, não exige que as duas entidades de comunicação estejam online ao mesmo tempo. É baseado nas especificações X3DH e Double Ratchet do Signal, com algumas adaptações para operar em ambiente P2P.
X3DH: X3DH, que significa Extended Triple Diffie-Hellman, é usado para estabelecer uma chave secreta compartilhada entre duas partes que se autenticam mutuamente com base em suas chaves públicas. Ele fornece sigilo direto e negação plausível em configurações assíncronas.
O X3DH usa o conceito de chaves de identidade, chaves efêmeras e pré-chaves assinadas durante a execução do protocolo que termina em Alice (A) e Bob (B) juntos, gerando uma chave simétrica compartilhada. As três rodadas Diffie-Hellman envolvidas são:
1. DH-1 = DH(IK-A, SPK-B)
2. DH-2 = DH(EK-A, IK-B) e
3. DH-3 = DH(EK-A, SPK-B)
e a última chave simétrica compartilhada (SK) é calculada como:
SK = KDF(DH-1 || DH-2 || DH-3)
onde IK = Chave de Identidade, EK = Chave Efêmera, SPK = PreKey Assinada, DH = Diffie-Hellman e KDF = Função de Derivação de Chave.
O protocolo X3DH especificado pelo Signal envolve três partes: Alice, Bob e um servidor. Mas em nossa configuração P2P, onde não há servidor, os pacotes criptográficos X3DH, que contêm IK (chave de bate-papo pública em nosso caso), assinaturas SPK e PreKey, são trocados pelo tópico de código de contato Waku conforme descrito anteriormente.
Não depender de servidores para distribuir pacotes X3DH torna mais difícil para um observador externo coletar metadados e, portanto, melhora a privacidade. Além disso, nossa implementação não usa PreKeys de uso único (OPK conforme descrito nas especificações do Signal), porque não podemos garantir que sejam usados apenas uma vez sem um protocolo interativo ou armazenando-os em servidores os quais não existem em nossa configuração P2P. Em vez disso, nós rodamos as chaves com mais frequência.
As fases do protocolo X3DH são executadas usando o tópico particionado do Waku, conforme descrito anteriormente, para gerar uma chave simétrica compartilhada entre Alice e Bob. Essa chave simétrica compartilhada é então usada para semear a derivação de chaves de criptografia para mensagens futuras usando o algoritmo Double Ratchet e também para derivar o tópico negociado do Waku utilizado depois disso.
Double Ratchet: O algoritmo Double Ratchet é usado por Alice e Bob para trocar mensagens criptografadas com base na chave secreta compartilhada (gerada pelo X3DH) que fornece criptografia ponta a ponta (E2EE) e sigilo de encaminhamento perfeito (PFS).
Novas chaves são derivadas para cada mensagem, de modo que as chaves anteriores não possam ser calculadas a partir das posteriores (sigilo de encaminhamento). As mensagens também contêm novas chaves públicas Diffie-Hellman que são combinadas com as chaves derivadas, de forma que as chaves posteriores não possam ser calculadas a partir das anteriores. Essas técnicas protegem mensagens criptografadas do passado ou do futuro se as chaves atuais forem comprometidas.
O algoritmo Double Ratchet usa o conceito de cadeias KDF. Em uma sessão entre Alice e Bob, cada entidade armazena uma chave KDF para três correntes: uma corrente raiz, uma corrente de envio e uma corrente de recebimento. A corrente de envio de Alice corresponde à corrente de recebimento de Bob e vice-versa.
Os segredos de saída Diffie-Hellman tornam-se as entradas para a corrente raiz e as chaves de saída da corrente raiz tornam-se novas chaves KDF para as correntes de envio e recebimento. Isso é chamado de catraca Diffie-Hellman. As correntes de envio e recebimento avançam conforme cada mensagem é enviada e recebida e suas chaves de saída são usadas para criptografar e descriptografar mensagens. Isso é chamado de catraca de chave simétrica. A combinação de catracas Diffie-Hellman e chave simétrica é conhecida como o algoritmo Double Ratchet, que é mostrado na ilustração acima da especificação do Signal. (Assista a este vídeo para uma explicação detalhada deste protocolo.)
Sincronização de Dados
Esta camada implementa o protocolo Minimum Viable Data Synchronization (MVDS) que garante consistência de dados com mensagens confiáveis entre pares em uma rede P2P não confiável onde os pares podem estar inacessíveis ou sem resposta.
As cargas nesta camada consistem em registros que são de quatro tipos: Ofertas, Solicitações, Mensagens e Acks (indica recebimento da mensagem). Quando Alice tem uma mensagem para enviar a Bob, ela envia uma Oferta. Se Bob responder com uma Solicitação, ela enviará a ele a Mensagem, à qual ele responderá com um Ack. Deve ser enviado no máximo uma carga por período. As mensagens podem ser enviadas no modo Interativo ou em Lote.
O diagrama acima ilustra o fluxo de cargas no modo interativo. Contagens de envio e períodos são rastreados para retransmissão de cargas.
Dados & Cargas
Esta camada é responsável por implementar a funcionalidade do usuário final de mensagens privadas 1:1, bate-papos de grupos privados e bate-papos públicos. A ilustração a seguir mostra as diferentes partes de uma mensagem, conforme descrito na especificação de carga.
O tipo de mensagem (1:1, grupo privado, grupo público) determina como criptografar uma mensagem específica e quais metadados precisam ser anexados. O tipo de conteúdo determina a carga real da mensagem. Por exemplo, o conteúdo pode ser texto simples, adesivo, emoji, imagem ou uma mensagem de áudio. Todas as cargas úteis são codificadas usando Protobuf.
Jornada de uma Mensagem
Agora que descrevemos todas as camadas de nossa pilha de protocolos e até rastreamos a mensagem de Alice através de partes dela, vamos resumir toda a sequência de etapas para Alice se comunicar com Bob usando o Status Messenger. Assumiremos que Alice e Bob têm o aplicativo Status Messenger instalado em seus respectivos dispositivos móveis que representam os nós Lights do app Status.
- Detecção de Pares: Os aplicativos Status Messenger nos dispositivos móveis de Alice e Bob descobrem pares Status na rede P2P que são nós Full usando os nós de Bootstrap. Esses nós Full são responsáveis por retransmitir as mensagens de Alice para Bob ou até mesmo armazená-las temporariamente se Bob não estiver online.
- Detecção de Contatos: a primeira etapa é para Alice descobrir Bob no Status Messenger. Isso pode acontecer de várias maneiras, como: lendo o QR code de Bob no app Status, adicionando a chave de bate-papo de Bob fora da banda (por um método diferente daquele da comunicação primária), pesquisando o nome ENS de Bob ou adicionando a chave de bate-papo de Bob através de um canal público compartilhado.
- Verificação de Contato: Alice pode então, opcionalmente, verificar se a chave pública de Bob realmente pertence a quem ela pensa ser Bob. Ao checar o pseudônimo de três palavras correspondente, o nome ENS de Bob ou fazendo alguma verificação fora da banda padrão de comunicação.
- Troca de Chave Inicial: Alice então obtém o pacote X3DH de Bob ao ouvir o tópico de código de contato de Bob como descrito anteriormente na seção sobre o Waku.
- Criação de Canal Seguro: protocolos X3DH e Double Ratchet funcionam sobre os tópicos particionados e negociados respectivamente para ajudar a criar um canal criptografado end-to-end seguro entre Alice e Bob.
- Transferência de Mensagens: Tendo estabelecido um canal seguro, Alice pode agora enviar mensagens privadas 1:1 para Bob. As mensagens criptografadas são envoltas em envelopes Waku com o tópico negociado e um TTL (15 segundos atualmente). As mensagens se propagam entre os nós de pares da Status por um tempo, até que seu TTL expire e, em seguida, são salvas em nós Full por até 30 dias. Se Bob estiver online nesse momento, ele receberá a mensagem de Alice. Caso contrário, ele terá que recuperá-la dos nós Full posteriormente.
- Notificação de Mensagem: com as notificações lançadas recentemente na versão 1.4 para Android, Bob também receberá uma notificação se estiver utilizando Android. As notificações para iOS são um trabalho em andamento.
- Retenção de Mensagens: depois que o aplicativo de Bob recebe a mensagem, ela é armazenada no banco de dados criptografado de bate-papo do aplicativo com uma chave derivada da frase inicial BIP-39 do usuário no endereço m/43’/60’/1581’/1/0 de acordo com EIP-1581.
Resumo
Descrevemos as cinco camadas da pilha de protocolos da Status e ilustramos a jornada da mensagem de Alice para Bob através dessas diferentes camadas.
As duas camadas inferiores que implementam a sobreposição P2P com os diferentes tipos de nós e transportam privacidade com tópicos do Waku desviam-se significativamente do paradigma padrão de arquiteturas cliente-servidor usadas por outros mensageiros. Os protocolos usados na camada de Transporte Seguro que fornecem garantias criptográficas foram adaptados para nossa rede P2P descentralizada. As duas camadas superiores que implementam a sincronização de dados e gerenciam as cargas para os três modos diferentes de bate-papo são exclusivas da pilha de protocolos do Status Messenger.
Esperamos que este artigo forneça insights técnicos importantes sobre a jornada da mensagem de Alice para Bob enquanto ela navega por essas camadas de protocolos e passa por vários nós com um único propósito em sua vida — privacidade.
O Status Messenger busca ser o mensageiro definitivo na preservação da privacidade, construído com tecnologia P2P descentralizada.
(Agradecimentos a Andrea Piana, Corey Petty, Jonathan Zerah e Tobias Heldt por revisar os rascunhos deste artigo e fornecer feedback útil. Agradecimentos a Alex Howell pelas atenciosas ilustrações. E, obrigado aos autores das especificações por fornecerem o material básico para este artigo.)