💾 Archived View for jfh.me › posts › 2019-03-04-js-quine.gmi captured on 2022-04-28 at 17:19:57. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2021-11-30)
-=-=-=-=-=-=-
After writing the first quine in bash, I decided to mess around with this a little bit more. I wanted to do two things. First, I wanted to see if I could add my own contact information to the quine so that it could be used on a business card. Second, I was interested to see if I could do anything to make the code even more self-referential. So I tried to add the hash of the code into the code itself.
Previous implementation in bash
This is what I ended up with:
const name = 'John Hilliard'; const company = 'Next Jump'; const twitter = '@praetorian'; const site = 'john.dev'; const crc32 = 0xd6d8c236; const q = String.fromCharCode(34); [ "const name = 'John Hilliard';", "const company = 'Next Jump';", "const twitter = '@praetorian';", "const site = 'john.dev';", "const crc32 = 0xd6d8c236;", "const q = String.fromCharCode(34);", "[", "].map((s,i) => {", " if (i < 7) console.log(s); return s;", "}).map(s => {", " console.log(' ' + q + s + q + ','); return s;", "}).map((s,i) => {if (i < 7 ) return s; console.log(s);});", ].map((s,i) => { if (i < 7) console.log(s); return s; }).map(s => { console.log(' ' + q + s + q + ','); return s; }).map((s,i) => {if (i < 7 ) return s; console.log(s);});
The idea of having a source code file like this that references it's own hash is very weird and cool. In most cases, a file can't contain it's own hash because if the file changes, the hash changes. Since the hash is part of the file, when you go to put the hash in the file, the resulting has of the file would also change so it would never match up.
I wrote this version in JavaScript, since I was thinking about putting it on a business card, JavaScript is a very common language and pretty easy to understand. After getting the basic quine working, I added a placeholder for a CRC32 hash and used a go program brute-force a CRC32 collision.
package main import ( "fmt" "hash/crc32" "io/ioutil" "log" "runtime" "strings" "sync" ) var routines uint32 = 8 func main() { runtime.GOMAXPROCS(int(routines)) var wg sync.WaitGroup var i uint32 for i = 0; i < routines; i = i + 1 { wg.Add(1) go func(buc uint32) { dataFile, err := ioutil.ReadFile("quine-crc.js") if err != nil { log.Fatal(err) } baseTemplateString := string(dataFile[:]) parts := strings.Split(baseTemplateString, "{{Pad}}") var pad uint32 = 681180860 for { pad += 1 if pad == 0 { wg.Done() break } if pad%routines != buc { continue } curQuine := strings.Join(parts, fmt.Sprintf("0x%08x", pad)) cksum := crc32.ChecksumIEEE([]byte(curQuine)) if cksum == pad { log.Printf("Found it dec: %d hex: 0x%08x", pad, pad) log.Println(cksum) log.Printf("%s", curQuine) log.Fatal("done") break } if pad%1000000 == 0 { log.Printf("%d", pad) log.Printf("%s", curQuine) } } }(i) } wg.Wait() log.Println("Finished without finding collision") }
One interesting behavior of the brute-force program is that it didn't use 100% of the CPU. I found that doing various types of string concatenation caused a big slowdown. I experimented with a few different ways of doing things like ~fmt.Sprintf~, ~text.Template~, and ~strings.Join~. They were all kind of slow. I think if I wanted to speed things up, I would have needed to treat the string like an array and just overwrite part of the string with different hash values. Then, under the hood, there would be no need for concatenation or growing the string.
In practice, finding the CRC32 collision even by ignorant brute force was pretty fast, so I didn't bother trying to optimize the code.
One thing that I did learn was that CRC32 isn't just one specific standard. It's an algorithm and there are different standards for the underlying polynomial that's used during the calculation. For my case, I just needed to match the standard that's used in the default ~crc32~ command line implementation on my system. In this case, it's the IEEE standard.
In the end, I took the source code and dropped it into Illustrator to make it fit on a card. Now if anyone ever type this out, they could always verify they typed it correctly by verifying the CRC32.