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

Alternative: Passing custom hash functions to ECDH #180

Merged
merged 7 commits into from
Dec 9, 2019

Conversation

elichai
Copy link
Member

@elichai elichai commented Nov 9, 2019

This is an alternative to #164.
It's a lot more code but I feel it's more idiomatic that way.
the ffi::SharedSecret wasn't really needed so I removed it.

Review Tip:

Basically all the user need to do is use an array and do hash.into(). I hope it's an easy enough to use API, I feel like the more complicated our trait system gets the harder it is for users to use, but I still think it's better than runtime checks.

@elichai elichai changed the title Alternative: Passing custom hash functions Alternative: Passing custom hash functions to ECDH Nov 9, 2019
@elichai
Copy link
Member Author

elichai commented Nov 9, 2019

Arghh this still isn't safe.
If the closure panics it will panic across into the C code.
and that's UB.

I need to wrap the whole hash_callback in catch_unwind

@elichai
Copy link
Member Author

elichai commented Nov 10, 2019

catch_unwind is a std thing :(

I see two options:

  1. Feature gating this for std only. (@shmishleniy @dvdplm Does that work for your usecase?).
  2. Marking it unsafe and documenting that the passed function can not panic and if it does there be dragons.
  3. Combining both 1 and 2 by providing 2 variants of the same function.

@apoelstra Would love to hear your thoughts :)

@elichai
Copy link
Member Author

elichai commented Nov 10, 2019

Last commit is an example of how it would look with feature = "std"

@dvdplm
Copy link

dvdplm commented Nov 11, 2019

Does that work for your usecase?

std-only should be fine for us, yes.

Re-panicking callbacks, good catch, it's a problem I didn't consider. Going the catch_unwind route would mean excluding switching to aborting panics, which is something we have considered doing in the past for binary size reasons (I think?). It's not a show-stopper for us, mind you, but somewhat unfortunate.
Makes me wonder if adding a new_raw() method wouldn't be an easier way though; wouldn't that help with both the std-only and panicking behaviour problem?

@elichai
Copy link
Member Author

elichai commented Nov 11, 2019

You can still compile with panic=abort it just won't catch any aborts.
That's actually the safest route when it comes to FFI

@apoelstra
Copy link
Member

Yeah, I think we should add an unsafe new_with_hash_no_panic variant which doesn't do the catch_unwind.

Concept ACK the existing code.

@elichai
Copy link
Member Author

elichai commented Nov 27, 2019

The exact traits implementation for SharedSecret is still open for debate as I'm not sure what's needed and what not https://docs.rs/secp256k1/0.16.0/secp256k1/ecdh/struct.SharedSecret.html
have Index, Eq, and an as_ptr(). the as_ptr() I don't think should be on the public API here. as it's just a helper struct, it's not an FFI one.
Regarding Index and Eq I don't know. because I implemented both AsRef and Deref to [u8] it should kind of behave and have all of [u8] functions, but on the other hand I had to implement PartialEq

@apoelstra
Copy link
Member

We should also implement Eq because sometimes you need that explicitly (e.g. when using as a hashmap key) and AsRef is not sufficient. Index is probably fine to just leave implicit.

I have a test case now (communication encryption with a Coldcard wallet) so I will try to use this PR for that today and report back.

@apoelstra
Copy link
Member

Tested the code; I'm happy with the API and it works (I can communicate with the Coldcard in an encrypted fashion.) Will review in detail tomorrow.

@stevenroose
Copy link
Contributor

ACK d2c2e5d did some testing locally, but CI is probably more complete

stevenroose
stevenroose previously approved these changes Dec 5, 2019
Copy link
Collaborator

@real-or-random real-or-random 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 overall! 👍

src/ecdh.rs Show resolved Hide resolved
src/ecdh.rs Outdated

#[test]
#[should_panic(expected = "This is UB")]
// This test is technically underined behavior. but it's interesting to see if the behavior changes.
Copy link
Collaborator

Choose a reason for hiding this comment

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

s/underined/undefined

Copy link
Collaborator

Choose a reason for hiding this comment

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

Is it really a good idea to have this test? I mean, it could fail on other C implementations even, no?

Copy link
Member Author

@elichai elichai Dec 6, 2019

Choose a reason for hiding this comment

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

Yeah you're right. It was mainly me curious when/where/if this test will ever get broken (even though it's UB it's an indicator of the current instability of unwrapping through FFI, there are some proposals to just make this abort or something similar.

Copy link
Member

Choose a reason for hiding this comment

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

Heh, yeah, we should probably remove this. Or maybe comment it out or something.

&self.0[index]
/// Set the length of the object.
pub(crate) fn set_len(&mut self, len: usize) {
self.len = len;
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if this should be marked unsafe. It is not technically unsafe, in that we're using safe code so it won't allow access to uninitialized memory or OOB access, but it seems a bit footgunny

src/ecdh.rs Outdated
where F: FnMut([u8; 32], [u8; 32]) -> SharedSecret
{
let mut ss = SharedSecret::empty();
let hashfp: ffi::EcdhHashFn = hash_callback::<F>;
Copy link
Member

Choose a reason for hiding this comment

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

This is really a fascinating use of Rust's type system. I did not know that generics and FFI would be allowed to interact like this.

src/ecdh.rs Outdated
&mut hash_function as *mut F as *mut c_void,
)
};
debug_assert!(res >= 16); // 128 bit is the minimum for a secure hash function and the minimum we let users.
Copy link
Member

Choose a reason for hiding this comment

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

I like this, though of course users are welcome to bypass it by calling set_len (or just copying into a slice and truncating) on the result.

@apoelstra
Copy link
Member

ACK except that I would like you to remove the UB test.

(The other nits would be nice to fix, but no big deal.)

@apoelstra
Copy link
Member

ping @elichai

@elichai
Copy link
Member Author

elichai commented Dec 9, 2019

@apoelstra Done

Copy link
Member

@apoelstra apoelstra left a comment

Choose a reason for hiding this comment

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

thanks!

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.

5 participants