Merge branch 'main' into RemoveRemote
commit
0ddb7309f1
43
README.md
43
README.md
|
@ -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
|
# 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.
|
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
|
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
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
Loading…
Reference in New Issue