Skip to content
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

Encrypt and Decrypt support #39

Merged
merged 4 commits into from
Apr 3, 2023
Merged

Conversation

neekolas
Copy link
Contributor

@neekolas neekolas commented Apr 3, 2023

Summary

Trying to teach myself some Rust here. Attempted to add Encrypt and Decrypt support. This also required me to change the output type from createInboundSession and createOutboundSession to include the sessionId in the payload. I went for the Box<JsValue> type and am returning the two values as a tuple for simplicity, but I'm not sure what the best pattern is here.

Notes

  • I initially wrote the last test with Alice decrypting her own message. That didn't work. Is that not possible with the way we have Olm configured? The error I got was Bad MAC.
  • This is my first ever Rust PR. Have at me. Be ruthless. I have no idea what I'm doing.

@neekolas neekolas requested a review from michaelx11 April 3, 2023 21:51
@neekolas neekolas marked this pull request as ready for review April 3, 2023 21:52
@michaelx11
Copy link
Contributor

@neekolas I don't think vodozemac/olm support decryption of your own encrypted message. I tried it briefly and came to the conclusion that the way they set up their ratchet chains etc, the onus is on the library consumer to record outgoing plaintexts separately. I could be wrong here.

Copy link
Contributor

@michaelx11 michaelx11 left a comment

Choose a reason for hiding this comment

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

LGTM - I think we're about evenly matched already in Rust so I found nothing to be harsh about


let result = instance.encrypt_message_serialized(session_id, message);
// Do we actually need to do this?
instances.get(sending_handle).unwrap().set(instance);
Copy link
Contributor

Choose a reason for hiding this comment

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

You don't have to do this here. Instead of using take() you should be able to use get_mut() on the cell (so .get(sending_handle).ok_or(...)?.get_mut() approximately.

This pattern comes from my own lack of Rust borrow-checker knowledge when implementing the inbound/outbound session functions. I couldn't take mutable references of two INSTANCE_MAP values in the same scope so ended up doing it via Cell in a weird way.

Copy link
Contributor

Choose a reason for hiding this comment

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

In fact, now that we're using .public_account, I think we can do away with this pattern in those existing functions too. I might tackle that in a separate PR.

Copy link
Contributor Author

@neekolas neekolas Apr 3, 2023

Choose a reason for hiding this comment

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

The type checker doesn't seem to like that, but it recommends that I replace the cell with a RefCell. RefCell then has a method borrow_mut. If I use that, everything is happy and I don't have to manually set the cell to updated values.

Does have some scary language about concurrent usage leading to a panic, but I think that's fine for now given that all our code is synchronous.

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've committed that change here

Copy link
Contributor

@michaelx11 michaelx11 Apr 3, 2023

Choose a reason for hiding this comment

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

Sounds good, anything works for now as long as borrow/type checker likes it. We can take a holistic look at the map/cell strategy in a subsequent PR.

Actually, on second look: RefCell seems much better than the take/set strategy. Nice!

bindings/wasm/crate/src/lib.rs Outdated Show resolved Hide resolved

// Alice can't decrypt her own message. Does work for Bob though
// const decrypted = await alice.decryptMessage(sessionId, encrypted);
const decrypted = await bob.decryptMessage(sessionId, encrypted);
Copy link
Contributor

Choose a reason for hiding this comment

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

beautiful

@neekolas neekolas merged commit 7701394 into vodozemac_dev Apr 3, 2023
@neekolas neekolas deleted the nmolnar/encrypt-and-decrypt branch April 3, 2023 22:33
@@ -29,7 +29,7 @@ pub fn create_outbound_session(
sending_handle: &str,
receiving_handle: &str,
message: &str,
) -> Result<String, JsValue> {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure I follow the need for the Box here.

My understanding was that JsValues through WASM_BINDGEN get copied between the memory space?

Heap Allocating here seems unnecessary at a high level.

Comment on lines +58 to +61
Ok((session_id, ciphertext_json)) => Ok(vec![
JsValue::from_str(&session_id),
JsValue::from_str(&ciphertext_json),
]
Copy link
Contributor

Choose a reason for hiding this comment

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

Using the Vec here changes the memory structure. Given the size cannot be known at compile time.

Imagine using a tuple would be easier to use and would avoid the need for boxing of the values.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately, wasm-bindgen doesn't appear to support tuples as return values. rustwasm/wasm-bindgen#122

Comment on lines +123 to +126
match result {
Ok(ciphertext_json) => Ok(ciphertext_json),
Err(e) => Err(JsValue::from_str(&e.to_string())),
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems fine to me, however there are more idomatic ways to use a Match statement.

  1. Pretty sure that RHS Ok() is not needed. Values are implicitly Ok
  2. result is an 'Result' which you are trying to map the error. You can use result.map_error(|e| JsValue::from_str(&e.to_string()))

bindings/wasm/crate/src/lib.rs Outdated Show resolved Hide resolved
sending_handle: &str,
session_id: &str,
message: &str,
) -> Result<String, JsValue> {
Copy link
Contributor

Choose a reason for hiding this comment

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

These types look both to be Strings, only the error gets converted to a wasmbind:JsValue. Shouldn't they be the same type?

let instances = INSTANCE_MAP.lock().unwrap();
let mut instance = instances
.get(sending_handle)
.ok_or("sending_handle not found")?
Copy link
Contributor

Choose a reason for hiding this comment

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

I like this conversion from an Option to Result.

You could have used a match, but this is much cleaner

@@ -20,7 +20,7 @@ lazy_static! {
pub fn new_voodoo_instance() -> String {
let mut instances = INSTANCE_MAP.lock().unwrap();
let handle = (instances.len() as u64).to_string();
instances.insert(handle.clone(), Cell::new(VoodooInstance::new()));
instances.insert(handle.clone(), RefCell::new(VoodooInstance::new()));
Copy link
Contributor

Choose a reason for hiding this comment

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

RefCell seems odd here: Though WASM is odd so it might be the best way. Naively I would have thought we could handle borrows at compile time.

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.

4 participants