Quantum Money with a Man-in-the-Middle Attack

In this example, we’ll see how the Wiesner Quantum Money can be implemented using QuNetSim. First, we create a network of three different parties: the bank, the customer, and Eve who plays the eavesdropper. In this protocol, the bank talks to the customer but the information is relayed through Eve, i.e. the “man in the middle”. First we Initialize the network:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def main():
    # Initialize a network
    network = Network.get_instance()
    nodes = ['Bank', 'Customer', 'Eve']
    network.start(nodes)

    host_bank = Host('Bank')
    host_bank.add_connection('Eve')
    host_bank.start()

    host_eve = Host('Eve')
    host_eve.add_connection('Bank')
    host_eve.add_connection('Customer')
    host_eve.start()

    host_customer = Host('Customer')
    host_customer.add_connection('Eve')
    host_customer.start()

    network.add_host(host_bank)
    network.add_host(host_eve)
    network.add_host(host_customer)

    host_eve.q_relay_sniffing = True
    host_eve.q_relay_sniffing_fn = sniffing_quantum

In this protocol, the aim of the bank is to create unforgeable bank notes and distribute it to the customers. To achieve this, for every bank note, the bank assigns a classical serial number and qubits that are polarized in random bases and random directions. These bases and distributions are recorded by the bank for later use. In the first part of this example, the bank creates the money and distributes it to the customer and the customer receives the qubits and the serial numbers:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def banker_protocol(host, customer):
    """
    The banker's protocol.
    Args:
        host (Host): The Host that runs the banker's protocol.
        customer: The ID of the customer.
    """
    bank_bits = [[] for _ in range(NO_OF_SERIALS)]
    bank_basis = [[] for _ in range(NO_OF_SERIALS)]

    def preparation_and_distribution():
        for serial in range(NO_OF_SERIALS):
            for bit_no in range(QUBITS_PER_MONEY):
                random_bit = randint(0, 1)
                random_base = randint(0, 1)

                bank_bits[serial].append(random_bit)
                bank_basis[serial].append(random_base)
                q = Qubit(host)
                if random_bit == 1:
                    q.X()
                if random_base == 1:
                    q.H()
                host.send_qubit(customer, q)

After the bank distributes the money, the customer possesses the money.

1
2
3
4
5
6
7
8
9
def customer_protocol(host, banker):
    money_qubits = [[] for _ in range(NO_OF_SERIALS)]

    def receive_money():
        for serial in range(NO_OF_SERIALS):
            for bit_no in range(QUBITS_PER_MONEY):
                q = host.get_qubit(banker, wait=10)
                money_qubits[serial].append(q)
        print('Customer received money')

To use this money, the customer has to get it verified by the bank. To do this, he sends the serial number of the banknote that he wants to use along with the qubits assigned to the banknote:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
def verify_money():
    print('Customer is verifying the money')
    serial_to_be_used = randint(0, NO_OF_SERIALS - 1)
    host.send_classical(banker, serial_to_be_used, await_ack=True)

    for qubit_no in range(QUBITS_PER_MONEY):
        host.send_qubit(banker, money_qubits[serial_to_be_used][qubit_no], await_ack=False)

    # Remove unused qubits
    unused_serials = list(range(NO_OF_SERIALS))
    del unused_serials[serial_to_be_used]
    if len(unused_serials) > 0:
        print('Customer removes unused qubits')
        for unused_serial in unused_serials:
            for q in money_qubits[unused_serial]:
                q.release()

After receiving the qubits associated with the serial number, the bank measures the qubits to check if measurement results match with the data in bank’s database. If there is a mismatch, the bank realizes that there is a cheating attempt. If measurement results are correct, the bank verifies the money.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def controlling():
    """
    Function to check if qubits representing the money are correct.
    Return:
        Prints out if the money is valid or if teh customer is cheating.
    """
    cheat_alert = False
    print('Banker waiting for serial')
    message = host.get_classical(customer, seq_num=0, wait=10)

    if message is None:
        print("Bank did not receive the serial number")
        return

    print('Serial received by Bank')
    serial_to_be_checked = message.content
    for qubit_no in range(QUBITS_PER_MONEY):
        q = host.get_qubit(customer, wait=10)
        if bank_basis[serial_to_be_checked][qubit_no] == 1:
            q.H()

        measurement = q.measure()
        if measurement != bank_bits[serial_to_be_checked][qubit_no]:
            cheat_alert = True

    if not cheat_alert:
        print('MONEY IS VALID')
    else:
        print('MONEY IS INVALID')

If Eve, being the relay node, acts as an attacker, she can only steal the money but can’t reproduce the money as she doesn’t know the polarization bases. Therefore, the money is unforgeable. Also, if she measures the qubits in a non-destructive way, she can disturb the state of the qubits, therefore invalidating the money. In this example, an example attack is shown. Eve measures the qubits in a while relaying the qubits causing the money that is transferred to the customer to be invalid. This attack is shown below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
def sniffing_quantum(sender, receiver, qubit):
    """
    Function to set what the relay node does to the qubit in transmission.

    Args:
        sender (Host) : Sender of the qubit
        receiver (Host) : Receiver of the qubit
        qubit (Qubit): Qubit in transmission
    """

    # Eavesdropper measures some of the qubits.
    if sender == 'Customer':
        r = random.random()
        if r > 0.5:
            print('Eavesdropper applied I to qubit sent from %s to %s' % (sender, receiver))
            qubit.I()
        else:
            print('Eavesdropper applied X to qubit sent from %s to %s' % (sender, receiver))
            qubit.X()

The full example is below:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
from qunetsim.components import Host
from qunetsim.components import Network
from qunetsim.objects import Logger
from qunetsim.objects import Qubit
from random import randint, random
from qunetsim.backends import ProjectQBackend

Logger.DISABLED = True

WAIT_TIME = 10
QUBITS_PER_MONEY = 8
NO_OF_SERIALS = 1


def banker_protocol(host, customer):
    """
    The banker's protocol.
    Args:
        host (Host): The Host that runs the banker's protocol.
        customer: The ID of the customer.
    """
    bank_bits = [[] for _ in range(NO_OF_SERIALS)]
    bank_basis = [[] for _ in range(NO_OF_SERIALS)]

    def preparation_and_distribution():
        for serial in range(NO_OF_SERIALS):
            for bit_no in range(QUBITS_PER_MONEY):
                random_bit = randint(0, 1)
                random_base = randint(0, 1)

                bank_bits[serial].append(random_bit)
                bank_basis[serial].append(random_base)
                q = Qubit(host)
                if random_bit == 1:
                    q.X()
                if random_base == 1:
                    q.H()
                host.send_qubit(customer, q, await_ack=False)

    def controlling():
        """
        Function to check if qubits representing the money are correct.
        Return:
            Prints out if the money is valid or if teh customer is cheating.
        """
        cheat_alert = False
        print('Banker waiting for serial')
        message = host.get_classical(customer, seq_num=0, wait=10)

        if message is None:
            print("Bank did not receive the serial number")
            return

        print('Serial received by Bank')
        serial_to_be_checked = message.content
        for qubit_no in range(QUBITS_PER_MONEY):
            q = host.get_qubit(customer, wait=10)
            if bank_basis[serial_to_be_checked][qubit_no] == 1:
                q.H()

            measurement = q.measure()
            if measurement != bank_bits[serial_to_be_checked][qubit_no]:
                cheat_alert = True

        if not cheat_alert:
            print('MONEY IS VALID')
        else:
            print('MONEY IS INVALID')

    print("Banker is preparing and distributing qubits")
    preparation_and_distribution()
    print("Banker is verifying the money from customer")
    controlling()


def customer_protocol(host, banker):
    """
    The customer's  protocol.

    Args:
        host (Host): The host who is acting as a customer.
        banker (str): The ID of the banker Host.
    """
    money_qubits = [[] for _ in range(NO_OF_SERIALS)]

    def receive_money():
        for serial in range(NO_OF_SERIALS):
            for bit_no in range(QUBITS_PER_MONEY):
                q = host.get_qubit(banker, wait=10)
                money_qubits[serial].append(q)
        print('Customer received money')

    def verify_money():
        print('Customer is verifying the money')
        serial_to_be_used = randint(0, NO_OF_SERIALS - 1)
        host.send_classical(banker, serial_to_be_used, await_ack=True)

        for qubit_no in range(QUBITS_PER_MONEY):
            host.send_qubit(banker, money_qubits[serial_to_be_used][qubit_no], await_ack=False)

        # Remove unused qubits
        unused_serials = list(range(NO_OF_SERIALS))
        del unused_serials[serial_to_be_used]
        if len(unused_serials) > 0:
            print('Customer removes unused qubits')
            for unused_serial in unused_serials:
                for q in money_qubits[unused_serial]:
                    q.release()

    print('Customer is awaiting serial number and qubits that represent the money')
    receive_money()
    print('Customer is getting his money verified')
    verify_money()


def sniffing_quantum(sender, receiver, qubit):
    """
    Function to set what the relay node does to the qubit in transmission.

    Args:
        sender (Host) : Sender of the qubit
        receiver (Host) : Receiver of the qubit
        qubit (Qubit): Qubit in transmission
    """
    # Eavesdropper measures some of the qubits.
    print('did this')
    if sender == 'Customer':
        r = random()
        if r > 0.5:
            print('Eavesdropper applied I to qubit sent from %s to %s' % (sender, receiver))
            qubit.I()
        else:
            print('Eavesdropper applied X to qubit sent from %s to %s' % (sender, receiver))
            qubit.X()


def main():
    # Initialize a network
    network = Network.get_instance()
    nodes = ['Bank', 'Customer', 'Eve']
    network.delay = 0.2
    network.start(nodes)

    host_bank = Host('Bank')
    host_bank.add_connection('Eve')
    host_bank.delay = 0.3
    host_bank.start()

    host_eve = Host('Eve')
    host_eve.add_connection('Bank')
    host_eve.add_connection('Customer')
    host_eve.start()

    host_customer = Host('Customer')
    host_customer.add_connection('Eve')
    host_customer.delay = 0.3
    host_customer.start()

    network.add_host(host_bank)
    network.add_host(host_eve)
    network.add_host(host_customer)

    host_eve.q_relay_sniffing = True
    host_eve.q_relay_sniffing_fn = sniffing_quantum

    print('Starting transfer')

    t = host_customer.run_protocol(customer_protocol, (host_bank.host_id,))
    host_bank.run_protocol(banker_protocol, (host_customer.host_id,), blocking=True)
    t.join()

    network.stop(True)


if __name__ == '__main__':
    main()