Skip to content

Commit 7ee4705

Browse files
authored
Add support for Light MLS (#436)
* Move tree storage from vector to map. * Add tree slice logic * Add a flags extension * Add the ability for clients to join as light * Implement LightCommit more or less along the lines in the draft * Fix build errors after rebase * Add the ability for a light client to upgrade to being a full client * Add the ability to handle an AnnotatedCommit * Add the ability to generate annotated commits * Add an AnnotatedCommit test * Handle GroupContextExtensions and PSKs via proposals, not annotations * Optionally run passive client tests as a light client * Add DS utilities library * Use TreeFollower in light passive client tests * Update AnnotatedCommit to work with new commit structure * Enable light clients to process external joins * Properly ignore sender membership proofs in the external commit case * Revert in-Welcome sending of membership proofs * Add a test case that shows client fast-join behavior * Fix external commit test * Clean up AnnotatedWelcome API * Allow light clients to validate parent hashes * clang-tidy * clang-format * Revert changes to passive client test vector processing * Fix shadowing warnings on Windows * clang-format * Add missing mls_ds dependency * Fix windows build * Remove unneeded flags extension * Don't replicate proposals in the AnnotatedCommit * clang-format
1 parent f977de9 commit 7ee4705

24 files changed

+1390
-71
lines changed

.clang-tidy

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Checks: '*,
1919
-google-runtime-references,
2020
-hicpp-no-assembler,
2121
-hicpp-special-member-functions,-warnings-as-errors,
22+
-hicpp-signed-bitwise,
2223
-llvm-include-order,
2324
-llvmlibc-callee-namespace,
2425
-llvmlibc-implementation-in-namespace,

include/mls/common.h

+11-1
Original file line numberDiff line numberDiff line change
@@ -216,11 +216,21 @@ std::vector<Value>
216216
transform(const Container& c, const UnaryOperation& op)
217217
{
218218
auto out = std::vector<Value>{};
219-
auto ins = std::inserter(out, out.begin());
219+
auto ins = std::back_inserter(out);
220220
std::transform(c.begin(), c.end(), ins, op);
221221
return out;
222222
}
223223

224+
template<typename Value, typename Container, typename UnaryOperation>
225+
std::vector<Value>
226+
filter(const Container& c, const UnaryOperation& op)
227+
{
228+
auto out = std::vector<Value>{};
229+
auto ins = std::back_inserter(out);
230+
std::copy_if(c.begin(), c.end(), ins, op);
231+
return out;
232+
}
233+
224234
template<typename Container, typename UnaryPredicate>
225235
bool
226236
any_of(const Container& c, const UnaryPredicate& pred)

include/mls/messages.h

+41
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,47 @@ external_proposal(CipherSuite suite,
691691
uint32_t signer_index,
692692
const SignaturePrivateKey& sig_priv);
693693

694+
struct AnnotatedWelcome
695+
{
696+
Welcome welcome;
697+
698+
TreeSlice sender_membership_proof;
699+
TreeSlice receiver_membership_proof;
700+
701+
static AnnotatedWelcome from(Welcome welcome,
702+
const TreeKEMPublicKey& tree,
703+
LeafIndex sender,
704+
LeafIndex joiner);
705+
706+
TreeKEMPublicKey tree() const;
707+
708+
TLS_SERIALIZABLE(welcome, sender_membership_proof, receiver_membership_proof);
709+
};
710+
711+
struct AnnotatedCommit
712+
{
713+
MLSMessage commit_message;
714+
std::optional<TreeSlice> sender_membership_proof_before;
715+
std::optional<uint32_t> resolution_index;
716+
717+
bytes tree_hash_after;
718+
TreeSlice sender_membership_proof_after;
719+
TreeSlice receiver_membership_proof_after;
720+
721+
static AnnotatedCommit from(LeafIndex receiver,
722+
const std::vector<MLSMessage>& proposals,
723+
const MLSMessage& commit_message,
724+
const TreeKEMPublicKey& tree_before,
725+
const TreeKEMPublicKey& tree_after);
726+
727+
TLS_SERIALIZABLE(commit_message,
728+
sender_membership_proof_before,
729+
resolution_index,
730+
tree_hash_after,
731+
sender_membership_proof_after,
732+
receiver_membership_proof_after);
733+
};
734+
694735
} // namespace MLS_NAMESPACE
695736

696737
namespace MLS_NAMESPACE::tls {

include/mls/state.h

+18-2
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,16 @@ struct RosterIndex : public UInt32
1919

2020
struct CommitOpts
2121
{
22+
// Include these proposals in the commit by value
2223
std::vector<Proposal> extra_proposals;
23-
bool inline_tree;
24-
bool force_path;
24+
25+
// Send a ratchet_tree extension in the Welcome
26+
bool inline_tree = false;
27+
28+
// Send an UpdatePath even if none is required
29+
bool force_path = false;
30+
31+
// Update the committer's LeafNode in the following way
2532
LeafNodeOptions leaf_node_opts;
2633
};
2734

@@ -127,6 +134,14 @@ class State
127134
std::optional<State> handle(const ValidatedContent& content_auth,
128135
std::optional<State> cached_state);
129136

137+
///
138+
/// Light MLS
139+
///
140+
void implant_tree_slice(const TreeSlice& slice);
141+
State handle(const AnnotatedCommit& annotated_commit);
142+
bool is_full_client() const { return _tree.is_complete(); }
143+
void upgrade_to_full_client(TreeKEMPublicKey tree);
144+
130145
///
131146
/// PSK management
132147
///
@@ -327,6 +342,7 @@ class State
327342
CommitMaterials prepare_commit(const bytes& leaf_secret,
328343
const std::optional<CommitOpts>& opts,
329344
const CommitParams& params) const;
345+
GroupInfo group_info(bool external_pub, bool inline_tree) const;
330346
Welcome welcome(bool inline_tree,
331347
const std::vector<PSKWithSecret>& psks,
332348
const std::vector<KeyPackage>& joiners,

include/mls/tree_math.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,8 @@ struct NodeIndex : public UInt32
9999
// of `ancestor` that is not in the direct path of this node.
100100
NodeIndex sibling(NodeIndex ancestor) const;
101101

102-
std::vector<NodeIndex> dirpath(LeafCount n);
103-
std::vector<NodeIndex> copath(LeafCount n);
102+
std::vector<NodeIndex> dirpath(LeafCount n) const;
103+
std::vector<NodeIndex> copath(LeafCount n) const;
104104

105105
uint32_t level() const;
106106
};

include/mls/treekem.h

+46-3
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,18 @@ struct OptionalNode
5959
TLS_SERIALIZABLE(node)
6060
};
6161

62+
struct TreeSlice
63+
{
64+
LeafIndex leaf_index;
65+
LeafCount n_leaves;
66+
std::vector<OptionalNode> direct_path_nodes;
67+
std::vector<bytes> copath_hashes;
68+
69+
bytes tree_hash(CipherSuite suite) const;
70+
71+
TLS_SERIALIZABLE(leaf_index, n_leaves, direct_path_nodes, copath_hashes);
72+
};
73+
6274
struct TreeKEMPublicKey;
6375

6476
struct TreeKEMPrivateKey
@@ -113,15 +125,19 @@ struct TreeKEMPrivateKey
113125
void implant(const TreeKEMPublicKey& pub,
114126
NodeIndex start,
115127
const bytes& path_secret);
128+
void implant_matching(const TreeKEMPublicKey& pub,
129+
NodeIndex start,
130+
const bytes& path_secret);
116131
};
117132

118133
struct TreeKEMPublicKey
119134
{
120135
CipherSuite suite;
121136
LeafCount size{ 0 };
122-
std::vector<OptionalNode> nodes;
137+
std::map<NodeIndex, OptionalNode> nodes;
123138

124139
explicit TreeKEMPublicKey(CipherSuite suite);
140+
TreeKEMPublicKey(CipherSuite suite, const TreeSlice& slice);
125141

126142
TreeKEMPublicKey() = default;
127143
TreeKEMPublicKey(const TreeKEMPublicKey& other) = default;
@@ -148,14 +164,29 @@ struct TreeKEMPublicKey
148164
const bytes& get_hash(NodeIndex index);
149165
bytes root_hash() const;
150166

167+
bool parent_hash_valid(LeafIndex from) const;
151168
bool parent_hash_valid(LeafIndex from, const UpdatePath& path) const;
152169
bool parent_hash_valid() const;
170+
bool is_complete() const;
153171

154172
bool has_leaf(LeafIndex index) const;
155173
std::optional<LeafIndex> find(const LeafNode& leaf) const;
156174
std::optional<LeafNode> leaf_node(LeafIndex index) const;
157175
std::vector<NodeIndex> resolve(NodeIndex index) const;
158176

177+
TreeSlice extract_slice(LeafIndex leaf) const;
178+
void implant_slice(const TreeSlice& slice);
179+
std::tuple<HPKECiphertext, NodeIndex> slice_path(UpdatePath path,
180+
LeafIndex from,
181+
LeafIndex to) const;
182+
183+
struct AncestorIndex
184+
{
185+
size_t ancestor_node_index;
186+
NodeIndex resolution_node;
187+
};
188+
AncestorIndex ancestor_index(LeafIndex to, LeafIndex from) const;
189+
159190
struct DecapCoords
160191
{
161192
size_t ancestor_node_index;
@@ -171,6 +202,13 @@ struct TreeKEMPublicKey
171202
bool all_leaves(const UnaryPredicate& pred) const
172203
{
173204
for (LeafIndex i{ 0 }; i < size; i.val++) {
205+
// Only test known nodes
206+
// XXX(RLB) This could be dangerous, since it allows for nodes to fail the
207+
// predicate as long as they are unknown.
208+
if (nodes.count(NodeIndex(i)) == 0) {
209+
continue;
210+
}
211+
174212
const auto& node = node_at(i);
175213
if (node.blank()) {
176214
continue;
@@ -201,8 +239,8 @@ struct TreeKEMPublicKey
201239
return false;
202240
}
203241

204-
using FilteredDirectPath =
205-
std::vector<std::tuple<NodeIndex, std::vector<NodeIndex>>>;
242+
using FilteredDirectPathEntry = std::tuple<NodeIndex, std::vector<NodeIndex>>;
243+
using FilteredDirectPath = std::vector<FilteredDirectPathEntry>;
206244
FilteredDirectPath filtered_direct_path(NodeIndex index) const;
207245

208246
void truncate();
@@ -225,6 +263,9 @@ struct TreeKEMPublicKey
225263
void clear_hash_path(LeafIndex index);
226264

227265
bool has_parent_hash(NodeIndex child, const bytes& target_ph) const;
266+
bool parent_hash_valid(LeafIndex from,
267+
const UpdatePath& path,
268+
const FilteredDirectPath& fdp) const;
228269

229270
bytes parent_hash(const ParentNode& parent, NodeIndex copath_child) const;
230271
std::vector<bytes> parent_hashes(
@@ -245,6 +286,8 @@ struct TreeKEMPublicKey
245286
bool exists_in_tree(const SignaturePublicKey& key,
246287
std::optional<LeafIndex> except) const;
247288

289+
void implant_slice_unchecked(const TreeSlice& slice);
290+
248291
OptionalNode blank_node;
249292

250293
friend struct TreeKEMPrivateKey;

lib/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
add_subdirectory(bytes)
22
add_subdirectory(hpke)
33
add_subdirectory(tls_syntax)
4+
add_subdirectory(mls_ds)
45
add_subdirectory(mls_vectors)

lib/bytes/src/bytes.cpp

-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ bytes::operator==(const std::vector<uint8_t>& other) const
3030

3131
unsigned char diff = 0;
3232
for (size_t i = 0; i < size; ++i) {
33-
// Not sure why the linter thinks `diff` is signed
34-
// NOLINTNEXTLINE(hicpp-signed-bitwise)
3533
diff |= (_data.at(i) ^ other.at(i));
3634
}
3735
return (diff == 0);

lib/hpke/src/certificate.cpp

-1
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,6 @@ Certificate::parse_pem(const bytes& pem)
404404
auto x509 = make_typed_unique<X509>(
405405
PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr));
406406
if (!x509) {
407-
// NOLINTNEXTLINE(hicpp-signed-bitwise)
408407
auto err = ERR_GET_REASON(ERR_peek_last_error());
409408
if (err == PEM_R_NO_START_LINE) {
410409
// No more objects to read

lib/hpke/src/rsa.cpp

-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ RSASignature::generate_key_pair(size_t bits)
3131
throw openssl_error();
3232
}
3333

34-
// NOLINTNEXTLINE(hicpp-signed-bitwise)
3534
if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx.get(), static_cast<int>(bits)) <=
3635
0) {
3736
throw openssl_error();

lib/mls_ds/CMakeLists.txt

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
set(CURRENT_LIB_NAME mls_ds)
2+
3+
###
4+
### Library Config
5+
###
6+
7+
file(GLOB_RECURSE LIB_HEADERS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.h")
8+
file(GLOB_RECURSE LIB_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
9+
10+
add_library(${CURRENT_LIB_NAME} ${LIB_HEADERS} ${LIB_SOURCES})
11+
add_dependencies(${CURRENT_LIB_NAME} mlspp)
12+
target_link_libraries(${CURRENT_LIB_NAME} mlspp bytes tls_syntax)
13+
target_include_directories(${CURRENT_LIB_NAME}
14+
PUBLIC
15+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
16+
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
17+
$<INSTALL_INTERFACE:include/${PROJECT_NAME}>
18+
)
19+
20+
###
21+
### Install
22+
###
23+
24+
install(TARGETS ${CURRENT_LIB_NAME} EXPORT mlspp-targets)
25+
install(
26+
DIRECTORY
27+
include/
28+
DESTINATION
29+
${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}
30+
)
31+
32+
###
33+
### Tests
34+
###
35+
36+
if (TESTING)
37+
add_subdirectory(test)
38+
endif()

lib/mls_ds/README.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# MLS Delivery Service Tools
2+
3+
This library provides tools that can be convenient for an MLS Delivery Service
4+
(DS). We do not cover the actual delivery mechanics, but instead on more
5+
advanced functions where the DS needs to be aware of the internals of the MLS
6+
protocol.
7+
8+
For example, it is sometimes useful for the DS to maintain a view of a group's
9+
ratchet tree based on seeing the group's Commits (sent as PublicMessage). To do
10+
this, the DS needs to parse commits and know how to apply them to the tree.
11+
The `TreeFollower` class provided in this library implements this functionality.
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#pragma once
2+
3+
#include <mls/messages.h>
4+
#include <mls/treekem.h>
5+
#include <vector>
6+
7+
namespace MLS_NAMESPACE::mls_ds {
8+
9+
using namespace MLS_NAMESPACE;
10+
11+
class TreeFollower
12+
{
13+
public:
14+
// Construct a one-member tree
15+
TreeFollower(const KeyPackage& key_package);
16+
17+
// Import a tree as a starting point for future updates
18+
TreeFollower(TreeKEMPublicKey tree);
19+
20+
// Update the tree with a set of proposals applied by a commit
21+
void update(const MLSMessage& commit_message,
22+
const std::vector<MLSMessage>& extra_proposals);
23+
24+
// Accessors
25+
CipherSuite cipher_suite() const { return _suite; }
26+
const TreeKEMPublicKey& tree() const { return _tree; }
27+
28+
private:
29+
CipherSuite _suite;
30+
TreeKEMPublicKey _tree;
31+
};
32+
33+
} // namespace MLS_NAMESPACE::mls_ds

0 commit comments

Comments
 (0)