BITE 第 2 阶段 MVP 规范

本文为机器翻译
展示原文

BITE 第 2 阶段 MVP 规范

在 BITE 第 2 阶段中,每个区块可以包含合约行动交易 (CAT)——由前一个区块中的智能合约执行发起的交易。

CAT 使智能合约能够Decrypt数据,然后对这些数据自动执行操作。

使用 BITE 第 2 阶段的主要优势

  • 自动化:合约自动对解密数据进行操作,无需其他用户交易。
  • 效率:解密与 BITE 第一阶段同一批完成,因此没有额外的性能开销。
  • 确定性:执行以可预测的顺序进行(CAT 在区块 N+1 中的常规交易之前运行)。

合约-行动-交易

  1. N NA SC调用cryptoAndExecute编译传递一个encryptedAruments数组一个plaintextArguments明文参数数组

  2. CAT 交易被添加到下一个区块。CAT 交易在The Block中位于常规交易之前。它们不受区块 gas 限制。

  3. CAT 交易具有与创建它们的交易相同msg.sender m s g . s end e r

  4. CAT 交易t o字段是发起该交易的 SC。SC 向自己发送交易。

  5. CAT 交易总是调用发起它们SConDecrypt函数

  6. 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
  • 发出EncryptedMoveSubmitted事件。

3. 安排CAT解密

  • 一旦两个动作都提交了:
    • 合约调用BITE第二阶段预编译
      BITE.decryptAndExecute(encArgs, plainArgs);
    • encArgs = [encMove1, encMove2] (加密移动)。
    • plainArgs = [gameId, p1, p2] (用于重建游戏的上下文信息)。
  • 这将创建一个CAT 交易,它将:
    • 在下一个区块运行,
    • 调用onDecrypt(decryptedMoves, plaintextArgs)

:zap:重要提示:CAT 交易无需用户提交。它们会由协议自动插入到下一个区块中,位于常规交易之前。


4. CAT 执行: onDecrypt

  • 在下一个块中,运行时:
    1. 在区块最终确定期间解密移动,
    2. 调用合约的onDecrypt回调:
      function onDecrypt(bytes[] calldata decryptedArguments, // [p1Move, p2Move]bytes[] calldata plaintextArguments // [gameId, p1, p2]) external;
  • 合同:
    • 解析来自decryptedArguments动作,
    • 根据plaintextArguments重建上下文( gameId , players ),
    • 使用石头剪刀布规则确定获胜者,
    • 将游戏标记为结束,
    • 发出WinnerDecided事件。

安全控制

  1. 待处理 CAT 标志

    • 合同在安排 CAT 时跟踪pendingCat = true
    • 防止重复调度并确保只需要一个 CAT。
  2. 呼叫者验证

    • 确保 CAT 的msg.senderdecryptAndExecute的原始调用者匹配。
    • 防止未经授权的外部调用onDecrypt
  3. 游戏状态

    • finished标志确保在确定获胜者后游戏无法重播。

活动

  • GameCreated(gameId, p1, p2)
    → 当新游戏初始化时发出。

  • EncryptedMoveSubmitted(gameId, player)
    → 当玩家提交其加密动作时发出。

  • WinnerDecided(gameId, winner, p1Move, p2Move)
    → 当 CAT 交易执行并确定获胜者时发出。


示例序列

  1. N座

    • 玩家 1 提交加密的动作。
    • 玩家 2 提交加密的动作。
    • 合约调用decryptAndExecute ,安排CAT。
  2. 区块 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)

来源
免责声明:以上内容仅为作者观点,不代表Followin的任何立场,不构成与Followin相关的任何投资建议。
喜欢
收藏
评论