├── 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 | [
](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 |
--------------------------------------------------------------------------------