Re: [MLS] Optimizing Unmerged Leaves (or: bringing the MLS tree size back to O(n))

Karthik Bhargavan <karthikeyan.bhargavan@inria.fr> Tue, 21 December 2021 14:47 UTC

Return-Path: <karthikeyan.bhargavan@inria.fr>
X-Original-To: mls@ietfa.amsl.com
Delivered-To: mls@ietfa.amsl.com
Received: from localhost (localhost [127.0.0.1]) by ietfa.amsl.com (Postfix) with ESMTP id 56E6E3A10A8 for <mls@ietfa.amsl.com>; Tue, 21 Dec 2021 06:47:36 -0800 (PST)
X-Virus-Scanned: amavisd-new at amsl.com
X-Spam-Flag: NO
X-Spam-Score: -1.896
X-Spam-Level:
X-Spam-Status: No, score=-1.896 tagged_above=-999 required=5 tests=[BAYES_00=-1.9, HTML_MESSAGE=0.001, RCVD_IN_MSPIKE_H3=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, URIBL_BLOCKED=0.001] autolearn=ham autolearn_force=no
Received: from mail.ietf.org ([4.31.198.44]) by localhost (ietfa.amsl.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 3muZCNK4RSbi for <mls@ietfa.amsl.com>; Tue, 21 Dec 2021 06:47:32 -0800 (PST)
Received: from mail3-relais-sop.national.inria.fr (mail3-relais-sop.national.inria.fr [192.134.164.104]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ietfa.amsl.com (Postfix) with ESMTPS id 68EC33A10A5 for <mls@ietf.org>; Tue, 21 Dec 2021 06:47:31 -0800 (PST)
IronPort-Data: A9a23:ldpS5aq9VUP4W1pKsLzecIFJ1X9eBmL7ZxIvgKrLsJaIsI5as4F+vjcXXj+DaPePYGf3f9onYYnioUgG7ZSDzII2GQFprCpnQiMRo6IpJ/zJdxaqZ3v6wu7rFR88sZ1GMrEsFC2FJ5Pljk/F3oPJ8D8shclkepKmULSdY3orHFc+IMscoUkLd9AR09cAbeeRU1vlVePa+6UzCXf9s9JGGjp8B5Gr9HuDiM/PVAYw5TTSUxzkUGj2zBH5BLpHTU24wuCRroN8RoZWTM6bpF21E/+wwvsjNj+luu6TnkwiWbvOJQOKi3dQR+6rmgBGq0Te0I5qb7xHMQEN23PTw4EZJNZl7PRcTS8qN7fQmOUeXghRFWd0PaRc97bZKH6XsMqJzkSAfWGEL/BGUx1uY9JCkgpwKSQUnRACExgRcRmHg++k6KC6T+N2j4IiKtPqNcURoBlIzDzFA6N6GYrKW6XD6NtC2z09nNwIFvHbT8YcYCBkKhXNfxMJPU0YYLo7mPyAh3TjfXtfsl39mEadywA/1yQrjOmraoqTIYTMFJ4ThEuG4HnI5SL/Dw1yCTBW8hLdmlrEuwMFtXmTtFouKYCF
IronPort-HdrOrdr: A9a23:MXLbuq1J7Vhzi0nQfOnvWgqjBKckLtp133Aq2lEZdPWaSKylfqGV8cjzuiWbtN98YhodcJW7WZVoP0m3yXcF2+Us1N6ZNWHbUSmTXeNfBODZrAEIdReOldK1rZ0QFpRDNA==
X-IronPort-AV: E=Sophos;i="5.88,223,1635199200"; d="scan'208,217";a="740672"
Received: from 249.28.30.93.rev.sfr.net (HELO smtpclient.apple) ([93.30.28.249]) by mail3-relais-sop.national.inria.fr with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 21 Dec 2021 15:47:29 +0100
From: Karthik Bhargavan <karthikeyan.bhargavan@inria.fr>
Message-Id: <AE7E3C33-0281-4CF1-AE3F-CC7923E99E77@inria.fr>
Content-Type: multipart/alternative; boundary="Apple-Mail=_D9E82DF6-5D80-40D6-A43D-08A1C67F7473"
Mime-Version: 1.0 (Mac OS X Mail 15.0 \(3693.20.0.1.32\))
Date: Tue, 21 Dec 2021 15:47:27 +0100
In-Reply-To: <42346e03-df57-57c1-c781-249ebe13e798@inria.fr>
Cc: Raphael Robert <ietf=40raphaelrobert.com@dmarc.ietf.org>, MLS List <mls@ietf.org>
To: Théophile Wallez <theophile.wallez@inria.fr>
References: <162040556024.10515.18434867074453969330@ietfa.amsl.com> <F94C2B20-DC04-4028-937E-698D598E319D@sn3rd.com> <81ED88F6-16A8-45C4-A9B9-FD6659CB68FB@sn3rd.com> <4614A3C1-8461-48F8-AD53-C1399BC5F673@sn3rd.com> <6B8101F5-A54A-429A-82B0-5386E321F525@inria.fr> <8E5EC435-7B97-4852-AB90-F4E501ACDEDA@raphaelrobert.com> <42346e03-df57-57c1-c781-249ebe13e798@inria.fr>
X-Mailer: Apple Mail (2.3693.20.0.1.32)
Archived-At: <https://mailarchive.ietf.org/arch/msg/mls/agouKtpPOwBtTIjvJa_sGBM-bmU>
Subject: Re: [MLS] Optimizing Unmerged Leaves (or: bringing the MLS tree size back to O(n))
X-BeenThere: mls@ietf.org
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: Messaging Layer Security <mls.ietf.org>
List-Unsubscribe: <https://www.ietf.org/mailman/options/mls>, <mailto:mls-request@ietf.org?subject=unsubscribe>
List-Archive: <https://mailarchive.ietf.org/arch/browse/mls/>
List-Post: <mailto:mls@ietf.org>
List-Help: <mailto:mls-request@ietf.org?subject=help>
List-Subscribe: <https://www.ietf.org/mailman/listinfo/mls>, <mailto:mls-request@ietf.org?subject=subscribe>
X-List-Received-Date: Tue, 21 Dec 2021 14:47:36 -0000

Hello All,

Getting back to this suggestion from several months ago.
Recall that the issue is that the MLS tree size with explicit unmerged leaves can grow to O(N log N) and the suggestion in this thread would bring it down to O(N).

We worked the precise changes needed to the spec to achieve this and it can be done with a small change. The patch is attached.
The key observation is that unmerged leaves for each node can be recomputed if each node has an epoch timestamp.

In implementations, members may want to pre-compute and store the unmerged leaves lists just like in the current design, 
and only use this compression proposal when creating Welcome messages.
However, as discussed in this thread and elsewhere, in practice the “log N” factor does not appear to make much of an impact.

So, we are not proposing this as a PR at this time, and are attaching the patch only as reference, in case this size optimization becomes important in the future.

Best,
Karthik.


> On 21 Jun 2021, at 13:14, Théophile Wallez <theophile.wallez@inria.fr> wrote:
> 
> Hi and sorry for the late answer,
> 
> I'll also respond inline:
> 
> On 22/05/2021 12:14, Raphael Robert wrote:
>> 
>>> On 21. May 2021, at 16:59, Karthik Bhargavan <karthikeyan.bhargavan@inria.fr <mailto:karthikeyan.bhargavan@inria.fr>> wrote:
>>> 
>>> Solution 1
>>> —————
>>> 
>>> One possibility, close to the current MLS spec would be to not store the unmerged leaf at a node if it is already stored at the parent.
>>> This way, every leaf is present at most once in some `unmerged_leaves` array.
>>> In the worst case, this uses 4*n + 4*(n/2) = 6*n bytes (4*n for the `unmerged_leaves` array size, 4*(n/2) because at most n/2 leaves are stored in the `unmerged_leaves` arrays).
>>> In the best case, this uses 4*n bytes.
>>> The tree resolution algorithm will now have to do a little more calculation to re-compute the unmerged leaves at each node.
>> 
>> 
>> This sounds rather straight forward on a logical level. It clearly reduces the stored/transmitted payload size, but it introduces a new cost: previously only O(log n) nodes needed to be modified during an Update/Remove. Now we might have to “push down” the book keeping information from parents to children, in the worst case this affects n/2 nodes, so the cost of modifying nodes goes up to O(n).
>> If we consider a very large group, there would be a measurable difference when clients load/store nodes from/to disk.
>> My current intuition is that currently I/O operations can be done in O(log n) with clever parent hash caching. The worst case here would be the following: A client is a member of a large group and has been offline for a while (which is not a far fetched scenario). The client now comes online and has to process a large number of handshake messages. For robustness, the client might be inclined to frequently persist the intermediary state. This is where the I/O cost kicks in.
> 
> Indeed, when "pushing down" the unmerged leaves, if it is pushed down to a blank node, we might have to modify more than O(log n) nodes. This could be solved by storing unmerged leaves in blank nodes, but it is starting to get quite ugly.
> 
>>> Solution 2+
>>> —————
>>> 
>>> However, if we wish to compress further, we can do so as well, using two facts:
>>> - if a node `n` has two children `l` and `r`, then n.last_update_epoch is equal to either l.last_update_epoch or r.last_update_epoch,
>>> - if `l` is unmerged for some node then l.creation_epoch = l.last_update_epoch (because in this case, the leaf never generated an UpdatePath) (the converse is not true, for example in the case when its sibling generates an UpdatePath).
>>> 
>>> So at each node, we can store the direction from which the node was last updated (for example with a boolean flag), then given any node `n` we can "follow the path" down the tree to obtain a leaf `l`, and we have n.last_update_epoch = l.last_update_epoch.
>>> Furthermore, we don't need to store the creation epoch for leaves:
>>> - if `l` is unmerged for `n`, then n.last_update_epoch < l.creation_epoch = l.last_update_epoch,
>>> - if `l` is not unmerged for `n`, then n.last_update_epoch >= l.last_update_epoch (because when `l` is updated, it updates all the nodes above it).
>>> This uses 8*n + 1*n = 9*n bytes for the whole tree.
>> 
>> I didn’t check all the ramifications here tbh. I think it is similar to solution 1 in terms of increased I/O cost.
> 
> Actually, there is no "pushing down" here, when modifying nodes on a path from leaf to the root, only those nodes are modified (we update the direction of the parent nodes in the path, and update the last update epoch of the leaf), so the I/O cost is actually O(log n).
> 
> Thank you for your comments,
> Théophile.
> 
> _______________________________________________
> MLS mailing list
> MLS@ietf.org
> https://www.ietf.org/mailman/listinfo/mls