├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake └── Findlibgit2.cmake └── main.cpp /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | include(CheckSymbolExists) 3 | 4 | project(git-power) 5 | 6 | set(CMAKE_CXX_STANDARD 14) 7 | 8 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") 9 | find_package(libgit2 REQUIRED) 10 | find_package(OpenSSL REQUIRED) 11 | 12 | add_executable(git-power main.cpp) 13 | 14 | check_symbol_exists(flsll string.h HAVE_FLSLL) 15 | if (${HAVE_FLSLL}) 16 | target_compile_definitions(git-power PRIVATE -DHAVE_FLSLL) 17 | endif () 18 | 19 | target_include_directories(git-power PRIVATE ${LIBGIT2_INCLUDE_DIR}) 20 | target_include_directories(git-power PRIVATE ${OPENSSL_INCLUDE_DIR}) 21 | target_link_libraries(git-power PRIVATE ${LIBGIT2_LIBRARIES}) 22 | target_link_libraries(git-power PRIVATE ${OPENSSL_CRYPTO_LIBRARIES}) 23 | 24 | if (UNIX AND NOT APPLE) 25 | # Ubuntu is a snowflake 26 | target_link_libraries(git-power PRIVATE pthread atomic) 27 | endif () 28 | 29 | install(TARGETS git-power) 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Glenn Smith 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons 7 | to whom the Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or 10 | substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 13 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 14 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 15 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 16 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 17 | DEALINGS IN THE SOFTWARE. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `git-power` 2 | emPOWer your commits. Pointlessly flex on your coworkers with bespoke commit hashes, 3 | all with the convenience of a single command. 4 | 5 | [demo](https://github.com/CouleeApps/git-power/commits/master) 6 | 7 | ## What is it? 8 | A [Proof of Work](https://en.wikipedia.org/wiki/Proof_of_work) is a cryptographic proof 9 | that an amount of work has been done. Often, these are seen in the form of 10 | `Hash(data || nonce)` where the result of the hash has some number of leading zero bits. 11 | Since hashes are one-way functions, this is effectively a O(2^n) brute force for n leading 12 | zero bits. Since git commits are identified with a hash, and you can insert arbitrary 13 | fields into a commit header, you can generate many hashes for one set of changes and 14 | effectively compute a Proof of Work for a git commit. This tool does that, with a 15 | configurable number of threads and leading zero bits on the commit hash. 16 | 17 | ## Why? 18 | Some joke about "Git is a blockchain" went too far, now we have this. 19 | 20 | ## Is it fast? 21 | Reasonably. On my fanless M2 MacBook Air, it can compute about 30MH/s at 22 | peak CPU core boost clocks. If you account for the less-than-stellar MacBook thermals, it 23 | drops to about 21MH/s. But assuming you can get good speeds, and assuming you want to 24 | calculate a hash with 32 leading zero bits, this should take 25 | (2^32 / 30,000,000) ~= 140 seconds on average, though the variance is pretty high. 26 | Hashcat's benchmark reports my CPU can do about 725MH/s for SHA-1, so OpenSSL's hash 27 | implementation is probably not optimized well for this. Maybe someone can look into 28 | adapting Hashcat into this, but it's a bit beyond the scope I'm willing to do. 29 | 30 | ## Usage 31 | 32 | git-power [bits [threads]] 33 | 34 | `git-power` operates on the git repository in the current working directory. 35 | The only supported run options are the number of leading bits to brute-force and the 36 | number of threads created to do the work. By default, `git-power` will use 32 bits and 37 | the max number of hardware threads supported. 38 | 39 | When a matching commit hash is found, it will automatically update your repository HEAD 40 | and replace the latest commit. NEW: If your commit is GPG-signed, it will stay signed 41 | even after running this! See the source for details on how this witchcraft is performed. 42 | 43 | ### Retroactively 44 | 45 | If you want to retroactively emPOWer all of your commits, you can combine `git power` 46 | with the brilliance of `git rebase --interactive`: 47 | 48 | # emPOWer entire tree (preferred method) 49 | git rebase --interactive --exec "git power" --root 50 | 51 | # emPOWer unpushed commits 52 | git rebase --interactive --exec "git power" origin/master 53 | 54 | # emPOWer everything after a specific commit 55 | git rebase --interactive --exec "git power" 00000000da6a1220576d8c00dff8aa9619b44048 56 | 57 | ## Building 58 | 59 | ### Linux 60 | This tool requires `cmake`, `libgit2`, and `OpenSSL` to build. You can get `libgit2` and 61 | `OpenSSL` through your package manager, or at [libgit2.org](https://libgit2.org/). 62 | Build steps are straight-forward from there: 63 | 64 | cmake -B build && cmake --build build 65 | 66 | ### macOS 67 | On macOS, Apple is rude and won't let you link with the system-provided `libcrypto`, so 68 | you need to `brew install openssl` (or build it yourself). Then you can pretend you have a 69 | real unix system: 70 | 71 | cmake -B build -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl && cmake --build build 72 | 73 | ### Windows 74 | On Windows, you need to compile `libgit2` and `OpenSSL` yourself. Then just point `cmake` 75 | at them, and you should be good: 76 | 77 | # Be sure to specify the correct arch 78 | cmake -B build -A x64 "-DCMAKE_PREFIX_PATH=C:\Program Files\libgit2" "-DOPENSSL_ROOT_DIR=C:\Program Files\OpenSSL" 79 | cmake --build build 80 | 81 | ## Installing 82 | 83 | ### macOS / Linux 84 | First, install via `cmake`: 85 | 86 | cmake --install build 87 | 88 | Then, you can use it through `git` like any other utility: 89 | 90 | # Default settings: 32 and 91 | git power 92 | 93 | # MacBook-friendly 94 | git power 24 8 95 | 96 | ### Windows 97 | Drop `git-power.exe`, `git2.dll`, and `crypto.dll` in your git installation's `bin` 98 | directory. On my machine, that's at `C:\Program Files\Git\mingw64\bin`. Then you can use 99 | it like normal. 100 | 101 | # Default settings: 32 and 102 | git power 103 | 104 | # APU-friendly 105 | git power 24 8 106 | 107 | ## License 108 | MIT license. I'm really not sure who would want to reuse this, but it's here if you want. 109 | 110 | ## How long did you spend running it on this repo 111 | Too long. 112 | 113 | ## Will you Rewrite it in Rust (TM)? 114 | ~~It wouldn't be too hard. Feel free to submit a pull request whose commit hash starts with 115 | at least 32 zero bits.~~ [I have received news that someone is doing this.](https://github.com/mkrasnitski/git-power-rs). 116 | 117 | There is also [lucky-commit, written in Rust](https://github.com/not-an-aardvark/lucky-commit), 118 | that has the same idea as `git-power`, but with GPU acceleration and the ability to choose 119 | a custom prefix. Looks like their Rust implementation (with or without GPU) is significantly 120 | faster than this C++ implementation. Crab language wins again, so it seems. 121 | 122 | ## Please apologize for creating this 123 | Sorry. 124 | -------------------------------------------------------------------------------- /cmake/Findlibgit2.cmake: -------------------------------------------------------------------------------- 1 | # https://github.com/libgit2/libgit2-backends/blob/master/CMake/FindLibgit2.cmake 2 | 3 | # - Try to find the libgit2 library 4 | # Once done this will define 5 | # 6 | # LIBGIT2_FOUND - System has libgit2 7 | # LIBGIT2_INCLUDE_DIR - The libgit2 include directory 8 | # LIBGIT2_LIBRARIES - The libraries needed to use libgit2 9 | # LIBGIT2_DEFINITIONS - Compiler switches required for using libgit2 10 | 11 | 12 | # use pkg-config to get the directories and then use these values 13 | # in the FIND_PATH() and FIND_LIBRARY() calls 14 | #FIND_PACKAGE(PkgConfig) 15 | #PKG_SEARCH_MODULE(PC_LIBGIT2 libgit2) 16 | 17 | SET(LIBGIT2_DEFINITIONS ${PC_LIBGIT2_CFLAGS_OTHER}) 18 | 19 | FIND_PATH(LIBGIT2_INCLUDE_DIR NAMES git2.h 20 | HINTS 21 | ${PC_LIBGIT2_INCLUDEDIR} 22 | ${PC_LIBGIT2_INCLUDE_DIRS} 23 | ) 24 | 25 | FIND_LIBRARY(LIBGIT2_LIBRARIES NAMES git2 26 | HINTS 27 | ${PC_LIBGIT2_LIBDIR} 28 | ${PC_LIBGIT2_LIBRARY_DIRS} 29 | ) 30 | 31 | 32 | INCLUDE(FindPackageHandleStandardArgs) 33 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(libgit2 DEFAULT_MSG LIBGIT2_LIBRARIES LIBGIT2_INCLUDE_DIR) 34 | 35 | MARK_AS_ADVANCED(LIBGIT2_INCLUDE_DIR LIBGIT2_LIBRARIES) 36 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #ifdef _WIN32 7 | // Why tho 8 | #define NOMINMAX 9 | #include 10 | #define getcwd _getcwd 11 | #else 12 | #include 13 | #endif 14 | 15 | // Did I use c++ only for its threads? Maybe 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | // Sanity check debugging 31 | // #define DEBUG_PRINT 32 | 33 | struct result_t { 34 | // Entire commit buffer (not including "commit 123" header) 35 | char buf[0x1000]; 36 | size_t buf_size; 37 | }; 38 | 39 | std::mutex m; 40 | std::condition_variable cv; 41 | // Can you have an atomic of length 0x1008? There's no way that's fast. 42 | std::atomic result; 43 | std::atomic finished; 44 | std::atomic success; 45 | std::atomic closest_bits; 46 | std::atomic closest; 47 | std::atomic attempts; 48 | 49 | void try_commits(size_t i, size_t threads, size_t bits, char *base_body, size_t base_size) { 50 | // We need to modify the original commit data to add a nonce for brute forcing 51 | // But where do we do that? 52 | // - If the commit is not GPG signed, we can just add an extra field into the commit 53 | // details with the nonce. One pitfall is that git expects the `tree` field to be 54 | // first, so we have to do it at the end. 55 | // - If the commit is GPG signed, we can't edit anything outside the signature wihout 56 | // breaking it. So...... just add it INSIDE the GPG signature!! 57 | // GPG doesn't check header values inside the signature, so we can just inject a new 58 | // header with the nonce and brute force that. Any GPG client will just ignore the 59 | // unknown header and do the signature check on the rest of the commit (which we 60 | // have not touched). This is probably the most cursed part of this whole project. 61 | // So let's do that. 62 | 63 | /* 64 | UNSIGNED 65 | 66 | tree d11fc77c07df4faadf669a4e397714a1bd588f5d 67 | parent 0000255c99724e379f925df516265e9535e3feb0 68 | author Glenn Smith 1624473924 +0613 69 | committer Glenn Smith 1624473924 -0302 70 | 71 | Test 72 | 73 | OR SIGNED 74 | 75 | tree 31c2eae32101258cfffc2e85f1d2d790b229a9ad 76 | parent 8dfe0ce00895c6a8f55bbd30d2543c1945fcad15 77 | author Glenn Smith 1625532218 -0400 78 | committer Glenn Smith 1625532226 -0400 79 | gpgsig -----BEGIN PGP SIGNATURE----- 80 | Comment: Created with Krypton 81 | 82 | iF4EABMKAAYFAmDjp0IACgkQm3HsKD8LehTDDwD/c9DB6IxtAXzF55FjgqevfoNO 83 | Eegmn53HSsYrRHOE9ZMA/Av/moBkr9Qh/nTO2c3XVfa228grAOIwcSYkn9s6WS25 84 | =XM24 85 | -----END PGP SIGNATURE----- 86 | 87 | I should not be allowed near this 88 | 89 | */ 90 | 91 | // Probably large enough for our shenanigans 92 | char *modified_commit = new char[base_size + 0x100]; 93 | size_t commit_size = 0; 94 | 95 | // Figure out where to insert our nonce header/field 96 | char *nonce_insert; 97 | // Offset into the commit data where our nonce starts 98 | size_t nonce_index; 99 | 100 | // Mega cursed: Check if this is gpg-signed 101 | char *gpgsig = strstr(base_body, "-----BEGIN PGP SIGNATURE-----"); 102 | if (gpgsig == nullptr) { 103 | // No it's not: insert nonce as a field right before the commit message 104 | nonce_insert = strstr(base_body, "\n\n"); 105 | 106 | // Copy commit until the nonce shenanigans 107 | // No, this pointer math is not safe AT ALL 108 | memcpy(modified_commit, base_body, nonce_insert - base_body); 109 | commit_size += nonce_insert - base_body; 110 | 111 | // Have we modified this commit before? Git will probably not be happy if we have 112 | // two copies of the same field 113 | char *previous_nonce = strstr(base_body, "\nnonce "); 114 | if (previous_nonce == nullptr) { 115 | // Insert nonce header/field 116 | // NB: Start with \n because we're going right before the newline(s) in the 117 | // header, and don't add one at the end for the same reason. 118 | 119 | // Nonce field in commit fields: "\nnonce 01AAAAAAAAAAAAAAAA" 120 | // ^ 121 | nonce_index = commit_size + 7; 122 | commit_size += snprintf(modified_commit + commit_size, 0x100, 123 | "\nnonce %zu%zuAAAAAAAAAAAAAAAA", i, threads); 124 | } else { 125 | nonce_index = (previous_nonce - base_body) + 9; 126 | } 127 | } else { 128 | // Yes it is: insert nonce as a header in the GPG signature 129 | nonce_insert = strstr(gpgsig, "\n\n"); 130 | 131 | memcpy(modified_commit, base_body, nonce_insert - base_body); 132 | commit_size += nonce_insert - base_body; 133 | 134 | // Have we modified this commit before? GPG will probably not be happy if we have 135 | // two copies of the same field 136 | char *previous_nonce = strstr(base_body, "\n Nonce: "); 137 | if (previous_nonce != nullptr) { 138 | commit_size = previous_nonce - base_body; 139 | } 140 | 141 | // Log10 but integer 142 | int width = 1; 143 | size_t t2 = threads; 144 | while (t2 > 9) { 145 | width ++; 146 | t2 /= 10; 147 | } 148 | 149 | // Nonce header in GPG signature: "\n Nonce: 01AAAAAAAAAAAAAAAA" 150 | // ^ 151 | // Important: There is a space at the start of every line in the signature 152 | nonce_index = commit_size + snprintf(modified_commit + commit_size, 0x100, 153 | "\n Nonce: %0*zu%0*zu", width, i, width, threads); 154 | commit_size = nonce_index + snprintf(modified_commit + nonce_index, 0x100, 155 | "AAAAAAAAAAA"); 156 | } 157 | // Copy the rest of the commit after 158 | memcpy(modified_commit + commit_size, nonce_insert, base_size - (nonce_insert - base_body)); 159 | commit_size += base_size - (nonce_insert - base_body); 160 | // Null terminate because I'm paranoid 161 | modified_commit[commit_size] = 0; 162 | 163 | // When git sha1's the commit, it includes the metadata for object type and size 164 | // In practice this just adds "commit \0" to the front of the buffer being 165 | // passed into SHA1. So by precomputing this ourselves we can save all of the error 166 | // checking done by git, and we can use our own SHA1 implementation (which is faster). 167 | 168 | char *obj_buf = new char[base_size + 0x100]; 169 | size_t obj_size = 0; 170 | size_t header_size = 0; 171 | 172 | // "commit \0" 173 | header_size += snprintf(obj_buf, 0x100, "commit %zd", commit_size); 174 | obj_buf[header_size] = 0; 175 | header_size += 1; 176 | 177 | // Then append our hacked-up commit body 178 | char *commit_buf = obj_buf + header_size; 179 | memcpy(commit_buf, modified_commit, commit_size); 180 | commit_buf[commit_size] = 0; 181 | 182 | // Not the best way to calculate size 183 | obj_size += header_size; 184 | obj_size += commit_size; 185 | 186 | // Null terminate for sanity 187 | obj_buf[obj_size] = 0; 188 | 189 | delete [] modified_commit; 190 | 191 | // Offset into the real buffer by the offset in the hacked-up buffer 192 | char *nonce_start = commit_buf + nonce_index; 193 | 194 | /* Now we have: 195 | 196 | commit 246\0 197 | tree d11fc77c07df4faadf669a4e397714a1bd588f5d 198 | parent 0000255c99724e379f925df516265e9535e3feb0 199 | author Glenn Smith 1624473924 +0613 200 | committer Glenn Smith 1624473924 -0302 201 | nonce 01AAAAAAAAAAAAAAAA 202 | 203 | Test 204 | 205 | OR 206 | commit 516\0 207 | tree 31c2eae32101258cfffc2e85f1d2d790b229a9ad 208 | parent 8dfe0ce00895c6a8f55bbd30d2543c1945fcad15 209 | author Glenn Smith 1625532218 -0400 210 | committer Glenn Smith 1625532226 -0400 211 | gpgsig -----BEGIN PGP SIGNATURE----- 212 | Comment: Created with Krypton 213 | 214 | iF4EABMKAAYFAmDjp0IACgkQm3HsKD8LehTDDwD/c9DB6IxtAXzF55FjgqevfoNO 215 | Eegmn53HSsYrRHOE9ZMA/Av/moBkr9Qh/nTO2c3XVfa228grAOIwcSYkn9s6WS25 216 | =XM24 217 | -----END PGP SIGNATURE----- 218 | Nonce: 01LOIIFMAAAAAAAAAA 219 | 220 | I should not be allowed near this 221 | 222 | */ 223 | EVP_MD_CTX *md_ctx_shared = EVP_MD_CTX_new(); 224 | EVP_DigestInit_ex(md_ctx_shared, EVP_sha1(), nullptr); 225 | EVP_DigestUpdate(md_ctx_shared, obj_buf, nonce_start - obj_buf); 226 | EVP_MD_CTX *md_ctx = EVP_MD_CTX_new(); 227 | EVP_DigestInit_ex(md_ctx, EVP_sha1(), nullptr); 228 | 229 | // Local copies of atomics because gotta go fast 230 | uint8_t good_bits; 231 | uint8_t local_closest = 0; 232 | 233 | // I hope 64 bits is enough 234 | uint64_t nonce = 0; 235 | 236 | #ifdef DEBUG_PRINT 237 | srand(time(NULL)); 238 | #endif 239 | 240 | do { 241 | // See if another thread got it 242 | if (finished) { 243 | EVP_MD_CTX_free(md_ctx); 244 | EVP_MD_CTX_free(md_ctx_shared); 245 | delete [] obj_buf; 246 | return; 247 | } 248 | 249 | // Increment nonce by 1 250 | nonce_start[0] = ' ' + (nonce & 0x3F); 251 | // Micro optimizing: these two first branches are slower than always writing 252 | // if ((nonce & 0x3f) == 0) 253 | nonce_start[1] = ' ' + ((nonce >> 6) & 0x3F); 254 | // if ((nonce & 0xfff) == 0) 255 | nonce_start[2] = ' ' + ((nonce >> 12) & 0x3F); 256 | if ((nonce & 0x3f000) != ((nonce - 1) & 0x3f000)) { 257 | nonce_start[3] = ' ' + ((nonce >> 18) & 0x3F); 258 | if ((nonce & 0xfc0000) != ((nonce - 1) & 0xfc0000)) { 259 | nonce_start[4] = ' ' + ((nonce >> 24) & 0x3F); 260 | if ((nonce & 0x3f000000) != ((nonce - 1) & 0x3f000000)) { 261 | nonce_start[5] = ' ' + ((nonce >> 30) & 0x3F); 262 | if ((nonce & 0xfc0000000) != ((nonce - 1) & 0xfc0000000)) { 263 | nonce_start[6] = ' ' + ((nonce >> 36) & 0x3F); 264 | if ((nonce & 0x3f000000000) != ((nonce - 1) & 0x3f000000000)) { 265 | nonce_start[7] = ' ' + ((nonce >> 42) & 0x3F); 266 | if ((nonce & 0xfc0000000000) != 267 | ((nonce - 1) & 0xfc0000000000)) { 268 | nonce_start[8] = ' ' + ((nonce >> 48) & 0x3F); 269 | if ((nonce & 0x3f000000000000) != 270 | ((nonce - 1) & 0x3f000000000000)) { 271 | nonce_start[9] = ' ' + ((nonce >> 54) & 0x3F); 272 | if ((nonce & 0xfc0000000000000) != 273 | ((nonce - 1) & 0xfc0000000000000)) { 274 | nonce_start[10] = ' ' + ((nonce >> 60) & 0x3F); 275 | } 276 | } 277 | } 278 | } 279 | } 280 | } 281 | } 282 | } 283 | 284 | #ifdef DEBUG_PRINT 285 | if ((rand() & 0xffff) == 1) { 286 | printf("%.20s\n", nonce_start - 11); 287 | } 288 | #endif 289 | 290 | // The slow part 291 | git_oid hash; 292 | unsigned int md_len; 293 | // Or insert your favorite SHA1 function here 294 | EVP_MD_CTX_copy_ex(md_ctx, md_ctx_shared); 295 | EVP_DigestUpdate(md_ctx, nonce_start, obj_size - (nonce_start - obj_buf)); 296 | EVP_DigestFinal_ex(md_ctx, &hash.id[0], &md_len); 297 | 298 | // If it's not 20 then we just demolished the stack lmao 299 | assert(md_len == 20); 300 | 301 | // Make sure it matches 302 | good_bits = 0; 303 | #if defined(HAVE_FLSLL) 304 | // Nobody is going to ask for more than 64 bits 305 | assert(bits <= 64); 306 | good_bits = 64 - flsll(htonll(*(uint64_t *)&hash.id[0])); 307 | #else 308 | int still_good = 1; 309 | for (int n = 0; n < bits; n++) { 310 | // 1 if the bit `n` is unset 311 | int bit = ((hash.id[n / 8] >> (7 - (n % 8))) & 1) ^ 1; 312 | // Will be 1 as long as no bits were set 313 | still_good &= bit; 314 | // Will add 1 as long as no bits were set 315 | good_bits += still_good; 316 | } 317 | #endif 318 | // Only update atomics if we PB because speed 319 | if (good_bits > local_closest) { 320 | local_closest = good_bits; 321 | if (good_bits > closest_bits) { 322 | closest_bits = good_bits; 323 | closest = hash; 324 | } 325 | } 326 | 327 | // Update attempts every 0x100. Probably not a big impact but I am going to 328 | // micro-optimize every part of this. 329 | if ((nonce & 0xFF) == 0) { 330 | attempts += 0x100; 331 | } 332 | nonce++; 333 | } while (good_bits < bits); 334 | 335 | // Big sanity check here since we think this is a good hash 336 | git_oid hash; 337 | git_odb_hash(&hash, commit_buf, commit_size, GIT_OBJECT_COMMIT); 338 | int test = 0; 339 | for (size_t n = 0; n < bits; n++) { 340 | // Test will be the OR of the first `n` bits 341 | test |= (hash.id[n / 8] >> (7 - (n % 8))) & 1; 342 | } 343 | 344 | // Hopefully this never fails 345 | assert(test == 0); 346 | 347 | // Report results WHILE HOLDING THE LOCK 348 | { 349 | std::unique_lock l(m); 350 | 351 | // If we are first 352 | if (!finished) { 353 | result_t local_result{}; 354 | local_result.buf_size = commit_size; 355 | memcpy(local_result.buf, commit_buf, commit_size); 356 | 357 | result = local_result; 358 | success = true; 359 | finished = true; 360 | cv.notify_all(); 361 | } 362 | } 363 | 364 | EVP_MD_CTX_free(md_ctx); 365 | EVP_MD_CTX_free(md_ctx_shared); 366 | delete [] obj_buf; 367 | } 368 | 369 | void sigint_handler(int sig) { 370 | success = false; 371 | finished = true; 372 | cv.notify_all(); 373 | } 374 | 375 | int main(int argc, const char **argv) { 376 | // Arg parsing could be improved 377 | if (argc == 2 && (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)) { 378 | fprintf(stderr, "Usage: %s [bits [threads]]\n", argv[0]); 379 | return 1; 380 | } 381 | // Sensible defaults 382 | int bits = 32; 383 | int threads = std::max((int)std::thread::hardware_concurrency(), 1); 384 | if (argc > 1) 385 | sscanf(argv[1], "%d", &bits); 386 | if (argc > 2) 387 | sscanf(argv[2], "%d", &threads); 388 | 389 | // libgit2 init is nice 390 | git_libgit2_init(); 391 | // OpenSSL init is not 392 | ERR_load_CRYPTO_strings(); 393 | OpenSSL_add_all_algorithms(); 394 | OPENSSL_no_config(); 395 | 396 | // Repo in cwd 397 | char dir[0x400]; 398 | getcwd(dir, 0x400); 399 | 400 | // Find our repo 401 | git_repository *repository; 402 | git_repository_open(&repository, dir); 403 | 404 | if (repository == nullptr) { 405 | fprintf(stderr, "Current directory is not a git repository.\n"); 406 | // I could use a goto here... or I could just not 407 | git_libgit2_shutdown(); 408 | 409 | EVP_cleanup(); 410 | CRYPTO_cleanup_all_ex_data(); 411 | ERR_free_strings(); 412 | return 2; 413 | } 414 | 415 | git_reference *head; 416 | git_repository_head(&head, repository); 417 | 418 | if (head == nullptr) { 419 | fprintf(stderr, "Current repository does not have a HEAD.\n"); 420 | // I could use a goto here... or I could just not 421 | git_repository_free(repository); 422 | git_libgit2_shutdown(); 423 | 424 | EVP_cleanup(); 425 | CRYPTO_cleanup_all_ex_data(); 426 | ERR_free_strings(); 427 | return 3; 428 | } 429 | 430 | const git_oid *target = git_reference_target(head); 431 | 432 | // Copy from the latest commit 433 | git_commit *commit; 434 | git_commit_lookup(&commit, repository, target); 435 | 436 | if (commit == nullptr) { 437 | fprintf(stderr, "No commit on current branch.\n"); 438 | // I could use a goto here... or I could just not 439 | git_reference_free(head); 440 | git_repository_free(repository); 441 | git_libgit2_shutdown(); 442 | 443 | EVP_cleanup(); 444 | CRYPTO_cleanup_all_ex_data(); 445 | ERR_free_strings(); 446 | return 4; 447 | } 448 | 449 | // Read 450 | git_odb *db; 451 | git_repository_odb(&db, repository); 452 | 453 | git_odb_object *commit_data; 454 | git_odb_read(&commit_data, db, git_commit_id(commit)); 455 | 456 | #ifdef DEBUG 457 | fprintf(stderr, "OLD COMMIT:\n%s\n", (char *)git_odb_object_data(commit_data)); 458 | #endif 459 | 460 | // Setup parallelization 461 | success = false; 462 | finished = false; 463 | attempts = 0; 464 | auto start = std::chrono::high_resolution_clock::now(); 465 | 466 | std::unique_lock lk(m); 467 | signal(SIGINT, &sigint_handler); 468 | 469 | std::vector spawned_threads; 470 | 471 | // Start!!! 472 | for (size_t i = 0; i < threads; i++) { 473 | spawned_threads.emplace_back([=]() { 474 | try_commits(i, threads, bits, (char *)git_odb_object_data(commit_data), 475 | git_odb_object_size(commit_data)); 476 | }); 477 | } 478 | 479 | // A thread will signal this when it finds a match 480 | while (!finished) { 481 | cv.wait_for(lk, std::chrono::seconds(1)); 482 | 483 | git_oid close = closest; 484 | uint64_t a = attempts; 485 | int b = closest_bits; 486 | 487 | char id[0x100]; 488 | git_oid_tostr(id, 0x100, &close); 489 | 490 | auto end = std::chrono::high_resolution_clock::now(); 491 | auto difference = end - start; 492 | 493 | fprintf(stderr, "\rRuns: %12llu Best found: %s (%d/%d bits) Time: %lld.%06lld ~%fMH/s", a, id, 494 | b, bits, std::chrono::duration_cast(difference).count(), 495 | std::chrono::duration_cast(difference).count() % 1000000, 496 | (double)a / (double)std::chrono::duration_cast(difference).count()); 497 | } 498 | 499 | // Wait for threads 500 | for (auto& thread: spawned_threads) { 501 | thread.join(); 502 | } 503 | 504 | // Extract results 505 | if (success) { 506 | result_t r = result; 507 | char commit_buffer[0x1000]; 508 | size_t commit_size = r.buf_size; 509 | memcpy(commit_buffer, r.buf, r.buf_size); 510 | 511 | // And make the commit for them 512 | git_oid hash; 513 | git_odb_write(&hash, db, commit_buffer, commit_size, GIT_OBJECT_COMMIT); 514 | 515 | // Fancy print 516 | char id[0x100]; 517 | git_oid_tostr(id, 0x100, &hash); 518 | 519 | #ifdef DEBUG 520 | fprintf(stderr, "NEW COMMIT:\n%s\n", commit_buffer); 521 | #endif 522 | printf("\nFound commit hash %s\n", id); 523 | 524 | // Soft reset to this commit so it is now branch head 525 | git_object *new_commit; 526 | git_object_lookup(&new_commit, repository, &hash, GIT_OBJECT_COMMIT); 527 | 528 | git_reset(repository, new_commit, GIT_RESET_SOFT, nullptr); 529 | 530 | git_object_free(new_commit); 531 | } else { 532 | printf("\nUser cancelled\n"); 533 | } 534 | 535 | uint64_t a = attempts; 536 | 537 | auto end = std::chrono::high_resolution_clock::now(); 538 | auto difference = end - start; 539 | printf("Stats: %llu attempts in %lld.%06lld time\n", a, 540 | std::chrono::duration_cast(difference).count(), 541 | std::chrono::duration_cast(difference).count() % 1000000); 542 | 543 | // Cleanup 544 | git_odb_free(db); 545 | git_commit_free(commit); 546 | 547 | git_reference_free(head); 548 | git_repository_free(repository); 549 | 550 | git_libgit2_shutdown(); 551 | 552 | EVP_cleanup(); 553 | CRYPTO_cleanup_all_ex_data(); 554 | ERR_free_strings(); 555 | 556 | return 0; 557 | } 558 | --------------------------------------------------------------------------------