エンジニア向け日本酒「ソースコード」を飲んだ感想と解読方法

日本酒「ソースコード」とは

オンライン酒屋「クランド」を運営するKURANDが2024年1月23日に発売した日本酒です。

システムエンジニア向けの日本酒として発売されましたが、誰でも購入可能です。

日本酒なのに名前は「ソースコード」となっており、ラベルにはコードが書かれていて、白文字でソースコード (SOURCE CODE)が表示されています。

エンジニア向け日本酒「ソースコード」

ソースコード | 山形県の日本酒

日本酒「ソースコード」について

山形県鶴岡市で約400年以上続く老舗酒蔵「渡會(わたらい)本店」が作った純米吟醸酒。

飲んでみたところ、濃醇辛口で完全発酵によるキレ良くすっきりとした味わいで飲みやすかったです。

原材料などは以下の通り。

原材料米(国産)、米麹(国産米)
原料米出羽の里(山形県産)
精米歩合60%
アルコール度数15.5%
内容量720ml
製造元渡會本店
価格1,990円
送料990円 (東京の場合)
日本酒「ソースコード」の原材料名など

ラベルのソースコードはJavaScript

ラベルのソースコードの先頭はconsole.logで始まっています。

エンジニア向け日本酒「ソースコード」

console.logが使用されるのはJavaScriptなので、このコードはJavaScriptだとわかります。

コード内にはconsole.logが3回書かれているので、何かを3回出力することもわかります。

さらにこのコードは簡単に答えがわからないようにString.fromCodePointなどを使用して難読化されていることもコードからわかります。

String.fromCodePointとは

コード内にあるString.fromCodePointは指定されたコードポイントの並びを使って生成された文字列を返します。

例えば、String.fromCodePointに65を指定して実行すると「A」、66だと「B」、67だと「C」を返します。

console.log(String.fromCodePoint(65)) // A
console.log(String.fromCodePoint(66)) // B
console.log(String.fromCodePoint(67)) // C

ちなみに String.fromCodePoint('65') のように文字列を指定しても結果は同じです。

どの数字で何を返すかはWikipediaにあるLatin scriptの一覧表で確認できます。

https://en.wikipedia.org/wiki/List_of_Unicode_characters

[].join("") とは

[].join("")はJavaScriptの配列を文字列にする際に使用されます。

例えば [6, 5].join("") だと文字列の65を返します。

console.log([6, 5].join("")) // 65

!![] 、 ~~!![] 、 ~~[] とは

コード内を見ると、!![] 、 ~~!![] 、 ~~[] がたくさん書かれています。

[]は配列で、これに!!を付けるとtrueになります。

JavaScriptではtrueにtrueまたは1を足すと2になります。

よって !![] + !![] だと2になります。

console.log(!![] + !![]) // 2
console.log(true + true) // 2
console.log(true + 1) // 2

JavaScriptの !![] にビット否定の「~」を付けると「-2」になり、「~~」だと「1」になります。

これはtrueが「1」で「~1」だと「-2」になるからです。

console.log(!![]) // true
console.log(~!![]) // -2
console.log(~~!![]) // 1

ビット否定に関してはMdNのビット否定のページに詳しく書かれています。

~~[] は「~[]」が「-1」で、「~-1」は「0」なので0になります。

「~[]」が「-1」になるのは、[]が空配列で、「~0」が「-1」になるからです。

console.log(~0) // -1
console.log(~[]) // -1
console.log(~~[]) // 0

ここまでの説明で !![] などは以下の数値に置き換えることができることがわかりました。

!![]1
~~!![]1
~~[]0

これらの知識を使って最初のconsole.logの最初の文字を解いてみましょう。

まず最初の文字のコードは以下のようになっています。

String.fromCodePoint([!![]+!![]+!![]+!![]+!![]+!![],!![]+!![]+!![]+!![]+!![]+!![]+!![],].join(""))

!![] は「1」なので !![] を「1」に置換すると以下のようになります。

String.fromCodePoint([1+1+1+1+1+1,1+1+1+1+1+1+1,].join(""))

数値を全部足すと「6」と「7」になります。

String.fromCodePoint([6,7,].join(""))

.join("") で文字列に変換すると '67' なので、このコードは String.fromCodePoint('67')、つまり「C」を返すことが実行しなくても人力でわかります。

String.fromCodePoint('67')

実際に以下のコードをブラウザのConsoleで実行すると「C」を返します。

console.log(String.fromCodePoint([!![]+!![]+!![]+!![]+!![]+!![],!![]+!![]+!![]+!![]+!![]+!![]+!![],].join(""))) // C

ほかの文字も同じ手順で入力していくと、最初のconsole.logの結果は「C o n g r a t u r a t i o n s !」だとわかります。

console.log(
  String.fromCodePoint([!![]+!![]+!![]+!![]+!![]+!![],!![]+!![]+!![]+!![]+!![]+!![]+!![],].join("")), // C
  String.fromCodePoint([+!![],+!![],+!![],].join("")), // o
  String.fromCodePoint([+!![],+!![],~~[],].join("")), // n
  String.fromCodePoint([+!![],~~[],!![]+!![]+!![]].join("")), // g
  String.fromCodePoint([+!![],+!![],!![]+!![]+!![]+!![]].join("")), // r
  String.fromCodePoint([!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![],!![]+!![]+!![]+!![]+!![]+!![]+!![]].join("")), // a
  String.fromCodePoint([+!![],+!![],!![]+!![]+!![]+!![]+!![]+!![],].join("")), // t
  String.fromCodePoint([+!![],+!![],+!![]+!![]+!![]+!![]+!![]+!![]+!![],].join("")), // u
  String.fromCodePoint([+!![],~~!![],!![]+!![]+!![]+!![]].join("")), // r
  String.fromCodePoint([!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![],!![]+!![]+!![]+!![]+!![]+!![]+!![],].join("")), // a
  String.fromCodePoint([+!![],+!![],!![]+!![]+!![]+!![]+!![]+!![],].join("")), // t
  String.fromCodePoint([+!![],~~[],!![]+!![]+!![]+!![]+!![],].join("")), // i
  String.fromCodePoint([+!![],+!![],+!![],].join("")), // o
  String.fromCodePoint([+!![],+!![],~~[],].join("")), // n
  String.fromCodePoint([+!![],+!![],+!![]+!![]+!![]+!![]+!![],].join("")), // s
  String.fromCodePoint([!![]+!![]+!![],!![]+!![]+!![],].join("")), // !
)

「Congraturations!」ではなく「C o n g r a t u r a t i o n s !」なのはconsole.logはカンマ区切りで出力した場合は間に半角スペースが入るからです。

console.log('a', 'b', 'c') // a b c

「Congraturations!」はスペルミス

「Congraturations!」はスペルミスで、正しいスペルは「Congratulations!」です。

congratulationの意味・使い方・読み方|英辞郎 on the WEB

このコードを書いたエンジニアとクランドの担当者の方はコードの実行結果は確認しており、メッセージとしては 「その先」が隠されています。

残りのconsole.logの2つの文字列も同様の手順で入力して実行すれば答えがわかりますので、ぜひ日本酒「ソースコード」を購入して、ラベルのコードを入力して試してみてください。

パソコンがなくても、この記事で説明した方法とString.fromCodePointの対応文字がわかっていれば人力でも解けます。(上級者向け)