Pular para o conteúdo

O registro assinado no ponto de encontro (ADR 0062)

Status: aceito. A direção é a da spec de 23/09/2026 («SEELE-ENC/2: o registro assinado»). Os valores do limite e do teto (D-enc-1), o portão no publicar.sh (D-enc-2), o teto por prefixo (D-enc-3) e o cookie de ida e volta (D-enc-4) foram decididos por quem responde pelo projeto em 2026-10-08, como estão abaixo; os dois últimos depois de uma revisão independente. Data: 2026-10-08 Sobre docs/superpowers/specs/2026-09-23-sala-pessoal-por-codigo-design.md («Achar a pessoa») e o spec-mestre da 0.16 (docs/superpowers/specs/2026-10-08-0.16-design.md, §2 «F2», §3 item 13, §4). Revê o §2.4 do ADR 0047, que recusou o registro assinado.

Contexto

O ADR 0047, no §2.4, recusou o registro assinado no quarto. O argumento era que ele compraria pouco: quem chega confere a impressão digital dentro do TLS, então o teto do estrago de quem ocupa uma marca é «não entrar», e quem escreveu primeiro fica enquanto reaviva. O ADR terminava dizendo que a camada seria a resposta certa para um problema que ainda não existia.

O código de uma pessoa (ADR 0060) cria o problema. O link carrega um endereço; o código não carrega nada, e a sala pessoal só é achável pelo quarto. Com o registro de hoje:

pessoa que só fechou o app há um minuto, e, com a marca da escuta, recebe pelo LEVE o IP, a porta e o instante de quem tenta ligar;

leva até um minuto para valer;

mesma casa se ligarem sem *hairpin* no roteador.

A chave Ed25519 da pessoa já existe, e o código sai dela: o ponto pode conferir que quem registra a marca de um código é o dono do código.

Decisão

Duas versões ao mesmo tempo

O ponto fala o SEELE-ENC/1 e o SEELE-ENC/2, separados pela magia e pelo tamanho. Quem está na 0.15 não muda nada: o /1 responde byte a byte como antes (um_ponto_de_encontro_reiniciado_responde_igual). A sala pessoal registra e é procurada pelo /2; um servidor guardado continua no /1, porque o certificado dele é P-256, e não a chave Ed25519 da pessoa.

A sala descobre os endereços públicos pelo /1 (ONDE e LEVE, que não guardam nada) e se registra pelo /2.

O fio

Todo datagrama do /2 tem 384 bytes, pedido e resposta, completados com .: a resposta nunca é maior que o pedido, e o ponto nunca amplifica. O maior MORO2 possível tem 322 bytes, com o cookie. A spec falava em «até 512 bytes»; o tamanho fixo é a regra mais simples de provar. A folga não compra um campo novo sem versão nova: cada linha tem um número exato de campos, e cada motivo é um de quatro, então um campo ou um motivo a mais faz o leitor de hoje ver lixo e calar.

quemlinha
anfitrião → pontoSEELE-ENC/2 MORO2 <marca> <chave> <carimbo> <dicas> <assinatura> <cookie> (- sem cookie)
quem liga → pontoSEELE-ENC/2 QUEM2 <marca>
ponto → anfitriãoSEELE-ENC/2 DESAFIO <marca> <cookie>
ponto → anfitriãoSEELE-ENC/2 MOROU <marca> <endereço visto> <cookie>
ponto → quem ligaSEELE-ENC/2 AQUI2 <marca> <endereço> <dicas>
ponto → quem ligaSEELE-ENC/2 NINGUEM <marca>
ponto → quem pediuSEELE-ENC/2 RECUSA <marca> <motivo> <agora>

base64url, sem = e sem bits sobrando; o carimbo e o agora em segundos Unix, sem sinal e sem zero à esquerda; um endereço só se o texto for o que o Display escreve. Medido: o SocketAddr aceita [fe80::1%3]:80 e :08385, e o u64 aceita +1 e 007.

≥ 1024, no máximo duas, - para nenhuma. O ponto recusa um registro com dica fora disso, e quem liga recusa uma resposta com dica fora disso: um ponto hostil não manda quem liga bater dentro da própria rede por uma dica inventada.

ROTULO_MORO2 ‖ u8 len(marca) ‖ marca ‖ chave ‖ carimbo u64 BE ‖ u8 n ‖ n × (família ‖ IP ‖ porta BE), com o rótulo "SEELE-ENC/2 moro\0", distinto do S2 e da sala. A menor mensagem tem 60 bytes, então nenhuma cabe nos 32 de um nonce. O endereço público não vai assinado: quem o diz é o ponto, que o vê.

O cookie de ida e volta (D-enc-4)

Um MORO2 sem cookie, ou com um que não confere para o IP de onde ele veio, ouve um DESAFIO com o cookie. O ponto não guarda nada para isso, e o pedido não gasta o balde. Só um MORO2 com o cookie válido segue.

"SEELE-ENC/2 cookie\0" ‖ família ‖ IP ‖ janela, com a janela de 120 s; vale a corrente e a anterior (de dois a quatro minutos). O HMAC é o hmac do RustCrypto, conferido com os vetores da RFC 4231, e a conferência é o verify_truncated_left dele, que compara em tempo constante (o ct_eq do ctutils). Nenhuma cripto escrita no ponto, e um guarda de fonte prende que o cookie não é comparado com ==. Não o ring, que também está na árvore: ele compila C no build, e o ponto é Rust puro para quem o reimplanta numa VPS sem compilador de C.

e o socket do servidor, que sai pelo mesmo IP público, usa o mesmo. O MOROU leva o cookie da janela corrente, para a sala renová-lo antes de ele vencer: ela só ouve DESAFIO na subida, depois de o ponto reiniciar, e depois de trocar de IP.

junta ao MORO2 sem assinar de novo.

(getrandom), só em memória: nunca em disco, nunca no --barulhento, e ninguém configura nada. Reiniciar o ponto sorteia outro, os cookies de antes param de valer, e cada sala ouve um DESAFIO e volta no reavivamento seguinte.

O que ele fecha: quem forja a origem não recebe o cookie do IP que forja. Ele não registra nada em nome desse IP, e não esvazia o balde de registro dele (o achado da revisão: com o IP que o QUEM2 entrega a quem tem o código de alguém, uns poucos pacotes por segundo calavam a sala dessa pessoa).

O que o ponto confere, nesta ordem

A ordem é a do custo: o cookie, um HMAC, vem antes do limite, para que pedido forjado não gaste o balde de ninguém; o limite por prefixo vem antes da assinatura, que é o que custa; e a assinatura vem antes do relógio, para que um carimbo torto só seja dito a quem assinou com uma chave que confere. A hora do ponto em si não é segredo: a RECUSA taxa a leva a qualquer um.

  1. o cookie (acima);
  2. o limite por prefixo (abaixo);
  3. que a marca é a escuta ou o servidor de Marcas::da_sala(Codigo::da_chave(chave))

(o aviso não mora no quarto, e não se registra);

  1. a assinatura, com verify_strict. Com o verify, a chave identidade e a

assinatura R = identidade, s = 0 conferem com qualquer mensagem, e qualquer um registraria a sala do código dela; o teste prende isso;

  1. |carimbo − agora| ≤ 300 s.

Uma assinatura que não confere é calada. O resto ouve a resposta.

O quarto assinado

Separado do quarto da versão 1. O QUEM2 só lê o assinado. Assim «o registro assinado vence o não assinado» vale por construção: um MORO sem assinatura não chega onde o QUEM2 lê. E o MORO do /1, que é de graça, não enche o lugar das salas.

endereço: uma troca de rede vale no reavivamento seguinte, sem os 60 s de trava do /1.

relógio de quem registra voltou. O carimbo guardado continua o maior. De outro endereço, RECUSA velho: é um MORO2 copiado no caminho e repetido de outro lugar.

e é lembrado enquanto o carimbo dele passaria na tolerância: a repetição depois do prazo continua recusada.

com o quarto no teto, ele sai antes de um registro novo ouvir cheio. Contado no teto, um MORO2 com o carimbo 300 s no futuro seguraria a vaga por ~600 s, e quatro IPv4 encheriam o quarto para sempre (medido na revisão de 08/10/2026). O preço é perder, sob pressão, a lembrança que recusa a repetição.

enquanto ele vale.

um CGNAT, duas marcas cada, com folga. A marca que passaria a viver num prefixo no teto, inclusive a que muda para ele, ouve RECUSA cheio; quem já mora ali continua reavivando. Contar os vivos do prefixo varre o quarto, e só se conta quando a marca não está viva ali.

quando não acha. O /1 cala para a marca vazia. Calar aqui repetiria o próprio ADR 0047: com o ponto de produção sem o /2, quem liga leria «ninguém mora» quando a verdade é «o ponto não fala a versão 2». Com o NINGUEM explícito e o ONDE correndo junto, quem liga separa os casos: achou; o ponto fala e ninguém mora; o ponto recusou por excesso; o ponto responde ao ONDE e cala o QUEM2 (Enc2Calado: é anterior ao /2, ou a recusa, dita uma vez por segundo, foi para outro pedido); o ponto está mudo. Enquanto só o ONDE respondeu, quem liga pergunta de novo dentro do prazo, e a volta depois da janela de um segundo ouve a recusa.

Os valores (D-enc-1)

valoro que ele aguenta
limite por prefixo (/32 no IPv4, /64 no IPv6), só no /2, um balde para o registro e outro para a perguntabalde de 64 pedidos, que enche a 8 por segundo60 SEELE abertos atrás de um mesmo IP, para sempre
teto do quarto assinado16 384 marcas, separado das 4096 do /1~8 000 apps abertos; ~4,6 MB cheio
teto da tabela de prefixos65 536; um balde cheio está ocioso e sai na varredura~4,6 MB cheia

A conta do CGNAT. Um app 0.16 aberto registra a sala sempre: dois MORO2 a cada 15 s, 0,133 por segundo. O que importa num CGNAT não é quantos assinantes dividem um IPv4, e sim quantos SEELE abertos o dividem: o limite só corta acima de 60. O fator do CGNAT de quem responde pelo projeto não foi medido, e com esta folga não precisa ser. A rajada de 64 cobre dez ligações tentadas ao mesmo tempo (seis QUEM2 cada).

Quem passa do limite ouve, e não é calado. O ponto responde RECUSA <marca> taxa <agora>, no máximo uma vez por segundo por prefixo; o resto da enxurrada é calado, para a recusa não virar um refletor de quem forja a origem. O anfitrião passa a Recusado(Taxa) e escreve no seele.log que o limite é por endereço público, e que a rede pode dividi-lo com outras pessoas; quem liga recebe PontoRecusou. Com a tabela de prefixos cheia de prefixos ativos, o prefixo novo também ouve a recusa.

O abuso que o limite segura: um prefixo sozinho ocupa no máximo 128 marcas vivas (D-enc-3), e o teto conta só as vivas, então encher as 16 384 pede 128 prefixos, e com o cookie cada um deles tem de ser de verdade. Um /48 IPv6 tem 65 536 /64, e o limite por /64 não o segura: um limite por /48 é o segundo degrau, se um dia for preciso.

Um balde para o registro e outro para a pergunta. Com um balde só, nove QUEM2 por segundo de um vizinho de CGNAT, ou de quem forja o IP de alguém (o IP que o próprio QUEM2 entrega a quem tem o código dessa pessoa), calavam os MORO2 daquela rede, e a sala sumia do quarto em 60 s (a revisão de 08/10/2026). A pergunta é o pedido mais barato de mandar, e ela não esvazia mais o balde do registro.

O anfitrião diz o que aconteceu

O MORO2 da escuta volta para a escuta, e é por ele que a sala sabe como está o registro: Registrado, RelogioTorto { diferenca_s }, Recusado(motivo) ou SemResposta (dois reavivamentos sem resposta: o ponto caiu, ou é anterior ao /2). Cada mudança vai ao seele.log em info, uma vez, e fica num estado que a casca lê. Um registro calado por relógio torto é dito, com a diferença em segundos.

O anfitrião não ajusta o carimbo pela hora do ponto. Uma RECUSA forjada, com uma hora falsa, faria a sala assinar carimbos do futuro, que alguém no caminho guardaria para repetir depois. O pedido é dizer, e é o que se faz.

A VPS vai antes do app (D-enc-2)

O ponto novo é compatível com a 0.15, e o app novo sem o ponto novo não acha ninguém pelo código. O sondar fala o /2 e mede o relógio do ponto; com --exige-enc2, ele reprova quando alguma família que respondeu ao ONDE não fala o /2, ou quando o relógio do ponto está a mais de um minuto do da máquina que sonda. O empacotar/publicar.sh roda essa conferência antes de qualquer passo com efeito, e para a publicação se o ponto em produção não falar o /2; o corpo do release diz qual ponto foi conferido. --sem-ponto pula, para a emergência que não depende do ponto, e o corpo do release diz. A linha legada não é conferida. Nenhum teste fala com o ponto de produção.

Consequências

ar, em que IP público, e com que dicas de rede local; quem tem o código aprende o mesmo, sem ligar (spec de 23/09, «Privacidade»). A alavanca de quem recebe é «não receber ligações». Isto vai para a página de privacidade.

registro de toda sala. O docs/ponto-de-encontro.md pede o NTP, e o sondar mede.

- Quem está no caminho entre a sala e o ponto, e entrega a cópia de um MORO2 antes do original, aponta a marca para si até o reavivamento seguinte; com a da escuta, recebe pelo LEVE o IP e a porta de quem liga nesse tempo. A cópia repetida depois do original é recusada de outro endereço; do mesmo socket, ela só renova o registro e volta as dicas às do MORO2 copiado. - A mensagem assinada não diz para qual ponto ela é, então um MORO2 vale em qualquer ponto. - Quem forja a origem não registra nem esvazia o balde de registro de ninguém (o cookie). Ele ainda esvazia o balde de perguntas de um IP com QUEM2 forjados: quem está nele não procura ninguém pelo código enquanto isso durar, e continua achável. E ele faz o ponto mandar um DESAFIO a um IP que não pediu nada, do tamanho do pedido, como o ONDE de sempre. - Quem é o ponto continua escolhendo o endereço público que devolve. - Em todos os casos, o TLS, com o código, recusa o servidor errado (ConfereCodigo).

Alternativas recusadas

provar a mesma propriedade.

chave: o ponto já é confiado com o endereço, e o ganho seria só nas dicas, que quem liga filtra.

assinado: o mesmo resultado com regras de convivência, e o /1 de graça enchendo o lugar das salas.

endereço do socket do servidor depois de perguntar, e um NAT que troca a porta deixaria a assinatura errada até o tique seguinte. O cookie prova a mesma coisa (o caminho de volta) sem a sala precisar saber.

ligou para ela (vem no certificado), e um MORO2 forjado com ela gastaria o balde antes da assinatura.

Como reverter

Desligar o /2 no ponto: ele passa a calar para a magia nova, e quem liga lê Enc2Calado. O /1 não muda.