#include <blake2.h>
#include <err.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#define GOOD_TEMPLATE "2024-09-14T22:00Z\nhttps://example.com/twtxt.txt\nUseful backup command: rsync -a \"$HOME\" /mnt/backup "
#define BAD_TEMPLATE "2024-09-14T22:00Z\nhttps://example.com/twtxt.txt\nUseful backup command: rm -rf /some_important_directory "
/* Size of hash tables of values already computed. Must be a power of 2. */
#define TABLE_SIZE 0x100000
#define TABLE_MASK (TABLE_SIZE-1)
int suffix_bits;
int64_t range;
struct table_entry {
int64_t seed, hash;
struct table_entry *next;
};
struct table_entry *good[TABLE_SIZE], *bad[TABLE_SIZE];
int64_t
hash_twt(const char *twt)
{
int i;
uint8_t out[32];
int64_t result;
if (blake2b(out, twt, NULL, sizeof(out), strlen(twt), 0))
errx(1, "blake2b failed");
/* Do this the slow way to avoid endianness issues. Maybe the compiler
will figure it out. */
result = 0;
for (i = 0; i < 8; ++i) {
result *= 0x100;
result += out[24+i];
}
return result & (range - 1);
}
char
base32_digit(int n) {
if (n < 26)
return 'a' + n;
else
return '0' + n - 24;
}
/* Does not add final \0. */
void
to_base32(int64_t n, char *out, int length)
{
while (length > 0) {
out[--length] = base32_digit(n % 32);
n /= 32;
}
}
void
show_example_hash()
{
/* One of my twts so I can compare the hash I compute to the hash jenny
computes. */
const char *twt = "https://www.falsifian.org/twtxt.txt\n2024-08-09T04:03:00Z\nHello twtxt! I'm James (or @<falsifian https://www.falsifian.org/twtxt.txt>). I live in Toronto. Recent interests include space complexity, simple software, and science fiction.";
char base_32[14];
int base_32_length;
/* The last group of 5 bits always ends in four zeroes. Set
base32_length to the (suffix_bits + 4) / 5 rounded up. */
base_32_length = (suffix_bits + 8) / 5;
printf("Test twt:\n%s\n", twt);
base_32[base_32_length] = 0;
to_base32(hash_twt(twt) * 16, base_32, base_32_length);
printf("Test twt hash: %s\n", base_32);
}
void
make_twt(const char *template, int64_t seed, char *out, size_t len) {
snprintf(out, len, template, seed);
}
void
add_twt(
int64_t seed, const char *twt, struct table_entry **this_table,
struct table_entry **other_table, const char *this_template,
const char *other_template)
{
char buf[1000];
struct table_entry *entry;
int64_t hash = hash_twt(twt);
int b32_len = (suffix_bits + 8) / 5; /* see explanation in show_example_hash() */
entry = malloc(sizeof(*entry));
if (entry == NULL) err(1, "malloc");
entry->next = this_table[hash % TABLE_MASK];
entry->seed = seed;
entry->hash = hash;
this_table[hash % TABLE_MASK] = entry;
/* Look for the same hash in the other table. */
entry = other_table[hash % TABLE_MASK];
while (entry != NULL) {
if (entry->hash == hash) {
to_base32(hash * 16, buf, b32_len);
buf[b32_len] = 0;
printf("\n\nCollision after %lld or %lld hashes with hash %s:\n\n",
2 * seed + 1, 2 * seed + 2, buf);
make_twt(this_template, seed, buf, sizeof(buf));
printf("%s\n\n", buf);
make_twt(other_template, entry->seed, buf, sizeof(buf));
printf("%s\n\n", buf);
exit(0);
}
entry = entry->next;
}
}
int
main(int argc, char **argv)
{
int64_t i;
char twt[1000];
if (argc != 2)
errx(1, "usage: %s suffix_length", argv[0]);
suffix_bits = strtol(argv[1], NULL, 10);
if (suffix_bits > 76)
errx(1, "suffix_bits must be at most 76");
range = 1LL << suffix_bits;
show_example_hash();
for (i = 0; i < 0x7fffffffffffffff; ++i) {
make_twt(GOOD_TEMPLATE, i, twt, sizeof(twt));
add_twt(i, twt, good, bad, GOOD_TEMPLATE, BAD_TEMPLATE);
make_twt(BAD_TEMPLATE, i, twt, sizeof(twt));
add_twt(i, twt, bad, good, BAD_TEMPLATE, GOOD_TEMPLATE);
}
printf("Wow, you left me running for a long time! Sorry, I didn't find a collision.\n");
}