/** * 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; }