Cheat sheet
Lookup reference. Identities that hold only up to a global phase are marked
as such. c = cos(θ/2), s = sin(θ/2),
w = 1/sqrt(2) ≈ 0.7071.
1. Single-qubit gates
| Gate | Matrix | Qiskit | QASM | Does |
|---|---|---|---|---|
| I | [[1,0],[0,1]] | qc.id(0) | id q[0]; | nothing; padding / timing |
| X | [[0,1],[1,0]] | qc.x(0) | x q[0]; | bit flip; |0>↔|1>; π about x |
| Y | [[0,-i],[i,0]] | qc.y(0) | y q[0]; | bit+phase flip; = iXZ‑ordered, π about y |
| Z | [[1,0],[0,-1]] | qc.z(0) | z q[0]; | phase flip; |1>→−|1>; |+>↔|−> |
| H | [[1,1],[1,-1]] * w | qc.h(0) | h q[0]; | Z basis ↔ X basis; makes superposition |
| S | [[1,0],[0,i]] | qc.s(0) | s q[0]; | quarter turn about z (90°); = P(pi/2) |
| S† | [[1,0],[0,-i]] | qc.sdg(0) | sdg q[0]; | inverse of S; used before H to read the Y basis |
| T | [[1,0],[0,e^(i*pi/4)]] | qc.t(0) | t q[0]; | eighth turn about z (45°); = P(pi/4); non-Clifford |
| T† | [[1,0],[0,e^(-i*pi/4)]] | qc.tdg(0) | tdg q[0]; | inverse of T |
| Rx(θ) | [[c,-i*s],[-i*s,c]] | qc.rx(th,0) | rx(th) q[0]; | rotation θ about x |
| Ry(θ) | [[c,-s],[s,c]] | qc.ry(th,0) | ry(th) q[0]; | rotation θ about y; real amplitudes, no phases |
| Rz(θ) | [[e^(-i*th/2),0],[0,e^(i*th/2)]] | qc.rz(th,0) | rz(th) q[0]; | rotation θ about z; invisible to a Z-basis measurement |
| P(λ) (=U1) | [[1,0],[0,e^(i*lam)]] | qc.p(lam,0) | p(lam) q[0];QASM2 legacy: u1(lam) | phase on |1> only; P = Rz up to global phase e^(i*lam/2) |
| U(θ,φ,λ) | [[c, -e^(i*lam)*s], [e^(i*phi)*s, e^(i*(phi+lam))*c]] | qc.u(th,phi,lam,0) | QASM3 U(th,phi,lam) q[0];QASM2 u(...) / u3(...) | most general 1-qubit unitary up to global phase |
| √X | 0.5*[[1+i,1-i],[1-i,1+i]] | qc.sx(0) | sx q[0]; | native on IBM hardware; sx*sx = X |
Useful specialisations: P(pi)=Z, P(pi/2)=S, P(pi/4)=T,
U(pi/2,0,pi)=H, U(pi,0,pi)=X.
Rx(pi)=X, Ry(pi)=Y, Rz(pi)=Z each up to the global phase -i.
2. Multi-qubit gates
Matrices below are in the textbook basis ordering
|control target> = 00, 01, 10, 11 (and |c1 c2 t> for 3 qubits).
Operator(qc).data prints the reversed ordering — see the endianness warning in §6.
Reproduce these exact matrices in Qiskit by putting the control on the higher qubit index,
e.g. qc.cx(1,0).
| Gate | Matrix (|c t> order) | Qiskit | QASM | Does |
|---|---|---|---|---|
| CX / CNOT | [[1,0,0,0], [0,1,0,0], [0,0,0,1], [0,0,1,0]] | qc.cx(c,t) | cx q[c],q[t]; | X on target iff control is |1>; the entangler |
| CY | [[1,0,0,0], [0,1,0,0], [0,0,0,-i], [0,0,i,0]] | qc.cy(c,t) | cy q[c],q[t]; | Y on target iff control |1> |
| CZ | diag(1,1,1,-1) | qc.cz(a,b) | cz q[a],q[b]; | phase −1 on |11>; symmetric — control and target interchangeable |
| CH | [[1,0,0,0], [0,1,0,0], [0,0,w,w], [0,0,w,-w]] | qc.ch(c,t) | ch q[c],q[t]; | H on target iff control |1>; used building W states |
| CP(λ) (=CU1) | diag(1,1,1,e^(i*lam)) | qc.cp(lam,a,b) | cp(lam) q[a],q[b]; | phase on |11>; symmetric; the QFT workhorse |
| SWAP | [[1,0,0,0], [0,0,1,0], [0,1,0,0], [0,0,0,1]] | qc.swap(a,b) | swap q[a],q[b]; | exchanges the two qubits |
| iSWAP | [[1,0,0,0], [0,0,i,0], [0,i,0,0], [0,0,0,1]] | qc.iswap(a,b) | not in qelib1/stdgates — Qiskit emits a gate iswap definition | swap plus i phase on the swapped amplitudes |
| CCX / Toffoli | 8×8 identity with the last 2×2 block swapped: |110>↔|111> | qc.ccx(c1,c2,t) | ccx q[a],q[b],q[c]; | classical AND into the target; reversible-logic primitive |
| CSWAP / Fredkin | 8×8 identity with |101>↔|110> | qc.cswap(c,a,b) | cswap q[a],q[b],q[c]; | swap a,b iff control |1>; core of the swap test |
| CCZ | diag(1,1,1,1,1,1,1,-1) | qc.ccz(a,b,c) | emitted as a gate ccz definition | phase −1 on |111>; symmetric in all three qubits |
| MCX | identity except |1…10>↔|1…11> | qc.mcx([c0,c1,c2],t) | expands to a long gate mcx definition | n-control X; the Grover oracle/diffuser core |
3. Identities and rewrite rules
"Up to phase" means the two operators differ by a scalar of modulus 1, which is unobservable unless the block sits inside a controlled operation — then the phase becomes relative and matters.
Pauli algebra
| Identity | Use it when |
|---|---|
XX = YY = ZZ = I | adjacent duplicates cancel — first pass of any golf |
HH = I, S S† = I, T T† = I | same |
XZ = -ZX, XY = -YX, YZ = -ZY | reordering Paulis costs only a global sign |
XY = iZ, YZ = iX, ZX = iY | collapse two Paulis into one |
XYZ = iI | three Paulis vanish entirely |
Conjugations (the golf ammunition)
| Identity | Use it when |
|---|---|
H X H = Z | eliminate an X by moving it through Hadamards |
H Z H = X (so X = HZH) | rebuild X when only H and Z are allowed |
H Y H = -Y | Y survives conjugation up to a global sign |
H = (X + Z) * w | sanity check on a decomposition |
S X S† = Y, S† X S = -Y, S Y S† = -X | convert between X and Y legs |
H Rz(θ) H = Rx(θ), H Rx(θ) H = Rz(θ) | retarget a rotation axis without new gate types |
Rz(a) Rz(b) = Rz(a+b), same for Rx, Ry | merge consecutive rotations about one axis |
H S H = e^(i*pi/4) Rx(pi/2) (up to phase) | Clifford rewriting when phase is irrelevant |
Phase-gate ladder
| Identity | Use it when |
|---|---|
S = T*T, Z = S*S = T^4, T^8 = I | build S/Z from a T-only gate set, or count T depth |
Rz(λ) = P(λ) up to global phase e^(-i*lam/2) | swapping rz↔p is free at top level, not under a control |
P(λ) commutes with Z, S, T and Rz (all diagonal) | slide diagonal gates past each other freely |
Two-qubit rewrites
| Identity | Use it when |
|---|---|
CZ = H(t) · CX(c,t) · H(t) | hardware/QASM only offers one of the two |
CX(c,t) = H(t) · CZ(c,t) · H(t) | same, other direction |
CX(b,a) = H(a) H(b) · CX(a,b) · H(a) H(b) | direction reversal with 4 H's — connectivity is one-way |
SWAP(a,b) = CX(a,b) CX(b,a) CX(a,b) | no native SWAP; costs 3 CNOTs |
CY(c,t) = S†(t) · CX(c,t) · S(t) | CY not in the allowed basis |
CH(c,t) = Ry(pi/4)(t) · CX(c,t) · Ry(-pi/4)(t)(circuit order left→right) | CH not in the allowed basis |
iSWAP = S(a) S(b) H(a) CX(a,b) CX(b,a) H(b) | iSWAP is not a stdgates gate |
CX·CX = I (same control, same target) | cancel duplicate CNOTs |
CZ and CP are symmetric: cz(a,b) = cz(b,a) | relabel to satisfy a coupling map for free |
Pauli propagation through CNOT (conjugation, control c → target t)
| Push through | Becomes | Note |
|---|---|---|
X on control | X on control and target | X copies forward |
X on target | X on target | commutes; move it freely |
Z on control | Z on control | commutes; move it freely |
Z on target | Z on control and target | Z copies backward |
Three-qubit rewrites
| Identity | Use it when |
|---|---|
CCX(a,b,c) = H(c) · CCZ(a,b,c) · H(c), and CCZ = diag(1,…,1,-1) | phase-oracle ↔ bit-oracle conversion in Grover |
CSWAP(c,a,b) = CX(b,a) · CCX(c,a,b) · CX(b,a) | Fredkin from Toffoli |
| CCX is symmetric in its two controls | relabel to fit connectivity |
Standard 6-CNOT + T decomposition of ccx(a,b,c) (verified exact, no phase slack):h c; cx b,c; tdg c; cx a,c; t c; cx b,c; tdg c; cx a,c; t b; t c; h c; cx a,b; t a; tdg b; cx a,b;Use when the basis is {h,t,tdg,cx} — 6 CNOTs, 7 T-type gates. | |
4. Common states
Written in circuit order |q0 q1> — leftmost symbol is qubit 0.
Qiskit counts strings print the reverse (§6).
| State | Amplitudes | Recipe from |0…0> | Notes |
|---|---|---|---|
| |0> | [1,0] | — | Z-basis +1 |
| |1> | [0,1] | x | Z-basis −1 |
| |+> | [w,w] | h | X-basis +1 |
| |−> | [w,-w] | x; h | X-basis −1 |
| |i> (|+i>) | [w, i*w] | h; s | Y-basis +1 |
| |−i> | [w, -i*w] | h; sdg | Y-basis −1 |
Bell states
| State | Definition | Recipe | <ZZ> | <XX> | <YY> |
|---|---|---|---|---|---|
| Φ⁺ | (|00> + |11>)*w | h q0; cx q0,q1; | +1 | +1 | −1 |
| Φ⁻ | (|00> − |11>)*w | x q0; h q0; cx q0,q1;or Φ⁺ then z on either qubit | +1 | −1 | +1 |
| Ψ⁺ | (|01> + |10>)*w | x q1; h q0; cx q0,q1;or Φ⁺ then x on either qubit | −1 | +1 | +1 |
| Ψ⁻ (singlet) | (|01> − |10>)*w | x q0; x q1; h q0; cx q0,q1;or Φ⁺ then z q0; x q0; | −1 | −1 | −1 |
Reading the correlations: <ZZ> = +1 means both Z-basis measurements always agree
(outcomes 00/11); −1 means they always disagree
(01/10). Φ⁺ is the only one with <ZZ>=<XX>=+1;
Ψ⁻ is the only one anti-correlated in all three bases, which identifies it instantly from
a transcript.
Multi-qubit
| State | Definition | Recipe | Notes |
|---|---|---|---|
| GHZ (n=3) | (|000> + |111>)*w | h q0; cx q0,q1; cx q0,q2; | maximally fragile — tracing out any qubit kills all entanglement |
| W (n=3) | (|100>+|010>+|001>)/sqrt(3) | ry(2*acos(1/sqrt(3))) q0;ch q0,q1; cx q1,q2; cx q0,q1; x q0; | robust — stays entangled after losing one qubit |
5. Measuring in a rotated basis
Hardware only measures in the Z basis. To measure observable M, apply the unitary
that maps M's eigenbasis onto the Z basis, then measure. Outcome 0 ⇒
eigenvalue +1, outcome 1 ⇒ −1.
| Basis | Apply before measure | Then 0 means | 1 means |
|---|---|---|---|
| Z (computational) | nothing | |0> | |1> |
| X | h q; | |+> | |−> |
| Y | sdg q; h q; | |i> | |−i> |
angle θ in the X–Z planecos θ|0> + sin θ|1> | ry(-2*theta) q; | the θ direction | the θ+π/2 direction |
| arbitrary (φ,λ) | u(-theta,-lam,-phi) q; — i.e. the inverse of the state-prep unitary | the prepared direction | its antipode |
CHSH angle convention: for the observable n·σ = cos(a)·Z + sin(a)·X, the pre-measurement
gate is ry(-a). The optimal CHSH settings on Φ⁺ are Alice a ∈ {0, pi/2},
Bob b ∈ {pi/4, -pi/4}, giving
S = E(a0,b0) + E(a0,b1) + E(a1,b0) − E(a1,b1) = 2*sqrt(2) (verified).
Watch the sign pattern — which of the four terms is negated depends on the challenge's stated
CHSH form.
BB84 basis labels: bit 0 = rectilinear/Z (|0>,|1>),
bit 1 = diagonal/X (|+>,|−>). Prepare X-basis with h after the
optional x; measure X-basis with h before measure.
6. Qiskit API (2.5.0)
Endianness. Qiskit is little-endian: qubit 0 is the RIGHTMOST character
of a counts string and of Statevector basis labels.
QuantumCircuit(3); qc.x(0) gives counts {'001': …}, not '100'.
Same for Operator(qc).data — the row/column ordering is
|q(n-1) … q1 q0>. When a challenge quotes a bitstring, decide which end is qubit 0
before writing any code. Fixes: read key[::-1], or call
qc.reverse_bits() once at the end.
Build
from qiskit import QuantumCircuit, transpile
qc = QuantumCircuit(3, 3) # 3 qubits, 3 classical bits
qc.h(0); qc.cx(0, 1); qc.ccx(0, 1, 2)
qc.rz(0.3, 1); qc.barrier()
qc.measure([0, 1, 2], [0, 1, 2]) # or qc.measure_all() (adds its own creg)
qc.reset(0)
qc.initialize([0.6, 0.8], 0) # explicit amplitudes
print(qc.draw()) # text diagram
qc.depth(); qc.count_ops(); qc.size()
qc2 = qc.inverse(); qc3 = qc.compose(other); qc.reverse_bits()
g = other.to_gate(); qc.append(g.control(2), [0, 1, 2, 3])
Mid-circuit conditionals
# c_if is REMOVED in Qiskit 2.x — use the context manager:
qc.measure(0, 0)
with qc.if_test((qc.clbits[0], 1)):
qc.x(1)
Simulate
from qiskit_aer import AerSimulator
sim = AerSimulator()
res = sim.run(transpile(qc, sim), shots=1024).result()
counts = res.get_counts() # {'00': 504, '11': 520}
# or the reference primitive (no Aer needed):
from qiskit.primitives import StatevectorSampler
r = StatevectorSampler().run([qc], shots=1024).result()
counts = r[0].data.c.get_counts() # .c = name of the classical register
quantum_info (exact, no shots — use this whenever possible)
from qiskit.quantum_info import (Statevector, Operator, DensityMatrix,
partial_trace, entropy, purity, state_fidelity, Pauli, SparsePauliOp)
sv = Statevector(qc_without_measurements)
sv.data # numpy amplitude array
sv.probabilities_dict() # {'00': 0.5, '11': 0.5}
sv.expectation_value(Pauli('XX'))
Statevector.from_label('+') # also '0','1','-','r'(=|i>),'l'
state_fidelity(sv_a, sv_b)
U = Operator(qc).data # unitary as numpy array
Operator(qc_a).equiv(Operator(qc_b)) # True if equal UP TO GLOBAL PHASE
Operator(qc_a) == Operator(qc_b) # exact, phase-sensitive
rho = partial_trace(sv, [1]) # trace out qubit 1
entropy(rho) # 1.0 for a Bell state half; 0.0 if separable
purity(rho) # 1.0 pure, 0.5 maximally mixed (1 qubit)
Transpile / basis change
t = transpile(qc, basis_gates=['u', 'cx'], optimization_level=3)
t = transpile(qc, basis_gates=['h','t','tdg','cx'])
t = transpile(qc, coupling_map=[[0,1],[1,2]], optimization_level=3)
t.count_ops(); t.depth()
qc.decompose() # one level of unrolling, no optimisation
QASM in / out
from qiskit import qasm2, qasm3
qc = qasm2.loads(src); qc = qasm2.load('c.qasm')
src = qasm2.dumps(qc) # fails on non-register-equality conditionals
src = qasm3.dumps(qc)
# qasm3.loads/load needs an extra package: pip install qiskit_qasm3_import
Counts handling
max(counts, key=counts.get) # most likely bitstring
sorted(counts.items(), key=lambda kv: -kv[1])[:5]
sum(v for k, v in counts.items() if k[-1] == '1') / shots # P(qubit0 = 1)
int(bitstring[::-1], 2) # reverse then parse to int
7. OpenQASM 2 vs OpenQASM 3
| Feature | OpenQASM 2 | OpenQASM 3 |
|---|---|---|
| Header | OPENQASM 2.0; | OPENQASM 3.0; (also 3;) |
| Include | include "qelib1.inc"; | include "stdgates.inc"; |
| Qubits | qreg q[2]; | qubit[2] q; |
| Classical bits | creg c[2]; | bit[2] c; |
| Gate call | cx q[0],q[1]; | cx q[0], q[1]; (identical) |
| Parametrised | rz(pi/4) q[1]; | rz(pi/4) q[1]; |
| Measure | measure q[0] -> c[0];whole register: measure q -> c; | c[0] = measure q[0];also measure q[0]; as a statement |
| Reset | reset q[0]; | reset q[0]; |
| Barrier | barrier q; | barrier q; |
| Custom gate | gate mg a,b { h a; cx a,b; } | gate mg a, b { h a; cx a, b; } |
| Conditional | if (c==3) x q[0]; — register equality only, single statement | if (c[0]) { x q[1]; } — real blocks, else, for, while |
| Extras | none | int/float/angle vars, def subroutines, ctrl @ / inv @ / pow(k) @ gate modifiers, timing (delay, duration) |
| Std gate set | Both cover id x y z h s sdg t tdg rx ry rz p u cx cy cz ch cp crx cry crz swap ccx cswap sx. Not included: iswap, ccz, mcx — these must be defined with a gate block (Qiskit emits one automatically). | |
Minimal complete OpenQASM 2 program (parses in Qiskit 2.5.0)
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c[2];
gate mygate a, b { h a; cx a, b; }
h q[0];
cx q[0], q[1];
rz(pi/4) q[1];
mygate q[0], q[1];
measure q -> c;
if (c==3) x q[0];
Minimal complete OpenQASM 3 program
OPENQASM 3.0;
include "stdgates.inc";
qubit[2] q;
bit[2] c;
gate mygate a, b { h a; cx a, b; }
h q[0];
cx q[0], q[1];
rz(pi/4) q[1];
mygate q[0], q[1];
c[0] = measure q[0];
c[1] = measure q[1];
if (c[0]) { x q[1]; } // register form: if (c == 3) { ... }
QASM golf checklist: strip barrier; cancel adjacent inverse pairs; merge same-axis
rotations; convert CZ↔CX with the H sandwich to reuse an existing H; reverse a CNOT with 4 H's if
that lets two H's cancel; replace SWAP with relabelled wires when the qubits are never
measured separately. Verify every rewrite with
Operator(a).equiv(Operator(b)) before submitting.
8. Classical crypto glue
The quantum part usually ends with a bitstring. Everything after that is normal CTF work.
Bits ↔ bytes
bits = [1,0,1,0,0,0,0,1] # or "10100001"
s = "".join(map(str, bits))
data = int(s, 2).to_bytes(len(s)//8, 'big') # b'\xa1'
# per-byte, avoids leading-zero loss:
data = bytes(int(s[i:i+8], 2) for i in range(0, len(s), 8))
# back:
s = "".join(f"{b:08b}" for b in data)
# MSB-first vs LSB-first: if the flag is garbage, try s[::-1] and per-byte reversal
XOR / encodings
xor = bytes(a ^ b for a, b in zip(ct, key)) # equal length
xor = bytes(c ^ key[i % len(key)] for i, c in enumerate(ct)) # repeating key
import base64, binascii
base64.b64decode("aGk=") # b'hi'
bytes.fromhex("6869") # b'hi'
binascii.hexlify(b'hi') # b'6869'
data.decode('utf-8', 'replace')
AES
from Crypto.Cipher import AES # pycryptodome
from Crypto.Util.Padding import unpad
AES.new(key, AES.MODE_ECB).decrypt(ct) # no IV, blocks independent
unpad(AES.new(key, AES.MODE_CBC, iv).decrypt(ct), 16) # IV = first 16 bytes of ct, usually
AES.new(key, AES.MODE_CTR, nonce=n).decrypt(ct) # stream, no padding
AES.new(key, AES.MODE_GCM, nonce=n).decrypt(ct) # +16-byte tag at the end
The standard QKD challenge chain
1. Parse the transcript -> Alice bits, Alice bases, Bob bases, Bob results
2. Sift: keep positions where -> alice_bases[i] == bob_bases[i] (~50% survive)
3. Drop the disclosed sample -> the bits used for the QBER estimate
4. Key bits -> bytes -> 128 sifted bits = AES-128 key (256 -> AES-256)
5. Decrypt -> AES-CBC/ECB with that key, IV often prepended
6. Flag -> if it decodes to noise, try: bit order reversed,
bases inverted (0=X not Z), key derived via
SHA-256(bitstring) instead of raw packing
Also try hashlib.sha256(bits_str.encode()).digest() and
hashlib.sha256(key_bytes).digest() as key derivations — both are common.
9. Constants
| Quantity | Value | Where it shows up |
|---|---|---|
| CHSH classical bound | S = 2, win probability 0.75 | any local hidden-variable strategy caps here |
| CHSH quantum (Tsirelson) bound | S = 2*sqrt(2) = 2.8284, win probability cos^2(pi/8) = 0.85355 | optimal entangled strategy; a measured 0.85 means the challenge wants the Bell strategy |
| Optimal CHSH angles (Φ⁺) | Alice {0, pi/2}, Bob {pi/4, -pi/4}; each |E| = w = 0.7071 | plug straight into a CHSH solver |
| BB84 sifting rate | ~50% of raw bits survive basis reconciliation | sanity-check the transcript length |
| Intercept-resend QBER | 25% | Eve measures every qubit in a random basis. QBER ≈ 0 ⇒ no eavesdropper; ≈ 0.25 ⇒ full intercept-resend; in between ⇒ partial attack, fraction ≈ 4·QBER |
| BB84 abort threshold | ~11% QBER | above this no secret key can be distilled |
| Grover iterations | floor((pi/4) * sqrt(N/M)) | N = search space, M = marked items. N=16,M=1 → 3. N=1024,M=1 → 25. Overshooting reduces success probability |
| Grover speedup | O(sqrt(N)) — halves effective key length | AES-128 → ~2^64 quantum work |
| Common values | 1/sqrt(2)=0.70711, 1/sqrt(3)=0.57735, cos(pi/8)=0.92388, sin(pi/8)=0.38268 | recognising amplitudes in a printed statevector |
| Entropy landmarks | 1 qubit maximally mixed → entropy = 1.0, purity = 0.5; pure → 0.0, 1.0 | detecting entanglement with partial_trace |
10. CTF conventions
Flag format is typically flag{...} or QV{...} — check the scoreboard rules page
first and grep for both. Submit exactly as printed, braces included, no trailing whitespace.
Flags are case-sensitive. If a decrypted blob contains printable text but no braces, the flag may need
one more decode step (base64, hex, rot13) or a different bit ordering.
Other habits worth keeping: each category's intro challenge unlocks the rest, so clear all intros first; save every transcript and circuit file before editing; when a numeric answer is requested, check whether the scoreboard wants it raw, rounded, or wrapped in the flag format.