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

Hashing functionality needed to support Fiat-Shamir #993

Merged
merged 11 commits into from
Apr 2, 2024
171 changes: 171 additions & 0 deletions ipa-core/src/protocol/ipa_prf/malicious_security/hashing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
use std::convert::Infallible;

use generic_array::GenericArray;
use sha2::{
digest::{Output, OutputSizeUser},
Digest, Sha256,
};

use crate::{
ff::{Field, Serializable},
protocol::prss::FromRandomU128,
};

#[derive(Debug, PartialEq)]
pub struct Hash(Output<Sha256>);
benjaminsavage marked this conversation as resolved.
Show resolved Hide resolved

impl Serializable for Hash {
type Size = <Sha256 as OutputSizeUser>::OutputSize;

type DeserializationError = Infallible;

fn serialize(&self, buf: &mut GenericArray<u8, Self::Size>) {
buf.copy_from_slice(&self.0);
}

fn deserialize(buf: &GenericArray<u8, Self::Size>) -> Result<Self, Self::DeserializationError> {
Ok(Hash(*Output::<Sha256>::from_slice(buf)))
benjaminsavage marked this conversation as resolved.
Show resolved Hide resolved
}

Check warning on line 28 in ipa-core/src/protocol/ipa_prf/malicious_security/hashing.rs

View check run for this annotation

Codecov / codecov/patch

ipa-core/src/protocol/ipa_prf/malicious_security/hashing.rs#L26-L28

Added lines #L26 - L28 were not covered by tests
}

pub fn compute_hash<'a, I, S>(input: I) -> Hash
benjaminsavage marked this conversation as resolved.
Show resolved Hide resolved
where
I: IntoIterator<Item = &'a S>,
S: Serializable + 'a,
{
// set up hash
let mut sha = Sha256::new();
let mut buf = GenericArray::default();
let mut is_empty = true;
benjaminsavage marked this conversation as resolved.
Show resolved Hide resolved

// set state
for x in input {
is_empty = false;
x.serialize(&mut buf);
sha.update(&buf);
}

assert!(!is_empty, "must not provide an empty iterator");
benjaminsavage marked this conversation as resolved.
Show resolved Hide resolved
// compute hash
Hash(sha.finalize())
}

/// This function takes two hash a vector of field elements into a single field element
/// # Panics
/// does not panic
pub fn hash_to_field<F>(left: &Hash, right: &Hash) -> F
where
F: Field + FromRandomU128,
{
// set up hash
let mut sha = Sha256::new();

// set state
let mut buf = GenericArray::default();
left.serialize(&mut buf);
sha.update(buf);
right.serialize(&mut buf);
benjaminsavage marked this conversation as resolved.
Show resolved Hide resolved
sha.update(buf);

// compute hash as a field element
// ideally we would generate `hash` as a `[u8;F::Size]` and `deserialize` it to generate `r`
// however, deserialize might fail for some fields so we use `from_random_128` instead
// this results in at most 128 bits of security/collision probability rather than 256 bits as offered by `Sha256`
// for field elements of size less than 129 bits, this does not make a difference
F::from_random_u128(u128::from_le_bytes(
sha.finalize()[0..16].try_into().unwrap(),
))
}

#[cfg(all(test, unit_test))]
mod test {
benjaminsavage marked this conversation as resolved.
Show resolved Hide resolved
use rand::{thread_rng, Rng};

use super::compute_hash;
use crate::{
ff::{Fp31, Fp32BitPrime},
protocol::ipa_prf::malicious_security::hashing::hash_to_field,
};

#[test]
fn hash_changes() {
const LIST_LENGTH: usize = 5;

let mut rng = thread_rng();

let mut list: Vec<Fp31> = Vec::with_capacity(LIST_LENGTH);
for _ in 0..LIST_LENGTH {
list.push(rng.gen::<Fp31>());
}
let hash_1 = compute_hash(&list);

// modify one, randomly selected element in the list
let random_index = rng.gen::<usize>() % LIST_LENGTH;
let mut different_field_element = list[random_index];
while different_field_element == list[random_index] {
different_field_element = rng.gen::<Fp31>();
}
list[random_index] = different_field_element;

let hash_2 = compute_hash(&list);

assert_ne!(
hash_1, hash_2,
"The hash should change if the input is different"

Check warning on line 114 in ipa-core/src/protocol/ipa_prf/malicious_security/hashing.rs

View check run for this annotation

Codecov / codecov/patch

ipa-core/src/protocol/ipa_prf/malicious_security/hashing.rs#L114

Added line #L114 was not covered by tests
);

// swapping two elements should change the hash
let mut index_1 = 0;
let mut index_2 = 0;
// There is a 1 in 31 chance that these two elements are the exact same value.
// To make sure this doesn't become a flaky test, let's just pick two different
// elements to swap when that happens.
// This will be an infinite loop if all elements are the same. The chances of that
// are (1 / 31) ^ (LIST_LENGTH - 1)
// which is 1 in 923,521 when LIST_LENGTH is 5. I'm OK with that.
while list[index_1] == list[index_2] {
index_1 = rng.gen_range(0..LIST_LENGTH);
index_2 = (index_1 + rng.gen_range(1..LIST_LENGTH)) % LIST_LENGTH;
}
list.swap(index_1, index_2);

let hash_3 = compute_hash(&list);

assert_ne!(
hash_2, hash_3,
"The hash should change if two elements are swapped"

Check warning on line 136 in ipa-core/src/protocol/ipa_prf/malicious_security/hashing.rs

View check run for this annotation

Codecov / codecov/patch

ipa-core/src/protocol/ipa_prf/malicious_security/hashing.rs#L136

Added line #L136 was not covered by tests
);
}

#[test]
fn field_element_changes() {
const LIST_LENGTH: usize = 5;

let mut rng = thread_rng();

let mut left = Vec::with_capacity(LIST_LENGTH);
let mut right = Vec::with_capacity(LIST_LENGTH);
for _ in 0..LIST_LENGTH {
left.push(rng.gen::<Fp32BitPrime>());
right.push(rng.gen::<Fp32BitPrime>());
}
let r1: Fp32BitPrime = hash_to_field(&compute_hash(&left), &compute_hash(&right));
benjaminsavage marked this conversation as resolved.
Show resolved Hide resolved

// modify one, randomly selected element in the list
let random_index = rng.gen::<usize>() % LIST_LENGTH;
// There is a 1 in 2^32 chance that we generate exactly the same value and the test fails.
let modified_value = rng.gen::<Fp32BitPrime>();
if rng.gen::<bool>() {
left[random_index] = modified_value;
} else {
right[random_index] = modified_value;
}

let r2: Fp32BitPrime = hash_to_field(&compute_hash(&left), &compute_hash(&right));

assert_ne!(
r1, r2,
"any modification to either list should change the hashed field element"

Check warning on line 168 in ipa-core/src/protocol/ipa_prf/malicious_security/hashing.rs

View check run for this annotation

Codecov / codecov/patch

ipa-core/src/protocol/ipa_prf/malicious_security/hashing.rs#L168

Added line #L168 was not covered by tests
);
}
}
1 change: 1 addition & 0 deletions ipa-core/src/protocol/ipa_prf/malicious_security/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod hashing;
pub mod lagrange;
pub mod prover;
pub mod verifier;
Loading
Loading