Com a fase 2 da mordida, cada bloco pode incluir transações com contrato (CATs)-transações iniciadas pela execução de contratos inteligentes no bloco anterior.
Os gatos permitem contratos inteligentes para descriptografar dados e executar ações automaticamente nesses dados.
Principais benefícios do uso da fase 2 da mordida
- Automação: Os contratos atuam em dados descriptografados automaticamente, sem exigir outra transação do usuário.
- Eficiência: A descriptografia é feita no mesmo lote da fase 1 da mordida, portanto, nenhuma sobrecarga extra de desempenho.
- Determinismo: A execução acontece em uma ordem previsível (os gatos são executados antes das transações regulares no bloco N+1).
-
Um sc em bloco N chamadas descriptografarexecute pré -compilado passando um EncrypteDaruments Array e um PlainTexTarguments Array de argumentos de texto simples.
-
Uma transação de gato é adicionada ao próximo bloco. As transações CAT são colocadas na frente de transações regulares no bloco. Eles não estão sujeitos a bloquear o limite de gás.
-
As transações de CAT têm o mesmo msg.sender como a transação que os criou.
-
Transação de gato para O campo é o SC que o originou. O SC envia uma transação para si mesma.
-
Transação de gato sempre chama OnDecrypt função do SC que os originou.
-
As transações de CAT são descriptografadas durante o mesmo lote descriptografando que a transação da fase 1 da mordida, durante a finalização do bloco N. Portanto, a fase 2 da mordida não muda o desempenho em comparação com a fase 1 da mordida.
Esta função cria uma transação de gato
/**
Create a CAT transaction that will be decrypted and executed in the next block
* @notice Decrypts the provided encrypted arguments and executes the associated logic using both decrypted and plaintext arguments in the next block
* @param encryptedArguments An array of encrypted byte arrays representing the arguments that need to be decrypted before execution.
* @param plaintextArguments An array of byte arrays representing the arguments that are already in plaintext and do not require decryption.
*/
function decryptAndExecute(
bytes() calldata encryptedArguments,
bytes() calldata plaintextArguments
) external;
Custo de gás pré -compilado
Para ser definido mais tarde
Se um contrato inteligente define um ONDECRYPT () função, ele pode iniciar a descriptografia no bloco Ne os resultados de descriptografia são passados para ONDECRYPT () em bloco N+1.
/**
Execute SC call on decrypted arguments
@param decryptedArguments An array of decrypted byte arrays representing the encrypted arguments that were passed to decryptAndExecute
* @param plaintextArguments An array of byte arrays representing the arguments that are already in plaintext and do not require decryption.
*/
function onDecrypt(
bytes() calldata decryptedArguments,
bytes() calldata plaintextArguments
) external;
Cada argumento criptografado terá formato RLP semelhante ao campo de dados criptografado da fase 1, mas incluirá um adicional PermitidoDecryPtoraddress parâmetro, especificando o endereço do contrato inteligente que pode descriptografar este argumento
O exemplo abaixo usa o Bite Protocol Fase 2 para implementar jogos de paper-tesouros para dois jogadores, onde o contrato inteligente coleta movimentos criptografados de dois jogadores e depois descriptografa ambos ao mesmo tempo
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/**
* Minimal interface to the Phase 2 precompile (void return).
* Replace PRECOMPILE_ADDR with the actual address on your network.
*/
interface IBitePhase2 {
/**
* @notice Creates a CAT that will decrypt args and call onDecrypt in the next block.
* @param encryptedArguments Encrypted arguments, decrypted during finalization of the current block.
* @param plaintextArguments Plaintext arguments, passed through as-is.
*/
function decryptAndExecute(
bytes() calldata encryptedArguments,
bytes() calldata plaintextArguments
) external;
}
contract RockPaperScissors {
// -------------------- Config --------------------
address constant PRECOMPILE_ADDR = 0x0000000000000000000000000000000000000100;
IBitePhase2 constant BITE = IBitePhase2(PRECOMPILE_ADDR);
enum Move {
None, // 0
Rock, // 1
Paper, // 2
Scissors // 3
}
// -------------------- Events --------------------
event GameCreated(uint256 indexed gameId, address indexed p1, address indexed p2);
event EncryptedMoveSubmitted(uint256 indexed gameId, address indexed player);
event WinnerDecided(
uint256 indexed gameId,
address winner, // address(0) means draw
Move p1Move,
Move p2Move
);
// -------------------- Storage --------------------
struct Game {
address p1;
address p2;
bytes encMove1; // encrypted Move for p1
bytes encMove2; // encrypted Move for p2
bool p1Submitted;
bool p2Submitted;
// Controls to ensure the CAT callback is expected
bool pendingCat;
address expectedCaller; // msg.sender that scheduled decryptAndExecute
bool finished;
}
uint256 public nextGameId;
mapping(uint256 => Game) public games;
// -------------------- Game Flow --------------------
function createGame(address opponent) external returns (uint256 gameId) {
require(opponent != address(0) && opponent != msg.sender, "bad opponent");
gameId = nextGameId++;
games(gameId).p1 = msg.sender;
games(gameId).p2 = opponent;
emit GameCreated(gameId, msg.sender, opponent);
}
/**
* @notice Each player submits their encrypted move (opaque bytes).
* The second submission triggers decryptAndExecute in the same tx.
*
* Expected decryption: each encrypted blob decrypts to a single byte 1..3 (Move enum).
*/
function submitEncryptedMove(uint256 gameId, bytes calldata encMove) external {
Game storage g = games(gameId);
require(!g.finished, "game finished");
require(msg.sender == g.p1 || msg.sender == g.p2, "not a player");
if (msg.sender == g.p1) {
require(!g.p1Submitted, "p1 already submitted");
g.encMove1 = encMove;
g.p1Submitted = true;
} else {
require(!g.p2Submitted, "p2 already submitted");
g.encMove2 = encMove;
g.p2Submitted = true;
}
emit EncryptedMoveSubmitted(gameId, msg.sender);
// If both moves are in and we haven't scheduled a CAT yet, schedule it now.
if (g.p1Submitted && g.p2Submitted && !g.pendingCat) {
g.pendingCat = true;
g.expectedCaller = msg.sender; // per spec, CAT msg.sender == caller of decryptAndExecute
// encryptedArguments: both encrypted moves
bytes;
encArgs(0) = g.encMove1;
encArgs(1) = g.encMove2;
// plaintextArguments: pass identifiers to reconstruct context in onDecrypt
// - gameId
// - p1, p2
bytes;
plain(0) = abi.encode(gameId);
plain(1) = abi.encode(g.p1);
plain(2) = abi.encode(g.p2);
// Schedule CAT; no return value
BITE.decryptAndExecute(encArgs, plain);
}
}
/**
* @notice CAT callback (executed in Block N+1). Receives decrypted moves and our plaintext context.
* Security notes for MVP:
* - We gate by `pendingCat` and by `expectedCaller` (the account that scheduled the CAT).
* - In production, consider adding a CAT nonce or blockTag in plaintext args for stronger domain separation.
*/
function onDecrypt(
bytes() calldata decryptedArguments, // ( p1MoveDecrypted, p2MoveDecrypted )
bytes() calldata plaintextArguments // ( gameId, p1, p2 )
) external {
// Decode context
require(plaintextArguments.length == 3, "bad plaintext len");
(uint256 gameId) = abi.decode(plaintextArguments(0), (uint256));
(address p1) = abi.decode(plaintextArguments(1), (address));
(address p2) = abi.decode(plaintextArguments(2), (address));
Game storage g = games(gameId);
require(!g.finished, "already finished");
require(g.pendingCat, "no pending CAT");
require(msg.sender == g.expectedCaller, "unexpected caller (not CAT origin)");
// Decode decrypted moves (each is expected to be a single byte 1..3)
require(decryptedArguments.length == 2, "bad decrypted len");
Move p1Move = _asMove(decryptedArguments(0));
Move p2Move = _asMove(decryptedArguments(1));
// Decide winner
address winner = _winnerOf(p1, p2, p1Move, p2Move);
// Mark finished and clear flags
g.finished = true;
g.pendingCat = false;
g.expectedCaller = address(0);
emit WinnerDecided(gameId, winner, p1Move, p2Move);
}
// -------------------- Helpers --------------------
function _asMove(bytes calldata b) private pure returns (Move) {
require(b.length == 1, "bad move len");
uint8 v = uint8(b(0));
require(v >= uint8(Move.Rock) && v <= uint8(Move.Scissors), "bad move value");
return Move(v);
}
function _winnerOf(
address p1,
address p2,
Move m1,
Move m2
) private pure returns (address) {
if (m1 == m2) return address(0);
// Rock(1) beats Scissors(3), Paper(2) beats Rock(1), Scissors(3) beats Paper(2)
if (
(m1 == Move.Rock && m2 == Move.Scissors) ||
(m1 == Move.Paper && m2 == Move.Rock) ||
(m1 == Move.Scissors && m2 == Move.Paper)
) {
return p1;
} else {
return p2;
}
}
}
Este contrato demonstra como Fase 2 de mordida Ativa contratos inteligentes para descriptografar dados e agir automaticamente via Transações de contrato (gatos).
O exemplo implementa um simples Jogo de paper-tesores de dois jogadores onde cada jogador envia um movimento criptografadoe uma vez que os dois movimentos são enviados, o contrato agenda automaticamente um Transação de gato Descriptografar os movimentos e determinar o vencedor.
Fluxo de jogo
1. Criação de jogos
- Um jogador liga
createGame(opponent)
Para configurar um novo jogo. - As lojas de contrato:
p1
(criador),p2
(adversário),- e atribui a
gameId
.
- Emite Gamecreated evento.
2. Enviar movimentos criptografados
- Cada jogador liga
submitEncryptedMove(gameId, encMove)
com eles movimento criptografado. - Os movimentos são armazenados no contrato:
encMove1
Para o jogador 1,encMove2
para o jogador 2.
- Emite EncryptedMoveSubmitt evento.
3. AGENDAR DESCRIPTIÇÃO DE CAT
- Uma vez Ambos os movimentos são enviados:
- Isso cria um Transação de gato isso vai:
- Correr no Próximo blocoAssim,
- Chamar
onDecrypt(decryptedMoves, plaintextArgs)
.
IMPORTANTE: As transações de CAT não são submitidas pelo usuário. Eles são inseridos automaticamente no próximo bloco pelo protocolo, antes das transações regulares.
4. Execução de gatos: onDecrypt
- No Próximo blocoo tempo de execução:
- Descriptografa os movimentos durante a finalização do bloco,
- Invoca o contrato
onDecrypt
ligar de volta:function onDecrypt( bytes() calldata decryptedArguments, // (p1Move, p2Move) bytes() calldata plaintextArguments // (gameId, p1, p2) ) external;
- O contrato:
- Parses se move de
decryptedArguments
Assim, - Reconstrua o contexto (
gameId
jogadores) deplaintextArguments
Assim, - Determina o vencedor usando regras de papel-paper-tesors,
- Marca o jogo como acabado,
- Emite Vencedor decidido evento.
- Parses se move de
Controles de segurança
-
Bandeira de gato pendente
- O contrato rastreia
pendingCat = true
Ao agendar um gato. - Impede a programação duplicada e garante que apenas um gato seja esperado.
- O contrato rastreia
-
Verificação do chamador
- Garante que o gato
msg.sender
corresponde ao chamador original dedecryptAndExecute
. - Evita chamadas externas não autorizadas para
onDecrypt
.
- Garante que o gato
-
Estado de jogo
finished
A bandeira garante que um jogo não possa ser reproduzido depois que um vencedor é decidido.
Eventos
-
GameCreated(gameId, p1, p2)
→ Emitido quando um novo jogo é inicializado. -
EncryptedMoveSubmitted(gameId, player)
→ Emitido quando um jogador envia seu movimento criptografado. -
WinnerDecided(gameId, winner, p1Move, p2Move)
→ emitido quando a transação CAT é executada e o vencedor é determinado.
Exemplo de sequência
-
Bloco n
- O jogador 1 envia um movimento criptografado.
- O jogador 2 envia um movimento criptografado.
- Chamadas contratadas
decryptAndExecute
agendar um gato.
-
Bloco n+1
- Durante a finalização, os movimentos criptografados são descriptografados.
- CAT executa
onDecrypt
passando(p1Move, p2Move)
e contexto(gameId, p1, p2)
. - Contrato decide o vencedor e emite Vencedor decidido.
Este exemplo demonstra como implementar um Leilão de Bid-Bid de primeiro preço usando Fase 2 de mordida.
- Licitantes envie o seu lances criptografados junto com um ETH depósito.
- Quando o período de licitação termina, o contrato agenda um Transação de contrato (CAT) que descriptografa todas as ofertas no Próximo bloco.
- O contrato
onDecrypt
retorno de chamada então determina o mais alto lancefinaliza o leilão e transfere os fundos.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
interface IBitePhase2 {
function decryptAndExecute(
bytes() calldata encryptedArguments,
bytes() calldata plaintextArguments
) external;
}
contract SealedBidAuction {
// -------------------- Config --------------------
address constant PRECOMPILE_ADDR = 0x0000000000000000000000000000000000000100;
IBitePhase2 constant BITE = IBitePhase2(PRECOMPILE_ADDR);
address public seller;
uint256 public biddingDeadline;
bool public finalized;
// -------------------- Storage --------------------
struct Bid {
address bidder;
bytes encBid; // encrypted bid (decrypted later)
uint256 deposit; // deposit in ETH
}
Bid() public bids;
bool public pendingCat;
address public expectedCaller;
// -------------------- Events --------------------
event BidSubmitted(address indexed bidder, uint256 deposit);
event AuctionFinalized(address winner, uint256 amount);
// -------------------- Init --------------------
constructor(uint256 _biddingPeriod) {
seller = msg.sender;
biddingDeadline = block.timestamp + _biddingPeriod;
}
// -------------------- Bidding --------------------
function submitEncryptedBid(bytes calldata encBid) external payable {
require(block.timestamp < biddingDeadline, "bidding closed");
require(msg.value > 0, "deposit required");
bids.push(Bid({
bidder: msg.sender,
encBid: encBid,
deposit: msg.value
}));
emit BidSubmitted(msg.sender, msg.value);
}
// -------------------- Close auction --------------------
function closeAuction() external {
require(block.timestamp >= biddingDeadline, "still open");
require(!pendingCat && !finalized, "already scheduled/finalized");
// Build arrays for CAT call
bytes() memory encArgs = new bytes()(bids.length);
bytes ; // auction context: total bids
for (uint256 i = 0; i < bids.length; i++) {
encArgs(i) = bids(i).encBid;
}
plainArgs(0) = abi.encode(bids.length);
pendingCat = true;
expectedCaller = msg.sender;
// Schedule CAT to decrypt all bids in the next block
BITE.decryptAndExecute(encArgs, plainArgs);
}
// -------------------- CAT callback --------------------
function onDecrypt(
bytes() calldata decryptedArguments, // decrypted bid values
bytes() calldata plaintextArguments // (numBids)
) external {
require(pendingCat && !finalized, "no pending auction");
require(msg.sender == expectedCaller, "unexpected caller");
uint256 numBids = abi.decode(plaintextArguments(0), (uint256));
require(numBids == bids.length, "mismatch");
// Find highest bid
uint256 highestAmount = 0;
uint256 winnerIndex = type(uint256).max;
for (uint256 i = 0; i < numBids; i++) {
uint256 amount = abi.decode(decryptedArguments(i), (uint256));
if (amount > highestAmount && bids(i).deposit >= amount) {
highestAmount = amount;
winnerIndex = i;
}
}
// Finalize auction
finalized = true;
pendingCat = false;
expectedCaller = address(0);
if (winnerIndex != type(uint256).max) {
// Pay seller
payable(seller).transfer(highestAmount);
// Refund losers + excess deposit
for (uint256 i = 0; i < numBids; i++) {
if (i == winnerIndex) {
uint256 refund = bids(i).deposit - highestAmount;
if (refund > 0) payable(bids(i).bidder).transfer(refund);
} else {
payable(bids(i).bidder).transfer(bids(i).deposit);
}
}
emit AuctionFinalized(bids(winnerIndex).bidder, highestAmount);
} else {
// No valid bids, refund everyone
for (uint256 i = 0; i < numBids; i++) {
payable(bids(i).bidder).transfer(bids(i).deposit);
}
emit AuctionFinalized(address(0), 0);
}
}
}
Fluxo de leilão
Fase de licitação
- Os usuários ligam
submitEncryptedBid(encBid)
com sua oferta criptografada e depósito de ETH. - Os depósitos garantem que os licitantes não possam sub -financiar sua oferta.
Fase de fechamento
Fontesethresear