Skip to content

hpicrypto/poksho

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

55 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Proof of Knowledge, Stateful Hash Object

A python implementation for Non-Interactive Zero-Knowledge Proofs (NIZK) of Arbitrary Linear Relations and Stateful Hash Objects.

Disclaimer

This module was developed as part of the 2022/23 Current Topics in Group Messaging seminar at the Hasso-Plattner-Institute (HPI) trying to reproduce the Signal Private Group system described by the paper "The Signal Private Group System and Anonymous Credentials Supporting Efficient Verifiable Encryption" by Chase et al. Therefore, it is supposed to solely fulfill academic purposes.

THE AUTHORS DO NOT ASSUME RESPONSIBILITY FOR THE CORRECTNESS OF THE PERFORMED CRYPTOGRAPHIC OPERATIONS. THERE WAS NO REVIEW PERFORMED BY AN EXPERT. DO NOT USE THIS PROJECT IN A PRODUCTION ENVIRONMENT.

Installation

This project can be installed in multiple ways:

requirements.txt: Add git+https://github.com/hpicrypto/poksho.git@0.1.0#egg=poksho.

pip(env): Run pip(env) install git+https://github.com/hpicrypto/poksho.git@0.1.0#egg=poksho.

poetry: Run poetry add git+https://github.com/hpicrypto/poksho.git@0.1.0.

Usage

NIZK

from poksho.equation import Element, Exponent, make_elements, make_exponents
from poksho.statement import Statement, Equation

# NIZKs can be performed on any group implementing the 
# poksho.group.group.GroupType interface.
from poksho.group.ristretto import Group as RistrettoGroup
group = RistrettoGroup

# Create statement variables as symbolic group elements (without a value for now).
# The variable names are not really used for anything, only for printing expressions.
U = Element("U")
V = Element("V")
W = Element("W")

# Helper for creating multiple variables. There is also a make_exponents helper that
# can be used for symbolic exponent variables.
G, H, I = make_elements("G", "H", "I")

# Create a symbolic exponent.
x = Exponent("x")
# Bind a value to the already created exponent variable.
# Note that in a real use case, the "real" values for the variables need to be input here.
# The random values in this example are just for demonstration purposes.
x.bind(group.exponent_cls.random())

# Value can also be bound at time of creation.
y = Exponent("y", group.exponent_cls.random())

# Also bind the other values.
G.bind(group.element_cls.random())
H.bind(group.element_cls.random())
I.bind(group.element_cls.random())

# Create a statement. This involves specifying the group implementation to use.
# Equations that should be contained in the statement can be composed
# from symbolic constants.
statement = Statement(group, Equation(U, G**x * H**y), Equation(V, I**x))

# Equations can also be added to a statement later.
statement.add_equation(Equation(W, G**y))

# Values can also be bound after they were used to describe a statement.
U.bind((G**x * H**y).value)
V.bind((I**x).value)
W.bind((G**y).value)

# Statement.prove() generates a NIZK proof for the statement using the
# currently bound values.
proof = statement.prove()

# The same statement can be used to verify the proof.
assert statement.verify(proof)

# We can also create the same statement from completely new variables.
G_ = Element("G_", G.value)
H_ = Element("H_", H.value)
I_ = Element("I_", I.value)
U_ = Element("U_", U.value)
V_ = Element("V_", V.value)
W_ = Element("W_", W.value)
# For verification, secrets (the exponents) don't have to have a value bound to them.
x_, y_ = make_exponents("x_", "y_")
statement_ = Statement(group,
    Equation(U_, G_**x_ * H_**y_),
    Equation(V_, I_**x_),
    Equation(W_, G_**y_)
)

# Verify the proof for a statement when the secret values are not bound.
assert statement_.verify(proof) is True

SHO

from poksho.sho import SHO

# Create a SHO object
sho = SHO(
    customization_label=b'label',
    hash_func='sha256', # refer to sho.ALLOWED_HASH_FUNCTIONS
    use_hmac=True
)

# Absorbing randomness (like a sponge).
# Generating this randomness is out of scope for this module.
sho.absorb(b"abc")
sho.absorb_and_ratchet(b"def")
sho.absorb(b"ghi")

# The ratchet step must always be performed after we have absorbed values 
# and before extracting values from the SHO
sho.ratchet()

# Clone the sho if we want to perform operations on it but still keep the current SHO
# state for another place in the code
sho2 = sho.clone()

# Deterministically "squeeze out" random output based on the random input
# we provided earlier.
# We can specify how long the output we receive should be
output_length = 16
assert sho.squeeze_and_ratchet(output_length) == sho2.squeeze_and_ratchet(output_length)
output = sho2.squeeze_and_ratchet(output_length)
# output = sho2.squeeze() # --> Not supported by our implementation, raises NotImplementedError 

# If we forget to ratchet after absorb...
sho.absorb(b'jkl')
# sho.squeeze_and_ratchet(output_length) # --> raises ValueError here
sho.ratchet()
sho.squeeze_and_ratchet(output_length)

Testing

PYTHONPATH+=":$PWD" pytest tests/