Skip to content

Commit

Permalink
Add the ability for clients to join as light
Browse files Browse the repository at this point in the history
  • Loading branch information
bifurcation committed May 24, 2024
1 parent 4628899 commit 1bc020d
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 27 deletions.
1 change: 1 addition & 0 deletions include/mls/core_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ struct ExtensionType
static constexpr Extension::Type external_senders = 5;

static constexpr Extension::Type flags = 6;
static constexpr Extension::Type membership_proof = 7;

// XXX(RLB) There is no IANA-registered type for this extension yet, so we use
// a value from the vendor-specific space
Expand Down
8 changes: 8 additions & 0 deletions include/mls/messages.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ struct RatchetTreeExtension
TLS_SERIALIZABLE(tree)
};

struct MembershipProofExtension
{
std::vector<TreeSlice> slices;

static const uint16_t type;
TLS_SERIALIZABLE(slices)
};

struct ExternalSender
{
SignaturePublicKey signature_key;
Expand Down
16 changes: 14 additions & 2 deletions include/mls/state.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,20 @@ struct RosterIndex : public UInt32

struct CommitOpts
{
// Include these proposals in the commit by value
std::vector<Proposal> extra_proposals;
bool inline_tree;
bool force_path;

// Send a ratchet_tree extension in the Welcome
bool inline_tree = false;

// Send an UpdatePath even if none is required
bool force_path = false;

// Send a membership_proof extension in the Welcome covering the committer and
// the new joiners
bool membership_proof = false;

// Update the committer's LeafNode in the following way
LeafNodeOptions leaf_node_opts;
};

Expand Down Expand Up @@ -145,6 +156,7 @@ class State
const ExtensionList& extensions() const { return _extensions; }
const TreeKEMPublicKey& tree() const { return _tree; }
const bytes& resumption_psk() const { return _key_schedule.resumption_psk; }
bool is_full_client() const { return _tree.is_complete(); }

bytes do_export(const std::string& label,
const bytes& context,
Expand Down
2 changes: 2 additions & 0 deletions include/mls/treekem.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ struct TreeSlice
std::vector<bytes> copath_hashes;

bytes tree_hash(CipherSuite suite) const;
void add(const TreeSlice& other);

TLS_SERIALIZABLE(leaf_index, n_leaves, direct_path_nodes, copath_hashes);
};
Expand Down Expand Up @@ -157,6 +158,7 @@ struct TreeKEMPublicKey

bool parent_hash_valid(LeafIndex from, const UpdatePath& path) const;
bool parent_hash_valid() const;
bool is_complete() const;

bool has_leaf(LeafIndex index) const;
std::optional<LeafIndex> find(const LeafNode& leaf) const;
Expand Down
1 change: 1 addition & 0 deletions src/messages.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace MLS_NAMESPACE {

const Extension::Type ExternalPubExtension::type = ExtensionType::external_pub;
const Extension::Type RatchetTreeExtension::type = ExtensionType::ratchet_tree;
const Extension::Type MembershipProofExtension::type = ExtensionType::membership_proof;
const Extension::Type ExternalSendersExtension::type =
ExtensionType::external_senders;
const Extension::Type FlagsExtension::type = ExtensionType::flags;
Expand Down
2 changes: 1 addition & 1 deletion src/session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ Session::commit()
auto commit_secret = inner->fresh_secret();
auto encrypt = inner->encrypt_handshake;
auto [commit, welcome, new_state] = inner->history.front().commit(
commit_secret, CommitOpts{ {}, true, encrypt, {} }, { encrypt, {}, 0 });
commit_secret, CommitOpts{ {}, true, encrypt, false, {} }, { encrypt, {}, 0 });

auto commit_msg = tls::marshal(commit);
auto welcome_msg = tls::marshal(welcome);
Expand Down
56 changes: 50 additions & 6 deletions src/state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,19 @@ State::import_tree(const bytes& tree_hash,
{
auto tree = TreeKEMPublicKey(_suite);
auto maybe_tree_extn = extensions.find<RatchetTreeExtension>();
auto maybe_membership_proof_extn = extensions.find<MembershipProofExtension>();

if (external) {
tree = opt::get(external);
} else if (maybe_tree_extn) {
tree = opt::get(maybe_tree_extn).tree;
} else if (maybe_membership_proof_extn) {
const auto& membership_proof = opt::get(maybe_membership_proof_extn);

tree = TreeKEMPublicKey(_suite, membership_proof.slices.at(0));
for (auto i = size_t(0); i < membership_proof.slices.size(); i++) {
tree.implant_slice(membership_proof.slices.at(i));
}
} else {
throw InvalidParameterError("No tree available");
}
Expand All @@ -74,6 +83,12 @@ State::import_tree(const bytes& tree_hash,
bool
State::validate_tree() const
{
// If we don't have a full tree, then we can't verify any properties
// TODO(RLB) We can in fact verify that the leaf node signatures are valid.
if (!_tree.is_complete()) {
return true;
}

// The functionality here is somewhat duplicative of State::valid(const
// LeafNode&). Simply calling that method, however, would result in this
// method having quadratic scaling, since each call to valid() does a linear
Expand Down Expand Up @@ -640,6 +655,11 @@ State::commit(const bytes& leaf_secret,
const MessageOpts& msg_opts,
CommitParams params)
{
// If we are not a full client, we can't handle commits
if (!is_full_client()) {
throw ProtocolError("Light clients can't create commits");
}

// Construct a commit from cached proposals
// TODO(rlb) ignore some proposals:
// * Update after Update
Expand Down Expand Up @@ -757,6 +777,26 @@ State::commit(const bytes& leaf_secret,
auto commit_message =
protect(std::move(commit_content_auth), msg_opts.padding_size);

// If we are adding membership proofs, add one covering this client and the
// joiners.
auto group_info_extensions = ExtensionList{};
if (opts && opt::get(opts).membership_proof) {
auto slices = std::vector<TreeSlice>{};
slices.reserve(joiner_locations.size() + 1);

slices.push_back(next._tree.extract_slice(next._index));
for (const auto& loc : joiner_locations) {
slices.push_back(next._tree.extract_slice(loc));
}

group_info_extensions.add(MembershipProofExtension{ std::move(slices) });
}

// If we are sending the whole tree, add that extension
if (opts && opt::get(opts).inline_tree) {
group_info_extensions.add(RatchetTreeExtension{ next._tree });
}

// Complete the GroupInfo and form the Welcome
auto group_info = GroupInfo{
{
Expand All @@ -767,12 +807,9 @@ State::commit(const bytes& leaf_secret,
next._transcript_hash.confirmed,
next._extensions,
},
{ /* No other extensions */ },
{ group_info_extensions },
{ confirmation_tag },
};
if (opts && opt::get(opts).inline_tree) {
group_info.extensions.add(RatchetTreeExtension{ next._tree });
}
group_info.sign(next._tree, next._index, next._identity_priv);

auto welcome =
Expand Down Expand Up @@ -857,6 +894,11 @@ State::handle(const ValidatedContent& val_content,
throw InvalidParameterError("Invalid content type");
}

// If we are not a full client, we can't handle commits
if (!is_full_client()) {
throw ProtocolError("Light clients can't handle commits");
}

switch (content.sender.sender_type()) {
case SenderType::member:
case SenderType::new_member_commit:
Expand Down Expand Up @@ -1007,6 +1049,7 @@ State::create_branch(bytes group_id,
proposals,
commit_opts.inline_tree,
commit_opts.force_path,
commit_opts.membership_proof,
commit_opts.leaf_node_opts,
};
auto [_commit, welcome, state] = new_group.commit(
Expand Down Expand Up @@ -1085,6 +1128,7 @@ State::Tombstone::create_welcome(HPKEPrivateKey enc_priv,
proposals,
commit_opts.inline_tree,
commit_opts.force_path,
commit_opts.membership_proof,
commit_opts.leaf_node_opts,
};
auto [_commit, welcome, state] = new_group.commit(
Expand Down Expand Up @@ -1985,12 +2029,12 @@ operator==(const State& lhs, const State& rhs)
auto suite = (lhs._suite == rhs._suite);
auto group_id = (lhs._group_id == rhs._group_id);
auto epoch = (lhs._epoch == rhs._epoch);
auto tree = (lhs._tree == rhs._tree);
auto tree_hash = (lhs._tree.root_hash() == rhs._tree.root_hash());
auto transcript_hash = (lhs._transcript_hash == rhs._transcript_hash);
auto key_schedule = (lhs._key_schedule == rhs._key_schedule);
auto extensions = (lhs._extensions == rhs._extensions);

return suite && group_id && epoch && tree && transcript_hash &&
return suite && group_id && epoch && tree_hash && transcript_hash &&
key_schedule && extensions;
}

Expand Down
11 changes: 11 additions & 0 deletions src/treekem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,12 @@ TreeKEMPublicKey::parent_hash_valid() const
return true;
}

bool
TreeKEMPublicKey::is_complete() const
{
return nodes.size() == NodeCount{size}.val;
}

std::vector<NodeIndex>
TreeKEMPublicKey::resolve(NodeIndex index) const
{
Expand Down Expand Up @@ -705,6 +711,11 @@ std::optional<LeafIndex>
TreeKEMPublicKey::find(const LeafNode& leaf) const
{
for (LeafIndex i{ 0 }; i < size; i.val++) {
if (nodes.count(NodeIndex{ i }) == 0) {
// Unknown leaf node
continue;
}

const auto& node = node_at(i);
if (!node.blank() && node.leaf_node() == leaf) {
return i;
Expand Down
Loading

0 comments on commit 1bc020d

Please sign in to comment.