forked from CCTF/DCTF
1
0
Fork 0

Compare commits

...

8 Commits
main ... main

Author SHA1 Message Date
six cb73d2e17c Code update from SI: thank you 2024-04-14 12:00:46 +04:00
six 600f5b2015 Upload files to "workshop" 2023-10-05 09:23:40 +00:00
six 4e06112356 Hacktivity 2023 2023-10-05 10:59:46 +02:00
six 9ebe21df4e Correction for main team and PMC hackathon participants 2023-08-25 08:17:26 +00:00
six 705c52b478 Actualize it 2023-08-25 09:34:13 +02:00
six 3cb7b9311e Hall of Fame and cleanup 2023-08-25 09:04:53 +02:00
six c72ac688c9 Vault draft 2023-08-24 10:26:30 +00:00
six 7d26cb80fb Merge pull request 'Update frontend/abi.js' (#1) from username/DCTF:main into main
Reviewed-on: CCTF/DCTF#1
2023-08-24 10:08:29 +00:00
10 changed files with 14739 additions and 309 deletions

View File

@ -1,10 +1,35 @@
# DCTF Beta
# DCTF v0.2
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
Deployment: TBA
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?)
- Check and fix main FE - contract integration, partially done, we need remote removals
- Add FE missing functions
- Re-architect in a way we don't need to redeploy
- Get version X ready
# 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 +48,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

@ -1,10 +1,6 @@
// 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 {
contract CryptoCTF10 {
enum PlayerStatus {
Unverified,
Verified,
@ -37,7 +33,7 @@ contract CryptoCTFX {
}
struct Challenge {
address obscuredFlag; // Essentially the public key of the flag, the flag being a private key
address obscuredFlag; // essentially the public key of the flag, the flag being a private key
uint worth;
uint256 descriptionFingerprint;
bool onlyFirstSolver;
@ -59,38 +55,26 @@ contract CryptoCTFX {
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;
}
function setContestDeadline(uint contestID, uint256 deadline) external onlyExistingContest(contestID) onlyAdmin(contestID) {
contests[contestID].deadline = deadline;
}
/* CTF Player functions */
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");
@ -98,7 +82,10 @@ contract CryptoCTFX {
contests[contestID].players[msg.sender].status = PlayerStatus.Verified;
}
// You can use six's eth_keygen to generate the signature
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;
@ -117,7 +104,6 @@ contract CryptoCTFX {
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);
@ -132,7 +118,6 @@ contract CryptoCTFX {
}
}
/* CTF View functions for feedback and FE */
function getContestDeadline(uint contestID) external view onlyExistingContest(contestID) returns (uint256) {
return contests[contestID].deadline;
}
@ -144,4 +129,4 @@ contract CryptoCTFX {
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;
}
}

View File

@ -0,0 +1,30 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.17;
import "cctf.sol";
contract CCTF_Vault {
address ctf_manager;
modifier ctf_manager_only {
require(ctf_manager == msg.sender);
_;
}
constructor() payable {
}
function deposit() external payable {}
function pay_winners() external {
// 1. Require that CTF finished
// 2. Check the top players and pay them out (based on what ratios/how?)
//CryptoCTFX.getPlayerScore(1, address);
}
function emergency_withdraw() external ctf_manager_only {
(bool success, ) = msg.sender.call{value: address(this).balance}("");
require(success, "Failed to send Ether");
}
}

3547
flag_encoder_fe/bn.js 100644

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,65 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>CryptoCTF zero-knowledge flag-submission encoder v0.5</title>
<script type="text/javascript" src="bn.js"></script>
<script type="text/javascript" src="nano-ethereum-signer-mod.js"></script>
</head>
<body>
<label for="inp-contestant-address">contestant's address:</label> <input id="inp-contestant-address" type="text" size="42" maxlength="42" pattern="0x[0-9a-fA-F]{40}" required placeholder="0x0000000000000000000000000000000000000000" autocomplete="cryptocurrency address" title="The cryptocurrency address of the contestant who is submitting the flag. It will be used as the message that is signed using an ECDSA signature scheme. It is incorporated to prevent submission forgery and theft. The resulting signature will be accepted by the CCTF smart-contract from only this address."/> <input id="inp-only-checksum-address" type="checkbox" checked autocomplete="checksum address" title="As an optional mechanism for protection against typos in the contestant's address field, this control panel can be set to reject an address value that fails the checksum test."/> <label for="inp-only-checksum-address">only accept correct checksum address</label><br/>
<label for="inp-flag">flag:</label> <input id="inp-flag" type="text" size="48" pattern="CCTF\{[ -~]*\}" required placeholder="CCTF{…}" autocomplete="flag" title="The flag for the challenge in question: a piece of data that can be found through solving the problem. It can be an outstanding character sequence of the form CCTF{…} that can be stumbled upon; otherwise its location/calculation (e.g. the deciphered message) and its formatting (e.g. hexadecimal) are precisely specified, and formatting always includes enclosing by CCTF{…}. The flag will be used as the secret key for signing using an ECDSA signature scheme." accesskey="f"/> <label for="outp-obscured-flag">⇒ obscured flag: </label><input id="outp-obscured-flag" type="text" size="42" maxlength="42" readonly title="The obscured flag: data to allow zero-knowledge verification of knowledge of the flag. It is the elliptic-curve public key related to the flag as the private key. It is the thing to be loaded into the CCTF smart-contract for the respective challenge."/><br />
<label for="outp-signature">⇒ signature: </label><input id="outp-signature" type="text" size="132" maxlength="132" readonly title="The signature: a zero-knowledge proof of knowledge of the flag, bound to the given address. It's an ECDSA signature of the address as the message (hash) with the flag as the key. It is to be passed to the CCTF smart-contract's flag submission function."/><!--<br />
<label for="inp-contract-address">CCTF smart-contract address:</label> <input id="inp-contract-address" type="text" size="42" maxlength="42" pattern="0x[0-9a-fA-F]{40}" placeholder="0x0000000000000000000000000000000000000000"/><br/>
<label for="inp-contest-id">contest ID:</label> <input id="inp-contest-id" type="text" size="66" pattern="0x[0-9a-fA-F]+|[0-9]+" placeholder="0x0000000000000000000000000000000000000000000000000000000000000000"/><br/>
<label for="inp-challenge-id">challenge ID:</label> <input id="inp-challenge-id" type="integer" size="4" placeholder="0" accesskey="c"/>-->
<script type="text/javascript">
var inpContestantAddress = document.querySelector("#inp-contestant-address");
var inpOnlyChecksumAddress = document.querySelector("#inp-only-checksum-address");
var inpFlag = document.querySelector("#inp-flag");
var outpObscuredFlag = document.querySelector("#outp-obscured-flag");
var outpSignature = document.querySelector("#outp-signature");
var secp256k1n = new BN('115792089237316195423570985008687907852837564279074904382605163141518161494337', 10);
function getPrivateKey() {
var s = inpFlag.value;
var h = "0x";
if (s.slice(0, 5) !== "CCTF{" || s.slice(-1) !== "}")
return null;
for (i in s) {
var c = s.charCodeAt(i);
if (c < 32 || c > 126)
return null;
h += c.toString(16).padStart(2, "0");
}
return "0x" + new BN(keccak256(h).slice(2), 16).mod(secp256k1n.sub(new BN(1))).add(new BN(1)).toString(16).padStart(64, 0);
}
function updateOutputValues(ev) {
var k = getPrivateKey();
if (k === null) {
outpObscuredFlag.value = "";
outpSignature.value = "";
} else {
outpObscuredFlag.value = addressFromKey(k);
var m = inpContestantAddress.value;
if (!/^0x[0-9a-fA-F]{40}$/.test(m) || inpOnlyChecksumAddress.checked && m !== addressChecksum(m)) {
outpSignature.value = "";
} else {
outpSignature.value = signMessage(m, k);
}
}
}
inpContestantAddress.oninput = updateOutputValues;
inpOnlyChecksumAddress.oninput = updateOutputValues;
inpFlag.oninput = updateOutputValues;
updateOutputValues();
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long