Open this lesson in your favourite AI. It'll walk you through the why, explain the demo, and quiz you on the try-it list.
A quantum gate is a unitary matrix — a complex matrix with — that acts on the state vector by ordinary matrix multiplication. That single sentence is the entire kinematic content of gate-based quantum computing. The reason this matters for cryptography is that every attack a quantum adversary mounts is a circuit of unitaries on inputs you control; Shor's algorithm is one specific such circuit, and Grover's is another. Understanding which transformations are unitary (and which are not — like cloning or measurement) tells you what an adversary can and cannot do. The Hadamard gate creates uniform superposition (the input layer of almost every quantum algorithm), the Pauli gates implement bit-flips and phase-flips (the algebra of single-qubit errors), and CNOT is the canonical entangling gate (the only place 'quantum-ness' really differs from 'classical probabilistic').
The Hadamard gate , the three Pauli gates , and the two-qubit CNOT gate are the workhorses. maps and ; is a bit-flip (); is a phase-flip (); CNOT flips its target qubit when its control qubit is .
Use these three in order. Each builds on the one before.
In one paragraph, explain what a quantum gate is and why all quantum gates must be unitary. What goes wrong physically (and mathematically) if a 'gate' is not unitary?
Walk me through what the Hadamard gate does step by step: (a) write it as a matrix, (b) apply it to $|0\rangle$ and to $|1\rangle$ explicitly, (c) explain why $H$ is its own inverse, (d) give the geometric picture on the Bloch sphere.
The CNOT gate plus the single-qubit gates $\{H, T\}$ are universal for quantum computation. Explain what 'universal' means precisely (Solovay-Kitaev), and why the CNOT alone — without single-qubit gates — is not. Tie this to what a cryptographic adversary can or cannot construct.
np.kron(ket1, ket0). Apply CNOT and verify the output is . Now apply CNOT to — it should be unchanged.// main.go
// run: go run main.go
package main
import (
"fmt"
"math"
"math/cmplx"
)
type Matrix [][]complex128
func matMul(a, b Matrix) Matrix {
n := len(a)
m := len(b[0])
p := len(b)
c := make(Matrix, n)
for i := range c {
c[i] = make([]complex128, m)
for j := 0; j < m; j++ {
for k := 0; k < p; k++ {
c[i][j] += a[i][k] * b[k][j]
}
}
}
return c
}
func conjT(a Matrix) Matrix {
n := len(a)
m := len(a[0])
b := make(Matrix, m)
for i := range b {
b[i] = make([]complex128, n)
for j := 0; j < n; j++ {
b[i][j] = cmplx.Conj(a[j][i])
}
}
return b
}
func eye(n int) Matrix {
e := make(Matrix, n)
for i := range e {
e[i] = make([]complex128, n)
e[i][i] = 1
}
return e
}
func normFro(a Matrix) float64 {
var s float64
for _, row := range a {
for _, v := range row {
s += real(v*cmplx.Conj(v))
}
}
return math.Sqrt(s)
}
func matvec(m Matrix, v []complex128) []complex128 {
n := len(m)
out := make([]complex128, n)
for i := 0; i < n; i++ {
for j, x := range v {
out[i] += m[i][j] * x
}
}
return out
}
func subMat(a, b Matrix) Matrix {
n := len(a)
c := make(Matrix, n)
for i := range a {
c[i] = make([]complex128, len(a[i]))
for j := range a[i] {
c[i][j] = a[i][j] - b[i][j]
}
}
return c
}
func main() {
s := 1 / math.Sqrt(2)
H := Matrix{
{complex(s, 0), complex(s, 0)},
{complex(s, 0), complex(-s, 0)},
}
X := Matrix{{0, 1}, {1, 0}}
Y := Matrix{{0, -1i}, {1i, 0}}
Z := Matrix{{1, 0}, {0, -1}}
CNOT := Matrix{
{1, 0, 0, 0},
{0, 1, 0, 0},
{0, 0, 0, 1},
{0, 0, 1, 0},
}
ket0 := []complex128{1, 0}
// Apply H to |0>
plus := matvec(H, ket0)
var norm float64
for _, v := range plus {
norm += real(v * cmplx.Conj(v))
}
fmt.Printf("H|0> = %v norm = %.6f\n", plus, norm)
// Verify unitarity: U^dagger U = I
gates := []struct {
name string
u Matrix
}{
{"H", H}, {"X", X}, {"Y", Y}, {"Z", Z}, {"CNOT", CNOT},
}
for _, g := range gates {
product := matMul(conjT(g.u), g.u)
err := normFro(subMat(product, eye(len(g.u))))
fmt.Printf("%-4s ||U^dagger U - I|| = %.2e\n", g.name, err)
}
}go run main.go