Skip to content

Commit

Permalink
[SQUASHED] ecdsa key recovery with test(s). (#346)
Browse files Browse the repository at this point in the history
init commit.

Recover pubkey works!

Make gcc happy.

Make gcc happy (again)

gcc fix.

don't use y = 0 as error condition. instead use (x = 0, y = 0) as failure return.
  • Loading branch information
suyash67 authored Apr 25, 2023
1 parent 6ef5df5 commit 70cdaff
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 1 deletion.
6 changes: 6 additions & 0 deletions cpp/src/barretenberg/crypto/ecdsa/ecdsa.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ template <typename Fr, typename G1> struct key_pair {
struct signature {
std::array<uint8_t, 32> r;
std::array<uint8_t, 32> s;
uint8_t v;
};

template <typename Hash, typename Fq, typename Fr, typename G1>
signature construct_signature(const std::string& message, const key_pair<Fr, G1>& account);

template <typename Hash, typename Fq, typename Fr, typename G1>
typename G1::affine_element recover_public_key(const std::string& message, const signature& sig);

template <typename Hash, typename Fq, typename Fr, typename G1>
bool verify_signature(const std::string& message,
const typename G1::affine_element& public_key,
Expand All @@ -39,12 +43,14 @@ template <typename B> inline void read(B& it, signature& sig)
{
read(it, sig.r);
read(it, sig.s);
read(it, sig.v);
}

template <typename B> inline void write(B& buf, signature const& sig)
{
write(buf, sig.r);
write(buf, sig.s);
write(buf, sig.v);
}

template <typename B> inline void read(B& it, key_pair<secp256k1::fr, secp256k1::g1>& keypair)
Expand Down
46 changes: 45 additions & 1 deletion cpp/src/barretenberg/crypto/ecdsa/ecdsa.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,50 @@ TEST(ecdsa, verify_signature_secp256r1_sha256)
EXPECT_EQ(result, true);
}

TEST(ecdsa, recover_public_key_secp256k1_sha256)
{
std::string message = "The quick brown dog jumped over the lazy fox.";

crypto::ecdsa::key_pair<secp256k1::fr, secp256k1::g1> account;
account.private_key = secp256k1::fr::random_element();
account.public_key = secp256k1::g1::one * account.private_key;

crypto::ecdsa::signature signature =
crypto::ecdsa::construct_signature<Sha256Hasher, secp256k1::fq, secp256k1::fr, secp256k1::g1>(message, account);

bool result = crypto::ecdsa::verify_signature<Sha256Hasher, secp256k1::fq, secp256k1::fr, secp256k1::g1>(
message, account.public_key, signature);

auto recovered_public_key =
crypto::ecdsa::recover_public_key<Sha256Hasher, secp256k1::fq, secp256k1::fr, secp256k1::g1>(message,
signature);

EXPECT_EQ(result, true);
EXPECT_EQ(recovered_public_key, account.public_key);
}

TEST(ecdsa, recover_public_key_secp256r1_sha256)
{
std::string message = "The quick brown dog jumped over the lazy fox.";

crypto::ecdsa::key_pair<secp256r1::fr, secp256r1::g1> account;
account.private_key = secp256r1::fr::random_element();
account.public_key = secp256r1::g1::one * account.private_key;

crypto::ecdsa::signature signature =
crypto::ecdsa::construct_signature<Sha256Hasher, secp256r1::fq, secp256r1::fr, secp256r1::g1>(message, account);

bool result = crypto::ecdsa::verify_signature<Sha256Hasher, secp256r1::fq, secp256r1::fr, secp256r1::g1>(
message, account.public_key, signature);

auto recovered_public_key =
crypto::ecdsa::recover_public_key<Sha256Hasher, secp256r1::fq, secp256r1::fr, secp256r1::g1>(message,
signature);

EXPECT_EQ(result, true);
EXPECT_EQ(recovered_public_key, account.public_key);
}

std::vector<uint8_t> HexToBytes(const std::string& hex)
{
std::vector<uint8_t> bytes;
Expand Down Expand Up @@ -131,7 +175,7 @@ TEST(ecdsa, verify_signature_secp256r1_sha256_NIST_1)
0xef, 0x97, 0xb2, 0x18, 0xe9, 0x6f, 0x17, 0x5a, 0x3c, 0xcd, 0xda, 0x2a, 0xcc, 0x05, 0x89, 0x03,
};

crypto::ecdsa::signature sig{ r, s };
crypto::ecdsa::signature sig{ r, s, 27 };
std::vector<uint8_t> message_vec =
HexToBytes("5905238877c77421f73e43ee3da6f2d9e2ccad5fc942dcec0cbd25482935faaf416983fe165b1a045ee2bcd2e6dca3bdf46"
"c4310a7461f9a37960ca672d3feb5473e253605fb1ddfd28065b53cb5858a8ad28175bf9bd386a5e471ea7a65c17cc934a9"
Expand Down
81 changes: 81 additions & 0 deletions cpp/src/barretenberg/crypto/ecdsa/ecdsa_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,90 @@ signature construct_signature(const std::string& message, const key_pair<Fr, G1>
Fr s_fr = (z + r_fr * account.private_key) / k;

Fr::serialize_to_buffer(s_fr, &sig.s[0]);

// compute recovery_id: given R = (x, y)
// 0: y is even && x < |Fr|
// 1: y is odd && x < |Fr|
// 2: y is even && |Fr| <= x < |Fq|
// 3: y is odd && |Fr| <= x < |Fq|
// v = offset + recovery_id
Fq r_fq = Fq(R.x);
bool is_r_finite = (uint256_t(r_fq) == uint256_t(r_fr));
bool y_parity = uint256_t(R.y).get_bit(0);
constexpr uint8_t offset = 27;

int value = offset + y_parity + static_cast<uint8_t>(2) * !is_r_finite;
ASSERT(value <= UINT8_MAX);
sig.v = static_cast<uint8_t>(value);
return sig;
}

template <typename Hash, typename Fq, typename Fr, typename G1>
typename G1::affine_element recover_public_key(const std::string& message, const signature& sig)
{
using serialize::read;
uint256_t r_uint;
uint256_t s_uint;
uint8_t v_uint;

const auto* r_buf = &sig.r[0];
const auto* s_buf = &sig.s[0];
const auto* v_buf = &sig.v;
read(r_buf, r_uint);
read(s_buf, s_uint);
read(v_buf, v_uint);

// We need to check that r and s are in Field according to specification
if ((r_uint >= Fr::modulus) || (s_uint >= Fr::modulus)) {
throw_or_abort("r or s value exceeds the modulus");
}
if ((r_uint == 0) || (s_uint == 0)) {
throw_or_abort("r or s value is zero");
}

// Check that v must either be in {27, 28, 29, 30}
Fr r = Fr(r_uint);
Fr s = Fr(s_uint);
Fq r_fq = Fq(r_uint);
bool is_r_finite = true;

if ((v_uint == 27) || (v_uint == 28)) {
ASSERT(uint256_t(r) == uint256_t(r_fq));
} else if ((v_uint == 29) || (v_uint == 30)) {
ASSERT(uint256_t(r) < uint256_t(r_fq));
is_r_finite = false;
} else {
throw_or_abort("v value is not in {27, 28, 29, 30}");
}

// Decompress the x-coordinate r_uint to get two possible R points
// The first uncompressed R point is selected when r < |Fr|
// The second uncompressed R point is selected when |Fr| <= r < |Fq|
// Note that the second condition can occur with probability 1/2^128 so its highly unlikely.
auto uncompressed_points = G1::affine_element::from_compressed_unsafe(r_uint);
typename G1::affine_element point_R = uncompressed_points[!is_r_finite];

// Negate the y-coordinate point of R based on the parity of v
bool y_parity_R = uint256_t(point_R.y).get_bit(0);
if ((v_uint & 1) == y_parity_R) {
point_R.y = -point_R.y;
}

// Start key recovery algorithm
std::vector<uint8_t> message_buffer;
std::copy(message.begin(), message.end(), std::back_inserter(message_buffer));
auto ev = Hash::hash(message_buffer);
Fr z = Fr::serialize_from_buffer(&ev[0]);

Fr r_inv = r.invert();

Fr u1 = -(z * r_inv);
Fr u2 = s * r_inv;

typename G1::affine_element recovered_public_key(typename G1::element(point_R) * u2 + G1::one * u1);
return recovered_public_key;
}

template <typename Hash, typename Fq, typename Fr, typename G1>
bool verify_signature(const std::string& message, const typename G1::affine_element& public_key, const signature& sig)
{
Expand Down
13 changes: 13 additions & 0 deletions cpp/src/barretenberg/ecc/groups/affine_element.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,19 @@ template <typename Fq, typename Fr, typename Params> class alignas(64) affine_el
typename CompileTimeEnabled = std::enable_if_t<(BaseField::modulus >> 255) == uint256_t(0), void>>
static constexpr affine_element from_compressed(const uint256_t& compressed) noexcept;

/**
* @brief Reconstruct a point in affine coordinates from compressed form.
* @details #LARGE_MODULUS_AFFINE_POINT_COMPRESSION Point compression is implemented for curves of a prime
* field F_p with p being 256 bits.
* TODO(Suyash): Check with kesha if this is correct.
*
* @param compressed compressed point
* @return constexpr affine_element
*/
template <typename BaseField = Fq,
typename CompileTimeEnabled = std::enable_if_t<(BaseField::modulus >> 255) == uint256_t(1), void>>
static constexpr std::array<affine_element, 2> from_compressed_unsafe(const uint256_t& compressed) noexcept;

constexpr affine_element& operator=(const affine_element& other) noexcept;

constexpr affine_element& operator=(affine_element&& other) noexcept;
Expand Down
22 changes: 22 additions & 0 deletions cpp/src/barretenberg/ecc/groups/affine_element.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@ template <typename G1> class test_affine_element : public testing::Test {
}
}

static void test_point_compression_unsafe()
{
for (size_t i = 0; i < 100; i++) {
affine_element P = affine_element(element::random_element());
uint256_t compressed = uint256_t(P.x);

// Note that we do not check the point Q_points[1] because its highly unlikely to hit a point P on the curve
// such that r < P.x < q.
std::array<affine_element, 2> Q_points = affine_element::from_compressed_unsafe(compressed);
EXPECT_EQ(P, Q_points[0]);
}
}

// Regression test to ensure that the point at infinity is not equal to its coordinate-wise reduction, which may lie
// on the curve, depending on the y-coordinate.
// TODO: add corresponding typed test class
Expand Down Expand Up @@ -91,6 +104,15 @@ TYPED_TEST(test_affine_element, point_compression)
}
}

TYPED_TEST(test_affine_element, point_compression_unsafe)
{
if constexpr (TypeParam::Fq::modulus.data[3] >= 0x4000000000000000ULL) {
TestFixture::test_point_compression_unsafe();
} else {
GTEST_SKIP();
}
}

// Regression test to ensure that the point at infinity is not equal to its coordinate-wise reduction, which may lie
// on the curve, depending on the y-coordinate.
TEST(affine_element, infinity_ordering_regression)
Expand Down
27 changes: 27 additions & 0 deletions cpp/src/barretenberg/ecc/groups/affine_element_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,33 @@ constexpr affine_element<Fq, Fr, T> affine_element<Fq, Fr, T>::from_compressed(c
return affine_element<Fq, Fr, T>(x, y);
}

template <class Fq, class Fr, class T>
template <typename BaseField, typename CompileTimeEnabled>
constexpr std::array<affine_element<Fq, Fr, T>, 2> affine_element<Fq, Fr, T>::from_compressed_unsafe(
const uint256_t& compressed) noexcept
{
auto get_y_coordinate = [](const uint256_t& x_coordinate) {
Fq x = Fq(x_coordinate);
Fq y2 = (x.sqr() * x + T::b);
if constexpr (T::has_a) {
y2 += (x * T::a);
}
return y2.sqrt();
};

uint256_t x_1 = compressed;
uint256_t x_2 = compressed + Fr::modulus;
auto [is_quadratic_remainder_1, y_1] = get_y_coordinate(x_1);
auto [is_quadratic_remainder_2, y_2] = get_y_coordinate(x_2);

auto output_1 = is_quadratic_remainder_1 ? affine_element<Fq, Fr, T>(Fq(x_1), y_1)
: affine_element<Fq, Fr, T>(Fq::zero(), Fq::zero());
auto output_2 = is_quadratic_remainder_2 ? affine_element<Fq, Fr, T>(Fq(x_2), y_2)
: affine_element<Fq, Fr, T>(Fq::zero(), Fq::zero());

return { output_1, output_2 };
}

template <class Fq, class Fr, class T>
constexpr affine_element<Fq, Fr, T> affine_element<Fq, Fr, T>::operator+(
const affine_element<Fq, Fr, T>& other) const noexcept
Expand Down

0 comments on commit 70cdaff

Please sign in to comment.