From 5a854e8b82d7bf04496a745fd03b9305dbbb0ec3 Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Sun, 13 Sep 2026 19:03:05 -0300 Subject: [PATCH 1/4] Speed up the Crypto reference implementation even more Signed-off-by: Juan Cruz Viotti --- src/core/crypto/crypto_bignum.h | 18 ++- src/core/crypto/crypto_ecc.h | 95 +++++++++++--- src/core/crypto/crypto_eddsa.h | 173 +++++++++++++++++++------- src/core/crypto/crypto_sha1_other.cc | 40 +++--- src/core/crypto/crypto_sha256_other.h | 45 ++++--- src/core/crypto/crypto_sha2_64.h | 43 ++++--- src/core/crypto/crypto_shake256.h | 43 ++++--- src/core/crypto/crypto_sign_other.cc | 2 +- 8 files changed, 324 insertions(+), 135 deletions(-) diff --git a/src/core/crypto/crypto_bignum.h b/src/core/crypto/crypto_bignum.h index 3460c0af2..1380c0ac6 100644 --- a/src/core/crypto/crypto_bignum.h +++ b/src/core/crypto/crypto_bignum.h @@ -818,9 +818,16 @@ inline auto bignum_divide(const BasicBignum &numerator, // Precomputed constants for Barrett reduction modulo a fixed modulus. The plain // setup below reads a public modulus, so it need not be constant time template struct BasicBarrettContext { + using Reduction = auto (*)(const BasicBignum &, + const BasicBarrettContext &) noexcept + -> BasicBignum; BasicBignum modulus; std::size_t words; BasicBignum factor; + // A constant-time reduction of a product below the square of the modulus that + // a special form of the modulus allows, taken instead of the Barrett one when + // set + Reduction reduce{nullptr}; }; using BarrettContext = BasicBarrettContext; @@ -924,9 +931,10 @@ field_mod_multiply_ct(const BasicBignum &left, const BasicBignum &right, const BasicBarrettContext &context) noexcept -> BasicBignum { - return barrett_reduce( - bignum_multiply_fixed(left, right, context.words, context.words), - context); + const auto product{ + bignum_multiply_fixed(left, right, context.words, context.words)}; + return context.reduce == nullptr ? barrett_reduce(product, context) + : context.reduce(product, context); } template @@ -934,7 +942,9 @@ inline auto field_square_ct(const BasicBignum &value, const BasicBarrettContext &context) noexcept -> BasicBignum { - return barrett_reduce(bignum_square_fixed(value, context.words), context); + const auto square{bignum_square_fixed(value, context.words)}; + return context.reduce == nullptr ? barrett_reduce(square, context) + : context.reduce(square, context); } template diff --git a/src/core/crypto/crypto_ecc.h b/src/core/crypto/crypto_ecc.h index e314bac7e..904aa52fb 100644 --- a/src/core/crypto/crypto_ecc.h +++ b/src/core/crypto/crypto_ecc.h @@ -583,24 +583,87 @@ inline auto point_complete_add(const JacobianPoint &left, return {.x = x3, .y = y3, .z = z3}; } -// For the signing path, where the scalar is the secret nonce: a fixed-length -// double-and-add-always ladder over the complete formula with a masked -// selection, so neither the per-bit branch nor the field arithmetic underneath -// depends on the scalar. The input point and the result are projective +// NIST P-521 field reduction in constant time, for the signing ladder. The +// prime is 2^521 - 1, so the bits of a product above position 521 fold back +// onto the low 521 bits with one fixed-width addition, and the sum, at most +// twice the prime, needs at most two masked subtractions +inline auto field_reduce_p521_ct(const CurveBignum &value, + const CurveBarrettContext &context) noexcept + -> CurveBignum { + const auto *value_data{value.words.data()}; + CurveBignum sum; + auto *sum_data{sum.words.data()}; + std::uint64_t carry{0}; + for (std::size_t index = 0; index < 9; ++index) { + const auto low{index < 8 ? value_data[index] : value_data[8] & 0x1ffULL}; + const auto high{(value_data[index + 8] >> 9U) | + (value_data[index + 9] << 55U)}; + const auto total{static_cast(low) + high + carry}; + sum_data[index] = static_cast(total); + carry = static_cast(total >> 64U); + } + + sum.size = 9; + auto reduced{bignum_conditional_subtract(sum, context.modulus, 9)}; + reduced = bignum_conditional_subtract(reduced, context.modulus, 9); + reduced.size = 9; + return reduced; +} + +// The constant-time field arithmetic context of a curve, taking the Mersenne +// reduction for P-521 over the generic Barrett one +inline auto curve_field_context(const EllipticCurveParameters &curve) + -> CurveBarrettContext { + auto field{barrett_context(curve.prime)}; + if (curve.reduction == NISTPrime::P521) { + field.reduce = &field_reduce_p521_ct; + } + + return field; +} + +// For the signing path, where the scalar is the secret nonce: a fixed four-bit +// window ladder over the complete formula. The window count is fixed by the +// public order length, every window doubles four times and adds one table entry +// taken through a masked scan over the whole table, and the complete formula +// absorbs the identity entry of a zero window, so neither the control flow nor +// the field arithmetic underneath depends on the scalar. The input point and +// the result are projective inline auto point_scalar_multiply_constant_time( const CurveBignum &scalar, const JacobianPoint &point, const EllipticCurveParameters &curve) -> JacobianPoint { - const auto field{barrett_context(curve.prime)}; - JacobianPoint result{.x = CurveBignum{}, - .y = bignum_from_u64(1), - .z = CurveBignum{}}; - const auto scalar_bits{bignum_bit_length(curve.order)}; - for (std::size_t index = scalar_bits; index > 0; --index) { - result = point_complete_add(result, result, curve.coefficient_b, field); - const auto sum{ - point_complete_add(result, point, curve.coefficient_b, field)}; - result = point_conditional_select(bignum_get_bit_fixed(scalar, index - 1), - sum, result, field.words); + const auto field{curve_field_context(curve)}; + std::array multiples{}; + multiples[0] = JacobianPoint{.x = CurveBignum{}, + .y = bignum_from_u64(1), + .z = CurveBignum{}}; + multiples[1] = point; + for (std::size_t index = 2; index < multiples.size(); ++index) { + multiples[index] = point_complete_add(multiples[index - 1], point, + curve.coefficient_b, field); + } + + auto result{multiples[0]}; + const auto windows{(bignum_bit_length(curve.order) + 3) / 4}; + for (std::size_t window = windows; window > 0; --window) { + for (std::size_t step = 0; step < 4; ++step) { + result = point_complete_add(result, result, curve.coefficient_b, field); + } + + std::size_t digit{0}; + for (std::size_t bit = 0; bit < 4; ++bit) { + digit |= static_cast( + bignum_get_bit_fixed(scalar, ((window - 1) * 4) + bit)) + << bit; + } + + JacobianPoint selected{}; + for (std::size_t index = 0; index < multiples.size(); ++index) { + selected = point_conditional_select(digit == index, multiples[index], + selected, field.words); + } + + result = point_complete_add(result, selected, curve.coefficient_b, field); } return result; @@ -609,7 +672,7 @@ inline auto point_scalar_multiply_constant_time( inline auto point_affine_x_constant_time(const JacobianPoint &point, const EllipticCurveParameters &curve) -> CurveBignum { - const auto field{barrett_context(curve.prime)}; + const auto field{curve_field_context(curve)}; const auto z_inverse{field_inverse_ct(point.z, field)}; auto result{field_mod_multiply_ct(point.x, z_inverse, field)}; bignum_normalize(result); diff --git a/src/core/crypto/crypto_eddsa.h b/src/core/crypto/crypto_eddsa.h index 25b434b8a..e25db14ee 100644 --- a/src/core/crypto/crypto_eddsa.h +++ b/src/core/crypto/crypto_eddsa.h @@ -15,6 +15,7 @@ #include "crypto_helpers.h" #include "crypto_shake256.h" +#include // std::array #include // std::size_t #include // std::uint8_t #include // std::optional, std::nullopt @@ -37,7 +38,14 @@ struct EdwardsParameters { CurveBignum order; CurveBignum coefficient_a; CurveBignum coefficient_d; + // A square root of -1 modulo the Ed25519 prime, which recovers the second + // candidate root when decoding a point, and left zero for Ed448 + CurveBignum square_root_of_minus_one; EdwardsPoint base; + // The constant-time field and group order arithmetic contexts, built with the + // parameters so that every ladder and signature does not rebuild them + CurveBarrettContext field; + CurveBarrettContext order_field; }; // Interpret the bytes as a little-endian unsigned integer, the encoding EdDSA @@ -49,6 +57,45 @@ inline auto bignum_from_bytes_little_endian(const std::string_view input) return bignum_from_bytes(reversed); } +// Ed25519 field reduction in constant time, for the signing ladder. The prime +// is 2^255 - 19, so 2^256 is congruent to 38 modulo it, and the high half of a +// product folds onto the low half scaled by 38. Two more folds absorb the carry +// out of the top word, which the second can raise only to one and the third +// clears, and the result, below 2^256, needs at most two masked subtractions +inline auto field_reduce_25519_ct(const CurveBignum &value, + const CurveBarrettContext &context) noexcept + -> CurveBignum { + const auto *value_data{value.words.data()}; + CurveBignum folded; + auto *folded_data{folded.words.data()}; + std::uint64_t carry{0}; + for (std::size_t index = 0; index < 4; ++index) { + const auto total{ + static_cast(value_data[index]) + + (static_cast(value_data[index + 4]) * 38U) + carry}; + folded_data[index] = static_cast(total); + carry = static_cast(total >> 64U); + } + + for (std::size_t fold = 0; fold < 2; ++fold) { + BignumDoubleWord addend{static_cast(carry) * 38U}; + for (std::size_t index = 0; index < 4; ++index) { + const auto total{static_cast(folded_data[index]) + + addend}; + folded_data[index] = static_cast(total); + addend = total >> 64U; + } + + carry = static_cast(addend); + } + + folded.size = 4; + auto reduced{bignum_conditional_subtract(folded, context.modulus, 4)}; + reduced = bignum_conditional_subtract(reduced, context.modulus, 4); + reduced.size = 4; + return reduced; +} + // The complete unified Edwards addition formulas in extended coordinates // (Hisil, Wong, Carter, and Dawson 2008), which hold for any two points, // including equal points and the identity, since the curve coefficient is a @@ -140,25 +187,51 @@ inline auto edwards_point_add_constant_time( .t = field_mod_multiply_ct(e, h, field)}; } -// For the signing path, where the scalar is secret: a fixed-length -// double-and-add-always ladder with a masked selection over the complete -// Edwards formulas evaluated in constant time, so neither the per-bit branch -// nor the field arithmetic underneath depends on the scalar +// For the signing path, where the scalar is secret: a fixed four-bit window +// ladder over the complete Edwards formulas evaluated in constant time. The +// window count is fixed by the public field size, every window doubles four +// times and adds one table entry taken through a masked scan over the whole +// table, and the complete formulas absorb the identity entry of a zero window, +// so neither the control flow nor the field arithmetic depends on the scalar inline auto edwards_point_scalar_multiply_constant_time( const CurveBignum &scalar, const EdwardsPoint &point, const EdwardsParameters ¶meters) -> EdwardsPoint { - const auto field{barrett_context(parameters.prime)}; - EdwardsPoint result{.x = CurveBignum{}, - .y = bignum_from_u64(1), - .z = bignum_from_u64(1), - .t = CurveBignum{}}; - const auto scalar_bits{bignum_bit_length(parameters.prime)}; - for (std::size_t index = scalar_bits; index > 0; --index) { - result = edwards_point_add_constant_time(result, result, parameters, field); - const auto sum{ - edwards_point_add_constant_time(result, point, parameters, field)}; - result = edwards_point_conditional_select( - bignum_get_bit_fixed(scalar, index - 1), sum, result, field.words); + const auto &field{parameters.field}; + std::array multiples{}; + // The identity element is (0 : 1 : 1 : 0) + multiples[0] = EdwardsPoint{.x = CurveBignum{}, + .y = bignum_from_u64(1), + .z = bignum_from_u64(1), + .t = CurveBignum{}}; + multiples[1] = point; + for (std::size_t index = 2; index < multiples.size(); ++index) { + multiples[index] = edwards_point_add_constant_time( + multiples[index - 1], point, parameters, field); + } + + auto result{multiples[0]}; + const auto windows{(bignum_bit_length(parameters.prime) + 3) / 4}; + for (std::size_t window = windows; window > 0; --window) { + for (std::size_t step = 0; step < 4; ++step) { + result = + edwards_point_add_constant_time(result, result, parameters, field); + } + + std::size_t digit{0}; + for (std::size_t bit = 0; bit < 4; ++bit) { + digit |= static_cast( + bignum_get_bit_fixed(scalar, ((window - 1) * 4) + bit)) + << bit; + } + + EdwardsPoint selected{}; + for (std::size_t index = 0; index < multiples.size(); ++index) { + selected = edwards_point_conditional_select( + digit == index, multiples[index], selected, field.words); + } + + result = + edwards_point_add_constant_time(result, selected, parameters, field); } return result; @@ -178,11 +251,11 @@ inline auto edwards_point_equal(const EdwardsPoint &left, // Encode a point into the little-endian y coordinate with the low bit of x in // the final bit (RFC 8032 Section 5.1.2), the inverse of the point decoding inline auto edwards_point_encode(const EdwardsPoint &point, - const CurveBignum &prime, + const EdwardsParameters ¶meters, const std::size_t length) -> std::string { // Only the signing path encodes points, and its projective z derives from the // secret scalar, so the coordinate recovery is taken in constant time - const auto field{barrett_context(prime)}; + const auto &field{parameters.field}; const auto z_inverse{field_inverse_ct(point.z, field)}; const auto x{field_mod_multiply_ct(point.x, z_inverse, field)}; const auto y{field_mod_multiply_ct(point.y, z_inverse, field)}; @@ -205,7 +278,7 @@ inline auto edwards_public_key_point(const CurveBignum &scalar, const std::size_t length) -> std::string { return edwards_point_encode(edwards_point_scalar_multiply_constant_time( scalar, parameters.base, parameters), - parameters.prime, length); + parameters, length); } // Recover an Ed25519 point from its 32-byte encoding (RFC 8032 Section 5.1.3), @@ -293,7 +366,7 @@ edwards25519_decode_point(const std::string_view encoding, } // The Edwards25519 domain parameters (RFC 8032 Section 5.1) -inline auto edwards25519() -> EdwardsParameters { +inline auto edwards25519_parameters() -> EdwardsParameters { EdwardsParameters parameters; parameters.prime = bignum_from_hex( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed"); @@ -333,9 +406,21 @@ inline auto edwards25519() -> EdwardsParameters { parameters.coefficient_d, square_root_of_minus_one) .value(); + parameters.square_root_of_minus_one = square_root_of_minus_one; + parameters.field = barrett_context(parameters.prime); + parameters.field.reduce = &field_reduce_25519_ct; + parameters.order_field = barrett_context(parameters.order); return parameters; } +// The Edwards25519 domain parameters derived once and shared, as every signing +// and verification would otherwise repeat the modular inverse and the +// exponentiations the derivation spends +inline auto edwards25519() -> const EdwardsParameters & { + static const EdwardsParameters PARAMETERS{edwards25519_parameters()}; + return PARAMETERS; +} + // Verify an Ed25519 signature over a message (RFC 8032 Section 5.1.7), given // the 32-byte public key and the 64-byte signature inline auto edwards25519_verify(const std::string_view public_key, @@ -345,18 +430,10 @@ inline auto edwards25519_verify(const std::string_view public_key, return false; } - const auto parameters{edwards25519()}; - auto square_root_exponent{parameters.prime}; - bignum_subtract_in_place(square_root_exponent, - bignum_from_u64(1)); - square_root_exponent = bignum_shift_right(square_root_exponent, 2); - const auto square_root_of_minus_one{ - bignum_mod_exp(bignum_from_u64(2), - square_root_exponent, parameters.prime)}; - + const auto ¶meters{edwards25519()}; const auto public_point{edwards25519_decode_point( public_key, parameters.prime, parameters.coefficient_d, - square_root_of_minus_one)}; + parameters.square_root_of_minus_one)}; if (!public_point.has_value()) { return false; } @@ -364,9 +441,9 @@ inline auto edwards25519_verify(const std::string_view public_key, // The signature is the encoded point R followed by the little-endian scalar // S, which must lie below the group order const auto encoded_r{signature.substr(0, 32)}; - const auto point_r{edwards25519_decode_point(encoded_r, parameters.prime, - parameters.coefficient_d, - square_root_of_minus_one)}; + const auto point_r{edwards25519_decode_point( + encoded_r, parameters.prime, parameters.coefficient_d, + parameters.square_root_of_minus_one)}; if (!point_r.has_value()) { return false; } @@ -417,7 +494,7 @@ inline auto edwards25519_public_key(const std::string_view secret) return std::nullopt; } - const auto parameters{edwards25519()}; + const auto ¶meters{edwards25519()}; auto hashed{sha512_digest(secret)}; const SecureBufferScope hashed_scope{hashed.data(), hashed.size()}; const std::string_view digest{reinterpret_cast(hashed.data()), @@ -437,7 +514,7 @@ inline auto edwards25519_sign(const std::string_view secret, return std::nullopt; } - const auto parameters{edwards25519()}; + const auto ¶meters{edwards25519()}; // The key derivation hash carries both the secret scalar and the nonce // prefix, so it and everything derived from it below is wiped before // returning @@ -471,7 +548,7 @@ inline auto edwards25519_sign(const std::string_view secret, const auto encoded_r{ edwards_point_encode(edwards_point_scalar_multiply_constant_time( scalar_r, parameters.base, parameters), - parameters.prime, 32)}; + parameters, 32)}; // k = SHA-512(R || A || M) reduced, then S = (r + k * a) mod L. The k * a // product carries the secret scalar, so it is wiped; r, k, and the resulting @@ -488,7 +565,7 @@ inline auto edwards25519_sign(const std::string_view secret, // nonce, so both run over the constant-time field arithmetic modulo the // order; k is public and r, k, and the resulting S are the public signature // material - const auto order_field{barrett_context(parameters.order)}; + const auto &order_field{parameters.order_field}; auto scalar_a_reduced{barrett_reduce(scalar_a, order_field)}; const SecureBignumScope scalar_a_reduced_scope{scalar_a_reduced}; auto challenge_product{ @@ -583,7 +660,7 @@ inline auto edwards448_decode_point(const std::string_view encoding, } // The Edwards448 domain parameters (RFC 8032 Section 5.2) -inline auto edwards448() -> EdwardsParameters { +inline auto edwards448_parameters() -> EdwardsParameters { EdwardsParameters parameters; // clang-format off parameters.prime = bignum_from_hex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); @@ -604,9 +681,19 @@ inline auto edwards448() -> EdwardsParameters { parameters.base = edwards448_decode_point(base_encoding, parameters.prime, parameters.coefficient_d) .value(); + parameters.field = barrett_context(parameters.prime); + parameters.order_field = barrett_context(parameters.order); return parameters; } +// The Edwards448 domain parameters derived once and shared, as every signing +// and verification would otherwise repeat the exponentiation that decoding the +// base point spends +inline auto edwards448() -> const EdwardsParameters & { + static const EdwardsParameters PARAMETERS{edwards448_parameters()}; + return PARAMETERS; +} + // Verify an Ed448 signature over a message (RFC 8032 Section 5.2.7), given the // 57-byte public key and the 114-byte signature inline auto edwards448_verify(const std::string_view public_key, @@ -616,7 +703,7 @@ inline auto edwards448_verify(const std::string_view public_key, return false; } - const auto parameters{edwards448()}; + const auto ¶meters{edwards448()}; const auto public_point{edwards448_decode_point(public_key, parameters.prime, parameters.coefficient_d)}; if (!public_point.has_value()) { @@ -681,7 +768,7 @@ inline auto edwards448_public_key(const std::string_view secret) return std::nullopt; } - const auto parameters{edwards448()}; + const auto ¶meters{edwards448()}; auto digest{shake256(secret, 114)}; const SecureStringScope digest_scope{digest}; std::string scalar_bytes{digest.substr(0, 57)}; @@ -699,7 +786,7 @@ inline auto edwards448_sign(const std::string_view secret, return std::nullopt; } - const auto parameters{edwards448()}; + const auto ¶meters{edwards448()}; // The key derivation hash carries both the secret scalar and the nonce // prefix, so it and everything derived from it below is wiped before // returning @@ -734,7 +821,7 @@ inline auto edwards448_sign(const std::string_view secret, const auto encoded_r{ edwards_point_encode(edwards_point_scalar_multiply_constant_time( scalar_r, parameters.base, parameters), - parameters.prime, 57)}; + parameters, 57)}; // k = SHAKE256(dom4 || R || A || M) reduced, then S = (r + k * a) mod L. The // k * a product carries the secret scalar, so it is wiped; r, k, and the @@ -750,7 +837,7 @@ inline auto edwards448_sign(const std::string_view secret, // nonce, so both run over the constant-time field arithmetic modulo the // order; k is public and r, k, and the resulting S are the public signature // material - const auto order_field{barrett_context(parameters.order)}; + const auto &order_field{parameters.order_field}; auto scalar_a_reduced{barrett_reduce(scalar_a, order_field)}; const SecureBignumScope scalar_a_reduced_scope{scalar_a_reduced}; auto challenge_product{ diff --git a/src/core/crypto/crypto_sha1_other.cc b/src/core/crypto/crypto_sha1_other.cc index e5a7a47df..67d4c5abb 100644 --- a/src/core/crypto/crypto_sha1_other.cc +++ b/src/core/crypto/crypto_sha1_other.cc @@ -41,9 +41,10 @@ inline auto sha1_process_block(const unsigned char *block, -> void { // Decode 16 big-endian 32-bit words from the block std::array schedule; + auto *schedule_data = schedule.data(); for (std::uint64_t word_index = 0; word_index < 16u; ++word_index) { const std::uint64_t byte_index = word_index * 4u; - schedule[word_index] = + schedule_data[word_index] = (static_cast(block[byte_index]) << 24u) | (static_cast(block[byte_index + 1u]) << 16u) | (static_cast(block[byte_index + 2u]) << 8u) | @@ -52,13 +53,14 @@ inline auto sha1_process_block(const unsigned char *block, // Extend the message schedule (RFC 3174 Section 6.1 step b) for (std::uint64_t index = 16u; index < 80u; ++index) { - schedule[index] = - rotate_left(schedule[index - 3u] ^ schedule[index - 8u] ^ - schedule[index - 14u] ^ schedule[index - 16u], + schedule_data[index] = + rotate_left(schedule_data[index - 3u] ^ schedule_data[index - 8u] ^ + schedule_data[index - 14u] ^ schedule_data[index - 16u], 1u); } auto working = state; + auto *working_data = working.data(); // Compression function (RFC 3174 Section 6.1 step d), with the round // constants of RFC 3174 Section 5 @@ -66,31 +68,37 @@ inline auto sha1_process_block(const unsigned char *block, std::uint32_t function_value; std::uint32_t round_constant; if (round_index < 20u) { - function_value = choice(working[1], working[2], working[3]); + function_value = + choice(working_data[1], working_data[2], working_data[3]); round_constant = 0x5a827999U; } else if (round_index < 40u) { - function_value = parity(working[1], working[2], working[3]); + function_value = + parity(working_data[1], working_data[2], working_data[3]); round_constant = 0x6ed9eba1U; } else if (round_index < 60u) { - function_value = majority(working[1], working[2], working[3]); + function_value = + majority(working_data[1], working_data[2], working_data[3]); round_constant = 0x8f1bbcdcU; } else { - function_value = parity(working[1], working[2], working[3]); + function_value = + parity(working_data[1], working_data[2], working_data[3]); round_constant = 0xca62c1d6U; } - const auto temporary = rotate_left(working[0], 5u) + function_value + - working[4] + schedule[round_index] + round_constant; + const auto temporary = rotate_left(working_data[0], 5u) + function_value + + working_data[4] + schedule_data[round_index] + + round_constant; - working[4] = working[3]; - working[3] = working[2]; - working[2] = rotate_left(working[1], 30u); - working[1] = working[0]; - working[0] = temporary; + working_data[4] = working_data[3]; + working_data[3] = working_data[2]; + working_data[2] = rotate_left(working_data[1], 30u); + working_data[1] = working_data[0]; + working_data[0] = temporary; } + auto *state_data = state.data(); for (std::uint64_t index = 0u; index < 5u; ++index) { - state[index] += working[index]; + state_data[index] += working_data[index]; } } diff --git a/src/core/crypto/crypto_sha256_other.h b/src/core/crypto/crypto_sha256_other.h index 1238baa5e..343fc93d4 100644 --- a/src/core/crypto/crypto_sha256_other.h +++ b/src/core/crypto/crypto_sha256_other.h @@ -79,9 +79,10 @@ inline auto sha256_process_block(const std::uint8_t *block, // Decode 16 big-endian 32-bit words from the block std::array schedule; + auto *schedule_data = schedule.data(); for (std::uint64_t word_index = 0; word_index < 16u; ++word_index) { const std::uint64_t byte_index = word_index * 4u; - schedule[word_index] = + schedule_data[word_index] = (static_cast(block[byte_index]) << 24u) | (static_cast(block[byte_index + 1u]) << 16u) | (static_cast(block[byte_index + 2u]) << 8u) | @@ -90,35 +91,39 @@ inline auto sha256_process_block(const std::uint8_t *block, // Extend the message schedule (FIPS 180-4 Section 6.2.2 step 1) for (std::uint64_t index = 16u; index < 64u; ++index) { - schedule[index] = - sha256_small_sigma_1(schedule[index - 2u]) + schedule[index - 7u] + - sha256_small_sigma_0(schedule[index - 15u]) + schedule[index - 16u]; + schedule_data[index] = sha256_small_sigma_1(schedule_data[index - 2u]) + + schedule_data[index - 7u] + + sha256_small_sigma_0(schedule_data[index - 15u]) + + schedule_data[index - 16u]; } auto working = state; + auto *working_data = working.data(); + const auto *constants_data = round_constants.data(); // Compression function (FIPS 180-4 Section 6.2.2 step 3) for (std::uint64_t round_index = 0u; round_index < 64u; ++round_index) { - const auto temporary_1 = working[7] + sha256_big_sigma_1(working[4]) + - sha256_choice(working[4], working[5], working[6]) + - round_constants[round_index] + - schedule[round_index]; + const auto temporary_1 = + working_data[7] + sha256_big_sigma_1(working_data[4]) + + sha256_choice(working_data[4], working_data[5], working_data[6]) + + constants_data[round_index] + schedule_data[round_index]; const auto temporary_2 = - sha256_big_sigma_0(working[0]) + - sha256_majority(working[0], working[1], working[2]); - - working[7] = working[6]; - working[6] = working[5]; - working[5] = working[4]; - working[4] = working[3] + temporary_1; - working[3] = working[2]; - working[2] = working[1]; - working[1] = working[0]; - working[0] = temporary_1 + temporary_2; + sha256_big_sigma_0(working_data[0]) + + sha256_majority(working_data[0], working_data[1], working_data[2]); + + working_data[7] = working_data[6]; + working_data[6] = working_data[5]; + working_data[5] = working_data[4]; + working_data[4] = working_data[3] + temporary_1; + working_data[3] = working_data[2]; + working_data[2] = working_data[1]; + working_data[1] = working_data[0]; + working_data[0] = temporary_1 + temporary_2; } + auto *state_data = state.data(); for (std::uint64_t index = 0u; index < 8u; ++index) { - state[index] += working[index]; + state_data[index] += working_data[index]; } } diff --git a/src/core/crypto/crypto_sha2_64.h b/src/core/crypto/crypto_sha2_64.h index 78ebff3b5..616854df4 100644 --- a/src/core/crypto/crypto_sha2_64.h +++ b/src/core/crypto/crypto_sha2_64.h @@ -94,9 +94,10 @@ inline auto sha2_64_process_block(const std::uint8_t *block, // Decode 16 big-endian 64-bit words from the block std::array schedule; + auto *schedule_data = schedule.data(); for (std::uint64_t word_index = 0; word_index < 16u; ++word_index) { const std::uint64_t byte_index = word_index * 8u; - schedule[word_index] = + schedule_data[word_index] = (static_cast(block[byte_index]) << 56u) | (static_cast(block[byte_index + 1u]) << 48u) | (static_cast(block[byte_index + 2u]) << 40u) | @@ -109,35 +110,39 @@ inline auto sha2_64_process_block(const std::uint8_t *block, // Extend the message schedule (FIPS 180-4 Section 6.4.2 step 1) for (std::uint64_t index = 16u; index < 80u; ++index) { - schedule[index] = - sha2_64_small_sigma_1(schedule[index - 2u]) + schedule[index - 7u] + - sha2_64_small_sigma_0(schedule[index - 15u]) + schedule[index - 16u]; + schedule_data[index] = sha2_64_small_sigma_1(schedule_data[index - 2u]) + + schedule_data[index - 7u] + + sha2_64_small_sigma_0(schedule_data[index - 15u]) + + schedule_data[index - 16u]; } auto working = state; + auto *working_data = working.data(); + const auto *constants_data = round_constants.data(); // Compression function (FIPS 180-4 Section 6.4.2 step 3) for (std::uint64_t round_index = 0u; round_index < 80u; ++round_index) { const auto temporary_1 = - working[7] + sha2_64_big_sigma_1(working[4]) + - sha2_64_choice(working[4], working[5], working[6]) + - round_constants[round_index] + schedule[round_index]; + working_data[7] + sha2_64_big_sigma_1(working_data[4]) + + sha2_64_choice(working_data[4], working_data[5], working_data[6]) + + constants_data[round_index] + schedule_data[round_index]; const auto temporary_2 = - sha2_64_big_sigma_0(working[0]) + - sha2_64_majority(working[0], working[1], working[2]); - - working[7] = working[6]; - working[6] = working[5]; - working[5] = working[4]; - working[4] = working[3] + temporary_1; - working[3] = working[2]; - working[2] = working[1]; - working[1] = working[0]; - working[0] = temporary_1 + temporary_2; + sha2_64_big_sigma_0(working_data[0]) + + sha2_64_majority(working_data[0], working_data[1], working_data[2]); + + working_data[7] = working_data[6]; + working_data[6] = working_data[5]; + working_data[5] = working_data[4]; + working_data[4] = working_data[3] + temporary_1; + working_data[3] = working_data[2]; + working_data[2] = working_data[1]; + working_data[1] = working_data[0]; + working_data[0] = temporary_1 + temporary_2; } + auto *state_data = state.data(); for (std::uint64_t index = 0u; index < 8u; ++index) { - state[index] += working[index]; + state_data[index] += working_data[index]; } } diff --git a/src/core/crypto/crypto_shake256.h b/src/core/crypto/crypto_shake256.h index 09b9e3d5a..b3e7e7bce 100644 --- a/src/core/crypto/crypto_shake256.h +++ b/src/core/crypto/crypto_shake256.h @@ -40,47 +40,56 @@ constexpr auto keccak_rotate_left(const std::uint64_t value, inline auto keccak_permute(std::array &state) noexcept -> void { + auto *state_data{state.data()}; + const auto *pi_lanes{KECCAK_PI_LANES.data()}; + const auto *rho_offsets{KECCAK_RHO_OFFSETS.data()}; + const auto *round_constants{KECCAK_ROUND_CONSTANTS.data()}; for (std::size_t round = 0; round < 24; ++round) { // Theta std::array column_parity{}; + auto *column_parity_data{column_parity.data()}; for (std::size_t column = 0; column < 5; ++column) { - column_parity[column] = state[column] ^ state[column + 5] ^ - state[column + 10] ^ state[column + 15] ^ - state[column + 20]; + column_parity_data[column] = state_data[column] ^ state_data[column + 5] ^ + state_data[column + 10] ^ + state_data[column + 15] ^ + state_data[column + 20]; } for (std::size_t column = 0; column < 5; ++column) { - const auto delta{column_parity[(column + 4) % 5] ^ - keccak_rotate_left(column_parity[(column + 1) % 5], 1)}; + const auto delta{ + column_parity_data[(column + 4) % 5] ^ + keccak_rotate_left(column_parity_data[(column + 1) % 5], 1)}; for (std::size_t row = 0; row < 25; row += 5) { - state[row + column] ^= delta; + state_data[row + column] ^= delta; } } // Rho and pi - auto current{state[1]}; + auto current{state_data[1]}; for (std::size_t index = 0; index < 24; ++index) { - const auto lane{KECCAK_PI_LANES[index]}; - const auto moved{state[lane]}; - state[lane] = keccak_rotate_left(current, KECCAK_RHO_OFFSETS[index]); + const auto lane{pi_lanes[index]}; + const auto moved{state_data[lane]}; + state_data[lane] = keccak_rotate_left(current, rho_offsets[index]); current = moved; } // Chi for (std::size_t row = 0; row < 25; row += 5) { std::array plane{}; + auto *plane_data{plane.data()}; for (std::size_t column = 0; column < 5; ++column) { - plane[column] = state[row + column]; + plane_data[column] = state_data[row + column]; } for (std::size_t column = 0; column < 5; ++column) { - state[row + column] = plane[column] ^ (~plane[(column + 1) % 5] & - plane[(column + 2) % 5]); + state_data[row + column] = + plane_data[column] ^ + (~plane_data[(column + 1) % 5] & plane_data[(column + 2) % 5]); } } // Iota - state[0] ^= KECCAK_ROUND_CONSTANTS[round]; + state_data[0] ^= round_constants[round]; } } @@ -90,10 +99,11 @@ inline auto shake256(const std::string_view input, // The bitrate is 1600 - 2 * 256 = 1088 bits, that is 136 octets constexpr std::size_t RATE{136}; std::array state{}; + auto *state_data{state.data()}; std::size_t pointer{0}; for (const auto character : input) { - state[pointer / 8] ^= + state_data[pointer / 8] ^= static_cast(static_cast(character)) << (8 * (pointer % 8)); pointer += 1; @@ -115,7 +125,8 @@ inline auto shake256(const std::string_view input, std::size_t squeeze_pointer{0}; while (output.size() < output_length) { output.push_back(static_cast( - (state[squeeze_pointer / 8] >> (8 * (squeeze_pointer % 8))) & 0xffU)); + (state_data[squeeze_pointer / 8] >> (8 * (squeeze_pointer % 8))) & + 0xffU)); squeeze_pointer += 1; if (squeeze_pointer == RATE) { keccak_permute(state); diff --git a/src/core/crypto/crypto_sign_other.cc b/src/core/crypto/crypto_sign_other.cc index 4de3c7c88..049764dbe 100644 --- a/src/core/crypto/crypto_sign_other.cc +++ b/src/core/crypto/crypto_sign_other.cc @@ -365,7 +365,7 @@ auto ec_public_from_scalar(const EllipticCurve curve, // path rather than the Jacobian point_to_affine const auto product{point_scalar_multiply_constant_time( scalar_number, generator, parameters)}; - const auto field{barrett_context(parameters.prime)}; + const auto field{curve_field_context(parameters)}; const auto z_inverse{field_inverse_ct(product.z, field)}; auto coordinate_x{field_mod_multiply_ct(product.x, z_inverse, field)}; auto coordinate_y{field_mod_multiply_ct(product.y, z_inverse, field)}; From d2131fafdc881a0e5d43003932ccf071e8ba9429 Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Sun, 13 Sep 2026 19:19:52 -0300 Subject: [PATCH 2/4] More Signed-off-by: Juan Cruz Viotti --- src/core/crypto/crypto_bignum.h | 72 +++++-- src/core/crypto/crypto_eddsa.h | 295 ++++++++++++++--------------- src/core/crypto/crypto_rsa_other.h | 9 +- 3 files changed, 205 insertions(+), 171 deletions(-) diff --git a/src/core/crypto/crypto_bignum.h b/src/core/crypto/crypto_bignum.h index 1380c0ac6..5d552838f 100644 --- a/src/core/crypto/crypto_bignum.h +++ b/src/core/crypto/crypto_bignum.h @@ -1000,6 +1000,64 @@ field_subtract_ct(const BasicBignum &left, return result; } +// Whether two field elements, both reduced below the modulus, are equal, +// comparing every word of the public width without an early exit +template +inline auto +field_equal_ct(const BasicBignum &left, + const BasicBignum &right, + const BasicBarrettContext &context) noexcept -> bool { + const auto *left_data{left.words.data()}; + const auto *right_data{right.words.data()}; + std::uint64_t difference{0}; + for (std::size_t index = 0; index < context.words; ++index) { + difference |= left_data[index] ^ right_data[index]; + } + + return difference == 0; +} + +// Raise a value to a public exponent over the field in fixed four-bit windows. +// The exponent is public, so its windows index the table of powers directly and +// skip the multiplication of a zero window, while the field multiplications +// underneath do not depend on the value being raised +template +inline auto +field_power_ct(const BasicBignum &value, + const BasicBignum &exponent, + const BasicBarrettContext &context) noexcept + -> BasicBignum { + std::array, 16> powers{}; + powers[0].words[0] = 1; + powers[0].size = context.words; + powers[1] = barrett_reduce(value, context); + for (std::size_t index = 2; index < powers.size(); ++index) { + powers[index] = + field_mod_multiply_ct(powers[index - 1], powers[1], context); + } + + auto result{powers[0]}; + const auto windows{(bignum_bit_length(exponent) + 3) / 4}; + for (std::size_t window = windows; window > 0; --window) { + for (std::size_t step = 0; step < 4; ++step) { + result = field_square_ct(result, context); + } + + std::size_t digit{0}; + for (std::size_t bit = 0; bit < 4; ++bit) { + digit |= static_cast( + bignum_get_bit(exponent, ((window - 1) * 4) + bit)) + << bit; + } + + if (digit != 0) { + result = field_mod_multiply_ct(result, powers[digit], context); + } + } + + return result; +} + // Fermat inverse over the field in constant time. The exponent is the public // modulus minus two, so its bit pattern reveals nothing secret, and the field // multiplications underneath do not depend on the value being inverted. The @@ -1011,19 +1069,7 @@ field_inverse_ct(const BasicBignum &value, -> BasicBignum { auto exponent{context.modulus}; bignum_subtract_in_place(exponent, bignum_from_u64(2)); - BasicBignum result; - result.words[0] = 1; - result.size = context.words; - const auto base{barrett_reduce(value, context)}; - const auto exponent_bits{bignum_bit_length(exponent)}; - for (std::size_t index = exponent_bits; index > 0; --index) { - result = field_square_ct(result, context); - if (bignum_get_bit(exponent, index - 1)) { - result = field_mod_multiply_ct(result, base, context); - } - } - - return result; + return field_power_ct(value, exponent, context); } // Modular exponentiation for a secret exponent (the RSA private key), in diff --git a/src/core/crypto/crypto_eddsa.h b/src/core/crypto/crypto_eddsa.h index e25db14ee..7d84f193d 100644 --- a/src/core/crypto/crypto_eddsa.h +++ b/src/core/crypto/crypto_eddsa.h @@ -96,55 +96,6 @@ inline auto field_reduce_25519_ct(const CurveBignum &value, return reduced; } -// The complete unified Edwards addition formulas in extended coordinates -// (Hisil, Wong, Carter, and Dawson 2008), which hold for any two points, -// including equal points and the identity, since the curve coefficient is a -// square and d is a non-square modulo p -inline auto edwards_point_add(const EdwardsPoint &left, - const EdwardsPoint &right, - const EdwardsParameters ¶meters) - -> EdwardsPoint { - const auto &prime{parameters.prime}; - const auto a{bignum_mod_multiply(left.x, right.x, prime)}; - const auto b{bignum_mod_multiply(left.y, right.y, prime)}; - const auto c{bignum_mod_multiply( - bignum_mod_multiply(parameters.coefficient_d, left.t, prime), right.t, - prime)}; - const auto d{bignum_mod_multiply(left.z, right.z, prime)}; - const auto e{bignum_mod_subtract( - bignum_mod_multiply(bignum_mod_add(left.x, left.y, prime), - bignum_mod_add(right.x, right.y, prime), prime), - bignum_mod_add(a, b, prime), prime)}; - const auto f{bignum_mod_subtract(d, c, prime)}; - const auto g{bignum_mod_add(d, c, prime)}; - const auto h{bignum_mod_subtract( - b, bignum_mod_multiply(parameters.coefficient_a, a, prime), prime)}; - return EdwardsPoint{.x = bignum_mod_multiply(e, f, prime), - .y = bignum_mod_multiply(g, h, prime), - .z = bignum_mod_multiply(f, g, prime), - .t = bignum_mod_multiply(e, h, prime)}; -} - -inline auto edwards_point_scalar_multiply(const CurveBignum &scalar, - const EdwardsPoint &point, - const EdwardsParameters ¶meters) - -> EdwardsPoint { - // The identity element is (0 : 1 : 1 : 0) - EdwardsPoint result{.x = CurveBignum{}, - .y = bignum_from_u64(1), - .z = bignum_from_u64(1), - .t = CurveBignum{}}; - const auto bits{bignum_bit_length(scalar)}; - for (std::size_t index = bits; index > 0; --index) { - result = edwards_point_add(result, result, parameters); - if (bignum_get_bit(scalar, index - 1)) { - result = edwards_point_add(result, point, parameters); - } - } - - return result; -} - inline auto edwards_point_conditional_select(const bool condition, const EdwardsPoint &when_true, const EdwardsPoint &when_false, @@ -160,9 +111,12 @@ inline auto edwards_point_conditional_select(const bool condition, words)}; } -// The same complete addition as above evaluated over the constant-time field -// arithmetic, for the signing path where the operands derive from the secret -// scalar +// The complete unified Edwards addition formulas in extended coordinates +// (Hisil, Wong, Carter, and Dawson 2008), which hold for any two points, +// including equal points and the identity, since the curve coefficient is a +// square and d is a non-square modulo p. They run over the constant-time field +// arithmetic, which signing needs for its secret operands and verification +// reuses on its public ones inline auto edwards_point_add_constant_time( const EdwardsPoint &left, const EdwardsPoint &right, const EdwardsParameters ¶meters, @@ -237,15 +191,65 @@ inline auto edwards_point_scalar_multiply_constant_time( return result; } -// Whether two points are equal, compared without leaving projective space by -// cross-multiplying through the Z factors -inline auto edwards_point_equal(const EdwardsPoint &left, - const EdwardsPoint &right, - const CurveBignum &prime) -> bool { - return bignum_compare(bignum_mod_multiply(left.x, right.z, prime), - bignum_mod_multiply(right.x, left.z, prime)) == 0 && - bignum_compare(bignum_mod_multiply(left.y, right.z, prime), - bignum_mod_multiply(right.y, left.z, prime)) == 0; +// The negation of a point in extended coordinates, (-X : Y : Z : -T) +inline auto edwards_point_negate(const EdwardsPoint &point, + const CurveBarrettContext &field) noexcept + -> EdwardsPoint { + return {.x = field_subtract_ct(CurveBignum{}, point.x, field), + .y = point.y, + .z = point.z, + .t = field_subtract_ct(CurveBignum{}, point.t, field)}; +} + +// Compute [first_scalar] first_point + [second_scalar] second_point with +// Shamir's trick, a single double-and-add over the longer scalar that adds the +// precomputed sum whenever both scalars have a set bit. Only verification uses +// it, where both scalars and both points are public, so the bits may steer the +// additions +inline auto edwards_point_double_scalar_multiply( + const CurveBignum &first_scalar, const EdwardsPoint &first_point, + const CurveBignum &second_scalar, const EdwardsPoint &second_point, + const EdwardsParameters ¶meters) -> EdwardsPoint { + const auto &field{parameters.field}; + const auto combined{edwards_point_add_constant_time(first_point, second_point, + parameters, field)}; + // The identity element is (0 : 1 : 1 : 0) + EdwardsPoint result{.x = CurveBignum{}, + .y = bignum_from_u64(1), + .z = bignum_from_u64(1), + .t = CurveBignum{}}; + const auto first_bits{bignum_bit_length(first_scalar)}; + const auto second_bits{bignum_bit_length(second_scalar)}; + const auto bits{first_bits > second_bits ? first_bits : second_bits}; + for (std::size_t index = bits; index > 0; --index) { + result = edwards_point_add_constant_time(result, result, parameters, field); + const auto first_bit{bignum_get_bit(first_scalar, index - 1)}; + const auto second_bit{bignum_get_bit(second_scalar, index - 1)}; + if (first_bit && second_bit) { + result = + edwards_point_add_constant_time(result, combined, parameters, field); + } else if (first_bit) { + result = edwards_point_add_constant_time(result, first_point, parameters, + field); + } else if (second_bit) { + result = edwards_point_add_constant_time(result, second_point, parameters, + field); + } + } + + return result; +} + +// Whether a projective point equals an affine one, whose Z coordinate is one, +// compared without leaving projective space as X = x * Z and Y = y * Z +inline auto edwards_point_matches_affine(const EdwardsPoint &point, + const EdwardsPoint &affine, + const CurveBarrettContext &field) + -> bool { + return field_equal_ct( + point.x, field_mod_multiply_ct(affine.x, point.z, field), field) && + field_equal_ct(point.y, + field_mod_multiply_ct(affine.y, point.z, field), field); } // Encode a point into the little-endian y coordinate with the low bit of x in @@ -282,12 +286,10 @@ inline auto edwards_public_key_point(const CurveBignum &scalar, } // Recover an Ed25519 point from its 32-byte encoding (RFC 8032 Section 5.1.3), -// returning no value when the encoding does not name a point on the curve -inline auto -edwards25519_decode_point(const std::string_view encoding, - const CurveBignum &prime, - const CurveBignum &coefficient_d, - const CurveBignum &square_root_of_minus_one) +// returning no value when the encoding does not name a point on the curve. The +// recovery runs over the field arithmetic context of the parameters +inline auto edwards25519_decode_point(const std::string_view encoding, + const EdwardsParameters ¶meters) -> std::optional { if (encoding.size() != 32) { return std::nullopt; @@ -302,53 +304,55 @@ edwards25519_decode_point(const std::string_view encoding, const auto y{bignum_from_bytes_little_endian(bytes)}; // A y coordinate at or beyond the field prime is not a canonical encoding + const auto &prime{parameters.prime}; if (bignum_compare(y, prime) >= 0) { return std::nullopt; } + const auto &field{parameters.field}; const auto one{bignum_from_u64(1)}; - const auto y_squared{bignum_mod_multiply(y, y, prime)}; + const auto y_squared{field_square_ct(y, field)}; // Solve x^2 = (y^2 - 1) / (d * y^2 + 1) (mod p) - const auto numerator{bignum_mod_subtract(y_squared, one, prime)}; - const auto denominator{bignum_mod_add( - bignum_mod_multiply(coefficient_d, y_squared, prime), one, prime)}; + const auto numerator{field_subtract_ct(y_squared, one, field)}; + const auto denominator{field_add_ct( + field_mod_multiply_ct(parameters.coefficient_d, y_squared, field), one, + field)}; // The candidate root is x = numerator * denominator^3 * // (numerator * denominator^7)^((p - 5) / 8) (mod p), a single powering that // folds in the inversion of the denominator - const auto denominator_squared{ - bignum_mod_multiply(denominator, denominator, prime)}; + const auto denominator_squared{field_square_ct(denominator, field)}; const auto denominator_cubed{ - bignum_mod_multiply(denominator_squared, denominator, prime)}; - const auto denominator_seventh{bignum_mod_multiply( - bignum_mod_multiply(denominator_cubed, denominator_cubed, prime), - denominator, prime)}; + field_mod_multiply_ct(denominator_squared, denominator, field)}; + const auto denominator_seventh{field_mod_multiply_ct( + field_square_ct(denominator_cubed, field), denominator, field)}; auto exponent{prime}; bignum_subtract_in_place(exponent, bignum_from_u64(5)); exponent = bignum_shift_right(exponent, 3); - const auto root{ - bignum_mod_exp(bignum_mod_multiply(numerator, denominator_seventh, prime), - exponent, prime)}; - auto candidate{bignum_mod_multiply( - bignum_mod_multiply(numerator, denominator_cubed, prime), root, prime)}; + const auto root{field_power_ct( + field_mod_multiply_ct(numerator, denominator_seventh, field), exponent, + field)}; + auto candidate{field_mod_multiply_ct( + field_mod_multiply_ct(numerator, denominator_cubed, field), root, field)}; // The candidate is correct when denominator * x^2 equals the numerator, off // by sqrt(-1) when it equals its negation, and otherwise no root exists - const auto check{bignum_mod_multiply( - denominator, bignum_mod_multiply(candidate, candidate, prime), prime)}; - if (bignum_compare(check, numerator) != 0) { - const auto negated_numerator{ - bignum_mod_subtract(CurveBignum{}, numerator, prime)}; - if (bignum_compare(check, negated_numerator) != 0) { + const auto check{field_mod_multiply_ct( + denominator, field_square_ct(candidate, field), field)}; + if (!field_equal_ct(check, numerator, field)) { + if (!field_equal_ct( + check, field_subtract_ct(CurveBignum{}, numerator, field), field)) { return std::nullopt; } - candidate = bignum_mod_multiply(candidate, square_root_of_minus_one, prime); + candidate = field_mod_multiply_ct( + candidate, parameters.square_root_of_minus_one, field); } // Reject the non-canonical zero root with a set sign bit, then select the // root whose low bit matches the encoded sign + bignum_normalize(candidate); if (bignum_is_zero(candidate) && sign_bit == 1) { return std::nullopt; } @@ -362,7 +366,7 @@ edwards25519_decode_point(const std::string_view encoding, return EdwardsPoint{.x = candidate, .y = y, .z = one, - .t = bignum_mod_multiply(candidate, y, prime)}; + .t = field_mod_multiply_ct(candidate, y, field)}; } // The Edwards25519 domain parameters (RFC 8032 Section 5.1) @@ -393,23 +397,20 @@ inline auto edwards25519_parameters() -> EdwardsParameters { bignum_subtract_in_place(root_exponent, bignum_from_u64(1)); root_exponent = bignum_shift_right(root_exponent, 2); - const auto square_root_of_minus_one{ + parameters.square_root_of_minus_one = bignum_mod_exp(bignum_from_u64(2), root_exponent, - parameters.prime)}; + parameters.prime); + parameters.field = barrett_context(parameters.prime); + parameters.field.reduce = &field_reduce_25519_ct; + parameters.order_field = barrett_context(parameters.order); // The base point is recovered from its canonical encoding, y = 4/5 with a // clear sign bit (RFC 8032 Section 5.1) std::string base_encoding; base_encoding.push_back('\x58'); base_encoding.append(31, '\x66'); - parameters.base = edwards25519_decode_point(base_encoding, parameters.prime, - parameters.coefficient_d, - square_root_of_minus_one) - .value(); - parameters.square_root_of_minus_one = square_root_of_minus_one; - parameters.field = barrett_context(parameters.prime); - parameters.field.reduce = &field_reduce_25519_ct; - parameters.order_field = barrett_context(parameters.order); + parameters.base = + edwards25519_decode_point(base_encoding, parameters).value(); return parameters; } @@ -431,9 +432,7 @@ inline auto edwards25519_verify(const std::string_view public_key, } const auto ¶meters{edwards25519()}; - const auto public_point{edwards25519_decode_point( - public_key, parameters.prime, parameters.coefficient_d, - parameters.square_root_of_minus_one)}; + const auto public_point{edwards25519_decode_point(public_key, parameters)}; if (!public_point.has_value()) { return false; } @@ -441,9 +440,7 @@ inline auto edwards25519_verify(const std::string_view public_key, // The signature is the encoded point R followed by the little-endian scalar // S, which must lie below the group order const auto encoded_r{signature.substr(0, 32)}; - const auto point_r{edwards25519_decode_point( - encoded_r, parameters.prime, parameters.coefficient_d, - parameters.square_root_of_minus_one)}; + const auto point_r{edwards25519_decode_point(encoded_r, parameters)}; if (!point_r.has_value()) { return false; } @@ -464,14 +461,14 @@ inline auto edwards25519_verify(const std::string_view public_key, reinterpret_cast(digest.data()), digest.size()})}; bignum_reduce(scalar_k, parameters.order); - // The signature holds when [S]B = R + [k]A - const auto left{ - edwards_point_scalar_multiply(scalar_s, parameters.base, parameters)}; - const auto right{edwards_point_add( - point_r.value(), - edwards_point_scalar_multiply(scalar_k, public_point.value(), parameters), + // The signature holds when [S]B = R + [k]A, checked as [S]B + [k](-A) = R so + // that both scalar multiplications share a single pass + const auto combination{edwards_point_double_scalar_multiply( + scalar_s, parameters.base, scalar_k, + edwards_point_negate(public_point.value(), parameters.field), parameters)}; - return edwards_point_equal(left, right, parameters.prime); + return edwards_point_matches_affine(combination, point_r.value(), + parameters.field); } // Prune the 32-byte secret scalar in place (RFC 8032 Section 5.1.5): "The @@ -581,10 +578,10 @@ inline auto edwards25519_sign(const std::string_view secret, } // Recover an Ed448 point from its 57-byte encoding (RFC 8032 Section 5.2.3), -// returning no value when the encoding does not name a point on the curve +// returning no value when the encoding does not name a point on the curve. The +// recovery runs over the field arithmetic context of the parameters inline auto edwards448_decode_point(const std::string_view encoding, - const CurveBignum &prime, - const CurveBignum &coefficient_d) + const EdwardsParameters ¶meters) -> std::optional { if (encoding.size() != 57) { return std::nullopt; @@ -599,50 +596,52 @@ inline auto edwards448_decode_point(const std::string_view encoding, const auto y{bignum_from_bytes_little_endian(bytes)}; // A y coordinate at or beyond the field prime is not a canonical encoding + const auto &prime{parameters.prime}; if (bignum_compare(y, prime) >= 0) { return std::nullopt; } + const auto &field{parameters.field}; const auto one{bignum_from_u64(1)}; - const auto y_squared{bignum_mod_multiply(y, y, prime)}; + const auto y_squared{field_square_ct(y, field)}; // Solve x^2 = (y^2 - 1) / (d * y^2 - 1) (mod p) - const auto numerator{bignum_mod_subtract(y_squared, one, prime)}; - const auto denominator{bignum_mod_subtract( - bignum_mod_multiply(coefficient_d, y_squared, prime), one, prime)}; + const auto numerator{field_subtract_ct(y_squared, one, field)}; + const auto denominator{field_subtract_ct( + field_mod_multiply_ct(parameters.coefficient_d, y_squared, field), one, + field)}; // The candidate root is x = numerator^3 * denominator * // (numerator^5 * denominator^3)^((p - 3) / 4) (mod p), the field having // p congruent to 3 modulo 4 - const auto numerator_squared{ - bignum_mod_multiply(numerator, numerator, prime)}; + const auto numerator_squared{field_square_ct(numerator, field)}; const auto numerator_cubed{ - bignum_mod_multiply(numerator_squared, numerator, prime)}; + field_mod_multiply_ct(numerator_squared, numerator, field)}; const auto numerator_fifth{ - bignum_mod_multiply(numerator_squared, numerator_cubed, prime)}; - const auto denominator_squared{ - bignum_mod_multiply(denominator, denominator, prime)}; + field_mod_multiply_ct(numerator_squared, numerator_cubed, field)}; + const auto denominator_squared{field_square_ct(denominator, field)}; const auto denominator_cubed{ - bignum_mod_multiply(denominator_squared, denominator, prime)}; + field_mod_multiply_ct(denominator_squared, denominator, field)}; auto exponent{prime}; bignum_subtract_in_place(exponent, bignum_from_u64(3)); exponent = bignum_shift_right(exponent, 2); - const auto root{bignum_mod_exp( - bignum_mod_multiply(numerator_fifth, denominator_cubed, prime), exponent, - prime)}; - auto candidate{bignum_mod_multiply( - bignum_mod_multiply(numerator_cubed, denominator, prime), root, prime)}; + const auto root{field_power_ct( + field_mod_multiply_ct(numerator_fifth, denominator_cubed, field), + exponent, field)}; + auto candidate{field_mod_multiply_ct( + field_mod_multiply_ct(numerator_cubed, denominator, field), root, field)}; // The candidate is correct when denominator * x^2 equals the numerator, and // otherwise no root exists, as the field admits a single square root - const auto check{bignum_mod_multiply( - denominator, bignum_mod_multiply(candidate, candidate, prime), prime)}; - if (bignum_compare(check, numerator) != 0) { + const auto check{field_mod_multiply_ct( + denominator, field_square_ct(candidate, field), field)}; + if (!field_equal_ct(check, numerator, field)) { return std::nullopt; } // Reject the non-canonical zero root with a set sign bit, then select the // root whose low bit matches the encoded sign + bignum_normalize(candidate); if (bignum_is_zero(candidate) && sign_bit == 1) { return std::nullopt; } @@ -656,7 +655,7 @@ inline auto edwards448_decode_point(const std::string_view encoding, return EdwardsPoint{.x = candidate, .y = y, .z = one, - .t = bignum_mod_multiply(candidate, y, prime)}; + .t = field_mod_multiply_ct(candidate, y, field)}; } // The Edwards448 domain parameters (RFC 8032 Section 5.2) @@ -678,11 +677,9 @@ inline auto edwards448_parameters() -> EdwardsParameters { // clang-format off const auto base_encoding{bignum_to_bytes(bignum_from_hex("14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f6900"), 57)}; // clang-format on - parameters.base = edwards448_decode_point(base_encoding, parameters.prime, - parameters.coefficient_d) - .value(); parameters.field = barrett_context(parameters.prime); parameters.order_field = barrett_context(parameters.order); + parameters.base = edwards448_decode_point(base_encoding, parameters).value(); return parameters; } @@ -704,8 +701,7 @@ inline auto edwards448_verify(const std::string_view public_key, } const auto ¶meters{edwards448()}; - const auto public_point{edwards448_decode_point(public_key, parameters.prime, - parameters.coefficient_d)}; + const auto public_point{edwards448_decode_point(public_key, parameters)}; if (!public_point.has_value()) { return false; } @@ -713,8 +709,7 @@ inline auto edwards448_verify(const std::string_view public_key, // The signature is the encoded point R followed by the little-endian scalar // S, which must lie below the group order const auto encoded_r{signature.substr(0, 57)}; - const auto point_r{edwards448_decode_point(encoded_r, parameters.prime, - parameters.coefficient_d)}; + const auto point_r{edwards448_decode_point(encoded_r, parameters)}; if (!point_r.has_value()) { return false; } @@ -737,14 +732,14 @@ inline auto edwards448_verify(const std::string_view public_key, auto scalar_k{bignum_from_bytes_little_endian(digest)}; bignum_reduce(scalar_k, parameters.order); - // The signature holds when [S]B = R + [k]A - const auto left{ - edwards_point_scalar_multiply(scalar_s, parameters.base, parameters)}; - const auto right{edwards_point_add( - point_r.value(), - edwards_point_scalar_multiply(scalar_k, public_point.value(), parameters), + // The signature holds when [S]B = R + [k]A, checked as [S]B + [k](-A) = R so + // that both scalar multiplications share a single pass + const auto combination{edwards_point_double_scalar_multiply( + scalar_s, parameters.base, scalar_k, + edwards_point_negate(public_point.value(), parameters.field), parameters)}; - return edwards_point_equal(left, right, parameters.prime); + return edwards_point_matches_affine(combination, point_r.value(), + parameters.field); } // Prune the 57-byte secret scalar in place (RFC 8032 Section 5.2.5): "The two diff --git a/src/core/crypto/crypto_rsa_other.h b/src/core/crypto/crypto_rsa_other.h index ae8ebc12a..efa5b993c 100644 --- a/src/core/crypto/crypto_rsa_other.h +++ b/src/core/crypto/crypto_rsa_other.h @@ -109,14 +109,7 @@ inline auto rsa_private_result_matches(const Bignum &candidate, } } - std::uint64_t difference{0}; - const auto *power_data{power.words.data()}; - const auto *input_data{input.words.data()}; - for (std::size_t index = 0; index < width; ++index) { - difference |= power_data[index] ^ input_data[index]; - } - - return difference == 0; + return field_equal_ct(power, input, context); } // RSASP1 and RSADP (RFC 8017 Sections 5.2.1 and 5.1.2) over an input already From 6f3e4926093c2bebeee6616e8c1a2dfa33179237 Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Sun, 13 Sep 2026 19:48:12 -0300 Subject: [PATCH 3/4] More Signed-off-by: Juan Cruz Viotti --- src/core/crypto/crypto_bignum.h | 297 +++++++++++++++++++++++--------- src/core/crypto/crypto_ecc.h | 116 +++++++++++-- src/core/crypto/crypto_eddsa.h | 106 +++++++++--- 3 files changed, 398 insertions(+), 121 deletions(-) diff --git a/src/core/crypto/crypto_bignum.h b/src/core/crypto/crypto_bignum.h index 5d552838f..af97021b2 100644 --- a/src/core/crypto/crypto_bignum.h +++ b/src/core/crypto/crypto_bignum.h @@ -304,34 +304,27 @@ inline auto bignum_reduce(BasicBignum &value, // Multiply the divisor by the estimate and subtract from the running value const auto quotient_word{static_cast(estimate)}; - BignumDoubleWord carry{0}; + std::uint64_t carry{0}; std::uint64_t borrow{0}; for (std::size_t index = 0; index < divisor_words; ++index) { const auto product{ (static_cast(quotient_word) * divisor_data[index]) + carry}; - carry = product >> 64U; - const auto subtrahend{static_cast(product)}; - const auto current{dividend_data[offset + index]}; - const auto without_subtrahend{current - subtrahend}; - auto next_borrow{current < subtrahend ? 1U : 0U}; - const auto result_word{without_subtrahend - borrow}; - if (without_subtrahend < borrow) { - next_borrow += 1U; - } - - dividend_data[offset + index] = result_word; - borrow = next_borrow; + carry = static_cast(product >> 64U); + const auto difference{ + static_cast(dividend_data[offset + index]) - + static_cast(product) - borrow}; + dividend_data[offset + index] = static_cast(difference); + borrow = static_cast(difference >> 64U) & 1U; } - const auto current{dividend_data[offset + divisor_words]}; - const auto subtrahend{static_cast(carry)}; - const auto without_subtrahend{current - subtrahend}; - auto next_borrow{current < subtrahend ? 1U : 0U}; - dividend_data[offset + divisor_words] = without_subtrahend - borrow; - if (without_subtrahend < borrow) { - next_borrow += 1U; - } + const auto top_difference{ + static_cast(dividend_data[offset + divisor_words]) - + carry - borrow}; + dividend_data[offset + divisor_words] = + static_cast(top_difference); + const auto next_borrow{static_cast(top_difference >> 64U) & + 1U}; // The estimate was at most one too large, so add the divisor back when the // subtraction borrowed past the top @@ -369,16 +362,14 @@ inline auto bignum_multiply(const BasicBignum &left, const auto *right_data{right.words.data()}; auto *result_data{result.words.data()}; for (std::size_t left_index = 0; left_index < left.size; ++left_index) { + const auto left_word{left_data[left_index]}; + const auto columns{std::min(right.size, Capacity - left_index)}; std::uint64_t carry{0}; - for (std::size_t right_index = 0; right_index < right.size; ++right_index) { + for (std::size_t right_index = 0; right_index < columns; ++right_index) { const auto destination{left_index + right_index}; - if (destination >= Capacity) { - break; - } - - const auto product{(static_cast(left_data[left_index]) * - right_data[right_index]) + - result_data[destination] + carry}; + const auto product{ + (static_cast(left_word) * right_data[right_index]) + + result_data[destination] + carry}; result_data[destination] = static_cast(product); carry = static_cast(product >> 64U); } @@ -407,7 +398,8 @@ inline auto bignum_mod_exp(const BasicBignum &base, const auto exponent_bits{bignum_bit_length(exponent)}; for (std::size_t index = exponent_bits; index > 0; --index) { - result = bignum_multiply(result, result); + result = bignum_square_fixed(result, result.size); + bignum_normalize(result); bignum_reduce(result, modulus); if (bignum_get_bit(exponent, index - 1)) { result = bignum_multiply(result, reduced_base); @@ -618,20 +610,37 @@ inline auto bignum_subtract_fixed(const BasicBignum &left, auto *out_data{out.words.data()}; std::uint64_t borrow{0}; for (std::size_t index = 0; index < words; ++index) { - const auto left_word{left_data[index]}; - const auto right_word{right_data[index]}; - const auto without_right{left_word - right_word}; - const std::uint64_t borrow_from_right{left_word < right_word ? 1U : 0U}; - const auto result_word{without_right - borrow}; - const std::uint64_t borrow_from_previous{without_right < borrow ? 1U : 0U}; - out_data[index] = result_word; - borrow = borrow_from_right | borrow_from_previous; + // A borrow out of the word wraps the double word, setting its high half + const auto total{static_cast(left_data[index]) - + right_data[index] - borrow}; + out_data[index] = static_cast(total); + borrow = static_cast(total >> 64U) & 1U; } out.size = words; return borrow; } +// Add the modulus, masked to all ones or to zero, over the given number of +// words, discarding the carry out. It is the branch-free correction step of +// the constant-time modular arithmetic, undoing a trial subtraction of the +// modulus that borrowed +template +inline auto bignum_add_masked(BasicBignum &value, + const BasicBignum &modulus, + const std::uint64_t mask, + const std::size_t words) noexcept -> void { + auto *value_data{value.words.data()}; + const auto *modulus_data{modulus.words.data()}; + std::uint64_t carry{0}; + for (std::size_t index = 0; index < words; ++index) { + const auto total{static_cast(value_data[index]) + + (modulus_data[index] & mask) + carry}; + value_data[index] = static_cast(total); + carry = static_cast(total >> 64U); + } +} + // Multiply visiting exactly the given word counts, so timing does not reveal // where either operand's significant words fall template @@ -645,13 +654,14 @@ inline auto bignum_multiply_fixed(const BasicBignum &left, const auto *right_data{right.words.data()}; auto *result_data{result.words.data()}; for (std::size_t left_index = 0; left_index < left_words; ++left_index) { + const auto left_word{left_data[left_index]}; std::uint64_t carry{0}; for (std::size_t right_index = 0; right_index < right_words; ++right_index) { const auto destination{left_index + right_index}; - const auto product{(static_cast(left_data[left_index]) * - right_data[right_index]) + - result_data[destination] + carry}; + const auto product{ + (static_cast(left_word) * right_data[right_index]) + + result_data[destination] + carry}; result_data[destination] = static_cast(product); carry = static_cast(product >> 64U); } @@ -709,13 +719,13 @@ inline auto bignum_square_fixed(const BasicBignum &value, const auto *value_data{value.words.data()}; auto *result_data{result.words.data()}; for (std::size_t left_index = 0; left_index < words; ++left_index) { + const auto left_word{value_data[left_index]}; std::uint64_t carry{0}; for (std::size_t right_index = left_index + 1; right_index < words; ++right_index) { const auto destination{left_index + right_index}; const auto product{ - (static_cast(value_data[left_index]) * - value_data[right_index]) + + (static_cast(left_word) * value_data[right_index]) + result_data[destination] + carry}; result_data[destination] = static_cast(product); carry = static_cast(product >> 64U); @@ -828,6 +838,11 @@ template struct BasicBarrettContext { // a special form of the modulus allows, taken instead of the Barrett one when // set Reduction reduce{nullptr}; + // The constants of a context that reduces through Montgomery multiplication: + // the negated inverse of the modulus modulo 2^64, left zero by a context that + // does not, and R^2 modulo the modulus for moving values into Montgomery form + std::uint64_t montgomery_factor{0}; + BasicBignum montgomery_square; }; using BarrettContext = BasicBarrettContext; @@ -925,6 +940,89 @@ barrett_reduce(const BasicBignum &value, return remainder; } +// Montgomery reduction in constant time, the separated operand scanning form +// (Koc, Acar, and Kaliski 1996). It divides a value below the modulus times +// R = 2^(64 * words) by R modulo the modulus, clearing one low word per step +// with a multiple of the modulus chosen from that word alone, so every step +// visits the same words whatever the operands hold, and the quotient, below +// twice the modulus, needs a single subtraction of the modulus, undone through +// a masked addition when it borrows past the overflow word +template +inline auto +montgomery_reduce_ct(const BasicBignum &value, + const BasicBarrettContext &context) noexcept + -> BasicBignum { + const auto width{context.words}; + auto accumulator{value}; + auto *accumulator_data{accumulator.words.data()}; + const auto *modulus_data{context.modulus.words.data()}; + std::uint64_t overflow{0}; + for (std::size_t index = 0; index < width; ++index) { + const std::uint64_t multiplier{accumulator_data[index] * + context.montgomery_factor}; + std::uint64_t carry{0}; + for (std::size_t word = 0; word < width; ++word) { + const auto total{ + (static_cast(multiplier) * modulus_data[word]) + + accumulator_data[index + word] + carry}; + accumulator_data[index + word] = static_cast(total); + carry = static_cast(total >> 64U); + } + + const auto total{ + static_cast(accumulator_data[index + width]) + carry + + overflow}; + accumulator_data[index + width] = static_cast(total); + overflow = static_cast(total >> 64U); + } + + BasicBignum result; + auto *result_data{result.words.data()}; + std::uint64_t borrow{0}; + for (std::size_t index = 0; index < width; ++index) { + const auto total{ + static_cast(accumulator_data[index + width]) - + modulus_data[index] - borrow}; + result_data[index] = static_cast(total); + borrow = static_cast(total >> 64U) & 1U; + } + + bignum_add_masked( + result, context.modulus, + std::uint64_t{0} - static_cast(borrow > overflow), width); + result.size = width; + return result; +} + +// A context reducing through Montgomery multiplication, built from a Barrett +// one. The negated inverse of the modulus modulo 2^64 comes from Newton +// iteration, which doubles the correct low bits at every step from the three +// that an odd word already has, and R^2 modulo the modulus comes from the +// Barrett quotient as 2^(128 * words) minus the quotient times the modulus. +// Both run in constant time, as the modulus may be a secret prime factor. The +// modulus must be odd +template +inline auto +montgomery_context(const BasicBarrettContext &barrett) noexcept + -> BasicBarrettContext { + auto context{barrett}; + const auto width{context.words}; + const auto lowest{context.modulus.words[0]}; + std::uint64_t inverse{lowest}; + for (std::size_t step = 0; step < 5; ++step) { + inverse *= 2U - (lowest * inverse); + } + + context.montgomery_factor = std::uint64_t{0} - inverse; + const auto product{bignum_multiply_low_fixed(context.factor, context.modulus, + width + 1, width, 2 * width)}; + const BasicBignum zero; + bignum_subtract_fixed(zero, product, 2 * width, context.montgomery_square); + context.montgomery_square.size = width; + context.reduce = &montgomery_reduce_ct; + return context; +} + template inline auto field_mod_multiply_ct(const BasicBignum &left, @@ -947,6 +1045,34 @@ field_square_ct(const BasicBignum &value, : context.reduce(square, context); } +// Move a reduced value into the representation of a context, its Montgomery +// form x * R modulo the modulus when the context reduces through Montgomery +// multiplication, and the value itself otherwise +template +inline auto +field_to_montgomery_ct(const BasicBignum &value, + const BasicBarrettContext &context) noexcept + -> BasicBignum { + if (context.montgomery_factor == 0) { + return value; + } + + return field_mod_multiply_ct(value, context.montgomery_square, context); +} + +// Move a value back out of the representation of a context +template +inline auto +field_from_montgomery_ct(const BasicBignum &value, + const BasicBarrettContext &context) noexcept + -> BasicBignum { + if (context.montgomery_factor == 0) { + return value; + } + + return montgomery_reduce_ct(value, context); +} + template inline auto field_add_ct(const BasicBignum &left, const BasicBignum &right, @@ -955,21 +1081,29 @@ inline auto field_add_ct(const BasicBignum &left, const auto width{context.words}; const auto *left_data{left.words.data()}; const auto *right_data{right.words.data()}; - BasicBignum sum; - auto *sum_data{sum.words.data()}; + const auto *modulus_data{context.modulus.words.data()}; + BasicBignum result; + auto *result_data{result.words.data()}; + // The modulus comes off the sum as the sum forms, and goes back on when that + // subtraction borrowed past the carry out of the sum std::uint64_t carry{0}; + std::uint64_t borrow{0}; for (std::size_t index = 0; index < width; ++index) { const auto total{static_cast(left_data[index]) + right_data[index] + carry}; - sum_data[index] = static_cast(total); carry = static_cast(total >> 64U); + const auto difference{ + static_cast(static_cast(total)) - + modulus_data[index] - borrow}; + result_data[index] = static_cast(difference); + borrow = static_cast(difference >> 64U) & 1U; } - sum_data[width] = carry; - sum.size = width + 1; - auto reduced{bignum_conditional_subtract(sum, context.modulus, width + 1)}; - reduced.size = width; - return reduced; + bignum_add_masked( + result, context.modulus, + std::uint64_t{0} - static_cast(borrow > carry), width); + result.size = width; + return result; } template @@ -981,23 +1115,9 @@ field_subtract_ct(const BasicBignum &left, const auto width{context.words}; BasicBignum difference; const auto borrow{bignum_subtract_fixed(left, right, width, difference)}; - const auto *difference_data{difference.words.data()}; - const auto *modulus_data{context.modulus.words.data()}; - BasicBignum wrapped; - auto *wrapped_data{wrapped.words.data()}; - std::uint64_t carry{0}; - for (std::size_t index = 0; index < width; ++index) { - const auto total{static_cast(difference_data[index]) + - modulus_data[index] + carry}; - wrapped_data[index] = static_cast(total); - carry = static_cast(total >> 64U); - } - - wrapped.size = width; - auto result{ - bignum_conditional_select(borrow != 0, wrapped, difference, width)}; - result.size = width; - return result; + bignum_add_masked(difference, context.modulus, std::uint64_t{0} - borrow, + width); + return difference; } // Whether two field elements, both reduced below the modulus, are equal, @@ -1020,27 +1140,30 @@ field_equal_ct(const BasicBignum &left, // Raise a value to a public exponent over the field in fixed four-bit windows. // The exponent is public, so its windows index the table of powers directly and // skip the multiplication of a zero window, while the field multiplications -// underneath do not depend on the value being raised +// underneath do not depend on the value being raised. A context without a +// special reduction runs the powering in Montgomery form, whose reduction is +// cheaper than the Barrett one, so its modulus must be odd template inline auto field_power_ct(const BasicBignum &value, const BasicBignum &exponent, const BasicBarrettContext &context) noexcept -> BasicBignum { + const auto field{context.reduce == nullptr ? montgomery_context(context) + : context}; std::array, 16> powers{}; - powers[0].words[0] = 1; + powers[0] = field_to_montgomery_ct(bignum_from_u64(1), field); powers[0].size = context.words; - powers[1] = barrett_reduce(value, context); + powers[1] = field_to_montgomery_ct(barrett_reduce(value, context), field); for (std::size_t index = 2; index < powers.size(); ++index) { - powers[index] = - field_mod_multiply_ct(powers[index - 1], powers[1], context); + powers[index] = field_mod_multiply_ct(powers[index - 1], powers[1], field); } auto result{powers[0]}; const auto windows{(bignum_bit_length(exponent) + 3) / 4}; for (std::size_t window = windows; window > 0; --window) { for (std::size_t step = 0; step < 4; ++step) { - result = field_square_ct(result, context); + result = field_square_ct(result, field); } std::size_t digit{0}; @@ -1051,11 +1174,11 @@ field_power_ct(const BasicBignum &value, } if (digit != 0) { - result = field_mod_multiply_ct(result, powers[digit], context); + result = field_mod_multiply_ct(result, powers[digit], field); } } - return result; + return field_from_montgomery_ct(result, field); } // Fermat inverse over the field in constant time. The exponent is the public @@ -1076,28 +1199,34 @@ field_inverse_ct(const BasicBignum &value, // constant time. The exponent is secret, so it is consumed in fixed four-bit // windows over a count fixed by the public modulus, and every window multiplies // by a power of the base taken from a precomputed table through a masked scan -// over all of its entries rather than an index. The modulus need not be prime +// over all of its entries rather than an index. The exponentiation runs in +// Montgomery form, whose reduction is cheaper than the Barrett one, and the +// Montgomery constants are wiped on return, as the modulus may be a secret +// prime factor. The modulus need not be prime, but must be odd template inline auto bignum_mod_exp_ct(const BasicBignum &base, const BasicBignum &exponent, const BasicBarrettContext &context) noexcept -> BasicBignum { + auto montgomery{montgomery_context(context)}; + const SecureBignumScope montgomery_modulus_scope{montgomery.modulus}; + const SecureBignumScope montgomery_factor_scope{montgomery.factor}; + const SecureBignumScope montgomery_square_scope{montgomery.montgomery_square}; const auto width{context.words}; std::array, 16> powers{}; - powers[0].words[0] = 1; - powers[0].size = width; - powers[1] = barrett_reduce(base, context); + powers[0] = field_to_montgomery_ct(bignum_from_u64(1), montgomery); + powers[1] = field_to_montgomery_ct(barrett_reduce(base, context), montgomery); for (std::size_t index = 2; index < powers.size(); ++index) { powers[index] = - field_mod_multiply_ct(powers[index - 1], powers[1], context); + field_mod_multiply_ct(powers[index - 1], powers[1], montgomery); } const auto windows{(bignum_bit_length(context.modulus) + 3) / 4}; auto result{powers[0]}; for (std::size_t window = windows; window > 0; --window) { for (std::size_t step = 0; step < 4; ++step) { - result = field_square_ct(result, context); + result = field_square_ct(result, montgomery); } std::size_t digit{0}; @@ -1113,10 +1242,10 @@ bignum_mod_exp_ct(const BasicBignum &base, selected, width); } - result = field_mod_multiply_ct(result, selected, context); + result = field_mod_multiply_ct(result, selected, montgomery); } - return result; + return field_from_montgomery_ct(result, montgomery); } template diff --git a/src/core/crypto/crypto_ecc.h b/src/core/crypto/crypto_ecc.h index 904aa52fb..a86c0fd1d 100644 --- a/src/core/crypto/crypto_ecc.h +++ b/src/core/crypto/crypto_ecc.h @@ -183,7 +183,8 @@ inline auto field_combine(CurveBignum &positive, const CurveBignum &negative, // (FIPS 186-4 Appendix D.2.3) inline auto field_reduce_p256(CurveBignum &value, const CurveBignum &prime) noexcept -> void { - std::array c{}; + std::array limbs{}; + auto *c{limbs.data()}; for (std::size_t index = 0; index < 16; ++index) { c[index] = field_word(value, index); } @@ -222,7 +223,8 @@ inline auto field_reduce_p256(CurveBignum &value, // (FIPS 186-4 Appendix D.2.4) inline auto field_reduce_p384(CurveBignum &value, const CurveBignum &prime) noexcept -> void { - std::array c{}; + std::array limbs{}; + auto *c{limbs.data()}; for (std::size_t index = 0; index < 24; ++index) { c[index] = field_word(value, index); } @@ -299,7 +301,10 @@ inline auto field_mod_multiply(const CurveBignum &left, inline auto field_square(const CurveBignum &value, const EllipticCurveParameters &curve) noexcept -> CurveBignum { - return field_mod_multiply(value, value, curve); + auto result{bignum_square_fixed(value, value.size)}; + bignum_normalize(result); + field_reduce(result, curve); + return result; } inline auto point_is_infinity(const JacobianPoint &point) noexcept -> bool { @@ -583,6 +588,51 @@ inline auto point_complete_add(const JacobianPoint &left, return {.x = x3, .y = y3, .z = z3}; } +// Exception-free projective point doubling for the same curves (Renes, +// Costello, and Batina 2016, Algorithm 6). It agrees with the complete addition +// of a point to itself, the identity included, for four fewer multiplications, +// three of the rest being squarings +inline auto point_complete_double(const JacobianPoint &point, + const CurveBignum &coefficient_b, + const CurveBarrettContext &field) noexcept + -> JacobianPoint { + auto t0{field_square_ct(point.x, field)}; + const auto t1{field_square_ct(point.y, field)}; + auto t2{field_square_ct(point.z, field)}; + auto t3{field_mod_multiply_ct(point.x, point.y, field)}; + t3 = field_add_ct(t3, t3, field); + auto z3{field_mod_multiply_ct(point.x, point.z, field)}; + z3 = field_add_ct(z3, z3, field); + auto y3{field_mod_multiply_ct(coefficient_b, t2, field)}; + y3 = field_subtract_ct(y3, z3, field); + auto x3{field_add_ct(y3, y3, field)}; + y3 = field_add_ct(x3, y3, field); + x3 = field_subtract_ct(t1, y3, field); + y3 = field_add_ct(t1, y3, field); + y3 = field_mod_multiply_ct(x3, y3, field); + x3 = field_mod_multiply_ct(x3, t3, field); + t3 = field_add_ct(t2, t2, field); + t2 = field_add_ct(t2, t3, field); + z3 = field_mod_multiply_ct(coefficient_b, z3, field); + z3 = field_subtract_ct(z3, t2, field); + z3 = field_subtract_ct(z3, t0, field); + t3 = field_add_ct(z3, z3, field); + z3 = field_add_ct(z3, t3, field); + t3 = field_add_ct(t0, t0, field); + t0 = field_add_ct(t3, t0, field); + t0 = field_subtract_ct(t0, t2, field); + t0 = field_mod_multiply_ct(t0, z3, field); + y3 = field_add_ct(y3, t0, field); + t0 = field_mod_multiply_ct(point.y, point.z, field); + t0 = field_add_ct(t0, t0, field); + z3 = field_mod_multiply_ct(t0, z3, field); + x3 = field_subtract_ct(x3, z3, field); + z3 = field_mod_multiply_ct(t0, t1, field); + z3 = field_add_ct(z3, z3, field); + z3 = field_add_ct(z3, z3, field); + return {.x = x3, .y = y3, .z = z3}; +} + // NIST P-521 field reduction in constant time, for the signing ladder. The // prime is 2^521 - 1, so the bits of a product above position 521 fold back // onto the low 521 bits with one fixed-width addition, and the sum, at most @@ -622,32 +672,68 @@ inline auto curve_field_context(const EllipticCurveParameters &curve) return field; } +// The field arithmetic context of the signing ladder, taking the Mersenne +// reduction for P-521 and Montgomery form for the other curves, whose +// reduction is cheaper than the Barrett one +inline auto curve_ladder_context(const EllipticCurveParameters &curve) + -> CurveBarrettContext { + const auto field{curve_field_context(curve)}; + if (curve.reduction == NISTPrime::P521) { + return field; + } + + return montgomery_context(field); +} + +// Move the coordinates of a projective point into the representation of a +// context, and back out of it +inline auto point_to_montgomery(const JacobianPoint &point, + const CurveBarrettContext &field) noexcept + -> JacobianPoint { + return {.x = field_to_montgomery_ct(point.x, field), + .y = field_to_montgomery_ct(point.y, field), + .z = field_to_montgomery_ct(point.z, field)}; +} + +inline auto point_from_montgomery(const JacobianPoint &point, + const CurveBarrettContext &field) noexcept + -> JacobianPoint { + return {.x = field_from_montgomery_ct(point.x, field), + .y = field_from_montgomery_ct(point.y, field), + .z = field_from_montgomery_ct(point.z, field)}; +} + // For the signing path, where the scalar is the secret nonce: a fixed four-bit // window ladder over the complete formula. The window count is fixed by the // public order length, every window doubles four times and adds one table entry // taken through a masked scan over the whole table, and the complete formula // absorbs the identity entry of a zero window, so neither the control flow nor -// the field arithmetic underneath depends on the scalar. The input point and -// the result are projective +// the field arithmetic underneath depends on the scalar. The arithmetic runs in +// the representation of the ladder context, and the input point and the result +// are projective outside of it inline auto point_scalar_multiply_constant_time( const CurveBignum &scalar, const JacobianPoint &point, const EllipticCurveParameters &curve) -> JacobianPoint { - const auto field{curve_field_context(curve)}; + const auto field{curve_ladder_context(curve)}; + const auto coefficient_b{field_to_montgomery_ct(curve.coefficient_b, field)}; + const auto base_point{point_to_montgomery(point, field)}; std::array multiples{}; - multiples[0] = JacobianPoint{.x = CurveBignum{}, - .y = bignum_from_u64(1), - .z = CurveBignum{}}; - multiples[1] = point; + multiples[0] = + JacobianPoint{.x = CurveBignum{}, + .y = field_to_montgomery_ct( + bignum_from_u64(1), field), + .z = CurveBignum{}}; + multiples[1] = base_point; for (std::size_t index = 2; index < multiples.size(); ++index) { - multiples[index] = point_complete_add(multiples[index - 1], point, - curve.coefficient_b, field); + multiples[index] = point_complete_add(multiples[index - 1], base_point, + coefficient_b, field); } auto result{multiples[0]}; const auto windows{(bignum_bit_length(curve.order) + 3) / 4}; for (std::size_t window = windows; window > 0; --window) { for (std::size_t step = 0; step < 4; ++step) { - result = point_complete_add(result, result, curve.coefficient_b, field); + result = point_complete_double(result, coefficient_b, field); } std::size_t digit{0}; @@ -663,10 +749,10 @@ inline auto point_scalar_multiply_constant_time( selected, field.words); } - result = point_complete_add(result, selected, curve.coefficient_b, field); + result = point_complete_add(result, selected, coefficient_b, field); } - return result; + return point_from_montgomery(result, field); } inline auto point_affine_x_constant_time(const JacobianPoint &point, diff --git a/src/core/crypto/crypto_eddsa.h b/src/core/crypto/crypto_eddsa.h index 7d84f193d..10035a51f 100644 --- a/src/core/crypto/crypto_eddsa.h +++ b/src/core/crypto/crypto_eddsa.h @@ -38,6 +38,10 @@ struct EdwardsParameters { CurveBignum order; CurveBignum coefficient_a; CurveBignum coefficient_d; + // Whether the coefficient a is -1, as on the twisted Ed25519 curve, rather + // than 1, as on Ed448, which lets the point formulas negate or keep a value + // instead of multiplying it by the coefficient + bool coefficient_a_is_minus_one{false}; // A square root of -1 modulo the Ed25519 prime, which recovers the second // candidate root when decoding a point, and left zero for Ed448 CurveBignum square_root_of_minus_one; @@ -59,11 +63,15 @@ inline auto bignum_from_bytes_little_endian(const std::string_view input) // Ed25519 field reduction in constant time, for the signing ladder. The prime // is 2^255 - 19, so 2^256 is congruent to 38 modulo it, and the high half of a -// product folds onto the low half scaled by 38. Two more folds absorb the carry -// out of the top word, which the second can raise only to one and the third -// clears, and the result, below 2^256, needs at most two masked subtractions -inline auto field_reduce_25519_ct(const CurveBignum &value, - const CurveBarrettContext &context) noexcept +// product folds onto the low half scaled by 38. A second fold absorbs the carry +// out of the top word, leaving at most one, and a third folds that carry, worth +// 38, and bit 255, worth 19, back in, leaving a value below 2^255 + 57. Such a +// value is at least the prime exactly when adding 19 to it reaches bit 255, and +// that sum without bit 255 is then the reduced value, so a single masked +// selection finishes the reduction +inline auto field_reduce_25519_ct( + const CurveBignum &value, + [[maybe_unused]] const CurveBarrettContext &context) noexcept -> CurveBignum { const auto *value_data{value.words.data()}; CurveBignum folded; @@ -77,23 +85,40 @@ inline auto field_reduce_25519_ct(const CurveBignum &value, carry = static_cast(total >> 64U); } - for (std::size_t fold = 0; fold < 2; ++fold) { - BignumDoubleWord addend{static_cast(carry) * 38U}; - for (std::size_t index = 0; index < 4; ++index) { - const auto total{static_cast(folded_data[index]) + - addend}; - folded_data[index] = static_cast(total); - addend = total >> 64U; - } + BignumDoubleWord addend{static_cast(carry) * 38U}; + for (std::size_t index = 0; index < 4; ++index) { + const auto total{static_cast(folded_data[index]) + + addend}; + folded_data[index] = static_cast(total); + addend = total >> 64U; + } - carry = static_cast(addend); + const auto excess{(folded_data[3] >> 63U) + + (static_cast(addend) << 1U)}; + folded_data[3] &= 0x7fffffffffffffffULL; + addend = static_cast(excess) * 19U; + for (std::size_t index = 0; index < 4; ++index) { + const auto total{static_cast(folded_data[index]) + + addend}; + folded_data[index] = static_cast(total); + addend = total >> 64U; + } + + CurveBignum reduced; + auto *reduced_data{reduced.words.data()}; + addend = 19U; + for (std::size_t index = 0; index < 4; ++index) { + const auto total{static_cast(folded_data[index]) + + addend}; + reduced_data[index] = static_cast(total); + addend = total >> 64U; } + const auto at_least_prime{(reduced_data[3] >> 63U) != 0}; + reduced_data[3] &= 0x7fffffffffffffffULL; folded.size = 4; - auto reduced{bignum_conditional_subtract(folded, context.modulus, 4)}; - reduced = bignum_conditional_subtract(reduced, context.modulus, 4); reduced.size = 4; - return reduced; + return bignum_conditional_select(at_least_prime, reduced, folded, 4); } inline auto edwards_point_conditional_select(const bool condition, @@ -133,14 +158,48 @@ inline auto edwards_point_add_constant_time( field_add_ct(a, b, field), field)}; const auto f{field_subtract_ct(d, c, field)}; const auto g{field_add_ct(d, c, field)}; - const auto h{field_subtract_ct( - b, field_mod_multiply_ct(parameters.coefficient_a, a, field), field)}; + // H = B - a * A, where a is -1 or 1 + const auto h{parameters.coefficient_a_is_minus_one + ? field_add_ct(b, a, field) + : field_subtract_ct(b, a, field)}; return EdwardsPoint{.x = field_mod_multiply_ct(e, f, field), .y = field_mod_multiply_ct(g, h, field), .z = field_mod_multiply_ct(f, g, field), .t = field_mod_multiply_ct(e, h, field)}; } +// Dedicated doubling in extended coordinates (Hisil, Wong, Carter, and Dawson +// 2008, Section 3.3), the formula RFC 8032 Section 5.1.4 recommends for +// Ed25519, written for a coefficient a of -1 or 1. It reads neither d nor the T +// coordinate of the input and, like the unified addition, holds for every point +// of these curves, the identity included. The T coordinate of the result is +// only computed when requested, as a doubling that feeds another doubling never +// reads it +inline auto edwards_point_double_constant_time( + const EdwardsPoint &point, const EdwardsParameters ¶meters, + const CurveBarrettContext &field, const bool extended) noexcept + -> EdwardsPoint { + const auto a{field_square_ct(point.x, field)}; + const auto b{field_square_ct(point.y, field)}; + const auto z_squared{field_square_ct(point.z, field)}; + const auto c{field_add_ct(z_squared, z_squared, field)}; + // D = a * A, where a is -1 or 1 + const auto d{parameters.coefficient_a_is_minus_one + ? field_subtract_ct(CurveBignum{}, a, field) + : a}; + const auto e{field_subtract_ct( + field_square_ct(field_add_ct(point.x, point.y, field), field), + field_add_ct(a, b, field), field)}; + const auto g{field_add_ct(d, b, field)}; + const auto f{field_subtract_ct(g, c, field)}; + const auto h{field_subtract_ct(d, b, field)}; + return EdwardsPoint{.x = field_mod_multiply_ct(e, f, field), + .y = field_mod_multiply_ct(g, h, field), + .z = field_mod_multiply_ct(f, g, field), + .t = extended ? field_mod_multiply_ct(e, h, field) + : CurveBignum{}}; +} + // For the signing path, where the scalar is secret: a fixed four-bit window // ladder over the complete Edwards formulas evaluated in constant time. The // window count is fixed by the public field size, every window doubles four @@ -167,8 +226,8 @@ inline auto edwards_point_scalar_multiply_constant_time( const auto windows{(bignum_bit_length(parameters.prime) + 3) / 4}; for (std::size_t window = windows; window > 0; --window) { for (std::size_t step = 0; step < 4; ++step) { - result = - edwards_point_add_constant_time(result, result, parameters, field); + result = edwards_point_double_constant_time(result, parameters, field, + step == 3); } std::size_t digit{0}; @@ -222,9 +281,10 @@ inline auto edwards_point_double_scalar_multiply( const auto second_bits{bignum_bit_length(second_scalar)}; const auto bits{first_bits > second_bits ? first_bits : second_bits}; for (std::size_t index = bits; index > 0; --index) { - result = edwards_point_add_constant_time(result, result, parameters, field); const auto first_bit{bignum_get_bit(first_scalar, index - 1)}; const auto second_bit{bignum_get_bit(second_scalar, index - 1)}; + result = edwards_point_double_constant_time(result, parameters, field, + first_bit || second_bit); if (first_bit && second_bit) { result = edwards_point_add_constant_time(result, combined, parameters, field); @@ -381,6 +441,7 @@ inline auto edwards25519_parameters() -> EdwardsParameters { parameters.coefficient_a = parameters.prime; bignum_subtract_in_place(parameters.coefficient_a, bignum_from_u64(1)); + parameters.coefficient_a_is_minus_one = true; // d = -121665 / 121666 (mod p) auto negated_numerator{parameters.prime}; @@ -668,6 +729,7 @@ inline auto edwards448_parameters() -> EdwardsParameters { // The curve coefficient a is 1, and d is -39081 (mod p) parameters.coefficient_a = bignum_from_u64(1); + parameters.coefficient_a_is_minus_one = false; parameters.coefficient_d = parameters.prime; bignum_subtract_in_place(parameters.coefficient_d, bignum_from_u64(39081)); From fbbf44e2cf39aab08ef14d545336453f24580ed3 Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Sun, 13 Sep 2026 19:59:49 -0300 Subject: [PATCH 4/4] More Signed-off-by: Juan Cruz Viotti --- src/core/crypto/crypto_ecc.h | 46 +++++++++++++++++++++------ src/core/crypto/crypto_eddsa.h | 38 ++++++++++------------ src/core/crypto/crypto_sha256_other.h | 24 ++++++-------- src/core/crypto/crypto_sha2_64.h | 24 ++++++-------- 4 files changed, 70 insertions(+), 62 deletions(-) diff --git a/src/core/crypto/crypto_ecc.h b/src/core/crypto/crypto_ecc.h index a86c0fd1d..a9df10557 100644 --- a/src/core/crypto/crypto_ecc.h +++ b/src/core/crypto/crypto_ecc.h @@ -635,29 +635,55 @@ inline auto point_complete_double(const JacobianPoint &point, // NIST P-521 field reduction in constant time, for the signing ladder. The // prime is 2^521 - 1, so the bits of a product above position 521 fold back -// onto the low 521 bits with one fixed-width addition, and the sum, at most -// twice the prime, needs at most two masked subtractions -inline auto field_reduce_p521_ct(const CurveBignum &value, - const CurveBarrettContext &context) noexcept +// onto the low 521 bits with one fixed-width addition, and folding the bit 521 +// of that sum once more leaves a value no greater than 2^521. Such a value is +// at least the prime exactly when adding one to it reaches bit 521, and that +// sum without bit 521 is then the reduced value, so a single masked selection +// finishes the reduction +inline auto field_reduce_p521_ct( + const CurveBignum &value, + [[maybe_unused]] const CurveBarrettContext &context) noexcept -> CurveBignum { const auto *value_data{value.words.data()}; CurveBignum sum; auto *sum_data{sum.words.data()}; std::uint64_t carry{0}; - for (std::size_t index = 0; index < 9; ++index) { - const auto low{index < 8 ? value_data[index] : value_data[8] & 0x1ffULL}; + for (std::size_t index = 0; index < 8; ++index) { const auto high{(value_data[index + 8] >> 9U) | (value_data[index + 9] << 55U)}; - const auto total{static_cast(low) + high + carry}; + const auto total{static_cast(value_data[index]) + high + + carry}; sum_data[index] = static_cast(total); carry = static_cast(total >> 64U); } + // The top word keeps only the nine bits below position 521, and the product + // bits folded onto it stay below 2^9, so its sum cannot overflow + sum_data[8] = (value_data[8] & 0x1ffULL) + + ((value_data[16] >> 9U) | (value_data[17] << 55U)) + carry; + + std::uint64_t addend{sum_data[8] >> 9U}; + sum_data[8] &= 0x1ffULL; + for (std::size_t index = 0; index < 9; ++index) { + const auto total{static_cast(sum_data[index]) + addend}; + sum_data[index] = static_cast(total); + addend = static_cast(total >> 64U); + } + + CurveBignum reduced; + auto *reduced_data{reduced.words.data()}; + addend = 1; + for (std::size_t index = 0; index < 9; ++index) { + const auto total{static_cast(sum_data[index]) + addend}; + reduced_data[index] = static_cast(total); + addend = static_cast(total >> 64U); + } + + const auto at_least_prime{(reduced_data[8] >> 9U) != 0}; + reduced_data[8] &= 0x1ffULL; sum.size = 9; - auto reduced{bignum_conditional_subtract(sum, context.modulus, 9)}; - reduced = bignum_conditional_subtract(reduced, context.modulus, 9); reduced.size = 9; - return reduced; + return bignum_conditional_select(at_least_prime, reduced, sum, 9); } // The constant-time field arithmetic context of a curve, taking the Mersenne diff --git a/src/core/crypto/crypto_eddsa.h b/src/core/crypto/crypto_eddsa.h index 10035a51f..84bd1acee 100644 --- a/src/core/crypto/crypto_eddsa.h +++ b/src/core/crypto/crypto_eddsa.h @@ -63,12 +63,12 @@ inline auto bignum_from_bytes_little_endian(const std::string_view input) // Ed25519 field reduction in constant time, for the signing ladder. The prime // is 2^255 - 19, so 2^256 is congruent to 38 modulo it, and the high half of a -// product folds onto the low half scaled by 38. A second fold absorbs the carry -// out of the top word, leaving at most one, and a third folds that carry, worth -// 38, and bit 255, worth 19, back in, leaving a value below 2^255 + 57. Such a -// value is at least the prime exactly when adding 19 to it reaches bit 255, and -// that sum without bit 255 is then the reduced value, so a single masked -// selection finishes the reduction +// product folds onto the low half scaled by 38, carrying at most 38 out of the +// top word. A second fold takes that carry, worth 38 each, and bit 255, worth +// 19, back in at once, leaving a value below 2^255 + 1463. Such a value is at +// least the prime exactly when adding 19 to it reaches bit 255, and that sum +// without bit 255 is then the reduced value, so a single masked selection +// finishes the reduction inline auto field_reduce_25519_ct( const CurveBignum &value, [[maybe_unused]] const CurveBarrettContext &context) noexcept @@ -85,23 +85,13 @@ inline auto field_reduce_25519_ct( carry = static_cast(total >> 64U); } - BignumDoubleWord addend{static_cast(carry) * 38U}; - for (std::size_t index = 0; index < 4; ++index) { - const auto total{static_cast(folded_data[index]) + - addend}; - folded_data[index] = static_cast(total); - addend = total >> 64U; - } - - const auto excess{(folded_data[3] >> 63U) + - (static_cast(addend) << 1U)}; + std::uint64_t addend{((folded_data[3] >> 63U) + (carry << 1U)) * 19U}; folded_data[3] &= 0x7fffffffffffffffULL; - addend = static_cast(excess) * 19U; for (std::size_t index = 0; index < 4; ++index) { const auto total{static_cast(folded_data[index]) + addend}; folded_data[index] = static_cast(total); - addend = total >> 64U; + addend = static_cast(total >> 64U); } CurveBignum reduced; @@ -111,14 +101,18 @@ inline auto field_reduce_25519_ct( const auto total{static_cast(folded_data[index]) + addend}; reduced_data[index] = static_cast(total); - addend = total >> 64U; + addend = static_cast(total >> 64U); } - const auto at_least_prime{(reduced_data[3] >> 63U) != 0}; + const std::uint64_t mask{std::uint64_t{0} - (reduced_data[3] >> 63U)}; reduced_data[3] &= 0x7fffffffffffffffULL; + for (std::size_t index = 0; index < 4; ++index) { + folded_data[index] = + (reduced_data[index] & mask) | (folded_data[index] & ~mask); + } + folded.size = 4; - reduced.size = 4; - return bignum_conditional_select(at_least_prime, reduced, folded, 4); + return folded; } inline auto edwards_point_conditional_select(const bool condition, diff --git a/src/core/crypto/crypto_sha256_other.h b/src/core/crypto/crypto_sha256_other.h index 343fc93d4..dc28a911b 100644 --- a/src/core/crypto/crypto_sha256_other.h +++ b/src/core/crypto/crypto_sha256_other.h @@ -12,36 +12,30 @@ namespace sourcemeta::core { -// The count must be between 1 and 31, as the complementary shift is -// undefined otherwise -inline constexpr auto sha256_rotate_right(std::uint32_t value, - std::uint64_t count) noexcept - -> std::uint32_t { - return (value >> count) | (value << (32u - count)); -} - -// FIPS 180-4 Section 4.1.2 logical functions +// FIPS 180-4 Section 4.1.2 logical functions. Each right rotation is written +// out as a shift pair rather than through a helper, so that an unoptimized +// build does not pay a function call per rotation inline constexpr auto sha256_big_sigma_0(std::uint32_t value) noexcept -> std::uint32_t { - return sha256_rotate_right(value, 2u) ^ sha256_rotate_right(value, 13u) ^ - sha256_rotate_right(value, 22u); + return ((value >> 2u) | (value << 30u)) ^ ((value >> 13u) | (value << 19u)) ^ + ((value >> 22u) | (value << 10u)); } inline constexpr auto sha256_big_sigma_1(std::uint32_t value) noexcept -> std::uint32_t { - return sha256_rotate_right(value, 6u) ^ sha256_rotate_right(value, 11u) ^ - sha256_rotate_right(value, 25u); + return ((value >> 6u) | (value << 26u)) ^ ((value >> 11u) | (value << 21u)) ^ + ((value >> 25u) | (value << 7u)); } inline constexpr auto sha256_small_sigma_0(std::uint32_t value) noexcept -> std::uint32_t { - return sha256_rotate_right(value, 7u) ^ sha256_rotate_right(value, 18u) ^ + return ((value >> 7u) | (value << 25u)) ^ ((value >> 18u) | (value << 14u)) ^ (value >> 3u); } inline constexpr auto sha256_small_sigma_1(std::uint32_t value) noexcept -> std::uint32_t { - return sha256_rotate_right(value, 17u) ^ sha256_rotate_right(value, 19u) ^ + return ((value >> 17u) | (value << 15u)) ^ ((value >> 19u) | (value << 13u)) ^ (value >> 10u); } diff --git a/src/core/crypto/crypto_sha2_64.h b/src/core/crypto/crypto_sha2_64.h index 616854df4..3da417ca1 100644 --- a/src/core/crypto/crypto_sha2_64.h +++ b/src/core/crypto/crypto_sha2_64.h @@ -12,36 +12,30 @@ namespace sourcemeta::core { -// The count must be between 1 and 63, as the complementary shift is -// undefined otherwise -inline constexpr auto sha2_64_rotate_right(std::uint64_t value, - std::uint64_t count) noexcept - -> std::uint64_t { - return (value >> count) | (value << (64u - count)); -} - -// FIPS 180-4 Section 4.1.3 logical functions +// FIPS 180-4 Section 4.1.3 logical functions. Each right rotation is written +// out as a shift pair rather than through a helper, so that an unoptimized +// build does not pay a function call per rotation inline constexpr auto sha2_64_big_sigma_0(std::uint64_t value) noexcept -> std::uint64_t { - return sha2_64_rotate_right(value, 28u) ^ sha2_64_rotate_right(value, 34u) ^ - sha2_64_rotate_right(value, 39u); + return ((value >> 28u) | (value << 36u)) ^ ((value >> 34u) | (value << 30u)) ^ + ((value >> 39u) | (value << 25u)); } inline constexpr auto sha2_64_big_sigma_1(std::uint64_t value) noexcept -> std::uint64_t { - return sha2_64_rotate_right(value, 14u) ^ sha2_64_rotate_right(value, 18u) ^ - sha2_64_rotate_right(value, 41u); + return ((value >> 14u) | (value << 50u)) ^ ((value >> 18u) | (value << 46u)) ^ + ((value >> 41u) | (value << 23u)); } inline constexpr auto sha2_64_small_sigma_0(std::uint64_t value) noexcept -> std::uint64_t { - return sha2_64_rotate_right(value, 1u) ^ sha2_64_rotate_right(value, 8u) ^ + return ((value >> 1u) | (value << 63u)) ^ ((value >> 8u) | (value << 56u)) ^ (value >> 7u); } inline constexpr auto sha2_64_small_sigma_1(std::uint64_t value) noexcept -> std::uint64_t { - return sha2_64_rotate_right(value, 19u) ^ sha2_64_rotate_right(value, 61u) ^ + return ((value >> 19u) | (value << 45u)) ^ ((value >> 61u) | (value << 3u)) ^ (value >> 6u); }