pip install qiskit pylatexenc qiskit-aer --quiet

from qiskit import QuantumCircuit, Aer, transpile
from qiskit.visualization import plot_histogram, plot_bloch_multivector
from numpy.random import randint
from qiskit.visualization import visualize_transition
import numpy as np
import matplotlib.pyplot as plt



qc = QuantumCircuit(1, 1)

# Alice prepara o qubit no estado |-> (1 na base de Hadamard)
qc.x(0)
qc.h(0)
qc.s(0)
qc.barrier()

# Alice envia o qubit para Bob
# Bob mede o qubit na base X
qc.sdg(0)
qc.h(0)
qc.measure(0, 0)

# Backend
display(qc.draw('mpl'))
aer_sim = Aer.get_backend('aer_simulator')
job = aer_sim.run(qc)
plot_h1 = plot_histogram(job.result().get_counts())
plot_h1

qc = QuantumCircuit(1, 1)

# Alice prepara o qubit no estado |-> (1 na base de Hadamard)
qc.x(0)
qc.h(0)
qc.barrier()

# Alice envia o qubit para Bob
# Bob mede o qubit na base X
qc.h(0)
qc.measure(0, 0)

# Backend
display(qc.draw('mpl'))
aer_sim = Aer.get_backend('aer_simulator')
job = aer_sim.run(qc)
plot_h1 = plot_histogram(job.result().get_counts())
plot_h1

qc = QuantumCircuit(1,1)

# Alice prepara o qubit no estado |-> e envia para Bob
qc.x(0)
qc.h(0)

# Eve intersepta e tenta ler o qubit
qc.barrier()
qc.measure(0, 0)
qc.barrier()

# Eve manda a mensagem para Bob

# Bob mede o qubit na base X
qc.h(0)
qc.measure(0,0)

# Backend
display(qc.draw('mpl'))
aer_sim = Aer.get_backend('aer_simulator')
job = aer_sim.run(qc)
plot_h2 = plot_histogram(job.result().get_counts())
plot_h2

n = 100
alice_bits = randint(2, size=n)
print(alice_bits)

#Lista representando a base escolhida para encodar cada bit de alice_bits.
#0 significa preparar na base Z e 1 significa preparar na base X

alice_bases = randint(2, size=n)
print(alice_bases)

#Função que cria uma lista de QuantumCircuits
#Cada um representa um bit da mensagem de Alice

def encode_message(bits, bases):
    message = []
    for i in range(n):
        qc = QuantumCircuit(1,1)
        if bases[i] == 0: # Prepara qubit na base Z
            if bits[i] == 0:
                pass
            else:
                qc.x(0)
        else: # Prepara qubit na base X
            if bits[i] == 0:
                qc.h(0)
            else:
                qc.x(0)
                qc.h(0)
        qc.barrier()
        message.append(qc)
    return message

#Estamos agora codificando a mensagem de Alice
message = encode_message(alice_bits, alice_bases)

#Exemplo para o primeiro bit
print('bit = %i' % alice_bits[0])
print('basis = %i' % alice_bases[0])
message[0].draw('mpl')

#Array representando a base escolhida para medir cada bit de alice_bits.
bob_bases = randint(2, size=n)
print(bob_bases)

#Função que mede cada qubit da mensagem
def measure_message(message, bases):
    backend = Aer.get_backend('aer_simulator')
    measurements = []
    for q in range(n):
        if bases[q] == 0: # mede na base Z
            message[q].measure(0,0)
        if bases[q] == 1: # mede na base X
            message[q].h(0)
            message[q].measure(0,0)
        aer_sim = Aer.get_backend('aer_simulator')
        result = aer_sim.run(message[q], shots=1, memory=True).result()
        measured_bit = int(result.get_memory()[0])
        measurements.append(measured_bit)
    return measurements

#Estamos agora medindo cada qubit da mensagem de Alice
bob_results = measure_message(message, bob_bases)
print(bob_results)

#primeiro bit
message[0].draw('mpl')

#segundo bit
message[1].draw('mpl')



#Função que retorna apenas os qubits em que as bases foram iguais
def remove_garbage(a_bases, b_bases, bits):
    good_bits = []
    for q in range(n):
        if a_bases[q] == b_bases[q]:
            # If both used the same basis, add
            # this to the list of 'good' bits
            good_bits.append(bits[q])
    return good_bits

#Chave da Alice
alice_key = remove_garbage(alice_bases, bob_bases, alice_bits)
print(alice_key)

#Chave do Bob
bob_key = remove_garbage(alice_bases, bob_bases, bob_results)
print(bob_key)

def sample_bits(bits, selection):
    sample = []
    for i in selection:
        #usamos o np.mod para garantir que o bit
        i = np.mod(i, len(bits))
        # pop(i) remove o elemento da lista
        sample.append(bits.pop(i))
    return sample

sample_size = 15
bit_selection = randint(n, size=sample_size)

bob_sample = sample_bits(bob_key, bit_selection)
print("  bob_sample = " + str(bob_sample))
alice_sample = sample_bits(alice_key, bit_selection)
print("alice_sample = "+ str(alice_sample))

#Verificamos se o protocolo funcionou corretamente sem interferências
bob_sample == alice_sample

np.random.seed()
n = 100

# PASSO 1
alice_bits = randint(2, size=n)

# PASSO 2
alice_bases = randint(2, size=n)
message = encode_message(alice_bits, alice_bases)

# PASSO 3
bob_bases = randint(2, size=n)
bob_results = measure_message(message, bob_bases)

# PASSO 4
alice_key = remove_garbage(alice_bases, bob_bases, alice_bits)
bob_key = remove_garbage(alice_bases, bob_bases, bob_results)

# PASSO 5
sample_size = 15
bit_selection = randint(n, size=sample_size)

bob_sample = sample_bits(bob_key, bit_selection)
print("  bob_sample = " + str(bob_sample))
alice_sample = sample_bits(alice_key, bit_selection)
print("alice_sample = "+ str(alice_sample))

# PASSO 1
np.random.seed(seed=3)
alice_bits = randint(2, size=n)
print(alice_bits)

# PASSO 1
np.random.seed(seed=3)
alice_bits = randint(2, size=n)
# PASSO 2
alice_bases = randint(2, size=n)
message = encode_message(alice_bits, alice_bases)
print(alice_bases)

message[0].draw('mpl')

# PASSO 1
np.random.seed(seed=3)
alice_bits = randint(2, size=n)
# PASSO 2
alice_bases = randint(2, size=n)
message = encode_message(alice_bits, alice_bases)
# Interceptação
eve_bases = randint(2, size=n)
intercepted_message = measure_message(message, eve_bases)
print(intercepted_message)

message[0].draw('mpl')

# PASSO 1
np.random.seed(seed=3)
alice_bits = randint(2, size=n)
# PASSO 2
alice_bases = randint(2, size=n)
message = encode_message(alice_bits, alice_bases)
## Interceptação
eve_bases = randint(2, size=n)
intercepted_message = measure_message(message, eve_bases)
# PASSO 3
bob_bases = randint(2, size=n)
bob_results = measure_message(message, bob_bases)
message[0].draw('mpl')

# PASSO 1
np.random.seed(seed=3)
alice_bits = randint(2, size=n)
# PASSO 2
alice_bases = randint(2, size=n)
message = encode_message(alice_bits, alice_bases)
## Interceptação
eve_bases = randint(2, size=n)
intercepted_message = measure_message(message, eve_bases)
# PASSO 3
bob_bases = randint(2, size=n)
bob_results = measure_message(message, bob_bases)
# PASSO 4
bob_key = remove_garbage(alice_bases, bob_bases, bob_results)
alice_key = remove_garbage(alice_bases, bob_bases, alice_bits)

# PASSO 1
np.random.seed(seed=3)
alice_bits = randint(2, size=n)
# PASSO 2
alice_bases = randint(2, size=n)
message = encode_message(alice_bits, alice_bases)
# Interceptação!
eve_bases = randint(2, size=n)
intercepted_message = measure_message(message, eve_bases)
# PASSO 3
bob_bases = randint(2, size=n)
bob_results = measure_message(message, bob_bases)
# PASSO 4
bob_key = remove_garbage(alice_bases, bob_bases, bob_results)
alice_key = remove_garbage(alice_bases, bob_bases, alice_bits)
# PASSO 5
sample_size = 15
bit_selection = randint(n, size=sample_size)
bob_sample = sample_bits(bob_key, bit_selection)
print("  bob_sample = " + str(bob_sample))
alice_sample = sample_bits(alice_key, bit_selection)
print("alice_sample = "+ str(alice_sample))

#Ao comparar as seleções arbitrárias de qubits de Alice e Bob
#Percebemos que elas não são iguais
bob_sample == alice_sample