-
Notifications
You must be signed in to change notification settings - Fork 551
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
Bridge pcurves into the main elliptic curve arithmetic #4143
base: master
Are you sure you want to change the base?
Conversation
These have a much more restrictive interface as compared to our existing EC_Point and BigInt Co-authored-by: René Meusel <[email protected]>
Co-authored-by: René Meusel <[email protected]>
Co-authored-by: René Meusel <[email protected]>
Co-authored-by: René Meusel <[email protected]>
This will allow switching in pcurves later
Without a full picture, yet: I think it would make sense to allow disabling the BN-based ECC implementation at compile time. |
In principle yes I agree. However it's greatly complicated by the fact that we have large swaths of public API which expose various BigInt based ec functionality ( |
To be able to get an idea of the big picture, I extended the diagram I started at some point. Feedback appreciated. :) Note that excalidraw isn't a shared collaboration space. To publish any modifications, one has to re-create a new snapshot via "save as... > shareable link" |
std::vector<uint8_t> bytes(m_order_bytes); | ||
bn.serialize_to(bytes); | ||
return this->scalar_deserialize(bytes); |
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.
std::vector<uint8_t> bytes(m_order_bytes); | |
bn.serialize_to(bytes); | |
return this->scalar_deserialize(bytes); | |
return this->scalar_deserialize(bn.serialize(m_order_bytes)); |
const EC_Scalar_Data& y) const = 0; | ||
}; | ||
|
||
class EC_Group_Data final : public std::enable_shared_from_this<EC_Group_Data> { |
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.
Given that virtually all method implementations have the form: if(m_pcurves) { /* pcurves */ } else { /* BigInt */ }
I'm somewhat inclined to suggest (yet another) virtual
interface here (or perhaps instead of the EC_Scalar_Data
)? But I feel you've had that idea and needed to pick the lesser evil.
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 would probably be cleaner. Main issue is that even with pcurves enabled we still have to support the full set of BigInt based APIs so we're still carrying around all of the same fields. I guess we could have the pcurves EC_Group_Data
derive from the generic one to avoid too much duplication ...
void EC_AffinePoint_Data_PC::serialize_compressed_to(std::span<uint8_t> bytes) const { | ||
const size_t fe_bytes = this->field_element_bytes(); | ||
BOTAN_ARG_CHECK(bytes.size() == 1 + fe_bytes, "Invalid output size"); | ||
const bool y_is_odd = (m_bytes[m_bytes.size() - 1] & 0x01) == 0x01; |
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.
const bool y_is_odd = (m_bytes[m_bytes.size() - 1] & 0x01) == 0x01; | |
const bool y_is_odd = (m_bytes.back() & 0x01) == 0x01; |
EC_AffinePoint_Data_PC::EC_AffinePoint_Data_PC(std::shared_ptr<const EC_Group_Data> group, | ||
PCurve::PrimeOrderCurve::AffinePoint pt) : | ||
m_group(std::move(group)), m_pt(std::move(pt)) { | ||
m_bytes = m_pt.serialize<secure_vector<uint8_t>>(); | ||
} | ||
|
||
EC_AffinePoint_Data_PC::EC_AffinePoint_Data_PC(std::shared_ptr<const EC_Group_Data> group, | ||
std::span<const uint8_t> bytes) : | ||
m_group(std::move(group)), m_pt(deserialize_pcurve_pt(m_group->pcurve(), bytes)) { | ||
m_bytes = m_pt.serialize<secure_vector<uint8_t>>(); | ||
} |
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.
Two things:
m_bytes
could be initialized in the initializer-list as well
EC_AffinePoint_Data_PC::EC_AffinePoint_Data_PC(std::shared_ptr<const EC_Group_Data> group, | |
PCurve::PrimeOrderCurve::AffinePoint pt) : | |
m_group(std::move(group)), m_pt(std::move(pt)) { | |
m_bytes = m_pt.serialize<secure_vector<uint8_t>>(); | |
} | |
EC_AffinePoint_Data_PC::EC_AffinePoint_Data_PC(std::shared_ptr<const EC_Group_Data> group, | |
std::span<const uint8_t> bytes) : | |
m_group(std::move(group)), m_pt(deserialize_pcurve_pt(m_group->pcurve(), bytes)) { | |
m_bytes = m_pt.serialize<secure_vector<uint8_t>>(); | |
} | |
EC_AffinePoint_Data_PC::EC_AffinePoint_Data_PC(std::shared_ptr<const EC_Group_Data> group, | |
PCurve::PrimeOrderCurve::AffinePoint pt) : | |
m_group(std::move(group)), m_pt(std::move(pt)), m_bytes(m_pt.serialize<secure_vector<uint8_t>>()) { | |
} | |
EC_AffinePoint_Data_PC::EC_AffinePoint_Data_PC(std::shared_ptr<const EC_Group_Data> group, | |
std::span<const uint8_t> bytes) : | |
m_group(std::move(group)), m_pt(deserialize_pcurve_pt(m_group->pcurve(), bytes)), m_bytes(m_pt.serialize<secure_vector<uint8_t>>()) { | |
} |
- Given that
m_bytes
is never written to, perhaps its worthwhile to addstd::span<>
s that centrally refer to the segments of the serialization. That would avoid spreading the knowledge about the structure inm_bytes
across several methods. Like so:
EC_AffinePoint_Data_PC::EC_AffinePoint_Data_PC(std::shared_ptr<const EC_Group_Data> group,
PCurve::PrimeOrderCurve::AffinePoint pt) :
m_group(std::move(group)), m_pt(std::move(pt)), m_bytes(m_pt.serialize<secure_vector<uint8_t>>()),
m_bytes_x(std::span{m_bytes}.subspan(1, field_element_bytes())),
m_bytes_y(std::span{m_bytes}.last(field_element_bytes())) {
}
if(!p) { | ||
abort(); | ||
throw Invalid_State("Failed conversion to EC_AffinePoint_Data_PC"); | ||
} |
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.
abort()
seems a bit harsh.
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.
Oops, was for debugging 🙈
auto x = BigInt::from_bytes(std::span{m_bytes}.subspan(1, fe_bytes)); | ||
auto y = BigInt::from_bytes(std::span{m_bytes}.last(fe_bytes)); | ||
|
||
return EC_Point(m_group->curve(), std::move(x), std::move(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.
clang-tidy says: move won't happen, because this constructor takes the BigInt
objects by const&
. I think, it shouldn't do that, though. Perhaps change the constructor?
@@ -11,12 +11,14 @@ brief -> "Wrapper for elliptic curve groups" | |||
<requires> | |||
asn1 | |||
numbertheory | |||
pcurves |
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.
Is that really required? If pcurves
is not compiled, I can still fall back to the bigint implementation also for the pcurves-supported curves, no?
Or do we want to model "no pcurves at compile time" by disabling all pcurves_*
submodules?
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.
Not really required in that if pcurves wasn't available at all we could just fall back to the generic code without issue. I'm just trying to avoid too much combinatoric explosion (more than is already in place anyway ...) with yet another module / yet more ifdef. IDK. I guess there are probably users who would prefer just using the generic code for size reasons.
return nullptr; | ||
} | ||
} else { | ||
BigInt r(bytes.data(), bytes.size()); |
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.
BigInt r(bytes.data(), bytes.size()); | |
BigInt r(bytes); |
if(auto s = m_pcurve->scalar_from_wide_bytes(bytes)) { | ||
return std::make_unique<EC_Scalar_Data_PC>(shared_from_this(), std::move(*s)); | ||
} else { | ||
throw Invalid_Argument("Failed to complete reduction of scalar bytes"); |
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 inconsistent. Many other methods that return a unique_ptr simply return nullptr
if something fails but don't throw. Frankly, I'd prefer things to throw over passing a nullptr
that I have to remember to check for.
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.
The behavior between pcurves and the generic code are somewhat inconsistent on this - generic always succeeds no matter how big the input is. Maybe the answer here is to also introduce a (somewhat artificial) limit for the generic case so they behave the same.
This results in roughly a 2x speedup for all elliptic curve algorithms, when using a supported curve.