Leia esta página para entender a máquina por trás de cada prova na Rand: o que ela prova, como a execução de um programa se transforma em algo que um validador consegue verificar em milissegundos e de onde vem a privacidade. Para saber o que uma prova custa e como a carteira a produz, veja Provas. Para os parâmetros e as construções de hash, veja Criptografia. Para o conjunto de instruções contra o qual um programa é escrito, veja A ISA RISC-V.
A única afirmação que toda prova faz
A zkVM prova um único tipo de afirmação, para todo programa que a cadeia executa:
Um programa cujo digest é
hc, iniciado embase_pc, executado sobre uma entrada privada comprometida porH_INe um segmento de entrada pública comprometido porH_PUB, terminou em até2ᵗ − 1ciclos, gastou no máximoGAS_LIMITde gas e escreveu estas oito palavras de saída.
Uma transferência blindada é essa afirmação sobre um único programa fixo, o guest do pacote de ativos ocultos, cujo hc está fixado no arquivo de gênese como hc_bundle. Desde a chain 17, ela é feita duas vezes por transferência: uma segunda prova, pequena, sobre o guest de autenticação (hc_auth no arquivo de gênese) mostra que o remetente possui a chave de gasto (Provas). Uma chamada confidencial é a mesma afirmação sobre um programa implantado. Há uma só máquina, um só verificador e um só conjunto de restrições para todos eles. Implantar um programa é, portanto, uma operação de dados (armazenar suas palavras e seu hc), não uma operação criptográfica: não existe circuito por programa nem trusted setup.
O sistema de provas é um STARK construído com Plonky3. Ele precisa apenas de uma função de hash: não há cerimônia de configuração nem curvas elípticas, de modo que não há nada que o algoritmo de Shor possa quebrar.
O pipeline
secret input ──► run the program ──► execution trace ──► polynomials ──► commit ──► FRI ──► proof
(keys, (RV32IM emulator) (nine tables, (one per (8× code, (80 queries,
notes, paths) one row per step) column) Merkle) 20-bit grinding)
every validator: recompute the challenges from the Merkle roots, open 80 random positions,
check the rules there → ≈ 16 ms
A linha superior roda uma vez, na máquina do remetente, em cerca de 100 segundos para um pacote e cerca de 7 segundos para a prova de autorização que o acompanha. A linha inferior roda em cada validador, a partir apenas dos campos públicos e da prova.
1. Execute o programa e registre tudo
A máquina é um processador RISC-V de 32 bits: RV32IM menos FENCE, as instruções CSR e EBREAK (A ISA RISC-V). O provador primeiro executa o programa em um emulador e registra uma linha por ciclo de clock. Esse registro é o rastro de execução. Quem o tivesse em mãos poderia reproduzir a execução passo a passo; o restante do esquema existe para que um verificador possa se convencer de que o rastro está correto sem possuí-lo.
Um programa se comunica com o exterior por meio de oito chamadas de sistema e nada mais:
| chamada de sistema | n.º | o que faz |
|---|---|---|
HALT | 0 | para; as oito palavras de saída tornam-se finais |
WRITE_OUTPUT | 1 | define uma das oito palavras de saída públicas |
READ_INPUT | 2 | lê uma palavra da entrada privada |
POSEIDON2 | 3 | calcula o hash de um buffer na memória, no próprio local |
KECCAK | 4 | uma permutação Keccak-f[1600] |
SHA256 | 5 | uma compressão SHA-256 |
READ_PUBLIC | 6 | lê uma palavra do segmento de entrada pública |
POSEIDON2_LEN | 7 | POSEIDON2 com o comprimento da mensagem incorporado ao hash (conjunto de restrições 7) |
Compromissos de nota, nulificadores e caminhos de Merkle não estão embutidos na máquina. São código guest comum, que monta na memória uma mensagem marcada por domínio e chama POSEIDON2. Essa chamada não faz padding, portanto [a] e [a, 0] geram o mesmo hash. Os guests distribuídos a chamam apenas com mensagens de comprimento fixo. Um programa novo que calcula o hash de dados de comprimento variável usa POSEIDON2_LEN (A ISA RISC-V).
Não existe abortar
O processador não tem nenhuma instrução que se recuse a terminar, então um programa não pode parar quando uma verificação falha. O guest do pacote registra a falha em vez disso: cada verificação que falha aplica um OR com 1 a um registrador chamado bad, e bad é a última palavra incluída no hash do digest de saída:
D = H(64; anchor, nf₀..₃, cm₀..₃, fee, burn_a, burn_r, burn_asset, time, c, bad)
c é o compromisso de autorização que a prova de autenticação também publica (desde a chain 17; o digest das chains 14 a 16 não tinha c).
A cadeia nunca recebe do remetente a pré-imagem de D. Ela reconstrói a pré-imagem a partir dos campos públicos da transação, define bad = 0 por conta própria, calcula o hash e compara. Um trapaceiro pode produzir uma prova perfeitamente válida de uma execução em que uma verificação falhou, mas essa execução imprimiu H(…, 1), e a cadeia procura H(…, 0). Fazê-los coincidir exigiria uma colisão de hash.
2. Divida o rastro em nove tabelas
Uma única tabela descrevendo um processador inteiro seria desnecessariamente larga, então o rastro é dividido em tabelas especializadas, provadas juntas como um único lote:
| tabela | linhas no nível t | o que registra |
|---|---|---|
program | declarado, ≥ 2⁷ | as palavras de instrução do programa, decodificadas no circuito; define o que a CPU pode buscar |
cpu | 2ᵗ | uma linha por ciclo, precedida por linhas que calculam o hash do programa, da entrada privada e do segmento público; contém os 35 valores públicos |
memory | declarado, ≥ 2ᵗ⁺² | registradores e RAM, ordenados por (espaço, endereço, tempo) |
alu | 2ᵗ⁺¹ | toda operação aritmética, lógica, de deslocamento, de comparação, de multiplicação e de divisão, em limbs de byte |
range, nibble | 256 cada | tabelas de consulta fixas: bytes, potências de dois, AND/OR/XOR de 4 bits |
poseidon2 | 2ᵗ⁺² | a permutação de hash, uma linha por rodada |
input | declarado, ≥ 2⁷ | as palavras da entrada privada comprometida |
public | declarado, ≥ 2⁷ | as palavras do segmento público (para um pacote, a vinculação da transação) |
Mais duas tabelas, keccak e sha256, estão presentes apenas em uma prova cujo programa as chamou, cada uma com no mínimo 2⁷ linhas quando presente; o guest do pacote não usa nenhuma das duas. O piso de 2⁷ linhas em toda tabela declarada veio com o conjunto de restrições 7 (seção 9).
O nível t é um entre 10, 12, 14, 16, 18 ou 20 e dimensiona as tabelas. Todo pacote é provado no nível 14: uma tabela de CPU de 16.384 linhas, das quais o guest usa cerca de 12.400. O guest de autenticação executa 250 ciclos no nível 10. O restante é padding, de modo que uma prova revela de qual nível um programa precisou, nunca quantos passos ele executou dentro dele (Níveis).
A tabela de memória é o que torna a RAM verificável. Como ela é ordenada por endereço e depois por tempo, “uma leitura retorna o último valor escrito naquele endereço” passa a ser uma regra entre duas linhas adjacentes.
3. Conecte as tabelas com barramentos
Como o verificador sabe que a soma usada pela CPU é a soma calculada pela ALU? Por meio de barramentos. Quando a CPU precisa de uma adição, ela envia uma mensagem (op, a, b, c); a ALU recebe uma mensagem para cada linha que calcula. A máquina tem dezessete barramentos:
| barramento | entre |
|---|---|
PROGRAM, PROGRAM_WORD | cpu ← program: buscas de instrução e as palavras que o digest do programa absorve |
MEMORY | cpu ↔ memory: toda leitura e escrita |
ALU | cpu → alu |
RANGE8, POW2, AND4, OR4, XOR4 | verificações de intervalo de byte, deslocamentos e operações bit a bit contra as tabelas fixas |
POSEIDON2 | cpu → poseidon2: uma mensagem por permutação |
INPUT_DIGEST, INPUT_READ | cpu ← input: as palavras que o digest da entrada absorve e as leituras do programa |
PUBLIC_DIGEST, PUBLIC_READ | cpu ← public: o mesmo par para o segmento público |
KECCAK, SHA256 | cpu → as tabelas de hash opcionais |
BLIND | cada tabela → a tabela seguinte em um ciclo fixo: um valor aleatório cada, na primeira linha (conjunto de restrições 7) |
Os barramentos são verificados com um argumento de derivada logarítmica (LogUp). Depois que as tabelas são comprometidas, sorteia-se um valor aleatório α, e cada tabela mantém uma soma acumulada de ±1/(α − m) sobre as mensagens que envia ou recebe. As somas se cancelam exatamente se, e somente se, toda mensagem enviada foi recebida, o mesmo multiconjunto nos dois lados. Uma única mensagem forjada deixa um resíduo que um α aleatório expõe com probabilidade esmagadora. O barramento de memória usa um parente próximo, uma verificação de permutação de multiconjuntos.
Cada tabela publica o valor final de sua soma acumulada, seu terminal, e o verificador confere que os terminais somam zero. Até o conjunto de restrições 6, um terminal era uma função verificável do conteúdo de sua tabela. Desde o conjunto de restrições 7, toda tabela carrega cinco colunas extras e, em sua primeira linha, envia um valor aleatório e recebe o de sua vizinha no barramento BLIND. Cada terminal se desloca por um elemento uniforme do corpo de desafios, e a soma continua sendo zero.
Os barramentos de digest e de leitura são deliberadamente pares separados. O LogUp equilibra por mensagem, não por consumidor, de modo que um barramento compartilhado permitiria a um provador omitir uma palavra do digest enquanto uma leitura dessa palavra ainda tivesse sucesso.
4. Ancore o rastro ao mundo exterior
Trinta e cinco valores públicos saem do rastro, todos na tabela da CPU:
| valores | o que são |
|---|---|
| 1 | a entrada pc |
| 1 | o nível (tier) |
| 8 | as palavras de saída (para um bundle, o digest D) |
| 8 | hc, o digest do programa, calculado dentro do trace a partir das palavras da tabela do programa |
| 8 | H_IN, o digest da entrada privada, calculado sobre a entrada com um salt novo de 128 bits na frente |
| 8 | H_PUB, o digest do segmento público, sem salt |
| 1 | GAS_LIMIT, o teto de gas que o provador declara (conjunto de restrições 8) |
Como hc é calculado no circuito, uma prova sobre um programa diferente não pode passar por uma prova sobre o guest do bundle. O verificador nunca vê uma palavra de instrução; ele compara hc com o digest que espera. Como H_IN tem salt, ele vincula a entrada privada sem revelá-la, mesmo a quem pudesse adivinhá-la. Como H_PUB não tem salt, o verificador pode recalculá-lo a partir de palavras que já tem. Para um bundle, essas palavras são o vínculo da transação (transaction binding), blake3("rand-tx-bind-1" ‖ chain_id ‖ tx) sobre a transação com os bytes da prova zerados, de modo que uma prova copiada para uma transação com outro destino, memo, envelope ou chain id não é verificada. Na chain 20 (genesis binding_domain: 1) ele é blake3("rand-tx-bind-2" ‖ genesis_hash ‖ chain_id ‖ tx), de modo que uma prova não é verificada em outra chain, mesmo sob o mesmo chain id; as palavras são uma entrada pública, então isso não mudou nada no circuito. A prova de autorização é feita sobre as mesmas oito palavras. Uma prova de chamada na chain 20 é vinculada da mesma forma: seu segmento são as palavras públicas do programa definidas na implantação, seguidas de oito palavras de um vínculo de chamada (rand-call-bind-2, rand-call-bind-1 antes da chain 20), de modo que uma prova de chamada copiada para outro bundle de taxas não é verificada.
O gas é a medida que a máquina faz do peso de uma execução, presente no circuito desde o conjunto de restrições 8. A tabela da CPU tem uma coluna GAS que começa em 1 e soma 1 a cada linha, 3 em uma linha de absorção do Poseidon2, 64 em uma linha SHA256 e 192 em uma linha KECCAK. Na linha HALT, o circuito prova GAS ≤ GAS_LIMIT por meio de quatro limbs da diferença com verificação de intervalo, que nunca são abertos: o limite é público e o gas que uma execução gastou não é. O verificador recusa, antes de construir uma chave, um limite acima do que o próprio cabeçalho da prova poderia exigir:
gas_max(t, k, s) = (2ᵗ − 1) + 2ᵗ⁻² + 191·(2ᵏ / 32) + 63·(2ˢ / 64)
com k e s as alturas logarítmicas declaradas das tabelas keccak e sha256, cada termo igual a zero para uma tabela ausente. O teto de um cabeçalho do tier 10 sem hashes é 1.279, e o de um do tier 14 é 20.479. A chain cobra uma chamada pelo limite que ela declara (Taxas).
5. Transformar as regras em identidades polinomiais
Cada coluna é uma lista de números no corpo Goldilocks, inteiros módulo p = 2⁶⁴ − 2³² + 1. Uma lista de n números é o mesmo que um polinômio de grau menor que n: aquele que passa por eles quando estão dispostos em um subgrupo {1, ω, ω², …} do corpo. O Goldilocks tem subgrupos assim de todo tamanho que seja potência de dois até 2³², de modo que a conversão é uma transformada rápida de Fourier.
Uma regra da máquina passa então a ser uma equação entre polinômios de coluna. “O contador de programa avança quatro, a menos que a instrução salte” torna-se, esquematicamente:
is_jump(x)·(pc(ωx) − target(x)) + (1 − is_jump(x))·(pc(ωx) − pc(x) − 4) = 0
para todo x no subgrupo, em que pc(ωx) significa “a próxima linha”. O conjunto completo dessas regras de uma tabela é sua AIR (representação algébrica intermediária). As regras da CPU e da ALU chegam ao grau 8; as demais ficam entre 2 e 4.
Um polinômio que é zero em todos os pontos do subgrupo é divisível por Z(x) = xⁿ − 1. O provador combina todas as regras, com pesos aleatórios, em um único polinômio C(x) e divide por Z(x). Se todas as regras valem em todas as linhas, o quociente é um genuíno polinômio de grau baixo. Se uma regra falha em uma linha, ele não é. “Todos os passos de uma execução de 16.000 ciclos estavam corretos?” tornou-se “esta função está próxima de um polinômio de grau baixo?”, e essa pergunta pode ser respondida olhando alguns valores escolhidos ao acaso.
6. Comprometer-se antes de conhecer as perguntas
O provador deve fixar seus polinômios antes de ver as perguntas aleatórias do verificador, ou poderia adaptar suas respostas. Ele avalia cada coluna em um domínio oito vezes maior que a tabela (blow-up 8, taxa de código 1/8) e publica uma raiz de Merkle sobre as avaliações. Essa redundância é um código de Reed–Solomon: um polinômio de grau baixo é determinado por qualquer oitava parte de seus valores, de modo que um trapaceiro que altere até uma única linha do trace precisa alterar a maior parte da palavra de código estendida para continuar consistente, e as verificações pontuais aleatórias vão cair sobre o dano.
As árvores de Merkle usam Poseidon2 sobre Goldilocks: largura 8, S-box x⁷, 4 rodadas completas, 22 rodadas parciais e 4 rodadas completas, com digests de quatro elementos do corpo (cerca de 256 bits). Cada folha é calculada com quatro elementos de aleatoriedade nova, de modo que uma raiz não revela nada sobre os valores abaixo dela. A mesma permutação é o compromisso de nota e o hash do nullifier dentro da máquina. Suas 86 constantes de rodada são uma tabela literal no código-fonte do nó (poseidon2_constants.rs), de modo que nenhuma atualização de biblioteca pode alterá-las.
7. Verificação pontual com FRI
O verificador agora tem raízes de Merkle. Primeiro ele amarra o quociente ao trace: escolhe um ponto aleatório z fora do domínio da tabela, pede o valor de cada coluna e o do quociente ali, e verifica C(z) = Q(z)·Z(z) diretamente. Resta confirmar que as funções comprometidas são de fato polinômios de grau baixo, e é isso que o FRI testa.
O FRI funciona por dobragem (folding). Divide-se um polinômio em partes par e ímpar, f(x) = fₑ(x²) + x·fₒ(x²). Para um β aleatório vindo do verificador, f′(y) = fₑ(y) + β·fₒ(y) tem metade do grau em um domínio de metade do tamanho. O provador se compromete com f′, recebe um novo β e dobra de novo (o Rand dobra em até oito por rodada) até restar uma constante, que ele envia às claras. Uma consulta (query) escolhe então uma posição aleatória na primeira camada e a acompanha até o fim, verificando em cada camada que os valores abertos acima se dobram no valor abaixo. Uma função que estava longe de ter grau baixo não consegue manter todas essas promessas consistentes.
O Rand executa 80 consultas. Sob o teorema de lacunas de proximidade (proximity gaps) demonstrado, cada consulta deixa ao trapaceiro no máximo 9/16 de chance, de modo que 80 consultas dão (9/16)⁸⁰ ≈ 2⁻⁶⁶·⁴. Sob a conjectura ethSTARK, na qual se apoiam os STARKs em produção, cada consulta deixa 1/8, o que é muito menor.
8. Remover a conversa
Tudo o que foi dito acima foi descrito como um diálogo, mas uma transação precisa ser uma única mensagem que qualquer validador possa verificar sozinho. A transformação Fiat–Shamir elimina o verificador: o provador calcula cada desafio “aleatório” como um hash de tudo o que foi comprometido até então, com uma esponja duplex Poseidon2. Alterar qualquer compromisso altera todos os desafios seguintes, de modo que o provador não pode escolher compromissos que sirvam aos desafios.
Duas medidas adicionais fecham as brechas que isso deixa:
- Corpo de extensão. Um corpo de
2⁶⁴elementos é pequeno demais para sortear desafios: um trapaceiro precisa que um desafio caia em um dos poucos valores ruins de um polinômio, e tem sucesso com probabilidade de cerca deN/2⁶⁴, por mais consultas que se façam. Assim, o trace permanece em Goldilocks, mas cada desafio é sorteado em sua extensão quadrática, com cerca de2¹²⁸elementos. - Grinding. Antes de derivar as posições das consultas, o provador deve encontrar um nonce cujo hash com a transcrição comece com 20 bits zero, cerca de um milhão de hashes. Um provador honesto paga isso uma vez. Um trapaceiro que sorteia de novo as posições das consultas até que errem sua falsificação paga isso a cada tentativa. Esta é a única prova de trabalho em todo o Rand, e não tem nada a ver com o consenso.
Somando: 66,4 bits das consultas mais 20 do grinding dão 86 bits demonstrados, e cerca de 100 bits sob a conjectura, em que o termo do corpo de extensão é o limite.
9. De onde vem o conhecimento zero
Um STARK não é de conhecimento zero por si só: os valores abertos nas posições das consultas são valores dos polinômios do trace e, sem cuidado, vazam informação. A configuração do Rand acrescenta quatro camadas:
- cada polinômio comprometido é randomizado (o FRI com ocultação do Plonky3 mistura quatro palavras de código aleatórias), de modo que os 80 valores abertos podem ser reproduzidos, em distribuição, por alguém que nunca viu o trace;
- cada folha de Merkle recebe salt, de modo que raízes e caminhos de autenticação não revelam nada;
- a aleatoriedade do provador é sorteada do zero no sistema operacional para cada prova, de modo que duas provas do mesmo pagamento parecem não ter relação;
- toda tabela declarada tem pelo menos
2⁷linhas e todo terminal LogUp é cegado (conjunto de restrições 7). Uma prova abre cada tabela em 80 pontos de consulta e dois pontos fora do domínio, e o compromisso com ocultação mistura tantas linhas aleatórias quantas a tabela tem, de modo que uma tabela só fica oculta enquanto tiver pelo menos 82 linhas. 128 é a menor potência de dois que satisfaz isso.
A Camada 4 é uma correção. A revisão de 2026-09-27 recuperou, a partir de provas de produção, as entradas privadas de chamadas com 31 palavras de entrada ou menos, leu as contagens de busca de um programa em sua tabela de programa curta e distinguiu os slots de entrada reais de um bundle de seus slots fictícios pelos terminais não ofuscados. Essas provas são as geradas na chain 15 e anteriores. A chain 16 (conjunto de restrições 7, com o guest de bundle sem ramificações) corrigiu os três problemas, e as chains 18 a 20 mantêm as correções.
A garantia é conhecimento zero estatístico no modelo de oráculo aleatório. O que uma prova não oculta é exato: seu tier, as alturas de tabela declaradas e seus valores públicos, entre eles o limite de gás. Uma altura declarada de keccak ou sha256 igual a 0 significa que a execução nunca usou esse hash; assim, o cabeçalho da prova de uma chamada mostra se a chamada fez hash e, acima do piso, quantas vezes, com precisão de um fator de dois. Para um bundle, os valores e campos públicos são a âncora, quatro anuladores, quatro compromissos, a taxa, as queimas, o ativo queimado, o tempo, o compromisso de autorização e o vínculo da transação. O ativo que uma transferência move é uma entrada privada, de modo que um pagamento em zUSD e um pagamento em RAND têm a mesma aparência na chain. hc é determinístico, portanto qualquer pessoa pode saber que uma prova diz respeito ao guest de bundle (toda prova que move valor diz) ou, no caso de uma chamada, qual programa implantado ela executou.
10. O que um validador verifica
Machine::verify_public(hc, words, proof) executa, do mais barato ao mais caro:
- o tier declarado pela prova e as alturas de tabela declaradas estão dentro dos limites (
check_declared_heights) antes que qualquer coisa seja dimensionada a partir deles, de modo que um cabeçalho malformado custa algumas comparações, e não uma construção de chave de vários segundos; um bundle deve declarar o tier 14 sem nenhuma das tabelas de hash, e uma prova de autorização, o tier 10; - há 35 valores públicos, cada um um elemento de campo canônico, com
pv[HC] = hc,pv[TIER]igual ao tier da prova, toda palavra de saída abaixo de2³²eGAS_LIMITno máximo igual aogas_maxdo cabeçalho; - o STARK em lote é verificado com uma chave de verificador em cache por formato
(tier, program, input, keccak, sha256, public heights), e não por programa; H_PUBé igual ao resumo dewords, que, para um bundle, é o vínculo da transação que o próprio nó calculou.
Antes de tudo isso, o nó examina toda prova submetida ou propagada por gossip quanto a uma codificação canônica: seu cronograma de dobramento FRI deve ser o que um provador honesto produz para os graus que o cabeçalho declara. Desde o fullnode v0.6.8 (audit v6, ZKV-4), o exame recusa primeiro qualquer grau declarado acima de 25 bits (MAX_HONEST_DEGREE_BITS, o teto de 2²⁴ da tabela de memória mais a duplicação de conhecimento zero, o máximo que uma prova aceita carrega). Antes desse limite, um cabeçalho que declarasse um grau enorme fazia o nó rederivar um cronograma de dobramento gigantesco em seu loop de eventos, mantendo-o ocupado pelo tempo que o remetente quisesse; o verificador recusaria a prova de qualquer maneira, de modo que o limite muda apenas o custo de dizer não. Um pânico dentro do exame agora recusa essa única transação, em vez de parar o nó.
Em seguida, o ledger recalcula o resumo do bundle D com bad = 0 e o compara com as oito palavras de saída da prova. Na chain 20, ele também exige que o GAS_LIMIT de uma prova de bundle seja exatamente o bundle_gas_limit do genesis, 20.479, e o de uma prova de autorização, 1.279, de modo que toda transferência declare os mesmos valores. Com a chave já em cache, a verificação completa leva cerca de 16 ms.
Os parâmetros e por que foram escolhidos
Todo número abaixo é consenso: o verificador o confere, uma prova feita com qualquer outro valor não é verificada, e alterar um deles é um novo conjunto de restrições e um hard fork (Conjuntos de restrições). O perfil FRI é fixado pelo arquivo genesis (fri_profile: production na chain 20), nunca pela prova.
| parâmetro | valor | por que este valor |
|---|---|---|
| corpo base | Goldilocks, p = 2⁶⁴ − 2³² + 1 | Um primo de 64 bits cuja aritmética custa poucas instruções de máquina, com subgrupos multiplicativos de todos os tamanhos em potência de dois até 2³², de modo que transformar uma coluna em polinômio é uma transformada rápida de Fourier. O trace vive aqui porque é o corpo barato. |
| corpo dos desafios | extensão quadrática F_p² (≈ 2¹²⁸) | Desafios sorteados do próprio Goldilocks deixariam um piso de segurança de cerca de N/2⁶⁴ que nenhum número de consultas consegue reduzir. A extensão leva esse termo a no máximo 2⁻¹⁰⁰ para traces de até 2²⁵ linhas. Só os desafios pagam pelo corpo maior; o trace não. Uma extensão cúbica (≈ 2¹⁹²) é a margem prevista caso 128 bits venham a ser exigidos. |
| blow-up | 8 (taxa ρ = 1/8, log_blowup = 3) | Duas razões ao mesmo tempo. As tabelas de CPU e ALU têm regras de grau 8 e, com o indicador de conhecimento zero, o quociente tem grau 9, o máximo que um blow-up de 8 comporta; uma regra de grau maior exigiria blow-up 16. E o poder de cada consulta para pegar um trapaceiro depende da taxa: (1 + ρ)/2 = 9/16 por consulta provado, ρ = 1/8 conjecturado. Um blow-up maior compraria menos consultas ao custo de uma extensão de baixo grau proporcionalmente maior para o prover calcular e manter em memória. |
| consultas FRI | 80 | O número que mantém a segurança provada em cerca de 86 bits. Ele foi cortado uma vez para 27 (conjuntos de restrições 3 e 4), o que atendia a uma meta conjecturada de 100 bits (3·27 + 20 = 101), mas deixava apenas cerca de 42 bits provados; a auditoria zk de 2026-09-12 (achado ZM1) restaurou 80. As consultas são a maior parte do tamanho de uma prova, e por isso uma prova de bundle tem ≈ 1,50 MB. 100 bits provados exigiriam 97 consultas. |
| grinding | 20 bits, na fase de consultas | Antes de as posições das consultas serem sorteadas, o prover precisa encontrar um nonce cujo hash comece com 20 bits zero: cerca de um milhão de hashes, pagos uma vez por um prover honesto e a cada nova tentativa por um trapaceiro que resorteia posições. Acrescenta 20 bits a baixo custo; mais grinding (34 bits também dariam 100 provados) torna mais lenta toda prova honesta. A fase de commit não tem grinding. |
| aridade de folding | até 8 por rodada (max_log_arity = 3) | Dobrar por oito em vez de dois dá um terço das camadas FRI, de modo que cada consulta abre menos caminhos de Merkle e a prova fica menor, com um pouco mais de trabalho por camada. |
| polinômio final | uma constante (log_final_poly_len = 0) | O FRI dobra até o fim e envia um único valor às claras, a verificação final mais simples para o verifier. |
| hash (no circuito, Merkle, transcrição) | Poseidon2, largura 8, S-box x⁷, 4 + 22 + 4 rodadas, taxa 4, capacidade 4 | Um hash construído para ser barato como aritmética de corpo, de modo que fazer hash dentro de uma prova (compromissos, nullifiers, caminhos de Merkle, o digest do programa) custa linhas, e não milhares de operações sobre bits. O expoente da S-box não pode ter fator em comum com p − 1 = 2³²·3·5·17·257·65537, e 7 é o menor que serve. Largura 8 com taxa 4 dá um digest de quatro elementos, cerca de 256 bits. As 86 constantes de rodada são uma tabela literal no nó (poseidon2_constants.rs). |
| largura do digest | 4 elementos de corpo (≈ 256 bits) | Suficiente para os papéis de pré-imagem (nullifiers, compromissos: 128 bits contra Grover). Não é suficiente para resistência quântica a colisões de 128 bits na transcrição (2²⁵⁶/³ ≈ 2⁸⁵); o design do whitepaper pede um hash de transcrição de 384 bits, e a chain em operação não o adotou. |
| hiding | 4 palavras de código aleatórias no FRI; 4 elementos de sal por folha de Merkle; aleatoriedade nova do sistema operacional a cada prova | O mínimo que torna as 80 posições abertas e os caminhos de Merkle simuláveis apenas a partir de valores públicos, de modo que a prova é estatisticamente de conhecimento zero. Provas do mesmo pagamento feitas duas vezes parecem não ter relação entre si. |
| piso de tabela | 2⁷ linhas para cada tabela declarada (conjunto de restrições 7) | Uma prova abre 80 pontos de consulta e 2 pontos fora do domínio de cada tabela, 82 ao todo, e uma tabela de h linhas é ofuscada por h linhas aleatórias. 64 linhas são poucas e 128 é a próxima potência de dois. Uma contagem de consultas acima de 126 exigiria 2⁸. |
| blind terminal | um valor de F_p² por tabela, no bus BLIND (conjunto de restrições 7) | Um terminal LogUp é calculado a partir do trace bruto; sem um blind, é um compromisso sem sal com a sua tabela. O blind é um elemento do corpo de extensão porque uma única coordenada do corpo base confinaria o deslocamento a um conjunto estreito, contra o qual se poderia testar uma testemunha candidata. |
| pesos de gas | 1 por linha; 3 por linha de absorção do Poseidon2; 64 por linha SHA256; 192 por linha KECCAK (conjunto de restrições 8) | Cada peso conta as linhas que a linha puxa para outras tabelas: uma linha de absorção, uma permutação de 32 linhas; uma linha SHA256, 64 linhas de 466 colunas; uma linha KECCAK, 32 linhas de 2.612 colunas e 100 linhas de memória. |
| grau máximo de restrição | 8 | Definido pelo blow-up acima; o grau de cada tabela é fixado por um teste, para que uma nova regra não o ultrapasse em silêncio. |
| tiers | 10, 12, 14, 16, 18, 20 | As tabelas são dimensionadas em seis degraus fixos, cada um quatro vezes o anterior, e uma prova revela apenas o seu tier: uma faixa grosseira e preenchida do comprimento da execução, e não a sua contagem de ciclos. Todo bundle é tier 14 e toda prova de autorização é tier 10; as chamadas são limitadas ao tier 14 desde o fullnode v0.5.6. |
| trace máximo | 2²⁴ linhas (2²⁵ com o bit de conhecimento zero) | O tamanho para o qual o termo do corpo de extensão é provado; um trace mais longo é recusado, em vez de provado sob uma afirmação de segurança que não se sustenta. |
| valores públicos | 35 elementos de corpo | pc de entrada, tier, oito palavras de saída, os três digests de oito palavras hc, H_IN, H_PUB, e o limite de gas declarado: o menor enunciado que nomeia o programa, a entrada privada, a entrada pública e um limite para o trabalho. Eram 34 até o conjunto de restrições 7. |
| limite de tamanho da prova | 2 MiB por padrão; 4 MiB na chain 20 (max_proof_bytes) | 2 MiB fica acima de toda prova que os guests implantados produzem sem uma tabela de hash e abaixo de uma que carregue keccak (≈ 3,3 MB); o genesis da chain 20 define 4.194.304 para admitir ambas, como as chains 17 a 19 faziam. As chains 13 a 16 definiram 8 MiB. O limite é verificado antes de a prova ser decodificada. |
O que esses números somam: 86 bits de segurança provados, cerca de 100 sob a conjectura ethSTARK (em que o limite é o corpo de extensão, não as consultas) e cerca de 85 bits contra um adversário quântico, definidos pelo hash de transcrição de 256 bits. O whitepaper mantém as duas cifras de segurança lado a lado de propósito: todo FRI-STARK implantado depende da conjectura para os seus últimos bits, e quem dimensiona uma implantação deve saber qual hipótese sustenta quais bits.
Provas sobre provas
Com cerca de 1,50 MB, uma prova de bundle é pesada, a prova de autorização de 1,37 MB viaja ao lado dela, e todo nó armazena todas elas. O remédio que está construído é a recursão: uma prova cujo enunciado é “verifiquei estas provas”. O fullnode traz uma segunda máquina, menor, para isso, a recursion VM, cuja palavra nativa é um elemento de Goldilocks e cujas instruções são as próprias operações do verifier (aritmética de corpo, Poseidon2, passos de Merkle e de FRI). O seu programa de agregação reexecuta a verificação completa de N provas de bundle e publica um digest sobre os valores públicas delas, vinculado à chain, ao aggregator e a um nonce. Verificar uma prova de bundle dentro dela custou 1.968.619 linhas sob o conjunto de restrições 6 e 2.044.506 sob o conjunto de restrições 7; não foi medido de novo sob o conjunto 8. Está implementada e desligada pelo genesis em todas as chains até agora, inclusive a chain 20. O programa de agregação verifica apenas provas de bundle, de modo que, numa chain de autorização dividida, a prova de autorização permaneceria. Suas provas de produção também exigem mais memória do que o planejado: medidas sob o conjunto de restrições 7, 133 GB ou mais (Em breve: agregação de blocos).
O que o esquema não afirma
- A margem quântica é de cerca de 85 bits, não 128. A busca quântica de colisões sobre um hash de
nbits custa cerca de2ⁿ/³. O desenho do whitepaper prevê um hash de transcrição de 384 bits; a cadeia em operação usa o digest Poseidon2 de 256 bits. Ampliá-lo exige um novo conjunto de restrições e um hard fork. - 100 bits é uma conjectura; 86 é provado. Elevar o valor provado para 100 custaria 97 consultas, cerca de um quinto a mais de dados de consulta por prova.
- As constantes do Poseidon2 são um sorteio com semente, não uma instanciação publicada. Não se conhece nenhum ataque a isso; adotar um conjunto padrão alteraria todos os hashes da cadeia.
- Algumas afirmações são verificadas ao lado da prova, não dentro dela. O valor público
TIERé comparado com a forma da prova pelo verifier, mas não é restringido pelo circuito (auditoria ZL1), e a disciplina de taint do bundle guest se apoia em argumento estrutural, em um harness de guest enfraquecido e em mutation fuzzing, e não em verificação mecanizada. - O cabeçalho de uma prova não é ocultado. O tier e as alturas das duas tabelas de hash ficam às claras, de modo que uma prova de chamada mostra se a chamada usou
KECCAKouSHA256. Existe uma opção do prover que preenche as duas tabelas, e nenhuma carteira a ativa: ela leva uma prova de tier 10 de cerca de 1,3 MB para cerca de 3,6 MB. - A syscall
POSEIDON2não é injetiva sobre entradas de comprimento variável. Ela é mantida como está porque todo compromisso, nullifier, raiz e digest de programa depende dela. Nenhum guest distribuído usa aindaPOSEIDON2_LEN. - A geração de provas é lenta e feita pelo remetente, ou por um prover com quem o remetente se associa: cerca de 100 s e 5,7 GB em uma CPU de notebook para um bundle. Os kernels de GPU rodaram pela primeira vez em uma GPU (uma NVIDIA H100) em 2026-09-28 e coincidiram com a referência em CPU; a carteira ainda gera provas na CPU.
Onde ler mais
- A seção “The Zero-Knowledge Scheme: How a Rand Proof Works” do whitepaper (
randprotocol.tex) percorre os mesmos sete ingredientes com os teoremas de solidez. - O paper de implementação (
randprotocol_implementation.tex) especifica o conjunto de instruções e as syscalls, o bundle guest, a esponja e os digests, cada tabela e barramento, a configuração da máquina e a VM de recursão. - O código:
crates/randprotocol-zkvm/src/no fullnode —emulator.rs,tables/,machine.rs,gas.rs,guests.rs,hash.rs,poseidon2_constants.rs.