├── README.md ├── secretstroll ├── README.md ├── client.py ├── credential.py ├── docker-compose.yaml ├── docker │ ├── client_img │ │ ├── Dockerfile │ │ ├── etc │ │ │ └── service │ │ │ │ └── tor │ │ │ │ ├── log │ │ │ │ └── run │ │ │ │ └── run │ │ └── requirements.txt │ └── server_img │ │ ├── Dockerfile │ │ ├── etc │ │ ├── service │ │ │ └── tor │ │ │ │ ├── log │ │ │ │ └── run │ │ │ │ └── run │ │ └── tor │ │ │ ├── torrc │ │ │ └── torsocks.conf │ │ └── requirements.txt ├── fingerprint.db ├── fingerprinting.py ├── handout │ ├── ABC_guide.pdf │ └── handout_project_secretstroll.pdf ├── privacy_evaluation │ ├── pois.csv │ ├── queries.csv │ └── query.py ├── report_template │ ├── IEEEtran.cls │ ├── bib.bib │ └── report.tex ├── requirements.txt ├── serialization.py ├── server.py ├── stroll.py └── tor │ └── .git_keep └── smcompiler ├── README.md ├── communication.py ├── expression.py ├── handout └── handout_project_smcompiler.pdf ├── protocol.py ├── report_template ├── bib.bib └── main.tex ├── requirements.txt ├── secret_sharing.py ├── server.py ├── smc_party.py ├── test_expression.py ├── test_integration.py ├── test_secret_sharing.py ├── test_ttp.py └── ttp.py /README.md: -------------------------------------------------------------------------------- 1 | Public Repo for CS-523 Projects 2 | ====================== 3 | 4 | The repo contains instructions and materials for students to do the two projects in the EPFL course CS-523 Advanced topics on privacy enhancing technologies. 5 | 6 | ## General Instructions 7 | 8 | This README contains instructions that are common for both projects. For project-specific descriptions, please refer to [SecretStroll README](./secretstroll/README.md) and [SMCompiler README](./smcompiler/README.md) respectively. 9 | 10 | ### Project Team Registration 11 | 12 | Please register your 3-person team using [this form](https://forms.gle/3LANqQrQCf2dTLef7) by Feb 28th. 13 | The team is for both projects. 14 | 15 | ### TA Slot Booking 16 | 17 | If you encounter some problems on the projects and would like to consult the TAs, please use [this sheet](https://docs.google.com/spreadsheets/d/1erESnNeBi2khQS-UP38QqR6iGraKc957w45Elz-dZ20/edit) to book a slot with TA on Fridays. 18 | 19 | ### Virtual Machines 20 | 21 | Using a virtual machine is optional for this project. However, we provide two VM images in case your device (e.g., with Apple M1 chip) cannot install necessary dependencies for the projects: 22 | 23 | - [VirtualBox image for x86_64 based hardware](https://drive.google.com/file/d/1pchAY6TKQNIaVJM6XRJt1CyDILJi7yzb/view?usp=share_link) 24 | 25 | - [UTM image for ARM64 based hardware like Apple Silicon Macs](https://drive.google.com/file/d/1QdDnRzLMeuc0prGngXZse_vLV4HAclm_/view?usp=share_link) 26 | 27 | **Note:** Please log in with your EPFL email to download from the above two links. The VMs support two credentials: "root:root" or "student:student". 28 | 29 | 30 | ### Submission 31 | 32 | **SMCompiler submission deadline:** 28th March, 2025 at 23:59. 33 | 34 | **SecretStroll submission deadline:** 30th May, 2025 at 23:59. 35 | 36 | **Only 1 member per team** needs to upload their project submissions via moodle. 37 | 38 | In the final report of each project, there must contain a paragraph describing the contributions of each team member. Here is [an example of such a paragraph](https://www.epj.org/images/stories/faq/examples-of-author-contributions.pdf). 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /secretstroll/README.md: -------------------------------------------------------------------------------- 1 | # SecretStroll 2 | 3 | 4 | ## Introduction 5 | 6 | In this project, you will develop a location-based application, SecretStroll, 7 | that enables users to search for nearby points of interest (POI). 8 | We provide you with an [instructions handout](./handout/handout_project_secretstroll.pdf) and a skeleton to help with the development. 9 | 10 | **Virtualization.** We provide a virtual machine (VM) to facilitate the setup 11 | and configuration of the SecretStroll and reduce potential networking problems 12 | while running the application in different environments. 13 | 14 | Also, we use Docker to enforce isolation between the client and server on the 15 | virtual machine. Docker is a piece of software that uses the capabilities of 16 | the Linux kernel to run processes in a sandboxed environment. Thus, both the 17 | server and the client will "think" they are running in two independent systems. 18 | 19 | **Skeleton.** Both the client and the server provide a command-line interface to 20 | run and interact with the server. The underlying location-based service is 21 | already implemented in the skeleton, and your task is to add the authentication 22 | with attribute-based credentials. 23 | 24 | We strongly recommend to use the `petrelic` cryptographic-pairing library to 25 | implement PS credentials. You can find the project repository in 26 | [petrelic](https://github.com/spring-epfl/petrelic/) and you can visit 27 | for documentation. This library is 28 | bundled in the provided Docker container and virtual machine. 29 | 30 | The skeleton has already implemented and integrated capabilities to save, 31 | manage, and load keys and credentials as byte arrays. You need to implement 32 | (de)serialization methods to match the API. We provide `serialization.py` as a 33 | `petrelic` extension of `jsonpickle`, a serialization library, to help you with 34 | the serialization of cryptographic objects. 35 | 36 | Our skeleton and Docker infrastructure takes care of syncing and deploying your 37 | code. You only need to implement attribute-based credentials and update the 38 | `stroll.py` file to use it. 39 | 40 | **Testing**. An integral part of system development is testing the system. In 41 | your implementation, you should check **both** success and failure paths when 42 | your working with cryptographic primitives. In this project, you **must** use 43 | the *pytest* framework to test your system. You can visit 44 | [pytest tutorial](https://www.tutorialspoint.com/pytest/index.htm) for guides. 45 | 46 | ## Setting up the development environment 47 | 48 | ### Virtual machine 49 | 50 | We provide you with two VM images for SecretStroll project on which we have 51 | already installed all the necessary applications and libraries: 52 | 53 | - A VirtualBox image for people using a x86_64 CPU architecture. (i.e. almost 54 | all laptops...) 55 | - A zipped UTM image for people using a ARM 64 CPU architecture. (i.e. Apple 56 | Silicon Macs) 57 | 58 | If it is not already done, you can install VirtualBox or UTM on your laptop by 59 | following the instructions in their respective documentation: 60 | 61 | - [VirtualBox](https://www.virtualbox.org/wiki/End-user_documentation) 62 | - [UTM](https://docs.getutm.app/) 63 | 64 | 65 | **We only provide support for projects running inside the VM and we strongly 66 | recommend you to develop inside the VM.** 67 | 68 | There are two accounts on the VM (`user:password`): 69 | 70 | ``` 71 | student:student 72 | root:root 73 | ``` 74 | 75 | You can set up SSH on the VM and connect from your host or directly use the VM 76 | as your development environment. 77 | 78 | 79 | #### Setting up SSH to the VM 80 | 81 | **For VirtualBox** 82 | 83 | In VirtualBox, you can set up SSH access to the VM by following these steps: 84 | 85 | * Open the settings of your image 86 | * Go to the "Network" panel 87 | * Choose "Advanced" and click on the "Port forwarding" button 88 | * Add a forwarding rule (green "plus" button on the side) 89 | * In the forwarding rule, leave both IP addresses empty, set **Host port** to _2222_, 90 | and **Guest port** to 22 (the default SSH port) 91 | * Restart the virtual machine 92 | 93 | **For UTM** 94 | 95 | In UTM, you can set up SSH access to the VM by following these steps: 96 | 97 | * Open the settings of your image 98 | * Go to the "Network" panel 99 | * Add a new port forward rule (*New* button on the right of the *Port Forward* frame) 100 | * In the forwarding rule, leave both IP addresses empty, set **Host Port** to _2222_, 101 | and **Guest Port** to 22 (the default SSH port) 102 | * Restart the virtual machine 103 | 104 | Once the port forwarding rule is effective, you can connect to your virtual machine via SSH: 105 | `ssh -p 2222 student@127.0.0.1` 106 | 107 | This is how you copy files _TO_ the VM: 108 | `scp -P 2222 student@127.0.0.1:` 109 | 110 | Copy files _FROM_ the VM: 111 | `scp -P 2222 student@127.0.0.1: ` 112 | 113 | ### Code Skeleton 114 | 115 | The first step of this project will be to retrieve the skeleton that you will 116 | have to use as a base implementation. The most convenient way to do this will 117 | be to clone the public repository of this course with `git`. 118 | 119 | ``` 120 | git clone https://github.com/spring-epfl/CS-523-public.git cs523 121 | ``` 122 | 123 | We use Python 3 in this project and all necessary Python components are already 124 | installed on the VM and Docker containers. You can find installed libraries in 125 | the file `requirements.txt`. 126 | 127 | Feel free to have a look at `client.py` and `server.py` to see how the classes 128 | and methods are used. 129 | 130 | **Note:** The library `petrelic`is currently only distributed for Linux 131 | systems, if you are not using the VM or a Linux system, you should still be 132 | able to test your code by running it within the Docker containers build with 133 | the provided configuration. 134 | 135 | ### Collaboration 136 | 137 | You can use git to sync your work with your teammates. However, keep in mind 138 | that you are not allowed to use public repositories, so make sure that your 139 | repository is **private**. 140 | 141 | If you cloned our git repository to retrieve the skeleton as we advised, you 142 | can replace the remote URL to your own git repository 143 | 144 | ``` 145 | cd cs523 146 | git remote set-url origin git@github.com:/ 147 | ``` 148 | 149 | ## Files in this repository 150 | 151 | This repository contains the skeleton code for Parts 1 and 3: 152 | 153 | * `credential.py`—Source code that you have to complete. 154 | * `stroll.py`—Source code that you have to complete. 155 | * `client.py`—Client CLI calling classes and methods defined in `stroll.py`. 156 | * `server.py`—Server CLI calling classes and methods defined in `stroll.py`. 157 | * `serialization.py`—Extends the library `jsonpickle` to serialize python 158 | objects. 159 | * `fingerprinting.py`—skeleton for Part 3. 160 | * `requirements.txt`—Required Python libraries. 161 | * `docker-compose.yaml`—*docker compose* configuration describing how to run the 162 | Docker containers. 163 | * `docker/`—Directory containing Docker configurations for running the client 164 | and the server. 165 | * `tor/`—Intentionally empty folder needed to run a Tor server. 166 | * `fingerprint.db`—Database containing POI information for Part 3. 167 | 168 | The directory `privacy_evaluation` contains files for the part 2. 169 | 170 | ## Server and client deployment 171 | 172 | The server and client code deployment is handled by Docker and our skeleton. In 173 | this section, we introduce our Docker infrastructure and how to use it. Then, we 174 | provide a step-by-step guide of running the client and server. 175 | 176 | ### Working with the Docker infrastructure 177 | 178 | *Before launching the infrastructure, ensure the `tor` directory in the project's 179 | directory has the correct permissions.* 180 | ``` 181 | student@cs523:~$ cd cs523/secretstroll/ 182 | student@cs523:~/cs523/secretstroll$ chmod 777 tor 183 | student@cs523:~/cs523/secretstroll$ ls -ld tor 184 | drwxrwxrwx 2 student student 4096 mar 24 15:31 tor 185 | ``` 186 | 187 | The server and the client run in a Docker infrastructure composed of 2 188 | containers, and a virtual network. 189 | 190 | Before setting up the Docker infrastructure for the first time, you must first 191 | build the images which will be used to run the client and server containers. To 192 | do so, run the following command in the directory which contains the 193 | file `docker-compose.yml`: 194 | ``` 195 | docker compose build 196 | ``` 197 | 198 | To set up the Docker infrastructure, run the following command in the directory 199 | containing the file `docker-compose.yml`: 200 | ``` 201 | docker compose up -d 202 | ``` 203 | 204 | When you stop working with the infrastructure, remember to shut it down by 205 | running the following command in the `secretstroll` directory containing the file 206 | `docker-compose.yml`: 207 | ``` 208 | docker compose down 209 | ``` 210 | 211 | **Note:** *If you forget to shut down the Docker infrastructure, e.g., before 212 | shutting down your computer, you might end up with stopped Docker containers 213 | preventing the creation of the new ones when you to re-launch the infrastructure 214 | the next time. This can be fixed by removing the network bridge with 215 | `docker compose down` and destroying the stopped Docker containers with 216 | `docker container prune -f`.* 217 | 218 | ### Accessing the data 219 | 220 | The code in the `secretstroll` directory is shared between your VM and the 221 | Docker containers, so modifications you make in your VM will also appear in 222 | containers. Feel free to read the file `docker-compose.yml` to see how it is 223 | done. 224 | 225 | If you need to transfer some data between your VM and your host machine, you 226 | can set up SSH access and use the `scp` command as detailed before. 227 | 228 | Another option for people who use VirtualBox is to have shared directories 229 | between the VM and your host. For this feature to work correctly you need to 230 | have VirtualBox's *Guest Additions* installed on the VM. We have already 231 | installed *Guest Additions* on the VM we provided for this course, but you 232 | might have to update it to work with your version of VirtualBox, in which case, 233 | please refer to their documentation. 234 | 235 | Note also that you will need to use Tor in this project (see section below), 236 | and that Tor is quite sensitive to its directories' permissions. As it is often 237 | not possible to map directly the permission between the host and guest when 238 | sharing some files, be careful to not run your project directly from the shared 239 | directory as incorrect permission given to the `tor` directory of your project 240 | can prevent Tor from starting. 241 | 242 | ### Tor integration 243 | 244 | Integrating Tor into your project should be seamless. The Docker configuration 245 | we provide is designed to run Tor in the background, and the code is designed 246 | to use the Tor if requested with no effort on your part. 247 | 248 | If your project works if used normally, but fails when using Tor, you can check 249 | if its log file in the Docker container gives a clue to what is happening: 250 | 251 | ``` 252 | cat /var/log/service/tor/current 253 | ``` 254 | 255 | If you still do not know what causes the problem or do not know how to correct 256 | it, call an assistant. 257 | 258 | ### Server 259 | 260 | It is easier to run the commands in a Docker container by opening a shell, and 261 | then running the commands inside this shell. 262 | 263 | To execute a shell in the container in which the server is to be launched, run 264 | the following command: 265 | 266 | ``` 267 | docker exec -it cs523-server /bin/bash 268 | ``` 269 | 270 | In this container, the root directory of the project is mounted on `/server`. 271 | ``` 272 | cd /server 273 | ``` 274 | 275 | The server has two subcommands: `gen-ca` and `run`. `gen-ca` generates 276 | the public and secret keys, and `run` runs the server. The server and its 277 | subcommands have a help option, which you can access using the `-h` argument. 278 | 279 | Key generation example: 280 | ``` 281 | python3 server.py setup -S restaurant -S bar -S sushi 282 | 283 | usage: server.py setup [-h] [-p PUB] [-s SEC] -S SUBSCRIPTIONS 284 | 285 | optional arguments: 286 | -h, --help show this help message and exit 287 | -p PUB, --pub PUB Name of the file in which to write the public key. 288 | (default: key.pub) 289 | -s SEC, --sec SEC Name of the file in which to write the secret key. 290 | (default: key.sec) 291 | -S SUBSCRIPTIONS, --subscriptions SUBSCRIPTIONS 292 | Subscriptions recognized by the server. 293 | ``` 294 | 295 | Server run example: 296 | ``` 297 | python3 server.py run 298 | 299 | usage: server.py run [-h] [-D DATABASE] [-p PUB] [-s SEC] 300 | 301 | optional arguments: 302 | -h, --help show this help message and exit 303 | -D DATABASE, --database DATABASE 304 | Path to the PoI database. 305 | -p PUB, --pub PUB Name of the file containing the public key. 306 | -s SEC, --sec SEC Name of the file containing the secret key. 307 | ``` 308 | 309 | In the Part 3 of the project, the server is expected to be accessible as a Tor 310 | hidden service. The server's Docker container configures Tor to create a hidden 311 | service and redirects the traffic to the Python server. The server serves local 312 | and hidden service requests simultaneously by default. 313 | 314 | The server also contains a database, `fingerprint.db`. This is used in Part 3. 315 | The database has a POI table that contains records for each POI. The server 316 | returns the list of POIs associated with a queried cell ID, and information 317 | about each POI in the list. You must not modify the database. 318 | 319 | ### Client 320 | 321 | To execute a shell in the client container, run the following command: 322 | 323 | ``` 324 | docker exec -it cs523-client /bin/bash 325 | ``` 326 | 327 | In this container, the root directory of the project is mounted on `/client`. 328 | ``` 329 | cd /client 330 | ``` 331 | 332 | The client has four subcommands: `get-pk`, `register`, `loc`, and `grid`. As for 333 | the server, the client and its subcommands have a help option, which you can 334 | access using the `-h` argument. 335 | 336 | Use `get-pk` to retrieve the public key from the server: 337 | ``` 338 | python3 client.py get-pk 339 | 340 | usage: client.py get-pk [-h] [-o OUT] [-t] 341 | 342 | optional arguments: 343 | -h, --help show this help message and exit 344 | -o OUT, --out OUT Name of the file in which to write the public key. 345 | (default: key-client.pub) 346 | -t, --tor Use Tor to connect to the server. 347 | ``` 348 | 349 | Use `register` to register an account on the serve: 350 | ``` 351 | python3 client.py register -u your_name -S restaurant -S bar 352 | 353 | usage: client.py register [-h] [-p PUB] -u USER [-o OUT] -S SUBSCRIPTIONS [-t] 354 | 355 | optional arguments: 356 | -h, --help show this help message and exit 357 | -p PUB, --pub PUB Name of the file from which to read the public key. 358 | (default: key-client.pub) 359 | -u USER, --user USER User name. 360 | -o OUT, --out OUT Name of the file in which to write the attribute-based 361 | credential. (default: anon.cred) 362 | -S SUBSCRIPTIONS, --subscriptions SUBSCRIPTIONS 363 | Subscriptions to register. 364 | -t, --tor Use Tor to connect to the server. 365 | ``` 366 | 367 | Use `loc` and `grid` commands to retrieve information about points of interests 368 | using lat/lon location (Part 1) and cell identifier (Part 3), respectively: 369 | ``` 370 | python3 client.py loc 46.52345 6.57890 -T restaurant -T bar 371 | 372 | usage: client.py loc [-h] [-p PUB] [-c CREDENTIAL] -T TYPES [-t] lat lon 373 | 374 | positional arguments: 375 | lat Latitude. 376 | lon Longitude. 377 | 378 | optional arguments: 379 | -h, --help show this help message and exit 380 | -p PUB, --pub PUB Name of the file from which to read the public key. 381 | (default: key-client.pub) 382 | -c CREDENTIAL, --credential CREDENTIAL 383 | Name of the file from which to read the attribute- 384 | based credential. (default: anon.cred) 385 | -T TYPES, --types TYPES 386 | Types of services to request. 387 | -t, --tor Use Tor to connect to the server. 388 | ``` 389 | 390 | **Warning**: The database only contains points of interest with latitude in 391 | range \[46.5, 46.57\] and longitude in range \[6.55, 6.65\] (Lausanne area). 392 | You can make queries outside these values, but you will not find anything 393 | interesting. 394 | 395 | ``` 396 | python3 client.py grid 42 -T restaurant 397 | 398 | usage: client.py grid [-h] [-p PUB] [-c CREDENTIAL] [-T TYPES] [-t] cell_id 399 | 400 | positional arguments: 401 | cell_id Cell identifier. 402 | 403 | optional arguments: 404 | -h, --help show this help message and exit 405 | -p PUB, --pub PUB Name of the file from which to read the public key. 406 | (default: key-client.pub) 407 | -c CREDENTIAL, --credential CREDENTIAL 408 | Name of the file from which to read the attribute- 409 | based credential. (default: anon.cred) 410 | -T TYPES, --types TYPES 411 | Types of services to request. 412 | -t, --tor Use Tor to connect to the server. 413 | ``` 414 | 415 | ## A sample run of Part 1 416 | Here we show a typical run of the system for Part 1. 417 | 418 | Initialization: 419 | 420 | 421 | Open a shell 422 | ``` 423 | $ cd cs523/secretstroll 424 | $ docker compose build 425 | $ docker compose up -d 426 | ``` 427 | 428 | Server side: 429 | 430 | Open a shell 431 | ``` 432 | $ cd cs523/secretstroll 433 | $ docker exec -it cs523-server /bin/bash 434 | (server) $ cd /server 435 | (server) $ python3 server.py setup -s key.sec -p key.pub -S restaurant -S bar -S dojo 436 | (server) $ python3 server.py run -D fingerprint.db -s key.sec -p key.pub 437 | ``` 438 | 439 | Client side: 440 | ``` 441 | Open a shell 442 | $ cd cs523/secretstroll 443 | $ docker exec -it cs523-client /bin/bash 444 | (client) $ cd /client 445 | (client) $ python3 client.py get-pk 446 | (client) $ python3 client.py register -u your_name -S restaurant -S bar -S dojo 447 | (client) $ python3 client.py loc 46.52345 6.57890 -T restaurant -T bar 448 | ``` 449 | 450 | Close everything down at the end of the experiment: 451 | ``` 452 | $ docker compose down 453 | ``` 454 | 455 | ## A sample run of Part 3 456 | Here we provide a typical run of the system for Part 3: 457 | 458 | Initialization: 459 | 460 | ``` 461 | Open a shell 462 | $ cd cs523/secretstroll 463 | $ docker compose build 464 | $ docker compose up -d 465 | ``` 466 | 467 | Server side: 468 | 469 | You should have already generated the keys in Part 1, so you do not need to 470 | repeat that step. 471 | 472 | ``` 473 | Open a shell 474 | $ cd cs523/secretstroll 475 | $ docker exec -it cs523-server /bin/bash 476 | (server) $ cd /server 477 | (server) $ python3 server.py run 478 | ``` 479 | 480 | Client side: 481 | 482 | You should have already performed the registration in Part 1, so you do not need 483 | to the repeat the step. Use the grid parameter to query for a particular cell. 484 | Set the reveal argument (-r) to an empty value. Set the -t argument to use Tor. The example run below queries the server for cell ID = 42. 485 | 486 | ``` 487 | Open a shell 488 | $ cd cs523/secretstroll 489 | $ docker exec -it cs523-client /bin/bash 490 | (client) $ cd /client 491 | (client) $ python3 client.py grid 42 -T restaurant -t 492 | ``` 493 | 494 | Close everything down at the end of the experiment: 495 | ``` 496 | $ docker compose down 497 | ``` 498 | -------------------------------------------------------------------------------- /secretstroll/client.py: -------------------------------------------------------------------------------- 1 | """ 2 | Client entrypoint. 3 | 4 | !!! DO NOT MODIFY THIS FILE !!! 5 | 6 | """ 7 | 8 | import argparse 9 | import copy 10 | import json 11 | import sys 12 | from pathlib import Path 13 | from typing import List, Optional, Tuple 14 | 15 | import requests 16 | 17 | from stroll import Client 18 | 19 | # 20 | # Network communications 21 | # 22 | 23 | 24 | SERVER_HOSTNAME = "cs523-server" 25 | TOR_PROXY = "socks5h://localhost:9050" 26 | TOR_HOSTNAME_FILENAME = Path("/client/tor/hidden_service/hostname") 27 | 28 | 29 | class ClientHTTPError(Exception): 30 | """An unexpected HTTP status was received.""" 31 | 32 | 33 | # 34 | # Parser 35 | # 36 | 37 | 38 | def main(args: List[str]) -> None: 39 | """Parse the arguments given to the client, and call the appropriate method.""" 40 | 41 | parser = argparse.ArgumentParser(description="Client for CS-523 project 2.") 42 | subparsers = parser.add_subparsers(help="Command") 43 | 44 | # Get public key parser. 45 | parser_get_pk = subparsers.add_parser( 46 | "get-pk", help="Retrieve the public key from the server." 47 | ) 48 | parser_get_pk.add_argument( 49 | "-o", 50 | "--out", 51 | help="Name of the file in which to write the public key.", 52 | type=argparse.FileType("wb"), 53 | default="key-client.pub", 54 | ) 55 | parser_get_pk.add_argument( 56 | "-t", 57 | "--tor", 58 | help="Use Tor to connect to the server.", 59 | action="store_true" 60 | ) 61 | parser_get_pk.set_defaults(callback=client_get_pk) 62 | 63 | # Register parser. 64 | parser_register = subparsers.add_parser( 65 | "register", help="Register the client to the server." 66 | ) 67 | parser_register.add_argument( 68 | "-p", 69 | "--pub", 70 | help="Name of the file from which to read the public key.", 71 | type=argparse.FileType("rb"), 72 | default="key-client.pub" 73 | ) 74 | parser_register.add_argument( 75 | "-u", 76 | "--user", 77 | help="User name.", 78 | type=str, 79 | required=True 80 | ) 81 | parser_register.add_argument( 82 | "-o", 83 | "--out", 84 | help="Name of the file in which to write the attribute-based credential.", 85 | type=argparse.FileType("wb"), 86 | default="anon.cred" 87 | ) 88 | parser_register.add_argument( 89 | "-S", 90 | "--subscriptions", 91 | help="Subscriptions to register.", 92 | type=str, 93 | required=True, 94 | action="append" 95 | ) 96 | parser_register.add_argument( 97 | "-t", 98 | "--tor", 99 | help="Use Tor to connect to the server.", 100 | action="store_true" 101 | ) 102 | 103 | parser_register.set_defaults(callback=client_register) 104 | 105 | # Parser for part 1 of the project 2 106 | parser_loc = subparsers.add_parser("loc", help="Part 1 of the project 2.") 107 | parser_loc.add_argument( 108 | "lat", 109 | help="Latitude.", 110 | type=float 111 | ) 112 | parser_loc.add_argument( 113 | "lon", 114 | help="Longitude.", 115 | type=float 116 | ) 117 | parser_loc.add_argument( 118 | "-p", 119 | "--pub", 120 | help="Name of the file from which to read the public key.", 121 | type=argparse.FileType("rb"), 122 | default="key-client.pub" 123 | ) 124 | parser_loc.add_argument( 125 | "-c", 126 | "--credential", 127 | help="Name of the file from which to read the attribute-based credential.", 128 | type=argparse.FileType("rb"), 129 | default="anon.cred" 130 | ) 131 | parser_loc.add_argument( 132 | "-T", 133 | "--types", 134 | help="Types of services to request.", 135 | type=str, 136 | required=True, 137 | action="append" 138 | ) 139 | parser_loc.add_argument( 140 | "-t", 141 | "--tor", 142 | help="Use Tor to connect to the server.", 143 | action="store_true" 144 | ) 145 | 146 | parser_loc.set_defaults(callback=client_loc) 147 | 148 | # Parser for part 3 of the project 2 149 | parser_grid = subparsers.add_parser("grid", help="Part 3 of the project 2.") 150 | parser_grid.add_argument( 151 | "cell_id", 152 | help="Cell identifier.", 153 | type=int 154 | ) 155 | parser_grid.add_argument( 156 | "-p", 157 | "--pub", 158 | help="Name of the file from which to read the public key.", 159 | type=argparse.FileType("rb"), 160 | default="key-client.pub" 161 | ) 162 | parser_grid.add_argument( 163 | "-c", 164 | "--credential", 165 | help="Name of the file from which to read the attribute-based credential.", 166 | type=argparse.FileType("rb"), 167 | default="anon.cred" 168 | ) 169 | parser_grid.add_argument( 170 | "-T", 171 | "--types", 172 | help="Types of services to request.", 173 | type=str, 174 | default=list(), 175 | action="append" 176 | ) 177 | parser_grid.add_argument( 178 | "-t", 179 | "--tor", 180 | help="Use Tor to connect to the server.", 181 | action="store_true" 182 | ) 183 | parser_grid.set_defaults(callback=client_grid) 184 | 185 | namespace = parser.parse_args(args) 186 | 187 | if "callback" in namespace: 188 | namespace.callback(namespace) 189 | 190 | else: 191 | parser.print_help() 192 | 193 | 194 | def read_hostname(hostname_path: Path) -> str: 195 | """Retrieve an hostname from a file.""" 196 | 197 | with hostname_path.open("r") as hostname_fd: 198 | hostname = hostname_fd.read().strip() 199 | 200 | return hostname 201 | 202 | 203 | def get_conn_params(use_tor: bool) -> Tuple[str, Optional[str]]: 204 | """Compute connections parameters.""" 205 | if use_tor: 206 | host = read_hostname(TOR_HOSTNAME_FILENAME) 207 | proxy = TOR_PROXY 208 | else: 209 | host = f"{SERVER_HOSTNAME}:8080" 210 | proxy = None 211 | 212 | return host, proxy 213 | 214 | 215 | def create_session(proxy: str) -> requests.Session: 216 | """Create a Requests session.""" 217 | 218 | session = requests.session() 219 | 220 | if proxy: 221 | session.proxies = {"http": proxy, "https": proxy} 222 | 223 | return session 224 | 225 | 226 | def client_get_pk(args: argparse.Namespace) -> None: 227 | """Handle `get-pk` subcommand.""" 228 | 229 | public_key_fd = args.out 230 | 231 | try: 232 | host, proxy = get_conn_params(args.tor) 233 | 234 | url = f"http://{host}/public-key" 235 | 236 | # Done in a proper way, we would use HTTPS instead of HTTP. 237 | session = create_session(proxy) 238 | res = session.get(url=url) 239 | 240 | if res.status_code != 200: 241 | raise ClientHTTPError( 242 | "The client failed to retrieve the public key from the server!" 243 | ) 244 | 245 | public_key = res.content 246 | 247 | public_key_fd.write(public_key) 248 | public_key_fd.flush() 249 | 250 | finally: 251 | args.out.close() 252 | 253 | 254 | def client_register(args: argparse.Namespace) -> None: 255 | """Handle `register` subcommand.""" 256 | 257 | try: 258 | public_key = args.pub.read() 259 | 260 | finally: 261 | args.pub.close() 262 | 263 | try: 264 | credential_fd = args.out 265 | 266 | username = args.user 267 | subscriptions = args.subscriptions 268 | 269 | # Copy to prepare registration 270 | subscriptions_client = copy.deepcopy(subscriptions) 271 | 272 | client = Client() 273 | issuance_req, state = client.prepare_registration( 274 | public_key, username, subscriptions_client 275 | ) 276 | 277 | host, proxy = get_conn_params(args.tor) 278 | 279 | # Done in a proper way, we would use HTTPS instead of HTTP. 280 | url = f"http://{host}/register" 281 | files = { 282 | "username": username, 283 | "subscriptions": json.dumps(subscriptions), 284 | "issuance_req": issuance_req, 285 | } 286 | 287 | session = create_session(proxy) 288 | res = session.post(url=url, files=files) 289 | 290 | if res.status_code != 200: 291 | raise ClientHTTPError("The client failed to register to the server!") 292 | 293 | issuance_res = res.content 294 | 295 | credential = client.process_registration_response( 296 | public_key, issuance_res, state 297 | ) 298 | 299 | credential_fd.write(credential) 300 | credential_fd.flush() 301 | 302 | finally: 303 | args.out.close() 304 | 305 | 306 | def client_loc(args: argparse.Namespace) -> None: 307 | """Handle `loc` subcommand.""" 308 | 309 | try: 310 | lat = args.lat 311 | lon = args.lon 312 | types = args.types 313 | public_key = args.pub.read() 314 | credential = args.credential.read() 315 | 316 | finally: 317 | args.pub.close() 318 | args.credential.close() 319 | 320 | client = Client() 321 | message = (f"{lat},{lon}").encode("utf-8") 322 | signature = client.sign_request(public_key, credential, message, types) 323 | 324 | host, proxy = get_conn_params(args.tor) 325 | 326 | url = f"http://{host}/poi-loc" 327 | files = { 328 | "lat": str(lat), 329 | "lon": str(lon), 330 | "types": json.dumps(types), 331 | "signature": signature, 332 | } 333 | 334 | # Done in a proper way, we would use HTTPS instead of HTTP. 335 | session = create_session(proxy) 336 | res = session.post(url=url, files=files) 337 | 338 | if res.status_code != 200: 339 | raise ClientHTTPError(f"Invalid return code {res.status_code}!") 340 | 341 | res_json = res.json() 342 | 343 | poi_ids = res_json["poi_list"] 344 | 345 | if not poi_ids: 346 | print("Sigh... nothing interesting nearby.") 347 | 348 | # No signature, etc... for retrieving the info about the PoIs themselves. 349 | for poi_id in poi_ids: 350 | url = f"http://{host}/poi" 351 | params = {"poi_id": poi_id} 352 | res = session.get(url=url, params=params) 353 | if res.status_code != 200: 354 | raise ClientHTTPError(f"Invalid return code {res.status_code}!") 355 | 356 | poi = res.json() 357 | print(f'You are near "{poi["poi_name"]}".') 358 | 359 | 360 | def client_grid(args: argparse.Namespace) -> None: 361 | """Handle `grid` subcommand.""" 362 | 363 | try: 364 | cell_id = args.cell_id 365 | types = args.types 366 | public_key = args.pub.read() 367 | credential = args.credential.read() 368 | 369 | finally: 370 | args.pub.close() 371 | args.credential.close() 372 | 373 | client = Client() 374 | message = (f"{cell_id}").encode("utf-8") 375 | signature = client.sign_request(public_key, credential, message, types) 376 | 377 | host, proxy = get_conn_params(args.tor) 378 | 379 | url = f"http://{host}/poi-grid" 380 | files = { 381 | "cell_id": str(cell_id), 382 | "types": json.dumps(types), 383 | "signature": signature, 384 | } 385 | 386 | # Done in a proper way, we would use HTTPS instead of HTTP. 387 | session = create_session(proxy) 388 | res = session.post(url=url, files=files) 389 | 390 | if res.status_code != 200: 391 | raise ClientHTTPError(f"Invalid return code {res.status_code}!") 392 | 393 | res_json = res.json() 394 | 395 | poi_ids = res_json["poi_list"] 396 | 397 | if not poi_ids: 398 | print("Sigh... nothing interesting nearby.") 399 | 400 | # No signature, etc... for retrieving the info about the PoIs themselves. 401 | for poi_id in poi_ids: 402 | url = f"http://{host}/poi" 403 | params = {"poi_id": poi_id} 404 | res = session.get(url=url, params=params) 405 | if res.status_code != 200: 406 | raise ClientHTTPError(f"Invalid return code {res.status_code}!") 407 | 408 | poi = res.json() 409 | print(f'You are near "{poi["poi_name"]}".') 410 | 411 | 412 | if __name__ == "__main__": 413 | main(sys.argv[1:]) 414 | -------------------------------------------------------------------------------- /secretstroll/credential.py: -------------------------------------------------------------------------------- 1 | """ 2 | Skeleton credential module for implementing PS credentials 3 | 4 | The goal of this skeleton is to help you implementing PS credentials. Following 5 | this API is not mandatory and you can change it as you see fit. This skeleton 6 | only provides major functionality that you will need. 7 | 8 | You will likely have to define more functions and/or classes. In particular, to 9 | maintain clean code, we recommend to use classes for things that you want to 10 | send between parties. You can then use `jsonpickle` serialization to convert 11 | these classes to byte arrays (as expected by the other classes) and back again. 12 | 13 | We also avoided the use of classes in this template so that the code more closely 14 | resembles the original scheme definition. However, you are free to restructure 15 | the functions provided to resemble a more object-oriented interface. 16 | """ 17 | 18 | from typing import Any, List, Tuple 19 | 20 | from serialization import jsonpickle 21 | 22 | 23 | # Type hint aliases 24 | # Feel free to change them as you see fit. 25 | # Maybe at the end, you will not need aliases at all! 26 | SecretKey = Any 27 | PublicKey = Any 28 | Signature = Any 29 | Attribute = Any 30 | AttributeMap = Any 31 | IssueRequest = Any 32 | BlindSignature = Any 33 | AnonymousCredential = Any 34 | DisclosureProof = Any 35 | 36 | 37 | ###################### 38 | ## SIGNATURE SCHEME ## 39 | ###################### 40 | 41 | 42 | def generate_key( 43 | attributes: List[Attribute] 44 | ) -> Tuple[SecretKey, PublicKey]: 45 | """ Generate signer key pair """ 46 | raise NotImplementedError() 47 | 48 | 49 | def sign( 50 | sk: SecretKey, 51 | msgs: List[bytes] 52 | ) -> Signature: 53 | """ Sign the vector of messages `msgs` """ 54 | raise NotImplementedError() 55 | 56 | 57 | def verify( 58 | pk: PublicKey, 59 | signature: Signature, 60 | msgs: List[bytes] 61 | ) -> bool: 62 | """ Verify the signature on a vector of messages """ 63 | raise NotImplementedError() 64 | 65 | 66 | ################################# 67 | ## ATTRIBUTE-BASED CREDENTIALS ## 68 | ################################# 69 | 70 | ## ISSUANCE PROTOCOL ## 71 | 72 | def create_issue_request( 73 | pk: PublicKey, 74 | user_attributes: AttributeMap 75 | ) -> IssueRequest: 76 | """ Create an issuance request 77 | 78 | This corresponds to the "user commitment" step in the issuance protocol. 79 | 80 | *Warning:* You may need to pass state to the `obtain_credential` function. 81 | """ 82 | raise NotImplementedError() 83 | 84 | 85 | def sign_issue_request( 86 | sk: SecretKey, 87 | pk: PublicKey, 88 | request: IssueRequest, 89 | issuer_attributes: AttributeMap 90 | ) -> BlindSignature: 91 | """ Create a signature corresponding to the user's request 92 | 93 | This corresponds to the "Issuer signing" step in the issuance protocol. 94 | """ 95 | raise NotImplementedError() 96 | 97 | 98 | def obtain_credential( 99 | pk: PublicKey, 100 | response: BlindSignature 101 | ) -> AnonymousCredential: 102 | """ Derive a credential from the issuer's response 103 | 104 | This corresponds to the "Unblinding signature" step. 105 | """ 106 | raise NotImplementedError() 107 | 108 | 109 | ## SHOWING PROTOCOL ## 110 | 111 | def create_disclosure_proof( 112 | pk: PublicKey, 113 | credential: AnonymousCredential, 114 | hidden_attributes: List[Attribute], 115 | message: bytes 116 | ) -> DisclosureProof: 117 | """ Create a disclosure proof """ 118 | raise NotImplementedError() 119 | 120 | 121 | def verify_disclosure_proof( 122 | pk: PublicKey, 123 | disclosure_proof: DisclosureProof, 124 | message: bytes 125 | ) -> bool: 126 | """ Verify the disclosure proof 127 | 128 | Hint: The verifier may also want to retrieve the disclosed attributes 129 | """ 130 | raise NotImplementedError() 131 | -------------------------------------------------------------------------------- /secretstroll/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | server: 4 | build: ./docker/server_img 5 | image: cs523-server-img:latest 6 | container_name: cs523-server 7 | networks: 8 | - cs523 9 | volumes: 10 | - ./tor:/var/lib/tor 11 | - ./:/server 12 | client: 13 | build: ./docker/client_img 14 | image: cs523-client-img:latest 15 | container_name: cs523-client 16 | networks: 17 | - cs523 18 | volumes: 19 | - ./:/client 20 | networks: 21 | cs523: 22 | driver: bridge 23 | 24 | -------------------------------------------------------------------------------- /secretstroll/docker/client_img/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bullseye 2 | 3 | # Identical parts between client and server. 4 | # Add Tor repo and install necessary packages. 5 | RUN \ 6 | apt-get update && \ 7 | apt-get install -y \ 8 | apt-utils \ 9 | apt-transport-https \ 10 | curl \ 11 | gnupg2 && \ 12 | curl -sSL https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc | gpg --dearmor | tee /usr/share/keyrings/tor-archive-keyring.gpg >/dev/null && \ 13 | echo "deb [signed-by=/usr/share/keyrings/tor-archive-keyring.gpg] https://deb.torproject.org/torproject.org bullseye main" > /etc/apt/sources.list.d/tor.list && \ 14 | apt-get update && \ 15 | apt-get install -y \ 16 | deb.torproject.org-keyring \ 17 | locales \ 18 | procps \ 19 | python3-dev \ 20 | python3-pip \ 21 | runit \ 22 | tcpdump \ 23 | tor \ 24 | uwsgi \ 25 | uwsgi-plugin-python3 26 | 27 | # UTF-8 support in console. 28 | RUN \ 29 | printf '%s\n' 'fr_CH.UTF-8 UTF-8' 'en_US.UTF-8 UTF-8' >> /etc/locale.gen && \ 30 | printf '%s\n' 'LANG="en_US.UTF-8"' 'LANGUAGE="en_US:en"' >> /etc/default/locale && \ 31 | dpkg-reconfigure --frontend=noninteractive locales && \ 32 | update-locale 'LANG=en_US.UTF-8' && \ 33 | echo "export VISIBLE=now" >> /etc/profile 34 | 35 | # Python dependancies. 36 | COPY requirements.txt /tmp/requirements.txt 37 | RUN pip3 install -r /tmp/requirements.txt 38 | 39 | # Client and server starts to differ here. 40 | 41 | # Add client directory. 42 | RUN mkdir -p /client/data 43 | 44 | COPY ./etc /tmp/etc 45 | RUN rm -r /etc/service && cp -R /tmp/etc/* /etc/ && chmod 755 -R /etc/service && rm -rf /tmp/etc 46 | 47 | # Volume containing code. 48 | VOLUME ["/client"] 49 | 50 | ENTRYPOINT ["runsvdir", "-P", "/etc/service"] 51 | -------------------------------------------------------------------------------- /secretstroll/docker/client_img/etc/service/tor/log/run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | LOG_DIR='/var/log/service/tor' 4 | 5 | if [ -e $LOG_DIR ] 6 | then 7 | if [ ! -d $LOG_DIR ] 8 | then 9 | exit 1 10 | fi 11 | else 12 | mkdir -p $LOG_DIR 13 | fi 14 | 15 | exec /usr/bin/svlogd -tt $LOG_DIR 16 | -------------------------------------------------------------------------------- /secretstroll/docker/client_img/etc/service/tor/run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | exec 2>&1 4 | exec chpst -P -u debian-tor:debian-tor env HOME=/var/lib/tor tor 5 | -------------------------------------------------------------------------------- /secretstroll/docker/client_img/requirements.txt: -------------------------------------------------------------------------------- 1 | astroid==2.14.2 2 | attrs==22.2.0 3 | certifi==2022.12.7 4 | cffi==1.15.1 5 | charset-normalizer==3.0.1 6 | click==8.1.3 7 | dill==0.3.6 8 | exceptiongroup==1.1.0 9 | Flask==2.2.2 10 | Flask-SQLAlchemy==3.0.3 11 | greenlet==2.0.2 12 | idna==3.4 13 | importlib-metadata==6.0.0 14 | iniconfig==2.0.0 15 | isort==5.12.0 16 | itsdangerous==2.1.2 17 | Jinja2==3.1.2 18 | jsonpickle==3.0.1 19 | lazy-object-proxy==1.9.0 20 | MarkupSafe==2.1.2 21 | mccabe==0.7.0 22 | msgpack==1.0.4 23 | mypy==1.0.0 24 | mypy-extensions==1.0.0 25 | packaging==23.0 26 | petrelic==0.1.5 27 | platformdirs==3.0.0 28 | pluggy==1.0.0 29 | pycparser==2.21 30 | pylint==2.16.1 31 | PySocks==1.7.1 32 | pytest==7.2.1 33 | requests==2.28.2 34 | SQLAlchemy==2.0.3 35 | tomli==2.0.1 36 | tomlkit==0.11.6 37 | typing-extensions==4.4.0 38 | urllib3==1.26.14 39 | Werkzeug==2.2.3 40 | wrapt==1.14.1 41 | zipp==3.13.0 42 | -------------------------------------------------------------------------------- /secretstroll/docker/server_img/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bullseye 2 | 3 | # Identical parts between client and server. 4 | # Add Tor repo and install necessary packages. 5 | RUN \ 6 | apt-get update && \ 7 | apt-get install -y \ 8 | apt-utils \ 9 | apt-transport-https \ 10 | curl \ 11 | gnupg2 && \ 12 | curl -sSL https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc | gpg --dearmor | tee /usr/share/keyrings/tor-archive-keyring.gpg >/dev/null && \ 13 | echo "deb [signed-by=/usr/share/keyrings/tor-archive-keyring.gpg] https://deb.torproject.org/torproject.org bullseye main" > /etc/apt/sources.list.d/tor.list && \ 14 | apt-get update && \ 15 | apt-get install -y \ 16 | deb.torproject.org-keyring \ 17 | locales \ 18 | procps \ 19 | python3-dev \ 20 | python3-pip \ 21 | runit \ 22 | tcpdump \ 23 | tor \ 24 | uwsgi \ 25 | uwsgi-plugin-python3 26 | 27 | # UTF-8 support in console. 28 | RUN \ 29 | printf '%s\n' 'fr_CH.UTF-8 UTF-8' 'en_US.UTF-8 UTF-8' >> /etc/locale.gen && \ 30 | printf '%s\n' 'LANG="en_US.UTF-8"' 'LANGUAGE="en_US:en"' >> /etc/default/locale && \ 31 | dpkg-reconfigure --frontend=noninteractive locales && \ 32 | update-locale 'LANG=en_US.UTF-8' && \ 33 | echo "export VISIBLE=now" >> /etc/profile 34 | 35 | # Python dependancies. 36 | COPY requirements.txt /tmp/requirements.txt 37 | RUN pip3 install -r /tmp/requirements.txt 38 | 39 | # Client and server starts to differ here. 40 | 41 | # Add server directory. 42 | RUN mkdir -p /server 43 | 44 | COPY ./etc /tmp/etc 45 | RUN rm -r /etc/service /etc/tor && \ 46 | cp -R /tmp/etc/* /etc/ && \ 47 | chmod 755 /etc/tor && \ 48 | chmod 644 /etc/tor/torrc && \ 49 | chmod 755 -R /etc/service && \ 50 | rm -rf /tmp/etc 51 | 52 | # Volume to store hidden service stuff. 53 | VOLUME ["/var/lib/tor/"] 54 | 55 | # Volume containing code. 56 | VOLUME ["/server/"] 57 | 58 | ENTRYPOINT ["runsvdir", "-P", "/etc/service"] 59 | -------------------------------------------------------------------------------- /secretstroll/docker/server_img/etc/service/tor/log/run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | LOG_DIR='/var/log/service/tor' 4 | 5 | if [ -e $LOG_DIR ] 6 | then 7 | if [ ! -d $LOG_DIR ] 8 | then 9 | exit 1 10 | fi 11 | else 12 | mkdir -p $LOG_DIR 13 | fi 14 | 15 | exec /usr/bin/svlogd -tt $LOG_DIR 16 | -------------------------------------------------------------------------------- /secretstroll/docker/server_img/etc/service/tor/run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Ensures Tor's HOME has the right owner. 4 | chown -R debian-tor:debian-tor /var/lib/tor 5 | chmod 750 /var/lib/tor 6 | 7 | if [ -e /var/lib/tor/hidden_service ] 8 | then 9 | chmod -R u+rwX go-rwx /var/lib/tor/hidden_service 10 | fi 11 | 12 | exec 2>&1 13 | 14 | # Specify the configuration file explicitly, otherwise Tor falls back on a safe default and continue running. 15 | exec chpst -P -u debian-tor:debian-tor env HOME=/var/lib/tor tor -f /etc/tor/torrc 16 | -------------------------------------------------------------------------------- /secretstroll/docker/server_img/etc/tor/torrc: -------------------------------------------------------------------------------- 1 | HiddenServiceDir /var/lib/tor/hidden_service/ 2 | HiddenServiceVersion 3 3 | HiddenServicePort 80 localhost:8080 4 | -------------------------------------------------------------------------------- /secretstroll/docker/server_img/etc/tor/torsocks.conf: -------------------------------------------------------------------------------- 1 | TorAddress 127.0.0.1 2 | TorPort 9050 3 | OnionAddrRange 127.42.42.0/24 4 | -------------------------------------------------------------------------------- /secretstroll/docker/server_img/requirements.txt: -------------------------------------------------------------------------------- 1 | astroid==2.14.2 2 | attrs==22.2.0 3 | certifi==2022.12.7 4 | cffi==1.15.1 5 | charset-normalizer==3.0.1 6 | click==8.1.3 7 | dill==0.3.6 8 | exceptiongroup==1.1.0 9 | Flask==2.2.2 10 | Flask-SQLAlchemy==3.0.3 11 | greenlet==2.0.2 12 | idna==3.4 13 | importlib-metadata==6.0.0 14 | iniconfig==2.0.0 15 | isort==5.12.0 16 | itsdangerous==2.1.2 17 | Jinja2==3.1.2 18 | jsonpickle==3.0.1 19 | lazy-object-proxy==1.9.0 20 | MarkupSafe==2.1.2 21 | mccabe==0.7.0 22 | msgpack==1.0.4 23 | mypy==1.0.0 24 | mypy-extensions==1.0.0 25 | packaging==23.0 26 | petrelic==0.1.5 27 | platformdirs==3.0.0 28 | pluggy==1.0.0 29 | pycparser==2.21 30 | pylint==2.16.1 31 | PySocks==1.7.1 32 | pytest==7.2.1 33 | requests==2.28.2 34 | SQLAlchemy==2.0.3 35 | tomli==2.0.1 36 | tomlkit==0.11.6 37 | typing-extensions==4.4.0 38 | urllib3==1.26.14 39 | Werkzeug==2.2.3 40 | wrapt==1.14.1 41 | zipp==3.13.0 42 | -------------------------------------------------------------------------------- /secretstroll/fingerprint.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-epfl/CS-523-public/624d148000290397cd4caab91b3f55bad54fd1df/secretstroll/fingerprint.db -------------------------------------------------------------------------------- /secretstroll/fingerprinting.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from sklearn.model_selection import train_test_split 4 | from sklearn.ensemble import RandomForestClassifier 5 | from sklearn.model_selection import StratifiedKFold 6 | 7 | 8 | def classify(train_features, train_labels, test_features, test_labels): 9 | 10 | """Function to perform classification, using a 11 | Random Forest. 12 | 13 | Reference: https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html 14 | 15 | Args: 16 | train_features (numpy array): list of features used to train the classifier 17 | train_labels (numpy array): list of labels used to train the classifier 18 | test_features (numpy array): list of features used to test the classifier 19 | test_labels (numpy array): list of labels (ground truth) of the test dataset 20 | 21 | Returns: 22 | predictions: list of labels predicted by the classifier for test_features 23 | 24 | Note: You are free to make changes the parameters of the RandomForestClassifier(). 25 | """ 26 | 27 | # Initialize a random forest classifier. Change parameters if desired. 28 | clf = RandomForestClassifier() 29 | # Train the classifier using the training features and labels. 30 | clf.fit(train_features, train_labels) 31 | # Use the classifier to make predictions on the test features. 32 | predictions = clf.predict(test_features) 33 | 34 | return predictions 35 | 36 | def perform_crossval(features, labels, folds=10): 37 | 38 | """Function to perform cross-validation. 39 | Args: 40 | features (list): list of features 41 | labels (list): list of labels 42 | folds (int): number of fold for cross-validation (default=10) 43 | Returns: 44 | You can modify this as you like. 45 | 46 | This function splits the data into training and test sets. It feeds 47 | the sets into the classify() function for each fold. 48 | 49 | You need to use the data returned by classify() over all folds 50 | to evaluate the performance. 51 | """ 52 | 53 | kf = StratifiedKFold(n_splits=folds) 54 | labels = np.array(labels) 55 | features = np.array(features) 56 | 57 | for train_index, test_index in kf.split(features, labels): 58 | X_train, X_test = features[train_index], features[test_index] 59 | y_train, y_test = labels[train_index], labels[test_index] 60 | predictions = classify(X_train, y_train, X_test, y_test) 61 | 62 | ############################################### 63 | # TODO: Write code to evaluate the performance of your classifier 64 | ############################################### 65 | 66 | def load_data(): 67 | 68 | """Function to load data that will be used for classification. 69 | 70 | Args: 71 | You can provide the args you want. 72 | Returns: 73 | features (list): the list of features you extract from every trace 74 | labels (list): the list of identifiers for each trace 75 | 76 | An example: Assume you have traces (trace1...traceN) for cells with IDs in the 77 | range 1-N. 78 | 79 | You extract a list of features from each trace: 80 | features_trace1 = [f11, f12, ...] 81 | . 82 | . 83 | features_traceN = [fN1, fN2, ...] 84 | 85 | Your inputs to the classifier will be: 86 | 87 | features = [features_trace1, ..., features_traceN] 88 | labels = [1, ..., N] 89 | 90 | Note: You will have to decide what features/labels you want to use and implement 91 | feature extraction on your own. 92 | """ 93 | 94 | ############################################### 95 | # TODO: Complete this function. 96 | ############################################### 97 | 98 | features = [] 99 | labels = [] 100 | 101 | return features, labels 102 | 103 | def main(): 104 | 105 | """Please complete this skeleton to implement cell fingerprinting. 106 | This skeleton provides the code to perform classification 107 | using a Random Forest classifier. You are free to modify the 108 | provided functions as you wish. 109 | 110 | Read about random forests: https://towardsdatascience.com/understanding-random-forest-58381e0602d2 111 | """ 112 | 113 | features, labels = load_data() 114 | perform_crossval(features, labels, folds=10) 115 | 116 | if __name__ == "__main__": 117 | try: 118 | main() 119 | except KeyboardInterrupt: 120 | sys.exit(0) -------------------------------------------------------------------------------- /secretstroll/handout/ABC_guide.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-epfl/CS-523-public/624d148000290397cd4caab91b3f55bad54fd1df/secretstroll/handout/ABC_guide.pdf -------------------------------------------------------------------------------- /secretstroll/handout/handout_project_secretstroll.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-epfl/CS-523-public/624d148000290397cd4caab91b3f55bad54fd1df/secretstroll/handout/handout_project_secretstroll.pdf -------------------------------------------------------------------------------- /secretstroll/privacy_evaluation/pois.csv: -------------------------------------------------------------------------------- 1 | poi_id cell_id poi_type lat lon 2 | 307 1 bar 46.504486492015666 6.559630522223834 3 | 331 1 bar 46.50025877596939 6.554720529747843 4 | 578 1 supermarket 46.50601728064788 6.551165279670896 5 | 897 1 club 46.504494240870855 6.553888840292449 6 | 972 1 supermarket 46.50298419258046 6.550518965743515 7 | 280 2 club 46.505651059517156 6.567442535469904 8 | 453 2 supermarket 46.50168922199655 6.564189722851528 9 | 589 2 restaurant 46.500944424360135 6.564773582928777 10 | 711 2 cafeteria 46.50145159105236 6.56920902874812 11 | 743 2 cafeteria 46.500284936372914 6.564343000817233 12 | 772 2 cafeteria 46.50697472912017 6.563022502216543 13 | 948 2 restaurant 46.50055839896267 6.564312304003391 14 | 992 2 cafeteria 46.50691217317973 6.565422252752104 15 | 341 3 club 46.50483123793074 6.575236871091997 16 | 493 3 bar 46.50471262514785 6.57197741773289 17 | 669 3 restaurant 46.505181930761424 6.571932458239587 18 | 825 3 gym 46.500747702967864 6.575048072204613 19 | 315 4 bar 46.50560647080356 6.585854963179787 20 | 318 4 gym 46.50005085562444 6.583769105491683 21 | 416 4 dojo 46.506416619385966 6.5804765638512865 22 | 449 4 club 46.50199869372544 6.584706874716172 23 | 512 4 gym 46.50308066972222 6.581553617586899 24 | 684 4 club 46.50278240156154 6.586575675144656 25 | 841 4 restaurant 46.501088035587536 6.583888836399997 26 | 843 4 gym 46.50218419313822 6.581013674529343 27 | 869 4 bar 46.50190772763826 6.587851315748939 28 | 980 4 dojo 46.501168913091085 6.586368493874403 29 | 1007 4 club 46.50174929978327 6.585574689664131 30 | 265 5 gym 46.501701089500386 6.597372493088931 31 | 293 5 club 46.505599096099125 6.5927733045872605 32 | 337 5 restaurant 46.50511337696825 6.590775848601839 33 | 358 5 dojo 46.50244306465801 6.59105469306489 34 | 404 5 supermarket 46.506456898589335 6.598229193113482 35 | 443 5 supermarket 46.50570950606372 6.5966090434772555 36 | 574 5 supermarket 46.50147243477804 6.596087808005498 37 | 708 5 cafeteria 46.506215695052276 6.5996733217721735 38 | 758 5 club 46.50479033372219 6.594049382935638 39 | 795 5 club 46.50248351663344 6.594087642488705 40 | 918 5 bar 46.50265017651884 6.597883537529296 41 | 285 6 restaurant 46.502702079968735 6.608550271523718 42 | 680 6 dojo 46.50048265261564 6.607436402199378 43 | 915 6 restaurant 46.500693811593905 6.602018986633117 44 | 1020 6 cafeteria 46.50545812703786 6.602354310490593 45 | 197 7 appartment_block 46.5067498844889 6.618531691777435 46 | 406 7 bar 46.50659041084129 6.615843730318591 47 | 538 7 club 46.50003195953242 6.616028703614571 48 | 540 7 club 46.502457631497975 6.6111712880537805 49 | 613 7 cafeteria 46.50115431443934 6.610757059541697 50 | 625 7 cafeteria 46.5021945453081 6.614764294511265 51 | 643 7 supermarket 46.50056538762795 6.610782789450008 52 | 658 7 bar 46.500629544858214 6.613635918395412 53 | 672 7 bar 46.50241126356187 6.614453635040824 54 | 686 7 cafeteria 46.500091250216826 6.61478956026554 55 | 829 7 club 46.50664556705327 6.614904612776403 56 | 914 7 cafeteria 46.50223896522834 6.614898315064322 57 | 945 7 supermarket 46.506408224185364 6.610495058003881 58 | 463 8 bar 46.50167409718998 6.622341361558457 59 | 551 8 supermarket 46.503390307685954 6.620846201664815 60 | 606 8 club 46.503650996649725 6.622950867708268 61 | 805 8 supermarket 46.502107569130196 6.621064231308987 62 | 833 8 club 46.503637844877005 6.6276736078942475 63 | 1027 8 dojo 46.50049771074951 6.62544408475984 64 | 312 9 supermarket 46.50362067153672 6.636210908295164 65 | 330 9 gym 46.50136568182529 6.632408548387381 66 | 383 9 supermarket 46.50073401010094 6.639241162212317 67 | 444 9 supermarket 46.50010466356885 6.63965380461618 68 | 505 9 gym 46.501626444188624 6.630849461435616 69 | 529 9 cafeteria 46.50242613640737 6.634063663916213 70 | 592 9 supermarket 46.501575140607066 6.63069134260276 71 | 632 9 cafeteria 46.50545302996118 6.63041536640722 72 | 653 9 gym 46.50383449496321 6.633682945702737 73 | 754 9 restaurant 46.50666442965108 6.635252587600409 74 | 851 9 supermarket 46.50108871345575 6.630368956569507 75 | 854 9 gym 46.50401018841022 6.634091332030542 76 | 1017 9 gym 46.50673364267848 6.639421299941641 77 | 156 10 villa 46.50499317111984 6.646247394953719 78 | 269 10 supermarket 46.50301841141297 6.6428132570098075 79 | 440 10 bar 46.50197837744923 6.645288581433664 80 | 502 10 dojo 46.50547984922185 6.647003420241536 81 | 549 10 club 46.505332161567814 6.647583970175094 82 | 832 10 dojo 46.50183845599772 6.646603594454618 83 | 865 10 bar 46.50426481997377 6.642105159041783 84 | 257 11 office 46.513706793542674 6.558102491752225 85 | 266 11 supermarket 46.50947379881822 6.558148203466772 86 | 382 11 bar 46.51180460845165 6.558537701363376 87 | 408 11 dojo 46.513895206529604 6.553306357459751 88 | 517 11 restaurant 46.50992186108203 6.553112214147229 89 | 671 11 cafeteria 46.51037328371071 6.557046700375656 90 | 683 11 club 46.50781556552811 6.554025928355682 91 | 816 11 gym 46.50739241629512 6.559545992515631 92 | 870 11 supermarket 46.50703110651438 6.558286437116808 93 | 1018 11 club 46.50977614328398 6.556385036508658 94 | 1019 11 dojo 46.51361949252411 6.552697755761173 95 | 4 12 appartment_block 46.51295691581392 6.560881345927797 96 | 297 12 club 46.51278194920244 6.56250998975277 97 | 364 12 cafeteria 46.509681052355816 6.565296819960671 98 | 405 12 supermarket 46.50776112363315 6.5641071210499575 99 | 601 12 gym 46.51171873633322 6.564026531433854 100 | 1050 12 dojo 46.51058355625161 6.561973615612783 101 | 546 13 dojo 46.512189418828186 6.574103996625784 102 | 600 13 gym 46.513216692285184 6.5716384897121465 103 | 703 13 supermarket 46.51025245444882 6.57435749481911 104 | 804 13 bar 46.507069977556164 6.574563724190313 105 | 855 13 bar 46.51192928739709 6.57785607541117 106 | 888 13 dojo 46.51381783313823 6.570403001508207 107 | 894 13 supermarket 46.513837848329295 6.579516522461013 108 | 360 14 bar 46.50772092605322 6.585696370278118 109 | 422 14 supermarket 46.508362187803414 6.5855058979244925 110 | 490 14 supermarket 46.51345023387242 6.587263822970813 111 | 747 14 dojo 46.512208493739095 6.581660564738633 112 | 837 14 club 46.50748837328541 6.5884893392830755 113 | 846 14 restaurant 46.50867479136212 6.5857490217396455 114 | 884 14 restaurant 46.51384145801624 6.588867324554384 115 | 1047 14 bar 46.507403790977364 6.587885218668146 116 | 238 15 laboratory 46.51175825237519 6.593465969706802 117 | 474 15 cafeteria 46.51313423357012 6.599609960758724 118 | 485 15 restaurant 46.50939186175912 6.594321928079543 119 | 575 15 dojo 46.513984951185925 6.59120410171733 120 | 681 15 gym 46.50776662227332 6.593521007721562 121 | 698 15 cafeteria 46.510086083604904 6.59295331641394 122 | 745 15 bar 46.50890095688539 6.598496670677178 123 | 785 15 gym 46.50871404646236 6.599634252764708 124 | 377 16 restaurant 46.5103352347787 6.608349205252134 125 | 451 16 club 46.50940649480904 6.601703082838692 126 | 753 16 restaurant 46.50796566334957 6.608999674699065 127 | 1037 16 gym 46.51080441174494 6.603668509115471 128 | 160 17 villa 46.509161861472805 6.616751243334942 129 | 174 17 villa 46.507692844471755 6.6109719577603165 130 | 184 17 villa 46.5126394441926 6.611692514342311 131 | 191 17 appartment_block 46.50773223502507 6.616459350802756 132 | 381 17 gym 46.50778294696411 6.618854324319899 133 | 480 17 dojo 46.510650386991706 6.618873471481859 134 | 500 17 dojo 46.50954670732587 6.614296876569411 135 | 864 17 restaurant 46.507164978818096 6.611651383555449 136 | 1043 17 restaurant 46.513245569065845 6.61495877973916 137 | 18 18 villa 46.50740788216217 6.623643666927378 138 | 44 18 appartment_block 46.510324835038304 6.620990696572315 139 | 124 18 appartment_block 46.510699517190126 6.6288432152377466 140 | 147 18 villa 46.50993850266074 6.623808625085496 141 | 179 18 villa 46.51061770987046 6.622002401188401 142 | 379 18 restaurant 46.507476975461685 6.627928229903694 143 | 439 18 club 46.5079175748129 6.629721077307051 144 | 640 18 club 46.507000532954336 6.626434535850412 145 | 757 18 restaurant 46.513655725369574 6.629129707945616 146 | 863 18 bar 46.50805266391539 6.627818116025058 147 | 868 18 gym 46.51174654797308 6.6204633346436506 148 | 58 19 appartment_block 46.513842631757456 6.631423772304648 149 | 102 19 appartment_block 46.512935112687565 6.634070885163769 150 | 564 19 gym 46.51149625919493 6.638691741119404 151 | 568 19 restaurant 46.51268746820304 6.63641899587261 152 | 736 19 dojo 46.50954460414406 6.63434146990168 153 | 809 19 restaurant 46.50853806553422 6.632247005090991 154 | 836 19 club 46.51362591170685 6.633745598798634 155 | 838 19 club 46.51384877401542 6.638021175914131 156 | 912 19 bar 46.51281351028029 6.639091366899841 157 | 934 19 dojo 46.50810661661717 6.632070145085537 158 | 950 19 restaurant 46.513035056725954 6.632843650497162 159 | 1044 19 cafeteria 46.51305868671747 6.6347891550941585 160 | 1056 19 club 46.51222070936319 6.630851508977772 161 | 355 20 cafeteria 46.513698510016496 6.645733768684887 162 | 428 20 restaurant 46.51079863007427 6.6432881558265775 163 | 429 20 dojo 46.513085864075094 6.645587573452365 164 | 654 20 cafeteria 46.510178368160425 6.644142949508075 165 | 787 20 cafeteria 46.509526718695874 6.642143586188201 166 | 800 20 gym 46.509750738150636 6.641607048994993 167 | 819 20 bar 46.50989513470177 6.647912914642325 168 | 414 21 club 46.519211979178515 6.5508972067353355 169 | 682 21 gym 46.51707261493953 6.55242897608778 170 | 928 21 bar 46.52062129938318 6.555800021440664 171 | 76 22 villa 46.5205175117895 6.5684831925581335 172 | 123 22 appartment_block 46.520482123753524 6.566755965077097 173 | 145 22 villa 46.52050186377512 6.562632321686435 174 | 519 22 club 46.51849161898271 6.561542953061924 175 | 527 22 bar 46.51760663293254 6.560419062912986 176 | 531 22 restaurant 46.51518756963081 6.567719102422657 177 | 641 22 club 46.51932870600342 6.5610638921225695 178 | 956 22 bar 46.51759193422245 6.568951847877198 179 | 208 23 company 46.52079424704686 6.579619289856218 180 | 281 23 restaurant 46.514112315967786 6.576096450036718 181 | 554 23 club 46.51966132365741 6.572543987075588 182 | 562 23 dojo 46.51725735034785 6.5781690830503035 183 | 657 23 restaurant 46.518751015496925 6.571163399817897 184 | 700 23 gym 46.51784422201947 6.574179774177418 185 | 709 23 cafeteria 46.52056200494824 6.577343198293258 186 | 768 23 gym 46.51410581884019 6.574071112346006 187 | 799 23 cafeteria 46.5185411790898 6.571159928379039 188 | 882 23 bar 46.520042152161096 6.574719499812286 189 | 999 23 restaurant 46.514209687929835 6.577225267686699 190 | 211 24 laboratory 46.519792245810606 6.587302357717578 191 | 304 24 supermarket 46.51790797395306 6.5809742857238165 192 | 339 24 dojo 46.51719518008068 6.588175929505403 193 | 400 24 club 46.51608394494556 6.58643221941556 194 | 417 24 dojo 46.5158379885683 6.586749912771263 195 | 665 24 dojo 46.517770811778114 6.583181236447614 196 | 702 24 restaurant 46.516671676426775 6.583772889244543 197 | 1022 24 cafeteria 46.518080287543434 6.580246672300061 198 | 223 25 office 46.520002985391244 6.591259889093861 199 | 340 25 gym 46.516803830677034 6.598118304012014 200 | 446 25 dojo 46.51588648047285 6.591542500542051 201 | 462 25 dojo 46.51739290859232 6.598055154662743 202 | 685 25 dojo 46.518848297029905 6.598314096596816 203 | 802 25 bar 46.520921480674325 6.597019664625002 204 | 900 25 dojo 46.516254473997826 6.598470593222779 205 | 902 25 dojo 46.52019723748516 6.598511333878344 206 | 1035 25 cafeteria 46.517222898761624 6.596116635185272 207 | 128 26 villa 46.51910367874457 6.602880620768146 208 | 185 26 appartment_block 46.518358956917496 6.604090810911124 209 | 192 26 villa 46.51771592680659 6.607285556126612 210 | 352 26 club 46.51985968234634 6.600356289083149 211 | 365 26 supermarket 46.51597746433378 6.602530352872279 212 | 398 26 gym 46.51880842907966 6.600750022746831 213 | 508 26 club 46.518087506202704 6.609351729189718 214 | 511 26 bar 46.51598145557709 6.609023123431117 215 | 570 26 supermarket 46.51703438299207 6.605997245109692 216 | 742 26 cafeteria 46.51954881232548 6.609912662756806 217 | 974 26 cafeteria 46.516394320061906 6.604863330586588 218 | 26 27 appartment_block 46.517381393419846 6.610435220404498 219 | 37 27 villa 46.52060438502039 6.614147033265448 220 | 57 27 appartment_block 46.5155636877895 6.615539725193222 221 | 230 27 office 46.52090343699363 6.613858586304442 222 | 344 27 supermarket 46.51933972941951 6.6110966558170094 223 | 380 27 gym 46.514344408748336 6.614430009278007 224 | 430 27 dojo 46.51600878341271 6.6181105325996175 225 | 467 27 gym 46.51984649028777 6.615329356108743 226 | 537 27 supermarket 46.51937741569636 6.616445441377301 227 | 748 27 restaurant 46.51620098617556 6.614915882525844 228 | 778 27 cafeteria 46.514413534843506 6.614449417616922 229 | 784 27 gym 46.52082218409635 6.61708555153659 230 | 830 27 supermarket 46.516583029225906 6.614728395090865 231 | 872 27 gym 46.51606689574136 6.614513238217371 232 | 21 28 villa 46.51548592828847 6.629982846955875 233 | 66 28 appartment_block 46.51411944099419 6.626759136043745 234 | 74 28 villa 46.51637128568548 6.622289267252754 235 | 82 28 appartment_block 46.51883945952658 6.628159384693025 236 | 139 28 villa 46.517510341805774 6.62406449347839 237 | 157 28 appartment_block 46.51628965190775 6.627494209037259 238 | 163 28 appartment_block 46.51819847455589 6.628088239470268 239 | 172 28 appartment_block 46.517775436917795 6.6245380039425825 240 | 187 28 appartment_block 46.52030395683175 6.628951542353067 241 | 195 28 appartment_block 46.520177235027326 6.6234222891655765 242 | 438 28 cafeteria 46.5184423859717 6.622542995457955 243 | 619 28 supermarket 46.51907799910527 6.623836875862341 244 | 734 28 cafeteria 46.51805154617668 6.628848046290494 245 | 749 28 bar 46.51927391810171 6.620749249449927 246 | 812 28 restaurant 46.515853182301285 6.620696945936285 247 | 920 28 club 46.51979783543467 6.629793606180892 248 | 941 28 dojo 46.517368039314434 6.6229178091818 249 | 119 29 appartment_block 46.51672805217409 6.632409198450512 250 | 167 29 villa 46.51507444661007 6.630618220662928 251 | 413 29 club 46.515865519529896 6.633858683693586 252 | 536 29 cafeteria 46.5151247680381 6.632821922237973 253 | 615 29 restaurant 46.51652959613139 6.6323209475724445 254 | 840 29 restaurant 46.51876926039557 6.637350847700228 255 | 859 29 gym 46.51699328278165 6.638153043888102 256 | 1002 29 bar 46.51947766527439 6.631142580528147 257 | 1034 29 club 46.52039609213168 6.631039708442891 258 | 20 30 appartment_block 46.52043802927792 6.6417813347027685 259 | 30 30 villa 46.5183499818889 6.647204779567722 260 | 534 30 club 46.51884224571625 6.647283311109681 261 | 732 30 gym 46.52068387583727 6.640871124106424 262 | 765 30 bar 46.514241267199814 6.6489809685109 263 | 924 30 restaurant 46.51538291785942 6.643320634554908 264 | 966 30 supermarket 46.51587112148028 6.648846419137678 265 | 10 31 appartment_block 46.525173546461055 6.557661623667331 266 | 98 31 appartment_block 46.52693073103209 6.553361026480404 267 | 178 31 villa 46.526569353977195 6.558359405472168 268 | 267 31 cafeteria 46.52432036865705 6.55044932072563 269 | 300 31 supermarket 46.5272660492645 6.555223097354508 270 | 338 31 dojo 46.52454150064009 6.559655676358596 271 | 441 31 bar 46.52678703015611 6.550450673050922 272 | 759 31 supermarket 46.526331726728614 6.55512628953481 273 | 769 31 supermarket 46.526458736423066 6.556966274147876 274 | 821 31 club 46.52733405868255 6.552217249560227 275 | 95 32 appartment_block 46.52221523081021 6.56240072076708 276 | 132 32 appartment_block 46.52622296475923 6.561731352922478 277 | 162 32 villa 46.525691232792106 6.562227989032106 278 | 227 32 office 46.5276215199923 6.565885586749954 279 | 235 32 office 46.523222357067546 6.569618055391519 280 | 308 32 gym 46.52455490582804 6.567708687785092 281 | 325 32 dojo 46.52464575205612 6.566362135021644 282 | 421 32 supermarket 46.526903791150964 6.562936913096273 283 | 663 32 gym 46.5226323942738 6.564536074795026 284 | 729 32 gym 46.52739768966798 6.562839305231099 285 | 979 32 supermarket 46.52426526058886 6.561703143843548 286 | 99 33 villa 46.52746136498619 6.578699041316887 287 | 248 33 office 46.52678682751778 6.576499968386724 288 | 249 33 company 46.52217965271469 6.578626016167531 289 | 335 33 cafeteria 46.52330463558275 6.574795871915323 290 | 336 33 dojo 46.525481873122736 6.5716662850479395 291 | 565 33 cafeteria 46.525010994256824 6.574921719842516 292 | 699 33 cafeteria 46.52580438335738 6.579521803737147 293 | 707 33 supermarket 46.52505563730274 6.574907368205421 294 | 750 33 gym 46.52448605071239 6.57721570383953 295 | 820 33 dojo 46.5242926404126 6.575393889574149 296 | 862 33 gym 46.52307409950145 6.570772452140204 297 | 871 33 dojo 46.52670047927354 6.575649185272522 298 | 996 33 club 46.522058466657924 6.575244941355057 299 | 137 34 villa 46.52639090968419 6.587582224037711 300 | 210 34 office 46.52184350250189 6.582789297417715 301 | 616 34 gym 46.525429460034346 6.58547180674062 302 | 667 34 dojo 46.52208696790571 6.588369674069096 303 | 696 34 dojo 46.522067360658106 6.582776121033948 304 | 831 34 dojo 46.521439298211995 6.586792253810881 305 | 876 34 restaurant 46.52427690145312 6.581384082490877 306 | 917 34 gym 46.527779618954874 6.581151320753935 307 | 134 35 appartment_block 46.52599304928776 6.593132946410932 308 | 150 35 villa 46.52694810525975 6.5931794420826035 309 | 240 35 laboratory 46.52606201484912 6.5944159957331925 310 | 242 35 office 46.52779159387114 6.59757122821799 311 | 375 35 dojo 46.52143109640125 6.591010119017851 312 | 425 35 supermarket 46.52514497844941 6.590087805515024 313 | 491 35 dojo 46.524957377539764 6.593814631842686 314 | 561 35 gym 46.525352164408446 6.5913904623125195 315 | 710 35 restaurant 46.52477640456768 6.590671290692184 316 | 763 35 supermarket 46.521377443769346 6.5943672891827285 317 | 898 35 supermarket 46.52652407150742 6.591251290873413 318 | 922 35 bar 46.523227369362175 6.596130556663732 319 | 1004 35 dojo 46.524477656546615 6.597174987587277 320 | 1048 35 cafeteria 46.52737488152437 6.598463771241293 321 | 33 36 appartment_block 46.524492440634354 6.602163533861554 322 | 73 36 appartment_block 46.52566425549746 6.608479084030039 323 | 220 36 company 46.52578117179067 6.600312404721805 324 | 279 36 bar 46.524128884717896 6.608588335523754 325 | 460 36 club 46.52640626665576 6.606498294700264 326 | 478 36 gym 46.52296360457558 6.601921041997585 327 | 501 36 bar 46.52160423246481 6.604391485107841 328 | 510 36 club 46.52688429081603 6.600681316798478 329 | 522 36 cafeteria 46.52371215899346 6.605223576734925 330 | 530 36 restaurant 46.52431245570464 6.60577894139017 331 | 550 36 club 46.52218469968766 6.604784599433461 332 | 827 36 gym 46.52323216751293 6.606948239844875 333 | 906 36 club 46.52113832669614 6.60476655540591 334 | 921 36 club 46.527383102865976 6.608597393944735 335 | 993 36 restaurant 46.527855172887165 6.602161010282355 336 | 997 36 dojo 46.52627908820327 6.601385156082595 337 | 1015 36 dojo 46.5252539047939 6.6010691115735485 338 | 131 37 villa 46.5219746608592 6.614630656167231 339 | 168 37 villa 46.52369513958695 6.619088563322073 340 | 183 37 villa 46.527388681083025 6.6136246101303175 341 | 209 37 laboratory 46.521809420957894 6.616377951061817 342 | 353 37 supermarket 46.52417945556334 6.612860478727237 343 | 472 37 cafeteria 46.52341082499104 6.612178598199161 344 | 676 37 gym 46.522342481777635 6.616967451501447 345 | 881 37 restaurant 46.52606198641061 6.612189937225266 346 | 911 37 cafeteria 46.52637130202117 6.612490373357279 347 | 960 37 cafeteria 46.5249841916728 6.613777691373235 348 | 6 38 villa 46.521552555437424 6.620344116552214 349 | 60 38 appartment_block 46.52603167401881 6.620173307139981 350 | 96 38 appartment_block 46.52206602146476 6.620993055252423 351 | 97 38 villa 46.52441048621232 6.625245575755371 352 | 138 38 villa 46.52782622797131 6.627547044490809 353 | 327 38 cafeteria 46.52705570339817 6.627075941125652 354 | 475 38 supermarket 46.52736256356015 6.628704659602126 355 | 539 38 club 46.52158525124979 6.620638179616659 356 | 791 38 club 46.522413590161825 6.622468351629067 357 | 853 38 dojo 46.52753146845546 6.622082256597748 358 | 1041 38 club 46.52651887523043 6.6258865805922005 359 | 1042 38 bar 46.52332959763645 6.626207262921378 360 | 41 39 villa 46.521562951029864 6.636328714252614 361 | 126 39 villa 46.52408578443066 6.6309700590938006 362 | 166 39 appartment_block 46.52652194990049 6.635801510392277 363 | 435 39 cafeteria 46.522200167235724 6.639097855947761 364 | 520 39 cafeteria 46.52616185578057 6.63030800047305 365 | 559 39 cafeteria 46.521580824530794 6.632446353273554 366 | 590 39 club 46.525058561978426 6.632969328734044 367 | 646 39 bar 46.521930232005076 6.631979844823808 368 | 670 39 club 46.52797278529415 6.637448610001385 369 | 728 39 supermarket 46.521846244904424 6.631577109238575 370 | 1024 39 gym 46.52308841286024 6.638179848418145 371 | 303 40 supermarket 46.52484862061061 6.640184133216839 372 | 374 40 gym 46.524256831462075 6.644871235244924 373 | 403 40 gym 46.52752708174012 6.645304143734263 374 | 605 40 gym 46.521115584387886 6.640635736026265 375 | 645 40 bar 46.52695439528575 6.647770297140876 376 | 674 40 supermarket 46.52118955496442 6.643831351895329 377 | 721 40 bar 46.52624831449558 6.64460487416118 378 | 777 40 supermarket 46.52455545100701 6.648028332567855 379 | 925 40 dojo 46.521655120315955 6.642748877559491 380 | 954 40 bar 46.52264603796607 6.648534676904079 381 | 961 40 dojo 46.52758559623631 6.646631572959733 382 | 968 40 gym 46.52585086334396 6.645068134235303 383 | 991 40 cafeteria 46.52105660776627 6.648861971310422 384 | 316 41 gym 46.53299677958913 6.55330676969459 385 | 497 41 dojo 46.52842966969693 6.55875682745419 386 | 518 41 club 46.530083942393595 6.553179042510182 387 | 558 41 bar 46.533412390327896 6.553060729234286 388 | 607 41 cafeteria 46.53211722983715 6.556877229376345 389 | 818 41 bar 46.529389338787176 6.554150382211988 390 | 1001 41 supermarket 46.529014783195315 6.558564335817187 391 | 106 42 appartment_block 46.52935262843734 6.5620409834276305 392 | 239 42 laboratory 46.529329911293566 6.569445450829043 393 | 289 42 club 46.532106926713574 6.565938970975228 394 | 350 42 dojo 46.53233913102222 6.566960878923904 395 | 466 42 dojo 46.53340623984677 6.564099239728048 396 | 617 42 bar 46.53023553732592 6.561534101292112 397 | 739 42 supermarket 46.53358197046419 6.5626196390057805 398 | 771 42 supermarket 46.53442503956229 6.569146496746472 399 | 801 42 bar 46.52966717180299 6.56794737714457 400 | 861 42 bar 46.52802262381494 6.560325392339181 401 | 935 42 restaurant 46.530298174512545 6.561513086016163 402 | 951 42 club 46.533873293151444 6.562579954620836 403 | 995 42 gym 46.53433437200253 6.5619966590783045 404 | 1054 42 dojo 46.52820725279411 6.567209924408094 405 | 79 43 villa 46.528740670166044 6.575120665879649 406 | 141 43 appartment_block 46.53032365451193 6.573426855758183 407 | 143 43 appartment_block 46.531165935854524 6.5796421461870915 408 | 169 43 villa 46.53348639823881 6.574006110717793 409 | 247 43 office 46.53054456558295 6.573858723708058 410 | 259 43 company 46.53152342804002 6.577225435588298 411 | 306 43 dojo 46.53176103380448 6.57641493990309 412 | 396 43 supermarket 46.528363178546854 6.57330955528229 413 | 427 43 dojo 46.53358092489281 6.579135934353342 414 | 713 43 club 46.53208336901415 6.575085448847319 415 | 891 43 bar 46.52834631154642 6.577894054508106 416 | 1008 43 cafeteria 46.530130269189684 6.5719204791615855 417 | 1028 43 dojo 46.52832210476921 6.571394909241295 418 | 1031 43 dojo 46.530044968476034 6.570568685801298 419 | 1045 43 restaurant 46.533266940765834 6.574073819130182 420 | 103 44 appartment_block 46.533437320497015 6.582198705923808 421 | 109 44 villa 46.53489278319065 6.580754909358112 422 | 120 44 appartment_block 46.533021574256665 6.585647595680997 423 | 204 44 office 46.52941519324925 6.583668226303588 424 | 219 44 laboratory 46.52967346084721 6.58593333695806 425 | 222 44 laboratory 46.53025538749741 6.585234443083124 426 | 260 44 office 46.532384476559194 6.585524740802544 427 | 361 44 bar 46.53014042124427 6.589839741187929 428 | 514 44 restaurant 46.53356084429047 6.581943720121332 429 | 604 44 dojo 46.53330991725769 6.584171515896152 430 | 677 44 supermarket 46.5326723815938 6.584522193098288 431 | 774 44 restaurant 46.5302145551298 6.58948975865895 432 | 986 44 cafeteria 46.52893675612681 6.589230493576637 433 | 1038 44 bar 46.53113201402904 6.581804972271765 434 | 3 45 appartment_block 46.53145304789465 6.59843214156202 435 | 212 45 company 46.53294222140508 6.591174086010503 436 | 236 45 company 46.53024869197168 6.593057959483075 437 | 397 45 bar 46.531886736174954 6.597994327262046 438 | 431 45 gym 46.53253729161581 6.596177577212483 439 | 465 45 club 46.531225538519614 6.592948892892196 440 | 594 45 dojo 46.533146890633546 6.594037239357323 441 | 909 45 bar 46.530740582250836 6.594086626403303 442 | 916 45 club 46.53060795275317 6.593150351330276 443 | 14 46 appartment_block 46.53107439057861 6.603576708748624 444 | 194 46 villa 46.531649243057416 6.60680883873068 445 | 226 46 office 46.53459873318287 6.605685295084861 446 | 310 46 supermarket 46.53195330966527 6.605405265648671 447 | 334 46 restaurant 46.52931160755857 6.606804382736753 448 | 351 46 supermarket 46.52877673149951 6.609020486413249 449 | 354 46 bar 46.533087483153636 6.601871727968327 450 | 385 46 club 46.531683998896455 6.608425289911538 451 | 445 46 cafeteria 46.52906427506115 6.603693179918181 452 | 666 46 bar 46.53293649500235 6.602644610467819 453 | 779 46 cafeteria 46.5308378335466 6.6017947261647185 454 | 849 46 bar 46.52870936211589 6.608407786567187 455 | 860 46 dojo 46.5322856851541 6.604585904755598 456 | 883 46 cafeteria 46.53214993358437 6.601756114377491 457 | 892 46 restaurant 46.52996512958107 6.604455380497258 458 | 896 46 club 46.531421408573365 6.601382746325085 459 | 994 46 dojo 46.52948259459757 6.603847718781456 460 | 8 47 appartment_block 46.530726911562894 6.6162035050991275 461 | 19 47 villa 46.53113280547201 6.612759088784233 462 | 81 47 appartment_block 46.533810744136545 6.611183296525103 463 | 93 47 appartment_block 46.53216691156872 6.614328475293815 464 | 158 47 appartment_block 46.53406847694781 6.61809334976229 465 | 391 47 dojo 46.528623095770016 6.616064871257605 466 | 423 47 club 46.53296780657772 6.615618181757108 467 | 563 47 bar 46.53020531127565 6.619738414091459 468 | 694 47 supermarket 46.531796687468265 6.612307719588115 469 | 706 47 gym 46.5335876158948 6.619182889298738 470 | 767 47 bar 46.53265953669294 6.615344714498467 471 | 781 47 supermarket 46.53302879755496 6.610427136924911 472 | 1011 47 cafeteria 46.53081588536656 6.619481435404826 473 | 1 48 villa 46.53086483286677 6.623208941351423 474 | 83 48 appartment_block 46.533339396213734 6.620658482449444 475 | 112 48 appartment_block 46.5343948235841 6.625878300384276 476 | 130 48 appartment_block 46.52855787912976 6.623285781699166 477 | 136 48 appartment_block 46.531492076368885 6.623080077617141 478 | 165 48 appartment_block 46.52960874611383 6.623739135027796 479 | 526 48 bar 46.52923575860911 6.629065672127778 480 | 817 48 gym 46.53274834767285 6.6280065136089705 481 | 965 48 club 46.529155702669506 6.627355926244203 482 | 1013 48 restaurant 46.534387638970315 6.620945617202634 483 | 50 49 villa 46.530348955292325 6.635416791907451 484 | 263 49 cafeteria 46.53257190931455 6.632985233939281 485 | 326 49 cafeteria 46.53240291554801 6.637651183132365 486 | 366 49 gym 46.53451294641933 6.639985826523506 487 | 569 49 cafeteria 46.52921591553664 6.6383536254653555 488 | 756 49 gym 46.52957885062927 6.63961529711899 489 | 886 49 supermarket 46.53440810321999 6.639206824799623 490 | 971 49 restaurant 46.52856079027028 6.631351863364709 491 | 1003 49 gym 46.53449741875761 6.630977422289561 492 | 402 50 dojo 46.5319844661267 6.64624175881482 493 | 693 50 restaurant 46.53385870333695 6.6413649581576095 494 | 867 50 restaurant 46.532566875353815 6.640423402063152 495 | 907 50 restaurant 46.529311850274 6.641349273611463 496 | 1012 50 club 46.53421182367783 6.6455659097770745 497 | 11 51 appartment_block 46.535640658095 6.55173020223239 498 | 13 51 appartment_block 46.540757676766255 6.559160298498956 499 | 17 51 villa 46.537799643552106 6.55078640152693 500 | 292 51 supermarket 46.53623694750694 6.550246049324395 501 | 434 51 dojo 46.536254271700045 6.550164125928204 502 | 437 51 bar 46.54027678641674 6.554043598030882 503 | 469 51 club 46.53656661352452 6.558755651256149 504 | 482 51 dojo 46.54186446391714 6.553290697368211 505 | 513 51 gym 46.53944938550854 6.556329832192673 506 | 752 51 dojo 46.53866594849134 6.556547663491369 507 | 931 51 restaurant 46.540985664127376 6.552021910211088 508 | 939 51 gym 46.53580120957717 6.558074693848586 509 | 1052 51 supermarket 46.540369837512394 6.55996701882549 510 | 51 52 villa 46.54089684299669 6.569174801623043 511 | 121 52 appartment_block 46.537044475294756 6.560588762389074 512 | 201 52 laboratory 46.539122859147675 6.564621034734202 513 | 233 52 laboratory 46.539967466095305 6.560652862279495 514 | 274 52 cafeteria 46.535804441006405 6.566291179601388 515 | 332 52 cafeteria 46.540110088384175 6.564769469781462 516 | 456 52 bar 46.535560851507086 6.562034165531097 517 | 459 52 club 46.53834422933897 6.562888176304264 518 | 571 52 cafeteria 46.540699423608814 6.566800028640767 519 | 577 52 supermarket 46.53682921956869 6.560303586815873 520 | 762 52 bar 46.5378759887058 6.565194528293657 521 | 766 52 restaurant 46.53747135861328 6.562160423030495 522 | 823 52 supermarket 46.54147877537056 6.564399589782846 523 | 839 52 dojo 46.5360771834723 6.5632709701423915 524 | 27 53 villa 46.541477816316196 6.576511323488738 525 | 31 53 appartment_block 46.540296876485584 6.573140007608456 526 | 88 53 appartment_block 46.53562944906701 6.574720525973535 527 | 133 53 villa 46.537690761737835 6.573417179766815 528 | 140 53 appartment_block 46.538382006330664 6.573998457895873 529 | 207 53 company 46.53976081739804 6.574063213164034 530 | 213 53 laboratory 46.535315728151275 6.574142638880201 531 | 244 53 laboratory 46.541163304078204 6.5717750748470305 532 | 245 53 office 46.53591906517015 6.575487739371415 533 | 264 53 supermarket 46.53950907021563 6.5778349774540334 534 | 373 53 dojo 46.538883519555384 6.573453646064172 535 | 557 53 supermarket 46.53645047391645 6.573101233472085 536 | 567 53 bar 46.53861745907966 6.575039950418598 537 | 576 53 gym 46.535387052723415 6.5712489525860605 538 | 751 53 club 46.541204273535556 6.578272377116883 539 | 1046 53 restaurant 46.536657439937265 6.575960575712088 540 | 39 54 appartment_block 46.53539723980053 6.581743911004511 541 | 64 54 villa 46.535962386984835 6.580151771930487 542 | 221 54 company 46.53647787290877 6.580334460506644 543 | 294 54 dojo 46.53991919347354 6.583580739672117 544 | 650 54 gym 46.541935766523636 6.589939374575381 545 | 738 54 restaurant 46.54121518314781 6.58514486814873 546 | 873 54 dojo 46.53918232194026 6.586801801760921 547 | 80 55 appartment_block 46.537083458534354 6.596870795677535 548 | 202 55 company 46.537173835569206 6.59957383904399 549 | 215 55 company 46.541133184348 6.597673448354418 550 | 232 55 company 46.539950420773735 6.5930554523082305 551 | 237 55 company 46.540782352683166 6.5918965877586055 552 | 251 55 laboratory 46.538985635518 6.599286632636916 553 | 273 55 club 46.541696420761376 6.590420330216444 554 | 433 55 club 46.53515555278796 6.598667805815232 555 | 544 55 bar 46.53723313367519 6.590917583646408 556 | 545 55 club 46.54154994129062 6.596295855191877 557 | 626 55 club 46.53517243963454 6.591932066761188 558 | 764 55 dojo 46.540281869465126 6.591586121328275 559 | 806 55 club 46.541254165217566 6.596565282956871 560 | 824 55 supermarket 46.53660227309845 6.595297974586247 561 | 850 55 bar 46.53903460335773 6.595283024855717 562 | 955 55 restaurant 46.53511732932572 6.59949876825855 563 | 25 56 appartment_block 46.535398948263214 6.608812308756106 564 | 47 56 appartment_block 46.53612376358626 6.603827785275397 565 | 53 56 villa 46.536808173851036 6.604243974608491 566 | 54 56 appartment_block 46.53896037388035 6.602514510523962 567 | 68 56 appartment_block 46.53572082453432 6.602127381063315 568 | 77 56 villa 46.54110124319544 6.60195468222601 569 | 86 56 appartment_block 46.53682729938896 6.600886468443971 570 | 89 56 villa 46.536951841060876 6.604531851444879 571 | 173 56 appartment_block 46.53602632448037 6.603209936653086 572 | 291 56 supermarket 46.53891440662743 6.604344279380452 573 | 642 56 bar 46.53563549196545 6.60360171556608 574 | 668 56 gym 46.53795463959577 6.605128263999426 575 | 770 56 cafeteria 46.53835770171049 6.602563627212279 576 | 773 56 gym 46.53851658404155 6.601979469984049 577 | 943 56 dojo 46.539318537853006 6.607186289538898 578 | 7 57 villa 46.54104687776021 6.61259119510408 579 | 70 57 appartment_block 46.537160034927844 6.614508918324992 580 | 234 57 office 46.53646078474718 6.616016520098503 581 | 499 57 restaurant 46.53785907234798 6.613337851318821 582 | 591 57 cafeteria 46.53512252535609 6.618828316966822 583 | 789 57 restaurant 46.536203994744426 6.613335402585683 584 | 856 57 bar 46.53905317378651 6.617777826888394 585 | 890 57 gym 46.536330840129736 6.61717881704941 586 | 901 57 restaurant 46.53876562252675 6.612790358390601 587 | 28 58 villa 46.53599174731868 6.622526180241612 588 | 49 58 appartment_block 46.536427095166864 6.62708390006003 589 | 171 58 villa 46.539103122169095 6.628330132635741 590 | 200 58 appartment_block 46.53536656879749 6.6294167252147975 591 | 409 58 dojo 46.53907939843077 6.625534039439054 592 | 553 58 dojo 46.54180047224407 6.628595524570803 593 | 572 58 supermarket 46.536681066297994 6.627394958808561 594 | 659 58 bar 46.5366428518606 6.627816929720463 595 | 678 58 supermarket 46.53515235716575 6.622520338284876 596 | 761 58 restaurant 46.538470439976535 6.627222751609865 597 | 834 58 dojo 46.53572746242241 6.627196490335366 598 | 932 58 bar 46.5392497833777 6.629559169785398 599 | 962 58 cafeteria 46.53759604378724 6.627837538569977 600 | 1005 58 dojo 46.53891190724139 6.62330477588136 601 | 104 59 villa 46.53512934325124 6.635832558791507 602 | 277 59 restaurant 46.5363725698221 6.632349313463975 603 | 286 59 club 46.53585556866501 6.639596967218586 604 | 346 59 cafeteria 46.53741303966518 6.635256261414824 605 | 411 59 club 46.53916820670895 6.637470547691005 606 | 498 59 dojo 46.54098316721524 6.6373908466031555 607 | 588 59 club 46.54198755177767 6.638013575605814 608 | 608 59 supermarket 46.5418032755496 6.6355682315352205 609 | 697 59 restaurant 46.53975158768789 6.636526498097977 610 | 798 59 dojo 46.54097839385571 6.637617809007035 611 | 1006 59 supermarket 46.54047147671097 6.630618722429231 612 | 250 60 company 46.5407945335009 6.647315431111454 613 | 295 60 supermarket 46.53586795803416 6.6487246646294835 614 | 393 60 cafeteria 46.54112067847996 6.641965925237699 615 | 464 60 supermarket 46.53867246775582 6.645857507559811 616 | 602 60 supermarket 46.536216836072214 6.640005246946015 617 | 746 60 restaurant 46.53523245813185 6.644957846567025 618 | 826 60 restaurant 46.53634902200356 6.647916901952899 619 | 1059 60 club 46.536408477777016 6.640146373038229 620 | 255 61 laboratory 46.54335495433577 6.5598587168744595 621 | 314 61 supermarket 46.54535981525563 6.559571804729551 622 | 323 61 cafeteria 46.54749106651446 6.554416855593343 623 | 786 61 supermarket 46.54349529762406 6.558835749499572 624 | 874 61 dojo 46.54478584167687 6.550836843691032 625 | 1051 61 bar 46.542525884601815 6.5593912506709335 626 | 84 62 appartment_block 46.54802301922531 6.569657883843577 627 | 258 62 laboratory 46.542134168389296 6.5642365412770545 628 | 296 62 bar 46.54455978578567 6.563610848275491 629 | 319 62 supermarket 46.54871837720485 6.566055826227824 630 | 390 62 gym 46.54502480588458 6.566681516080095 631 | 660 62 cafeteria 46.54476352937464 6.565450602600494 632 | 712 62 gym 46.5422742266073 6.566355463748351 633 | 790 62 club 46.54740162320577 6.561575518181828 634 | 796 62 cafeteria 46.544061474325396 6.565506234412427 635 | 929 62 gym 46.54373114737136 6.565583395864785 636 | 969 62 cafeteria 46.54437498880877 6.5670952673982255 637 | 1032 62 bar 46.54396486315108 6.5692074591425325 638 | 1039 62 club 46.54626561650729 6.56631754731559 639 | 29 63 villa 46.54212550832081 6.5714980281479916 640 | 40 63 appartment_block 46.542033682768036 6.571660281828687 641 | 110 63 villa 46.54664922602038 6.576859658636211 642 | 203 63 company 46.54242230623346 6.577281728790473 643 | 225 63 laboratory 46.54648429922497 6.57344706420223 644 | 229 63 laboratory 46.54637740125457 6.575353054770723 645 | 243 63 laboratory 46.54673993140806 6.57737743004016 646 | 252 63 laboratory 46.54562561630371 6.577847748714086 647 | 317 63 bar 46.54378725041696 6.578708128601186 648 | 357 63 dojo 46.544409611768295 6.579111041539487 649 | 547 63 bar 46.54715418583834 6.57867323088001 650 | 582 63 bar 46.54632861344323 6.575825419818011 651 | 586 63 restaurant 46.54851857096879 6.574898808785238 652 | 822 63 bar 46.54570343477227 6.571229858529678 653 | 949 63 cafeteria 46.54770439347994 6.570948869476082 654 | 34 64 villa 46.544757650049455 6.582152738978855 655 | 42 64 villa 46.542151109225166 6.5892952064 656 | 45 64 villa 46.54622162104134 6.580625621044892 657 | 63 64 appartment_block 46.5484954266604 6.58604740277291 658 | 180 64 appartment_block 46.543859901552175 6.5800455056380365 659 | 324 64 cafeteria 46.544497023669706 6.582856908567262 660 | 333 64 bar 46.54715136487096 6.587987432470696 661 | 424 64 club 46.54425116348349 6.5858751166104685 662 | 454 64 gym 46.54611960120137 6.587890897100484 663 | 597 64 dojo 46.54221637082584 6.589586183859968 664 | 631 64 restaurant 46.54262789535444 6.583058508686404 665 | 782 64 restaurant 46.54542696245239 6.589218285924974 666 | 973 64 dojo 46.54247512944868 6.58293979553281 667 | 978 64 supermarket 46.54333547009023 6.583475386324259 668 | 987 64 dojo 46.547510956525954 6.588127452879729 669 | 1025 64 club 46.54740090450424 6.580643959088152 670 | 205 65 company 46.54267878942992 6.593287653940389 671 | 241 65 laboratory 46.54491018953797 6.598044109751461 672 | 272 65 cafeteria 46.54449001207393 6.590641678624517 673 | 311 65 supermarket 46.548639325907246 6.597653257906773 674 | 399 65 cafeteria 46.5451491289406 6.597267875273009 675 | 457 65 gym 46.54851880903374 6.597461096290162 676 | 533 65 cafeteria 46.548886750133114 6.593898432961415 677 | 687 65 restaurant 46.545804553894804 6.597741520958279 678 | 714 65 club 46.548204199850936 6.5919777935847055 679 | 866 65 cafeteria 46.54788086899757 6.59445697975574 680 | 903 65 dojo 46.5472205863723 6.593153747110023 681 | 919 65 restaurant 46.546267196149905 6.597359967838802 682 | 1060 65 restaurant 46.54730148823182 6.595584162227114 683 | 477 66 restaurant 46.54774591757422 6.602091202279659 684 | 598 66 restaurant 46.54868772613013 6.600151627732742 685 | 622 66 gym 46.54282179513126 6.600563346898323 686 | 655 66 cafeteria 46.54509689915562 6.608855276521703 687 | 673 66 gym 46.54649800654328 6.602435931762764 688 | 723 66 bar 46.54397041251054 6.600613093368665 689 | 835 66 gym 46.54279023631667 6.6068948699113665 690 | 885 66 bar 46.54211442942339 6.60517201657064 691 | 1021 66 gym 46.544021138792125 6.6063120693977035 692 | 214 67 laboratory 46.54849046584162 6.615589389063846 693 | 231 67 laboratory 46.54543276359627 6.617573546300909 694 | 299 67 supermarket 46.54517750683682 6.619884195410649 695 | 486 67 bar 46.54269809123635 6.616083122554809 696 | 627 67 supermarket 46.544305719764 6.612627294604457 697 | 635 67 dojo 46.546965956323845 6.6107477440862 698 | 692 67 gym 46.548674033285266 6.618495009004099 699 | 776 67 bar 46.544803481132696 6.61028626247397 700 | 803 67 bar 46.547772080940504 6.614738676642423 701 | 811 67 bar 46.54551297579216 6.6128047676706245 702 | 957 67 club 46.54323076909007 6.619039033534347 703 | 142 68 villa 46.548655213190685 6.628301192480812 704 | 343 68 cafeteria 46.54228232966969 6.62795947303962 705 | 362 68 bar 46.54493262819762 6.6207035621112755 706 | 420 68 club 46.54620034286106 6.628156931220865 707 | 628 68 club 46.543338785642646 6.6214428984086675 708 | 794 68 dojo 46.545571781998845 6.6237643587431645 709 | 899 68 supermarket 46.54514928034462 6.626151274621766 710 | 38 69 appartment_block 46.54589046387057 6.636943951844813 711 | 262 69 gym 46.54879694038339 6.632254882185472 712 | 309 69 club 46.548340036241264 6.63969992176715 713 | 368 69 supermarket 46.547893482955494 6.6302561759671494 714 | 386 69 cafeteria 46.54665796119017 6.631208774280346 715 | 741 69 supermarket 46.548958941200794 6.635236055173915 716 | 1030 69 cafeteria 46.54685628432405 6.638686696720697 717 | 125 70 villa 46.54798944925552 6.645860365522411 718 | 154 70 appartment_block 46.548031221111074 6.642394290808251 719 | 516 70 supermarket 46.546508968736966 6.646213622490748 720 | 581 70 club 46.547186927939244 6.648468571463564 721 | 623 70 cafeteria 46.543922985612156 6.641054855183668 722 | 910 70 cafeteria 46.54772014845813 6.649023102470785 723 | 1040 70 bar 46.54637046724508 6.649892848816985 724 | 276 71 supermarket 46.55348479167112 6.554671375997222 725 | 347 71 dojo 46.54994330732129 6.552679571336317 726 | 484 71 bar 46.55117703142574 6.55005143958594 727 | 618 71 gym 46.5537654569153 6.551427326793875 728 | 664 71 cafeteria 46.552574267941566 6.558613655249042 729 | 740 71 restaurant 46.55189079686224 6.5581563226121915 730 | 927 71 bar 46.55561265673343 6.559143600142633 731 | 984 71 supermarket 46.55349366509908 6.551478855063699 732 | 1049 71 bar 46.54948200762157 6.555770529748022 733 | 152 72 appartment_block 46.55412520922334 6.56395590289575 734 | 182 72 appartment_block 46.55425529332338 6.568311905114485 735 | 301 72 supermarket 46.55561141535487 6.566816137997719 736 | 329 72 cafeteria 46.55029269699835 6.567196229100139 737 | 410 72 dojo 46.549988222556784 6.56070445249322 738 | 808 72 restaurant 46.55550997552533 6.566247418699852 739 | 810 72 dojo 46.55457571533505 6.565985849465906 740 | 813 72 restaurant 46.553683815829665 6.564425811815387 741 | 844 72 dojo 46.55043998691759 6.569253845013994 742 | 845 72 restaurant 46.55455536086254 6.562751669408471 743 | 879 72 cafeteria 46.554278117280205 6.568650536894085 744 | 913 72 gym 46.55430182544121 6.567716995584349 745 | 426 73 bar 46.55000586278374 6.578310546179499 746 | 503 73 gym 46.552660085864886 6.5703956525171865 747 | 651 73 dojo 46.551179994562524 6.579720028047782 748 | 735 73 bar 46.55140952194583 6.577131100896326 749 | 807 73 gym 46.551598768688805 6.575685583578583 750 | 878 73 club 46.550248054737324 6.575327579188784 751 | 989 73 restaurant 46.552423416041044 6.575269866781807 752 | 62 74 appartment_block 46.5554402784649 6.585752170638967 753 | 69 74 villa 46.55146118284475 6.583340094605329 754 | 87 74 appartment_block 46.55474484137863 6.580148270937842 755 | 111 74 appartment_block 46.551333112731825 6.585498468798148 756 | 148 74 villa 46.54965471273194 6.582245165098295 757 | 151 74 appartment_block 46.551667691678645 6.5841346283303 758 | 198 74 appartment_block 46.54987279652849 6.582757515785049 759 | 356 74 dojo 46.5506419533555 6.582467118871244 760 | 359 74 cafeteria 46.550951311771364 6.584687767277779 761 | 384 74 restaurant 46.550572436628116 6.581835367611154 762 | 476 74 supermarket 46.554051392505166 6.583896376853933 763 | 504 74 restaurant 46.553004133367544 6.581208366963053 764 | 908 74 dojo 46.55406856173199 6.584666004059658 765 | 946 74 club 46.55441734505941 6.5864349944406655 766 | 1053 74 supermarket 46.55233420791458 6.580213015411177 767 | 12 75 appartment_block 46.55077911962885 6.592034367876887 768 | 113 75 appartment_block 46.55075967660064 6.595063265100554 769 | 284 75 cafeteria 46.55296706097582 6.594882496858184 770 | 392 75 bar 46.549569208457 6.590501730340814 771 | 473 75 restaurant 46.553602379359326 6.596860076777859 772 | 556 75 cafeteria 46.54957864700706 6.592365437279992 773 | 621 75 cafeteria 46.55569372514275 6.599630658818851 774 | 630 75 dojo 46.55229171037083 6.598604649288269 775 | 675 75 supermarket 46.550880217055884 6.590212540024128 776 | 690 75 restaurant 46.55328436934735 6.598023697380661 777 | 719 75 gym 46.55041416629489 6.593812338492929 778 | 858 75 cafeteria 46.55271602335305 6.595233471577528 779 | 1009 75 cafeteria 46.55518213624093 6.595315639398155 780 | 122 76 appartment_block 46.55247290421611 6.606547194866624 781 | 127 76 villa 46.55034241161493 6.6028524983313 782 | 161 76 villa 46.55560681155299 6.605922310098472 783 | 196 76 villa 46.55448266229751 6.60635282403752 784 | 363 76 gym 46.55444565035239 6.604491314087964 785 | 479 76 dojo 46.54988036021286 6.609448555546026 786 | 521 76 bar 46.55215382261362 6.604171825604082 787 | 704 76 gym 46.553190144955884 6.605039189884793 788 | 705 76 dojo 46.54932574391166 6.609644632126942 789 | 159 77 appartment_block 46.55277244215642 6.612931455154788 790 | 188 77 appartment_block 46.55336001627811 6.618107035298816 791 | 218 77 company 46.55107163808287 6.618198724563678 792 | 261 77 dojo 46.55428127076087 6.612564249093142 793 | 268 77 dojo 46.55021013988373 6.617139634147749 794 | 278 77 cafeteria 46.55209034948656 6.613122137179422 795 | 288 77 supermarket 46.55537130938439 6.611089267014504 796 | 634 77 dojo 46.550037788065666 6.6108008298633925 797 | 636 77 supermarket 46.553639962217574 6.618626662833154 798 | 647 77 restaurant 46.549203219767236 6.6143902678632776 799 | 744 77 dojo 46.55182342857438 6.617544875296484 800 | 783 77 restaurant 46.55044540469045 6.61642812631821 801 | 852 77 cafeteria 46.54905539219855 6.615458218365911 802 | 933 77 restaurant 46.54962978925237 6.610752630812769 803 | 982 77 restaurant 46.551366298871855 6.613664449606408 804 | 983 77 dojo 46.55074438096746 6.613147464321533 805 | 65 78 villa 46.55110943570394 6.62113073682305 806 | 254 78 laboratory 46.55252166054096 6.622535132777766 807 | 282 78 gym 46.54960709033736 6.628232953145537 808 | 367 78 club 46.55151147879781 6.6216447143206185 809 | 566 78 cafeteria 46.55592503630414 6.6288869613001475 810 | 587 78 cafeteria 46.55445691691927 6.626139744864034 811 | 893 78 bar 46.55205912101262 6.623924119706933 812 | 923 78 club 46.55356640126972 6.621111217013711 813 | 958 78 club 46.555947707205696 6.626944478678066 814 | 1000 78 bar 46.55329132391861 6.62223698276002 815 | 1055 78 restaurant 46.549186548133775 6.626406639320937 816 | 9 79 appartment_block 46.55168943207761 6.6380135567421865 817 | 52 79 villa 46.55525386239025 6.638705965250762 818 | 146 79 appartment_block 46.55256870880249 6.636124675985517 819 | 584 79 supermarket 46.55185387592534 6.635800366882492 820 | 638 79 restaurant 46.55413479527682 6.633872543413956 821 | 727 79 restaurant 46.55520876531812 6.63235857556247 822 | 842 79 restaurant 46.54948729328999 6.631418436102023 823 | 976 79 dojo 46.55271583482651 6.6390856327038055 824 | 1036 79 dojo 46.54934253889166 6.630330852582375 825 | 90 80 appartment_block 46.54936963422763 6.646761835752967 826 | 116 80 villa 46.555477335084646 6.64966433741202 827 | 224 80 laboratory 46.55110456416908 6.64573343937228 828 | 253 80 office 46.55542129132483 6.649592109494784 829 | 313 80 bar 46.554859972894874 6.642546969847562 830 | 345 80 bar 46.555864991898495 6.646411900688784 831 | 348 80 dojo 46.551170172474656 6.647649002368083 832 | 378 80 gym 46.54931219338381 6.6435538045179 833 | 494 80 dojo 46.55261501074511 6.645384523970188 834 | 722 80 supermarket 46.55083324689874 6.649550626407314 835 | 904 80 supermarket 46.54999051223962 6.647399013410631 836 | 959 80 gym 46.55422842632981 6.649003082802787 837 | 964 80 restaurant 46.55058765758253 6.649575205396806 838 | 287 81 restaurant 46.557246009903196 6.557164324218696 839 | 543 81 gym 46.560554752658575 6.558890847425623 840 | 579 81 restaurant 46.56256651308925 6.551054739923451 841 | 612 81 restaurant 46.556824341332444 6.550901067759469 842 | 1058 81 restaurant 46.557750967522765 6.558567246207164 843 | 2 82 appartment_block 46.55914096802978 6.568463328818292 844 | 23 82 appartment_block 46.560671261555484 6.5673916046544685 845 | 46 82 villa 46.56225961465293 6.561324762084731 846 | 100 82 appartment_block 46.56087790594241 6.564926350869301 847 | 101 82 appartment_block 46.557039299933116 6.56970666275866 848 | 298 82 dojo 46.56201569603773 6.569334286484226 849 | 407 82 gym 46.55760986519298 6.563680880208682 850 | 418 82 club 46.56025122242475 6.564547676765642 851 | 515 82 gym 46.56105153345838 6.56165979685951 852 | 541 82 club 46.556234401598765 6.566648258054148 853 | 583 82 cafeteria 46.562476834530706 6.56577150269302 854 | 780 82 dojo 46.557797150615535 6.564334860620351 855 | 1057 82 cafeteria 46.562496864368626 6.567720231351327 856 | 108 83 villa 46.55759393549761 6.570066617752999 857 | 149 83 villa 46.56016208036648 6.575798417291636 858 | 175 83 appartment_block 46.56219523756007 6.578396130657112 859 | 432 83 dojo 46.55799494522263 6.572065538671336 860 | 981 83 restaurant 46.56086024063136 6.572804716022585 861 | 1014 83 bar 46.55603484391742 6.57240739224497 862 | 22 84 appartment_block 46.55940455354007 6.583655340167974 863 | 24 84 appartment_block 46.559452365673984 6.584885787391103 864 | 43 84 appartment_block 46.5567023165945 6.5834667546552295 865 | 342 84 bar 46.56195277785406 6.58717966269131 866 | 370 84 gym 46.56294519805225 6.584247362336498 867 | 489 84 restaurant 46.56197881220141 6.5838149506549755 868 | 524 84 club 46.56040825544335 6.583158103479198 869 | 614 84 supermarket 46.55818826012992 6.581040255697304 870 | 716 84 supermarket 46.561154508879746 6.588536821346322 871 | 35 85 appartment_block 46.562099162583294 6.599880753581032 872 | 61 85 villa 46.557880594438785 6.59933060620753 873 | 67 85 appartment_block 46.55678107606664 6.593531637611862 874 | 115 85 villa 46.56110935784577 6.594068489845967 875 | 117 85 villa 46.56014415936724 6.598203678264672 876 | 155 85 appartment_block 46.558367810027065 6.599673120084034 877 | 181 85 villa 46.56275609277602 6.592948670096482 878 | 305 85 bar 46.55626358771141 6.591222669318815 879 | 436 85 bar 46.557453635243554 6.593135121452934 880 | 447 85 dojo 46.556516722962456 6.593581353284495 881 | 458 85 dojo 46.56252868802353 6.596979919954148 882 | 788 85 cafeteria 46.55998101235366 6.598492025794768 883 | 797 85 cafeteria 46.5581068683577 6.598927912376763 884 | 848 85 dojo 46.55864874843346 6.592768762893283 885 | 926 85 bar 46.5613247900066 6.59239490482244 886 | 930 85 bar 46.56075161011403 6.596973196900907 887 | 1033 85 club 46.55665477953538 6.596498313785977 888 | 59 86 villa 46.56235289019092 6.609461684641 889 | 75 86 appartment_block 46.56183411981479 6.6063114467693556 890 | 118 86 appartment_block 46.559541951761666 6.604415041171931 891 | 164 86 appartment_block 46.55871340653822 6.607167815059308 892 | 216 86 laboratory 46.55824758482931 6.606724163400365 893 | 394 86 dojo 46.56196258642426 6.606494229326883 894 | 481 86 cafeteria 46.562919788855496 6.607073631084566 895 | 528 86 dojo 46.56028597176022 6.608620034253792 896 | 552 86 club 46.562182430538996 6.601660508766784 897 | 585 86 bar 46.559794040573806 6.601127554334035 898 | 599 86 cafeteria 46.55704157421738 6.60647098037846 899 | 633 86 gym 46.56286295836819 6.601243007376003 900 | 656 86 club 46.55815482114441 6.6076551187557 901 | 661 86 bar 46.55828459175914 6.6007302762080595 902 | 724 86 club 46.55981589709377 6.608252393836579 903 | 905 86 gym 46.55837999990501 6.608619310499562 904 | 937 86 cafeteria 46.5621590636802 6.603985633031928 905 | 942 86 club 46.56274553363043 6.602862914588234 906 | 217 87 laboratory 46.55698786381021 6.615454534560619 907 | 376 87 cafeteria 46.55690594670802 6.618343704918935 908 | 412 87 cafeteria 46.56108099219819 6.618573895845669 909 | 442 87 cafeteria 46.55750539274756 6.610587883692194 910 | 450 87 restaurant 46.55789546641419 6.617717907915944 911 | 548 87 club 46.55848482855093 6.613843337331459 912 | 593 87 club 46.5598799483029 6.610856452134927 913 | 688 87 supermarket 46.562099757299556 6.618341748269497 914 | 815 87 bar 46.557484365786934 6.6120613986084695 915 | 970 87 bar 46.56101506031815 6.611020847852006 916 | 977 87 gym 46.5613016625365 6.615822465307741 917 | 1016 87 gym 46.556920420732034 6.615014812272611 918 | 105 88 villa 46.56124732736666 6.623012391353158 919 | 186 88 villa 46.559770121801 6.626727890739528 920 | 369 88 club 46.55866641200051 6.6273420608514675 921 | 388 88 gym 46.562627399306415 6.628051999104296 922 | 487 88 dojo 46.56033645687696 6.625724108156354 923 | 496 88 bar 46.56244863775187 6.6204216517855246 924 | 573 88 restaurant 46.56073920875702 6.629501233778073 925 | 689 88 bar 46.55781576376344 6.622564169884261 926 | 715 88 restaurant 46.55939481405288 6.624273283731404 927 | 760 88 dojo 46.5603811609544 6.623560493977317 928 | 877 88 restaurant 46.55970482620175 6.6237499546017915 929 | 895 88 dojo 46.56068647997178 6.626962485016818 930 | 938 88 dojo 46.55985266816673 6.62298687983279 931 | 967 88 club 46.56025307651261 6.620866060390487 932 | 988 88 restaurant 46.557910336906104 6.623674142152583 933 | 16 89 appartment_block 46.55998841851462 6.636433958847102 934 | 36 89 appartment_block 46.56281765400373 6.635770743311548 935 | 85 89 villa 46.562353199705186 6.6364253150235255 936 | 401 89 cafeteria 46.559652547930334 6.633160402563814 937 | 461 89 dojo 46.55740624954952 6.636327227379055 938 | 701 89 dojo 46.56061086755204 6.638496376106191 939 | 720 89 dojo 46.5629357515204 6.6352750449747795 940 | 952 89 cafeteria 46.55721409588843 6.637221694023033 941 | 5 90 appartment_block 46.56264638728534 6.64482339836969 942 | 71 90 villa 46.562814906720085 6.643755137436221 943 | 129 90 appartment_block 46.560848709753834 6.649831590857916 944 | 193 90 villa 46.559541474360884 6.644918277145602 945 | 228 90 laboratory 46.55873755390918 6.640894930889063 946 | 509 90 dojo 46.561462190812 6.648100494756942 947 | 595 90 restaurant 46.557310479810184 6.643756283677161 948 | 652 90 gym 46.562443656835285 6.6455714522550595 949 | 755 90 cafeteria 46.56091716210734 6.6415272045588605 950 | 936 90 restaurant 46.560171909490215 6.648719746247985 951 | 1023 90 bar 46.561722061282055 6.641283646502063 952 | 1029 90 dojo 46.559835322712935 6.644365363185335 953 | 56 91 villa 46.56937460595079 6.559369891607701 954 | 270 91 dojo 46.56681557972856 6.552538948740661 955 | 328 91 dojo 46.56512109868866 6.550563711439773 956 | 395 91 gym 46.56506386910501 6.55867949921195 957 | 448 91 club 46.568324604806115 6.554119541209758 958 | 506 91 supermarket 46.56775807403467 6.552788874944967 959 | 525 91 gym 46.56970387026415 6.559932268867981 960 | 560 91 supermarket 46.56722084939954 6.551519407997665 961 | 624 91 gym 46.56341541640604 6.555415783402424 962 | 648 91 dojo 46.56903728407441 6.554870695776669 963 | 662 91 supermarket 46.56672894264203 6.55064248423304 964 | 857 91 club 46.56781394161367 6.550152145395554 965 | 963 91 club 46.564384767624254 6.554667483824879 966 | 975 91 gym 46.56403808949643 6.5531573956034626 967 | 15 92 villa 46.56659687061008 6.5624628481490435 968 | 290 92 supermarket 46.56388681073386 6.563487625477079 969 | 389 92 supermarket 46.56445701958251 6.5668001376284515 970 | 468 92 supermarket 46.56523092437586 6.564880827791146 971 | 471 92 bar 46.56623380746044 6.565510743366786 972 | 610 92 cafeteria 46.56576830053862 6.568500308407957 973 | 620 92 cafeteria 46.56445962754545 6.569680804997137 974 | 691 92 dojo 46.569850585348725 6.566489054138286 975 | 695 92 dojo 46.56833114070417 6.56734063372878 976 | 733 92 cafeteria 46.566582520573 6.563690463550461 977 | 737 92 supermarket 46.56818450031175 6.568199708460923 978 | 72 93 appartment_block 46.563854224097106 6.574057165568125 979 | 92 93 appartment_block 46.565568384126415 6.576238045726297 980 | 639 93 restaurant 46.56745497475385 6.57942065498713 981 | 726 93 bar 46.5630790313292 6.573492602273968 982 | 944 93 restaurant 46.56361615494989 6.574217529271534 983 | 55 94 villa 46.56949176624713 6.584151597569569 984 | 455 94 dojo 46.568310987846566 6.581953692586463 985 | 492 94 dojo 46.56930168715437 6.58175270694941 986 | 507 94 dojo 46.567083670689804 6.586751189942195 987 | 532 94 gym 46.56733098198556 6.589903266132924 988 | 596 94 restaurant 46.56391545618498 6.584900072852644 989 | 629 94 restaurant 46.56800622790808 6.581612271054834 990 | 679 94 gym 46.568403524649284 6.584226388329639 991 | 880 94 bar 46.56300579653948 6.582356438519566 992 | 953 94 restaurant 46.56422486816315 6.585129756341301 993 | 1026 94 supermarket 46.569737240304114 6.586529196933279 994 | 32 95 villa 46.56873368194404 6.5929109209266 995 | 94 95 appartment_block 46.56701747735918 6.591801984405611 996 | 153 95 appartment_block 46.563651469651404 6.5922281047628175 997 | 176 95 villa 46.56792155827954 6.593651956085694 998 | 189 95 appartment_block 46.56772251539121 6.596409070721897 999 | 302 95 dojo 46.564880105967966 6.590822611044351 1000 | 322 95 restaurant 46.56619195380561 6.59829167944087 1001 | 847 95 restaurant 46.5638488106658 6.594140662382369 1002 | 887 95 bar 46.56674178586452 6.5940861190615125 1003 | 1010 95 supermarket 46.56881391849652 6.599710688975191 1004 | 114 96 villa 46.56545098726604 6.604974103150837 1005 | 144 96 villa 46.567189616167845 6.604013765126465 1006 | 371 96 cafeteria 46.56779725919087 6.60331314108905 1007 | 387 96 supermarket 46.56459122569213 6.606164502763473 1008 | 415 96 club 46.56628328857454 6.600897844826069 1009 | 535 96 cafeteria 46.56816878788168 6.604727752396665 1010 | 542 96 club 46.56853325936272 6.601271050091588 1011 | 555 96 dojo 46.56373688769788 6.603279485173149 1012 | 793 96 dojo 46.56962914042669 6.6021439632008345 1013 | 947 96 bar 46.565966546719544 6.600540552566687 1014 | 998 96 gym 46.56561008427464 6.601374078502725 1015 | 91 97 villa 46.564689479298714 6.616599470628401 1016 | 135 97 appartment_block 46.563529110172986 6.610314288949249 1017 | 190 97 appartment_block 46.56830858426094 6.6181010435269965 1018 | 246 97 company 46.5684471723514 6.612007421146351 1019 | 320 97 cafeteria 46.56446086400098 6.614012003451464 1020 | 470 97 bar 46.56529386107807 6.6130903442749105 1021 | 488 97 club 46.564722473734626 6.613406559071218 1022 | 611 97 gym 46.56350500194433 6.616128789156958 1023 | 775 97 dojo 46.5639067083474 6.617265484333373 1024 | 875 97 club 46.56793392871534 6.610247371519883 1025 | 889 97 restaurant 46.56944137162869 6.6159692496715135 1026 | 985 97 dojo 46.565168973305624 6.611464132181089 1027 | 206 98 company 46.56748779729455 6.622810821844465 1028 | 256 98 company 46.56489716353281 6.624231814027893 1029 | 275 98 restaurant 46.563743451115954 6.620531303913811 1030 | 349 98 gym 46.564719502235484 6.621319514111195 1031 | 419 98 dojo 46.56551245580578 6.624238806796255 1032 | 452 98 club 46.5661632341685 6.621484191047763 1033 | 495 98 cafeteria 46.56654973978156 6.6207754139159585 1034 | 580 98 dojo 46.568868229813965 6.624569644328853 1035 | 637 98 gym 46.56567393447606 6.6287557572964015 1036 | 718 98 gym 46.56882308782026 6.626290872146283 1037 | 940 98 gym 46.56707607002897 6.623042022346933 1038 | 48 99 appartment_block 46.56659336692821 6.636975909707584 1039 | 199 99 appartment_block 46.56585219840633 6.6386752713787365 1040 | 483 99 gym 46.56334651181309 6.634352581439679 1041 | 523 99 restaurant 46.563562567268924 6.631950196640158 1042 | 603 99 restaurant 46.563789056207554 6.633619490136448 1043 | 609 99 cafeteria 46.56602926701362 6.632546101416334 1044 | 644 99 dojo 46.56673623002838 6.638835491331567 1045 | 717 99 club 46.56402773490587 6.63925512437427 1046 | 725 99 bar 46.566898981268 6.631668508497343 1047 | 731 99 club 46.56708817541979 6.638119460789737 1048 | 792 99 cafeteria 46.563257077610345 6.634084385031178 1049 | 814 99 supermarket 46.568003469488055 6.63482320142031 1050 | 78 100 villa 46.56920952755545 6.64953112929113 1051 | 107 100 appartment_block 46.566273995842316 6.644205760088191 1052 | 170 100 villa 46.568819955744104 6.64294176710424 1053 | 177 100 villa 46.56844377224735 6.648119001685419 1054 | 271 100 supermarket 46.56336871719933 6.648431242252003 1055 | 283 100 cafeteria 46.56548607015575 6.649854303084311 1056 | 321 100 club 46.56602075384202 6.643414048570928 1057 | 372 100 restaurant 46.5638517119462 6.648454410388916 1058 | 649 100 bar 46.56404670949558 6.64024986552269 1059 | 730 100 gym 46.56596058841714 6.642228369125065 1060 | 828 100 club 46.56917669301612 6.642400892973268 1061 | 990 100 bar 46.56529045262247 6.6408547077838564 1062 | -------------------------------------------------------------------------------- /secretstroll/privacy_evaluation/query.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from os import path 3 | 4 | # Globals 5 | DIST_THRESH = 0.01 6 | 7 | 8 | def load_poi_data(): 9 | cwd = path.dirname(__file__) 10 | dat = np.loadtxt(path.join(cwd, 'pois.csv'), delimiter=" ", dtype=object, skiprows=1) 11 | poi_ids = dat[:, 0].astype(int) 12 | poi_type = dat[:, 2] 13 | poi_loc = dat[:, -2:].astype(float) 14 | 15 | return poi_ids, poi_type, poi_loc 16 | 17 | 18 | POI_IDS, POI_TYPES, POI_LOCS = load_poi_data() 19 | 20 | 21 | def get_nearby_pois(loc: np.ndarray, poi_type: str): 22 | """ Find nearby POIs of the specified type """ 23 | poi_ids = [] 24 | 25 | for i, poi_loc in enumerate(POI_LOCS): 26 | if POI_TYPES[i] == poi_type: 27 | d = np.linalg.norm(loc - poi_loc) 28 | 29 | if d <= DIST_THRESH: 30 | poi_ids.append(POI_IDS[i]) 31 | 32 | return poi_ids 33 | 34 | -------------------------------------------------------------------------------- /secretstroll/report_template/bib.bib: -------------------------------------------------------------------------------- 1 | @article{article, 2 | author = {Doe, John}, 3 | year = {1987}, 4 | month = {01}, 5 | pages = {}, 6 | title = {Example title}, 7 | journal = {Example conference} 8 | } 9 | -------------------------------------------------------------------------------- /secretstroll/report_template/report.tex: -------------------------------------------------------------------------------- 1 | \documentclass[10pt,conference,compsocconf]{IEEEtran} 2 | 3 | \usepackage{hyperref} 4 | \usepackage{graphicx} 5 | \usepackage{xcolor} 6 | \usepackage{blindtext, amsmath, comment, subfig, epsfig } 7 | \usepackage{grffile} 8 | \usepackage{caption} 9 | %\usepackage{subcaption} 10 | \usepackage{algorithmic} 11 | \usepackage[utf8]{inputenc} 12 | 13 | 14 | \title{CS-523 SecretStroll Report} 15 | \author{Author 1, Author 2, Author 3} 16 | \date{XX 2024} 17 | 18 | \begin{document} 19 | 20 | \maketitle 21 | 22 | \begin{abstract} 23 | Please report your design, implementation details, and findings of the second project in this report. \\ 24 | You can add references if necessary \cite{article}. \\ 25 | Remember to add a paragraph describing the contributions of each team member.\\ 26 | THE REPORT SHOULD NOT EXCEED 5 PAGES. 27 | \end{abstract} 28 | 29 | \section{Introduction} 30 | 31 | Provide a brief introduction about the aim of the project, and your road-map about the design/implementation for each sub-part. 32 | 33 | \section{Attribute-based credential} 34 | Explain how you mapped the system to the attribute based credential. How did you 35 | use the Fiat-Shamir heuristic? 36 | 37 | \subsection{Test} 38 | How did you test the system? 39 | You need to test the correct path and at least two failure paths. 40 | 41 | \subsection{Evaluation} 42 | Evaluate your ABC: report communication and computation stats (mean and standard 43 | deviation). Report statistic on key generation, issuance, signing, and 44 | verification. 45 | 46 | \section{(De)Anonymization of User Trajectories} 47 | 48 | \subsection{Privacy Evaluation} 49 | Provide a privacy analysis of the dataset. You should explicitly state your assumptions, adversary 50 | models, methods, and findings. 51 | 52 | \subsection{Defences} 53 | Propose a defence that users of the service could deploy to protect their privacy. You 54 | should state your assumptions, adversary models, and provide an experimental evaluation of your 55 | defences using the datasets and the grid specification. You should also discuss the 56 | privacy-utility trade-offs of your defence. 57 | 58 | \section{Cell Fingerprinting via Network Traffic Analysis} 59 | 60 | \subsection{Implementation details} 61 | Provide a description of your implementation here. You should provide details on your data collection methods, feature extraction, and classifier training. 62 | 63 | \subsection{Evaluation} 64 | Provide an evaluation of your classifier here -- the metrics after 10-fold cross validation. 65 | 66 | \subsection{Discussion and Countermeasures} 67 | Comment on your findings here. How well did your classifier perform? What factors could influence its performance? Are there countermeasures against this kind of attack? 68 | 69 | \bibliographystyle{IEEEtran} 70 | \bibliography{bib} 71 | \end{document} 72 | -------------------------------------------------------------------------------- /secretstroll/requirements.txt: -------------------------------------------------------------------------------- 1 | astroid==2.14.2 2 | attrs==22.2.0 3 | certifi==2022.12.7 4 | cffi==1.15.1 5 | charset-normalizer==3.0.1 6 | click==8.1.3 7 | dill==0.3.6 8 | exceptiongroup==1.1.0 9 | Flask==2.2.2 10 | Flask-SQLAlchemy==3.0.3 11 | greenlet==2.0.2 12 | idna==3.4 13 | importlib-metadata==6.0.0 14 | iniconfig==2.0.0 15 | isort==5.12.0 16 | itsdangerous==2.1.2 17 | Jinja2==3.1.2 18 | jsonpickle==3.0.1 19 | lazy-object-proxy==1.9.0 20 | MarkupSafe==2.1.2 21 | mccabe==0.7.0 22 | msgpack==1.0.4 23 | mypy==1.0.0 24 | mypy-extensions==1.0.0 25 | packaging==23.0 26 | petrelic==0.1.5 27 | platformdirs==3.0.0 28 | pluggy==1.0.0 29 | pycparser==2.21 30 | pylint==2.16.1 31 | PySocks==1.7.1 32 | pytest==7.2.1 33 | requests==2.28.2 34 | SQLAlchemy==2.0.3 35 | tomli==2.0.1 36 | tomlkit==0.11.6 37 | typing-extensions==4.4.0 38 | urllib3==1.26.14 39 | Werkzeug==2.2.3 40 | wrapt==1.14.1 41 | zipp==3.13.0 42 | -------------------------------------------------------------------------------- /secretstroll/serialization.py: -------------------------------------------------------------------------------- 1 | """ 2 | Serialisation for Petrelic's binary types. 3 | 4 | !!! DO NOT MODIFY THIS FILE !!! 5 | 6 | For this project, data can be serialized with the library `jsonpickle`. 7 | The underlying library, `pickle`, is notoriously insecure, and should never be 8 | used to de-serialize untrusted data. Therefore, even if this serialization 9 | library is OK for a student project of this scope, it should never be used for 10 | a real life project. 11 | 12 | One of the reason why `jsonpickle` is proposed is that is it easy to use, and 13 | allows you to concentrate on the important parts of the project. Here is an 14 | example: 15 | 16 | >>> import jsonpickle 17 | >>> class Foo: 18 | ... def __init__(self): 19 | ... self.a = 'Foo' 20 | ... self.b = 42 21 | ... def __eq__(self, other): 22 | ... return self.a == other.a and self.b == other.b 23 | ... 24 | >>> x_ori = Foo() 25 | >>> x_ser = jsonpickle.encode(x_ori) 26 | >>> x_des = jsonpickle.decode(x_ser) 27 | >>> x_ori == x_des 28 | True 29 | 30 | Because the `petrelic` library binds some binary types, `jsonpickle` needs to 31 | know how to handle them. Therefore, some handlers are defined and registered in 32 | this file. As such, if you need to serialize an object containing some 33 | `petrelic` types, with `jsonpickle`, you can do so by importing the "extended" 34 | `jsonpickle` from this sub-module such as: 35 | 36 | >>> from serialization import jsonpickle 37 | 38 | """ 39 | 40 | import base64 41 | 42 | import jsonpickle 43 | 44 | from petrelic.bn import Bn 45 | from petrelic.additive.pairing import ( 46 | G1Element as G1EA, 47 | G2Element as G2EA, 48 | GTElement as GtEA, 49 | ) 50 | from petrelic.multiplicative.pairing import ( 51 | G1Element as G1EM, 52 | G2Element as G2EM, 53 | GTElement as GtEM, 54 | ) 55 | from petrelic.native.pairing import ( 56 | G1Element as G1EN, 57 | G2Element as G2EN, 58 | GTElement as GtEN, 59 | ) 60 | from petrelic.petlib.pairing import G1Elem as G1EP, G2Elem as G2EP, GTElem as GtEP 61 | 62 | # 63 | # Define handlers for jsonpickle. 64 | # 65 | 66 | # Handler for big number ised intrnally by RELIC. 67 | 68 | 69 | class BnHandler(jsonpickle.handlers.BaseHandler): 70 | """JSONPickle handler for Bn""" 71 | 72 | def flatten(self, obj, data): 73 | data["b64repr"] = base64.b64encode(obj.binary()).decode("utf-8") 74 | return data 75 | 76 | def restore(self, obj): 77 | return Bn.from_binary(base64.b64decode(obj["b64repr"])) 78 | 79 | 80 | # Handlers for additive API. 81 | 82 | 83 | class G1EAHandler(jsonpickle.handlers.BaseHandler): 84 | """JSONPickle handler for G1Element""" 85 | 86 | def flatten(self, obj, data): 87 | data["b64repr"] = base64.b64encode(obj.to_binary()).decode("utf-8") 88 | return data 89 | 90 | def restore(self, obj): 91 | return G1EA.from_binary(base64.b64decode(obj["b64repr"])) 92 | 93 | 94 | class G2EAHandler(jsonpickle.handlers.BaseHandler): 95 | """JSONPickle handler for G2Element""" 96 | 97 | def flatten(self, obj, data): 98 | data["b64repr"] = base64.b64encode(obj.to_binary()).decode("utf-8") 99 | return data 100 | 101 | def restore(self, obj): 102 | return G2EA.from_binary(base64.b64decode(obj["b64repr"])) 103 | 104 | 105 | class GtEAHandler(jsonpickle.handlers.BaseHandler): 106 | """JSONPickle handler for GtElement""" 107 | 108 | def flatten(self, obj, data): 109 | data["b64repr"] = base64.b64encode(obj.to_binary()).decode("utf-8") 110 | return data 111 | 112 | def restore(self, obj): 113 | return GtEA.from_binary(base64.b64decode(obj["b64repr"])) 114 | 115 | 116 | # Handlers for multiplicative API. 117 | 118 | 119 | class G1EMHandler(jsonpickle.handlers.BaseHandler): 120 | """JSONPickle handler for G1Element""" 121 | 122 | def flatten(self, obj, data): 123 | data["b64repr"] = base64.b64encode(obj.to_binary()).decode("utf-8") 124 | return data 125 | 126 | def restore(self, obj): 127 | return G1EM.from_binary(base64.b64decode(obj["b64repr"])) 128 | 129 | 130 | class G2EMHandler(jsonpickle.handlers.BaseHandler): 131 | """JSONPickle handler for G2Element""" 132 | 133 | def flatten(self, obj, data): 134 | data["b64repr"] = base64.b64encode(obj.to_binary()).decode("utf-8") 135 | return data 136 | 137 | def restore(self, obj): 138 | return G2EM.from_binary(base64.b64decode(obj["b64repr"])) 139 | 140 | 141 | class GtEMHandler(jsonpickle.handlers.BaseHandler): 142 | """JSONPickle handler for GtElement""" 143 | 144 | def flatten(self, obj, data): 145 | data["b64repr"] = base64.b64encode(obj.to_binary()).decode("utf-8") 146 | return data 147 | 148 | def restore(self, obj): 149 | return GtEM.from_binary(base64.b64decode(obj["b64repr"])) 150 | 151 | 152 | # Handlers for RELIC's native API. 153 | 154 | 155 | class G1ENHandler(jsonpickle.handlers.BaseHandler): 156 | """JSONPickle handler for G1Element""" 157 | 158 | def flatten(self, obj, data): 159 | data["b64repr"] = base64.b64encode(obj.to_binary()).decode("utf-8") 160 | return data 161 | 162 | def restore(self, obj): 163 | return G1EN.from_binary(base64.b64decode(obj["b64repr"])) 164 | 165 | 166 | class G2ENHandler(jsonpickle.handlers.BaseHandler): 167 | """JSONPickle handler for G2Element""" 168 | 169 | def flatten(self, obj, data): 170 | data["b64repr"] = base64.b64encode(obj.to_binary()).decode("utf-8") 171 | return data 172 | 173 | def restore(self, obj): 174 | return G2EN.from_binary(base64.b64decode(obj["b64repr"])) 175 | 176 | 177 | class GtENHandler(jsonpickle.handlers.BaseHandler): 178 | """JSONPickle handler for GtElement""" 179 | 180 | def flatten(self, obj, data): 181 | data["b64repr"] = base64.b64encode(obj.to_binary()).decode("utf-8") 182 | return data 183 | 184 | def restore(self, obj): 185 | return GtEN.from_binary(base64.b64decode(obj["b64repr"])) 186 | 187 | 188 | # Handlers for petlib's API. 189 | 190 | 191 | class G1EPHandler(jsonpickle.handlers.BaseHandler): 192 | """JSONPickle handler for G1Element""" 193 | 194 | def flatten(self, obj, data): 195 | data["b64repr"] = base64.b64encode(obj.to_binary()).decode("utf-8") 196 | return data 197 | 198 | def restore(self, obj): 199 | return G1EP.from_binary(base64.b64decode(obj["b64repr"])) 200 | 201 | 202 | class G2EPHandler(jsonpickle.handlers.BaseHandler): 203 | """JSONPickle handler for G2Element""" 204 | 205 | def flatten(self, obj, data): 206 | data["b64repr"] = base64.b64encode(obj.to_binary()).decode("utf-8") 207 | return data 208 | 209 | def restore(self, obj): 210 | return G2EP.from_binary(base64.b64decode(obj["b64repr"])) 211 | 212 | 213 | class GtEPHandler(jsonpickle.handlers.BaseHandler): 214 | """JSONPickle handler for GtElement""" 215 | 216 | def flatten(self, obj, data): 217 | data["b64repr"] = base64.b64encode(obj.to_binary()).decode("utf-8") 218 | return data 219 | 220 | def restore(self, obj): 221 | return GtEP.from_binary(base64.b64decode(obj["b64repr"])) 222 | 223 | 224 | # Register handlers for jsonpickle. 225 | 226 | jsonpickle.handlers.register(Bn, BnHandler, base=True) 227 | 228 | jsonpickle.handlers.register(G1EA, G1EAHandler, base=True) 229 | jsonpickle.handlers.register(G2EA, G2EAHandler, base=True) 230 | jsonpickle.handlers.register(GtEA, GtEAHandler, base=True) 231 | 232 | jsonpickle.handlers.register(G1EM, G1EMHandler, base=True) 233 | jsonpickle.handlers.register(G2EM, G2EMHandler, base=True) 234 | jsonpickle.handlers.register(GtEM, GtEMHandler, base=True) 235 | 236 | jsonpickle.handlers.register(G1EN, G1ENHandler, base=True) 237 | jsonpickle.handlers.register(G2EN, G2ENHandler, base=True) 238 | jsonpickle.handlers.register(GtEN, GtENHandler, base=True) 239 | 240 | jsonpickle.handlers.register(G1EP, G1EPHandler, base=True) 241 | jsonpickle.handlers.register(G2EP, G2EPHandler, base=True) 242 | jsonpickle.handlers.register(GtEP, GtEPHandler, base=True) 243 | -------------------------------------------------------------------------------- /secretstroll/server.py: -------------------------------------------------------------------------------- 1 | """ 2 | Server entrypoint. 3 | 4 | !!! DO NOT MODIFY THIS FILE !!! 5 | 6 | """ 7 | 8 | import argparse 9 | import json 10 | from pathlib import Path 11 | import random 12 | import sys 13 | from typing import Dict, List, Union 14 | 15 | from flask import Flask, jsonify, make_response, request 16 | from flask_sqlalchemy import SQLAlchemy 17 | 18 | from stroll import Server 19 | 20 | 21 | APP = Flask(__name__) 22 | DB = SQLAlchemy() 23 | 24 | 25 | PUBLIC_KEY = None 26 | SECRET_KEY = None 27 | SERVER = None 28 | 29 | 30 | def main(args: List[str]) -> None: 31 | """Parse the arguments given to the server, and call the appropriate method.""" 32 | 33 | parser = argparse.ArgumentParser(description="Server for CS-523 project 2.") 34 | subparsers = parser.add_subparsers(help="Command") 35 | 36 | parser_setup = subparsers.add_parser( 37 | "setup", help="Generate a pair of secret and public keys." 38 | ) 39 | parser_setup.add_argument( 40 | "-p", 41 | "--pub", 42 | help="Name of the file in which to write the public key.", 43 | default="key.pub", 44 | type=argparse.FileType("wb") 45 | ) 46 | parser_setup.add_argument( 47 | "-s", 48 | "--sec", 49 | help="Name of the file in which to write the secret key.", 50 | default="key.sec", 51 | type=argparse.FileType("wb") 52 | ) 53 | parser_setup.add_argument( 54 | "-S", 55 | "--subscriptions", 56 | help="Subscriptions recognized by the server.", 57 | type=str, 58 | required=True, 59 | default=list(), 60 | action="append" 61 | ) 62 | 63 | parser_setup.set_defaults(callback=server_setup) 64 | 65 | parser_run = subparsers.add_parser("run", help="Run the server.") 66 | parser_run.add_argument( 67 | "-D", 68 | "--database", 69 | help="Path to the PoI database.", 70 | default=Path("fingerprint.db"), 71 | type=Path 72 | ) 73 | parser_run.add_argument( 74 | "-p", 75 | "--pub", 76 | help="Name of the file containing the public key.", 77 | default="key.pub", 78 | type=argparse.FileType("rb") 79 | ) 80 | parser_run.add_argument( 81 | "-s", 82 | "--sec", 83 | help="Name of the file containing the secret key.", 84 | default="key.sec", 85 | type=argparse.FileType("rb") 86 | ) 87 | 88 | parser_run.set_defaults(callback=server_run) 89 | 90 | namespace = parser.parse_args(args) 91 | 92 | if "callback" in namespace: 93 | namespace.callback(namespace) 94 | 95 | else: 96 | parser.print_help() 97 | 98 | 99 | def server_setup(args: argparse.Namespace) -> None: 100 | """Handle `setup` subcommand.""" 101 | 102 | public_key_fd = args.pub 103 | secret_key_fd = args.sec 104 | subscriptions = args.subscriptions 105 | subscriptions.append("username") 106 | 107 | try: 108 | secret_key, public_key = Server.generate_ca(subscriptions) 109 | 110 | public_key_fd.write(public_key) 111 | secret_key_fd.write(secret_key) 112 | 113 | public_key_fd.flush() 114 | secret_key_fd.flush() 115 | 116 | finally: 117 | args.pub.close() 118 | args.sec.close() 119 | 120 | 121 | def server_run(args: argparse.Namespace) -> None: 122 | """Handle `run` subcommand.""" 123 | 124 | # pylint: disable=global-statement 125 | global PUBLIC_KEY 126 | global SECRET_KEY 127 | global SERVER 128 | 129 | try: 130 | PUBLIC_KEY = args.pub.read() 131 | SECRET_KEY = args.sec.read() 132 | 133 | finally: 134 | args.pub.close() 135 | args.sec.close() 136 | 137 | db_path = args.database.resolve() 138 | APP.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{db_path}" 139 | APP.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 140 | DB.init_app(APP) 141 | 142 | SERVER = Server() 143 | 144 | host = "0.0.0.0" 145 | port = 8080 146 | 147 | APP.run(host=host, port=port, debug=True, threaded=False, processes=1) 148 | 149 | 150 | 151 | class PoI(DB.Model): 152 | """A PoI object consists of the following: 153 | 154 | poi_id -- ID of the PoI 155 | poi_name -- Name of the PoI 156 | poi_address -- Address of the PoI 157 | grid_id -- Grid in which the PoI is present 158 | poi_ratings -- List of ratings for the PoI. 159 | """ 160 | 161 | # pylint: disable=no-member 162 | 163 | __tablename__ = "POI" 164 | 165 | poi_id = DB.Column(DB.Integer, primary_key=True) 166 | poi_name = DB.Column(DB.String) 167 | poi_address = DB.Column(DB.String) 168 | grid_id = DB.Column(DB.Integer) 169 | poi_ratings = DB.Column(DB.String) 170 | 171 | def to_dict(self) -> Dict[str, Union[int, str]]: 172 | """ 173 | Return a dictionary representation of the object. 174 | """ 175 | return dict( 176 | poi_id=self.poi_id, 177 | poi_name=self.poi_name, 178 | poi_address=self.poi_address, 179 | grid_id=self.grid_id, 180 | poi_ratings=self.poi_ratings, 181 | ) 182 | 183 | 184 | 185 | 186 | @APP.route("/public-key", methods=["GET"]) 187 | def get_public_key(): 188 | """Handle requests for public key.""" 189 | return PUBLIC_KEY, 200 190 | 191 | 192 | @APP.route("/register", methods=["POST"]) 193 | def register(): 194 | """Handle registrations.""" 195 | username = request.files.get("username").read().decode("utf-8") 196 | subscriptions_raw = request.files.get("subscriptions").read().decode("utf-8") 197 | issuance_req = request.files.get("issuance_req").read() 198 | subscriptions = json.loads(subscriptions_raw) 199 | registration_res = SERVER.process_registration( 200 | SECRET_KEY, 201 | PUBLIC_KEY, 202 | issuance_req, 203 | username, 204 | subscriptions 205 | ) 206 | 207 | server_res = make_response(registration_res) 208 | return server_res 209 | 210 | 211 | def convert_loc_to_gridval(loc): 212 | """Placeholder function. Final function would convert the location to a grid value.""" 213 | return int(loc) 214 | 215 | 216 | @APP.route("/poi-loc", methods=["POST"]) 217 | def get_poi_loc(): 218 | """Takes in a latitude and longitude as input, returns a list of associated POIs.""" 219 | 220 | lat = float(request.files.get("lat").read().decode("utf-8")) 221 | lon = float(request.files.get("lon").read().decode("utf-8")) 222 | types = json.loads(request.files.get("types").read().decode("utf-8")) 223 | signature = request.files.get("signature").read() 224 | message = (f"{lat},{lon}").encode("utf-8") 225 | 226 | valid = SERVER.check_request_signature( 227 | PUBLIC_KEY, message, types, signature 228 | ) 229 | 230 | if not valid: 231 | return "Invalid signature", 401 232 | 233 | # PoIs are within coordinates (46.5, 6.55) and (46.57, 6.65) 234 | # mapped to a 10 x 10 grid 235 | if 46.5 <= lat <= 46.57 and 6.55 <= lon <= 6.65: 236 | cell_x = ((lat - 46.5) / 0.07) * 10 237 | cell_y = ((lon - 6.55) / 0.1) * 10 238 | cell_id = int(cell_x + (cell_y * 10)) 239 | records = PoI.query.filter_by(grid_id=cell_id).all() 240 | 241 | if records: 242 | poi_list = [record.to_dict()["poi_id"] for record in records] 243 | poi_list_res = {"poi_list": poi_list} 244 | else: 245 | poi_list_res = {"poi_list": []} 246 | else: 247 | poi_list_res = {"poi_list": []} 248 | 249 | return jsonify(poi_list_res) 250 | 251 | 252 | @APP.route("/poi-grid", methods=["POST"]) 253 | def get_poi_list(): 254 | """Takes in a cell ID as input, returns a list of associated POIs.""" 255 | 256 | cell_id = int(request.files.get("cell_id").read().decode("utf-8")) 257 | types = json.loads(request.files.get("types").read().decode("utf-8")) 258 | signature = request.files.get("signature").read() 259 | message = (f"{cell_id}").encode("utf-8") 260 | 261 | valid = SERVER.check_request_signature( 262 | PUBLIC_KEY, message, types, signature 263 | ) 264 | 265 | if not valid: 266 | return "Invalid signature", 401 267 | 268 | records = PoI.query.filter_by(grid_id=cell_id).all() 269 | 270 | if records: 271 | poi_list = [record.to_dict()["poi_id"] for record in records] 272 | poi_list_res = {"poi_list": poi_list} 273 | 274 | else: 275 | return "Not found", 404 276 | 277 | return jsonify(poi_list_res) 278 | 279 | 280 | @APP.route("/poi", methods=["GET"]) 281 | def get_poi_info(): 282 | """Takes in a PoI ID as input, returns information about that PoI. 283 | We have a paramter 'noise_factor' for tuning. 284 | This is used to slightly alter the size of responses sent by the server. 285 | Server adds padding records based on the noise factor. Do not change 286 | the noise code, this is used to simulate slight variations in traces 287 | from the server.""" 288 | 289 | poi_id = request.args.get('poi_id') 290 | noise_factor = 10 291 | 292 | records = PoI.query.filter_by(poi_id=int(poi_id)).all() 293 | if records: 294 | poi_info = records[0].to_dict() 295 | poi_info["poi_ratings"] = json.loads(poi_info["poi_ratings"]) 296 | 297 | random_length = random.randint(0, noise_factor) 298 | padding = [-1 for x in range(0, random_length)] 299 | poi_info["padding"] = padding 300 | 301 | else: 302 | return "Not found", 404 303 | 304 | return jsonify(poi_info) 305 | 306 | 307 | if __name__ == "__main__": 308 | main(sys.argv[1:]) 309 | -------------------------------------------------------------------------------- /secretstroll/stroll.py: -------------------------------------------------------------------------------- 1 | """ 2 | Classes that you need to complete. 3 | """ 4 | 5 | from typing import Any, Dict, List, Union, Tuple 6 | 7 | # Optional import 8 | from serialization import jsonpickle 9 | 10 | # Type aliases 11 | State = Any 12 | 13 | 14 | class Server: 15 | """Server""" 16 | 17 | 18 | def __init__(self): 19 | """ 20 | Server constructor. 21 | """ 22 | ############################################### 23 | # TODO: Complete this function. 24 | ############################################### 25 | raise NotImplementedError 26 | 27 | 28 | @staticmethod 29 | def generate_ca( 30 | subscriptions: List[str] 31 | ) -> Tuple[bytes, bytes]: 32 | """Initializes the credential system. Runs exactly once in the 33 | beginning. Decides on schemes public parameters and choses a secret key 34 | for the server. 35 | 36 | Args: 37 | subscriptions: a list of all valid attributes. Users cannot get a 38 | credential with a attribute which is not included here. 39 | 40 | Returns: 41 | tuple containing: 42 | - server's secret key 43 | - server's public information 44 | You are free to design this as you see fit, but the return types 45 | should be encoded as bytes. 46 | """ 47 | ############################################### 48 | # TODO: Complete this function. 49 | ############################################### 50 | raise NotImplementedError 51 | 52 | 53 | def process_registration( 54 | self, 55 | server_sk: bytes, 56 | server_pk: bytes, 57 | issuance_request: bytes, 58 | username: str, 59 | subscriptions: List[str] 60 | ) -> bytes: 61 | """ Registers a new account on the server. 62 | 63 | Args: 64 | server_sk: the server's secret key (serialized) 65 | server_pk: the server's public key (serialized) 66 | issuance_request: The issuance request (serialized) 67 | username: username 68 | subscriptions: attributes 69 | 70 | 71 | Return: 72 | serialized response (the client should be able to build a 73 | credential with this response). 74 | """ 75 | ############################################### 76 | # TODO: Complete this function. 77 | ############################################### 78 | raise NotImplementedError 79 | 80 | 81 | def check_request_signature( 82 | self, 83 | server_pk: bytes, 84 | message: bytes, 85 | revealed_attributes: List[str], 86 | signature: bytes 87 | ) -> bool: 88 | """ Verify the signature on the location request 89 | 90 | Args: 91 | server_pk: the server's public key (serialized) 92 | message: The message to sign 93 | revealed_attributes: revealed attributes 94 | signature: user's authorization (serialized) 95 | 96 | Returns: 97 | whether a signature is valid 98 | """ 99 | ############################################### 100 | # TODO: Complete this function. 101 | ############################################### 102 | raise NotImplementedError 103 | 104 | 105 | class Client: 106 | """Client""" 107 | 108 | def __init__(self): 109 | """ 110 | Client constructor. 111 | """ 112 | ############################################### 113 | # TODO: Complete this function. 114 | ############################################### 115 | raise NotImplementedError() 116 | 117 | 118 | def prepare_registration( 119 | self, 120 | server_pk: bytes, 121 | username: str, 122 | subscriptions: List[str] 123 | ) -> Tuple[bytes, State]: 124 | """Prepare a request to register a new account on the server. 125 | 126 | Args: 127 | server_pk: a server's public key (serialized) 128 | username: user's name 129 | subscriptions: user's subscriptions 130 | 131 | Return: 132 | A tuple containing: 133 | - an issuance request 134 | - A private state. You can use state to store and transfer information 135 | from prepare_registration to proceed_registration_response. 136 | You need to design the state yourself. 137 | """ 138 | ############################################### 139 | # TODO: Complete this function. 140 | ############################################### 141 | raise NotImplementedError 142 | 143 | 144 | def process_registration_response( 145 | self, 146 | server_pk: bytes, 147 | server_response: bytes, 148 | private_state: State 149 | ) -> bytes: 150 | """Process the response from the server. 151 | 152 | Args: 153 | server_pk a server's public key (serialized) 154 | server_response: the response from the server (serialized) 155 | private_state: state from the prepare_registration 156 | request corresponding to this response 157 | 158 | Return: 159 | credentials: create an attribute-based credential for the user 160 | """ 161 | ############################################### 162 | # TODO: Complete this function. 163 | ############################################### 164 | raise NotImplementedError 165 | 166 | 167 | def sign_request( 168 | self, 169 | server_pk: bytes, 170 | credentials: bytes, 171 | message: bytes, 172 | types: List[str] 173 | ) -> bytes: 174 | """Signs the request with the client's credential. 175 | 176 | Arg: 177 | server_pk: a server's public key (serialized) 178 | credential: client's credential (serialized) 179 | message: message to sign 180 | types: which attributes should be sent along with the request? 181 | 182 | Returns: 183 | A message's signature (serialized) 184 | """ 185 | ############################################### 186 | # TODO: Complete this function. 187 | ############################################### 188 | raise NotImplementedError 189 | -------------------------------------------------------------------------------- /secretstroll/tor/.git_keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-epfl/CS-523-public/624d148000290397cd4caab91b3f55bad54fd1df/secretstroll/tor/.git_keep -------------------------------------------------------------------------------- /smcompiler/README.md: -------------------------------------------------------------------------------- 1 | # Secure Multi-party Computation System 2 | 3 | ## Introduction 4 | 5 | In this project, you will develop a secure multi-party computation system in 6 | Python 3. We provide you an [instructions handout](./handout/handout_project_smcompiler.pdf) and this code skeleton to help with the development. 7 | 8 | We also provide a virtual machine (VM) to facilitate the setup and configuration 9 | of the system and reduce potential networking problems which might happen while 10 | running the application in different environments. 11 | 12 | ### Skeleton 13 | You will have to implement the SMC client, the trusted parameter generator, 14 | secret sharing mechanisms, and tools for specifying expressions to compute. We 15 | already implemented network communications and the trusted server, along with a 16 | test suite that your implementation will have to pass. 17 | 18 | #### Files in the skeleton 19 | 20 | The skeleton contains the following files. 21 | 22 | Components for building an SMC protocol. You should modify these: 23 | * `expression.py`—Tools for defining arithmetic expressions. 24 | * `secret_sharing.py`—Secret sharing scheme 25 | * `ttp.py`—Trusted parameter generator for the Beaver multiplication scheme. 26 | * `smc_party.py`—SMC party implementation 27 | * `test_integration.py`—Integration test suite. 28 | * `test_expression.py`—Template of a test suite for expression handling. 29 | * `test_ttp.py`—Template of a test suite for the trusted parameter generator. 30 | * `test_secret_sharing.py`—Template of a test suite for secret sharing. 31 | 32 | Code that handles the communication. You should not need to modify these files unless 33 | you bump into some serialization issues. 34 | * `protocol.py`—Specification of SMC protocol 35 | * `communication.py`—SMC party-side of communication 36 | * `server.py`—Trusted server to exchange information between SMC parties 37 | 38 | Read the comments in each of the files for more details and pointers. 39 | 40 | #### Requirements and what files should you change 41 | As you can see above, you can change most of the files in this skeleton. 42 | However, the **requirement** is that all existing tests in `test_integration.py` 43 | should pass **without any modification** (you can, however, add new tests.) We 44 | will test your solution using our own version of ``test_integration.py`` and all 45 | failing tests will negatively contribute to the grade. 46 | 47 | ### Testing 48 | 49 | An integral part of a system development is testing. 50 | For this first project, we provide you with an integration test suite to ensure 51 | the functionalities you will have to implement works correctly. 52 | 53 | They are implemented using *pytest*, and you can run them using the command 54 | ``` 55 | python3 -m pytest 56 | ``` 57 | in the directory of the skeleton. 58 | 59 | If you want to run only one specific test suite, you can specify the file in 60 | the command line. For example, if we only want to test our implementation for 61 | handling the expression, we will run the following command: 62 | ``` 63 | python3 -m pytest test_expression.py 64 | ``` 65 | In some versions, pytest captures the program output, and only displays the 66 | result of the test. When debugging, you can disable this capture by passing the 67 | option `-s` to Pytest. 68 | ``` 69 | python3 -m pytest -s 70 | ``` 71 | 72 | When running the integration test suite, you will notice that it will run SMC 73 | parties and trusted server as independent processes and that these processes 74 | will communicate via a network. 75 | 76 | You are free to write additional test suites to ensure your code is working as 77 | you expect. Consult the description of the files in the project for some 78 | skeleton test files. 79 | 80 | ## Setting up the development environment 81 | 82 | We provide you a VM for this project with all necessary Python dependencies 83 | already installed. 84 | 85 | If you are using the provided VM you can skip this section. 86 | 87 | If you are not using the VM, you will need to install Python 3 on your machine. 88 | This code was implemented and tested with Python 3.9, you may want to install a 89 | higher version, in which case, ensure that you only use features supported by 90 | Python 3.9 in your code. 91 | 92 | You can install the dependant python libraries by running the command 93 | ``` 94 | python3 -m pip install -r requirements.txt 95 | ``` 96 | in the directory of the skeleton. 97 | 98 | Also, the name given to the Python binary might differ depending on your 99 | system, in the VM we provided, the command which refers specifically to the 100 | Python 3 interpreter is `python3`, in some systems, the command might simply be 101 | called `python`. So when running the commands we provide, ensure you are using 102 | the correct interpreter. 103 | 104 | **Note:** For your curiosity, the network communications rely on the library *Requests*, 105 | and on the *Flask* framework, while the tests are implemented with the *pytest* 106 | framework. 107 | Feel free to check their documentation, if you would like to understand in 108 | details how they work, and feel free to have a look at the files 109 | `communication.py`, `server.py`, and at the test suites to see how we use these 110 | library and frameworks in practice. 111 | 112 | ### Collaboration 113 | 114 | You can use git repositories to sync your work with your teammates. However, 115 | keep in mind that you are not allowed to use public repositories, so make sure 116 | that your repository is **private**. 117 | 118 | ### Virtual machine 119 | 120 | We provide you with a VM for the secure multi-party computation system. 121 | We have already installed the skeleton, and all the necessary applications and 122 | libraries on the VM. 123 | **We only provide support for projects running inside the VM and we strongly 124 | recommend you to develop inside the VM.** 125 | 126 | There are two accounts on the VM (`user:password`): 127 | 128 | ``` 129 | student:student 130 | root:root 131 | ``` 132 | 133 | You can set up ssh on the VM and connect from your host or directly use the VM 134 | as your development environment. 135 | 136 | ### Setting up ssh in VirtualBox 137 | 138 | In VirtualBox, you can set up ssh access to the VM by following these steps: 139 | 140 | * Open the settings of your image 141 | * Go to the "Network" panel 142 | * Choose "Advanced" and click on the "Port forwarding" button 143 | * Add a forwarding rule (green "plus" button on the side) 144 | * In the forwarding rule, leave IP addresses empty, set **Host port** to _2222_, 145 | and **Guest port** to 22 (the default SSH port) 146 | * Restart the virtual machine 147 | 148 | Now, you can connect to your virtual machine via ssh: 149 | ``` 150 | ssh -p 2222 student@127.0.0.1 151 | ``` 152 | 153 | This is how you copy files _TO_ the VM: 154 | ``` 155 | scp -P 2222 student@127.0.0.1: 156 | ``` 157 | 158 | Copy files _FROM_ the VM: 159 | ``` 160 | scp -P 2222 student@127.0.0.1: 161 | ``` 162 | 163 | Another option to exchange data is to have shared directories between the VM 164 | and your host. 165 | For this feature to work correctly you have to install *Guest Additions* from 166 | VirtualBox on the VM and refer to their documentation. 167 | 168 | 169 | ## Example of a test run 170 | 171 | Once a part of your project is finished, you can test if it works correctly with 172 | *pytest*. Here is an example of output that the tests might produce if your 173 | project is well implemented. 174 | 175 | ``` 176 | python3 -m pytest -s 177 | ============================= test session starts ============================= 178 | platform linux -- Python 3.6.9, pytest-6.2.2, py-1.10.0, pluggy-0.13.1 179 | rootdir: /home/student/Desktop/solution 180 | plugins: cov-2.11.1 181 | collected 25 items 182 | 183 | test_expression.py . 184 | test_integration.py [ PUBLISH ] SENDER Alice / LABEL final 185 | Alice has finished! 186 | Server stopped. 187 | .[ PUBLISH ] SENDER Alice / LABEL final 188 | Alice has finished! 189 | Server stopped. 190 | .[ SEND ] SENDER Alice / LABEL 4f3963326a673d3d / RECEIVER Bob 191 | [ SEND ] SENDER Bob / LABEL 434e38516c413d3d / RECEIVER Alice 192 | [ SEND ] SENDER Alice / LABEL 3067437662413d3d / RECEIVER Bob 193 | [ RETRIEVE ] RECEIVER Bob / LABEL 4f3963326a673d3d 194 | [ RETRIEVE ] RECEIVER Alice / LABEL 434e38516c413d3d 195 | [ RETRIEVE ] RECEIVER Bob / LABEL 3067437662413d3d 196 | [ PUBLISH ] SENDER Alice / LABEL final 197 | [ PUBLISH ] SENDER Bob / LABEL final 198 | [ RETRIEVE ] RECEIVER Alice. LABEL final / SENDER Bob 199 | [ RETRIEVE ] RECEIVER Bob. LABEL final / SENDER Alice 200 | Alice has finished! 201 | Bob has finished! 202 | Server stopped. 203 | .[ SEND ] SENDER Alice / LABEL 3562684370413d3d / RECEIVER Bob 204 | [ SEND ] SENDER Bob / LABEL 4d6d706f35673d3d / RECEIVER Alice 205 | [ RETRIEVE ] RECEIVER Alice / LABEL 4d6d706f35673d3d 206 | [ RETRIEVE ] RECEIVER Bob / LABEL 3562684370413d3d 207 | [ PUBLISH ] SENDER Alice / LABEL final 208 | [ PUBLISH ] SENDER Bob / LABEL final 209 | [ RETRIEVE ] RECEIVER Alice. LABEL final / SENDER Bob 210 | [ RETRIEVE ] RECEIVER Bob. LABEL final / SENDER Alice 211 | Alice has finished! 212 | Bob has finished! 213 | Server stopped. 214 | .[ SEND ] SENDER Alice / LABEL 783156716e773d3d / RECEIVER Bob 215 | 216 | ... 217 | 218 | [ RETRIEVE ] RECEIVER Bob. LABEL final / SENDER Alice 219 | Bob has finished! 220 | Server stopped. 221 | . 222 | test_secret_sharing.py .. 223 | 224 | ======================= 25 passed in 116.59s (0:01:56) ======================== 225 | ``` 226 | 227 | **Note:** You might encounter some warnings when running the tests. 228 | In this case, all of them were related to the libraries used in the project, so 229 | they were not related to the project code by itself. 230 | If you encounter some warning, you might pay attention to them if some relate 231 | to your code, they might reveal some code soon to be obsolete, or some part of 232 | your code that you might want to change. 233 | -------------------------------------------------------------------------------- /smcompiler/communication.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utilities for client communication with the trusted server. 3 | You should not need to change this file. 4 | """ 5 | 6 | import json 7 | import time 8 | from typing import Union, Tuple 9 | 10 | import requests 11 | 12 | from secret_sharing import Share 13 | 14 | 15 | def sanitize_url_param(url_param: Union[bytes, str]) -> str: 16 | """ 17 | Sanitize URL parameter to be URL-safe. 18 | """ 19 | if isinstance(url_param, bytes): 20 | # Mypy "dislikes" variable redefinition. 21 | url_param = url_param.decode("ASCII") # type: ignore 22 | 23 | # %2F is indistinguishable from / due to WSGI standard. 24 | url_param = url_param.replace(r"%2F", "_").replace(r"%2f", "_") 25 | 26 | return url_param.replace("/", "_").replace("+", "-") # type: ignore 27 | 28 | 29 | class Communication: 30 | """ 31 | Network communications with the server. 32 | 33 | Attributes: 34 | server_host: hostname of the server 35 | server_port: port of the server 36 | client_id: Identifier of this client 37 | poll_delay: delay between requests in seconds (default: 0.2 s) 38 | protocol: network protocol to use (default: "http") 39 | """ 40 | 41 | def __init__( 42 | self, 43 | server_host: str, 44 | server_port: int, 45 | client_id: str, 46 | poll_delay: float = 0.2, 47 | protocol: str = "http" 48 | ): 49 | self.base_url = f"{protocol}://{server_host}:{server_port}" 50 | self.client_id = client_id 51 | self.poll_delay = poll_delay 52 | 53 | 54 | def send_private_message( 55 | self, 56 | receiver_id: str, 57 | label: str, 58 | message: Union[bytes, str] 59 | ) -> None: 60 | """ 61 | Send a private message to the server. 62 | """ 63 | 64 | client_id_san = sanitize_url_param(self.client_id) 65 | receiver_id_san = sanitize_url_param(receiver_id) 66 | label_san = sanitize_url_param(label) 67 | 68 | url = f"{self.base_url}/private/{client_id_san}/{receiver_id_san}/{label_san}" 69 | print(f"POST {url}") 70 | requests.post(url, message) 71 | 72 | 73 | def retrieve_private_message( 74 | self, 75 | label: str 76 | ) -> bytes: 77 | """ 78 | Retrieve a private message from the server. 79 | """ 80 | 81 | client_id_san = sanitize_url_param(self.client_id) 82 | label_san = sanitize_url_param(label) 83 | 84 | url = f"{self.base_url}/private/{client_id_san}/{label_san}" 85 | # We can either use a websocket, or do some polling, but websockets would require asyncio. 86 | # So we are doing polling to avoid introducing a new programming paradigm. 87 | while True: 88 | print(f"GET {url}") 89 | res = requests.get(url) 90 | if res.status_code == 200: 91 | return res.content 92 | time.sleep(self.poll_delay) 93 | 94 | 95 | def publish_message( 96 | self, 97 | label: str, 98 | message: Union[bytes, str] 99 | ) -> None: 100 | """ 101 | Publish a message on the server. 102 | """ 103 | 104 | client_id_san = sanitize_url_param(self.client_id) 105 | label_san = sanitize_url_param(label) 106 | 107 | url = f"{self.base_url}/public/{client_id_san}/{label_san}" 108 | print(f"POST {url}") 109 | requests.post(url, message) 110 | 111 | 112 | def retrieve_public_message( 113 | self, 114 | sender_id: str, 115 | label: str 116 | ) -> bytes: 117 | """ 118 | Retrieve a public message from the server. 119 | """ 120 | 121 | client_id_san = sanitize_url_param(self.client_id) 122 | sender_id_san = sanitize_url_param(sender_id) 123 | label_san = sanitize_url_param(label) 124 | 125 | url = f"{self.base_url}/public/{client_id_san}/{sender_id_san}/{label_san}" 126 | 127 | # We can either use a websocket, or do some polling, but websockets would require asyncio. 128 | # So we are doing polling to avoid introducing a new programming paradigm. 129 | while True: 130 | print(f"GET {url}") 131 | res = requests.get(url) 132 | if res.status_code == 200: 133 | return res.content 134 | time.sleep(self.poll_delay) 135 | 136 | 137 | def retrieve_beaver_triplet_shares( 138 | self, 139 | op_id: str 140 | ) -> Tuple[Share, Share, Share]: 141 | """ 142 | Retrieve a triplet of shares generated by the trusted server. 143 | """ 144 | 145 | client_id_san = sanitize_url_param(self.client_id) 146 | op_id_san = sanitize_url_param(op_id) 147 | 148 | url = f"{self.base_url}/shares/{client_id_san}/{op_id_san}" 149 | print(f"GET {url}") 150 | 151 | res = requests.get(url) 152 | return tuple([Share.deserialize(s) for s in json.loads(res.text)]) # type: ignore 153 | -------------------------------------------------------------------------------- /smcompiler/expression.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tools for building arithmetic expressions to execute with SMC. 3 | 4 | Example expression: 5 | >>> alice_secret = Secret() 6 | >>> bob_secret = Secret() 7 | >>> expr = alice_secret * bob_secret * Scalar(2) 8 | 9 | MODIFY THIS FILE. 10 | """ 11 | 12 | import base64 13 | import random 14 | from typing import Optional 15 | 16 | 17 | ID_BYTES = 4 18 | 19 | 20 | def gen_id() -> bytes: 21 | id_bytes = bytearray( 22 | random.getrandbits(8) for _ in range(ID_BYTES) 23 | ) 24 | return base64.b64encode(id_bytes) 25 | 26 | 27 | class Expression: 28 | """ 29 | Base class for an arithmetic expression. 30 | """ 31 | 32 | def __init__( 33 | self, 34 | id: Optional[bytes] = None 35 | ): 36 | # If ID is not given, then generate one. 37 | if id is None: 38 | id = gen_id() 39 | self.id = id 40 | 41 | def __add__(self, other): 42 | raise NotImplementedError("You need to implement this method.") 43 | 44 | 45 | def __sub__(self, other): 46 | raise NotImplementedError("You need to implement this method.") 47 | 48 | 49 | def __mul__(self, other): 50 | raise NotImplementedError("You need to implement this method.") 51 | 52 | 53 | def __hash__(self): 54 | return hash(self.id) 55 | 56 | 57 | # Feel free to add as many methods as you like. 58 | 59 | 60 | class Scalar(Expression): 61 | """Term representing a scalar finite field value.""" 62 | 63 | def __init__( 64 | self, 65 | value: int, 66 | id: Optional[bytes] = None 67 | ): 68 | self.value = value 69 | super().__init__(id) 70 | 71 | 72 | def __repr__(self): 73 | return f"{self.__class__.__name__}({repr(self.value)})" 74 | 75 | 76 | def __hash__(self): 77 | return 78 | 79 | 80 | # Feel free to add as many methods as you like. 81 | 82 | 83 | class Secret(Expression): 84 | """Term representing a secret finite field value (variable).""" 85 | 86 | def __init__( 87 | self, 88 | id: Optional[bytes] = None 89 | ): 90 | super().__init__(id) 91 | 92 | 93 | def __repr__(self): 94 | return ( 95 | f"{self.__class__.__name__}({self.value if self.value is not None else ''})" 96 | ) 97 | 98 | 99 | # Feel free to add as many methods as you like. 100 | 101 | 102 | # Feel free to add as many classes as you like. 103 | -------------------------------------------------------------------------------- /smcompiler/handout/handout_project_smcompiler.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-epfl/CS-523-public/624d148000290397cd4caab91b3f55bad54fd1df/smcompiler/handout/handout_project_smcompiler.pdf -------------------------------------------------------------------------------- /smcompiler/protocol.py: -------------------------------------------------------------------------------- 1 | from expression import Expression 2 | 3 | 4 | class ProtocolSpec: 5 | """Specification of the SMC protocol. 6 | 7 | Attributes: 8 | participant_ids: List of IDs of the participating clients 9 | expr: Expression to be computed 10 | """ 11 | 12 | def __init__(self, participant_ids: list, expr: Expression): 13 | self.participant_ids = participant_ids 14 | self.expr = expr 15 | -------------------------------------------------------------------------------- /smcompiler/report_template/bib.bib: -------------------------------------------------------------------------------- 1 | 2 | @article{article, 3 | author = {Goldreich, Oded and Micali, Siltnb and Wigderson, Avi}, 4 | year = {1987}, 5 | month = {01}, 6 | pages = {}, 7 | title = {A Completeness Theorem for Protocols with Honest Majority}, 8 | journal = {Conference Proceedings of the Annual ACM Symposium on Theory of Computing} 9 | } -------------------------------------------------------------------------------- /smcompiler/report_template/main.tex: -------------------------------------------------------------------------------- 1 | \documentclass[10pt,conference,compsocconf]{IEEEtran} 2 | 3 | \usepackage{hyperref} 4 | \usepackage{graphicx} 5 | \usepackage{xcolor} 6 | \usepackage{blindtext, amsmath, comment, subfig, epsfig } 7 | \usepackage{caption} 8 | \usepackage{algorithmic} 9 | \usepackage{cite} 10 | \usepackage[utf8]{inputenc} 11 | 12 | 13 | \title{CS-523 SMCompiler Report} 14 | \author{Author 1, Author 2, Author 3} 15 | \date{} 16 | 17 | \begin{document} 18 | 19 | \maketitle 20 | 21 | \begin{abstract} 22 | Please report your design, implementation details, findings of the first project in this report. \\ 23 | You can add references if necessary \cite{article}. \\ 24 | Remember to add a paragraph describing the contributions of each team member.\\ 25 | THE REPORT SHOULD NOT EXCEED 2 PAGES. 26 | \end{abstract} 27 | 28 | \section{Introduction} 29 | Give a brief introduction about the project and its aims. 30 | 31 | \section{Threat model} 32 | Describe the threat model for the SMC framework that you have implemented. 33 | 34 | \section{Implementation details} 35 | Provide any useful details about your implementation. 36 | 37 | \section{Performance evaluation} 38 | Report the computation and communication cost of the framework for different circuit and protocol parameters. 39 | 40 | \section{Application} 41 | Detail the use case of SMC and a circuit for this use case. Discuss possible privacy leakage not 42 | covered by SMC. Discuss a mitigation if needed. 43 | 44 | \bibliographystyle{IEEEtran} 45 | \bibliography{bib} 46 | \end{document} 47 | -------------------------------------------------------------------------------- /smcompiler/requirements.txt: -------------------------------------------------------------------------------- 1 | astroid==2.14.2 2 | attrs==22.2.0 3 | certifi==2022.12.7 4 | cffi==1.15.1 5 | charset-normalizer==3.0.1 6 | click==8.1.3 7 | dill==0.3.6 8 | exceptiongroup==1.1.0 9 | Flask==2.2.2 10 | Flask-SQLAlchemy==3.0.3 11 | greenlet==2.0.2 12 | idna==3.4 13 | importlib-metadata==6.0.0 14 | iniconfig==2.0.0 15 | isort==5.12.0 16 | itsdangerous==2.1.2 17 | Jinja2==3.1.2 18 | jsonpickle==3.0.1 19 | lazy-object-proxy==1.9.0 20 | MarkupSafe==2.1.2 21 | mccabe==0.7.0 22 | msgpack==1.0.4 23 | mypy==1.0.0 24 | mypy-extensions==1.0.0 25 | packaging==23.0 26 | petrelic==0.1.5 27 | platformdirs==3.0.0 28 | pluggy==1.0.0 29 | pycparser==2.21 30 | pylint==2.16.1 31 | PySocks==1.7.1 32 | pytest==7.2.1 33 | requests==2.28.2 34 | SQLAlchemy==2.0.3 35 | tomli==2.0.1 36 | tomlkit==0.11.6 37 | typing-extensions==4.4.0 38 | urllib3==1.26.14 39 | Werkzeug==2.2.3 40 | wrapt==1.14.1 41 | zipp==3.13.0 42 | -------------------------------------------------------------------------------- /smcompiler/secret_sharing.py: -------------------------------------------------------------------------------- 1 | """ 2 | Secret sharing scheme. 3 | """ 4 | 5 | from __future__ import annotations 6 | 7 | from typing import List 8 | 9 | 10 | class Share: 11 | """ 12 | A secret share in a finite field. 13 | """ 14 | 15 | def __init__(self, *args, **kwargs): 16 | # Adapt constructor arguments as you wish 17 | raise NotImplementedError("You need to implement this method.") 18 | 19 | def __repr__(self): 20 | # Helps with debugging. 21 | raise NotImplementedError("You need to implement this method.") 22 | 23 | def __add__(self, other): 24 | raise NotImplementedError("You need to implement this method.") 25 | 26 | def __sub__(self, other): 27 | raise NotImplementedError("You need to implement this method.") 28 | 29 | def __mul__(self, other): 30 | raise NotImplementedError("You need to implement this method.") 31 | 32 | def serialize(self): 33 | """Generate a representation suitable for passing in a message.""" 34 | raise NotImplementedError("You need to implement this method.") 35 | 36 | @staticmethod 37 | def deserialize(serialized) -> Share: 38 | """Restore object from its serialized representation.""" 39 | raise NotImplementedError("You need to implement this method.") 40 | 41 | 42 | def share_secret(secret: int, num_shares: int) -> List[Share]: 43 | """Generate secret shares.""" 44 | raise NotImplementedError("You need to implement this method.") 45 | 46 | 47 | def reconstruct_secret(shares: List[Share]) -> int: 48 | """Reconstruct the secret from shares.""" 49 | raise NotImplementedError("You need to implement this method.") 50 | 51 | 52 | # Feel free to add as many methods as you want. 53 | -------------------------------------------------------------------------------- /smcompiler/server.py: -------------------------------------------------------------------------------- 1 | """ 2 | Trusted server that should help SMC client to communicate. 3 | You should not need to change this file. 4 | """ 5 | 6 | import collections 7 | import sys 8 | from typing import Dict, List, Optional, Tuple 9 | 10 | from flask import Flask, request, Response, jsonify 11 | 12 | from ttp import TrustedParamGenerator 13 | 14 | 15 | app: Flask = Flask("Trusted Third Party Server") 16 | store: Dict[str, Dict[Tuple[str, str], bytes]] = collections.defaultdict(dict) 17 | ttp: TrustedParamGenerator = TrustedParamGenerator() 18 | 19 | 20 | @app.route("/private///