Skip to main content

dkls23_core/utilities/
hashes.rs

1//! Functions relating hashes and byte conversions.
2//!
3//! Each subprotocol uses a different random oracle via [`tagged_hash`]
4//! and fixed tags in `utilities::oracle_tags`.
5
6use elliptic_curve::ops::Reduce;
7use elliptic_curve::{CurveArithmetic, FieldBytes};
8use rustcrypto_group::GroupEncoding;
9use sha2::{Digest, Sha256};
10
11use crate::SECURITY;
12
13/// Represents the output of the hash function.
14///
15/// We are using SHA-256, so the hash values have 256 bits.
16pub 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/// Length-delimited tagged hash with result in bytes.
28///
29/// Encoding is:
30/// `len(tag)||tag||len(component_0)||component_0||...`.
31#[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/// Length-delimited tagged hash with result as a scalar.
43#[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
52/// Reduce a 32-byte hash output into a scalar for curve `C`.
53///
54/// Uses `Reduce<FieldBytes<C>>` which is already a bound on `CurveArithmetic::Scalar`.
55fn 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/// Converts a `Scalar` to bytes.
67///
68/// The scalar is represented by an integer.
69/// This function writes this integer as a byte array.
70#[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/// Converts a point on the elliptic curve to bytes.
77///
78/// Apart from the point at infinity, it computes the compressed
79/// representation of `point`.
80#[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    /// Tests if [`scalar_to_bytes`] converts a `Scalar`
106    /// in the expected way.
107    #[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    /// Tests if [`point_to_bytes`] indeed returns the compressed
120    /// representation of a point on the elliptic curve.
121    #[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}