LibCrypto: Refactor Edwards-curves implementation with OpenSSL

This commit is contained in:
devgianlu 2025-02-16 13:02:13 +01:00 committed by Ali Mohammad Pur
parent 0fcd7f9aea
commit 60dcf3e023
Notes: github-actions[bot] 2025-02-24 10:12:26 +00:00
16 changed files with 216 additions and 1626 deletions

View file

@ -22,12 +22,8 @@ set(SOURCES
Checksum/CRC32.cpp
Cipher/AES.cpp
Cipher/Cipher.cpp
Curves/Curve25519.cpp
Curves/Ed25519.cpp
Curves/Ed448.cpp
Curves/EdwardsCurve.cpp
Curves/SECPxxxr1.cpp
Curves/X25519.cpp
Curves/X448.cpp
Hash/BLAKE2b.cpp
Hash/MD5.cpp
Hash/SHA1.cpp

View file

@ -1,360 +0,0 @@
/*
* Copyright (c) 2022, stelar7 <dudedbz@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Endian.h>
#include <AK/Types.h>
#include <LibCrypto/Curves/Curve25519.h>
namespace Crypto::Curves {
void Curve25519::set(u32* state, u32 value)
{
state[0] = value;
for (auto i = 1; i < WORDS; i++) {
state[i] = 0;
}
}
void Curve25519::modular_square(u32* state, u32 const* value)
{
// Compute R = (A ^ 2) mod p
modular_multiply(state, value, value);
}
void Curve25519::modular_subtract(u32* state, u32 const* first, u32 const* second)
{
// R = (A - B) mod p
i64 temp = -19;
for (auto i = 0; i < WORDS; i++) {
temp += first[i];
temp -= second[i];
state[i] = temp & 0xFFFFFFFF;
temp >>= 32;
}
// Compute R = A + (2^255 - 19) - B
state[7] += 0x80000000;
modular_reduce(state, state);
}
void Curve25519::modular_add(u32* state, u32 const* first, u32 const* second)
{
// R = (A + B) mod p
u64 temp = 0;
for (auto i = 0; i < WORDS; i++) {
temp += first[i];
temp += second[i];
state[i] = temp & 0xFFFFFFFF;
temp >>= 32;
}
modular_reduce(state, state);
}
void Curve25519::modular_multiply(u32* state, u32 const* first, u32 const* second)
{
// Compute R = (A * B) mod p
u64 temp = 0;
u64 carry = 0;
u32 output[WORDS * 2];
// Comba's method
for (auto i = 0; i < 16; i++) {
if (i < WORDS) {
for (auto j = 0; j <= i; j++) {
temp += (u64)first[j] * second[i - j];
carry += temp >> 32;
temp &= 0xFFFFFFFF;
}
} else {
for (auto j = i - 7; j < WORDS; j++) {
temp += (u64)first[j] * second[i - j];
carry += temp >> 32;
temp &= 0xFFFFFFFF;
}
}
output[i] = temp & 0xFFFFFFFF;
temp = carry & 0xFFFFFFFF;
carry >>= 32;
}
// Reduce bit 255 (2^255 = 19 mod p)
temp = (output[7] >> 31) * 19;
// Mask the most significant bit
output[7] &= 0x7FFFFFFF;
// Fast modular reduction 1st pass
for (auto i = 0; i < WORDS; i++) {
temp += output[i];
temp += (u64)output[i + 8] * 38;
output[i] = temp & 0xFFFFFFFF;
temp >>= 32;
}
// Reduce bit 256 (2^256 = 38 mod p)
temp *= 38;
// Reduce bit 255 (2^255 = 19 mod p)
temp += (output[7] >> 31) * 19;
// Mask the most significant bit
output[7] &= 0x7FFFFFFF;
// Fast modular reduction 2nd pass
for (auto i = 0; i < WORDS; i++) {
temp += output[i];
output[i] = temp & 0xFFFFFFFF;
temp >>= 32;
}
modular_reduce(state, output);
}
void Curve25519::export_state(u32* state, u8* output)
{
for (u32 i = 0; i < WORDS; i++) {
state[i] = AK::convert_between_host_and_little_endian(state[i]);
}
memcpy(output, state, BYTES);
}
void Curve25519::import_state(u32* state, u8 const* data)
{
memcpy(state, data, BYTES);
for (u32 i = 0; i < WORDS; i++) {
state[i] = AK::convert_between_host_and_little_endian(state[i]);
}
}
void Curve25519::modular_subtract_single(u32* r, u32 const* a, u32 b)
{
i64 temp = -19;
temp -= b;
// Compute R = A - 19 - B
for (u32 i = 0; i < 8; i++) {
temp += a[i];
r[i] = temp & 0xFFFFFFFF;
temp >>= 32;
}
// Compute R = A + (2^255 - 19) - B
r[7] += 0x80000000;
modular_reduce(r, r);
}
void Curve25519::modular_add_single(u32* state, u32 const* first, u32 second)
{
u64 temp = second;
// Compute R = A + B
for (u32 i = 0; i < 8; i++) {
temp += first[i];
state[i] = temp & 0xFFFFFFFF;
temp >>= 32;
}
modular_reduce(state, state);
}
u32 Curve25519::modular_square_root(u32* r, u32 const* a, u32 const* b)
{
u32 c[8];
u32 u[8];
u32 v[8];
// To compute the square root of (A / B), the first step is to compute the candidate root x = (A / B)^((p+3)/8)
modular_square(v, b);
modular_multiply(v, v, b);
modular_square(v, v);
modular_multiply(v, v, b);
modular_multiply(c, a, v);
modular_square(u, c);
modular_multiply(u, u, c);
modular_square(u, u);
modular_multiply(v, u, c);
to_power_of_2n(u, v, 3);
modular_multiply(u, u, v);
modular_square(u, u);
modular_multiply(v, u, c);
to_power_of_2n(u, v, 7);
modular_multiply(u, u, v);
modular_square(u, u);
modular_multiply(v, u, c);
to_power_of_2n(u, v, 15);
modular_multiply(u, u, v);
modular_square(u, u);
modular_multiply(v, u, c);
to_power_of_2n(u, v, 31);
modular_multiply(v, u, v);
to_power_of_2n(u, v, 62);
modular_multiply(u, u, v);
modular_square(u, u);
modular_multiply(v, u, c);
to_power_of_2n(u, v, 125);
modular_multiply(u, u, v);
modular_square(u, u);
modular_square(u, u);
modular_multiply(u, u, c);
// The first candidate root is U = A * B^3 * (A * B^7)^((p - 5) / 8)
modular_multiply(u, u, a);
modular_square(v, b);
modular_multiply(v, v, b);
modular_multiply(u, u, v);
// The second candidate root is V = U * sqrt(-1)
modular_multiply(v, u, SQRT_MINUS_1);
modular_square(c, u);
modular_multiply(c, c, b);
// Check whether B * U^2 = A
u32 first_comparison = compare(c, a);
modular_square(c, v);
modular_multiply(c, c, b);
// Check whether B * V^2 = A
u32 second_comparison = compare(c, a);
// Select the first or the second candidate root
select(r, u, v, first_comparison);
// Return 0 if the square root exists
return first_comparison & second_comparison;
}
u32 Curve25519::compare(u32 const* a, u32 const* b)
{
u32 mask = 0;
for (u32 i = 0; i < 8; i++) {
mask |= a[i] ^ b[i];
}
// Return 0 if A = B, else 1
return ((u32)(mask | (~mask + 1))) >> 31;
}
void Curve25519::modular_reduce(u32* state, u32 const* data)
{
// R = A mod p
u64 temp = 19;
u32 other[WORDS];
for (auto i = 0; i < WORDS; i++) {
temp += data[i];
other[i] = temp & 0xFFFFFFFF;
temp >>= 32;
}
// Compute B = A - (2^255 - 19)
other[7] -= 0x80000000;
u32 mask = (other[7] & 0x80000000) >> 31;
select(state, other, data, mask);
}
void Curve25519::to_power_of_2n(u32* state, u32 const* value, u8 n)
{
// Pre-compute (A ^ 2) mod p
modular_square(state, value);
// Compute R = (A ^ (2^n)) mod p
for (u32 i = 1; i < n; i++) {
modular_square(state, state);
}
}
void Curve25519::select(u32* state, u32 const* a, u32 const* b, u32 condition)
{
// If B < (2^255 - 19) then R = B, else R = A
u32 mask = condition - 1;
for (auto i = 0; i < WORDS; i++) {
state[i] = (a[i] & mask) | (b[i] & ~mask);
}
}
void Curve25519::copy(u32* state, u32 const* value)
{
for (auto i = 0; i < WORDS; i++) {
state[i] = value[i];
}
}
void Curve25519::modular_multiply_inverse(u32* state, u32 const* value)
{
// Compute R = A^-1 mod p
u32 u[WORDS];
u32 v[WORDS];
// Fermat's little theorem
modular_square(u, value);
modular_multiply(u, u, value);
modular_square(u, u);
modular_multiply(v, u, value);
to_power_of_2n(u, v, 3);
modular_multiply(u, u, v);
modular_square(u, u);
modular_multiply(v, u, value);
to_power_of_2n(u, v, 7);
modular_multiply(u, u, v);
modular_square(u, u);
modular_multiply(v, u, value);
to_power_of_2n(u, v, 15);
modular_multiply(u, u, v);
modular_square(u, u);
modular_multiply(v, u, value);
to_power_of_2n(u, v, 31);
modular_multiply(v, u, v);
to_power_of_2n(u, v, 62);
modular_multiply(u, u, v);
modular_square(u, u);
modular_multiply(v, u, value);
to_power_of_2n(u, v, 125);
modular_multiply(u, u, v);
modular_square(u, u);
modular_square(u, u);
modular_multiply(u, u, value);
modular_square(u, u);
modular_square(u, u);
modular_multiply(u, u, value);
modular_square(u, u);
modular_multiply(state, u, value);
}
void Curve25519::modular_multiply_single(u32* state, u32 const* first, u32 second)
{
// Compute R = (A * B) mod p
u64 temp = 0;
u32 output[WORDS];
for (auto i = 0; i < WORDS; i++) {
temp += (u64)first[i] * second;
output[i] = temp & 0xFFFFFFFF;
temp >>= 32;
}
// Reduce bit 256 (2^256 = 38 mod p)
temp *= 38;
// Reduce bit 255 (2^255 = 19 mod p)
temp += (output[7] >> 31) * 19;
// Mask the most significant bit
output[7] &= 0x7FFFFFFF;
// Fast modular reduction
for (auto i = 0; i < WORDS; i++) {
temp += output[i];
output[i] = temp & 0xFFFFFFFF;
temp >>= 32;
}
modular_reduce(state, output);
}
}

View file

@ -1,71 +0,0 @@
/*
* Copyright (c) 2022, stelar7 <dudedbz@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
namespace Crypto::Curves {
class Curve25519 {
public:
static constexpr u8 BASE_POINT_L_ORDER[33] {
0xED, 0xD3, 0xF5, 0x5C, 0x1A, 0x63, 0x12, 0x58,
0xD6, 0x9C, 0xF7, 0xA2, 0xDE, 0xF9, 0xDE, 0x14,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
0x00
};
static constexpr u32 CURVE_D[8] {
0x135978A3, 0x75EB4DCA, 0x4141D8AB, 0x00700A4D,
0x7779E898, 0x8CC74079, 0x2B6FFE73, 0x52036CEE
};
static constexpr u32 CURVE_D_2[8] {
0x26B2F159, 0xEBD69B94, 0x8283B156, 0x00E0149A,
0xEEF3D130, 0x198E80F2, 0x56DFFCE7, 0x2406D9DC
};
static constexpr u32 ZERO[8] {
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000
};
static constexpr u32 SQRT_MINUS_1[8] {
0x4A0EA0B0, 0xC4EE1B27, 0xAD2FE478, 0x2F431806,
0x3DFBD7A7, 0x2B4D0099, 0x4FC1DF0B, 0x2B832480
};
static constexpr u8 BARRETT_REDUCTION_QUOTIENT[33] {
0x1B, 0x13, 0x2C, 0x0A, 0xA3, 0xE5, 0x9C, 0xED,
0xA7, 0x29, 0x63, 0x08, 0x5D, 0x21, 0x06, 0x21,
0xEB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x0F
};
static constexpr u8 BITS = 255;
static constexpr u8 BYTES = 32;
static constexpr u8 WORDS = 8;
static constexpr u32 A24 = 121666;
static void set(u32* a, u32 b);
static void select(u32* r, u32 const* a, u32 const* b, u32 c);
static void copy(u32* a, u32 const* b);
static void modular_square(u32* r, u32 const* a);
static void modular_subtract(u32* r, u32 const* a, u32 const* b);
static void modular_reduce(u32* r, u32 const* a);
static void modular_add(u32* r, u32 const* a, u32 const* b);
static void modular_multiply(u32* r, u32 const* a, u32 const* b);
static void modular_multiply_inverse(u32* r, u32 const* a);
static void to_power_of_2n(u32* r, u32 const* a, u8 n);
static void export_state(u32* a, u8* data);
static void import_state(u32* a, u8 const* data);
static void modular_subtract_single(u32* r, u32 const* a, u32 b);
static void modular_multiply_single(u32* r, u32 const* a, u32 b);
static void modular_add_single(u32* r, u32 const* a, u32 b);
static u32 modular_square_root(u32* r, u32 const* a, u32 const* b);
static u32 compare(u32 const* a, u32 const* b);
};
}

View file

@ -1,434 +0,0 @@
/*
* Copyright (c) 2022, stelar7 <dudedbz@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Random.h>
#include <LibCrypto/Curves/Curve25519.h>
#include <LibCrypto/Curves/Ed25519.h>
#include <LibCrypto/Hash/SHA2.h>
#include <LibCrypto/SecureRandom.h>
namespace Crypto::Curves {
// https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.5
ErrorOr<ByteBuffer> Ed25519::generate_private_key()
{
// The private key is 32 octets (256 bits, corresponding to b) of
// cryptographically secure random data. See [RFC4086] for a discussion
// about randomness.
auto buffer = TRY(ByteBuffer::create_uninitialized(key_size()));
fill_with_secure_random(buffer);
return buffer;
}
// https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.5
ErrorOr<ByteBuffer> Ed25519::generate_public_key(ReadonlyBytes private_key)
{
// The 32-byte public key is generated by the following steps.
// 1. Hash the 32-byte private key using SHA-512, storing the digest in a 64-octet large buffer, denoted h.
// Only the lower 32 bytes are used for generating the public key.
auto digest = Crypto::Hash::SHA512::hash(private_key);
// NOTE: we do these steps in the opposite order (since its easier to modify s)
// 3. Interpret the buffer as the little-endian integer, forming a secret scalar s.
memcpy(s, digest.data, 32);
// 2. Prune the buffer:
// The lowest three bits of the first octet are cleared,
s[0] &= 0xF8;
// the highest bit of the last octet is cleared,
s[31] &= 0x7F;
// and the second highest bit of the last octet is set.
s[31] |= 0x40;
// Perform a fixed-base scalar multiplication [s]B.
point_multiply_scalar(&sb, s, &BASE_POINT);
// 4. The public key A is the encoding of the point [s]B.
// First, encode the y-coordinate (in the range 0 <= y < p) as a little-endian string of 32 octets.
// The most significant bit of the final octet is always zero.
// To form the encoding of the point [s]B, copy the least significant bit of the x coordinate
// to the most significant bit of the final octet. The result is the public key.
auto public_key = TRY(ByteBuffer::create_uninitialized(key_size()));
encode_point(&sb, public_key.data());
return public_key;
}
// https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.6
ErrorOr<ByteBuffer> Ed25519::sign(ReadonlyBytes public_key, ReadonlyBytes private_key, ReadonlyBytes message)
{
// 1. Hash the private key, 32 octets, using SHA-512.
// Let h denote the resulting digest.
auto h = Crypto::Hash::SHA512::hash(private_key);
// Construct the secret scalar s from the first half of the digest,
memcpy(s, h.data, 32);
// NOTE: This is done later in step 4, but we can also do it here.
s[0] &= 0xF8;
s[31] &= 0x7F;
s[31] |= 0x40;
// and the corresponding public key A, as described in the previous section.
// NOTE: The public key A is taken as input to this function.
// Let prefix denote the second half of the hash digest, h[32],...,h[63].
memcpy(p, h.data + 32, 32);
// 2. Compute SHA-512(dom2(F, C) || p || PH(M)), where M is the message to be signed.
auto hash = Hash::SHA512::create();
// NOTE: dom2(F, C) is a blank octet string when signing or verifying Ed25519
hash->update(p, 32);
// NOTE: PH(M) = M
hash->update(message.data(), message.size());
// Interpret the 64-octet digest as a little-endian integer r.
// For efficiency, do this by first reducing r modulo L, the group order of B.
auto digest = hash->digest();
barrett_reduce(r, digest.data);
// 3. Compute the point [r]B.
point_multiply_scalar(&rb, r, &BASE_POINT);
auto R = TRY(ByteBuffer::create_uninitialized(32));
// Let the string R be the encoding of this point
encode_point(&rb, R.data());
// 4. Compute SHA512(dom2(F, C) || R || A || PH(M)),
// NOTE: We can reuse hash here, since digest() calls reset()
// NOTE: dom2(F, C) is a blank octet string when signing or verifying Ed25519
hash->update(R.data(), R.size());
// NOTE: A == public_key
hash->update(public_key.data(), public_key.size());
// NOTE: PH(M) = M
hash->update(message.data(), message.size());
digest = hash->digest();
// and interpret the 64-octet digest as a little-endian integer k.
memcpy(k, digest.data, 64);
// 5. Compute S = (r + k * s) mod L. For efficiency, again reduce k modulo L first.
barrett_reduce(p, k);
multiply(k, k + 32, p, s, 32);
barrett_reduce(p, k);
add(s, p, r, 32);
// modular reduction
auto reduced_s = TRY(ByteBuffer::create_uninitialized(32));
auto is_negative = subtract(p, s, Curve25519::BASE_POINT_L_ORDER, 32);
select(reduced_s.data(), p, s, is_negative, 32);
// 6. Form the signature of the concatenation of R (32 octets) and the little-endian encoding of S
// (32 octets; the three most significant bits of the final octet are always zero).
auto signature = TRY(ByteBuffer::copy(R));
signature.append(reduced_s);
return signature;
}
// https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.7
bool Ed25519::verify(ReadonlyBytes public_key, ReadonlyBytes signature, ReadonlyBytes message)
{
auto not_valid = false;
// 1. To verify a signature on a message M using public key A,
// with F being 0 for Ed25519ctx, 1 for Ed25519ph, and if Ed25519ctx or Ed25519ph is being used, C being the context
// first split the signature into two 32-octet halves.
// If any of the decodings fail (including S being out of range), the signature is invalid.
// NOTE: We dont care about F, since we dont implement Ed25519ctx or Ed25519PH
// NOTE: C is the internal state, so its not a parameter
auto half_signature_size = signature_size() / 2;
// Decode the first half as a point R
memcpy(r, signature.data(), half_signature_size);
// and the second half as an integer S, in the range 0 <= s < L.
memcpy(s, signature.data() + half_signature_size, half_signature_size);
// NOTE: Ed25519 and Ed448 signatures are not malleable due to the verification check that decoded S is smaller than l.
// Without this check, one can add a multiple of l into a scalar part and still pass signature verification,
// resulting in malleable signatures.
auto is_negative = subtract(p, s, Curve25519::BASE_POINT_L_ORDER, half_signature_size);
not_valid |= 1 ^ is_negative;
// Decode the public key A as point A'.
not_valid |= decode_point(&ka, public_key.data());
// 2. Compute SHA512(dom2(F, C) || R || A || PH(M)), and interpret the 64-octet digest as a little-endian integer k.
auto hash = Hash::SHA512::create();
// NOTE: dom2(F, C) is a blank octet string when signing or verifying Ed25519
hash->update(r, half_signature_size);
// NOTE: A == public_key
hash->update(public_key.data(), key_size());
// NOTE: PH(M) = M
hash->update(message.data(), message.size());
auto digest = hash->digest();
auto k = digest.data;
// 3. Check the group equation [8][S]B = [8]R + [8][k]A'.
// It's sufficient, but not required, to instead check [S]B = R + [k]A'.
// NOTE: For efficiency, do this by first reducing k modulo L.
barrett_reduce(k, k);
// NOTE: We check [S]B - [k]A' == R
Curve25519::modular_subtract(ka.x, Curve25519::ZERO, ka.x);
Curve25519::modular_subtract(ka.t, Curve25519::ZERO, ka.t);
point_multiply_scalar(&sb, s, &BASE_POINT);
point_multiply_scalar(&ka, k, &ka);
point_add(&ka, &sb, &ka);
encode_point(&ka, p);
not_valid |= compare(p, r, half_signature_size);
return !not_valid;
}
void Ed25519::point_double(Ed25519Point* result, Ed25519Point const* point)
{
Curve25519::modular_square(a, point->x);
Curve25519::modular_square(b, point->y);
Curve25519::modular_square(c, point->z);
Curve25519::modular_add(c, c, c);
Curve25519::modular_add(e, a, b);
Curve25519::modular_add(f, point->x, point->y);
Curve25519::modular_square(f, f);
Curve25519::modular_subtract(f, e, f);
Curve25519::modular_subtract(g, a, b);
Curve25519::modular_add(h, c, g);
Curve25519::modular_multiply(result->x, f, h);
Curve25519::modular_multiply(result->y, e, g);
Curve25519::modular_multiply(result->z, g, h);
Curve25519::modular_multiply(result->t, e, f);
}
void Ed25519::point_multiply_scalar(Ed25519Point* result, u8 const* scalar, Ed25519Point const* point)
{
// Set U to the neutral element (0, 1, 1, 0)
Curve25519::set(u.x, 0);
Curve25519::set(u.y, 1);
Curve25519::set(u.z, 1);
Curve25519::set(u.t, 0);
for (i32 i = Curve25519::BITS - 1; i >= 0; i--) {
u8 b = (scalar[i / 8] >> (i % 8)) & 1;
// Compute U = 2 * U
point_double(&u, &u);
// Compute V = U + P
point_add(&v, &u, point);
// If b is set, then U = V
Curve25519::select(u.x, u.x, v.x, b);
Curve25519::select(u.y, u.y, v.y, b);
Curve25519::select(u.z, u.z, v.z, b);
Curve25519::select(u.t, u.t, v.t, b);
}
Curve25519::copy(result->x, u.x);
Curve25519::copy(result->y, u.y);
Curve25519::copy(result->z, u.z);
Curve25519::copy(result->t, u.t);
}
// https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.2
void Ed25519::encode_point(Ed25519Point* point, u8* data)
{
// Retrieve affine representation
Curve25519::modular_multiply_inverse(point->z, point->z);
Curve25519::modular_multiply(point->x, point->x, point->z);
Curve25519::modular_multiply(point->y, point->y, point->z);
Curve25519::set(point->z, 1);
Curve25519::modular_multiply(point->t, point->x, point->y);
// First, encode the y-coordinate (in the range 0 <= y < p) as a little-endian string of 32 octets.
// The most significant bit of the final octet is always zero.
Curve25519::export_state(point->y, data);
// To form the encoding of the point [s]B,
// copy the least significant bit of the x coordinate to the most significant bit of the final octet.
data[31] |= (point->x[0] & 1) << 7;
}
void Ed25519::barrett_reduce(u8* result, u8 const* input)
{
// Barrett reduction b = 2^8 && k = 32
u8 is_negative;
u8 u[33];
u8 v[33];
multiply(NULL, u, input + 31, Curve25519::BARRETT_REDUCTION_QUOTIENT, 33);
multiply(v, NULL, u, Curve25519::BASE_POINT_L_ORDER, 33);
subtract(u, input, v, 33);
is_negative = subtract(v, u, Curve25519::BASE_POINT_L_ORDER, 33);
select(u, v, u, is_negative, 33);
is_negative = subtract(v, u, Curve25519::BASE_POINT_L_ORDER, 33);
select(u, v, u, is_negative, 33);
copy(result, u, 32);
}
void Ed25519::multiply(u8* result_low, u8* result_high, u8 const* a, u8 const* b, u8 n)
{
// Comba's algorithm
u32 temp = 0;
for (u32 i = 0; i < n; i++) {
for (u32 j = 0; j <= i; j++) {
temp += (uint16_t)a[j] * b[i - j];
}
if (result_low != NULL) {
result_low[i] = temp & 0xFF;
}
temp >>= 8;
}
if (result_high != NULL) {
for (u32 i = n; i < (2 * n); i++) {
for (u32 j = i + 1 - n; j < n; j++) {
temp += (uint16_t)a[j] * b[i - j];
}
result_high[i - n] = temp & 0xFF;
temp >>= 8;
}
}
}
void Ed25519::add(u8* result, u8 const* a, u8 const* b, u8 n)
{
// Compute R = A + B
u16 temp = 0;
for (u8 i = 0; i < n; i++) {
temp += a[i];
temp += b[i];
result[i] = temp & 0xFF;
temp >>= 8;
}
}
u8 Ed25519::subtract(u8* result, u8 const* a, u8 const* b, u8 n)
{
i16 temp = 0;
// Compute R = A - B
for (i8 i = 0; i < n; i++) {
temp += a[i];
temp -= b[i];
result[i] = temp & 0xFF;
temp >>= 8;
}
// Return 1 if the result of the subtraction is negative
return temp & 1;
}
void Ed25519::select(u8* r, u8 const* a, u8 const* b, u8 c, u8 n)
{
u8 mask = c - 1;
for (u8 i = 0; i < n; i++) {
r[i] = (a[i] & mask) | (b[i] & ~mask);
}
}
// https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.3
u32 Ed25519::decode_point(Ed25519Point* point, u8 const* data)
{
u32 u[8];
u32 v[8];
u32 ret;
u64 temp = 19;
// 1. First, interpret the string as an integer in little-endian representation.
// Bit 255 of this number is the least significant bit of the x-coordinate and denote this value x_0.
u8 x0 = data[31] >> 7;
// The y-coordinate is recovered simply by clearing this bit.
Curve25519::import_state(point->y, data);
point->y[7] &= 0x7FFFFFFF;
// Compute U = Y + 19
for (u32 i = 0; i < 8; i++) {
temp += point->y[i];
u[i] = temp & 0xFFFFFFFF;
temp >>= 32;
}
// If the resulting value is >= p, decoding fails.
ret = (u[7] >> 31) & 1;
// 2. To recover the x-coordinate, the curve equation implies x^2 = (y^2 - 1) / (d y^2 + 1) (mod p).
// The denominator is always non-zero mod p.
// Let u = y^2 - 1 and v = d * y^2 + 1
Curve25519::modular_square(v, point->y);
Curve25519::modular_subtract_single(u, v, 1);
Curve25519::modular_multiply(v, v, Curve25519::CURVE_D);
Curve25519::modular_add_single(v, v, 1);
// 3. Compute u = sqrt(u / v)
ret |= Curve25519::modular_square_root(u, u, v);
// If x = 0, and x_0 = 1, decoding fails.
ret |= (Curve25519::compare(u, Curve25519::ZERO) ^ 1) & x0;
// 4. Finally, use the x_0 bit to select the right square root.
Curve25519::modular_subtract(v, Curve25519::ZERO, u);
Curve25519::select(point->x, u, v, (x0 ^ u[0]) & 1);
Curve25519::set(point->z, 1);
Curve25519::modular_multiply(point->t, point->x, point->y);
// Return 0 if the point has been successfully decoded, else 1
return ret;
}
void Ed25519::point_add(Ed25519Point* result, Ed25519Point const* p, Ed25519Point const* q)
{
// Compute R = P + Q
Curve25519::modular_add(c, p->y, p->x);
Curve25519::modular_add(d, q->y, q->x);
Curve25519::modular_multiply(a, c, d);
Curve25519::modular_subtract(c, p->y, p->x);
Curve25519::modular_subtract(d, q->y, q->x);
Curve25519::modular_multiply(b, c, d);
Curve25519::modular_multiply(c, p->z, q->z);
Curve25519::modular_add(c, c, c);
Curve25519::modular_multiply(d, p->t, q->t);
Curve25519::modular_multiply(d, d, Curve25519::CURVE_D_2);
Curve25519::modular_add(e, a, b);
Curve25519::modular_subtract(f, a, b);
Curve25519::modular_add(g, c, d);
Curve25519::modular_subtract(h, c, d);
Curve25519::modular_multiply(result->x, f, h);
Curve25519::modular_multiply(result->y, e, g);
Curve25519::modular_multiply(result->z, g, h);
Curve25519::modular_multiply(result->t, e, f);
}
u8 Ed25519::compare(u8 const* a, u8 const* b, u8 n)
{
u8 mask = 0;
for (u32 i = 0; i < n; i++) {
mask |= a[i] ^ b[i];
}
// Return 0 if A = B, else 1
return ((u8)(mask | (~mask + 1))) >> 7;
}
void Ed25519::copy(u8* a, u8 const* b, u32 n)
{
for (u32 i = 0; i < n; i++) {
a[i] = b[i];
}
}
}

View file

@ -1,74 +0,0 @@
/*
* Copyright (c) 2022, stelar7 <dudedbz@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteBuffer.h>
namespace Crypto::Curves {
struct Ed25519Point {
u32 x[8] {};
u32 y[8] {};
u32 z[8] {};
u32 t[8] {};
};
class Ed25519 {
public:
static constexpr Ed25519Point BASE_POINT = {
{ 0x8F25D51A, 0xC9562D60, 0x9525A7B2, 0x692CC760, 0xFDD6DC5C, 0xC0A4E231, 0xCD6E53FE, 0x216936D3 },
{ 0x66666658, 0x66666666, 0x66666666, 0x66666666, 0x66666666, 0x66666666, 0x66666666, 0x66666666 },
{ 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 },
{ 0xA5B7DDA3, 0x6DDE8AB3, 0x775152F5, 0x20F09F80, 0x64ABE37D, 0x66EA4E8E, 0xD78B7665, 0x67875F0F }
};
size_t key_size() { return 32; }
size_t signature_size() { return 64; }
ErrorOr<ByteBuffer> generate_private_key();
ErrorOr<ByteBuffer> generate_public_key(ReadonlyBytes private_key);
ErrorOr<ByteBuffer> sign(ReadonlyBytes public_key, ReadonlyBytes private_key, ReadonlyBytes message);
bool verify(ReadonlyBytes public_key, ReadonlyBytes signature, ReadonlyBytes message);
private:
void encode_point(Ed25519Point* point, u8* data);
u32 decode_point(Ed25519Point* point, u8 const* data);
void point_add(Ed25519Point* result, Ed25519Point const* p, Ed25519Point const* q);
void point_double(Ed25519Point* result, Ed25519Point const* point);
void point_multiply_scalar(Ed25519Point* result, u8 const* scalar, Ed25519Point const* point);
void barrett_reduce(u8* result, u8 const* input);
void add(u8* result, u8 const* a, u8 const* b, u8 n);
u8 subtract(u8* result, u8 const* a, u8 const* b, u8 n);
void multiply(u8* result_low, u8* result_high, u8 const* a, u8 const* b, u8 n);
void select(u8* result, u8 const* a, u8 const* b, u8 c, u8 n);
u8 compare(u8 const* a, u8 const* b, u8 n);
void copy(u8* a, u8 const* b, u32 n);
u8 k[64] {};
u8 p[32] {};
u8 r[32] {};
u8 s[32] {};
Ed25519Point ka {};
Ed25519Point rb {};
Ed25519Point sb {};
Ed25519Point u {};
Ed25519Point v {};
u32 a[8] {};
u32 b[8] {};
u32 c[8] {};
u32 d[8] {};
u32 e[8] {};
u32 f[8] {};
u32 g[8] {};
u32 h[8] {};
};
}

View file

@ -1,82 +0,0 @@
/*
* Copyright (c) 2024, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ScopeGuard.h>
#include <LibCrypto/Curves/Ed448.h>
#include <LibCrypto/OpenSSL.h>
#include <openssl/core_names.h>
#include <openssl/evp.h>
namespace Crypto::Curves {
ErrorOr<ByteBuffer> Ed448::generate_private_key()
{
auto key = TRY(OpenSSL_PKEY::wrap(EVP_PKEY_Q_keygen(nullptr, nullptr, "ED448")));
size_t key_size = EVP_PKEY_get_size(key.ptr());
auto buf = TRY(ByteBuffer::create_uninitialized(key_size));
OPENSSL_TRY(EVP_PKEY_get_raw_private_key(key.ptr(), buf.data(), &key_size));
return buf.slice(0, key_size);
}
ErrorOr<ByteBuffer> Ed448::generate_public_key(ReadonlyBytes private_key)
{
auto key = TRY(OpenSSL_PKEY::wrap(EVP_PKEY_new_raw_private_key(EVP_PKEY_ED448, nullptr, private_key.data(), private_key.size())));
size_t key_size = EVP_PKEY_get_size(key.ptr());
auto buf = TRY(ByteBuffer::create_uninitialized(key_size));
OPENSSL_TRY(EVP_PKEY_get_raw_public_key(key.ptr(), buf.data(), &key_size));
return buf.slice(0, key_size);
}
ErrorOr<ByteBuffer> Ed448::sign(ReadonlyBytes private_key, ReadonlyBytes message, ReadonlyBytes context)
{
auto key = TRY(OpenSSL_PKEY::wrap(EVP_PKEY_new_raw_private_key_ex(nullptr, "ED448", nullptr, private_key.data(), private_key.size())));
auto ctx = TRY(OpenSSL_MD_CTX::create());
OSSL_PARAM params[] = {
OSSL_PARAM_octet_string(OSSL_SIGNATURE_PARAM_CONTEXT_STRING, const_cast<u8*>(context.data()), context.size()),
OSSL_PARAM_END
};
OPENSSL_TRY(EVP_DigestSignInit_ex(ctx.ptr(), nullptr, nullptr, nullptr, nullptr, key.ptr(), params));
size_t sig_len = signature_size();
auto sig = TRY(ByteBuffer::create_uninitialized(sig_len));
OPENSSL_TRY(EVP_DigestSign(ctx.ptr(), sig.data(), &sig_len, message.data(), message.size()));
return sig.slice(0, sig_len);
}
ErrorOr<bool> Ed448::verify(ReadonlyBytes public_key, ReadonlyBytes signature, ReadonlyBytes message, ReadonlyBytes context)
{
auto key = TRY(OpenSSL_PKEY::wrap(EVP_PKEY_new_raw_public_key_ex(nullptr, "ED448", nullptr, public_key.data(), public_key.size())));
auto ctx = TRY(OpenSSL_MD_CTX::create());
OSSL_PARAM params[] = {
OSSL_PARAM_octet_string(OSSL_SIGNATURE_PARAM_CONTEXT_STRING, const_cast<u8*>(context.data()), context.size()),
OSSL_PARAM_END
};
OPENSSL_TRY(EVP_DigestVerifyInit_ex(ctx.ptr(), nullptr, nullptr, nullptr, nullptr, key.ptr(), params));
auto res = EVP_DigestVerify(ctx.ptr(), signature.data(), signature.size(), message.data(), message.size());
if (res == 1)
return true;
if (res == 0)
return false;
OPENSSL_TRY(res);
VERIFY_NOT_REACHED();
}
}

View file

@ -1,24 +0,0 @@
/*
* Copyright (c) 2024, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteBuffer.h>
namespace Crypto::Curves {
class Ed448 {
public:
constexpr size_t key_size() const { return 57; }
constexpr size_t signature_size() const { return 114; }
ErrorOr<ByteBuffer> generate_private_key();
ErrorOr<ByteBuffer> generate_public_key(ReadonlyBytes private_key);
ErrorOr<ByteBuffer> sign(ReadonlyBytes private_key, ReadonlyBytes message, ReadonlyBytes context = {});
ErrorOr<bool> verify(ReadonlyBytes public_key, ReadonlyBytes signature, ReadonlyBytes message, ReadonlyBytes context = {});
};
}

View file

@ -0,0 +1,113 @@
/*
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ScopeGuard.h>
#include <LibCrypto/Curves/EdwardsCurve.h>
#include <LibCrypto/OpenSSL.h>
#include <openssl/core_names.h>
#include <openssl/evp.h>
namespace Crypto::Curves {
ErrorOr<ByteBuffer> EdwardsCurve::generate_private_key()
{
auto key = TRY(OpenSSL_PKEY::wrap(EVP_PKEY_Q_keygen(nullptr, nullptr, m_curve_name)));
size_t key_size = 0;
OPENSSL_TRY(EVP_PKEY_get_raw_private_key(key.ptr(), nullptr, &key_size));
auto buf = TRY(ByteBuffer::create_uninitialized(key_size));
OPENSSL_TRY(EVP_PKEY_get_raw_private_key(key.ptr(), buf.data(), &key_size));
return buf;
}
ErrorOr<ByteBuffer> EdwardsCurve::generate_public_key(ReadonlyBytes private_key)
{
auto key = TRY(OpenSSL_PKEY::wrap(EVP_PKEY_new_raw_private_key_ex(nullptr, m_curve_name, nullptr, private_key.data(), private_key.size())));
size_t key_size = 0;
OPENSSL_TRY(EVP_PKEY_get_raw_public_key(key.ptr(), nullptr, &key_size));
auto buf = TRY(ByteBuffer::create_uninitialized(key_size));
OPENSSL_TRY(EVP_PKEY_get_raw_public_key(key.ptr(), buf.data(), &key_size));
return buf;
}
ErrorOr<ByteBuffer> SignatureEdwardsCurve::sign(ReadonlyBytes private_key, ReadonlyBytes message, ReadonlyBytes context)
{
auto key = TRY(OpenSSL_PKEY::wrap(EVP_PKEY_new_raw_private_key_ex(nullptr, m_curve_name, nullptr, private_key.data(), private_key.size())));
auto ctx = TRY(OpenSSL_MD_CTX::create());
OSSL_PARAM params[2] = {
OSSL_PARAM_END,
OSSL_PARAM_END
};
if (!context.is_null()) {
params[0] = OSSL_PARAM_octet_string(OSSL_SIGNATURE_PARAM_CONTEXT_STRING, const_cast<u8*>(context.data()), context.size());
}
OPENSSL_TRY(EVP_DigestSignInit_ex(ctx.ptr(), nullptr, nullptr, nullptr, nullptr, key.ptr(), params));
size_t sig_len = 0;
OPENSSL_TRY(EVP_DigestSign(ctx.ptr(), nullptr, &sig_len, message.data(), message.size()));
auto sig = TRY(ByteBuffer::create_uninitialized(sig_len));
OPENSSL_TRY(EVP_DigestSign(ctx.ptr(), sig.data(), &sig_len, message.data(), message.size()));
return sig;
}
ErrorOr<bool> SignatureEdwardsCurve::verify(ReadonlyBytes public_key, ReadonlyBytes signature, ReadonlyBytes message, ReadonlyBytes context)
{
auto key = TRY(OpenSSL_PKEY::wrap(EVP_PKEY_new_raw_public_key_ex(nullptr, m_curve_name, nullptr, public_key.data(), public_key.size())));
auto ctx = TRY(OpenSSL_MD_CTX::create());
OSSL_PARAM params[2] = {
OSSL_PARAM_END,
OSSL_PARAM_END
};
if (!context.is_null()) {
params[0] = OSSL_PARAM_octet_string(OSSL_SIGNATURE_PARAM_CONTEXT_STRING, const_cast<u8*>(context.data()), context.size());
}
OPENSSL_TRY(EVP_DigestVerifyInit_ex(ctx.ptr(), nullptr, nullptr, nullptr, nullptr, key.ptr(), params));
auto res = EVP_DigestVerify(ctx.ptr(), signature.data(), signature.size(), message.data(), message.size());
if (res == 1)
return true;
if (res == 0)
return false;
OPENSSL_TRY(res);
VERIFY_NOT_REACHED();
}
ErrorOr<ByteBuffer> ExchangeEdwardsCurve::compute_coordinate(ReadonlyBytes private_key, ReadonlyBytes public_key)
{
auto key = TRY(OpenSSL_PKEY::wrap(EVP_PKEY_new_raw_private_key_ex(nullptr, m_curve_name, nullptr, private_key.data(), private_key.size())));
auto peerkey = TRY(OpenSSL_PKEY::wrap(EVP_PKEY_new_raw_public_key_ex(nullptr, m_curve_name, nullptr, public_key.data(), public_key.size())));
auto ctx = TRY(OpenSSL_PKEY_CTX::wrap(EVP_PKEY_CTX_new(key.ptr(), nullptr)));
OPENSSL_TRY(EVP_PKEY_derive_init(ctx.ptr()));
OPENSSL_TRY(EVP_PKEY_derive_set_peer(ctx.ptr(), peerkey.ptr()));
size_t key_size = 0;
OPENSSL_TRY(EVP_PKEY_derive(ctx.ptr(), nullptr, &key_size));
auto buf = TRY(ByteBuffer::create_uninitialized(key_size));
OPENSSL_TRY(EVP_PKEY_derive(ctx.ptr(), buf.data(), &key_size));
return buf;
}
}

View file

@ -0,0 +1,83 @@
/*
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteBuffer.h>
#include <LibCrypto/OpenSSL.h>
namespace Crypto::Curves {
class EdwardsCurve {
public:
ErrorOr<ByteBuffer> generate_private_key();
ErrorOr<ByteBuffer> generate_public_key(ReadonlyBytes private_key);
protected:
EdwardsCurve(char const* curve_name)
: m_curve_name(curve_name)
{
}
char const* m_curve_name;
};
class SignatureEdwardsCurve : public EdwardsCurve {
public:
ErrorOr<ByteBuffer> sign(ReadonlyBytes private_key, ReadonlyBytes message, ReadonlyBytes context = {});
ErrorOr<bool> verify(ReadonlyBytes public_key, ReadonlyBytes signature, ReadonlyBytes message, ReadonlyBytes context = {});
protected:
explicit SignatureEdwardsCurve(char const* curve_name)
: EdwardsCurve(curve_name)
{
}
};
class ExchangeEdwardsCurve : public EdwardsCurve {
public:
ErrorOr<ByteBuffer> compute_coordinate(ReadonlyBytes private_key, ReadonlyBytes public_key);
protected:
explicit ExchangeEdwardsCurve(char const* curve_name)
: EdwardsCurve(curve_name)
{
}
};
class Ed448 : public SignatureEdwardsCurve {
public:
Ed448()
: SignatureEdwardsCurve("ED448")
{
}
};
class X448 : public ExchangeEdwardsCurve {
public:
X448()
: ExchangeEdwardsCurve("X448")
{
}
};
class Ed25519 : public SignatureEdwardsCurve {
public:
Ed25519()
: SignatureEdwardsCurve("ED25519")
{
}
};
class X25519 : public ExchangeEdwardsCurve {
public:
X25519()
: ExchangeEdwardsCurve("X25519")
{
}
};
}

View file

@ -1,129 +0,0 @@
/*
* Copyright (c) 2022, stelar7 <dudedbz@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ByteReader.h>
#include <AK/Random.h>
#include <LibCrypto/Curves/Curve25519.h>
#include <LibCrypto/Curves/X25519.h>
#include <LibCrypto/SecureRandom.h>
namespace Crypto::Curves {
static constexpr u8 BITS = 255;
static constexpr u8 BYTES = 32;
static constexpr u8 WORDS = 8;
static constexpr u32 A24 = 121666;
static void conditional_swap(u32* first, u32* second, u32 condition)
{
u32 mask = ~condition + 1;
for (auto i = 0; i < WORDS; i++) {
u32 temp = mask & (first[i] ^ second[i]);
first[i] ^= temp;
second[i] ^= temp;
}
}
ErrorOr<ByteBuffer> X25519::generate_private_key()
{
auto buffer = TRY(ByteBuffer::create_uninitialized(BYTES));
fill_with_secure_random(buffer);
return buffer;
}
ErrorOr<ByteBuffer> X25519::generate_public_key(ReadonlyBytes a)
{
u8 generator[BYTES] { 9 };
return compute_coordinate(a, { generator, BYTES });
}
// https://datatracker.ietf.org/doc/html/rfc7748#section-5
ErrorOr<ByteBuffer> X25519::compute_coordinate(ReadonlyBytes input_k, ReadonlyBytes input_u)
{
u32 k[WORDS] {};
u32 u[WORDS] {};
u32 x1[WORDS] {};
u32 x2[WORDS] {};
u32 z1[WORDS] {};
u32 z2[WORDS] {};
u32 t1[WORDS] {};
u32 t2[WORDS] {};
// Copy input to internal state
Curve25519::import_state(k, input_k.data());
// Set the three least significant bits of the first byte and the most significant bit of the last to zero,
// set the second most significant bit of the last byte to 1
k[0] &= 0xFFFFFFF8;
k[7] &= 0x7FFFFFFF;
k[7] |= 0x40000000;
// Copy coordinate to internal state
Curve25519::import_state(u, input_u.data());
// mask the most significant bit in the final byte.
u[7] &= 0x7FFFFFFF;
// Implementations MUST accept non-canonical values and process them as
// if they had been reduced modulo the field prime.
Curve25519::modular_reduce(u, u);
Curve25519::set(x1, 1);
Curve25519::set(z1, 0);
Curve25519::copy(x2, u);
Curve25519::set(z2, 1);
// Montgomery ladder
u32 swap = 0;
for (auto i = BITS - 1; i >= 0; i--) {
u32 b = (k[i / BYTES] >> (i % BYTES)) & 1;
conditional_swap(x1, x2, swap ^ b);
conditional_swap(z1, z2, swap ^ b);
swap = b;
Curve25519::modular_add(t1, x2, z2);
Curve25519::modular_subtract(x2, x2, z2);
Curve25519::modular_add(z2, x1, z1);
Curve25519::modular_subtract(x1, x1, z1);
Curve25519::modular_multiply(t1, t1, x1);
Curve25519::modular_multiply(x2, x2, z2);
Curve25519::modular_square(z2, z2);
Curve25519::modular_square(x1, x1);
Curve25519::modular_subtract(t2, z2, x1);
Curve25519::modular_multiply_single(z1, t2, A24);
Curve25519::modular_add(z1, z1, x1);
Curve25519::modular_multiply(z1, z1, t2);
Curve25519::modular_multiply(x1, x1, z2);
Curve25519::modular_subtract(z2, t1, x2);
Curve25519::modular_square(z2, z2);
Curve25519::modular_multiply(z2, z2, u);
Curve25519::modular_add(x2, x2, t1);
Curve25519::modular_square(x2, x2);
}
conditional_swap(x1, x2, swap);
conditional_swap(z1, z2, swap);
// Retrieve affine representation
Curve25519::modular_multiply_inverse(u, z1);
Curve25519::modular_multiply(u, u, x1);
// Encode state for export
auto buffer = TRY(ByteBuffer::create_uninitialized(BYTES));
Curve25519::export_state(u, buffer.data());
return buffer;
}
ErrorOr<ByteBuffer> X25519::derive_premaster_key(ReadonlyBytes shared_point)
{
VERIFY(shared_point.size() == BYTES);
ByteBuffer premaster_key = TRY(ByteBuffer::copy(shared_point));
return premaster_key;
}
}

View file

@ -1,22 +0,0 @@
/*
* Copyright (c) 2022, stelar7 <dudedbz@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteBuffer.h>
namespace Crypto::Curves {
class X25519 {
public:
size_t key_size() { return 32; }
ErrorOr<ByteBuffer> generate_private_key();
ErrorOr<ByteBuffer> generate_public_key(ReadonlyBytes a);
ErrorOr<ByteBuffer> compute_coordinate(ReadonlyBytes a, ReadonlyBytes b);
ErrorOr<ByteBuffer> derive_premaster_key(ReadonlyBytes shared_point);
};
}

View file

@ -1,384 +0,0 @@
/*
* Copyright (c) 2022, stelar7 <dudedbz@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ByteReader.h>
#include <AK/Endian.h>
#include <AK/Random.h>
#include <LibCrypto/Curves/X448.h>
#include <LibCrypto/SecureRandom.h>
namespace Crypto::Curves {
static constexpr u16 BITS = 448;
static constexpr u8 BYTES = 56;
static constexpr u8 WORDS = 14;
static constexpr u32 A24 = 39082;
static void import_state(u32* state, ReadonlyBytes data)
{
for (auto i = 0; i < WORDS; i++) {
u32 value = ByteReader::load32(data.offset_pointer(sizeof(u32) * i));
state[i] = AK::convert_between_host_and_little_endian(value);
}
}
static ErrorOr<ByteBuffer> export_state(u32* data)
{
auto buffer = TRY(ByteBuffer::create_uninitialized(BYTES));
for (auto i = 0; i < WORDS; i++) {
u32 value = AK::convert_between_host_and_little_endian(data[i]);
ByteReader::store(buffer.offset_pointer(sizeof(u32) * i), value);
}
return buffer;
}
static void select(u32* state, u32* a, u32* b, u32 condition)
{
// If B < (2^448 - 2^224 + 1) then R = B, else R = A
u32 mask = condition - 1;
for (auto i = 0; i < WORDS; i++) {
state[i] = (a[i] & mask) | (b[i] & ~mask);
}
}
static void set(u32* state, u32 value)
{
state[0] = value;
for (auto i = 1; i < WORDS; i++) {
state[i] = 0;
}
}
static void copy(u32* state, u32* value)
{
for (auto i = 0; i < WORDS; i++) {
state[i] = value[i];
}
}
static void conditional_swap(u32* first, u32* second, u32 condition)
{
u32 mask = ~condition + 1;
for (auto i = 0; i < WORDS; i++) {
u32 temp = mask & (first[i] ^ second[i]);
first[i] ^= temp;
second[i] ^= temp;
}
}
static void modular_reduce(u32* state, u32* data, u32 a_high)
{
u64 temp = 1;
u32 other[WORDS];
// Compute B = A - (2^448 - 2^224 - 1)
for (auto i = 0; i < WORDS / 2; i++) {
temp += data[i];
other[i] = temp & 0xFFFFFFFF;
temp >>= 32;
}
temp += 1;
for (auto i = 7; i < WORDS; i++) {
temp += data[i];
other[i] = temp & 0xFFFFFFFF;
temp >>= 32;
}
auto condition = (a_high + (u32)temp - 1) & 1;
select(state, other, data, condition);
}
static void modular_multiply_single(u32* state, u32* first, u32 second)
{
// Compute R = (A * B) mod p
u64 temp = 0;
u64 carry = 0;
u32 output[WORDS];
for (auto i = 0; i < WORDS; i++) {
temp += (u64)first[i] * second;
output[i] = temp & 0xFFFFFFFF;
temp >>= 32;
}
// Fast modular reduction
carry = temp;
for (auto i = 0; i < WORDS / 2; i++) {
temp += output[i];
output[i] = temp & 0xFFFFFFFF;
temp >>= 32;
}
temp += carry;
for (auto i = WORDS / 2; i < WORDS; i++) {
temp += output[i];
output[i] = temp & 0xFFFFFFFF;
temp >>= 32;
}
modular_reduce(state, output, (u32)temp);
}
static void modular_multiply(u32* state, u32* first, u32* second)
{
// Compute R = (A * B) mod p
u64 temp = 0;
u64 carry = 0;
u32 output[WORDS * 2];
// Comba's method
for (auto i = 0; i < WORDS * 2; i++) {
if (i < 14) {
for (auto j = 0; j <= i; j++) {
temp += (u64)first[j] * second[i - j];
carry += temp >> 32;
temp &= 0xFFFFFFFF;
}
} else {
for (auto j = i - 13; j < WORDS; j++) {
temp += (u64)first[j] * second[i - j];
carry += temp >> 32;
temp &= 0xFFFFFFFF;
}
}
output[i] = temp & 0xFFFFFFFF;
temp = carry & 0xFFFFFFFF;
carry >>= 32;
}
// Fast modular reduction (first pass)
temp = 0;
for (auto i = 0; i < WORDS / 2; i++) {
temp += output[i];
temp += output[i + 14];
temp += output[i + 21];
output[i] = temp & 0xFFFFFFFF;
temp >>= 32;
}
for (auto i = WORDS / 2; i < WORDS; i++) {
temp += output[i];
temp += output[i + 7];
temp += output[i + 14];
temp += output[i + 14];
output[i] = temp & 0xFFFFFFFF;
temp >>= 32;
}
// Fast modular reduction (second pass)
carry = temp;
for (auto i = 0; i < WORDS / 2; i++) {
temp += output[i];
output[i] = temp & 0xFFFFFFFF;
temp >>= 32;
}
temp += carry;
for (auto i = WORDS / 2; i < WORDS; i++) {
temp += output[i];
output[i] = temp & 0xFFFFFFFF;
temp >>= 32;
}
modular_reduce(state, output, (u32)temp);
}
static void modular_square(u32* state, u32* value)
{
// Compute R = (A ^ 2) mod p
modular_multiply(state, value, value);
}
static void modular_add(u32* state, u32* first, u32* second)
{
u64 temp = 0;
// Compute R = A + B
for (auto i = 0; i < WORDS; i++) {
temp += first[i];
temp += second[i];
state[i] = temp & 0xFFFFFFFF;
temp >>= 32;
}
modular_reduce(state, state, (u32)temp);
}
static void modular_subtract(u32* state, u32* first, u32* second)
{
i64 temp = -1;
// Compute R = A + (2^448 - 2^224 - 1) - B
for (auto i = 0; i < 7; i++) {
temp += first[i];
temp -= second[i];
state[i] = temp & 0xFFFFFFFF;
temp >>= 32;
}
temp -= 1;
for (auto i = 7; i < 14; i++) {
temp += first[i];
temp -= second[i];
state[i] = temp & 0xFFFFFFFF;
temp >>= 32;
}
temp += 1;
modular_reduce(state, state, (u32)temp);
}
static void to_power_of_2n(u32* state, u32* value, u8 n)
{
// Compute R = (A ^ (2^n)) mod p
modular_square(state, value);
for (auto i = 1; i < n; i++) {
modular_square(state, state);
}
}
static void modular_multiply_inverse(u32* state, u32* value)
{
// Compute R = A^-1 mod p
u32 u[WORDS];
u32 v[WORDS];
modular_square(u, value);
modular_multiply(u, u, value);
modular_square(u, u);
modular_multiply(v, u, value);
to_power_of_2n(u, v, 3);
modular_multiply(v, u, v);
to_power_of_2n(u, v, 6);
modular_multiply(u, u, v);
modular_square(u, u);
modular_multiply(v, u, value);
to_power_of_2n(u, v, 13);
modular_multiply(u, u, v);
modular_square(u, u);
modular_multiply(v, u, value);
to_power_of_2n(u, v, 27);
modular_multiply(u, u, v);
modular_square(u, u);
modular_multiply(v, u, value);
to_power_of_2n(u, v, 55);
modular_multiply(u, u, v);
modular_square(u, u);
modular_multiply(v, u, value);
to_power_of_2n(u, v, 111);
modular_multiply(v, u, v);
modular_square(u, v);
modular_multiply(u, u, value);
to_power_of_2n(u, u, 223);
modular_multiply(u, u, v);
modular_square(u, u);
modular_square(u, u);
modular_multiply(state, u, value);
}
ErrorOr<ByteBuffer> X448::generate_private_key()
{
auto buffer = TRY(ByteBuffer::create_uninitialized(BYTES));
fill_with_secure_random(buffer);
return buffer;
}
ErrorOr<ByteBuffer> X448::generate_public_key(ReadonlyBytes a)
{
u8 generator[BYTES] { 5 };
return compute_coordinate(a, { generator, BYTES });
}
// https://datatracker.ietf.org/doc/html/rfc7748#section-5
ErrorOr<ByteBuffer> X448::compute_coordinate(ReadonlyBytes input_k, ReadonlyBytes input_u)
{
u32 k[WORDS] {};
u32 u[WORDS] {};
u32 x1[WORDS] {};
u32 x2[WORDS] {};
u32 z1[WORDS] {};
u32 z2[WORDS] {};
u32 t1[WORDS] {};
u32 t2[WORDS] {};
// Copy input to internal state
import_state(k, input_k);
// Set the two least significant bits of the first byte to 0, and the most significant bit of the last byte to 1
k[0] &= 0xFFFFFFFC;
k[13] |= 0x80000000;
// Copy coordinate to internal state
import_state(u, input_u);
// Implementations MUST accept non-canonical values and process them as
// if they had been reduced modulo the field prime.
modular_reduce(u, u, 0);
set(x1, 1);
set(z1, 0);
copy(x2, u);
set(z2, 1);
// Montgomery ladder
u32 swap = 0;
for (auto i = BITS - 1; i >= 0; i--) {
u32 b = (k[i / 32] >> (i % 32)) & 1;
conditional_swap(x1, x2, swap ^ b);
conditional_swap(z1, z2, swap ^ b);
swap = b;
modular_add(t1, x2, z2);
modular_subtract(x2, x2, z2);
modular_add(z2, x1, z1);
modular_subtract(x1, x1, z1);
modular_multiply(t1, t1, x1);
modular_multiply(x2, x2, z2);
modular_square(z2, z2);
modular_square(x1, x1);
modular_subtract(t2, z2, x1);
modular_multiply_single(z1, t2, A24);
modular_add(z1, z1, x1);
modular_multiply(z1, z1, t2);
modular_multiply(x1, x1, z2);
modular_subtract(z2, t1, x2);
modular_square(z2, z2);
modular_multiply(z2, z2, u);
modular_add(x2, x2, t1);
modular_square(x2, x2);
}
conditional_swap(x1, x2, swap);
conditional_swap(z1, z2, swap);
// Retrieve affine representation
modular_multiply_inverse(u, z1);
modular_multiply(u, u, x1);
// Encode state for export
return export_state(u);
}
ErrorOr<ByteBuffer> X448::derive_premaster_key(ReadonlyBytes shared_point)
{
VERIFY(shared_point.size() == BYTES);
ByteBuffer premaster_key = TRY(ByteBuffer::copy(shared_point));
return premaster_key;
}
}

View file

@ -1,22 +0,0 @@
/*
* Copyright (c) 2022, stelar7 <dudedbz@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteBuffer.h>
namespace Crypto::Curves {
class X448 {
public:
size_t key_size() { return 56; }
ErrorOr<ByteBuffer> generate_private_key();
ErrorOr<ByteBuffer> generate_public_key(ReadonlyBytes a);
ErrorOr<ByteBuffer> compute_coordinate(ReadonlyBytes a, ReadonlyBytes b);
ErrorOr<ByteBuffer> derive_premaster_key(ReadonlyBytes shared_point);
};
}

View file

@ -3,7 +3,7 @@
* Copyright (c) 2024, stelar7 <dudedbz@gmail.com>
* Copyright (c) 2024, Jelle Raaijmakers <jelle@ladybird.org>
* Copyright (c) 2024, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2024, Altomani Gianluca <altomanigianluca@gmail.com>
* Copyright (c) 2024-2025, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -17,11 +17,8 @@
#include <LibCrypto/Authentication/HMAC.h>
#include <LibCrypto/Certificate/Certificate.h>
#include <LibCrypto/Cipher/AES.h>
#include <LibCrypto/Curves/Ed25519.h>
#include <LibCrypto/Curves/Ed448.h>
#include <LibCrypto/Curves/EdwardsCurve.h>
#include <LibCrypto/Curves/SECPxxxr1.h>
#include <LibCrypto/Curves/X25519.h>
#include <LibCrypto/Curves/X448.h>
#include <LibCrypto/Hash/HKDF.h>
#include <LibCrypto/Hash/HashManager.h>
#include <LibCrypto/Hash/PBKDF2.h>
@ -6091,7 +6088,7 @@ WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> ED25519::sign([[maybe_unused]] Alg
return WebIDL::OperationError::create(realm, "Failed to generate public key"_string);
auto public_key = maybe_public_key.release_value();
auto maybe_signature = curve.sign(public_key, private_key, message);
auto maybe_signature = curve.sign(private_key, message);
if (maybe_signature.is_error())
return WebIDL::OperationError::create(realm, "Failed to sign message"_string);
auto signature = maybe_signature.release_value();
@ -6122,10 +6119,14 @@ WebIDL::ExceptionOr<JS::Value> ED25519::verify([[maybe_unused]] AlgorithmParams
// 9. Let result be a boolean with the value true if the signature is valid and the value false otherwise.
::Crypto::Curves::Ed25519 curve;
auto result = curve.verify(public_key, signature, message);
auto maybe_verified = curve.verify(key->handle().get<ByteBuffer>(), signature, message);
if (maybe_verified.is_error()) {
auto error_message = MUST(String::from_utf8(maybe_verified.error().string_literal()));
return WebIDL::OperationError::create(realm, error_message);
}
// 10. Return result.
return JS::Value(result);
return maybe_verified.release_value();
}
// https://wicg.github.io/webcrypto-secure-curves/#ed448-operations

View file

@ -5,9 +5,8 @@
*/
#include <AK/ByteBuffer.h>
#include <LibCrypto/Curves/EdwardsCurve.h>
#include <LibCrypto/Curves/SECPxxxr1.h>
#include <LibCrypto/Curves/X25519.h>
#include <LibCrypto/Curves/X448.h>
#include <LibTest/TestCase.h>
TEST_CASE(test_x25519)

View file

@ -5,7 +5,7 @@
*/
#include <AK/ByteBuffer.h>
#include <LibCrypto/Curves/Ed25519.h>
#include <LibCrypto/Curves/EdwardsCurve.h>
#include <LibTest/TestCase.h>
// https://datatracker.ietf.org/doc/html/rfc8032#section-7.1
@ -45,9 +45,9 @@ TEST_CASE(TEST_1)
Crypto::Curves::Ed25519 curve;
auto generated_signature = MUST(curve.sign(public_key, private_key, message));
auto generated_signature = TRY_OR_FAIL(curve.sign(private_key, message));
EXPECT_EQ(generated_signature, expected_signature);
EXPECT_EQ(true, curve.verify(public_key, expected_signature, message));
EXPECT(TRY_OR_FAIL(curve.verify(public_key, expected_signature, message)));
}
TEST_CASE(TEST_2)
@ -86,9 +86,9 @@ TEST_CASE(TEST_2)
Crypto::Curves::Ed25519 curve;
auto generated_signature = MUST(curve.sign(public_key, private_key, message));
auto generated_signature = TRY_OR_FAIL(curve.sign(private_key, message));
EXPECT_EQ(generated_signature, expected_signature);
EXPECT_EQ(true, curve.verify(public_key, expected_signature, message));
EXPECT(TRY_OR_FAIL(curve.verify(public_key, expected_signature, message)));
}
TEST_CASE(TEST_3)
@ -128,9 +128,9 @@ TEST_CASE(TEST_3)
Crypto::Curves::Ed25519 curve;
auto generated_signature = MUST(curve.sign(public_key, private_key, message));
auto generated_signature = TRY_OR_FAIL(curve.sign(private_key, message));
EXPECT_EQ(generated_signature, expected_signature);
EXPECT_EQ(true, curve.verify(public_key, expected_signature, message));
EXPECT(TRY_OR_FAIL(curve.verify(public_key, expected_signature, message)));
}
TEST_CASE(TEST_SHA_ABC)
@ -179,7 +179,7 @@ TEST_CASE(TEST_SHA_ABC)
Crypto::Curves::Ed25519 curve;
auto generated_signature = MUST(curve.sign(public_key, private_key, message));
auto generated_signature = TRY_OR_FAIL(curve.sign(private_key, message));
EXPECT_EQ(generated_signature, expected_signature);
EXPECT_EQ(true, curve.verify(public_key, expected_signature, message));
EXPECT(TRY_OR_FAIL(curve.verify(public_key, expected_signature, message)));
}