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

Make comparison functions stable #518

Merged
merged 8 commits into from Nov 21, 2022

Conversation

tcharding
Copy link
Member

@tcharding tcharding commented Nov 17, 2022

Supersedes #515

The primary aim of this PR is to fix the fact that we currently implement various comparison traits (Ord, PartialEq) by comparing the inner byte arrays. These bytes are meant to be opaque and are not guaranteed across versions of libsecp256k1.

This PR is a bit involved because I had to detangle all the various types (across both secp256k1 and secp256k1-sys) that use the impl_array_newtype macro.

  • Patch 1: is trivial cleanup
  • Patch 2: Step one of the PR is duplicating the macro into both crates so we can tease them apart.
  • Patch 3-5: Are cleanup of the now duplicated impl_array_newtype macros
  • Patch 6: Is the meat and potatoes
  • Patch 7,8: Further minor clean ups to the macro

I had a lot of fun with this PR, I hope you enjoy it too.

Fix: #463

@tcharding tcharding changed the title 11 17 impl array newtype Make comparison functions stable Nov 17, 2022
@tcharding
Copy link
Member Author

Will fix Rust 1.41.1 problems tomorrow.

@apoelstra
Copy link
Member

Needs rebase.

Inline with UNIX convention add a trailing newline to file.
The two crates `secp256k1` and `secp256k1-sys` serve very different
purposes, having a macro defined in one that is used in both makes it
hard to get nuanced things correct in the macro, for example the
comparison implementations (`Ord`, `PartialEq` etc.) are semantically
different in each crate.

In an effort to decouple `secp256k1` and `secp256k1-sys` duplicate the
`impl_array_newtype` macro.
For interfacing with the FFI layer we implement `ffi::CPtr`, there is
not need to provide methods `as_ptr` and `as_mut_ptr` as well.
The leading colons are an artifact of Rust 1.29, remove them.
An array in Rust has no concept of length, it is a fixed size data type.
Equally an array cannot be "empty", again since it is a fixed size data
type. These are methods/concepts seen in slices and vectors.

Remove the `len` and `is_empty` methods.
@tcharding
Copy link
Member Author

tcharding commented Nov 17, 2022

Rebased and fixed Rust 1.41.1 stuff which meant removing derives and implementing them, when fuzzing, in impl_array_newtype. Also, please note that I had to patch recovery.rs even after you asked me not to touch that module because of the changes to the macro.

Currently we rely on the inner bytes with types that are passed across
the FFI boundry when implementing comparison functions (e.g. `Ord`,
`PartialEq`), this is incorrect because the bytes are opaque, meaning
the byte layout is not guaranteed across versions of `libsecp26k1`.

Implement stable comparison functionality by doing:

- Implement `core::cmp` traits by first coercing the data into a stable
  form e.g., by serializing it.
- Add fast comparison methods to `secp256k1-sys` types that wrap types
  from libsecp, add similar methods to types in `secp256k1` that wrap
  `secp256k1-sys` types (just call through to inner type).
- In `secp256k1-sys` feature gate the new `core::cmp` impls on
  `not(fuzzing)`, when fuzzing just derive the impls instead.

Any additional methods added to `secp256k1-sys` types are private,
justified by the fact the -sys is meant to be just a thin wrapper around
libsecp256k1, we don't want to commit to supporting additional API
functions.

Please note, the solution presented in this patch is already present for
`secp256k1::PublicKey`, this PR removes that code in favour of deriving
traits that then call down to the same logic in `secp256k1-sys`.
There is no obvious reason why not to derive `Copy` and `Clone` for
types that use the `impl_newtype_macro`. Derives are less surprising so
deriving makes the code marginally easier to read.
These two traits are related, in as much as they both give access to the
inner byte array. Put them next to each other to assist clarity.
@apoelstra
Copy link
Member

As an semi-related thing, all this

let mut Thing(ref thing) = x`

crap is because I didn't know about the .0 notation when I wrote these macros, in 2014. We could probably simplify a lot of them.

@apoelstra
Copy link
Member

apoelstra commented Nov 18, 2022

I don't think we should implement Copy on secret key types (SecretKey and KeyPair). Partly to encourage users to pass by reference, minimizing the number of copies of these objects that rustc makes in memory, and partly because eventually we probably want to implement Drop in a way that zeroizes them. If we impl Drop we'll have to get rid of Copy and then that'll be a breaking change.

The reason we haven't done any of this yet is that it's impossible to get right; see this blog post by benma where he eventually starts boxing secret data, which we can't do without requiring alloc :/. But maybe we should just bite the bullet and do something even if it's an incomplete solution.

@apoelstra
Copy link
Member

Done reviewing 9850550. This looks great! The only thing blocking an ACK is the secret key Copy thing.

@tcharding
Copy link
Member Author

Thanks for review man, will respin without Copy.

@apoelstra apoelstra mentioned this pull request Nov 18, 2022
@tcharding
Copy link
Member Author

Quick Rust question please, while implementing the suggestion to remove Copy the compiler pushed me to add a bunch of calls to clone, this made be realise that I do not know how many copies of a secret will exist in the following code during the call to baz? If the clone inside the call to bar creates a second copy on the stack (which is my guess as to what will happen) then this is no better than implementing Copy and a second secret being on the stack as part of the function call to bar - its kind of worse because the stack frame of bar would be overwritten sooner than the stack frame of the calling function (ignoring the fact that that is main here).

#[derive(Clone, Debug)]
struct Secret([u8; 32]);

impl Secret {
    pub fn foo(self) -> u8 {
        0                       // Some meaningful use of secret.
    }

    pub fn baz(&self) {
        // Some function that borrows self.
    }
}

pub fn bar(x: u8) {
    println!("got {}", x);
}

fn main() {
    let s = Secret([0_u8; 32]);
    bar(s.clone().foo());

    s.baz()
}

I guess the main takeaway here is that if I implement changes designed around secrets then its going to be guess work at best without me putting in a decent amount of time and thought.

@apoelstra
Copy link
Member

@tcharding are we taking SecretKey by value in a lot of places? Even prior to this PR? How did we get by without Copy?

@apoelstra
Copy link
Member

Oh, I see, we already have Copy on the secret types through impl_array_newtype. Jeez, that macro was hiding some pretty dangerous stuff.

In that case I don't think the Copy thing has anything to do with this PR. We can revisit it in a later iteration.

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.

ACK 9850550

@apoelstra apoelstra merged commit d5065cc into rust-bitcoin:master Nov 21, 2022
@tcharding
Copy link
Member Author

Jeez, that macro was hiding some pretty dangerous stuff.

For real. I'm keen to work on the secret stuff at some stage but I get the feeling that it will require a bunch of time spent reading assembler to make sure we know exactly what is going on where.

@tcharding tcharding deleted the 11-17-impl-array-newtype branch November 21, 2022 19:48
@Kixunil
Copy link
Collaborator

Kixunil commented Dec 2, 2022

will require a bunch of time spent reading assembler

Don't get your hopes high. Neither Rust nor C compiler makes any guarantees about the resulting instructions that would help hide the data. You may see good assembly code on your machine but on a different one the optimizer arranges the code differently and you're screwed.

However, the only way to exfiltrate secrets (apart from logic bugs zeroing won't protect you from) is to exploit UB. Rust prevents almost all UB and I think we would better spend our time at minimizing UB in unsafe code instead.

boxing secret data, which we can't do without requiring alloc

The closest you could get would be to Pin the key but I bet lot of people would become annoyed by such API and probably fork the crate. And I guess I would be the first one to do it. :)

@apoelstra
Copy link
Member

Don't worry, I've tried pinning and I couldn't even deal with the ergonomic hit within this crate ;)

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.

impl_array_newtype in FFI should not autoderive std::Hash
3 participants