Commit a944bb84 authored by Aditya Damodaran's avatar Aditya Damodaran
Browse files

PEP-8 and doc updates

parent e45a6a1a
......@@ -9,14 +9,14 @@ Authors:
-->
# Unlinkable Updatable Hiding Databases and Privacy-Preserving Loyalty Programs
This repository contains the source code for an implementation of the HD (Unlinkable Updatable Hiding Database) primitive described in our paper titled "Unlinkable Updatable Hiding Databases and Privacy-Preserving Loyalty Programs". It also includes an implementation of the Privacy-Preserving Loyalty Program protocol put forward in this paper, which uses the aforementioned primitive.
This repository contains the source code for an implementation of the HD (Unlinkable Updatable Hiding Database) primitive described in our paper titled "Unlinkable Updatable Hiding Databases and Privacy-Preserving Loyalty Programs", to be published at PETS 2021. It also includes an implementation of the Privacy-Preserving Loyalty Program protocol put forward in this paper, which uses the aforementioned primitive.
These implementations were used to measure the storage and computation costs of the cryptographic operations in our primitive and the protocol, for the _Efficiency Analysis_ sections of our paper.
# Installation
## Installation
## Virtual Machine (Vagrant)
### Virtual Machine (Vagrant)
You can the use `Vagrantfile` file in this repository to spin up a virtual machine with a pre-configured execution environment using Vagrant: https://www.vagrantup.com.
You must first install Vagrant, and then run the following command from the root directory of this repository:
......@@ -24,24 +24,29 @@ You must first install Vagrant, and then run the following command from the root
$ vagrant up
```
Once the virtual machine is ready, log in with `vagrant:vagrant`.
The code will then be accessible from the `/vagrant` directory, in the virtual machine window that shows up after the script downloads and installs all prerequisites.
### Manual Installation (Ubuntu)
Instructions are available [here](docs/install_bionic.md).
## Manual Installation
Our code requires Python 3.7, and the Charm-Crypto library (v0.50) built with the Relic toolkit (v0.5.0) , as described on these pages:
### Manual Installation
Our code requires Python 3.6, and the Charm-Crypto library (v0.50) built with the Relic toolkit (v0.5.0), as described on these pages:
1. Relic toolkit : https://jhuisi.github.io/charm/relic.html#charm-with-relic
2. Charm-Crypto https://jhuisi.github.io/charm/install_source.html#platform-install-manual
1. Relic toolkit: https://jhuisi.github.io/charm/relic.html#charm-with-relic
2. Charm-Crypto: https://jhuisi.github.io/charm/install_source.html#platform-install-manual
> Note: Charm-Crypto additionally requires PBC (v0.5.14) and GMP (v6.2.1).
Finally, use the following command to install Openpyxl and Texttable:
Finally, use the following command to install Openpyxl (v3.0.6) and Texttable (v1.6.3):
```bash
$ python3 -m pip install -r requirements.txt
```
# Usage
## Usage
```bash
Usage: python3 ./protocol.py [-h] [-k K] [-r] N
......@@ -53,13 +58,48 @@ optional arguments:
-k K, --keylength K Paillier Encryption key size (Supported values: 1024, 2048)
-r, --randomise Randomise database state
```
### Examples
- Run tests against a database of size N = 100, random database values, and a Paillier key length of 2048 bits:
```bash
$ python3 ./protocol.py -k 2048 -r 100
```
# Acknowledgements
- Run tests against a database of size N = 16000, and a Paillier key length of 2048 bits:
```bash
$ python3 ./protocol.py -k 2048 16000
```
- Run tests against a database of size N = 65000, random database values, and a Paillier key length of 2048 bits:
```bash
$ python3 ./protocol.py -k 2048 -r 65000
```
### Results
The program prints measurements to console, and also appends these measurements to a file named `UUHD-PPLS-Timing-Data.xlsx`.
```
+----+---------+---------------------+--------------+---------------------+
| N | DB Size | Paillier Key Length | First Update | Computation of Vcom |
+====+=========+=====================+==============+=====================+
| 10 | 100 | 2048 | 0.384 | 0.001 |
+----+---------+---------------------+--------------+---------------------+
+----------------+--------------+--------------+--------------+----------+
| 1 Entry Update | 1 Entry Read | 5 Entry Read | Registration | Purchase |
+================+==============+==============+==============+==========+
| 0.000 | 4.271 | 14.013 | 0.385 | 7.642 |
+----------------+--------------+--------------+--------------+----------+
+------------+-------------------+------------------+------------------+-------+
| Redemption | 1 Entry Profiling | 5 Entry | 10 Entry | Setup |
| | | Profiling | Profiling | |
+============+===================+==================+==================+=======+
| 5.806 | 6.245 | 33.575 | 61.165 | 0.366 |
+------------+-------------------+------------------+------------------+-------+
```
## License
This project is licensed under the GPLv3 license.
## Acknowledgements
This research was supported by the Luxembourg National Research Fund (FNR) CORE
project “Stateful Zero-Knowledge” (Project code: C17/11650748).
\ No newline at end of file
<!--
SPDX-FileCopyrightText: 2021 University of Luxembourg
SPDX-License-Identifier: GPL-3.0-or-later
SPDXVersion: SPDX-2.2
Authors:
Aditya Damodaran, aditya.damodaran@uni.lu
Alfredo Rial, alfredo.rial@uni.lu
-->
# Installation instructions (Ubuntu)
For best results, please use a fresh installation of Ubuntu 18.04 LTS (Bionic Beaver). We ran into a few issues whilst trying to install this project's dependencies on Ubuntu 20.04 LTS.
You could alternatively use our `install_ubuntu.sh` script located in the root directory of this repository, as it includes all of the following installation commands.
## Instructions
1. Start by installing all prerequisites for building charm:
```
$ apt-get update
$ apt-get install -y openssl gcc python3.6 python3-pip flex bison byacc git cmake libssl-dev
```
2. Clone our repository:
```
$ git clone https://gitlab.uni.lu/APSIA/uuhd-ppls.git
```
3. Download GMP, PBC, and Charm-Crypto:
```
$ cd uuhd-ppls
$ mkdir requirements && cd requirements
$ wget https://gmplib.org/download/gmp/gmp-6.2.1.tar.xz
$ wget https://crypto.stanford.edu/pbc/files/pbc-0.5.14.tar.gz
$ git clone "https://github.com/JHUISI/charm.git"
$ tar -xvf gmp-6.2.1.tar.xz
$ tar -xvf pbc-0.5.14.tar.gz
```
4. Build and install GMP and PBC:
```
$ cd gmp-6.2.1
$ ./configure
$ make
$ make install
$ cd ..
$ cd pbc-0.5.14
$ ./configure
$ make
$ make install
$ cd ..
```
5. Download and prepare Relic pairing libraries:
```
$ cd charm/charm/core/math/pairing/relic
$ wget https://github.com/relic-toolkit/relic/archive/relic-toolkit-0.5.0.tar.gz
$ tar -xvf relic-toolkit-0.5.0.tar.gz
$ mkdir relic-target && cd relic-target
$ ../buildRELIC.sh ../relic-relic-toolkit-0.5.0/
$ cd ../../../../../
```
6. Build and install Charm-Crypto:
```
$ ./configure.sh --enable-pairing-relic
$ make
$ make install
$ ldconfig -v
$ cd ../../
```
7. Install Openpyxl and Texttable:
```
$ python3.6 -m pip install -r requirements.txt
```
Things should now work as expected.
```
$ python3.6 ./protocol.py -h
```
#!/bin/bash
# SPDX-FileCopyrightText: 2021 University of Luxembourg
# SPDX-License-Identifier: CC0-1.0
# SPDXVersion: SPDX-2.2
apt-get update
apt-get install -y openssl gcc python3.6 python3-pip flex bison byacc git cmake libssl-dev
mkdir requirements && cd requirements
wget https://gmplib.org/download/gmp/gmp-6.2.1.tar.xz
wget https://crypto.stanford.edu/pbc/files/pbc-0.5.14.tar.gz
git clone "https://github.com/JHUISI/charm.git"
tar -xvf gmp-6.2.1.tar.xz
tar -xvf pbc-0.5.14.tar.gz
cd gmp-6.2.1
./configure
make
make install
cd ..
cd pbc-0.5.14
./configure
make
make install
cd ..
cd charm/charm/core/math/pairing/relic
wget https://github.com/relic-toolkit/relic/archive/relic-toolkit-0.5.0.tar.gz
tar -xvf relic-toolkit-0.5.0.tar.gz
mkdir relic-target && cd relic-target
../buildRELIC.sh ../relic-relic-toolkit-0.5.0/
cd ../../../../../
./configure.sh --enable-pairing-relic
make
make install
ldconfig -v
cd ../../
python3.6 -m pip install -r requirements.txt
python3.6 ./protocol.py -h
\ No newline at end of file
......@@ -61,7 +61,6 @@ parser.add_argument(
default=False,
help="Randomise database state",
)
# parser.add_argument('-w','--writetofile', action='writetofile', help='Paillier
db_size = 100
......@@ -73,7 +72,14 @@ if args.keylength == 1024 or args.keylength == 2048:
keylength = args.keylength
if args.size != None and int(args.size) > 10 and int(args.size) < 800000:
db_size = int(args.size)
print(
"Please enter a database size between 11 and 800000 (We need a database containing atleast 10 entries to test the profiling phase)."
)
exit()
# Note: The variable names used here may not reflect the actual names used in our paper because we have renamed them for PEP-8 compliance.
# However, dictionary keys and messages use names from the paper for brevity.
# Curve specification
......@@ -115,20 +121,22 @@ InstanceRecord = namedtuple("InstanceRecord", "ccom_i ccom_ri")
def draw_table(headings, data):
"""Prints an ASCII table to console."""
table = Texttable()
table.add_rows([headings, data])
print(table.draw())
# Sets up the CRS (public parameters for a Vector Commitment with a database of size 'nsize', and parameters for the Pedersen commitment scheme
def setup(nsize):
"""Sets up the CRS (public parameters for a Vector Commitment with a database of size 'nsize', and parameters for the Pedersen commitment scheme)."""
par = vector_commitment.setup(nsize)
par_c = pedersen_commitment.setup()
f_crs.set(par, par_c)
# Party U (Updater)
class Updater:
"""Party U (Updater)."""
def __init__(self):
pass
......@@ -139,22 +147,22 @@ class Updater:
l_par = []
# 'sid', 'par', 'par_c', 'sps_public_key', 'sps_secret_key'
# Checks if pseudonym p has been seen before
def is_unique_pseudonym(self, p):
"""Checks if pseudonym p has been seen before."""
for item in self.l_store:
if p == item["p"]:
return 0
return 1
# Returns the corresponding record in l_store for pseudonym = p
def get_record_for_pseudonym(self, p):
"""Returns the corresponding record in l_store for pseudonym = p."""
for item in self.l_store:
if p == item["p"]:
return item
return 0
# Responses from U to messages from other parties/functionalities
def message_in(self, m, p):
"""Handles messages from other parties/functionalities to U."""
if m["message"] == "com1":
if not self.is_unique_pseudonym(p):
print("Abort: (Updater) p is not unique. (com1)")
......@@ -191,8 +199,8 @@ class Updater:
)
exit()
# Responses from U to messages from other FZKs
def proof_in(self, instance, p):
"""Handles proof related messages from other FZKs to U."""
if not self.is_unique_pseudonym(p):
print("Abort: (Updater) p is not unique. (proof)")
exit()
......@@ -217,8 +225,8 @@ class Updater:
)
f_nym.reply({"message": "open", "com": instance["comd"]}, p)
# update interface
def update(self, sid, p, values):
"""Update interface."""
record = self.get_record_for_pseudonym(p)
if record == 0:
......@@ -315,6 +323,8 @@ updater_id = weak_reference.remember(updater)
class Reader:
"""Party Rk (Reader)."""
def __init__(self):
pass
......@@ -324,9 +334,8 @@ class Reader:
l_store = []
# {'sid','par','par_c','sps_public_key','vcom','x','r','com','s','open','sig'}
# Returns a list consisting of commitments to the positions in i_list and their corresponding values in the database x
def prepare_committed_record(self, i_list):
"""Returns a list consisting of commitments to the positions in i_list and their corresponding values in the database x."""
com_list = []
for i in i_list:
ccom_i = pedersen_commitment.commit(self.l_par[0]["par_c"], i)
......@@ -345,20 +354,21 @@ class Reader:
return com_list
def update_table(self, i_list):
"""Update x with values from i_list."""
for i in range(1, len(i_list)):
self.l_store[0]["x"][i] = self.l_store[0]["x"][i] + i_list[i]
def prepare_blinded_witness(self, instance, witness):
"""Blinds witnesses as required by the ZK compiler referenced in our paper (ref [10])."""
# Unpack signatures
sig = witness["sig"]
R, S, T = sig["R"], sig["S"], sig["T"]
sps_public_key = instance["sps_public_key"]
# Bases for blinding elements in g and gt(h from G@)
# Bases for blinding elements in g and gt
h = instance["par"]["group"].random(G1)
ht = instance["par"]["group"].random(G2)
#
[d1, d2, d5] = group.random(ZR, 3)
d3, d4 = witness["r2"], witness["open2"]
......@@ -369,7 +379,7 @@ class Reader:
comd, vcomd = instance["comd"], instance["vcomd"]
#
# We refer to instance and witness values related to database entries as subinstances and subwitnesses.
subinstance_list = instance["subinstance"]
subwitness_list = witness["subwitness"]
......@@ -395,7 +405,6 @@ class Reader:
sig_i = subwitness_record.sig_i
R_i, S_i, T_i = sig_i["R"], sig_i["S"], sig_i["T"]
#
[di_1, di_2, di_3, di_4, di_5] = group.random(ZR, 5)
# Blind sigs
......@@ -441,6 +450,7 @@ class Reader:
)
def test_witness_update(self, sid, p, i_list):
"""Measures witness update times for HD."""
if len(self.l_par) == 0 or len(self.l_store) == 0:
print(
"Abort: (Reader) Party hasn't been initialised. (HD witness update tests)"
......@@ -496,7 +506,7 @@ class Reader:
)
def read(self, sid, p, i_list):
"""Read interface."""
if len(self.l_par) == 0 or len(self.l_store) == 0:
print("Abort: (Reader) Party hasn't been initialised. (read)")
exit()
......@@ -570,13 +580,13 @@ class Reader:
blinded_instance, blinded_witness = self.prepare_blinded_witness(
instance_read, witness_read
)
# self.l_store[0]['ics'] = bics
f_zk.prove(
sid, blinded_witness, blinded_instance, p, self.obj_id, updater_id
)
return p
def first_read(self, sid, p):
"""Read interface, first execution."""
if len(self.l_par) == 0:
par, par_c = f_crs.get()
s_1 = par["group"].random(ZR)
......@@ -602,6 +612,7 @@ class Reader:
return p
def message_in(self, m, p):
"""Handles messages from other parties to Rk."""
if m["message"] == "setup":
vcom = vector_commitment.commit(self.l_par[0]["par"], m["x"], 0)
com_2 = pedersen_commitment.commit_0(
......@@ -712,17 +723,19 @@ class Reader:
)
# Init reader and set sid
reader_k = Reader()
sid = random.randint(0, 99999999)
setup(db_size)
# CRS setup
setup(db_size)
# Registration
db_list = []
empty_db_list = []
def register():
"""PPLS Registration interface."""
t_register_start = time.time()
p = reader_k.first_read(sid, 0)
for i in range(0, db_size):
......@@ -740,8 +753,8 @@ def register():
print("lp.register.end; sid = " + str(sid) + "; P = " + str(p))
# Purchase
def purchase(i, v, v_n):
"""PPLS Purchase interface."""
t_purchase_start = time.time()
p = random.randint(0, 99999999)
com_list = reader_k.prepare_committed_record([2, 3])
......@@ -755,9 +768,8 @@ def purchase(i, v, v_n):
print("lp.purchase.end; sid = " + str(sid) + "; P = " + str(p))
# Redemption
def redeem(points):
# TODO: Store positional coms
"""PPLS Redemption interface."""
t_redeem_start = time.time()
if points > reader_k.l_store[0]["x"][db_size]:
print(
......@@ -805,10 +817,7 @@ def redeem(points):
)
exit()
com_list = reader_k.prepare_committed_record(
# [db_size, reader_k.l_store[0]["x"][db_size]]
[db_size]
)
com_list = reader_k.prepare_committed_record([db_size])
p = reader_k.read(sid, p, com_list)
temp_db_list = list(empty_db_list)
......@@ -827,7 +836,7 @@ def redeem(points):
def profile(start, end, val):
"""PPLS Profile interface (Checks whether the sum of the values contained in the database between positions 'start' and 'end' is greater than 'val')."""
com_list = []
open_list = []
......@@ -865,6 +874,7 @@ def profile(start, end, val):
end,
reader_k.l_par[0]["par_c"],
group,
val,
)
for i in range(start, end + 1):
......
# SPDX-FileCopyrightText: 2021 University of Luxembourg
# SPDX-License-Identifier: CC0-1.0
# SPDXVersion: SPDX-2.2
openpyxl
texttable
openpyxl==3.0.6
texttable==1.6.3
......@@ -14,7 +14,6 @@ from charm.toolbox.pairinggroup import ZR, G1, G2, pair
from charm.toolbox.integergroup import RSAGroup
from charm.core.math.integer import integer
from uuhd.jsonobjects import dict_from_class
from uuhd.primitives import PaillierEncryption, SHA256, DSA, IntegerCommitment
from uuhd.sigmaprotocol import (
......@@ -27,6 +26,8 @@ from uuhd.sigmaprotocol import (
class WeakReference:
"""Stores and returns identifiers for parties in the protocol."""
_id2obj_dict = weakref.WeakValueDictionary()
def __init__(self):
......@@ -42,6 +43,8 @@ class WeakReference:
class FNYM:
"""Ideal Functionality for a pseudonymous channel."""
txn = []
def __init__(self, weak_reference):
......@@ -67,6 +70,8 @@ class FNYM:
class FZK:
"""Ideal Functionality for Zero Knowledge."""
txn = []
def __init__(self, fnym, keylength):
......@@ -256,6 +261,8 @@ class FZK:
class FZK_RD:
"""Ideal functionality for Zero Knowledge (reading)."""
l_store = []
def __init__(self, f_nym, keylength):
......@@ -400,6 +407,8 @@ class FZK_RD:
class FZK_PR3:
"""Ideal functionality for Zero Knowledge (profiling)."""
l_store = []
def __init__(self, f_nym, keylength):
......@@ -407,7 +416,16 @@ class FZK_PR3:
self.keylength = keylength
def prove(
self, sid, witness_pr, instance_pr, id, start, end, par_c, group
self,
sid,
witness_pr,
instance_pr,
id,
start,
end,
par_c,
group,
threshold,
):
ped_g = par_c["g"]
......@@ -518,11 +536,9 @@ class FZK_PR3:
"penco": paillier_ciphertext_open_v,
}
)
paillier_ciphertext_random_open_v = (
self.paillier_encryption.encrypt(
self.public_key,
integer(SHA256(bytes(str(random_opening_v), "utf-8"))),
)
paillier_ciphertext_random_open_v = self.paillier_encryption.encrypt(
self.public_key,
integer(SHA256(bytes(str(random_opening_v), "utf-8"))),
)
paillier_ciphertext_random_v = self.paillier_encryption.encrypt(
self.public_key, integer(SHA256(bytes(str(random_v), "utf-8")))
......@@ -705,13 +721,18 @@ class FZK_PR3:
ped_h,
group,
)
if result < threshold:
f_result = 0
else:
f_result = 1
self.f_nym.insert(sid, id, id)
self.l_store.append({"sid": sid, "p": id, "Tk": id})
return instance_pr, result
return instance_pr, f_result
class FREG:
"""Ideal functionality for key registration."""
def __init__(self):
self.key = ""
......@@ -723,6 +744,8 @@ class FREG:
class FCRS:
"""Ideal functionality for Common Reference String generation."""
def __init__(self):
self.par = ""
self.par_c = ""
......
......@@ -125,18 +125,6 @@ class ZKWitness:
class SubWitnessRecord:
# def __init__(self):
# self.index=0
# self.i=0
# self.vr=0
# self.copen_i=0
# self.copen_ri=0
# self.di_1 = 0
# self.di_2 = 0
# self.di_3 = 0
# self.di_4 = 0
# self.di_5 = 0
def __init__(
self,
index=0,
......
......@@ -12,6 +12,7 @@ import sys
def get_real_size(obj, seen=None):
"""Needed because we use dicts and lists to store the CRS."""
size = sys.getsizeof(obj)
if seen is None:
seen = set()
......
......@@ -22,17 +22,13 @@ from charm.toolbox.integergroup import (
RSAGroup,
IntegerGroupQ,
)
from charm.core.math import integer as charminteger
from charm.toolbox.integergroup import lcm, integer
from uuhd.measurement_util import get_real_size
# Paillier Enc
from charm.schemes.pkenc import pkenc_paillier99