Add template argument to green up build. Remove discrete log as we do not have an overflow-resistant mul_mod in boost.

This commit is contained in:
Nick Thompson
2018-10-26 16:58:30 -06:00
parent 3632ae43b2
commit e0646cb7ec
5 changed files with 1 additions and 397 deletions

View File

@ -1,99 +0,0 @@
[section:discrete_log Discrete Log]
[section Introduction]
The discrete log is the inverse of modular exponentiation.
To wit, if /a/[super /x/] = /b/ mod /p/, then we write /x/ = log[sub a](/b/).
Fast algorithms for modular exponentiation exists, but currently there are no polynomial time algorithms known for the discrete logarithm,
a fact which is the basis for the security of Diffie-Hellman key exchange.
Despite having exponential complexity in the number of bits, the algorithms for discrete logarithm provided by Boost are still useful,
for there are many uses of the discrete logarithm outside of cryptography which do not require massive inputs.
The algorithms provided by Boost should be acceptable up to roughly 32 bits.
[endsect]
[section Synopsis]
#include <boost/integer/discrete_log.hpp>
namespace boost { namespace integer {
template<class Z>
boost::optional<Z> trial_multiplication_discrete_log(Z base, Z arg, Z p);
template<class Z>
class bsgs_discrete_log
{
public:
bsgs_discrete_log(Z base, Z p);
boost::optional<Z> operator()(Z arg) const;
};
}}
[endsect]
[section Usage]
Boost provides two algorithms for the discrete log: Trial multiplication and the "baby-step giant-step" algorithm.
Basic usage is shown below:
auto logarithm = trial_multiplication_discrete_log(2, 3, 5);
if (logarithm)
{
std::cout << "log_2(3) mod 5 = " << l.value() << std::endl;
}
auto log_2 = bsgs_discrete_log(2, 5);
int log = log_2(3).value();
std::cout << "log_2(3) mod 5 = " << log << std::endl;
Of these, trial multiplication is more general, requires [bigo](/p/) time and [bigo](1) storage.
The baby-step giant step algorithm requires [bigo]([radic] p) time and [bigo]([radic] p) storage,
and is slightly less general as the base must be coprime to the the modulus.
Let's illustrate this with a few examples: Suppose we wish to compute log[sub 2](3) mod 4.
Since 2[super /x/] = 3 mod 4 has no solution, the result is undefined.
boost::optional<int> l = trial_multiplication_discrete_log(2, 3, 4);
if (!l)
{
std::cout << "log_2(3) mod 4 is undefined!\n";
}
The baby-step giant-step algorithm is less polite when the base and the modulus are not coprime:
try
{
auto log_2 = bsgs_discrete_log(2, 4);
}
catch(std::exception const & e)
{
// e.what() is gonna tell you 2 and 4 are not coprime:
std::cout << e.what() << std::endl;
}
The baby-step giant-step discrete log will *never* compute a logarithm when the base and modulus are not coprime,
because it relies on the existence of modular multiplicative inverses.
However, discrete logarithms can exist even when the base and modulus share a common divisor greater than 1.
For example, since 2[super 1] = 2 mod 4, log[sub 2](2) = 1.
Trial multiplication successfully recovers this value, and `bsgs_discrete_log` blows up.
[endsect]
[section References]
Wagstaff, Samuel S., ['The Joy of Factoring], Vol. 68. American Mathematical Soc., 2013.
[endsect]
[endsect]
[/
Copyright 2018 Nick Thompson.
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE_1_0.txt or copy at
http://www.boost.org/LICENSE_1_0.txt).
]

View File

@ -1,171 +0,0 @@
/*
* (C) Copyright Nick Thompson 2018.
* Use, modification and distribution are subject to the
* Boost Software License, Version 1.0. (See accompanying file
* LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
*
* Two methods of computing the discrete logarithm over the multiplicative group of integers mod p.
*/
#ifndef BOOST_INTEGER_DISCRETE_LOG_HPP
#define BOOST_INTEGER_DISCRETE_LOG_HPP
#include <stdexcept>
#include <limits>
#include <sstream>
#include <unordered_map>
#include <boost/throw_exception.hpp>
#include <boost/optional.hpp>
#include <boost/multiprecision/integer.hpp>
#include <boost/integer/common_factor_rt.hpp>
#include <boost/integer/mod_inverse.hpp>
namespace boost { namespace integer {
// base^^x = a mod p <-> x = log_base(a) mod p
template<class Z>
boost::optional<Z> trial_multiplication_discrete_log(Z base, Z arg, Z modulus)
{
if (base <= 1)
{
std::ostringstream oss;
oss << "The base b is " << base << ", but must be > 1.\n";
BOOST_THROW_EXCEPTION(std::domain_error(oss.str()));
}
if (modulus < 3)
{
std::ostringstream oss;
oss << "The modulus must be > 2, but is " << modulus << ".\n";
BOOST_THROW_EXCEPTION(std::domain_error(oss.str()));
}
if (arg < 1)
{
std::ostringstream oss;
oss << "The argument must be > 0, but is " << arg << ".\n";
BOOST_THROW_EXCEPTION(std::domain_error(oss.str()));
}
if (base >= modulus || arg >= modulus)
{
if (base >= modulus)
{
std::ostringstream oss;
oss << "Error computing the discrete log: The base " << base
<< " is greater than the modulus " << modulus
<< ". Are the arguments in the wrong order?";
BOOST_THROW_EXCEPTION(std::domain_error(oss.str()));
}
if (arg >= modulus)
{
std::ostringstream oss;
oss << "Error computing the discrete log: The argument " << arg
<< " is greater than the modulus " << modulus
<< ". Are the arguments in the wrong order?";
BOOST_THROW_EXCEPTION(std::domain_error(oss.str()));
}
}
if (arg == 1)
{
return 0;
}
Z s = 1;
for (Z i = 1; i < modulus; ++i)
{
s = (s * base) % modulus;
if (s == arg)
{
// Maybe a bit trivial assertion. But still a negligible fraction of the total compute time.
BOOST_ASSERT(arg == boost::multiprecision::powm(base, i, modulus));
return i;
}
}
return {};
}
template<class Z>
class bsgs_discrete_log
{
public:
bsgs_discrete_log(Z base, Z modulus) : m_p{modulus}, m_base{base}
{
using std::numeric_limits;
static_assert(numeric_limits<Z>::is_integer,
"The baby-step, giant-step discrete log works on integral types.\n");
if (base <= 1)
{
BOOST_THROW_EXCEPTION(std::logic_error("The base must be > 1.\n"));
}
if (modulus < 3)
{
BOOST_THROW_EXCEPTION(std::logic_error("The modulus must be > 2.\n"));
}
if (base >= modulus)
{
BOOST_THROW_EXCEPTION(std::logic_error("Error computing the discrete log: Are your arguments in the wrong order?\n"));
}
m_root_p = boost::multiprecision::sqrt(modulus);
if (m_root_p*m_root_p != modulus)
{
m_root_p += 1;
}
boost::optional<Z> x = mod_inverse(base, modulus);
if (!x)
{
Z d = boost::integer::gcd(base, modulus);
std::ostringstream oss;
oss << "The gcd of the base " << base << " and the modulus " << modulus << " is " << d
<< ", which is not equal 1; hence the discrete log is not guaranteed to exist.\n"
<< "This breaks the baby-step giant step algorithm.\n"
<< "If you don't require existence for all inputs, use trial multiplication.\n";
BOOST_THROW_EXCEPTION(std::logic_error(oss.str()));
}
m_inv_base_pow_m = boost::multiprecision::powm(x.value(), m_root_p, modulus);
m_lookup_table.reserve(m_root_p);
// Now the expensive part:
Z k = 1;
for (Z j = 0; j < m_root_p; ++j)
{
m_lookup_table.emplace(k, j);
k = k*base % modulus;
}
}
boost::optional<Z> operator()(Z arg) const
{
Z ami = m_inv_base_pow_m;
Z k = arg % m_p;
if(k == 0)
{
return {};
}
for (Z i = 0; i < m_lookup_table.size(); ++i)
{
auto it = m_lookup_table.find(k);
if (it != m_lookup_table.end())
{
Z log_b_arg = (i*m_root_p + it->second) % m_p;
// This computation of the modular exponentiation is laughably quick relative to computing the discrete log.
// Why not put an assert here for our peace of mind?
BOOST_ASSERT(arg == boost::multiprecision::powm(m_base, log_b_arg, m_p));
return log_b_arg;
}
ami = (ami*m_inv_base_pow_m) % m_p;
k = k * ami % m_p;
}
return {};
}
private:
Z m_p;
Z m_base;
Z m_root_p;
Z m_inv_base_pow_m;
std::unordered_map<Z, Z> m_lookup_table;
};
}}
#endif

View File

@ -16,7 +16,6 @@ test-suite integer
[ run integer_mask_test.cpp ]
[ run static_log2_test.cpp ]
[ run static_min_max_test.cpp ]
[ run discrete_log_test.cpp ]
[ run extended_euclidean_test.cpp ]
[ run mod_inverse_test.cpp ]
[ compile integer_traits_include_test.cpp ]

View File

@ -1,125 +0,0 @@
/*
* (C) Copyright Nick Thompson 2018.
* Use, modification and distribution are subject to the
* Boost Software License, Version 1.0. (See accompanying file
* LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
*/
#define BOOST_TEST_MODULE discrete_log_test
#include <boost/test/included/unit_test.hpp>
#include <boost/integer/discrete_log.hpp>
#include <boost/math/special_functions/prime.hpp>
using boost::integer::trial_multiplication_discrete_log;
using boost::integer::bsgs_discrete_log;
template<class Z>
void test_trial_multiplication_discrete_log()
{
std::cout << "Testing basic trial multiplication discrete logarithm on type " << boost::typeindex::type_id<Z>().pretty_name() << "\n";
boost::optional<Z> x = trial_multiplication_discrete_log<Z>(2, 1, 3);
BOOST_CHECK_EQUAL(0, x.value());
x = trial_multiplication_discrete_log<Z>(2, 2, 3);
BOOST_CHECK_EQUAL(1, x.value());
x = trial_multiplication_discrete_log<Z>(2, 1, 4);
BOOST_CHECK_EQUAL(0, x.value());
x = trial_multiplication_discrete_log<Z>(2, 2, 4);
BOOST_CHECK_EQUAL(1, x.value());
// No solution to 2^^x mod 4 = 3:
x = trial_multiplication_discrete_log<Z>(2, 3, 4);
BOOST_TEST(!x);
x = trial_multiplication_discrete_log<Z>(7, 7, 41);
BOOST_CHECK_EQUAL(1, x.value());
x = trial_multiplication_discrete_log<Z>(7, 8, 41);
BOOST_CHECK_EQUAL(2, x.value());
x = trial_multiplication_discrete_log<Z>(7, 15, 41);
BOOST_CHECK_EQUAL(3, x.value());
x = trial_multiplication_discrete_log<Z>(7, 23, 41);
BOOST_CHECK_EQUAL(4, x.value());
x = trial_multiplication_discrete_log<Z>(7, 38, 41);
BOOST_CHECK_EQUAL(5, x.value());
x = trial_multiplication_discrete_log<Z>(7, 20, 41);
BOOST_CHECK_EQUAL(6, x.value());
Z k = 1;
for (Z i = 0; i < 20; ++i)
{
x = trial_multiplication_discrete_log<Z>(7, k, 41);
BOOST_CHECK_EQUAL(i, x.value());
k = (7*k) % 41;
}
}
template<class Z>
void test_bsgs_discrete_log()
{
std::cout << "Testing basic baby-step giant-step discrete logarithm on type " << boost::typeindex::type_id<Z>().pretty_name() << "\n";
bsgs_discrete_log<Z> dl_7(7, 41);
BOOST_CHECK_EQUAL(dl_7(7).value(), 1);
BOOST_CHECK_EQUAL(dl_7(8).value(), 2);
BOOST_CHECK_EQUAL(dl_7(15).value(), 3);
BOOST_CHECK_EQUAL(dl_7(23).value(), 4);
BOOST_CHECK_EQUAL(dl_7(38).value(), 5);
BOOST_CHECK_EQUAL(dl_7(20).value(), 6);
}
template<class Z>
void test_trial_multiplication_with_prime_modulus()
{
std::cout << "Testing trial multiplication with prime modulus on type " << boost::typeindex::type_id<Z>().pretty_name() << "\n";
// There are roughly 10,000 primes in the boost.math prime table,
// so this test could run indefinitely. I leave the syntax to get the test to run
// for years as commented-out code, since ideally we would test every input.
for (Z i = 0; i < 10 /*boost::math::max_prime*/; ++i)
{
Z modulus = boost::math::prime(i);
for (Z base = 2; base < modulus; ++base)
{
for (Z arg = 1; arg < modulus; ++arg)
{
boost::optional<Z> dl = trial_multiplication_discrete_log(base, arg, modulus);
if (dl)
{
BOOST_CHECK_EQUAL(arg, boost::multiprecision::powm(base, dl.value(), modulus));
}
}
}
}
}
template<class Z>
void test_bsgs_with_prime_modulus()
{
std::cout << "Testing baby-step, giant-step with prime modulus on type " << boost::typeindex::type_id<Z>().pretty_name() << "\n";
for (Z i = 0; i < 10 /*boost::math::max_prime*/; ++i)
{
Z p = boost::math::prime(i);
for (Z j = 2; j < p; ++j)
{
bsgs_discrete_log<Z> dl_j(j, p);
for (Z k = 1; k < p; ++k)
{
boost::optional<Z> dl = dl_j(k);
if (dl)
{
BOOST_CHECK_EQUAL(k, boost::multiprecision::powm(j, dl.value(), p));
}
}
}
}
}
BOOST_AUTO_TEST_CASE(discrete_log_test)
{
test_trial_multiplication_discrete_log<size_t>();
//test_bsgs_discrete_log<long long>();
//test_trial_multiplication_with_prime_modulus<long long>();
//test_bsgs_with_prime_modulus<long long>();
}

View File

@ -26,7 +26,7 @@ void test_extended_euclidean()
{
for (Z n = m; n > 0; --n)
{
boost::integer::euclidean_result_t u = extended_euclidean(m, n);
boost::integer::euclidean_result_t<Z> u = extended_euclidean(m, n);
int256_t gcdmn = gcd(m, n);
int256_t x = u.x;
int256_t y = u.y;