Shout out to another student on my university course. We had to implement this algorithm as a class exercise. Most people "discovered" the same kind of algorithm which solves it in 3-4 steps. However this other student came up with an implementation which seemed almost supernatural: It would get the right answer in a single step!
The random number generator on the old Sparcstations we were using used the time_t and PID as a seed, and the exercise test harness used a random number straight from rand(3) to generate the secret. The student's implementation simply worked out the answer from the date and process PID and knowledge of how the linear congruential generator worked.
(from a research paper by F. Dörre and V. Klebanov)
The bitcoin theft incident.
The presumed genesis of the attack is as follows: Bitcoin operates a public database of all transactions, the block chain. Each transaction is cryptographically signed by its initiator using the ECDSA scheme. Creating ECDSA signatures requires a per-transaction nonce. Partial predictability of nonces allows for one class of attacks, but using the same nonce for two transactions signed by the same key---which is what probably happened---constitutes a catastrophic security failure. Anyone can easily identify this case from the information recorded in the block chain, reconstruct the victim’s private key, and divert their money to a bitcoin address of choice. No intrusion into the victim’s system is necessary. A loss of seed entropy in the PRNG used for
generating nonces increases the probability of the breach.
This ultimately caused Google to replace the Android PRNG, which was only providing 40% of the requested entropy for this particular use.
-----
I also recall someone had discovered an online poker service's shuffle algorithm was something common (maybe Fisher-Yates?) based on java.util.Random with system time as a seed. Once you see your own hand and part of the board, you get a fairly accurate idea of what could be in someone else's hand from the small-ish sample of possible deals around system time.
The scoring function is wrong, checking for white is not as simple as calling contains.
if guess[i] == secret_code[i]:
red += 1
else:
if guess[i] in secret_code:
white += 1
With the secret XXXY a guess of YYYY will be scored as one red and three white but it should be just one red. You have to keep track of which positions in the secret have already been consumed, in this case the Y in the secret gets consumed by the Y in the same position in the guess yielding the one red, for the guess YYYX the Y in the secret will be consumed by the first Y in the guess yielding only one white instead of three. Plus of course a second white for the X. When implementing this, one has to be careful that a white does not consume a later red if one wants to do it in a single loop, i.e. it is not good enough to look for any unconsumed match, it must be unconsumed and also not yield a red.
Wordle programs often start with a similar bug in their (similar) scoring function. Apart from tracking positions and consuming them as you suggested, there's also another approach to writing the scoring function, suggested by Knuth's notation in the paper mentioned in another comment:
from collections import Counter
def score_guess(guess, secret_code):
red = sum(guess[i] == secret_code[i] for i in range(len(guess)))
total = (Counter(guess) & Counter(secret_code)).total()
return (red, total - red)
assert score_guess('YYYY', 'XXXY') == (1, 0)
assert score_guess('YYYX', 'XXXY') == (0, 2)
(The `&` on `Counter` computes minimum of the two counts.)
The Knuth approach said to be implemented in this post is described in:
> The computer as Master Mind. Journal of Recreational Mathematics 9 (1976), pp. 1–6. Reprinted with an addendum as Chapter 25 of Selected Papers on Fun and Games.
• the scoring function is wrong, as pointed out in comment here by danbruc,
• the code in the post simply guesses at random ("It only randomly selects its next guess from a pool of possible remaining guessing"), while Knuth's approach is of "choosing at every stage a test pattern that minimizes the maximum number of remaining possibilities over all conceivable responses by the codemaker". (In fact if you run the program in the post, it takes 6 guesses to win!)
Neat. In college we created a version for smart-tv that contained a challenge mode where a certain game would have to be finished in a single turn. Therefore we calculated all possible games where the player always chooses an option that still makes sense until only one move was sensible and would still win the game (we then took only the ones with three of four moves i think and scraped the last move). Sadly I somehow lost the source for the generation, but it took quite a while to calculate -- only the database with the result is left... (the challenge mode can still be played in our super 'fancy' online version: https://www.phwitti.com/projects/mastermind/ ). To write a blog post about it is on the todo list ;).
Mastermind intrigued me in the same way as the author some time ago, and I've used it as a standard problem when trying out new computational frameworks/methods ever since.
22 comments
[ 3.0 ms ] story [ 73.1 ms ] threadThe random number generator on the old Sparcstations we were using used the time_t and PID as a seed, and the exercise test harness used a random number straight from rand(3) to generate the secret. The student's implementation simply worked out the answer from the date and process PID and knowledge of how the linear congruential generator worked.
The bitcoin theft incident.
The presumed genesis of the attack is as follows: Bitcoin operates a public database of all transactions, the block chain. Each transaction is cryptographically signed by its initiator using the ECDSA scheme. Creating ECDSA signatures requires a per-transaction nonce. Partial predictability of nonces allows for one class of attacks, but using the same nonce for two transactions signed by the same key---which is what probably happened---constitutes a catastrophic security failure. Anyone can easily identify this case from the information recorded in the block chain, reconstruct the victim’s private key, and divert their money to a bitcoin address of choice. No intrusion into the victim’s system is necessary. A loss of seed entropy in the PRNG used for generating nonces increases the probability of the breach.
This ultimately caused Google to replace the Android PRNG, which was only providing 40% of the requested entropy for this particular use.
-----
I also recall someone had discovered an online poker service's shuffle algorithm was something common (maybe Fisher-Yates?) based on java.util.Random with system time as a seed. Once you see your own hand and part of the board, you get a fairly accurate idea of what could be in someone else's hand from the small-ish sample of possible deals around system time.
Found it:
https://algs4.cs.princeton.edu/lectures/keynote/21Elementary...
Slide 61-62, has some more references.
https://news.ycombinator.com/item?id=639976
https://www.youtube.com/watch?v=v68zYyaEmEA
https://www.youtube.com/watch?v=fRed0Xmc2Wg
> The computer as Master Mind. Journal of Recreational Mathematics 9 (1976), pp. 1–6. Reprinted with an addendum as Chapter 25 of Selected Papers on Fun and Games.
https://www.cs.uni.edu/~wallingf/teaching/cs3530/resources/k... is the original paper, but the addendum in the 2011 book is 5 pages long, longer than the original article itself (not counting its figure/table), and discusses various later results/ideas. For example, the addendum mentions that while Knuth's approach only minimizes the worst-case number of guesses (5, with 4.4753 on average), we can try to also minimize the expected number of guesses: I don't want to quote at length, but see https://stackoverflow.com/a/54917672 and the discussion on the German Wikipedia https://de.wikipedia.org/w/index.php?title=Mastermind_(Spiel....
In this post however, it looks like
• the scoring function is wrong, as pointed out in comment here by danbruc,
• the code in the post simply guesses at random ("It only randomly selects its next guess from a pool of possible remaining guessing"), while Knuth's approach is of "choosing at every stage a test pattern that minimizes the maximum number of remaining possibilities over all conceivable responses by the codemaker". (In fact if you run the program in the post, it takes 6 guesses to win!)
Here is my Rust version with multi-threading, SIMD, WASM running on your device inside a WebApp: https://0xbe7a.github.io/mastermind/
Repo: https://github.com/0xbe7a/mastermind
It is quite fast (1.8 Billion position pairs evaluated in 1652ms on my device) and can also exploit some symmetries inside the solution space.
Click "Solve within 5 moves"
https://ludi317.github.io/
Gory details here: https://github.com/ludi317/ludi317.github.io
I also have a copy of Mastermind that my parents had as kids, it's probably from the 70s? Cool vibes all around, HN. :-)