Skip to content

Commit

Permalink
Implement stable comparison functionality
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
tcharding committed Nov 17, 2022
1 parent 630fc1f commit 24b3ad7
Show file tree
Hide file tree
Showing 8 changed files with 323 additions and 72 deletions.
202 changes: 202 additions & 0 deletions secp256k1-sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,59 @@ impl PublicKey {
pub fn underlying_bytes(self) -> [c_uchar; 64] {
self.0
}

/// Serializes this public key as a byte-encoded pair of values, in compressed form.
fn serialize(&self) -> [u8; 33] {
let mut buf = [0u8; 33];
let mut len = 33;
unsafe {
let ret = secp256k1_ec_pubkey_serialize(
secp256k1_context_no_precomp,
buf.as_mut_c_ptr(),
&mut len,
self,
SECP256K1_SER_COMPRESSED,
);
debug_assert_eq!(ret, 1);
debug_assert_eq!(len, 33);
};
buf
}
}

#[cfg(not(fuzzing))]
impl PartialOrd for PublicKey {
fn partial_cmp(&self, other: &PublicKey) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}

#[cfg(not(fuzzing))]
impl Ord for PublicKey {
fn cmp(&self, other: &PublicKey) -> core::cmp::Ordering {
let ret = unsafe {
secp256k1_ec_pubkey_cmp(secp256k1_context_no_precomp, self, other)
};
ret.cmp(&0i32)
}
}

#[cfg(not(fuzzing))]
impl PartialEq for PublicKey {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == core::cmp::Ordering::Equal
}
}

#[cfg(not(fuzzing))]
impl Eq for PublicKey {}

#[cfg(not(fuzzing))]
impl core::hash::Hash for PublicKey {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
let ser = self.serialize();
ser.hash(state);
}
}

/// Library-internal representation of a Secp256k1 signature
Expand Down Expand Up @@ -209,6 +262,54 @@ impl Signature {
pub fn underlying_bytes(self) -> [c_uchar; 64] {
self.0
}

/// Serializes the signature in compact format.
fn serialize(&self) -> [u8; 64] {
let mut buf = [0u8; 64];
unsafe {
let ret = secp256k1_ecdsa_signature_serialize_compact(
secp256k1_context_no_precomp,
buf.as_mut_c_ptr(),
self,
);
debug_assert!(ret == 1);
}
buf
}
}

#[cfg(not(fuzzing))]
impl PartialOrd for Signature {
fn partial_cmp(&self, other: &Signature) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}

#[cfg(not(fuzzing))]
impl Ord for Signature {
fn cmp(&self, other: &Signature) -> core::cmp::Ordering {
let this = self.serialize();
let that = other.serialize();
this.cmp(&that)
}
}

#[cfg(not(fuzzing))]
impl PartialEq for Signature {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == core::cmp::Ordering::Equal
}
}

#[cfg(not(fuzzing))]
impl Eq for Signature {}

#[cfg(not(fuzzing))]
impl core::hash::Hash for Signature {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
let ser = self.serialize();
ser.hash(state);
}
}

#[repr(C)]
Expand Down Expand Up @@ -248,6 +349,55 @@ impl XOnlyPublicKey {
pub fn underlying_bytes(self) -> [c_uchar; 64] {
self.0
}

/// Serializes this key as a byte-encoded x coordinate value (32 bytes).
fn serialize(&self) -> [u8; 32] {
let mut buf = [0u8; 32];
unsafe {
let ret = secp256k1_xonly_pubkey_serialize(
secp256k1_context_no_precomp,
buf.as_mut_c_ptr(),
self,
);
assert_eq!(ret, 1);
};
buf
}
}

#[cfg(not(fuzzing))]
impl PartialOrd for XOnlyPublicKey {
fn partial_cmp(&self, other: &XOnlyPublicKey) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}

#[cfg(not(fuzzing))]
impl Ord for XOnlyPublicKey {
fn cmp(&self, other: &XOnlyPublicKey) -> core::cmp::Ordering {
let ret = unsafe {
secp256k1_xonly_pubkey_cmp(secp256k1_context_no_precomp, self, other)
};
ret.cmp(&0i32)
}
}

#[cfg(not(fuzzing))]
impl PartialEq for XOnlyPublicKey {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == core::cmp::Ordering::Equal
}
}

#[cfg(not(fuzzing))]
impl Eq for XOnlyPublicKey {}

#[cfg(not(fuzzing))]
impl core::hash::Hash for XOnlyPublicKey {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
let ser = self.serialize();
ser.hash(state);
}
}

#[repr(C)]
Expand Down Expand Up @@ -287,6 +437,58 @@ impl KeyPair {
pub fn underlying_bytes(self) -> [c_uchar; 96] {
self.0
}

/// Creates a new compressed public key from this key pair.
fn public_key(&self) -> PublicKey {
unsafe {
let mut pk = PublicKey::new();
let ret = secp256k1_keypair_pub(
secp256k1_context_no_precomp,
&mut pk,
self,
);
debug_assert_eq!(ret, 1);
pk
}
}
}

#[cfg(not(fuzzing))]
impl PartialOrd for KeyPair {
fn partial_cmp(&self, other: &KeyPair) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}

#[cfg(not(fuzzing))]
impl Ord for KeyPair {
fn cmp(&self, other: &KeyPair) -> core::cmp::Ordering {
let this = self.public_key();
let that = other.public_key();
this.cmp(&that)
}
}

#[cfg(not(fuzzing))]
impl PartialEq for KeyPair {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == core::cmp::Ordering::Equal
}
}

#[cfg(not(fuzzing))]
impl Eq for KeyPair {}

#[cfg(not(fuzzing))]
impl core::hash::Hash for KeyPair {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
// To hash the key pair we just hash the serialized public key. Since any change to the
// secret key would also be a change to the public key this is a valid one way function from
// the key pair to the digest.
let pk = self.public_key();
let ser = pk.serialize();
ser.hash(state);
}
}

extern "C" {
Expand Down
33 changes: 33 additions & 0 deletions secp256k1-sys/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,34 @@ macro_rules! impl_array_newtype {
($thing:ident, $ty:ty, $len:expr) => {
impl Copy for $thing {}

impl $thing {
/// Like `cmp::Ord` but faster and with no guarantees across library versions.
///
/// The inner byte array of `Self` is passed across the FFI boundry, as such there are
/// no guarantees on its layout and it is subject to change across library versions,
/// even minor versions. For this reason comparison function implementations (e.g.
/// `Ord`, `PartialEq`) take measures to ensure the data will remain constant (e.g., by
/// serializing it to a guaranteed format). This means they may be slow, this function
/// provides a faster comparison if you know that your types come from the same library
/// version.
pub fn cmp_fast_unstable(&self, other: &Self) -> core::cmp::Ordering {
self[..].cmp(&other[..])
}

/// Like `cmp::Eq` but faster and with no guarantees across library versions.
///
/// The inner byte array of `Self` is passed across the FFI boundry, as such there are
/// no guarantees on its layout and it is subject to change across library versions,
/// even minor versions. For this reason comparison function implementations (e.g.
/// `Ord`, `PartialEq`) take measures to ensure the data will remain constant (e.g., by
/// serializing it to a guaranteed format). This means they may be slow, this function
/// provides a faster equality check if you know that your types come from the same
/// library version.
pub fn eq_fast_unstable(&self, other: &Self) -> bool {
self[..].eq(&other[..])
}
}

impl AsRef<[$ty; $len]> for $thing {
#[inline]
/// Gets a reference to the underlying array
Expand All @@ -28,28 +56,33 @@ macro_rules! impl_array_newtype {
}
}

#[cfg(fuzzing)]
impl PartialEq for $thing {
#[inline]
fn eq(&self, other: &$thing) -> bool {
&self[..] == &other[..]
}
}

#[cfg(fuzzing)]
impl Eq for $thing {}

#[cfg(fuzzing)]
impl core::hash::Hash for $thing {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
(&self[..]).hash(state)
}
}

#[cfg(fuzzing)]
impl PartialOrd for $thing {
#[inline]
fn partial_cmp(&self, other: &$thing) -> Option<core::cmp::Ordering> {
self[..].partial_cmp(&other[..])
}
}

#[cfg(fuzzing)]
impl Ord for $thing {
#[inline]
fn cmp(&self, other: &$thing) -> core::cmp::Ordering {
Expand Down
53 changes: 52 additions & 1 deletion secp256k1-sys/src/recovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

//! # FFI of the recovery module

use crate::{Context, Signature, NonceFn, PublicKey, CPtr, impl_array_newtype};
use crate::{Context, Signature, NonceFn, PublicKey, CPtr, impl_array_newtype, secp256k1_context_no_precomp};
use crate::types::*;
use core::fmt;

Expand All @@ -27,6 +27,23 @@ impl_array_newtype!(RecoverableSignature, c_uchar, 65);
impl RecoverableSignature {
/// Create a new (zeroed) signature usable for the FFI interface
pub fn new() -> RecoverableSignature { RecoverableSignature([0; 65]) }

/// Serializes the signature in compact format.
fn serialize(&self) -> [u8; 65] {
let mut buf = [0u8; 65];
let mut recid = 0;
unsafe {
let ret = secp256k1_ecdsa_recoverable_signature_serialize_compact(
secp256k1_context_no_precomp,
buf.as_mut_c_ptr(),
&mut recid,
self,
);
debug_assert!(ret == 1);
}
buf[64] = (recid & 0xFF) as u8;
buf
}
}

impl Default for RecoverableSignature {
Expand Down Expand Up @@ -59,6 +76,40 @@ impl fmt::Debug for RecoverableSignature {
}
}

#[cfg(not(fuzzing))]
impl PartialOrd for RecoverableSignature {
fn partial_cmp(&self, other: &RecoverableSignature) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}

#[cfg(not(fuzzing))]
impl Ord for RecoverableSignature {
fn cmp(&self, other: &RecoverableSignature) -> core::cmp::Ordering {
let this = self.serialize();
let that = other.serialize();
this.cmp(&that)
}
}

#[cfg(not(fuzzing))]
impl PartialEq for RecoverableSignature {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == core::cmp::Ordering::Equal
}
}

#[cfg(not(fuzzing))]
impl Eq for RecoverableSignature {}

#[cfg(not(fuzzing))]
impl core::hash::Hash for RecoverableSignature {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
let ser = self.serialize();
ser.hash(state);
}
}

extern "C" {
#[cfg_attr(not(rust_secp_no_symbol_renaming), link_name = "rustsecp256k1_v0_6_1_ecdsa_recoverable_signature_parse_compact")]
pub fn secp256k1_ecdsa_recoverable_signature_parse_compact(cx: *const Context, sig: *mut RecoverableSignature,
Expand Down
3 changes: 2 additions & 1 deletion src/ecdsa/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ use crate::{
};

/// An ECDSA signature
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct Signature(pub(crate) ffi::Signature);
impl_fast_comparisons!(Signature);

impl fmt::Debug for Signature {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self, f) }
Expand Down

0 comments on commit 24b3ad7

Please sign in to comment.