Skip to content

Incorrect Hash/Eq implementation for BoxedUint #1031

@elichai

Description

@elichai

The Hash docs says the following:

§ Hash and Eq
When implementing both Hash and Eq, it is important that the following property holds:

k1 == k2 -> hash(k1) == hash(k2)

In other words, if two keys are equal, their hashes must also be equal. HashMap and HashSet both rely on this behavior.

Thankfully, you won’t need to worry about upholding this property when deriving both Eq and Hash with #[derive(PartialEq, Eq, Hash)].

Violating this property is a logic error. The behavior resulting from a logic error is not specified, but users of the trait must ensure that such logic errors do not result in undefined behavior. This means that unsafe code must not rely on the correctness of these methods.

But the current Hash implementation from: #350 doesn't align with the current PartialEq implementation.

Using the following code: (works on 64/32 bit machines), Tested on version 0.7.0-rc.10

use std::hash::{DefaultHasher, Hash, Hasher};
use crypto_bigint::BoxedUint;

fn hash(hasher: &DefaultHasher, value: &BoxedUint) -> u64 {
    let mut hasher = hasher.clone();
    value.hash(&mut hasher);
    hasher.finish()
}

fn main() {
    let hasher = DefaultHasher::new();

    let a = BoxedUint::from_words_with_precision([1], 64);
    let b = BoxedUint::from_words_with_precision([1], 128);

    assert_eq!(a, b); // Passes
    let hash_a = hash(&hasher, &a);
    let hash_b = hash(&hasher, &b);
    assert_eq!(hash_a, hash_b);  //assertion `left == right` failed left: 7912899488978770657, right: 6739742464691574241
}

Using a custom Hasher we can inspect the difference:

assertion `left == right` failed
  left: [1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
 right: [2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Buffer Hasher
use std::hash::{Hash, Hasher};
use crypto_bigint::BoxedUint;

struct HasherBytes(Vec<u8>);
impl HasherBytes {
  fn new() -> Self {
      Self(Vec::new())
  }
}

impl Hasher for HasherBytes {
  fn finish(&self) -> u64 {
      unimplemented!()
  }
  fn write(&mut self, bytes: &[u8]) {
      self.0.extend_from_slice(bytes);
  }
}

fn hash_bytes(value: &BoxedUint) -> Vec<u8> {
  let mut hasher = HasherBytes::new();
  value.hash(&mut hasher);
  hasher.0
}

fn main() {
  let a = BoxedUint::from_words_with_precision([1], 64);
  let b = BoxedUint::from_words_with_precision([1], 128);

  assert_eq!(a, b);
  let hash_a = hash_bytes(&a);
  let hash_b = hash_bytes(&b);
  assert_eq!(hash_a, hash_b);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions