dkls23_core/utilities/
hashes.rs1use elliptic_curve::ops::Reduce;
7use elliptic_curve::{CurveArithmetic, FieldBytes};
8use rustcrypto_group::GroupEncoding;
9use sha2::{Digest, Sha256};
10
11use crate::SECURITY;
12
13pub type HashOutput = [u8; SECURITY as usize];
17
18fn hash_bytes(msg: &[u8]) -> HashOutput {
19 Sha256::digest(msg).into()
20}
21
22fn append_len_prefixed(encoded: &mut Vec<u8>, component: &[u8]) {
23 encoded.extend_from_slice(&(component.len() as u64).to_be_bytes());
24 encoded.extend_from_slice(component);
25}
26
27#[must_use]
32pub fn tagged_hash(tag: &[u8], components: &[&[u8]]) -> HashOutput {
33 let mut encoded =
34 Vec::with_capacity(8 + tag.len() + components.iter().map(|c| 8 + c.len()).sum::<usize>());
35 append_len_prefixed(&mut encoded, tag);
36 for component in components {
37 append_len_prefixed(&mut encoded, component);
38 }
39 hash_bytes(&encoded)
40}
41
42#[must_use]
44pub fn tagged_hash_as_scalar<C: CurveArithmetic>(tag: &[u8], components: &[&[u8]]) -> C::Scalar
45where
46 C::Scalar: Reduce<FieldBytes<C>>,
47{
48 let as_bytes = tagged_hash(tag, components);
49 reduce_hash_to_scalar::<C>(&as_bytes)
50}
51
52fn reduce_hash_to_scalar<C: CurveArithmetic>(hash: &HashOutput) -> C::Scalar
56where
57 C::Scalar: Reduce<FieldBytes<C>>,
58{
59 let field_bytes: &FieldBytes<C> = hash
60 .as_slice()
61 .try_into()
62 .expect("hash output length matches field size");
63 <C::Scalar as Reduce<FieldBytes<C>>>::reduce(field_bytes)
64}
65
66#[must_use]
71pub fn scalar_to_bytes<C: CurveArithmetic>(scalar: &C::Scalar) -> Vec<u8> {
72 let fb: FieldBytes<C> = (*scalar).into();
73 <FieldBytes<C> as AsRef<[u8]>>::as_ref(&fb).to_vec()
74}
75
76#[must_use]
81pub fn point_to_bytes<C: CurveArithmetic>(point: &C::AffinePoint) -> Vec<u8>
82where
83 C::AffinePoint: GroupEncoding,
84{
85 point.to_bytes().as_ref().to_vec()
86}
87
88#[cfg(test)]
89mod tests {
90
91 use super::*;
92 use crate::utilities::rng;
93 use k256::elliptic_curve::{point::AffineCoordinates, Field};
94 use k256::{AffinePoint, Scalar, Secp256k1};
95 use rand::RngExt;
96
97 #[test]
98 fn test_tagged_hash_is_length_delimited() {
99 let tag = b"tag";
100 let hash1 = tagged_hash(tag, &[b"ab", b"c"]);
101 let hash2 = tagged_hash(tag, &[b"a", b"bc"]);
102 assert_ne!(hash1, hash2);
103 }
104
105 #[test]
108 fn test_scalar_to_bytes() {
109 for _ in 0..100 {
110 let number: u32 = rng::get_rng().random();
111 let scalar = Scalar::from(number);
112
113 let number_as_bytes = [vec![0u8; 28], number.to_be_bytes().to_vec()].concat();
114
115 assert_eq!(number_as_bytes, scalar_to_bytes::<Secp256k1>(&scalar));
116 }
117 }
118
119 #[test]
122 fn test_point_to_bytes() {
123 for _ in 0..100 {
124 let point = (AffinePoint::GENERATOR * Scalar::random(&mut rng::get_rng())).to_affine();
125 if point == AffinePoint::IDENTITY {
126 continue;
127 }
128
129 let mut compressed_point = Vec::with_capacity(33);
130 compressed_point.push(if bool::from(point.y_is_odd()) { 3 } else { 2 });
131 compressed_point.extend_from_slice(point.x().as_ref());
132
133 assert_eq!(compressed_point, point_to_bytes::<Secp256k1>(&point));
134 }
135 }
136}