BITE 2단계 MVP 사양
BITE 2단계에서는 각 블록 CAT(계약-행동-거래)가 포함될 수 있습니다. CAT는 이전 블록 에서 스마트 계약 실행을 통해 시작된 거래입니다.
CAT를 사용하면 스마트 계약으로 데이터를 디크립트(Decrypt) 다음 이 데이터에 대해 자동으로 작업을 수행할 수 있습니다.
BITE 2단계 사용의 주요 이점
- 자동화: 계약은 다른 사용자 거래 없이 해독된 데이터에 대해 자동으로 적용됩니다.
- 효율성: 복호화는 BITE 1단계와 동일한 배치에서 수행되므로 추가적인 성능 오버헤드가 발생하지 않습니다.
- 결정론: 실행은 예측 가능한 순서로 발생합니다(CAT는 블록 N+1의 일반 트랜잭션보다 먼저 실행됩니다).
계약-행동-거래
블록 N 의 SC 는 decryptAndExecute 를 호출 하고 암호화 된 인수 배열 과 평문 인수 배열 을 전달 하여 사전 컴파일 을 실행 합니다 .
CAT 트랜잭션은 다음 블록 에 추가됩니다. CAT 트랜잭션은 블록 내 일반 트랜잭션 앞에 배치되며, 블록 가스 한도의 영향을 받지 않습니다.
CAT 거래에는 이를 생성한 거래 와 동일한 msg.sender ms g .send e r 이 있습니다 .
CAT 트랜잭션을 t o 필드 로 보내는 것은 해당 트랜잭션을 생성한 SC입니다. SC는 자신에게 트랜잭션을 전송합니다.
CAT 거래는 항상 이를 발생 시킨 SC 의 onDecrypt 함수 를 호출 합니다 .
CAT 트랜잭션은 블록 N N 의 최종화 과정에서 BITE 1단계 트랜잭션과 동일한 배치 디크립트(Decrypt) 과정에서 복호화됩니다. 따라서 BITE 2단계는 BITE 1단계에 비해 성능에 변화가 없습니다.
decryptAndExecute 사전 컴파일
이 기능은 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;
가스 비용 사전 컴파일
나중에 정의됨
onDecrypt 호출
스마트 계약이 D e c r y p t ( ) 함수 에 onDecrypt()를 정의하는 경우 블록 N N 에서 복호화를 시작할 수 있으며 복호화 결과는 블록 N+1 N + 1 의 D e c r y p t ( ) 에 onDecrypt() 로 전달됩니다.
/**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 형식을 갖지만 이 인수를 디크립트(Decrypt) 할 수 있는 스마트 계약 의 주소 를 지정 하는 추가 allowedDecryptorAddress ( 암호 해독 가능 주소 매개 변수 ) 를 포함 합니다 .
가위바위보의 예.
아래 예에서는 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단계 가 스마트 계약을 통해 데이터를 해독하고 CAT(계약-행동-거래)를 통해 디크립트(Decrypt) 으로 작동하는 방식을 보여줍니다.
이 예제에서는 각 플레이어가 암호화된 움직임을 제출하고, 두 움직임이 모두 제출되면 계약에서 자동으로 CAT 거래를 예약하여 움직임을 디크립트(Decrypt) 하고 승자를 결정하는 간단한 2인용 가위바위보 게임을 구현합니다.
게임 흐름
1. 게임 제작
- 플레이어는
createGame(opponent)
호출하여 새로운 게임을 설정합니다. - 계약은 다음을 저장합니다.
-
p1
(생성자), -
p2
(상대방), - 그리고
gameId
할당합니다.
-
- GameCreated 이벤트를 내보냅니다.
2. 암호화된 이동 제출
- 각 플레이어는 암호화된 이동 으로
submitEncryptedMove(gameId, encMove)
호출합니다. - 이동 내용은 계약서에 저장됩니다.
- 플레이어 1의
encMove1
, - 플레이어 2의 경우
encMove2
.
- 플레이어 1의
- EncryptedMoveSubmitted 이벤트를 내보냅니다.
3. CAT 복호화 스케줄링
- 두 가지 동작이 모두 제출되면:
- 이 계약에서는 BITE 2단계 사전 컴파일을 다음과 같이 정의합니다.
BITE.decryptAndExecute(encArgs, plainArgs);
-
encArgs
=[encMove1, encMove2]
(암호화된 이동). -
plainArgs
=[gameId, p1, p2]
(게임을 재구성하기 위한 컨텍스트 정보).
- 이 계약에서는 BITE 2단계 사전 컴파일을 다음과 같이 정의합니다.
- 이렇게 하면 다음과 같은 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
, 플레이어)를 재구성합니다. - 가위바위보 규칙을 사용하여 승자를 결정합니다.
- 게임이 완료되었다고 표시합니다.
- 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는
[p1Move, p2Move]
와 컨텍스트[gameId, p1, p2]
전달하여onDecrypt
실행합니다. - 계약은 승자를 결정하고 WinnerDecided를 방출합니다.
봉인 입찰 경매 예시(BITE 2단계)
이 예제에서는 BITE 2단계 를 사용하여 1차 가격 밀폐 입찰 경매를 구현하는 방법을 보여줍니다.
- 입찰자는 이더리움(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)