0

I am trying to check if some compressed public key corresponds to an elliptic curve equation (secp256k1). As far as I know it should be valid once the following equation is fulfill y^2 = x^3 + ax + b or y^2 % p = (x^3 +ax +b) % p. Supposing that I have the following key:

pubkey = 027d550bc2384fd76a47b8b0871165395e4e4d5ab9cb4ee286d1c60d074d7d60ef

I am able to extract x-coordinate (do to it in this case I strip 02), in theory to calculate y without sqrt operation (due to losing precision) we can do in this case: y = (x^exp) % p, where exp = (p+1)/4 based on https://crypto.stackexchange.com/questions/101142/proof-that-user-compressed-public-key-corresponds-the-curve-equation-secp256k1

Now based on how I calculate y:

bmp::uint1024_t const y = bmp::powm(x, pp, p);

//or

bmp::uint1024_t const yy = (x^pp) % p;

I have other results, which impact later computations, generally the second example gives correct final result, but it seems, that for some reasons it doesn't work as it should... for power operation it gives the following result:

bmp::pow(x, pp): 5668936922878426254536308284732873549115769122415677675761389126416312181579**1**
x^pp: 5668936922878426254536308284732873549115769122415677675761389126416312181579**0**

Even in python3 code for x^pp I have the same result as the second one, so should I use something other than boost::multiprecision ? or do these computation in other way ?

Code can be test here: https://wandbox.org/permlink/JQ3ipCq6yQjptUet

#include <numeric>
#include <iostream>
#include <string>

#include <boost/multiprecision/cpp_int.hpp>

namespace bmp = boost::multiprecision;

bool verify(std::string const& address, std::size_t const stripped_prefix_size)
{
    auto is_address_correct{false};
    bmp::uint1024_t const p = bmp::uint1024_t{"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"} % 4;//3 % 4;//{"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"};
    bmp::uint1024_t const a{"0x0000000000000000000000000000000000000000000000000000000000000000"};
    bmp::uint1024_t const b{"0x0000000000000000000000000000000000000000000000000000000000000007"};

    bmp::uint1024_t x{std::string{"0x"} + address.substr(2, address.size() - stripped_prefix_size)};

        
        auto const right = (bmp::pow(x, 3) + (a * x) + b) % p;
        //bmp::uint1024_t const y = bmp::sqrt(right) % p;
        bmp::uint1024_t pp = (p + 1) / 4;
        bmp::uint1024_t const y = bmp::powm(x, pp, p);
        bmp::uint1024_t const yy = (x^pp) % p;//bmp::powm(x, pp, p);
        auto const left = bmp::powm(y, 2, p);
        auto const left2 = bmp::powm(yy, 2, p);

        std::cout << "x: " << x << std::endl;
        std::cout << "y pow pp: " << bmp::pow(x, pp.convert_to<int>()) << std::endl;
        std::cout << "    y^pp: " << bmp::uint1024_t{x^pp} << std::endl;
        std::cout << "yy mod p: " << yy << std::endl;
        std::cout << " y mod p: " << y << std::endl;
        std::cout << "yy: " << yy << std::endl;
        std::cout << "right: " << right << std::endl; 
        std::cout << " left: " << left << std::endl;
        std::cout << "left2: " << left2 << std::endl;
        is_address_correct = (left == right);

    return is_address_correct;
}

int main()
{
    auto const res = verify("027d550bc2384fd76a47b8b0871165395e4e4d5ab9cb4ee286d1c60d074d7d60ef", 2);
    std::cout << "\nis valid: " << res << std::endl;
 
    return 0;
}

Output:

x: 56689369228784262545363082847328735491157691224156776757613891264163121815791
y pow pp: 56689369228784262545363082847328735491157691224156776757613891264163121815791
    y^pp: 56689369228784262545363082847328735491157691224156776757613891264163121815790

yy mod p: 2

 y mod p: 0

yy: 2

right: 1

 left: 0

left2: 1

is valid: 0
President James K. Polk
  • 40,516
  • 21
  • 95
  • 125
bladzio
  • 414
  • 3
  • 15
  • Can you give a more specific technical problem than "it doesn't work as it should"? – tadman Jul 26 '22 at 07:35
  • can you use openssl ? I could write an answer that uses ossl 3.0 to do this. – carce-bo Jul 26 '22 at 11:43
  • For some reason you are essentially defining `p` as 3, because the real p is 3 mod 4 and you compute `p` as ` bmp::uint1024_t const p = bmp::uint1024_t{"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"} % 4;` – President James K. Polk Jul 26 '22 at 11:53
  • 1
    That `p` mod 4 == 3 is a relevant fact because it means you can compute the modular square root of something mod `p` by raising it to the (p+1)/4 power. However, you never need to compute `p` mod 4. – President James K. Polk Jul 26 '22 at 11:54
  • President James K. Polk ahh ok, so I wrongly interpreted it. Thanks for the answer. – bladzio Jul 26 '22 at 14:48
  • carce-bo sure if you can do it, I would be keen to see solution which uses openssl – bladzio Jul 26 '22 at 14:49

1 Answers1

0

For the compressed key

027d550bc2384fd76a47b8b0871165395e4e4d5ab9cb4ee286d1c60d074d7d60ef

the uncompressed representation is

047d550bc2384fd76a47b8b0871165395e4e4d5ab9cb4ee286d1c60d074d7d60effbb6217403fe57ff1b2f84f74086b413c7682027bd6ddde4538c340ba1a25638

i.e.

x = 0x7d550bc2384fd76a47b8b0871165395e4e4d5ab9cb4ee286d1c60d074d7d60ef = 56689369228784262545363082847328735491157691224156776757613891264163121815791
y = 0xfbb6217403fe57ff1b2f84f74086b413c7682027bd6ddde4538c340ba1a25638 = 113852322045593354727100676608445520152048120867463853258291211042951302108728

This is also confirmed by the following code using the Boost library:

bool verify(std::string const& address, std::size_t const stripped_prefix_size)
{
    auto is_address_correct{false};
    
    bmp::uint1024_t const p = bmp::uint1024_t{"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"};
    bmp::uint1024_t const a{"0x0000000000000000000000000000000000000000000000000000000000000000"};
    bmp::uint1024_t const b{"0x0000000000000000000000000000000000000000000000000000000000000007"};

    bmp::uint1024_t x{std::string{"0x"} + address.substr(2, address.size() - stripped_prefix_size)};
    bmp::uint1024_t right = (bmp::powm(x, 3, p) + (a * x) + b) % p;     
    bmp::uint1024_t y = bmp::powm(right, (p + 1) / 4, p); // even, i.e. equals the searched y value because of leading 0x02 byte
    bmp::uint1024_t left = bmp::powm(y, 2, p);
 
    std::cout << "x: " << x << std::endl;           // x: 56689369228784262545363082847328735491157691224156776757613891264163121815791
    std::cout << "y: " << y << std::endl;           // y: 113852322045593354727100676608445520152048120867463853258291211042951302108728
    std::cout << "right: " << right << std::endl;   // right: 33769945388650438579771708095049232540048570303667364755388658443270938208149 
    std::cout << "left: " << left << std::endl;     // left: 33769945388650438579771708095049232540048570303667364755388658443270938208149
    is_address_correct = (left == right);

    return is_address_correct;
}

The code contains the following fixes/considerations:

  • When calculating y, not x but right must be applied.
  • The solution has two values -y and +y, s. here. Because of the leading 0x02 byte, that value must be used which has an even parity in the positive, and this is true here for +y.
  • The value for p must not be taken modulo 4 (see comment by President James K. Polk). The congruence of the modulus to 3 modulo 4 is only relevant for finding the correct solution path. A more detailed explanation can be found here.
Topaco
  • 40,594
  • 4
  • 35
  • 62
  • btw if the leading byte will be 0x03 the formula to calculate is different ? if so how it should look like ? just want to make sure with leading byte 0x02 when we calculating right we additionally apply mod p operation for x ^ 3, that's correct ? – bladzio Jul 27 '22 at 05:22
  • @bladzio - Generally: For `-y`, the positive value `p-y` is applied. One of the two positive values `+y, p-y` is even, the other odd (since `p` is prime, not equal to 2 and therefore odd). If the leading byte is 0x02, the searched `y` is the even value, for 0x03 the odd. – Topaco Jul 27 '22 at 07:58
  • @bladzio - I replaced `(bmp::pow(x,3)+...)%p` with `(bmp::powm(x,3,p)+...)%p` purely as a precaution to avoid too large intermediate values (modulo is distributive). But you can also use `(bmp::pow(x,3)+...)%p` (for the `x` here). This has nothing to do with the leading byte (not sure if this is what you mean in the last part). – Topaco Jul 27 '22 at 08:02
  • so ok, if solution has two values -y and +y it means I guess I need to change y type from bmp::uint1024_t to bmp::int1024_t right ? How about uncompressed keys ? Did I do sth wrong again ? https://wandbox.org/permlink/hXj67zNarWlXYkxe – bladzio Jul 27 '22 at 10:14
  • @bladzio - Note that `p-y` is positive, look [here](https://crypto.stackexchange.com/a/60771). Regarding your key `0x0411952303f0f1f1...`, this does not seem to be a valid (uncompressed) public key for secp256k1 (at least bitcoin-ts cannot import it). Check this again. Anyway, this is beyond the scope of this question and you should ask a new question if necessary. – Topaco Jul 27 '22 at 11:16
  • Topaco, ok so, it is only about that after calculating y, -y is also correct value (which belongs to the curve) and then y or p-y should be even and the second one odd ? I guess for the leading byte 0x02 y should be even and p-y odd, and for 0x03 y should be odd and p-y even, that's correct ? – bladzio Jul 28 '22 at 05:24
  • @bladzio - Yes to the first question, no to the second. For 0x02, `y` does not have to be even and `p-y` odd, it can be the other way around. For 0x02 you simply take the even value of both as the searched value. Analogous for 0x03, here you take the odd value. That's all. Look on the web, there are many posts about this topic, e.g. https://medium.com/asecuritysite-when-bob-met-alice/02-03-or-04-so-what-are-compressed-and-uncompressed-public-keys-6abcb57efeb6 – Topaco Jul 28 '22 at 06:18
  • in this statement - "For 0x02, y does not have to be even and p-y odd, it can be the other way around." did you mean for 0x04 ? :) – bladzio Jul 28 '22 at 07:51
  • @bladzio - No, I mean 0x02. For an `x` there are two possible `y`, one is even, the other odd. If only the `x` value is given, this does not uniquely determine the key. You must therefore add the information whether the key uses the even `y` (0x02) or the odd one (0x03). Only this is unique. Look carefully at the link posted in the last comment. – Topaco Jul 28 '22 at 09:27