Skip to content

Conversation

reneme
Copy link
Collaborator

@reneme reneme commented Sep 27, 2023

This adds C++20 range-based overloads to the relevant functions in mem_ops.h. This header is public, so I kept the legacy ptr-based overloads (also because they are still used throughout the code base). But perhaps those should be deprecated and removed at one point, @randombit?

For convenience, this adds a few helpers to concepts.h. Details can be found in their doxygen comments.

@coveralls
Copy link

coveralls commented Sep 27, 2023

Coverage Status

coverage: 92.075% (-0.007%) from 92.082%
when pulling dd160f3 on Rohde-Schwarz:span/memops
into dc4ac41 on randombit:master.

@randombit
Copy link
Owner

Let's hold on this for a little bit: NDK 26 and XCode 15 are both out now, I have been meaning to do a CI upgrade

@reneme reneme added this to the Botan 3.3.0 milestone Sep 27, 2023
Comment on lines 70 to 77
uint8_t ct_compare_u8(std::span<const uint8_t> x, std::span<const uint8_t> y) {
const auto min_size = CT::Mask<size_t>::is_lte(x.size(), y.size()).select(x.size(), y.size());

for(size_t i = 0; i != len; ++i) {
volatile uint8_t difference = 0;
for(size_t i = 0; i != min_size; ++i) {
difference = difference | (x[i] ^ y[i]);
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@randombit please triple-check for constant time correctness. :)

requires std::is_same_v<std::ranges::range_value_t<OutR>, std::ranges::range_value_t<InR>> &&
std::is_trivially_copyable_v<std::ranges::range_value_t<InR>>
{
ranges::assert_equal_byte_lengths(out, in);
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 both in and out have static extents (e.g. static-extent std::span<> and/or std::array<>), assert_equal_byte_lengths will only verify the extents statically. There won't be another check during runtime.

@reneme reneme requested a review from devnexen September 28, 2023 11:17
@devnexen
Copy link
Collaborator

devnexen commented Sep 28, 2023

So I gave a shot with the NDK 26 locally and got this

In file included from build/include/botan/buf_comp.h:11:
build/include/botan/concepts.h:148:94: error: use of undeclared identifier 'uint8_t'
   contiguous_container<T> && resizable_container<T> && std::same_as<typename T::value_type, uint8_t>;
                                                                                             ^
In file included from src/lib/base/buf_comp.cpp:7:
build/include/botan/buf_comp.h:76:27: error: no type named 'resizable_byte_buffer' in namespace 'Botan::concepts'
      template <concepts::resizable_byte_buffer T = secure_vector<uint8_t>>
                ~~~~~~~~~~^
build/include/botan/buf_comp.h:76:75: error: expected '(' for function-style cast or type construction
      template <concepts::resizable_byte_buffer T = secure_vector<uint8_t>>

once fixed locally, everything else passes.

@reneme reneme force-pushed the span/memops branch 3 times, most recently from 7bfafa2 to 7c8601c Compare September 29, 2023 07:16
@reneme
Copy link
Collaborator Author

reneme commented Sep 29, 2023

once fixed locally, everything else passes.

I'm guessing this was just lacking an include to <cstdint> somewhere, right?

@randombit
Copy link
Owner

randombit commented Oct 11, 2023

One thing we should consider here is if we want to have these additions in the public mem_ops.h at all.

This header is kind of a grab bag of various things, some of which are required to be public for API or ABI reasons (especially allocate_memory which is used by the secure_vector) but much of this is not really intended to be for use by applications.

It might be better to add a new internal header which contains the span versions, convert the library internals to use this, and then deprecate most of the functions in mem_ops.h which were never intended for end users.

@reneme
Copy link
Collaborator Author

reneme commented Oct 11, 2023

One thing we should consider here is if we want to have these additions in the public mem_ops.h at all.

I think it makes a lot of sense to split this header for a cleaner structure. The challenge with making it private, though, is that it'll prevent us from using these helpers in templates of other (public) headers.

@randombit
Copy link
Owner

This is true. I'll take the first step here - split the parts that are required to implement secmem.h into a new header allocator.h, and modify secmem.h to no longer include mem_ops.h [*] This will force all headers or source files which use functionality from this header to include the header explicitly. At that point it will be easier to evaluate how to split everything else out.

[*] And to maintain compatability mem_ops.h just additionally includes allocator.h

randombit added a commit that referenced this pull request Oct 11, 2023
randombit added a commit that referenced this pull request Oct 11, 2023
randombit added a commit that referenced this pull request Oct 11, 2023
@lieser
Copy link
Collaborator

lieser commented Oct 12, 2023

The challenge with making it private, though, is that it'll prevent us from using these helpers in templates of other (public) headers.

A solution could be to keep what is needed in the public header, but in a e.g. a detailed namespace. That should make it clear to users that it is not part of the public API.

@reneme
Copy link
Collaborator Author

reneme commented Oct 18, 2023

Rebased to master after #3760 conflicted with the changes in here.

randombit added a commit that referenced this pull request Oct 18, 2023
randombit added a commit that referenced this pull request Oct 18, 2023
randombit added a commit that referenced this pull request Oct 18, 2023
randombit added a commit that referenced this pull request Oct 19, 2023
randombit added a commit that referenced this pull request Oct 26, 2023
randombit added a commit that referenced this pull request Oct 29, 2023
@randombit
Copy link
Owner

@reneme This needs a rebase with #3752 merged but I'll review after

@reneme

This comment was marked as outdated.

Comment on lines 176 to 178
/**
* Copy a rande of a trivially copyable type into another range of trivially
* copyable type of matching byte length.
*/
template <ranges::contiguous_output_range ToR, ranges::contiguous_range FromR>
inline constexpr void typecast_copy(ToR&& out, FromR&& in)
requires std::is_trivially_copyable_v<std::ranges::range_value_t<FromR>> &&
std::is_trivially_copyable_v<std::ranges::range_value_t<ToR>>
Copy link
Collaborator Author

@reneme reneme Nov 1, 2023

Choose a reason for hiding this comment

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

This is very closely related to C++20's std::bit_cast, right? I didn't find a way to use std::bit_cast on arbitrary ranges, though. Probably that's by design, as it is supposed to work closely with the type system.

But given that Botan's typecast_copy and STL's bit_cast are so closely related: Perhaps we should adopt the naming here and deprecate typecast_copy in the public API. For instance, we could call these functions bitcast_copy or bitcast_range.

Comment on lines +70 to 71
BOTAN_DEPRECATED("This function is deprecated, use constant_time_compare()")
BOTAN_PUBLIC_API(2, 9) uint8_t ct_compare_u8(const uint8_t x[], const uint8_t y[], size_t len);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We don't use it anywhere in the code base anymore. Okay to deprecate?

Copy link
Owner

Choose a reason for hiding this comment

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

Yeah fine to deprecate

Copy link
Collaborator

@FAlbertDev FAlbertDev left a comment

Choose a reason for hiding this comment

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

All in all, LGTM. Some improvements of this PR and #3707 will be very helpful for implementing Classic McEliece :)

Comment on lines 69 to 75
/**
* Check whether a given range type has a length known at compile time.
*/
template <spanable_range R>
bool consteval has_static_extent() {
return decltype(std::span{std::declval<R>()})::extent != std::dynamic_extent;
}
Copy link
Collaborator

@FAlbertDev FAlbertDev Nov 16, 2023

Choose a reason for hiding this comment

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

When I try this in godbolt I cannot call this on non-span types like std::array or std::vector. When implementing this logic as a concept it works for me using the following example code:

template <typename S>
concept static_extent_span = S::extent != std::dynamic_extent;

template <typename R>
concept static_extent_range = requires(R r) {
    {std::span(r)} -> static_extent_span;
};

template <spanable_range R>
consteval bool has_static_extent_alt() {
    return static_extent_range<R>;
}

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 really like your approach, apart from being fairly verbose. In the hope that we won't need this in many more places: I had another pass at this, and ended up simply spelling it out, like:

const std::span s{r};
if constexpr(decltype(s)::extent != std::dynamic_extent) { /*...*/ }
// ...

No idea why has_static_extent<R>() seemed to work for me, before.

template <size_t expected, spanable_range R>
inline constexpr void assert_exact_byte_length(R&& r) {
const std::span s{r};
if constexpr(has_static_extent<R>()) {
static_assert(s.size_bytes() == expected, "memory region does not have expected byte lengths");
} else {
BOTAN_ARG_CHECK(s.size_bytes() == expected, "memory region does not have expected byte lengths");
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's interesting, because a call of this method works, but only with type deduction. For example:

    std::array<uint8_t,32> a;
    // Does not work 
    assert_exact_byte_length<32, std::array<uint8_t,32>>(a);
    // Works
    assert_exact_byte_length<32>(a);

It seems the input type is implicitly converted to a span.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That's somewhat expected, because of the forwarding reference (R&&). When passing a with type deduction, you'll end up with R = std::array<>& (an lvalue reference). I.e. If you were to add the & to your explicit declaration, it should work. Here's some reading: https://medium.com/factset/modern-c-in-depth-perfect-forwarding-570f242261f8

* @param y another range of bytes
* @return true iff x and y have equal lengths and x[i] == y[i] forall i in [0...n)
*/
BOTAN_PUBLIC_API(3, 3) bool constant_time_compare(std::span<const uint8_t> x, std::span<const uint8_t> y);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do you think we want to move this to ct_utils.h?

Copy link
Collaborator Author

@reneme reneme Nov 16, 2023

Choose a reason for hiding this comment

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

Probably. This PR has more than enough complexity, though. Let's keep the interface stable, at least. I suggest we do that as a follow up.

Copy link
Owner

@randombit randombit Nov 20, 2023

Choose a reason for hiding this comment

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

ct_utils is our internal header while constant_time_compare is intended for library users since this comes up for application also.

All internal uses should use CT::Mask instead - I have a WIP branch on this somewhere.

Actually this was done already in #3760

Comment on lines +428 to 429
// TODO: deprecate and replace, use .subspan()
inline void xor_buf(std::span<uint8_t> out, std::span<const uint8_t> in, size_t n) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree this function interface is somehow weird...

Copy link
Collaborator

@FAlbertDev FAlbertDev left a comment

Choose a reason for hiding this comment

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

👍

@reneme
Copy link
Collaborator Author

reneme commented Nov 20, 2023

@randombit Could we go ahead with this? I'd love to have this and #3707 merged before doing another pass over the Kyber/Dilithium implementations.

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.

Yeah fine to merge, sorry I've been sick since early Nov so way behind on reviews right now.

Comment on lines +70 to 71
BOTAN_DEPRECATED("This function is deprecated, use constant_time_compare()")
BOTAN_PUBLIC_API(2, 9) uint8_t ct_compare_u8(const uint8_t x[], const uint8_t y[], size_t len);
Copy link
Owner

Choose a reason for hiding this comment

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

Yeah fine to deprecate

* @param y another range of bytes
* @return true iff x and y have equal lengths and x[i] == y[i] forall i in [0...n)
*/
BOTAN_PUBLIC_API(3, 3) bool constant_time_compare(std::span<const uint8_t> x, std::span<const uint8_t> y);
Copy link
Owner

@randombit randombit Nov 20, 2023

Choose a reason for hiding this comment

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

ct_utils is our internal header while constant_time_compare is intended for library users since this comes up for application also.

All internal uses should use CT::Mask instead - I have a WIP branch on this somewhere.

Actually this was done already in #3760

@reneme reneme merged commit 3005ae6 into randombit:master Nov 21, 2023
@reneme reneme deleted the span/memops branch November 21, 2023 08:08
@reneme
Copy link
Collaborator Author

reneme commented Nov 21, 2023

sorry I've been sick since early Nov so way behind on reviews right now

No worries at all. Hope you're feeling better now. 😄
Thanks for the review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants