-
Notifications
You must be signed in to change notification settings - Fork 606
Range-based mem_ops #3715
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
Range-based mem_ops #3715
Conversation
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 |
src/lib/utils/mem_ops.cpp
Outdated
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]); | ||
} | ||
|
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
So I gave a shot with the NDK 26 locally and got this
once fixed locally, everything else passes. |
7bfafa2
to
7c8601c
Compare
I'm guessing this was just lacking an include to |
126262c
to
aa7b79b
Compare
One thing we should consider here is if we want to have these additions in the public 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 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 |
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. |
This is true. I'll take the first step here - split the parts that are required to implement [*] And to maintain compatability |
A solution could be to keep what is needed in the public header, but in a e.g. a |
Rebased to master after #3760 conflicted with the changes in here. |
This comment was marked as outdated.
This comment was marked as outdated.
src/lib/utils/mem_ops.h
Outdated
/** | ||
* 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>> |
There was a problem hiding this comment.
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
.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah fine to deprecate
There was a problem hiding this 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 :)
src/lib/utils/concepts.h
Outdated
/** | ||
* 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; | ||
} |
There was a problem hiding this comment.
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>;
}
There was a problem hiding this comment.
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.
src/lib/utils/concepts.h
Outdated
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"); | ||
} | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
// TODO: deprecate and replace, use .subspan() | ||
inline void xor_buf(std::span<uint8_t> out, std::span<const uint8_t> in, size_t n) { |
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
@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. |
There was a problem hiding this 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.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
No worries at all. Hope you're feeling better now. 😄 |
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.