├── .gitignore ├── README.md ├── challenges ├── movectf-1 │ ├── Move.toml │ └── sources │ │ └── module.move ├── movectf-4 │ ├── Move.toml │ └── sources │ │ └── module.move ├── movectf-5 │ ├── Move.toml │ └── sources │ │ └── move_lock.move └── movectf-6 │ ├── Move.toml │ └── sources │ ├── adventure.move │ ├── hero.move │ ├── inventory.move │ └── random.move ├── encrypt ├── .gitignore ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── flash_sol ├── Move.toml └── sources │ └── module.move ├── hero_sol ├── Move.toml └── sources │ └── sol.move └── z3_sol └── sol.py /.gitignore: -------------------------------------------------------------------------------- 1 | **/build/ 2 | sui.log* 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Move CTF (Nov 5 — Nov 7 / 2022) Writeup 2 | 3 | Thanks to the organizers. It was interesting. 4 | 5 | Disclaimer: I write everything from memory, so there may be mistakes in both the code and the text. 6 | 7 | ## `checkin` (100) 8 | 9 | - Source: [movectf-1](https://github.com/movebit/movectf-1), [local](./challenges/movectf-1) 10 | - Deploy: 11 | 12 | ```shell 13 | $ sui client publish --gas-budget 10000 --path ./challenges/movectf-1 14 | $ export PACKAGE_ADDRESS=0x... 15 | ``` 16 | 17 | The first task, it's the simplest. It was included in the CTF, apparently, in order to teach participants how to use the system (although a few days before that, exactly the same task had already been posted). 18 | 19 | For all challenges, it was necessary to create an account (Sui wallet or address in a blockchain network, call it what you want), then transfer some gas (`SUI`) to it from your wallet (or get gas from a faucet). 20 | 21 | ```shell 22 | sui client transfer-sui --to $ACCOUNT_ADDRESS --sui-coin-object-id 0x... --gas-budget 100 --amount 1000000 23 | ``` 24 | 25 | After that, you had to deploy a smart contract to a blockchain (this is done automatically, just click on `Deploy` button, see Fig. 1 below). As a result, you should get a transaction ID, from which you can find out an address of the deployed package and other objects that were created as a result of executing the `init` function. 26 | 27 | ![Interface of a challenge](https://user-images.githubusercontent.com/4244396/200733697-e2cbdb99-2667-41e2-a995-37da3fba8517.png) 28 | > Fig. 1. Just press `Deploy` button 29 | 30 | To get a flag, in all tasks you need to call `get_flag` function from a smart contract, as well as in this challenge. The `Flag` event occurs inside `get_flag` with `flag` field set to `true`. Thus, the system reads information about the transaction (an ID of which you'll input) and checks for the presence of an event. 31 | 32 | Summary: 33 | 34 | ```shell 35 | sui client call --gas-budget 10000 --package $PACKAGE_ADDRESS --module "checkin" --function "get_flag" | jq ".[1].events" 36 | ``` 37 | 38 | ## `simple game` (400) 39 | 40 | - Source: [movectf-6](https://github.com/movebit/movectf-6), [local](./challenges/movectf-6) 41 | - Deploy: 42 | 43 | ```shell 44 | $ sui client publish --gas-budget 10000 --path ./challenges/movectf-6 45 | $ export PACKAGE_ADDRESS=0x... # and change addresses of `ctf` and `game` packages in `Move.toml` 46 | $ export HERO=0x... 47 | ``` 48 | 49 | A "simple game", but not a simple challenge, judging by the number of assigned points. 50 | 51 | Investigating the smart contract code, we understand that we are facing a primitive RPG game in which we need to kill monsters (easier and more difficult) and loot them. In order for `get_flag` function to work, it is necessary to provide it with `TreasuryBox` as an argument, which can be looted only in the following cases: 52 | 53 | - you have to kill a boss monster; 54 | - random must be kind to you so that 0 came up on a d100 virtual dice (on a real dice it would be one). 55 | 56 | In addition, in `get_flag` function, the first thing that happens is the destruction of `TreasuryBox`, and then with the help of the same dice, a fate of a occurrence of `Flag` event is decided. 57 | 58 | That is, there are at least two calls of the random function on our way. You should definitely look into this the random function (or trust fate and brute force random)! In `random.move` file, the CTF creators explicitly hint to us that it is necessary to hack the random function: 59 | 60 | ```move 61 | /// @title pseudorandom 62 | /// @notice A pseudo random module on-chain. 63 | /// @dev Warning: 64 | /// The random mechanism in smart contracts is different from 65 | /// that in traditional programming languages. The value generated 66 | /// by random is predictable to Miners, so it can only be used in 67 | /// simple scenarios where Miners have no incentive to cheat. If 68 | /// large amounts of money are involved, DO NOT USE THIS MODULE to 69 | /// generate random numbers; try a more secure way. 70 | ``` 71 | 72 | Of course, it could have been a trick, so I glanced at the game code again. But everything pointed to the random function, or rather to `seed` function responsible for seed generation. 73 | 74 | ```move 75 | fun seed(ctx: &mut TxContext): vector { 76 | ``` 77 | 78 | The function operates only with `ctx` argument, therefore we need to do something with this `ctx: &mut TxContext`. Since I'm still a complete noob in Move, I started looking for an opportunity to modify the `TxContext` by reading the documentation, Sui's whitepaper, studying Sui CLI. If I understood everything correctly, then this argument cannot be modified, it is created inside a VM on nodes. *Here I got a little desperate, lost a lot of time, and started to investigate the smart contract code again in useless.* Then the thought appeared: "maybe nothing is added to `TxContext` by a node, but it only does calculations using an input data received from a transaction?..". Since I am also a Rust developer, it was easier for me to find the answer to the question by researching Sui code. Everything pointed to the fact that `TxContext` could be precomputed. 79 | 80 | To calculate `TxContext` I used Rust SDK. Unfortunately, it was not possible to create a separate project due to a dependency error (now you can read [issue](https://github.com/MystenLabs/sui/issues/5887)). There was no time to fix the error, so I chose another way to use Rust SDK: I created a file `hero.rs` in the directory with examples for `sui-sdk` crate ([commit 34944fe5](https://github.com/saruman9/sui/commit/34944fe5c2966c1944e8075e466b0bc4a45114c2)). Fortunately, it worked. 81 | 82 | Now we need to understand what `TxContext` should be so that the dice always come up on the side with 0. To do this, just repeat all the operations ([`seed`](https://github.com/movebit/movectf-6/blob/aaa3694518897ee304e8f0e88d48314aacea52cd/sources/random.move#L23-L35), [`bytes_to_u64`](https://github.com/movebit/movectf-6/blob/aaa3694518897ee304e8f0e88d48314aacea52cd/sources/random.move#L37-L45)) in Rust code ([`seed`](https://github.com/saruman9/sui/blob/34944fe5c2966c1944e8075e466b0bc4a45114c2/crates/sui-sdk/examples/hero.rs#L78-L88), [`bytes_to_u64`](https://github.com/saruman9/sui/blob/34944fe5c2966c1944e8075e466b0bc4a45114c2/crates/sui-sdk/examples/hero.rs#L138-L155)). 83 | 84 | So that a new `TxContext` is generated each time I changed a count of gas, I think it was possible not to do it. Also, due to the fact that `move_call` function constantly makes a request for a signature of a function from a package, the working time of all this DIY was greatly increased, but I didn't have time to figure out how it could be optimized. 85 | 86 | In order to get the treasure, it was necessary to kill a boss monster. To kill a boss with a higher probability, it was necessary [to level up on small monsters](./hero_sol/sources/sol.move). At the same time, during the killing of a boss, it is necessary to get 0 on the dice, and for this it was necessary [to calculate future changes in `TxContext`](https://github.com/saruman9/sui/blob/34944fe5c2966c1944e8075e466b0bc4a45114c2/crates/sui-sdk/examples/hero.rs#L74-L77): 87 | 88 | - calling [`object::new`](https://github.com/MystenLabs/sui/blob/a149bdaea53276c205a1ccf1ecb21b19ad81cdb2/crates/sui-framework/sources/object.move#L107-L113) , 89 | - i.e. calling [`new_object(ctx: &mut TxContext)`](https://github.com/MystenLabs/sui/blob/a149bdaea53276c205a1ccf1ecb21b19ad81cdb2/crates/sui-framework/sources/tx_context.move#L52-L58) 90 | 91 | increment of a value in the field [`ids_created`](https://github.com/MystenLabs/sui/blob/a149bdaea53276c205a1ccf1ecb21b19ad81cdb2/crates/sui-framework/sources/tx_context.move#L34), which affected a generation of `seed`. 92 | 93 | After the treasure is in our hands, we need to calculate `TxContext` again so that everything is not in waste, because in `get_flag` function, the treasure is first destroyed, and then the dice is checked: 94 | 95 | ```move 96 | let TreasuryBox { id } = box; 97 | object::delete(id); 98 | let d100 = random::rand_u64_range(0, 100, ctx); 99 | if (d100 == 0) { 100 | event::emit(Flag { user: tx_context::sender(ctx), flag: true }); 101 | } 102 | ``` 103 | 104 | Summary: 105 | 106 | ```shell 107 | $ export WALLET_ADDRESS=$(sui client active-address) 108 | $ sui client publish --path ./hero_sol --gas-budget 10000 109 | $ export SOLUTION_ADDRESS=0x... 110 | $ sui client call --gas-budget 1000000 --package $SOLUTION_ADDRESS --module "sol" --function "kill_slay_boar" --args $HERO 111 | $ cd sui # use `sui` repo and `move_ctf_2022` branch from https://github.com/saruman9/sui 112 | $ cargo run --example hero -- -p $PACKAGE_ADDRESS -m adventure -f slay_boar_king -o 4 $HERO 113 | $ export TREASURY_BOX=0x... 114 | $ cargo run --example hero -- -p $PACKAGE_ADDRESS -m inventory -f get_flag $TREASURY_BOX 115 | ``` 116 | 117 | ## `flash loan` (200) 118 | 119 | - Source: [movectf-4](https://github.com/movebit/movectf-4), [local](./challenges/movectf-4) 120 | - Deploy: 121 | 122 | ```shell 123 | $ sui client publish --gas-budget 10000 --path ./challenges/movectf-4 124 | $ export PACKAGE_ADDRESS=0x... # and change address of `movectf` package in `Move.toml` 125 | $ export LENDER=0x... 126 | ``` 127 | 128 | To be honest, I'm still not sure that I solved this task correctly, because it seemed too easy to me. 129 | 130 | Starting from the end: in order for `Flag` event to be generated, it is necessary that `FlashLender` has no money in the account. So let's borrow from them, [call `get_flag` and immediately return the debt](./flash_sol/sources/module.move): 131 | 132 | ```move 133 | let (coins, receipt) = flash::loan(lender, 1000, ctx); 134 | flash::get_flag(lender, ctx); 135 | flash::repay(lender, coins); 136 | flash::check(lender, receipt); 137 | ``` 138 | 139 | Summary: 140 | 141 | ```shell 142 | $ sui client publish --path ./flash_sol --gas-budget 10000 143 | $ export SOLUTION_ADDRESS=0x... 144 | $ sui client call --json --gas-budget 10000 --package $SOLUTION_ADDRESS --module sol --function main --args $LENDER | jq ".[1].events" 145 | ``` 146 | 147 | ## `move lock` (300) 148 | 149 | - Source: [movectf-5](https://github.com/movebit/movectf-5), [local](./challenges/movectf-5) 150 | - Deploy: 151 | 152 | ```shell 153 | $ sui client publish --gas-budget 10000 --path ./challenges/movectf-5 154 | $ export PACKAGE_ADDRESS=0x... # and change address of `movectf` package in `Move.toml` 155 | $ export LENDER=0x... 156 | $ export RESOURCE_OBJECT=0x... 157 | ``` 158 | 159 | Again, we start from the end: in order for `Flag` event to be generated, it is necessary that `resource_object.q1 == true`. This will be true only under one condition — the return of `movectf_lock` function must be equal to `encrypted_flag`. 160 | 161 | Two arguments are used by `movectf_lock` function, later we will understand that these are `plaintext` and `key`, and at the output we get `ciphertext`. After looking at the function code, the first thing I thought about was SMT Solver, for example, Z3, which helped me out more than once when writing keygen or tricky exploits. 162 | 163 | Here I will not describe Z3 and its API, I will only tell you about the problems I encountered. My first attempt was [to implement the entire code](https://github.com/saruman9/move_ctf_writeup/blob/c7cf330446a5e033a9e33fe55b40619ff3f4013c/z3_sol/sol.py#L144-L250) head-on. In the during of developing, I ran the script every time, checking a speed. After writing the last constraint ([`ciphertext == complete_ciphertext`](https://github.com/saruman9/move_ctf_writeup/blob/c7cf330446a5e033a9e33fe55b40619ff3f4013c/z3_sol/sol.py#L226-L227)), Z3 hung deadly, could not check a satisfiability of even one iteration. In desperate attempts, I tried to add additional constraints to make it easier for Z3, but I was only able to get a satisfiability check, but a model was built indefinitely (everything was worked on a laptop, i.e. a mobile processor was used, maybe this is also the reason). In general, I have killed a lot of time optimizing the solver constraints. Much later, I decided to read the smart contract code thoughtfully. Here I found that it is possible to calculate the `key` ([`solve_key`](https://github.com/saruman9/move_ctf_writeup/blob/c7cf330446a5e033a9e33fe55b40619ff3f4013c/z3_sol/sol.py#L41-L122)) without problems, there is more than enough input data, you can even solve equations, SMT Solver is not needed here at all. Well, then I calculated `plaintext` ([`solve_plaintext`](https://github.com/saruman9/move_ctf_writeup/blob/c7cf330446a5e033a9e33fe55b40619ff3f4013c/z3_sol/sol.py#L4-L38)). 164 | 165 | I tried to pass the resulting answer — of course, a mistake. In general, I had [to rewrite the encryption algorithm in Rust](./encrypt) (it was not difficult, a primitive Move code is practically copied to Rust) to fix a bunch of arithmetic errors that I made when porting the Move code to Python Z3. 166 | 167 | Summary: 168 | 169 | ```shell 170 | $ python ./z3_sol/sol.py 171 | $ sui client call --gas-budget 1000000 --package $PACKAGE_ADDRESS --module "move_lock" --function "movectf_unlock" --args "[184, 14, ..., 65, 4, 695]" "[25, 11, 6, ..., 19, 2]" $RESOURCE_OBJECT 172 | $ sui client call --json --gas-budget 1000000 --package $PACKAGE_ADDRESS --module "move_lock" --function "get_flag" --args $RESOURCE_OBJECT | jq ".[1].events" 173 | ``` 174 | -------------------------------------------------------------------------------- /challenges/movectf-1/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "checkin" 3 | version = "0.0.1" 4 | 5 | [dependencies] 6 | Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework", rev = "devnet" } 7 | 8 | [addresses] 9 | movectf = "0x0" 10 | sui = "0000000000000000000000000000000000000002" 11 | -------------------------------------------------------------------------------- /challenges/movectf-1/sources/module.move: -------------------------------------------------------------------------------- 1 | module movectf::checkin { 2 | use sui::event; 3 | use sui::tx_context::{Self, TxContext}; 4 | 5 | struct Flag has copy, drop { 6 | user: address, 7 | flag: bool 8 | } 9 | 10 | public entry fun get_flag(ctx: &mut TxContext) { 11 | event::emit(Flag { 12 | user: tx_context::sender(ctx), 13 | flag: true 14 | }) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /challenges/movectf-4/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "flash" 3 | version = "0.0.1" 4 | 5 | [dependencies] 6 | Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework", rev = "devnet" } 7 | 8 | [addresses] 9 | movectf = "0x0" 10 | sui = "0000000000000000000000000000000000000002" 11 | -------------------------------------------------------------------------------- /challenges/movectf-4/sources/module.move: -------------------------------------------------------------------------------- 1 | module movectf::flash{ 2 | 3 | // use sui::object::{Self, UID}; 4 | // use std::vector; 5 | use sui::transfer; 6 | use sui::tx_context::{Self, TxContext}; 7 | use sui::object::{Self, ID, UID}; 8 | use sui::balance::{Self, Balance}; 9 | use sui::coin::{Self, Coin}; 10 | use sui::vec_map::{Self, VecMap}; 11 | use sui::event; 12 | // use std::debug; 13 | 14 | 15 | struct FLASH has drop {} 16 | 17 | struct FlashLender has key { 18 | id: UID, 19 | to_lend: Balance, 20 | last: u64, 21 | lender: VecMap 22 | } 23 | 24 | struct Receipt { 25 | flash_lender_id: ID, 26 | amount: u64 27 | } 28 | 29 | struct AdminCap has key, store { 30 | id: UID, 31 | flash_lender_id: ID, 32 | } 33 | 34 | struct Flag has copy, drop { 35 | user: address, 36 | flag: bool 37 | } 38 | 39 | // creat a FlashLender 40 | public fun create_lend(lend_coin: Coin, ctx: &mut TxContext) { 41 | let to_lend = coin::into_balance(lend_coin); 42 | let id = object::new(ctx); 43 | let lender = vec_map::empty(); 44 | let balance = balance::value(&to_lend); 45 | vec_map::insert(&mut lender ,tx_context::sender(ctx), balance); 46 | let flash_lender = FlashLender { id, to_lend, last: balance, lender}; 47 | transfer::share_object(flash_lender); 48 | } 49 | 50 | // get the loan 51 | public fun loan( 52 | self: &mut FlashLender, amount: u64, ctx: &mut TxContext 53 | ): (Coin, Receipt) { 54 | let to_lend = &mut self.to_lend; 55 | assert!(balance::value(to_lend) >= amount, 0); 56 | let loan = coin::take(to_lend, amount, ctx); 57 | let receipt = Receipt { flash_lender_id: object::id(self), amount }; 58 | 59 | (loan, receipt) 60 | } 61 | 62 | // repay coion to FlashLender 63 | public fun repay(self: &mut FlashLender, payment: Coin) { 64 | coin::put(&mut self.to_lend, payment) 65 | } 66 | 67 | // check the amount in FlashLender is correct 68 | public fun check(self: &mut FlashLender, receipt: Receipt) { 69 | let Receipt { flash_lender_id, amount: _ } = receipt; 70 | assert!(object::id(self) == flash_lender_id, 0); 71 | assert!(balance::value(&self.to_lend) >= self.last, 0); 72 | } 73 | 74 | // init Flash 75 | fun init(witness: FLASH, ctx: &mut TxContext) { 76 | let cap = coin::create_currency(witness, 2, ctx); 77 | let owner = tx_context::sender(ctx); 78 | 79 | let flash_coin = coin::mint(&mut cap, 1000, ctx); 80 | 81 | create_lend(flash_coin, ctx); 82 | transfer::transfer(cap, owner); 83 | } 84 | 85 | // get the balance of FlashLender 86 | public fun balance(self: &mut FlashLender, ctx: &mut TxContext) :u64 { 87 | *vec_map::get(&self.lender, &tx_context::sender(ctx)) 88 | } 89 | 90 | // deposit token to FlashLender 91 | public entry fun deposit( 92 | self: &mut FlashLender, coin: Coin, ctx: &mut TxContext 93 | ) { 94 | let sender = tx_context::sender(ctx); 95 | if (vec_map::contains(&self.lender, &sender)) { 96 | let balance = vec_map::get_mut(&mut self.lender, &sender); 97 | *balance = *balance + coin::value(&coin); 98 | }else { 99 | vec_map::insert(&mut self.lender, sender, coin::value(&coin)); 100 | }; 101 | coin::put(&mut self.to_lend, coin); 102 | } 103 | 104 | // withdraw you token from FlashLender 105 | public entry fun withdraw( 106 | self: &mut FlashLender, 107 | amount: u64, 108 | ctx: &mut TxContext 109 | ){ 110 | let owner = tx_context::sender(ctx); 111 | let balance = vec_map::get_mut(&mut self.lender, &owner); 112 | assert!(*balance >= amount, 0); 113 | *balance = *balance - amount; 114 | 115 | let to_lend = &mut self.to_lend; 116 | transfer::transfer(coin::take(to_lend, amount, ctx), owner); 117 | } 118 | 119 | // check whether you can get the flag 120 | public entry fun get_flag(self: &mut FlashLender, ctx: &mut TxContext) { 121 | if (balance::value(&self.to_lend) == 0) { 122 | event::emit(Flag { user: tx_context::sender(ctx), flag: true }); 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /challenges/movectf-5/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "move_lock" 3 | version = "0.0.1" 4 | 5 | [dependencies] 6 | Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework", rev = "devnet" } 7 | # Sui = { local = "/Users/chris/src/sui/crates/sui-framework/"} 8 | 9 | [addresses] 10 | movectf = "0x0" 11 | sui = "0000000000000000000000000000000000000002" 12 | -------------------------------------------------------------------------------- /challenges/movectf-5/sources/move_lock.move: -------------------------------------------------------------------------------- 1 | module movectf::move_lock { 2 | 3 | // [*] Import dependencies 4 | use std::vector; 5 | 6 | use sui::event; 7 | use sui::object::{Self, UID}; 8 | use sui::transfer; 9 | use sui::tx_context::{Self, TxContext}; 10 | 11 | // [*] Structs 12 | struct ResourceObject has key, store { 13 | id : UID, 14 | balance: u128, 15 | q1: bool 16 | } 17 | 18 | struct Flag has copy, drop { 19 | user: address, 20 | flag: bool 21 | } 22 | 23 | // [*] Module initializer 24 | fun init(ctx: &mut TxContext) { 25 | transfer::share_object(ResourceObject { 26 | id: object::new(ctx), 27 | balance: 100, 28 | q1: false, 29 | }) 30 | } 31 | 32 | // [*] Public functions 33 | public entry fun movectf_unlock(data1 : vector, data2 : vector, resource_object: &mut ResourceObject, _ctx: &mut TxContext) { 34 | 35 | let encrypted_flag : vector = vector[19, 16, 17, 11, 9, 21, 18, 36 | 2, 3, 22, 7, 4, 25, 21, 5, 37 | 7, 23, 6, 23, 5, 13, 3, 5, 38 | 9, 16, 12, 22, 14, 3, 14, 12, 39 | 22, 18, 4, 3, 9, 2, 19, 5, 40 | 16, 7, 20, 1, 11, 18, 23, 4, 41 | 15, 20, 5, 24, 9, 1, 12, 5, 42 | 16, 10, 7, 2, 1, 21, 1, 25, 43 | 18, 22, 2, 2, 7, 25, 15, 7, 10]; 44 | 45 | if (movectf_lock(data1, data2) == encrypted_flag) { 46 | if (!resource_object.q1) { 47 | resource_object.q1 = true; 48 | } 49 | } 50 | 51 | } 52 | 53 | fun movectf_lock(data1 : vector, data2 : vector) : vector { 54 | 55 | let input1 = copy data1; 56 | let plaintext = &mut input1; 57 | let plaintext_length = vector::length(plaintext); 58 | assert!(plaintext_length > 3, 0); 59 | 60 | if ( plaintext_length % 3 != 0) { 61 | if (3 - (plaintext_length % 3) == 2) { 62 | vector::push_back(plaintext, 0); 63 | vector::push_back(plaintext, 0); 64 | plaintext_length = plaintext_length + 2; 65 | } 66 | else { 67 | vector::push_back(plaintext, 0); 68 | plaintext_length = plaintext_length + 1; 69 | } 70 | }; 71 | 72 | let complete_plaintext = vector::empty(); 73 | vector::push_back(&mut complete_plaintext, 4); 74 | vector::push_back(&mut complete_plaintext, 15); 75 | vector::push_back(&mut complete_plaintext, 11); 76 | vector::push_back(&mut complete_plaintext, 0); 77 | vector::push_back(&mut complete_plaintext, 13); 78 | vector::push_back(&mut complete_plaintext, 4); 79 | vector::push_back(&mut complete_plaintext, 19); 80 | vector::push_back(&mut complete_plaintext, 19); 81 | vector::push_back(&mut complete_plaintext, 19); 82 | vector::append(&mut complete_plaintext, *plaintext); 83 | plaintext_length = plaintext_length + 9; 84 | 85 | let input2 = copy data2; 86 | let key = &mut input2; 87 | let a11 = *vector::borrow(key, 0); 88 | let a12 = *vector::borrow(key, 1); 89 | let a13 = *vector::borrow(key, 2); 90 | let a21 = *vector::borrow(key, 3); 91 | let a22 = *vector::borrow(key, 4); 92 | let a23 = *vector::borrow(key, 5); 93 | let a31 = *vector::borrow(key, 6); 94 | let a32 = *vector::borrow(key, 7); 95 | let a33 = *vector::borrow(key, 8); 96 | 97 | assert!(vector::length(key) == 9, 0); 98 | 99 | let i : u64 = 0; 100 | let ciphertext = vector::empty(); 101 | while (i < plaintext_length) { 102 | let p11 = *vector::borrow(&mut complete_plaintext, i+0); 103 | let p21 = *vector::borrow(&mut complete_plaintext, i+1); 104 | let p31 = *vector::borrow(&mut complete_plaintext, i+2); 105 | 106 | let c11 = ( (a11 * p11) + (a12 * p21) + (a13 * p31) ) % 26; 107 | let c21 = ( (a21 * p11) + (a22 * p21) + (a23 * p31) ) % 26; 108 | let c31 = ( (a31 * p11) + (a32 * p21) + (a33 * p31) ) % 26; 109 | 110 | vector::push_back(&mut ciphertext, c11); 111 | vector::push_back(&mut ciphertext, c21); 112 | vector::push_back(&mut ciphertext, c31); 113 | 114 | i = i + 3; 115 | }; 116 | 117 | ciphertext 118 | 119 | } 120 | 121 | public entry fun get_flag(resource_object: &ResourceObject, ctx: &mut TxContext) { 122 | if (resource_object.q1) { 123 | event::emit(Flag { user: tx_context::sender(ctx), flag: true }) 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /challenges/movectf-6/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ctf" 3 | version = "0.0.1" 4 | 5 | [dependencies] 6 | Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework", rev = "devnet" } 7 | 8 | [addresses] 9 | ctf = "0x0" 10 | game = "0x0" 11 | sui = "0x2" 12 | -------------------------------------------------------------------------------- /challenges/movectf-6/sources/adventure.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | /// Example of a game character with basic attributes, inventory, and 4 | /// associated logic. 5 | module game::adventure { 6 | use game::inventory; 7 | use game::hero::{Self, Hero}; 8 | use ctf::random; 9 | 10 | use sui::event; 11 | use sui::object::{Self, ID, UID}; 12 | use sui::transfer; 13 | use sui::tx_context::{Self, TxContext}; 14 | 15 | /// A creature that the hero can slay to level up 16 | struct Monster has key { 17 | id: UID, 18 | /// Hit points before the boar is slain 19 | hp: u64, 20 | /// Strength of this particular boar 21 | strength: u64, 22 | /// Defense of this particular boar 23 | defense: u64, 24 | } 25 | 26 | struct Boar {} 27 | struct BoarKing {} 28 | 29 | /// Event emitted each time a Hero slays a Boar 30 | struct SlainEvent has copy, drop { 31 | /// Address of the user that slayed the boar 32 | slayer_address: address, 33 | /// ID of the Hero that slayed the boar 34 | hero: ID, 35 | /// ID of the now-deceased boar 36 | boar: ID, 37 | } 38 | 39 | // TODO: proper error codes 40 | /// The hero is too tired to fight 41 | const EHERO_TIRED: u64 = 1; 42 | /// Trying to remove a sword, but the hero does not have one 43 | const ENO_SWORD: u64 = 4; 44 | const ENO_ARMOR: u64 = 5; 45 | 46 | 47 | /// Boar attributes values 48 | const BOAR_MIN_HP: u64 = 80; 49 | const BOAR_MAX_HP: u64 = 120; 50 | const BOAR_MIN_STRENGTH: u64 = 5; 51 | const BOAR_MAX_STRENGTH: u64 = 15; 52 | const BOAR_MIN_DEFENSE: u64 = 4; 53 | const BOAR_MAX_DEFENSE: u64 = 6; 54 | 55 | /// BoarKing attributes values 56 | const BOARKING_MIN_HP: u64 = 180; 57 | const BOARKING_MAX_HP: u64 = 220; 58 | const BOARKING_MIN_STRENGTH: u64 = 20; 59 | const BOARKING_MAX_STRENGTH: u64 = 25; 60 | const BOARKING_MIN_DEFENSE: u64 = 10; 61 | const BOARKING_MAX_DEFENSE: u64 = 15; 62 | 63 | fun create_monster( 64 | min_hp: u64, max_hp: u64, 65 | min_strength: u64, max_strength: u64, 66 | min_defense: u64, max_defense: u64, 67 | ctx: &mut TxContext 68 | ): Monster { 69 | let id = object::new(ctx); 70 | let hp = random::rand_u64_range(min_hp, max_hp, ctx); 71 | let strength = random::rand_u64_range(min_strength, max_strength, ctx); 72 | let defense = random::rand_u64_range(min_defense, max_defense, ctx); 73 | Monster { 74 | id, 75 | hp, 76 | strength, 77 | defense, 78 | } 79 | } 80 | 81 | // --- Gameplay --- 82 | 83 | /// Fight with the monster 84 | /// return: 0: tie, 1: hero win, 2: monster win; 85 | fun fight_monster(hero: &Hero, monster: &Monster): u64 { 86 | let hero_strength = hero::strength(hero); 87 | let hero_defense = hero::defense(hero); 88 | let hero_hp = hero::hp(hero); 89 | let monster_hp = monster.hp; 90 | // attack the monster until its HP goes to zero 91 | let cnt = 0u64; // max fight times 92 | let rst = 0u64; // 0: tie, 1: hero win, 2: monster win; 93 | while (monster_hp > 0) { 94 | // first, the hero attacks 95 | let damage = if (hero_strength > monster.defense) { 96 | hero_strength - monster.defense 97 | } else { 98 | 0 99 | }; 100 | if (damage < monster_hp) { 101 | monster_hp = monster_hp - damage; 102 | // then, the boar gets a turn to attack. if the boar would kill 103 | // the hero, abort--we can't let the boar win! 104 | let damage = if (monster.strength > hero_defense) { 105 | monster.strength - hero_defense 106 | } else { 107 | 0 108 | }; 109 | if (damage >= hero_hp) { 110 | rst = 2; 111 | break 112 | } else { 113 | hero_hp = hero_hp - damage; 114 | } 115 | } else { 116 | rst = 1; 117 | break 118 | }; 119 | cnt = cnt + 1; 120 | if (cnt > 20) { 121 | break 122 | } 123 | }; 124 | rst 125 | } 126 | 127 | public entry fun slay_boar(hero: &mut Hero, ctx: &mut TxContext) { 128 | assert!(hero::stamina(hero) > 0, EHERO_TIRED); 129 | let boar = create_monster( 130 | BOAR_MIN_HP, BOAR_MAX_HP, 131 | BOAR_MIN_STRENGTH, BOAR_MAX_STRENGTH, 132 | BOAR_MIN_DEFENSE, BOAR_MAX_DEFENSE, 133 | ctx 134 | ); 135 | let fight_result = fight_monster(hero, &boar); 136 | hero::decrease_stamina(hero, 1); 137 | // hero takes their licks 138 | if (fight_result == 1) { 139 | hero::increase_experience(hero, 1); 140 | 141 | let d100 = random::rand_u64_range(0, 100, ctx); 142 | if (d100 < 10) { 143 | let sword = inventory::create_sword(ctx); 144 | hero::equip_or_levelup_sword(hero, sword, ctx); 145 | } else if (d100 < 20) { 146 | let armor = inventory::create_armor(ctx); 147 | hero::equip_or_levelup_armor(hero, armor, ctx); 148 | }; 149 | }; 150 | // let the world know about the hero's triumph by emitting an event! 151 | event::emit(SlainEvent { 152 | slayer_address: tx_context::sender(ctx), 153 | hero: hero::id(hero), 154 | boar: object::uid_to_inner(&boar.id), 155 | }); 156 | let Monster { id, hp: _, strength: _, defense: _} = boar; 157 | object::delete(id); 158 | } 159 | 160 | public entry fun slay_boar_king(hero: &mut Hero, ctx: &mut TxContext) { 161 | assert!(hero::stamina(hero) > 0, EHERO_TIRED); 162 | let boar = create_monster( 163 | BOARKING_MIN_HP, BOARKING_MAX_HP, 164 | BOARKING_MIN_STRENGTH, BOARKING_MAX_STRENGTH, 165 | BOARKING_MIN_DEFENSE, BOARKING_MAX_DEFENSE, 166 | ctx 167 | ); 168 | let fight_result = fight_monster(hero, &boar); 169 | hero::decrease_stamina(hero, 2); 170 | // hero takes their licks 171 | if (fight_result == 1) { // hero won 172 | hero::increase_experience(hero, 2); 173 | 174 | let d100 = random::rand_u64_range(0, 100, ctx); 175 | if (d100 == 0) { 176 | let box = inventory::create_treasury_box(ctx); 177 | transfer::transfer(box, tx_context::sender(ctx)); 178 | }; 179 | }; 180 | // let the world know about the hero's triumph by emitting an event! 181 | event::emit(SlainEvent { 182 | slayer_address: tx_context::sender(ctx), 183 | hero: hero::id(hero), 184 | boar: object::uid_to_inner(&boar.id), 185 | }); 186 | let Monster { id, hp: _, strength: _, defense: _} = boar; 187 | object::delete(id); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /challenges/movectf-6/sources/hero.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | /// Example of a game character with basic attributes, inventory, and 4 | /// associated logic. 5 | module game::hero { 6 | use game::inventory::{Self, Sword, Armor}; 7 | 8 | use sui::object::{Self, ID, UID}; 9 | use sui::transfer; 10 | use sui::tx_context::TxContext; 11 | use std::option::{Self, Option}; 12 | 13 | friend game::adventure; 14 | 15 | 16 | /// Our hero! 17 | struct Hero has key, store { 18 | id: UID, 19 | /// Level of the hero. 20 | level: u64, 21 | /// Stamina points, once exhausted, the hero can't fight. 22 | stamina: u64, 23 | /// Hit points. If they go to zero, the hero can't do anything 24 | hp: u64, 25 | /// Experience of the hero. Begins at zero 26 | experience: u64, 27 | /// Level of the hero. High level will have more high attributes 28 | 29 | /// Attributes of the hero. Increasing with level and weapons. 30 | strength: u64, 31 | defense: u64, 32 | 33 | /// The hero's inventory 34 | sword: Option, 35 | armor: Option, 36 | } 37 | 38 | const MAX_LEVEL: u64 = 2; 39 | const INITAL_HERO_HP: u64 = 100; 40 | const INITIAL_HERO_STRENGTH: u64 = 10; 41 | const INITIAL_HERO_DEFENSE: u64 = 5; 42 | const HERO_STAMINA: u64 = 200; 43 | 44 | // TODO: proper error codes 45 | /// The boar won the battle 46 | const EBOAR_WON: u64 = 0; 47 | /// The hero is too tired to fight 48 | const EHERO_TIRED: u64 = 1; 49 | /// Trying to initialize from a non-admin account 50 | const ENOT_ADMIN: u64 = 2; 51 | /// Not enough money to purchase the given item 52 | const EINSUFFICIENT_FUNDS: u64 = 3; 53 | /// Trying to remove a sword, but the hero does not have one 54 | const ENO_SWORD: u64 = 4; 55 | const ENO_ARMOR: u64 = 5; 56 | /// Assertion errors for testing 57 | const ASSERT_ERR: u64 = 6; 58 | const EHERO_REACH_MAX_LEVEL: u64 = 7; 59 | 60 | // --- Object creation --- 61 | fun init(ctx: &mut TxContext) { 62 | let hero = create_hero(ctx); 63 | transfer::share_object(hero); 64 | } 65 | 66 | /// Create a hero. 67 | public(friend) fun create_hero(ctx: &mut TxContext): Hero { 68 | Hero { 69 | id: object::new(ctx), 70 | level: 1, 71 | stamina: HERO_STAMINA, 72 | hp: INITAL_HERO_HP, 73 | experience: 0, 74 | strength: INITIAL_HERO_STRENGTH, 75 | defense: INITIAL_HERO_DEFENSE, 76 | sword: option::none(), 77 | armor: option::none(), 78 | } 79 | } 80 | 81 | /// Strength of the hero when attacking 82 | public fun strength(hero: &Hero): u64 { 83 | // a hero with zero HP is too tired to fight 84 | if (hero.hp == 0) { 85 | return 0 86 | }; 87 | 88 | let sword_strength = if (option::is_some(&hero.sword)) { 89 | inventory::strength(option::borrow(&hero.sword)) 90 | } else { 91 | // hero can fight without a sword, but will not be very strong 92 | 0 93 | }; 94 | hero.strength + sword_strength 95 | } 96 | 97 | /// Defense of the hero when attacking 98 | public fun defense(hero: &Hero): u64 { 99 | // a hero with zero HP is too tired to fight 100 | if (hero.hp == 0) { 101 | return 0 102 | }; 103 | 104 | let armor_defense = if (option::is_some(&hero.armor)) { 105 | inventory::defense(option::borrow(&hero.armor)) 106 | } else { 107 | // hero can fight without a sword, but will not be very strong 108 | 0 109 | }; 110 | hero.defense + armor_defense 111 | } 112 | 113 | public fun hp(hero: &Hero): u64 { 114 | hero.hp 115 | } 116 | 117 | public fun experience(hero: &Hero): u64 { 118 | hero.experience 119 | } 120 | 121 | public fun stamina(hero: &Hero): u64 { 122 | hero.stamina 123 | } 124 | 125 | public(friend) fun increase_experience(hero: &mut Hero, experience: u64) { 126 | hero.experience = hero.experience + experience; 127 | } 128 | 129 | public(friend) fun id(hero: &Hero): ID { 130 | object::uid_to_inner(&hero.id) 131 | } 132 | 133 | public(friend) fun decrease_stamina(hero: &mut Hero, stamina: u64) { 134 | hero.stamina = hero.stamina - stamina; 135 | } 136 | 137 | public entry fun level_up(hero: &mut Hero) { 138 | assert!(hero.level < MAX_LEVEL, EHERO_REACH_MAX_LEVEL); 139 | if (hero.experience >= 100) { 140 | hero.level = hero.level + 1; 141 | hero.strength = hero.strength + INITIAL_HERO_STRENGTH; 142 | hero.defense = hero.defense + INITIAL_HERO_DEFENSE; 143 | hero.hp = hero.hp + INITAL_HERO_HP; 144 | hero.experience = hero.experience - 100; 145 | } 146 | } 147 | // --- Equipments --- 148 | /// Add `new_sword` to the hero's inventory and return the old sword 149 | /// (if any) 150 | public fun equip_or_levelup_sword(hero: &mut Hero, new_sword: Sword, ctx: &mut TxContext) { 151 | let sword = if (option::is_some(&hero.sword)) { 152 | let sword = option::extract(&mut hero.sword); 153 | inventory::level_up_sword(&mut sword, new_sword, ctx); 154 | sword 155 | } else { 156 | new_sword 157 | }; 158 | option::fill(&mut hero.sword, sword); 159 | } 160 | 161 | /// Disarm the hero by returning their sword. 162 | /// Aborts if the hero does not have a sword. 163 | public fun remove_sword(hero: &mut Hero): Sword { 164 | assert!(option::is_some(&hero.sword), ENO_SWORD); 165 | option::extract(&mut hero.sword) 166 | } 167 | 168 | /// Add `new_sword` to the hero's inventory and return the old sword 169 | /// (if any) 170 | public fun equip_or_levelup_armor(hero: &mut Hero, new_armor: Armor, ctx: &mut TxContext) { 171 | let armor = if (option::is_some(&hero.armor)) { 172 | let armor = option::extract(&mut hero.armor); 173 | inventory::level_up_armor(&mut armor, new_armor, ctx); 174 | armor 175 | } else { 176 | new_armor 177 | }; 178 | option::fill(&mut hero.armor, armor); 179 | } 180 | 181 | /// Disarm the hero by returning their armor. 182 | /// Aborts if the hero does not have a armor. 183 | public fun remove_armor(hero: &mut Hero): Armor { 184 | assert!(option::is_some(&hero.armor), ENO_ARMOR); 185 | option::extract(&mut hero.armor) 186 | } 187 | 188 | public fun destroy_hero(hero: Hero) { 189 | let Hero {id, level: _, stamina: _, hp: _, experience: _, strength: _, defense: _, sword, armor} = hero; 190 | object::delete(id); 191 | if (option::is_some(&sword)) { 192 | let sword = option::destroy_some(sword); 193 | inventory::destroy_sword(sword); 194 | } else { 195 | option::destroy_none(sword); 196 | }; 197 | if (option::is_some(&armor)) { 198 | let armor = option::destroy_some(armor); 199 | inventory::destroy_armor(armor); 200 | } else { 201 | option::destroy_none(armor); 202 | }; 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /challenges/movectf-6/sources/inventory.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | /// Equipments of hero 4 | module game::inventory { 5 | use ctf::random; 6 | 7 | use sui::object::{Self, UID}; 8 | use sui::tx_context::{Self, TxContext}; 9 | use sui::event; 10 | 11 | friend game::adventure; 12 | 13 | /// Upper bound on how magical a sword can be 14 | const MAX_RARITY: u64 = 5; 15 | const BASE_SWORD_STRENGTH: u64 = 2; 16 | const BASE_ARMOR_DEFENSE: u64 = 1; 17 | 18 | /// The hero's trusty sword 19 | struct Sword has store { 20 | /// Constant set at creation. Acts as a multiplier on sword's strength. 21 | /// Swords with high rarity are rarer, rarity ranges between [1, 5] 22 | rarity: u64, 23 | /// Strength that will be add to hero once equipped. 24 | strength: u64, 25 | } 26 | 27 | /// Armor 28 | struct Armor has store { 29 | /// Constant set at creation. Acts as a multiplier on armor's defense. 30 | /// Armors with high rarity are rarer, rarity ranges between [1, 5] 31 | rarity: u64, 32 | /// Defense that will be add to hero once euipped. 33 | defense: u64, 34 | } 35 | 36 | struct TreasuryBox has key, store { 37 | id: UID, 38 | } 39 | 40 | struct Flag has copy, drop { 41 | user: address, 42 | flag: bool 43 | } 44 | 45 | public(friend) fun create_treasury_box(ctx: &mut TxContext): TreasuryBox { 46 | TreasuryBox { 47 | id: object::new(ctx) 48 | } 49 | } 50 | 51 | /// Create a new sword, the rarity value is random generated. 52 | public(friend) fun create_sword(_ctx: &mut TxContext): Sword { 53 | Sword { 54 | rarity: 1, 55 | strength: BASE_SWORD_STRENGTH, 56 | } 57 | } 58 | 59 | public fun destroy_sword(sword: Sword) { 60 | let Sword { rarity: _, strength: _} = sword; 61 | } 62 | 63 | /// Create a new armor, the rarity value is random generated. 64 | public(friend) fun create_armor(_ctx: &mut TxContext): Armor { 65 | Armor { 66 | rarity: 1, 67 | defense: BASE_ARMOR_DEFENSE, 68 | } 69 | } 70 | 71 | public fun destroy_armor(armor: Armor) { 72 | let Armor { rarity: _, defense: _} = armor; 73 | } 74 | 75 | /// Get the strength of a sword: rarity * strength 76 | public fun strength(sword: &Sword): u64 { 77 | sword.strength * sword.rarity 78 | } 79 | 80 | /// Get the defense of an armor: rarity * defense 81 | public fun defense(armor: &Armor): u64 { 82 | armor.defense * armor.rarity 83 | } 84 | 85 | public fun sword_rarity(sword: &Sword): u64 { 86 | sword.rarity 87 | } 88 | 89 | public fun armor_rarity(armor: &Armor): u64 { 90 | armor.rarity 91 | } 92 | 93 | /// Level up a sword with another one. 94 | /// The probability of success is: 1 / sword.rarity. 95 | public fun level_up_sword(sword: &mut Sword, material: Sword, ctx: &mut TxContext) { 96 | if (sword.rarity < MAX_RARITY) { 97 | let prob = random::rand_u64_range(0, sword.rarity, ctx); 98 | if (prob < 1) { 99 | sword.rarity = sword.rarity + 1; 100 | } 101 | }; 102 | destroy_sword(material); 103 | } 104 | 105 | /// Level up an armor with another one. 106 | /// The probability of success is: 1 / armor.rarity. 107 | public fun level_up_armor(armor: &mut Armor, material: Armor, ctx: &mut TxContext) { 108 | if (armor.rarity < MAX_RARITY) { 109 | let prob = random::rand_u64_range(0, armor.rarity, ctx); 110 | if (prob < 1) { 111 | armor.rarity = armor.rarity + 1; 112 | } 113 | }; 114 | destroy_armor(material); 115 | } 116 | 117 | public entry fun get_flag(box: TreasuryBox, ctx: &mut TxContext) { 118 | let TreasuryBox { id } = box; 119 | object::delete(id); 120 | let d100 = random::rand_u64_range(0, 100, ctx); 121 | if (d100 == 0) { 122 | event::emit(Flag { user: tx_context::sender(ctx), flag: true }); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /challenges/movectf-6/sources/random.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Based on: https://github.com/starcoinorg/starcoin-framework-commons/blob/main/sources/PseudoRandom.move 3 | 4 | /// @title pseudorandom 5 | /// @notice A pseudo random module on-chain. 6 | /// @dev Warning: 7 | /// The random mechanism in smart contracts is different from 8 | /// that in traditional programming languages. The value generated 9 | /// by random is predictable to Miners, so it can only be used in 10 | /// simple scenarios where Miners have no incentive to cheat. If 11 | /// large amounts of money are involved, DO NOT USE THIS MODULE to 12 | /// generate random numbers; try a more secure way. 13 | module ctf::random { 14 | use std::hash; 15 | use std::vector; 16 | 17 | use sui::bcs; 18 | use sui::object; 19 | use sui::tx_context::TxContext; 20 | 21 | const ERR_HIGH_ARG_GREATER_THAN_LOW_ARG: u64 = 101; 22 | 23 | fun seed(ctx: &mut TxContext): vector { 24 | let ctx_bytes = bcs::to_bytes(ctx); 25 | let uid = object::new(ctx); 26 | let uid_bytes: vector = object::uid_to_bytes(&uid); 27 | object::delete(uid); 28 | 29 | let info: vector = vector::empty(); 30 | vector::append(&mut info, ctx_bytes); 31 | vector::append(&mut info, uid_bytes); 32 | 33 | let hash: vector = hash::sha3_256(info); 34 | hash 35 | } 36 | 37 | fun bytes_to_u64(bytes: vector): u64 { 38 | let value = 0u64; 39 | let i = 0u64; 40 | while (i < 8) { 41 | value = value | ((*vector::borrow(&bytes, i) as u64) << ((8 * (7 - i)) as u8)); 42 | i = i + 1; 43 | }; 44 | return value 45 | } 46 | 47 | /// Generate a random u64 48 | fun rand_u64_with_seed(_seed: vector): u64 { 49 | bytes_to_u64(_seed) 50 | } 51 | 52 | /// Generate a random integer range in [low, high). 53 | fun rand_u64_range_with_seed(_seed: vector, low: u64, high: u64): u64 { 54 | assert!(high > low, ERR_HIGH_ARG_GREATER_THAN_LOW_ARG); 55 | let value = rand_u64_with_seed(_seed); 56 | (value % (high - low)) + low 57 | } 58 | 59 | /// Generate a random u64 60 | public fun rand_u64(ctx: &mut TxContext): u64 { 61 | rand_u64_with_seed(seed(ctx)) 62 | } 63 | 64 | /// Generate a random integer range in [low, high). 65 | public fun rand_u64_range(low: u64, high: u64, ctx: &mut TxContext): u64 { 66 | rand_u64_range_with_seed(seed(ctx), low, high) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /encrypt/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /encrypt/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "encrypt" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /encrypt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "encrypt" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /encrypt/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let mut plaintext = vec![ 3 | 184, 14, 2015, 58, 43, 1118, 71, 72, 1649, 156, 123, 3154, 92, 65, 1760, 102, 66, 1866, 4 | 194, 156, 3965, 26, 6, 316, 575, 71, 6670, 53, 43, 1070, 182, 140, 3633, 163, 134, 3361, 5 | 86, 63, 1675, 184, 164, 3967, 7, 4, 121, 189, 156, 3902, 114, 97, 2399, 56, 7, 650, 28, 10, 6 | 388, 15, 37, 624, 65, 4, 695, 7 | ]; 8 | let key = vec![25, 11, 6, 166, 91, 25, 558, 19, 2]; 9 | // let mut ciphertext = vec![ 10 | // 19, 16, 17, 11, 9, 21, 18, 2, 3, 22, 7, 4, 25, 21, 5, 7, 23, 6, 23, 5, 13, 3, 5, 9, 16, 12, 11 | // 22, 14, 3, 14, 12, 22, 18, 4, 3, 9, 2, 19, 5, 16, 7, 20, 1, 11, 18, 23, 4, 15, 20, 5, 24, 12 | // 9, 1, 12, 5, 16, 10, 7, 2, 1, 21, 1, 25, 18, 22, 2, 2, 7, 25, 15, 7, 10, 13 | // ]; 14 | println!("{:03?}", encrypt(&mut plaintext, &key)); 15 | // println!("{:03?}", decrypt(&mut ciphertext, &key)); 16 | } 17 | 18 | // fn decrypt(ciphertext: &mut Vec, key: &[u64]) -> Vec { 19 | // let ciphertext = ciphertext.to_vec(); 20 | // let (_, mut ciphertext) = ciphertext.split_at(9); 21 | // let mut ciphertext = ciphertext.to_vec(); 22 | // let c31 = ciphertext.remove(0); 23 | // let c21 = ciphertext.remove(0); 24 | // let c11 = ciphertext.remove(0); 25 | // todo!(); 26 | // } 27 | 28 | fn encrypt(plaintext: &mut Vec, key: &[u64]) -> Vec { 29 | assert!(plaintext.len() > 3); 30 | let mut plaintext_len = plaintext.len(); 31 | 32 | if plaintext_len % 3 != 0 { 33 | if (3 - plaintext_len % 3) == 2 { 34 | plaintext.push(0); 35 | plaintext.push(0); 36 | plaintext_len += 2; 37 | } else { 38 | plaintext.push(0); 39 | plaintext_len += 1; 40 | } 41 | } 42 | let mut complete_plaintext = vec![4, 15, 11, 0, 13, 4, 19, 19, 19]; 43 | plaintext_len += 9; 44 | complete_plaintext.append(plaintext); 45 | 46 | let a11 = key[0]; 47 | let a12 = key[1]; 48 | let a13 = key[2]; 49 | let a21 = key[3]; 50 | let a22 = key[4]; 51 | let a23 = key[5]; 52 | let a31 = key[6]; 53 | let a32 = key[7]; 54 | let a33 = key[8]; 55 | assert!(key.len() == 9); 56 | let mut ciphertext = Vec::new(); 57 | let mut i = 0; 58 | while i < plaintext_len { 59 | let p11 = complete_plaintext[i]; 60 | let p21 = complete_plaintext[i + 1]; 61 | let p31 = complete_plaintext[i + 2]; 62 | 63 | let c11 = ((a11 * p11) + (a12 * p21) + (a13 * p31)) % 26; 64 | let c21 = ((a21 * p11) + (a22 * p21) + (a23 * p31)) % 26; 65 | let c31 = ((a31 * p11) + (a32 * p21) + (a33 * p31)) % 26; 66 | 67 | ciphertext.push(c11); 68 | ciphertext.push(c21); 69 | ciphertext.push(c31); 70 | 71 | i += 3; 72 | } 73 | ciphertext 74 | } 75 | -------------------------------------------------------------------------------- /flash_sol/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "flash_sol" 3 | version = "0.0.1" 4 | 5 | [dependencies] 6 | Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework", rev = "devnet" } 7 | flash = { local = "../challenges/movectf-4" } 8 | 9 | [addresses] 10 | flash_sol = "0x0" 11 | sui = "0000000000000000000000000000000000000002" 12 | -------------------------------------------------------------------------------- /flash_sol/sources/module.move: -------------------------------------------------------------------------------- 1 | module flash_sol::sol{ 2 | 3 | use sui::tx_context::TxContext; 4 | use movectf::flash::{Self, FlashLender}; 5 | 6 | public entry fun main(lender: &mut FlashLender, ctx: &mut TxContext) { 7 | let (coins, receipt) = flash::loan(lender, 1000, ctx); 8 | flash::get_flag(lender, ctx); 9 | flash::repay(lender, coins); 10 | flash::check(lender, receipt); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /hero_sol/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hero_sol" 3 | version = "0.0.1" 4 | 5 | [dependencies] 6 | Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework", rev = "devnet" } 7 | ctf = { local = "../challenges/movectf-6" } 8 | 9 | [addresses] 10 | hero_sol = "0x0" 11 | sui = "0x2" 12 | -------------------------------------------------------------------------------- /hero_sol/sources/sol.move: -------------------------------------------------------------------------------- 1 | module hero_sol::sol { 2 | use game::hero::{Self, Hero}; 3 | use game::adventure::slay_boar; 4 | use ctf::random::rand_u64_range; 5 | use sui::tx_context::TxContext; 6 | 7 | public entry fun random(ctx: &mut TxContext) { 8 | assert!(rand_u64_range(0, 100, ctx) == 0, 1337); 9 | } 10 | 11 | public entry fun kill_slay_boar(hero: &mut Hero, ctx: &mut TxContext) { 12 | let i = 150; 13 | while (i > 0) { 14 | slay_boar(hero, ctx); 15 | i = i - 1; 16 | }; 17 | hero::level_up(hero); 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /z3_sol/sol.py: -------------------------------------------------------------------------------- 1 | from z3 import * 2 | 3 | 4 | def solve_plaintext(): 5 | data = [19, 16, 17, 11, 9, 21, 18, 2, 6 | 3, 22, 7, 4, 25, 21, 5, 7, 7 | 23, 6, 23, 5, 13, 3, 5, 9, 8 | 16, 12, 22, 14, 3, 14, 12, 22, 9 | 18, 4, 3, 9, 2, 19, 5, 16, 10 | 7, 20, 1, 11, 18, 23, 4, 15, 11 | 20, 5, 24, 9, 1, 12, 5, 16, 12 | 10, 7, 2, 1, 21, 1, 25, 18, 13 | 22, 2, 2, 7, 25, 15, 7, 10] 14 | key = [25, 11, 6, 166, 91, 25, 558, 19, 2] 15 | 16 | i = 0 17 | while i < 63: 18 | solver = Solver() 19 | p11 = Int("p11") 20 | p21 = Int("p21") 21 | p31 = Int("p31") 22 | solver.add(p11 > 0) 23 | solver.add(p21 > 0) 24 | solver.add(p31 > 0) 25 | solver.add(data[i + 0 + 9] == 26 | (key[0] * p11 + key[1] * p21 + key[2] * p31) % 26) 27 | solver.add(data[i + 1 + 9] == 28 | (key[3] * p11 + key[4] * p21 + key[5] * p31) % 26) 29 | solver.add(data[i + 2 + 9] == 30 | (key[6] * p11 + key[7] * p21 + key[8] * p31) % 26) 31 | if solver.check() == sat: 32 | m = solver.model() 33 | print("%s" % m.evaluate(p11), end=', ') 34 | print("%s" % m.evaluate(p21), end=', ') 35 | print("%s" % m.evaluate(p31), end=', ') 36 | i += 3 37 | 38 | exit(0) 39 | 40 | 41 | def solve_key(): 42 | solver = Solver() 43 | 44 | p11_1 = Int("p11_1") 45 | p21_1 = Int("p21_1") 46 | p31_1 = Int("p31_1") 47 | solver.add(p11_1 == 4) 48 | solver.add(p21_1 == 15) 49 | solver.add(p31_1 == 11) 50 | p11_2 = Int("p11_2") 51 | p21_2 = Int("p21_2") 52 | p31_2 = Int("p31_2") 53 | solver.add(p11_2 == 0) 54 | solver.add(p21_2 == 13) 55 | solver.add(p31_2 == 4) 56 | p11_3 = Int("p11_3") 57 | p21_3 = Int("p21_3") 58 | p31_3 = Int("p31_3") 59 | solver.add(p11_3 == 19) 60 | solver.add(p21_3 == 19) 61 | solver.add(p31_3 == 19) 62 | 63 | a11 = Int("a11") 64 | a12 = Int("a12") 65 | a13 = Int("a13") 66 | a21 = Int("a21") 67 | a22 = Int("a22") 68 | a23 = Int("a23") 69 | a31 = Int("a31") 70 | a32 = Int("a32") 71 | a33 = Int("a33") 72 | solver.add(a11 >= 0) 73 | solver.add(a12 >= 0) 74 | solver.add(a13 >= 0) 75 | solver.add(a21 >= 0) 76 | solver.add(a22 >= 0) 77 | solver.add(a23 >= 0) 78 | solver.add(a31 >= 0) 79 | solver.add(a32 >= 0) 80 | solver.add(a33 >= 0) 81 | 82 | c11_1 = Int("c11_1") 83 | c21_1 = Int("c21_1") 84 | c31_1 = Int("c31_1") 85 | solver.add(c11_1 == 19) 86 | solver.add(c21_1 == 16) 87 | solver.add(c31_1 == 17) 88 | c11_2 = Int("c11_2") 89 | c21_2 = Int("c21_2") 90 | c31_2 = Int("c31_2") 91 | solver.add(c11_2 == 11) 92 | solver.add(c21_2 == 9) 93 | solver.add(c31_2 == 21) 94 | c11_3 = Int("c11_3") 95 | c21_3 = Int("c21_3") 96 | c31_3 = Int("c31_3") 97 | solver.add(c11_3 == 18) 98 | solver.add(c21_3 == 2) 99 | solver.add(c31_3 == 3) 100 | 101 | solver.add(c11_1 == ((a11 * p11_1) + (a12 * p21_1) + (a13 * p31_1)) % 26) 102 | solver.add(c21_1 == ((a21 * p11_1) + (a22 * p21_1) + (a23 * p31_1)) % 26) 103 | solver.add(c31_1 == ((a31 * p11_1) + (a32 * p21_1) + (a33 * p31_1)) % 26) 104 | solver.add(c11_2 == ((a11 * p11_2) + (a12 * p21_2) + (a13 * p31_2)) % 26) 105 | solver.add(c21_2 == ((a21 * p11_2) + (a22 * p21_2) + (a23 * p31_2)) % 26) 106 | solver.add(c31_2 == ((a31 * p11_2) + (a32 * p21_2) + (a33 * p31_2)) % 26) 107 | solver.add(c11_3 == ((a11 * p11_3) + (a12 * p21_3) + (a13 * p31_3)) % 26) 108 | solver.add(c21_3 == ((a21 * p11_3) + (a22 * p21_3) + (a23 * p31_3)) % 26) 109 | solver.add(c31_3 == ((a31 * p11_3) + (a32 * p21_3) + (a33 * p31_3)) % 26) 110 | 111 | if solver.check() == sat: 112 | m = solver.model() 113 | print("%s" % m.evaluate(a11), end=', ') 114 | print("%s" % m.evaluate(a12), end=', ') 115 | print("%s" % m.evaluate(a13), end=', ') 116 | print("%s" % m.evaluate(a21), end=', ') 117 | print("%s" % m.evaluate(a22), end=', ') 118 | print("%s" % m.evaluate(a23), end=', ') 119 | print("%s" % m.evaluate(a31), end=', ') 120 | print("%s" % m.evaluate(a32), end=', ') 121 | print("%s" % m.evaluate(a33)) 122 | # exit(0) 123 | 124 | 125 | def asset_vector(solver, vec, start_index, data): 126 | for offset in range(len(data)): 127 | solver.add(vec[start_index + offset] == data[offset]) 128 | 129 | 130 | def all_unsigned(solver, vec): 131 | for offset in range(len(vec)): 132 | solver.add(vec[offset] >= 0) 133 | 134 | 135 | def lower_26(solver, vec): 136 | for offset in range(len(vec)): 137 | solver.add(vec[offset] < 26) 138 | 139 | 140 | if __name__ == "__main__": 141 | solve_key() 142 | solve_plaintext() 143 | 144 | for j in range(13, 15): 145 | solver = Solver() 146 | plaintext_len = j 147 | 148 | if plaintext_len % 3 != 0: 149 | if (3 - (plaintext_len % 3)) == 2: 150 | plaintext_len += 2 151 | plaintext = IntVector("plaintext", plaintext_len) 152 | solver.add(plaintext[plaintext_len - 1] == 0) 153 | solver.add(plaintext[plaintext_len - 2] == 0) 154 | else: 155 | plaintext_len += 1 156 | plaintext = IntVector("plaintext", plaintext_len) 157 | solver.add(plaintext[plaintext_len - 1] == 0) 158 | else: 159 | plaintext = IntVector("plaintext", plaintext_len) 160 | 161 | complete_plaintext = IntVector("complete_plaintext", 9 + plaintext_len) 162 | solver.add(complete_plaintext[0] == 4) 163 | solver.add(complete_plaintext[1] == 15) 164 | solver.add(complete_plaintext[2] == 11) 165 | solver.add(complete_plaintext[3] == 0) 166 | solver.add(complete_plaintext[4] == 13) 167 | solver.add(complete_plaintext[5] == 4) 168 | solver.add(complete_plaintext[6] == 19) 169 | solver.add(complete_plaintext[7] == 19) 170 | solver.add(complete_plaintext[8] == 19) 171 | for i in range(9, plaintext_len+9): 172 | solver.add(complete_plaintext[i] == plaintext[i-9]) 173 | 174 | key = IntVector("key", 9) 175 | a11 = Int("a11") 176 | a12 = Int("a12") 177 | a13 = Int("a13") 178 | a21 = Int("a21") 179 | a22 = Int("a22") 180 | a23 = Int("a23") 181 | a31 = Int("a31") 182 | a32 = Int("a32") 183 | a33 = Int("a33") 184 | solver.add(a11 == key[0]) 185 | solver.add(a12 == key[1]) 186 | solver.add(a13 == key[2]) 187 | solver.add(a21 == key[3]) 188 | solver.add(a22 == key[4]) 189 | solver.add(a23 == key[5]) 190 | solver.add(a31 == key[6]) 191 | solver.add(a32 == key[7]) 192 | solver.add(a33 == key[8]) 193 | 194 | data = [19, 16, 17, 11, 9, 21, 18, 2, 195 | 3, 22, 7, 4, 25, 21, 5, 7, 196 | 23, 6, 23, 5, 13, 3, 5, 9, 197 | 16, 12, 22, 14, 3, 14, 12, 22, 198 | 18, 4, 3, 9, 2, 19, 5, 16, 199 | 7, 20, 1, 11, 18, 23, 4, 15, 200 | 20, 5, 24, 9, 1, 12, 5, 16, 201 | 10, 7, 2, 1, 21, 1, 25, 18, 202 | 22, 2, 2, 7, 25, 15, 7, 10] 203 | complete_ciphertext = IntVector("complete_ciphertext", len(data)) 204 | ciphertext = IntVector("ciphertext", len(data)) 205 | asset_vector(solver, ciphertext, 0, data) 206 | 207 | for i in range(0, plaintext_len + 9, 3): 208 | p11 = Int("p11_%s" % i) 209 | p21 = Int("p21_%s" % i) 210 | p31 = Int("p31_%s" % i) 211 | solver.add(p11 == complete_plaintext[i+0]) 212 | solver.add(p21 == complete_plaintext[i+1]) 213 | solver.add(p31 == complete_plaintext[i+2]) 214 | 215 | c11 = Int("c11_%s" % i) 216 | c21 = Int("c21_%s" % i) 217 | c31 = Int("c31_%s" % i) 218 | solver.add(c11 == ((a11 * p11) + (a12 * p21) + (a13 * p31)) % 26) 219 | solver.add(c21 == ((a21 * p11) + (a22 * p21) + (a23 * p31)) % 26) 220 | solver.add(c31 == ((a31 * p11) + (a32 * p21) + (a33 * p31)) % 26) 221 | 222 | solver.add(complete_ciphertext[i + 0] == c11) 223 | solver.add(complete_ciphertext[i + 1] == c21) 224 | solver.add(complete_ciphertext[i + 2] == c31) 225 | 226 | for i in range(len(data)): 227 | solver.add(ciphertext[i] == complete_ciphertext[i]) 228 | 229 | all_unsigned(solver, plaintext) 230 | all_unsigned(solver, complete_plaintext) 231 | all_unsigned(solver, ciphertext) 232 | all_unsigned(solver, complete_ciphertext) 233 | all_unsigned(solver, key) 234 | lower_26(solver, ciphertext) 235 | lower_26(solver, complete_ciphertext) 236 | 237 | print(solver.sexpr()) 238 | print(plaintext_len, solver.check()) 239 | if solver.check() == sat: 240 | m = solver.model() 241 | final_plaintext = [] 242 | final_key = [] 243 | for i in range(plaintext_len): 244 | final_plaintext.append(m.evaluate(plaintext[i], 245 | model_completion=True)) 246 | for i in range(9): 247 | final_key.append(m.evaluate(key[i], model_completion=True)) 248 | print("plaintext = %s" % final_plaintext) 249 | print("key = %s" % final_key) 250 | exit(0) 251 | --------------------------------------------------------------------------------