Desenvolvimento
Este capítulo apresenta o desenvolvimento técnico do trabalho em quatro blocos integrados: o problema e o modelo de ameaça (§4.1), a solução proposta (§4.2), a especificação e a modelagem do sistema (§4.3) e a implementação concreta (§4.4), encerrando com a consolidação das garantias obtidas pela arquitetura (§4.5).
4.1 Problema e Modelo de Ameaça
Antes de desenhar a solução, esta seção fixa o que se está protegendo e de quem: recapitula o problema (§4.1.1), define o modelo de adversário (§4.1.2) e os objetivos de privacidade exigidos (§4.1.3), delimita o papel da TSN no modelo de confiança (§4.1.4), mapeia qualitativamente o vazamento por estado do leilão (§4.1.5) e declara as limitações do modelo (§4.1.6).
4.1.1 Recapitulação do problema
O capítulo 3 fechou apontando uma lacuna específica: ainda não existia, na data deste trabalho, uma implementação acadêmica documentada de leilão de Vickrey privado sobre a Fhenix CoFHE, com análise formal de vazamento de informação. As seções seguintes deste capítulo entregam essa implementação. Antes disso, contudo, é preciso fixar o que significa precisamente "privacidade" no contexto deste mecanismo. Esta seção define formalmente o adversário considerado, os objetivos de sigilo exigidos, o papel da Threshold Services Network (TSN) no modelo de confiança e os limites declarados do que o sistema protege. A análise quantitativa correspondente, com medida de incerteza residual sobre os lances perdedores, é apresentada no capítulo 5.
4.1.2 Modelo de adversário
Adota-se um modelo conservador de adversário, próximo do que a literatura de leilões privados costuma denominar honest-but-curious adversary (adversário honesto-mas-curioso, que segue o protocolo do mecanismo mas tenta inferir o máximo do que observa) no nível da plataforma, combinado com malicious bidder (licitante malicioso, que pode desviar do protocolo) no nível da aplicação. Em termos concretos, o adversário é tratado como uma entidade única (representando, sem perda de generalidade, um conluio entre múltiplos atores) com as capacidades descritas a seguir.
Quanto ao que observa, o adversário tem acesso integral à blockchain pública. Isso inclui: todas as transações que circulam pelo mempool (a área pública de espera por onde transações passam antes da confirmação, definida na §1.1), todos os blocos confirmados, todo o estado de armazenamento (storage) do contrato, todos os eventos emitidos, toda a calldata (a porção da transação que carrega os argumentos da chamada de função), e todos os endereços de carteira que assinam transações. Em termos práticos, ele tem permissão equivalente à de qualquer consulta possível por block explorer público.
Quanto ao que controla, o adversário envia suas próprias transações com qualquer conteúdo, em qualquer momento, com qualquer prioridade de gás. Ele pode operar uma ou várias carteiras simultaneamente e coordenar com validadores para influenciar o ordenamento de transações dentro de um bloco, conforme descrito em §1.2.2. Ele não controla, contudo, as transações alheias antes de elas serem difundidas.
Quanto aos objetivos que o adversário persegue, três interessam diretamente a este trabalho. Primeiro, ele tenta inferir o valor de cada um dos lances individuais submetidos por outros participantes, sobretudo os lances perdedores. Segundo, ele tenta inferir o lance específico do vencedor além daquilo que é necessariamente revelado pelo mecanismo (isto é, além do segundo preço). Terceiro, ele tenta manipular o preço de fechamento ou a identidade do vencedor por meio de transações estratégicas próprias, como frontrunning, shill bidding (lances dados pelo próprio vendedor para inflar artificialmente o preço) ou submissões repetidas.
Há também o que o adversário não controla, e essas suposições são as premissas explícitas do modelo. Em particular, supõe-se que pelo menos t dentre N operadores da TSN cooperem honestamente: isto é, não revelem a chave de decifração ao adversário e não decifrem ciphertexts além daqueles que o contrato autoriza por meio de FHE.allowPublic. Supõe-se também que a rede subjacente (Arbitrum Sepolia, no caso da implementação avaliada) está disponível, isto é, transações válidas eventualmente entram em algum bloco. A análise de cenários de censura completa, em que validadores se recusam a incluir transações, está fora do escopo (ver §4.1.6).
4.1.3 Objetivos de privacidade exigidos
Sob o modelo de adversário acima, o sistema deve preservar quatro propriedades simultaneamente, listadas em ordem decrescente de força.
A primeira, e mais central, é o sigilo permanente dos lances perdedores. Para qualquer participante que não tenha vencido o leilão, o valor exato de seu lance deve permanecer inacessível ao adversário em todos os momentos, inclusive após o encerramento do leilão. Essa propriedade é a razão econômica fundamental que justifica adotar Fully Homomorphic Encryption como infraestrutura criptográfica do mecanismo, conforme discutido na §1.2.5 e na §3.5.
A segunda é o sigilo parcial do lance do vencedor. O vencedor é necessariamente identificado pelo mecanismo, e o segundo maior lance é necessariamente publicado (é o preço a pagar). Mas o lance específico do vencedor, em sua plenitude, deve permanecer oculto. Ele pode ser arbitrariamente maior que o segundo preço, e essa folga deve permanecer confidencial.
A terceira é o conhecimento público mínimo necessário. O mecanismo de Vickrey, por sua natureza, exige que ao final dois valores sejam tornados públicos: a identidade do vencedor (para que o item lhe seja entregue) e o segundo maior lance (para que esse valor lhe seja cobrado). Nenhum outro dado individual sobre lances precisa ser revelado, e nenhum outro o será.
A quarta é a preservação da propriedade de truthfulness intrínseca ao mecanismo de Vickrey, descrita na §2.5.5. Truthfulness depende que os lances permaneçam efetivamente selados, o que, por sua vez, depende das três propriedades anteriores. Sob o adversário considerado, se as três primeiras forem garantidas, a quarta segue automaticamente.
4.1.4 Papel da TSN no modelo de confiança
A TSN é o ponto de articulação entre dois regimes que parecem incompatíveis em um leilão sobre FHE: a confidencialidade integral durante a computação e a revelação seletiva ao final. Sem ela, ou os lances permaneceriam cifrados para sempre (e o mecanismo não conseguiria entregar o item ao vencedor), ou seria preciso confiar em um operador centralizado para guardar a chave de decifração (regredindo ao modelo de leiloeiro confiável que se buscava evitar). A TSN resolve essa tensão por meio de Multi-Party Computation por limiar (MPC, definida na §2.3.4), conforme descrito na §2.3.4.
A premissa de confiança da TSN é, formalmente, a seguinte: existem pelo menos t operadores dentre os N da rede que se comportam honestamente, isto é, somente decifram aqueles ciphertexts que o contrato Solidity explicitamente autorizou por meio de FHE.allowPublic. Essa premissa é qualitativamente análoga à dos sistemas de validação por Proof of Stake (em que se supõe maioria honesta dos validadores) e à de toda família threshold cryptography. Não é uma premissa trivial, mas é o tipo de premissa que a literatura de criptografia distribuída considera padrão e que a Fhenix opera como base de seu produto comercial (FHENIX, 2026b).
O que a TSN garante, sob essa premissa, é forte. Primeiro, nenhum ciphertext é decifrável sem que o contrato Solidity tenha emitido a autorização correspondente. Segundo, decifrações autorizadas só produzem resultado quando o quórum coopera, e cada resultado vem acompanhado de uma assinatura coletiva que o contrato verifica antes de aceitar (via FHE.verifyDecryptResult). Terceiro, a chave de decifração nunca é reconstruída em um único ponto da rede; ela existe apenas em fragmentos distribuídos.
O que a TSN não garante, e que precisa ser dito explicitamente, é igualmente importante para o modelo. Ela não impede que operadores se recusem a participar de uma decifração legítima (situação em que o leilão fica em estado pendente até que o quórum coopere). Ela não controla a latência do round-trip de decifração, que depende da carga e da disponibilidade dos operadores. E ela depende, no limite, da operação econômica e técnica da própria Fhenix como rede; um cenário de desligamento permanente da TSN tornaria não-decifráveis para sempre os handles (referências curtas aos dados cifrados, que o contrato armazena no lugar dos ciphertexts) previamente cifrados sob suas chaves, embora a confidencialidade ainda fosse preservada.
4.1.5 Análise qualitativa de vazamento por estado do leilão
Combinando o modelo de adversário (§4.1.2), os objetivos de privacidade (§4.1.3) e o papel da TSN (§4.1.4), é possível mapear, para cada estado do leilão, exatamente o que se torna visível e o que permanece oculto. O contrato modela quatro estados, conforme descrito em §4.2.6: Active, SettlementRequested, Settled e Cancelled. A tabela 2 sintetiza o vazamento qualitativo em cada um deles; nela figura o colateral: o depósito de garantia em ETH que cada bidder entrega ao submeter o lance, com valor uniforme definido publicamente pelo seller na criação do leilão e devolvido ao final do ciclo (integral para os perdedores; descontado o segundo preço para o vencedor), como detalha a §4.2.5.
| Estado | Visível ao adversário | Oculto ao adversário |
|---|---|---|
Active | Número de lances submetidos, endereços dos bidders, valor uniforme do colateral, instante de cada lance, identidade do seller, estado | Valor de cada lance individual, identidade do vencedor parcial, valor parcial do maior e segundo maior lance |
SettlementRequested | Tudo o que era visível em Active; mais dois ciphertext hashes (winnerCtHash e secondPriceCtHash) marcados como publicamente decifráveis | Os valores em texto claro do vencedor e do segundo preço (até a TSN responder); valores de todos os lances perdedores |
Settled | Tudo o que era visível em SettlementRequested; mais a identidade do vencedor (endereço) e o segundo preço em texto claro, ambos verificados via FHE.verifyDecryptResult | Lance específico do vencedor (sabe-se apenas que é maior ou igual ao segundo preço); valores de todos os lances perdedores |
Cancelled | Número de lances (tipicamente zero, pois cancelamento exige totalBids == 0), endereços, status | Não se aplica em prática (sem lances submetidos) |
Tabela 2: Capabilities do adversário por estado do leilão.
Quatro observações sobre a tabela merecem destaque. Primeiro, o adversário aprende as identidades dos bidders desde o início. Isso é uma consequência inevitável de a transação bid ser assinada por uma carteira pseudoanônima, mas pública. Mecanismos avançados para ocultar também essa informação (por exemplo, stealth addresses) estão fora do escopo deste trabalho (ver §4.1.6).
Segundo, o adversário aprende o número de lances mas não os valores, durante todo o período ativo. Essa é uma propriedade especialmente útil em leilões competitivos: o adversário sabe quantos concorrentes participam do leilão, mas não sabe como precificar seu próprio lance para superá-los.
Terceiro, os lances perdedores nunca são revelados, em momento algum. Eles ficam armazenados no estado do coprocessador CoFHE como ciphertexts opacos, e nenhuma transação subsequente os marca como decifráveis. Mesmo o vencedor, se quiser saber por quanto seu lance excedeu o segundo preço, só consegue inspecionar o próprio handle por meio de um permit específico, conforme discutido em §2.3.5. Os lances perdedores dos demais permanecem cifrados indefinidamente.
Quarto, o lance do vencedor é parcialmente revelado. Sabe-se, após o Settled, que ele é maior ou igual ao segundo preço (pois o vencedor é o maior lance) e menor ou igual ao colateral depositado (pois o contrato cifra o lance pelo colateral por meio da operação descrita em §4.2.5). O intervalo [secondPrice, collateralAmount] é o espaço de incerteza residual sobre o lance do vencedor, e o capítulo 5 deriva esse intervalo em forma fechada e o ilustra com um exemplo numérico.
4.1.6 Limitações declaradas do modelo
Quatro classes de vazamento ficam reconhecidamente fora do que o sistema protege. Reconhecê-las explicitamente é parte do compromisso metodológico deste trabalho.
A primeira é o vazamento de ordem temporal. O contrato resolve empates de valor por ordem de chegada, o que significa que, em caso de empate no maior lance, o primeiro endereço a submetê-lo é declarado vencedor, conforme detalhado em §4.2.4. Isso introduz uma assimetria observável: o adversário aprende, indiretamente, que o vencedor declarado é o primeiro a chegar com o valor vencedor (no caso de empate). A análise temporal mais ampla, incluindo padrões de submissão como sinalização estratégica, não é tratada como ataque relevante neste modelo.
A segunda é a identidade do vencedor, revelada por design. O mecanismo de Vickrey exige que o item seja entregue ao vencedor, o que requer conhecer publicamente quem ele é. Mecanismos de ocultação adicional do vencedor (por exemplo, alocando o item a uma stealth address derivada do endereço público) são possíveis em princípio, mas constituem extensão fora do escopo deste TCC e ficam registrados como trabalho futuro no capítulo 6.
A terceira é o upper bound público dos lances pelo colateral. Cada bidder deposita exatamente o valor collateralAmount em ETH plaintext ao submeter o lance, e esse valor é definido publicamente pelo seller na criação do leilão. O adversário sabe, portanto, que todo lance é menor ou igual a esse teto. A escolha de colateral uniforme (todos depositam o mesmo, em vez de cada um escolher o seu) mitiga o vazamento individual: em vez de aprender o teto próprio de cada bidder, o adversário aprende apenas o teto comum imposto pelo seller. Mas o teto em si é público, e isso reduz o espaço de inferência sobre os lances. Essa é uma limitação intrínseca de leilões on-chain sem mecanismos adicionais de crédito; ela é discutida em mais profundidade em §4.2.5.
A quarta é a censura no nível do consenso. Validadores podem se recusar a incluir uma transação bid específica, atrasando ou impedindo sua entrada no leilão. Mempools privados como o Flashbots Protect, discutidos em §1.2.2, mitigam parcialmente esse vetor, mas a defesa completa contra censura está fora do escopo. O modelo deste trabalho assume liveness razoável da rede subjacente (a propriedade de que a rede continua processando e incluindo transações, sem parar indefinidamente).
Reconhecidas essas limitações, o sistema garante, sob as premissas declaradas, exatamente o que foi listado em §4.1.3: sigilo permanente dos lances perdedores, sigilo parcial do lance do vencedor, conhecimento público mínimo necessário ao mecanismo, e truthfulness preservada. A solução técnica que viabiliza essas garantias é apresentada a seguir.
4.2 Solução Proposta
Esta seção apresenta a solução em camadas: a visão geral da arquitetura (§4.2.1), o problema da exclusão que distingue o segundo preço do primeiro (§4.2.2), o padrão top-2 incremental que o resolve em custo constante (§4.2.3), o tratamento determinístico de empates (§4.2.4), as decisões de design defensivas (§4.2.5) e o fluxo de execução com a máquina de estados (§4.2.6).
4.2.1 Visão geral da arquitetura
A solução proposta organiza-se em quatro componentes que trocam mensagens em uma topologia bem definida. Do lado do usuário, há o cliente off-chain, executado no navegador. Ele é responsável por cifrar os lances do participante antes de qualquer envio para a rede, autenticar o participante por meio da carteira e orquestrar a recuperação do resultado decifrado ao final. Do lado da rede pública, há o contrato Solidity, denominado ConfidentialVickreyAuction, hospedado em uma blockchain pública compatível com a Ethereum Virtual Machine (a EVM, conforme §2.1.3); ele detém o estado do leilão, recebe os lances cifrados, computa homomorficamente o vencedor e o segundo preço, e governa a máquina de estados do mecanismo. Do lado da infraestrutura criptográfica, há o coprocessador CoFHE, que armazena os ciphertexts efetivos e executa as operações homomórficas requisitadas pelo contrato. E há a Threshold Services Network (TSN), que detém em conjunto a chave de decifração e a aciona, sob autorização do contrato, mediante cooperação de um quórum mínimo de operadores. Cada uma dessas peças foi caracterizada em detalhe no capítulo 2 (§2.3.4 e §2.3.5); aqui o foco é como elas se articulam.
A figura 1 sintetiza a topologia. O cliente envia ao contrato uma estrutura cifrada InEuint64, contendo o lance pré-cifrado em texto cifrado homomórfico do tipo euint64. O contrato armazena apenas referências curtas a esses ciphertexts (chamadas de handles) e delega a computação cifrada efetiva ao coprocessador CoFHE, que opera em rede própria fora da blockchain. Ao final do leilão, o contrato marca os handles de interesse (o do vencedor e o do segundo preço) como publicamente decifráveis e a TSN responde, sob solicitação do cliente, com os valores em texto claro acompanhados de assinatura coletiva, obtendo do coprocessador os ciphertexts cuja decifração o contrato autorizou. O cliente publica esses valores de volta na blockchain, e o contrato verifica que os valores em texto claro correspondem aos handles cifrados originais por meio da função FHE.verifyDecryptResult. A verificação on-chain garante que o cliente não pode inventar resultados arbitrários, sem precisar do callback de oráculo (serviço externo que entrega dados ao contrato) adotado pela fhEVM da Zama (HINDI, 2024).
Figura 1. Topologia da solução.
Essa topologia tem três propriedades atraentes do ponto de vista deste trabalho. Primeiro, ela é cliente-orquestrada: o contrato é passivo entre solicitação e finalização do settlement, o que reduz o acoplamento entre on-chain e off-chain a um padrão de duas etapas declarativo. Segundo, ela é modular: o coprocessador CoFHE é externo à blockchain hospedeira, o que permite que o mesmo contrato seja implantado em diferentes redes EVM (Ethereum Sepolia, Arbitrum Sepolia, Base Sepolia) sem alteração. Terceiro, ela é verificável on-chain: nenhum valor em texto claro é aceito pelo contrato sem assinatura da TSN sobre o ciphertext correspondente, o que torna a confiança no cliente irrelevante para a corretude do mecanismo.
4.2.2 O problema da exclusão
A propriedade central do leilão de Vickrey, conforme estabelecido em §2.5.4, é que o vencedor (o maior lance) paga o valor do segundo maior lance. Computar essa regra em texto claro é trivial: ordene o vetor de lances em ordem decrescente, atribua o item ao primeiro e o preço ao segundo. Em FHE, porém, essa operação enfrenta uma dificuldade estrutural, que a literatura denomina problema da exclusão.
A dificuldade pode ser enunciada de forma direta: para identificar o segundo maior valor, é necessário antes excluir o vencedor do conjunto de candidatos. Em texto claro, essa exclusão é feita comparando cada valor com o máximo já calculado e descartando o que coincidir. Em FHE, contudo, não se pode olhar para os valores: tanto o lance quanto o máximo são ciphertexts; a comparação bid_i == max produz um booleano cifrado (ebool), não uma decisão observável; e tomar uma ação condicional sobre esse booleano (descartar o lance ou mantê-lo) também precisa ocorrer no domínio cifrado.
A solução padrão na literatura, descrita em Faruukku (2026) no contexto da fhEVM da Zama, é construir uma máscara homomórfica: para cada lance b_i, computar ehVencedor_i = FHE.eq(b_i, max) (um ebool cifrado indicando se o lance é igual ao máximo) e, em seguida, b_mascarado_i = FHE.select(ehVencedor_i, 0, b_i). A operação FHE.select (operador ternário cifrado) substitui o lance vencedor por zero, sem que o contrato precise saber qual era esse vencedor. Aplicando FHE.max sobre o vetor mascarado, o segundo maior valor original emerge.
Essa solução tem três limitações práticas para implementação on-chain. Primeiro, ela exige armazenar todos os lances cifrados durante todo o leilão, para que o vetor possa ser revisitado na fase de settlement; o custo de armazenamento em FHE é alto, pois cada euint64 consome um handle que ocupa slot de storage no contrato. Segundo, ela faz a computação do segundo preço integralmente na fase de settlement, em complexidade O(N) sobre o número de lances; cada iteração realiza uma operação FHE.eq e uma FHE.select, ambas caras (cada uma dispara um bootstrapping TFHE, conforme §2.3.1). Terceiro, ela exige tratamento explícito de empates no topo: se dois lances são iguais ao máximo, a máscara zera ambos, e o segundo maior cai erroneamente para o terceiro valor real. O VeilBid de Faruukku (2026) resolve essa última limitação com um padrão de first-match determinístico, usando FHE.and e FHE.not sobre flags de exclusão acumuladas iterativamente, o que adiciona ainda mais comparações por bid no settlement.
4.2.3 Padrão top-2 incremental
A solução adotada neste trabalho parte de uma observação simples: não é necessário guardar todos os lances individualmente para calcular o segundo maior valor. Basta manter, ao longo de todo o leilão, dois ciphertexts agregados: o maior lance corrente (highestBid) e o segundo maior lance corrente (secondHighestBid). A cada novo lance que chega, esses dois valores são atualizados em uma única passada, sem revisitar o histórico. O algoritmo é exatamente o mesmo que seria usado para manter o top-2 de um fluxo de valores em texto claro, com a diferença de que cada operação é homomórfica.
O pseudocódigo é o seguinte. Para cada lance novo b submetido pelo bidder X (Listagem 1):
maiorQueOPrimeiro = FHE.gt(b, highestBid)
maiorQueOSegundo = FHE.gt(b, secondHighestBid)
novoSegundo = FHE.select(
maiorQueOPrimeiro,
highestBid, # antigo primeiro cai para segundo
FHE.select(maiorQueOSegundo, b, secondHighestBid) # ou b vira o novo segundo
)
novoPrimeiro = FHE.select(maiorQueOPrimeiro, b, highestBid)
novoVencedor = FHE.select(maiorQueOPrimeiro, X, highestBidder)
highestBid = novoPrimeiro
secondHighestBid = novoSegundo
highestBidder = novoVencedorListagem 1: Pseudocódigo do padrão top-2 incremental executado a cada lance.
A implementação real em Solidity, no trecho relevante do contrato ConfidentialVickreyAuction.sol, reproduz literalmente esse padrão (Listagem 2):
ebool isHigherThanFirst = FHE.gt(bidAmount, auction.highestBid);
ebool isHigherThanSecond = FHE.gt(bidAmount, auction.secondHighestBid);
euint64 newSecond = FHE.select(
isHigherThanFirst,
auction.highestBid,
FHE.select(isHigherThanSecond, bidAmount, auction.secondHighestBid)
);
euint64 newHighest = FHE.select(isHigherThanFirst, bidAmount, auction.highestBid);
eaddress newHighestBidder = FHE.select(
isHigherThanFirst,
FHE.asEaddress(msg.sender),
auction.highestBidder
);Listagem 2: Atualização homomórfica dos agregados top-2 na função bid do contrato.
Esse desenho difere do padrão de máscara iterativa em três dimensões mensuráveis. Primeiro, o custo por lance é constante em relação a N: cada bid executa exatamente duas comparações FHE.gt e quatro FHE.select, independentemente de quantos lances já foram submetidos. Na máscara iterativa, o custo do settlement cresce linearmente em N (uma FHE.eq e uma FHE.select por lance, dentro de um loop). Segundo, o armazenamento on-chain por leilão é constante: apenas três handles cifrados são persistidos (highestBid, secondHighestBid, highestBidder), em vez de N handles individuais. Terceiro, a fase de settlement torna-se trivial: como o segundo preço já está calculado no momento em que o leilão fecha, a função requestSettlement apenas marca o handle correspondente como publicamente decifrável; nenhuma comparação adicional é necessária. Os capítulos seguintes (capítulo 5) quantificam o impacto dessas três propriedades em gás e latência.
4.2.4 Tratamento determinístico de empates
Uma propriedade importante do algoritmo top-2 incremental, decorrente do uso de FHE.gt (estritamente maior) em vez de FHE.gte (maior ou igual), é que empates entre o lance novo e o highestBid corrente preservam o vencedor anterior. Considere o cenário em que o bidder X já está com o maior lance corrente e o bidder Y submete um lance de mesmo valor. Como FHE.gt(b_Y, highestBid) = FHE.gt(highestBid, highestBid) = false, o FHE.select mantém o estado: highestBid continua sendo o valor anterior, e highestBidder continua sendo X. O lance de Y cai para o segundo, pois ele é estritamente maior que o secondHighestBid anterior.
Essa propriedade resolve o tratamento de empates sem qualquer estrutura adicional. O VeilBid, que opera sobre a máscara iterativa, precisa de uma flag de exclusão acumulada para garantir que apenas o primeiro participante a chegar com o valor máximo seja contado como vencedor; isso adiciona operações FHE.and e FHE.not no settlement. No padrão top-2 incremental, a estrita desigualdade do FHE.gt faz o mesmo trabalho de forma gratuita, e a regra resultante coincide com a regra de desempate clássica em teoria de leilões: em caso de empate no maior lance, vence quem chegou primeiro (KRISHNA, 2009).
A propriedade vai além do empate simples. Considere um cenário com três bidders iguais ao valor máximo (uma "carona tripla"): X ofereceu primeiro, depois Y, depois Z. Após o lance de X, o estado é (highestBid = v, secondHighestBid = 0). Após o lance de Y, FHE.gt(v, v) = false para o primeiro nível, então Y não substitui X; mas FHE.gt(v, 0) = true para o segundo nível, então secondHighestBid passa a ser v (o lance de Y). Após o lance de Z, novamente o primeiro nível rejeita (mantém X), e o segundo nível também rejeita porque FHE.gt(v, v) = false. O estado final é (highestBid = v, secondHighestBid = v, highestBidder = X). O vencedor é X (o primeiro a chegar), e ele paga v (o valor do empate), exatamente como define a regra clássica de Vickrey em presença de empates (§2.5.4). Esse cenário é verificado empiricamente nos testes do contrato (§4.4.5), no caso triple tie at the top.
A interação do tratamento de empates com o cálculo do segundo preço também merece atenção. Em um cenário com um único maior (X ofereceu v_max) e dois bidders empatados no segundo lugar (Y e Z ofereceram ambos v_2 < v_max), o algoritmo produz highestBid = v_max, secondHighestBid = v_2, e o vencedor X paga v_2. Esse comportamento também coincide com Vickrey clássico, e é coberto pelo teste tie at the second place.
4.2.5 Decisões de design defensivas
A formulação enxuta do algoritmo top-2 incremental é o coração da solução, mas a implementação final precisa enfrentar quatro vetores adicionais que apareceram durante o desenvolvimento e foram resolvidos por decisões de design explícitas. Cada decisão é discutida a seguir, com a justificativa do problema atacado, a alternativa adotada e o trade-off implicado.
A primeira é o cap homomórfico do lance ao valor do colateral. Sem essa salvaguarda, um bidder mal-intencionado poderia submeter um lance cifrado de valor arbitrariamente alto (por exemplo, próximo ao máximo de uint64), depositando apenas o colateral mínimo definido pelo seller. Esse lance venceria o leilão sem que o contrato pudesse detectá-lo (todos os valores são cifrados), e na fase de finalização o segundo preço calculado poderia ser maior que o colateral disponível do vencedor, impossibilitando o pagamento. O contrato ficaria travado em SettlementRequested indefinidamente, com os colaterais dos demais bidders presos. Esse é um vetor de griefing claro. A solução adotada é interceptar a aplicação na própria função bid e truncar o valor cifrado ao colateral (Listagem 3):
euint64 cap = FHE.asEuint64(uint64(auction.collateralAmount));
ebool tooHigh = FHE.gt(bidAmount, cap);
bidAmount = FHE.select(tooHigh, cap, bidAmount);Listagem 3: Truncagem homomórfica do lance ao teto do colateral (cap).
O bidder não percebe a truncagem (a operação é homomórfica), mas a propriedade estabelecida é forte: para qualquer leilão e qualquer bidder, o lance armazenado é menor ou igual ao colateral depositado. Essa truncagem tem uma implicação sobre a truthfulness quando o valor verdadeiro de um participante excede o colateral, discutida no capítulo 5 (§5.6). O custo são três operações FHE adicionais por bid (uma FHE.asEuint64, uma FHE.gt e uma FHE.select), cujo impacto no gás é discutido no capítulo 5.
A segunda decisão é a proibição de o seller participar como bidder do próprio leilão. Em teoria de mecanismos, essa prática é chamada de shill bidding e quebra a propriedade de truthfulness: o seller pode inflar artificialmente o preço de fechamento por meio de lances próprios, sabendo que, se vencer seu próprio leilão, pagará a si mesmo (KRISHNA, 2009). Como a identidade do seller é registrada em texto claro durante a criação do leilão, a proteção é direta: a função bid revertia explicitamente se msg.sender == auction.seller. Essa verificação é feita em texto claro, sem custo FHE adicional. O bug na implementação deste trabalho que motivou essa proteção foi descoberto durante o primeiro teste em rede pública, em que o seller participou inadvertidamente do próprio leilão e o seu colateral ficou contabilmente preso no contrato; o capítulo 5 detalha o incidente como evidência do valor de testes em testnet pública.
A terceira decisão é a exigência de pelo menos três bidders distintos para que o leilão possa ser finalizado, expressa pela constante MIN_BIDDERS = 3 no contrato. Essa exigência é uma defesa contra leilões com apenas dois participantes em que ambos ofertam o mesmo valor: nesse cenário, o segundo preço revelado coincide com o maior preço, e o adversário aprende que houve empate de fato. Com três bidders ou mais, o segundo preço pode coincidir ou diferir do maior, e o adversário não consegue distinguir esses casos sem conhecer outros lances. Essa decisão segue o padrão adotado por Faruukku (2026) no VeilBid, e é discutida no capítulo 6 como ponto de extensibilidade (leilões com menos participantes seriam possíveis mediante mecanismo de empate alternativo).
A quarta decisão é o modelo de pagamento por pull pattern para o withdraw. Em vez de o contrato transferir ETH automaticamente ao vencedor, ao seller e aos perdedores durante a finalização, a função finalizeSettlement apenas registra o resultado, e cada participante (incluindo o seller) chama withdraw ativamente para sacar seu saldo devido. Essa decisão segue a recomendação canônica de segurança em Solidity (OpenZeppelin, 2024): minimizar as transferências automáticas reduz a superfície de ataque por reentrância1 e elimina a possibilidade de uma falha de transferência de um único participante travar a finalização do leilão inteiro. O custo é uma transação adicional por participante (em vez de tudo ser resolvido em finalizeSettlement), mas como cada participante paga seu próprio gás de saque, o custo agregado de gás permanece distribuído.
4.2.6 Fluxo de execução e máquina de estados
O ciclo de vida de um leilão é modelado por uma máquina de estados de quatro nós, definida no contrato pelo enum Status { Active, SettlementRequested, Settled, Cancelled } e ilustrada na figura 2. As transições válidas são as seguintes:
Figura 2. Máquina de estados do leilão (versão introdutória).
O fluxo de execução de uma instância do leilão atravessa essa máquina de estados em duas etapas distintas, que correspondem ao padrão arquitetural da Fhenix CoFHE descrito em §2.3.5: o leilão executa em uma fase ativa, em que apenas operações cifradas ocorrem, e revela em uma fase finalística, em que dois (e somente dois) valores são tornados públicos. A separação dessas duas fases é o que permite que o sistema garanta as propriedades de privacidade enunciadas na §4.1.3 sob o modelo de adversário de §4.1.2.
A fase ativa começa com a criação do leilão pelo seller, via createAuction, e termina na chegada do deadline configurado. Durante essa fase, qualquer endereço não-seller pode chamar bid, submetendo um lance cifrado e depositando exatamente o colateral exigido. Cada chamada de bid atualiza incrementalmente os agregados top-2 (conforme §4.2.3), aplica o cap homomórfico (conforme §4.2.5), e registra o bidder no array interno de participantes. Nenhuma decifração ocorre, e nenhum lance é revelado. O contrato permanece em estado Active durante toda essa fase.
A fase finalística começa quando qualquer endereço (seller ou bidder) chama requestSettlement após o deadline. Essa chamada transita o estado para SettlementRequested, marca dois handles específicos como publicamente decifráveis (o do highestBidder e o do secondHighestBid) por meio de FHE.allowPublic, e armazena os respectivos ciphertext hashes em variáveis públicas do contrato, conforme detalhado em §2.3.5. A partir desse momento, qualquer cliente off-chain pode consultar a TSN, recuperar os valores em texto claro acompanhados de assinatura coletiva, e submetê-los de volta on-chain via finalizeSettlement. O contrato verifica as assinaturas com FHE.verifyDecryptResult, registra os valores decifrados em campos públicos e transita para Settled. Essa transição ocorre uma única vez por leilão: a função exige o estado SettlementRequested e o consome ao gravá-lo como Settled, de modo que uma segunda chamada é revertida. Os demais participantes não a acionam; após o Settled, perdedores, vencedor e seller sacam individualmente seus saldos via withdraw, no modelo pull descrito em §4.2.5. Esse fluxo é desenhado em maior detalhe nos Diagramas de Sequência apresentados em §4.3.6.
O estado Cancelled é uma rota alternativa restrita ao caso em que o leilão é criado mas não recebe nenhum lance. Apenas o seller pode chamar cancelAuction, e apenas enquanto totalBids == 0 (essa restrição é parte do contrato). A função existe para evitar que leilões abandonados ocupem auctionIds permanentemente.
Após Settled ou Cancelled, o leilão é terminal: nenhuma transição adicional é possível, e o estado permanece registrado para auditoria. As funções withdraw operam independentemente da máquina de estados principal, conforme o modelo de pull pattern descrito em §4.2.5: cada participante elegível (vencedor, seller, perdedores) saca o valor devido em uma transação separada, em qualquer ordem, em qualquer momento após a finalização.
4.3 Especificação e Modelagem
A §4.2 apresentou a ideia da solução e o algoritmo que está em seu núcleo. Esta seção formaliza o que o sistema precisa fazer e como suas peças se organizam, antes que a §4.4 apresente os detalhes, com exemplos de código. O percurso é o seguinte: os Requisitos Funcionais e Não-Funcionais (§4.3.1), os Atores e Casos de Uso (§4.3.2), o modelo de dados e o estado mantido pelo contrato (§4.3.3), a interface pública e suas pré-condições (§4.3.4), o catálogo das operações de Fully Homomorphic Encryption empregadas (§4.3.5), os Diagramas de Sequência dos dois fluxos principais (§4.3.6) e a máquina de estados formal do leilão (§4.3.7).
4.3.1 Requisitos
Os requisitos do sistema derivam diretamente de duas fontes já estabelecidas neste capítulo. Os objetivos de privacidade e o modelo de ameaça da §4.1 originam as exigências de sigilo e verificabilidade; a solução técnica da §4.2 origina as exigências de comportamento do mecanismo. Adota-se a distinção clássica entre Requisitos Funcionais (RF), que descrevem o que o sistema faz, e Requisitos Não-Funcionais (RNF), que descrevem as qualidades que ele deve exibir. Cada requisito é ancorado na seção que o motiva, de modo que a rastreabilidade entre problema, solução e especificação fique explícita.
A tabela 3 lista os Requisitos Funcionais.
| ID | Requisito Funcional | Origem |
|---|---|---|
| RF-01 | Permitir que um seller crie um leilão definindo nome, identificador do item, colateral uniforme e janela temporal de lances. | §4.2.6 |
| RF-02 | Permitir que qualquer endereço não-seller submeta um único lance cifrado, depositando exatamente o colateral exigido. | §4.2.6 |
| RF-03 | Truncar homomorficamente todo lance ao valor do colateral, sem revelar o lance nem a ocorrência da truncagem. | §4.2.5 |
| RF-04 | Impedir que o seller participe como bidder do próprio leilão. | §4.2.5 |
| RF-05 | Manter, de forma incremental e cifrada, o maior lance, o segundo maior lance e a identidade do maior lance. | §4.2.3 |
| RF-06 | Resolver empates no maior lance por ordem de chegada, de forma determinística e sem estrutura adicional. | §4.2.4 |
| RF-07 | Exigir um mínimo de três bidders distintos para que o leilão possa ser finalizado. | §4.2.5 |
| RF-08 | Após o encerramento do prazo, marcar o vencedor e o segundo preço como publicamente decifráveis pela TSN. | §4.2.6 |
| RF-09 | Finalizar o leilão somente após verificação on-chain das provas de decifração emitidas pela TSN. | §4.2.1, §4.2.6 |
| RF-10 | Liquidar pagamentos por pull pattern: o seller saca o segundo preço, o vencedor saca o troco do colateral e os perdedores sacam o colateral integral. | §4.2.5 |
| RF-11 | Permitir o cancelamento de um leilão que não recebeu nenhum lance. | §4.2.6 |
| RF-12 | Expor consultas públicas do estado do leilão e do resultado da liquidação. | §4.2.1 |
Tabela 3: Requisitos Funcionais do sistema.
Dois requisitos merecem comentário por concentrarem a originalidade da solução. O RF-05 é a tradução direta do padrão top-2 incremental da §4.2.3: em vez de armazenar os N lances, o contrato mantém apenas três agregados cifrados, atualizados a cada lance. O RF-09 é o que torna a confiança no cliente off-chain irrelevante para a corretude: nenhum valor em texto claro entra no estado do contrato sem que a assinatura coletiva da TSN sobre o ciphertext correspondente seja verificada na própria blockchain.
A tabela 4 lista os Requisitos Não-Funcionais. Cada um deles é uma qualidade transversal, exigida em todo o ciclo de vida do leilão, e não uma função isolada.
| ID | Requisito Não-Funcional | Origem |
|---|---|---|
| RNF-01 | Sigilo permanente dos lances perdedores, em todos os estados do leilão, inclusive após o encerramento. | §4.1.3 |
| RNF-02 | Sigilo parcial do lance do vencedor: revela-se apenas que ele é maior ou igual ao segundo preço. | §4.1.3 |
| RNF-03 | Verificabilidade: nenhum valor em texto claro é aceito pelo contrato sem assinatura da TSN sobre o ciphertext correspondente. | §4.2.1 |
| RNF-04 | Resistência a griefing: nenhum participante isolado pode travar a finalização do leilão. | §4.2.5 |
| RNF-05 | Custo de lance constante em relação ao número de participantes (O(1) por bid) e armazenamento constante por leilão. | §4.2.3 |
| RNF-06 | Portabilidade: o contrato deve poder ser implantado em qualquer rede compatível com a EVM em que o CoFHE esteja ativo, sem alteração de código. | §4.2.1 |
| RNF-07 | Resistência a reentrância nas transferências de ETH, assegurada pelo padrão checks-effects-interactions e pelo saque via pull. | §4.2.5 |
| RNF-08 | Resistência a ataques quânticos, herdada da base criptográfica TFHE. | §2.4.3 |
| RNF-09 | Compatibilidade com Solidity 0.8.25 (com evmVersion cancun) e com a biblioteca @fhenixprotocol/cofhe-contracts. | §4.4 |
Tabela 4: Requisitos Não-Funcionais do sistema.
O RNF-08 é o que conecta este trabalho à discussão de criptografia pós-quântica da §2.4: como o sigilo dos lances repousa sobre o TFHE, e o TFHE é considerado resistente a ataques quânticos (§2.4.3), a confidencialidade dos lances perdedores não é apenas robusta contra adversários clássicos, mas também contra um eventual adversário equipado com computação quântica capaz de quebrar a criptografia assimétrica convencional. Essa é uma propriedade desejável em leilões de ativos de longa duração, em que um lance perdedor não deveria poder ser revelado anos depois (a ameaça que a §2.4.4 descreve como harvest now, decrypt later).
4.3.2 Atores e Casos de Uso
O sistema envolve dois Atores humanos e dois sistemas externos. Os Atores humanos são o seller, que cria o leilão e oferta o item, e o bidder, que submete um lance cifrado e disputa o item. Os sistemas externos são o coprocessador CoFHE, que armazena os ciphertexts e executa as operações homomórficas, e a Threshold Services Network (TSN), que decifra sob autorização os dois valores revelados ao final. O cliente off-chain executado no navegador não é um ator no sentido de mecanismo: ele é o instrumento por meio do qual o bidder cifra o lance e por meio do qual qualquer participante consulta a TSN, conforme caracterizado na §4.2.1.
A figura 3 apresenta o Diagrama de Casos de Uso. Os casos requestSettlement e finalizeSettlement são permissionless: qualquer endereço pode acioná-los desde que as pré-condições sejam satisfeitas (o prazo encerrado, no primeiro caso, e a posse das provas da TSN, no segundo). O diagrama expressa essa abertura por meio de uma generalização de atores: Seller e Bidder herdam do ator genérico Usuário, ao qual os casos permissionless (requestSettlement, finalizeSettlement) e o saque (withdraw) estão associados; a §4.3.4 detalha as pré-condições de cada operação. Essa abertura é deliberada: ela garante que o leilão possa ser finalizado mesmo que o vencedor se recuse a cooperar, atendendo ao requisito de robustez discutido na §4.1.4.
Figura 3. Diagrama de Casos de Uso do contrato.
Cada caso de uso corresponde a uma função externa do contrato, com a seguinte intenção: createAuction instancia um novo leilão; bid submete um lance cifrado com colateral; requestSettlement encerra a fase ativa e marca os handles para decifração; finalizeSettlement publica o resultado verificado; withdraw realiza o saque devido a cada participante; e cancelAuction descarta um leilão vazio. A §4.3.4 especifica cada um em detalhe.
4.3.3 Modelo de dados e estado do contrato
O estado do contrato é organizado em torno de uma estrutura central, a Auction, indexada por um identificador numérico sequencial (auctionId). Cada instância de leilão é uma entrada no mapeamento auctions. A estrutura agrupa quatro categorias de dados, que convém distinguir porque têm naturezas de privacidade distintas.
A primeira categoria são os metadados públicos em texto claro: o nome do leilão, o endereço do seller, o identificador do item, o valor do colateral uniforme, os instantes de início e fim, o estado corrente (Status) e o contador de lances (totalBids). Esses campos são deliberadamente públicos; nenhum deles carrega informação estratégica sobre os lances.
A segunda categoria é o estado cifrado, mantido como três handles: highestBid e secondHighestBid, do tipo euint64, e highestBidder, do tipo eaddress. São exatamente os três agregados do padrão top-2 incremental (§4.2.3). Como discutido na §2.3.2, esses campos não armazenam os ciphertexts em si, mas apenas handles: referências curtas (de 32 bytes) que apontam para os ciphertexts efetivos, que vivem no coprocessador CoFHE fora da blockchain. A tabela 5 resume esses três campos.
| Campo | Tipo | Inicialização | Atualizado em |
|---|---|---|---|
highestBid | euint64 | FHE.asEuint64(0) | bid (top-2) |
secondHighestBid | euint64 | FHE.asEuint64(0) | bid (top-2) |
highestBidder | eaddress | FHE.asEaddress(address(0)) | bid (argmax: o endereço associado ao maior lance) |
Tabela 5: Campos cifrados do estado do leilão.
A terceira categoria é o resultado da decifração, preenchido somente na transição para Settled: o endereço do vencedor (decryptedWinner) e o segundo preço (decryptedSecondPrice), ambos em texto claro. Junto deles, dois ciphertext hashes (winnerCtHash e secondPriceCtHash) são capturados no momento da requestSettlement, para que o cliente off-chain saiba quais handles submeter à TSN. A captura desses hashes é feita pelas operações eaddress.unwrap e euint64.unwrap, que extraem a referência de 32 bytes de um handle cifrado.
A quarta categoria são os mapeamentos auxiliares de contabilidade, mantidos fora da estrutura Auction por serem indexados por par (leilão, endereço): collateral registra quanto cada bidder depositou (sempre igual ao collateralAmount, por construção); hasBid impede o lance duplo; e hasWithdrawn impede o saque duplo. A esses soma-se a constante MIN_BIDDERS, fixada em três, que materializa o RF-07.
Há ainda uma estrutura derivada, a AuctionView, que espelha os metadados públicos da Auction sem os campos cifrados. Ela é o tipo de retorno da função de consulta getAuction: como handles cifrados não têm representação útil para um cliente que apenas inspeciona o estado, a view os omite, expondo somente o que é público e legível. Essa separação entre a estrutura de armazenamento e a estrutura de consulta é uma decisão de modelagem que mantém a interface de leitura limpa e evita expor handles em contextos onde eles não têm utilidade.
4.3.4 Interface pública do contrato
A interface do contrato compõe-se de seis funções que alteram o estado e quatro funções de consulta (somente leitura). A tabela 6 especifica as funções de estado: para cada uma, quem pode chamá-la, as pré-condições que, se violadas, revertem a transação, e o efeito principal sobre o estado.
| Função | Quem pode chamar | Pré-condições (revertem se violadas) | Efeito principal |
|---|---|---|---|
createAuction | qualquer endereço (torna-se seller) | nome com 1 a 32 caracteres; endTime > startTime; endTime > agora; 0 < collateral ≤ uint64 máx | cria leilão em Active; inicializa o top-2 cifrado em zero |
bid | qualquer não-seller | estado Active; dentro da janela temporal; msg.value == collateral; ainda não ter dado lance; não ser o seller | aplica o cap homomórfico; atualiza o top-2 cifrado; registra o bidder |
requestSettlement | qualquer endereço | estado Active; após o endTime; totalBids ≥ MIN_BIDDERS | transita para SettlementRequested; aplica allowPublic; grava os ctHashes |
finalizeSettlement | qualquer endereço (de posse das provas) | estado SettlementRequested; provas válidas; vencedor com colateral suficiente | grava vencedor e segundo preço; transita para Settled |
withdraw | participante elegível | estado Settled ou Cancelled; ainda não ter sacado; ser elegível | transfere o ETH devido (modelo pull) |
cancelAuction | apenas o seller | estado Active; totalBids == 0 | transita para Cancelled |
Tabela 6: Interface das funções de estado.
As quatro funções de consulta complementam a interface: getAuction retorna a AuctionView (metadados públicos); getSettlementResult retorna o vencedor e o segundo preço, mas apenas após Settled; getSettlementCtHashes retorna os dois ctHashes para o cliente alimentar a TSN, disponível a partir de SettlementRequested; e getBidderEncryptedBid retorna um handle cifrado, de utilidade deliberadamente limitada (como apenas os agregados top-2 são retidos, e não os lances individuais, essa função existe sobretudo por simetria com a interface do PoC oficial que serviu de ponto de partida).
A robustez da interface é sustentada por um conjunto de dezessete erros customizados, que tornam cada falha diagnosticável. Eles cobrem violações de estado (AuctionNotActive, AuctionNotEnded, AuctionEnded, SettlementNotRequested), de autorização (NotSeller, NotEligible, SellerCannotBid), de unicidade (AlreadyBid, AlreadyWithdrawn), de parâmetros (InvalidTimeRange, WrongCollateral, NameRequired, NameTooLong, InsufficientBidders), de verificação criptográfica (InvalidDecryptionProof) e de liquidação (TransferFailed, WinnerCannotPay). O uso de erros customizados, em vez de mensagens de texto, é também uma escolha de economia de gás, pois cada erro é identificado por um seletor de quatro bytes.
4.3.5 Catálogo de operações FHE
A solução emprega um subconjunto enxuto das primitivas oferecidas pela biblioteca FHE.sol da Fhenix, caracterizadas em alto nível na §2.3.3. A tabela 7 cataloga cada operação utilizada, onde ela aparece e com que propósito. A contenção desse conjunto é proposital: como cada operação de comparação ou seleção é cara em FHE (cada uma envolve a maquinaria do TFHE descrita na §2.3.1), minimizar a variedade e a quantidade de operações é uma exigência de custo, formalizada no RNF-05.
| Operação | Papel | Onde é usada | Propósito |
|---|---|---|---|
FHE.asEuint64 | importa ou cria valor cifrado | createAuction, bid | converte o InEuint64 do cliente em euint64; cria as constantes cifradas (zero inicial, teto do cap) |
FHE.asEaddress | cifra um endereço | createAuction, bid | cifra o endereço do bidder para compor o argmax do vencedor |
FHE.gt | comparação estrita cifrada | bid | decide o cap (bid > teto) e o top-2 (bid > maior, bid > segundo) |
FHE.select | operador ternário cifrado | bid | aplica o cap, atualiza o top-2 e seleciona o vencedor sem decifrar |
FHE.allowThis | autorização de ACL | createAuction, bid | permite que o próprio contrato reutilize o handle em chamadas futuras |
FHE.allow | autorização de ACL | bid | permite que o bidder inspecione o próprio lance cifrado |
FHE.allowPublic | autorização de ACL | requestSettlement | permite que a TSN decifre o vencedor e o segundo preço |
FHE.verifyDecryptResult | verificação on-chain | finalizeSettlement | confirma que o valor em texto claro corresponde ao ciphertext, com a prova da TSN |
euint64.unwrap / eaddress.unwrap | extração de handle | requestSettlement | extrai o ctHash de 32 bytes para o cliente alimentar a TSN |
Tabela 7: Catálogo das operações FHE empregadas.
Um ponto da modelagem que costuma ser fonte de erro merece destaque: a lista de controle de acesso (ACL) sobre os handles. Na Fhenix CoFHE, um handle cifrado não pode ser operado livremente; o contrato precisa ter autorização explícita para usá-lo. Três operações governam essa autorização, em três escopos distintos. A FHE.allowThis concede ao próprio contrato o direito de reutilizar um handle em transações futuras, e é por isso que cada novo highestBid, secondHighestBid e highestBidder precisa ser reautorizado a cada bid (sem isso, a próxima chamada de bid não conseguiria operar sobre os agregados). A FHE.allow concede a um endereço específico o direito de decifrar um handle, usada para que o bidder possa inspecionar o próprio lance. E a FHE.allowPublic torna o handle decifrável por qualquer um por meio da TSN, reservada exclusivamente aos dois valores que o mecanismo de Vickrey precisa revelar. Essa gradação de escopos (contrato, endereço, público) é o que operacionaliza, no nível da plataforma, os objetivos de privacidade da §4.1.3.
4.3.6 Diagramas de Sequência
Os dois fluxos centrais do leilão, o lance e a liquidação, atravessam os quatro componentes da arquitetura em ordens bem definidas. Modelá-los como Diagramas de Sequência torna explícito quem fala com quem, em que ordem, e onde ocorre cada operação cifrada.
A figura 4 mostra o fluxo de um lance. O ponto essencial é que a cifração ocorre inteiramente no cliente, antes que qualquer dado deixe o navegador: o valor em texto claro do lance nunca trafega pela rede. O contrato recebe apenas a estrutura InEuint64 já cifrada, valida as pré-condições em texto claro (estado, prazo, colateral, anti-shill, lance duplo) e delega ao CoFHE tanto o cap homomórfico quanto a atualização top-2.
Figura 4. Diagrama de Sequência do fluxo de lance.
A figura 5 mostra o fluxo de liquidação, que materializa o padrão de duas etapas cliente-orquestrado da Fhenix descrito na §2.3.5. Após o prazo, requestSettlement marca os dois handles de interesse como publicamente decifráveis e emite os ctHashes. O cliente off-chain consulta a TSN, recebe os valores em texto claro acompanhados de provas, e os submete de volta via finalizeSettlement. O contrato então verifica as provas com FHE.verifyDecryptResult antes de aceitar qualquer valor. Os pagamentos não ocorrem nesse fluxo: cada participante os reivindica depois, via withdraw, conforme o modelo pull (§4.2.5).
Figura 5. Diagrama de Sequência do fluxo de liquidação.
4.3.7 Máquina de estados formal
A §4.2.6 já introduziu, em forma textual, a máquina de estados do leilão. Esta seção a apresenta de forma completa e formal, com a figura 6 e a tabela de transições correspondente. A máquina tem quatro estados (Active, SettlementRequested, Settled, Cancelled) e é determinística: cada transição é disparada por exatamente uma função externa, e cada função externa só transita o estado a partir de uma origem fixa, sob um guarda explícito.
Figura 6. Máquina de estados formal do leilão, com as guardas de transição.
A tabela 8 enumera as transições com suas guardas. As guardas são exatamente as pré-condições já especificadas na §4.3.4, reorganizadas aqui sob a ótica da transição de estado que cada uma protege.
| Origem | Transição | Guarda | Destino |
|---|---|---|---|
| (inicial) | createAuction() | parâmetros válidos (nome, prazos, colateral) | Active |
Active | requestSettlement() | block.timestamp ≥ endTime e totalBids ≥ MIN_BIDDERS | SettlementRequested |
Active | cancelAuction() | msg.sender == seller e totalBids == 0 | Cancelled |
SettlementRequested | finalizeSettlement() | provas válidas (verifyDecryptResult) e vencedor solvente | Settled |
Settled | (terminal) | nenhuma transição de saída | Settled |
Cancelled | (terminal) | nenhuma transição de saída | Cancelled |
Tabela 8: Transições da máquina de estados e suas guardas.
Duas propriedades dessa máquina interessam à análise do capítulo seguinte. A primeira é que não há transição de retorno: uma vez encerrada a fase ativa, não se pode voltar a aceitar lances, o que elimina a possibilidade de um adversário reabrir o leilão após observar o segundo preço revelado. A segunda é que o caminho de cancelamento é estritamente restrito a leilões vazios (totalBids == 0), o que impede que um seller descontente cancele um leilão já disputado para suprimir um resultado desfavorável. Ambas as propriedades reforçam, no nível da modelagem, as garantias de integridade que a §4.1 exige do mecanismo. A withdraw, por operar fora da máquina de estados principal, é discutida na §4.4 junto à implementação do modelo de pagamento.
A modelagem apresentada nesta seção fixa o contrato entre o que a solução promete e o que a implementação entrega. A §4.4 descreve como cada um desses elementos foi concretizado em Solidity e em TypeScript, e relata os resultados da suíte de testes que verifica a aderência da implementação a essa especificação.
4.4 Implementação
Esta seção relata como a especificação da §4.3 foi concretizada em código executável. Descreve as tecnologias e o ambiente de execução (§4.4.1), o contrato em Solidity (§4.4.2), a cifração no cliente e o frontend (§4.4.3), a implantação em testnet pública e a demonstração ponta-a-ponta (§4.4.4) e, por fim, a estrutura da suíte de testes que verifica a aderência da implementação à especificação (§4.4.5). As medições de custo e latência produzidas por essa implementação não são apresentadas aqui, mas no capítulo 5, dedicado à avaliação; esta seção trata de como o sistema foi construído, não de quanto ele custa.
4.4.1 Tecnologias e ambiente de execução
O projeto é organizado como um monorepo (repositório único que reúne, versionados em conjunto, vários pacotes de código relacionados) gerenciado por pnpm workspaces, com dois pacotes independentes. O pacote hardhat contém o contrato Solidity, os scripts de implantação e a suíte de testes; o pacote nextjs contém a interface gráfica web. Essa separação permite que o contrato seja desenvolvido, testado e implantado sem qualquer dependência do frontend, e vice-versa. Todo o código-fonte descrito nesta seção (contrato, frontend, scripts de implantação e suíte de testes) está disponível publicamente no repositório do projeto, em https://github.com/thiagorochatr/fhe-vickrey-auction, atendendo ao objetivo de reprodutibilidade por terceiros assumido pelo trabalho. A tabela 9 lista os componentes principais das tecnologias e suas versões.
| Categoria | Tecnologia | Versão |
|---|---|---|
| Contrato | Solidity2 (com evmVersion cancun) | 0.8.25 |
| Biblioteca FHE | @fhenixprotocol/cofhe-contracts3 | 0.1.3 |
| Cliente FHE (navegador) | @cofhe/sdk4 | 0.5.1 |
| Plugin e mock de testes | @cofhe/hardhat-plugin, @cofhe/mock-contracts5 | 0.5.1 |
| Framework de contrato | Hardhat6 | 2.22 |
| Biblioteca Ethereum (scripts) | ethers7 | 6 |
| Frontend | Next.js8 | 16 |
| Web3 (frontend) | wagmi9, viem10, RainbowKit11 | 2.x |
| Estado (frontend) | Zustand12 | 5 |
| Estilo | Tailwind CSS13, daisyUI14 | 3, 4 |
Tabela 9: Componentes principais das tecnologias de implementação.
A implementação suporta dois ambientes de execução, conforme antecipado na §2.3. O primeiro é o mock local, fornecido por @cofhe/mock-contracts através do @cofhe/hardhat-plugin. Nesse ambiente, as operações homomórficas são simuladas em memória, o que permite rodar a suíte de testes inteira sem rede e sem custo de gás real, e sem depender da disponibilidade da Threshold Services Network. O segundo é a testnet pública Arbitrum Sepolia, em que o CoFHE está ativo e a computação cifrada ocorre de fato no coprocessador. A disciplina de desenvolvimento adotada foi escrever e estabilizar os testes no mock primeiro, e só então promover o contrato à testnet, conforme detalhado na §4.4.4.
4.4.2 O contrato em Solidity
O contrato ConfidentialVickreyAuction.sol tem cerca de 450 linhas e materializa, em um único arquivo, toda a máquina de estados e as operações homomórficas especificadas na §4.3. A lógica de submissão de lance, que inclui o cap homomórfico e a atualização incremental do top-2, já foi exibida em código na §4.2.3 e na §4.2.5 e não é repetida aqui. Esta subseção concentra-se no trio de funções que conduz a fase finalística, ainda não mostrado: requestSettlement, finalizeSettlement e withdraw.
Antes de detalhar essas funções, convém explicitar a procedência do contrato, separando o que foi herdado do que é contribuição própria. O arcabouço de ciclo de vida e o padrão de liquidação em duas etapas, com as funções requestSettlement e finalizeSettlement, partem do proof-of-concept oficial da Fhenix (FHENIX, 2026c), que serviu de ponto de partida de engenharia. Esse padrão de duas etapas não é uma escolha estética, e sim o formato idiomático da decifração cliente-orquestrada no CoFHE, em que o contrato apenas marca os handles como publicamente decifráveis e o texto claro retorna verificado em uma segunda transação (§4.3.4); a coincidência de nomes de funções com o PoC decorre de refletirem o vocabulário natural de um leilão (criar, dar lance, solicitar liquidação, finalizar, cancelar), não de uma transposição de projeto. A técnica de máscara homomórfica para obter o segundo maior valor sobre lances cifrados, por sua vez, provém do VeilBid (FARUUKKU, 2026), conforme creditado na §4.2.2. A contribuição original deste trabalho concentra-se na lógica de Vickrey e no que a cerca: o cálculo do segundo preço por um padrão top-2 incremental de custo constante, materializado no campo secondHighestBid atualizado a cada lance e ausente do PoC, que é de primeiro preço; o cap homomórfico do lance ao colateral (§4.2.3); o modelo de colateral uniforme em ETH nativo, em lugar do modelo baseado em NFT e token confidencial adotado pelo PoC; e o tratamento determinístico de empates por ordem de chegada, obtido gratuitamente da desigualdade estrita do FHE.gt, sem a flag de exclusão acumulada de que o VeilBid depende (§4.2.4). A esse núcleo somam-se os outros dois pilares do trabalho, alheios a ambas as fontes: a avaliação empírica de custo, no capítulo 5, e a análise formal de vazamento sob modelo de adversário explícito, na §4.1 e no capítulo 5.
A função requestSettlement encerra a fase ativa e prepara a revelação. Após validar as guardas de estado, prazo e quórum mínimo, ela marca os dois handles de interesse como publicamente decifráveis e extrai seus ciphertext hashes para que o cliente saiba o que submeter à TSN (Listagem 4):
function requestSettlement(uint256 auctionId) external {
Auction storage auction = auctions[auctionId];
if (auction.status != Status.Active) revert AuctionNotActive();
if (block.timestamp < auction.endTime) revert AuctionNotEnded();
if (auction.totalBids < MIN_BIDDERS) revert InsufficientBidders();
auction.status = Status.SettlementRequested;
FHE.allowPublic(auction.highestBidder);
FHE.allowPublic(auction.secondHighestBid);
bytes32 winnerCt = eaddress.unwrap(auction.highestBidder);
bytes32 secondPriceCt = euint64.unwrap(auction.secondHighestBid);
auction.winnerCtHash = winnerCt;
auction.secondPriceCtHash = secondPriceCt;
emit SettlementRequested(auctionId, winnerCt, secondPriceCt);
}Listagem 4: Função requestSettlement: marcação dos handles como publicamente decifráveis.
A função finalizeSettlement é o ponto em que a confiança no cliente off-chain é neutralizada, materializando o RF-09 e o RNF-03. Ela recebe os valores em texto claro (vencedor e segundo preço) e as provas emitidas pela TSN, e só os aceita se FHE.verifyDecryptResult confirmar, na própria blockchain, que cada valor corresponde ao ciphertext original. Um cliente malicioso que tentasse injetar um vencedor ou um preço forjado seria barrado, porque não disporia de uma prova válida da TSN para o valor forjado (Listagem 5):
function finalizeSettlement(
uint256 auctionId,
address winner,
uint64 secondPrice,
bytes calldata winnerProof,
bytes calldata secondPriceProof
) external {
Auction storage auction = auctions[auctionId];
if (auction.status != Status.SettlementRequested)
revert SettlementNotRequested();
if (!FHE.verifyDecryptResult(auction.highestBidder, winner, winnerProof))
revert InvalidDecryptionProof();
if (!FHE.verifyDecryptResult(auction.secondHighestBid, secondPrice, secondPriceProof))
revert InvalidDecryptionProof();
if (uint256(secondPrice) > collateral[auctionId][winner])
revert WinnerCannotPay();
auction.decryptedWinner = winner;
auction.decryptedSecondPrice = secondPrice;
auction.status = Status.Settled;
emit AuctionSettled(auctionId, winner, secondPrice);
}Listagem 5: Função finalizeSettlement: verificação das provas da TSN e registro do resultado.
A verificação WinnerCannotPay é a salvaguarda complementar ao cap homomórfico discutido na §4.2.5: ela garante que o segundo preço a ser cobrado do vencedor não exceda o colateral que ele depositou, abortando a finalização no caso patológico em que o teto público tenha sido de algum modo violado.
A função withdraw implementa o modelo pull (RF-10). Em vez de empurrar pagamentos durante a finalização, o contrato deixa que cada participante saque ativamente o valor a que tem direito, calculado segundo o seu papel no leilão (Listagem 6):
if (auction.status == Status.Cancelled) {
if (!hasBid[auctionId][msg.sender]) revert NotEligible();
amount = collateral[auctionId][msg.sender];
} else {
// Settled
if (msg.sender == auction.seller) {
amount = auction.decryptedSecondPrice;
} else if (msg.sender == auction.decryptedWinner) {
amount = collateral[auctionId][msg.sender] - auction.decryptedSecondPrice;
} else if (hasBid[auctionId][msg.sender]) {
amount = collateral[auctionId][msg.sender];
} else {
revert NotEligible();
}
}Listagem 6: Função withdraw: saque por papel, no modelo pull.
Os três ramos do caso Settled traduzem diretamente a regra de Vickrey: o seller recebe o segundo preço, o vencedor recupera a diferença entre o colateral depositado e o segundo preço que efetivamente paga, e cada perdedor recupera o colateral integral. A flag hasWithdrawn (omitida no trecho) impede o saque duplo, e a transferência segue o padrão checks-effects-interactions (verificar as condições, aplicar os efeitos no estado e só então interagir com contas externas, ordem que evita a reentrância), com a marcação de estado feita antes da chamada externa.
4.4.3 Cifração no cliente e frontend
O frontend possui uma aplicação Next.js cuja responsabilidade criptográfica central é cifrar o lance no navegador, de modo que o valor em texto claro nunca deixe a máquina do usuário. Essa cifração é executada por um módulo em WebAssembly (WASM), escolha motivada pelo custo computacional das operações de cifração e geração de prova do TFHE, proibitivo em JavaScript puro. A integração com o coprocessador é feita pela biblioteca @cofhe/sdk, instanciada uma única vez como cliente singleton. A configuração declara as redes suportadas e cria o cliente (Listagem 7):
const config = createCofheConfig({
supportedChains: [chains.arbSepolia, chains.baseSepolia, chains.sepolia],
});
export const cofheClient = createCofheClient(config);Listagem 7: Configuração e criação do cliente CoFHE na interface gráfica.
A inicialização segue um fluxo de três passos (configuração, criação do cliente e conexão), em que a etapa de conexão associa o cliente à carteira e ao provedor da rede via cofheClient.connect(publicClient, walletClient). Esse desenho substitui a antiga chamada única cofhejs.initializeWithViem das versões anteriores do SDK, e é encapsulado no hook useCofhe, que reage à conexão da carteira.
A submissão de um lance ocorre no hook useVickreyAuction. A cifração é uma chamada encadeada que aceita um callback de progresso (usado para alimentar a barra de etapas da interface) e devolve a entrada cifrada, que é então passada diretamente como argumento da função bid do contrato (Listagem 8):
const [encryptedBid] = await cofheClient
.encryptInputs([Encryptable.uint64(amount)])
.onStep((step) => opts?.onEncryptStep?.(step))
.execute();
await walletClient.writeContract({
address: contract(),
abi: vickreyAuctionAbi,
functionName: "bid",
args: [auctionId, encryptedBid],
value: collateral,
});Listagem 8: Cifração do lance no cliente via encryptInputs.
A revelação do resultado é orquestrada pelo cliente, conforme o padrão de duas etapas da Fhenix descrito na §2.3.5 e diagramado na figura 5. Após requestSettlement, o cliente busca os ciphertext hashes on-chain, solicita à TSN a decifração de cada um, e submete os valores em texto claro de volta ao contrato. As assinaturas devolvidas pela TSN (winnerRes.signature e secondRes.signature) são exatamente as provas que finalizeSettlement verifica via FHE.verifyDecryptResult (Listagem 9):
const cts = await getSettlementCtHashes(auctionId);
const winnerRes = await cofheClient.decryptForTx(cts.winnerCt).withoutPermit().execute();
const secondRes = await cofheClient.decryptForTx(cts.secondPriceCt).withoutPermit().execute();
await walletClient.writeContract({
functionName: "finalizeSettlement",
args: [auctionId, winnerAddr, secondRes.decryptedValue,
winnerRes.signature, secondRes.signature],
});Listagem 9: Consulta à TSN e obtenção das provas de decifração para a liquidação.
Um detalhe de modelagem aparece aqui: como o vencedor é um eaddress, a TSN o devolve como um inteiro de 160 bits, que o cliente reconverte para o formato de endereço hexadecimal (prefixado por 0x) antes de submeter. A autorização de decifração de valores privados ao próprio dono (por exemplo, um bidder consultar o próprio lance) é tratada por permits, encapsulados no hook usePermit; já a revelação do vencedor e do segundo preço dispensa permit (withoutPermit), pois esses dois handles foram tornados públicos pelo contrato.
A interface é composta por um conjunto de componentes React organizados em torno de três telas principais. A tela de lista (AuctionList, com AuctionCard) apresenta os leilões existentes e seus estados; a tela de criação (CreateAuctionForm) permite ao seller instanciar um novo leilão; e a tela de detalhe (AuctionDetail) concentra a submissão de lance, a solicitação e a finalização do settlement. A conexão de carteira é feita via RainbowKit, e o estado de interface é mantido em stores Zustand. As figuras 7 a 10 ilustram essas telas.
Figura 7. Tela de lista de leilões.
Figura 8. Formulário de criação de leilão.
O campo de identificador do item é o ponto em que o caso de uso de RWA, discutido ao longo do trabalho, se materializa na interface: em uma instância real, ele referenciaria o token do ativo do mundo real sendo leiloado (por exemplo, uma fração de recebíveis tokenizados); no protótipo, é um identificador mock, coerente com o escopo assumido de que os tokens de RWA são simulados. O campo de nome reforça essa leitura, sugerindo rótulos como o de um lote de recebíveis.
Figura 9. Tela de submissão de um lance.
Dois componentes merecem destaque por tornarem visível, para o usuário, a propriedade de privacidade do sistema. O StepTimeline exibe as fases de cifração (na submissão do lance) e de decifração via TSN (na finalização), tornando explícito ao usuário o que está acontecendo off-chain a cada momento. O PrivacyPanel exibe os ciphertext hashes dos handles do leilão e deixa evidente o que está cifrado e o que é público em cada estado, funcionando como uma materialização visual da tabela de vazamento por estado apresentada na §4.1.5.
Figura 10. Painel de resultado (PrivacyPanel) no estado Settled.
4.4.4 Implantação e demonstração ponta-a-ponta
A implantação é automatizada por um script hardhat-deploy. O contrato não recebe argumentos de construtor, pois cada leilão é uma entrada no mapeamento interno, criada em tempo de execução por createAuction (Listagem 10):
const vickrey = await deploy("ConfidentialVickreyAuction", {
from: deployer,
args: [],
log: true,
autoMine: true,
});Listagem 10: Script de implantação do contrato via hardhat-deploy.
O contrato foi implantado na testnet pública Arbitrum Sepolia (chainId 421614), no endereço 0x1eEa76147cBCD878D1cb5B8fdCb6bd0Ed836D811, com um custo de implantação de aproximadamente 1,90 milhão de unidades de gás. Sobre essa implantação, a validação ponta-a-ponta ocorreu em dois níveis. Pela interface gráfica, foram executados leilões reais de demonstração percorrendo todo o ciclo (criação, submissão de lances cifrados, encerramento do prazo, requestSettlement, consulta à TSN pelo cliente, finalizeSettlement com verificação on-chain das provas e saque de cada participante via withdraw) com um seller e três licitantes, exatamente o mínimo exigido pelo contrato (MIN_BIDDERS = 3), e também com quatro licitantes em execução posterior. Na demonstração com três licitantes, a soma dos fundos ao final foi conferida e fechou: o total sacado (segundo preço para o seller, troco para o vencedor, colateral integral para os perdedores) igualou o total depositado. Pelo script de benchmark descrito no capítulo 5, o mesmo ciclo completo foi exercitado nas populações definidas no escopo, com 3, 5 e 10 licitantes e cinco repetições por configuração.
Foi durante os smoke tests (testes rápidos que verificam se as funcionalidades essenciais do sistema operam, antes de baterias de teste mais completas) nessa testnet que emergiram duas das decisões de design defensivas descritas na §4.2.5: a proibição de o seller participar do próprio leilão e o cap homomórfico do lance ao colateral. Ambas resolveram falhas observadas apenas em condições reais de rede. A análise detalhada do incidente que motivou a primeira é retomada no capítulo 5, como evidência do valor de testar em testnet pública e não apenas no mock.
4.4.5 Estrutura de testes
A suíte de testes é executada sobre o mock CoFHE e contém vinte testes, organizados em cinco blocos que cobrem o contrato de ponta a ponta. A tabela 10 mapeia cada teste ao cenário crítico que ele verifica, demonstrando a cobertura dos casos exigidos pelo escopo do trabalho (lance válido e inválido, encerramento antes do prazo, lance duplo, cálculo correto de vencedor e segundo preço, tratamento de empate e recuperação de perdedores). Em termos de cobertura de código, medida com a ferramenta solidity-coverage, a suíte exercita cerca de 85% das linhas e dos comandos do contrato ConfidentialVickreyAuction.sol (70% de suas funções e 59% de seus ramos); os trechos não cobertos concentram-se em ramos de exceção defensivos e em funções de consulta auxiliares.
| Bloco | Teste | Cenário verificado |
|---|---|---|
createAuction | cria leilão em Active com os parâmetros dados | criação válida |
createAuction | reverte com nome vazio | validação de nome |
createAuction | reverte com colateral zero | validação de colateral |
createAuction | aceita startTime no passado | leilão de início imediato |
createAuction | reverte com endTime já no passado | validação de prazo |
bid | aceita lance com o colateral exato | lance válido |
bid | rejeita lance com colateral errado | colateral incorreto |
bid | rejeita segundo lance do mesmo endereço | lance duplo |
bid | rejeita lance após o prazo | encerramento por prazo |
bid | rejeita lance do seller | anti-shill |
requestSettlement | rejeita pedido antes do prazo | guarda de prazo |
requestSettlement | rejeita com menos de MIN_BIDDERS | quórum mínimo |
| fluxo Vickrey | 3 bidders distintos: paga o segundo preço | cálculo correto (N=3) |
| fluxo Vickrey | 5 bidders distintos: paga o segundo preço | cálculo correto (N=5) |
| fluxo Vickrey | empate no topo: vence o primeiro, paga o empate | empate no topo |
| fluxo Vickrey | empate triplo no topo | empate triplo |
| fluxo Vickrey | trunca lance acima do colateral | cap homomórfico |
| fluxo Vickrey | empate no segundo lugar: paga o segundo valor | empate no segundo lugar |
withdraw | perdedor saca colateral, vencedor saca troco, seller saca segundo preço | recuperação por papel |
withdraw | rejeita saque duplo | saque duplo |
Tabela 10: Mapeamento dos vinte testes aos cenários verificados.
Os testes apoiam-se em um módulo de helpers (vickreySetup) que encapsula as operações repetitivas (implantação, criação de leilão, submissão de lance cifrado via Encryptable.uint64, avanço do relógio para além do prazo, e o ciclo de settlement), o que mantém cada teste legível e focado na propriedade que verifica. O teste do fluxo com três participantes ilustra o padrão: três lances de 100, 300 e 200 produzem o vencedor de lance 300 pagando o segundo preço de 200 (Listagem 11).
await placeBid(env, auctionId, env.bidders[0], 100n);
await placeBid(env, auctionId, env.bidders[1], 300n);
await placeBid(env, auctionId, env.bidders[2], 200n);
await advancePastDeadline(env, auctionId);
await requestSettlement(env, auctionId);
const { winner, secondPrice } = await finalizeSettlement(env, auctionId);
expect(winner.toLowerCase()).to.equal(env.bidders[1].address.toLowerCase());
expect(secondPrice).to.equal(200n);Listagem 11: Teste do fluxo de três lances com o vencedor pagando o segundo preço.
Os dois casos de empate citados na §4.2.4, triple tie at the top (empate triplo no topo) e tie at the second place (empate no segundo lugar), são exatamente os que verificam empiricamente o comportamento determinístico do algoritmo top-2 incremental sob empates, confirmando que a estrita desigualdade do FHE.gt resolve o desempate por ordem de chegada sem estrutura adicional. A capacidade de rodar todos esses cenários no mock, sem custo e sem rede, foi o que permitiu iterar com rapidez sobre a lógica homomórfica antes de cada promoção à testnet.
4.5 Garantias obtidas pela arquitetura
Como fechamento do capítulo, a tabela 11 consolida as garantias que a arquitetura oferece e o mecanismo que sustenta cada uma, com remissão à seção em que cada mecanismo foi descrito. A tabela serve de ponte para o capítulo 5: as garantias qualitativas aqui resumidas são exatamente o objeto da avaliação empírica que segue.
| Garantia | Como é satisfeita |
|---|---|
| Sigilo permanente dos lances | Lances submetidos já cifrados no cliente e nunca armazenados individualmente: o contrato mantém apenas os agregados do padrão top-2 (§4.2.3), e nenhum lance perdedor é jamais marcado como decifrável (§4.1.5). |
| Truthfulness | Regra do segundo preço (§2.5.5) operando sobre lances efetivamente selados durante toda a disputa; condicional ao pressuposto v ≤ C imposto pelo cap homomórfico (§4.2.5, §5.6). |
| Verificabilidade do resultado | O contrato só aceita o resultado acompanhado das provas coletivas da TSN, validadas on-chain por FHE.verifyDecryptResult (§4.2.6); o cliente que publica não consegue forjar valores. |
| Resistência ao frontrunning de conteúdo | O valor do lance nunca transita em claro: a cifração ocorre no cliente antes do envio, e o mempool expõe apenas o ciphertext e a prova (§4.4.3); observar ou copiar a transação não revela o valor. Censura e reordenação permanecem possíveis (§5.5). |
| Integridade de fundos e de estados | Máquina de estados com guardas explícitas (§4.2.6); cap homomórfico garante vencedor solvente (§4.2.5); pagamentos por pull pattern com checks-effects-interactions contra reentrância (§4.2.5); soma de fundos conferida na demonstração (§4.4.4). |
| Liquidação sem depender do vencedor | requestSettlement e finalizeSettlement são permissionless: qualquer participante pode encerrar e finalizar o leilão após o prazo (§4.3.4). |
Tabela 11: Garantias oferecidas pela arquitetura e mecanismos que as sustentam.
Com a implementação descrita e verificada, o capítulo seguinte avalia o sistema sob os três eixos definidos para o trabalho: custo computacional, latência e privacidade, e discute os trade-offs e as limitações observadas.
Notas
-
Ataque em que o destinatário de uma transferência, ao receber o controle, chama o contrato de volta antes de o estado ter sido atualizado, podendo sacar repetidamente. ↩
-
https://www.npmjs.com/package/@fhenixprotocol/cofhe-contracts ↩