Use esta página para colocar um programa na cadeia e chamá-lo: o que é um programa, como construir um, o que uma chamada publica e o que oculta, como ler um recibo de volta e os limites que você vai encontrar. Todo programa é código público; toda chamada é uma prova. Programas de exemplo traz aplicações completas, e randprotocol/example-programs traz o código-fonte dos programas implantados na chain 20, incluindo os RPL-2 (abaixo).
O que é um programa
Um programa é código de máquina RV32IM para a zkVM da Rand: { base_pc: u32, words: Vec<u32> }, com no máximo o limite de programa da cadeia (65.535 palavras na chain 20, como em toda cadeia desde a chain 13; 4.096 em um genesis sem max_program_words, como a chain 12), com base_pc múltiplo de 4 e cada palavra decodificando como uma instrução (uma palavra que não decodifica é rejeitada na implantação, nunca em tempo de execução). A chain 20, assim como a chain 19, também se recusa a implantar um programa que nenhuma chamada conseguiria comportar: mais de 8.180 palavras sem segmento público, menos com um (abaixo). Ele é endereçado por conteúdo e imutável:
program_id = blake3("rand-program" || base_pc || words) // the id, with no public input
program_id = blake3("rand-program-2", base_pc || u32_le(len(words)) || words || u32_le(len(public)) || public) // deployed with --public
code_hash = hc = Poseidon2 digest of (base_pc, len, words) // what calls are verified against
Uma chamada é uma prova STARK de que o programa com digest hc foi executado sobre algumas entradas privadas, gastou no máximo um limite de gas declarado e publicou oito palavras de saída. O nó verifica a prova, registra um recibo e nada mais acontece: saídas são dados, não efeitos. Uma chamada não pode mover RAND; quem chama move valor no bundle que paga pela chamada, e as saídas de um programa significam o que o programa e quem o chama combinarem que significam. Na chain 20, um Invoke é a exceção: sua prova atesta uma transição de estado declarada, que o ledger então aplica (Estado de programa (RPL-2)).
O que o guest pode fazer:
| grupo | instruções |
|---|---|
| superiores / saltos | LUI AUIPC JAL JALR |
| desvios | BEQ BNE BLT BGE BLTU BGEU |
| cargas / armazenamentos | LW SW LB LH LBU LHU SB SH |
| ALU | ADDI SLTI SLTIU XORI ORI ANDI SLLI SRLI SRAI ADD SUB SLL SLT SLTU XOR SRL SRA OR AND |
| RV32M | MUL MULH MULHU MULHSU DIV DIVU REM REMU |
| sistema | ECALL; nunca FENCE, CSRs, EBREAK, nem as extensões A ou C |
Chamadas de sistema, por número em a7:
| # | chamada de sistema | efeito |
|---|---|---|
| 0 | HALT | encerra a execução |
| 1 | WRITE_OUTPUT slot word | fixa a saída pública slot < 8; cada slot no máximo uma vez, slots não escritos valem zero |
| 2 | READ_INPUT idx | retorna a palavra de entrada privada idx, vinculada ao compromisso com sal H_IN |
| 3 | POSEIDON2 ptr n | calcula o hash de n ≤ 4096 palavras no próprio lugar com a esponja Poseidon2 |
| 4 | KECCAK ptr | uma permutação Keccak-f[1600] no próprio lugar; faz a prova carregar a tabela keccak |
| 5 | SHA256 ptr | uma compressão SHA-256 no próprio lugar; faz a prova carregar a tabela sha256 |
| 6 | READ_PUBLIC idx | retorna a palavra idx do segmento público; na chain 20 o segmento público é fixado na implantação (rand program deploy --public); em um genesis sem max_program_public_words ele é vazio e esta chamada não lê nada |
| 7 | POSEIDON2_LEN ptr n | POSEIDON2 com o comprimento da mensagem vinculado à esponja, de modo que [a] e [a, 0] tenham hashes diferentes (conjunto de restrições 7, chain 16 e posteriores). rand-guest check nos circuits aeacf31 ainda a recusa (próxima seção) |
Memória: 32 registradores, com x0 igual a zero; RAM que começa zerada; todo endereço abaixo de 2³⁰; uma pilha de 64 KiB para guests compilados. Um programa construído com rand-guest carrega seus dados somente leitura e inicializados no contêiner de imagem, e um prólogo de carregamento que faz parte do programa grava esses dados antes de o código do próprio programa ser executado (A ISA RISC-V). Compromissos de nota, nullifiers e verificações de Merkle não são chamadas de sistema, mas rotinas de biblioteca sobre POSEIDON2.
Desde a chain 18 (conjunto de restrições 8), toda execução é medida em gas. Uma instrução comum e as chamadas de sistema 0, 1, 2 e 6 custam 1 gas cada, POSEIDON2 sobre n palavras custa 3 + 3 × ⌈n / 4⌉, uma permutação KECCAK custa 192 e uma compressão SHA256 custa 64. Programas de exemplo traz a tabela e o que uma chamada declara.
Construindo um
Três caminhos, todos produzindo palavras RV32IM:
O assembler embutido. A carteira traz cinco guests montados à mão e os monta em um arquivo JSON { "base_pc", "words" }:
rand program build --guest private_payment --arg 1000 --out pp.json # prints the program id
rand program build --guest balance_check --arg 1000 --out bc.json
# guests: fib, memcpy, bubble_sort, balance_check, private_payment, public_echo, rpl2_counter (v0.6.8); --arg is repeatable
Programas compilados, com rand-guest. O rand-guest do repositório de circuits compila um crate Rust no_std (riscv32im-unknown-none-elf, Rust 1.98.1) ou um diretório C (clang 23.1.1) em um contêiner de imagem, confere cada palavra contra o decodificador da máquina e imprime hc e o id do programa. A compilação é hermética, de modo que o mesmo código-fonte gera o mesmo hc em qualquer máquina. Para as chains 18 a 20, compile a toolchain no commit aeacf31 dos circuits (branch feat/v067-circuits), o commit que o fullnode v0.6.7 e o v0.6.8 fixam. Nesse commit, rand-guest check lista apenas as chamadas de sistema 0 a 6; assim, um guest que chama guest_sdk::poseidon2_len (chamada de sistema 7) é rejeitado na compilação, embora a máquina a execute (zkp-circuits issue #1). Passo a passo: Implantar um programa Rust e Implantar um programa RISC-V.
Programas traduzidos. sbpf2rv traduz um ELF sBPF da Solana, e evm2rv traduz bytecode de runtime EVM, em programas cujas saídas são iguais, palavra por palavra, às dos guests interpretadores da Rand: O transpilador da Solana e O transpilador da Ethereum.
A própria carteira não tem compilador. rand program deploy aceita um .json, um .bin bruto de palavras little-endian ou o contêiner de imagem que o rand-guest grava (fullnode v0.3 e posteriores).
Implantando
rand program deploy pp.json # a bundle pays the floor; waits for the commit; prints the id
rand program show <id> # id, base_pc, words_len, code_hash, deployed_at, public_words_len, public_digest
A testnet é a chain 20, e implantar, chamar ou invocar nela exige a carteira rand do fullnode v0.6.8 ou posterior. Uma carteira mais antiga não consegue enviar lá de forma alguma: o genesis da chain 20 define binding_domain: 1, de modo que cada vinculação de prova e cada mensagem assinada inclui o hash do genesis, e as vinculações de uma carteira v0.6.7 carregam apenas o id da cadeia. O endpoint público https://rpc.randprotocol.org atende a sincronização, as implantações e as chamadas de uma carteira (configuração).
A carteira primeiro imprime o id do programa, o número de palavras e hc, e pede ao nó a taxa com rand_estimateFee, que recusa um programa acima do limite da cadeia antes de qualquer prova. Em seguida, monta um bundle de autotransferência de valor zero (o único bundle de ativos ocultos de 4 entradas e 4 saídas que toda transação carrega; a taxa sai de seus slots de RAND) que paga 0,001 RAND + 100.000 unidades × palavras com as próprias notas (as palavras do segmento público contam como palavras de código), anexa Action::Deploy { base_pc, words, public }, prova o bundle contra a vinculação da transação e o envia. Desde a chain 17, são duas provas, e a carteira imprime uma linha para cada: uma autorização de gasto de tier 10 (segundos, cerca de 1,37 MB) e o bundle de tier 14 (cerca de dois minutos em um laptop, cerca de 1,5 MB). Antes da chain 17, era apenas a prova do bundle, cerca de 100 s e cerca de 1,42 MB na chain 15. A taxa de implantação não tem termo de gas nem de bytes: o gas precifica apenas chamadas. O nó confere que cada palavra decodifica, calcula hc, armazena o registro e começa a construir a chave do verificador em segundo plano. O código é público; quem pagou por ele não é: rand_getProgram não tem campo deployer. Implantar as mesmas palavras duas vezes é idempotente.
→ {"method":"rand_getProgram","params":["675adeea…0065"]}
← { "id": "675adeea…0065", "base_pc": 0, "words_len": 42, "code_hash": "…", "deployed_at": 10,
"public_words_len": 0, "public_digest": null }
→ {"method":"rand_getProgramCode","params":["675adeea…0065"]}
← { "base_pc": 0, "words": [ … ] }
Chamando
rand call <program id> --input 400 --input 250 --input 300 --input 75
rand receipt <tx hash>
A carteira obtém do nó o código e o segmento público e confere que o hash deles corresponde ao id. Ela executa a chamada no emulador e imprime o gas e a taxa antes de provar qualquer coisa:
gas: 5636 (declaring 6144, tier 12)
fee 0.006191437 RAND (incl. two price steps of headroom)
Em seguida, sela a transcrição de entrada (abaixo), prova a chamada localmente sob o perfil FRI da cadeia, com as entradas como o vetor privado, prova o bundle que paga a taxa, envia, aguarda e imprime o recibo. Na chain 20, a prova da chamada é vinculada à sua própria transação (uma vinculação que inclui o hash do genesis) e o bundle são duas provas, de modo que uma chamada são três: a chamada, a autorização de gasto de tier 10 e o bundle de tier 14. As duas linhas acima são da chamada da votação anônima na chain 18, em 2026-09-30 (Uma votação anônima).
A tarifa. Na chain 20, o piso é BUNDLE_BASE + gas_price × declared gas limit + byte_price × ⌈bytes / 1024⌉, em que BUNDLE_BASE é 0,001 RAND, os bytes são a prova da chamada mais o envelope de entrada, e os preços começaram em 100 e 800 unidades (uma unidade é 10⁻⁹ RAND) e se movem com o preenchimento dos blocos. A chain 20 os limita a 10.000 e 80.000 unidades, e apenas os bytes que pagam o preço por byte (provas e envelopes das chamadas) contam para a sua carga (byte_load: "paying"); nas chains 18 e 19 os preços não tinham teto e os bytes de toda transação moviam o preço por byte. O cronograma fixo do ledger, 0.001 RAND + call_fee(tier), a regra da chain 15, não se aplica ali. A carteira fixa a tarifa antes de a prova existir; por isso precifica a prova no limite de 4 MiB da chain e soma uma margem de dois passos de preço. Quatro chamadas medidas na chain 18 custaram de 0,0057 a 0,0075 RAND cada (a aritmética). rand fee call <tier> --gas <limit> --bytes <B> pergunta ao nó o próprio piso. private_payment lê quatro saldos privados e, se eles somarem pelo menos o seu limiar, publica o excedente: saídas [1, 0, 25, 0, 0, 0, 0, 0]. A chain vê a prova, o tier e essas oito palavras; nunca vê os saldos.
Flags: --tier T força um tier; --gas-limit <N|max> declara um limite de gas exato ou o teto do tier no lugar do padrão, o gas exato arredondado para cima a um múltiplo de 2^(tier − 2); --fee <RAND> define a tarifa; --expect-public <FILE> recusa antes de provar, a menos que o segmento público do programa seja as palavras desse arquivo; --auditor <rand1…> sela também a transcrição para um auditor nomeado; --print-call-key imprime a chave por chamada; --no-envelope não publica transcrição; --cuda seleciona o backend de GPU (não testado em hardware e incompatível com selar um envelope). A chain 20 recusa --cuda em uma chamada de forma categórica: uma prova de chamada vinculada à sua transação é feita no backend de CPU.
Recibos
Um recibo é armazenado por chamada confirmada e recalculado quando um nó sincroniza ou verifica a sua chain:
→ {"method":"rand_getReceipt","params":["b082…09b0"]}
← { "tx": "b082…09b0", "program": "675adeea…0065", "tier": 14,
"outputs": [1, 0, 25, 0, 0, 0, 0, 0], "height": 105, "index": 0, "h_in": "9c0e…7f", "h_pub": null }
h_in é o compromisso com salt da prova para as entradas privadas; por si só, não revela nada. h_pub é o digest do segmento público do programa definido na implantação, o public_digest do programa, ou null para um programa implantado sem um. Não há campo effect, e o limite de gas declarado não consta do recibo. O explorer mostra o mesmo objeto como receipt em GET /transactions/:hash para uma call, lista as chamadas de um programa em GET /programs/:id e filtra GET /transactions?kind=call&program=<id>.
Envelopes de entrada: revelar uma chamada depois
Por padrão, rand call publica a transcrição (salt, inputs) da chamada selada em um envelope de entrada da chamada, de modo que as entradas possam ser mostradas depois sem uma segunda prova:
| parte | bytes | o que é |
|---|---|---|
body | 12 + 16 + 4·n + 16 | salt ‖ inputs sob uma chave nova por chamada K_call |
to_sender | 60 | K_call envolvida na chave de visualização de saída do chamador |
kem_ct | 1.088 ou 0 | uma encapsulação ML-KEM-768 para o auditor, vazia sem auditor |
to_auditor | 60 ou 0 | K_call envolvida sob o segredo dessa encapsulação |
Os dados associados do corpo são H_IN, de modo que um envelope transplantado para outra chamada não abre para ninguém. A chain verifica apenas o tamanho (no máximo max_call_envelope_bytes: 65.536 bytes na chain 20, 18.432 em um genesis sem o campo) e o serve sem alterações com rand_getCallEnvelope(tx). Três chaves o abrem, e nenhuma além delas: a chave de visualização do chamador (toda chamada feita por aquela carteira), a chave por chamada (aquela chamada) e a chave de visualização do auditor (aquela chamada).
rand open-call <txhash> # as the caller
rand open-call <txhash> --as-auditor # as the auditor
rand open-call <txhash> --call-key <hex> # with the per-call key alone
open-call abre a transcrição, verifica input_digest(salt, inputs) == H_IN, executa novamente o programa sobre as entradas recuperadas por meio do emulador, compara as saídas com as do recibo — e encerra com código diferente de zero se qualquer verificação falhar, para que um script não confunda um aviso com um sim. --no-envelope é irreversível: uma vez que o salt se perde, ninguém, nem mesmo o chamador, pode abrir aquela chamada.
Desde a v0.6.8, a nova execução é feita sobre o segmento público sobre o qual a prova foi feita: as palavras públicas do programa definidas na implantação e, sob hardening_v6, a vinculação de chamada de oito palavras depois delas (issue #116 do fullnode). Na v0.6.7, open-call funcionava apenas para um programa implantado sem segmento público; para um programa com segmento, ele imprimia as entradas e depois parava com re-running the program on these inputs failed: PublicIndex(0), encerrando com código diferente de zero sem executar nenhuma das duas verificações.
Estado do programa (RPL-2)
A chain 20 é a primeira chain com RPL-2, ativado pela seção program_state do seu genesis. Um programa ganha três coisas que não tinha (docs/program-state.md do fullnode, crates/randprotocol-core/src/ledger/program_state.rs na v0.6.8):
| cells | um mapa de uma chave de 8 palavras para um valor de 8 palavras, por programa. Uma célula ausente é lida como oito zeros; escrever oito zeros a remove. Comprometida na raiz de estado |
| um vault | um saldo público por ativo (0 é RAND). O valor entra pelo bundle de uma invoke e sai como notes que a chain calcula |
| um token próprio | um token registrado com rand token create --program <id> (autoridade de emissão Program(id), sem suprimento inicial) é emitido e queimado apenas pelas invokes desse programa |
Células e saldos do vault são públicos, como o suprimento de um token. Nada neles é um note.
Invoke (Action 33) é uma chamada que também declara uma transição, e a sua prova de chamada mostra que o programa aceita exatamente essa transição:
Invoke { program, proof, input_envelope, transition }
Transition { reads: [{ key, value }] // cells read, with the values read; keys strictly ascending
writes: [{ key, value }] // cells written; keys strictly ascending
inflow: none | deposit | burn
pays: [{ asset, amount, recipient, r, envelope }] // notes out of the vault
mints: [{ asset, amount, recipient, r, envelope }] } // new units of the program's own token
O que entra é o que o bundle queima: burn_r é RAND depositado no vault; burn_a de burn_asset é um token depositado (inflow deposit) ou destruído (inflow burn, apenas o token do próprio programa). O que sai é um note calculado pela chain por pagamento, primeiro os pagamentos e depois as emissões, com o valor e o pk do destinatário públicos, como os de uma emissão. O ledger aplica a transição apenas se o programa existir, a forma for válida, todas as células em reads ainda contiverem o valor declarado, o vault (após o depósito desta própria transação) cobrir todos os pagamentos, toda emissão for do token do próprio programa e as provas forem verificadas. Uma leitura que deixou de corresponder é recusada como StaleRead; a carteira lê de novo e prova de novo.
Como uma invoke difere de uma call.
Call (RPL-1) | Invoke (RPL-2) | |
|---|---|---|
| segmento público sobre o qual a prova é feita | public ‖ call_binding | public ‖ call_binding ‖ context, a transição como palavras |
| o que a chain faz em caso de sucesso | armazena um recibo; as saídas são dados | armazena o recibo e aplica a transição: escreve células, move o vault, acrescenta notes de pagamento |
| valor | apenas a transferência do próprio bundle | depósitos no vault por meio das queimas do bundle; pagamentos e emissões a partir dele |
| depende do estado | não | a prova não (o segmento é construído a partir da transação), mas a verificação de leitura sim, na admissão e no pool |
| tarifa | o piso da call | o piso da call mais cell_fee por célula criada |
Um programa lê a palavra de contexto i com READ_PUBLIC(public_len + 8 + i). A palavra 0 é a versão (1), as palavras 1 a 4 são as contagens de leituras, escritas, pagamentos e emissões, depois burn_r, o tipo de inflow, burn_asset e burn_a, depois cada leitura e escrita como chave e valor (8 palavras cada), depois cada pagamento como ativo e valor. Os destinatários não estão no contexto: o programa decide os valores, a vinculação fixa quem é pago. Um programa deve restringir toda palavra de contexto que lhe é mostrada e não pode ter nenhum caminho que cause panic, porque o panic handler do guest-sdk interrompe a execução, e uma execução interrompida é uma execução provável.
Limites da chain 20 (rand_getLimits.program_state):
| limite | chain 20 |
|---|---|
cell_fee | 10.000.000 unidades, 0,01 RAND, por célula criada (uma célula escrita com valor diferente de zero onde nada estava armazenado); reescrever ou apagar uma célula é gratuito |
max_reads | 8 células |
max_writes | 8 células |
max_payouts | 4 notes, pagamentos e emissões juntos |
A regra do segmento costuma ser mais restritiva: o segmento inteiro deve caber na tabela pública sobre a qual uma chamada ao mesmo programa é provada, 127 palavras para um programa sem entrada pública. Isso deixa 119 palavras de contexto, por exemplo três leituras, três escritas e quatro pagamentos.
Comandos e RPC (carteira v0.6.8):
rand program build --guest rpl2_counter --out counter.json # one cell, incremented by one per invoke
rand program invoke <id> --transition step.json [--input N | --inputs-file F.json]
rand program state <id> [--cell <64 hex>] # rand_getProgramCell / rand_getProgramCells
rand program vault <id> # rand_getProgramVault
program invoke verifica cada entrada de reads contra a célula atual antes de provar e encerra com código 3 diante de uma leitura obsoleta, antes de provar ou na inclusão, de modo que um script lê de novo e tenta outra vez. rand_estimateFee com "kind": "invoke" cota a tarifa. O formato do arquivo de transição está em docs/cli.md do fullnode. Programas RPL-2 completos implantados na chain 20 (amm, stableswap, orderbook, stablecoin, lending, perp, crowdfund, stoploss) e programas RPL-1 (sealed-auction, ballot, eligibility, private-join, credit-score) estão em randprotocol/example-programs.
O que é público e o que fica oculto
| público | oculto |
|---|---|
o id do programa e o seu código; o tier; o limite de gas declarado; as oito saídas; H_IN; a taxa e os campos públicos do bundle; o tamanho do envelope; se a chamada usou Keccak ou SHA-256 e, aproximadamente, com que frequência (o cabeçalho da prova declara a altura de cada tabela de hash) | as entradas privadas; registradores, memória, ramificações; a contagem real de ciclos e o gas exato; o salt de H_IN; quem fez a chamada e com o que pagou |
Duas provas da mesma execução são sequências de bytes diferentes. Isso não bastava na chain 15 e anteriores: uma chamada com 31 ou menos palavras privadas provava uma tabela pequena demais para ocultar, e suas entradas podiam ser recuperadas a partir da prova. O prover fixa um piso de 128 linhas para essas tabelas desde a v0.6, e o constraint set 7 (chain 16 e posteriores) mascara os totais por tabela que ainda vazavam. hc é vinculante, mas não ocultante: quem puder enumerar programas candidatos consegue confirmar qual deles foi implantado.
O que ainda não é possível
Nos termos em que o fullnode os declara:
- Sem chamadas entre programas e sem estado fora do RPL-2. Um
Callé uma prova sobre uma execução; nada passa de uma chamada para outra, exceto o que o chamador devolve como entrada. Na chain 20, umInvokelê e escreve as células e o vault do próprio programa (acima); nenhuma chain anterior à 20 tem estado de programa, e um invoke não pode tocar as células de outro programa. - Sem transferências conduzidas por programa fora de um vault. O effect kind 1, a transferência paga pelo programa para uma conta, foi removido junto com as contas, e as saídas de um
Callsão declarações. Na chain 20, umInvokepaga a partir do vault do próprio programa, no máximo quatro notes, com valores e destinatários públicos. - Sem compilador na carteira. Os programas são os guests montados que já vêm embutidos, ou imagens construídas com
rand-guestno repositório de circuitos. - A entrada pública é fixada no deploy, não a cada chamada. O constraint set 6 acrescentou
READ_PUBLICeH_PUB. Na chain 20, um deploy pode carregar até 32.768 palavras públicas, o id do programa as vincula, e a prova de cada chamada é verificada contra o digest delas. Não há palavras públicas por chamada. - As syscalls de hash geram provas grandes. Uma prova com Keccak tem cerca de 3,2 MB, acima do limite padrão de prova de 2 MiB; uma prova com SHA-256 acrescenta cerca de 400 KB. O limite da chain 20 é 4.194.304 bytes (4 MiB, como nas chains 17 a 19; era 8 MiB nas chains 13 a 16), o que admite ambas: na chain 18, uma chamada de tier 12 com a tabela Keccak mediu 3,35 MB, e uma chamada de tier 14 com a tabela SHA-256 mediu 1,91 MB (Example programs). Uma chamada também é limitada a 128 permutações Keccak-f e 128 compressões SHA-256 (abaixo), e cada KiB de prova é cobrado ao preço por byte.
- Programas EVM e Solana não são implantados na chain 20, e suas chamadas excedem o limite de tier de chamada. Os guests interpretadores (
evm.bin, 18.009 palavras;sbpf.bin, 8.317) e as imagens traduzidas (o ERC-20, 11.686 palavras; SPL Token, 65.096) estão acima do limite padrão de 4.096 palavras e abaixo das 65.535 que a chain 13 definiu e todas as chains seguintes mantiveram. As duas imagens traduzidas foram implantadas na chain 13 em 2026-09-19, o SPL Token com o seu ELF como entrada pública. As chains 14 e 15 foram criadas do zero, e nenhuma das imagens foi reimplantada em nenhuma delas. A chain 20, como a chain 19, recusa implantar qualquer um dos quatro: pela sua regrahardening_v6, um programa que nenhuma chamada de tier 14 conseguiria comportar, com mais de 8.180 palavras sem segmento público e menos com um, é recusado no deploy (ProgramUncallable), para que não se pague uma taxa de deploy por um programa que ninguém pode chamar. Uma chamada ERC-20 é provada no tier 16 ou 18: oapprovede tier 16 foi chamado na chain 13 em 2026-09-19 com uma prova de produção de 3.412.405 bytes (Ethereum transpiler), e a chain 20 recusa qualquer chamada acima do tier 14 (próximo item), de modo que hoje nenhuma chamada ERC-20 é admissível ali. As chamadas SPL Token são de tier 20, que exige cerca de 330 GB; não existe nenhuma prova de chamada SPL (proving memory). Os opcodes EVM de contexto de bloco geram trap, e não há estado EVM on-chain. - Uma chamada é provada no tier 14 ou abaixo. Desde o fullnode v0.5.6 (2026-09-25), uma prova de chamada acima do tier 14 é recusada (
CallTierTooHigh), assim como uma com mais de 128 permutações Keccak-f ou 128 compressões SHA-256. OapproveERC-20 de tier 16 da chain 13 seria recusado na chain 20, como teria sido nas chains 15, 18 e 19, e o mesmo vale para toda chamada ERC-20 de tier 18 ou SPL Token de tier 20; essas ainda são provadas off-chain (the budget). - A prova por GPU nunca rodou em hardware.
--cudaexige um build com--features cudae PTX commitado; nenhum dos dois existe na frota. Na chain 20, a prova de uma chamada é feita no backend de CPU, qualquer que seja o build. hcnão é ocultante, como acima, e uma solicitação de witness ainda informa ao nó qual leaf uma carteira gasta.