Roteamento Onion
Como cada hop aprende só o próprio próximo passo
Lightning · Técnico
No encaminhamento de HTLCs, um pagamento pode passar por vários canais antes de chegar ao destino. Sem uma camada de privacidade, cada hop poderia aprender origem, destino, tamanho da rota e talvez correlacionar tentativas. O roteamento onion, especificado na BOLT 4, reduz esse vazamento: cada hop aprende apenas de quem recebeu, para quem deve encaminhar, quanto deve encaminhar e qual CLTV usar.
A Lightning usa um esquema baseado em Sphinx. A origem calcula a rota inteira, cria um pacote de tamanho fixo e coloca uma camada criptografada para cada hop. O pacote viaja dentro do campo onion_routing_packet da mensagem update_add_htlc, descrita na BOLT 2.
Onion routing não torna o pagamento anônimo. Ele limita o que cada hop vê. Tráfego de rede, timing, valores incomuns, probing e observação on-chain ainda podem vazar informação.
Conteúdo
Modelo mental
Pense em uma rota simples:
Alice -> Bob -> Carol -> Dina Alice é a origem. Dina é o destino. Bob e Carol são hops intermediários. Alice conhece a rota inteira porque ela fez a busca de caminho. Mas Bob não deve saber que Dina é o destino final, e Carol não deve saber que Alice é a origem.
O pacote onion resolve isso assim:
- Bob recebe uma camada que diz: encaminhe certo valor para Carol por certo canal e com certo CLTV.
- Carol recebe uma camada que diz: encaminhe certo valor para Dina por certo canal e com certo CLTV.
- Dina recebe a camada final, com
payment_secret, valor final e, se existir,payment_metadata. - Todos veem um pacote de tamanho fixo, então o tamanho não revela facilmente a posição do hop na rota.
Descrição longa do diagrama
O diagrama mostra uma cebola com camadas externas e internas. A camada externa pertence ao primeiro hop, as camadas seguintes pertencem aos hops intermediários, e o núcleo pertence ao destino. Setas indicam que cada hop remove apenas sua camada e repassa o restante.
Pacote onion
Na BOLT 4, o pacote onion de pagamento tem 1366 bytes e quatro partes principais:
| Campo | Tamanho | Função |
|---|---|---|
| version | 1 byte | Versão do pacote onion. Na versão 0, o valor é 0x00. |
| public_key | 33 bytes | Chave pública efêmera comprimida usada pelo hop para calcular o segredo compartilhado. |
| hop_payloads | 1300 bytes | Área fixa com payloads por hop, HMACs e filler ofuscado. |
| hmac | 32 bytes | Autenticação do pacote para o hop atual. |
O campo hop_payloads tem 1300 bytes. Dentro dele aparecem payloads variáveis por hop, cada um prefixado por um tamanho em BigSize, seguido do payload, de um HMAC e de filler. Esse tamanho fixo ajuda a esconder quantos hops ainda restam.
O pacote onion não substitui o HTLC. Ele viaja dentro do HTLC e diz aos hops como verificar e encaminhar o próximo HTLC.
Segredos e chaves derivadas
A origem cria uma chave efêmera e calcula um segredo compartilhado com cada hop usando ECDH em secp256k1. Cada hop também consegue calcular o próprio segredo compartilhado usando sua chave privada e a chave efêmera recebida. Os outros segredos continuam escondidos.
Descrição longa do diagrama
O diagrama mostra a chave efêmera da origem sendo combinada com a chave pública de cada hop. De cada combinação sai um segredo compartilhado. Setas a partir do segredo mostram as chaves derivadas usadas para ofuscação, HMAC, erro e filler.
Todas essas chaves saem do mesmo segredo compartilhado, via HMAC-SHA256 com rótulos fixos (rho, mu, um, ammag, pad). Como cada hop tem um segredo diferente, cada hop deriva um conjunto de chaves diferente.
| Chave | Uso |
|---|---|
| rho | Gerar o stream que ofusca cada hop_payload — e o filler determinístico que mantém o tamanho fixo. |
| mu | Gerar e verificar os HMACs de integridade do pacote. |
| um | Autenticar (HMAC) as mensagens de erro retornadas ao pagador. |
| ammag | Gerar o stream que ofusca a failure message no caminho de volta. |
| pad | Gerar o preenchimento pseudoaleatório inicial do pacote, antes dos payloads reais. |
A chave efêmera também é cegada a cada salto. Isso significa que o próximo hop vê outra chave efêmera, impedindo que hops diferentes simplesmente comparem o mesmo ponto público para correlacionar a rota.
Payload TLV por hop
O payload moderno por hop usa o formato TLV da BOLT 1. Isso permite extensões sem mudar a estrutura inteira do pacote.
| Tipo | Nome | Hop final | Hop intermediário | Significado |
|---|---|---|---|---|
| 2 | amt_to_forward | sim | sim | Valor em msat que este hop deve encaminhar ou entregar. |
| 4 | outgoing_cltv_value | sim | sim | CLTV que o HTLC de saída deve usar. |
| 6 | short_channel_id | não | sim | Canal de saída para o próximo hop. |
| 8 | payment_data | sim | não | payment_secret e total_msat vindos da invoice BOLT 11. |
| 10 | encrypted_recipient_data | blinded | blinded | Dados cifrados fornecidos pelo recebedor para caminhos cegados. |
| 12 | current_path_key | blinded | blinded | Chave de caminho usada em route blinding. |
| 16 | payment_metadata | sim | não | Metadados da invoice, se o campo m existir. |
| 18 | total_amount_msat | blinded | não | Valor total em msat usado em pagamentos com caminho cegado. |
Para um hop intermediário fora de caminho cegado, os campos centrais são amt_to_forward, outgoing_cltv_value e short_channel_id. Para o destino, não há short_channel_id, porque não existe próximo canal. O destino recebe payment_data com o payment_secret da invoice BOLT 11 e o total_msat.
hop intermediário:
type 2 amt_to_forward = 101000 msat
type 4 outgoing_cltv_value = 850144
type 6 short_channel_id = 700000x42x0
hop final:
type 2 amt_to_forward = 100000 msat
type 4 outgoing_cltv_value = 850018
type 8 payment_data:
payment_secret = 32 bytes
total_msat = 100000 O hop usa esses dados para conferir se o HTLC recebido bate com o que a origem prometeu no onion. Por exemplo, se o HTLC recebido não tem valor suficiente para pagar a taxa e ainda encaminhar amt_to_forward, o hop deve falhar o pagamento.
Construção da cebola
A origem monta o pacote de dentro para fora, porque a camada externa precisa conter, cifrada, a camada interna pronta. O cálculo de valor e CLTV também anda de trás para frente: o destino recebe o valor final; cada hop anterior precisa receber esse valor mais sua taxa, e um CLTV de entrada maior que o CLTV de saída.
rota escolhida:
Alice -> Bob -> Carol -> Dina
1. Alice pega as chaves públicas dos hops.
2. Alice cria uma chave efêmera inicial.
3. Para cada hop, Alice calcula um segredo ECDH.
4. De cada segredo, Alice deriva rho, mu, um e pad.
5. Alice monta os payloads do destino para a origem:
- Dina recebe payment_data e valor final.
- Carol recebe amt_to_forward, outgoing_cltv_value e short_channel_id para Dina.
- Bob recebe amt_to_forward, outgoing_cltv_value e short_channel_id para Carol.
6. Alice adiciona HMACs e filler para manter hop_payloads com 1300 bytes.
7. Alice envia update_add_htlc para Bob com onion_routing_packet.
8. Cada hop descasca uma camada e encaminha a cebola interna. Esse processo conecta onion routing com gossip e busca de caminho. O gossip fornece short_channel_id, taxas e cltv_expiry_delta. A busca escolhe a rota. O onion transforma essa rota em instruções privadas por hop.
Descascando uma camada
Quando um hop recebe o HTLC, ele não conhece a rota inteira. Ele recebe um pacote onion e processa apenas a camada destinada a ele:
ao receber um onion packet:
1. calcular shared_secret = ECDH(chave_privada_do_hop, public_key_efêmera)
2. derivar rho, mu, um e pad
3. verificar o HMAC do pacote atual (com a chave mu)
4. descriptografar o primeiro hop_payload (com o stream rho)
5. validar amt_to_forward e outgoing_cltv_value contra o HTLC recebido
6. olhar o HMAC destinado ao próximo hop:
- se for 32 bytes de zero -> este nó é o destino final
- se for diferente de zero -> ainda há hops adiante
7. se ainda há hops adiante:
- ler short_channel_id
- cegar a public_key efêmera para o próximo hop
- encaminhar update_add_htlc com a cebola restante
8. se este nó é o destino:
- validar payment_secret
- validar total_msat, metadata e CLTV final
- cumprir ou falhar o HTLC O passo 6 esconde um detalhe elegante do design: nada no pacote diz explicitamente quem é o destino. Cada payload carrega o HMAC do próximo hop. Quando a origem monta a camada mais interna (a do destino), não existe próximo hop, então ela coloca ali um HMAC de 32 bytes de zero. É só isso que o último nó vê: ao descascar sua camada e encontrar um HMAC todo zerado, ele conclui que é o destino final e para de encaminhar.
Descrição longa do diagrama
O diagrama mostra um hop recebendo uma cebola e uma chave efêmera. O hop calcula o segredo compartilhado, verifica o HMAC, abre sua camada, lê valor, CLTV e próximo canal, cega a chave efêmera e encaminha uma cebola restante ao próximo hop.
Filler e tamanho fixo
Um pacote onion tem sempre o mesmo tamanho, tenha a rota 3 ou 20 hops. Se o pacote encolhesse a cada camada descascada, qualquer hop poderia estimar quantos saltos faltam só medindo o que recebeu. O truque que mantém os 1300 bytes constantes é o filler: um preenchimento pseudoaleatório, porém determinístico, que a origem consegue prever de antemão.
hop_payloads tem sempre 1300 bytes, em todos os hops.
construção (origem, do destino para a origem):
para cada hop, desloca hop_payloads para a direita por
shift = bigsize(payload) + tamanho(payload) + 32 (HMAC)
e escreve o payload + HMAC do hop no espaço aberto.
a origem também pré-calcula o "filler": os bytes que
vão aparecer na cauda quando cada hop descascar sua camada.
processamento (em cada hop):
1. anexa 1300 bytes de zero ao hop_payloads recebido
2. aplica o stream rho (XOR) sobre os 2600 bytes
3. lê o próprio payload e o HMAC do próximo hop
4. os 1300 bytes seguintes já são o hop_payloads a
encaminhar; a cauda reaparece como o filler previsto
como a origem incluiu esse mesmo filler ao calcular cada HMAC,
o HMAC do próximo hop confere sem que ninguém precise saber
quantos hops ainda faltam. A parte contraintuitiva é por que o HMAC continua válido. Quando um hop descasca sua camada, a cauda do campo passa a conter bytes que ele nunca viu antes — o filler. Se esses bytes fossem imprevisíveis, o HMAC calculado pela origem para o próximo hop não bateria. Como o filler é gerado deterministicamente a partir da chave rho de cada hop, a origem consegue calcular exatamente os mesmos bytes na hora de montar o pacote e já os inclui no HMAC. Assim, cada hop verifica a integridade do que recebeu sem nunca saber quantas camadas existem antes ou depois dele.
Filler e pad não são a mesma coisa. O filler é a cauda que reaparece conforme os hops descascam a cebola. A chave pad gera o preenchimento inicial do pacote na origem, quando a rota é curta e sobra espaço nos 1300 bytes.
Ferramenta: construtor didático de rota onion
A ferramenta abaixo calcula valor e CLTV de trás para frente e mostra o que cada hop veria em sua camada. Ela é didática: não implementa Sphinx real, não cria HMACs reais e não deve ser usada para construir pagamentos reais. Use apenas nomes e valores fictícios.
Construtor de Rota Onion
Erros onion
Quando algo falha, o erro precisa voltar para a origem sem revelar o diagnóstico aos intermediários. O hop que detecta a falha monta uma failure message, autentica com a chave um, ofusca com um stream derivado da chave ammag e devolve o pacote pelo caminho reverso. Cada hop anterior aplica sua própria camada de ofuscação ammag. No fim, só a origem consegue descascar todas as camadas, testar o HMAC de cada hop e descobrir exatamente onde a tentativa falhou.
Estrutura da failure message
O pacote de erro tem tamanho fixo, pela mesma razão do pacote onion: o tamanho não deve revelar qual erro ocorreu nem a que distância aconteceu.
failure onion (devolvido ao pagador):
hmac 32 bytes HMAC com a chave um do hop que falhou
failure_len 2 bytes tamanho da failuremsg (u16)
failuremsg variável código de 2 bytes + dados do erro
pad_len 2 bytes tamanho do preenchimento (u16)
pad variável zeros de preenchimento
regra: failure_len + pad_len >= 256
(o recomendado é somar exatamente 256)
para que o tamanho não revele qual erro ocorreu. A failuremsg começa com um código de 2 bytes. Alguns códigos carregam dados extras — por exemplo, um channel_update assinado, para que a origem atualize sua visão das taxas ou do estado do canal antes de tentar de novo.
update_fail_htlc × update_fail_malformed_htlc
Há dois jeitos de falhar um HTLC recebido, e a diferença importa:
update_fail_htlc— caso normal. O onion foi decifrado e o HMAC conferiu, mas alguma regra falhou (liquidez, política, CLTV). O erro volta cifrado comammag, como acima.update_fail_malformed_htlc— usado quando o próprio onion é inválido (falha do tipo BADONION): versão desconhecida, HMAC do pacote não confere ou chave efêmera inválida. Aqui o hop não conseguiu derivar as chaves para cifrar um erro normal, então devolve apenas o código BADONION e o hash SHA256 do onion recebido, e o peer anterior refaz o erro cifrado para a origem.
Códigos de falha
Cada código de 2 bytes combina, no byte alto, bits de categoria que dizem como tratar a falha:
BADONION(0x8000) — a cebola recebida é impossível de interpretar.PERM(0x4000) — falha permanente; não adianta repetir por este trecho.NODE(0x2000) — a falha é do nó, não de um canal específico.UPDATE(0x1000) — parâmetro de canal violado; o erro traz umchannel_updatemais recente.
| Categoria | Código | Significado |
|---|---|---|
| BADONION | PERM | invalid_onion_hmac | O HMAC do pacote onion não confere; a cebola chegou corrompida. |
| PERM | unknown_next_peer | O short_channel_id pedido não leva a nenhum peer conhecido. |
| UPDATE | temporary_channel_failure | Falha temporária no canal, por exemplo liquidez insuficiente agora. |
| UPDATE | fee_insufficient | A taxa embutida no HTLC é menor que a política atual do canal. |
| UPDATE | incorrect_cltv_expiry | O CLTV de saída não respeita o cltv_expiry_delta anunciado pelo hop. |
| NODE | temporary_node_failure | O nó está temporariamente incapaz de processar o encaminhamento. |
| PERM | incorrect_or_unknown_payment_details | O destino não reconhece o pagamento (hash, valor ou payment_secret). |
| BADONION | PERM | invalid_onion_blinding | Erro em caminho cegado; todos os hops devolvem este mesmo código para não vazar a posição da falha. |
Isso permite retry inteligente. Se um canal falhou por liquidez insuficiente, política desatualizada, CLTV incorreto ou canal desativado, a carteira pode penalizar aquele trecho e tentar outra rota. Essa informação alimenta heurísticas de pathfinding, mas não vira verdade global sobre a rede.
Erros também vazam informação para a origem. Isso é necessário para retry, mas também permite probing: enviar tentativas para aprender sobre liquidez e topologia privada.
Route blinding
No onion routing clássico, a origem conhece todos os hops, inclusive o destino. Isso protege bastante contra intermediários, mas não esconde o destino da origem. Route blinding, também especificado na BOLT 4, permite que o recebedor forneça uma parte final da rota em forma cegada.
Em alto nível:
- O recebedor escolhe um ponto de entrada público ou alcançável.
- O recebedor cria uma sequência de hops cegados até ele.
- Para cada hop cegado, o recebedor fornece
encrypted_recipient_datacom instruções cifradas. - O pagador monta a rota até o ponto de entrada e inclui os dados cegados no payload.
- Os hops cegados encaminham usando as instruções cifradas, sem revelar ao pagador a identidade real do destino final.
Em caminhos cegados, qualquer falha é convertida no mesmo código invalid_onion_blinding (e devolvida via update_fail_malformed_htlc), justamente para que a posição da falha dentro do trecho cegado não vaze.
BOLT 12 usa esse mecanismo para offers e invoices modernas. Por isso, em BOLT 12, a privacidade do recebedor melhora: o pagador não precisa aprender necessariamente o node id público final.
Privacidade e limites
O onion routing protege contra um hop individual curioso, mas não contra todos os adversários. Alguns limites reais:
- Um hop sabe o peer anterior, o peer seguinte, o valor que recebeu, o valor que encaminhou e o CLTV de entrada/saída.
- Dois hops controlados pelo mesmo atacante podem tentar correlacionar timing e valores.
- O primeiro hop sabe quem enviou o HTLC para ele, mas não sabe se esse peer é a origem real ou apenas outro encaminhador.
- O último hop sabe que está entregando ao destino, mas não sabe a origem real.
- Valores exatos, rotas curtas e tentativas repetidas podem reduzir o conjunto de anonimato.
- Falhas retornadas ajudam a origem, mas também tornam probing possível.
Armadilhas comuns
- Dizer que onion routing torna pagamentos anônimos. Ele reduz vazamento por hop, mas não elimina correlação.
- Confundir payload TLV de hop com mensagem wire. O payload viaja dentro do onion packet, que viaja dentro de
update_add_htlc. - Esquecer que o pacote tem tamanho fixo e filler. Sem isso, o tamanho revelaria a posição na rota.
- Achar que algum campo marca o destino. Não há: o destino é reconhecido pelo HMAC de 32 bytes de zero na camada final.
- Colocar
short_channel_idno payload do destino final fora de route blinding. O destino não tem próximo canal a encaminhar. - Não validar
amt_to_forwardeoutgoing_cltv_valuecontra o HTLC recebido. - Tratar route blinding como obrigatório em todo pagamento. Ele é recurso específico, importante para BOLT 12 e fluxos modernos, mas nem todo pagamento BOLT 11 usa blinded paths.
Resumo
- A origem escolhe a rota inteira e monta o pacote de dentro para fora.
- Cada hop recebe apenas suas instruções: valor, CLTV e próximo canal ou dados finais.
- O pacote usa chave efêmera, segredos ECDH, HMACs, filler e tamanho fixo de 1366 bytes.
- O destino se reconhece pelo HMAC de 32 bytes de zero, e o filler determinístico mantém os HMACs válidos sem revelar a distância.
- O payload moderno por hop é TLV e se conecta diretamente a BOLT 1, BOLT 4, BOLT 7 e BOLT 11.
- Erros voltam cifrados (com
umeammag) para permitir retry sem revelar diagnóstico aos intermediários. - Route blinding permite esconder a parte final da rota do pagador e é essencial em BOLT 12.
Mapa de dependências conceituais
Antes de ler esta página, ajuda conhecer:
- Operação de Canais e Encaminhamento
- Pedidos de Pagamento BOLT 11
- Protocolo Wire
- Curva Elíptica
- Função Hash
Depois desta página, siga para:
- Gossip e o Grafo de Canais
- Busca de Caminho
- Segurança e Privacidade a fundo
- Construtor de Rota Onion
Referências técnicas usadas
- BOLT 4 — Onion Routing Protocol
- BOLT 1 — Base Protocol e TLV
- BOLT 2 — Peer Protocol for Channel Management
- BOLT 7 — Routing Gossip
- BOLT 11 — Invoice Protocol
- BOLT 12 — Offers e blinded paths
A seguir: como os nós anunciam canais públicos, políticas de roteamento e identidades para formar o grafo usado na busca de caminho.