Esempio n. 1
0
    def __init__(self, n):
        """Initialize a secret key."""
        # Public parameters
        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
        self.f, self.g, self.F, self.G = ntru_gen(n)

        # From f, g, F, G, compute the basis B0 of a NTRU lattice
        # as well as its Gram matrix and their fft's.
        B0 = [[self.g, neg(self.f)], [self.G, neg(self.F)]]
        G0 = gram(B0)
        self.B0_fft = [[fft(elt) for elt in row] for row in B0]
        G0_fft = [[fft(elt) for elt in row] for row in G0]

        self.T_fft = ffldl_fft(G0_fft)

        # Normalize Falcon tree
        normalize_tree(self.T_fft, self.sigma)

        # The public key is a polynomial such that h*f = g mod (Phi,q)
        self.h = div_zq(self.g, self.f)
Esempio n. 2
0
    def __init__(self, n):
        """Initialize a secret key."""
        """Public parameters"""
        self.n = n
        self.q = q
        self.hash_function = hashlib.shake_256
        """Private key part 1: NTRU polynomials f, g, F, G verifying fG - gF = q mod Phi"""
        self.f, self.g, self.F, self.G = ntru_gen(n)
        """Private key part 2: fft's of f, g, F, G"""
        self.f_fft = fft(self.f)
        self.g_fft = fft(self.g)
        self.F_fft = fft(self.F)
        self.G_fft = fft(self.G)
        """Private key part 3: from f, g, F, G, compute the basis B0 of a NTRU lattice as well as its Gram matrix and their fft's"""
        self.B0 = [[self.g, neg(self.f)], [self.G, neg(self.F)]]
        self.G0 = gram(self.B0)
        self.B0_fft = [[fft(elt) for elt in row] for row in self.B0]
        self.G0_fft = [[fft(elt) for elt in row] for row in self.G0]

        # self.T = ffldl(self.G0)
        self.T_fft = ffldl_fft(self.G0_fft)
        """Private key part 4: compute sigma and signature bound."""
        sq_gs_norm = gs_norm(self.f, self.g, q)
        self.sigma = 1.28 * sqrt(sq_gs_norm)
        self.signature_bound = 2 * self.n * (self.sigma**2)
        """Private key part 5: set leaves of tree to be the standard deviations."""
        normalize_tree(self.T_fft, self.sigma)
        """Public key: h such that h*f = g mod (Phi,q)"""
        self.h = div_zq(self.g, self.f)
Esempio n. 3
0
def verify_1(n, m, sig, uid, pk, MPK):
    print("sig: {}".format(sig))
    # parsing sig
    e = sig[0]
    z = sig[1]
    z1, z2, z1a, z2a = z[0], z[1], z[2], z[3]

    # restore e
    vev1 = add_zq(add_zq(z1, mul_zq(z2, MPK)), mul_zq(uid, neg(e)))
    vec2 = add_zq(add_zq(z1a, mul_zq(z2a, MPK)), mul_zq(pk, neg(e)))
    e_check = H1(n, vev1, vec2, m)
    #print('e from sig: {}'.format(e))
    #print('e resttored: {}'.format(e_check))
    return e == e_check
Esempio n. 4
0
    def sample_preimage(self, point):
        """
        Sample a short vector s such that s[0] + s[1] * h = point.
        """
        [[a, b], [c, d]] = self.B0_fft

        # We compute a vector t_fft such that:
        #     (fft(point), fft(0)) * B0_fft = t_fft
        # Because fft(0) = 0 and the inverse of B has a very specific form,
        # we can do several optimizations.
        point_fft = fft(point)
        t0_fft = [(point_fft[i] * d[i]) / q for i in range(self.n)]
        t1_fft = [(-point_fft[i] * b[i]) / q for i in range(self.n)]
        t_fft = [t0_fft, t1_fft]

        # We now compute v such that:
        #     v = z * B0 for an integral vector z
        #     v is close to (point, 0)
        z_fft = ffsampling_fft(t_fft, self.T_fft, self.sigmin)
        v0_fft = add_fft(mul_fft(z_fft[0], a), mul_fft(z_fft[1], c))
        v1_fft = add_fft(mul_fft(z_fft[0], b), mul_fft(z_fft[1], d))
        v0 = [int(round(elt)) for elt in ifft(v0_fft)]
        v1 = [int(round(elt)) for elt in ifft(v1_fft)]

        # The difference s = (point, 0) - v is such that:
        #     s is short
        #     s[0] + s[1] * h = point
        s = [sub(point, v0), neg(v1)]
        return s
Esempio n. 5
0
def test_ffnp(n, iterations):
    """Test ffnp.

    This functions check that:
    1. the two versions (coefficient and FFT embeddings) of ffnp are consistent
    2. ffnp output lattice vectors close to the targets.
    """
    f = sign_KAT[n][0]["f"]
    g = sign_KAT[n][0]["g"]
    F = sign_KAT[n][0]["F"]
    G = sign_KAT[n][0]["G"]
    B = [[g, neg(f)], [G, neg(F)]]
    G0 = gram(B)
    G0_fft = [[fft(elt) for elt in row] for row in G0]
    T = ffldl(G0)
    T_fft = ffldl_fft(G0_fft)

    sqgsnorm = gs_norm(f, g, q)
    m = 0
    for i in range(iterations):
        t = [[random() for i in range(n)], [random() for i in range(n)]]
        t_fft = [fft(elt) for elt in t]

        z = ffnp(t, T)
        z_fft = ffnp_fft(t_fft, T_fft)

        zb = [ifft(elt) for elt in z_fft]
        zb = [[round(coef) for coef in elt] for elt in zb]
        if z != zb:
            print("ffnp and ffnp_fft are not consistent")
            return False
        diff = [sub(t[0], z[0]), sub(t[1], z[1])]
        diffB = vecmatmul(diff, B)
        norm_zmc = int(round(sqnorm(diffB)))
        m = max(m, norm_zmc)
    th_bound = (n / 4.) * sqgsnorm
    if m > th_bound:
        print("Warning: ffnp does not output vectors as short as expected")
        return False
    else:
        return True
Esempio n. 6
0
    def sample_preimage(self, point, seed=None):
        """
        Sample a short vector s such that s[0] + s[1] * h = point.
        """
        [[a, b], [c, d]] = self.B0_fft

        # We compute a vector t_fft such that:
        #     (fft(point), fft(0)) * B0_fft = t_fft
        # Because fft(0) = 0 and the inverse of B has a very specific form,
        # we can do several optimizations.
        point_fft = fft(point)
        t0_fft = [(point_fft[i] * d[i]) / q for i in range(self.n)]
        t1_fft = [(-point_fft[i] * b[i]) / q for i in range(self.n)]
        t_fft = [t0_fft, t1_fft]

        # We now compute v such that:
        #     v = z * B0 for an integral vector z
        #     v is close to (point, 0)
        if seed is None:
            # If no seed is defined, use urandom as the pseudo-random source.
            z_fft = ffsampling_fft(t_fft, self.T_fft, self.sigmin, urandom)
        else:
            # If a seed is defined, initialize a ChaCha20 PRG
            # that is used to generate pseudo-randomness.
            chacha_prng = ChaCha20(seed)
            z_fft = ffsampling_fft(t_fft, self.T_fft, self.sigmin,
                                   chacha_prng.randombytes)

        v0_fft = add_fft(mul_fft(z_fft[0], a), mul_fft(z_fft[1], c))
        v1_fft = add_fft(mul_fft(z_fft[0], b), mul_fft(z_fft[1], d))
        v0 = [int(round(elt)) for elt in ifft(v0_fft)]
        v1 = [int(round(elt)) for elt in ifft(v1_fft)]

        # The difference s = (point, 0) - v is such that:
        #     s is short
        #     s[0] + s[1] * h = point
        s = [sub(point, v0), neg(v1)]
        return s
Esempio n. 7
0
def module_ntru_gen(d, q, m):
    """
    Take as input system parameters, and output two "module-NTRU" matrices A and B such that:
    - B * A = 0 [mod q]
    - B has small polynomials
    - A is in Hermite normal form
    Also compute the inverse of B (over the field K = Q[x] / (x ** d + 1)).

    Input:
    d           The degree of the underlying ring R = Z[x] / (x ** d + 1)
    q           An integer
    m           An integer

    Output:
    A           A matrix in R ^ ((m + 1) x 1)
    B           A matrix in R ^ ((m + 1) x (m + 1))
    inv_B       A matrix in K ^ ((m + 1) x (m + 1))
    sq_gs_norm  A real number, the square of the Gram-Schmidt norm of B

    Format:     Coefficient
    """
    if m == 1:
        magic_constant = [1.15]
        gs_slack = 1.17
    elif m == 2:
        magic_constant = [1.07, 1.14]
        gs_slack = 1.17
    elif m == 3:
        magic_constant = [1.21, 1.10, 1.06]
        gs_slack = 1.24
    else:
        print("No parameters implemented yet for m = {m}".format(m=m))
        return
    max_gs_norm = gs_slack * (q ** (1 / (m + 1)))
    while True:
        # We generate all rows of B except the last
        B = [[None for j in range(m + 1)] for i in range(m + 1)]
        for i in range(m):
            for j in range(m + 1):
                # Each coefficient B[i][j] is a polynomial
                sigma = magic_constant[i] * (q ** (1 / (m + 1))) # ==> ||bi~|| = gs_slack * q^(1/(m+1))
                sig = sqrt(1 / (d * (m + 1 - i))) * sigma        # sig = stdv. dev. des coef de bi
                B[i][j] = [int(round(gauss(0, sig))) for k in range(d)]
        # We check that the GS norm is not larger than tolerated
        Bp_fft = [[fft(poly) for poly in row] for row in B[:-1]]
        Gp = gram_fft(Bp_fft)
        [Lp_fft, Dp_fft] = ldl_fft(Gp)
        Dp = [[[0] * d for col in range(m)] for row in range(m)]
        for i in range(m):
            Dp[i][i] = ifft(Dp_fft[i][i])
        prod_di = [1] + [0] * (d - 1)
        for i in range(m):
            prod_di = mul(prod_di, Dp[i][i])
        last = div([q ** 2] + [0] * (d - 1), prod_di)
        norms = [Dp[i][i][0] for i in range(m)] + [last[0]]
        # If the GS norm is too large, restart
        if sqrt(max(norms)) > max_gs_norm:
            continue
        # Try to solve the module-NTRU equation
        f = submatrix(B, m, 0)
        f = [[neg(elt) for elt in row] for row in f]
        g = [B[j][0] for j in range(m)]
        fp = my_det(f)
        adjf = my_adjugate(f)
        gp = [0] * d
        for i in range(m):
            gp = add(gp, karamul(adjf[0][i], g[i]))
        try:
            # Compute f^(-1) mod q
            fp_q = [elt % q for elt in fp]
            inv_f = [[elt[:] for elt in row] for row in adjf]
            for i in range(m):
                for j in range(m):
                    inv_f[i][j] = [elt % q for elt in inv_f[i][j]]
                    inv_f[i][j] = div_zq(inv_f[i][j], fp_q, q)
            # Compute h = f^(-1) * g mod q and A = [1 | h]
            h = [None] * m
            for i in range(m):
                elt = [0] * d
                for j in range(m):
                    elt = add_zq(elt, mul_zq(inv_f[i][j], g[j], q), q)
                h[i] = elt
            one = [1] + [0 for _ in range(1, d)]
            A = [one] + h
            Fp, Gp = ntru_solve(fp, gp, q)
            B[m][0] = Gp
            B[m][1] = [- coef for coef in Fp]
            for i in range(2, m + 1):
                B[m][i] = [0 for _ in range(d)]
            # Compute the inverse of B
            det_B = my_det(B)
            inv_B = my_adjugate(B)
            inv_B = [[div(poly, det_B) for poly in row] for row in inv_B]
            return A, B, inv_B, max(norms)
        # If any step failed, restart
        except (ZeroDivisionError, ValueError):
            continue