示例#1
0
def ldl(G):
    """
    Compute the LDL decomposition of G.

    Input:
    G           A self-adjoint matrix (i.e. G is equal to its conjugate transpose)

    Output:
    L, D        The LDL decomposition of G, that is G = L * D * (L*), where:
                - L is lower triangular with a diagonal of 1's
                - D is diagonal

    Format:     Coefficient
    """
    deg = len(G[0][0])
    dim = len(G)
    L = [[[0 for k in range(deg)] for j in range(dim)] for i in range(dim)]
    D = [[[0 for k in range(deg)] for j in range(dim)] for i in range(dim)]
    for i in range(dim):
        L[i][i] = [1] + [0 for j in range(deg - 1)]
        D[i][i] = G[i][i]
        for j in range(i):
            L[i][j] = G[i][j]
            for k in range(j):
                L[i][j] = sub(L[i][j], mul(mul(L[i][k], adj(L[j][k])),
                                           D[k][k]))
            L[i][j] = div(L[i][j], D[j][j])
            D[i][i] = sub(D[i][i], mul(mul(L[i][j], adj(L[i][j])), D[j][j]))
    return [L, D]
示例#2
0
def ldl(G):
    """Compute the LDL decomposition of G.

	Args:
		G: a Gram matrix

	Format: coefficient

	Corresponds to algorithm 14 (LDL) of Falcon's documentation,
	except it's in polynomial representation.
	"""
    deg = len(G[0][0])
    dim = len(G)
    L = [[[0 for k in range(deg)] for j in range(dim)] for i in range(dim)]
    D = [[[0 for k in range(deg)] for j in range(dim)] for i in range(dim)]
    for i in range(dim):
        L[i][i] = [1] + [0 for j in range(deg - 1)]
        D[i][i] = G[i][i]
        for j in range(i):
            L[i][j] = G[i][j]
            for k in range(j):
                L[i][j] = sub(L[i][j], mul(mul(L[i][k], adj(L[j][k])),
                                           D[k][k]))
            L[i][j] = div(L[i][j], D[j][j])
            D[i][i] = sub(D[i][i], mul(mul(L[i][j], adj(L[i][j])), D[j][j]))
    return [L, D]
示例#3
0
 def sample_preimage_fft(self, point):
     """Sample preimage."""
     B = self.B0_fft
     c = point, [0] * self.n
     t_fft = self.get_coord_in_fft(c)
     z_fft = ffsampling_fft(t_fft, self.T_fft)
     v0_fft = add_fft(mul_fft(z_fft[0], B[0][0]),
                      mul_fft(z_fft[1], B[1][0]))
     v1_fft = add_fft(mul_fft(z_fft[0], B[0][1]),
                      mul_fft(z_fft[1], B[1][1]))
     v0 = [int(round(elt)) for elt in ifft(v0_fft)]
     v1 = [int(round(elt)) for elt in ifft(v1_fft)]
     v = v0, v1
     s = [sub(c[0], v[0]), sub(c[1], v[1])]
     return s
示例#4
0
    def sample_preimage_fft(self, point):
        """
        Sample preimage.

        Input:
        self        The private key
        point       An element of Z_q[x] / (x ** d + 1)

        Output:
        s           A short element such that s * B = point

        Format:     Coefficient
        """
        d = self.d
        m = self.m
        # Compute large preimage
        c = [point] + [[0] * d for _ in range(m)]
        # Move to FFT domain
        c_fft = [fft(elt) for elt in c]
        # Compute coefficients in span(B)
        t_fft = vecmatmul_fft(c_fft, self.invB_fft)
        # Fast Fourier sampling
        z_fft = ffsampling_fft(t_fft, self.T_fft)
        # Compute short preimage s = (c - v) = (t - z) * B
        v_fft = vecmatmul_fft(z_fft, self.B_fft)
        v = [[int(round(coef)) for coef in ifft(elt)] for elt in v_fft]
        s = [sub(c[i], v[i]) for i in range(m + 1)]
        return s
示例#5
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
def ldl(G):
    """
    Compute the LDL decomposition of G. Only works with 2 * 2 matrices.

    Args:
        G: a Gram matrix

    Format: coefficient

    Corresponds to algorithm 8 (LDL*) of Falcon's documentation,
    except it's in polynomial representation.
    """
    deg = len(G[0][0])
    dim = len(G)
    assert (dim == 2)
    assert (dim == len(G[0]))

    zero = [0] * deg
    one = [1] + [0] * (deg - 1)
    D00 = G[0][0][:]
    L10 = div(G[1][0], G[0][0])
    D11 = sub(G[1][1], mul(mul(L10, adj(L10)), G[0][0]))
    L = [[one, zero], [L10, one]]
    D = [[D00, zero], [zero, D11]]

    return [L, D]
示例#7
0
def ffnp(t, T):
    """
    Compute the FFNP reduction of t, using T as auxilary information.

    Input:
    t           A vector
    T           The LDL decomposition tree of an (implicit) matrix G

    Output:
    z           An integer vector such that (t - z) * B is short

    Format:     Coefficient
    """
    m = len(t)
    n = len(t[0])
    z = [None] * m
    # General case
    if (n > 1):
        L = T[0]
        for i in range(m - 1, -1, -1):
            # t[i] is "corrected", taking into accounts the t[j], z[j] (j > i)
            tib = t[i][:]
            for j in range(m - 1, i, -1):
                tib = add(tib, mul(sub(t[j], z[j]), L[j][i]))
            # Recursive call
            z[i] = merge(ffnp(split(tib), T[i + 1]))
        return z
    # Bottom case: round each coefficient in parallel
    elif (n == 1):
        z[0] = [round(t[0][0])]
        z[1] = [round(t[1][0])]
        return z
示例#8
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
示例#9
0
def test_ffnp(d, m, 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.
    """
    q = q_12289
    A, B, inv_B, sqr_gsnorm = module_ntru_gen(d, q, m)
    G0 = gram(B)
    G0_fft = [[fft(elt) for elt in row] for row in G0]
    T = ffldl(G0)
    T_fft = ffldl_fft(G0_fft)
    th_bound = (m + 1) * d * sqr_gsnorm / 4.

    mn = 0
    for i in range(iterations):
        t = [[random() for coef in range(d)] for poly in range(m + 1)]
        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[i], z[i]) for i in range(m + 1)]
        diffB = vecmatmul(diff, B)
        norm_zmc = int(round(sqnorm(diffB)))
        mn = max(mn, norm_zmc)

    if mn > th_bound:
        print("z = {z}".format(z=z))
        print("t = {t}".format(t=t))
        print("mn = {mn}".format(mn=mn))
        print("th_bound = {th_bound}".format(th_bound=th_bound))
        print("sqr_gsnorm = {sqr_gsnorm}".format(sqr_gsnorm=sqr_gsnorm))
        print("Warning: the algorithm outputs vectors longer than expected")
        return False
    else:
        return True
def ffnp(t, T):
    """Compute the ffnp reduction of t, using T as auxilary information.

    Args:
        t: a vector
        T: a ldl decomposition tree

    Format: coefficient
    """
    n = len(t[0])
    z = [None, None]
    if (n > 1):
        l10, T0, T1 = T
        z[1] = merge(ffnp(split(t[1]), T1))
        t0b = add(t[0], mul(sub(t[1], z[1]), l10))
        z[0] = merge(ffnp(split(t0b), T0))
        return z
    elif (n == 1):
        z[0] = [round(t[0][0])]
        z[1] = [round(t[1][0])]
        return z
示例#11
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