Skip to content

Commit

Permalink
Clarify and improve constant timeness of bitvector + use ct variants …
Browse files Browse the repository at this point in the history
…in CMCE
  • Loading branch information
atreiber94 committed Feb 29, 2024
1 parent 68a91e3 commit 54395c2
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 12 deletions.
2 changes: 1 addition & 1 deletion src/lib/pubkey/classic_mceliece/cmce_keys_internal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ std::shared_ptr<Classic_McEliece_PublicKeyInternal> Classic_McEliece_PublicKeyIn
throw Decoding_Error("Cannot create public key from private key. Private key is invalid.");
}
auto& [pk_matrix, pivot] = pk_matrix_and_pivot.value();
if(!pivot.subvector(0, pivot.size() / 2).all() || !pivot.subvector(pivot.size() / 2).none()) {
if(!pivot.subvector(0, pivot.size() / 2).ct_all() || !pivot.subvector(pivot.size() / 2).ct_none()) {
// There should not be a pivot other than 0xff ff ff ff 00 00 00 00. Otherwise
// the gauss algorithm failed effectively.
throw Decoding_Error("Cannot create public key from private key. Private key is invalid.");
Expand Down
4 changes: 2 additions & 2 deletions src/lib/pubkey/classic_mceliece/cmce_matrix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ std::optional<CmceColumnSelection> move_columns(CmceMatrix& mat, const Classic_M
row_acc |= sub_mat.at(next_row);
}

if(row_acc.none()) {
if(row_acc.ct_none()) {
// If the current row and all subsequent rows are zero
// we cannot create a semi-systematic matrix
return std::nullopt;
Expand Down Expand Up @@ -250,7 +250,7 @@ CmceCodeWord Classic_McEliece_Matrix::mul(const Classic_McEliece_Parameters& par
auto pk_current_bytes = pk_slicer.take(params.pk_row_size_bytes());
auto row = secure_bitvector(pk_current_bytes, params.n() - params.pk_no_rows());
row &= e_T;
s[i] ^= row.has_odd_hamming_weight();
s[i] ^= row.ct_has_odd_hamming_weight();
}

BOTAN_ASSERT_NOMSG(pk_slicer.empty());
Expand Down
35 changes: 29 additions & 6 deletions src/lib/utils/bitvector.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/*
* An abstraction for an arbitrarily large bitvector that can
* optionally use the secure_allocator.
* optionally use the secure_allocator. All bitwise accesses and all
* constructors are implemented in constant time. Otherwise, only methods
* with the "ct_" pre-fix run in constant time.
*
* (C) 2023-2024 Jack Lloyd
* (C) 2023-2024 René Meusel, Rohde & Schwarz Cybersecurity
Expand Down Expand Up @@ -404,7 +406,7 @@ class bitvector_base final {
/**
* @returns true iff the number of 1-bits in this is odd, false otherwise
*/
bool has_odd_hamming_weight() const {
bool ct_has_odd_hamming_weight() const {
uint64_t acc = 0;
full_range_operation([&](std::unsigned_integral auto block) { acc ^= block; }, *this);

Expand All @@ -418,7 +420,7 @@ class bitvector_base final {
}

/**
* Counts the number of 1-bits in the bitvector.
* Counts the number of 1-bits in the bitvector in constant time.
* @returns the "population count" (or hamming weight) of the bitvector
*/
size_type ct_hamming_weight() const {
Expand Down Expand Up @@ -446,6 +448,14 @@ class bitvector_base final {
detail::unwrap(other));
}

/**
* @returns true if @p other contains the same bit pattern as this
*/
template <bitvectorish OtherT>
bool ct_equals(const OtherT& other) const noexcept {
return (*this ^ other).ct_none();
}

/// @name Serialization
/// @{

Expand Down Expand Up @@ -640,27 +650,40 @@ class bitvector_base final {
}

/**
* TODO: Maybe not CT, because working with booleans.
* @returns true iff no bit is set
*/
bool none() const {
return full_range_operation([](std::unsigned_integral auto block) { return block == 0; }, *this);
}

/**
* @returns true iff no bit is set in constant time
*/
bool ct_none() const { return ct_hamming_weight() == 0; }

/**
* @returns true iff at least one bit is set
*/
bool any() const { return !none(); }

/**
* TODO: Maybe not CT, because working with booleans.
* @returns true iff at least one bit is set in constant time
*/
bool ct_any() const { return !ct_none(); }

/**
* @returns true iff all bits are set
*/
bool all() const {
return full_range_operation(
[]<std::unsigned_integral BlockT>(BlockT block, BlockT mask) { return block == mask; }, *this);
}

/**
* @returns true iff all bits are set in constant time
*/
bool ct_all() const { return ct_hamming_weight() == m_bits; }

auto operator[](size_type pos) { return ref(pos); }

// TODO C++23: deducing this
Expand Down Expand Up @@ -1275,7 +1298,7 @@ class Strong_Adapter<T> : public Container_Strong_Adapter_Base<T> {

auto none() const { return this->get().none(); }

auto has_odd_hamming_weight() const { return this->get().has_odd_hamming_weight(); }
auto ct_has_odd_hamming_weight() const { return this->get().ct_has_odd_hamming_weight(); }

auto ct_hamming_weight() const { return this->get().ct_hamming_weight(); }

Expand Down
39 changes: 36 additions & 3 deletions src/tests/test_utils_bitvector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -480,35 +480,51 @@ std::vector<Test::Result> test_bitvector_global_modifiers_and_predicates() {
result.confirm("default construction yields all-zero", bv.none());
result.confirm("default construction yields all-zero 2", !bv.any());
result.confirm("default construction yields all-zero 3", !bv.all());
result.confirm("default construction yields all-zero 4", bv.ct_none());
result.confirm("default construction yields all-zero 5", !bv.ct_any());
result.confirm("default construction yields all-zero 6", !bv.ct_all());

bv.set(42);
result.confirm("setting a bit means there's a bit set", !bv.none());
result.confirm("setting a bit means there's a bit set 2", bv.any());
result.confirm("setting a bit means there's not all bits set", !bv.all());
result.confirm("setting a bit means there's a bit set 3", !bv.ct_none());
result.confirm("setting a bit means there's a bit set 4", bv.ct_any());
result.confirm("setting a bit means there's not all bits set 2", !bv.ct_all());

bv.set();
result.confirm("setting all bits means there's a bit set", !bv.none());
result.confirm("setting all bits means there's a bit set 2", bv.any());
result.confirm("setting all bits means all bits are set", bv.all());
result.confirm("setting all bits means there's a bit set 3", !bv.ct_none());
result.confirm("setting all bits means there's a bit set 4", bv.ct_any());
result.confirm("setting all bits means all bits are set 2", bv.ct_all());

bv.unset(97);
result.confirm("a single 0 at the end means that there's a bit set", !bv.none());
result.confirm("a single 0 at the end means that there are bits set", bv.any());
result.confirm("a single 0 at the end means that there are not all bits set", !bv.all());
result.confirm("a single 0 at the end means that there's a bit set 2", !bv.ct_none());
result.confirm("a single 0 at the end means that there are bits set 2", bv.ct_any());
result.confirm("a single 0 at the end means that there are not all bits set 2",
!bv.ct_all());

bv.unset();
result.confirm("unsetting all bits means there's no bit set", bv.none());
result.confirm("unsetting all bits means there's no bit set 2", !bv.any());
result.confirm("unsetting all bits means there's not all bits set", !bv.all());
result.confirm("unsetting all bits means there's no bit set 3", bv.ct_none());
result.confirm("unsetting all bits means there's no bit set 4", !bv.ct_any());
result.confirm("unsetting all bits means there's not all bits set 2", !bv.ct_all());
}),

Botan_Tests::CHECK("hamming weight oddness",
[](auto& result) {
const auto evn = Botan::hex_decode("FE3410CB0278E4D26602");
const auto odd = Botan::hex_decode("BB2418C2B4F288921203");

result.confirm("odd hamming", Botan::bitvector(odd).has_odd_hamming_weight());
result.confirm("even hamming", !Botan::bitvector(evn).has_odd_hamming_weight());
result.confirm("odd hamming", Botan::bitvector(odd).ct_has_odd_hamming_weight());
result.confirm("even hamming", !Botan::bitvector(evn).ct_has_odd_hamming_weight());
}),

Botan_Tests::CHECK("hamming weight",
Expand Down Expand Up @@ -565,6 +581,23 @@ std::vector<Test::Result> test_bitvector_binary_operators() {
};

return {
Botan_Tests::CHECK("bitwise_equals",
[&](auto& result) {
Botan::bitvector lhs(20);
lhs.set(0).set(4).set(15).set(16).set(19);
Botan::bitvector rhs(20);
rhs.set(1).set(4).set(16).set(17).set(18);

result.test_eq("Not equal bitvectors", lhs.equals(rhs), false);
result.test_eq("Not equal bitvectors 2", lhs.ct_equals(rhs), false);

lhs.unset().set(13);
rhs.unset().set(13);

result.test_eq("equal bitvectors", lhs.equals(rhs), true);
result.test_eq("equal bitvectors 2", lhs.ct_equals(rhs), true);
}),

Botan_Tests::CHECK("bitwise OR",
[&](auto& result) {
Botan::bitvector lhs(20);
Expand Down Expand Up @@ -968,7 +1001,7 @@ Test::Result test_bitvector_strongtype_adapter() {
result.confirm("bv1 is not all zero", !bv1.none());
result.confirm("bv1 is not all one", !bv1.all());

result.test_eq("hamming weight of bv1", bv1.has_odd_hamming_weight(), true);
result.test_eq("hamming weight of bv1", bv1.ct_has_odd_hamming_weight(), true);

for(size_t i = 0; auto bit : bv1) {
const bool expected = (i == 0 || i == 1 || i == 2 || i == 4 || i == 10);
Expand Down

0 comments on commit 54395c2

Please sign in to comment.