<jupyter_start><jupyter_text>Part 5 - Intro to Encrypted ProgramsBelieve it or not, it is possible to compute with encrypted data. In other words, it's possible to run a program where ALL of the variables in the program are encrypted! In this tutorial, we're going to walk through very basic tools of encrypted computation. In particular, we're going to focus on one popular approach called Secure Multi-Party Computation. In this lesson, we'll learn how to build an encrypted calculator which can perform calculations on encrypted numbers.Authors:- <NAME> - Twitter: [@iamtrask](https://twitter.com/iamtrask)References: - <NAME> - [Blog](https://mortendahl.github.io) - Twitter: [@mortendahlcs](https://twitter.com/mortendahlcs)  Step 1: Encryption Using Secure Multi-Party ComputationSMPC is at first glance a rather strange form of "encryption". Instead of using a public/private key to encrypt a variable, each value is split into multiple "shares", each of which operate like a private key. Typically, these "shares" will be distributed amongst 2 or more "owners". Thus, in order to decrypt the variable, all owners must agree to allow the decryption. In essence, everyone has a private key. Encrypt()So, let's say we wanted to "encrypt" a varible "x", we could do so in the following way.<jupyter_code>Q = 1234567891011
x = 25
import random

def encrypt(x):
    share_a = random.randint(0,Q)
    share_b = random.randint(0,Q)
    share_c = (x - share_a - share_b) % Q
    return (share_a, share_b,  share_c)
encrypt(x)<jupyter_output><empty_output><jupyter_text>As you can see here, we have split our variable "x" into 3 different shares, which could be sent to 3 different owners. Decrypt()If we wanted to decrypt these 3 shares, we could simply sum them together and take the modulus of the result (mod Q).<jupyter_code>def decrypt(*shares):
    return sum(shares) % Q
a,b,c = encrypt(25)
decrypt(a, b, c)<jupyter_output><empty_output><jupyter_text>Importantly, notice that if we try to decrypt with only two shares, the decryption does not work!<jupyter_code>decrypt(a, b)<jupyter_output><empty_output><jupyter_text>Thus, we need all of the owners to participate in order to decrypt the value. It is in this way that the "shares" act like private keys, all of which must be present in order to decrypt a value.  Step 2: Basic Arithmetic Using SMPCHowever, the truly extraordinary property of Secure Multi-Party Computation is the ability to perform computation **while the variables are still encrypted**. Let's demonstrate simple addition below.<jupyter_code>x = encrypt(25)
y = encrypt(5)
def add(x, y):
    z = list()
    # the first worker adds their shares together
    z.append((x[0] + y[0]) % Q)
    
    # the second worker adds their shares together
    z.append((x[1] + y[1]) % Q)
    
    # the third worker adds their shares together
    z.append((x[2] + y[2]) % Q)
    
    return z
decrypt(*add(x,y))<jupyter_output><empty_output><jupyter_text>Success!!!And there you have it! If each worker (separately) adds their shares together, then the resulting shares will decrypt to the correct value (25 + 5 == 30). As it turns out, SMPC protocols exist which can allow this encrypted computation for the following operations:- addition (which we've just seen)- multiplication- comparisonand using these basic underlying primitives, we can perform arbitrary computation!!!In the next section, we're going to learn how to use the PySyft library to perform these operations!  Step 3: SMPC Using PySyftIn the previous sections, we outlined some basic intuitions around SMPC is supposed to work. However, in practice we don't want to have to hand-write all of the primitive operations ourselves when writing our encrypted programs. So, in this section we're going to walk through the basics of how to do encrypted computation using PySyft. In particualr, we're going to focus on how to do the 3 primitives previously mentioned: addition, multiplication, and comparison.First, we need to create a few Virtual Workers (which hopefully you're now familiar with given our previous tutorials).<jupyter_code>import syft as sy
hook = sy.TorchHook()

bob = sy.VirtualWorker(id="bob")
alice = sy.VirtualWorker(id="alice")
bill = sy.VirtualWorker(id="bill")<jupyter_output><empty_output><jupyter_text>Basic Encryption/DecryptionEncryption is as simple as taking any PySyft tensor and calling .share(). Decryption is as simple as calling .get() on the shared variable<jupyter_code>x = sy.LongTensor([25])
encrypted_x = x.share(bob, alice, bill)
encrypted_x.get()<jupyter_output><empty_output><jupyter_text>Introspecting the Encrypted ValuesIf we look closer at Bob, Alice, and Bill's workers, we can see the shares that get created!<jupyter_code>bob._objects
x = sy.LongTensor([25]).share(bob, alice,bill)
bob._objects
# Bob's share
bobs_share = list(bob._objects.values())[0].parent[0]
bobs_share
# Alice's share
alices_share = list(alice._objects.values())[0].parent[0]
alices_share
# Bill's share
bills_share = list(bill._objects.values())[0].parent[0]
bills_share<jupyter_output><empty_output><jupyter_text>And if we wanted to, we could decrypt these values using the SAME approach we talked about earlier!!!<jupyter_code>Q = sy.spdz.spdz.field

(bobs_share + alices_share + bills_share) % Q<jupyter_output><empty_output><jupyter_text>As you can see, when we called .share() it simply split the value into 3 shares and sent one share to each of the parties!  Encrypted ArithmeticAnd now you see that we can perform arithmetic on the underlying values! The API is constructed so that we can simply perform arithmetic like we would with regular PyTorch tensors.Note: for comparison, it returns boolean outputs (True/False) in the form of Integers (1/0). 1 corresponds to True. 0 corresponds to False.<jupyter_code>x = sy.LongTensor([25]).share(bob,alice)
y = sy.LongTensor([5]).share(bob,alice)
z = x + y
z.get()
z = x * y
z.get()
z = x > y
z.get()
z = x < y
z.get()
z = x == y
z.get()
z = x == y + 20
z.get()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08871234690499835, 'fraction_numerical': 0.010261502813637868, 'mean_word_length': 5.061183550651956, 'pattern_counts': {'":': 0, '<': 40, '<?xml version=': 0, '>': 40, 'https://': 3, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '20207', 'license': 'Apache-2.0', 'n_tokens_mistral': 1881, 'n_tokens_neox': 1762, 'n_words': 762, 'path': 'examples/tutorials/Part 5 - Intro to Encrypted Programs.ipynb', 'repo_name': 'uhvardhan/PySyft', 'size': '6053'}