Skip to content

refactor: refactor class SecretKey & PublicKey #2

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,42 @@ b'9\xe8%\xdf\xbb\xa2\x06TcH\xa6\x93\xb9q>\xe2\xec\x99\xf7\xc4\xe5>\xe8\x1dz\x9fX
True
```

Another example for real case:

```python
# file: sender.py
from falcon.falcon import SecretKey

N = 512
# f & g polynomial can be static
# len(f) == len(g) == N
f = [...(512 items)...]
g = [...(512 items)...]

# SFPK: Sender Falcon Public Key
# SFSK: Sender Falcon Secret Key
SFSK = SecretKey(N, f, g)
SFPK = SFSK.h # SFPK should be sent to receiver

sig = SFSK.sign(b'byte_message')

# after sign, send `sig` & `byte_message` to receiver.py
```

```python
# file: receiver.py
from falcon.falcon import PublicKey
N = 512

# received `sig` & `byte_message`, need to valid the message

# SFPK: Sender Falcon Public Key
SFPK = PublicKey(N, SFPK)
SFPK.verify(b'byte_message', sig)
```



Upon first use, consider running `make test` to make sure that the code runs properly on your machine. You should obtain (the test battery for `n = 1024` may take a few minutes):

```
Expand Down
124 changes: 61 additions & 63 deletions falcon.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from fft import fft, ifft, sub, neg, add_fft, mul_fft
from ntt import sub_zq, mul_zq, div_zq
from ffsampling import gram, ffldl_fft, ffsampling_fft
from ntrugen import ntru_gen
from ntrugen import ntru_solve
from encoding import compress, decompress
# https://pycryptodome.readthedocs.io/en/latest/src/hash/shake256.html
from Crypto.Hash import SHAKE256
Expand Down Expand Up @@ -134,6 +134,7 @@


def print_tree(tree, pref=""):
# print(f"print_tree tree : {tree}")
"""
Display a LDL tree in a readable form.

Expand Down Expand Up @@ -165,11 +166,9 @@ def print_tree(tree, pref=""):
def normalize_tree(tree, sigma):
"""
Normalize leaves of a LDL tree (from values ||b_i||**2 to sigma/||b_i||).

Args:
T: a LDL tree
sigma: a standard deviation

Format: coefficient or fft
"""
if len(tree) == 3:
Expand All @@ -180,61 +179,18 @@ def normalize_tree(tree, sigma):
tree[1] = 0


class PublicKey:
"""
This class contains methods for performing public key operations in Falcon.
"""

def __init__(self, sk):
"""Initialize a public key."""
self.n = sk.n
self.h = sk.h
self.hash_to_point = sk.hash_to_point
self.signature_bound = sk.signature_bound
self.verify = sk.verify

def __repr__(self):
"""Print the object in readable form."""
rep = "Public for n = {n}:\n\n".format(n=self.n)
rep += "h = {h}\n".format(h=self.h)
return rep


class SecretKey:
"""
This class contains methods for performing
secret key operations (and also public key operations) in Falcon.

One can:
- initialize a secret key for:
- n = 128, 256, 512, 1024,
- phi = x ** n + 1,
- q = 12 * 1024 + 1
- find a preimage t of a point c (both in ( Z[x] mod (Phi,q) )**2 ) such that t*B0 = c
- hash a message to a point of Z[x] mod (Phi,q)
- sign a message
- verify the signature of a message
"""

def __init__(self, n, polys=None):
"""Initialize a secret key."""
# Public parameters
def __init__(self, n, f, g, polys=None):
self.n = n
self.sigma = Params[n]["sigma"]
self.sigmin = Params[n]["sigmin"]
self.signature_bound = Params[n]["sig_bound"]
self.sig_bytelen = Params[n]["sig_bytelen"]

# Compute NTRU polynomials f, g, F, G verifying fG - gF = q mod Phi
if polys is None:
self.f, self.g, self.F, self.G = ntru_gen(n)
else:
[f, g, F, G] = polys
assert all((len(poly) == n) for poly in [f, g, F, G])
self.f = f[:]
self.g = g[:]
self.F = F[:]
self.G = G[:]
self.f = f
self.g = g

self.F, self.G = self.genFG(f, g)

# From f, g, F, G, compute the basis B0 of a NTRU lattice
# as well as its Gram matrix and their fft's.
Expand All @@ -251,17 +207,22 @@ def __init__(self, n, polys=None):
# The public key is a polynomial such that h*f = g mod (Phi,q)
self.h = div_zq(self.g, self.f)

def __repr__(self, verbose=False):
"""Print the object in readable form."""
rep = "Private key for n = {n}:\n\n".format(n=self.n)
rep += "f = {f}\n".format(f=self.f)
rep += "g = {g}\n".format(g=self.g)
rep += "F = {F}\n".format(F=self.F)
rep += "G = {G}\n".format(G=self.G)
if verbose:
rep += "\nFFT tree\n"
rep += print_tree(self.T_fft, pref="")
return rep

def genFG(self, f, g):
"""
Generate F, G by `ntru_solve` function declare in ntrugen,
for static f, g poly parameters
Args:
f: a list with length N for Secret Key polynomial
g: a list with length N for Secret Key polynomial
"""
if len(f) != self.n or len(g) != self.n:
raise ValueError("The length of f, g should be same with N")

F, G = ntru_solve(f, g)
F = [int(coef) for coef in F]
G = [int(coef) for coef in G]
return F, G

def hash_to_point(self, message, salt):
"""
Expand Down Expand Up @@ -360,6 +321,43 @@ def sign(self, message, randombytes=urandom):
if (enc_s is not False):
return header + salt + enc_s


class PublicKey:
def __init__(self, n, FPK):
self.n = n
self.h = FPK
self.signature_bound = Params[n]["sig_bound"]
self.sig_bytelen = Params[n]["sig_bytelen"]

def hash_to_point(self, message, salt):
"""
Hash a message to a point in Z[x] mod(Phi, q).
Inspired by the Parse function from NewHope.
"""
n = self.n
if q > (1 << 16):
raise ValueError("The modulus is too large")

k = (1 << 16) // q
# Create a SHAKE object and hash the salt and message.
shake = SHAKE256.new()
shake.update(salt)
shake.update(message)
# Output pseudorandom bytes and map them to coefficients.
hashed = [0 for i in range(n)]
i = 0
j = 0
while i < n:
# Takes 2 bytes, transform them in a 16 bits integer
twobytes = shake.read(2)
elt = (twobytes[0] << 8) + twobytes[1] # This breaks in Python 2.x
# Implicit rejection sampling
if elt < k * q:
hashed[i] = elt % q
i += 1
j += 1
return hashed

def verify(self, message, signature):
"""
Verify a signature.
Expand Down Expand Up @@ -387,4 +385,4 @@ def verify(self, message, signature):
return False

# If all checks are passed, accept
return True
return True
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pycryptodome==3.11.0