In [1]:
import numpy as np
from qiskit import *

My protocol from 10.1088/1367-2630/10/8/083037 :

  1. Alice chooses a and sends psi_a to Bob
  2. Bob chooses b and sends it classically to Alice
  3. Alice tells a to Bob
  4. Bob checks by measuring <psi_a|psi_received>
In [2]:
# This phase can be choosen arbitrarily as 0<phase<pi
# This changes the balance between how easily Alice can cheat and how easily Bob can
phase = np.pi/2 # balanced
#phase = np.pi/4 # More cheating for Alice
#phase = 3*np.pi/4 # More cheating for Bob
In [3]:
psi   = QuantumRegister(1,"ψ")
qr   = QuantumRegister(1,"q")
pubcr   = ClassicalRegister(3,"public c") # a, b, fail
acr   = ClassicalRegister(1,"Alice's c")
In [4]:
AliceHonestStep1 = QuantumCircuit(psi,qr,pubcr,acr)
AliceHonestStep1.initialize('+0') # Be careful with the endianness!!
AliceHonestStep1.measure(qr[0],acr[0])
AliceHonestStep1.ry(phase,psi[0]).c_if(acr[0],1)
AliceHonestStep1.reset(qr[0])
AliceHonestStep1.draw('mpl')
Out[4]:
In [5]:
# Generate a random number from a qbit because why not
BobHonestStep2 = QuantumCircuit(psi,qr,pubcr,acr)
BobHonestStep2.barrier()
BobHonestStep2.initialize('+',[qr[0]])
BobHonestStep2.measure(qr[0],pubcr[1])
BobHonestStep2.reset(qr[0])
BobHonestStep2.draw('mpl')
Out[5]:
In [6]:
# Dirty trick to copy a classical bit through a pure quantum state
AliceHonestStep3 = QuantumCircuit(psi,qr,pubcr,acr)
AliceHonestStep3.barrier()
AliceHonestStep3.initialize('0',[qr[0]])
AliceHonestStep3.x(qr[0]).c_if(acr[0],1)
AliceHonestStep3.measure(qr[0],pubcr[0])
AliceHonestStep3.reset(1)
AliceHonestStep3.draw('mpl')
Out[6]:
In [7]:
# cbit 0 = a
# cbit 1 = b
# cbit 2 = failure/cheat detection
BobHonestStep4 = QuantumCircuit(psi,qr,pubcr,acr)
BobHonestStep4.barrier()
BobHonestStep4.ry(-phase,psi[0]).c_if(pubcr[0],1)
BobHonestStep4.measure(psi[0],pubcr[2])
BobHonestStep4.draw('mpl')
Out[7]:
In [8]:
from qiskit.visualization import plot_histogram
def testcirc(qc):
    backend_sim = Aer.get_backend('qasm_simulator')
    job_sim = backend_sim.run(transpile(qc, backend_sim), shots=1024)
    result_sim = job_sim.result()
    counts = result_sim.get_counts(qc)
    display(plot_histogram(counts))
In [9]:
# Honest should give public **0 with equal probabilities
# In binary representation a[0]p[2]p[1]p[0] = x0**
# I note 'x' for "I don't care" and '*' for "equal probability"
HonestFullcirc = AliceHonestStep1.compose(BobHonestStep2.compose(AliceHonestStep3.compose(BobHonestStep4)))
display(HonestFullcirc.draw('mpl'))
testcirc(HonestFullcirc)
/afs/ifh.de/user/j/jfrison/lustre/lustreconda/lib/python3.10/site-packages/scipy/__init__.py:146: UserWarning: A NumPy version >=1.16.5 and <1.23.0 is required for this version of SciPy (detected version 1.23.3
  warnings.warn(f"A NumPy version >={np_minversion} and <{np_maxversion}"
In [10]:
# Do not commit yet, we will see later
AliceDishonestStep1 = QuantumCircuit(psi,qr,pubcr,acr)
AliceDishonestStep1.ry(phase/2,psi[0])
AliceDishonestStep1.draw('mpl')
Out[10]:
In [11]:
# Now use pubcr[1] to choose pubcr[0]
# I am going to assume Alice wants a XOR b to be 0 and Bob wants 1
AliceDishonestStep3 = QuantumCircuit(psi,qr,pubcr,acr)
AliceDishonestStep3.barrier()
AliceDishonestStep3.initialize('0',[qr[0]])
AliceDishonestStep3.x(qr[0]).c_if(pubcr[1],1)
AliceDishonestStep3.measure(qr[0],pubcr[0])
AliceDishonestStep3.reset(qr[0])
AliceDishonestStep3.draw('mpl')
Out[11]:
In [12]:
# Dishonest Alice will keep XOR b at 0, so x x00 + x x11
# Sometimes the cheating is detected so x 100 + x 111
# And Alice's cbit is not used so x=0
DishonestAFullcirc = AliceDishonestStep1.compose(BobHonestStep2.compose(AliceDishonestStep3.compose(BobHonestStep4)))
display(DishonestAFullcirc.draw('mpl'))
testcirc(DishonestAFullcirc)
In [13]:
# Dishonest Bob tries to guess a instead of sending a random number
# Then sends the opposite into b
BobDishonestStep2 = QuantumCircuit(psi,qr,pubcr,acr)
BobDishonestStep2.barrier()
BobDishonestStep2.ry(np.pi+phase/2,psi[0])
BobDishonestStep2.measure(psi[0],pubcr[1])
BobDishonestStep2.draw('mpl')
Out[13]:
In [14]:
# I cannot check if Alice cheated because I destroyed |Psi> by cheating
# So just do nothing.
# Still, let's put fail to 0 just in case
BobDishonestStep4 = QuantumCircuit(psi,qr,pubcr,acr)
BobDishonestStep4.barrier()
BobDishonestStep4.initialize('0',qr[0])
BobDishonestStep4.measure(qr[0],pubcr[2])
BobDishonestStep4.draw('mpl')
Out[14]:
In [15]:
# Dishonest Bob will try to enforce a XOR b at 1, so x x01 + x x10
# It will never fail, but sometimes a XOR b will still be 0
# So mostly x 001 + x 010
DishonestBFullcirc = AliceHonestStep1.compose(BobDishonestStep2.compose(AliceHonestStep3.compose(BobDishonestStep4)))
display(DishonestBFullcirc.draw('mpl'))
testcirc(DishonestBFullcirc)
In [16]:
def compute_res(qc):
    shots=1024
    backend_sim = Aer.get_backend('qasm_simulator')
    job_sim = backend_sim.run(transpile(qc, backend_sim), shots=shots)
    result_sim = job_sim.result()
    counts = result_sim.get_counts(qc)
    res={'perp':0,'0*':0,'1*':0,'*0':0,'*1':0,'0':0,'1':0}
    for k,v in counts.items():
        # k = acr[0] space pubcr[2] pubcr[1] pubcr[0]
        a = k[4]
        b = k[3]
        perp = k[2]
        r = str(int(bool(int(a)) != bool(int(b))))
        dp = v/shots
        #print(f"{v} shots ({dp}) for a={a} b={b} a^b={r} perp={perp}")
        if perp=='1':
            res['perp']+=dp
        else:
            res[f"{a}*"]+=dp
            res[f"*{b}"]+=dp
            res[r]+=dp
    return res
In [17]:
# See e.g. quant-ph/0611234
# For honest Alice vs dishonest Bob the criterion only look at the bias in Alice's ouput for r
# In principle here Alice and Bob agree on r, the only disagreement being if Alice refuses Bob's perp
# For honest Alice vs dishonest Bob, she won't, she trusts him
# For dishonest Alice vs honest Bob, refusing a perp will not increase Bobs's 0 or 1 anyway
KiatevBound = 1/np.sqrt(2)-0.5
def KitaevEpsilon(qc):
    res = compute_res(qc)
    #print(res)
    eps0 = res["0"]-0.5
    eps1 = res["1"]-0.5
    eps = np.max((eps0,eps1))
    return eps
In [18]:
# The merit criterion
# M is defined as (1-p*0)(1-p1*)/2+(1-p*1)(1-p0*)-pperp
# But M was a characteristic of the *protocol*, with p* being maximal probabilities
# Here I define it instead as a characteristic of the *circuit*, where p* is the empirical prob for this strategy
# The M of the protocol would be the minimum of the M of the circuit, 
#      over all cheating strategies (*both* for Alice and for Bob, depending on the terms) for this protocol
# Therefore the classical bound doesn't hold on this version of M.
# Warning: p0* doesn't mean a=0 and b=whatever, it means Alice consider that c=0, regardless of what Bob thinks
# As above we will consider that the two players always agree, because disagreeing can only reduce M.
# Therefore, in a circuit where one player is honest, M = (0.5-eps0)/4+(0.5-eps1)/4-pperp
classicalMbound = 0.0 # Cannot do any better classically (note: the example in the paper requires A to send *2* cbits)
quantumMbound = (1-1/np.sqrt(2))**2 # The best QM can hope for
perfectM = 0.25 # Always as if they were honest. Not even doable with QM but would be the ideal scenario
def computeM(qc):
    res = compute_res(qc)
    M = (1-res['0'])/4 + (1-res['1'])/4 - res['perp']
    return M
In [19]:
print("Kitaev's lower bound: ",KiatevBound)
print("Both honest: ϵ =",KitaevEpsilon(HonestFullcirc))
print("Alice cheats: ϵ =",KitaevEpsilon(DishonestAFullcirc))
print("Bob cheats: ϵ =",KitaevEpsilon(DishonestBFullcirc))
Kitaev's lower bound:  0.20710678118654746
Both honest: ϵ = 0.0078125
Alice cheats: ϵ = 0.3525390625
Bob cheats: ϵ = 0.3505859375
In [20]:
# Only remotely related to the M of my paper
quantumMbound=(1-1/np.sqrt(2))**2
print("Upper bound for the M of the paper:",quantumMbound)
print("Both honest: M =",computeM(HonestFullcirc))
print("Alice cheats: M =",computeM(DishonestAFullcirc))
print("Bob cheats: M =",computeM(DishonestBFullcirc))
Upper bound for the M of the paper: 0.085786437626905
Both honest: M = 0.25
Alice cheats: M = 0.1416015625
Bob cheats: M = 0.25
In [ ]: