frost_bluepallas/
hasher.rs1use alloc::string::{String, ToString};
4use ark_ff::PrimeField;
5use frost_core::Field;
6use mina_hasher::{create_legacy, Hashable, Hasher, ROInput};
7
8use crate::PallasScalarField;
9
10#[derive(Clone, Debug)]
13pub(crate) struct PallasHashElement<'a> {
14 value: &'a [&'a [u8]],
15}
16
17const HASH_ELEMENT_STRING: &str = "PallasHashElement";
18
19impl Hashable for PallasHashElement<'_> {
21 type D = ();
22
23 fn to_roinput(&self) -> ROInput {
24 let mut roi = ROInput::new();
25 let count_bytes = (self.value.len() as u64).to_le_bytes();
26 roi = roi.append_bytes(&count_bytes);
27 for segment in self.value {
28 let len_bytes = (segment.len() as u64).to_le_bytes();
29 roi = roi.append_bytes(&len_bytes);
30 roi = roi.append_bytes(segment);
31 }
32
33 roi
34 }
35
36 fn domain_string(_domain_param: Self::D) -> Option<String> {
38 HASH_ELEMENT_STRING.to_string().into()
39 }
40}
41
42type Fq = <PallasScalarField as Field>::Scalar;
43
44pub fn hash_to_scalar(input: &[&[u8]]) -> Fq {
46 let wrap = PallasHashElement { value: input };
49 let mut hasher = create_legacy::<PallasHashElement>(());
50
51 Fq::from(hasher.hash(&wrap).into_bigint())
55}
56
57pub fn hash_to_array(input: &[&[u8]]) -> <PallasScalarField as frost_core::Field>::Serialization {
59 let scalar = hash_to_scalar(input);
60
61 PallasScalarField::serialize(&scalar)
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn test_hash_to_scalar_is_deterministic_and_differs() {
70 let input = &[&b"abc"[..]];
71 let s1 = hash_to_scalar(input);
72 let s2 = hash_to_scalar(input);
73 assert_eq!(s1, s2, "same input must yield same scalar");
74
75 let other = &[&b"def"[..]];
76 let s3 = hash_to_scalar(other);
77 assert_ne!(s1, s3, "different input must yield a different scalar");
78 }
79
80 #[test]
81 fn test_hash_to_array_length() {
82 let arr = hash_to_array(&[&b"hello"[..]]);
83 assert_eq!(arr.len(), 32);
85 }
86
87 #[test]
88 fn test_padding_attack_resistance() {
89 let base = &[&b"1"[..]];
90 let padded = &[&b"1"[..], &[0u8][..]];
91 let h_base = hash_to_scalar(base);
92 let h_padded = hash_to_scalar(padded);
93 assert_ne!(
94 h_base, h_padded,
95 "trailing zero-byte padding collides for this variable-length encoding",
96 );
97 }
98
99 #[test]
100 fn test_segment_boundary_collision() {
101 let a = hash_to_scalar(&[b"ab", b"c"]);
102 let b = hash_to_scalar(&[b"a", b"bc"]);
103 assert_ne!(a, b, "different segmentation can yield the same hash");
104 }
105}