Permalink
Browse files

no spoilers

  • Loading branch information...
0 parents commit 1b1e55283a3d67c1624a84aefd7cdb0bba2640c5 @alx committed Oct 11, 2012
@@ -0,0 +1,171 @@
+# "This Machine Kills Secret" Puzzle
+
+![Book Cover](https://raw.github.com/alx/this_machine_kills_secrets/master/img/this-machine-kills-secrets-cover.png)
+
+On this project, I'm trying to crowd-source the solution of the puzzle you can find inside Andy Greenberg new book: [This Machine Kills Secrets](http://www.thismachinekillssecrets.com/puzzle/)
+
+You'll find:
+
+* *secret.csv*: a csv format file containing the page number and the decrypted morse code
+* *scripts/verify_code_on_page.rb*: a script to display the morse code that has been entered in *secret.csv* and verify it matches with the morse on the selected page
+* *scripts/lrs.rb*: a script ot find longest repeated substring
+* *scripts/double_dots.rb*: cut the full code depending on the position of the double_dots
+
+**Important**: Andy Greenberg tells in a tweet that the current translation is invalid, but it should not prevent us from solving the puzzle! (Source: [tweet_1](https://twitter.com/a_greenberg/status/254604683518308352) , [tweet_2](https://twitter.com/a_greenberg/status/254604991615094784) )
+
+Some correction on secret.csv has been made after discovering the segment-modulo deciphering, so that might have correct this alert.
+
+# More informations
+
+From the book website:
+
+This Machine Kills Secrets contains a cryptographic puzzle designed by puzzlemaster G. Mark Hardy. The first three readers to solve it (not including those who had access to advance review copies, please) and email their answer to agreenberg (at) forbes (dot) com will have their name published in the paperback edition of the book and any subsequent editions.
+
+While there are several “intermediate” answers to be found in the puzzle, winners must find the “final” answer. Solvers will know they’ve found that final answer if it closely relates to Tim May’s cat.
+
+# Links
+
+* Book website: http://www.thismachinekillssecrets.com/
+* r/cryptography thread: http://www.reddit.com/r/cryptography/comments/111846/this_machine_kills_secrets_book_puzzle/
+* Andy Greenberg twitter: http://twitter.com/a_greenberg
+* G. Mark Hardy: http://www.gmarkhardy.com/
+
+# Decrypting...
+
+## Double-dot segments
+
+```
+$ ruby double_dots.rb
+double-dot on page 216
+code: MYENZVMYZZRYNHDW
+first part: MYENZVMY
+second part: ZZRYNHDW
+---
+double-dot on page 265
+code: IHNIDOUOYUJFITCT
+first part: IHNIDOUO
+second part: YUJFITCT
+---
+section 0 (length 720): AGMZNYFPISGIHWLNDLAFVTAZESRMFHLUDKTHIESDYHPGPOCXAUHKAUSAZUDXNSUALXEDBVDJCPKMSEMLLTMXAEJFVFCWHVWGOYAVQYAWYZPOOEUHFNTVZOZXCXOALRAZTJIVFYTQXLOPYYUMHBCFDTBIYMQDXREEESIDWIOWLEIMXEGECAALIXWTONPURMRFOBNDTSCJPWJLKOOBLFSEXPCIWNHVKTAHUMMTXCZRPOIABECMMWAIRDSOVUBHTNYFNVYTXDCGSRLVPEYGSBBRNGHTLXMTESIUTQEQIFRWGMXEGSGMONJKPUZCHVUSCFTMGIUUNOXQNVTKNRLYEJSAUCUIXMSJBORATQTUIVPMZXAODMPBHOIIZXWDBVGCNOBLEJFGCTHHMSKXMRXTRNITBSJCIDAEWDVLTVECZTKUIXJSSIUFOBDHISRQKKUGWNAGRKIZYILZFCNAEATQEYNGMFHEIBHXORQSVYRFGXTMVCWHRYSQEVEDROKEPMHTJHKPADJYNEXKAJTUHUSRLHOTRBTZQXPHJMKSATIAOMHFYNCHGAESTFCIESRRPVDLINWDRMIXCVZUCECYKABEUVNUQXIEKTWSVCTNTYGYDTLSOEJJLTYKHUTGFSDNPEKEOIYBBMHAZORBDBAWWVRRUOACCTWVEKBDVIISTOTXGIQEAFXSOTYLRLTRXJTGECBMAIWFEEHXDEFDYXQLBEABMFEXFYLEQWVDKSRREVRBEULOVZQQISJKMYENZVMY
+section 1 (length 160): ZZRYNHDWBNIBXEJSZAMPNOYPEXSFHYURFWHKNUBERJOHIOICNUZDILZBTSNGWIXILWONODQCHXENBVLWHVONIKYZEUSMVJDCUXSXVEIBMWCLWTKIJMMAWEXWEOILISHEQUSJHPVGDDEEVCVGMSAUMIBCIHNIDOUO
+section 2 (length 232): YUJFITCTUACNCAUTSLDYYGAMMCEHGECHEXCBEEOKIZABLMHGEJBPANOTSXTHVCOUXYBGYTOXTVAIRVUSQFIHSLKXHKNZGKQGQGBLOEICESYEOXTVERNFLARHMSRBREPNURIXFNVCELAIVKFFALMUTDGKSYTRWSEFLPFOLCZVSTEODXSNJFSSULMOAGAJMQHNHNZQMEVYGCNJBEVTABADHAJEEJDCSGNGOBTQPNY
+```
+
+## Code features
+
+72 segments of 16 chars.
+
+Double-dots on top of Z char (page 216) and of space (page 265).
+
+## Letter frequency
+
+![letter frequency](https://raw.github.com/alx/this_machine_kills_secrets/master/img/letter_frequency.png)
+
+Tool: http://www.wiley.com/college/mat/gilbert139343/java/java11_applet.html
+
+## Longest repeated substring
+
+```
+ruby scripts/lrs.rb AGMZ...
+"MXEG" (4 characters)
+```
+
+This substring is repeated twice in the entire document.
+
+## Strange double-dots on top of 2 codes
+
+A strange double-dots appear twice on top of the code. These dots were not included in the morse translation, but surely have a meaning.
+
+### Page 216 code
+
+![img/page_216.jpg](https://raw.github.com/alx/this_machine_kills_secrets/master/img/page_216.jpg)
+
+### Page 265 code
+
+![img/page_265.jpg](https://raw.github.com/alx/this_machine_kills_secrets/master/img/page_265.jpg)
+
+
+## Complete code
+
+Morse conversion: http://www.onlineconversion.com/morse_code.htm
+
+```
+AGMZNYFPISGIHWLNDLAFVTAZESRMFHLUDKTHIESDYHPGPOCXAUHKAUSAZUDXNSUALXEDBVDJCPKMSEMLLTMXAEJFVFCWHVWGOYAVQYAWYZPOOEUHFNTVZOZXCXOALRAZTJIVFYTQXLOPYYUMHBCFDTBIYMQDXREEESIDWIOWLEIMXEGECAALIXWTONPURMRFOBNDTSCJPWJLKOOBLFSEXPCIWNHVKTAHUMMTXCZRPOIABECMMWAIRDSOVUBHTNYFNVYTXDCGSRLVPEYGSBBRNGHTLXMTESIUTQEQIFRWGMXEGSGMONJKPUZCHVUSCFTMGIUUNOXQNVTKNRLYEJSAUCUIXMSJBORATQTUIVPMZXAODMPBHOIIZXWDBVGCNOBLEJFGCTHHMSKXMRXTRNITBSJCIDAEWDVLTVECZTKUIXJSSIUFOBDHISRQKKUGWNAGRKIZYILZFCNAEATQEYNGMFHEIBHXORQSVYRFGXTMVCWHRYSQEVEDROKEPMHTJHKPADJYNEXKAJTUHUSRLHOTRBTZQXPHJMKSATIAOMHFYNCHGAESTFCIESRRPVDLINWDRMIXCVZUCECYKABEUVNUQXIEKTWSVCTNTYGYDTLSOEJJLTYKHUTGFSDNPEKEOIYBBMHAZORBDBAWWVRRUOACCTWVEKBDVIISTOTXGIQEAFXSOTYLRLTRXJTGECBMAIWFEEHXDEFDYXQLBEABMFEXFYLEQWVDKSRREVRBEULOVZQQISJKMYENZVMYZZRYNHDWBNIBXEJSZAMPNOYPEXSFHYURFWHKNUBERJOHIOICNUZDILZBTSNGWIXILWONODQCHXENBVLWHVONIKYZEUSMVJDCUXSXVEIBMWCLWTKIJMMAWEXWEOILISHEQUSJHPVGDDEEVCVGMSAUMIBCIHNIDOUOYUJFITCTUACNCAUTSLDYYGAMMCEHGECHEXCBEEOKIZABLMHGEJBPANOTSXTHVCOUXYBGYTOXTVAIRVUSQFIHSLKXHKNZGKQGQGBLOEICESYEOXTVERNFLARHMSRBREPNURIXFNVCELAIVKFFALMUTDGKSYTRWSEFLPFOLCZVSTEODXSNJFSSULMOAGAJMQHNHNZQMEVYGCNJBEVTABADHAJEEJDCSGNGOBTQPNYI
+```
+
+## Codes by Parts
+
+Part One:
+
+```
+AGMZNYFPISGIHWLNDLAFVTAZESRMFHLUDKTHIESDYHPGPOCXAUHKAUSAZUDXNSUALXEDBVDJCPKMSEMLLTMXAEJFVFCWHVWGOYAVQYAWYZPOOEUHFNTVZOZXCXOALRAZTJIVFYTQXLOPYYUMHBCFDTBIYMQDXREEESIDWIOWLEIMXEGECAALIXWTONPURMRF
+```
+
+Part Two:
+
+```
+OBNDTSCJPWJLKOOBLFSEXPCIWNHVKTAHUMMTXCZRPOIABECMMWAIRDSOVUBHTNYFNVYTXDCGSRLVPEYGSBBRNGHTLXMTESIUTQEQIFRWGMXEGSGMONJKPUZCHVUSCFTMGIUUNOXQNVTKNRLYEJSAUCUIXMSJBORATQTUIVPMZXAODMPBHOIIZXWDBVGCNOBLEJFGCTHHMSKXMRXTRNITBSJCIDAEWDVLTVECZTKUIXJSSIUFOBDHISRQKKUGWNAGRKIZYILZFCNAEATQEYNGMFHEIBHXORQSVYRFGXTMVCWHRYSQEVEDROKEPMHTJHKPADJYNEXKAJTUHUSRLHOTRBTZQXPHJMKSATIAOMHFYNCHGAESTFCIESRRPVDLINWDRMIXCVZUCECYKABEUVNUQXIEKTWSVCTN
+```
+
+Part Three:
+
+```
+TYGYDTLSOEJJLTYKHUTGFSDNPEKEOIYBBMHAZORBDBAWWVRRUOACCTWVEKBDVIISTOTXGIQEAFXSOTYLRLTRXJTGECBMAIWFEEHXDEFDYXQLBEABMFEXFYLEQWVDKSRREVRBEULOVZQQISJKMYENZVMYZZRYNHDWBNIBXEJSZAMPNOYPEXSFHYURFWHKNUBERJOHIOICNUZDILZBTSNGWIXILWONODQCHXENBVLWHVONIKYZEUSMVJDCUXSXVEIBMWCLWTKIJMMAWEXWEOILISHEQUSJHPVGDDEEVCVGMSAUMIBCIHNIDOUOYUJFITCTUACNCAUTSLDYYGAMMCEHGECHEXCBEEOKIZABLMHGEJBPANOTSXTHVCOUXYBGYTOXTVAIRVUSQFIHSLKXHKNZGKQGQGBLOEICESYEOXTVERNFLARHMSRBREPNURIXFNVCELAIVKFFALMUTDGKSYTRWSEFLPFOLCZVSTEODXSNJFSSULMOAGAJMQHNHNZQMEVYGCNJBEVTABADHAJEEJDCSGNGOBTQPNYI
+```
+
+## Codes by Chapters
+
+Introduction
+
+```
+AGMZNYFPISGIHWLN
+```
+
+Chapter 1
+
+```
+DLAFVTAZESRMFHLUDKTHIESDYHPGPOCXAUHKAUSAZUDXNSUALXEDBVDJCPKMSEMLLTMXAEJFVFCWHVWGOYAVQYAWYZPOOEUHFNTVZOZXCXOALRAZTJIVFYTQXLOPYYUMHBCFDTBIYMQDXREEESIDWIOWLEIMXEGECAALIXWTONPURMRF
+```
+
+Chapter 2
+
+```
+OBNDTSCJPWJLKOOBLFSEXPCIWNHVKTAHUMMTXCZRPOIABECMMWAIRDSOVUBHTNYFNVYTXDCGSRLVPEYGSBBRNGHTLXMTESIUTQEQIFRWGMXEGSGMONJKPUZCHVUSCFTMGIUUNOXQNVTKNRLY
+```
+
+Chapter 3
+
+```
+EJSAUCUIXMSJBORATQTUIVPMZXAODMPBHOIIZXWDBVGCNOBLEJFGCTHHMSKXMRXTRNITBSJCIDAEWDVLTVECZTKUIXJSSIUFOBDHISRQKKUGWNAGRKIZYILZFCNAEATQEYNGMFHEIBHXORQS
+```
+
+Chapter 4
+
+```
+VYRFGXTMVCWHRYSQEVEDROKEPMHTJHKPADJYNEXKAJTUHUSRLHOTRBTZQXPHJMKSATIAOMHFYNCHGAESTFCIESRRPVDLINWDRMIXCVZUCECYKABEUVNUQXIEKTWSVCTN
+```
+
+Chapter 5
+
+```
+TYGYDTLSOEJJLTYKHUTGFSDNPEKEOIYBBMHAZORBDBAWWVRRUOACCTWVEKBDVIISTOTXGIQEAFXSOTYLRLTRXJTGECBMAIWFEEHXDEFDYXQLBEABMFEXFYLEQWVDKSRREVRBEULOVZQQISJKMYENZVMYZZRYNHDWBNIBXEJSZAMPNOYP
+```
+
+Chapter 6
+
+```
+EXSFHYURFWHKNUBERJOHIOICNUZDILZBTSNGWIXILWONODQCHXENBVLWHVONIKYZEUSMVJDCUXSXVEIBMWCLWTKIJMMAWEXWEOILISHEQUSJHPVGDDEEVCVGMSAUMIBCIHNIDOUOYUJFITCTUACNCAUTSLDYYGAM
+```
+
+Chapter 7
+
+```
+MCEHGECHEXCBEEOKIZABLMHGEJBPANOTSXTHVCOUXYBGYTOXTVAIRVUSQFIHSLKXHKNZGKQGQGBLOEICESYEOXTVERNFLARHMSRBREPNURIXFNVCELAIVKFFALMUTDGKSYTRWSEFLPFOLCZVSTEODXSNJFSSULMO
+```
+
+Conclusion
+
+```
+AGAJMQHNHNZQMEVYGCNJBEVTABADHAJEEJDCSGNGOBTQPNYI
+```
+
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@@ -0,0 +1,21 @@
+require 'csv'
+#
+# This script ca be used to display the morse code on the argument page
+#
+# Usage :
+# $ ruby verify_code_on_page.rb 202
+# .- .. -.-- --- ...
+#
+secrets = CSV.read("../secret.csv", {:headers => true})
+
+16.times do |i|
+ message = ""
+ secrets.each do |r|
+ message += r["code"][i]
+ end
+ if i < 9
+ puts "#{i + 1} : #{message}"
+ else
+ puts "#{i + 1} : #{message}"
+ end
+end
@@ -0,0 +1,39 @@
+require 'csv'
+
+secrets = CSV.read("../secret.csv", {:headers => true})
+
+sections = []
+currentSection = ""
+
+secrets.each do |r|
+ case r["page"].to_i
+ when 216
+ puts "double-dot on page 216"
+ puts "code: #{r["code"]}"
+ puts "first part: #{r["code"][0...8]}"
+ puts "second part: #{r["code"][8...16]}"
+ puts "---"
+ currentSection << r["code"][0...8]
+ sections << currentSection
+ currentSection = r["code"][8...16]
+ when 265
+ puts "double-dot on page 265"
+ puts "code: #{r["code"]}"
+ puts "first part: #{r["code"][0...8]}"
+ puts "second part: #{r["code"][8...16]}"
+ puts "---"
+ currentSection << r["code"][0...8]
+ sections << currentSection
+ currentSection = r["code"][8...16]
+ else
+ currentSection << r["code"]
+ end
+end
+
+sections << currentSection
+
+sections.each_with_index do |code, index|
+ puts "section #{index} (length #{code.length}): #{code}"
+end
+
+
@@ -0,0 +1,159 @@
+# A solution to RubyQuiz #153.
+#
+# Finds the longest, non-overlapping repeated substring in its input.
+#
+# See http://www.rubyquiz.com/quiz153.html for details.
+#
+# The latest version of this solution can also be found at
+# http://learnruby.com/examples/ruby-quiz-153.shtml .
+
+# When run, the input can be on the command line, come from standard
+# input, or come from a file:
+#
+# ruby lrs.rb banana
+# ruby lrs.rb "Madam I'm Adam."
+# ruby lrs.rb -f homer-illiad.txt
+# cat homer-illiad.txt | ruby lrs.rb
+
+# The basic technique used by this solution is to create an array of
+# all suffixes of the data. So if the input were "banana", the array
+# would contain ["banana", "anana", "nana", "ana", "na", "a"]. Then
+# we sort this array, so it would now contain ["a", "ana", "anana",
+# "banana", "na", "nana"]. Finally we can compare neighboring entries
+# in the array to see if they share a long enough prefix to beat the
+# current best.
+
+# Extra care must be taken if the substrings are not allowed to
+# overlap. Consider the input "ananana"; the longest non-overlapping
+# substring is "ana". The array of sorted suffixes of is ["a", "ana",
+# "anana", "ananana", "na", "nana", "nanana"]. The 2nd and 3rd items
+# can only have a match of "an" because the "ana" would overlap, and
+# the same is true with the 3rd and 4th items. However by comparing
+# the 2nd and 4th items we can get the desired result of "ana". So
+# under certain circumstances we have to compare an item with more
+# than just its immediate predecessor.
+
+# This program seems to run reasonably fast. It should run in O(n *
+# log n) time in most cases, assuming that Array's sort method
+# provides that performance. Due to the rare cases when the program
+# cannot just compare an item and its immediate predecessor, there may
+# be some strange cases where it requires O(n ** 2). Because Ruby
+# allows a computed substring to share the data with the original
+# string (until one of the strings is altered, i.e., "copy on write"),
+# the memory used is linear to the input size.
+
+
+# returns the maximum of the two parameters
+def max(a, b)
+ a >= b ? a : b
+end
+
+
+# Return the longest common prefix between two strings. If max is
+# specified then the longest common prefix cannot exceed it
+def longest_common_prefix(s1, s2, max = nil)
+ l1, l2 = s1.size, s2.size
+ min = l1 < l2 ? l1 : l2
+ min = min < max ? min : max if max
+ min.times do |i|
+ return s1.slice(0, i) if s1[i] != s2[i]
+ end
+ return s1.slice(0, min)
+end
+
+
+# Returns the longest repeated substring in a given string.
+def longest_repeated_substring(string)
+ size = string.length
+
+ # put every possible suffix into an array
+ suffixes = Array.new(size)
+ size.times do |i|
+ suffixes[i] = string.slice(i, size)
+ end
+
+ # sort the array of suffixes, so common substrings (i.e., prefixes
+ # of suffixes) will be found in neighboring elements of the array
+ suffixes.sort!
+
+ best = ""
+ at_least_size = 1 # the size to meet or exceed to be the new best
+ distance = nil
+ neighbors_to_check = 1
+
+ # compare pairs of consecutive suffixes and see how much initial
+ # commonality there is
+ # (size - 1).times do |i|
+ (1...size).each do |i|
+ # p [i, neighbors_to_check]
+ s1 = suffixes[i]
+
+ # generally we will only need to compare the ith item and the one
+ # preceding it; however if we were in a position to reject a long
+ # enough common substring due to overlap issues, then we may have
+ # to compare an ith item with additional preceding items;
+ # neighbors_to_check tracks how many neighbors we need to check
+ neighbors_to_check.downto(1) do |neighbor|
+ s2 = suffixes[i - neighbor]
+
+ # make sure that these to suffixes further apart than the size
+ # of the current best; we don't explicitly track the index of
+ # these suffixes, but since all suffixes go to the end of the
+ # initial string, the size can be used as a proxy
+ distance = (s1.size - s2.size).abs
+ if distance < at_least_size
+ if s1.size >= at_least_size &&
+ s2.size >= at_least_size &&
+ s1.slice(0, at_least_size) == s2.slice(0, at_least_size)
+ neighbors_to_check = max(neighbors_to_check, neighbor + 1)
+ else
+ neighbors_to_check = neighbor
+ end
+ next
+ end
+
+ # if neighboring suffixes don't at least match as far as the best,
+ # no need to check more carefully
+ unless s1.slice(0, at_least_size) == s2.slice(0, at_least_size)
+ neighbors_to_check = neighbor
+ next
+ end
+
+ # get the longest common prefix that's no larger than distance,
+ # since at that point the substrings overlap
+ best = longest_common_prefix(s1, s2, distance)
+ p best
+ at_least_size = best.size + 1
+ if best.size == distance
+ neighbors_to_check = max(neighbors_to_check, neighbor + 1)
+ else
+ neighbors_to_check = neighbor
+ end
+ end
+ end
+
+ best
+end
+
+
+if $0 == __FILE__
+ string = nil
+ if ARGV[0] == "-f"
+ open(ARGV[1]) do |f|
+ string = f.read
+ end
+ elsif ARGV.size == 0
+ string = STDIN.read
+ elsif ARGV[0] =~ /^-/ || ARGV.size > 1
+ STDERR.puts "usage:"
+ STDERR.puts " #{$0} (note: input comes from standard input)"
+ STDERR.puts " #{$0} string"
+ STDERR.puts " #{$0} -f filename"
+ exit
+ else
+ string = ARGV[0]
+ end
+
+ result = longest_repeated_substring(string)
+ puts result && "\"#{result}\" (#{result.length} characters)" || "none"
+end
Oops, something went wrong.

0 comments on commit 1b1e552

Please sign in to comment.