├── .gitignore ├── crt └── benchmarks │ ├── Cargo.toml │ └── src │ └── lib.rs ├── image-analysis ├── Feature extraction.ipynb ├── Fine-tuning.ipynb ├── Keras.ipynb ├── Pre-training.ipynb ├── Prediction.ipynb ├── Presentation.ipynb ├── Private Image Analysis.ipynb ├── dataset.py ├── layer0_bias.npy ├── layer0_bias_0.npy ├── layer0_bias_1.npy ├── layer0_weights.npy ├── layer0_weights_0.npy ├── layer0_weights_1.npy ├── layer2_bias.npy ├── layer2_bias_0.npy ├── layer2_bias_1.npy ├── layer2_weights.npy ├── layer2_weights_0.npy ├── layer2_weights_1.npy ├── pond │ ├── nn.py │ └── tensor.py └── pre-trained.h5 ├── notebook_importer.py ├── paillier ├── Paillier.ipynb └── benchmarks │ ├── .gitignore │ ├── Cargo.toml │ └── src │ └── lib.rs ├── secret-sharing ├── Fast Fourier Transform Walk-through.ipynb ├── Fast Fourier Transform.ipynb ├── Performance.ipynb ├── Reed-Solomon.ipynb └── Schemes.ipynb ├── simple-boolean-functions ├── A Simple Tutorial from Scratch.ipynb └── Approximating Sigmoid.ipynb ├── spdz ├── Basic SPDZ.ipynb ├── CRT.ipynb ├── Tensor SPDZ.ipynb └── primegen.java └── tensorflow ├── .gitignore ├── Linear.ipynb ├── parsetrace.py ├── simple-distributed-task ├── config.py ├── task.py ├── worker0.py └── worker1.py └── spdz ├── README.md ├── configs ├── cpus │ └── config.py ├── gcp │ ├── README.md │ ├── link.py │ ├── ps.sh │ ├── pull.sh │ ├── restart.sh │ ├── run.sh │ ├── server │ │ ├── config.py │ │ ├── monitor.sh │ │ └── role.py │ ├── setup │ │ ├── debian-install.sh │ │ ├── gcp-create-images.sh │ │ ├── gcp-create-instances.sh │ │ ├── gcp-create.sh │ │ ├── gcp-destroy-images.sh │ │ ├── gcp-destroy-instances.sh │ │ └── gcp-destroy.sh │ ├── start.sh │ └── stop.sh └── localhost │ ├── README.md │ ├── config.py │ ├── cryptoproducer.py │ ├── inputprovider.py │ ├── outputreceiver.py │ ├── run.sh │ ├── server0.py │ ├── server1.py │ ├── start.sh │ └── stop.sh ├── logistic-regression-simple ├── prediction.py └── training.py ├── playground ├── config.py ├── main.py └── run.sh ├── run.sh ├── simple-dense ├── README.md ├── compute-time.png ├── config.py ├── main.py └── role-operations.png └── tensorspdz.py /.gitignore: -------------------------------------------------------------------------------- 1 | .ipynb_checkpoints/ 2 | __pycache__/ 3 | **/*.pyc 4 | **/env/ 5 | **/.vscode/ 6 | **/.DS_Store 7 | -------------------------------------------------------------------------------- /crt/benchmarks/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "benchmarks" 3 | version = "0.1.0" 4 | authors = ["Morten Dahl "] 5 | 6 | [dependencies] 7 | rayon = "*" -------------------------------------------------------------------------------- /crt/benchmarks/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate rayon; 4 | extern crate test; 5 | 6 | use std::ops::*; 7 | 8 | use rayon::prelude::*; 9 | 10 | #[cfg(test)] 11 | use test::Bencher; 12 | 13 | const NB_MODULI: usize = 5; 14 | const MODULI: [i64; NB_MODULI] = [89702869, 78489023, 69973811, 70736797, 79637461]; 15 | 16 | #[derive(Clone, Debug, PartialEq)] 17 | pub struct CrtScalar([i64; NB_MODULI]); 18 | 19 | impl From for CrtScalar { 20 | fn from(x: i64) -> Self { 21 | let mut backing = [0; NB_MODULI]; 22 | backing.iter_mut().enumerate() 23 | .for_each(|(i, zi)| { 24 | let mi = MODULI[i]; 25 | *zi = x % mi; 26 | }); 27 | CrtScalar(backing) 28 | } 29 | } 30 | 31 | impl Index for CrtScalar { 32 | type Output = i64; 33 | fn index(&self, i: usize) -> &Self::Output { 34 | &self.0[i] 35 | } 36 | } 37 | 38 | #[test] 39 | fn test_from() { 40 | let x = CrtScalar::from(123456789); 41 | assert_eq!(x.0, [33753920, 44967766, 53482978, 52719992, 43819328]) 42 | } 43 | 44 | #[bench] 45 | fn bench_from(b: &mut Bencher) { 46 | b.iter(|| { 47 | let _ = CrtScalar::from(123456789); 48 | }) 49 | } 50 | 51 | // impl Add for CrtScalar { 52 | // type Output = CrtScalar; 53 | // fn add(self, other: CrtScalar) -> Self::Output { 54 | // let mut z = [0; NB_MODULI]; 55 | // z.iter_mut().enumerate() 56 | // .for_each(|(i, zi)| { 57 | // let xi = self[i]; 58 | // let yi = other[i]; 59 | // let mi = MODULI[i]; 60 | // *zi = (xi + yi) % mi 61 | // }); 62 | // CrtScalar(z) 63 | // } 64 | // } 65 | 66 | impl<'s, 'o> Add<&'o CrtScalar> for &'s CrtScalar { 67 | type Output = CrtScalar; 68 | fn add(self, other: &'o CrtScalar) -> Self::Output { 69 | let mut backing = [0; NB_MODULI]; 70 | backing.iter_mut().enumerate() 71 | .for_each(|(i, zi)| { 72 | let xi = self[i]; 73 | let yi = other[i]; 74 | let mi = MODULI[i]; 75 | *zi = (xi + yi) % mi 76 | }); 77 | CrtScalar(backing) 78 | } 79 | } 80 | 81 | #[test] 82 | fn test_add() { 83 | let x = CrtScalar::from(1234567); 84 | let y = CrtScalar::from(7654321); 85 | let z = &x + &y; 86 | assert_eq!(z, CrtScalar::from(1234567+7654321)) 87 | } 88 | 89 | // #[bench] 90 | // fn bench_add_crt(b: &mut Bencher) { 91 | // let x = CrtScalar::from(1234567); 92 | // let y = CrtScalar::from(7654321); 93 | // b.iter(|| { 94 | // let _z = &x + &y; 95 | // }) 96 | // } 97 | 98 | // #[bench] 99 | // fn bench_add_native(b: &mut Bencher) { 100 | // let x = 1234567; 101 | // let y = 7654321; 102 | // b.iter(|| { 103 | // let _z = &x + &y; 104 | // }) 105 | // } 106 | 107 | // impl<'s, 'o> Sub<&'o CrtScalar> for &'s CrtScalar { 108 | // type Output = CrtScalar; 109 | // fn sub(self, other: &'o CrtScalar) -> Self::Output { 110 | // let mut backing = [0; NB_MODULI]; 111 | // backing.iter_mut().enumerate() 112 | // .for_each(|(i, zi)| { 113 | // let xi = self[i]; 114 | // let yi = other[i]; 115 | // let mi = MODULI[i]; 116 | // *zi = (xi - yi) % mi; 117 | // }); 118 | // CrtScalar(backing) 119 | // } 120 | // } 121 | 122 | // #[test] 123 | // fn test_sub() { 124 | // let x = CrtScalar::from(1234567); 125 | // let y = CrtScalar::from(7654321); 126 | // let z = &x - &y; 127 | // assert_eq!(z, CrtScalar::from(1234567-7654321)) 128 | // } 129 | 130 | // impl<'s, 'o> Mul<&'o CrtScalar> for &'s CrtScalar { 131 | // type Output = CrtScalar; 132 | // fn mul(self, other: &'o CrtScalar) -> Self::Output { 133 | // let mut backing = [0; NB_MODULI]; 134 | // backing.iter_mut().enumerate() 135 | // .for_each(|(i, zi)| { 136 | // let xi = self[i]; 137 | // let yi = other[i]; 138 | // let mi = MODULI[i]; 139 | // *zi = (xi * yi) % mi 140 | // }); 141 | // CrtScalar(backing) 142 | // } 143 | // } 144 | 145 | // #[test] 146 | // fn test_mul() { 147 | // let x = CrtScalar::from(1234567); 148 | // let y = CrtScalar::from(7654321); 149 | // let z = &x * &y; 150 | // assert_eq!(z, CrtScalar::from(1234567 * 7654321)) 151 | // } 152 | -------------------------------------------------------------------------------- /image-analysis/Feature extraction.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stderr", 10 | "output_type": "stream", 11 | "text": [ 12 | "Using TensorFlow backend.\n" 13 | ] 14 | } 15 | ], 16 | "source": [ 17 | "import numpy as np\n", 18 | "import keras\n", 19 | "\n", 20 | "from dataset import load_mnist" 21 | ] 22 | }, 23 | { 24 | "cell_type": "markdown", 25 | "metadata": {}, 26 | "source": [ 27 | "# Define extractor from pre-trained model" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": 2, 33 | "metadata": {}, 34 | "outputs": [ 35 | { 36 | "name": "stdout", 37 | "output_type": "stream", 38 | "text": [ 39 | "_________________________________________________________________\n", 40 | "Layer (type) Output Shape Param # \n", 41 | "=================================================================\n", 42 | "conv2d_1 (Conv2D) (None, 28, 28, 32) 320 \n", 43 | "_________________________________________________________________\n", 44 | "activation_1 (Activation) (None, 28, 28, 32) 0 \n", 45 | "_________________________________________________________________\n", 46 | "conv2d_2 (Conv2D) (None, 28, 28, 32) 9248 \n", 47 | "_________________________________________________________________\n", 48 | "activation_2 (Activation) (None, 28, 28, 32) 0 \n", 49 | "_________________________________________________________________\n", 50 | "average_pooling2d_1 (Average (None, 14, 14, 32) 0 \n", 51 | "_________________________________________________________________\n", 52 | "dropout_1 (Dropout) (None, 14, 14, 32) 0 \n", 53 | "_________________________________________________________________\n", 54 | "flatten_1 (Flatten) (None, 6272) 0 \n", 55 | "_________________________________________________________________\n", 56 | "dense_1 (Dense) (None, 128) 802944 \n", 57 | "_________________________________________________________________\n", 58 | "activation_3 (Activation) (None, 128) 0 \n", 59 | "_________________________________________________________________\n", 60 | "dropout_2 (Dropout) (None, 128) 0 \n", 61 | "_________________________________________________________________\n", 62 | "dense_2 (Dense) (None, 5) 645 \n", 63 | "_________________________________________________________________\n", 64 | "activation_4 (Activation) (None, 5) 0 \n", 65 | "=================================================================\n", 66 | "Total params: 813,157\n", 67 | "Trainable params: 813,157\n", 68 | "Non-trainable params: 0\n", 69 | "_________________________________________________________________\n" 70 | ] 71 | } 72 | ], 73 | "source": [ 74 | "model = keras.models.load_model('pre-trained.h5')\n", 75 | "model.summary()" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": 3, 81 | "metadata": {}, 82 | "outputs": [], 83 | "source": [ 84 | "flatten_layer = model.get_layer(index=7)\n", 85 | "assert flatten_layer.name.startswith('flatten_')\n", 86 | "\n", 87 | "extractor = keras.models.Model(\n", 88 | " inputs=model.input, \n", 89 | " outputs=flatten_layer.output\n", 90 | ")" 91 | ] 92 | }, 93 | { 94 | "cell_type": "markdown", 95 | "metadata": {}, 96 | "source": [ 97 | "# Extract features from private data (unencrypted for now)" 98 | ] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "execution_count": 4, 103 | "metadata": {}, 104 | "outputs": [], 105 | "source": [ 106 | "_, private_dataset = load_mnist()\n", 107 | "\n", 108 | "(x_train_images, y_train), (x_test_images, y_test) = private_dataset" 109 | ] 110 | }, 111 | { 112 | "cell_type": "code", 113 | "execution_count": 5, 114 | "metadata": {}, 115 | "outputs": [], 116 | "source": [ 117 | "x_train_features = extractor.predict(x_train_images)\n", 118 | "x_test_features = extractor.predict(x_test_images)" 119 | ] 120 | }, 121 | { 122 | "cell_type": "markdown", 123 | "metadata": {}, 124 | "source": [ 125 | "# Save extracted features for use in fine-tuning" 126 | ] 127 | }, 128 | { 129 | "cell_type": "code", 130 | "execution_count": 6, 131 | "metadata": {}, 132 | "outputs": [], 133 | "source": [ 134 | "np.save('x_train_features.npy', x_train_features)\n", 135 | "np.save('y_train.npy', y_train)\n", 136 | "\n", 137 | "np.save('x_test_features.npy', x_test_features)\n", 138 | "np.save('y_test.npy', y_test)" 139 | ] 140 | }, 141 | { 142 | "cell_type": "code", 143 | "execution_count": null, 144 | "metadata": {}, 145 | "outputs": [], 146 | "source": [] 147 | } 148 | ], 149 | "metadata": { 150 | "kernelspec": { 151 | "display_name": "Python 3", 152 | "language": "python", 153 | "name": "python3" 154 | }, 155 | "language_info": { 156 | "codemirror_mode": { 157 | "name": "ipython", 158 | "version": 3 159 | }, 160 | "file_extension": ".py", 161 | "mimetype": "text/x-python", 162 | "name": "python", 163 | "nbconvert_exporter": "python", 164 | "pygments_lexer": "ipython3", 165 | "version": "3.5.3" 166 | } 167 | }, 168 | "nbformat": 4, 169 | "nbformat_minor": 2 170 | } 171 | -------------------------------------------------------------------------------- /image-analysis/Fine-tuning.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import numpy as np\n", 10 | "from datetime import datetime" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "metadata": {}, 16 | "source": [ 17 | "# Load features previously extracted from training data" 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": 2, 23 | "metadata": {}, 24 | "outputs": [ 25 | { 26 | "name": "stdout", 27 | "output_type": "stream", 28 | "text": [ 29 | "(29404, 6272) (29404, 5) (4861, 6272) (4861, 5)\n" 30 | ] 31 | } 32 | ], 33 | "source": [ 34 | "x_train_features = np.load('x_train_features.npy')\n", 35 | "y_train = np.load('y_train.npy')\n", 36 | "\n", 37 | "x_test_features = np.load('x_test_features.npy')\n", 38 | "y_test = np.load('y_test.npy')\n", 39 | "\n", 40 | "print(x_train_features.shape, y_train.shape, x_test_features.shape, y_test.shape)" 41 | ] 42 | }, 43 | { 44 | "cell_type": "markdown", 45 | "metadata": {}, 46 | "source": [ 47 | "# Prepare new classification model" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": 3, 53 | "metadata": {}, 54 | "outputs": [], 55 | "source": [ 56 | "from pond.tensor import NativeTensor, PrivateEncodedTensor, PublicEncodedTensor\n", 57 | "from pond.nn import Dense, Sigmoid, Reveal, Diff, Softmax, CrossEntropy, Sequential, DataLoader" 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": 4, 63 | "metadata": {}, 64 | "outputs": [], 65 | "source": [ 66 | "classifier = Sequential([\n", 67 | " Dense(128, 6272),\n", 68 | " Sigmoid(),\n", 69 | " # Dropout(.5),\n", 70 | " Dense(5, 128),\n", 71 | " Reveal(),\n", 72 | " Softmax()\n", 73 | "])" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": 5, 79 | "metadata": {}, 80 | "outputs": [], 81 | "source": [ 82 | "def accuracy(classifier, x, y, verbose=0, wrapper=NativeTensor):\n", 83 | " predicted_classes = classifier \\\n", 84 | " .predict(DataLoader(x, wrapper), verbose=verbose).reveal() \\\n", 85 | " .argmax(axis=1)\n", 86 | " \n", 87 | " correct_classes = NativeTensor(y) \\\n", 88 | " .argmax(axis=1)\n", 89 | " \n", 90 | " matches = predicted_classes.unwrap() == correct_classes.unwrap()\n", 91 | " return sum(matches)/len(matches)" 92 | ] 93 | }, 94 | { 95 | "cell_type": "markdown", 96 | "metadata": {}, 97 | "source": [ 98 | "# [optional] Fine-tune using NativeTensor to get accuracy expectations" 99 | ] 100 | }, 101 | { 102 | "cell_type": "code", 103 | "execution_count": 6, 104 | "metadata": {}, 105 | "outputs": [ 106 | { 107 | "name": "stdout", 108 | "output_type": "stream", 109 | "text": [ 110 | "2018-01-29 08:39:08.836028 Epoch 0\n", 111 | "2018-01-29 08:39:29.404299 Epoch 1\n", 112 | "2018-01-29 08:39:49.102325 Epoch 2\n", 113 | "Elapsed: 0:01:01.768282\n" 114 | ] 115 | } 116 | ], 117 | "source": [ 118 | "classifier.initialize()\n", 119 | "\n", 120 | "start = datetime.now()\n", 121 | "classifier.fit(\n", 122 | " DataLoader(x_train_features, wrapper=NativeTensor), \n", 123 | " DataLoader(y_train, wrapper=NativeTensor), \n", 124 | " loss=CrossEntropy(), \n", 125 | " epochs=3,\n", 126 | " learning_rate=.01,\n", 127 | " verbose=1\n", 128 | ")\n", 129 | "stop = datetime.now()\n", 130 | "\n", 131 | "print(\"Elapsed:\", stop - start)" 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": 7, 137 | "metadata": {}, 138 | "outputs": [], 139 | "source": [ 140 | "train_accuracy = accuracy(classifier, x_train_features, y_train)\n", 141 | "test_accuracy = accuracy(classifier, x_test_features, y_test)" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": 8, 147 | "metadata": {}, 148 | "outputs": [ 149 | { 150 | "name": "stdout", 151 | "output_type": "stream", 152 | "text": [ 153 | "Train accuracy: 0.92361583458\n", 154 | "Test accuracy: 0.921826784612\n" 155 | ] 156 | } 157 | ], 158 | "source": [ 159 | "print(\"Train accuracy:\", train_accuracy)\n", 160 | "print(\"Test accuracy:\", test_accuracy)" 161 | ] 162 | }, 163 | { 164 | "cell_type": "markdown", 165 | "metadata": {}, 166 | "source": [ 167 | "# [optional] Fine-tune using PublicEncodedTensor to get baseline" 168 | ] 169 | }, 170 | { 171 | "cell_type": "code", 172 | "execution_count": 9, 173 | "metadata": {}, 174 | "outputs": [ 175 | { 176 | "name": "stdout", 177 | "output_type": "stream", 178 | "text": [ 179 | "2018-01-29 08:41:32.014263 Epoch 0\n", 180 | "2018-01-29 12:18:44.271448 Epoch 1\n", 181 | "2018-01-29 15:21:50.934498 Epoch 2\n", 182 | "Elapsed: 9:22:20.037328\n" 183 | ] 184 | } 185 | ], 186 | "source": [ 187 | "classifier.initialize()\n", 188 | "\n", 189 | "start = datetime.now()\n", 190 | "classifier.fit(\n", 191 | " DataLoader(x_train_features, wrapper=PublicEncodedTensor), \n", 192 | " DataLoader(y_train, wrapper=PublicEncodedTensor),\n", 193 | " loss=CrossEntropy(), \n", 194 | " epochs=3,\n", 195 | " learning_rate=.01,\n", 196 | " verbose=1\n", 197 | ")\n", 198 | "stop = datetime.now()\n", 199 | "\n", 200 | "print(\"Elapsed:\", stop - start)" 201 | ] 202 | }, 203 | { 204 | "cell_type": "code", 205 | "execution_count": 13, 206 | "metadata": {}, 207 | "outputs": [], 208 | "source": [ 209 | "np.save('layer0_weights.npy', classifier.layers[0].weights.elements)\n", 210 | "np.save('layer0_bias.npy', classifier.layers[0].bias.elements)\n", 211 | "\n", 212 | "np.save('layer2_weights.npy', classifier.layers[2].weights.elements)\n", 213 | "np.save('layer2_bias.npy', classifier.layers[2].bias.elements)" 214 | ] 215 | }, 216 | { 217 | "cell_type": "code", 218 | "execution_count": 10, 219 | "metadata": {}, 220 | "outputs": [], 221 | "source": [ 222 | "train_accuracy = accuracy(classifier, x_train_features, y_train)\n", 223 | "test_accuracy = accuracy(classifier, x_test_features, y_test)" 224 | ] 225 | }, 226 | { 227 | "cell_type": "code", 228 | "execution_count": 11, 229 | "metadata": {}, 230 | "outputs": [ 231 | { 232 | "name": "stdout", 233 | "output_type": "stream", 234 | "text": [ 235 | "Train accuracy: 0.923513807645\n", 236 | "Test accuracy: 0.920798189673\n" 237 | ] 238 | } 239 | ], 240 | "source": [ 241 | "print(\"Train accuracy:\", train_accuracy)\n", 242 | "print(\"Test accuracy:\", test_accuracy)" 243 | ] 244 | }, 245 | { 246 | "cell_type": "markdown", 247 | "metadata": {}, 248 | "source": [ 249 | "# Fine-tune using PrivateEncodedTensor" 250 | ] 251 | }, 252 | { 253 | "cell_type": "code", 254 | "execution_count": null, 255 | "metadata": {}, 256 | "outputs": [], 257 | "source": [ 258 | "classifier.initialize()\n", 259 | "\n", 260 | "start = datetime.now()\n", 261 | "classifier.fit(\n", 262 | " DataLoader(x_train_features, wrapper=PrivateEncodedTensor), \n", 263 | " DataLoader(y_train, wrapper=PrivateEncodedTensor),\n", 264 | " loss=CrossEntropy(), \n", 265 | " epochs=3,\n", 266 | " learning_rate=.01,\n", 267 | " verbose=1\n", 268 | ")\n", 269 | "stop = datetime.now()\n", 270 | "\n", 271 | "print(\"Elapsed:\", stop - start)" 272 | ] 273 | }, 274 | { 275 | "cell_type": "markdown", 276 | "metadata": {}, 277 | "source": [ 278 | "## Save encrypted weights for future prediction" 279 | ] 280 | }, 281 | { 282 | "cell_type": "code", 283 | "execution_count": 17, 284 | "metadata": {}, 285 | "outputs": [], 286 | "source": [ 287 | "np.save('layer0_weights_0.npy', classifier.layers[0].weights.shares0)\n", 288 | "np.save('layer0_weights_1.npy', classifier.layers[0].weights.shares1)\n", 289 | "np.save('layer0_bias_0.npy', classifier.layers[0].bias.shares0)\n", 290 | "np.save('layer0_bias_1.npy', classifier.layers[0].bias.shares1)\n", 291 | "\n", 292 | "np.save('layer2_weights_0.npy', classifier.layers[2].weights.shares0)\n", 293 | "np.save('layer2_weights_1.npy', classifier.layers[2].weights.shares1)\n", 294 | "np.save('layer2_bias_0.npy', classifier.layers[2].bias.shares0)\n", 295 | "np.save('layer2_bias_1.npy', classifier.layers[2].bias.shares1)" 296 | ] 297 | }, 298 | { 299 | "cell_type": "code", 300 | "execution_count": 18, 301 | "metadata": {}, 302 | "outputs": [], 303 | "source": [ 304 | "train_accuracy = accuracy(classifier, x_train_features, y_train)\n", 305 | "test_accuracy = accuracy(classifier, x_test_features, y_test)" 306 | ] 307 | }, 308 | { 309 | "cell_type": "code", 310 | "execution_count": 20, 311 | "metadata": {}, 312 | "outputs": [ 313 | { 314 | "name": "stdout", 315 | "output_type": "stream", 316 | "text": [ 317 | "Train accuracy: 0.923751870494\n", 318 | "Test accuracy: 0.921209627649\n" 319 | ] 320 | } 321 | ], 322 | "source": [ 323 | "print(\"Train accuracy:\", train_accuracy)\n", 324 | "print(\"Test accuracy:\", test_accuracy)" 325 | ] 326 | }, 327 | { 328 | "cell_type": "code", 329 | "execution_count": null, 330 | "metadata": {}, 331 | "outputs": [], 332 | "source": [] 333 | } 334 | ], 335 | "metadata": { 336 | "kernelspec": { 337 | "display_name": "Python 3", 338 | "language": "python", 339 | "name": "python3" 340 | }, 341 | "language_info": { 342 | "codemirror_mode": { 343 | "name": "ipython", 344 | "version": 3 345 | }, 346 | "file_extension": ".py", 347 | "mimetype": "text/x-python", 348 | "name": "python", 349 | "nbconvert_exporter": "python", 350 | "pygments_lexer": "ipython3", 351 | "version": "3.5.3" 352 | } 353 | }, 354 | "nbformat": 4, 355 | "nbformat_minor": 2 356 | } 357 | -------------------------------------------------------------------------------- /image-analysis/Pre-training.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stderr", 10 | "output_type": "stream", 11 | "text": [ 12 | "Using TensorFlow backend.\n" 13 | ] 14 | } 15 | ], 16 | "source": [ 17 | "import numpy as np\n", 18 | "import keras\n", 19 | "\n", 20 | "from dataset import load_mnist" 21 | ] 22 | }, 23 | { 24 | "cell_type": "markdown", 25 | "metadata": {}, 26 | "source": [ 27 | "# Model in Keras" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": 2, 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "feature_layers = [\n", 37 | " keras.layers.Conv2D(32, (3, 3), padding='same', input_shape=(28, 28, 1)),\n", 38 | " keras.layers.Activation('sigmoid'),\n", 39 | " keras.layers.Conv2D(32, (3, 3), padding='same'),\n", 40 | " keras.layers.Activation('sigmoid'),\n", 41 | " keras.layers.AveragePooling2D(pool_size=(2,2)),\n", 42 | " keras.layers.Dropout(.25),\n", 43 | " keras.layers.Flatten()\n", 44 | "]\n", 45 | "\n", 46 | "classification_layers = [\n", 47 | " keras.layers.Dense(128),\n", 48 | " keras.layers.Activation('sigmoid'),\n", 49 | " keras.layers.Dropout(.50),\n", 50 | " keras.layers.Dense(5),\n", 51 | " keras.layers.Activation('softmax')\n", 52 | "]\n", 53 | "\n", 54 | "model = keras.models.Sequential(feature_layers + classification_layers)" 55 | ] 56 | }, 57 | { 58 | "cell_type": "markdown", 59 | "metadata": {}, 60 | "source": [ 61 | "# Pre-train on public dataset" 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": 3, 67 | "metadata": {}, 68 | "outputs": [], 69 | "source": [ 70 | "public_dataset, _ = load_mnist()\n", 71 | "\n", 72 | "(x_train, y_train), (x_test, y_test) = public_dataset" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": 4, 78 | "metadata": {}, 79 | "outputs": [ 80 | { 81 | "name": "stdout", 82 | "output_type": "stream", 83 | "text": [ 84 | "Train on 30596 samples, validate on 5139 samples\n", 85 | "Epoch 1/5\n", 86 | "30596/30596 [==============================] - 108s - loss: 0.4806 - acc: 0.8187 - val_loss: 0.0700 - val_acc: 0.9792\n", 87 | "Epoch 2/5\n", 88 | "30596/30596 [==============================] - 97s - loss: 0.1081 - acc: 0.9691 - val_loss: 0.0516 - val_acc: 0.9848\n", 89 | "Epoch 3/5\n", 90 | "30596/30596 [==============================] - 92s - loss: 0.0867 - acc: 0.9744 - val_loss: 0.0430 - val_acc: 0.9866\n", 91 | "Epoch 4/5\n", 92 | "30596/30596 [==============================] - 104s - loss: 0.0727 - acc: 0.9786 - val_loss: 0.0337 - val_acc: 0.9885\n", 93 | "Epoch 5/5\n", 94 | "30596/30596 [==============================] - 108s - loss: 0.0662 - acc: 0.9798 - val_loss: 0.0335 - val_acc: 0.9885\n" 95 | ] 96 | }, 97 | { 98 | "data": { 99 | "text/plain": [ 100 | "" 101 | ] 102 | }, 103 | "execution_count": 4, 104 | "metadata": {}, 105 | "output_type": "execute_result" 106 | } 107 | ], 108 | "source": [ 109 | "model.compile(\n", 110 | " loss='categorical_crossentropy',\n", 111 | " optimizer='adam',\n", 112 | " metrics=['accuracy'])\n", 113 | "\n", 114 | "model.fit(\n", 115 | " x_train, y_train,\n", 116 | " epochs=5,\n", 117 | " batch_size=32,\n", 118 | " verbose=1,\n", 119 | " validation_data=(x_test, y_test))" 120 | ] 121 | }, 122 | { 123 | "cell_type": "markdown", 124 | "metadata": {}, 125 | "source": [ 126 | "# Save resulting model for use doing feature extraction" 127 | ] 128 | }, 129 | { 130 | "cell_type": "code", 131 | "execution_count": 5, 132 | "metadata": {}, 133 | "outputs": [], 134 | "source": [ 135 | "model.save('pre-trained.h5')" 136 | ] 137 | }, 138 | { 139 | "cell_type": "code", 140 | "execution_count": null, 141 | "metadata": {}, 142 | "outputs": [], 143 | "source": [] 144 | } 145 | ], 146 | "metadata": { 147 | "kernelspec": { 148 | "display_name": "Python 3", 149 | "language": "python", 150 | "name": "python3" 151 | }, 152 | "language_info": { 153 | "codemirror_mode": { 154 | "name": "ipython", 155 | "version": 3 156 | }, 157 | "file_extension": ".py", 158 | "mimetype": "text/x-python", 159 | "name": "python", 160 | "nbconvert_exporter": "python", 161 | "pygments_lexer": "ipython3", 162 | "version": "3.6.2" 163 | } 164 | }, 165 | "nbformat": 4, 166 | "nbformat_minor": 2 167 | } 168 | -------------------------------------------------------------------------------- /image-analysis/Prediction.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import numpy as np\n", 10 | "from datetime import datetime" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 2, 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "from pond.tensor import NativeTensor, PrivateEncodedTensor, PublicEncodedTensor\n", 20 | "from pond.nn import Dense, Sigmoid, Reveal, Diff, Softmax, CrossEntropy, Sequential, DataLoader" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": 3, 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [ 29 | "classifier = Sequential([\n", 30 | " Dense(128, 6272),\n", 31 | " Sigmoid(),\n", 32 | " # Dropout(.5),\n", 33 | " Dense(5, 128),\n", 34 | " Reveal(),\n", 35 | " Softmax()\n", 36 | "])" 37 | ] 38 | }, 39 | { 40 | "cell_type": "markdown", 41 | "metadata": {}, 42 | "source": [ 43 | "# Load sample data" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": 4, 49 | "metadata": {}, 50 | "outputs": [], 51 | "source": [ 52 | "def predict(classifier, wrapper, sample_index):\n", 53 | " x = np.load('x_test_features.npy')[sample_index].reshape(1, -1)\n", 54 | " y = np.argmax(np.load('y_test.npy')[sample_index])\n", 55 | "\n", 56 | " likelihoods = classifier.predict(wrapper(x))\n", 57 | " y_predicted = np.argmax(likelihoods.unwrap())\n", 58 | "\n", 59 | " return y, y_predicted" 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "metadata": {}, 65 | "source": [ 66 | "# Perform prediction using unencrypted weights" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": 5, 72 | "metadata": {}, 73 | "outputs": [], 74 | "source": [ 75 | "classifier.layers[0].weights = PublicEncodedTensor.from_elements(np.load('layer0_weights.npy'))\n", 76 | "classifier.layers[0].bias = PublicEncodedTensor.from_elements(np.load('layer0_bias.npy'))\n", 77 | "\n", 78 | "classifier.layers[2].weights = PublicEncodedTensor.from_elements(np.load('layer2_weights.npy'))\n", 79 | "classifier.layers[2].bias = PublicEncodedTensor.from_elements(np.load('layer2_bias.npy'))" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": 6, 85 | "metadata": {}, 86 | "outputs": [ 87 | { 88 | "name": "stdout", 89 | "output_type": "stream", 90 | "text": [ 91 | "2 2\n", 92 | "4 4\n", 93 | "0 1\n", 94 | "4 4\n", 95 | "1 1\n", 96 | "4 4\n", 97 | "0 0\n", 98 | "4 4\n", 99 | "2 2\n", 100 | "4 4\n" 101 | ] 102 | } 103 | ], 104 | "source": [ 105 | "for sample in range(10):\n", 106 | " y_correct, y_predicted = predict(classifier, PublicEncodedTensor, sample)\n", 107 | " print(y_correct, y_predicted)" 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "metadata": {}, 113 | "source": [ 114 | "# Perform prediction using encrypted weights" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": 7, 120 | "metadata": {}, 121 | "outputs": [], 122 | "source": [ 123 | "classifier.layers[0].weights = PrivateEncodedTensor.from_shares(np.load('layer0_weights_0.npy'), np.load('layer0_weights_1.npy'))\n", 124 | "classifier.layers[0].bias = PrivateEncodedTensor.from_shares(np.load('layer0_bias_0.npy'), np.load('layer0_bias_1.npy'))\n", 125 | "\n", 126 | "classifier.layers[2].weights = PrivateEncodedTensor.from_shares(np.load('layer2_weights_0.npy'), np.load('layer2_weights_1.npy'))\n", 127 | "classifier.layers[2].bias = PrivateEncodedTensor.from_shares(np.load('layer2_bias_0.npy'), np.load('layer2_bias_1.npy'))" 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": 8, 133 | "metadata": {}, 134 | "outputs": [ 135 | { 136 | "name": "stdout", 137 | "output_type": "stream", 138 | "text": [ 139 | "2 2\n", 140 | "4 4\n", 141 | "0 1\n", 142 | "4 4\n", 143 | "1 1\n", 144 | "4 4\n", 145 | "0 0\n", 146 | "4 4\n", 147 | "2 2\n", 148 | "4 4\n" 149 | ] 150 | } 151 | ], 152 | "source": [ 153 | "for sample in range(10):\n", 154 | " y_correct, y_predicted = predict(classifier, PrivateEncodedTensor, sample)\n", 155 | " print(y_correct, y_predicted)" 156 | ] 157 | }, 158 | { 159 | "cell_type": "code", 160 | "execution_count": null, 161 | "metadata": {}, 162 | "outputs": [], 163 | "source": [] 164 | } 165 | ], 166 | "metadata": { 167 | "kernelspec": { 168 | "display_name": "Python 3", 169 | "language": "python", 170 | "name": "python3" 171 | }, 172 | "language_info": { 173 | "codemirror_mode": { 174 | "name": "ipython", 175 | "version": 3 176 | }, 177 | "file_extension": ".py", 178 | "mimetype": "text/x-python", 179 | "name": "python", 180 | "nbconvert_exporter": "python", 181 | "pygments_lexer": "ipython3", 182 | "version": "3.5.3" 183 | } 184 | }, 185 | "nbformat": 4, 186 | "nbformat_minor": 2 187 | } 188 | -------------------------------------------------------------------------------- /image-analysis/Private Image Analysis.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from pond.tensor import NativeTensor, PrivateEncodedTensor, PublicEncodedTensor\n", 10 | "from pond.nn import Dense, Sigmoid, Reveal, Diff, Softmax, CrossEntropy, Sequential, DataLoader" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 2, 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "import numpy as np\n", 20 | "from datetime import datetime" 21 | ] 22 | }, 23 | { 24 | "cell_type": "markdown", 25 | "metadata": {}, 26 | "source": [ 27 | "# Feature extraction" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": null, 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "import keras\n", 37 | "from keras.utils import to_categorical" 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": null, 43 | "metadata": {}, 44 | "outputs": [], 45 | "source": [ 46 | "def preprocess_data(dataset):\n", 47 | " \n", 48 | " (x_train, y_train), (x_test, y_test) = dataset\n", 49 | " \n", 50 | " # NOTE: this is the shape used by Tensorflow; other backends may differ\n", 51 | " x_train = x_train.reshape(x_train.shape[0], 28, 28, 1)\n", 52 | " x_test = x_test.reshape(x_test.shape[0], 28, 28, 1)\n", 53 | " \n", 54 | " x_train = x_train.astype('float32')\n", 55 | " x_test = x_test.astype('float32')\n", 56 | " x_train /= 255\n", 57 | " x_test /= 255\n", 58 | "\n", 59 | " y_train = to_categorical(y_train, 5)\n", 60 | " y_test = to_categorical(y_test, 5)\n", 61 | " \n", 62 | " return (x_train, y_train), (x_test, y_test)\n", 63 | "\n", 64 | "def load_data():\n", 65 | " \n", 66 | " (x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()\n", 67 | "\n", 68 | " x_train_public = x_train[y_train < 5]\n", 69 | " y_train_public = y_train[y_train < 5]\n", 70 | " x_test_public = x_test[y_test < 5]\n", 71 | " y_test_public = y_test[y_test < 5]\n", 72 | " public_dataset = (x_train_public, y_train_public), (x_test_public, y_test_public)\n", 73 | "\n", 74 | " x_train_private = x_train[y_train >= 5]\n", 75 | " y_train_private = y_train[y_train >= 5] - 5\n", 76 | " x_test_private = x_test[y_test >= 5]\n", 77 | " y_test_private = y_test[y_test >= 5] - 5\n", 78 | " private_dataset = (x_train_private, y_train_private), (x_test_private, y_test_private)\n", 79 | " \n", 80 | " return preprocess_data(public_dataset), preprocess_data(private_dataset)" 81 | ] 82 | }, 83 | { 84 | "cell_type": "markdown", 85 | "metadata": {}, 86 | "source": [ 87 | "## Pre-train on public data" 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": null, 93 | "metadata": {}, 94 | "outputs": [], 95 | "source": [ 96 | "public_dataset, private_dataset = load_data()\n", 97 | "\n", 98 | "feature_layers = [\n", 99 | " keras.layers.Conv2D(32, (3, 3), padding='same', input_shape=(28, 28, 1)),\n", 100 | " keras.layers.Activation('sigmoid'),\n", 101 | " keras.layers.Conv2D(32, (3, 3), padding='same'),\n", 102 | " keras.layers.Activation('sigmoid'),\n", 103 | " keras.layers.AveragePooling2D(pool_size=(2,2)),\n", 104 | " keras.layers.Dropout(.25),\n", 105 | " keras.layers.Flatten()\n", 106 | "]\n", 107 | "\n", 108 | "classification_layers = [\n", 109 | " keras.layers.Dense(128),\n", 110 | " keras.layers.Activation('sigmoid'),\n", 111 | " keras.layers.Dropout(.50),\n", 112 | " keras.layers.Dense(5),\n", 113 | " keras.layers.Activation('softmax')\n", 114 | "]\n", 115 | "\n", 116 | "model = keras.models.Sequential(feature_layers + classification_layers)\n", 117 | "\n", 118 | "model.compile(\n", 119 | " loss='categorical_crossentropy',\n", 120 | " optimizer='adam',\n", 121 | " metrics=['accuracy'])\n", 122 | "\n", 123 | "(x_train, y_train), (x_test, y_test) = public_dataset\n", 124 | "\n", 125 | "model.fit(\n", 126 | " x_train, y_train,\n", 127 | " epochs=5,\n", 128 | " batch_size=32,\n", 129 | " verbose=1,\n", 130 | " validation_data=(x_test, y_test))" 131 | ] 132 | }, 133 | { 134 | "cell_type": "markdown", 135 | "metadata": {}, 136 | "source": [ 137 | "## Extract features from private data (unencrypted for now)" 138 | ] 139 | }, 140 | { 141 | "cell_type": "code", 142 | "execution_count": null, 143 | "metadata": {}, 144 | "outputs": [], 145 | "source": [ 146 | "model.summary()" 147 | ] 148 | }, 149 | { 150 | "cell_type": "code", 151 | "execution_count": null, 152 | "metadata": {}, 153 | "outputs": [], 154 | "source": [ 155 | "flatten_layer = model.get_layer(index=7)\n", 156 | "assert flatten_layer.name.startswith('flatten_')\n", 157 | "\n", 158 | "extractor = keras.models.Model(\n", 159 | " inputs=model.input, \n", 160 | " outputs=flatten_layer.output\n", 161 | ")" 162 | ] 163 | }, 164 | { 165 | "cell_type": "code", 166 | "execution_count": null, 167 | "metadata": {}, 168 | "outputs": [], 169 | "source": [ 170 | "(x_train_images, y_train), (x_test_images, y_test) = private_dataset\n", 171 | "\n", 172 | "x_train_features = extractor.predict(x_train_images)\n", 173 | "x_test_features = extractor.predict(x_test_images)" 174 | ] 175 | }, 176 | { 177 | "cell_type": "markdown", 178 | "metadata": {}, 179 | "source": [ 180 | "## Save extracted features" 181 | ] 182 | }, 183 | { 184 | "cell_type": "code", 185 | "execution_count": null, 186 | "metadata": {}, 187 | "outputs": [], 188 | "source": [ 189 | "np.save('x_train_features.npy', x_train_features)\n", 190 | "np.save('y_train.npy', y_train)\n", 191 | "\n", 192 | "np.save('x_test_features.npy', x_test_features)\n", 193 | "np.save('y_test.npy', y_test)" 194 | ] 195 | }, 196 | { 197 | "cell_type": "markdown", 198 | "metadata": {}, 199 | "source": [ 200 | "## Load extracted features" 201 | ] 202 | }, 203 | { 204 | "cell_type": "code", 205 | "execution_count": 3, 206 | "metadata": {}, 207 | "outputs": [ 208 | { 209 | "name": "stdout", 210 | "output_type": "stream", 211 | "text": [ 212 | "(29404, 6272) (29404, 5) (4861, 6272) (4861, 5)\n" 213 | ] 214 | } 215 | ], 216 | "source": [ 217 | "x_train_features = np.load('x_train_features.npy')\n", 218 | "y_train = np.load('y_train.npy')\n", 219 | "\n", 220 | "x_test_features = np.load('x_test_features.npy')\n", 221 | "y_test = np.load('y_test.npy')\n", 222 | "\n", 223 | "print(x_train_features.shape, y_train.shape, x_test_features.shape, y_test.shape)" 224 | ] 225 | }, 226 | { 227 | "cell_type": "markdown", 228 | "metadata": {}, 229 | "source": [ 230 | "# Fine-tune" 231 | ] 232 | }, 233 | { 234 | "cell_type": "code", 235 | "execution_count": 4, 236 | "metadata": {}, 237 | "outputs": [], 238 | "source": [ 239 | "classifier = Sequential([\n", 240 | " Dense(128, 6272),\n", 241 | " Sigmoid(),\n", 242 | " # Dropout(.5),\n", 243 | " Dense(5, 128),\n", 244 | " Reveal(),\n", 245 | " Softmax()\n", 246 | "])" 247 | ] 248 | }, 249 | { 250 | "cell_type": "code", 251 | "execution_count": 5, 252 | "metadata": {}, 253 | "outputs": [], 254 | "source": [ 255 | "def accuracy(classifier, x, y, verbose=0, wrapper=NativeTensor):\n", 256 | " predicted_classes = classifier \\\n", 257 | " .predict(DataLoader(x, wrapper), verbose=verbose).reveal() \\\n", 258 | " .argmax(axis=1)\n", 259 | " \n", 260 | " correct_classes = NativeTensor(y) \\\n", 261 | " .argmax(axis=1)\n", 262 | " \n", 263 | " matches = predicted_classes.unwrap() == correct_classes.unwrap()\n", 264 | " return sum(matches)/len(matches)" 265 | ] 266 | }, 267 | { 268 | "cell_type": "markdown", 269 | "metadata": {}, 270 | "source": [ 271 | "## ... using NativeTensor" 272 | ] 273 | }, 274 | { 275 | "cell_type": "code", 276 | "execution_count": 6, 277 | "metadata": {}, 278 | "outputs": [ 279 | { 280 | "name": "stdout", 281 | "output_type": "stream", 282 | "text": [ 283 | "2017-12-29 11:02:50.827373 Epoch 0\n", 284 | "2017-12-29 11:03:07.306861 Epoch 1\n", 285 | "2017-12-29 11:03:22.950060 Epoch 2\n", 286 | "Elapsed: 0:00:48.221433\n" 287 | ] 288 | } 289 | ], 290 | "source": [ 291 | "classifier.initialize()\n", 292 | "\n", 293 | "start = datetime.now()\n", 294 | "classifier.fit(\n", 295 | " DataLoader(x_train_features, wrapper=NativeTensor), \n", 296 | " DataLoader(y_train, wrapper=NativeTensor), \n", 297 | " loss=CrossEntropy(), \n", 298 | " epochs=3,\n", 299 | " verbose=1\n", 300 | ")\n", 301 | "stop = datetime.now()\n", 302 | "\n", 303 | "print(\"Elapsed:\", stop - start)" 304 | ] 305 | }, 306 | { 307 | "cell_type": "code", 308 | "execution_count": null, 309 | "metadata": {}, 310 | "outputs": [ 311 | { 312 | "name": "stdout", 313 | "output_type": "stream", 314 | "text": [ 315 | "Train accuracy: 0.90671337233\n", 316 | "Test accuracy: 0.908660769389\n" 317 | ] 318 | } 319 | ], 320 | "source": [ 321 | "print(\"Train accuracy:\", accuracy(classifier, x_train_features, y_train))\n", 322 | "print(\"Test accuracy:\", accuracy(classifier, x_test_features, y_test))" 323 | ] 324 | }, 325 | { 326 | "cell_type": "markdown", 327 | "metadata": {}, 328 | "source": [ 329 | "## ... using PublicEncodedTensor" 330 | ] 331 | }, 332 | { 333 | "cell_type": "code", 334 | "execution_count": null, 335 | "metadata": {}, 336 | "outputs": [], 337 | "source": [ 338 | "classifier.initialize()\n", 339 | "\n", 340 | "start = datetime.now()\n", 341 | "classifier.fit(\n", 342 | " DataLoader(x_train_features, wrapper=PublicEncodedTensor), \n", 343 | " DataLoader(y_train, wrapper=PublicEncodedTensor),\n", 344 | " loss=CrossEntropy(), \n", 345 | " epochs=3,\n", 346 | " verbose=2\n", 347 | ")\n", 348 | "stop = datetime.now()\n", 349 | "\n", 350 | "print(\"Elapsed:\", stop - start)" 351 | ] 352 | }, 353 | { 354 | "cell_type": "code", 355 | "execution_count": null, 356 | "metadata": {}, 357 | "outputs": [], 358 | "source": [ 359 | "print(\"Train accuracy:\", accuracy(classifier, x_train_features, y_train, verbose=2))\n", 360 | "print(\"Test accuracy:\", accuracy(classifier, x_test_features, y_test, verbose=2))" 361 | ] 362 | }, 363 | { 364 | "cell_type": "markdown", 365 | "metadata": {}, 366 | "source": [ 367 | "## ... using PrivateEncodedTensor" 368 | ] 369 | }, 370 | { 371 | "cell_type": "code", 372 | "execution_count": null, 373 | "metadata": {}, 374 | "outputs": [ 375 | { 376 | "name": "stdout", 377 | "output_type": "stream", 378 | "text": [ 379 | "2017-12-29 11:03:42.926628 Epoch 0\n", 380 | "2017-12-29 11:03:43.243759 Batch 0\n", 381 | "2017-12-29 11:04:54.253005 Batch 1\n", 382 | "2017-12-29 11:06:37.272832 Batch 2\n", 383 | "2017-12-29 11:08:20.516759 Batch 3\n", 384 | "2017-12-29 11:09:56.260065 Batch 4\n", 385 | "2017-12-29 11:11:30.663451 Batch 5\n", 386 | "2017-12-29 11:13:04.473740 Batch 6\n", 387 | "2017-12-29 11:14:39.077599 Batch 7\n" 388 | ] 389 | } 390 | ], 391 | "source": [ 392 | "classifier.initialize()\n", 393 | "\n", 394 | "start = datetime.now()\n", 395 | "classifier.fit(\n", 396 | " DataLoader(x_train_features, wrapper=PrivateEncodedTensor), \n", 397 | " DataLoader(y_train, wrapper=PrivateEncodedTensor),\n", 398 | " loss=CrossEntropy(), \n", 399 | " epochs=3,\n", 400 | " verbose=2\n", 401 | ")\n", 402 | "stop = datetime.now()\n", 403 | "\n", 404 | "print(\"Elapsed:\", stop - start)" 405 | ] 406 | }, 407 | { 408 | "cell_type": "code", 409 | "execution_count": null, 410 | "metadata": {}, 411 | "outputs": [ 412 | { 413 | "name": "stdout", 414 | "output_type": "stream", 415 | "text": [ 416 | "2018-01-02 14:13:39.032988 Batch 0\n" 417 | ] 418 | } 419 | ], 420 | "source": [ 421 | "train_accuracy = accuracy(classifier, x_train_features, y_train, verbose=2)\n", 422 | "test_accuracy = accuracy(classifier, x_test_features, y_test, verbose=2)" 423 | ] 424 | }, 425 | { 426 | "cell_type": "code", 427 | "execution_count": 13, 428 | "metadata": {}, 429 | "outputs": [ 430 | { 431 | "name": "stdout", 432 | "output_type": "stream", 433 | "text": [ 434 | "Train accuracy: 0.906611345395\n", 435 | "Test accuracy: 0.908249331413\n" 436 | ] 437 | } 438 | ], 439 | "source": [ 440 | "print(\"Train accuracy:\", train_accuracy)\n", 441 | "print(\"Test accuracy:\", test_accuracy)" 442 | ] 443 | }, 444 | { 445 | "cell_type": "code", 446 | "execution_count": 19, 447 | "metadata": {}, 448 | "outputs": [], 449 | "source": [ 450 | "np.save('layer0_weights_0.npy', classifier.layers[0].weights.shares0)\n", 451 | "np.save('layer0_weights_1.npy', classifier.layers[0].weights.shares1)\n", 452 | "np.save('layer0_bias_0.npy', classifier.layers[0].bias.shares0)\n", 453 | "np.save('layer0_bias_1.npy', classifier.layers[0].bias.shares1)\n", 454 | "\n", 455 | "np.save('layer2_weights_0.npy', classifier.layers[2].weights.shares0)\n", 456 | "np.save('layer2_weights_1.npy', classifier.layers[2].weights.shares1)\n", 457 | "np.save('layer2_bias_0.npy', classifier.layers[2].bias.shares0)\n", 458 | "np.save('layer2_bias_1.npy', classifier.layers[2].bias.shares1)" 459 | ] 460 | }, 461 | { 462 | "cell_type": "code", 463 | "execution_count": null, 464 | "metadata": {}, 465 | "outputs": [], 466 | "source": [] 467 | } 468 | ], 469 | "metadata": { 470 | "kernelspec": { 471 | "display_name": "Python 3", 472 | "language": "python", 473 | "name": "python3" 474 | }, 475 | "language_info": { 476 | "codemirror_mode": { 477 | "name": "ipython", 478 | "version": 3 479 | }, 480 | "file_extension": ".py", 481 | "mimetype": "text/x-python", 482 | "name": "python", 483 | "nbconvert_exporter": "python", 484 | "pygments_lexer": "ipython3", 485 | "version": "3.5.3" 486 | } 487 | }, 488 | "nbformat": 4, 489 | "nbformat_minor": 2 490 | } 491 | -------------------------------------------------------------------------------- /image-analysis/dataset.py: -------------------------------------------------------------------------------- 1 | from keras.utils import to_categorical 2 | from keras.datasets import mnist 3 | 4 | 5 | def preprocess_data(dataset): 6 | 7 | (x_train, y_train), (x_test, y_test) = dataset 8 | 9 | # NOTE: this is the shape used by Tensorflow; other backends may differ 10 | x_train = x_train.reshape(x_train.shape[0], 28, 28, 1) 11 | x_test = x_test.reshape(x_test.shape[0], 28, 28, 1) 12 | 13 | x_train = x_train.astype('float32') 14 | x_test = x_test.astype('float32') 15 | x_train /= 255 16 | x_test /= 255 17 | 18 | y_train = to_categorical(y_train, 5) 19 | y_test = to_categorical(y_test, 5) 20 | 21 | return (x_train, y_train), (x_test, y_test) 22 | 23 | 24 | def load_mnist(): 25 | 26 | (x_train, y_train), (x_test, y_test) = mnist.load_data() 27 | 28 | x_train_public = x_train[y_train < 5] 29 | y_train_public = y_train[y_train < 5] 30 | x_test_public = x_test[y_test < 5] 31 | y_test_public = y_test[y_test < 5] 32 | public_dataset = (x_train_public, y_train_public), (x_test_public, y_test_public) 33 | 34 | x_train_private = x_train[y_train >= 5] 35 | y_train_private = y_train[y_train >= 5] - 5 36 | x_test_private = x_test[y_test >= 5] 37 | y_test_private = y_test[y_test >= 5] - 5 38 | private_dataset = (x_train_private, y_train_private), (x_test_private, y_test_private) 39 | 40 | return preprocess_data(public_dataset), preprocess_data(private_dataset) -------------------------------------------------------------------------------- /image-analysis/layer0_bias.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortendahl/privateml/d993ba37ed77a52cecba0a661e45673f841d5755/image-analysis/layer0_bias.npy -------------------------------------------------------------------------------- /image-analysis/layer0_bias_0.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortendahl/privateml/d993ba37ed77a52cecba0a661e45673f841d5755/image-analysis/layer0_bias_0.npy -------------------------------------------------------------------------------- /image-analysis/layer0_bias_1.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortendahl/privateml/d993ba37ed77a52cecba0a661e45673f841d5755/image-analysis/layer0_bias_1.npy -------------------------------------------------------------------------------- /image-analysis/layer0_weights.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortendahl/privateml/d993ba37ed77a52cecba0a661e45673f841d5755/image-analysis/layer0_weights.npy -------------------------------------------------------------------------------- /image-analysis/layer0_weights_0.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortendahl/privateml/d993ba37ed77a52cecba0a661e45673f841d5755/image-analysis/layer0_weights_0.npy -------------------------------------------------------------------------------- /image-analysis/layer0_weights_1.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortendahl/privateml/d993ba37ed77a52cecba0a661e45673f841d5755/image-analysis/layer0_weights_1.npy -------------------------------------------------------------------------------- /image-analysis/layer2_bias.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortendahl/privateml/d993ba37ed77a52cecba0a661e45673f841d5755/image-analysis/layer2_bias.npy -------------------------------------------------------------------------------- /image-analysis/layer2_bias_0.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortendahl/privateml/d993ba37ed77a52cecba0a661e45673f841d5755/image-analysis/layer2_bias_0.npy -------------------------------------------------------------------------------- /image-analysis/layer2_bias_1.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortendahl/privateml/d993ba37ed77a52cecba0a661e45673f841d5755/image-analysis/layer2_bias_1.npy -------------------------------------------------------------------------------- /image-analysis/layer2_weights.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortendahl/privateml/d993ba37ed77a52cecba0a661e45673f841d5755/image-analysis/layer2_weights.npy -------------------------------------------------------------------------------- /image-analysis/layer2_weights_0.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortendahl/privateml/d993ba37ed77a52cecba0a661e45673f841d5755/image-analysis/layer2_weights_0.npy -------------------------------------------------------------------------------- /image-analysis/layer2_weights_1.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortendahl/privateml/d993ba37ed77a52cecba0a661e45673f841d5755/image-analysis/layer2_weights_1.npy -------------------------------------------------------------------------------- /image-analysis/pond/nn.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from datetime import datetime 3 | from functools import reduce 4 | 5 | 6 | class Layer: 7 | pass 8 | 9 | 10 | class Dense(Layer): 11 | 12 | def __init__(self, num_nodes, num_features, initial_scale=.01): 13 | self.num_nodes = num_nodes 14 | self.num_features = num_features 15 | self.initial_scale = initial_scale 16 | self.weights = None 17 | self.bias = None 18 | 19 | def initialize(self): 20 | self.weights = np.random.randn(self.num_features, self.num_nodes) * self.initial_scale 21 | self.bias = np.zeros((1, self.num_nodes)) 22 | 23 | def forward(self, x): 24 | y = x.dot(self.weights) + self.bias 25 | # cache result for backward pass 26 | self.cache = x 27 | return y 28 | 29 | def backward(self, d_y, learning_rate): 30 | x = self.cache 31 | # compute gradients for internal parameters and update 32 | d_weights = x.transpose().dot(d_y) 33 | d_bias = d_y.sum(axis=0) 34 | self.weights = (d_weights * learning_rate).neg() + self.weights 35 | self.bias = (d_bias * learning_rate).neg() + self.bias 36 | # compute and return external gradient 37 | d_x = d_y.dot(self.weights.transpose()) 38 | return d_x 39 | 40 | 41 | class SigmoidExact(Layer): 42 | 43 | def __init__(self): 44 | self.cache = None 45 | 46 | def initialize(self): 47 | pass 48 | 49 | def forward(self, x): 50 | y = (x.neg().exp() + 1).inv() 51 | self.cache = y 52 | return y 53 | 54 | def backward(self, d_y, learning_rate): 55 | y = self.cache 56 | d_x = d_y * y * (y.neg() + 1) 57 | return d_x 58 | 59 | 60 | class Sigmoid(Layer): 61 | 62 | def __init__(self): 63 | self.cache = None 64 | 65 | def initialize(self): 66 | pass 67 | 68 | def forward(self, x): 69 | w0 = 0.5 70 | w1 = 0.2159198015 71 | w3 = -0.0082176259 72 | w5 = 0.0001825597 73 | w7 = -0.0000018848 74 | w9 = 0.0000000072 75 | 76 | x2 = x * x 77 | x3 = x2 * x 78 | x5 = x2 * x3 79 | x7 = x2 * x5 80 | x9 = x2 * x7 81 | y = x9*w9 + x7*w7 + x5*w5 + x3*w3 + x*w1 + w0 82 | 83 | self.cache = y 84 | return y 85 | 86 | def backward(self, d_y, learning_rate): 87 | y = self.cache 88 | d_x = d_y * y * (y.neg() + 1) 89 | return d_x 90 | 91 | 92 | class Softmax(Layer): 93 | 94 | def __init__(self): 95 | pass 96 | 97 | def initialize(self): 98 | pass 99 | 100 | def forward(self, x): 101 | likelihoods = x.exp() 102 | probs = likelihoods.div(likelihoods.sum(axis=1)) 103 | self.cache = probs 104 | return probs 105 | 106 | def backward(self, d_probs, learning_rate): 107 | # TODO does the split between Softmax and CrossEntropy make sense? 108 | probs = self.cache 109 | batch_size = probs.shape[0] 110 | d_scores = probs - d_probs 111 | d_scores = d_scores.div(batch_size) 112 | return d_scores 113 | 114 | 115 | class Dropout(Layer): 116 | # TODO 117 | pass 118 | 119 | 120 | class Flatten(Layer): 121 | # TODO 122 | pass 123 | 124 | 125 | class AveragePooling2D(Layer): 126 | # TODO 127 | pass 128 | 129 | 130 | class Convolution2D(Layer): 131 | # TODO 132 | pass 133 | 134 | 135 | class Reveal(Layer): 136 | 137 | def __init__(self): 138 | pass 139 | 140 | def initialize(self): 141 | pass 142 | 143 | def forward(self, x): 144 | return x.reveal() 145 | 146 | def backward(self, d_y, learning_rate): 147 | return d_y 148 | 149 | 150 | class Loss: 151 | pass 152 | 153 | 154 | class Diff(Loss): 155 | 156 | def derive(self, y_pred, y_train): 157 | return y_pred - y_train 158 | 159 | 160 | class CrossEntropy(Loss): 161 | 162 | def evaluate(self, probs_pred, probs_correct): 163 | batch_size = probs_pred.shape[0] 164 | losses = (probs_pred * probs_correct).log().neg() 165 | loss = losses.sum(axis=0).div(batch_size) 166 | return loss 167 | 168 | def derive(self, y_pred, y_correct): 169 | return y_correct 170 | 171 | 172 | class DataLoader: 173 | 174 | def __init__(self, data, wrapper=lambda x: x): 175 | self.data = data 176 | self.wrapper = wrapper 177 | 178 | def batches(self, batch_size=None): 179 | if batch_size is None: batch_size = data.shape[0] 180 | return ( 181 | self.wrapper(self.data[i:i+batch_size]) 182 | for i in range(0, self.data.shape[0], batch_size) 183 | ) 184 | 185 | 186 | class Model: 187 | pass 188 | 189 | 190 | class Sequential(Model): 191 | 192 | def __init__(self, layers): 193 | self.layers = layers 194 | 195 | def initialize(self): 196 | for layer in self.layers: 197 | layer.initialize() 198 | 199 | def forward(self, x): 200 | for layer in self.layers: 201 | x = layer.forward(x) 202 | return x 203 | 204 | def backward(self, d_y, learning_rate): 205 | for layer in reversed(self.layers): 206 | d_y = layer.backward(d_y, learning_rate) 207 | 208 | def fit(self, x_train, y_train, loss, batch_size=32, epochs=1000, learning_rate=.01, verbose=0): 209 | if not isinstance(x_train, DataLoader): x_train = DataLoader(x_train) 210 | if not isinstance(y_train, DataLoader): y_train = DataLoader(y_train) 211 | for epoch in range(epochs): 212 | if verbose >= 1: print(datetime.now(), "Epoch %s" % epoch) 213 | batches = zip(x_train.batches(batch_size), y_train.batches(batch_size)) 214 | for batch_index, (x_batch, y_batch) in enumerate(batches): 215 | if verbose >= 2: print(datetime.now(), "Batch %s" % batch_index) 216 | y_pred = self.forward(x_batch) 217 | d_y = loss.derive(y_pred, y_batch) 218 | self.backward(d_y, learning_rate) 219 | 220 | def predict(self, x, batch_size=32, verbose=0): 221 | if not isinstance(x, DataLoader): x = DataLoader(x) 222 | batches = [] 223 | for batch_index, x_batch in enumerate(x.batches(batch_size)): 224 | if verbose >= 2: print(datetime.now(), "Batch %s" % batch_index) 225 | y_batch = self.forward(x_batch) 226 | batches.append(y_batch) 227 | return reduce(lambda x, y: x.concatenate(y), batches) 228 | 229 | -------------------------------------------------------------------------------- /image-analysis/pre-trained.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortendahl/privateml/d993ba37ed77a52cecba0a661e45673f841d5755/image-analysis/pre-trained.h5 -------------------------------------------------------------------------------- /notebook_importer.py: -------------------------------------------------------------------------------- 1 | import io, os, sys, types 2 | import nbformat 3 | 4 | from IPython import get_ipython 5 | from IPython.core.interactiveshell import InteractiveShell 6 | 7 | class NotebookFinder(object): 8 | """Module finder that locates IPython Notebooks""" 9 | def __init__(self): 10 | self.loaders = {} 11 | 12 | def find_module(self, fullname, path=None): 13 | nb_path = find_notebook(fullname, path) 14 | if not nb_path: 15 | return 16 | 17 | key = path 18 | if path: 19 | # lists aren't hashable 20 | key = os.path.sep.join(path) 21 | 22 | if key not in self.loaders: 23 | self.loaders[key] = NotebookLoader(path) 24 | return self.loaders[key] 25 | sys.meta_path.append(NotebookFinder()) 26 | 27 | def find_notebook(fullname, path=None): 28 | """find a notebook, given its fully qualified name and an optional path 29 | This turns "foo.bar" into "foo/bar.ipynb" 30 | and tries turning "Foo_Bar" into "Foo Bar" if Foo_Bar 31 | does not exist. 32 | """ 33 | name = fullname.rsplit('.', 1)[-1] 34 | if not path: 35 | path = [''] 36 | for d in path: 37 | nb_path = os.path.join(d, name + ".ipynb") 38 | if os.path.isfile(nb_path): 39 | return nb_path 40 | # let import Notebook_Name find "Notebook Name.ipynb" 41 | nb_path = nb_path.replace("_", " ") 42 | if os.path.isfile(nb_path): 43 | return nb_path 44 | 45 | class NotebookLoader(object): 46 | """Module Loader for IPython Notebooks""" 47 | def __init__(self, path=None): 48 | self.shell = InteractiveShell.instance() 49 | self.path = path 50 | 51 | def load_module(self, fullname): 52 | """import a notebook as a module""" 53 | path = find_notebook(fullname, self.path) 54 | 55 | print ("importing notebook from %s" % path) 56 | 57 | # load the notebook object 58 | nb = nbformat.read(path, as_version=4) 59 | 60 | 61 | # create the module and add it to sys.modules 62 | # if name in sys.modules: 63 | # return sys.modules[name] 64 | mod = types.ModuleType(fullname) 65 | mod.__file__ = path 66 | mod.__loader__ = self 67 | mod.__dict__['get_ipython'] = get_ipython 68 | sys.modules[fullname] = mod 69 | 70 | # extra work to ensure that magics that would affect the user_ns 71 | # actually affect the notebook module's ns 72 | save_user_ns = self.shell.user_ns 73 | self.shell.user_ns = mod.__dict__ 74 | 75 | try: 76 | for cell in nb.cells: 77 | if cell.cell_type == 'code': 78 | # transform the input to executable Python 79 | code = self.shell.input_transformer_manager.transform_cell(cell.source) 80 | # run the code in themodule 81 | exec(code, mod.__dict__) 82 | finally: 83 | self.shell.user_ns = save_user_ns 84 | return mod -------------------------------------------------------------------------------- /paillier/Paillier.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import random" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 2, 17 | "metadata": { 18 | "collapsed": true 19 | }, 20 | "outputs": [], 21 | "source": [ 22 | "P = 137\n", 23 | "Q = 139\n", 24 | "\n", 25 | "N = P * Q\n", 26 | "N_order = (P-1) * (Q-1)" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": 3, 32 | "metadata": {}, 33 | "outputs": [ 34 | { 35 | "name": "stdout", 36 | "output_type": "stream", 37 | "text": [ 38 | "AdditiveElement(0)\n", 39 | "AdditiveElement(7)\n", 40 | "AdditiveElement(3)\n", 41 | "AdditiveElement(15)\n" 42 | ] 43 | } 44 | ], 45 | "source": [ 46 | "class AdditiveElement:\n", 47 | " \n", 48 | " def __init__(self, value):\n", 49 | " self._value = value % N\n", 50 | " \n", 51 | " def __repr__(self):\n", 52 | " return \"AdditiveElement(%d)\" % self._value\n", 53 | " \n", 54 | " def __eq__(self, other):\n", 55 | " return self._value == other._value\n", 56 | " \n", 57 | " def __add__(self, other):\n", 58 | " return AdditiveElement(\n", 59 | " (self._value + other._value) % N\n", 60 | " )\n", 61 | " \n", 62 | " def inv(self):\n", 63 | " return AdditiveElement(\n", 64 | " (self._value * -1) % N\n", 65 | " )\n", 66 | " \n", 67 | " def __sub__(self, other):\n", 68 | " return self + other.inv()\n", 69 | " \n", 70 | " def __mul__(self, constant):\n", 71 | " return AdditiveElement(\n", 72 | " (self._value * constant) % N\n", 73 | " )\n", 74 | " \n", 75 | " \n", 76 | "x = AdditiveElement(5)\n", 77 | "y = AdditiveElement(2)\n", 78 | "print(x + x.inv())\n", 79 | "print(x + y)\n", 80 | "print(x - y)\n", 81 | "print(x * 3)" 82 | ] 83 | }, 84 | { 85 | "cell_type": "code", 86 | "execution_count": 32, 87 | "metadata": {}, 88 | "outputs": [ 89 | { 90 | "name": "stdout", 91 | "output_type": "stream", 92 | "text": [ 93 | "MultiplicativeElement(1)\n", 94 | "MultiplicativeElement(10)\n", 95 | "MultiplicativeElement(9524)\n", 96 | "MultiplicativeElement(125)\n" 97 | ] 98 | } 99 | ], 100 | "source": [ 101 | "class MultiplicativeElement:\n", 102 | " \n", 103 | " def __init__(self, value):\n", 104 | " self._value = value % N\n", 105 | " \n", 106 | " def __repr__(self):\n", 107 | " return \"MultiplicativeElement(%d)\" % self._value\n", 108 | " \n", 109 | " def __eq__(self, other):\n", 110 | " return self._value == other._value\n", 111 | " \n", 112 | " def __mul__(self, other):\n", 113 | " return MultiplicativeElement(\n", 114 | " (self._value * other._value) % N\n", 115 | " )\n", 116 | " \n", 117 | " def inv(self):\n", 118 | " return MultiplicativeElement(\n", 119 | " pow(self._value, N_order-1, N)\n", 120 | " )\n", 121 | " \n", 122 | " def __truediv__(self, other):\n", 123 | " return self * other.inv()\n", 124 | " \n", 125 | " def __pow__(self, constant):\n", 126 | " return MultiplicativeElement(\n", 127 | " pow(self._value, constant, N)\n", 128 | " )\n", 129 | " \n", 130 | " \n", 131 | "x = MultiplicativeElement(5)\n", 132 | "y = MultiplicativeElement(2)\n", 133 | "print(x * x.inv())\n", 134 | "print(x * y)\n", 135 | "print(x / y)\n", 136 | "print(x ** 3)" 137 | ] 138 | }, 139 | { 140 | "cell_type": "code", 141 | "execution_count": 33, 142 | "metadata": { 143 | "collapsed": true 144 | }, 145 | "outputs": [], 146 | "source": [ 147 | "class IdealCiphertext:\n", 148 | " \n", 149 | " def __init__(self, message, randomness):\n", 150 | " self.__msg = message\n", 151 | " self.__rnd = randomness\n", 152 | " \n", 153 | " def __repr__(self):\n", 154 | " return \"IdealCiphertext(??, ??)\"\n", 155 | " \n", 156 | " def __eq__(self, other):\n", 157 | " return self.__msg == other.__msg and \\\n", 158 | " self.__rnd == other.__rnd\n", 159 | " \n", 160 | " def encrypt(message):\n", 161 | " randomness = random.randrange(N)\n", 162 | " return IdealCiphertext(\n", 163 | " AdditiveElement(message),\n", 164 | " MultiplicativeElement(randomness)\n", 165 | " )\n", 166 | " \n", 167 | " def decrypt(self):\n", 168 | " return self.__msg._value\n", 169 | " \n", 170 | " def add(self, other):\n", 171 | " return IdealCiphertext(\n", 172 | " self.__msg + other.__msg,\n", 173 | " self.__rnd * other.__rnd\n", 174 | " )\n", 175 | " \n", 176 | " def sub(self, other):\n", 177 | " other_neg = IdealCiphertext(\n", 178 | " other.__msg.inv(),\n", 179 | " other.__rnd.inv()\n", 180 | " )\n", 181 | " return self + other_neg\n", 182 | " \n", 183 | " def mul(self, k):\n", 184 | " return IdealCiphertext(\n", 185 | " self.__msg * k,\n", 186 | " self.__rnd ** k\n", 187 | " )\n", 188 | " \n", 189 | " def __add__(self, other):\n", 190 | " return IdealCiphertext.add(self, other)\n", 191 | "\n", 192 | " def __sub__(self, other):\n", 193 | " return IdealCiphertext.sub(self, other)\n", 194 | " \n", 195 | " def __mul__(self, k):\n", 196 | " return IdealCiphertext.mul(self, k)" 197 | ] 198 | }, 199 | { 200 | "cell_type": "code", 201 | "execution_count": 21, 202 | "metadata": { 203 | "collapsed": true 204 | }, 205 | "outputs": [], 206 | "source": [ 207 | "def encrypt(message):\n", 208 | " return Ciphertext.encrypt(message)" 209 | ] 210 | }, 211 | { 212 | "cell_type": "code", 213 | "execution_count": 22, 214 | "metadata": { 215 | "collapsed": true 216 | }, 217 | "outputs": [], 218 | "source": [ 219 | "def decrypt(ciphertext):\n", 220 | " return ciphertext.decrypt()" 221 | ] 222 | }, 223 | { 224 | "cell_type": "code", 225 | "execution_count": 23, 226 | "metadata": {}, 227 | "outputs": [ 228 | { 229 | "name": "stdout", 230 | "output_type": "stream", 231 | "text": [ 232 | "Ciphertext(??, ??)\n" 233 | ] 234 | } 235 | ], 236 | "source": [ 237 | "m = 5\n", 238 | "c = encrypt(m)\n", 239 | "\n", 240 | "print(c)\n", 241 | "assert decrypt(c) == m" 242 | ] 243 | }, 244 | { 245 | "cell_type": "code", 246 | "execution_count": 24, 247 | "metadata": {}, 248 | "outputs": [], 249 | "source": [ 250 | "c1 = encrypt(5)\n", 251 | "c2 = encrypt(5)\n", 252 | "\n", 253 | "assert c1 != c2" 254 | ] 255 | }, 256 | { 257 | "cell_type": "code", 258 | "execution_count": 25, 259 | "metadata": {}, 260 | "outputs": [], 261 | "source": [ 262 | "cx = encrypt(5)\n", 263 | "cy = encrypt(3)\n", 264 | "\n", 265 | "assert decrypt(cx + cy) == 8\n", 266 | "assert decrypt(cx - cy) == 2" 267 | ] 268 | }, 269 | { 270 | "cell_type": "code", 271 | "execution_count": 26, 272 | "metadata": {}, 273 | "outputs": [], 274 | "source": [ 275 | "cx = encrypt(5)\n", 276 | "k = 3\n", 277 | "\n", 278 | "assert decrypt(cx * k) == 15" 279 | ] 280 | }, 281 | { 282 | "cell_type": "code", 283 | "execution_count": null, 284 | "metadata": { 285 | "collapsed": true 286 | }, 287 | "outputs": [], 288 | "source": [] 289 | }, 290 | { 291 | "cell_type": "code", 292 | "execution_count": null, 293 | "metadata": { 294 | "collapsed": true 295 | }, 296 | "outputs": [], 297 | "source": [] 298 | } 299 | ], 300 | "metadata": { 301 | "kernelspec": { 302 | "display_name": "Python 3", 303 | "language": "python", 304 | "name": "python3" 305 | }, 306 | "language_info": { 307 | "codemirror_mode": { 308 | "name": "ipython", 309 | "version": 3 310 | }, 311 | "file_extension": ".py", 312 | "mimetype": "text/x-python", 313 | "name": "python", 314 | "nbconvert_exporter": "python", 315 | "pygments_lexer": "ipython3", 316 | "version": "3.6.2" 317 | } 318 | }, 319 | "nbformat": 4, 320 | "nbformat_minor": 2 321 | } 322 | -------------------------------------------------------------------------------- /paillier/benchmarks/.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target/ 3 | -------------------------------------------------------------------------------- /paillier/benchmarks/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "paillier" 3 | version = "0.1.0" 4 | authors = ["Morten Dahl "] 5 | 6 | [dependencies] 7 | rayon = "1.0" 8 | rust-gmp = "0.5" 9 | 10 | [dev-dependencies] 11 | -------------------------------------------------------------------------------- /secret-sharing/Fast Fourier Transform Walk-through.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "def horner_evaluate(coeffs, point):\n", 12 | " result = 0\n", 13 | " for coef in reversed(coeffs):\n", 14 | " result = (coef + point * result) % Q\n", 15 | " return result" 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": 2, 21 | "metadata": { 22 | "collapsed": true 23 | }, 24 | "outputs": [], 25 | "source": [ 26 | "Q = 433" 27 | ] 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "metadata": {}, 32 | "source": [ 33 | "## Polynomial A" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 3, 39 | "metadata": {}, 40 | "outputs": [ 41 | { 42 | "name": "stdout", 43 | "output_type": "stream", 44 | "text": [ 45 | "[1, 179, 432, 254]\n" 46 | ] 47 | } 48 | ], 49 | "source": [ 50 | "OMEGA4 = 179\n", 51 | "\n", 52 | "w = [ pow(OMEGA4, e, Q) for e in range(4) ]\n", 53 | "\n", 54 | "print(w)" 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": 4, 60 | "metadata": { 61 | "collapsed": true 62 | }, 63 | "outputs": [], 64 | "source": [ 65 | "A_coeffs = [ 1, 2, 3, 4 ]" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": 5, 71 | "metadata": { 72 | "collapsed": true 73 | }, 74 | "outputs": [], 75 | "source": [ 76 | "A = lambda x: horner_evaluate(A_coeffs, x)\n", 77 | "\n", 78 | "assert([ A(wi) for wi in w ]\n", 79 | " == [ 10, 73, 431, 356 ])" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": 6, 85 | "metadata": { 86 | "collapsed": true 87 | }, 88 | "outputs": [], 89 | "source": [ 90 | "# split A into B and C\n", 91 | "B_coeffs = A_coeffs[0::2] # == [ 1, 3, ]\n", 92 | "C_coeffs = A_coeffs[1::2] # == [ 2, 4 ]\n", 93 | "\n", 94 | "v = [ wi * wi % Q for wi in w ]\n", 95 | "\n", 96 | "# compute values for B and C at v points\n", 97 | "B = lambda x: horner_evaluate(B_coeffs, x)\n", 98 | "C = lambda x: horner_evaluate(C_coeffs, x)\n", 99 | "B_values = [ B(vi) for vi in v ]\n", 100 | "C_values = [ C(vi) for vi in v ]\n", 101 | "assert( B_values == [ B(vi) for vi in v ] )\n", 102 | "assert( C_values == [ C(vi) for vi in v ] )\n", 103 | "\n", 104 | "# combine results into values for A at w points\n", 105 | "A_values = [ ( B_values[i] + w[i] * C_values[i] ) % Q for i,_ in enumerate(w) ]\n", 106 | "\n", 107 | "assert( A_values == [ A(wi) for wi in w ] )" 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "metadata": {}, 113 | "source": [ 114 | "## Polynomial B and C" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": 7, 120 | "metadata": {}, 121 | "outputs": [ 122 | { 123 | "name": "stdout", 124 | "output_type": "stream", 125 | "text": [ 126 | "[1, 432, 1, 432]\n", 127 | "[1, 432]\n" 128 | ] 129 | } 130 | ], 131 | "source": [ 132 | "OMEGA2 = OMEGA4 * OMEGA4 % Q\n", 133 | "\n", 134 | "w_squared = [ pow(OMEGA2, e, Q) for e in range(4) ]\n", 135 | "print(w_squared)\n", 136 | "\n", 137 | "v = [ pow(OMEGA2, e, Q) for e in range(2) ]\n", 138 | "print(v)" 139 | ] 140 | }, 141 | { 142 | "cell_type": "markdown", 143 | "metadata": {}, 144 | "source": [ 145 | "## Order test" 146 | ] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": 8, 151 | "metadata": {}, 152 | "outputs": [ 153 | { 154 | "name": "stdout", 155 | "output_type": "stream", 156 | "text": [ 157 | "[1, 179, 432, 254, 1, 179, 432, 254]\n", 158 | "[1, 432, 1, 432, 1, 432, 1, 432]\n" 159 | ] 160 | } 161 | ], 162 | "source": [ 163 | "print([ pow(OMEGA4, e, Q) for e in range(8) ])\n", 164 | "print([ pow(OMEGA2, e, Q) for e in range(8) ])" 165 | ] 166 | }, 167 | { 168 | "cell_type": "code", 169 | "execution_count": null, 170 | "metadata": { 171 | "collapsed": true 172 | }, 173 | "outputs": [], 174 | "source": [] 175 | } 176 | ], 177 | "metadata": { 178 | "kernelspec": { 179 | "display_name": "Python 3", 180 | "language": "python", 181 | "name": "python3" 182 | }, 183 | "language_info": { 184 | "codemirror_mode": { 185 | "name": "ipython", 186 | "version": 3 187 | }, 188 | "file_extension": ".py", 189 | "mimetype": "text/x-python", 190 | "name": "python", 191 | "nbconvert_exporter": "python", 192 | "pygments_lexer": "ipython3", 193 | "version": "3.6.1" 194 | } 195 | }, 196 | "nbformat": 4, 197 | "nbformat_minor": 2 198 | } 199 | -------------------------------------------------------------------------------- /secret-sharing/Reed-Solomon.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "from copy import copy\n", 12 | "import random" 13 | ] 14 | }, 15 | { 16 | "cell_type": "markdown", 17 | "metadata": {}, 18 | "source": [ 19 | "# Base field arithmetic" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": 2, 25 | "metadata": { 26 | "collapsed": true 27 | }, 28 | "outputs": [], 29 | "source": [ 30 | "PRIME = 433" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": 3, 36 | "metadata": { 37 | "collapsed": true 38 | }, 39 | "outputs": [], 40 | "source": [ 41 | "def base_egcd(a, b):\n", 42 | " r0, r1 = a, b\n", 43 | " s0, s1 = 1, 0\n", 44 | " t0, t1 = 0, 1\n", 45 | " \n", 46 | " while r1 != 0:\n", 47 | " q, r2 = divmod(r0, r1)\n", 48 | " r0, s0, t0, r1, s1, t1 = \\\n", 49 | " r1, s1, t1, \\\n", 50 | " r2, s0 - s1*q, t0 - t1*q\n", 51 | "\n", 52 | " d = r0\n", 53 | " s = s0\n", 54 | " t = t0\n", 55 | " return d, s, t" 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": 4, 61 | "metadata": { 62 | "collapsed": true 63 | }, 64 | "outputs": [], 65 | "source": [ 66 | "def base_inverse(a):\n", 67 | " _, b, _ = base_egcd(a, PRIME)\n", 68 | " return b if b >= 0 else b+PRIME" 69 | ] 70 | }, 71 | { 72 | "cell_type": "code", 73 | "execution_count": 5, 74 | "metadata": { 75 | "collapsed": true 76 | }, 77 | "outputs": [], 78 | "source": [ 79 | "def base_add(a, b):\n", 80 | " return (a + b) % PRIME" 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": 6, 86 | "metadata": { 87 | "collapsed": true 88 | }, 89 | "outputs": [], 90 | "source": [ 91 | "def base_sub(a, b):\n", 92 | " return (a - b) % PRIME" 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": 7, 98 | "metadata": { 99 | "collapsed": true 100 | }, 101 | "outputs": [], 102 | "source": [ 103 | "def base_mul(a, b):\n", 104 | " return (a * b) % PRIME" 105 | ] 106 | }, 107 | { 108 | "cell_type": "code", 109 | "execution_count": 8, 110 | "metadata": { 111 | "collapsed": true 112 | }, 113 | "outputs": [], 114 | "source": [ 115 | "def base_div(a, b):\n", 116 | " return base_mul(a, base_inverse(b))" 117 | ] 118 | }, 119 | { 120 | "cell_type": "markdown", 121 | "metadata": {}, 122 | "source": [ 123 | "# Polynomial arithmetic" 124 | ] 125 | }, 126 | { 127 | "cell_type": "code", 128 | "execution_count": 9, 129 | "metadata": { 130 | "collapsed": true 131 | }, 132 | "outputs": [], 133 | "source": [ 134 | "def expand_to_match(A, B):\n", 135 | " diff = len(A) - len(B)\n", 136 | " if diff > 0:\n", 137 | " return A, B + [0] * diff\n", 138 | " elif diff < 0:\n", 139 | " diff = abs(diff)\n", 140 | " return A + [0] * diff, B\n", 141 | " else:\n", 142 | " return A, B\n", 143 | "\n", 144 | "assert( expand_to_match([1,1], []) == ([1,1], [0,0]) )\n", 145 | "assert( expand_to_match([1,1], [1]) == ([1,1], [1,0]) )" 146 | ] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": 10, 151 | "metadata": { 152 | "collapsed": true 153 | }, 154 | "outputs": [], 155 | "source": [ 156 | "def canonical(A):\n", 157 | " for i in reversed(range(len(A))):\n", 158 | " if A[i] != 0:\n", 159 | " return A[:i+1]\n", 160 | " return []\n", 161 | "\n", 162 | "assert( canonical([ ]) == [] )\n", 163 | "assert( canonical([0]) == [] )\n", 164 | "assert( canonical([0,0]) == [] )\n", 165 | "assert( canonical([0,1,2]) == [0,1,2] )\n", 166 | "assert( canonical([0,1,2,0,0]) == [0,1,2] )" 167 | ] 168 | }, 169 | { 170 | "cell_type": "code", 171 | "execution_count": 11, 172 | "metadata": { 173 | "collapsed": true 174 | }, 175 | "outputs": [], 176 | "source": [ 177 | "def lc(A):\n", 178 | " B = canonical(A)\n", 179 | " return B[-1]\n", 180 | "\n", 181 | "assert( lc([0,1,2,0]) == 2 )" 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "execution_count": 12, 187 | "metadata": { 188 | "collapsed": true 189 | }, 190 | "outputs": [], 191 | "source": [ 192 | "def deg(A):\n", 193 | " return len(canonical(A)) - 1\n", 194 | "\n", 195 | "assert( deg([ ]) == -1 )\n", 196 | "assert( deg([0]) == -1 )\n", 197 | "assert( deg([1,0]) == 0 )\n", 198 | "assert( deg([0,0,1]) == 2 )" 199 | ] 200 | }, 201 | { 202 | "cell_type": "code", 203 | "execution_count": 13, 204 | "metadata": { 205 | "collapsed": true 206 | }, 207 | "outputs": [], 208 | "source": [ 209 | "def poly_add(A, B):\n", 210 | " F, G = expand_to_match(A, B)\n", 211 | " return canonical([ base_add(f, g) for f, g in zip(F, G) ])\n", 212 | "\n", 213 | "assert( poly_add([1,2,3], [2,1]) == [3,3,3] )" 214 | ] 215 | }, 216 | { 217 | "cell_type": "code", 218 | "execution_count": 14, 219 | "metadata": { 220 | "collapsed": true 221 | }, 222 | "outputs": [], 223 | "source": [ 224 | "def poly_sub(A, B):\n", 225 | " F, G = expand_to_match(A, B)\n", 226 | " return canonical([ base_sub(f, g) for f, g in zip(F, G) ])\n", 227 | "\n", 228 | "assert( poly_sub([1,2,3], [1,2]) == [0,0,3] )" 229 | ] 230 | }, 231 | { 232 | "cell_type": "code", 233 | "execution_count": 15, 234 | "metadata": { 235 | "collapsed": true 236 | }, 237 | "outputs": [], 238 | "source": [ 239 | "def poly_scalarmul(A, b):\n", 240 | " return canonical([ base_mul(a, b) for a in A ])\n", 241 | "\n", 242 | "def poly_scalardiv(A, b):\n", 243 | " return canonical([ base_div(a, b) for a in A ])" 244 | ] 245 | }, 246 | { 247 | "cell_type": "code", 248 | "execution_count": 16, 249 | "metadata": { 250 | "collapsed": true 251 | }, 252 | "outputs": [], 253 | "source": [ 254 | "def poly_mul(A, B):\n", 255 | " C = [0] * (len(A) + len(B) - 1)\n", 256 | " for i in range(len(A)):\n", 257 | " for j in range(len(B)):\n", 258 | " C[i+j] = base_add(C[i+j], base_mul(A[i], B[j]))\n", 259 | " return canonical(C)" 260 | ] 261 | }, 262 | { 263 | "cell_type": "code", 264 | "execution_count": 17, 265 | "metadata": { 266 | "collapsed": true 267 | }, 268 | "outputs": [], 269 | "source": [ 270 | "def poly_divmod(A, B):\n", 271 | " t = base_inverse(lc(B))\n", 272 | " Q = [0] * len(A)\n", 273 | " R = copy(A)\n", 274 | " for i in reversed(range(0, len(A) - len(B) + 1)):\n", 275 | " Q[i] = base_mul(t, R[i + len(B) - 1])\n", 276 | " for j in range(len(B)):\n", 277 | " R[i+j] = base_sub(R[i+j], base_mul(Q[i], B[j]))\n", 278 | " return canonical(Q), canonical(R)\n", 279 | "\n", 280 | "A = [7,4,5,4]\n", 281 | "B = [1,0,1]\n", 282 | "Q, R = poly_divmod(A, B)\n", 283 | "assert( poly_add(poly_mul(Q, B), R) == A )" 284 | ] 285 | }, 286 | { 287 | "cell_type": "code", 288 | "execution_count": 18, 289 | "metadata": { 290 | "collapsed": true 291 | }, 292 | "outputs": [], 293 | "source": [ 294 | "def poly_div(A, B):\n", 295 | " Q, _ = poly_divmod(A, B)\n", 296 | " return Q\n", 297 | "\n", 298 | "def poly_mod(A, B):\n", 299 | " _, R = poly_divmod(A, B)\n", 300 | " return R" 301 | ] 302 | }, 303 | { 304 | "cell_type": "markdown", 305 | "metadata": {}, 306 | "source": [ 307 | "# Polynomial evaluation" 308 | ] 309 | }, 310 | { 311 | "cell_type": "code", 312 | "execution_count": 19, 313 | "metadata": { 314 | "collapsed": true 315 | }, 316 | "outputs": [], 317 | "source": [ 318 | "def poly_eval(A, x):\n", 319 | " result = 0\n", 320 | " for coef in reversed(A):\n", 321 | " result = base_add(coef, base_mul(x, result))\n", 322 | " return result" 323 | ] 324 | }, 325 | { 326 | "cell_type": "markdown", 327 | "metadata": {}, 328 | "source": [ 329 | "# Polynomial interpolation" 330 | ] 331 | }, 332 | { 333 | "cell_type": "code", 334 | "execution_count": 20, 335 | "metadata": { 336 | "collapsed": true 337 | }, 338 | "outputs": [], 339 | "source": [ 340 | "def lagrange_polynomials(xs):\n", 341 | " polys = []\n", 342 | " for i, xi in enumerate(xs):\n", 343 | " numerator = [1]\n", 344 | " denominator = 1\n", 345 | " for j, xj in enumerate(xs):\n", 346 | " if i == j: continue\n", 347 | " numerator = poly_mul(numerator, [base_sub(0, xj), 1])\n", 348 | " denominator = base_mul(denominator, base_sub(xi, xj))\n", 349 | " poly = poly_scalardiv(numerator, denominator)\n", 350 | " polys.append(poly)\n", 351 | " return polys" 352 | ] 353 | }, 354 | { 355 | "cell_type": "code", 356 | "execution_count": 21, 357 | "metadata": { 358 | "collapsed": true 359 | }, 360 | "outputs": [], 361 | "source": [ 362 | "def lagrange_interpolation(xs, ys):\n", 363 | " ls = lagrange_polynomials(xs)\n", 364 | " poly = []\n", 365 | " for i in range(len(ys)):\n", 366 | " term = poly_scalarmul(ls[i], ys[i])\n", 367 | " poly = poly_add(poly, term)\n", 368 | " return poly" 369 | ] 370 | }, 371 | { 372 | "cell_type": "code", 373 | "execution_count": 22, 374 | "metadata": { 375 | "collapsed": true 376 | }, 377 | "outputs": [], 378 | "source": [ 379 | "F = [1,2,3]\n", 380 | "\n", 381 | "xs = [10,20,30,40]\n", 382 | "ys = [ poly_eval(F, x) for x in xs ]\n", 383 | "\n", 384 | "G = lagrange_interpolation(xs, ys)\n", 385 | "assert( G == F )" 386 | ] 387 | }, 388 | { 389 | "cell_type": "markdown", 390 | "metadata": {}, 391 | "source": [ 392 | "# Reed-Solomon decoding via EGCD" 393 | ] 394 | }, 395 | { 396 | "cell_type": "code", 397 | "execution_count": 23, 398 | "metadata": { 399 | "collapsed": true 400 | }, 401 | "outputs": [], 402 | "source": [ 403 | "def poly_gcd(A, B):\n", 404 | " R0, R1 = A, B\n", 405 | " while R1 != []:\n", 406 | " R2 = poly_mod(R0, R1)\n", 407 | " R0, R1 = R1, R2\n", 408 | " D = poly_scalardiv(R0, lc(R0))\n", 409 | " return D" 410 | ] 411 | }, 412 | { 413 | "cell_type": "code", 414 | "execution_count": 24, 415 | "metadata": { 416 | "collapsed": true 417 | }, 418 | "outputs": [], 419 | "source": [ 420 | "def poly_egcd(A, B):\n", 421 | " R0, R1 = A, B\n", 422 | " S0, S1 = [1], []\n", 423 | " T0, T1 = [], [1]\n", 424 | " \n", 425 | " while R1 != []:\n", 426 | " Q, R2 = poly_divmod(R0, R1)\n", 427 | " \n", 428 | " R0, S0, T0, R1, S1, T1 = \\\n", 429 | " R1, S1, T1, \\\n", 430 | " R2, poly_sub(S0, poly_mul(S1, Q)), poly_sub(T0, poly_mul(T1, Q))\n", 431 | " \n", 432 | " c = lc(R0)\n", 433 | " D = poly_scalardiv(R0, c)\n", 434 | " S = poly_scalardiv(S0, c)\n", 435 | " T = poly_scalardiv(T0, c)\n", 436 | " return D, S, T\n", 437 | "\n", 438 | "A = [2,0,2]\n", 439 | "B = [1,3]\n", 440 | "G = [1,0,0,1]\n", 441 | "assert( poly_gcd(A, B) == [1] )\n", 442 | "assert( poly_gcd(A, G) == [1] )\n", 443 | "assert( poly_gcd(B, G) == [1] )\n", 444 | "\n", 445 | "F = poly_mul(G, A)\n", 446 | "H = poly_mul(G, B)\n", 447 | "D, S, T = poly_egcd(F, H)\n", 448 | "assert( D == poly_gcd(F, H) )\n", 449 | "assert( D == poly_add(poly_mul(F, S), poly_mul(H, T)) )" 450 | ] 451 | }, 452 | { 453 | "cell_type": "code", 454 | "execution_count": 25, 455 | "metadata": { 456 | "collapsed": true 457 | }, 458 | "outputs": [], 459 | "source": [ 460 | "def poly_eea(F, H):\n", 461 | " R0, R1 = F, H\n", 462 | " S0, S1 = [1], []\n", 463 | " T0, T1 = [], [1]\n", 464 | " \n", 465 | " triples = []\n", 466 | " \n", 467 | " while R1 != []:\n", 468 | " Q, R2 = poly_divmod(R0, R1)\n", 469 | " \n", 470 | " triples.append( (R0, S0, T0) )\n", 471 | " \n", 472 | " R0, S0, T0, R1, S1, T1 = \\\n", 473 | " R1, S1, T1, \\\n", 474 | " R2, poly_sub(S0, poly_mul(S1, Q)), poly_sub(T0, poly_mul(T1, Q))\n", 475 | " \n", 476 | " return triples" 477 | ] 478 | }, 479 | { 480 | "cell_type": "code", 481 | "execution_count": 26, 482 | "metadata": { 483 | "collapsed": true 484 | }, 485 | "outputs": [], 486 | "source": [ 487 | "def gao_decoding(points, values, max_degree, max_error_count):\n", 488 | " assert(len(values) == len(points))\n", 489 | " assert(len(points) >= 2*max_error_count + max_degree)\n", 490 | " \n", 491 | " # interpolate faulty polynomial\n", 492 | " H = lagrange_interpolation(points, values)\n", 493 | " \n", 494 | " # compute f\n", 495 | " F = [1]\n", 496 | " for xi in points:\n", 497 | " Fi = [base_sub(0, xi), 1]\n", 498 | " F = poly_mul(F, Fi)\n", 499 | " \n", 500 | " # run EEA-like algorithm on (F,H) to find EEA triple\n", 501 | " R0, R1 = F, H\n", 502 | " S0, S1 = [1], []\n", 503 | " T0, T1 = [], [1]\n", 504 | " while True:\n", 505 | " Q, R2 = poly_divmod(R0, R1)\n", 506 | " \n", 507 | " if deg(R0) < max_degree + max_error_count:\n", 508 | " G, leftover = poly_divmod(R0, T0)\n", 509 | " if leftover == []:\n", 510 | " decoded_polynomial = G\n", 511 | " error_locator = T0\n", 512 | " return decoded_polynomial, error_locator\n", 513 | " else:\n", 514 | " return None\n", 515 | " \n", 516 | " R0, S0, T0, R1, S1, T1 = \\\n", 517 | " R1, S1, T1, \\\n", 518 | " R2, poly_sub(S0, poly_mul(S1, Q)), poly_sub(T0, poly_mul(T1, Q))" 519 | ] 520 | }, 521 | { 522 | "cell_type": "markdown", 523 | "metadata": {}, 524 | "source": [ 525 | "# Application to Secret Sharing\n", 526 | "\n", 527 | "Using it Shamir's scheme here but it generalises to the packed variant naturally." 528 | ] 529 | }, 530 | { 531 | "cell_type": "code", 532 | "execution_count": 27, 533 | "metadata": { 534 | "collapsed": true 535 | }, 536 | "outputs": [], 537 | "source": [ 538 | "K = 1 # fixed in Shamir's scheme\n", 539 | "\n", 540 | "N = 15\n", 541 | "T = 5\n", 542 | "R = T+K\n", 543 | "assert(R <= N)\n", 544 | "\n", 545 | "MAX_MISSING = 3\n", 546 | "MAX_MANIPULATED = 3\n", 547 | "assert(R + MAX_MISSING + 2*MAX_MANIPULATED <= N)\n", 548 | "\n", 549 | "POINTS = [ p for p in range(1, N+1) ]\n", 550 | "assert(0 not in POINTS)\n", 551 | "assert(len(POINTS) == N)" 552 | ] 553 | }, 554 | { 555 | "cell_type": "code", 556 | "execution_count": 28, 557 | "metadata": { 558 | "collapsed": true 559 | }, 560 | "outputs": [], 561 | "source": [ 562 | "def shamir_share(secret):\n", 563 | " polynomial = [secret] + [random.randrange(PRIME) for _ in range(T)]\n", 564 | " shares = [ poly_eval(polynomial, p) for p in POINTS ]\n", 565 | " return shares\n", 566 | "\n", 567 | "def shamir_robust_reconstruct(shares):\n", 568 | " assert(len(shares) == N)\n", 569 | " \n", 570 | " # filter missing shares\n", 571 | " points_values = [ (p,v) for p,v in zip(POINTS, shares) if v is not None ]\n", 572 | " assert(len(points_values) >= N - MAX_MISSING)\n", 573 | " \n", 574 | " # decode remaining faulty\n", 575 | " points, values = zip(*points_values)\n", 576 | " polynomial, error_locator = gao_decoding(points, values, R, MAX_MANIPULATED)\n", 577 | " \n", 578 | " # check if recovery was possible\n", 579 | " if polynomial is None: raise Exception(\"Too many errors, cannot reconstruct\")\n", 580 | "\n", 581 | " # recover secret\n", 582 | " secret = poly_eval(polynomial, 0)\n", 583 | " \n", 584 | " # find error indices\n", 585 | " error_indices = [ i for i,v in enumerate( poly_eval(error_locator, p) for p in POINTS ) if v == 0 ]\n", 586 | "\n", 587 | " return secret, error_indices" 588 | ] 589 | }, 590 | { 591 | "cell_type": "code", 592 | "execution_count": 29, 593 | "metadata": {}, 594 | "outputs": [ 595 | { 596 | "name": "stdout", 597 | "output_type": "stream", 598 | "text": [ 599 | "Original shares: [25, 208, 143, 27, 327, 143, 334, 149, 353, 291, 148, 178, 366, 90, 381]\n", 600 | "Received shares: [25, 89, 143, None, None, 143, 407, 149, 353, 241, 148, 178, None, 90, 381]\n" 601 | ] 602 | } 603 | ], 604 | "source": [ 605 | "# sharing\n", 606 | "original_shares = shamir_share(5)\n", 607 | "print(\"Original shares: %s\" % original_shares)\n", 608 | "\n", 609 | "# introduce faults in shares\n", 610 | "received_shares = copy(original_shares)\n", 611 | "indices = random.sample(range(N), MAX_MISSING + MAX_MANIPULATED)\n", 612 | "missing, manipulated = indices[:MAX_MISSING], indices[MAX_MISSING:]\n", 613 | "for i in missing: received_shares[i] = None\n", 614 | "for i in manipulated: received_shares[i] = random.randrange(PRIME)\n", 615 | "print(\"Received shares: %s\" % received_shares)\n", 616 | "\n", 617 | "# robust reconstruction\n", 618 | "recovered_secret, error_indices = shamir_robust_reconstruct(received_shares)\n", 619 | "assert(recovered_secret == 5)\n", 620 | "assert(sorted(error_indices) == sorted(manipulated))" 621 | ] 622 | }, 623 | { 624 | "cell_type": "code", 625 | "execution_count": null, 626 | "metadata": { 627 | "collapsed": true 628 | }, 629 | "outputs": [], 630 | "source": [] 631 | } 632 | ], 633 | "metadata": { 634 | "kernelspec": { 635 | "display_name": "Python 3", 636 | "language": "python", 637 | "name": "python3" 638 | }, 639 | "language_info": { 640 | "codemirror_mode": { 641 | "name": "ipython", 642 | "version": 3 643 | }, 644 | "file_extension": ".py", 645 | "mimetype": "text/x-python", 646 | "name": "python", 647 | "nbconvert_exporter": "python", 648 | "pygments_lexer": "ipython3", 649 | "version": "3.4.3" 650 | } 651 | }, 652 | "nbformat": 4, 653 | "nbformat_minor": 2 654 | } 655 | -------------------------------------------------------------------------------- /secret-sharing/Schemes.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import random" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | "# Encoding" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 2, 24 | "metadata": { 25 | "collapsed": true 26 | }, 27 | "outputs": [], 28 | "source": [ 29 | "Q = 41" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": 3, 35 | "metadata": { 36 | "collapsed": true 37 | }, 38 | "outputs": [], 39 | "source": [ 40 | "def encode(x):\n", 41 | " return x % Q\n", 42 | "\n", 43 | "def decode(x):\n", 44 | " return x if x <= Q/2 else x-Q" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": 4, 50 | "metadata": {}, 51 | "outputs": [ 52 | { 53 | "name": "stdout", 54 | "output_type": "stream", 55 | "text": [ 56 | "encoded: 36\n", 57 | "decoded: -5\n" 58 | ] 59 | } 60 | ], 61 | "source": [ 62 | "x = encode(-5)\n", 63 | "print(\"encoded: %d\" % x)\n", 64 | "print(\"decoded: %d\" % decode(x))" 65 | ] 66 | }, 67 | { 68 | "cell_type": "markdown", 69 | "metadata": {}, 70 | "source": [ 71 | "# Additive sharing" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": 5, 77 | "metadata": { 78 | "collapsed": true 79 | }, 80 | "outputs": [], 81 | "source": [ 82 | "N = 10" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": 6, 88 | "metadata": {}, 89 | "outputs": [ 90 | { 91 | "name": "stdout", 92 | "output_type": "stream", 93 | "text": [ 94 | "[37, 23, 36, 26, 20, 14, 28, 40, 14, 13]\n", 95 | "5\n" 96 | ] 97 | } 98 | ], 99 | "source": [ 100 | "def additive_share(secret):\n", 101 | " shares = [ random.randrange(Q) for _ in range(N-1) ]\n", 102 | " shares += [ (secret - sum(shares)) % Q ]\n", 103 | " return shares\n", 104 | "\n", 105 | "def additive_reconstruct(shares):\n", 106 | " return sum(shares) % Q\n", 107 | "\n", 108 | "shares = additive_share(5)\n", 109 | "print(shares)\n", 110 | "print(additive_reconstruct(shares))" 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "execution_count": 7, 116 | "metadata": {}, 117 | "outputs": [ 118 | { 119 | "name": "stdout", 120 | "output_type": "stream", 121 | "text": [ 122 | "guess 0 can be explained by 8\n", 123 | "guess 1 can be explained by 9\n", 124 | "guess 2 can be explained by 10\n", 125 | "guess 3 can be explained by 11\n", 126 | "guess 4 can be explained by 12\n", 127 | "guess 5 can be explained by 13\n", 128 | "guess 6 can be explained by 14\n", 129 | "guess 7 can be explained by 15\n", 130 | "guess 8 can be explained by 16\n", 131 | "guess 9 can be explained by 17\n", 132 | "guess 10 can be explained by 18\n", 133 | "guess 11 can be explained by 19\n", 134 | "guess 12 can be explained by 20\n", 135 | "guess 13 can be explained by 21\n", 136 | "guess 14 can be explained by 22\n", 137 | "guess 15 can be explained by 23\n", 138 | "guess 16 can be explained by 24\n", 139 | "guess 17 can be explained by 25\n", 140 | "guess 18 can be explained by 26\n", 141 | "guess 19 can be explained by 27\n", 142 | "guess 20 can be explained by 28\n", 143 | "guess 21 can be explained by 29\n", 144 | "guess 22 can be explained by 30\n", 145 | "guess 23 can be explained by 31\n", 146 | "guess 24 can be explained by 32\n", 147 | "guess 25 can be explained by 33\n", 148 | "guess 26 can be explained by 34\n", 149 | "guess 27 can be explained by 35\n", 150 | "guess 28 can be explained by 36\n", 151 | "guess 29 can be explained by 37\n", 152 | "guess 30 can be explained by 38\n", 153 | "guess 31 can be explained by 39\n", 154 | "guess 32 can be explained by 40\n", 155 | "guess 33 can be explained by 0\n", 156 | "guess 34 can be explained by 1\n", 157 | "guess 35 can be explained by 2\n", 158 | "guess 36 can be explained by 3\n", 159 | "guess 37 can be explained by 4\n", 160 | "guess 38 can be explained by 5\n", 161 | "guess 39 can be explained by 6\n", 162 | "guess 40 can be explained by 7\n" 163 | ] 164 | } 165 | ], 166 | "source": [ 167 | "def explain(seen_shares, guess):\n", 168 | " # compute the unseen share that justifies the seen shares and the guess\n", 169 | " simulated_unseen_share = (guess - sum(seen_shares)) % Q\n", 170 | " # and would-be sharing by combining seen and unseen shares\n", 171 | " simulated_shares = seen_shares + [simulated_unseen_share]\n", 172 | " if additive_reconstruct(simulated_shares) == guess:\n", 173 | " # found an explanation\n", 174 | " return simulated_unseen_share\n", 175 | "\n", 176 | "seen_shares = shares[:N-1]\n", 177 | "for guess in range(Q):\n", 178 | " explanation = explain(seen_shares, guess)\n", 179 | " if explanation is not None: \n", 180 | " print(\"guess %d can be explained by %d\" % (guess, explanation))" 181 | ] 182 | }, 183 | { 184 | "cell_type": "code", 185 | "execution_count": 8, 186 | "metadata": { 187 | "collapsed": true 188 | }, 189 | "outputs": [], 190 | "source": [ 191 | "def additive_add(x, y):\n", 192 | " return [ (xi + yi) % Q for xi, yi in zip(x, y) ]\n", 193 | "\n", 194 | "def additive_sub(x, y):\n", 195 | " return [ (xi - yi) % Q for xi, yi in zip(x, y) ]" 196 | ] 197 | }, 198 | { 199 | "cell_type": "code", 200 | "execution_count": 9, 201 | "metadata": {}, 202 | "outputs": [ 203 | { 204 | "name": "stdout", 205 | "output_type": "stream", 206 | "text": [ 207 | "Additive(5)\n", 208 | "Additive(8)\n", 209 | "Additive(-3)\n" 210 | ] 211 | } 212 | ], 213 | "source": [ 214 | "class Additive:\n", 215 | " \n", 216 | " def __init__(self, secret=None):\n", 217 | " self.shares = additive_share(encode(secret)) if secret is not None else []\n", 218 | " \n", 219 | " def reveal(self):\n", 220 | " return decode(additive_reconstruct(self.shares))\n", 221 | " \n", 222 | " def __repr__(self):\n", 223 | " return \"Additive(%d)\" % self.reveal()\n", 224 | " \n", 225 | " def __add__(x, y):\n", 226 | " z = Additive()\n", 227 | " z.shares = additive_add(x.shares, y.shares)\n", 228 | " return z\n", 229 | " \n", 230 | " def __sub__(x, y):\n", 231 | " z = Additive()\n", 232 | " z.shares = additive_sub(x.shares, y.shares)\n", 233 | " return z\n", 234 | "\n", 235 | "x = Additive(5)\n", 236 | "print(x)\n", 237 | "\n", 238 | "y = Additive(8)\n", 239 | "print(y)\n", 240 | "\n", 241 | "z = x - y\n", 242 | "print(z)\n", 243 | "assert(z.reveal() == -3)" 244 | ] 245 | }, 246 | { 247 | "cell_type": "markdown", 248 | "metadata": {}, 249 | "source": [ 250 | "# Polynomials" 251 | ] 252 | }, 253 | { 254 | "cell_type": "code", 255 | "execution_count": 10, 256 | "metadata": { 257 | "collapsed": true 258 | }, 259 | "outputs": [], 260 | "source": [ 261 | "# using Horner's rule\n", 262 | "\n", 263 | "def evaluate_at_point(coefs, point):\n", 264 | " result = 0\n", 265 | " for coef in reversed(coefs):\n", 266 | " result = (coef + point * result) % Q\n", 267 | " return result" 268 | ] 269 | }, 270 | { 271 | "cell_type": "code", 272 | "execution_count": 11, 273 | "metadata": { 274 | "collapsed": true 275 | }, 276 | "outputs": [], 277 | "source": [ 278 | "def egcd(a, b):\n", 279 | " if a == 0:\n", 280 | " return (b, 0, 1)\n", 281 | " else:\n", 282 | " g, y, x = egcd(b % a, a)\n", 283 | " return (g, x - (b // a) * y, y)\n", 284 | "\n", 285 | "# from http://www.ucl.ac.uk/~ucahcjm/combopt/ext_gcd_python_programs.pdf\n", 286 | "def egcd_binary(a,b):\n", 287 | " u, v, s, t, r = 1, 0, 0, 1, 0\n", 288 | " while (a % 2 == 0) and (b % 2 == 0):\n", 289 | " a, b, r = a//2, b//2, r+1\n", 290 | " alpha, beta = a, b\n", 291 | " while (a % 2 == 0):\n", 292 | " a = a//2\n", 293 | " if (u % 2 == 0) and (v % 2 == 0):\n", 294 | " u, v = u//2, v//2\n", 295 | " else:\n", 296 | " u, v = (u + beta)//2, (v - alpha)//2\n", 297 | " while a != b:\n", 298 | " if (b % 2 == 0):\n", 299 | " b = b//2\n", 300 | " if (s % 2 == 0) and (t % 2 == 0):\n", 301 | " s, t = s//2, t//2\n", 302 | " else:\n", 303 | " s, t = (s + beta)//2, (t - alpha)//2\n", 304 | " elif b < a:\n", 305 | " a, b, u, v, s, t = b, a, s, t, u, v\n", 306 | " else:\n", 307 | " b, s, t = b - a, s - u, t - v\n", 308 | " return (2 ** r) * a, s, t\n", 309 | "\n", 310 | "\n", 311 | "def inverse(a):\n", 312 | " _, b, _ = egcd_binary(a, Q)\n", 313 | " return b" 314 | ] 315 | }, 316 | { 317 | "cell_type": "code", 318 | "execution_count": 12, 319 | "metadata": { 320 | "collapsed": true 321 | }, 322 | "outputs": [], 323 | "source": [ 324 | "# see https://en.wikipedia.org/wiki/Lagrange_polynomial\n", 325 | "\n", 326 | "def lagrange_constants_for_point(points, point):\n", 327 | " constants = [0] * len(points)\n", 328 | " for i in range(len(points)):\n", 329 | " xi = points[i]\n", 330 | " num = 1\n", 331 | " denum = 1\n", 332 | " for j in range(len(points)):\n", 333 | " if j != i:\n", 334 | " xj = points[j]\n", 335 | " num = (num * (xj - point)) % Q\n", 336 | " denum = (denum * (xj - xi)) % Q\n", 337 | " constants[i] = (num * inverse(denum)) % Q\n", 338 | " return constants\n", 339 | "\n", 340 | "def interpolate_at_point(points_values, point):\n", 341 | " points, values = zip(*points_values)\n", 342 | " constants = lagrange_constants_for_point(points, point)\n", 343 | " return sum( vi * ci for vi, ci in zip(values, constants) ) % Q" 344 | ] 345 | }, 346 | { 347 | "cell_type": "markdown", 348 | "metadata": {}, 349 | "source": [ 350 | "# Shamir sharing" 351 | ] 352 | }, 353 | { 354 | "cell_type": "code", 355 | "execution_count": 13, 356 | "metadata": { 357 | "collapsed": true 358 | }, 359 | "outputs": [], 360 | "source": [ 361 | "N = 10\n", 362 | "T = 4\n", 363 | "\n", 364 | "assert(T+1 <= N)" 365 | ] 366 | }, 367 | { 368 | "cell_type": "code", 369 | "execution_count": 14, 370 | "metadata": { 371 | "collapsed": true 372 | }, 373 | "outputs": [], 374 | "source": [ 375 | "def sample_shamir_polynomial(zero_value):\n", 376 | " coefs = [zero_value] + [random.randrange(Q) for _ in range(T)]\n", 377 | " return coefs" 378 | ] 379 | }, 380 | { 381 | "cell_type": "code", 382 | "execution_count": 15, 383 | "metadata": { 384 | "collapsed": true 385 | }, 386 | "outputs": [], 387 | "source": [ 388 | "SHARE_POINTS = [ p for p in range(1, N+1) ]\n", 389 | "assert(0 not in SHARE_POINTS)\n", 390 | "\n", 391 | "def shamir_share(secret):\n", 392 | " polynomial = sample_shamir_polynomial(secret)\n", 393 | " shares = [ evaluate_at_point(polynomial, p) for p in SHARE_POINTS ]\n", 394 | " return shares\n", 395 | "\n", 396 | "def shamir_reconstruct(shares):\n", 397 | " polynomial = [ (p,v) for p,v in zip(SHARE_POINTS, shares) if v is not None ]\n", 398 | " secret = interpolate_at_point(polynomial, 0)\n", 399 | " return secret\n", 400 | "\n", 401 | "shares = shamir_share(5)\n", 402 | "for i in range(N-(T+1)):\n", 403 | " shares[i] = None\n", 404 | "#shares[-1] = None # would fail; we need T+K points to reconstruct\n", 405 | "x = shamir_reconstruct(shares)\n", 406 | "assert(x == 5)" 407 | ] 408 | }, 409 | { 410 | "cell_type": "code", 411 | "execution_count": 16, 412 | "metadata": { 413 | "collapsed": true 414 | }, 415 | "outputs": [], 416 | "source": [ 417 | "def shamir_add(x, y):\n", 418 | " return [ (xi + yi) % Q for xi, yi in zip(x, y) ]\n", 419 | "\n", 420 | "def shamir_sub(x, y):\n", 421 | " return [ (xi - yi) % Q for xi, yi in zip(x, y) ]" 422 | ] 423 | }, 424 | { 425 | "cell_type": "code", 426 | "execution_count": 17, 427 | "metadata": { 428 | "collapsed": true 429 | }, 430 | "outputs": [], 431 | "source": [ 432 | "def shamir_mul(x, y):\n", 433 | " return [ (xi * yi) % Q for xi, yi in zip(x, y) ]" 434 | ] 435 | }, 436 | { 437 | "cell_type": "code", 438 | "execution_count": 18, 439 | "metadata": {}, 440 | "outputs": [ 441 | { 442 | "name": "stdout", 443 | "output_type": "stream", 444 | "text": [ 445 | "Shamir(2)\n", 446 | "Shamir(3)\n", 447 | "Shamir(-1)\n", 448 | "Shamir(6)\n" 449 | ] 450 | } 451 | ], 452 | "source": [ 453 | "class Shamir:\n", 454 | " \n", 455 | " def __init__(self, secret=None):\n", 456 | " self.shares = shamir_share(encode(secret)) if secret is not None else []\n", 457 | " self.degree = T\n", 458 | " \n", 459 | " def reveal(self):\n", 460 | " assert(self.degree+1 <= N)\n", 461 | " return decode(shamir_reconstruct(self.shares))\n", 462 | " \n", 463 | " def __repr__(self):\n", 464 | " return \"Shamir(%d)\" % self.reveal()\n", 465 | " \n", 466 | " def __add__(x, y):\n", 467 | " z = Shamir()\n", 468 | " z.shares = shamir_add(x.shares, y.shares)\n", 469 | " z.degree = max(x.degree, y.degree)\n", 470 | " return z\n", 471 | " \n", 472 | " def __sub__(x, y):\n", 473 | " z = Shamir()\n", 474 | " z.shares = shamir_sub(x.shares, y.shares)\n", 475 | " z.degree = max(x.degree, y.degree)\n", 476 | " return z\n", 477 | " \n", 478 | " def __mul__(x, y):\n", 479 | " z = Shamir()\n", 480 | " z.shares = shamir_mul(x.shares, y.shares)\n", 481 | " z.degree = x.degree + y.degree\n", 482 | " return z\n", 483 | " \n", 484 | "x = Shamir(2)\n", 485 | "print(x)\n", 486 | "\n", 487 | "y = Shamir(3)\n", 488 | "print(y)\n", 489 | "\n", 490 | "z = x - y\n", 491 | "print(z)\n", 492 | "assert(z.reveal() == -1)\n", 493 | "\n", 494 | "v = x * y\n", 495 | "print(v)\n", 496 | "assert(v.reveal() == 6)" 497 | ] 498 | }, 499 | { 500 | "cell_type": "markdown", 501 | "metadata": {}, 502 | "source": [ 503 | "# Packed sharing" 504 | ] 505 | }, 506 | { 507 | "cell_type": "code", 508 | "execution_count": 19, 509 | "metadata": { 510 | "collapsed": true 511 | }, 512 | "outputs": [], 513 | "source": [ 514 | "N = 20\n", 515 | "T = 8\n", 516 | "K = 2\n", 517 | "\n", 518 | "assert(T+K <= N)" 519 | ] 520 | }, 521 | { 522 | "cell_type": "code", 523 | "execution_count": 20, 524 | "metadata": { 525 | "collapsed": true 526 | }, 527 | "outputs": [], 528 | "source": [ 529 | "SECRET_POINTS = [ -p % Q for p in range(1, K+1) ]\n", 530 | "RANDOMNESS_POINTS = [ -p % Q for p in range(K+1, K+T+1) ]\n", 531 | "assert(set(SECRET_POINTS).intersection(RANDOMNESS_POINTS) == set())\n", 532 | "\n", 533 | "def sample_packed_polynomial(secrets):\n", 534 | " assert(len(secrets) == K)\n", 535 | " points = SECRET_POINTS + RANDOMNESS_POINTS\n", 536 | " values = secrets + [ random.randrange(Q) for _ in range(T) ]\n", 537 | " return list(zip(points, values))" 538 | ] 539 | }, 540 | { 541 | "cell_type": "code", 542 | "execution_count": 21, 543 | "metadata": { 544 | "collapsed": true 545 | }, 546 | "outputs": [], 547 | "source": [ 548 | "SHARE_POINTS = [ p for p in range(1, N+1) ]\n", 549 | "assert(set(SHARE_POINTS).intersection(SECRET_POINTS) == set())\n", 550 | "assert(set(SHARE_POINTS).intersection(RANDOMNESS_POINTS) == set())\n", 551 | "\n", 552 | "def packed_share(secrets):\n", 553 | " polynomial = sample_packed_polynomial(secrets)\n", 554 | " shares = [ interpolate_at_point(polynomial, p) for p in SHARE_POINTS ]\n", 555 | " return shares\n", 556 | "\n", 557 | "def packed_reconstruct(shares):\n", 558 | " points = SHARE_POINTS\n", 559 | " values = shares\n", 560 | " polynomial = [ (p,v) for p,v in zip(points, values) if v is not None ]\n", 561 | " secrets = [ interpolate_at_point(polynomial, p) for p in SECRET_POINTS ]\n", 562 | " return secrets\n", 563 | "\n", 564 | "secrets = [5,6]\n", 565 | "shares = packed_share(secrets)\n", 566 | "for i in range(N-(T+K)):\n", 567 | " shares[i] = None\n", 568 | "#shares[-1] = None # would fail; we need T+K points to reconstruct\n", 569 | "reconstructed_secrets = packed_reconstruct(shares)\n", 570 | "assert(reconstructed_secrets == secrets)" 571 | ] 572 | }, 573 | { 574 | "cell_type": "code", 575 | "execution_count": 22, 576 | "metadata": { 577 | "collapsed": true 578 | }, 579 | "outputs": [], 580 | "source": [ 581 | "def packed_add(x, y):\n", 582 | " return [ (xi + yi) % Q for xi, yi in zip(x, y) ]\n", 583 | "\n", 584 | "def packed_sub(x, y):\n", 585 | " return [ (xi - yi) % Q for xi, yi in zip(x, y) ]" 586 | ] 587 | }, 588 | { 589 | "cell_type": "code", 590 | "execution_count": 23, 591 | "metadata": { 592 | "collapsed": true 593 | }, 594 | "outputs": [], 595 | "source": [ 596 | "def packed_mul(x, y):\n", 597 | " return [ (xi * yi) % Q for xi, yi in zip(x, y) ]" 598 | ] 599 | }, 600 | { 601 | "cell_type": "code", 602 | "execution_count": 24, 603 | "metadata": {}, 604 | "outputs": [ 605 | { 606 | "name": "stdout", 607 | "output_type": "stream", 608 | "text": [ 609 | "Packed([2, 3])\n", 610 | "Packed([2, 3])\n", 611 | "Packed([0, 0])\n", 612 | "Packed([4, 9])\n" 613 | ] 614 | } 615 | ], 616 | "source": [ 617 | "class Packed:\n", 618 | " \n", 619 | " def __init__(self, secrets=None):\n", 620 | " self.shares = packed_share([ encode(s) for s in secrets ]) if secrets is not None else []\n", 621 | " self.degree = T+K-1\n", 622 | " \n", 623 | " def reveal(self):\n", 624 | " assert(self.degree+1 <= N)\n", 625 | " #print(packed_reconstruct(self.shares))\n", 626 | " return [ decode(s) for s in packed_reconstruct(self.shares) ]\n", 627 | " \n", 628 | " def __repr__(self):\n", 629 | " return \"Packed(%s)\" % self.reveal()\n", 630 | " \n", 631 | " def __add__(x, y):\n", 632 | " z = Packed()\n", 633 | " z.shares = packed_add(x.shares, y.shares)\n", 634 | " z.degree = max(x.degree, y.degree)\n", 635 | " return z\n", 636 | " \n", 637 | " def __sub__(x, y):\n", 638 | " z = Packed()\n", 639 | " z.shares = packed_sub(x.shares, y.shares)\n", 640 | " z.degree = max(x.degree, y.degree)\n", 641 | " return z\n", 642 | " \n", 643 | " def __mul__(x, y):\n", 644 | " z = Packed()\n", 645 | " z.shares = packed_mul(x.shares, y.shares)\n", 646 | " z.degree = x.degree + y.degree\n", 647 | " return z\n", 648 | " \n", 649 | "x = Packed([2,3])\n", 650 | "print(x)\n", 651 | "\n", 652 | "y = Packed([2,3])\n", 653 | "print(y)\n", 654 | "\n", 655 | "z = x - y\n", 656 | "print(z)\n", 657 | "assert(z.reveal() == [0,0])\n", 658 | "\n", 659 | "v = x * y\n", 660 | "print(v)\n", 661 | "assert(v.reveal() == [4,9])" 662 | ] 663 | }, 664 | { 665 | "cell_type": "code", 666 | "execution_count": null, 667 | "metadata": { 668 | "collapsed": true 669 | }, 670 | "outputs": [], 671 | "source": [] 672 | } 673 | ], 674 | "metadata": { 675 | "kernelspec": { 676 | "display_name": "Python 3", 677 | "language": "python", 678 | "name": "python3" 679 | }, 680 | "language_info": { 681 | "codemirror_mode": { 682 | "name": "ipython", 683 | "version": 3 684 | }, 685 | "file_extension": ".py", 686 | "mimetype": "text/x-python", 687 | "name": "python", 688 | "nbconvert_exporter": "python", 689 | "pygments_lexer": "ipython3", 690 | "version": "3.6.1" 691 | } 692 | }, 693 | "nbformat": 4, 694 | "nbformat_minor": 2 695 | } 696 | -------------------------------------------------------------------------------- /spdz/Tensor SPDZ.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import random\n", 12 | "import numpy as np\n", 13 | "\n", 14 | "from functools import reduce\n", 15 | "from scipy.special import comb\n", 16 | "binom = lambda n, k: comb(n, k, exact=True)" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": 2, 22 | "metadata": { 23 | "collapsed": true 24 | }, 25 | "outputs": [], 26 | "source": [ 27 | "Q = 2657003489534545107915232808830590043" 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "metadata": {}, 33 | "source": [ 34 | "# Tensor of public values" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": 3, 40 | "metadata": { 41 | "collapsed": true 42 | }, 43 | "outputs": [], 44 | "source": [ 45 | "class PublicTensor:\n", 46 | " \n", 47 | " def __init__(self, values):\n", 48 | " self.values = values\n", 49 | " \n", 50 | " def reveal(self):\n", 51 | " return self\n", 52 | " \n", 53 | " def unwrap(self):\n", 54 | " return self.values\n", 55 | " \n", 56 | " def add(x, y):\n", 57 | " if isinstance(y, PublicTensor):\n", 58 | " values = (x.values + y.values) % Q\n", 59 | " return PublicTensor(values)\n", 60 | " if isinstance(y, PrivateTensor):\n", 61 | " shares0 = (x.values + y.shares0) % Q\n", 62 | " shares1 = y.shares1\n", 63 | " return PrivateTensor(None, shares0, shares1)\n", 64 | " \n", 65 | " def sub(x, y):\n", 66 | " if isinstance(y, PublicTensor):\n", 67 | " values = (x.values - y.values) % Q\n", 68 | " return PublicTensor(values)\n", 69 | " if isinstance(y, PrivateTensor):\n", 70 | " shares0 = (x.values + Q - y.shares0) % Q\n", 71 | " shares1 = ( Q - y.shares1) % Q\n", 72 | " return PrivateTensor(None, shares0, shares1)\n", 73 | " \n", 74 | " def mul(x, y):\n", 75 | " if isinstance(y, PublicTensor):\n", 76 | " values = (x.values * y.values) % Q\n", 77 | " return PublicTensor(values)\n", 78 | " if isinstance(y, PrivateTensor):\n", 79 | " shares0 = (x.values * y.shares0) % Q\n", 80 | " shares1 = (x.values * y.shares1) % Q\n", 81 | " return PrivateTensor(None, shares0, shares1)\n", 82 | " \n", 83 | " def dot(x, y):\n", 84 | " if isinstance(y, PublicTensor): \n", 85 | " values = x.values.dot(y.values) % Q\n", 86 | " return PublicTensor(values)\n", 87 | " if isinstance(y, PrivateTensor):\n", 88 | " shares0 = x.values.dot(y.shares0) % Q\n", 89 | " shares1 = x.values.dot(y.shares1) % Q\n", 90 | " return PrivateTensor(None, shares0, shares1)\n", 91 | " \n", 92 | " def pows(x, highest_power):\n", 93 | " x_powers = ( np.power(x.values, e) % Q for e in range(0, highest_power+1) )\n", 94 | " return [ PublicTensor(v) for v in x_powers ]\n", 95 | " \n", 96 | " def __add__(x, y):\n", 97 | " return x.add(y)\n", 98 | " \n", 99 | " def __sub__(x, y):\n", 100 | " return x.sub(y)\n", 101 | " \n", 102 | " def __mul__(x, y):\n", 103 | " return x.mul(y)\n", 104 | " \n", 105 | " @property\n", 106 | " def shape(self):\n", 107 | " return self.values.shape\n", 108 | " \n", 109 | " def __repr__(self):\n", 110 | " return \"PublicTensor(\\n%s)\" % self.values" 111 | ] 112 | }, 113 | { 114 | "cell_type": "markdown", 115 | "metadata": {}, 116 | "source": [ 117 | "# Tensor of private values" 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": 4, 123 | "metadata": { 124 | "collapsed": true 125 | }, 126 | "outputs": [], 127 | "source": [ 128 | "def sample_random_tensor(shape):\n", 129 | " values = [ random.randrange(Q) for _ in range(np.prod(shape)) ]\n", 130 | " return np.array(values).reshape(shape)" 131 | ] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "execution_count": 5, 136 | "metadata": { 137 | "collapsed": true 138 | }, 139 | "outputs": [], 140 | "source": [ 141 | "def share(secrets):\n", 142 | " shares0 = sample_random_tensor(secrets.shape)\n", 143 | " shares1 = (secrets - shares0) % Q\n", 144 | " return shares0, shares1\n", 145 | "\n", 146 | "def reconstruct(shares0, shares1):\n", 147 | " secrets = (shares0 + shares1) % Q\n", 148 | " return secrets" 149 | ] 150 | }, 151 | { 152 | "cell_type": "code", 153 | "execution_count": 6, 154 | "metadata": { 155 | "collapsed": true 156 | }, 157 | "outputs": [], 158 | "source": [ 159 | "def generate_mul_triple(x_shape, y_shape):\n", 160 | " a = sample_random_tensor(x_shape)\n", 161 | " b = sample_random_tensor(y_shape)\n", 162 | " c = np.multiply(a, b) % Q\n", 163 | " return PrivateTensor(a), PrivateTensor(b), PrivateTensor(c)" 164 | ] 165 | }, 166 | { 167 | "cell_type": "code", 168 | "execution_count": 7, 169 | "metadata": { 170 | "collapsed": true 171 | }, 172 | "outputs": [], 173 | "source": [ 174 | "def generate_dot_triple(x_shape, y_shape):\n", 175 | " a = sample_random_tensor(x_shape)\n", 176 | " b = sample_random_tensor(y_shape)\n", 177 | " c = np.dot(a, b) % Q\n", 178 | " return PrivateTensor(a), PrivateTensor(b), PrivateTensor(c)" 179 | ] 180 | }, 181 | { 182 | "cell_type": "code", 183 | "execution_count": 8, 184 | "metadata": { 185 | "collapsed": true 186 | }, 187 | "outputs": [], 188 | "source": [ 189 | "# TODO\n", 190 | "# - use eg repeated squaring instead, but NumPy doesn't support that\n", 191 | "# - avoid resharing '1' every time\n", 192 | "\n", 193 | "def generate_pows_triple(shape, highest_power):\n", 194 | " a = random_values(shape)\n", 195 | "# a = np.zeros(shape).astype('int').astype('object')\n", 196 | " a_powers = ( np.power(a, e) % Q for e in range(0, highest_power+1) )\n", 197 | " return [ PrivateTensor(v) for v in a_powers ]" 198 | ] 199 | }, 200 | { 201 | "cell_type": "code", 202 | "execution_count": 9, 203 | "metadata": { 204 | "collapsed": true 205 | }, 206 | "outputs": [], 207 | "source": [ 208 | "class PrivateTensor:\n", 209 | " \n", 210 | " def __init__(self, values, shares0=None, shares1=None):\n", 211 | " if not values is None:\n", 212 | " shares0, shares1 = share(values)\n", 213 | " self.shares0 = shares0\n", 214 | " self.shares1 = shares1\n", 215 | " \n", 216 | " def reconstruct(self):\n", 217 | " return PublicTensor(reconstruct(self.shares0, self.shares1))\n", 218 | " \n", 219 | " def unwrap(self):\n", 220 | " return reconstruct(self.shares0, self.shares1)\n", 221 | " \n", 222 | " def add(x, y):\n", 223 | " if type(y) is PublicTensor:\n", 224 | " shares0 = (x.shares0 + y.values) % Q\n", 225 | " shares1 = x.shares1\n", 226 | " return PrivateTensor(None, shares0, shares1)\n", 227 | " if type(y) is PrivateTensor:\n", 228 | " shares0 = (x.shares0 + y.shares0) % Q\n", 229 | " shares1 = (x.shares1 + y.shares1) % Q\n", 230 | " return PrivateTensor(None, shares0, shares1)\n", 231 | " \n", 232 | " def sub(x, y):\n", 233 | " if type(y) is PublicTensor:\n", 234 | " shares0 = (x.shares0 - y.values) % Q\n", 235 | " shares1 = x.shares1\n", 236 | " return PrivateTensor(None, shares0, shares1)\n", 237 | " if type(y) is PrivateTensor:\n", 238 | " shares0 = (x.shares0 - y.shares0) % Q\n", 239 | " shares1 = (x.shares1 - y.shares1) % Q\n", 240 | " return PrivateTensor(None, shares0, shares1)\n", 241 | " \n", 242 | " def mul(x, y):\n", 243 | " if type(y) is PublicTensor:\n", 244 | " shares0 = (x.shares0 * y.values) % Q\n", 245 | " shares1 = (x.shares1 * y.values) % Q\n", 246 | " return PrivateTensor(None, shares0, shares1)\n", 247 | " if type(y) is PrivateTensor:\n", 248 | " a, b, a_mul_b = generate_mul_triple(x.shape, y.shape)\n", 249 | " alpha = (x - a).reconstruct()\n", 250 | " beta = (y - b).reconstruct()\n", 251 | " return alpha.mul(beta) + \\\n", 252 | " alpha.mul(b) + \\\n", 253 | " a.mul(beta) + \\\n", 254 | " a_mul_b\n", 255 | " \n", 256 | " def dot(x, y):\n", 257 | " if type(y) is PublicTensor:\n", 258 | " shares0 = x.shares0.dot(y.values) % Q\n", 259 | " shares1 = x.shares1.dot(y.values) % Q\n", 260 | " return PrivateTensor(None, shares0, shares1)\n", 261 | " if type(y) is PrivateTensor:\n", 262 | " a, b, a_dot_b = generate_dot_triple(x.shape, y.shape)\n", 263 | " alpha = (x - a).reconstruct()\n", 264 | " beta = (y - b).reconstruct()\n", 265 | " return alpha.dot(beta) + \\\n", 266 | " alpha.dot(b) + \\\n", 267 | " a.dot(beta) + \\\n", 268 | " a_dot_b\n", 269 | " \n", 270 | " def conv2d(x, y):\n", 271 | " pass\n", 272 | " \n", 273 | " def pows(x, highest_power):\n", 274 | " assert highest_power >= 1 \n", 275 | " a_powers = generate_pows_triple(x.shape, highest_power)\n", 276 | " one = a_powers[0]\n", 277 | " a = a_powers[1]\n", 278 | " alpha = (x - a).reveal()\n", 279 | " alpha_powers = alpha.pows(highest_power)\n", 280 | " x_powers = []\n", 281 | " for power in range(1, highest_power+2):\n", 282 | " # compute binomial coefficients\n", 283 | " coeffs = [ PublicTensor(binom(power, k)) for k in range(power+1) ]\n", 284 | " # compute and sum terms\n", 285 | " foo = a_powers[:power+1]\n", 286 | " bar = list(reversed(alpha_powers[:power]))\n", 287 | " print(len(foo), len(bar))\n", 288 | " terms = [\n", 289 | " a_power * (alpha_power * coeff) \\\n", 290 | " for a_power, alpha_power, coeff in zip (\\\n", 291 | " foo,\n", 292 | " bar,\n", 293 | " coeffs\n", 294 | " )\n", 295 | " ]\n", 296 | " x_powers.append(reduce(lambda x, y: x + y, terms))\n", 297 | " return x_powers\n", 298 | " \n", 299 | " def __add__(x, y):\n", 300 | " return x.add(y)\n", 301 | " \n", 302 | " def __sub__(x, y):\n", 303 | " return x.sub(y)\n", 304 | " \n", 305 | " def __mul__(x, y):\n", 306 | " return x.mul(y)\n", 307 | " \n", 308 | " @property\n", 309 | " def shape(self):\n", 310 | " return self.shares0.shape\n", 311 | " \n", 312 | " def __repr__(self):\n", 313 | " return \"PrivateTensor(\\n%s)\" % reconstruct(self.shares0, self.shares1)" 314 | ] 315 | }, 316 | { 317 | "cell_type": "markdown", 318 | "metadata": {}, 319 | "source": [ 320 | "# Tests" 321 | ] 322 | }, 323 | { 324 | "cell_type": "code", 325 | "execution_count": 10, 326 | "metadata": {}, 327 | "outputs": [], 328 | "source": [ 329 | "v = np.arange(27).reshape(3,3,3)\n", 330 | "w = np.arange(27).reshape(3,3,3)\n", 331 | "\n", 332 | "for x_type in [PublicTensor, PrivateTensor]:\n", 333 | " for y_type in [PublicTensor, PrivateTensor]:\n", 334 | " \n", 335 | " x = x_type(v)\n", 336 | " y = y_type(w)\n", 337 | "\n", 338 | " z = x + y; assert (z.unwrap() == v + w).all()\n", 339 | " z = x - y; assert (z.unwrap() == v - w).all()\n", 340 | " z = x * y; assert (z.unwrap() == v * w).all()\n", 341 | " z = x.dot(y); assert (z.unwrap() == v.dot(w)).all()\n", 342 | "\n", 343 | " # z = x.pows(2);\n", 344 | " # print(len(z), z)\n", 345 | " # assert (z[0].unwrap() == v**0).all()\n", 346 | " # assert (z[1].unwrap() == v**1).all()\n", 347 | " # assert (z[2].unwrap() == v**2).all()" 348 | ] 349 | }, 350 | { 351 | "cell_type": "code", 352 | "execution_count": null, 353 | "metadata": { 354 | "collapsed": true 355 | }, 356 | "outputs": [], 357 | "source": [] 358 | } 359 | ], 360 | "metadata": { 361 | "kernelspec": { 362 | "display_name": "Python 3", 363 | "language": "python", 364 | "name": "python3" 365 | }, 366 | "language_info": { 367 | "codemirror_mode": { 368 | "name": "ipython", 369 | "version": 3 370 | }, 371 | "file_extension": ".py", 372 | "mimetype": "text/x-python", 373 | "name": "python", 374 | "nbconvert_exporter": "python", 375 | "pygments_lexer": "ipython3", 376 | "version": "3.6.4" 377 | } 378 | }, 379 | "nbformat": 4, 380 | "nbformat_minor": 2 381 | } 382 | -------------------------------------------------------------------------------- /spdz/primegen.java: -------------------------------------------------------------------------------- 1 | import java.util.*; 2 | import java.math.*; 3 | 4 | public class primegen 5 | { 6 | public static void main(String[] args) 7 | { 8 | BigInteger q = BigInteger.probablePrime(60, new Random()); 9 | System.out.println(q); 10 | } 11 | } -------------------------------------------------------------------------------- /tensorflow/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /tensorflow/Linear.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [] 7 | }, 8 | { 9 | "cell_type": "code", 10 | "execution_count": 1, 11 | "metadata": { 12 | "collapsed": true 13 | }, 14 | "outputs": [], 15 | "source": [] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "Idea below is to simulate five different players on different devices. Hopefully Tensorflow can take care of (optimising?) networking like this." 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": 2, 27 | "metadata": { 28 | "collapsed": true 29 | }, 30 | "outputs": [], 31 | "source": [] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": {}, 36 | "source": [ 37 | "# CRT" 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": 3, 43 | "metadata": {}, 44 | "outputs": [], 45 | "source": [ 46 | "from spdz import *" 47 | ] 48 | }, 49 | { 50 | "cell_type": "markdown", 51 | "metadata": {}, 52 | "source": [ 53 | "# SPDZ in Tensorflow" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": null, 59 | "metadata": { 60 | "collapsed": true 61 | }, 62 | "outputs": [], 63 | "source": [] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": 4, 68 | "metadata": { 69 | "collapsed": true 70 | }, 71 | "outputs": [], 72 | "source": [] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": 5, 77 | "metadata": { 78 | "collapsed": true 79 | }, 80 | "outputs": [], 81 | "source": [ 82 | "# def assign(x):\n", 83 | " \n", 84 | "# x0, x1 = x\n", 85 | " \n", 86 | " \n", 87 | " " 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": null, 93 | "metadata": { 94 | "collapsed": true 95 | }, 96 | "outputs": [], 97 | "source": [] 98 | }, 99 | { 100 | "cell_type": "markdown", 101 | "metadata": {}, 102 | "source": [ 103 | "# Simple addition of large numbers" 104 | ] 105 | }, 106 | { 107 | "cell_type": "code", 108 | "execution_count": 6, 109 | "metadata": {}, 110 | "outputs": [ 111 | { 112 | "ename": "NameError", 113 | "evalue": "name 'INPUT_PROVIDER' is not defined", 114 | "output_type": "error", 115 | "traceback": [ 116 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 117 | "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", 118 | "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# Inputs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0minput_x\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mx\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdefine_private\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m10\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m10\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3\u001b[0m \u001b[0minput_w\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mw\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdefine_private\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m10\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m10\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0minput_b\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mb\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdefine_private\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m10\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m10\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", 119 | "\u001b[0;32m~/work/mortendahl/privateml/tensorflow/spdz.py\u001b[0m in \u001b[0;36mdefine_private\u001b[0;34m(shape)\u001b[0m\n\u001b[1;32m 164\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mtf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mname_scope\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"input\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 165\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 166\u001b[0;31m \u001b[0;32mwith\u001b[0m \u001b[0mtf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdevice\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mINPUT_PROVIDER\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 167\u001b[0m \u001b[0minput_x\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m \u001b[0mtf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mplaceholder\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mNATIVE_TYPE\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mshape\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0m_\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mm\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 168\u001b[0m \u001b[0mx\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mshare\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minput_x\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 120 | "\u001b[0;31mNameError\u001b[0m: name 'INPUT_PROVIDER' is not defined" 121 | ] 122 | } 123 | ], 124 | "source": [] 125 | }, 126 | { 127 | "cell_type": "code", 128 | "execution_count": null, 129 | "metadata": { 130 | "collapsed": true 131 | }, 132 | "outputs": [], 133 | "source": [] 134 | }, 135 | { 136 | "cell_type": "markdown", 137 | "metadata": {}, 138 | "source": [ 139 | "# Linear regression" 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": null, 145 | "metadata": { 146 | "collapsed": true 147 | }, 148 | "outputs": [], 149 | "source": [ 150 | " \n", 151 | "# res = sess.run([p], {\n", 152 | "# input_x: np.arange(10*10).reshape(10,10).astype(int),\n", 153 | "# })\n", 154 | "# input_x, x = define((10,10))\n", 155 | "# \n", 156 | "# w = variable((10,10))\n", 157 | "# b = variable((10,10))\n", 158 | "# \n", 159 | "# y_pred = add(mul(x, w), b)\n", 160 | "# \n", 161 | "# # error = sub(y_pred, y)\n", 162 | "# # gradients = 2/m * tf.matmul(tf.transpose(X), error)\n", 163 | "# # training_op = tf.assign(theta, theta - learning_rate * gradients)\n", 164 | "# \n", 165 | "# p = reveal(y_pred)\n", 166 | "\n", 167 | "# print(res)\n", 168 | "\n", 169 | "# with tf.Session(config=config) as sess:\n", 170 | "# sess.run(tf.global_variables_initializer())\n", 171 | "# res = sess.run(z, values)\n", 172 | " \n", 173 | "# res = sess.run([p], {\n", 174 | "# input_x: np.arange(10*10).reshape(10,10).astype(int),\n", 175 | "# })\n", 176 | "# print(res)" 177 | ] 178 | }, 179 | { 180 | "cell_type": "markdown", 181 | "metadata": {}, 182 | "source": [ 183 | "# Dump" 184 | ] 185 | }, 186 | { 187 | "cell_type": "code", 188 | "execution_count": null, 189 | "metadata": { 190 | "collapsed": true 191 | }, 192 | "outputs": [], 193 | "source": [] 194 | } 195 | ], 196 | "metadata": { 197 | "kernelspec": { 198 | "display_name": "Python 3", 199 | "language": "python", 200 | "name": "python3" 201 | }, 202 | "language_info": { 203 | "codemirror_mode": { 204 | "name": "ipython", 205 | "version": 3 206 | }, 207 | "file_extension": ".py", 208 | "mimetype": "text/x-python", 209 | "name": "python", 210 | "nbconvert_exporter": "python", 211 | "pygments_lexer": "ipython3", 212 | "version": "3.6.4" 213 | } 214 | }, 215 | "nbformat": 4, 216 | "nbformat_minor": 2 217 | } 218 | -------------------------------------------------------------------------------- /tensorflow/parsetrace.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import json 3 | import sys 4 | 5 | def parse_tracefile(filename): 6 | 7 | with open(filename, 'r') as f: 8 | raw = json.load(f) 9 | 10 | if 'traceEvents' not in raw: 11 | return None 12 | 13 | traceEvents = raw['traceEvents'] 14 | 15 | timestamps = ( 16 | ( 17 | event['ts'], 18 | event['ts'] + event['dur'] 19 | ) 20 | for event in traceEvents 21 | if 'ts' in event and 'dur' in event 22 | ) 23 | timestamps = sorted(timestamps, key=lambda x: x[1]) 24 | 25 | min_ts = timestamps[0] 26 | max_ts = timestamps[-1] 27 | return max_ts[1] - min_ts[0] 28 | 29 | durations = [] 30 | 31 | for filename in glob.glob(sys.argv[1]): 32 | duration = parse_tracefile(filename) 33 | durations.append(duration) 34 | print float(duration) / 1000 35 | 36 | print 'average:', float(sum(durations)) / len(durations) -------------------------------------------------------------------------------- /tensorflow/simple-distributed-task/config.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | 3 | JOB_NAME = 'local' 4 | 5 | CLUSTER = tf.train.ClusterSpec({ 6 | JOB_NAME: ['localhost:4444', 'localhost:5555'] 7 | }) 8 | 9 | WORKER_0 = '/job:{}/task:0'.format(JOB_NAME) 10 | WORKER_1 = '/job:{}/task:1'.format(JOB_NAME) 11 | 12 | MASTER = 'grpc://localhost:4444' -------------------------------------------------------------------------------- /tensorflow/simple-distributed-task/task.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | 3 | from config import * 4 | 5 | w = tf.constant(2) 6 | b = tf.constant(2) 7 | 8 | with tf.device(WORKER_0): 9 | x = tf.placeholder(tf.int32, shape=()) 10 | y = x * w 11 | 12 | with tf.device(WORKER_1): 13 | z = y + b 14 | 15 | with tf.Session(MASTER) as sess: 16 | 17 | writer = tf.summary.FileWriter('/tmp/tensorflow', sess.graph) 18 | 19 | result = sess.run(z, {x: 2}) 20 | print(result) 21 | 22 | writer.close() -------------------------------------------------------------------------------- /tensorflow/simple-distributed-task/worker0.py: -------------------------------------------------------------------------------- 1 | 2 | import tensorflow as tf 3 | from config import CLUSTER, JOB_NAME 4 | 5 | print("Starting server") 6 | 7 | server = tf.train.Server(CLUSTER, job_name=JOB_NAME, task_index=0) 8 | server.start() 9 | server.join() 10 | -------------------------------------------------------------------------------- /tensorflow/simple-distributed-task/worker1.py: -------------------------------------------------------------------------------- 1 | 2 | import tensorflow as tf 3 | from config import CLUSTER, JOB_NAME 4 | 5 | print("Starting server") 6 | 7 | server = tf.train.Server(CLUSTER, job_name=JOB_NAME, task_index=1) 8 | server.start() 9 | server.join() 10 | -------------------------------------------------------------------------------- /tensorflow/spdz/README.md: -------------------------------------------------------------------------------- 1 | 2 | # TensorSpdz 3 | 4 | This repository contains an implementation of the SPDZ protocol in TensorFlow as a way of doing private machine learning; see [this blog post](http://mortendahl.github.io/2018/03/01/secure-computation-as-dataflow-programs/) for an introduction. 5 | 6 | The `spdz` directory referred to below is the directory where this readme file is located (i.e. `privateml/tensorflow/spdz/`). 7 | 8 | ## Configuration 9 | 10 | The code runs on four processes that first needs to be configured. Many will probably want to start [running locally](./configs/localhost/) but there's also one for [running on GCP](./configs/gcp). 11 | 12 | ## Examples 13 | 14 | To run these examples first make sure to set up one of the configurations above. 15 | 16 | ### Private logistic regression 17 | 18 | This example is divided into two depending on whether training is done publicly or privately. 19 | 20 | To do public training and private prediction simply run: 21 | 22 | ```sh 23 | spdz $ ./run.sh logistic-regression-simple/prediction.py 24 | ``` 25 | 26 | which will first train on a public model, privately distribute the trained parameters to the two servers, and finally make a prediction on a private input so that the two servers know neither the parameters nor the input. 27 | 28 | To do both private training and prediction simply run: 29 | 30 | ```sh 31 | spdz $ ./run.sh logistic-regression-simple/training.py 32 | ``` 33 | 34 | which of course will take longer. 35 | -------------------------------------------------------------------------------- /tensorflow/spdz/configs/cpus/config.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | 3 | SERVER_0 = '/device:CPU:0' 4 | SERVER_1 = '/device:CPU:1' 5 | CRYPTO_PRODUCER = '/device:CPU:2' 6 | INPUT_PROVIDER = '/device:CPU:3' 7 | OUTPUT_RECEIVER = '/device:CPU:4' 8 | 9 | SESSION_CONFIG = tf.ConfigProto( 10 | log_device_placement=False, 11 | device_count={"CPU": 5}, 12 | inter_op_parallelism_threads=1, 13 | intra_op_parallelism_threads=1 14 | ) 15 | 16 | TENSORBOARD_DIR = '/tmp/tensorflow' 17 | 18 | MASTER = '' -------------------------------------------------------------------------------- /tensorflow/spdz/configs/gcp/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Running on GCP 3 | 4 | This configuration is for running on the [Google Cloud Platform](https://cloud.google.com/compute/). All steps here assume that the [Cloud SDK](https://cloud.google.com/sdk/) is already installed (for macOS this may e.g. be done using Homebrew: `brew cask install google-cloud-sdk`). 5 | 6 | ## Setup 7 | 8 | Script `gcp-create.sh` provides an easy way to create four GCP instances to be used with this configuration; simply run it from the `setup` directory. 9 | 10 | To later remove everything again run `gcp-destroy.sh` from the `setup` directory. 11 | 12 | ## Starting and Stopping 13 | 14 | Right after setup all instances should be stopped, but scripts `start.sh`, `stop.sh`, and `restart.sh` provides easy ways of managing their state. Run them from the `gcp` directory. 15 | 16 | `start.sh` will take care of linking the four instances together and starting a TensorFlow server on each in a `screen`. 17 | 18 | ## Running 19 | 20 | The `run.sh` script provides a way to run a program on the four instances. Symlink to it from the `spdz` directory and run it from there, e.g.: 21 | 22 | ```sh 23 | spdz $ ./run.sh logistic-regression-simple/prediction.py 24 | ``` 25 | 26 | This script will copy the specified file to the input provider, run it, and pull back TensorBoard results to `/tmp/tensorboard`. Run `tensorboard --logdir=/tmp/tensorboard` on the local host to view them. 27 | 28 | ## Other 29 | 30 | Script `ps.sh` can be used to verify that a server is running on each instance. Script `pull.sh` will update the GitHub repository on each instance. -------------------------------------------------------------------------------- /tensorflow/spdz/configs/gcp/link.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import json 4 | import subprocess 5 | 6 | BASE_PORT = 4440 7 | HOSTS_FILE = '/tmp/hosts.json' 8 | INSTANCE_NAMES = [ 9 | 'server0', 10 | 'server1', 11 | 'cryptoproducer', 12 | 'inputoutput' 13 | ] 14 | CMD_INTERNAL_HOST = "gcloud --format='value(networkInterfaces[0].networkIP)' compute instances list {instance_name}" 15 | CMD_HOSTS_SCP = "gcloud compute scp {hosts_file} {instance_name}:{hosts_file}" 16 | 17 | HOSTS = [ 18 | subprocess.check_output(CMD_INTERNAL_HOST.format(instance_name=instance_name), shell=True).strip() 19 | for instance_name in INSTANCE_NAMES 20 | ] 21 | 22 | HOSTS = [ 23 | host + ':' + str(BASE_PORT + index) 24 | for index, host in enumerate(HOSTS) 25 | ] 26 | 27 | print HOSTS 28 | 29 | with open(HOSTS_FILE, 'w') as f: 30 | json.dump(HOSTS, f) 31 | 32 | for instance_name in INSTANCE_NAMES: 33 | subprocess.call(CMD_HOSTS_SCP.format(hosts_file=HOSTS_FILE, instance_name=instance_name), shell=True) -------------------------------------------------------------------------------- /tensorflow/spdz/configs/gcp/ps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | gcloud compute ssh server0 --command='ps au | egrep python2' 4 | gcloud compute ssh server1 --command='ps au | egrep python2' 5 | gcloud compute ssh cryptoproducer --command='ps au | egrep python2' 6 | gcloud compute ssh inputoutput --command='ps au | egrep python2' -------------------------------------------------------------------------------- /tensorflow/spdz/configs/gcp/pull.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | gcloud compute ssh server0 --command='cd privateml && git reset --hard && git pull' 4 | gcloud compute ssh server1 --command='cd privateml && git reset --hard && git pull' 5 | gcloud compute ssh cryptoproducer --command='cd privateml && git reset --hard && git pull' 6 | gcloud compute ssh inputoutput --command='cd privateml && git reset --hard && git pull' 7 | -------------------------------------------------------------------------------- /tensorflow/spdz/configs/gcp/restart.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | gcloud compute ssh server0 --command='killall python2' 4 | gcloud compute ssh server1 --command='killall python2' 5 | gcloud compute ssh cryptoproducer --command='killall python2' 6 | gcloud compute ssh inputoutput --command='killall python2' 7 | 8 | gcloud compute ssh server0 --command='screen -dmS tensorspdz python2 ~/role.py 0' 9 | gcloud compute ssh server1 --command='screen -dmS tensorspdz python2 ~/role.py 1' 10 | gcloud compute ssh cryptoproducer --command='screen -dmS tensorspdz python2 ~/role.py 2' 11 | gcloud compute ssh inputoutput --command='screen -dmS tensorspdz python2 ~/role.py 3' 12 | 13 | gcloud compute instances list -------------------------------------------------------------------------------- /tensorflow/spdz/configs/gcp/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -rf /tmp/tensorboard 4 | rm *.pyc 5 | 6 | gcloud compute ssh inputoutput --command='rm -rf /tmp/tensorboard; rm -f ~/*.pyc' 7 | gcloud compute scp $1 inputoutput:./main.py 8 | gcloud compute ssh inputoutput --command='python2 main.py' 9 | gcloud compute scp --recurse inputoutput:/tmp/tensorboard /tmp/tensorboard 10 | -------------------------------------------------------------------------------- /tensorflow/spdz/configs/gcp/server/config.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | import json 3 | import os 4 | 5 | HOSTS_FILE = '/tmp/hosts.json' 6 | 7 | SESSION_CONFIG = tf.ConfigProto( 8 | log_device_placement=True, 9 | allow_soft_placement=False 10 | ) 11 | 12 | JOB_NAME = 'spdz' 13 | 14 | # SERVER_0 = '/job:{}/task:0'.format(JOB_NAME) 15 | # SERVER_1 = '/job:{}/task:1'.format(JOB_NAME) 16 | # CRYPTO_PRODUCER = '/job:{}/task:2'.format(JOB_NAME) 17 | # INPUT_PROVIDER = '/job:{}/task:3'.format(JOB_NAME) 18 | # OUTPUT_RECEIVER = '/job:{}/task:3'.format(JOB_NAME) 19 | 20 | SERVER_0 = '/job:{}/replica:0/task:0/cpu:0'.format(JOB_NAME) 21 | SERVER_1 = '/job:{}/replica:0/task:1/cpu:0'.format(JOB_NAME) 22 | CRYPTO_PRODUCER = '/job:{}/replica:0/task:2/cpu:0'.format(JOB_NAME) 23 | INPUT_PROVIDER = '/job:{}/replica:0/task:3/cpu:0'.format(JOB_NAME) 24 | OUTPUT_RECEIVER = '/job:{}/replica:0/task:3/cpu:0'.format(JOB_NAME) 25 | 26 | with open(HOSTS_FILE, 'r') as f: 27 | HOSTS = json.load(f) 28 | 29 | MASTER = 'grpc://{}'.format(HOSTS[0]) 30 | 31 | CLUSTER = tf.train.ClusterSpec({ 32 | JOB_NAME: HOSTS 33 | }) 34 | 35 | TENSORBOARD_DIR = '/tmp/tensorboard' 36 | 37 | session = lambda: tf.Session(MASTER, config=SESSION_CONFIG) -------------------------------------------------------------------------------- /tensorflow/spdz/configs/gcp/server/monitor.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # sudo tcptrack -i eth0 -r 60 4 | sudo iftop -i eth0 -B -------------------------------------------------------------------------------- /tensorflow/spdz/configs/gcp/server/role.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import sys 4 | import tensorflow as tf 5 | from config import CLUSTER, JOB_NAME 6 | 7 | TASK = int(sys.argv[1]) 8 | 9 | server = tf.train.Server(CLUSTER, job_name=JOB_NAME, task_index=TASK) 10 | server.start() 11 | server.join() -------------------------------------------------------------------------------- /tensorflow/spdz/configs/gcp/setup/debian-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo 'export LANGUAGE=en_US.UTF-8' >> .bashrc 4 | echo 'export LANG=en_US.UTF-8' >> .bashrc 5 | echo 'export LC_ALL=en_US.UTF-8' >> .bashrc 6 | 7 | # install OS packages 8 | sudo apt-get update 9 | sudo apt-get -y upgrade 10 | sudo apt-get install -y python-pip 11 | sudo apt-get install -y git 12 | sudo apt-get install -y psmisc # for killall 13 | sudo apt-get install -y tcptrack iftop # for network monitoring 14 | 15 | # install python packages 16 | sudo pip install numpy tensorflow tensorboard 17 | 18 | # repo 19 | cd ~ 20 | git clone https://github.com/mortendahl/privateml.git 21 | 22 | # links 23 | ln -s ~/privateml/tensorflow/spdz/tensorspdz.py ~/tensorspdz.py 24 | ln -s ~/privateml/tensorflow/spdz/configs/gcp/server/config.py ~/config.py 25 | ln -s ~/privateml/tensorflow/spdz/configs/gcp/server/role.py ~/role.py 26 | ln -s ~/privateml/tensorflow/spdz/configs/gcp/server/monitor.sh ~/monitor.sh 27 | -------------------------------------------------------------------------------- /tensorflow/spdz/configs/gcp/setup/gcp-create-images.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # create base instance for generating template image 4 | gcloud compute instances create tensorspdz-template \ 5 | --image-project debian-cloud \ 6 | --image-family debian-9 \ 7 | --boot-disk-size 10GB \ 8 | --custom-cpu 2 \ 9 | --custom-memory 10 10 | 11 | # install software on it 12 | gcloud compute ssh tensorspdz-template < debian-install.sh 13 | 14 | # turn its disk into an image and clean up 15 | gcloud compute instances delete tensorspdz-template --keep-disks=boot --quiet 16 | gcloud compute images create tensorspdz-image --source-disk tensorspdz-template 17 | gcloud compute disks delete tensorspdz-template --quiet -------------------------------------------------------------------------------- /tensorflow/spdz/configs/gcp/setup/gcp-create-instances.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | gcloud compute instances create \ 4 | server0 server1 cryptoproducer inputoutput \ 5 | --image tensorspdz-image \ 6 | --custom-cpu 2 \ 7 | --custom-memory 10 8 | -------------------------------------------------------------------------------- /tensorflow/spdz/configs/gcp/setup/gcp-create.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | sh ./gcp-create-images.sh 4 | sh ./gcp-create-instances.sh 5 | 6 | gcloud compute instances stop \ 7 | server0 server1 cryptoproducer inputoutput 8 | -------------------------------------------------------------------------------- /tensorflow/spdz/configs/gcp/setup/gcp-destroy-images.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # clean up from creation, in case it got aborted 4 | gcloud compute instances delete tensorspdz-template --quiet 5 | gcloud compute disks delete tensorspdz-template --quiet 6 | 7 | # actual purpose 8 | gcloud compute images delete tensorspdz-image --quiet -------------------------------------------------------------------------------- /tensorflow/spdz/configs/gcp/setup/gcp-destroy-instances.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | gcloud compute instances delete \ 4 | server0 server1 cryptoproducer inputoutput \ 5 | --quiet -------------------------------------------------------------------------------- /tensorflow/spdz/configs/gcp/setup/gcp-destroy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | sh ./gcp-destroy-instances.sh 4 | sh ./gcp-destroy-images.sh -------------------------------------------------------------------------------- /tensorflow/spdz/configs/gcp/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | gcloud compute instances start \ 4 | server0 server1 cryptoproducer inputoutput 5 | 6 | ./link.py 7 | 8 | gcloud compute ssh server0 --command='screen -dmS tensorspdz python2 ~/role.py 0' 9 | gcloud compute ssh server1 --command='screen -dmS tensorspdz python2 ~/role.py 1' 10 | gcloud compute ssh cryptoproducer --command='screen -dmS tensorspdz python2 ~/role.py 2' 11 | gcloud compute ssh inputoutput --command='screen -dmS tensorspdz python2 ~/role.py 3' 12 | 13 | gcloud compute instances list -------------------------------------------------------------------------------- /tensorflow/spdz/configs/gcp/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | gcloud compute instances stop \ 4 | server0 server1 cryptoproducer inputoutput 5 | 6 | gcloud compute instances list -------------------------------------------------------------------------------- /tensorflow/spdz/configs/localhost/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Localhost configuration 3 | 4 | This is the easiest way to get started since very little setup is required. But since all players are running on the same machine we of course don't have the desired security properties nor accurate performance measures. 5 | 6 | ## Setup 7 | 8 | To use this configuration we first specify that we wish to it by symlinking `config.py` and `run.sh` to the root directory: 9 | 10 | ```sh 11 | spdz $ ln -s configs/localhost/config.py 12 | spdz $ ln -s configs/localhost/run.sh 13 | ``` 14 | 15 | and simply start the five local processes: 16 | 17 | ```sh 18 | spdz $ cd configs/localhost/ 19 | localhost $ ./start.sh 20 | ``` 21 | 22 | This will block the current terminal, so to actually execute the code we need another terminal. 23 | 24 | ## Running 25 | 26 | Once setup simply execute 27 | 28 | ```sh 29 | spdz $ ./run.sh 30 | ``` 31 | 32 | ## Cleanup 33 | 34 | A script is also provided to stop the processes again (but be a bit careful as it's currently not doing so in a particularly nice way): 35 | 36 | ```sh 37 | spdz $ cd configs/localhost/ 38 | localhost $ ./stop.sh 39 | ``` 40 | 41 | At this point it's safe to delete the symlink in case you e.g. wish to experiment with another configuration: 42 | 43 | ```sh 44 | spdz $ rm config.py \ 45 | rm run.sh 46 | ``` 47 | -------------------------------------------------------------------------------- /tensorflow/spdz/configs/localhost/config.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | 3 | SESSION_CONFIG = tf.ConfigProto( 4 | log_device_placement=True, 5 | allow_soft_placement=False 6 | ) 7 | 8 | JOB_NAME = 'spdz' 9 | 10 | SERVER_0 = '/job:{}/replica:0/task:0/cpu:0'.format(JOB_NAME) 11 | SERVER_1 = '/job:{}/replica:0/task:1/cpu:0'.format(JOB_NAME) 12 | CRYPTO_PRODUCER = '/job:{}/replica:0/task:2/cpu:0'.format(JOB_NAME) 13 | INPUT_PROVIDER = '/job:{}/replica:0/task:3/cpu:0'.format(JOB_NAME) 14 | OUTPUT_RECEIVER = '/job:{}/replica:0/task:4/cpu:0'.format(JOB_NAME) 15 | 16 | HOSTS = [ 17 | 'localhost:4440', 18 | 'localhost:4441', 19 | 'localhost:4442', 20 | 'localhost:4443', 21 | 'localhost:4444' 22 | ] 23 | 24 | MASTER = 'grpc://{}'.format(HOSTS[0]) 25 | 26 | CLUSTER = tf.train.ClusterSpec({ 27 | JOB_NAME: HOSTS 28 | }) 29 | 30 | TENSORBOARD_DIR = '/tmp/tensorboard' 31 | 32 | session = lambda: tf.Session(MASTER, config=SESSION_CONFIG) -------------------------------------------------------------------------------- /tensorflow/spdz/configs/localhost/cryptoproducer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import tensorflow as tf 4 | from config import CLUSTER, JOB_NAME 5 | 6 | server = tf.train.Server(CLUSTER, job_name=JOB_NAME, task_index=2) 7 | server.start() 8 | server.join() -------------------------------------------------------------------------------- /tensorflow/spdz/configs/localhost/inputprovider.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import tensorflow as tf 4 | from config import CLUSTER, JOB_NAME 5 | 6 | server = tf.train.Server(CLUSTER, job_name=JOB_NAME, task_index=3) 7 | server.start() 8 | server.join() -------------------------------------------------------------------------------- /tensorflow/spdz/configs/localhost/outputreceiver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import tensorflow as tf 4 | from config import CLUSTER, JOB_NAME 5 | 6 | server = tf.train.Server(CLUSTER, job_name=JOB_NAME, task_index=4) 7 | server.start() 8 | server.join() -------------------------------------------------------------------------------- /tensorflow/spdz/configs/localhost/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -r /tmp/tensorboard 4 | rm *.pyc 5 | 6 | python2 $1 -------------------------------------------------------------------------------- /tensorflow/spdz/configs/localhost/server0.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import tensorflow as tf 4 | from config import CLUSTER, JOB_NAME 5 | 6 | server = tf.train.Server(CLUSTER, job_name=JOB_NAME, task_index=0) 7 | server.start() 8 | server.join() -------------------------------------------------------------------------------- /tensorflow/spdz/configs/localhost/server1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import tensorflow as tf 4 | from config import CLUSTER, JOB_NAME 5 | 6 | server = tf.train.Server(CLUSTER, job_name=JOB_NAME, task_index=1) 7 | server.start() 8 | server.join() -------------------------------------------------------------------------------- /tensorflow/spdz/configs/localhost/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | python2 server0.py & 4 | python2 server1.py & 5 | python2 cryptoproducer.py & 6 | python2 inputprovider.py & 7 | python2 outputreceiver.py -------------------------------------------------------------------------------- /tensorflow/spdz/configs/localhost/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | kill $(ps aux | egrep -i 'python2? server0.py' | egrep -v 'egrep' | awk '{print $2}') || true 4 | kill $(ps aux | egrep -i 'python2? server1.py' | egrep -v 'egrep' | awk '{print $2}') || true 5 | kill $(ps aux | egrep -i 'python2? cryptoproducer.py' | egrep -v 'egrep' | awk '{print $2}') || true 6 | kill $(ps aux | egrep -i 'python2? inputprovider.py' | egrep -v 'egrep' | awk '{print $2}') || true 7 | kill $(ps aux | egrep -i 'python2? outputreceiver.py' | egrep -v 'egrep' | awk '{print $2}') || true -------------------------------------------------------------------------------- /tensorflow/spdz/logistic-regression-simple/prediction.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | # hack to import tensorspdz from parent directory 4 | # - https://stackoverflow.com/questions/714063/importing-modules-from-parent-folder 5 | import sys, os 6 | sys.path.insert(1, os.path.join(sys.path[0], '..')) 7 | 8 | 9 | from datetime import datetime 10 | 11 | from config import session, TENSORBOARD_DIR 12 | from tensorspdz import * 13 | 14 | from tensorflow.python.client import timeline 15 | 16 | 17 | np_sigmoid = lambda x: 1 / (1 + np.exp(-x)) 18 | 19 | ############################## 20 | # Generate training data # 21 | ############################## 22 | 23 | def generate_dataset(size, num_features): 24 | assert size % 2 == 0 25 | 26 | np.random.seed(42) 27 | 28 | # X0 = np.random.multivariate_normal([0, 0], [[1, .75],[.75, 1]], size//2) 29 | # X1 = np.random.multivariate_normal([1, 4], [[1, .75],[.75, 1]], size//2) 30 | # X = np.vstack((X0, X1)).astype(np.float32) 31 | 32 | X = np.random.random_sample((size, num_features)) 33 | 34 | Y0 = np.zeros(size//2).reshape(-1, 1) 35 | Y1 = np.ones (size//2).reshape(-1, 1) 36 | Y = np.vstack((Y0, Y1)).astype(np.float32) 37 | 38 | # shuffle 39 | perm = np.random.permutation(len(X)) 40 | X = X[perm] 41 | Y = Y[perm] 42 | 43 | return X, Y 44 | 45 | ############################## 46 | # Public training # 47 | ############################## 48 | 49 | def public_training(X, Y, batch_size=100, learning_rate=0.01, epochs=10): 50 | 51 | data_size, num_features = X.shape 52 | assert data_size % batch_size == 0 53 | 54 | num_batches = data_size // batch_size 55 | 56 | batches_X = [ X[batch_size*batch_index : batch_size*(batch_index+1)] for batch_index in range(num_batches) ] 57 | batches_Y = [ Y[batch_size*batch_index : batch_size*(batch_index+1)] for batch_index in range(num_batches) ] 58 | 59 | W = np.zeros((num_features, 1)) 60 | B = np.zeros((1, 1)) 61 | 62 | for _ in range(epochs): 63 | 64 | for X_batch, Y_batch in zip(batches_X, batches_Y): 65 | 66 | # forward 67 | Y_pred = np_sigmoid(np.dot(X_batch, W) + B) 68 | 69 | # backward 70 | error = Y_pred - Y_batch 71 | d_W = np.dot(X_batch.transpose(), error) * 1./batch_size 72 | d_B = np.sum(error, axis=0) * 1./batch_size 73 | W = W - d_W * learning_rate 74 | B = B - d_B * learning_rate 75 | 76 | preds = np.round(np_sigmoid(np.dot(X, W) + B)) 77 | accuracy = (preds == Y).sum().astype(float) / len(preds) 78 | print 'accuracy:', accuracy 79 | 80 | return W, B 81 | 82 | ############################## 83 | # Public prediction # 84 | ############################## 85 | 86 | def setup_public_prediction(W, B, shape_X): 87 | 88 | def public_prediction(X): 89 | X = X.reshape(shape_X) 90 | return np_sigmoid(np.dot(X, W) + B) 91 | 92 | return public_prediction 93 | 94 | ############################## 95 | # Private prediction # 96 | ############################## 97 | 98 | def setup_private_prediction(W, B, shape_X): 99 | 100 | input_x, x = define_input(shape_X, name='x') 101 | 102 | w = define_variable(W, name='w') 103 | b = define_variable(B, name='b') 104 | 105 | # w = cache(mask(w)) 106 | # b = cache(b) 107 | y = sigmoid(add(dot(x, w), b)) 108 | 109 | def private_prediction(X): 110 | X = X.reshape(shape_X) 111 | 112 | with session() as sess: 113 | 114 | writer = tf.summary.FileWriter(TENSORBOARD_DIR, sess.graph) 115 | run_options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE) 116 | run_metadata = tf.RunMetadata() 117 | 118 | def write_metadata(tag): 119 | writer.add_run_metadata(run_metadata, tag) 120 | chrome_trace = timeline.Timeline(run_metadata.step_stats).generate_chrome_trace_format() 121 | with open('{}/{}.ctr'.format(TENSORBOARD_DIR, tag), 'w') as f: 122 | f.write(chrome_trace) 123 | 124 | sess.run( 125 | tf.global_variables_initializer(), 126 | options=run_options, 127 | run_metadata=run_metadata 128 | ) 129 | write_metadata('init') 130 | 131 | sess.run( 132 | cache_updators, 133 | options=run_options, 134 | run_metadata=run_metadata 135 | ) 136 | write_metadata('populate') 137 | 138 | for i in range(10): 139 | 140 | y_pred = sess.run( 141 | reveal(y), 142 | feed_dict=encode_input((input_x, X)), 143 | options=run_options, 144 | run_metadata=run_metadata 145 | ) 146 | write_metadata('predict-{}'.format(i)) 147 | 148 | y_pred_private = decode_output(y_pred) 149 | 150 | writer.close() 151 | 152 | return y_pred_private 153 | 154 | return private_prediction 155 | 156 | ############################## 157 | # Running # 158 | ############################## 159 | 160 | X, Y = generate_dataset(10000, 100) 161 | W, B = public_training(X, Y) 162 | 163 | # predict on batch size of 100 164 | private_input = X[:100] 165 | 166 | public_prediction = setup_public_prediction(W, B, shape_X=private_input.shape) 167 | private_prediction = setup_private_prediction(W, B, shape_X=private_input.shape) 168 | 169 | y_pred_public = public_prediction(private_input) 170 | y_pred_private = private_prediction(private_input) 171 | 172 | print y_pred_public[:10], len(y_pred_public) 173 | print y_pred_private[:10], len(y_pred_private) 174 | 175 | assert (abs(y_pred_private - y_pred_public) < 0.01).all(), y_pred_private 176 | -------------------------------------------------------------------------------- /tensorflow/spdz/logistic-regression-simple/training.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | # hack to import tensorspdz from parent directory 4 | # - https://stackoverflow.com/questions/714063/importing-modules-from-parent-folder 5 | import sys, os 6 | sys.path.insert(1, os.path.join(sys.path[0], '..')) 7 | 8 | 9 | from datetime import datetime 10 | 11 | from config import session, TENSORBOARD_DIR 12 | from tensorspdz import * 13 | 14 | from tensorflow.python.client import timeline 15 | 16 | 17 | np_sigmoid = lambda x: 1 / (1 + np.exp(-x)) 18 | 19 | ############################## 20 | # Generate training data # 21 | ############################## 22 | 23 | np.random.seed(42) 24 | 25 | data_size = 10000 26 | assert data_size % 2 == 0 27 | 28 | X0 = np.random.multivariate_normal([0, 0], [[1, .75],[.75, 1]], data_size//2) 29 | X1 = np.random.multivariate_normal([1, 4], [[1, .75],[.75, 1]], data_size//2) 30 | X = np.vstack((X0, X1)).astype(np.float32) 31 | 32 | # augment x with all-one column 33 | B = np.ones(data_size).reshape(-1, 1) 34 | X = np.hstack((X, B)) 35 | 36 | Y0 = np.zeros(data_size//2).reshape(-1, 1) 37 | Y1 = np.ones (data_size//2).reshape(-1, 1) 38 | Y = np.vstack((Y0, Y1)).astype(np.float32) 39 | 40 | # shuffle 41 | perm = np.random.permutation(len(X)) 42 | X = X[perm] 43 | Y = Y[perm] 44 | 45 | batch_size = 100 46 | assert data_size % batch_size == 0 47 | num_batches = data_size // batch_size 48 | 49 | batches_x = [ X[batch_size*batch_index : batch_size*(batch_index+1)] for batch_index in range(num_batches) ] 50 | batches_y = [ Y[batch_size*batch_index : batch_size*(batch_index+1)] for batch_index in range(num_batches) ] 51 | 52 | shape_x = (batch_size,3) 53 | shape_y = (batch_size,1) 54 | shape_w = (3,1) 55 | 56 | ############################## 57 | # training # 58 | ############################## 59 | 60 | learning_rate = 0.01 61 | epochs = 10 62 | 63 | def accuracy(w): 64 | preds = np.round(np_sigmoid(np.dot(X, w))) 65 | return (preds == Y).sum().astype(float) / len(preds) 66 | 67 | ############################## 68 | # Public training # 69 | ############################## 70 | 71 | def public_training(): 72 | 73 | w = np.zeros(shape_w) 74 | 75 | start = datetime.now() 76 | 77 | for _ in range(epochs): 78 | for x, y in zip(batches_x, batches_y): 79 | # forward 80 | y_pred = np_sigmoid(np.dot(x, w)) 81 | # backward 82 | error = y_pred - y 83 | gradients = np.dot(x.transpose(), error) * 1./batch_size 84 | w = w - gradients * learning_rate 85 | 86 | end = datetime.now() 87 | print end-start 88 | 89 | print w, accuracy(w) 90 | 91 | return w 92 | 93 | public_training() 94 | 95 | ############################## 96 | # Private training # 97 | ############################## 98 | 99 | def define_batch_buffer(shape, capacity, varname): 100 | 101 | # each server holds three values: xi, ai, and alpha 102 | server_packed_shape = (3, len(m),) + tuple(shape) 103 | 104 | # the crypto producer holds just one value: a 105 | cryptoprovider_packed_shape = (len(m),) + tuple(shape) 106 | 107 | with tf.device(SERVER_0): 108 | queue_0 = tf.FIFOQueue( 109 | capacity=capacity, 110 | dtypes=[INT_TYPE], 111 | shapes=[server_packed_shape], 112 | name='buffer_{}_0'.format(varname), 113 | ) 114 | 115 | with tf.device(SERVER_1): 116 | queue_1 = tf.FIFOQueue( 117 | capacity=capacity, 118 | dtypes=[INT_TYPE], 119 | shapes=[server_packed_shape], 120 | name='buffer_{}_1'.format(varname), 121 | ) 122 | 123 | with tf.device(CRYPTO_PRODUCER): 124 | queue_cp = tf.FIFOQueue( 125 | capacity=capacity, 126 | dtypes=[INT_TYPE], 127 | shapes=[cryptoprovider_packed_shape], 128 | name='buffer_{}_cp'.format(varname), 129 | ) 130 | 131 | return (queue_0, queue_1, queue_cp) 132 | 133 | def pack_server(tensors): 134 | with tf.name_scope('pack'): 135 | return tf.stack([ tf.stack(tensor, axis=0) for tensor in tensors ], axis=0) 136 | 137 | def unpack_server(shape, tensors): 138 | with tf.name_scope('unpack'): 139 | return [ 140 | [ 141 | tf.reshape(subtensor, shape) 142 | for subtensor in tf.split(tf.reshape(tensor, (len(m),) + shape), len(m)) 143 | ] 144 | for tensor in tf.split(tensors, 3) 145 | ] 146 | 147 | def pack_cryptoproducer(tensor): 148 | with tf.name_scope('pack'): 149 | return tf.stack(tensor, axis=0) 150 | 151 | def unpack_cryptoproducer(shape, tensor): 152 | with tf.name_scope('unpack'): 153 | return [ 154 | tf.reshape(subtensor, shape) 155 | for subtensor in tf.split(tensor, len(m)) 156 | ] 157 | 158 | def distribute_batch(shape, buffer, varname): 159 | 160 | queue_0, queue_1, queue_cp = buffer 161 | 162 | with tf.name_scope('distribute_{}'.format(varname)): 163 | 164 | input_x = [ tf.placeholder(INT_TYPE, shape=shape) for _ in m ] 165 | 166 | with tf.device(INPUT_PROVIDER): 167 | with tf.name_scope('preprocess'): 168 | # share x 169 | x0, x1 = share(input_x) 170 | # precompute mask 171 | a = sample(shape) 172 | a0, a1 = share(a) 173 | alpha = crt_sub(input_x, a) 174 | 175 | with tf.device(SERVER_0): 176 | enqueue_0 = queue_0.enqueue(pack_server([x0, a0, alpha])) 177 | 178 | with tf.device(SERVER_1): 179 | enqueue_1 = queue_1.enqueue(pack_server([x1, a1, alpha])) 180 | 181 | with tf.device(CRYPTO_PRODUCER): 182 | enqueue_cp = queue_cp.enqueue(pack_cryptoproducer(a)) 183 | 184 | populate_op = [enqueue_0, enqueue_1, enqueue_cp] 185 | 186 | return input_x, populate_op 187 | 188 | def load_batch(shape, buffer, varname): 189 | 190 | shape = tuple(shape) 191 | queue_0, queue_1, queue_cp = buffer 192 | 193 | with tf.name_scope('load_batch_{}'.format(varname)): 194 | 195 | with tf.device(SERVER_0): 196 | packed_0 = queue_0.dequeue() 197 | x0, a0, alpha_on_0 = unpack_server(shape, packed_0) 198 | 199 | with tf.device(SERVER_1): 200 | packed_1 = queue_1.dequeue() 201 | x1, a1, alpha_on_1 = unpack_server(shape, packed_1) 202 | 203 | with tf.device(CRYPTO_PRODUCER): 204 | packed_cp = queue_cp.dequeue() 205 | a = unpack_cryptoproducer(shape, packed_cp) 206 | 207 | x = PrivateTensor(x0, x1) 208 | 209 | node_key = ('mask', x) 210 | nodes[node_key] = (a, a0, a1, alpha_on_0, alpha_on_1) 211 | 212 | return x 213 | 214 | def training_loop(buffers, shapes, iterations, initial_weights, training_step): 215 | 216 | buffer_x, buffer_y = buffers 217 | shape_x, shape_y = shapes 218 | 219 | initial_w0, initial_w1 = share(initial_weights) 220 | 221 | def loop_op(w0, w1): 222 | w = PrivateTensor(w0, w1) 223 | x = load_batch(shape_x, buffer_x, varname='x') 224 | y = load_batch(shape_y, buffer_y, varname='y') 225 | new_w = training_step(w, x, y) 226 | return new_w.share0, new_w.share1 227 | 228 | _, final_w0, final_w1 = tf.while_loop( 229 | cond=lambda i, w0, w1: tf.less(i, iterations), 230 | body=lambda i, w0, w1: (i+1,) + loop_op(w0, w1), 231 | loop_vars=(0, initial_w0, initial_w1), 232 | parallel_iterations=1 233 | ) 234 | 235 | return final_w0, final_w1 236 | 237 | buffer_x = define_batch_buffer(shape_x, num_batches, varname='x') 238 | buffer_y = define_batch_buffer(shape_y, num_batches, varname='y') 239 | 240 | input_x, distribute_x = distribute_batch(shape_x, buffer_x, varname='x') 241 | input_y, distribute_y = distribute_batch(shape_y, buffer_y, varname='y') 242 | 243 | def training_step(w, x, y): 244 | with tf.name_scope('forward'): 245 | y_pred = sigmoid(dot(x, w)) 246 | with tf.name_scope('backward'): 247 | error = sub(y_pred, y) 248 | gradients = scale(dot(transpose(x), error), 1./batch_size) 249 | return sub(w, scale(gradients, learning_rate)) 250 | 251 | training = training_loop( 252 | buffers=(buffer_x, buffer_y), 253 | shapes=(shape_x, shape_y), 254 | iterations=num_batches, 255 | initial_weights=decompose(np.zeros(shape=shape_w)), 256 | training_step=training_step 257 | ) 258 | 259 | ############################## 260 | # Private prediction # 261 | ############################## 262 | 263 | # def private_prediction(sess, w): 264 | 265 | # INPUT_SIZE = 300 266 | 267 | # input_x, x = define_input((INPUT_SIZE,3)) 268 | 269 | # y = reveal(sigmoid(dot(x, w))) 270 | 271 | # writer = tf.summary.FileWriter(TENSORBOARD_DIR, sess.graph) 272 | # run_options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE) 273 | # run_metadata = tf.RunMetadata() 274 | 275 | # for i in range(10): 276 | 277 | # Y = sess.run( 278 | # y, 279 | # feed_dict=dict( 280 | # [ (xi, Xi) for xi, Xi in zip(input_x, decompose(encode(X[i:i+INPUT_SIZE].reshape(INPUT_SIZE,3)))) ] 281 | # ), 282 | # options=run_options, 283 | # run_metadata=run_metadata 284 | # ) 285 | # # print decode(recombine(Y)) 286 | 287 | # writer.add_run_metadata(run_metadata, 'prediction-{}'.format(i)) 288 | 289 | # chrome_trace = timeline.Timeline(run_metadata.step_stats).generate_chrome_trace_format() 290 | # with open('{}/{}.ctr.json'.format(TENSORBOARD_DIR, 'prediction-{}'.format(i)), 'w') as f: 291 | # f.write(chrome_trace) 292 | 293 | # writer.close() 294 | 295 | # return Y 296 | 297 | ############################## 298 | # Run # 299 | ############################## 300 | 301 | with session() as sess: 302 | 303 | writer = tf.summary.FileWriter(TENSORBOARD_DIR, sess.graph) 304 | run_options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE) 305 | run_metadata = tf.RunMetadata() 306 | 307 | print 'Distributing...' 308 | for batch_index, (batch_x, batch_y) in enumerate(zip(batches_x, batches_y)): 309 | 310 | sess.run( 311 | distribute_x, 312 | feed_dict=dict([ 313 | (input_xi, Xi) for input_xi, Xi in zip(input_x, decompose(encode(batch_x))) 314 | ]), 315 | options=run_options, 316 | run_metadata=run_metadata 317 | ) 318 | writer.add_run_metadata(run_metadata, 'distribute-x-{}'.format(batch_index)) 319 | 320 | sess.run( 321 | distribute_y, 322 | feed_dict=dict([ 323 | (input_yi, Yi) for input_yi, Yi in zip(input_y, decompose(encode(batch_y))) 324 | ]), 325 | options=run_options, 326 | run_metadata=run_metadata 327 | ) 328 | writer.add_run_metadata(run_metadata, 'distribute-y-{}'.format(batch_index)) 329 | 330 | print 'Training...' 331 | w0, w1 = sess.run( 332 | training, 333 | options=run_options, 334 | run_metadata=run_metadata 335 | ) 336 | writer.add_run_metadata(run_metadata, 'train') 337 | chrome_trace = timeline.Timeline(run_metadata.step_stats).generate_chrome_trace_format() 338 | with open('{}/{}.ctr.json'.format(TENSORBOARD_DIR, 'train'), 'w') as f: 339 | f.write(chrome_trace) 340 | 341 | w = decode(recombine(reconstruct(w0, w1))) 342 | print w, accuracy(w) 343 | 344 | # y = prediction(sess) 345 | 346 | writer.close() -------------------------------------------------------------------------------- /tensorflow/spdz/playground/config.py: -------------------------------------------------------------------------------- 1 | ../configs/gcp/server/config.py -------------------------------------------------------------------------------- /tensorflow/spdz/playground/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | # hack to import tensorspdz from parent directory 4 | # - https://stackoverflow.com/questions/714063/importing-modules-from-parent-folder 5 | import sys, os 6 | sys.path.insert(1, os.path.join(sys.path[0], '..')) 7 | 8 | 9 | from datetime import datetime 10 | 11 | from config import MASTER, SESSION_CONFIG, TENSORBOARD_DIR 12 | from tensorspdz import * 13 | 14 | from tensorflow.python.client import timeline 15 | 16 | 17 | # Inputs 18 | input_x, x = define_input((100,100)) 19 | input_y, y = define_input((100,100)) 20 | input_z, z = define_input((100,100)) 21 | 22 | # Computation 23 | # v = reveal(dot(x, y) + dot(x, z)) 24 | # v = reveal(square(x) + square(y) + square(z)) 25 | v = reveal(square(x)) 26 | 27 | # Actual inputs 28 | X = np.random.randn(100,100) 29 | Y = np.random.randn(100,100) 30 | Z = np.random.randn(100,100) 31 | 32 | # Decomposed values outside Tensorflow 33 | inputs = dict( 34 | [ (xi, Xi) for xi, Xi in zip(input_x, decompose(encode(X))) ] + 35 | [ (yi, Yi) for yi, Yi in zip(input_y, decompose(encode(Y))) ] + 36 | [ (zi, Zi) for zi, Zi in zip(input_z, decompose(encode(Z))) ] 37 | ) 38 | 39 | # Run computation using Tensorflow 40 | with tf.Session(MASTER, config=SESSION_CONFIG) as sess: 41 | 42 | writer = tf.summary.FileWriter(TENSORBOARD_DIR, sess.graph) 43 | run_options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE) 44 | run_metadata = tf.RunMetadata() 45 | 46 | sess.run(tf.global_variables_initializer()) 47 | 48 | durations = [] 49 | for i in range(10): 50 | 51 | start = datetime.now() 52 | res = sess.run( 53 | v, 54 | inputs, 55 | options=run_options, 56 | run_metadata=run_metadata 57 | ) 58 | end = datetime.now() 59 | durations.append(end - start) 60 | 61 | writer.add_run_metadata(run_metadata, 'run-{}'.format(i)) 62 | 63 | chrome_trace = timeline \ 64 | .Timeline(run_metadata.step_stats) \ 65 | .generate_chrome_trace_format() 66 | with open('{}/timeline_{}.ctr.json'.format(TENSORBOARD_DIR, i), 'w') as f: 67 | f.write(chrome_trace) 68 | 69 | for duration in durations: 70 | print(duration) 71 | 72 | writer.close() 73 | 74 | # Recover result outside Tensorflow 75 | V = decode(recombine(res)) 76 | 77 | # expected = np.dot(X, Y) + np.dot(X, Z) 78 | # expected = X * X + Y * Y + Z * Z 79 | expected = X * X 80 | actual = V 81 | diff = expected - actual 82 | assert (abs(diff) < 1e-2).all(), abs(diff).max() -------------------------------------------------------------------------------- /tensorflow/spdz/playground/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -r /tmp/tensorflow 4 | rm *.pyc 5 | 6 | python2 main.py -------------------------------------------------------------------------------- /tensorflow/spdz/run.sh: -------------------------------------------------------------------------------- 1 | configs/gcp/run.sh -------------------------------------------------------------------------------- /tensorflow/spdz/simple-dense/README.md: -------------------------------------------------------------------------------- 1 | # Dense layer example 2 | 3 | ## Installation 4 | 5 | We first need a few Python libraries: 6 | 7 | ```sh 8 | $ pip install numpy tensorflow tensorboard 9 | ``` 10 | 11 | Git should have automatically created two symlinks in this directory, `config.py` and `tensorspdz.py`, that points to support files, but if not then run 12 | 13 | ```sh 14 | simple-dense $ ln -s ../config/localhost/config.py config.py 15 | simple-dense $ ln -s ../tensorspdz.py tensorspdz.py 16 | ``` 17 | 18 | 19 | ## Starting the services 20 | 21 | To run this example we need to run the five parties given in `config/localhost/`. These can be launched manually via e.g. `python2 server0.py` but there's also a convenient `start-services.sh` script that will launch all at once. Likewise, `stop-services.sh` may be used to stop all five afterwards. 22 | 23 | ```sh 24 | localhost $ ./start-services.sh 25 | localhost $ ./stop-services.sh 26 | ``` 27 | 28 | ## Running 29 | 30 | With all services running simply run 31 | 32 | ```sh 33 | simple-dense $ python2 main.py 34 | ``` 35 | 36 | ## Inspection 37 | 38 | Log files from the execution is logging to `/tmp/tensorflow` by default. To visualise these using Tensorboard simply run 39 | 40 | ```sh 41 | tensorboard --logdir=/tmp/tensorflow 42 | ``` 43 | 44 | and navigate to `http://localhost:6006`. 45 | 46 | 47 | 48 | 49 | # Complete GCP setup 50 | 51 | In one terminal run the following, starting from your local machine: 52 | 53 | ```sh 54 | # create new GCP instance 55 | gcloud compute instances create tensorspdz --custom-cpu=1 --custom-memory=6GB 56 | gcloud compute instances start tensorspdz 57 | 58 | # connect to it 59 | gcloud compute ssh tensorspdz 60 | 61 | # install needed software 62 | sudo apt-get update 63 | sudo apt-get install python python-pip git 64 | pip install numpy tensorflow tensorboard 65 | 66 | # clone repo 67 | git clone https://github.com/mortendahl/privateml.git 68 | cd privateml/tensorflow/spdz/config/localhost 69 | 70 | # launch services 71 | ./start-services.sh 72 | ``` 73 | 74 | In another terminal run the following, starting from your local machine: 75 | 76 | ```sh 77 | # connect to it 78 | gcloud compute ssh tensorspdz 79 | 80 | # run computation 81 | cd privateml/tensorflow/spdz/simple-dense/ 82 | python main.py 83 | ``` 84 | 85 | Once done, run the following, starting from your local machine, in any terminal: 86 | 87 | ```sh 88 | # connect to it 89 | gcloud compute ssh tensorspdz 90 | 91 | # stop services 92 | cd privateml/tensorflow/spdz/config/localhost 93 | ./stop-services 94 | 95 | # disconnect 96 | logoud 97 | 98 | # shut down 99 | gcloud compute instances stop tensorspdz 100 | ``` -------------------------------------------------------------------------------- /tensorflow/spdz/simple-dense/compute-time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortendahl/privateml/d993ba37ed77a52cecba0a661e45673f841d5755/tensorflow/spdz/simple-dense/compute-time.png -------------------------------------------------------------------------------- /tensorflow/spdz/simple-dense/config.py: -------------------------------------------------------------------------------- 1 | ../config/localhost/config.py -------------------------------------------------------------------------------- /tensorflow/spdz/simple-dense/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | # hack to import tensorspdz from parent directory 4 | # - https://stackoverflow.com/questions/714063/importing-modules-from-parent-folder 5 | import sys, os 6 | sys.path.insert(1, os.path.join(sys.path[0], '..')) 7 | 8 | 9 | from datetime import datetime 10 | 11 | from config import MASTER, SESSION_CONFIG, TENSORBOARD_DIR 12 | from tensorspdz import * 13 | 14 | # Inputs 15 | input_x, x = define_input((100,100)) 16 | input_w, w = define_input((100,100)) 17 | input_b, b = define_input((100,100)) 18 | 19 | # Computation 20 | y = dot(x, w) + b 21 | z = reveal(y) 22 | 23 | # Actual inputs 24 | X = np.random.randn(100,100) 25 | W = np.random.randn(100,100) 26 | B = np.random.randn(100,100) 27 | 28 | # Decomposed values outside Tensorflow 29 | inputs = dict( 30 | [ (xi, Xi) for xi, Xi in zip(input_x, decompose(encode(X))) ] + 31 | [ (wi, Wi) for wi, Wi in zip(input_w, decompose(encode(W))) ] + 32 | [ (bi, Bi) for bi, Bi in zip(input_b, decompose(encode(B))) ] 33 | ) 34 | 35 | # Run computation using Tensorflow 36 | with tf.Session(MASTER, config=SESSION_CONFIG) as sess: 37 | 38 | writer = tf.summary.FileWriter(TENSORBOARD_DIR, sess.graph) 39 | run_options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE) 40 | run_metadata = tf.RunMetadata() 41 | 42 | sess.run(tf.global_variables_initializer()) 43 | 44 | start = datetime.now() 45 | for i in range(1): 46 | res = sess.run( 47 | z, 48 | inputs, 49 | options=run_options, 50 | run_metadata=run_metadata 51 | ) 52 | writer.add_run_metadata(run_metadata, 'run-{}'.format(i)) 53 | end = datetime.now() 54 | 55 | print(end - start) 56 | 57 | writer.close() 58 | 59 | # Recover result outside Tensorflow 60 | Z = decode(recombine(res)) 61 | 62 | expected = np.dot(X, W) + B 63 | actual = Z 64 | diff = expected - actual 65 | assert (abs(diff) < 1e-3).all(), diff -------------------------------------------------------------------------------- /tensorflow/spdz/simple-dense/role-operations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortendahl/privateml/d993ba37ed77a52cecba0a661e45673f841d5755/tensorflow/spdz/simple-dense/role-operations.png -------------------------------------------------------------------------------- /tensorflow/spdz/tensorspdz.py: -------------------------------------------------------------------------------- 1 | 2 | # TODOs: 3 | # - use TensorArray as training buffers instead of FIFOQueue 4 | # - recombine without blowing up numbers (should fit in 64bit word) 5 | # - gradient computation + SGD 6 | # - compare performance if native type is float64 instead of int64 7 | # - performance on GPU 8 | # - better cache strategy? 9 | # - does it make sense to cache additions, subtractions, etc as well? 10 | # - make truncation optional; should work even with cached results 11 | # - lazy mods 12 | # - sigmoid() is showing some unused substructures in TensorBoard; why? 13 | 14 | from functools import reduce 15 | from math import log 16 | 17 | import numpy as np 18 | import tensorflow as tf 19 | 20 | log2 = lambda x: log(x)/log(2) 21 | prod = lambda xs: reduce(lambda x,y: x*y, xs) 22 | 23 | from config import ( 24 | SERVER_0, SERVER_1, 25 | CRYPTO_PRODUCER, INPUT_PROVIDER, OUTPUT_RECEIVER 26 | ) 27 | 28 | # Idea is to simulate five different players on different devices. 29 | # Hopefully Tensorflow can take care of (optimising?) networking like this. 30 | 31 | # 32 | # 64 bit CRT 33 | # - 5 components for modulus ~120 bits (encoding 16.32) 34 | # 35 | # BITPRECISION_INTEGRAL = 16 36 | # BITPRECISION_FRACTIONAL = 30 37 | # INT_TYPE = tf.int64 38 | # FLOAT_TYPE = tf.float64 39 | # TRUNCATION_GAP = 20 40 | # m = [89702869, 78489023, 69973811, 70736797, 79637461] 41 | # M = 2775323292128270996149412858586749843569 # == prod(m) 42 | # lambdas = [ 43 | # 875825745388370376486957843033325692983, 44 | # 2472444909335399274510299476273538963924, 45 | # 394981838173825602426191615931186905822, 46 | # 2769522470404025199908727961712750149119, 47 | # 1813194913083192535116061678809447818860 48 | # ] 49 | 50 | # 51 | # 32 bit CRT 52 | # - we need this to do dot product as int32 is the only supported type for that 53 | # - tried tf.float64 but didn't work out of the box 54 | # - 10 components for modulus ~100 bits 55 | # 56 | BITPRECISION_INTEGRAL = 16 57 | BITPRECISION_FRACTIONAL = 16 58 | INT_TYPE = tf.int32 59 | FLOAT_TYPE = tf.float32 60 | TRUNCATION_GAP = 20 61 | m = [1201, 1433, 1217, 1237, 1321, 1103, 1129, 1367, 1093, 1039] 62 | M = 6616464272061971915798970247351 # == prod(m) 63 | lambdas = [ 64 | 1008170659273389559193348505633, 65 | 678730110253391396805616626909, 66 | 3876367317978788805229799331439, 67 | 1733010852181147049893990590252, 68 | 2834912019672275627813941831946, 69 | 5920625781074493455025914446179, 70 | 4594604064921688203708053741296, 71 | 4709451160728821268524065874669, 72 | 4618812662015813880836792588041, 73 | 3107636732210050331963327700392 74 | ] 75 | 76 | for mi in m: assert 2*log2(mi) + log2(1024) < log2(INT_TYPE.max) 77 | assert log2(M) >= 2 * (BITPRECISION_INTEGRAL + BITPRECISION_FRACTIONAL) + log2(1024) + TRUNCATION_GAP 78 | 79 | K = 2 ** BITPRECISION_FRACTIONAL 80 | 81 | def egcd(a, b): 82 | if a == 0: 83 | return (b, 0, 1) 84 | else: 85 | g, y, x = egcd(b % a, a) 86 | return (g, x - (b // a) * y, y) 87 | 88 | def gcd(a, b): 89 | g, _, _ = egcd(a, b) 90 | return g 91 | 92 | def inverse(a, m): 93 | _, b, _ = egcd(a, m) 94 | return b % m 95 | 96 | def encode(rationals, precision=BITPRECISION_FRACTIONAL): 97 | """ [NumPy] Encode tensor of rational numbers into tensor of ring elements """ 98 | return (rationals * (2**precision)).astype(int).astype(object) % M 99 | 100 | def decode(elements, precision=BITPRECISION_FRACTIONAL): 101 | """ [NumPy] Decode tensor of ring elements into tensor of rational numbers """ 102 | map_negative_range = np.vectorize(lambda element: element if element <= M/2 else element - M) 103 | return map_negative_range(elements).astype(float) / (2**precision) 104 | 105 | def decompose(x): 106 | return tuple( x % mi for mi in m ) 107 | 108 | def recombine(x): 109 | return sum( xi * li for xi, li in zip(x, lambdas) ) % M 110 | 111 | # *** NOTE *** 112 | # keeping mod operations in-lined here for simplicity; 113 | # we should do them lazily 114 | 115 | def crt_add(x, y): 116 | with tf.name_scope("crt_add"): 117 | return [ (xi + yi) % mi for xi, yi, mi in zip(x, y, m) ] 118 | 119 | def crt_sub(x, y): 120 | with tf.name_scope("crt_sub"): 121 | return [ (xi - yi) % mi for xi, yi, mi in zip(x, y, m) ] 122 | 123 | def crt_scale(x, k): 124 | with tf.name_scope("crt_scale"): 125 | return [ (xi * ki) % mi for xi, ki, mi in zip(x, k, m) ] 126 | 127 | def crt_mul(x, y): 128 | with tf.name_scope("crt_mul"): 129 | return [ (xi * yi) % mi for xi, yi, mi in zip(x, y, m) ] 130 | 131 | def crt_dot(x, y): 132 | with tf.name_scope("crt_dot"): 133 | return [ tf.matmul(xi, yi) % mi for xi, yi, mi in zip(x, y, m) ] 134 | 135 | def gen_crt_mod(): 136 | 137 | # precomputation 138 | q = [ inverse(M // mi, mi) for mi in m ] 139 | B = M % K 140 | b = [ (M // mi) % K for mi in m ] 141 | 142 | def crt_mod(x): 143 | with tf.name_scope("crt_mod"): 144 | t = [ (xi * qi) % mi for xi, qi, mi in zip(x, q, m) ] 145 | alpha = tf.cast( 146 | tf.round( 147 | tf.reduce_sum( 148 | [ tf.cast(ti, FLOAT_TYPE) / mi for ti, mi in zip(t, m) ], 149 | axis=0 150 | ) 151 | ), 152 | INT_TYPE 153 | ) 154 | v = tf.reduce_sum( 155 | [ ti * bi for ti, bi in zip(t, b) ], 156 | axis=0 157 | ) - B * alpha 158 | return decompose(v % K) 159 | 160 | return crt_mod 161 | 162 | crt_mod = gen_crt_mod() 163 | 164 | def sample(shape): 165 | with tf.name_scope("sample"): 166 | return [ tf.random_uniform(shape, maxval=mi, dtype=INT_TYPE) for mi in m ] 167 | 168 | def share(secret): 169 | with tf.name_scope("share"): 170 | shape = secret[0].shape 171 | share0 = sample(shape) 172 | share1 = crt_sub(secret, share0) 173 | return share0, share1 174 | 175 | def reconstruct(share0, share1): 176 | with tf.name_scope("reconstruct"): 177 | return crt_add(share0, share1) 178 | 179 | nodes = dict() 180 | 181 | class PrivateTensor(object): 182 | 183 | def __init__(self, share0, share1): 184 | self.share0 = share0 185 | self.share1 = share1 186 | 187 | @property 188 | def shape(self): 189 | return self.share0[0].shape 190 | 191 | @property 192 | def unwrapped(self): 193 | return (self.share0, self.share1) 194 | 195 | def __add__(x, y): 196 | return add(x, y) 197 | 198 | def __sub__(x, y): 199 | return sub(x, y) 200 | 201 | def __mul__(x, y): 202 | return mul(x, y) 203 | 204 | def dot(x, y): 205 | return dot(x, y) 206 | 207 | def truncate(x): 208 | return truncate(x) 209 | 210 | class MaskedPrivateTensor(object): 211 | 212 | def __init__(self, a, a0, a1, alpha_on_0, alpha_on_1): 213 | self.a = a 214 | self.a0 = a0 215 | self.a1 = a1 216 | self.alpha_on_0 = alpha_on_0 217 | self.alpha_on_1 = alpha_on_1 218 | 219 | @property 220 | def shape(self): 221 | return self.a[0].shape 222 | 223 | @property 224 | def unwrapped(self): 225 | return (self.a, self.a0, self.a1, self.alpha_on_0, self.alpha_on_1) 226 | 227 | def transpose(x): 228 | assert isinstance(x, PrivateTensor) 229 | 230 | x0, x1 = x.share0, x.share1 231 | 232 | with tf.name_scope('transpose'): 233 | 234 | with tf.device(SERVER_0): 235 | x0_t = [ tf.transpose(t) for t in x0 ] 236 | 237 | with tf.device(SERVER_1): 238 | x1_t = [ tf.transpose(t) for t in x1 ] 239 | 240 | x_t = PrivateTensor(x0_t, x1_t) 241 | 242 | x_masked = nodes.get(('mask', x), None) 243 | if x_masked: 244 | # use mask for `x` to get mask for `y` 245 | 246 | # TODO: use MaskedPrivateTensor 247 | (a, a0, a1, alpha_on_0, alpha_on_1) = x_masked 248 | 249 | with tf.device(CRYPTO_PRODUCER): 250 | a_t = [ tf.transpose(t) for t in a ] 251 | 252 | with tf.device(SERVER_0): 253 | a0_t = [ tf.transpose(t) for t in a0 ] 254 | alpha_on_0_t = [ tf.transpose(t) for t in alpha_on_0 ] 255 | 256 | with tf.device(SERVER_1): 257 | a1_t = [ tf.transpose(t) for t in a1 ] 258 | alpha_on_1_t = [ tf.transpose(t) for t in alpha_on_1 ] 259 | 260 | x_masked_t = MaskedPrivateTensor(a_t, a0_t, a1_t, alpha_on_0_t, alpha_on_1_t) 261 | nodes[('mask', x_t)] = x_masked_t 262 | 263 | return x_t 264 | 265 | def add(x, y): 266 | assert isinstance(x, PrivateTensor) 267 | assert isinstance(y, PrivateTensor) 268 | 269 | node_key = ('add', x, y) 270 | z = nodes.get(node_key, None) 271 | 272 | if z is None: 273 | 274 | x0, x1 = x.unwrapped 275 | y0, y1 = y.unwrapped 276 | 277 | with tf.name_scope("add"): 278 | 279 | with tf.device(SERVER_0): 280 | z0 = crt_add(x0, y0) 281 | 282 | with tf.device(SERVER_1): 283 | z1 = crt_add(x1, y1) 284 | 285 | z = PrivateTensor(z0, z1) 286 | nodes[node_key] = z 287 | 288 | return z 289 | 290 | def sub(x, y): 291 | assert isinstance(x, PrivateTensor) 292 | assert isinstance(y, PrivateTensor) 293 | 294 | node_key = ('sub', x, y) 295 | z = nodes.get(node_key, None) 296 | 297 | if z is None: 298 | 299 | x0, x1 = x.unwrapped 300 | y0, y1 = y.unwrapped 301 | 302 | with tf.name_scope("sub"): 303 | 304 | with tf.device(SERVER_0): 305 | z0 = crt_sub(x0, y0) 306 | 307 | with tf.device(SERVER_1): 308 | z1 = crt_sub(x1, y1) 309 | 310 | z = PrivateTensor(z0, z1) 311 | nodes[node_key] = z 312 | 313 | return z 314 | 315 | def scale(x, k, apply_encoding=None): 316 | assert isinstance(x, PrivateTensor) 317 | assert type(k) in [int, float] 318 | 319 | x0, x1 = x.unwrapped 320 | 321 | if apply_encoding is None: 322 | # determine automatically 323 | apply_encoding = type(k) is float 324 | 325 | c = np.array([k]) 326 | if apply_encoding: c = encode(c) 327 | c = decompose(c) 328 | 329 | with tf.name_scope('scale'): 330 | 331 | with tf.device(SERVER_0): 332 | y0 = crt_scale(x0, c) 333 | 334 | with tf.device(SERVER_1): 335 | y1 = crt_scale(x1, c) 336 | 337 | y = PrivateTensor(y0, y1) 338 | if apply_encoding: 339 | y = truncate(y) 340 | 341 | return y 342 | 343 | def mask(x): 344 | assert isinstance(x, PrivateTensor) 345 | 346 | node_key = ('mask', x) 347 | masked = nodes.get(node_key, None) 348 | 349 | if masked is None: 350 | 351 | x0, x1 = x.unwrapped 352 | shape = x.shape 353 | 354 | with tf.name_scope('mask'): 355 | 356 | with tf.device(CRYPTO_PRODUCER): 357 | a = sample(shape) 358 | a0, a1 = share(a) 359 | 360 | with tf.device(SERVER_0): 361 | alpha0 = crt_sub(x0, a0) 362 | 363 | with tf.device(SERVER_1): 364 | alpha1 = crt_sub(x1, a1) 365 | 366 | # exchange of alphas 367 | 368 | with tf.device(SERVER_0): 369 | alpha_on_0 = reconstruct(alpha0, alpha1) 370 | 371 | with tf.device(SERVER_1): 372 | alpha_on_1 = reconstruct(alpha0, alpha1) 373 | 374 | masked = MaskedPrivateTensor(a, a0, a1, alpha_on_0, alpha_on_1) 375 | nodes[node_key] = masked 376 | 377 | return masked 378 | 379 | cache_updators = [] 380 | 381 | def cache(x): 382 | 383 | node_key = ('cache', x) 384 | cached = nodes.get(node_key, None) 385 | 386 | if cached is None: 387 | 388 | if isinstance(x, PrivateTensor): 389 | 390 | x0, x1 = x.unwrapped 391 | 392 | with tf.name_scope('cache'): 393 | 394 | with tf.device(SERVER_0): 395 | cached_x0 = [ tf.Variable(tf.random_uniform(shape=vi.shape, maxval=mi, dtype=INT_TYPE), dtype=INT_TYPE) for vi, mi in zip(x0, m) ] 396 | cache_updators.append([ tf.assign(var, val) for var, val in zip(cached_x0, x0) ]) 397 | 398 | with tf.device(SERVER_1): 399 | cached_x1 = [ tf.Variable(tf.random_uniform(shape=vi.shape, maxval=mi, dtype=INT_TYPE), dtype=INT_TYPE) for vi, mi in zip(x1, m) ] 400 | cache_updators.append([ tf.assign(var, val) for var, val in zip(cached_x1, x1) ]) 401 | 402 | cached = PrivateTensor(cached_x0, cached_x1) 403 | nodes[node_key] = cached 404 | 405 | elif isinstance(x, MaskedPrivateTensor): 406 | 407 | a, a0, a1, alpha_on_0, alpha_on_1 = x.unwrapped 408 | 409 | with tf.name_scope('cache'): 410 | 411 | with tf.device(CRYPTO_PRODUCER): 412 | cached_a = [ tf.Variable(tf.random_uniform(shape=vi.shape, maxval=mi, dtype=INT_TYPE), dtype=INT_TYPE) for vi, mi in zip(a, m) ] 413 | cache_updators.append([ tf.assign(var, val) for var, val in zip(cached_a, a) ]) 414 | 415 | with tf.device(SERVER_0): 416 | cached_a0 = [ tf.Variable(tf.random_uniform(shape=vi.shape, maxval=mi, dtype=INT_TYPE), dtype=INT_TYPE) for vi, mi in zip(a0, m) ] 417 | cache_updators.append([ tf.assign(var, val) for var, val in zip(cached_a0, a0) ]) 418 | 419 | cached_alpha_on_0 = [ tf.Variable(tf.random_uniform(shape=vi.shape, maxval=mi, dtype=INT_TYPE), dtype=INT_TYPE) for vi, mi in zip(alpha_on_0, m) ] 420 | cache_updators.append([ tf.assign(var, val) for var, val in zip(cached_alpha_on_0, alpha_on_0) ]) 421 | 422 | with tf.device(SERVER_1): 423 | cached_a1 = [ tf.Variable(tf.random_uniform(shape=vi.shape, maxval=mi, dtype=INT_TYPE), dtype=INT_TYPE) for vi, mi in zip(a1, m) ] 424 | cache_updators.append([ tf.assign(var, val) for var, val in zip(cached_a1, a1) ]) 425 | 426 | cached_alpha_on_1 = [ tf.Variable(tf.random_uniform(shape=vi.shape, maxval=mi, dtype=INT_TYPE), dtype=INT_TYPE) for vi, mi in zip(alpha_on_1, m) ] 427 | cache_updators.append([ tf.assign(var, val) for var, val in zip(cached_alpha_on_1, alpha_on_1) ]) 428 | 429 | cached = MaskedPrivateTensor( 430 | cached_a, 431 | cached_a0, 432 | cached_a1, 433 | cached_alpha_on_0, 434 | cached_alpha_on_1 435 | ) 436 | nodes[node_key] = cached 437 | 438 | else: 439 | raise AssertionError("'x' not of supported type") 440 | 441 | return cached 442 | 443 | def square(x): 444 | 445 | node_key = ('square', x) 446 | y = nodes.get(node_key, None) 447 | 448 | if y is None: 449 | 450 | if isinstance(x, PrivateTensor): 451 | x = mask(x) 452 | 453 | assert isinstance(x, MaskedPrivateTensor) 454 | a, a0, a1, alpha_on_0, alpha_on_1 = x.unwrapped 455 | 456 | with tf.name_scope('square'): 457 | 458 | with tf.device(CRYPTO_PRODUCER): 459 | aa = crt_mul(a, a) 460 | aa0, aa1 = share(aa) 461 | 462 | with tf.device(SERVER_0): 463 | alpha = alpha_on_0 464 | y0 = crt_add(aa0, 465 | crt_add(crt_mul(a0, alpha), 466 | crt_add(crt_mul(alpha, a0), # TODO replace with `scale(, 2)` op 467 | crt_mul(alpha, alpha)))) 468 | 469 | with tf.device(SERVER_1): 470 | alpha = alpha_on_1 471 | y1 = crt_add(aa1, 472 | crt_add(crt_mul(a1, alpha), 473 | crt_mul(alpha, a1))) # TODO replace with `scale(, 2)` op 474 | 475 | y = PrivateTensor(y0, y1) 476 | y = truncate(y) 477 | nodes[node_key] = y 478 | 479 | return y 480 | 481 | def mul(x, y): 482 | 483 | node_key = ('mul', x, y) 484 | z = nodes.get(node_key, None) 485 | 486 | if z is None: 487 | 488 | if isinstance(x, PrivateTensor): 489 | x = mask(x) 490 | 491 | if isinstance(y, PrivateTensor): 492 | y = mask(y) 493 | 494 | assert isinstance(x, MaskedPrivateTensor) 495 | assert isinstance(y, MaskedPrivateTensor) 496 | 497 | a, a0, a1, alpha_on_0, alpha_on_1 = x.unwrapped 498 | b, b0, b1, beta_on_0, beta_on_1 = y.unwrapped 499 | 500 | with tf.name_scope("mul"): 501 | 502 | with tf.device(CRYPTO_PRODUCER): 503 | ab = crt_mul(a, b) 504 | ab0, ab1 = share(ab) 505 | 506 | with tf.device(SERVER_0): 507 | alpha = alpha_on_0 508 | beta = beta_on_0 509 | z0 = crt_add(ab0, 510 | crt_add(crt_mul(a0, beta), 511 | crt_add(crt_mul(alpha, b0), 512 | crt_mul(alpha, beta)))) 513 | 514 | with tf.device(SERVER_1): 515 | alpha = alpha_on_1 516 | beta = beta_on_1 517 | z1 = crt_add(ab1, 518 | crt_add(crt_mul(a1, beta), 519 | crt_mul(alpha, b1))) 520 | 521 | z = PrivateTensor(z0, z1) 522 | z = truncate(z) 523 | nodes[node_key] = z 524 | 525 | return z 526 | 527 | def dot(x, y): 528 | 529 | node_key = ('dot', x, y) 530 | z = nodes.get(node_key, None) 531 | 532 | if z is None: 533 | 534 | if isinstance(x, PrivateTensor): 535 | x = mask(x) 536 | 537 | if isinstance(y, PrivateTensor): 538 | y = mask(y) 539 | 540 | assert isinstance(x, MaskedPrivateTensor) 541 | assert isinstance(y, MaskedPrivateTensor) 542 | 543 | a, a0, a1, alpha_on_0, alpha_on_1 = x.unwrapped 544 | b, b0, b1, beta_on_0, beta_on_1 = y.unwrapped 545 | 546 | with tf.name_scope("dot"): 547 | 548 | with tf.device(CRYPTO_PRODUCER): 549 | ab = crt_dot(a, b) 550 | ab0, ab1 = share(ab) 551 | 552 | with tf.device(SERVER_0): 553 | alpha = alpha_on_0 554 | beta = beta_on_0 555 | z0 = crt_add(ab0, 556 | crt_add(crt_dot(a0, beta), 557 | crt_add(crt_dot(alpha, b0), 558 | crt_dot(alpha, beta)))) 559 | 560 | with tf.device(SERVER_1): 561 | alpha = alpha_on_1 562 | beta = beta_on_1 563 | z1 = crt_add(ab1, 564 | crt_add(crt_dot(a1, beta), 565 | crt_dot(alpha, b1))) 566 | 567 | z = PrivateTensor(z0, z1) 568 | z = truncate(z) 569 | nodes[node_key] = z 570 | 571 | return z 572 | 573 | def gen_truncate(): 574 | assert gcd(K, M) == 1 575 | 576 | # precomputation for truncation 577 | K_inv = decompose(inverse(K, M)) 578 | M_wrapped = decompose(M) 579 | 580 | def raw_truncate(x): 581 | y = crt_sub(x, crt_mod(x)) 582 | return crt_mul(y, K_inv) 583 | 584 | def truncate(x): 585 | assert isinstance(x, PrivateTensor) 586 | 587 | x0, x1 = x.share0, x.share1 588 | 589 | with tf.name_scope("truncate"): 590 | 591 | with tf.device(SERVER_0): 592 | y0 = raw_truncate(x0) 593 | 594 | with tf.device(SERVER_1): 595 | y1 = crt_sub(M_wrapped, raw_truncate(crt_sub(M_wrapped, x1))) 596 | 597 | return PrivateTensor(y0, y1) 598 | 599 | return truncate 600 | 601 | truncate = gen_truncate() 602 | 603 | def sigmoid(x): 604 | assert isinstance(x, PrivateTensor) 605 | 606 | w0 = 0.5 607 | w1 = 0.2159198015 608 | w3 = -0.0082176259 609 | w5 = 0.0001825597 610 | w7 = -0.0000018848 611 | w9 = 0.0000000072 612 | 613 | with tf.name_scope('sigmoid'): 614 | 615 | # TODO optimise depth 616 | x2 = square(x) 617 | x3 = mul(x2, x) 618 | x5 = mul(x2, x3) 619 | x7 = mul(x2, x5) 620 | x9 = mul(x2, x7) 621 | 622 | y1 = scale(x, w1) 623 | y3 = scale(x3, w3) 624 | y5 = scale(x5, w5) 625 | y7 = scale(x7, w7) 626 | y9 = scale(x9, w9) 627 | 628 | with tf.device(SERVER_0): 629 | z0 = crt_add(y1.share0, 630 | crt_add(y3.share0, 631 | crt_add(y5.share0, 632 | crt_add(y7.share0, 633 | crt_add(y9.share0, 634 | decompose(encode(np.array([w0])))))))) 635 | 636 | with tf.device(SERVER_1): 637 | z1 = crt_add(y1.share1, 638 | crt_add(y3.share1, 639 | crt_add(y5.share1, 640 | crt_add(y7.share1, 641 | y9.share1)))) 642 | 643 | z = PrivateTensor(z0, z1) 644 | return z 645 | 646 | def define_input(shape, name=None): 647 | 648 | with tf.name_scope('input{}'.format('-'+name if name else '')): 649 | 650 | with tf.device(INPUT_PROVIDER): 651 | input_x = [ tf.placeholder(INT_TYPE, shape=shape) for _ in m ] 652 | x0, x1 = share(input_x) 653 | 654 | return input_x, PrivateTensor(x0, x1) 655 | 656 | # TODO implement this better 657 | def define_variable(initial_value, apply_encoding=True, name=None): 658 | 659 | v = initial_value 660 | v = encode(v) if apply_encoding else v 661 | v = decompose(v) 662 | 663 | with tf.name_scope('var{}'.format('-'+name if name else '')): 664 | 665 | with tf.device(INPUT_PROVIDER): 666 | v0, v1 = share(v) 667 | 668 | with tf.device(SERVER_0): 669 | x0 = [ tf.Variable(vi, dtype=INT_TYPE).read_value() for vi in v0 ] 670 | 671 | with tf.device(SERVER_1): 672 | x1 = [ tf.Variable(vi, dtype=INT_TYPE).read_value() for vi in v1 ] 673 | 674 | x = PrivateTensor(x0, x1) 675 | 676 | return x 677 | 678 | def assign(x, v): 679 | assert isinstance(x, PrivateTensor) 680 | assert isinstance(v, PrivateTensor) 681 | 682 | x0, x1 = x.share0, x.share1 683 | v0, v1 = v.share0, v.share1 684 | 685 | with tf.name_scope("assign"): 686 | 687 | with tf.device(SERVER_0): 688 | y0 = [ tf.assign(xi, vi) for xi, vi in zip(x0, v0) ] 689 | 690 | with tf.device(SERVER_1): 691 | y1 = [ tf.assign(xi, vi) for xi, vi in zip(x1, v1) ] 692 | 693 | return y0, y1 694 | 695 | def reveal(x): 696 | assert isinstance(x, PrivateTensor) 697 | 698 | x0, x1 = x.share0, x.share1 699 | 700 | with tf.name_scope("reveal"): 701 | 702 | with tf.device(OUTPUT_RECEIVER): 703 | y = reconstruct(x0, x1) 704 | 705 | return y 706 | 707 | def encode_input(vars_and_values): 708 | if not isinstance(vars_and_values, list): 709 | vars_and_values = [vars_and_values] 710 | result = dict() 711 | for input_x, X in vars_and_values: 712 | result.update( (input_xi, Xi) for input_xi, Xi in zip(input_x, decompose(encode(X))) ) 713 | return result 714 | 715 | def decode_output(value): 716 | return decode(recombine(value)) 717 | --------------------------------------------------------------------------------