기존의 가위바위보 게임은 자신이 사용할 가위
바위
보
중 하나를 선택하여 방을 만들거나 들어가게 되는데 트랜잭션 Input Data
를 통해 사전에 값을 확인할 수 있었다.
악용가능성이 있으므로 Data
를 keccak256을 사용, 암호화하여 사전에 확인이 어렵게 수정했다.
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;
rock
paper
scissors
인덱스(0,1,2)로 구분한다.function keccak(uint256 _hand, string memory _key) public pure returns(byte32) {
return keccak256(abi.encodePacked(_hand, _key));
}
keccak256 함수에서 인자1은 hand, 인자2는 임의의 문자를 입력하면 암호화된 키 값이 반환된다.
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를 가지고 방을 생성하거나, 방에 입장한다.
단, 입장의 경우 방장이 낸 배팅금액과 같아야지만 입장이 가능하다.
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로 저장한다.
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을 통해 암호화 하는 방법을 찾아보게 되었다.
사실 위의 코드는 다른분이 작성한 코드를 복붙하여 테스트했다.
아직 솔리디티의 개념과 문법이 어려워서 복붙하여 테스트하고 하나하나 찾아보면서 이해하는게 전부이다보니 많이 벅찬거같다.
좀 더 공부해서 다시 개발해 보겠다.