
Série: Zcash from First Principles . Artigo 4 . Árvores de Merkle Público: iniciantes. Partimos do Artigo 3 (hashing e compromissos). Se você sabe o que é uma impressão digital e um compromisso, está pronto. O que você vai levar: uma visão intuitiva e correta das árvores de Merkle, de como provar pertencimento sem revelar a qual item você se refere, e exatamente como isso se torna a árvore de compromissos de note do Zcash.
O Artigo 0 descreveu um "quadro público" que guarda cada note já criada e que só cresce. A esta altura você já pode adivinhar o que está preso nele: compromissos (Artigo 3), os envelopes lacrados. Mas um quadro real conteria centenas de milhões deles. Como a rede armazena isso, verifica isso e permite que você prove que seu envelope está no quadro sem apontar para ele? A resposta é uma das estruturas mais elegantes da ciência da computação: a árvore de Merkle.
Dois problemas aparecem no momento em que você tem uma lista pública gigante de compromissos.
Problema um: integridade em escala. Se a lista tem 300 milhões de entradas, como alguém confirma que nem uma única foi alterada secretamente? Verificar novamente 300 milhões de itens a cada olhada é inviável.
Problema dois: pertencimento privado. Para gastar uma note (Artigo 0), você precisa provar que seu compromisso realmente está no quadro. Mas se você apontar para ele ("é a entrada número 4,201,337!"), acabou de se desanonimizar. Você precisa provar "meu envelope está em algum lugar deste quadro" sem revelar qual deles.
Uma árvore de Merkle resolve ambos de uma vez. Ela comprime a lista inteira em uma única impressão digital e permite provar pertencimento com uma prova minúscula que oculta a posição.
Imagine uma chave de torneio eliminatório, mas em vez de jogadores avançando, impressões digitais são combinadas.

A propriedade mais importante decorre diretamente do efeito avalanche (Artigo 3):
A raiz é uma impressão digital de tudo o que está abaixo dela. Mude qualquer folha, mesmo em um único bit, e sua impressão digital muda, o que muda seu pai, o que muda aquele pai, até o topo. A raiz muda. Portanto, um único pequeno valor de raiz certifica a integridade da lista inteira. Isso resolve o Problema um.
Vamos construir a árvore de quatro folhas acima com impressões digitais SHA-256 reais sobre as folhas A, B, C, D (digests mostrados truncados para facilitar a leitura):
hA = 559aead08264... hB = df7e70e50215...
hC = 6b23c0d5f35d... hD = 3f39d5c348e5...
hAB = H(hA , hB) = 63956f0ce48e...
hCD = H(hC , hD) = 98a2fbfddbc7...
ROOT = H(hAB , hCD) = 1b3faa3fcc5e...
Tudo é apenas "aplicar hash a uma coisa e depois aplicar hash a pares de hashes". Nada mais exótico do que o Artigo 3, organizado em forma de árvore.
Agora o Problema dois. Digamos que você queira provar que a folha C está na árvore, para alguém que conhece apenas a raiz. Você não entrega a árvore inteira. Você entrega apenas as impressões digitais necessárias para subir de C até a raiz, chamadas de caminho de autenticação (ou prova de Merkle):
Para provar que
Cestá na árvore, forneça:
- seu irmão
hD, e- seu tio
hAB.
O verificador, conhecendo apenas a raiz, recomputa a subida:
step 1: H(hC , hD) = hCD (combine C with its sibling)
step 2: H(hAB , hCD) = ROOT? (combine with the uncle)
Calculado de verdade: isso produz 1b3faa3fcc5e..., que corresponde à raiz. A folha está comprovadamente na árvore.

Duas coisas tornam isso poderoso:
n folhas, você fornece apenas cerca de log_2(n) hashes. Para um bilhão de folhas, isso dá aproximadamente 30 hashes, não um bilhão. A prova quase não cresce enquanto a árvore explode de tamanho.Agora podemos afirmar com precisão o que realmente é o "quadro público" do Artigo 0:
A árvore de compromissos de note é uma árvore de Merkle cujas folhas são compromissos de note. Toda vez que uma note é criada em qualquer lugar do mundo, seu compromisso é anexado como a próxima folha, e a raiz é atualizada.
Alguns detalhes reais:
2^(32) (mais de quatro bilhões) notes.
A árvore prova que uma note existe. Ela não impede, por si só, que você gaste a mesma note duas vezes. Esse trabalho pertence ao conjunto de nulificadores do Artigo 0: uma coleção separada de "tokens anulados". Quando você gasta, publica o nulificador da note, e a rede rejeita qualquer nulificador que já tenha visto antes.
Assim, as duas estruturas públicas desempenham papéis complementares, e mantê-las separadas é exatamente o que rompe o vínculo entre o nascimento de uma note e sua morte:
| Estrutura | Pergunta que responde | Atualizada quando |
|---|---|---|
| Árvore de compromissos de notes | "Esta note existe?" | Uma note é criada (compromisso anexado) |
| Conjunto de nulificadores | "Esta note já foi gasta?" | Uma note é gasta (nulificador publicado) |
Simplificações, como sempre. Árvores de Merkle incrementais reais acompanham nós de "fronteira" para que a raiz possa ser atualizada sem reconstruir tudo; a rede mantém uma janela de anchors recentes, não apenas o mais recente, para que as carteiras não quebrem a cada novo bloco; e folhas vazias usam um valor de padding definido. Também desenhamos árvores binárias com potências de dois bem organizadas. Nada disso muda a intuição: folhas de compromissos, com hash aplicado em pares até uma única raiz, com provas curtas de pertencimento. A contabilidade exata volta no artigo sobre o protocolo.
| Termo | Significado em linguagem simples |
|---|---|
| Merkle tree | Uma árvore de hashes; as folhas são impressões digitais dos dados, os pais aplicam hash aos seus filhos |
| Leaf | Um nó na base; no Zcash, um compromisso de note |
| Merkle root | A única impressão digital no topo que resume a árvore inteira |
| Authentication path / Merkle proof | Os hashes irmãos necessários para provar que uma folha está na árvore |
| Incremental Merkle tree | Uma árvore de Merkle somente de anexação (as folhas são apenas adicionadas) |
| Anchor | Uma raiz de Merkle à qual um gasto faz referência como "o estado da árvore contra o qual estou provando" |
| Nullifier set | A coleção separada de marcadores de gasto que bloqueia gastos duplos |
Por que uma árvore e não apenas uma lista longa de hashes? Uma lista plana obrigaria você a revelar ou processar cada entrada para provar pertencimento. Uma árvore oferece provas de tamanho logarítmico e uma única raiz para integridade.
O verificador precisa da árvore inteira? Não. O verificador só precisa da raiz mais o seu curto caminho de autenticação. Esse é exatamente o ponto.
Por que profundidade 32 especificamente? Ela limita a árvore a cerca de quatro bilhões de notes, o que dá uma boa margem, ao mesmo tempo em que mantém a prova de pertencimento (e seu custo dentro do circuito) em um tamanho fixo e administrável.
Se a raiz muda com cada nova note, como provas antigas continuam válidas? A rede se lembra de uma janela de raízes recentes (anchors), então uma prova feita contra um anchor um pouco mais antigo ainda é verificada. O artigo sobre o protocolo explica isso com precisão.
Na nossa árvore de 4 folhas, suponha que um atacante troque secretamente a folha C por um valor diferente, mas deixe a raiz publicada inalterada. O que dá errado para ele e por que ele não consegue corrigir isso discretamente? (Resposta abaixo.)
Mudar C muda hC (efeito avalanche), o que muda hCD = H(hC, hD), o que muda ROOT = H(hAB, hCD). Então a raiz recomputada não corresponde mais à raiz publicada, e a adulteração é detectada. Para "corrigir isso discretamente", ele precisaria encontrar um C diferente que produzisse o mesmo hC, o que é uma colisão de hash, inviável pelo Artigo 3. A integridade se mantém.
Artigo 5 . Provas de conhecimento zero: o crescendo. Agora já construímos notes, compromissos e a árvore, e continuamos dizendo "provado em conhecimento zero". O Artigo 5 finalmente explica como você pode provar que uma afirmação é verdadeira, que sua note está na árvore, que seu nulificador está correto, que o dinheiro fecha, sem revelar nada disso.
Parte da série Zcash from First Principles para ZecHub. Licenciado sob CC BY-SA 4.0.