1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
//! # [Day 4: The Ideal Stocking Stuffer](https://adventofcode.com/2015/day/4)
//!
//! Santa needs help [mining](https://en.wikipedia.org/wiki/Bitcoin#Mining) some AdventCoins
//! (very similar to [bitcoins](https://en.wikipedia.org/wiki/Bitcoin)) to use as gifts for all
//! the economically forward-thinking little girls and boys.
//!
//! To do this, he needs to find [MD5](https://en.wikipedia.org/wiki/MD5) hashes which,
//! in [hexadecimal](https://en.wikipedia.org/wiki/Hexadecimal), start with at least five zeroes.
//! The input to the MD5 hash is some secret key (your puzzle input, given below) followed by a
//! number in decimal. To mine AdventCoins, **you must find Santa the lowest positive
//! number (no leading zeroes: `1`, `2`, `3`, ...) that produces such a hash.**
//!
//! For example:
//!
//! -   If your secret key is `abcdef`, the answer is `609043`, because the MD5
//!     hash of `abcdef609043` starts with five zeroes (`000001dbbfa...`), and it is the
//!     lowest such number to do so.
//! -   If your secret key is `pqrstuv`, the lowest number it combines with to make an MD5 hash
//!     starting with five zeroes is `1048970`; that is, the MD5 hash of `pqrstuv1048970`
//!     looks like `000006136ef...`
//!
//! # Part Two
//!
//! Now find one that starts with **six zeroes**.

use crypto::digest::Digest;

/// Part 1: lowest positive number (no leading zeroes: `1`, `2`, `3`, ...) that produces a hash which
/// start with at least five zeroes
#[aoc(day4, part1)]
fn part1(input: &str) -> u64 {
    md5_suffix_increment_until(input, |output| {
        let first_five = output[0] as i32 + output[1] as i32 + (output[2] >> 4) as i32;
        first_five == 0
    })
}

/// Part 2: lowest positive number (no leading zeroes: `1`, `2`, `3`, ...) that produces a hash which
/// start with at least **six** zeroes
#[aoc(day4, part2)]
fn part2(input: &str) -> u64 {
    md5_suffix_increment_until(input, |output| {
        let first_six = output[0] as i32 + output[1] as i32 + output[2] as i32;
        first_six == 0
    })
}

/// increments a counter starting at 0 which is appended to `input` until `test` returns
/// true for the md5 hash buffer, then returns the counter
fn md5_suffix_increment_until(input: &str, test: fn(&[u8; 16]) -> bool) -> u64 {
    let mut hasher = crypto::md5::Md5::new();
    let mut output = [0; 16]; // An MD5 is 16 bytes
    for i in 0..u64::MAX {
        hasher.input(input.as_bytes());
        hasher.input(i.to_string().as_bytes());
        hasher.result(&mut output);
        if test(&output) {
            return i;
        }
        hasher.reset();
    }
    0
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn part1_example() {
        /* If your secret key is `abcdef`, the answer is `609043`, because the MD5
        hash of `abcdef609043` starts with five zeroes (`000001dbbfa...`), and it is the
        lowest such number to do so. */
        assert_eq!(part1("abcdef"), 609043);

        /* If your secret key is `pqrstuv`, the lowest number it combines with to make an MD5 hash
        starting with five zeroes is `1048970`; that is, the MD5 hash of `pqrstuv1048970`
        looks like `000006136ef...`. */
        assert_eq!(part1("pqrstuv"), 1048970);
    }
}