Merge branch 'main' into RemoveRemote

pull/2/head
six 2023-12-02 13:15:28 +00:00
commit 0ddb7309f1
5 changed files with 8506 additions and 279 deletions

View File

@ -1,10 +1,38 @@
# DCTF Beta
# DCTF v0.1
Decentralized CTF engine, used by CCTF
The first decentralized CTF engine, used by CCTF
Testnet deployment on Moonbase Alpha: https://moonbase.moonscan.io/address/0x919f68cc35ce5d49a45c94dc44e7bf444f9a7531
Testnet deployment on Moonbase Alpha: https://moonbase.moonscan.io/address/0x70f0cec3c99103113d96ed8ad82ae6a8d9a735a0#code
Testnet Hall of Fame on Moonbase Alpha: https://moonbase.moonscan.io/address/0xb70780843be242474025f185bf26dddadfc27f43#code
Main team contributors: six, Silur, Bazsi
Hackathon contributors: SI, username, Anon, GuyFromUniversity, ZK, Metafaka team, BMEta team
### Short-term ToDo
- Finish the Vault
- Finish the Hall of Fame with multi-management DAO style setup (approval needed for valid flag?)
- Architect the topology, research best way so all features work together
- Check and fix FE - contract integration, partially done, we need remote removals
- Add FE missing functions
- Re-architect in a way we don't need to redeploy
- Deploy version X
# Workshop at Hacktivity 2023
Intro to blockchain, cryptography and learn to play CCTF. Smart contract steps:
1. Deploy in Remix
2. createContest()
3. Generate a priv/pub key for the flag.
4. addOrUpdateChallenge()
5. Play, find flag
6. register()
7. submitFlag()
8. Challenge
Contributors: six, Silur, Anonz team, Metafaka team, BMEta team
# Workshop at HITB 2023
@ -23,10 +51,3 @@ We will go through a fast intro to Web3 and how to hack DCTF. Anyone present at
This is a free flow workshop. Six can explain these you in 20 mins, but vibes will tell how long you stay and hack.
For generating the ECDSA signatures and message hashes, use: https://git.hsbp.org/six/eth_keygen/src/branch/master/ethkeygen.py
### Short-term ToDo
- [ ] Update ABI
- [ ] Check and fix FE - contract integration
- [ ] Add FE missing functions
- [ ] Re-architect in a way we don't need to redeploy

View File

@ -1,138 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
// Authors: six and Silur
pragma solidity ^0.8.16;
contract CCTF9 {
address public admin;
uint256 public volStart;
uint256 public volMaxPoints;
uint256 public powDiff;
bool public started;
enum PlayerStatus {
Unverified,
Verified,
Banned
}
struct Player {
PlayerStatus status;
uint256 points;
}
modifier onlyAdmin {
require(msg.sender == admin, "Not admin");
_;
}
modifier onlyActive {
require(started == true, "CCTF not started.");
_;
}
struct Flag {
address signer;
bool onlyFirstSolver;
uint256 points;
string skill_name;
}
mapping (address => Player) public players;
mapping (uint256 => Flag) public flags;
event CCTFStarted(uint256 timestamp);
event FlagAdded(uint256 indexed flagId, address flagSigner);
event FlagRemoved(uint256 indexed flagId);
event FlagSolved(uint256 indexed flagId, address indexed solver);
constructor(uint256 _volMaxPoints, uint256 _powDiff) {
admin = msg.sender;
volMaxPoints = _volMaxPoints;
powDiff = _powDiff;
started = false;
}
function setAdmin(address _admin) external onlyAdmin {
require(_admin != address(0));
admin = _admin;
}
function setCCTFStatus(bool _started) external onlyAdmin {
started = _started;
}
function setFlag(uint256 _flagId, address _flagSigner, bool _onlyFirstSolver, uint256 _points, string memory _skill) external onlyAdmin{
flags[_flagId] = Flag(_flagSigner, _onlyFirstSolver, _points, _skill);
emit FlagAdded(_flagId, _flagSigner);
}
function setPowDiff(uint256 _powDiff) external onlyAdmin {
powDiff = _powDiff;
}
function register(string memory _RTFM) external {
require(players[msg.sender].status == PlayerStatus.Unverified, 'Already registered or banned');
//uint256 pow = uint256(keccak256(abi.encodePacked("CCTF", msg.sender,"registration", nonce)));
//require(pow < powDiff, "invalid pow");
require(keccak256(abi.encodePacked('I_read_it')) == keccak256(abi.encodePacked(_RTFM))); // PoW can be used for harder challenges, this is Entry!
players[msg.sender].status = PlayerStatus.Verified;
}
function setPlayerStatus(address player, PlayerStatus status) external onlyAdmin {
players[player].status = status;
}
////////// Submit flags
mapping(bytes32 => bool) usedNs; // Against replay attack (we only check message signer)
mapping (address => mapping (uint256 => bool)) Solves; // address -> challenge ID -> solved/not
uint256 public submission_success_count = 0; // For statistics
function SubmitFlag(bytes32 _message, bytes memory signature, uint256 _submitFor) external onlyActive {
require(players[msg.sender].status == PlayerStatus.Verified, "You are not even playing");
require(bytes32(_message).length <= 256, "Too long message.");
require(!usedNs[_message]);
usedNs[_message] = true;
require(recoverSigner(_message, signature) == flags[_submitFor].signer, "Not signed with the correct key.");
require(Solves[msg.sender][_submitFor] == false);
Solves[msg.sender][_submitFor] = true;
players[msg.sender].points += flags[_submitFor].points;
players[msg.sender].points = players[msg.sender].points < volMaxPoints ? players[msg.sender].points : volMaxPoints;
if (flags[_submitFor].onlyFirstSolver) {
flags[_submitFor].points = 0;
}
submission_success_count = submission_success_count + 1;
emit FlagSolved(_submitFor, msg.sender);
}
function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature) public pure returns (address) {
(bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);
return ecrecover(_ethSignedMessageHash, 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)))
}
}
////////// Check status, scores, etc
function getPlayerStatus(address _player) external view returns (PlayerStatus) {
return players[_player].status;
}
function getPlayerPoints(address _player) external view returns (uint256) {
return players[_player].points < volMaxPoints ? players[_player].points : volMaxPoints;
}
function getSuccessfulSubmissionCount() external view returns (uint256){
return submission_success_count;
}
}

View File

@ -1,130 +0,0 @@
// 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.
pragma solidity ^0.8.16;
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(contests[contestID].submissionsOpen, "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; // public key of the flag
uint worth;
uint256 descriptionFingerprint;
bool onlyFirstSolver;
string skill;
}
struct Contest {
address admin;
mapping (uint => Challenge) challenges;
mapping (address => Player) players;
bool submissionsOpen;
mapping (address => mapping (uint => bool)) solves; // address -> challengeID -> solved/not
mapping (uint => bool) anySolves; // challengeID -> solved/not
}
mapping (uint => Contest) public contests;
event ChallengeAddedOrUpdated(uint contestID, uint indexed challengeID);
event ChallengeSolved(uint contestID, uint indexed challengeID, address indexed solver);
function createContest(uint contestID) external {
require(contests[contestID].admin == address(0), "This contest ID has already been registered");
contests[contestID].admin = msg.sender;
contests[contestID].submissionsOpen = false;
}
function setAdmin(uint contestID, address newAdmin) external onlyExistingContest(contestID) onlyAdmin(contestID) {
require(newAdmin != address(0));
contests[contestID].admin = newAdmin;
}
function setSubmissionsStatus(uint contestID, bool open) external onlyExistingContest(contestID) onlyAdmin(contestID) {
contests[contestID].submissionsOpen = open;
}
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 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("I_read_it")) == keccak256(abi.encodePacked(password)));
contests[contestID].players[msg.sender].status = PlayerStatus.Verified;
}
function setPlayerStatus(uint contestID, address player, PlayerStatus status) external onlyExistingContest(contestID) onlyAdmin(contestID) {
contests[contestID].players[player].status = status;
}
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(abi.encodePacked(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);
}
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)))
}
}
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;
}
}

View File

@ -0,0 +1,60 @@
// SPDX-License-Identifier: GPL-3.0
// Author: six
// State: v0.1
pragma solidity ^0.8.17;
contract CCTF_Hall_of_Fame {
address fame_manager;
constructor () {
fame_manager = msg.sender; // TBA There should be more, eg. DAO-like feature.
}
modifier only_famman {
require(msg.sender == fame_manager, 'Not Fame Manager.');
_;
}
//Contributors
uint contributor_count;
struct Contributor{
uint256 id;
string name;
string skills;
uint record_date;
}
Contributor[] public contributors;
// Players
uint player_count;
struct Player{
uint256 id;
string name;
string team_name;
string skills;
uint record_date;
}
Player[] public players;
function addContributor(string memory _name, string memory _skills) external only_famman {
contributor_count = contributor_count+1;
contributors.push(Contributor(contributor_count, _name, _skills, block.timestamp));
}
// There is no remove, once someone contributed it is safu.
function addPlayer(string memory _name, string memory _skills, string memory _team_name) external only_famman {
player_count = player_count+1;
players.push(Player(contributor_count, _name, _skills, _team_name, block.timestamp));
}
function listContributors() public view returns (Contributor[] memory) {
return contributors;
}
function listTopPlayers() public view returns (Player[] memory) {
return players;
}
}

File diff suppressed because one or more lines are too long