가위바위보 게임 개선

김경식·2022년 4월 6일
1

개발 목적

기존의 가위바위보 게임은 자신이 사용할 가위 바위 중 하나를 선택하여 방을 만들거나 들어가게 되는데 트랜잭션 Input Data를 통해 사전에 값을 확인할 수 있었다.
악용가능성이 있으므로 Data를 keccak256을 사용, 암호화하여 사전에 확인이 어렵게 수정했다.

Variable

enum Hand {
	rock,
    paper,
    scissors
 }
       
enum PlayerStatus {
	STATUS_WIN,
    STATUS_LOSE,
    STATUS_TIE,
    STATUS_PENDING
 }
 
enum GameStatus {
	STATUS_NOT_STRTED,
    STATUS_STARTED,
    STATUS_COMPLETE,
    STATUS_ERROR
 }
 
 struct Player {
 	address payble addr;
    uint256 playerBetAount;
    bytes32 hand;
    PlayerStatus playerStatus;
    uint result;
    uint count;
 }
 
 struct Game {
 	Player originator;
    Player taker;
    uint256 betAmount;
    GameStatus gameStatus;
 }
 
 mappint(uint256 => Game) rooms;
 uint256 roomLen = 0;
  • Hand는 rock paper scissors 인덱스(0,1,2)로 구분한다.
  • Player는 전송가능한 payable 주소, 배팅금액, Hand, Player 상태등으로 구성한다.
  • Game은 방장, 참여자, 배팅금액의 합계, 게임 상태로 구성한다.
  • rooms는 방목록으로 방번호 및 Game의 구조를 값으로 받는다.
  • roomLen은 방번호로 사용되고 방이 만들어질 때 마다 방번호가 +1이 된다.

keccak256

function keccak(uint256 _hand, string memory _key) public pure returns(byte32) {
	return keccak256(abi.encodePacked(_hand, _key));
   }

keccak256 함수에서 인자1은 hand, 인자2는 임의의 문자를 입력하면 암호화된 키 값이 반환된다.

Create Room, Join Room

function createRoom(bytes32 _hand) public payable returns (uint256 roomNum)
  {
    rooms[roomLen] = Game({
      betAmount: msg.value,
      gameStatus: GameStatus.STATUS_NOT_STRTED,
      originator: Player({
        hand: _hand,
        addr: payable(msg.sender),
        playerStatus: PlayerStatus.STATUS_PENDING,
        playerBetAmount: msg.value,
        result : 0,
        count : 0
      }),
      taker: Player({
        hand: 0,
        addr: payable(msg.sender),
        playerStatus: PlayerStatus.STATUS_PENDING,
        playerBetAmount: 0,
        result : 0,
        count : 0
      })
    });
    roomNum = roomLen;
    roomLen = roomLen + 1;
  }
 
 function joinRoom(uint256 roomNum, bytes32 _hand) public payable {
   require(msg.value == rooms[roomNum].betAmount);
    rooms[roomNum].taker = Player({
      hand: _hand,
      addr: payable(msg.sender),
      playerStatus: PlayerStatus.STATUS_PENDING,
      playerBetAmount: msg.value,
      result : 0,
      count : 0
    });
    rooms[roomNum].betAmount = rooms[roomNum].betAmount + msg.value;
  }

암호화된 hand를 가지고 방을 생성하거나, 방에 입장한다.
단, 입장의 경우 방장이 낸 배팅금액과 같아야지만 입장이 가능하다.

Decryption

function useOriginator(uint256  _roomNum, uint256 _hand, string memory _secretKey) public {
    require(msg.sender == rooms[_roomNum].originator.addr);
    require(rooms[_roomNum].originator.hand == keccak(_hand,_secretKey));
    rooms[_roomNum].originator.result = _hand;
    rooms[_roomNum].originator.count = 1;
  }

    function useTaker(uint256  _roomNum, uint256 _hand, string memory _secretKey) public {
    require(msg.sender == rooms[_roomNum].taker.addr);
    require(rooms[_roomNum].taker.hand == keccak(_hand,_secretKey));
    rooms[_roomNum].taker.result = _hand;
    rooms[_roomNum].taker.count = 1;
  }

방장 또는 참여자는 자신이 암호화한 패만을 풀수 있으며 result로 저장한다.

CompareHands, Payout

 function compareHands(uint256 roomNum) private {
    require(rooms[roomNum].taker.count==1 && rooms[roomNum].originator.count ==1);
    uint8 originator = uint8(rooms[roomNum].originator.result);
    uint8 taker = uint8(rooms[roomNum].taker.result);

    rooms[roomNum].gameStatus = GameStatus.STATUS_STARTED;

    if (taker == originator) {

      rooms[roomNum].originator.playerStatus = PlayerStatus.STATUS_TIE;
      rooms[roomNum].taker.playerStatus = PlayerStatus.STATUS_TIE;
    } else if ((taker + 1) % 3 == originator) {
  
      rooms[roomNum].originator.playerStatus = PlayerStatus.STATUS_WIN;
      rooms[roomNum].taker.playerStatus = PlayerStatus.STATUS_LOSE;
    } else if ((originator + 1) % 3 == taker) {
  
      rooms[roomNum].originator.playerStatus = PlayerStatus.STATUS_LOSE;
      rooms[roomNum].taker.playerStatus = PlayerStatus.STATUS_WIN;
    } else {
  
      rooms[roomNum].gameStatus = GameStatus.STATUS_ERROR;
    }
  }
  
modifier isPlayer(uint256 roomNum, address sender) {
    require(
      sender == rooms[roomNum].originator.addr ||
        sender == rooms[roomNum].taker.addr
    );
    _;
  }

  function payout(uint256 roomNum) public payable
    isPlayer(roomNum, msg.sender)
  {
    compareHands(roomNum);
}

회고

처음 input data를 어떻게 숨길 수 있을까 고민을 했을때 단순히 트랜잭션에서 값을 숨기면 되는게 아닌가 싶었다.
열심히 구글링을 했지만 숨기는 방법은 없었고, keccak을 통해 암호화 하는 방법을 찾아보게 되었다.
사실 위의 코드는 다른분이 작성한 코드를 복붙하여 테스트했다.
아직 솔리디티의 개념과 문법이 어려워서 복붙하여 테스트하고 하나하나 찾아보면서 이해하는게 전부이다보니 많이 벅찬거같다.
좀 더 공부해서 다시 개발해 보겠다.

코드공유(Github)

0개의 댓글