Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: x448 and Ed448 #3933

Merged
merged 4 commits into from
Mar 25, 2024
Merged

Feature: x448 and Ed448 #3933

merged 4 commits into from
Mar 25, 2024

Conversation

FAlbertDev
Copy link
Collaborator

@FAlbertDev FAlbertDev commented Mar 6, 2024

This pull request adds support for the x448 key agreement and the ed448 signature scheme as defined in RFC 7748 and RFC 8032. Requested in #3895.

Pull Request Dependencies

Features

  • X448 key agreement
  • Ed448 signature scheme without and with prehashing (Ed448 (default) and Ed448ph). As with Ed25519, the usage of custom contexts (i.e. Ed448ctx) is not supported.
  • FFI bindings for both algorithms
  • TLS support for both algorithms
  • Hybrid key exchange instances x448/eFrodoKEM-976-SHAKE, x448/eFrodoKEM-976-AES, and x448/Kyber-768-r3

Internals

The algorithms used for modular arithmetic in $GF(p)$, where $p = 2^{448} - 2^{224} - 1$, are based on the paper "Reduction Modulo 2^448 - 2^224 - 1" from Nath and Sarkar. These algorithms operate on 64-bit limbs and are implemented in the Gf448Elem class.

In addition, the Ed448 algorithm requires modular arithmetic on the group order of Curve448. To handle this, a Scalar448 class has been implemented, providing the necessary operations modulo the group order (L in RFC 8032).

Both implementations are designed to be constant-time. As the current big integer implementation is not constant-time for all required operations (especially reduction), the Gf448Elem and Scalar448 classes are independent of the big integer implementation and directly utilize the mp_core operations. This design may have future potential for an abstract GfElem class that can be used for other prime fields as well.

The test cases for these implementations are based on wycheproof.

Performance

$ ./botan speed --msec=3000 Curve448 Ed448
Curve448 3157 keygen/sec; 0.32 ms/op 528275 cycles/op (2 ops in 1 ms)
Curve448 3355 keygen/sec; 0.30 ms/op 503325 cycles/op (2 ops in 1 ms)
Curve448 3009 key agreements/sec; 0.33 ms/op 561397 cycles/op (9028 ops in 3000 ms)

Ed448 747 keygen/sec; 1.34 ms/op 2259032 cycles/op (1 op in 1 ms)
Ed448 Pure 1077 sign/sec; 0.93 ms/op 1568058 cycles/op (3233 ops in 3001 ms)
Ed448 Pure 550 verify/sec; 1.82 ms/op 3070291 cycles/op (1652 ops in 3002 ms)

@FAlbertDev FAlbertDev requested a review from reneme March 6, 2024 15:21
@coveralls
Copy link

coveralls commented Mar 6, 2024

Coverage Status

coverage: 92.082% (+0.04%) from 92.045%
when pulling a9a3d8b on Rohde-Schwarz:ec/448
into f03c6a9 on randombit:master.

Copy link
Collaborator

@reneme reneme left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First pass. Didn't look at the actual implementation of Ed/X448, yet. Mostly, higher-level API and design nits.

src/build-data/policy/modern.txt Outdated Show resolved Hide resolved
src/lib/ffi/ffi.h Outdated Show resolved Hide resolved
src/lib/ffi/ffi_pkey_algs.cpp Outdated Show resolved Hide resolved
src/lib/ffi/ffi_pkey_algs.cpp Outdated Show resolved Hide resolved
src/lib/ffi/ffi_pkey_algs.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/curve448/curve448_utils/curve448_scalar.cpp Outdated Show resolved Hide resolved
src/python/botan3.py Show resolved Hide resolved
src/python/botan3.py Show resolved Hide resolved
src/scripts/run_tls_fuzzer.py Show resolved Hide resolved
src/scripts/test_cli.py Show resolved Hide resolved
Copy link
Collaborator

@reneme reneme left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some more remarks mostly focussing on the utility classes.

src/lib/pubkey/curve448/curve448_utils/curve448_scalar.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/curve448/curve448_utils/curve448_scalar.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/curve448/curve448_utils/curve448_scalar.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/curve448/curve448_utils/curve448_scalar.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/curve448/curve448_utils/curve448_scalar.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/curve448/curve448_utils/curve448_gf.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/curve448/curve448_utils/curve448_gf.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/curve448/curve448_utils/curve448_gf.cpp Outdated Show resolved Hide resolved
* the value might be larger than the prime modulus. When calling the to_bytes() method, the
* canonical representation is returned.
*/
class Gf448Elem {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps its worthwhile defining some commonly used types:

using byte_span = std::span<const uint8_t, to_bytes(448)>;
using word_span = std::span<const uint64_t, to_words(448)>;
// same for arrays, perhaps

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't use such aliases because they hide the actual type from the user. Also, I would need one variant with and without const. However, I think using constants for 56 (bytes per 448) and 7 (words per 448) are sensible.

src/lib/pubkey/curve448/curve448_utils/curve448_gf.cpp Outdated Show resolved Hide resolved
@reneme reneme added this to the Botan 3.4.0 milestone Mar 13, 2024
@reneme reneme added the enhancement Enhancement or new feature label Mar 13, 2024
@reneme reneme linked an issue Mar 13, 2024 that may be closed by this pull request
Copy link
Collaborator

@reneme reneme left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments regarding the actual implementation of x448. Looks really concise to me! 😃

src/lib/pubkey/curve448/curve448_internal.h Outdated Show resolved Hide resolved
src/lib/pubkey/curve448/curve448_internal.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/curve448/curve448_internal.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/curve448/curve448_internal.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/curve448/curve448_internal.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/curve448/curve448.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/curve448/curve448.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/curve448/curve448.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/curve448/curve448.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/curve448/curve448.cpp Outdated Show resolved Hide resolved
Copy link
Collaborator

@reneme reneme left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments regarding Ed448.

src/lib/pubkey/ed448/ed448_internal.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/ed448/ed448_internal.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/ed448/ed448_internal.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/ed448/ed448_internal.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/ed448/ed448_internal.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/ed448/ed448.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/ed448/ed448.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/ed448/ed448.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/ed448/ed448.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/ed448/ed448.cpp Outdated Show resolved Hide resolved
@reneme
Copy link
Collaborator

reneme commented Mar 14, 2024

Generally, the implementation is well-structured and thought out. 😃 Don't be put off by the number of comments, most address small nits or potential memory inefficiencies. Some minor architectural things.

One thing, I'd really appreciate in general: Let's try to consolidate magic strings and magic numbers. First and foremost, the lengths of key material arrays (both in word length and byte length). But also the hash functions used. Please try to have as little "atomic" constants as possible and calculate dependent values were reasonably possible.

As mentioned before somewhere, sooner or later we should establish a standard interface to be able to access such constants statically from within the library.

@FAlbertDev
Copy link
Collaborator Author

Thank you very much for your helpful and detailed review, @reneme. I applied your suggestions and rebased to the current head of #3869.

Comment on lines 41 to 47
/// @return a word array for c = 0x8335dc163bb124b65129c96fde933d8d723a70aadc873d6d54a7bb0d
consteval std::array<word, WORDS_C> c_words() {
// Currently load_le does not work with constexpr. Therefore, we have to use this workaround.
const std::array<uint8_t, WORDS_C * sizeof(word)> c_bytes{0x0d, 0xbb, 0xa7, 0x54, 0x6d, 0x3d, 0x87, 0xdc, 0xaa, 0x70,
0x3a, 0x72, 0x8d, 0x3d, 0x93, 0xde, 0x6f, 0xc9, 0x29, 0x51,
0xb6, 0x24, 0xb1, 0x3b, 0x16, 0xdc, 0x35, 0x83};
return load_le<std::array<word, WORDS_C>>(c_bytes);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a blunt nerd-sniping attempt: if we would manage to make Botan::hex_decode() constexpr, this could be:

Suggested change
/// @return a word array for c = 0x8335dc163bb124b65129c96fde933d8d723a70aadc873d6d54a7bb0d
consteval std::array<word, WORDS_C> c_words() {
// Currently load_le does not work with constexpr. Therefore, we have to use this workaround.
const std::array<uint8_t, WORDS_C * sizeof(word)> c_bytes{0x0d, 0xbb, 0xa7, 0x54, 0x6d, 0x3d, 0x87, 0xdc, 0xaa, 0x70,
0x3a, 0x72, 0x8d, 0x3d, 0x93, 0xde, 0x6f, 0xc9, 0x29, 0x51,
0xb6, 0x24, 0xb1, 0x3b, 0x16, 0xdc, 0x35, 0x83};
return load_le<std::array<word, WORDS_C>>(c_bytes);
}
consteval std::array<word, WORDS_C> c_words() {
return load_le<std::array<word, WORDS_C>>(Botan::hex_decode(
"0dbba7546d3d87dcaa703a728d3d93de6fc92951b624b13b16dc3583"));
}

... which would be quite fantastic.

src/lib/pubkey/curve448/curve448_scalar.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/curve448/curve448_utils/curve448_scalar.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/ed448/ed448_internal.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/ed448/ed448.h Outdated Show resolved Hide resolved
src/lib/pubkey/ed448/ed448.cpp Outdated Show resolved Hide resolved
@FAlbertDev
Copy link
Collaborator Author

FAlbertDev commented Mar 19, 2024

Thanks for your re-review, @reneme! However, it seems that you somehow managed to review the files that were moved (i.e., the files in src/lib/pubkey/ed448/). Your last two suggestions are already addressed in my latest commit. I also cannot answer to these suggestions. Weird...

Regarding:

By any chance: Did you compile with the amalgamation?

No, I measured the default static build.

@reneme
Copy link
Collaborator

reneme commented Mar 19, 2024

However, it seems that you somehow managed to review the files that were moved

Mhm, I replied to discussions that were already open, while having started a pending review. Apparently GitHub treats those comments in a funny way; half reply, half free-standing but unanswerable new threads. >.<

@reneme
Copy link
Collaborator

reneme commented Mar 19, 2024

Mhh, on Windows the typecast_copy<StrongTypeOfStdArray> doesn't seem to work. :( I'm having a look.

Copy link
Collaborator

@reneme reneme left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One little nit, otherwise I'm happy. 👍

src/lib/pubkey/curve448/ed448/ed448_internal.cpp Outdated Show resolved Hide resolved
Copy link
Owner

@randombit randombit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks very good! Didn’t have time to finish review but here are some initial comments.

src/cli/speed.cpp Outdated Show resolved Hide resolved
src/lib/pubkey/curve448/curve448_scalar.h Outdated Show resolved Hide resolved
src/lib/pubkey/curve448/ed448/ed448_internal.cpp Outdated Show resolved Hide resolved
Ed448Point Ed448Point::scalar_mul(const Scalar448& s) const {
Ed448Point res(0, 1);

// Square and multiply (double and add) in constant time.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of room for optimization here if performance becomes an issue.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have any optimizations in mind, let me know 👍 Otherwise, I think the performance is currently acceptable.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just that double-and-always-add is (literally) the worst case scenario for a multiplication algorithm since for scalar length l it performs l doublings and l additions. Easy optimization would be a fixed window with a constant time table lookup, eg a 4 bit window you instead do l doublings and l/4 additions.

Fine to use this if you consider current performance ok.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds promising! I will leave it with the simple variant for now since the PR is already complex enough. Maybe we can speed it up in a follow-up PR.

src/lib/pubkey/curve448/info.txt Outdated Show resolved Hide resolved
src/lib/pubkey/curve448/x448/curve448.cpp Outdated Show resolved Hide resolved
@FAlbertDev
Copy link
Collaborator Author

Thanks for your comments, @randombit. Any suggestions are welcome :)
I renamed Curve448 to X448 and applied your other suggestions.

@randombit
Copy link
Owner

@FAlbertDev thanks. This needs a rebase post #3869 and possibly some history cleanup. I'll do a final review after.

@FAlbertDev
Copy link
Collaborator Author

This needs a rebase post #3869 and possibly some history cleanup.

Done :)

@reneme
Copy link
Collaborator

reneme commented Mar 22, 2024

I'm guessing this caused botan3.py to conflict with master. Could you rebase once more, please?

Copy link
Owner

@randombit randombit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks very good, thanks

@FAlbertDev
Copy link
Collaborator Author

Could you rebase once more, please?

Done. Should be ready to merge.

@FAlbertDev FAlbertDev merged commit 850b267 into randombit:master Mar 25, 2024
43 checks passed
@FAlbertDev FAlbertDev deleted the ec/448 branch March 25, 2024 08:31
bjosv added a commit to Nordix/SoftHSMv2 that referenced this pull request Nov 29, 2024
The test can be re-enabled when we support Botan 3 due to:
randombit/botan#3933

Signed-off-by: Björn Svensson <[email protected]>
bjosv added a commit to Nordix/SoftHSMv2 that referenced this pull request Nov 29, 2024
The test can be re-enabled when we support Botan 3 due to:
randombit/botan#3933

Signed-off-by: Björn Svensson <[email protected]>
bjosv added a commit to Nordix/SoftHSMv2 that referenced this pull request Nov 29, 2024
The test can be re-enabled when we support Botan 3 due to:
randombit/botan#3933

Signed-off-by: Björn Svensson <[email protected]>
bjosv added a commit to Nordix/SoftHSMv2 that referenced this pull request Nov 29, 2024
The test can be re-enabled when we support Botan 3 due to:
randombit/botan#3933

Signed-off-by: Björn Svensson <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Enhancement or new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support for Ed448 and X448
4 participants