Provas de conhecimento zero: o que são zk-STARKs e como funcionam? (zk-Stark V2)

Publicado em 21 de out. de 2024Atualizado em 10 de nov. de 2024Leitura de 14min14

O que é Prova de Reservas e Prova de conhecimento zero?

Prova de Reservas (PoR)

Este é um processo para corretoras de criptomoedas mostrarem que têm ativos suficientes para cobrir todos os saldos dos clientes. A Prova de Reservas gera confiança ao provar que a corretora não está ocultando nenhum passivo. A maneira mais simples de fazer isso é publicando tanto o valor de ativos da corretora quanto uma lista de saldos de usuários para que todos possam conferir:

  • O total de ativos em holding que a OKX afirma possuir é a soma do saldo total de ativos de cada usuário.

  • O saldo total de cada usuário é superior a zero, e seus ativos são contabilizados, cobrindo seus passivos e garantindo que cada usuário tenha patrimônio líquido positivo.

  • O valor total que a corretora declara abrange todos os usuários, permitindo que cada usuário verifique se seu valor líquido está incluído no total.

No entanto, revelar esses saldos pode comprometer a privacidade do usuário. Para resolver isso, usamos um método chamado Prova de conhecimento zero (ZKP - Zero Knowledge Proof) para proteger a privacidade do usuário.

Prova de conhecimento zero (ZKP)

É uma técnica de segurança que possibilita que a corretora de criptomoedas prove que uma afirmação é verdadeira sem revelar nenhuma informação adicional.

No nosso caso, queremos provar que temos fundos suficientes sem compartilhar detalhes específicos do usuário. A maioria dos ZKP se enquadra em duas categorias:

  • zk-SNARK

  • zk-STARK

Usamos zk-STARK porque é mais seguro e tem uma premissa de segurança mínima. Neste artigo, explicaremos como usamos zk-STARK para proteger a privacidade do usuário enquanto demonstramos nossa solvência.Antes de continuarmos, é importante entender alguns termos básicos de ZKP, como Circuito, árvore de Merkle e Compromissos.

Para iniciantes, existem muitos recursos disponíveis para ajudar você a começar. Para usuários experientes, você pode conferir o Curso MOOC e a monografia acadêmica.

Como o zk-STARK funciona?

Criamos uma árvore de Merkle usando o hash da conta de cada usuário como as folhas. Cada conta mostra saldos em USD para vários tokens (porexemplo, BTC, ETH) Para gerenciar esses saldos, os separamos em patrimônio que não seja negativo e dívida para cada token. Dessa forma trabalhamos apenas com números positivos, facilitando os cálculos e evitando erros.

Por exemplo:

  • Se o saldo do token BTC de um usuário for A, seu patrimônio em BTC será A e a dívida em BTC será 0.

  • Se o saldo do token ETH de um usuário for -B, seu patrimônio correspondente será 0 e a dívida será B

Em seguida, criaremos uma arvore de Merkle com esses valores de conta como folhas. A raiz da árvore atua como um valor único representando todos os saldos dos usuários. Cada usuário pode provar que sua conta faz parte dessa árvore usando um caminho de Merkle que mostra como sua conta se conecta à raiz.

Também publicamos o patrimônio total e a dívida somados de todos os tokens e usuários. Em seguida, criamos uma Prova de conhecimento zero (ZKP) para mostrar duas coisas:

  • Prova de soma: os valores de patrimônio e dívida na árvore de Merkle são somam corretamente

  • Prova não negativa: o patrimônio total de cada usuário é maior do que sua dívida total

Quando tentamos verificar a árvore de Merkle para um grande número de contas, se torna muito difícil lidar com tudo de uma vez. Para superar esse desafio, dividimos as contas em grupos menores chamados lotes. Cada lote é processado separadamente usando circuitos em lote, que verifica a parte inferior da árvore de Merkle.

Esses lotes facilitam o gerenciamento e também nos possibilitam executar essas verificações ao mesmo tempo (processamento paralelo). Uma vez que temos os resultados de cada lote, usamos outra camada de circuitos, chamada circuitos recursivos, para combinar e verificar todos os lotes juntos até que tenhamos conferido toda a árvore de Merkle.

O que é circuito em lote?

O circuito em lote recebe 1024 contas (acc0, acc1,..., acc1023) como entradas e gera três saídas principais: um hash (hbatch), um valor total de patrimônio (ebatch) e um valor total de dívida (dbatch). O que confirma que:

  • O patrimônio total denominado em USD de cada conta é maior do que sua dívida total

  • ebatch é a soma de todos os valores de patrimônio denominados em USD nessas contas

  • dbatch é a soma de todos os valores de dívida denominados em USD nessas contas

  • hbatch é a raiz da árvore de Merkle criada usando os hashes das contas

  • Não há sobrecarga durante a soma para ebatch e dbatch

O que é circuito recursivo?

O circuito recursivo conta com 64 provas diferentes (π0, ..., π63), hashes (h0, ..., h63), patrimônios (e0, ..., e63) e dívidas (d0, ..., d63) dos circuitos da camada inferior como entradas. Ele combina essas entradas e produz três saídas: um novo hash (hrecursive), patrimônio total (erecursive) e dívida total (drecursive). O que confirma que:

  • Cada uma das 64 provas é válida

  • Cada prova π0, ..., π63 do circuito de camada inferior é válido

  • erecursive é a soma de e0, ..., e63

  • drecursive é a soma de d0, ..., d63

  • hrecursive é o hash da concatenação de h0, ..., h63, porexemplo,

    • hrecursive = hash (h0 || h1 || ... || h63)

  • Não há sobrecarga durante a soma para erecursive e drecursive

Qual é a relação entre circuitos em lote e circuitos recursivos?

O diagrama abaixo ilustra como o circuito em lote e os circuitos recursivos se conectam e trocam dados entre si. Tenha em mente que no diagrama duplicamos os circuitos para fins ilustrativos, mas na nossa implementação usamos apenas um circuito para cada camada.

CT-web-PoR-relationship

Nossa árvore de Merkle é estruturada de uma forma um pouco diferente. Nos 10 níveis inferiores, cada nó pai tem 2 filhos, enquanto nos níveis superiores, cada pai tem 64 filhos. Isto ocorre porque os circuitos em lote gerenciam a parte inferior e os circuitos recursivos gerenciam a parte superior. O diagrama abaixo usa um exemplo com "Alice" para mostrar a árvore de Merkle e sua prova de Merkle (em verde).

CT-web-PoR-example

Para saber mais detalhes técnicos sobre como ajustamos os números das contas para se adequarem ao tamanho do lote ou escolhemos o algoritmo de hash correto, confira esta página.

Avanços na versão 2 do zk-PoR

Nossa versão 2 do zk-PoR fez vários avanços em relação à versão anterior:

  • Maior eficiência: agora ele está 50 vezes mais rápido do que a versão anterior. Leva 3 horas em uma única máquina de 10 núcleos, em comparação com as 36 horas da versão anterior usando nove máquinas de 64 núcleos. Esse aumento de velocidade se deve ao uso da estrutura Plonky2, que compila circuitos codificados em Rust em uma linguagem de máquina eficiente, em vez de usar scripts Python mais lentos. Também aprimoramos o Plonky2 para executar alguns cálculos em GPUs, reduzindo o tempo em mais 30%.

  • Auditoria facilitada: com a versão 2, usamos uma estrutura de alto nível que gerencia detalhes criptográficos complexos para nós. Isso torna nosso código mais simples, legível e menos propenso a erros.

  • Prova concisa: o tamanho da prova da V2 (~500 KB) é apenas 0,05% da V1 (~1.2 GB). Graças ao método recursivo, as provas podem ser agregadas e condensadas repetidamente em uma única prova.

Como faço a autoverificação da Prova de Reservas (PoR)?

Saiba como você pode verificar se o saldo do seu ativo está incluído como uma folha da árvore de Merkle no zk-STARK:

  1. Faça login na sua conta da OKX, navegue até Ativos e selecione Relatórios de PoR

  2. Selecione Detalhes para visualizar seus dados de auditoria

    CT-web-PoR-step2
  3. Obtenha os dados necessários para verificação manual ao selecionar Copiar dados

    CT-web-PoR-step3
  4. Depois de selecionar Copiar dados, abra o editor de texto (porexemplo, o bloco de notas) em seguida cole e salve a string JSON como um arquivo. O arquivo deve terminar com o nome "_inclusion_proof.json." A string JSON contém o saldo da sua conta e um snapshot do caminho de Merkle, salve o arquivo em uma nova pasta.

  5. Abra um editor de texto (porexemplo, o bloco de notas) em seguida cole e salve a string JSON como um arquivo. O nome do arquivo deve terminar com "_inclusion_proof.json.", salve o arquivo em uma nova pasta.

    • A string JSON contém o saldo da sua conta e um snapshot do caminho de Merkle.

    • O texto JSON é mostrado abaixo:

      {"sum_tree_siblings":["9ffb169fecf075e203edca2af65e4c69fa4331d13ac75ccae4cd5b990c91b675","7149661a789763cb61293ebf5d8bdd5570e79ee203738f87a444c79642b89a79","788aac9e392fa62bc3f79c98c7afd7bb41ee7d5bd496876cd0580080f19e002f","e828a44d345e6799e232aabc57cb2b92986ee1c52b65344d83e79d84b4b571b7","6c0675de9cd6b2be1abd6a98260e7ea776492c4aa9aadf31086f23452cb7c48d","2dfe3aadb5ac00ee0b1110ee8c313afdee85d9f9c62904d6ee79c8f02354d80a","5068ae26192587432892a6de8b54ea25a8aafd1c010ab5e67b55b2c30c6257fa","a1bb026ec9f3d8a1fa1b6f498c40ed8b117a57e1af9816d08d9135ab4fe43a60","119dfcd214191405b7f7f7c7091b89196c0cae818bfcd8252a48f20d9cf3c378","4d9403482ca177c669df34a60bb2afab7a18097012d0b70703c8e59258cdfee6"],"recursive_tree_siblings":[{"right_hashes":["e041eaa366259f873e9e1477aac77362f4b1b460c2d5e1c14907fa9288d66cff","b45a8c503e649ff39543a918996b06fc65f4df9b61d071b22f7342f94862c9be","e00ec1225dfe6b7e950f6b9b8e9d1121bf17eb60c444fd7191b861a2ddddad23","c02c12beb73c03f996508cdce7bef927f0aa8b77ebd899f6a75df83de9d4022e","d36b95f14c5fd5bfaf1347e3177340e2fc9475a77b852321b80527132e7d539c","c0b9770178e70a7bba4ac8aeaadab2bcb2ae7f90d0f678bd463f2c42ff4f4a7b","fab5e7c6f7f8bc6d51f515c5db235cc1ebe987adee8c19c9bc7313e9e266d72c","b3884fb88fc95949c78ca8867cfa9e8a3c4c59fa1a48d8371f7fbfbebda0acfd","0c6da9bdbd40065f92ddaa45297670f2f0bffedb74020c5d5752e70d8b507b77","left_hashes":["1101beee3c6a36a168ceee9d43fcf6cb6de7e5c87ed4d22cd0308c9870d17839","d40a8e9eb4c873996ec515600def480eaa9378ca8481a7bcdf5f77725dbec4ae","63b12566ba8473f502386e92d500664cb63683dca6c26593378dcc9715257b77","166440a8ccbfbc1ce6ec5efaf8bc0b25e1bf692fa972e2729e45ce709d1d35a3","724451ad1d937fc47de5ede930d159dce78093d5e6a1f2e698452f8a29b4de3a","081a88f12d4e23173a1bf5038d4a9413cc92dd421c92261065de06492b5010ec","a76dbb1d4c393539b9546f4460d50ebc7582748d7de63c62c463b793c55bac7c","91e6c21de3f4060e1bd864131a570af42de31bbcd84a5afcbbc8fedcbf806002","fad08eca5bfdc5f37d39eabb44c2216afc6498afcb6b913d72586eaaf132a572","d39b06fe28387ba8045e2b2f95e90613916beef4f79df7961514e6e4cbfd07fa","81d07e300a116a0e4fcb56c39715c5fd5921abe8d10329b07c3f33d417b70ca8","7b72a7e62a45c9958a8a55eec2ba47352f2af701bacba098668589f6a3ce0423","8766bc64c38c2bb4188d89de0e732bca103daaed0c779cba9a8b191e24b51c9c","fa57ae4409e46c605f3cbfd01dfd9ccebc86cbd765cdc067206cb9367832442f"]}, ...... "index":9583119,"account":{"id":"50f5f08cc5036e15a541c64ac4ac6d2d9aa8ddab1ec32ed58b10e6ed3edfad59","debt":["0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0"],"equity":["8412384","9386185","45265193","0","0","8751","3824171","2716990","0","313671","28319","0","0","0","41261","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","142353","0","0","0","0","0","4435","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","662","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","993","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","25132","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","305","0","0","0","0","0","0","0","0","6141","0","0","0","0","0","0","0","0","0","0","0","5511","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0"]}}

  6. Baixe a ferramenta de verificação de código aberto da OKX: zk-STARKValidator

  7. Salve a ferramenta de verificação de código aberto da OKX, zk-STARKValidator, e o arquivo da string JSON juntos na nova pasta criada no passo 5. No nosso caso, colocamos a ferramenta e o arquivo de dados na pasta Downloads, com o nome de "proof-of-reserves”, conforme mostrado abaixo:

    CT-web-PoR-json
  8. Abra o zk-STARKValidator, ele executará automaticamente o arquivo JSON que você salvou na pasta

  9. Verifique o resultado:

    • Se a verificação for aprovada, será exibido o resultado Inclusion constraint validation passed:

      zero-knowledge-proofs-what-are-zk-starks-and-how-do-they-work image 21
    • Se a verificação falhar, será exibido o resultado Inclusion constraint validation failed:

      zero-knowledge-proofs-what-are-zk-starks-and-how-do-they-work image 22

Como faço para verificar o saldo total e a restrição não negativa do zk-STARK?

Saiba como você pode verificar se os ativos que afirmamos possuir são legítimos e que nenhum usuário possui patrimônio líquido negativo:

  1. Navegue até a nossa página Prova de Reservas e selecione Relatório de passivo

  2. Baixe o arquivo zk-STARK e salve-o em uma nova pasta

    CT-web-PoR-self-verification-step2
  3. Descompacte o arquivo para extrair um arquivo “sum_proof_data.json

    CT-web-PoR-json-sum
  4. Baixe a ferramenta de verificação de código aberto da OKX: zk-STARKValidator

  5. Salve a ferramenta de verificação de código aberto da OKX, zk-STARKValidator, e o arquivo"sum_proof_data.json" juntos na nova pasta criada no passo 2. No nosso caso, colocamos a ferramenta e o arquivo de dados na pasta Downloads, com o nome de "proof-of-reserves”, conforme mostrado abaixo:

    CT-web-PoR-json
  6. Abra o zk-STARKValidator, ele executará automaticamente o arquivo sum proof data que você salvou na pasta.

  7. Verifique o resultado

    • Se a verificação for aprovada, será exibido o resultado Total sum and non-negative constraint validation passed:

      zero-knowledge-proofs-what-are-zk-starks-and-how-do-they-work image 21
    • Se a verificação falhar, será exibido o resultado Total sum and non-negative constraint validation failed:

      zero-knowledge-proofs-what-are-zk-starks-and-how-do-they-work image 22

Para explorar mais detalhes técnicos, nosso sistema de Prova de Reservas é de código aberto e está disponível para análise e uso no github.