| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455 |
- /**
- * secp224r1 point arithmetic, in process and with no platform provider.
- * Windows offers this curve through CNG, Wine does not, so the agreement carries its own math.
- * Field values live in the Montgomery domain between the entry points.
- */
- #include "ecc_p224_curve.h"
- #include <algorithm>
- namespace sunrise::middleware::crypto::ecc::curve {
- namespace {
- /** Bits in one field word. */
- constexpr unsigned kWordBits = 32;
- /** Bytes in one field word. */
- constexpr std::size_t kWordBytes = 4;
- /** Bits in one byte of the wire form. */
- constexpr unsigned kByteBits = 8;
- /** Every word of a value the mask selects. */
- constexpr std::uint32_t kAllBits = 0xFFFFFFFFU;
- /** p = 2^224 - 2^96 + 1, the secp224r1 field prime. */
- constexpr Field kPrime{
- 0x00000001, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF};
- /** p - 2, the exponent that inverts a field element by Fermat's theorem. */
- constexpr Field kPrimeMinusTwo{
- 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF};
- /** n, the order of the generator. A private key must be below it. */
- constexpr Field kOrder{
- 0x5C5C2A3D, 0x13DD2945, 0xE0B8F03E, 0xFFFF16A2, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF};
- /** b of the curve equation y^2 = x^3 - 3x + b. */
- constexpr Field kCoefficientB{
- 0x2355FFB4, 0x270B3943, 0xD7BFD8BA, 0x5044B0B7, 0xF5413256, 0x0C04B3AB, 0xB4050A85};
- /** x of the generator. */
- constexpr Field kGeneratorX{
- 0x115C1D21, 0x343280D6, 0x56C21122, 0x4A03C1D3, 0x321390B9, 0x6BB4BF7F, 0xB70E0CBD};
- /** y of the generator. */
- constexpr Field kGeneratorY{
- 0x85007E34, 0x44D58199, 0x5A074764, 0xCD4375A0, 0x4C22DFE6, 0xB5F723FB, 0xBD376388};
- /** R mod p, which is the value one takes in the Montgomery domain. */
- constexpr Field kMontgomeryOne{
- 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000, 0x00000000};
- /** R squared mod p, the multiplier that moves a plain value into the Montgomery domain. */
- constexpr Field kMontgomeryR2{
- 0x00000001, 0x00000000, 0x00000000, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000};
- /** -p^-1 mod 2^32, the word each reduction step multiplies by. p ends in 1, so this is all bits. */
- constexpr std::uint32_t kMontgomeryFactor = 0xFFFFFFFFU;
- /** One point in Jacobian coordinates, where x = X/Z^2 and y = Y/Z^3. Z of zero is infinity. */
- struct Jacobian {
- Field x{};
- Field y{};
- Field z{};
- };
- /**
- * Adds two values as plain words.
- * @param augend Value added to.
- * @param addend Value added.
- * @param sum Receives the low 224 bits.
- * @return The carry out of the top word.
- */
- [[nodiscard]] std::uint32_t
- add_words(const Field& augend, const Field& addend, Field& sum) noexcept {
- std::uint64_t carry = 0;
- for (std::size_t index = 0; index < kWords; ++index) {
- const std::uint64_t total =
- static_cast<std::uint64_t>(augend[index]) + addend[index] + carry;
- sum[index] = static_cast<std::uint32_t>(total);
- carry = total >> kWordBits;
- }
- return static_cast<std::uint32_t>(carry);
- }
- /**
- * Subtracts one value from another as plain words.
- * @param minuend Value subtracted from.
- * @param subtrahend Value subtracted.
- * @param difference Receives the low 224 bits.
- * @return One when the subtraction borrowed, which means the minuend is the smaller value.
- */
- [[nodiscard]] std::uint32_t
- subtract_words(const Field& minuend, const Field& subtrahend, Field& difference) noexcept {
- std::uint64_t borrow = 0;
- for (std::size_t index = 0; index < kWords; ++index) {
- const std::uint64_t total =
- static_cast<std::uint64_t>(minuend[index]) - subtrahend[index] - borrow;
- difference[index] = static_cast<std::uint32_t>(total);
- borrow = (total >> kWordBits) & 1U;
- }
- return static_cast<std::uint32_t>(borrow);
- }
- /** @return True when every word is zero. */
- [[nodiscard]] bool is_zero(const Field& value) noexcept {
- std::uint32_t bits = 0;
- for (const std::uint32_t word : value) {
- bits |= word;
- }
- return bits == 0;
- }
- /** @return True when left is below right. */
- [[nodiscard]] bool less_than(const Field& left, const Field& right) noexcept {
- Field ignored{};
- return subtract_words(left, right, ignored) != 0;
- }
- /**
- * Picks one of two values without branching on the choice.
- * @param take True to take the first value.
- * @param first Value taken when the choice holds.
- * @param second Value taken otherwise.
- * @param output Receives the picked value.
- */
- void select(bool take, const Field& first, const Field& second, Field& output) noexcept {
- const std::uint32_t mask = take ? kAllBits : 0U;
- for (std::size_t index = 0; index < kWords; ++index) {
- output[index] = (first[index] & mask) | (second[index] & ~mask);
- }
- }
- /**
- * Multiplies in the Montgomery domain and reduces in the same pass.
- * @param multiplicand First factor, below p.
- * @param multiplier Second factor, below p.
- * @param product Receives multiplicand * multiplier * R^-1 mod p. May alias either factor.
- */
- void montgomery_multiply(const Field& multiplicand,
- const Field& multiplier,
- Field& product) noexcept {
- // Two words above the field hold the running carries the reduction consumes.
- std::array<std::uint32_t, kWords + 2> accumulator{};
- for (std::size_t step = 0; step < kWords; ++step) {
- std::uint64_t carry = 0;
- for (std::size_t index = 0; index < kWords; ++index) {
- const std::uint64_t sum =
- static_cast<std::uint64_t>(accumulator[index])
- + static_cast<std::uint64_t>(multiplicand[index]) * multiplier[step] + carry;
- accumulator[index] = static_cast<std::uint32_t>(sum);
- carry = sum >> kWordBits;
- }
- std::uint64_t top = static_cast<std::uint64_t>(accumulator[kWords]) + carry;
- accumulator[kWords] = static_cast<std::uint32_t>(top);
- accumulator[kWords + 1] = static_cast<std::uint32_t>(top >> kWordBits);
- // Clearing the low word by a multiple of p is what divides the result by R.
- const auto factor = static_cast<std::uint32_t>(static_cast<std::uint64_t>(accumulator[0])
- * kMontgomeryFactor);
- carry = (static_cast<std::uint64_t>(accumulator[0])
- + static_cast<std::uint64_t>(factor) * kPrime[0])
- >> kWordBits;
- for (std::size_t index = 1; index < kWords; ++index) {
- const std::uint64_t sum = static_cast<std::uint64_t>(accumulator[index])
- + static_cast<std::uint64_t>(factor) * kPrime[index] + carry;
- accumulator[index - 1] = static_cast<std::uint32_t>(sum);
- carry = sum >> kWordBits;
- }
- top = static_cast<std::uint64_t>(accumulator[kWords]) + carry;
- accumulator[kWords - 1] = static_cast<std::uint32_t>(top);
- accumulator[kWords] =
- accumulator[kWords + 1] + static_cast<std::uint32_t>(top >> kWordBits);
- }
- Field result{};
- std::copy_n(accumulator.begin(), kWords, result.begin());
- Field reduced{};
- const std::uint32_t borrow = subtract_words(result, kPrime, reduced);
- // The result is below 2p, so the one conditional subtraction always finishes it.
- select(accumulator[kWords] != 0 || borrow == 0, reduced, result, product);
- }
- /** Adds two field elements mod p. The sum may alias either input. */
- void field_add(const Field& augend, const Field& addend, Field& sum) noexcept {
- Field total{};
- const std::uint32_t carry = add_words(augend, addend, total);
- Field reduced{};
- const std::uint32_t borrow = subtract_words(total, kPrime, reduced);
- select(carry != 0 || borrow == 0, reduced, total, sum);
- }
- /** Subtracts one field element from another mod p. The difference may alias either input. */
- void field_subtract(const Field& minuend, const Field& subtrahend, Field& difference) noexcept {
- Field total{};
- const std::uint32_t borrow = subtract_words(minuend, subtrahend, total);
- Field wrapped{};
- (void)add_words(total, kPrime, wrapped);
- select(borrow != 0, wrapped, total, difference);
- }
- /** Moves a plain value into the Montgomery domain. */
- void to_montgomery(const Field& value, Field& output) noexcept {
- montgomery_multiply(value, kMontgomeryR2, output);
- }
- /** Moves a Montgomery value back to its plain form. */
- void from_montgomery(const Field& value, Field& output) noexcept {
- Field one{};
- one[0] = 1;
- montgomery_multiply(value, one, output);
- }
- /**
- * Inverts a field element by raising it to p - 2.
- * @param value Montgomery value, not zero.
- * @param output Receives the Montgomery inverse.
- */
- void montgomery_inverse(const Field& value, Field& output) noexcept {
- Field result = kMontgomeryOne;
- for (std::size_t index = kWords; index-- > 0;) {
- for (unsigned bit = kWordBits; bit-- > 0;) {
- montgomery_multiply(result, result, result);
- if (((kPrimeMinusTwo[index] >> bit) & 1U) != 0) {
- montgomery_multiply(result, value, result);
- }
- }
- }
- output = result;
- }
- /**
- * Doubles one Jacobian point, using that the curve has a of -3.
- * @param point Point to double, infinity allowed.
- * @param output Receives the doubled point. May alias the input.
- */
- void jacobian_double(const Jacobian& point, Jacobian& output) noexcept {
- Field delta{};
- Field gamma{};
- Field beta{};
- Field alpha{};
- Field first{};
- Field second{};
- Field third{};
- montgomery_multiply(point.z, point.z, delta);
- montgomery_multiply(point.y, point.y, gamma);
- montgomery_multiply(point.x, gamma, beta);
- field_subtract(point.x, delta, first);
- field_add(point.x, delta, second);
- montgomery_multiply(first, second, third);
- field_add(third, third, alpha);
- field_add(alpha, third, alpha);
- Jacobian result{};
- montgomery_multiply(alpha, alpha, first);
- field_add(beta, beta, second);
- field_add(second, second, second);
- field_add(second, second, third);
- field_subtract(first, third, result.x);
- field_add(point.y, point.z, first);
- montgomery_multiply(first, first, first);
- field_subtract(first, gamma, first);
- field_subtract(first, delta, result.z);
- field_subtract(second, result.x, first);
- montgomery_multiply(alpha, first, first);
- montgomery_multiply(gamma, gamma, third);
- field_add(third, third, third);
- field_add(third, third, third);
- field_add(third, third, third);
- field_subtract(first, third, result.y);
- output = result;
- }
- /**
- * Adds two Jacobian points.
- * @param left First point, infinity allowed.
- * @param right Second point, infinity allowed.
- * @param output Receives the sum. May alias either input.
- */
- void jacobian_add(const Jacobian& left, const Jacobian& right, Jacobian& output) noexcept {
- if (is_zero(left.z)) {
- output = right;
- return;
- }
- if (is_zero(right.z)) {
- output = left;
- return;
- }
- Field leftSquare{};
- Field rightSquare{};
- Field leftScaled{};
- Field rightScaled{};
- Field leftLine{};
- Field rightLine{};
- Field first{};
- Field second{};
- montgomery_multiply(left.z, left.z, leftSquare);
- montgomery_multiply(right.z, right.z, rightSquare);
- montgomery_multiply(left.x, rightSquare, leftScaled);
- montgomery_multiply(right.x, leftSquare, rightScaled);
- montgomery_multiply(right.z, rightSquare, first);
- montgomery_multiply(left.y, first, leftLine);
- montgomery_multiply(left.z, leftSquare, second);
- montgomery_multiply(right.y, second, rightLine);
- Field difference{};
- Field slope{};
- field_subtract(rightScaled, leftScaled, difference);
- field_subtract(rightLine, leftLine, slope);
- if (is_zero(difference)) {
- // Equal points need the doubling formula; opposite points sum to infinity.
- if (is_zero(slope)) {
- jacobian_double(left, output);
- } else {
- output = {};
- }
- return;
- }
- field_add(slope, slope, slope);
- Field square{};
- Field cube{};
- Field scaled{};
- field_add(difference, difference, square);
- montgomery_multiply(square, square, square);
- montgomery_multiply(difference, square, cube);
- montgomery_multiply(leftScaled, square, scaled);
- Jacobian result{};
- montgomery_multiply(slope, slope, first);
- field_subtract(first, cube, first);
- field_add(scaled, scaled, second);
- field_subtract(first, second, result.x);
- field_subtract(scaled, result.x, first);
- montgomery_multiply(slope, first, first);
- montgomery_multiply(leftLine, cube, second);
- field_add(second, second, second);
- field_subtract(first, second, result.y);
- field_add(left.z, right.z, first);
- montgomery_multiply(first, first, first);
- field_subtract(first, leftSquare, first);
- field_subtract(first, rightSquare, first);
- montgomery_multiply(first, difference, result.z);
- output = result;
- }
- /**
- * Converts a Jacobian point to affine coordinates.
- * @param point Point with a non-zero z.
- * @param output Receives the plain affine coordinates.
- */
- void to_affine(const Jacobian& point, Point& output) noexcept {
- Field inverse{};
- Field square{};
- Field cube{};
- Field value{};
- montgomery_inverse(point.z, inverse);
- montgomery_multiply(inverse, inverse, square);
- montgomery_multiply(square, inverse, cube);
- montgomery_multiply(point.x, square, value);
- from_montgomery(value, output.x);
- montgomery_multiply(point.y, cube, value);
- from_montgomery(value, output.y);
- }
- } // namespace
- /** @return The curve generator, which both sides multiply. */
- Point generator() noexcept {
- return Point{kGeneratorX, kGeneratorY};
- }
- /** Checks that a point may be multiplied. */
- bool on_curve(const Point& point) noexcept {
- if (!less_than(point.x, kPrime) || !less_than(point.y, kPrime)) {
- return false;
- }
- Field x{};
- Field y{};
- to_montgomery(point.x, x);
- to_montgomery(point.y, y);
- Field left{};
- Field right{};
- Field term{};
- montgomery_multiply(y, y, left);
- montgomery_multiply(x, x, right);
- montgomery_multiply(right, x, right);
- field_add(x, x, term);
- field_add(term, x, term);
- field_subtract(right, term, right);
- to_montgomery(kCoefficientB, term);
- field_add(right, term, right);
- return left == right;
- }
- /** Checks one private key. */
- bool valid_scalar(const Field& scalar) noexcept {
- return !is_zero(scalar) && less_than(scalar, kOrder);
- }
- /** Multiplies a point by a scalar. */
- bool multiply(const Field& scalar, const Point& point, Point& output) noexcept {
- Jacobian base{};
- to_montgomery(point.x, base.x);
- to_montgomery(point.y, base.y);
- base.z = kMontgomeryOne;
- // Both branches of every bit run, so the scalar does not steer the work that is done.
- Jacobian accumulator{};
- for (std::size_t index = kWords; index-- > 0;) {
- for (unsigned bit = kWordBits; bit-- > 0;) {
- Jacobian doubled{};
- jacobian_double(accumulator, doubled);
- Jacobian summed{};
- jacobian_add(doubled, base, summed);
- const bool take = ((scalar[index] >> bit) & 1U) != 0;
- select(take, summed.x, doubled.x, accumulator.x);
- select(take, summed.y, doubled.y, accumulator.y);
- select(take, summed.z, doubled.z, accumulator.z);
- }
- }
- if (is_zero(accumulator.z)) {
- return false;
- }
- to_affine(accumulator, output);
- return true;
- }
- /** Reads a wire value into a field element. */
- void load(std::span<const std::byte> bytes, Field& output) noexcept {
- output = {};
- if (bytes.size() != kWords * kWordBytes) {
- return;
- }
- for (std::size_t index = 0; index < kWords; ++index) {
- const std::size_t offset = (kWords - 1 - index) * kWordBytes;
- std::uint32_t word = 0;
- for (std::size_t step = 0; step < kWordBytes; ++step) {
- word = (word << kByteBits) | std::to_integer<std::uint32_t>(bytes[offset + step]);
- }
- output[index] = word;
- }
- }
- /** Writes a field element in its wire form. */
- void store(const Field& value, std::span<std::byte> bytes) noexcept {
- if (bytes.size() != kWords * kWordBytes) {
- return;
- }
- for (std::size_t index = 0; index < kWords; ++index) {
- const std::size_t offset = (kWords - 1 - index) * kWordBytes;
- for (std::size_t step = 0; step < kWordBytes; ++step) {
- const auto shift = static_cast<unsigned>((kWordBytes - 1 - step) * kByteBits);
- bytes[offset + step] = static_cast<std::byte>((value[index] >> shift) & 0xFFU);
- }
- }
- }
- } // namespace sunrise::middleware::crypto::ecc::curve
|