fmtseq: working (tbd: tree cache updates)
This commit is contained in:
parent
42d8ddbd07
commit
902a2f541d
223
src/fmtseq.cpp
223
src/fmtseq.cpp
|
@ -19,14 +19,16 @@
|
||||||
#include "fmtseq.h"
|
#include "fmtseq.h"
|
||||||
#include "arcfour.h"
|
#include "arcfour.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
using namespace fmtseq;
|
using namespace fmtseq;
|
||||||
|
|
||||||
void prepare_keygen (arcfour<char>& kg, const std::vector<char>&SK, uint idx)
|
void prepare_keygen (arcfour<byte>& kg, const std::vector<byte>&SK, uint idx)
|
||||||
{
|
{
|
||||||
kg.clear();
|
kg.clear();
|
||||||
kg.init (8);
|
kg.init (8);
|
||||||
kg.load_key (SK);
|
kg.load_key (SK);
|
||||||
std::vector<char>tmp;
|
std::vector<byte>tmp;
|
||||||
while (idx) {
|
while (idx) {
|
||||||
tmp.push_back (idx & 0xff);
|
tmp.push_back (idx & 0xff);
|
||||||
idx >>= 8;
|
idx >>= 8;
|
||||||
|
@ -35,25 +37,57 @@ void prepare_keygen (arcfour<char>& kg, const std::vector<char>&SK, uint idx)
|
||||||
kg.load_key (tmp);
|
kg.load_key (tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
//don't feed zero
|
static void add_zero_checksum (bvector& v)
|
||||||
static uint log2 (uint x)
|
|
||||||
{
|
{
|
||||||
uint r = 0;
|
|
||||||
while (x) {
|
uint s = v.size();
|
||||||
++r;
|
if (!s) return;
|
||||||
x >>= 1;
|
|
||||||
|
uint z = s - v.hamming_weight(); //0's instead of 1's
|
||||||
|
|
||||||
|
v.resize (fmtseq_commitments (s) );
|
||||||
|
while (z) {
|
||||||
|
v[s] = z & 1;
|
||||||
|
z >>= 1;
|
||||||
|
++s;
|
||||||
}
|
}
|
||||||
return r - 1;
|
}
|
||||||
|
|
||||||
|
static void alloc_exist (privkey&priv)
|
||||||
|
{
|
||||||
|
priv.exist.resize (priv.l);
|
||||||
|
uint ts = (1 << (priv.h + 1) ) - 2;
|
||||||
|
for (uint i = 0; i < priv.l; ++i)
|
||||||
|
priv.exist[i].resize (ts);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void store_exist (privkey&priv, const privkey::tree_stk_item&i)
|
||||||
|
{
|
||||||
|
uint level = i.level / priv.h;
|
||||||
|
if (level >= priv.l) return; //top node
|
||||||
|
uint sublevel = priv.h - (i.level % priv.h);
|
||||||
|
if (i.pos >= (1 << sublevel) ) return; //too far right
|
||||||
|
|
||||||
|
priv.exist[level][i.pos + (1 << sublevel) - 2] = i.item;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_trees (privkey&priv, hash_func&hf)
|
||||||
|
{
|
||||||
|
//TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
int fmtseq::generate (pubkey&pub, privkey&priv,
|
int fmtseq::generate (pubkey&pub, privkey&priv,
|
||||||
prng&rng, hash_func&hf,
|
prng&rng, hash_func&hf,
|
||||||
uint h, uint l)
|
uint hs, uint h, uint l)
|
||||||
{
|
{
|
||||||
|
|
||||||
uint i, j;
|
uint i, j;
|
||||||
|
|
||||||
//first off, generate a secret key for commitment generator.
|
/*
|
||||||
|
* first off, generate a secret key for commitment generator.
|
||||||
|
* exactly THIS gives the amount of all possible FMTseq privkeys.
|
||||||
|
*
|
||||||
|
* in our case it's around 2^2048, which is Enough.
|
||||||
|
*/
|
||||||
priv.SK.resize (1 << 8);
|
priv.SK.resize (1 << 8);
|
||||||
for (i = 0; i < (1 << 8); ++i) {
|
for (i = 0; i < (1 << 8); ++i) {
|
||||||
priv.SK[i] = rng.random (1 << 8);
|
priv.SK[i] = rng.random (1 << 8);
|
||||||
|
@ -61,22 +95,24 @@ int fmtseq::generate (pubkey&pub, privkey&priv,
|
||||||
|
|
||||||
priv.h = h;
|
priv.h = h;
|
||||||
priv.l = l;
|
priv.l = l;
|
||||||
|
priv.hs = hs;
|
||||||
|
priv.sigs_used = 0;
|
||||||
|
|
||||||
std::vector<privkey::tree_stk_item> stk;
|
std::vector<privkey::tree_stk_item> stk;
|
||||||
stk.reserve (h * l + 1);
|
stk.reserve (h * l + 1);
|
||||||
|
|
||||||
uint sigs = 1 << (h * l);
|
uint sigs = 1 << (h * l);
|
||||||
|
|
||||||
//number of commitments needed for signature (bits+log2(bits))
|
uint commitments = fmtseq_commitments (hs);
|
||||||
uint commitments = 8 * hf.size();
|
|
||||||
commitments += log2 (commitments);
|
|
||||||
|
|
||||||
arcfour<char> generator;
|
arcfour<byte> generator;
|
||||||
std::vector<char> x, y, Y;
|
std::vector<byte> x, y, Y;
|
||||||
|
|
||||||
x.resize (hf.size() );
|
x.resize (hf.size() );
|
||||||
y.resize (hf.size() );
|
y.resize (hf.size() );
|
||||||
|
|
||||||
|
alloc_exist (priv);
|
||||||
|
|
||||||
for (i = 0; i < sigs; ++i) {
|
for (i = 0; i < sigs; ++i) {
|
||||||
//generate commitments and concat publics into Y
|
//generate commitments and concat publics into Y
|
||||||
Y.clear();
|
Y.clear();
|
||||||
|
@ -88,8 +124,10 @@ int fmtseq::generate (pubkey&pub, privkey&priv,
|
||||||
Y.insert (Y.end(), y.begin(), y.end() );
|
Y.insert (Y.end(), y.begin(), y.end() );
|
||||||
}
|
}
|
||||||
|
|
||||||
stk.push_back (privkey::tree_stk_item (0, hf (Y) ) );
|
stk.push_back (privkey::tree_stk_item (0, i, hf (Y) ) );
|
||||||
|
store_exist (priv, stk.back() );
|
||||||
|
|
||||||
|
//try squashing the stack
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (stk.size() < 2) break;
|
if (stk.size() < 2) break;
|
||||||
if ( (stk.end() - 1)->level != (stk.end() - 2)->level) break;
|
if ( (stk.end() - 1)->level != (stk.end() - 2)->level) break;
|
||||||
|
@ -102,16 +140,163 @@ int fmtseq::generate (pubkey&pub, privkey&priv,
|
||||||
(stk.end() - 1)->item.begin(),
|
(stk.end() - 1)->item.begin(),
|
||||||
(stk.end() - 1)->item.end() );
|
(stk.end() - 1)->item.end() );
|
||||||
uint l = stk.back().level + 1;
|
uint l = stk.back().level + 1;
|
||||||
|
uint p = stk.back().pos / 2;
|
||||||
stk.pop_back();
|
stk.pop_back();
|
||||||
stk.pop_back();
|
stk.pop_back();
|
||||||
stk.push_back (privkey::tree_stk_item (l, hf (Y) ) );
|
stk.push_back (privkey::tree_stk_item (l, p, hf (Y) ) );
|
||||||
|
store_exist (priv, stk.back() );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//now there's the public verification key available in the stack.
|
//now there's the public verification key available in the stack.
|
||||||
pub.check = stk.back().item;
|
pub.check = stk.back().item;
|
||||||
pub.H = h * l;
|
pub.H = h * l;
|
||||||
|
pub.hs = hs;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SIGNATURE STRUCTURE
|
||||||
|
* is variable, but following stuff is concatenated exactly in this order:
|
||||||
|
*
|
||||||
|
* - private/public commitments (less than size+log2(size) in bits times hash
|
||||||
|
* size) in the "natural" left-to-right order (or from first to last bits of
|
||||||
|
* hash. Checksum goes last, with least significant bit first). Private
|
||||||
|
* commitment goes whenever there's 1 in message, public on 0.
|
||||||
|
* - h*l hashes of verification chain, from bottom to top, h*l times hash size
|
||||||
|
* - i (so that we can guess left/right concatenation before hashing) stored as
|
||||||
|
* H-bit number in little endian.
|
||||||
|
* TODO Why cannot the tree be just XORed together? would be WAY simpler!
|
||||||
|
*
|
||||||
|
* summed up:
|
||||||
|
*
|
||||||
|
* Sig=(x0, y1, x2, x3, y4, ..... , xComm-1,path0,path1,...,pathH-1, i)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ios.h"
|
||||||
|
|
||||||
|
int privkey::sign (const bvector& hash, bvector& sig, hash_func& hf)
|
||||||
|
{
|
||||||
|
if (hash.size() != hash_size() ) return 2;
|
||||||
|
if (!sigs_remaining() ) return 2;
|
||||||
|
|
||||||
|
uint commitments = fmtseq_commitments (hs);
|
||||||
|
|
||||||
|
bvector M2 = hash;
|
||||||
|
add_zero_checksum (M2);
|
||||||
|
|
||||||
|
std::vector<byte> Sig, t;
|
||||||
|
uint i;
|
||||||
|
|
||||||
|
Sig.reserve (hf.size() * (commitments + h * l) );
|
||||||
|
//first, compute the commitments and push them to the signature
|
||||||
|
arcfour<byte> generator;
|
||||||
|
prepare_keygen (generator, SK, sigs_used);
|
||||||
|
for (i = 0; i < commitments; ++i) {
|
||||||
|
//generate x_i
|
||||||
|
generator.gen (hf.size(), t);
|
||||||
|
|
||||||
|
//if it's 0, publish y_i, else publish x_i
|
||||||
|
if (!M2[i]) t = hf (t);
|
||||||
|
|
||||||
|
//append it to signature
|
||||||
|
Sig.insert (Sig.end(), t.begin(), t.end() );
|
||||||
|
}
|
||||||
|
|
||||||
|
//now retrieve the authentication path
|
||||||
|
uint pos = sigs_used;
|
||||||
|
uint exlev, expos, exid;
|
||||||
|
for (i = 0; i < h * l; ++i) {
|
||||||
|
exid = i / h;
|
||||||
|
exlev = h - (i % h);
|
||||||
|
//flip the last bit of pos so it gets the neighbor
|
||||||
|
expos = (pos ^ 1) % (1 << exlev);
|
||||||
|
Sig.insert (Sig.end(),
|
||||||
|
exist[exid][expos + (1 << exlev) - 2].begin(),
|
||||||
|
exist[exid][expos + (1 << exlev) - 2].end() );
|
||||||
|
pos >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//prepare the signature
|
||||||
|
sig.clear();
|
||||||
|
sig.resize (signature_size (hf), 0);
|
||||||
|
|
||||||
|
//convert to bits
|
||||||
|
uint sig_no_start = (commitments + h * l) * hf.size() * 8;
|
||||||
|
for (i = 0; i < sig_no_start; ++i)
|
||||||
|
sig[i] = 1 & (Sig[i / 8] >> (i % 8) );
|
||||||
|
|
||||||
|
//append signature number
|
||||||
|
pos = sigs_used;
|
||||||
|
for (i = 0; i < h * l; ++i) {
|
||||||
|
sig[i + sig_no_start] = pos & 1;
|
||||||
|
pos >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//move on to the next signature
|
||||||
|
++sigs_used;
|
||||||
|
//update the cache
|
||||||
|
update_trees (*this, hf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pubkey::verify (const bvector& sig, const bvector& hash, hash_func& hf)
|
||||||
|
{
|
||||||
|
uint i, j;
|
||||||
|
if (sig.size() != signature_size (hf) ) return 2;
|
||||||
|
if (hash.size() != hash_size() ) return 2;
|
||||||
|
|
||||||
|
uint commitments = fmtseq_commitments (hs);
|
||||||
|
|
||||||
|
bvector M2 = hash;
|
||||||
|
add_zero_checksum (M2);
|
||||||
|
if (M2.size() != commitments) return 3; //likely internal failure
|
||||||
|
|
||||||
|
//retrieve i
|
||||||
|
uint sig_no = 0;
|
||||||
|
for (i = sig.size() - 1; i >= (commitments + H) * hf.size() * 8; --i)
|
||||||
|
sig_no = (sig_no << 1) + (sig[i] ? 1 : 0);
|
||||||
|
|
||||||
|
std::vector<byte> t, Y;
|
||||||
|
std::vector<std::vector<byte> > Sig;
|
||||||
|
|
||||||
|
//split and convert to byte form for convenient hashing
|
||||||
|
Sig.resize (commitments + H);
|
||||||
|
for (i = 0; i < (commitments + H); ++i) {
|
||||||
|
Sig[i].resize (hf.size(), 0);
|
||||||
|
for (j = 0; j < hf.size() * 8; ++j)
|
||||||
|
if (sig[j + i * hf.size() * 8])
|
||||||
|
Sig[i][j / 8] |= (1 << (j % 8) );
|
||||||
|
}
|
||||||
|
|
||||||
|
Y.clear();
|
||||||
|
for (i = 0; i < commitments; ++i) {
|
||||||
|
if (M2[i]) t = hf (Sig[i]); //convert pk_i to sk_i at 1's
|
||||||
|
else t = Sig[i]; //else it should already be pk_i
|
||||||
|
Y.insert (Y.end(), t.begin(), t.end() ); //append it to Y_i
|
||||||
|
}
|
||||||
|
|
||||||
|
//create the leaf
|
||||||
|
t = hf (Y);
|
||||||
|
|
||||||
|
//walk the authentication path
|
||||||
|
for (i = 0; i < H; ++i) {
|
||||||
|
Y.clear();
|
||||||
|
Y = Sig[commitments + i];
|
||||||
|
if ( (sig_no >> i) & 1) {
|
||||||
|
//append path auth from left
|
||||||
|
Y.insert (Y.end(), t.begin(), t.end() );
|
||||||
|
t = hf (Y);
|
||||||
|
} else {
|
||||||
|
//append from right
|
||||||
|
t.insert (t.end(), Y.begin(), Y.end() );
|
||||||
|
t = hf (t);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t == check) return 0; //all went okay
|
||||||
|
else return 1;
|
||||||
|
}
|
||||||
|
|
50
src/fmtseq.h
50
src/fmtseq.h
|
@ -34,27 +34,38 @@
|
||||||
namespace fmtseq
|
namespace fmtseq
|
||||||
{
|
{
|
||||||
|
|
||||||
|
//helper function used to calculate hash sizes percisely
|
||||||
|
inline uint fmtseq_commitments (uint l)
|
||||||
|
{
|
||||||
|
uint x = l;
|
||||||
|
while (x) {
|
||||||
|
++l;
|
||||||
|
x >>= 1;
|
||||||
|
}
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
class privkey
|
class privkey
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
std::vector<char> SK; //secret key
|
std::vector<byte> SK; //secret key
|
||||||
uint h, l; //l=level count, h=level height (root-leaf path length)
|
uint h, l; //l=level count, h=level height (root-leaf path length)
|
||||||
//therefore, H = h*l
|
//therefore, H = h*l
|
||||||
uint sigs_used;
|
uint sigs_used;
|
||||||
|
uint hs;
|
||||||
|
|
||||||
//FMT caches
|
//FMT caches
|
||||||
std::vector<std::vector<char> > exist;
|
std::vector<std::vector<std::vector<byte> > > exist, desired;
|
||||||
std::vector<std::vector<char> > desired;
|
|
||||||
|
|
||||||
struct tree_stk_item {
|
struct tree_stk_item {
|
||||||
uint level;
|
uint level, pos;
|
||||||
std::vector<char> item;
|
std::vector<byte> item;
|
||||||
tree_stk_item() {}
|
tree_stk_item() {}
|
||||||
tree_stk_item (uint L, std::vector<char> i)
|
tree_stk_item (uint L, uint P, std::vector<byte> i)
|
||||||
: level (L), item (i) {}
|
: level (L), pos (P), item (i) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<std::list<tree_stk_item> > desired_stack;
|
std::vector<std::list<tree_stk_item> > desired_stack;
|
||||||
|
std::vector<uint> desired_progress;
|
||||||
|
|
||||||
int sign (const bvector&, bvector&, hash_func&);
|
int sign (const bvector&, bvector&, hash_func&);
|
||||||
|
|
||||||
|
@ -62,8 +73,12 @@ public:
|
||||||
return (1 << (h * l) ) - sigs_used;
|
return (1 << (h * l) ) - sigs_used;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint hash_size (hash_func&hf) {
|
uint hash_size () {
|
||||||
return hf.size() * 8;
|
return hs;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint signature_size (hash_func&hf) {
|
||||||
|
return ( (h * l + fmtseq_commitments (hs) ) * hf.size() * 8) + (h * l);
|
||||||
}
|
}
|
||||||
|
|
||||||
sencode* serialize();
|
sencode* serialize();
|
||||||
|
@ -73,20 +88,25 @@ public:
|
||||||
class pubkey
|
class pubkey
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
std::vector<char> check; //tree top verification hash
|
std::vector<byte> check; //tree top verification hash
|
||||||
uint H;
|
uint H, hs;
|
||||||
|
|
||||||
int verify (const bvector&, const bvector&, hash_func&);
|
int verify (const bvector&, const bvector&, hash_func&);
|
||||||
|
|
||||||
uint hash_size (hash_func&hf) {
|
uint hash_size () {
|
||||||
return hf.size() * 8;
|
return hs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint signature_size (hash_func&hf) {
|
||||||
|
return ( (H + fmtseq_commitments (hs) ) * hf.size() * 8) + H;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
sencode* serialize();
|
sencode* serialize();
|
||||||
bool unserialize (sencode*);
|
bool unserialize (sencode*);
|
||||||
};
|
};
|
||||||
|
|
||||||
int generate (pubkey&, privkey&, prng&, hash_func&, uint h, uint l);
|
int generate (pubkey&, privkey&, prng&, hash_func&, uint hs, uint h, uint l);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -28,8 +28,8 @@
|
||||||
class hash_func
|
class hash_func
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual std::vector<char> operator() (const std::vector<char>&) = 0;
|
virtual std::vector<byte> operator() (const std::vector<byte>&) = 0;
|
||||||
virtual uint size(); //in bytes
|
virtual uint size() = 0; //in bytes
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
59
src/main.cpp
59
src/main.cpp
|
@ -16,13 +16,13 @@
|
||||||
* along with Codecrypt. If not, see <http://www.gnu.org/licenses/>.
|
* along with Codecrypt. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "arcfour.h"
|
|
||||||
#include "prng.h"
|
#include "prng.h"
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include "ios.h"
|
||||||
|
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
@ -38,8 +38,62 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#include "hash.h"
|
||||||
|
#include "sha2.h"
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
class sha2hash : public hash_func
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
uint size() {
|
||||||
|
//return 4;
|
||||||
|
return SHA256_DIGEST_LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<byte> operator() (const vector<byte>&a) {
|
||||||
|
SHA256_CTX ctx;
|
||||||
|
SHA256_Init (&ctx);
|
||||||
|
SHA256_Update (&ctx, (const uint8_t*) & (a[0]), a.size() );
|
||||||
|
vector<byte> r;
|
||||||
|
r.resize (size() );
|
||||||
|
//r.resize (SHA256_DIGEST_LENGTH);
|
||||||
|
SHA256_Final ( (uint8_t*) & (r[0]), &ctx);
|
||||||
|
//r.resize(size());
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "fmtseq.h"
|
||||||
|
#include "arcfour.h"
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
primitiverng r;
|
||||||
|
r.seed (0);
|
||||||
|
sha2hash sha2;
|
||||||
|
fmtseq::privkey priv;
|
||||||
|
fmtseq::pubkey pub;
|
||||||
|
|
||||||
|
cout << fmtseq::generate (pub, priv, r, sha2, 128, 3, 3) << endl;
|
||||||
|
bvector h, sig;
|
||||||
|
h.resize (priv.hash_size(), 0);
|
||||||
|
h[0] = 1;
|
||||||
|
h[1] = 1;
|
||||||
|
//for (uint i = 0; i < 10; ++i) h[2 * i] = 1;
|
||||||
|
|
||||||
|
cout << "HASH " << h;
|
||||||
|
|
||||||
|
for (uint i = 0; i < 8; ++i) {
|
||||||
|
cout << priv.sign (h, sig, sha2) << endl;
|
||||||
|
//cout << i << "-th SIG " << sig;
|
||||||
|
|
||||||
|
cout << "VERIFY ERROR: " << pub.verify (sig, h, sha2) << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
#if 0
|
||||||
arcfour<unsigned short> c;
|
arcfour<unsigned short> c;
|
||||||
if (!c.init (10) ) {
|
if (!c.init (10) ) {
|
||||||
cout << "haha." << endl;
|
cout << "haha." << endl;
|
||||||
|
@ -59,7 +113,6 @@ int main()
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
#if 0
|
|
||||||
primitiverng r;
|
primitiverng r;
|
||||||
r.seed (0);
|
r.seed (0);
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
* adviseable when computing with m=16 and larger.
|
* adviseable when computing with m=16 and larger.
|
||||||
*/
|
*/
|
||||||
typedef unsigned int uint;
|
typedef unsigned int uint;
|
||||||
|
typedef unsigned char byte;
|
||||||
|
|
||||||
//TODO add separate type for GF(2^m) elements!
|
//TODO add separate type for GF(2^m) elements!
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue