A collection of functions for working modular arithmetic, polynomials over finite fields, and related things.
Implements factorization of 64 bit numbers using trial division, Pollard's Rho algorithm with Brent or Floyd cycle finding, and Lenstra's Elliptic Curve algorithm with Wierstrass or Montgomery curves. Planned support for arbitrary precision integers and Kraitcheck style methods (ie quadratic sieve and number field sieve), although Flint and its extension library Arb may be more suited for your needs.
For polynomials, implements finding roots of a polynomial over a prime field using the Cantor-Zassenhaus algorithm.
Also includes general purpose modular arithmetic routines, including powers, Miller-Rabin prime checking, random numbers, random quadratic nonresidues, Jacobi symbols, modular square root including Tonelli-Shanks and Cippola, extended gcd, Chinese remainder theorem, and Euclidean remainder.
For polynomials, also includes arithmetic (addition, subtraction, scalar multiplication, coefficientwise multiplication, multiplication, quotient and remainder, Horner evaluation, gcd, and powers), printing/normalization, and distinct degree factorization. Currently there is a simple driver function that will find all roots of a polynomial over a prime field, but if a full factorization is desired the squarefree step must still be done manually. Like the factorization part of the library, polynomials use 64 bit integers, so computations involving numbers larger than about 230 potentially can fail due to overflow.
This library uses Waf as the build system.
To build the release variant, simply run ./waf configure build_release
in the project root directory.
The resulting files will be in build/release
and build/release/bin
,
or you can run sudo ./waf install_release
after building.
The general usage of Waf is ./waf subcommand_1 subcommand_2 ...
, where each subcommand is either user defined/modified
or builtin, and many subcommands can be specified.
If you need to change the compiler or other environment variables, you can try setting them,
but CC
and some others get overridden, so you will have to modify wscript
.
In wscript.configure
, find the if/elif/else
block that defines GCC
, CLANG
, XGCC
, and XAR
, and change GCC
and CLANG
to both be the compiler you want.
The main subcommands to be aware of are
configure
: set up Waf, must be run before most other commandsbuild_<variant>
: build the indicated variant. Results are stored inbuild/<variant>
. The currently extant variants are:coverage
: debug variant, includes address sanitizer, some ub sanitizers, and coverage collection. builds usinggcc
debug
: debug variant, includes memory sanitizer and most ub sanitizers. builds usingclang
release
: release variant, no sanitizers. builds usinggcc
with-O3
valgrind
: debug variant, no sanitizers. builds usingclang
with-O1
windows
: release variant, no sanitizers. builds usingx86_64-w64-mingw32-gcc
clean_<variant>
: delete build filesinstall_<variant>
: copy headers, built libraries, and build binaries (but not test binaries) to the system. Typically requiressudo
. Consider running withoutsudo
first to ensure it's doing what you want.uninstall_<variant>
: remove installed files. Typically requiressudo
. Consider running withoutsudo
first to ensure it's doing what you want.test_<variant>
: run tests (invoketests/test.py
). This is a special rule, it will always run tests, unlikebuild_*
which only builds outputs if their inputs have changed. Requiresbuild_<variant>
to have been run successfully first.- For the
coverage
variant,geninfo
andgenhtml
are automatically invoked after tests succeed.
- For the
distclean
: delete the whole build directory (build files for all variants)docs
: rundoxygen
to create the documentation. Similar totest_<variant>
this always executes, but it is not tied to a variant.
To introduce new variants, look in wscript
and add an appropriate section in configure
as well as the for
loop at the end of the file.
Waf depends on two main files: waf
, the compressed python script comprising Waf itself, and wscript
, the main user config.
These are somewhat analagous to the systemwide make
executable and the Makefile
.
Waf is intended to be distributed with the project, with a separate copy in each project.
Executables, static libraries, and test executables are automatically created based on the contents of src
:
every C file or directory in src/bin
is turned into its own executable in $build/<variant>/bin
, every C file
or directory in src/lib
is turned into its own static library in build/<variant>
, and every C file or
directory in src/test
is turned into its own test executable in build/<variant>
.
./waf distclean
will not remove cov
and docs
, the directories generated by test_coverage
and docs
respectively;
these can be removed with rm -rf cov docs
.
Only Linux is properly supported. To build, only (gcc
and ar
or clang
) and python
are strictly required, but lcov
and doxygen
are required for coverage and documentation, valgrind
is required for testing, and it's recommended to have both gcc
and clang
.
Cross compiling for windows requires x86_64-w64-mingw32-gcc
and libwinpthread
and isn't fully supported. WSL or similar may be better for windows users.
Once the library has been built, it can be linked with C programs by adding the flags -Lbuild/release -lnut
(
or -Lbuild/<variant> -lnut
for a different variant).
You can also install it to your system libraries by doing sudo ./waf install_release
.
This will place the libraries in /usr/local/lib/
, the headers in /usr/local/include/nut/
, and non-test binaries in /usr/local/bin/
.
Then the -L
flag can be omitted and the library can be linked with simply -lnut
.
Having the release variant installed systemwide and linking manually against the debug variant if some need arises is probably most convenient.
The tests in src/test
offer a lot more interesting examples, with basically every function being at least used.
for(uint64_t i = 0; i < 100; ++i){
int64_t p = nut_u64_rand(2, 1ull << 30);
while(!nut_u64_is_prime_dmr(p)){
++p;
}
//Want to solve n**2 - n + 1 == 0 mod p
//<=> 4*n**2 - 4*n + 4 == 0 mod p
//<=> (2*n - 1)**2 == -3 mod p
//<=> n == 2**-1*(1 +- sqrt(-3)) mod p
switch(nut_i64_jacobi(p - 3, p)){
case -1:
printf("n**2 - n + 1 has no roots mod %"PRId64"\n", p);
break;
case 0://-3 is a multiple of p, ie p == 3 so we only have 1 solution
printf("n**2 - n + 1 has a root at %"PRId64" mod %"PRId64"\n", (p + 1)/2, p);
break;
default: {
//WARNING: here we know p - 3 is a quadratic residue, but nut_i64_sqrt_mod does not check this and thus if a nonresidue is given and
//Tonelli-Shanks is selected as the optimal algorithm for this p, this would be an infinite loop
int64_t r = nut_i64_sqrt_mod(p - 3, p);
printf("n**2 - n + 1 has roots at %"PRId64" and %"PRId64" mod %"PRId64"\n", nut_i64_mod((p + 1)/2*(1 + r), p), nut_i64_mod((p + 1)/2*(1 - r), p), p);
}
}
}
factors_t *factors = nut_make_Factors_w(NUT_MAX_PRIMES_64);
for(uint64_t i = 0; i < 100; ++i){
uint64_t n = nut_u64_rand(2, 1ull << 30);
//nut_u64_factor_heuristic will check if the input and intermediate factors are prime
//it returns the input n divided by all factors found, ie if n is factored completely it returns 1,
//and it should not return > 1 unless n is greater than all the *_max fields in factor_conf and composite.
//the primes and num_primes arguments here are null because primes for trial division must be generated by
//some other function
if(nut_u64_factor_heuristic(n, 0, NULL, &nut_default_factor_conf, &factors) != 1){
printf("Failed to factor %"PRIu64"!\n", n);
}else{
printf("%"PRIu64" = ", n);
nut_Factor_fprint(stdout, factors);
printf("\n");
}
}
free(factors);
nut_Poly f[1];
nut_Poly_init(f, 9);
const char *end = "";
int64_t modulus;
if(!nut_Poly_parse(f, &modulus, "x^8 + x^7 - x^5 - x^4 - x^3 + x + 1", &end) || *end){
printf("Failed to parse polynomial\n");
}
fprintf(stderr, "\e[1;34mComputing roots of (");
nut_Poly_fprint(stderr, f, "x", " + ", " - ", "**", 1);
fprintf(stderr, ") nut_i64_mod p for %"PRIu64" random primes...\e[0m\n", trials);
nut_Roots roots[1];
nut_Roots_init(roots, 8);
for(uint64_t i = 0; i < 50; ++i){
int64_t p = nut_u64_rand(2, 1ull << 30);
while(!nut_u64_is_prime_dmr(p)){
++p;
}
if(!nut_Poly_roots_modn_tmptmp(f, p, roots)){
printf("Failed to factor (");
nut_Poly_fprint(stdout, f, "x", " + ", " - ", "**", 1);
printf(") nut_i64_mod %"PRId64"\n", p)
continue;
}
printf("Found %"PRIu64" roots of (", roots->len);
nut_Poly_fprint(stdout, f, "x", " + ", " - ", "**", 1);
printf(") nut_i64_mod %"PRId64, p);
if(roots->len){
printf(": ");
}else{
printf("\n");
}
for(uint64_t i = 0; i < roots->len; ++i){
printf();
if(i != roots->len - 1){
printf("%"PRId64", ", roots->roots[i]);
}else{
printf("and %"PRId64"\n", roots->roots[i]);
}
}
}
nut_Roots_destroy(roots);
nut_Poly_destroy(f);