diff --git a/crypto/Makefile b/crypto/Makefile index 1d83619de..627814136 100644 --- a/crypto/Makefile +++ b/crypto/Makefile @@ -124,6 +124,7 @@ SRCS += zkp_ecdsa.c SRCS += zkp_bip340.c SRCS += cardano.c SRCS += tls_prf.c +SRCS += hash_to_curve.c OBJS = $(SRCS:.c=.o) OBJS += secp256k1-zkp.o diff --git a/crypto/hash_to_curve.c b/crypto/hash_to_curve.c new file mode 100644 index 000000000..e0ca547fc --- /dev/null +++ b/crypto/hash_to_curve.c @@ -0,0 +1,413 @@ +/** + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +#include +#include +#include + +#include "bignum.h" +#include "ecdsa.h" +#include "memzero.h" +#include "nist256p1.h" +#include "sha2.h" + +#include "hash_to_curve.h" + +// https://www.rfc-editor.org/rfc/rfc9380.html#name-hash_to_field-implementatio +static bool hash_to_field(const uint8_t *msg, size_t msg_len, + const uint8_t *dst, // domain separation tag + const size_t dst_len, size_t expansion_len, + const bignum256 *prime, + bool expand(const uint8_t *, size_t, const uint8_t *, + size_t, uint8_t *, size_t), + bignum256 *out, size_t out_len) { + const size_t max_expansion_len = 64; + if (expansion_len > max_expansion_len) { + // Not supported by this implementation + return false; + } + + const size_t expanded_msg_length = out_len * expansion_len; + uint8_t expanded_msg[expanded_msg_length]; + memzero(expanded_msg, sizeof(expanded_msg)); + + if (!expand(msg, msg_len, dst, dst_len, expanded_msg, expanded_msg_length)) { + return false; + } + + uint8_t raw_number[max_expansion_len]; + memzero(raw_number, sizeof(raw_number)); + bignum512 bn_number = {0}; + + for (size_t i = 0; i < out_len; i++) { + memcpy(raw_number + (max_expansion_len - expansion_len), + expanded_msg + i * expansion_len, expansion_len); + + bn_read_be_512(raw_number, &bn_number); + bn_reduce(&bn_number, prime); + bn_copy_lower(&bn_number, &out[i]); + bn_mod(&out[i], prime); + } + + memzero(expanded_msg, sizeof(expanded_msg)); + memzero(raw_number, sizeof(raw_number)); + memzero(&bn_number, sizeof(bn_number)); + + return true; +} + +// Simplified Shallue-van de Woestijne-Ulas Method +// https://www.rfc-editor.org/rfc/rfc9380.html#name-simplified-shallue-van-de-w +// Algorithm assumptions: +// * z is a non-square modulo p +// * z != -1 modulo p +// * x^2 + a * x + b - z is an irreducible polynomial modulo p +// * (b/(z*a))^2 + a * (b/(z*a)) + b is a square modulo p +// * z is not zero +// * a is not zero +// * b is not zero +// * p is at least 6 +// Implementation assumptions: +// * p is a prime +// * 2**256 - 2**224 <= prime <= 2**256 +// * p % 4 == 3 +static bool simple_swu(const bignum256 *u, const bignum256 *a, + const bignum256 *b, const bignum256 *p, + const bignum256 *z, int sign_function(const bignum256 *), + curve_point *point) { + if (bn_is_zero(a) || bn_is_zero(b) || (p->val[0] % 4 != 3)) { + return false; + } + + // c1 = -b / a + bignum256 c1 = {0}; + bn_copy(a, &c1); + bn_subtract(p, &c1, &c1); + bn_inverse(&c1, p); + bn_multiply(b, &c1, p); + bn_mod(&c1, p); + + // c2 = -1 / z + bignum256 c2 = {0}; + bn_copy(z, &c2); + bn_subtract(p, &c2, &c2); + bn_inverse(&c2, p); + bn_mod(&c2, p); + + // t1 = z * u^2 + bignum256 t1 = {0}; + bn_copy(u, &t1); + bn_multiply(&t1, &t1, p); + bn_mod(&t1, p); + bn_multiply(z, &t1, p); + bn_mod(&t1, p); + + // t2 = t1^2 + bignum256 t2 = {0}; + bn_copy(&t1, &t2); + bn_multiply(&t2, &t2, p); + bn_mod(&t2, p); + + // x1 = t1 + t2 + bignum256 x1 = {0}; + bn_copy(&t1, &x1); + bn_add(&x1, &t2); + bn_mod(&x1, p); + + // x1 = inv0(1) + bn_inverse(&x1, p); + + // e1 = x1 == 0 + int e1 = bn_is_zero(&x1); + + // x1 = x1 + 1 + bn_addi(&x1, 1); + bn_mod(&x1, p); + + // x1 = CMOV(x1, c2, e1) + bn_cmov(&x1, e1, &c2, &x1); + memzero(&c2, sizeof(c2)); + + // x1 = x1 * c1 + bn_multiply(&c1, &x1, p); + memzero(&c1, sizeof(c1)); + bn_mod(&x1, p); + + // gx1 = x1^2 + bignum256 gx1 = {0}; + bn_copy(&x1, &gx1); + bn_multiply(&x1, &gx1, p); + bn_mod(&gx1, p); + + // gx1 = gx1 + A + bn_add(&gx1, a); + bn_mod(&gx1, p); + + // gx1 = gx1 * x1 + bn_multiply(&x1, &gx1, p); + bn_mod(&gx1, p); + + // gx1 = gx1 + B + bn_add(&gx1, b); + bn_mod(&gx1, p); + + // x2 = t1 * x1 + bignum256 x2 = {0}; + bn_copy(&t1, &x2); + bn_multiply(&x1, &x2, p); + bn_mod(&x2, p); + + // t2 = t1 * t2 + bn_multiply(&t1, &t2, p); + memzero(&t1, sizeof(t1)); + bn_mod(&t2, p); + + // gx2 = gx1 * t2 + bignum256 gx2 = {0}; + bn_copy(&gx1, &gx2); + bn_multiply(&t2, &gx2, p); + memzero(&t2, sizeof(t2)); + bn_mod(&gx2, p); + + // e2 = is_square(gx1) + int e2 = bn_legendre(&gx1, p) >= 0; + + // x = CMOV(x2, x1, e2) + bignum256 x = {0}; + bn_cmov(&x, e2, &x1, &x2); + memzero(&x1, sizeof(x1)); + memzero(&x2, sizeof(x2)); + + // y2 = CMOV(gx2, gx1, e2) + bignum256 y2 = {0}; + bn_cmov(&y2, e2, &gx1, &gx2); + memzero(&gx1, sizeof(gx1)); + memzero(&gx2, sizeof(gx2)); + + // y = sqrt(y2) + bignum256 y = {0}; + bn_copy(&y2, &y); + memzero(&y2, sizeof(y2)); + bn_sqrt(&y, p); // This is the slowest operation + + // e3 = sgn0(u) == sgn0(y) + int e3 = sign_function(u) == sign_function(&y); + + bignum256 minus_y = {0}; + bn_subtract(p, &y, &minus_y); + + // y = CMOV(-y, y, e3) + bn_cmov(&y, e3, &y, &minus_y); + memzero(&minus_y, sizeof(minus_y)); + + bn_copy(&x, &point->x); + bn_copy(&y, &point->y); + memzero(&x, sizeof(x)); + memzero(&y, sizeof(y)); + + return true; +} + +static void bn_read_int32(int32_t in_number, const bignum256 *prime, + bignum256 *out_number) { + if (in_number < 0) { + bn_read_uint32(-in_number, out_number); + bn_subtract(prime, out_number, out_number); + } else { + bn_read_uint32(in_number, out_number); + } +} + +// https://www.rfc-editor.org/rfc/rfc9380.html#name-encoding-byte-strings-to-el +static bool hash_to_curve(const uint8_t *msg, size_t msg_len, + const ecdsa_curve *curve, const uint8_t *suite_id, + const uint8_t suite_id_len, int z, int cofactor, + bool expand_function(const uint8_t *, size_t, + const uint8_t *, size_t, + uint8_t *, size_t), + int sign_function(const bignum256 *), + curve_point *point) { + if (cofactor != 1) { + // Not supported by this implementation + return false; + } + + bignum256 bn_z = {0}; + bn_read_int32(z, &curve->prime, &bn_z); + + bignum256 bn_a = {0}; + bn_read_int32(curve->a, &curve->prime, &bn_a); + + bignum256 u[2] = {0}; + + if (!hash_to_field(msg, msg_len, suite_id, suite_id_len, 48, &curve->prime, + expand_function, u, 2)) { + return false; + } + + curve_point point1 = {0}, point2 = {0}; + + if (!simple_swu(&u[0], &bn_a, &curve->b, &curve->prime, &bn_z, sign_function, + &point1)) { + memzero(&u[0], sizeof(u[0])); + return false; + } + memzero(&u[0], sizeof(u[0])); + + if (!simple_swu(&u[1], &bn_a, &curve->b, &curve->prime, &bn_z, sign_function, + &point2)) { + memzero(&u[1], sizeof(u[1])); + return false; + } + memzero(&u[1], sizeof(u[1])); + + point_add(curve, &point1, &point2); + + point->x = point2.x; + point->y = point2.y; + + memzero(&point1, sizeof(point1)); + memzero(&point2, sizeof(point2)); + + return true; +} + +static int sgn0(const bignum256 *a) { + // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-05#section-4.1.2 + if (bn_is_even(a)) { + return 1; + } + + return -1; +} + +// https://www.rfc-editor.org/rfc/rfc9380.html#hashtofield-expand-xmd +bool expand_message_xmd_sha256(const uint8_t *msg, size_t msg_len, + const uint8_t *dst, // domain separation tag + size_t dst_len, uint8_t *output, + size_t output_len) { + if (dst_len > 255) { + return false; + } + + if ((output_len > 65535) || (output_len > 255 * SHA256_DIGEST_LENGTH)) { + return false; + } + + const uint8_t zero_block[SHA256_BLOCK_LENGTH] = {0}; + const uint8_t output_len_bytes[2] = {(output_len >> 8) & 255, + output_len & 255}; + const uint8_t dst_len_bytes[1] = {dst_len & 255}; + const uint8_t zero[1] = {0}; + + SHA256_CTX ctx = {0}; + sha256_Init(&ctx); + + // Z_pad = I2OSP(0, s_in_bytes) + sha256_Update(&ctx, zero_block, sizeof(zero_block)); + + // msg + sha256_Update(&ctx, msg, msg_len); + + // l_i_b_str = I2OSP(len_in_bytes, 2) + sha256_Update(&ctx, output_len_bytes, sizeof(output_len_bytes)); + + // I2OSP(0, 1) + sha256_Update(&ctx, zero, sizeof(zero)); + + // DST_prime = DST || I2OSP(len(DST), 1) + sha256_Update(&ctx, dst, dst_len); + sha256_Update(&ctx, dst_len_bytes, sizeof(dst_len_bytes)); + + uint8_t first_digest[SHA256_DIGEST_LENGTH] = {0}; // b_0 + sha256_Final(&ctx, first_digest); + + uint8_t current_digest[SHA256_DIGEST_LENGTH] = {0}; // b_i + + size_t output_position = 0; + size_t remaining_output_length = output_len; + int i = 1; + + while (remaining_output_length > 0) { + const uint8_t i_bytes[1] = {i & 255}; + + // strxor(b_0, b_(i - 1)) + for (size_t j = 0; j < sizeof(current_digest); j++) { + current_digest[j] ^= first_digest[j]; + } + + sha256_Init(&ctx); + + // strxor(b_0, b_(i - 1)) + sha256_Update(&ctx, current_digest, sizeof(current_digest)); + + // I2OSP(i, 1) + sha256_Update(&ctx, i_bytes, sizeof(i_bytes)); + + // DST_prime = DST || I2OSP(len(DST), 1) + sha256_Update(&ctx, dst, dst_len); + sha256_Update(&ctx, dst_len_bytes, sizeof(dst_len_bytes)); + + sha256_Final(&ctx, current_digest); + + const size_t copy_length = remaining_output_length > SHA256_DIGEST_LENGTH + ? SHA256_DIGEST_LENGTH + : remaining_output_length; + memcpy(output + output_position, current_digest, copy_length); + + output_position += copy_length; + remaining_output_length -= copy_length; + i++; + } + + memzero(&ctx, sizeof(ctx)); + memzero(first_digest, sizeof(first_digest)); + memzero(current_digest, sizeof(current_digest)); + + return true; +} + +bool hash_to_curve_p256(const uint8_t *msg, size_t msg_len, const uint8_t *dst, + size_t dst_len, curve_point *point) { + // https://www.rfc-editor.org/rfc/rfc9380.html#suites-p256 + // P256_XMD:SHA-256_SSWU_RO_ + if (!hash_to_curve(msg, msg_len, &nist256p1, dst, dst_len, -10, 1, + expand_message_xmd_sha256, sgn0, point)) { + return false; + } + + return true; +} + +bool hash_to_curve_optiga(const uint8_t input[32], uint8_t public_key[65]) { + char dst[] = "OPTIGA-SECRET-V0-P256_XMD:SHA-256_SSWU_RO_"; + curve_point point = {0}; + + if (!hash_to_curve_p256(input, 32, (uint8_t *)dst, sizeof(dst) - 1, &point)) { + return false; + } + + public_key[0] = 0x04; + bn_write_be(&point.x, public_key + 1); + bn_write_be(&point.y, public_key + 33); + + memzero(&point, sizeof(point)); + + return true; +} diff --git a/crypto/hash_to_curve.h b/crypto/hash_to_curve.h new file mode 100644 index 000000000..aee5d7ef8 --- /dev/null +++ b/crypto/hash_to_curve.h @@ -0,0 +1,32 @@ +/** + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __HASH_TO_CURVE_H__ +#define __HASH_TO_CURVE_H__ + +#include "ecdsa.h" + +bool expand_message_xmd_sha256(const uint8_t *msg, size_t msg_len, + const uint8_t *dst, size_t dst_len, + uint8_t *output, size_t output_len); +bool hash_to_curve_p256(const uint8_t *msg, size_t msg_len, const uint8_t *dst, + size_t dst_len, curve_point *point); +bool hash_to_curve_optiga(const uint8_t input[32], uint8_t public_key[65]); +#endif diff --git a/crypto/tests/test_check.c b/crypto/tests/test_check.c index 128667af5..2dc80ca1d 100644 --- a/crypto/tests/test_check.c +++ b/crypto/tests/test_check.c @@ -58,6 +58,7 @@ #include "ed25519-donna/ed25519-donna.h" #include "ed25519-donna/ed25519-keccak.h" #include "ed25519-donna/ed25519.h" +#include "hash_to_curve.h" #include "hmac_drbg.h" #include "memzero.h" #include "monero/monero.h" @@ -9859,6 +9860,171 @@ START_TEST(test_zkp_bip340_verify_publickey) { } END_TEST +START_TEST(test_expand_message_xmd_sha256) { + static struct { + const char *msg; + const char *dst; + const char *expected_output; + } tests[] = { + // https://www.rfc-editor.org/rfc/rfc9380.html#name-expand_message_xmdsha-256 + {"", "QUUX-V01-CS02-with-expander-SHA256-128", + "68a985b87eb6b46952128911f2a4412bbc302a9d759667f87f7a21d803f07235"}, + {"abc", "QUUX-V01-CS02-with-expander-SHA256-128", + "d8ccab23b5985ccea865c6c97b6e5b8350e794e603b4b97902f53a8a0d605615"}, + {"abcdef0123456789", "QUUX-V01-CS02-with-expander-SHA256-128", + "eff31487c770a893cfb36f912fbfcbff40d5661771ca4b2cb4eafe524333f5c1"}, + {"q128_" + "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq" + "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + "QUUX-V01-CS02-with-expander-SHA256-128", + "b23a1d2b4d97b2ef7785562a7e8bac7eed54ed6e97e29aa51bfe3f12ddad1ff9"}, + {"a512_" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaa", + "QUUX-V01-CS02-with-expander-SHA256-128", + "4623227bcc01293b8c130bf771da8c298dede7383243dc0993d2d94823958c4c"}, + {"", "QUUX-V01-CS02-with-expander-SHA256-128", + "af84c27ccfd45d41914fdff5df25293e221afc53d8ad2ac06d5e3e29485dadbee0d1215" + "87713a3e0dd4d5e69e93eb7cd4f5df4cd103e188cf60cb02edc3edf18eda8576c412b18" + "ffb658e3dd6ec849469b979d444cf7b26911a08e63cf31f9dcc541708d3491184472c2c" + "29bb749d4286b004ceb5ee6b9a7fa5b646c993f0ced"}, + {"abc", "QUUX-V01-CS02-with-expander-SHA256-128", + "abba86a6129e366fc877aab32fc4ffc70120d8996c88aee2fe4b32d6c7b6437a647e6c3" + "163d40b76a73cf6a5674ef1d890f95b664ee0afa5359a5c4e07985635bbecbac65d747d" + "3d2da7ec2b8221b17b0ca9dc8a1ac1c07ea6a1e60583e2cb00058e77b7b72a298425cd1" + "b941ad4ec65e8afc50303a22c0f99b0509b4c895f40"}, + {"abcdef0123456789", "QUUX-V01-CS02-with-expander-SHA256-128", + "ef904a29bffc4cf9ee82832451c946ac3c8f8058ae97d8d629831a74c6572bd9ebd0df6" + "35cd1f208e2038e760c4994984ce73f0d55ea9f22af83ba4734569d4bc95e18350f740c" + "07eef653cbb9f87910d833751825f0ebefa1abe5420bb52be14cf489b37fe1a72f7de2d" + "10be453b2c9d9eb20c7e3f6edc5a60629178d9478df"}, + {"q128_" + "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq" + "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + "QUUX-V01-CS02-with-expander-SHA256-128", + "80be107d0884f0d881bb460322f0443d38bd222db8bd0b0a5312a6fedb49c1bbd88fd75" + "d8b9a09486c60123dfa1d73c1cc3169761b17476d3c6b7cbbd727acd0e2c942f4dd96ae" + "3da5de368d26b32286e32de7e5a8cb2949f866a0b80c58116b29fa7fabb3ea7d520ee60" + "3e0c25bcaf0b9a5e92ec6a1fe4e0391d1cdbce8c68a"}, + {"a512_" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaa", + "QUUX-V01-CS02-with-expander-SHA256-128", + "546aff5444b5b79aa6148bd81728704c32decb73a3ba76e9e75885cad9def1d06d6792f" + "8a7d12794e90efed817d96920d728896a4510864370c207f99bd4a608ea121700ef01ed" + "879745ee3e4ceef777eda6d9e5e38b90c86ea6fb0b36504ba4a45d22e86f6db5dd43d98" + "a294bebb9125d5b794e9d2a81181066eb954966a487"}, + }; + for (size_t i = 0; i < sizeof(tests) / sizeof(*tests); i++) { + const size_t output_length = strlen(tests[i].expected_output) / 2; + uint8_t output[output_length]; + uint8_t expected_output[output_length]; + + memcpy(expected_output, fromhex(tests[i].expected_output), output_length); + + int res = expand_message_xmd_sha256( + (uint8_t *)tests[i].msg, strlen(tests[i].msg), (uint8_t *)tests[i].dst, + strlen(tests[i].dst), output, output_length); + ck_assert_int_eq(res, true); + ck_assert_mem_eq(output, expected_output, output_length); + } +} +END_TEST + +START_TEST(test_hash_to_curve_p256) { + static struct { + const char *msg; + const char *dst; + const char *expected_x; + const char *expected_y; + } tests[] = { + // https://www.rfc-editor.org/rfc/rfc9380.html#name-p256_xmdsha-256_sswu_ro_ + {"", "QUUX-V01-CS02-with-P256_XMD:SHA-256_SSWU_RO_", + "2c15230b26dbc6fc9a37051158c95b79656e17a1a920b11394ca91c44247d3e4", + "8a7a74985cc5c776cdfe4b1f19884970453912e9d31528c060be9ab5c43e8415"}, + {"abc", "QUUX-V01-CS02-with-P256_XMD:SHA-256_SSWU_RO_", + "0bb8b87485551aa43ed54f009230450b492fead5f1cc91658775dac4a3388a0f", + "5c41b3d0731a27a7b14bc0bf0ccded2d8751f83493404c84a88e71ffd424212e"}, + {"abcdef0123456789", "QUUX-V01-CS02-with-P256_XMD:SHA-256_SSWU_RO_", + "65038ac8f2b1def042a5df0b33b1f4eca6bff7cb0f9c6c1526811864e544ed80", + "cad44d40a656e7aff4002a8de287abc8ae0482b5ae825822bb870d6df9b56ca3"}, + {"q128_" + "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq" + "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + "QUUX-V01-CS02-with-P256_XMD:SHA-256_SSWU_RO_", + "4be61ee205094282ba8a2042bcb48d88dfbb609301c49aa8b078533dc65a0b5d", + "98f8df449a072c4721d241a3b1236d3caccba603f916ca680f4539d2bfb3c29e"}, + {"a512_" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaa", + "QUUX-V01-CS02-with-P256_XMD:SHA-256_SSWU_RO_", + "457ae2981f70ca85d8e24c308b14db22f3e3862c5ea0f652ca38b5e49cd64bc5", + "ecb9f0eadc9aeed232dabc53235368c1394c78de05dd96893eefa62b0f4757dc"}, + }; + for (size_t i = 0; i < sizeof(tests) / sizeof(*tests); i++) { + curve_point point = {0}; + uint8_t expected_x[32] = {0}; + uint8_t expected_y[32] = {0}; + uint8_t x[32] = {0}; + uint8_t y[32] = {0}; + + memcpy(expected_x, fromhex(tests[i].expected_x), 32); + memcpy(expected_y, fromhex(tests[i].expected_y), 32); + + int res = hash_to_curve_p256((uint8_t *)tests[i].msg, strlen(tests[i].msg), + (uint8_t *)tests[i].dst, strlen(tests[i].dst), + &point); + bn_write_be(&point.x, x); + bn_write_be(&point.y, y); + ck_assert_int_eq(res, true); + ck_assert_mem_eq(x, expected_x, 32); + ck_assert_mem_eq(y, expected_y, 32); + } +} +END_TEST + +START_TEST(test_hash_to_curve_optiga) { + static struct { + const char *input; + const char *public_key; + } tests[] = { + {"0000000000000000000000000000000000000000000000000000000000000000", + "043d2ce2e6ab3d75430c7ce3627d840fef7856bf6a1b4aa7579d583e5906bb3c897ee7a" + "af81ebe6d18a2534e563ba67bb71964d0494737e282f9a99c8370cc7ea6"}, + }; + for (size_t i = 0; i < sizeof(tests) / sizeof(*tests); i++) { + uint8_t input[32] = {0}; + uint8_t expected_public_key[65] = {0}; + uint8_t public_key[65] = {0}; + + memcpy(input, fromhex(tests[i].input), 32); + memcpy(expected_public_key, fromhex(tests[i].public_key), 65); + + int res = hash_to_curve_optiga(input, public_key); + ck_assert_int_eq(res, true); + ck_assert_mem_eq(public_key, expected_public_key, 65); + } +} +END_TEST + static int my_strncasecmp(const char *s1, const char *s2, size_t n) { size_t i = 0; while (i < n) { @@ -10181,6 +10347,12 @@ Suite *test_suite(void) { tcase_add_test(tc, test_zkp_bip340_verify_publickey); suite_add_tcase(s, tc); + tc = tcase_create("hash_to_curve"); + tcase_add_test(tc, test_expand_message_xmd_sha256); + tcase_add_test(tc, test_hash_to_curve_p256); + tcase_add_test(tc, test_hash_to_curve_optiga); + suite_add_tcase(s, tc); + #if USE_CARDANO tc = tcase_create("bip32-cardano");