|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <ctype.h> |
|
#include <openssl/sha.h> |
|
|
|
#define TARGET_PREFIX "20250327" |
|
#define MAX_WORDS 256 |
|
#define MAX_TEXT 2048 |
|
#define MAX_ATTEMPTS (1ULL << 32) // 2^32 attempts (~4.3B, enough for 8-char prefix) |
|
|
|
const char *ORIGINAL_TEXT = "They're not particularly rare, but I think they're more pleasing to see.\n\n" |
|
"For instance this \"2822302\" makes me especially happy: https://github.com/BrowserBox/BrowserBox/commit/2822302387c4cb7ff71c4239da3dc5fa4c07e165\n\n" |
|
"It even extends up to 10 digits! That's not particularly rare - roughly 1% chance (10^10/16^10 i think) - but I just think they look nice.\n\n" |
|
"Are there any other people out there who are particularly pleased when they hit that? Sorta like hitting 7777 on the odometer, or whatever.\n\n" |
|
"I'm also a fan of the purely numeric identifiers Twitter/X uses (and has for ages).\n\n" |
|
"This all reminds me of that \"commit fuzzing tool\" that can make your log have whatever commit you want. Asked AI, turns out it's: https://github.com/not-an-aardvark/lucky-commit\n\n" |
|
"Lucky Commit! What a perfect name. I guess this Ask HN should be: Does anyone else like lucky commits?\n\n" |
|
"BTW the SHA has of this message starts with: 20250327"; |
|
|
|
typedef struct { |
|
char *lower; |
|
char *upper; |
|
int start; |
|
int len; |
|
} Word; |
|
|
|
void normalize_text(char *dest, const char *src, int len) { |
|
int j = 0, last_space = 0; |
|
for (int i = 0; i < len && src[i]; i++) { |
|
if (32 == src[i]) { |
|
if (!last_space && j > 0) dest[j++] = ' '; |
|
last_space = 1; |
|
} else { |
|
dest[j++] = src[i]; |
|
last_space = 0; |
|
} |
|
} |
|
dest[j] = '\0'; |
|
} |
|
|
|
void compute_hash(const char *text, char *hash_str) { |
|
unsigned char hash[SHA_DIGEST_LENGTH]; |
|
SHA1((unsigned char *)text, strlen(text), hash); |
|
for (int i = 0; i < SHA_DIGEST_LENGTH; i++) { |
|
sprintf(hash_str + (i * 2), "%02x", hash[i]); |
|
} |
|
hash_str[SHA_DIGEST_LENGTH * 2] = '\0'; |
|
} |
|
|
|
int is_in_url(int pos, const char *text) { |
|
const char *ptr = text; |
|
while (ptr < text + pos) { |
|
if (strncmp(ptr, "http", 4) == 0) { |
|
const char *start = ptr; |
|
while (*ptr && !isspace(*ptr)) ptr++; |
|
if (pos >= start - text && pos < ptr - text) return 1; |
|
} else { |
|
ptr++; |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
int parse_words(const char *text, Word *words, int *skip_indices, int *mutable_count) { |
|
int word_count = 0, i = 0, start = 0; |
|
int text_len = strlen(text); |
|
char *buf = malloc(text_len + 1); |
|
|
|
while (i <= text_len) { |
|
if (i == text_len || text[i] == 32) { |
|
if (i > start) { |
|
int len = i - start; |
|
strncpy(buf, text + start, len); |
|
buf[len] = '\0'; |
|
|
|
if (strncmp(buf, "http", 4) == 0 || !isalpha(buf[0])) { |
|
skip_indices[word_count] = 1; |
|
} else { |
|
words[*mutable_count].lower = strdup(buf); |
|
words[*mutable_count].upper = strdup(buf); |
|
words[*mutable_count].upper[0] = toupper(buf[0]); |
|
words[*mutable_count].lower[0] = tolower(buf[0]); |
|
words[*mutable_count].start = start; |
|
words[*mutable_count].len = len; |
|
(*mutable_count)++; |
|
} |
|
word_count++; |
|
} |
|
start = i + 1; |
|
} |
|
i++; |
|
} |
|
free(buf); |
|
return word_count; |
|
} |
|
|
|
int main(int argc, char *argv[]) { |
|
const char *file_path = (argc > 1) ? argv[1] : "input.txt"; |
|
printf("Processing file: %s\n", file_path); |
|
printf("Target prefix: %s\n", TARGET_PREFIX); |
|
|
|
Word words[MAX_WORDS]; |
|
int skip_indices[MAX_WORDS] = {0}; |
|
int mutable_count = 0; |
|
int total_words = parse_words(ORIGINAL_TEXT, words, skip_indices, &mutable_count); |
|
|
|
printf("Total mutable words: %d\n", mutable_count); |
|
unsigned long long total_combinations = (mutable_count > 64) ? MAX_ATTEMPTS : (1ULL << mutable_count); |
|
printf("Total combinations: %llu\n", total_combinations); |
|
|
|
char *current_text = malloc(MAX_TEXT); |
|
char hash_str[SHA_DIGEST_LENGTH * 2 + 1]; |
|
unsigned long long counter = 0; |
|
const unsigned long long report_interval = 1000000; |
|
|
|
FILE *fp = fopen(file_path, "w"); |
|
if (!fp) { perror("File open failed"); return 1; } |
|
fprintf(fp, "%s", ORIGINAL_TEXT); |
|
fclose(fp); |
|
|
|
while (counter < total_combinations) { |
|
strcpy(current_text, ORIGINAL_TEXT); |
|
int word_idx = 0; |
|
|
|
for (int i = 0; i < total_words && word_idx < mutable_count; i++) { |
|
if (!skip_indices[i]) { |
|
int bit = (counter >> word_idx) & 1; |
|
char *variant = bit ? words[word_idx].upper : words[word_idx].lower; |
|
memcpy(current_text + words[word_idx].start, variant, words[word_idx].len); |
|
word_idx++; |
|
} |
|
} |
|
|
|
normalize_text(current_text, current_text, MAX_TEXT); |
|
compute_hash(current_text, hash_str); |
|
|
|
if (strncmp(hash_str, TARGET_PREFIX, strlen(TARGET_PREFIX)) == 0) { |
|
printf("Success! Hash: %s\n", hash_str); |
|
printf("Counter: %llu (binary: ", counter); |
|
for (int i = mutable_count - 1; i >= 0; i--) printf("%d", (int)((counter >> i) & 1)); |
|
printf(")\n"); |
|
char *output_file = malloc(strlen(file_path) + 8); |
|
strcpy(output_file, file_path); |
|
strcat(output_file, ".vanity"); |
|
fp = fopen(output_file, "w"); |
|
fprintf(fp, "%s", current_text); |
|
fclose(fp); |
|
free(output_file); |
|
break; |
|
} |
|
|
|
if (counter % report_interval == 0) { |
|
printf("Progress: %llu/%llu - %.8s\n", counter, total_combinations, hash_str); |
|
} |
|
counter++; |
|
} |
|
|
|
if (counter == total_combinations) { |
|
printf("Failed after trying all %llu combinations\n", total_combinations); |
|
} |
|
|
|
for (int i = 0; i < mutable_count; i++) { |
|
free(words[i].lower); |
|
free(words[i].upper); |
|
} |
|
free(current_text); |
|
return 0; |
|
} |