From 3643b67ba0453ba1dc4889278f921b45055f7ba0 Mon Sep 17 00:00:00 2001 From: six <51x@keemail.me> Date: Wed, 16 Aug 2023 10:41:42 +0200 Subject: [PATCH] Init --- README.md | 4 +- Solidity/CCTF.sol | 130 ++++++++++++++++++++++++++++++++++++++++++++++ Solidity/Easy.sol | 72 +++++++++++++++++++++++++ 3 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 Solidity/CCTF.sol create mode 100644 Solidity/Easy.sol diff --git a/README.md b/README.md index e964062..18227fd 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ # From_Solidity_To_Substrate -Workshop repository for the workshop at BME, 2023 August. \ No newline at end of file +Workshop repository for the workshop at BME, 2023 August. + +Presentation slides and references TBA diff --git a/Solidity/CCTF.sol b/Solidity/CCTF.sol new file mode 100644 index 0000000..58d93df --- /dev/null +++ b/Solidity/CCTF.sol @@ -0,0 +1,130 @@ +// 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; + } +} diff --git a/Solidity/Easy.sol b/Solidity/Easy.sol new file mode 100644 index 0000000..00a062d --- /dev/null +++ b/Solidity/Easy.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.8.2 <0.9.0; + +/** + * @title Storage + * @dev Store & retrieve value in a variable + * @custom:dev-run-script ./scripts/deploy_with_ethers.ts + */ +contract Storage { + + address private owner; + + // event for EVM logging + event OwnerSet(address indexed oldOwner, address indexed newOwner); + + // modifier to check if caller is owner + modifier isOwner() { + // If the first argument of 'require' evaluates to 'false', execution terminates and all + // changes to the state and to Ether balances are reverted. + // This used to consume all gas in old EVM versions, but not anymore. + // It is often a good idea to use 'require' to check if functions are called correctly. + // As a second argument, you can also provide an explanation about what went wrong. + require(msg.sender == owner, "Caller is not owner"); + _; + } + + /** + * @dev Set contract deployer as owner + */ + constructor() { + console.log("Owner contract deployed by:", msg.sender); + owner = msg.sender; // 'msg.sender' is sender of current call, contract deployer for a constructor + emit OwnerSet(address(0), owner); + } + + /** + * @dev Change owner + * @param newOwner address of new owner + */ + function changeOwner(address newOwner) public isOwner { + emit OwnerSet(owner, newOwner); + owner = newOwner; + } + + /** + * @dev Return owner address + * @return address of owner + */ + function getOwner() external view returns (address) { + return owner; + } + + + uint256 number; + + /** + * @dev Store value in variable + * @param num value to store + */ + function store(uint256 num) public { + number = num; + } + + /** + * @dev Return value + * @return value of 'number' + */ + function retrieve() public view returns (uint256){ + return number; + } +}