-
Notifications
You must be signed in to change notification settings - Fork 120
[E2E] Upload keys #233
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[E2E] Upload keys #233
Conversation
non-Jedi
left a comment
There was a problem hiding this 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.
matrix_client/crypto/olm_device.py
Outdated
| } | ||
| self.sign_json(device_keys) | ||
| ret = self.api.upload_keys(device_keys=device_keys) | ||
| self.one_time_key_counts = ret['one_time_key_counts'] |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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!
matrix_client/crypto/olm_device.py
Outdated
| # 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: |
There was a problem hiding this comment.
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.
matrix_client/crypto/olm_device.py
Outdated
| 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 = {} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)),
...
}There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it does.
matrix_client/crypto/olm_device.py
Outdated
| 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: |
There was a problem hiding this comment.
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?
matrix_client/crypto/olm_device.py
Outdated
| 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)) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha. I understand now. 👍
matrix_client/crypto/olm_device.py
Outdated
| 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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
matrix_client/crypto/olm_device.py
Outdated
| 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'] = \ |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
matrix_client/crypto/olm_device.py
Outdated
| if not num_to_create: | ||
| continue | ||
| keys_uploaded[key_type] = num_to_create | ||
| self.olm_account.generate_one_time_keys(sum(keys_uploaded.values())) |
There was a problem hiding this comment.
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.
matrix_client/crypto/olm_device.py
Outdated
| 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)) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
|
Tried the lightweight class idea. Let me know how it looks! Needs squashing before merge obviously. |
non-Jedi
left a comment
There was a problem hiding this 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): |
There was a problem hiding this comment.
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?).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to key_counts.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
uhoreg
left a comment
There was a problem hiding this 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
Second part of the crypto stuff.
Signed-off-by: Valentin Deniaud <valentin.deniaud@inpt.fr>