Misja Gynvaela 006

MISJA 006            goo.gl/te47XT                  DIFFICULTY: ███░░░░░░░ [3/10]
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
Przeglądając stare dokumenty z lat '60 zeszłego wieku natrafiliśmy na taką oto
notatkę:

  c5 c2 c3 c4 c9 c3 40 82 a8 93 82 a8 40 86 81 91 95 a8 40 87 84
  a8 82 a8 40 82 a8 93 40 93 96 87 89 83 a9 95 a8 4b 40 c1 93 85
  40 95 89 85 40 91 85 a2 a3 4b

Przypuszczamy, że to jakieś zdanie w języku Polskim, ale nie udało nam się tego
zdekodować. Zrzucimy to więc na Ciebie. Powodzenia!

--

Odzyskaną wiadomość umieśc w komentarzu pod tym video 🙂
Linki do kodu/wpisów na blogu/etc z opisem rozwiązania są również mile
widziane!

P.S. Rozwiązanie zadania przedstawie na początku kolejnego livestreama.

Przede wszystkim, bardzo mi wstyd, że rozwiązanie tej zagadki zajęło mi ±90 minut. Przy czym chciałbym tutaj zwalić winę na Gynvaela, ponieważ wprowadził w zadaniu zmyłkę, ale o tym za chwilę.

Metoda ręczno-statystyczna

Jeśli to polskie zdanie, to na pewno znaki powinny występować z częstością zgodną z językiem polskim:

a 	8.91% 	  w 	4.65% 	  p 	3.13% 	  g 	1.42% 	  ć 	0.40%
i 	8.21% 	  s 	4.32% 	  m 	2.80% 	  ę 	1.11% 	  f 	0.30%
o 	7.75% 	  t 	3.98% 	  u 	2.50% 	  h 	1.08% 	  ń 	0.20%
e 	7.66% 	  c 	3.96% 	  j 	2.28% 	  ą 	0.99% 	  q 	0.14%
z 	5.64% 	  y 	3.76% 	  l 	2.10% 	  ó 	0.85% 	  ź 	0.06%
n 	5.52% 	  k 	3.51% 	  ł 	1.82% 	  ż 	0.83% 	  v 	0.04%
r 	4.69% 	  d 	3.25% 	  b 	1.47% 	  ś 	0.66% 	  x 	0.02%

Na szybko zapisałem podstawowe wartości skryptu w Rubym:

TXT = <<TXT
  c5 c2 c3 c4 c9 c3 40 82 a8 93 82 a8 40 86 81 91 95 a8 40 87 84
  a8 82 a8 40 82 a8 93 40 93 96 87 89 83 a9 95 a8 4b 40 c1 93 85
  40 95 89 85 40 91 85 a2 a3 4b
TXT
NUMS = TXT.split.map{ |n| n.to_i(16) }

Policzenie częstotliwości występowania znaków okazało się proste (ale jeśli jakiś Ruby-golfista umie ładniej to bardzo zapraszam do komentarzy):

COUNTS = NUMS.inject({}){ |t, el| t[el] = t.fetch(el, 0) + 1; t }
COUNTS_BY_FREQ = COUNTS.to_a.sort_by{|a,b| b }.reverse

Najczęściej występującymi okazały się:

[64, 8], [168, 7], [130, 4], [147, 4], [133, 3], [149, 3]

64 założyłem, że jest spacją (alternatywnie – 168, a 64 to byłoby a). Naprędce sprawdziłem kilka rozwiązań:

[
    ' aio',
    'a io',
    ' iao',
    'i ao'
].each do |s|
    chars = s.each_codepoint.to_a.map(&:chr)
    replace = COUNTS_BY_FREQ.map(&:first).zip(chars).select{ |a,b| b }.to_h
 
    p NUMS.map{ |n| replace.fetch(n, '.') }.join
end

Wyniki:

...... iaoia ....a ..aia iao o......a. .o. ... .....
......ai oi a.... a.. i ai oao...... .a.o.a...a.....
...... aioai ....i ..iai aio o......i. .o. ... .....
......ia oa i.... i.. a ia oio...... .i.o.i...i.....

Nope. Spacja wydaje się faktycznie najczęstsza (patrząc po wielkości wyrazów), ale poza tym tworzone są zbitki znakowe niemające sensu w języku polskim. Podjąłem jeszcze kilka prób, dodając znaki interpunkcyjne, używając innych map frekwencji, ale to nie pomogło.

Bardziej matematycznie

Może to jedna wielka liczba, którą należy pomnożyć lub podzielić?

VAL = TXT.split.join.to_i(16)
 
(1..10000).each do |n|
    x = VAL / n
    str = x.to_s(16).each_codepoint.each_slice(2).to_a.map{|n| n.map(&:chr).join.to_i(16) }.map(&:chr).join
    p str unless str =~ /[^[:print:]]/
end

Nic.

VAL = TXT.split.join.to_i(16)
 
(1..10000).each do |n|
    x = VAL * n
    str = x.to_s(16).each_codepoint.each_slice(2).to_a.map{|n| n.map(&:chr).join.to_i(16) }.map(&:chr).join
    p str unless str =~ /[^[:print:]]/
end

Nic. Swoją drogą, zamiana stringa na codepointy i z powrotem to jakaś masakra, pomimo tego, że to oneliner.

Może pierwsze 6 lub 7 znaków to klucz xor? (dość znacząco wyróżniają się)

p NUMS[7..-1].each_with_index.map{|e, i| e ^ NUMS[i % 6]}.map(&:chr).join
p NUMS[6..-1].each_with_index.map{|e, i| e ^ NUMS[i % 6]}.map(&:chr).join
"GjPFa\x83CCRQa\x83BFkFa\x83GjP\x84ZUBK@m\\k\x8E\x82\x02WL\x83PKF\x84XFga\x88"
"\x85@kWKk\x85DBU\\k\x85EGlKk\x85@kW\x89PSEJG`Vm\x89\x83\x05ZF\x85WJA\x89R@``\x8F"

Nope. Gdyby nie zgadzała się długość klucza, to chociaż pierwsze znaki by były sensowne.

A co jeśli pytanie wprowadza w błąd?

Równie dobrze mógłbym to opisać jako błędne założenia poczynione przeze mnie, ale zdecydowanie preferuję zwalić na kogoś winę. Polskie zadanie dla polskiego odbiorcy oznajmiło, że polski tekst jest ukryty w zakodowanej wiadomości, prawdopodobnie z lat 60-tych XX w.

Oczywistym dla mnie było, że wszystko odbyło się w Polsce. Wobec tego ruszyłem na poszukiwania kodowania używanego w komputerach Odra. Najbliższe co zauważyłem to kodowanie Mazovia, ale niezbyt pasowało: ani wartości się nie zgadzały, ani dekady.

Rozwiązanie

Finalnie, poddałem się i zacząłem szukać kodowań “z dekady”, niezależnie od przypisanej im lokacji geograficznej. Pierwsze na ruszt poszło EBCDIC. Już na pierwszy rzut oka wydawało się mieć sens, ponieważ EBCDIC są odpowiednio 5., 2., 3., 4., 9. i 3. literami alfabetu, a szyfrogram zaczyna się od “c5 c2 c3 c4 c9 c3” – czyli dolne nibble się zgadzają. Dla pewności:

require 'iconv'
p Iconv.conv('ASCII', 'EBCDIC-US', NUMS.map(&:chr).join)
EBCDIC bylby fajny gdyby byl logiczny. Ale nie jest.

W końcu! No cóż, ostatnio pisałem “liczę, że czasem pojawią się zadania o wyższym poziomie trudności”, ale liczyłem, że to będzie obiektywnie większa trudność, a nie mój problem z prostym zadaniem.

Leave a Reply

Your email address will not be published.