BITE 第 2 階段 MVP 規範
在 BITE 第 2 階段中,每個區塊可以包含合約行動交易 (CAT)——由前一個區塊中的智能合約執行發起的交易。
CAT 使智能合約能夠Decrypt數據,然後對這些數據自動執行操作。
使用 BITE 第 2 階段的主要優勢
- 自動化:合約自動對解密數據進行操作,無需其他用戶交易。
- 效率:解密與 BITE 第一階段同一批完成,因此沒有額外的性能開銷。
- 確定性:執行以可預測的順序進行(CAT 在區塊 N+1 中的常規交易之前運行)。
合約-行動-交易
塊N N中的A SC調用cryptoAndExecute預編譯,傳遞一個encryptedAruments數組和一個plaintextArguments明文參數數組。
CAT 交易被添加到下一個區塊。CAT 交易在The Block中位於常規交易之前。它們不受區塊 gas 限制。
CAT 交易具有與創建它們的交易相同的msg.sender m s g . s end e r 。
CAT 交易到t o字段是發起該交易的 SC。SC 向自己發送交易。
CAT 交易總是調用發起它們的SC的onDecrypt函數。
CAT 交易與 BITE 第一階段交易在同一批次Decrypt中進行解密,即在區塊N N最終確定期間。因此,BITE 第二階段的性能與 BITE 第一階段相比沒有變化。
解密並執行預編譯
此函數創建 CAT 交易
/**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;
預編譯 gas 成本
稍後定義
onDecrypt 調用
如果智能合約定義了onDecrypt() onD e c r y p t ( )函數,則可以在Block N N中發起解密,並將解密結果傳遞給Block N+1 N + 1中的onDecrypt( ) onD e c r y p t ( ) 。
/**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;
加密參數規範
每個加密參數都將具有與 BITE 第 1 階段加密數據字段類似的RLP格式,但將包含一個額外的allowDecryptorAddress允許解密地址參數,指定允許解密此參數的智能合約的Decrypt
石頭剪刀布的例子。
下面的示例使用 BITE 協議第 2 階段為兩個玩家實現石頭剪刀布遊戲,其中智能合約從兩個玩家收集加密的動作,然後同時解密
// SPDX-License-Identifier: MITpragma 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, // 0Rock, // 1Paper, // 2Scissors // 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 drawMove p1Move,Move p2Move);// -------------------- Storage --------------------struct Game {address p1;address p2;bytes encMove1; // encrypted Move for p1bytes encMove2; // encrypted Move for p2bool p1Submitted;bool p2Submitted;// Controls to ensure the CAT callback is expectedbool pendingCat;address expectedCaller; // msg.sender that scheduled decryptAndExecutebool 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 movesbytes;encArgs[0] = g.encMove1;encArgs[1] = g.encMove2;// plaintextArguments: pass identifiers to reconstruct context in onDecrypt// - gameId// - p1, p2bytes;plain[0] = abi.encode(gameId);plain[1] = abi.encode(g.p1);plain[2] = abi.encode(g.p2);// Schedule CAT; no return valueBITE.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 contextrequire(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 winneraddress winner = _winnerOf(p1, p2, p1Move, p2Move);// Mark finished and clear flagsg.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;}}}
石頭剪刀布的解釋
該合約展示了BITE 第 2 階段如何使智能合約能夠Decrypt數據並通過合約-行動-交易 (CAT)自動採取行動。
該示例實現了一個簡單的雙人石頭剪刀布遊戲,其中每個玩家提交一個加密的動作,一旦兩個動作都提交,合約就會自動安排CAT 交易來Decrypt動作並確定獲勝者。
遊戲流程
1.遊戲創作
- 玩家調用
createGame(opponent)
來設置新遊戲。 - 合同存儲:
-
p1
(創建者), -
p2
(對手), - 並分配一個
gameId
。
-
- 發出GameCreated事件。
2. 提交加密動作
- 每個玩家使用其加密的動作調用
submitEncryptedMove(gameId, encMove)
。 - 動作存儲在合約中:
- 為玩家 1
encMove1
, - 為玩家 2
encMove2
。
- 為玩家 1
- 發出EncryptedMoveSubmitted事件。
3. 安排CAT解密
- 一旦兩個動作都提交了:
- 合約調用BITE第二階段預編譯:
BITE.decryptAndExecute(encArgs, plainArgs);
-
encArgs
=[encMove1, encMove2]
(加密移動)。 -
plainArgs
=[gameId, p1, p2]
(用於重建遊戲的上下文信息)。
- 合約調用BITE第二階段預編譯:
- 這將創建一個CAT 交易,它將:
- 在下一個區塊運行,
- 調用
onDecrypt(decryptedMoves, plaintextArgs)
。
重要提示:CAT 交易無需用戶提交。它們會由協議自動插入到下一個區塊中,位於常規交易之前。
4. CAT 執行: onDecrypt
- 在下一個塊中,運行時:
- 在區塊最終確定期間解密移動,
- 調用合約的
onDecrypt
回調:function onDecrypt(bytes[] calldata decryptedArguments, // [p1Move, p2Move]bytes[] calldata plaintextArguments // [gameId, p1, p2]) external;
- 合同:
- 解析來自
decryptedArguments
動作, - 根據
plaintextArguments
重建上下文(gameId
, players ), - 使用石頭剪刀布規則確定獲勝者,
- 將遊戲標記為結束,
- 發出WinnerDecided事件。
- 解析來自
安全控制
待處理 CAT 標誌
- 合同在安排 CAT 時跟蹤
pendingCat = true
。 - 防止重複調度並確保只需要一個 CAT。
- 合同在安排 CAT 時跟蹤
呼叫者驗證
- 確保 CAT 的
msg.sender
與decryptAndExecute
的原始調用者匹配。 - 防止未經授權的外部調用
onDecrypt
。
- 確保 CAT 的
遊戲狀態
finished
標誌確保在確定獲勝者後遊戲無法重播。
活動
GameCreated(gameId, p1, p2)
→ 當新遊戲初始化時發出。EncryptedMoveSubmitted(gameId, player)
→ 當玩家提交其加密動作時發出。WinnerDecided(gameId, winner, p1Move, p2Move)
→ 當 CAT 交易執行並確定獲勝者時發出。
示例序列
N座
- 玩家 1 提交加密的動作。
- 玩家 2 提交加密的動作。
- 合約調用
decryptAndExecute
,安排CAT。
區塊 N+1
- 在最終確定過程中,加密的動作會被解密。
- CAT 執行
onDecrypt
,傳遞[p1Move, p2Move]
和上下文[gameId, p1, p2]
。 - 合約決定獲勝者併發出WinnerDecided 。
密封投標拍賣示例(BITE 第 2 階段)
此示例演示如何使用BITE 第 2 階段實現第一價格密封投標拍賣。
- 競標者提交其加密的出價以及ETH押金。
- 競標期結束後,合約將安排合約行動交易(CAT),在下一個區塊中解密所有競標。
- 然後,合約的
onDecrypt
回調確定最高出價者,完成拍賣,並轉移資金。
// SPDX-License-Identifier: MITpragma 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 callbytes[] memory encArgs = new bytes[](bids.length);bytes ; // auction context: total bidsfor (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 blockBITE.decryptAndExecute(encArgs, plainArgs);}// -------------------- CAT callback --------------------function onDecrypt(bytes[] calldata decryptedArguments, // decrypted bid valuesbytes[] 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 biduint256 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 auctionfinalized = true;pendingCat = false;expectedCaller = address(0);if (winnerIndex != type(uint256).max) {// Pay sellerpayable(seller).transfer(highestAmount);// Refund losers + excess depositfor (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 everyonefor (uint256 i = 0; i < numBids; i++) {payable(bids[i].bidder).transfer(bids[i].deposit);}emit AuctionFinalized(address(0), 0);}}}
拍賣流程
投標階段
- 用戶使用其加密出價和ETH存款調用
submitEncryptedBid(encBid)
。 - 押金可確保投標人的投標資金不會不足。
結束階段
- 一旦截止日期過去,
closeAuction()
就會安排一次CAT :BITE.decryptAndExecute(encArgs, plainArgs)