├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md └── src └── 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 = 3 4 | 5 | [[package]] 6 | name = "ansi_term" 7 | version = "0.11.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 10 | dependencies = [ 11 | "winapi", 12 | ] 13 | 14 | [[package]] 15 | name = "anyhow" 16 | version = "1.0.41" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61" 19 | 20 | [[package]] 21 | name = "atty" 22 | version = "0.2.14" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 25 | dependencies = [ 26 | "hermit-abi", 27 | "libc", 28 | "winapi", 29 | ] 30 | 31 | [[package]] 32 | name = "autocfg" 33 | version = "1.0.1" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 36 | 37 | [[package]] 38 | name = "bitflags" 39 | version = "1.2.1" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 42 | 43 | [[package]] 44 | name = "block-buffer" 45 | version = "0.9.0" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 48 | dependencies = [ 49 | "generic-array", 50 | ] 51 | 52 | [[package]] 53 | name = "cc" 54 | version = "1.0.68" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" 57 | dependencies = [ 58 | "jobserver", 59 | ] 60 | 61 | [[package]] 62 | name = "cfg-if" 63 | version = "1.0.0" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 66 | 67 | [[package]] 68 | name = "clap" 69 | version = "2.33.3" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 72 | dependencies = [ 73 | "ansi_term", 74 | "atty", 75 | "bitflags", 76 | "strsim", 77 | "textwrap", 78 | "unicode-width", 79 | "vec_map", 80 | ] 81 | 82 | [[package]] 83 | name = "cpufeatures" 84 | version = "0.1.5" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" 87 | dependencies = [ 88 | "libc", 89 | ] 90 | 91 | [[package]] 92 | name = "digest" 93 | version = "0.9.0" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 96 | dependencies = [ 97 | "generic-array", 98 | ] 99 | 100 | [[package]] 101 | name = "form_urlencoded" 102 | version = "1.0.1" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 105 | dependencies = [ 106 | "matches", 107 | "percent-encoding", 108 | ] 109 | 110 | [[package]] 111 | name = "generic-array" 112 | version = "0.14.4" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" 115 | dependencies = [ 116 | "typenum", 117 | "version_check", 118 | ] 119 | 120 | [[package]] 121 | name = "git-power" 122 | version = "0.1.0" 123 | dependencies = [ 124 | "anyhow", 125 | "git2", 126 | "num_cpus", 127 | "sha-1", 128 | "structopt", 129 | ] 130 | 131 | [[package]] 132 | name = "git2" 133 | version = "0.13.20" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "d9831e983241f8c5591ed53f17d874833e2fa82cac2625f3888c50cbfe136cba" 136 | dependencies = [ 137 | "bitflags", 138 | "libc", 139 | "libgit2-sys", 140 | "log", 141 | "openssl-probe", 142 | "openssl-sys", 143 | "url", 144 | ] 145 | 146 | [[package]] 147 | name = "heck" 148 | version = "0.3.3" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 151 | dependencies = [ 152 | "unicode-segmentation", 153 | ] 154 | 155 | [[package]] 156 | name = "hermit-abi" 157 | version = "0.1.19" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 160 | dependencies = [ 161 | "libc", 162 | ] 163 | 164 | [[package]] 165 | name = "idna" 166 | version = "0.2.3" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 169 | dependencies = [ 170 | "matches", 171 | "unicode-bidi", 172 | "unicode-normalization", 173 | ] 174 | 175 | [[package]] 176 | name = "jobserver" 177 | version = "0.1.22" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "972f5ae5d1cb9c6ae417789196c803205313edde988685da5e3aae0827b9e7fd" 180 | dependencies = [ 181 | "libc", 182 | ] 183 | 184 | [[package]] 185 | name = "lazy_static" 186 | version = "1.4.0" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 189 | 190 | [[package]] 191 | name = "libc" 192 | version = "0.2.97" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" 195 | 196 | [[package]] 197 | name = "libgit2-sys" 198 | version = "0.12.21+1.1.0" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "86271bacd72b2b9e854c3dcfb82efd538f15f870e4c11af66900effb462f6825" 201 | dependencies = [ 202 | "cc", 203 | "libc", 204 | "libssh2-sys", 205 | "libz-sys", 206 | "openssl-sys", 207 | "pkg-config", 208 | ] 209 | 210 | [[package]] 211 | name = "libssh2-sys" 212 | version = "0.2.21" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "e0186af0d8f171ae6b9c4c90ec51898bad5d08a2d5e470903a50d9ad8959cbee" 215 | dependencies = [ 216 | "cc", 217 | "libc", 218 | "libz-sys", 219 | "openssl-sys", 220 | "pkg-config", 221 | "vcpkg", 222 | ] 223 | 224 | [[package]] 225 | name = "libz-sys" 226 | version = "1.1.3" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66" 229 | dependencies = [ 230 | "cc", 231 | "libc", 232 | "pkg-config", 233 | "vcpkg", 234 | ] 235 | 236 | [[package]] 237 | name = "log" 238 | version = "0.4.14" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 241 | dependencies = [ 242 | "cfg-if", 243 | ] 244 | 245 | [[package]] 246 | name = "matches" 247 | version = "0.1.8" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 250 | 251 | [[package]] 252 | name = "num_cpus" 253 | version = "1.13.0" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 256 | dependencies = [ 257 | "hermit-abi", 258 | "libc", 259 | ] 260 | 261 | [[package]] 262 | name = "opaque-debug" 263 | version = "0.3.0" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 266 | 267 | [[package]] 268 | name = "openssl-probe" 269 | version = "0.1.4" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" 272 | 273 | [[package]] 274 | name = "openssl-sys" 275 | version = "0.9.65" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "7a7907e3bfa08bb85105209cdfcb6c63d109f8f6c1ed6ca318fff5c1853fbc1d" 278 | dependencies = [ 279 | "autocfg", 280 | "cc", 281 | "libc", 282 | "pkg-config", 283 | "vcpkg", 284 | ] 285 | 286 | [[package]] 287 | name = "percent-encoding" 288 | version = "2.1.0" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 291 | 292 | [[package]] 293 | name = "pkg-config" 294 | version = "0.3.19" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" 297 | 298 | [[package]] 299 | name = "proc-macro-error" 300 | version = "1.0.4" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 303 | dependencies = [ 304 | "proc-macro-error-attr", 305 | "proc-macro2", 306 | "quote", 307 | "syn", 308 | "version_check", 309 | ] 310 | 311 | [[package]] 312 | name = "proc-macro-error-attr" 313 | version = "1.0.4" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 316 | dependencies = [ 317 | "proc-macro2", 318 | "quote", 319 | "version_check", 320 | ] 321 | 322 | [[package]] 323 | name = "proc-macro2" 324 | version = "1.0.27" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" 327 | dependencies = [ 328 | "unicode-xid", 329 | ] 330 | 331 | [[package]] 332 | name = "quote" 333 | version = "1.0.9" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 336 | dependencies = [ 337 | "proc-macro2", 338 | ] 339 | 340 | [[package]] 341 | name = "sha-1" 342 | version = "0.9.6" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "8c4cfa741c5832d0ef7fab46cabed29c2aae926db0b11bb2069edd8db5e64e16" 345 | dependencies = [ 346 | "block-buffer", 347 | "cfg-if", 348 | "cpufeatures", 349 | "digest", 350 | "opaque-debug", 351 | ] 352 | 353 | [[package]] 354 | name = "strsim" 355 | version = "0.8.0" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 358 | 359 | [[package]] 360 | name = "structopt" 361 | version = "0.3.22" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "69b041cdcb67226aca307e6e7be44c8806423d83e018bd662360a93dabce4d71" 364 | dependencies = [ 365 | "clap", 366 | "lazy_static", 367 | "structopt-derive", 368 | ] 369 | 370 | [[package]] 371 | name = "structopt-derive" 372 | version = "0.4.15" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "7813934aecf5f51a54775e00068c237de98489463968231a51746bbbc03f9c10" 375 | dependencies = [ 376 | "heck", 377 | "proc-macro-error", 378 | "proc-macro2", 379 | "quote", 380 | "syn", 381 | ] 382 | 383 | [[package]] 384 | name = "syn" 385 | version = "1.0.73" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" 388 | dependencies = [ 389 | "proc-macro2", 390 | "quote", 391 | "unicode-xid", 392 | ] 393 | 394 | [[package]] 395 | name = "textwrap" 396 | version = "0.11.0" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 399 | dependencies = [ 400 | "unicode-width", 401 | ] 402 | 403 | [[package]] 404 | name = "tinyvec" 405 | version = "1.2.0" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" 408 | dependencies = [ 409 | "tinyvec_macros", 410 | ] 411 | 412 | [[package]] 413 | name = "tinyvec_macros" 414 | version = "0.1.0" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 417 | 418 | [[package]] 419 | name = "typenum" 420 | version = "1.13.0" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" 423 | 424 | [[package]] 425 | name = "unicode-bidi" 426 | version = "0.3.5" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" 429 | dependencies = [ 430 | "matches", 431 | ] 432 | 433 | [[package]] 434 | name = "unicode-normalization" 435 | version = "0.1.19" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" 438 | dependencies = [ 439 | "tinyvec", 440 | ] 441 | 442 | [[package]] 443 | name = "unicode-segmentation" 444 | version = "1.8.0" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" 447 | 448 | [[package]] 449 | name = "unicode-width" 450 | version = "0.1.8" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 453 | 454 | [[package]] 455 | name = "unicode-xid" 456 | version = "0.2.2" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 459 | 460 | [[package]] 461 | name = "url" 462 | version = "2.2.2" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 465 | dependencies = [ 466 | "form_urlencoded", 467 | "idna", 468 | "matches", 469 | "percent-encoding", 470 | ] 471 | 472 | [[package]] 473 | name = "vcpkg" 474 | version = "0.2.15" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 477 | 478 | [[package]] 479 | name = "vec_map" 480 | version = "0.8.2" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 483 | 484 | [[package]] 485 | name = "version_check" 486 | version = "0.9.3" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 489 | 490 | [[package]] 491 | name = "winapi" 492 | version = "0.3.9" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 495 | dependencies = [ 496 | "winapi-i686-pc-windows-gnu", 497 | "winapi-x86_64-pc-windows-gnu", 498 | ] 499 | 500 | [[package]] 501 | name = "winapi-i686-pc-windows-gnu" 502 | version = "0.4.0" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 505 | 506 | [[package]] 507 | name = "winapi-x86_64-pc-windows-gnu" 508 | version = "0.4.0" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 511 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "git-power" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1.0.41" 10 | git2 = "0.13" 11 | num_cpus = "1.13.0" 12 | sha-1 = "0.9.6" 13 | structopt = "0.3" 14 | 15 | [profile.release] 16 | debug = true 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `git-power-rs` 2 | 3 | ## What is this? 4 | Make your git tree into a blockchain! Inspired by [this project](https://github.com/CouleeApps/git-power), I noticed that there was a call to Rewrite it in Rust™, so I decided to tackle it as a way to learn about multithreading in Rust. More details on the What and Why can be found in the above repo. 5 | 6 | ## How fast does it go? 7 | On my Ryzen 3600 @ 3.6 GHz with 12 threads, it achieves 54 MH/s peak, with this figure decreasing for longer commit messages (this includes signed commits). So, if you want to set the leading 8 digits of your commit to 0, that's 2^32 / 54000000 ~= 80 seconds, though variance is pretty high depending on the commit in question. 8 | 9 | ## Building 10 | Just run `cargo build`. 11 | 12 | ## Installing 13 | Run `cargo install --path .`, and the binary will be copied to `$CARGO_HOME/bin`. Make sure you have this directory in your `$PATH`, at which point you'll be able to invoke it through git itself via `git power`. 14 | 15 | ## Usage 16 | 17 | git-power 0.1.0 18 | 19 | USAGE: 20 | git-power [OPTIONS] 21 | 22 | FLAGS: 23 | -h, --help Prints help information 24 | -V, --version Prints version information 25 | 26 | OPTIONS: 27 | -b, --bits [default: 32] 28 | -t, --threads 29 | 30 | By default, git powre will brute-force the HEAD commit of the repository located in the current directory to start with 32 zero bits, and will use all available logical cores to perform the computation. If you want to apply this to multiple commits at a time, and not just the most recent one, perform an interactive rebase like so: 31 | 32 | git rebase --interactive --exec "git power" 33 | 34 | The reference you give can be any git object, for example `--root`, `origin/master`, or a specific commit hash. 35 | 36 | ## Possible Further Optimization 37 | * Improve SHA-1 caching - Based on [prior work](https://github.com/CouleeApps/git-power/issues/1), it seems that there is space for optimizing the SHA-1 state to prevent redundant computation. 38 | * Use OpenCL to run it on GPUs - According to hashcat, my Radeon 5700XT is capable of a hashrate several hundred times higher than what I'm currently achieving just on my CPU. 39 | 40 | Feedback is appreciated for any other possible optimization improvements. 41 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Error, Result}; 2 | use git2::{ObjectType, Oid, Repository, ResetType}; 3 | use sha1::{Digest, Sha1}; 4 | use std::convert::TryInto; 5 | use std::fmt; 6 | use std::io::{stdout, Write}; 7 | use std::sync::atomic::*; 8 | use std::sync::{mpsc, Arc}; 9 | use std::time::Instant; 10 | use structopt::StructOpt; 11 | 12 | const NONCE_LENGTH: usize = 32; 13 | 14 | #[derive(StructOpt)] 15 | struct Config { 16 | #[structopt(short, long, default_value = "32")] 17 | bits: u16, 18 | 19 | #[structopt(short, long)] 20 | threads: Option, 21 | } 22 | 23 | #[derive(Clone)] 24 | struct CommitBuffer { 25 | buf: String, 26 | header_len: usize, 27 | nonce_start: usize, 28 | nonce_end: usize, 29 | } 30 | 31 | impl CommitBuffer { 32 | fn new(buf: &[u8]) -> Result { 33 | let mut buf = String::from_utf8(buf.iter().cloned().collect())?; 34 | 35 | // The nonce will be in different locations depending on whether the commit is signed. 36 | // - If it isn't, then we add the nonce as an additional field in the commit details, 37 | // after the committer date. 38 | // - If the commit is signed, then we add the Nonce as a header *inside* the GPG sig. 39 | // Since the signature is only on the commit contents, the commit will stay signed. 40 | // Any proper GPG client will ignore this header and verify the signature just fine. 41 | let start = match buf.find("-----BEGIN PGP SIGNATURE-----") { 42 | Some(pgp_idx) => match buf[pgp_idx..].find("Nonce") { 43 | Some(idx) => pgp_idx + idx + 7, 44 | None => { 45 | let pgp_header_end = pgp_idx 46 | + buf[pgp_idx..] 47 | .find("\n ") 48 | .ok_or("Malformed PGP header") 49 | .map_err(Error::msg)?; 50 | buf.insert_str(pgp_header_end, "\nNonce: "); 51 | pgp_header_end + 8 52 | } 53 | }, 54 | None => { 55 | let header_end = buf 56 | .find("\n\n") 57 | .ok_or("Malformed commit") 58 | .map_err(Error::msg)?; 59 | match buf.find("nonce") { 60 | Some(idx) => idx + 6, 61 | None => { 62 | buf.insert_str(header_end, "\nnonce "); 63 | header_end + 7 64 | } 65 | } 66 | } 67 | }; 68 | 69 | // Here we insert the initial value of the nonce. In case the nonce already exists 70 | // and is shorter than the one we're inserting, we do a `replace_range`. 71 | let line_end = start 72 | + buf[start..] 73 | .find("\n") 74 | .ok_or("Malformed Nonce") 75 | .map_err(Error::msg)?; 76 | if line_end - start != NONCE_LENGTH { 77 | buf.replace_range(start..line_end, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); 78 | } 79 | 80 | // When git hashes an object, it prepends a header to the data which inclues the type 81 | // of the object, as well as its size. We opt to call out to a faster SHA-1 library, 82 | // so we need to prepend the header ourselves. 83 | let commit_header = format!("commit {}\0", buf.len()); 84 | buf.insert_str(0, &commit_header); 85 | let header_len = commit_header.len(); 86 | Ok(Self { 87 | buf, 88 | header_len, 89 | nonce_start: start + header_len, 90 | nonce_end: start + NONCE_LENGTH + header_len, 91 | }) 92 | } 93 | 94 | fn write_nonce(&mut self, val: u128) { 95 | // Only covers 128/160 bits of entropy. Possible chars: ABCDEFGHIJKLMNOP 96 | let mut nonce_bytes = [b'A'; NONCE_LENGTH]; 97 | for i in 0..NONCE_LENGTH { 98 | let idx = (val >> (4 * i)) & 0xF; 99 | nonce_bytes[31 - i] = b'A' + idx as u8; 100 | } 101 | // SAFETY: The nonce is always valid ASCII, and contain no multi-byte codepoints 102 | unsafe { self.buf[self.nonce_start..self.nonce_end].as_bytes_mut() } 103 | .copy_from_slice(&nonce_bytes); 104 | } 105 | 106 | fn data(&self) -> &str { 107 | // We just want the commit data itself, minus the prepended metadata header 108 | &self.buf[self.header_len..] 109 | } 110 | } 111 | 112 | impl fmt::Display for CommitBuffer { 113 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 114 | write!(f, "{}", self.data()) 115 | } 116 | } 117 | 118 | enum PowMessage { 119 | Update([u8; 20], u16), 120 | Done(CommitBuffer, [u8; 20]), 121 | } 122 | 123 | fn num_leading_zero_bits(hash: &[u8]) -> u16 { 124 | let mut zeros = 0; 125 | for &byte in hash.iter() { 126 | zeros += byte.leading_zeros(); 127 | if byte != 0 { 128 | break; 129 | } 130 | } 131 | zeros as u16 132 | } 133 | 134 | fn run_pow(commit: CommitBuffer, config: &Config) -> Result<(CommitBuffer, Oid)> { 135 | let start_time = Instant::now(); 136 | let (tx, rx) = mpsc::channel(); 137 | let stop = Arc::new(AtomicBool::new(false)); 138 | let num_hashes = Arc::new(AtomicU64::new(0)); 139 | let max_zeros = Arc::new(AtomicU16::new(0)); 140 | let target_zeros = config.bits; 141 | 142 | // We divide the range of possible nonces evenly among each thread. Each thread loops 143 | // through each nonce in its given range and calculates a hash, and if it satisfies the 144 | // POW requirement, sends it to the main thread. When this happens, we stop all other 145 | // threads using the AtomicBool `stop`. 146 | let num_threads = config.threads.unwrap_or(num_cpus::get() as u8) as u128; 147 | let chunk_size = u128::MAX / num_threads; 148 | for i in 0..num_threads { 149 | let tx = tx.clone(); 150 | let stop = Arc::clone(&stop); 151 | let num_hashes = Arc::clone(&num_hashes); 152 | let max_zeros = Arc::clone(&max_zeros); 153 | let mut commit = commit.clone(); 154 | std::thread::spawn(move || -> Result<()> { 155 | // Since the part of the commit before the nonce never changes, 156 | // we reuse the SHA-1 state computed up to the nonce's location. 157 | let mut hasher = Sha1::new(); 158 | hasher.update(&commit.buf[..commit.nonce_start]); 159 | for nonce in i * chunk_size..(i + 1) * chunk_size { 160 | // Check if a hash was already found by another thread 161 | if stop.load(Ordering::Relaxed) { 162 | break; 163 | } 164 | num_hashes.fetch_add(1, Ordering::Relaxed); 165 | 166 | // Write the nonce to the commit buffer and hash the buffer 167 | commit.write_nonce(nonce); 168 | let mut hasher = hasher.clone(); 169 | hasher.update(&commit.buf[commit.nonce_start..]); 170 | let hash: [u8; 20] = hasher.finalize().as_slice().try_into()?; 171 | 172 | // Check against our win condition 173 | let num_zeros = num_leading_zero_bits(&hash); 174 | if num_zeros > max_zeros.load(Ordering::Relaxed) { 175 | tx.send(PowMessage::Update(hash, num_zeros))?; 176 | max_zeros.store(num_zeros, Ordering::Relaxed); 177 | } 178 | if num_zeros >= target_zeros { 179 | tx.send(PowMessage::Done(commit, hash))?; 180 | break; 181 | } 182 | } 183 | Ok(()) 184 | }); 185 | } 186 | let mut stdout = stdout(); 187 | loop { 188 | match rx.recv()? { 189 | PowMessage::Update(hash, num_zeros) => { 190 | let hash = Oid::from_bytes(&hash)?; 191 | print!( 192 | "\rFound {} ({}/{} leading zeros)", 193 | hash, num_zeros, config.bits 194 | ); 195 | stdout.flush().unwrap(); 196 | } 197 | PowMessage::Done(buf, hash) => { 198 | stop.store(true, Ordering::Relaxed); 199 | // Print out some statistics once we're done 200 | let num_hashes = num_hashes.load(Ordering::Relaxed); 201 | let num_seconds = 202 | Instant::now().duration_since(start_time).as_millis() as f64 / 1000.0; 203 | println!( 204 | "\n{} attempts / {} seconds = {:.3}MH/s", 205 | num_hashes, 206 | num_seconds, 207 | num_hashes as f64 / 1_000_000.0 / num_seconds as f64 208 | ); 209 | let hash = Oid::from_bytes(&hash)?; 210 | return Ok((buf, hash)); 211 | } 212 | } 213 | } 214 | } 215 | 216 | fn main() -> Result<()> { 217 | let config = Config::from_args(); 218 | let repo = Repository::open(std::env::current_dir()?)?; 219 | let head_commit_hash = repo.head()?.peel_to_commit()?.id(); 220 | let odb = repo.odb()?; 221 | let buf = CommitBuffer::new(odb.read(head_commit_hash)?.data())?; 222 | 223 | // Find the hash we're looking for, then commit the buffer to the git object db, 224 | // and finally soft reset to point HEAD to the new commit. 225 | let (buf, hash) = run_pow(buf, &config)?; 226 | odb.write(ObjectType::Commit, buf.data().as_bytes())?; 227 | repo.reset(&repo.find_object(hash, None)?, ResetType::Soft, None)?; 228 | Ok(()) 229 | } 230 | --------------------------------------------------------------------------------