Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend Operators #27

Merged
merged 29 commits into from
Aug 7, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
91a8560
Add .zkif files to gitignore
gxavier38 Jun 17, 2021
38454e1
Move fixedpoint back to its own file
gxavier38 Jun 10, 2021
e9d5620
Add LinComb operators
gxavier38 Jun 21, 2021
d0d85d2
Add fixedpoint operators
gxavier38 Jun 21, 2021
cb8c489
Add LinCombBool and add bitwise ops to LinComb
gxavier38 Jun 21, 2021
73212f4
Add tests
gxavier38 Jun 21, 2021
6d2fb8a
Add CircleCI
gxavier38 Jun 21, 2021
c75f476
Rename ggh_hash
gxavier38 Jun 16, 2021
9b7e12f
Add Poseidon hash
gxavier38 Jun 17, 2021
8d7e4bb
Fix benchmark comment
gxavier38 Jun 21, 2021
2cb743e
Add scaling back to constructor in LinCombFxp
gxavier38 Jun 22, 2021
47edd87
Fix Poseidon dropping LinCombFxps and LinCombBools
gxavier38 Jun 22, 2021
a5d7534
Make fixedpoint truediv lose resolution rather than throw an error
gxavier38 Jun 22, 2021
00a4456
Fix truediv constraint with ignore_errors
gxavier38 Jun 29, 2021
87a9030
Make divmod return PrivVal instead of ConstVal
gxavier38 Jun 29, 2021
7c93f8c
Add missing division constraint and update constraint docs
gxavier38 Jun 29, 2021
ea49bc2
Remove unneeded scaling in fixedpoint multiplication
gxavier38 Jul 6, 2021
ebe33c0
Make LinComb rshift use to_bits
gxavier38 Jul 6, 2021
ab6b8c3
Add more bench tests for LinCombFxp operations
gxavier38 Jul 6, 2021
277e8ca
Improve LinCombBool __pow__
gxavier38 Jul 12, 2021
a63f950
Remove unneeded constraint from assert_range
gxavier38 Jul 12, 2021
63b1d37
Simplify check_nonzero
gxavier38 Jul 12, 2021
b0bbeb0
Improve check_zero performance
gxavier38 Jul 12, 2021
2dfc9a1
Allow unlimited exponents in pow
gxavier38 Jul 12, 2021
13d2bd8
Allow boolean operations with non-bool/non-int types
gxavier38 Jul 12, 2021
2b4ce09
Test LinCombBool operators with non-LinComb types
gxavier38 Jul 12, 2021
347c89b
Fix scaling removal in LinCombFxp.__mul__
gxavier38 Jul 12, 2021
9063e2c
Remove unneeded comment
gxavier38 Jul 12, 2021
b3c8971
Increase nobackend modulus to pass tests
gxavier38 Jul 15, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 44 additions & 11 deletions pysnark/boolean.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,23 @@

"""
Support for Boolean values
Costs 0 constraints to convert a LinComb to a LinCombBool
Costs 1 constraint to convert a LinComb to a LinCombBool
"""

class LinCombBool:
def __init__(self, lc):
def __init__(self, lc, constrain=True):
"""
Constructs a LinCombBool
Costs 2 constraints
Costs 1 constraint
"""
if not isinstance(lc, LinComb):
raise RuntimeError("Wrong type for LinCombBool")
if not LinCombBool.is_boolean_value(lc.value):
raise ValueError("LinCombBool can only take Boolean values")

# Add boolean constraint to circuit
add_constraint(lc, 1 - lc, LinComb.ZERO)
if constrain:
add_constraint(lc, 1 - lc, LinComb.ZERO)

self.lc = lc

Expand Down Expand Up @@ -59,12 +60,28 @@ def _ensurebool(cls, val):
raise RuntimeError("Wrong type for LinCombBool")

def __add__(self, other):
"""
Performs an arithmetic addition with an integer or a LinComb
Returns a LinComb
Costs 0 constraints
"""
return self.lc + other

def __sub__(self, other):
"""
Performs an arithmetic subtraction by an integer or a LinComb
Returns a LinComb
Costs 0 constraints
"""
return self.lc - other

def __mul__(self, other):
"""
Performs an arithmetic multiplication with an integer or a LinComb
Returns a LinComb
Costs 0 constraints to multiply with an integer
Costs 1 constraint to multiply with a LinComb
"""
return self.lc * (other)

def __truediv__(self, other):
Expand All @@ -83,6 +100,11 @@ def __divmod__(self, divisor):
__rmul__ = __mul__

def __rsub__(self, other):
"""
Performs an arithmetic subtraction by an integer or a LinComb
Returns a LinComb
Costs 0 constraints
"""
return other + (-self.lc)

def __rtruediv__(self, other):
Expand All @@ -91,12 +113,18 @@ def __rtruediv__(self, other):
def __neg__(self):
"""
Performs an arithmetic negation
Returns a LinComb
Costs 0 constraints
"""
return -self.lc

def __invert__(self):
return ConstVal(1) - self.lc
"""
Performs a logical not
Returns a LinCombBool
Costs 1 constraint
gxavier38 marked this conversation as resolved.
Show resolved Hide resolved
"""
return LinCombBool(1 - self.lc, False)

def __and__(self, other):
"""
Expand All @@ -105,9 +133,9 @@ def __and__(self, other):
Costs 1 constraint to AND with a LinComb
"""
if isinstance(other, int):
return LinCombBool(self.lc * other)
return LinCombBool(self.lc * other, False)
other = LinCombBool._ensurebool(other)
return LinCombBool(self.lc * other.lc)
return LinCombBool(self.lc * other.lc, False)

def __xor__(self, other):
"""
Expand All @@ -116,9 +144,9 @@ def __xor__(self, other):
Costs 1 constraint to XOR with a LinComb
"""
if isinstance(other, int):
return LinCombBool(self + other - 2 * self * other)
return LinCombBool(self + other - 2 * self * other, False)
other = LinCombBool._ensurebool(other)
return LinCombBool(self.lc + other.lc - 2 * self.lc * other.lc)
return LinCombBool(self.lc + other.lc - 2 * self.lc * other.lc, False)

def __or__(self, other):
"""
Expand All @@ -127,9 +155,9 @@ def __or__(self, other):
Costs 1 constraint to OR with a LinComb
"""
if isinstance(other, int):
return LinCombBool(self.lc * other)
return LinCombBool(self.lc * other, False)
other = LinCombBool._ensurebool(other)
return LinCombBool(self.lc + other.lc - self.lc * other.lc)
return LinCombBool(self.lc + other.lc - self.lc * other.lc, False)

def __eq__(self, other): return self.lc == self._ensurebool(other).lc
def __ne__(self, other): return self.lc != self._ensurebool(other).lc
Expand All @@ -150,6 +178,11 @@ def __bool__(self):
Instead of if statements, use if_then_else from pysnark.branching")

def __pow__(self, other, mod=None):
"""
Raises a LinCombBool to the power of an integer, LinComb, or LinCombBool
Returns a LinComb
Costs 36 constraints
"""
gxavier38 marked this conversation as resolved.
Show resolved Hide resolved
return self.lc.__pow__(other, mod)

def __lshift__(self, other):
Expand Down
36 changes: 32 additions & 4 deletions pysnark/fixedpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ def __add__(self, other):
__radd__ = __add__

def __sub__(self, other):
"""
Subtracts a LinCombFxp by an integer, float, LinComb, or another LinCombFxp
Costs 0 constraints
"""
return self + (-other)

def __rsub__(self, other):
Expand Down Expand Up @@ -157,18 +161,33 @@ def __truediv__(self, other):
return NotImplemented

def __floordiv__(self, other):
"""
Divides LinCombFxp by an integer, float, LinComb, or LinCombFxp using floor division
Costs 0 constraints to divide modulo an integer or float
Costs 2 * bitlength + 4 constraints to divide modulo a LinComb or LinCombFxp
"""
res = self.__divmod__(other)
if res is NotImplemented:
return NotImplemented
return res[0]

def __mod__(self, other):
"""
Returns the remainder of a LinCombFxp divided with an integer, float, LinComb, or LinCombFxp
Costs 0 constraints to divide modulo an integer or float
Costs 2 * bitlength + 4 constraints to divide modulo a LinComb or LinCombFxp
"""
res = self.__divmod__(other)
if res is NotImplemented:
return NotImplemented
return res[1]

def __divmod__(self, divisor):
"""
Divides a LinCombFxp with an integer, float, LinComb, or LinCombFxp and returns the quotient and the remainder
Costs 0 constraints to divide with an integer or a float
Costs 2 * bitlength + 4 constraints to divide with a LinComb or a LinCombFxp
"""
if isinstance(divisor, int) or isinstance(divisor, float) or isinstance(divisor, LinComb):
divisor = LinCombFxp.add_scaling(divisor)
res = self.lc.__divmod__(divisor)
Expand Down Expand Up @@ -215,13 +234,17 @@ def assert_ge(self, other, err=None): self.lc.assert_ge(self._ensurefxp(other).l
def __bool__(self): return bool(self.lc)

def __pow__(self, other, mod=None):
""" Exponentiation with public integral power p>=0 """
"""
Raises a LinCombFxp to the power of an integer
Costs n-1 constraints to raise to the power n
The exponent n must be <= 31 to prevent Python crashing
"""
if mod!=None: raise ValueError("cannot provide modulus")
if not is_base_value(other): return NotImplemented
if not isinstance(other, int): return NotImplemented
if other<0: raise ValueError("exponent cannot be negative", other)
if other==0: return LinCombFxp(add_scaling(LinComb.ONE))
if other==0: return LinCombFxp(LinCombFxp.add_scaling(LinComb.ONE))
if other==1: return self
return self*pow(self, other-1)
return self * self ** (other - 1)

def __lshift__(self, other): return LinCombFxp(self.lc<<other)

Expand All @@ -239,8 +262,13 @@ def __int__(self): raise NotImplementedError("Should not run int() on LinComb")
def check_positive(self): return self.lc.check_positive()
def assert_positive(self): self.lc.assert_positive()
def check_zero(self): return self.lc.check_zero()
def check_nonzero(self): return self.lc.check_nonzero()
def assert_zero(self): self.lc.assert_zero()
def assert_nonzero(self): self.lc.assert_nonzero()
def assert_range(self, minrange, maxrange):
minrange = LinCombFxp._ensurefxp(minrange)
maxrange = LinCombFxp._ensurefxp(maxrange)
self.lc.assert_range(minrange.lc, maxrange.lc)

def PubValFxp(val, doconvert=True):
if doconvert:
Expand Down
55 changes: 36 additions & 19 deletions pysnark/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,11 @@ def _if_guard(*args, **kwargs):

igprint = if_guard(print)

""" Add constraint v*w=y to the constraint system, and update running computation hash. """
def add_constraint(v,w,y,check=True):
"""
Add the constraint v * w = y to the constraint system
Updates running computation hash
"""
if not guard is None:
dummy = PrivVal(v.value*w.value-y.value)
add_constraint_unsafe(v,w,y+dummy)
Expand Down Expand Up @@ -343,7 +346,7 @@ def __mod__(self, other):
"""
Returns the remainder of a LinComb divided with an integer or another LinComb
Costs 0 constraints to divide modulo an integer
Costs 1 constraint to divide modulo a LinComb
Costs 2 * bitlength + 4 constraints to divide modulo a LinComb
"""
res = self.__divmod__(other)
if res is NotImplemented:
Expand All @@ -354,7 +357,7 @@ def __divmod__(self, divisor):
"""
Divides a LinComb with an integer or another LinComb and returns the quotient and the remainder
Costs 0 constraints to divide with an integer
Costs 3 constraints to divide with a LinComb
Costs 2 * bitlength + 4 constraints to divide with a LinComb
"""
if isinstance(divisor, int):
if divisor == 0:
Expand Down Expand Up @@ -384,8 +387,9 @@ def __divmod__(self, divisor):
def __pow__(self, other, mod=None):
"""
Raises a LinComb to the power of an integer or a LinComb
Costs 0 constraints to raise to the power of an integer
Costs ? constraints to raise to the power of a LinComb
Costs n constraints to raise to an integer power n
Costs 41 constraints to raise to the power of a LinComb,
The exponent n must be <= 31 to prevent Python crashing
"""
if mod != None:
raise ValueError("Cannot provide modulus")
Expand Down Expand Up @@ -429,7 +433,8 @@ def __lshift__(self, other):
"""
Shifts a LinComb bitwise to the left
Costs 0 constraints to shift by an integer number of bits
Costs ? constraints to raise to the power of a LinComb
Costs 42 constraints to shift by a LinComb number of bits,
given 41 operations to raise a LinComb to the power of a LinComb
"""
if isinstance(other, int):
res = self * (1 << other)
Expand All @@ -447,7 +452,7 @@ def __rshift__(self, other):
"""
Shifts a LinComb bitwise to the right
Costs 0 constraints to shift by an integer number of bits
Costs ? constraints to raise to the power of a LinComb
Costs 2 * bitlength + 45 constraints shift by a LinComb number of bits
"""
if isinstance(other, int):
res = self // (1 << other)
Expand All @@ -462,7 +467,11 @@ def __rshift__(self, other):
return NotImplemented

def __and__(self, other):
""" Bitwise and &. Cost: 1 constraint """
"""
Computes the bitwise and of a LinComb with an integer or a LinComb
Costs 0 operations to and with an integer
Costs 3 * bitlength + 3 operations to and with a LinComb
"""
if isinstance(other, int):
return PrivVal(self.value & other)
if isinstance(other, LinComb):
Expand All @@ -473,7 +482,11 @@ def __and__(self, other):
return NotImplemented

def __xor__(self, other):
"""Bitwise exclusive-or ^. Cost: 1 constraint """
"""
Computes the bitwise xor of a LinComb with an integer or a LinComb
Costs 0 operations to xor with an integer
Costs 3 * bitlength + 3 operations to xor with a LinComb
"""
if isinstance(other, int):
return PrivVal(self.value ^ other)
if isinstance(other, LinComb):
Expand All @@ -484,7 +497,11 @@ def __xor__(self, other):
return NotImplemented

def __or__(self, other):
"""Bitwise or |. Cost: 1 constraint """
"""
Computes the bitwise or of a LinComb with an integer or a LinComb
Costs 0 operations to or with an integer
Costs 3 * bitlength + 3 operations to or with a LinComb
"""
if isinstance(other, int):
return PrivVal(self.value | other)
if isinstance(other, LinComb):
Expand Down Expand Up @@ -536,8 +553,8 @@ def __abs__(self):
return if_then_else(self>=0, self, -self)

def __invert__(self):
# we do not want to do ~1=0 and ~0=1 since this is also not true for native ints
raise NotImplementedError("Operator ~ not supported. For binary not, use 1-x")
inverted_bits = [~x for x in self.to_bits()]
return LinComb.from_bits(inverted_bits)

def __complex__(self): return NotImplemented
def __int__(self): raise NotImplementedError("Should not run int() on LinComb")
Expand All @@ -555,7 +572,7 @@ def to_bits(self, bits=None):
"""
Splits an integer LinComb into an bitlength-length array of LinCombBool bits
Raises AssertionError if LinComb cannot be represented with bitlength bits
Costs 2 * bitlength + 1 constraints to split a LinComb into bits
Costs bitlength + 1 constraints to split a LinComb into bits
"""
from pysnark.boolean import PrivValBool

Expand Down Expand Up @@ -590,7 +607,7 @@ def from_bits(cls, bits):
def check_positive(self, bits=None):
"""
Checks if a LinComb is a positive bitlength-bit integer
Costs 2 * bitlength + 3 constraints
Costs bitlength + 2 constraints
"""
from pysnark.boolean import PrivValBool

Expand All @@ -617,7 +634,7 @@ def check_positive(self, bits=None):
def assert_positive(self, bits=None, err=None):
"""
Ensures a LinComb is a positive bitlength-bit integer
Costs 2 * bitlength + 1 constraints
Costs bitlength + 1 constraints
"""

if bits is None:
Expand All @@ -632,7 +649,7 @@ def assert_positive(self, bits=None, err=None):
def check_zero(self):
"""
Checks whether a LinComb is zero
Costs 4 constraints
Costs 3 constraints
"""
from pysnark.boolean import LinCombBool

Expand All @@ -649,7 +666,7 @@ def check_zero(self):
def check_nonzero(self):
gxavier38 marked this conversation as resolved.
Show resolved Hide resolved
"""
Checks whether a LinComb is nonzero
Costs 4 constraints
Costs 3 constraints
"""
from pysnark.boolean import LinCombBool

Expand Down Expand Up @@ -689,14 +706,14 @@ def assert_nonzero(self, err=None):
def assert_range(self, rangemin, rangemax, err=None):
"""
Ensures a LinComb is within the range [rangemin, rangemax]
Costs 4 * bitlength + 3 constraints
Costs 2 * bitlength + 3 constraints
"""
rangemin = LinComb._ensurelc(rangemin)
rangemax = LinComb._ensurelc(rangemax)

if not ignore_errors():
if self.value < rangemin.value or self.value >= rangemax.value:
raise AssertionError(err if err is not None else str(self.value) + " is not in the range [" + str(rangemin.value) + "," + str(rangemax.value) + "]")
raise AssertionError(err if err is not None else str(self.value) + " is not in the range [" + str(rangemin.value) + "," + str(rangemax.value) + ")")

# Use bounds check gadget from Bulletproofs
a = self - rangemin
Expand Down
Loading