Skip to content

Conversation

@Zil0
Copy link
Contributor

@Zil0 Zil0 commented Jun 13, 2018

Second part of the crypto stuff.

Signed-off-by: Valentin Deniaud <valentin.deniaud@inpt.fr>

Copy link
Collaborator

@non-Jedi non-Jedi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't have time to review tests, but in interest of getting things merged before your GSOC is over, I'm assuming those are correct. Tests in general have been excellent so far.

}
self.sign_json(device_keys)
ret = self.api.upload_keys(device_keys=device_keys)
self.one_time_key_counts = ret['one_time_key_counts']
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a situation where the server can return key counts for one type of key but not others? Seems like there could be.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This happens when the server has no more key of that type. So when a key is missing in self.one_time_key_counts, we infer that it means a count of 0 (hence we do self.one_time_key_counts.get(type, 0)). I'm not really sure keeping this dict is good, even though it is the solution with the less overhead.
Would you prefer if there was two interger attribute for the counts, and two more for the target_counts? This would allow to get rid of the weird dicts, but if there are new types of one-time keys later it could lead to a lot of attributes...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't see your last comment about a lightweight class, that sure seems like an option. I'll see what I can do!

# used instantly, and we want them to stay in libolm, until the limit is reached
# and it starts discarding keys, starting by the oldest.
target_keys_number = self.olm_account.max_one_time_keys // 2
if not 0 <= signed_otk_proportion <= 1:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's put all input validation at the top of the method.

target_keys_number = self.olm_account.max_one_time_keys // 2
if not 0 <= signed_otk_proportion <= 1:
raise ValueError('signed_otk_proportion must be between 0 and 1.')
self.otk_target_counts = {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's either use the "otk" abbreviation throughout the code or expand it everywhere throughout the code. In previous commit, you added the self.one_time_key_counts attribute. I'll try to resist any more bikeshedding. Sorry.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not add both the attributes below during the dictionary instantiation for clarity?

self.otk_target_counts = {
    'signed_curve25519': int(round(signed_otk_proportion * target_keys_number)),
    ...
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like bikeshedding!

Args:
force_update (bool): Fetch the number of one-time keys currently on the HS
before uploading, even if we already know one. In most cases this should
not be necessary, as we get this value from sync responses.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming the logic for pulling this from sync comes in later commit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it does.

uploaded, the corresponding key will not be present. Consequently, an
empty dict indicates that no keys were uploaded.
"""
if not self.one_time_key_counts or force_update:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't remember enough about operator priority in python, but I'm assuming this is equivalent to (not self.one_time_key_counts) or force_update. Could we reverse that so people don't have to think about operator priority? force_update or not self.one_time_key_counts?

raise ValueError('signed_otk_proportion must be between 0 and 1.')
self.otk_target_counts = {}
self.otk_target_counts['signed_curve25519'] = \
int(round(signed_otk_proportion * target_keys_number))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't int(round(x)) redundant?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

round(1.5) will give you 2.0 with py2. It led to trouble.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the same thing in py3 afaict. Isn't that the desired behavior?

Copy link
Contributor Author

@Zil0 Zil0 Jul 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My py3 tells me 2, not 2.0. It fails with py2 at the generate_one_time_keys call, which wants an int and not a float.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. I understand now. 👍

user_id (str): Matrix user ID. Must match the one used when logging in.
device_id (str): Must match the one used when logging in.
signed_otk_proportion (float): Optional. The proportion of signed one-time keys we
should maintain on the HS compared to unsigned keys. The maximum value of
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://matrix.org/docs/guides/e2e_implementation.html#creating-and-registering-one-time-keys says that all one-time keys uploaded should be signed. I'm guessing this is one of the differences @richvdh mentioned in current E2E compared to that guide?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmmh, I think that Riot only uploads signed keys. However, there are benefits to uploading unsigned keys (see https://git.matrix.org/git/olm/about/docs/signing.rst), so it is a nice ability to have! Note that from the default parameters of __init__, the default behavior is to only upload signed keys.

if not 0 <= signed_otk_proportion <= 1:
raise ValueError('signed_otk_proportion must be between 0 and 1.')
self.otk_target_counts = {}
self.otk_target_counts['signed_curve25519'] = \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we encode the difference between signed/unsigned keys in the type system somehow instead of just having two arbitrary strings?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I see that these strings are actually used in the upload request. Still feels weird to encode the difference as string keys in a dict for some reason.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably the "proper" way of doing it would be to have lightweight classes for this, and move most of the logic for manipulating an individual key to the class methods.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is for consistency with self.one_time_key_counts.

if not num_to_create:
continue
keys_uploaded[key_type] = num_to_create
self.olm_account.generate_one_time_keys(sum(keys_uploaded.values()))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that you explicitly name the keys as curve25519 in self.otk_target_counts, doesn't this do the wrong thing? Having "curve25519" included in name of dictionary keys implies there could be other algos in dictionary in the future, and this call shouldn't care about that.

self.otk_target_counts['signed_curve25519'] = \
int(round(signed_otk_proportion * target_keys_number))
self.otk_target_counts['curve25519'] = \
int(round((1 - signed_otk_proportion) * target_keys_number))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incorrect. In case of partial number, this leaves a single key unaccounted for. e.g. target_keys_number = 9 and signed_otk_proportion = 0.5.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, there was an arbitrary choice to make: add a signed key, add an unsigned key or "lose" a key. I don't see why randomly adding a key would be a better behavior?
Anyway you're right that this is problematic as is. How about I document the behavior in the docstring?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, I think I'm wrong here. round should give a pair of numbers that sums to target_keys_number.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You found the edge case where it can't. I really think it's not a big deal though.

@Zil0
Copy link
Contributor Author

Zil0 commented Jul 2, 2018

Tried the lightweight class idea. Let me know how it looks! Needs squashing before merge obviously.

Copy link
Collaborator

@non-Jedi non-Jedi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the class for one-time keys is a pretty big win for readability. I would probably move all of the functionality related to otks (including the upload/download itself) into the class, but I think it's pretty reasonable/readable as is. Add some clarification to OneTimeKeysManager.counts, squash down, and we can get this merged.

self.to_upload = {}

@property
def counts(self):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More descriptive name here please or brief docstring (what is this count of?).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to key_counts.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry. Should've been more explicit. Was looking for something like server_counts or even server_key_counts.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, server_counts it is.

Copy link
Member

@uhoreg uhoreg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good to me

@uhoreg
Copy link
Member

uhoreg commented Jul 13, 2018

@non-Jedi it looks like this, #234, and #235 are ready to be merged now?

@non-Jedi non-Jedi merged commit 7153a61 into matrix-org:master Jul 13, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants