DCTF/contracts/CCTF_2023.sol

147 lines
6.3 KiB
Solidity

// SPDX-License-Identifier: Apache-2.0
// Authors: Anonz team, developed at Polkadot Metaverse Championship, as part of their CCTF track solution.
// Based on Six's and Silur's CCTF 2022 code.
// Currently taken from and being hacked: https://git.hsbp.org/money36/PCM_CCTF_challenge/src/branch/main
pragma solidity ^0.8.17;
contract CryptoCTFX {
enum PlayerStatus {
Unverified,
Verified,
Banned
}
struct Player {
PlayerStatus status;
uint score;
}
modifier onlyExistingContest(uint contestID) {
require(contests[contestID].admin != address(0), "Unknown contest ID");
_;
}
modifier onlyAdmin(uint contestID) {
require(msg.sender == contests[contestID].admin, "You are not the admin of this contest");
_;
}
modifier onlyOpen(uint contestID) {
require(block.timestamp < contests[contestID].deadline, "Submissions are not open for this contest at this time");
_;
}
modifier onlyExistingChallenge(uint contestID, uint challengeID) {
require(contests[contestID].challenges[challengeID].obscuredFlag != address(0), "Unknown challenge ID");
_;
}
struct Challenge {
address obscuredFlag; // Essentially the public key of the flag, the flag being a private key
uint worth;
uint256 descriptionFingerprint;
bool onlyFirstSolver;
string skill;
}
struct Contest {
address admin;
mapping (uint => Challenge) challenges;
mapping (address => Player) players;
uint256 deadline;
mapping (address => mapping (uint => bool)) solves; // address -> challengeID -> solved/not
mapping (uint => bool) anySolves; // challengeID -> solved/not
string password;
}
mapping (uint => Contest) public contests;
event ChallengeAddedOrUpdated(uint contestID, uint indexed challengeID);
event ChallengeSolved(uint contestID, uint indexed challengeID, address indexed solver);
/* CTF Manager functions */
// 1. Create a contest
function createContest(uint contestID, string memory password) external {
require(contests[contestID].admin == address(0), "This contest ID has already been registered");
contests[contestID].admin = msg.sender;
contests[contestID].password = password;
}
// 2. Set the contest's deadline
function setContestDeadline(uint contestID, uint256 deadline) external onlyExistingContest(contestID) onlyAdmin(contestID) {
contests[contestID].deadline = deadline;
}
// 3. Add the public keys of the challenges and their relevant data
function addOrUpdateChallenge(uint contestID, uint challengeID, address obscuredFlag, uint worth, uint256 descriptionFingerprint, bool onlyFirstSolver, string memory skill) external onlyExistingContest(contestID) onlyAdmin(contestID) {
require(obscuredFlag != address(0), "The obscured flag value must not be 0");
contests[contestID].challenges[challengeID] = Challenge(obscuredFlag, worth, descriptionFingerprint, onlyFirstSolver, skill);
emit ChallengeAddedOrUpdated(contestID, challengeID);
}
function setPlayerStatus(uint contestID, address player, PlayerStatus status) external onlyExistingContest(contestID) onlyAdmin(contestID) {
contests[contestID].players[player].status = status;
}
function setAdmin(uint contestID, address newAdmin) external onlyExistingContest(contestID) onlyAdmin(contestID) {
require(newAdmin != address(0));
contests[contestID].admin = newAdmin;
}
/* CTF Player functions */
function register(uint contestID, string memory password) external onlyExistingContest(contestID) {
require(contests[contestID].players[msg.sender].status == PlayerStatus.Unverified, "You are already registered or banned in this contest");
require(keccak256(abi.encodePacked(password)) == keccak256(abi.encodePacked(contests[contestID].password)), "Wrong password");
contests[contestID].players[msg.sender].status = PlayerStatus.Verified;
}
// You can use six's eth_keygen to generate the signature
function submitFlag(uint contestID, uint challengeID, bytes memory signature) external onlyExistingContest(contestID) onlyExistingChallenge(contestID, challengeID) onlyOpen(contestID) {
require(contests[contestID].players[msg.sender].status == PlayerStatus.Verified, "You are unverified or banned in this contest");
// the correct signature is an ECDSA signature where (1) the message (hash) is the sender address and (2) the private key is the flag;
// (2) is checked by testing against the public key, which can then be public information
address recoveredSigner = recoverSigner(bytes32(uint256(uint160(msg.sender))), signature);
require(recoveredSigner != address(0), "Invalid signature");
require(recoveredSigner == contests[contestID].challenges[challengeID].obscuredFlag, "Wrong answer");
require(!contests[contestID].solves[msg.sender][challengeID], "You have already solved this challenge of this contest");
if (!contests[contestID].anySolves[challengeID] || !contests[contestID].challenges[challengeID].onlyFirstSolver) {
contests[contestID].players[msg.sender].score += contests[contestID].challenges[challengeID].worth;
}
contests[contestID].solves[msg.sender][challengeID] = true;
contests[contestID].anySolves[challengeID] = true;
emit ChallengeSolved(contestID, challengeID, msg.sender);
}
/* CTF ECDSA related functions */
function recoverSigner(bytes32 messageHash, bytes memory signature) public pure returns (address) {
(bytes32 r, bytes32 s, uint8 v) = splitSignature(signature);
return ecrecover(messageHash, v, r, s);
}
function splitSignature(bytes memory sig) public pure returns (bytes32 r, bytes32 s, uint8 v) {
require(sig.length == 65, "Invalid signature length");
assembly {
r := mload(add(sig, 32))
s := mload(add(sig, 64))
v := byte(0, mload(add(sig, 96)))
}
}
/* CTF View functions for feedback and FE */
function getContestDeadline(uint contestID) external view onlyExistingContest(contestID) returns (uint256) {
return contests[contestID].deadline;
}
function getPlayerStatus(uint contestID, address player) external view onlyExistingContest(contestID) returns (PlayerStatus) {
return contests[contestID].players[player].status;
}
function getPlayerScore(uint contestID, address player) external view onlyExistingContest(contestID) returns (uint) {
return contests[contestID].players[player].score;
}
}