简体   繁体   中英

How to find a point of an elliptic curve in crypto++ (with given x)? Or how to compute a root in finite field? Or root of Polynomial Ring?

Is there any way in crypto++ to check if an EC contains a point with a given x-coordinate?

One solution would be solving the EC polynomial for given x. One side of the equation already done in code. I 'just' need to compute the root of it (over a finite field)

//clang++ -o ectest ectest.cpp -lcryptopp
#include "cryptopp/eccrypto.h"
#include "cryptopp/oids.h"

int main(int argc, char* argv[]) {
    using namespace CryptoPP;
    typedef DL_GroupParameters_EC<ECP> GroupParameters;
    typedef DL_GroupParameters_EC<ECP>::Element Element;
    
    GroupParameters group;
    group.Initialize(ASN1::secp256k1());
        
    //I want to check if the EC has a point with x-cooridnate x=13
    Integer x = 13;
    
    Integer ecmod = group.GetCurve().GetField().GetModulus();
    ModularArithmetic ma = ModularArithmetic(ecmod);

    //the following equation need to be solved:
    //y^2 = x^3 + Ax + B \mod ecmod
    Integer x3 = ma.Multiply(x,x);
    x3 = ma.Multiply(x3,x);
    Integer xa = ma.Multiply(group.GetCurve().GetA(),x);
    Integer xb = group.GetCurve().GetB();
    Integer sr = ma.Add(x3,xa);
    sr = ma.Add(sr,xb);

    //y^2 = sr
    //how to compute the root out of sr?
    //or is there any other way to check if the EC contains a point with x?
}

Yes, you can. The library has support for compression and decompression of points. During the decompression, the library must find y if it can.

The header of the DecodePoint

bool ECP::DecodePoint(ECP::Point &P, BufferedTransformation &bt, size_t encodedPointLen) const

The error returned with

if (Jacobi(P.y, p) !=1)
    return false;

You can construct your point as compressed, or better use this part from DecodePoint; just add the below lines into your code;

//include these
#include "cryptopp/nbtheory.h"
#include <iostream>

using namespace CryptoPP;

Integer getSquareRoot( Integer &y, Integer &mod) {

   if (Jacobi(y, mod) !=1)
        return -1;

    return y  = ModularSquareRoot(y, mod);
     
}

    // In the main
    //After sr = ma.Add(sr,xb);

    Integer y  = getSquareRoot(sr, ecmod);
    if ( y != -1 ) {
        std::cout << IntToString<Integer>(y) << std::endl;
    } else {         
        std::cout << IntToString<Integer>(sr);
        std::cout << " has not square root to " << ecmod << std::endl;
    }

outputs

20267456347483554069520440766283645831919514026818877192810320909941447705364


Note : if you print with

std::cout << y << std::endl;

There will be a dot in the end. The library warns about this .

/// The output includes the suffix \a h (for hex), \a . (\a dot, for dec)
/// and \a o (for octal). There is currently no way to suppress the suffix.
/// \details If you want to print an Integer without the suffix or using an arbitrary base, then
///   use IntToString<Integer>().

Although you already have valid and simple answer , I still decided to implement my own solution from scratch in pure C++, because your task is very interesting.

My quite large solution doesn't use Crypto++, but tries to implement all algorithms from scratch in C++. It is intended only for curiosity and scientific and educational purpose. It is not intended to be as fast and as simple as pure Crypto++ solution.

It is known that if modulus is any prime of any form, then square root can be found through well known and very fast algorithm of Tonelli Shanks . It is main part of code that follows below.

Also if modulus is a prime of form p % 4 == 3 then its root also can be found easily by formula root = n^((p + 1) / 4) % p . Which is much shorter than Shanks Tonelli algorithm, but Shanks Tonelli works for all cases, even when p % 4 != 3 .

Besides mentioned above algorithms I also used Modular Exponentation by Squaring and Euler's Criterion .

Params of curve Secp256k1 were taken from here and here :

p      = 0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F
a      = 0
b      = 7
base_x = 0x79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798
base_y = 0x483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8
q      = 0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141

My code can be run straight away after compilation and it shows all timings and results on console, see example output after code below. For test purpose I iterate all X in range [0..50] and show which of them give valid root for Y. As it is known around 50% values of X will give correct solution.

To compile my program besides standard C++ library I use only GMP library for arbitrary integer arithmetics. It can be installed in Linux through sudo apt install libgmp-dev . And in Windows after install VCPKG package manager you can do vcpkg install gmp , plus Windows will need to provide manually vckpg include path and libraries for linking. I tested compilation and work of program in CLang and MSVC.

Also click Try it online! link below to test run of program online on GodBolt servers.

Try it online!

#include <cstdint>
#include <vector>
#include <functional>
#include <string>
#include <stdexcept>
#include <iostream>
#include <iomanip>
#include <chrono>
#include <mutex>

#include <gmpxx.h>

#define ASSERT_MSG(cond, msg) { if (!(cond)) throw std::runtime_error("Assertion (" #cond ") failed at line " + std::to_string(__LINE__) + "! Msg: '" + std::string(msg) + "'."); }
#define ASSERT(cond) ASSERT_MSG(cond, "")
#define COUT(code) { std::unique_lock<std::recursive_mutex> lock(cout_mux); std::cout code; std::cout << std::flush; }
#define LN { COUT(<< "LN " << __LINE__ << std::endl); }
#define JOIN0(x, y) x##y
#define JOIN(x, y) JOIN0(x, y)
#define TEST(name) static void JOIN(Test_##name, __LINE__)(); static bool const JOIN(reg_test_##name, __LINE__) = []{ g_tests.push_back(std::make_pair(#name, &JOIN(Test_##name, __LINE__))); return true; }(); static void JOIN(Test_##name, __LINE__)()
#define RUN_TESTS() { for (auto & [name, f]: g_tests) { std::cout << "Test " << name << " " << std::flush; auto tb = Time(); f(); tb = Time() - tb; std::cout << std::fixed << std::setprecision(3) << tb << " sec" << std::endl; } }

using u8  = uint8_t;
using u16 = uint16_t;
using i16 = int16_t;
using u32 = uint32_t;
using i32 = int32_t;
using u64 = uint64_t;
using i64 = int64_t;

static std::recursive_mutex cout_mux;
static std::vector<std::pair<std::string, std::function<void()>>> g_tests;

template <typename T> struct DWordOf;
template <> struct DWordOf<mpz_class> { using type = mpz_class; };

template <typename T>
u32 ToU32(T const & N) { return mpz_class(N).get_ui(); }

double Time() {
    static auto const gtb = std::chrono::high_resolution_clock::now();
    return std::chrono::duration_cast<std::chrono::duration<double>>(
        std::chrono::high_resolution_clock::now() - gtb).count();
}

template <typename T>
std::vector<T> Uniquize(std::vector<T> vec) {
    std::sort(vec.begin(), vec.end());
    vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
    return std::move(vec);
}

template <typename T, typename DT = typename DWordOf<T>::type>
DT DTMul(T const & a, T const & b) {
    return DT(a) * b;
}

template <typename T>
T PowMod(T a, T b, T const & c)  {
    // https://en.wikipedia.org/wiki/Modular_exponentiation#Right-to-left_binary_method
    using DT = typename DWordOf<T>::type;
    T r = 1;
    while (b != 0) {
        if (ToU32<T>(b) & 1)
            r = T(DTMul(r, a) % c);
        a = T(DTMul(a, a) % c);
        b >>= 1;
    }
    return r;
}

template <typename T>
int EulerCriterion(T const & n, T const & p) {
    // https://en.wikipedia.org/wiki/Euler%27s_criterion
    if (n == 0)
        return 0;
    auto const val = PowMod<T>(n, (p - 1) >> 1, p);
    ASSERT_MSG(val == 1 || val == p - 1, "val " + IntToStr(val) + " p " + IntToStr(p));
    return val == 1 ? 1 : -1;
}

template <typename T>
std::vector<T> SquareRoot_Mod4_3(T n, T const & p) {
    using DT = typename DWordOf<T>::type;
    ASSERT(p % 4 == 3);
    n %= p;
    T const r = PowMod<T>(n, (p + 1) >> 2, p);
    if (DTMul(r, r) % p == n % p)
        return Uniquize<T>(std::vector<T>({r, p - r}));
    return {};
}

template <typename T>
std::vector<T> SquareRoot_TonelliShanks(T n, T p) {
    // https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm
    // see also https://eprint.iacr.org/2020/1407.pdf
    using DT = typename DWordOf<T>::type;
    n %= p;
    if (n == 0)
        return {0};
    if (p == 2)
        return {n};
    {
        auto const ec = EulerCriterion<T>(n, p);
        ASSERT(ec == 1 || ec == -1);
        if (ec == -1)
            return {};
    }
    if (p % 4 == 3) {
        return SquareRoot_Mod4_3<T>(n, p);
    }
    T Q = p - 1, S = 0;
    while ((Q & 1) == 0) {
        Q >>= 1;
        ++S;
    }
    T z = 0;
    for (z = 2; z < p; ++z) {
        if (EulerCriterion<T>(z, p) == -1)
            break;
    }
    ASSERT_MSG(z < p, "n " + IntToStr(n) + ", p " + IntToStr(p));
    T M = S, c = PowMod<T>(z, Q, p), t = PowMod<T>(n, Q, p), R = PowMod<T>(n, (Q + 1) / 2, p), r = 0;
    ASSERT(M < u32(-1));
    while (true) {
        if (t == 0) {
            r = 0;
            break;
        }
        if (t == 1) {
            r = R;
            break;
        }
        T sqt = t;
        u32 i = 0;
        for (i = 1; i < M; ++i) {
            sqt = T(DTMul<T>(sqt, sqt) % p);
            if (sqt == 1)
                break;
        }
        ASSERT(i < M);
        //ASSERT(M - i - 1 < BiSize(T));
        T b = PowMod<T>(c, T(1) << ToU32(M - i - 1), p);
        M = i;
        c = T(DTMul<T>(b, b) % p);
        t = T(DTMul<T>(t, c) % p);
        R = T(DTMul<T>(R, b) % p);
    }
    ASSERT(DTMul<T>(r, r) % p == n);
    return Uniquize<T>(std::vector<T>({r, p - r}));
}

mpz_class MpzFromStr(std::string const & s, size_t base = 0) {
    mpz_class r;
    mpz_set_str(r.get_mpz_t(), s.c_str(), int(base));
    return r;
}

std::string MpzToStr(mpz_class n, size_t base = 10) {
    std::string s(mpz_sizeinbase(n.get_mpz_t(), base) + 5, '\x00');
    mpz_get_str(s.data(), base, n.get_mpz_t());
    return s.c_str();
}

template <typename T>
std::string IntToStr(T const & n) { return MpzToStr(n); }

TEST(Main) {
    COUT(<< std::endl);
    // https://en.bitcoin.it/wiki/Secp256k1
    // https://www.secg.org/sec2-v2.pdf
    // secp256k1 params
    mpz_class const
        p  = MpzFromStr("0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F"),
        a  = 0,
        b  = 7,
        bx = MpzFromStr("0x79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798"),
        by = MpzFromStr("0x483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8"),
        q  = MpzFromStr("0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141");
    auto EllipticCurve = [&](auto const & x) {
        return mpz_class((x * x * x + a * x + b) % p);
    };
    auto const p_len = IntToStr(p).size();
    for (u32 ix = 0; ix <= 50; ++ix) {
        mpz_class const x = ix, ec_value = EllipticCurve(x);
        auto roots = SquareRoot_TonelliShanks<mpz_class>(ec_value, p);
        if (roots.empty())
            continue;
        ASSERT(DTMul(roots.at(0), roots.at(0)) % p == ec_value);
        COUT(<< "x = " << std::setw(3) << ix << ", y = " << std::setw(p_len) << IntToStr(roots.at(0)) << std::endl);
    }
}

int main() {
    try {
        RUN_TESTS();
        return 0;
    } catch (std::exception const & ex) {
        COUT(<< "Exception: " << ex.what() << std::endl);
        return -1;
    }
}

Console output:

Test Main 
x =   1, y =  29896722852569046015560700294576055776214335159245303116488692907525646231534
x =   2, y =  46580984542418694471253469931035885126779956971428003686700937153791839982430
x =   3, y =  21320899557911560362763253855565071047772010424612278905734793689199612115787
x =   4, y =  40508090799132825824753983223610497876805216745196355809233758402754120847507
x =   6, y =  19112057249303445409876026535760519114630369653212530612662492210011362204224
x =   8, y =  24055953608229461237864090884685780858714989825500507741703654067262135535697
x =  12, y =  31068864722486242021761838950999795945745157936084027215252040495978276421796
x =  13, y =  20267456347483554069520440766283645831919514026818877192810320909941447705364
x =  14, y =  44647516152956573151520437476900496747965187933683819148138195218441524686495
x =  16, y =   6086653149631013477190265492692874244435390487330439650246572165529255539543
x =  20, y =  20676141886993640210986884816394413846392747094661403271705441051670760124834
x =  22, y =  17384355509374335117451332188362459136087449825697451396219196747631138909398
x =  25, y =   1102551495006820024539592786237854453433258304779672753607085440481650452602
x =  27, y =  12150456207077715994570159310959166905435600970349836880310459338926638464144
x =  32, y =  25427332243426016000490343799988059539558736656305693852522166340470154069724
x =  33, y =  51173090447386785350599579912891887007487842540657484998530120736905211706882
x =  38, y =   9646363146228555846652555429881922614278153944052502987030828554623605767881
x =  39, y =  28197313212501135724540236089620685652765900040943429810123028520443371359923
x =  43, y =  56572214809736663028893280596571268084211571988499307500846727553699476699834
x =  44, y =  29878230023088480380896492456768186169213097553238389007631075402030495033678
x =  45, y =   9813211421898343856404272161982377846064062305226732735279529255438243343429
x =  47, y =  37644263369555793562992696144870056406951060257194647817532744844486199964398
x =  48, y =  22158093920271983590355401047321466603843412217042649359409719523363101162922
0.064 sec

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM