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)