1
0
mirror of https://github.com/biergaizi/codecrypt synced 2024-06-29 18:33:10 +00:00
codecrypt/src/mce_qcmdpc.cpp

219 lines
5.4 KiB
C++
Raw Normal View History

2015-11-07 18:35:04 +00:00
/*
* This file is part of Codecrypt.
*
* Codecrypt is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Codecrypt is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Codecrypt. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mce_qcmdpc.h"
#include "gf2m.h"
#include "polynomial.h"
using namespace mce_qcmdpc;
int mce_qcmdpc::generate (pubkey&pub, privkey&priv, prng&rng,
uint block_size, uint block_count, uint wi,
uint t, uint rounds, uint delta)
{
uint i, j;
priv.H.resize (block_count);
if (wi > block_size / 2) return 1; //safety
/*
* Trick. Cyclomatic matrix of size n is invertible if a
* polynomial that's made up from its first row is coprime to
* (x^n-1), the polynomial inversion and matrix inversion are
* then isomorphic.
*/
gf2m gf;
gf.create (1); //binary
polynomial xmm1; //x^m-1
xmm1.resize (block_size + 1, 0);
xmm1[0] = 1;
xmm1[block_size] = 1;
polynomial last_inv_H;
for (;;) {
//retry generating the rightmost block until it is invertible
polynomial g;
g.resize (block_size, 0);
for (i = 0; i < wi; ++i)
for (uint pos = rng.random (block_size);
g[pos] ? 1 : (g[pos] = 1, 0);
pos = rng.random (block_size));
//try if it is coprime to (x^n-1)
polynomial gcd = g.gcd (xmm1, gf);
if (!gcd.one()) continue; //it isn't.
//if it is, save it to matrix (in "reverse" order for columns)
priv.H[block_count - 1].resize (block_size, 0);
for (i = 0; i < block_size && i < g.size(); ++i)
priv.H[block_count - 1][i] = g[ (-i) % block_size];
//invert it, save for later and succeed.
g.inv (xmm1, gf);
last_inv_H = g;
break;
}
//generate the rests of matrix blocks, fill the G right away.
pub.G.resize (block_count - 1);
for (i = 0; i < block_count - 1; ++i) {
polynomial hi;
hi.resize (block_size, 0);
//generate the polynomial corresponding to the first row
for (j = 0; j < wi; ++j)
for (uint pos = rng.random (block_size);
hi[pos] ? 1 : (hi[pos] = 1, 0);
pos = rng.random (block_size));
//save it to H
priv.H[i].resize (block_size);
for (j = 0; j < block_size; ++j) priv.H[i][j] = hi[ (-j) % block_size];
//compute inv(H[last])*H[i]
hi.mult (last_inv_H, gf);
hi.mod (xmm1, gf);
//save it to G
pub.G[i].resize (block_size);
for (j = 0; j < block_size; ++j) pub.G[i][j] = hi[j % block_size];
}
//save the target params
pub.t = priv.t = t;
priv.rounds = rounds;
priv.delta = delta;
return 0;
}
int privkey::prepare()
{
return 0;
}
int pubkey::encrypt (const bvector& in, bvector&out, prng&rng)
{
uint s = cipher_size();
if (t > s) return 1;
//create the error vector
bvector e;
e.resize (s);
for (uint i = 0; i < t; ++i)
for (uint pos = rng.random (s);
e[pos] ? 1 : (e[pos] = 1, 0);
pos = rng.random (s));
return encrypt (in, out, e);
}
int pubkey::encrypt (const bvector&in, bvector&out, const bvector&errors)
{
uint ps = plain_size();
if (in.size() != ps) return 1;
uint bs = G[0].size();
for (uint i = 1; i < G.size(); ++i) if (G[i].size() != bs) return 1; //prevent mangled keys
//first, the checksum part
bvector bcheck;
//G stores first row(s) of the circulant matrix blocks, proceed row-by-row and construct the checkum
for (uint i = 0; i < ps; ++i)
if (in[i]) bcheck.rot_add (G[ (i % ps) / bs], i % bs);
//compute the ciphertext
out = in;
out.append (bcheck);
out.add (errors);
return 0;
}
int privkey::decrypt (const bvector & in, bvector & out)
{
bvector tmp_errors;
return decrypt (in, out, tmp_errors);
}
#include <vector>
int privkey::decrypt (const bvector & in_orig, bvector & out, bvector & errors)
{
uint i;
uint cs = cipher_size();
if (in_orig.size() != cs) return 1;
uint bs;
bs = H[0].size();
/*
* probabilistic decoding!
*/
//compute the syndrome first
bvector syndrome;
syndrome.resize (bs, 0);
bvector in = in_orig; //we will modify it
for (i = 0; i < cs; ++i) if (in[i])
syndrome.rot_add (H[i / bs], (cs - i) % bs);
//minimize counts of unsatisfied equations by flipping
std::vector<uint> unsatisfied;
unsatisfied.resize (cs, 0);
for (i = 0; i < rounds; ++i) {
uint bit, max_unsat;
bvector tmp;
max_unsat = 0;
for (bit = 0; bit < cs; ++bit) {
tmp.fill_zeros();
tmp.rot_add (H[bit / bs], (cs - bit) % bs);
unsatisfied[bit] = tmp.and_hamming_weight (syndrome);
if (unsatisfied[bit] > max_unsat) max_unsat = unsatisfied[bit];
}
//TODO what about timing attacks?
if (!max_unsat) break;
uint threshold = 0;
if (max_unsat > delta) threshold = max_unsat - delta;
//TODO also timing (but it gets pretty statistically hard here I guess)
uint flipped = 0;
for (bit = 0; bit < cs; ++bit)
if (unsatisfied[bit] > threshold) {
in[bit] = !in[bit];
syndrome.rot_add (H[bit / bs], (cs - bit) % bs);
++flipped;
}
}
if (i == rounds) return 2; //we simply failed
errors = in_orig;
errors.add (in); //get the difference
out = in;
out.resize (plain_size());
return 0;
}