1
0
Fork 0
forked from lthn/blockchain

clsag first implementation + tests

This commit is contained in:
sowle 2022-07-22 05:36:38 +02:00
parent e4293af219
commit 53caed6d38
No known key found for this signature in database
GPG key ID: C07A24B2D89D49FC
6 changed files with 458 additions and 43 deletions

View file

@ -6,14 +6,162 @@
// This file contains implementation of CLSAG (s.a. https://eprint.iacr.org/2019/654.pdf by Goodel at el)
//
#include "clsag.h"
//#include "misc_log_ex.h"
#include "../currency_core/crypto_config.h"
namespace crypto
{
#define DBG_VAL_PRINT(x) (void(0)) // std::cout << #x ": " << x << std::endl
#define DBG_PRINT(x) (void(0)) // std::cout << x << std::endl
bool generate_CLSAG_GG(const hash& m, const std::vector<CLSAG_GG_input_ref_t>& ring, const point_t& pseudo_out_amount_commitment, const key_image& ki,
const scalar_t& secret_x, const scalar_t& secret_f, CLSAG_GG_signature& sig)
const scalar_t& secret_x, const scalar_t& secret_f, uint64_t secret_index, CLSAG_GG_signature& sig)
{
return false;
size_t ring_size = ring.size();
CRYPTO_CHECK_AND_THROW_MES(ring_size > 0, "ring size is zero");
CRYPTO_CHECK_AND_THROW_MES(secret_index < ring_size, "secret_index is out of range");
// calculate key images
point_t ki_base = hash_helper_t::hp(ring[secret_index].stealth_address);
point_t key_image = secret_x * ki_base;
CRYPTO_CHECK_AND_THROW_MES(key_image == point_t(ki), "key image 0 mismatch");
point_t K1_div8 = (c_scalar_1div8 * secret_f) * ki_base;
K1_div8.to_public_key(sig.K1);
point_t K1 = K1_div8;
K1.modify_mul8();
// calculate aggregation coefficients
hash_helper_t::hs_t hsc(3 + 2 * ring_size);
hsc.add_scalar(m);
for(size_t i = 0; i < ring_size; ++i)
{
hsc.add_pub_key(ring[i].stealth_address);
hsc.add_pub_key(ring[i].amount_commitment);
}
hsc.add_point(pseudo_out_amount_commitment);
hsc.add_key_image(ki);
hash input_hash = hsc.calc_hash_no_reduce();
hsc.add_32_chars(CRYPTO_HDS_CLSAG_GG_LAYER_0);
hsc.add_hash(input_hash);
scalar_t agg_coeff_0 = hsc.calc_hash();
DBG_VAL_PRINT(agg_coeff_0);
hsc.add_32_chars(CRYPTO_HDS_CLSAG_GG_LAYER_1);
hsc.add_hash(input_hash);
scalar_t agg_coeff_1 = hsc.calc_hash();
DBG_VAL_PRINT(agg_coeff_1);
// calculate aggregate pub keys
std::vector<point_t> W_pub_keys;
W_pub_keys.reserve(ring_size);
for(size_t i = 0; i < ring_size; ++i)
{
W_pub_keys.emplace_back(agg_coeff_0 * point_t(ring[i].stealth_address) + agg_coeff_1 * (point_t(ring[i].amount_commitment).modify_mul8() - pseudo_out_amount_commitment));
DBG_VAL_PRINT(W_pub_keys[i]);
}
// aggregate secret key
scalar_t w_sec_key = agg_coeff_0 * secret_x + agg_coeff_1 * secret_f;
// calculate aggregate key image
point_t W_key_image = agg_coeff_0 * key_image + agg_coeff_1 * K1;
DBG_VAL_PRINT(W_key_image);
// initial commitment
scalar_t alpha = scalar_t::random();
hsc.add_32_chars(CRYPTO_HDS_CLSAG_GG_CHALLENGE);
hsc.add_hash(input_hash);
hsc.add_point(alpha * c_point_G);
hsc.add_point(alpha * ki_base);
scalar_t c_prev = hsc.calc_hash(); // c_{secret_index + 1}
sig.r.clear();
sig.r.reserve(ring_size);
for(size_t i = 0; i < ring_size; ++i)
sig.r.emplace_back(scalar_t::random());
for(size_t j = 0, i = (secret_index + 1) % ring_size; j < ring_size - 1; ++j, i = (i + 1) % ring_size)
{
if (i == 0)
sig.c = c_prev; // c_0
hsc.add_32_chars(CRYPTO_HDS_CLSAG_GG_CHALLENGE);
hsc.add_hash(input_hash);
hsc.add_point(sig.r[i] * c_point_G + c_prev * W_pub_keys[i]);
hsc.add_point(sig.r[i] * hash_helper_t::hp(ring[i].stealth_address) + c_prev * W_key_image);
c_prev = hsc.calc_hash(); // c_{i + 1}
}
if (secret_index == 0)
sig.c = c_prev;
sig.r[secret_index] = alpha - c_prev * w_sec_key;
return true;
}
bool verify_CLSAG_GG(const hash& m, const std::vector<CLSAG_GG_input_ref_t>& ring, const crypto::public_key& pseudo_out_amount_commitment, const key_image& ki,
const CLSAG_GG_signature& sig)
{
size_t ring_size = ring.size();
CRYPTO_CHECK_AND_THROW_MES(ring_size > 0, "ring size is zero");
CRYPTO_CHECK_AND_THROW_MES(ring_size == sig.r.size(), "ring size != r size");
point_t key_image(ki);
CRYPTO_CHECK_AND_THROW_MES(key_image.is_in_main_subgroup(), "key image 0 does not belong to the main subgroup");
point_t pseudo_out_amount_commitment_pt(pseudo_out_amount_commitment);
pseudo_out_amount_commitment_pt.modify_mul8();
// calculate aggregation coefficients
hash_helper_t::hs_t hsc(3 + 2 * ring_size);
hsc.add_scalar(m);
for(size_t i = 0; i < ring_size; ++i)
{
hsc.add_pub_key(ring[i].stealth_address);
hsc.add_pub_key(ring[i].amount_commitment);
}
hsc.add_point(pseudo_out_amount_commitment_pt);
hsc.add_key_image(ki);
hash input_hash = hsc.calc_hash_no_reduce();
hsc.add_32_chars(CRYPTO_HDS_CLSAG_GG_LAYER_0);
hsc.add_hash(input_hash);
scalar_t agg_coeff_0 = hsc.calc_hash();
DBG_VAL_PRINT(agg_coeff_0);
hsc.add_32_chars(CRYPTO_HDS_CLSAG_GG_LAYER_1);
hsc.add_hash(input_hash);
scalar_t agg_coeff_1 = hsc.calc_hash();
DBG_VAL_PRINT(agg_coeff_1);
// calculate aggregate pub keys
std::vector<point_t> W_pub_keys;
W_pub_keys.reserve(ring_size);
for(size_t i = 0; i < ring_size; ++i)
{
W_pub_keys.emplace_back(agg_coeff_0 * point_t(ring[i].stealth_address) + agg_coeff_1 * (point_t(ring[i].amount_commitment).modify_mul8() - pseudo_out_amount_commitment_pt));
DBG_VAL_PRINT(W_pub_keys[i]);
}
// calculate aggregate key image
point_t W_key_image = agg_coeff_0 * point_t(ki) + agg_coeff_1 * point_t(sig.K1).modify_mul8();
DBG_VAL_PRINT(W_key_image);
scalar_t c_prev = sig.c;
for(size_t i = 0; i < ring_size; ++i)
{
hsc.add_32_chars(CRYPTO_HDS_CLSAG_GG_CHALLENGE);
hsc.add_hash(input_hash);
hsc.add_point(sig.r[i] * c_point_G + c_prev * W_pub_keys[i]);
hsc.add_point(sig.r[i] * hash_helper_t::hp(ring[i].stealth_address) + c_prev * W_key_image);
c_prev = hsc.calc_hash(); // c_{i + 1}
}
return c_prev == sig.c;
}
} // namespace crypto

View file

@ -10,7 +10,7 @@
namespace crypto
{
// GG stands for double layers (ring dimentions) both with respect to group element G
// 2-CLSAG signature where both dimensions are with respect to the group element G (that's why 'GG')
struct CLSAG_GG_signature
{
scalar_t c;
@ -19,6 +19,16 @@ namespace crypto
};
inline bool operator==(const CLSAG_GG_signature& lhs, const CLSAG_GG_signature& rhs)
{
return
lhs.c == rhs.c &&
lhs.r == rhs.r &&
lhs.K1 == rhs.K1;
}
inline bool operator!=(const CLSAG_GG_signature& lhs, const CLSAG_GG_signature& rhs) { return !(lhs == rhs); }
struct CLSAG_GG_input_ref_t
{
CLSAG_GG_input_ref_t(const public_key& stealth_address, const public_key& amount_commitment)
@ -29,6 +39,10 @@ namespace crypto
};
bool generate_CLSAG_GG(const hash& m, const std::vector<CLSAG_GG_input_ref_t>& ring, const point_t& pseudo_out_amount_commitment, const key_image& ki,
const scalar_t& secret_x, const scalar_t& secret_f, CLSAG_GG_signature& sig);
const scalar_t& secret_x, const scalar_t& secret_f, uint64_t secret_index, CLSAG_GG_signature& sig);
bool verify_CLSAG_GG(const hash& m, const std::vector<CLSAG_GG_input_ref_t>& ring, const public_key& pseudo_out_amount_commitment, const key_image& ki,
const CLSAG_GG_signature& sig);
} // namespace crypto

View file

@ -9,3 +9,7 @@
#define CRYPTO_HDS_OUT_AMOUNT_MASK "ZANO_HDS_OUT_AMOUNT_MASK_______"
#define CRYPTO_HDS_OUT_BLINDING_MASK "ZANO_HDS_OUT_BLINDING_MASK_____"
#define CRYPTO_HDS_OUT_CONCEALING_POINT "ZANO_HDS_OUT_CONCEALING_POINT__"
#define CRYPTO_HDS_CLSAG_GG_LAYER_0 "ZANO_HDS_CLSAG_GG_LAYER_ZERO___"
#define CRYPTO_HDS_CLSAG_GG_LAYER_1 "ZANO_HDS_CLSAG_GG_LAYER_ONE____"
#define CRYPTO_HDS_CLSAG_GG_CHALLENGE "ZANO_HDS_CLSAG_GG_CHALLENGE____"

View file

@ -16,6 +16,7 @@
#include "crypto/crypto-sugar.h"
#include "crypto/range_proofs.h"
#include "../core_tests/random_helper.h"
#include "crypto_torsion_elements.h"
using namespace crypto;
@ -490,6 +491,7 @@ struct test_keeper_t
////////////////////////////////////////////////////////////////////////////////
#include "crypto_tests_ml2s.h"
#include "crypto_tests_range_proofs.h"
#include "crypto_tests_clsag.h"
////////////////////////////////////////////////////////////////////////////////
@ -1272,35 +1274,6 @@ TEST(crypto, calc_lsb_32)
TEST(crypto, torsion_elements)
{
// let ty = -sqrt((-sqrt(D+1)-1) / D), is_neg(ty) == false
// canonical serialization sig order EC point
// 26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05 0 8 (sqrt(-1)*ty, ty)
// 0000000000000000000000000000000000000000000000000000000000000000 0 4 (sqrt(-1), 0)
// c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a 0 8 (sqrt(-1)*ty, -ty)
// ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f 0 2 (0, -1)
// c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa 1 8 (-sqrt(-1)*ty, -ty)
// 0000000000000000000000000000000000000000000000000000000000000080 1 4 (-sqrt(-1), 0)
// 26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85 1 8 (-sqrt(-1)*ty, ty)
struct canonical_torsion_elements_t
{
const char* string;
bool sign;
uint8_t order;
uint8_t incorrect_order_0;
uint8_t incorrect_order_1;
};
canonical_torsion_elements_t canonical_torsion_elements[] = {
{"26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05", false, 8, 4, 7},
{"0000000000000000000000000000000000000000000000000000000000000000", false, 4, 2, 3},
{"c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a", false, 8, 4, 7},
{"ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", false, 2, 1, 3},
{"c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", true, 8, 4, 7},
{"0000000000000000000000000000000000000000000000000000000000000080", true, 4, 2, 3},
{"26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85", true, 8, 4, 7}
};
point_t tor;
for (size_t i = 0, n = sizeof canonical_torsion_elements / sizeof canonical_torsion_elements[0]; i < n; ++i)
@ -1318,16 +1291,6 @@ TEST(crypto, torsion_elements)
}
// non-canonical elements should not load at all (thanks to the checks in ge_frombytes_vartime)
const char* noncanonical_torsion_elements[] = {
"0100000000000000000000000000000000000000000000000000000000000080", // (-0, 1)
"ECFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", // (-0, -1)
"EEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F", // (0, 2*255-18)
"EEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", // (-0, 2*255-18)
"EDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F", // (sqrt(-1), 2*255-19)
"EDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" // (-sqrt(-1), 2*255-19)
};
for (size_t i = 0, n = sizeof noncanonical_torsion_elements / sizeof noncanonical_torsion_elements[0]; i < n; ++i)
{
ASSERT_FALSE(tor.from_string(noncanonical_torsion_elements[i]));

View file

@ -0,0 +1,230 @@
// Copyright (c) 2022 Zano Project (https://zano.org/)
// Copyright (c) 2022 sowle (val@zano.org, crypto.sowle@gmail.com)
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#pragma once
#include <crypto/clsag.h>
inline std::ostream& operator<<(std::ostream& ss, const CLSAG_GG_signature &sig)
{
ss << "CLSAG_GG: c: " << sig.c << ENDL;
ss << " r: ";
size_t i = 0;
for(auto el: sig.r)
{
if (i++ != 0)
ss << " ";
ss << el << ENDL;
}
ss << " K1: " << sig.K1 << ENDL;
return ss;
}
struct clsag_gg_sig_check_t
{
crypto::hash prefix_hash;
crypto::key_image ki;
std::vector<public_key> stealth_addresses;
std::vector<public_key> amount_commitments; // div 8
std::vector<CLSAG_GG_input_ref_t> ring;
crypto::public_key pseudo_output_commitment; // div 8
scalar_t secret_x;
scalar_t secret_f;
size_t secret_index;
CLSAG_GG_signature sig;
clsag_gg_sig_check_t()
{}
clsag_gg_sig_check_t& operator=(const clsag_gg_sig_check_t& rhs)
{
prefix_hash = rhs.prefix_hash;
ki = rhs.ki;
stealth_addresses = rhs.stealth_addresses;
amount_commitments = rhs.amount_commitments;
ring.clear();
for(size_t i = 0; i < stealth_addresses.size(); ++i)
ring.emplace_back(stealth_addresses[i], amount_commitments[i]);
pseudo_output_commitment = rhs.pseudo_output_commitment;
secret_x = rhs.secret_x;
secret_f = rhs.secret_f;
secret_index = rhs.secret_index;
return *this;
}
void prepare_random_data(size_t ring_size)
{
stealth_addresses.clear();
amount_commitments.clear();
ring.clear();
crypto::generate_random_bytes(sizeof prefix_hash, &prefix_hash);
stealth_addresses.reserve(ring_size);
amount_commitments.reserve(ring_size);
for(size_t i = 0; i < ring_size; ++i)
{
stealth_addresses.push_back(hash_helper_t::hp(scalar_t::random()).to_public_key());
amount_commitments.push_back(hash_helper_t::hp(scalar_t::random()).to_public_key()); // div 8
ring.emplace_back(stealth_addresses.back(), amount_commitments.back());
}
secret_x = scalar_t::random();
secret_f = scalar_t::random();
secret_index = random_in_range(0, ring_size - 1);
stealth_addresses[secret_index] = (secret_x * c_point_G).to_public_key();
ki = (secret_x * hash_helper_t::hp(stealth_addresses[secret_index])).to_key_image();
pseudo_output_commitment = (point_t(amount_commitments[secret_index]) - c_scalar_1div8 * secret_f * c_point_G).to_public_key();
}
bool generate()
{
try
{
return generate_CLSAG_GG(prefix_hash, ring, point_t(pseudo_output_commitment).modify_mul8(), ki, secret_x, secret_f, secret_index, sig);
}
catch(std::exception& e)
{
LOG_PRINT_RED(ENDL << "EXCEPTION: " << e.what(), LOG_LEVEL_0);
return false;
}
}
bool verify()
{
try
{
return verify_CLSAG_GG(prefix_hash, ring, pseudo_output_commitment, ki, sig);
}
catch(std::exception& e)
{
LOG_PRINT_RED(ENDL << "EXCEPTION: " << e.what(), LOG_LEVEL_0);
return false;
}
}
};
TEST(clsag, basics)
{
clsag_gg_sig_check_t cc;
cc.prepare_random_data(1);
ASSERT_TRUE(cc.generate());
ASSERT_TRUE(cc.verify());
cc.prepare_random_data(2);
ASSERT_TRUE(cc.generate());
ASSERT_TRUE(cc.verify());
cc.prepare_random_data(8);
ASSERT_TRUE(cc.generate());
ASSERT_TRUE(cc.verify());
cc.prepare_random_data(123);
ASSERT_TRUE(cc.generate());
ASSERT_TRUE(cc.verify());
return true;
}
TEST(clsag, bad_pub_keys)
{
clsag_gg_sig_check_t cc, cc_orig;
cc.prepare_random_data(10);
cc_orig = cc;
ASSERT_TRUE(cc.generate());
cc.ki = parse_tpod_from_hex_string<key_image>(noncanonical_torsion_elements[0]);
ASSERT_FALSE(cc.verify());
cc.ki = parse_tpod_from_hex_string<key_image>(canonical_torsion_elements[0].string); // ki not in main subgroup
ASSERT_FALSE(cc.verify());
// check all torsion elements (paranoid mode on)
for(size_t t = 0; t < sizeof canonical_torsion_elements / sizeof canonical_torsion_elements[0]; ++t)
{
point_t tor = point_t(parse_tpod_from_hex_string<public_key>(canonical_torsion_elements[t].string));
ASSERT_FALSE(tor.is_in_main_subgroup());
ASSERT_FALSE(tor.is_zero());
cc = cc_orig;
ASSERT_TRUE(cc.generate());
cc.ki = (point_t(cc.ki) + tor).to_key_image(); // ki not in main subgroup
ASSERT_FALSE(cc.verify());
// torsion component in pseudo_output_commitment should not affect protocol
cc = cc_orig;
cc.pseudo_output_commitment = (point_t(cc.pseudo_output_commitment) + tor).to_public_key();
ASSERT_TRUE(cc.generate());
ASSERT_TRUE(cc.verify());
// torsion component in amount_commitments[i] should not affect protocol
cc = cc_orig;
for(size_t i = 0; i < cc.ring.size(); ++i)
cc.amount_commitments[i] = (point_t(cc.amount_commitments[i]) + tor).to_public_key();
ASSERT_TRUE(cc.generate());
ASSERT_TRUE(cc.verify());
// torsion component in K1 should not affect protocol
cc = cc_orig;
ASSERT_TRUE(cc.generate());
cc.sig.K1 = (point_t(cc.sig.K1) + tor).to_public_key();
ASSERT_TRUE(cc.verify());
}
return true;
}
TEST(clsag, bad_sig)
{
clsag_gg_sig_check_t cc;
// wrong prefix hash
cc.prepare_random_data(10);
ASSERT_TRUE(cc.generate());
ASSERT_TRUE(cc.verify());
cc.prefix_hash.data[5] ^= 0x7f;
ASSERT_FALSE(cc.verify());
// ring size > sig.r size
ASSERT_TRUE(cc.generate());
cc.sig.r.clear();
ASSERT_FALSE(cc.verify());
// ring size < sig.r size
ASSERT_TRUE(cc.generate());
cc.sig.r.push_back(scalar_t::random());
ASSERT_FALSE(cc.verify());
return true;
}
TEST(clsag, sig_difference)
{
int cmp_res = 0;
clsag_gg_sig_check_t cc0, cc1;
cc0.prepare_random_data(8);
cc1 = cc0; // get the same input data
{
// make sure two signatures generated with the same input and randoms are equal
random_state_test_restorer rstr; // to restore random state on exit of the scope
random_state_test_restorer::reset_random(0);
ASSERT_TRUE(cc0.generate());
random_state_test_restorer::reset_random(0);
ASSERT_TRUE(cc1.generate());
LOG_PRINT_L0("cc0: " << ENDL << cc0.sig << ", cc1: " << ENDL << cc1.sig);
ASSERT_TRUE(cc0.sig == cc1.sig);
ASSERT_TRUE(cc0.verify());
}
// make sure two signatures generated with the same input are not equal
ASSERT_TRUE(cc0.generate());
ASSERT_TRUE(cc1.generate());
ASSERT_TRUE(cc0.sig != cc1.sig);
return true;
}

View file

@ -0,0 +1,56 @@
// Copyright (c) 2021-2022 Zano Project
// Copyright (c) 2021-2022 sowle (val@zano.org, crypto.sowle@gmail.com)
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
//
#pragma once
//
// Tosion elements of ed25519 group in canonical and non-canonical form
//
// (partly inspired by "Taming the many EdDSAs" by Chalkias et al https://eprint.iacr.org/2020/1244.pdf)
//
namespace crypto
{
// let ty = -sqrt((-sqrt(D+1)-1) / D), is_neg(ty) == false
// canonical serialization sig order EC point
// 26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05 0 8 (sqrt(-1)*ty, ty)
// 0000000000000000000000000000000000000000000000000000000000000000 0 4 (sqrt(-1), 0)
// c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a 0 8 (sqrt(-1)*ty, -ty)
// ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f 0 2 (0, -1)
// c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa 1 8 (-sqrt(-1)*ty, -ty)
// 0000000000000000000000000000000000000000000000000000000000000080 1 4 (-sqrt(-1), 0)
// 26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85 1 8 (-sqrt(-1)*ty, ty)
struct canonical_torsion_elements_t
{
const char* string;
bool sign;
uint8_t order;
uint8_t incorrect_order_0;
uint8_t incorrect_order_1;
};
canonical_torsion_elements_t canonical_torsion_elements[] = {
{"26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05", false, 8, 4, 7},
{"0000000000000000000000000000000000000000000000000000000000000000", false, 4, 2, 3},
{"c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a", false, 8, 4, 7},
{"ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", false, 2, 1, 3},
{"c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", true, 8, 4, 7},
{"0000000000000000000000000000000000000000000000000000000000000080", true, 4, 2, 3},
{"26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85", true, 8, 4, 7}
};
// non-canonical elements (should not load at all thanks to the checks in ge_frombytes_vartime)
const char* noncanonical_torsion_elements[] = {
"0100000000000000000000000000000000000000000000000000000000000080", // (-0, 1)
"ECFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", // (-0, -1)
"EEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F", // (0, 2*255-18)
"EEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", // (-0, 2*255-18)
"EDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F", // (sqrt(-1), 2*255-19)
"EDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" // (-sqrt(-1), 2*255-19)
};
} // namespace crypto