Skip to content

Commit 1cc50a1

Browse files
authored
Fix failure on light client rejoining (#447)
* Add a test case to exercise light client rejoining * Use the public tree to guide path secret derivation instead of FDP * clang-format * Remove unused variable
1 parent 3d6d20d commit 1cc50a1

File tree

4 files changed

+73
-14
lines changed

4 files changed

+73
-14
lines changed

lib/mls_ds/src/tree_follower.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,7 @@ TreeFollower::update(const MLSMessage& commit_message,
145145
const auto commit_auth_content =
146146
commit_public_message.authenticated_content();
147147
const auto group_content = commit_auth_content.content;
148-
const auto& commit =
149-
var::get<Commit>(commit_auth_content.content.content);
148+
const auto& commit = var::get<Commit>(commit_auth_content.content.content);
150149

151150
// Apply proposals
152151
apply(_tree, _suite, group_content.sender, commit.proposals, extra_proposals);
@@ -155,8 +154,7 @@ TreeFollower::update(const MLSMessage& commit_message,
155154

156155
// Merge the update path
157156
if (commit.path) {
158-
const auto sender =
159-
var::get<MemberSender>(group_content.sender.sender);
157+
const auto sender = var::get<MemberSender>(group_content.sender.sender);
160158
const auto from = LeafIndex(sender.sender);
161159
const auto& path = opt::get(commit.path);
162160
_tree.merge(from, path);

src/treekem.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ TreeKEMPrivateKey::joiner(const TreeKEMPublicKey& pub,
100100
auto priv = TreeKEMPrivateKey{ pub.suite, index, {}, {}, {} };
101101
priv.private_key_cache.insert({ NodeIndex(index), std::move(leaf_priv) });
102102
if (path_secret) {
103-
priv.implant(pub, intersect, opt::get(path_secret));
103+
priv.implant_matching(pub, intersect, opt::get(path_secret));
104104
}
105105
return priv;
106106
}

test/state.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,62 @@ TEST_CASE_METHOD(StateTest, "Light client can participate")
511511
REQUIRE(first4 == third4);
512512
}
513513

514+
TEST_CASE_METHOD(StateTest, "Light client can rejoin")
515+
{
516+
// Initialize the creator's state
517+
auto first0 = State{ group_id,
518+
suite,
519+
leaf_privs[0],
520+
identity_privs[0],
521+
key_packages[0].leaf_node,
522+
{} };
523+
524+
// Add the second and third participants
525+
auto add1a = first0.add_proposal(key_packages[1]);
526+
auto add1b = first0.add_proposal(key_packages[2]);
527+
auto [commit1, welcome1, first1_] = first0.commit(
528+
fresh_secret(), CommitOpts{ { add1a, add1b }, true, false, {} }, {});
529+
silence_unused(commit1);
530+
auto first1 = first1_;
531+
532+
auto third1 = State{ init_privs[2],
533+
leaf_privs[2],
534+
identity_privs[2],
535+
key_packages[2],
536+
welcome1,
537+
std::nullopt,
538+
{} };
539+
540+
REQUIRE(first1 == third1);
541+
542+
// Remove the second participant and re-add them in the same commit
543+
auto remove2 = first1.remove_proposal(LeafIndex{ 1 });
544+
auto add2 = first1.add_proposal(key_packages[1]);
545+
auto [commit2, welcome2, first2_] = first1.commit(
546+
fresh_secret(), CommitOpts{ { remove2, add2 }, false, false, {} }, {});
547+
silence_unused(welcome2);
548+
auto first2 = first2_;
549+
550+
auto third2 = opt::get(third1.handle(commit2));
551+
552+
REQUIRE(first2 == third2);
553+
554+
// Second participant (re-)joins as a light client
555+
const auto annotated_welcome_2 = AnnotatedWelcome::from(
556+
welcome2, first2.tree(), LeafIndex{ 0 }, LeafIndex{ 1 });
557+
558+
auto second2 = State{ init_privs[1],
559+
leaf_privs[1],
560+
identity_privs[1],
561+
key_packages[1],
562+
annotated_welcome_2.welcome,
563+
annotated_welcome_2.tree(),
564+
{} };
565+
566+
REQUIRE(first2 == second2);
567+
REQUIRE_FALSE(second2.is_full_client());
568+
}
569+
514570
TEST_CASE_METHOD(StateTest, "Light client can upgrade after several commits")
515571
{
516572
// Initialize the first two users

test/treekem.cpp

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ TEST_CASE_METHOD(TreeKEMTest, "Node public key")
6161
TEST_CASE_METHOD(TreeKEMTest, "TreeKEM Private Key")
6262
{
6363
const auto size = LeafCount{ 5 };
64-
const auto index = LeafIndex{ 2 };
65-
const auto intersect = NodeIndex{ 3 };
64+
const auto adder_index = LeafIndex{ 1 };
65+
const auto joiner_index = LeafIndex{ 2 };
6666
const auto random = random_bytes(32);
6767
const auto priv = HPKEPrivateKey::derive(suite, random);
6868
const auto priv2 = HPKEPrivateKey::generate(suite);
@@ -75,22 +75,27 @@ TEST_CASE_METHOD(TreeKEMTest, "TreeKEM Private Key")
7575
pub.add_leaf(leaf_node);
7676
}
7777

78-
// create() populates the direct path
79-
auto priv_create = TreeKEMPrivateKey::create(pub, index, random);
80-
REQUIRE(priv_create.path_secrets.find(NodeIndex(4)) !=
78+
// create() populates the direct path in the private key
79+
auto priv_create = TreeKEMPrivateKey::create(pub, adder_index, random);
80+
REQUIRE(priv_create.path_secrets.find(NodeIndex(2)) !=
8181
priv_create.path_secrets.end());
82-
REQUIRE(priv_create.path_secrets.find(NodeIndex(5)) !=
82+
REQUIRE(priv_create.path_secrets.find(NodeIndex(1)) !=
8383
priv_create.path_secrets.end());
8484
REQUIRE(priv_create.path_secrets.find(NodeIndex(3)) !=
8585
priv_create.path_secrets.end());
8686
REQUIRE(priv_create.path_secrets.find(NodeIndex(7)) !=
8787
priv_create.path_secrets.end());
8888
REQUIRE(priv_create.update_secret.size() == hash_size);
8989

90+
// Populate the direct path from the into the public key manually
91+
pub.node_at(NodeIndex{ 1 }) = OptionalNode{ Node{ ParentNode{} } };
92+
pub.node_at(NodeIndex{ 3 }) = OptionalNode{ Node{ ParentNode{} } };
93+
pub.node_at(NodeIndex{ 7 }) = OptionalNode{ Node{ ParentNode{} } };
94+
9095
// joiner() populates the leaf and the path above the ancestor,
9196
// but not the direct path in the middle
92-
auto priv_joiner =
93-
TreeKEMPrivateKey::joiner(pub, index, priv, intersect, random);
97+
auto priv_joiner = TreeKEMPrivateKey::joiner(
98+
pub, joiner_index, priv, joiner_index.ancestor(adder_index), random);
9499
REQUIRE(priv_joiner.private_key(NodeIndex(4)));
95100
REQUIRE(priv_joiner.path_secrets.find(NodeIndex(3)) !=
96101
priv_joiner.path_secrets.end());
@@ -103,7 +108,7 @@ TEST_CASE_METHOD(TreeKEMTest, "TreeKEM Private Key")
103108

104109
// set_leaf_priv() properly sets the leaf secret
105110
priv_joiner.set_leaf_priv(priv2);
106-
REQUIRE(priv_joiner.private_key(NodeIndex(index)) == priv2);
111+
REQUIRE(priv_joiner.private_key(NodeIndex(joiner_index)) == priv2);
107112
REQUIRE(priv_joiner.update_secret.size() == hash_size);
108113
REQUIRE(priv_joiner.update_secret == last_update_secret);
109114

0 commit comments

Comments
 (0)