2023-07-12
Tags: misc
[1mSchrödinger's Cat[22m was a quantum computing misc challenge
[9mrepurposed[29m written for UIUCTF 2023.
The challenge had unrated difficulty and had 17 solves during the
competition.
== [1m[4mDescription[22m[24m
Our boss got mad that our SSH keys were weak, so now we're
using a quantum computer to be extra secure!
=== [1m[4mHints[22m[24m
There are known bugs in OpenQASM transpilation — please use
Qiskit, and optimize your circuit before serialization.
[9mShendefraude? Bullocks I say! You're way off the
Markov.[29m (This was a pretty terrible allusion to this paper
[1] that describes Qiskit's
[40m[35m`StatePreparation`[39m[49m algorithm.)
=== [1m[4mFiles[22m[24m
== [1m[4mSolution[22m[24m
=== [1m[4mCode[22m[24m
[33mimport[0m[1m[37m [0m[1m[37mnumpy[0m[1m[37m [0m[33mas[0m[1m[37m [0m[1m[37mnp[0m[1m[37m[0m
[33mfrom[0m[1m[37m [0m[1m[37mbase64[0m[1m[37m [0m[33mimport[0m[1m[37m [0m[1m[37mb64encode[0m[1m[37m[0m
[33mfrom[0m[1m[37m [0m[1m[37mqiskit[0m[1m[37m [0m[33mimport[0m[1m[37m [0m[1m[36m*[0m[1m[37m[0m
[33mfrom[0m[1m[37m [0m[1m[37mqiskit.circuit.library[0m[1m[37m [0m[33mimport[0m[1m[37m [0m[1m[37mStatePreparation[0m[1m[37m[0m
[33mfrom[0m[1m[37m [0m[1m[37mqiskit.compiler[0m[1m[37m [0m[33mimport[0m[1m[37m [0m[1m[37mtranspile[0m[1m[37m[0m
[33mimport[0m[1m[37m [0m[1m[37mqiskit.quantum_info[0m[1m[37m [0m[33mas[0m[1m[37m [0m[1m[37mqi[0m[1m[37m[0m
[1m[37m[0m
[1m[37mWIRES[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[36m5[0m[1m[37m[0m
[1m[37m[0m
[33m# helper functions from challenge[0m[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mnormalization[0m[1m[37m([0m[1m[37mmsg[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[33massert[0m[1m[37m([0m[37mlen[0m[1m[37m([0m[1m[37mmsg[0m[1m[37m)[0m[1m[37m [0m[1m[36m<=[0m[1m[37m [0m[1m[37mWIRES[0m[1m[36m**[0m[1m[36m2[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mstate[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mnp[0m[1m[36m.[0m[1m[37marray[0m[1m[37m([[0m[37mord[0m[1m[37m([0m[1m[37mc[0m[1m[37m)[0m[1m[37m [0m[33mfor[0m[1m[37m [0m[1m[37mc[0m[1m[37m [0m[1m[36min[0m[1m[37m [0m[1m[37mmsg[0m[1m[36m.[0m[1m[37mljust[0m[1m[37m([0m[1m[36m2[0m[1m[36m**[0m[1m[37mWIRES[0m[1m[37m,[0m[1m[37m [0m[33m' '[0m[1m[37m)])[0m[1m[37m[0m
[1m[37m [0m[1m[37mnorm[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mnp[0m[1m[36m.[0m[1m[37mlinalg[0m[1m[36m.[0m[1m[37mnorm[0m[1m[37m([0m[1m[37mstate[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mstate[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mstate[0m[1m[37m [0m[1m[36m/[0m[1m[37m [0m[1m[37mnorm[0m[1m[37m[0m
[1m[37m [0m[33mreturn[0m[1m[37m [0m[1m[37m([0m[1m[37mstate[0m[1m[37m,[0m[1m[37m [0m[1m[37mnorm[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mtransform[0m[1m[37m([0m[1m[37msv[0m[1m[37m,[0m[1m[37m [0m[1m[37mn[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mlegal[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[33mlambda[0m[1m[37m [0m[1m[37mc[0m[1m[37m:[0m[1m[37m [0m[37mord[0m[1m[37m([0m[33m' '[0m[1m[37m)[0m[1m[37m [0m[1m[36m<=[0m[1m[37m [0m[1m[37mc[0m[1m[37m [0m[1m[36mand[0m[1m[37m [0m[1m[37mc[0m[1m[37m [0m[1m[36m<=[0m[1m[37m [0m[37mord[0m[1m[37m([0m[33m'~'[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mrenormalized[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37m[[0m[37mfloat[0m[1m[37m([0m[1m[37mi[0m[1m[36m.[0m[1m[37mreal[0m[1m[37m)[0m[1m[36m*[0m[1m[37mn[0m[1m[37m [0m[33mfor[0m[1m[37m [0m[1m[37mi[0m[1m[37m [0m[1m[36min[0m[1m[37m [0m[1m[37msv[0m[1m[37m][0m[1m[37m[0m
[1m[37m [0m[1m[37mrn_rounded[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37m[[0m[37mround[0m[1m[37m([0m[1m[37mi[0m[1m[37m)[0m[1m[37m [0m[33mfor[0m[1m[37m [0m[1m[37mi[0m[1m[37m [0m[1m[36min[0m[1m[37m [0m[1m[37mrenormalized[0m[1m[37m][0m[1m[37m[0m
[1m[37m [0m[33mif[0m[1m[37m [0m[1m[36mnot[0m[1m[37m [0m[1m[37mnp[0m[1m[36m.[0m[1m[37mallclose[0m[1m[37m([0m[1m[37mrenormalized[0m[1m[37m,[0m[1m[37m [0m[1m[37mrn_rounded[0m[1m[37m,[0m[1m[37m [0m[1m[37mrtol[0m[1m[36m=[0m[1m[36m0[0m[1m[37m,[0m[1m[37m [0m[1m[37matol[0m[1m[36m=[0m[1m[36m1e-2[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[37mprint[0m[1m[37m([0m[33m"Your rehydrated statevector isn't very precise. Try adding at least 6 decimal places of precision, or contact the challenge author if you think this is a mistake."[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[37mprint[0m[1m[37m([0m[1m[37mrn_rounded[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mexit[0m[1m[37m([0m[1m[36m0[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[33mif[0m[1m[37m [0m[1m[37mnp[0m[1m[36m.[0m[1m[37many[0m[1m[37m([[0m[1m[36mnot[0m[1m[37m [0m[1m[37mlegal[0m[1m[37m([0m[1m[37mc[0m[1m[37m)[0m[1m[37m [0m[33mfor[0m[1m[37m [0m[1m[37mc[0m[1m[37m [0m[1m[36min[0m[1m[37m [0m[1m[37mrn_rounded[0m[1m[37m]):[0m[1m[37m[0m
[1m[37m [0m[37mprint[0m[1m[37m([0m[33m"Invalid ASCII characters."[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mexit[0m[1m[37m([0m[1m[36m0[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[33mreturn[0m[1m[37m [0m[33m''[0m[1m[36m.[0m[1m[37mjoin[0m[1m[37m([[0m[37mchr[0m[1m[37m([0m[1m[37mn[0m[1m[37m)[0m[1m[37m [0m[33mfor[0m[1m[37m [0m[1m[37mn[0m[1m[37m [0m[1m[36min[0m[1m[37m [0m[1m[37mrn_rounded[0m[1m[37m])[0m[1m[37m[0m
[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37msolve_qiskit[0m[1m[37m():[0m[1m[37m[0m
[1m[37m [0m[1m[37mecho_sv[0m[1m[37m,[0m[1m[37m [0m[1m[37m_[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mnormalization[0m[1m[37m([0m[33m"echo 'Hello, world!'"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mcat_sv[0m[1m[37m,[0m[1m[37m [0m[1m[37mcat_n[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mnormalization[0m[1m[37m([0m[33m"cat /flag.txt"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37m [0m[1m[37mcirc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mQuantumCircuit[0m[1m[37m([0m[1m[37mWIRES[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mcirc[0m[1m[36m.[0m[1m[37mappend[0m[1m[37m([0m[1m[37mStatePreparation[0m[1m[37m([0m[1m[37mcat_sv[0m[1m[37m,[0m[1m[37m [0m[1m[37mlabel[0m[1m[36m=[0m[33m"sp"[0m[1m[37m),[0m[1m[37m [0m[37mrange[0m[1m[37m([0m[1m[37mWIRES[0m[1m[37m))[0m[1m[37m[0m
[1m[37m [0m[1m[37mcirc[0m[1m[36m.[0m[1m[37mappend[0m[1m[37m([0m[1m[37mStatePreparation[0m[1m[37m([0m[1m[37mecho_sv[0m[1m[37m,[0m[1m[37m [0m[1m[37mlabel[0m[1m[36m=[0m[33m"inv_sp"[0m[1m[37m,[0m[1m[37m [0m[1m[37minverse[0m[1m[36m=[0m[1m[36mTrue[0m[1m[37m),[0m[1m[37m [0m[37mrange[0m[1m[37m([0m[1m[37mWIRES[0m[1m[37m))[0m[1m[37m[0m
[1m[37m[0m
[1m[37m [0m[33m# append the original StatePreparation to confirm that this works; remove for payload[0m[1m[37m[0m
[1m[37m [0m[33m#circ.append(StatePreparation(echo_sv), range(WIRES))[0m[1m[37m[0m
[1m[37m [0m[1m[37mcirc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mtranspile[0m[1m[37m([0m[1m[37mcirc[0m[1m[37m,[0m[1m[37m [0m[1m[37mbackend[0m[1m[36m=[0m[1m[37mAer[0m[1m[36m.[0m[1m[37mget_backend[0m[1m[37m([0m[33m"aer_simulator"[0m[1m[37m),[0m[1m[37m [0m[1m[37moptimization_level[0m[1m[36m=[0m[1m[36m3[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37msv[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mqi[0m[1m[36m.[0m[1m[37mStatevector[0m[1m[36m.[0m[1m[37mfrom_instruction[0m[1m[37m([0m[1m[37mcirc[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37m [0m[33mreturn[0m[1m[37m [0m[1m[37mb64encode[0m[1m[37m([0m[1m[37mcirc[0m[1m[36m.[0m[1m[37mqasm[0m[1m[37m()[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m()),[0m[1m[37m [0m[1m[37mcat_n[0m[1m[37m[0m
[1m[37m[0m
[33mif[0m[1m[37m [0m[1m[36m__name__[0m[1m[37m [0m[1m[36m==[0m[1m[37m [0m[33m"__main__"[0m[1m[37m:[0m[1m[37m[0m
[1m[37m [0m[37mprint[0m[1m[37m([0m[1m[37msolve_qiskit[0m[1m[37m())[0m[1m[37m[0m
=== [1m[4mExplanation[22m[24m
We connect to the challenge server and are greeted with the
following:
[37mprint[0m[1m[37m([0m[33m"Welcome to the Quantum Secure Shell. Instead of dealing with pesky encryption, just embed your commands into our quantum computer! I batched the next command in with yours, hope you're ok with that!"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mgiven_sv[0m[1m[37m,[0m[1m[37m [0m[1m[37mgiven_n[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mnormalization[0m[1m[37m([0m[33m"echo 'Hello, world!'"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mprint_given[0m[1m[37m([0m[1m[37mgiven_sv[0m[1m[37m,[0m[1m[37m [0m[1m[37mgiven_n[0m[1m[37m)[0m[1m[37m[0m
[37mprint[0m[1m[37m([0m[33m"[0m[33m\n[0m[33mPlease type your OpenQASM circuit as a base64 encoded string: "[0m[1m[37m)[0m[1m[37m[0m
[1m[37mWelcome to the Quantum Secure Shell. Instead of dealing with pesky encryption, just embed your commands into our quantum computer! I batched the next command in with yours, hope you're ok with that![0m
[1m[37m ┌─────────────────┐┌───────────────────────┐[0m
[1m[37mq_0: ┤0 ├┤0 ├[0m
[1m[37m │ ││ │[0m
[1m[37mq_1: ┤1 ├┤1 ├[0m
[1m[37m │ ││ │[0m
[1m[37mq_2: ┤2 Your Circ Here ├┤2 echo 'Hello, world!' ├[0m
[1m[37m │ ││ │[0m
[1m[37mq_3: ┤3 ├┤3 ├[0m
[1m[37m │ ││ │[0m
[1m[37mq_4: ┤4 ├┤4 ├[0m
[1m[37m └─────────────────┘└───────────────────────┘[0m
[1m[37mNormalization constant: 419.1873089681986[0m
[1m[37m[0m
[1m[37mExecuting...[0m
[1m[37m[0m
[1m[37mHello, world![0m
[1m[37m[0m
[1m[37mPlease type your OpenQASM circuit as a base64 encoded string:[0m
Taking a look at the source, we see that we input some quantum
circuit which is inserted before a different circuit, and the
resultant statevector is then "unnormalized", interpreted as a
string, and fed to [40m[35m`os.system`[39m[49m.
Our end goal should be to embed a string like
[40m[35m`ls`[39m[49m or [40m[35m`cat flag.txt`[39m[49m;
this is trivial with the [40m[35m`normalization`[39m[49m
function provided.
[1m[37mcat_sv[0m[1m[37m,[0m[1m[37m [0m[1m[37mcat_n[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mnormalization[0m[1m[37m([0m[33m"cat /flag.txt"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mcat_circ[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mStatePreparation[0m[1m[37m([0m[1m[37mcat_sv[0m[1m[37m,[0m[1m[37m [0m[1m[37mlabel[0m[1m[36m=[0m[33m"state_prep"[0m[1m[37m)[0m[1m[37m[0m
==== [1m[4mWhat is the normalization constant?[22m[24m
The "measurement rule" in quantum mechanics dictates that the sum
of all amplitudes squared must equal 1. In order to encode a vector
of ASCII values in the circut, we first need to normalize it; to
get back to the original vector, we "undo" the normalization by
multiplying the scalar normalization constant.
Good news is, we didn't receive any mod mail on the normalization
constant so... hopefully no one had any issues?
==== [1m[4mWhere's the measurement?[22m[24m
You might be wondering how one might fit a 32 character string into
5 qubits, and more importantly; where are the measurement gates?
The output of the circuit is the [1mstatevector[22m, which means
if you were to measure the qubits instead, the probability of
measuring a specific outcome would correspond to amplitudes in the
statevector. Using the statevector, we're able to losslessly
finangle lots of data into the rotation of a qubit that would
otherwise be lost through measurement.
==== [1m[4mAbout [40m[35m`StatePreparation`[39m[49m[22m[24m
The existing circuit embeddeds the string [40m[35m`echo 'Hello,
world!'`[39m[49m using Qiskit's
[40m[35m`StatePreparation`[39m[49m function. This is a form of
[1mamplitude encoding[22m, a way to encode information in the
probability amplitudes of discrete quantum states.
A common point of confusion was that
[40m[35m`StatePreparation`[39m[49m returns a statevector; it's
actually an algorithm for creating a [4mcircuit[24m that will
transform \ket{0} into a desired statevector.
[1m[37mcirc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mStatePreparation[0m[1m[37m([0m[1m[37mgiven_sv[0m[1m[37m)[0m[1m[37m[0m
[37mprint[0m[1m[37m([0m[1m[37mqi[0m[1m[36m.[0m[1m[37mStatevector[0m[1m[36m.[0m[1m[37mfrom_instruction[0m[1m[37m([0m[1m[37mcirc[0m[1m[37m)[0m[1m[37m [0m[1m[36m==[0m[1m[37m [0m[1m[37mqi[0m[1m[36m.[0m[1m[37mStatevector[0m[1m[37m([0m[1m[37mgiven_sv[0m[1m[37m))[0m[1m[37m[0m
[1m[37mTrue[0m
==== [1m[4mHope you didn't sleep through linalg (I did)[22m[24m
The next part is to figure out how to "get rid of" the circuit
applied after your input, which encodes the string [40m[35m`echo
'Hello, world!'`[39m[49m. Quantum circuits are all really just
unitary matrices, which means they're invertible.
So if we have input I, [40m[35m`echo`[39m[49m input E, and
desired payload P in IE=P, that means I=(PE^{\dagger}).
In order to calculate E^{\dagger}, we need to grab E:
[1m[37mecho_sv[0m[1m[37m,[0m[1m[37m [0m[1m[37m_[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mnormalization[0m[1m[37m([0m[33m"echo 'Hello, world!'"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mE[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mStatePreparation[0m[1m[37m([0m[1m[37mecho_sv[0m[1m[37m,[0m[1m[37m [0m[1m[37mlabel[0m[1m[36m=[0m[33m"echo"[0m[1m[37m)[0m[1m[37m[0m
And then take its inverse:
[1m[37mE_inv[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mE[0m[1m[36m.[0m[1m[37minverse[0m[1m[37m()[0m[1m[37m[0m
Finally, we compose the individual circuits together:
[1m[37mcirc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mQuantumCircuit[0m[1m[37m([0m[1m[37mWIRES[0m[1m[37m)[0m[1m[37m[0m
[1m[37mcirc[0m[1m[36m.[0m[1m[37mappend[0m[1m[37m([0m[1m[37mcat_circ[0m[1m[37m,[0m[1m[37m [0m[37mrange[0m[1m[37m([0m[1m[37mWIRES[0m[1m[37m))[0m[1m[37m[0m
[1m[37mcirc[0m[1m[36m.[0m[1m[37mappend[0m[1m[37m([0m[1m[37mE_inv[0m[1m[37m,[0m[1m[37m [0m[37mrange[0m[1m[37m([0m[1m[37mWIRES[0m[1m[37m))[0m[1m[37m[0m
[1m[37m[0m
[1m[37mcirc[0m[1m[36m.[0m[1m[37mdraw[0m[1m[37m([0m[33m'mpl'[0m[1m[37m,[0m[1m[37m [0m[1m[37mfilename[0m[1m[36m=[0m[33m"schroedingers_cat/circuit.png"[0m[1m[37m)[0m[1m[37m[0m
(IMG) image
Gross (and also kinda wrong). Qiskit's QASM converter will happily
spit out black boxes like above, assuming whoever will consume the
QASM will know what that means.
==== [1m[4mAn aside on OpenQASM[22m[24m
If we try to convert our circuit as is to QASM, we run into some
issues:
[1m[37mOPENQASM 2.0;[0m
[1m[37minclude "qelib1.inc";[0m
[1m[37mgate multiplex1_reverse_dg q0 { ry(0.6960408807071358) q0; }[0m
[1m[37mgate multiplex1_reverse_dg_5365343888 q0 { ry(1.5295115311526133) q0; }[0m
[1m[37mgate multiplex1_reverse_reverse_dg q0 { ry(-0.04128479564228338) q0; }[0m
[1m[37mgate multiplex2_reverse_dg q0,q1 { multiplex1_reverse_dg_5365343888 q0; cx q1,q0; multiplex1_reverse_reverse_dg q0; cx q1,q0; }[0m
[1m[37mgate multiplex1_reverse_dg_5273843856 q0 { ry(1.4620958103644106) q0; }[0m
[1m[37mgate multiplex1_reverse_reverse_dg_5342559248 q0 { ry(0.10867083226993679) q0; }[0m
[1m[37mgate multiplex2_reverse_dg_5365334864 q0,q1 { multiplex1_reverse_dg_5273843856 q0; cx q1,q0; multiplex1_reverse_reverse_dg_5342559248 q0; }[0m
[1m[37mgate multiplex1_reverse_reverse_reverse_dg q0 { ry(0.10867083226993673) q0; }[0m
In order to emit QASM that Qiskit will actually understand, we need
to transpile our circuit to a set of universal basis gates. This is
also how quantum circuits are run on hardware, as each computer
only understands a certain set of basis gates.
[33mfrom[0m[1m[37m [0m[1m[37mqiskit[0m[1m[37m [0m[33mimport[0m[1m[37m [0m[1m[37mAer[0m[1m[37m[0m
[33mfrom[0m[1m[37m [0m[1m[37mqiskit.compiler[0m[1m[37m [0m[33mimport[0m[1m[37m [0m[1m[37mtranspile[0m[1m[37m[0m
[1m[37mcirc_transpiled[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mtranspile[0m[1m[37m([0m[1m[37mcirc[0m[1m[37m,[0m[1m[37m [0m[1m[37mbackend[0m[1m[36m=[0m[1m[37mAer[0m[1m[36m.[0m[1m[37mget_backend[0m[1m[37m([0m[33m"aer_simulator"[0m[1m[37m),[0m[1m[37m [0m[1m[37moptimization_level[0m[1m[36m=[0m[1m[36m3[0m[1m[37m)[0m[1m[37m[0m
[1m[37mgood_qasm[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mcirc_transpiled[0m[1m[36m.[0m[1m[37mqasm[0m[1m[37m()[0m[1m[37m[0m
[37mprint[0m[1m[37m([0m[33m'[0m[33m\n[0m[33m'[0m[1m[36m.[0m[1m[37mjoin[0m[1m[37m([0m[1m[37mgood_qasm[0m[1m[36m.[0m[1m[37msplit[0m[1m[37m([0m[33m'[0m[33m\n[0m[33m'[0m[1m[37m)[:[0m[1m[36m10[0m[1m[37m]))[0m[1m[37m[0m
[1m[37mOPENQASM 2.0;[0m
[1m[37minclude "qelib1.inc";[0m
[1m[37mqreg q[5];[0m
[1m[37mu3(1.4344105714005226,0,0) q[0];[0m
[1m[37mu3(1.5262619493655363,0,0) q[1];[0m
[1m[37mu3(1.4620958103644108,0,0) q[2];[0m
[1m[37mu3(1.5295115311526135,0,0) q[3];[0m
[1m[37mu3(0.6960408807071358,0,0) q[4];[0m
[1m[37mcx q[4],q[3];[0m
[1m[37mu3(0.04128479564228338,-pi,-pi) q[3];[0m
A little tangent on OpenQASM: the implementation is [3mso[23m
scuffed and incomplete to the point it makes Yandere Simulator look
like enterprise software. I had poked around trying to develop a
challenge focused on OpenQASM, but found that literally every
interesting part of the spec was unimplemented 😢
Here's Qiskit's implementation of OpenQASM3's
[40m[35m`include`[39m[49m statement:
[33mdef[0m[1m[37m [0m[37mvisit_Include[0m[1m[37m([0m[37mself[0m[1m[37m,[0m[1m[37m [0m[1m[37mnode[0m[1m[37m:[0m[1m[37m [0m[1m[37mast[0m[1m[36m.[0m[1m[37mInclude[0m[1m[37m,[0m[1m[37m [0m[1m[37mcontext[0m[1m[37m:[0m[1m[37m [0m[1m[37mState[0m[1m[37m)[0m[1m[37m [0m[1m[36m->[0m[1m[37m [0m[1m[37mState[0m[1m[37m:[0m[1m[37m[0m
[1m[37m [0m[33mif[0m[1m[37m [0m[1m[37mnode[0m[1m[36m.[0m[1m[37mfilename[0m[1m[37m [0m[1m[36m!=[0m[1m[37m [0m[33m"stdgates.inc"[0m[1m[37m:[0m[1m[37m[0m
[1m[37m [0m[1m[37mraise_from_node[0m[1m[37m([0m[1m[37mnode[0m[1m[37m,[0m[1m[37m [0m[33m"non-stdgates imports not currently supported"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[33mfor[0m[1m[37m [0m[1m[37mname[0m[1m[37m,[0m[1m[37m [0m[1m[37m([0m[1m[37mbuilder[0m[1m[37m,[0m[1m[37m [0m[1m[37mn_arguments[0m[1m[37m,[0m[1m[37m [0m[1m[37mn_qubits[0m[1m[37m)[0m[1m[37m [0m[1m[36min[0m[1m[37m [0m[1m[37m_STDGATES[0m[1m[36m.[0m[1m[37mitems[0m[1m[37m():[0m[1m[37m[0m
[1m[37m [0m[1m[37mcontext[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37mself[0m[1m[36m.[0m[1m[37m_define_gate[0m[1m[37m([0m[1m[37mname[0m[1m[37m,[0m[1m[37m [0m[1m[37mbuilder[0m[1m[37m,[0m[1m[37m [0m[1m[37mn_arguments[0m[1m[37m,[0m[1m[37m [0m[1m[37mn_qubits[0m[1m[37m,[0m[1m[37m [0m[1m[37mnode[0m[1m[37m,[0m[1m[37m [0m[1m[37mcontext[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[33mreturn[0m[1m[37m [0m[1m[37mcontext[0m[1m[37m[0m
I understand that this is alpha software for a rather small part of
Qiskit, and I'm not trying to pile on the Qiskit devs for not
lavishing more attention on this, but... bruh.
==== [1m[4mFinishing up[22m[24m
All that's left is to [40m[35m`b64encode`[39m[49m this bad boy
and ship it off to remote.
[1m[37mpayload[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mb64encode[0m[1m[37m([0m[1m[37mgood_qasm[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[33m# server side[0m[1m[37m[0m
[1m[37mtransform[0m[1m[37m([0m[1m[37mqi[0m[1m[36m.[0m[1m[37mStatevector[0m[1m[36m.[0m[1m[37mfrom_instruction[0m[1m[37m([0m[1m[37mmake_circ[0m[1m[37m([0m[1m[37mgiven_sv[0m[1m[37m,[0m[1m[37m [0m[1m[37mQuantumCircuit[0m[1m[36m.[0m[1m[37mfrom_qasm_str[0m[1m[37m([0m[1m[37mb64decode[0m[1m[37m([0m[1m[37mpayload[0m[1m[37m)[0m[1m[36m.[0m[1m[37mdecode[0m[1m[37m()))),[0m[1m[37m [0m[1m[37mcat_n[0m[1m[37m)[0m[1m[37m[0m
[1m[37mcat /flag.txt[0m
==== [1m[4mRetrospection[22m[24m
When the challenge was first released (on the second day of the
CTF), I decided to not release the source. Although that was
intended to prevent code reuse from
[40m[35m`server.py`[39m[49m, it made the challenge waaaay too
guessy. Not only that, but it was probably better to provide
primitives so you could focus on solving the challenge instead of
placating Qiskit's fussiness.
This very much locks you into using Qiskit and — according to some
modmail I received — a particular way of solving the challenge.
Even though the server was built against the latest version of
Qiskit (and thus the version people would get from [40m[35m`pip
install -r requirements.txt`[39m[49m), in the future I would
release the server Dockerfile to remove any source of impurity.
(This is somewhat ironic, given that the challenge author uses
NixOS.)
References:
(HTM) [1] this paper
(HTM) [2] server.py
(HTM) [3] requirements.txt
>=================================================================<
(DIR) Blog
(DIR) Writeups
(DIR) jp
copyright 2026 George Huebner
(HTM) email