├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── lib.rs └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "az" 7 | version = "1.2.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" 10 | 11 | [[package]] 12 | name = "base64" 13 | version = "0.22.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 16 | 17 | [[package]] 18 | name = "bitflags" 19 | version = "2.9.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 22 | 23 | [[package]] 24 | name = "cfg-if" 25 | version = "1.0.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 28 | 29 | [[package]] 30 | name = "getrandom" 31 | version = "0.3.3" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" 34 | dependencies = [ 35 | "cfg-if", 36 | "libc", 37 | "r-efi", 38 | "wasi", 39 | ] 40 | 41 | [[package]] 42 | name = "gmp-mpfr-sys" 43 | version = "1.6.5" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "c66d61197a68f6323b9afa616cf83d55d69191e1bf364d4eb7d35ae18defe776" 46 | dependencies = [ 47 | "libc", 48 | "windows-sys", 49 | ] 50 | 51 | [[package]] 52 | name = "kctf-pow" 53 | version = "2.1.0" 54 | dependencies = [ 55 | "base64", 56 | "rand", 57 | "rug", 58 | ] 59 | 60 | [[package]] 61 | name = "libc" 62 | version = "0.2.172" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 65 | 66 | [[package]] 67 | name = "libm" 68 | version = "0.2.15" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" 71 | 72 | [[package]] 73 | name = "ppv-lite86" 74 | version = "0.2.21" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 77 | dependencies = [ 78 | "zerocopy", 79 | ] 80 | 81 | [[package]] 82 | name = "proc-macro2" 83 | version = "1.0.95" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 86 | dependencies = [ 87 | "unicode-ident", 88 | ] 89 | 90 | [[package]] 91 | name = "quote" 92 | version = "1.0.40" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 95 | dependencies = [ 96 | "proc-macro2", 97 | ] 98 | 99 | [[package]] 100 | name = "r-efi" 101 | version = "5.2.0" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 104 | 105 | [[package]] 106 | name = "rand" 107 | version = "0.9.1" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" 110 | dependencies = [ 111 | "rand_chacha", 112 | "rand_core", 113 | ] 114 | 115 | [[package]] 116 | name = "rand_chacha" 117 | version = "0.9.0" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 120 | dependencies = [ 121 | "ppv-lite86", 122 | "rand_core", 123 | ] 124 | 125 | [[package]] 126 | name = "rand_core" 127 | version = "0.9.3" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 130 | dependencies = [ 131 | "getrandom", 132 | ] 133 | 134 | [[package]] 135 | name = "rug" 136 | version = "1.27.0" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "4207e8d668e5b8eb574bda8322088ccd0d7782d3d03c7e8d562e82ed82bdcbc3" 139 | dependencies = [ 140 | "az", 141 | "gmp-mpfr-sys", 142 | "libc", 143 | "libm", 144 | ] 145 | 146 | [[package]] 147 | name = "syn" 148 | version = "2.0.101" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" 151 | dependencies = [ 152 | "proc-macro2", 153 | "quote", 154 | "unicode-ident", 155 | ] 156 | 157 | [[package]] 158 | name = "unicode-ident" 159 | version = "1.0.18" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 162 | 163 | [[package]] 164 | name = "wasi" 165 | version = "0.14.2+wasi-0.2.4" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 168 | dependencies = [ 169 | "wit-bindgen-rt", 170 | ] 171 | 172 | [[package]] 173 | name = "windows-sys" 174 | version = "0.59.0" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 177 | dependencies = [ 178 | "windows-targets", 179 | ] 180 | 181 | [[package]] 182 | name = "windows-targets" 183 | version = "0.52.6" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 186 | dependencies = [ 187 | "windows_aarch64_gnullvm", 188 | "windows_aarch64_msvc", 189 | "windows_i686_gnu", 190 | "windows_i686_gnullvm", 191 | "windows_i686_msvc", 192 | "windows_x86_64_gnu", 193 | "windows_x86_64_gnullvm", 194 | "windows_x86_64_msvc", 195 | ] 196 | 197 | [[package]] 198 | name = "windows_aarch64_gnullvm" 199 | version = "0.52.6" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 202 | 203 | [[package]] 204 | name = "windows_aarch64_msvc" 205 | version = "0.52.6" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 208 | 209 | [[package]] 210 | name = "windows_i686_gnu" 211 | version = "0.52.6" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 214 | 215 | [[package]] 216 | name = "windows_i686_gnullvm" 217 | version = "0.52.6" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 220 | 221 | [[package]] 222 | name = "windows_i686_msvc" 223 | version = "0.52.6" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 226 | 227 | [[package]] 228 | name = "windows_x86_64_gnu" 229 | version = "0.52.6" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 232 | 233 | [[package]] 234 | name = "windows_x86_64_gnullvm" 235 | version = "0.52.6" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 238 | 239 | [[package]] 240 | name = "windows_x86_64_msvc" 241 | version = "0.52.6" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 244 | 245 | [[package]] 246 | name = "wit-bindgen-rt" 247 | version = "0.39.0" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 250 | dependencies = [ 251 | "bitflags", 252 | ] 253 | 254 | [[package]] 255 | name = "zerocopy" 256 | version = "0.8.25" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" 259 | dependencies = [ 260 | "zerocopy-derive", 261 | ] 262 | 263 | [[package]] 264 | name = "zerocopy-derive" 265 | version = "0.8.25" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" 268 | dependencies = [ 269 | "proc-macro2", 270 | "quote", 271 | "syn", 272 | ] 273 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kctf-pow" 3 | version = "2.1.0" 4 | edition = "2024" 5 | description = "A library and CLI to solve, check, and generate proof-of-work challenges using kCTF's scheme." 6 | license = "BSD-3-Clause" 7 | authors = ["Aplet123 "] 8 | repository = "https://github.com/Aplet123/kctf-pow" 9 | documentation = "https://docs.rs/kctf-pow" 10 | 11 | [dependencies] 12 | rug = { version = "1.27.0", features = ["integer", "std"], default-features = false } 13 | rand = { version = "0.9.1", features = ["thread_rng"], default-features = false} 14 | base64 = "0.22.1" 15 | 16 | [lib] 17 | name = "kctf_pow" 18 | path = "src/lib.rs" 19 | 20 | [[bin]] 21 | name = "kctf-pow" 22 | path = "src/main.rs" 23 | doc = false 24 | 25 | [profile.release] 26 | opt-level = 3 27 | lto = "fat" 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, redpwn 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kctf-pow 2 | 3 | A library and CLI to solve, check, and generate proof-of-work challenges using [kCTF](https://google.github.io/kctf/)'s scheme. 4 | 5 | # Installation 6 | 7 | For use as a library, add the [`kctf-pow`](https://crates.io/crates/kctf-pow) crate into your dependencies. 8 | 9 | The CLI can be installed with `cargo install kctf-pow`, or by cloning the repository, building with `cargo build --release`, and manually copying the executable. 10 | 11 | # CLI Usage 12 | 13 | To solve a challenge and print the solution to stdout: 14 | ``` 15 | kctf-pow solve 16 | ``` 17 | For example: 18 | ```bash 19 | kctf-pow solve s.AAAAMg==.NDtqORW1uZlIgzszbdMGZA== 20 | # Outputs s.NUH3arymnKB+ysUGdv+67ypDamn4wOKCPORB2ivWE1Yhinam2v4S6q4nAoC5LP97LScdVoq+NuFVF++Win5mNRYZS6bJAs8fk0h8XgvfcC/7JfmFISqeCIo/CIUgIucVAM+eGDjqitRULGXqIOyviJoJjW8DMouMRuJM/3eg/z18kutQHkX0N3sqPeF7Nzkk8S3Bs6aiHUORM30syUKYug== 21 | ``` 22 | To check a solution for a challenge: 23 | ``` 24 | kctf-pow check 25 | ``` 26 | The solution is read from stdin. If the solution is correct, the program will exit with status code 0 and `correct` will be outputted. If the solution is incorrect, the program will exit with status code 1 and `incorrect` will be outputted. If the solution is malformed, the program will exit with status code 1 and an error message will be printed to stderr. 27 | 28 | For example: 29 | ```bash 30 | kctf-pow check s.AAAAMg==.NDtqORW1uZlIgzszbdMGZA== 31 | # Input s.NUH3arymnKB+ysUGdv+67ypDamn4wOKCPORB2ivWE1Yhinam2v4S6q4nAoC5LP97LScdVoq+NuFVF++Win5mNRYZS6bJAs8fk0h8XgvfcC/7JfmFISqeCIo/CIUgIucVAM+eGDjqitRULGXqIOyviJoJjW8DMouMRuJM/3eg/z18kutQHkX0N3sqPeF7Nzkk8S3Bs6aiHUORM30syUKYug== 32 | # Outputs correct and exits with status code 0 33 | ``` 34 | 35 | To randomly generate a challenge: 36 | ``` 37 | kctf-pow gen 38 | ``` 39 | For example: 40 | ```bash 41 | kctf-pow gen 50 42 | # Outputs s.AAAAMg==.NDtqORW1uZlIgzszbdMGZA== 43 | ``` 44 | 45 | To chain challenge generation and checking: 46 | ``` 47 | kctf-pow ask 48 | ``` 49 | For example: 50 | ```bash 51 | kctf-pow ask 50 52 | # Outputs s.AAAAMg==.NDtqORW1uZlIgzszbdMGZA== 53 | # Input s.NUH3arymnKB+ysUGdv+67ypDamn4wOKCPORB2ivWE1Yhinam2v4S6q4nAoC5LP97LScdVoq+NuFVF++Win5mNRYZS6bJAs8fk0h8XgvfcC/7JfmFISqeCIo/CIUgIucVAM+eGDjqitRULGXqIOyviJoJjW8DMouMRuJM/3eg/z18kutQHkX0N3sqPeF7Nzkk8S3Bs6aiHUORM30syUKYug== 54 | # Outputs correct and exits with status code 0 55 | ``` 56 | 57 | # Library Usage 58 | 59 | ```rust 60 | use kctf_pow::ChallengeParams; 61 | 62 | fn main() { 63 | // decoding then solving a challenge 64 | let chall = ChallengeParams::decode_challenge("s.AAAAMg==.H+fPiuL32DPbfN97cpd0nA==").unwrap(); 65 | println!("{}", chall.solve()); 66 | // decoding then checking a challenge 67 | let chall = ChallengeParams::decode_challenge("s.AAAAMg==.NDtqORW1uZlIgzszbdMGZA==").unwrap(); 68 | let sol = "s.NUH3arymnKB+ysUGdv+67ypDamn4wOKCPORB2ivWE1Yhinam2v4S6q4nAoC5LP97LScdVoq+NuFVF++Win5mNRYZS6bJAs8fk0h8XgvfcC/7JfmFISqeCIo/CIUgIucVAM+eGDjqitRULGXqIOyviJoJjW8DMouMRuJM/3eg/z18kutQHkX0N3sqPeF7Nzkk8S3Bs6aiHUORM30syUKYug=="; 69 | assert_eq!(chall.check(sol), Ok(true)); 70 | assert_eq!(chall.check("s.asdf"), Ok(false)); 71 | // generating a random challenge of difficulty 50 72 | let chall = ChallengeParams::generate_challenge(50); 73 | println!("{}", chall); 74 | } 75 | ``` 76 | 77 | # Library Documentation 78 | 79 | The documentation for the library is available on [docs.rs](https://docs.rs/kctf-pow). 80 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A library to solve, check, and generate proof-of-work challenges using [kCTF](https://google.github.io/kctf/)'s scheme. 2 | //! 3 | //! ```rust 4 | //! use kctf_pow::ChallengeParams; 5 | //! 6 | //! // decoding then solving a challenge 7 | //! let chall = ChallengeParams::decode_challenge("s.AAAAMg==.H+fPiuL32DPbfN97cpd0nA==").unwrap(); 8 | //! println!("{}", chall.solve()); 9 | //! // decoding then checking a challenge 10 | //! let chall = ChallengeParams::decode_challenge("s.AAAAMg==.NDtqORW1uZlIgzszbdMGZA==").unwrap(); 11 | //! let sol = "s.NUH3arymnKB+ysUGdv+67ypDamn4wOKCPORB2ivWE1Yhinam2v4S6q4nAoC5LP97LScdVoq+NuFVF++Win5mNRYZS6bJAs8fk0h8XgvfcC/7JfmFISqeCIo/CIUgIucVAM+eGDjqitRULGXqIOyviJoJjW8DMouMRuJM/3eg/z18kutQHkX0N3sqPeF7Nzkk8S3Bs6aiHUORM30syUKYug=="; 12 | //! assert_eq!(chall.check(sol), Ok(true)); 13 | //! assert_eq!(chall.check("s.asdf"), Ok(false)); 14 | //! // generating a random challenge of difficulty 50 15 | //! let chall = ChallengeParams::generate_challenge(50); 16 | //! println!("{}", chall); 17 | //! ``` 18 | 19 | use base64::prelude::*; 20 | use rand::prelude::*; 21 | use rug::Integer; 22 | use rug::integer::Order; 23 | use rug::ops::Pow; 24 | use std::convert::TryInto; 25 | use std::fmt; 26 | 27 | const VERSION: &str = "s"; 28 | 29 | /// The parameters for a proof-of-work challenge. 30 | /// 31 | /// This contains most of the logic, however [`KctfPow`] and [`Challenge`] should be used instead as they provide a nicer API. 32 | /// If you want to serialize it to a string, use the [`Display`](std::fmt::Display) implementation. 33 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] 34 | pub struct ChallengeParams { 35 | /// The difficulty of the challenge. 36 | pub difficulty: u32, 37 | /// The starting value of the challenge. 38 | pub val: Integer, 39 | } 40 | 41 | /// Squares an integer in-place modulo 2^1279-1 42 | fn square_mod(n: &mut Integer) { 43 | n.square_mut(); 44 | let high = Integer::from(&*n >> 1279); 45 | n.keep_bits_mut(1279); 46 | *n += high; 47 | if n.get_bit(1279) { 48 | n.set_bit(1279, false); 49 | *n += 1; 50 | } 51 | } 52 | 53 | impl ChallengeParams { 54 | /// Decodes a challenge from a string and returns it. 55 | /// 56 | /// For optimization purposes, the difficulty of the challenge must be able to fit in a [`u32`]. 57 | /// This shouldn't be an issue, since difficulties that can't fit into a [`u32`] will probably take too long anyways. 58 | pub fn decode_challenge(chall_string: &str) -> Result { 59 | let mut parts = chall_string.split('.'); 60 | if parts.next() != Some(VERSION) { 61 | return Err("Incorrect version"); 62 | } 63 | let data: Vec<_> = parts.collect(); 64 | if data.len() != 2 { 65 | return Err("Incorrect number of parts"); 66 | } 67 | let decoded_data: Vec<_> = data 68 | .into_iter() 69 | .map(|x| { 70 | BASE64_STANDARD 71 | .decode(x) 72 | .map_err(|_| "Parts aren't valid base64") 73 | }) 74 | .collect::>()?; 75 | let difficulty_bytes = &decoded_data[0]; 76 | let difficulty: u32 = if difficulty_bytes.len() > 4 { 77 | let (first, last) = difficulty_bytes.split_at(difficulty_bytes.len() - 4); 78 | // if difficulty is 0-padded to longer than 4 bytes it should still work 79 | if first.iter().any(|&x| x != 0) { 80 | return Err("Difficulty is too large"); 81 | } 82 | u32::from_be_bytes(last.try_into().unwrap()) 83 | } else { 84 | let mut difficulty_array = [0; 4]; 85 | difficulty_array[4 - difficulty_bytes.len()..].copy_from_slice(difficulty_bytes); 86 | u32::from_be_bytes(difficulty_array) 87 | }; 88 | Ok(Self { 89 | val: Integer::from_digits(&decoded_data[1], Order::Msf), 90 | difficulty, 91 | }) 92 | } 93 | 94 | /// Generates a random challenge given a difficulty. 95 | pub fn generate_challenge(difficulty: u32) -> ChallengeParams { 96 | let mut bytes: [u8; 16] = [0; 16]; 97 | rand::rng().fill(&mut bytes[..]); 98 | Self { 99 | val: Integer::from_digits(&bytes, Order::Msf), 100 | difficulty, 101 | } 102 | } 103 | 104 | /// Solves a challenge given a proof-of-work system and returns the solution. 105 | pub fn solve(mut self) -> String { 106 | for _ in 0..self.difficulty { 107 | // guaranteed to succeed so ignore the result 108 | for _ in 0..1277 { 109 | square_mod(&mut self.val); 110 | } 111 | self.val ^= 1; 112 | } 113 | format!( 114 | "{}.{}", 115 | VERSION, 116 | BASE64_STANDARD.encode(self.val.to_digits(Order::Msf)) 117 | ) 118 | } 119 | 120 | /// Checks a solution to see if it satisfies the challenge under a given proof-of-work system. 121 | pub fn check(&self, sol: &str) -> Result { 122 | let mut parts = sol.split('.'); 123 | if parts.next() != Some(VERSION) { 124 | return Err("Incorrect version"); 125 | } 126 | let Some(data) = parts.next() else { 127 | return Err("Incorrect number of parts"); 128 | }; 129 | if parts.next().is_some() { 130 | return Err("Incorrect number of parts"); 131 | } 132 | let decoded_data = BASE64_STANDARD 133 | .decode(data) 134 | .map_err(|_| "Parts aren't valid base64")?; 135 | let mut sol_val = Integer::from_digits(&decoded_data, Order::Msf); 136 | for _ in 0..self.difficulty { 137 | sol_val ^= 1; 138 | square_mod(&mut sol_val); 139 | } 140 | Ok(self.val == sol_val || Integer::from(2).pow(1279) - 1 - &self.val == sol_val) 141 | } 142 | } 143 | 144 | impl fmt::Display for ChallengeParams { 145 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 146 | write!( 147 | fmt, 148 | "{}.{}.{}", 149 | VERSION, 150 | BASE64_STANDARD.encode(self.difficulty.to_be_bytes()), 151 | BASE64_STANDARD.encode(self.val.to_digits(Order::Msf)) 152 | ) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use kctf_pow::ChallengeParams; 2 | 3 | fn gen_usage(name: &str) -> String { 4 | format!( 5 | "Could not parse arguments 6 | Usage: 7 | To solve a challenge: {name} solve 8 | To check a challenge: {name} check 9 | To randomly generate a challenge: {name} gen 10 | To chain generation with checking: {name} ask " 11 | ) 12 | } 13 | 14 | fn actual_main() -> Result<(), String> { 15 | let args: Vec<_> = std::env::args().collect(); 16 | let name = args.first().map(|x| x as _).unwrap_or("kctf-pow"); 17 | if args.len() < 3 { 18 | return Err(gen_usage(name)); 19 | } 20 | match &args[1] as _ { 21 | "solve" => { 22 | let chall = ChallengeParams::decode_challenge(&args[2])?; 23 | println!("{}", chall.solve()); 24 | } 25 | "check" => { 26 | let chall = ChallengeParams::decode_challenge(&args[2])?; 27 | let mut inp = String::new(); 28 | std::io::stdin() 29 | .read_line(&mut inp) 30 | .map_err(|_| "Could not read from stdin")?; 31 | let res = chall.check(inp.trim())?; 32 | if res { 33 | println!("correct"); 34 | } else { 35 | println!("incorrect"); 36 | return Err("Challenge verification failed".into()); 37 | } 38 | } 39 | "gen" => { 40 | let difficulty: u32 = args[2] 41 | .parse() 42 | .map_err(|_| "Difficulty is not a valid 32-bit unsigned integer")?; 43 | println!("{}", ChallengeParams::generate_challenge(difficulty)); 44 | } 45 | "ask" => { 46 | let difficulty: u32 = args[2] 47 | .parse() 48 | .map_err(|_| "Difficulty is not a valid 32-bit unsigned integer")?; 49 | let chall = ChallengeParams::generate_challenge(difficulty); 50 | println!("{chall}"); 51 | let mut inp = String::new(); 52 | std::io::stdin() 53 | .read_line(&mut inp) 54 | .map_err(|_| "Could not read from stdin")?; 55 | let res = chall.check(inp.trim())?; 56 | if res { 57 | println!("correct"); 58 | } else { 59 | println!("incorrect"); 60 | return Err("Challenge verification failed".into()); 61 | } 62 | } 63 | _ => { 64 | return Err(gen_usage(name)); 65 | } 66 | } 67 | Ok(()) 68 | } 69 | 70 | fn main() { 71 | if let Err(s) = actual_main() { 72 | eprintln!("Error: {s}"); 73 | std::process::exit(1); 74 | } 75 | } 76 | --------------------------------------------------------------------------------