Skip to main content

script/dom/webcrypto/subtlecrypto/
turboshake_operation.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use keccak::{Keccak, State1600};
6use sponge_cursor::SpongeCursor;
7
8use crate::dom::bindings::error::Error;
9use crate::dom::subtlecrypto::{CryptoAlgorithm, SubtleTurboShakeParams};
10
11/// <https://wicg.github.io/webcrypto-modern-algos/#turboshake-operations-digest>
12pub(crate) fn digest(
13    normalized_algorithm: &SubtleTurboShakeParams,
14    message: &[u8],
15) -> Result<Vec<u8>, Error> {
16    // Step 1. Let outputLength be the outputLength member of normalizedAlgorithm.
17    let output_length = normalized_algorithm.output_length;
18
19    // Step 2. If outputLength is zero or is not a multiple of 8, then throw an OperationError.
20    if output_length == 0 || !output_length.is_multiple_of(8) {
21        return Err(Error::Operation(Some(
22            "The outputLength is zero or is not a multiple of 8".to_string(),
23        )));
24    }
25
26    // Step 3. Let domainSeparation be the domainSeparation member of normalizedAlgorithm if
27    // present, or 0x1F otherwise.
28    let domain_separation = normalized_algorithm.domain_separation.unwrap_or(0x1f);
29
30    // Step 4. If domainSeparation is less than 0x01 or greater than 0x7F, then throw an
31    // OperationError.
32    if !(0x01..=0x7f).contains(&domain_separation) {
33        return Err(Error::Operation(Some(
34            "The domainSeparation is less than 0x01 or greater than 0x7F".to_string(),
35        )));
36    }
37
38    // Step 5.
39    // If the name member of normalizedAlgorithm is a case-sensitive string match for
40    // "TurboSHAKE128":
41    //     Let result be the result of performing the TurboSHAKE128 function defined in Section 2
42    //     of [RFC9861] using message as the M input parameter, domainSeparation as the D input
43    //     parameter, and outputLength divided by 8 as the L input parameter.
44    // If the name member of normalizedAlgorithm is a case-sensitive string match for
45    // "TurboSHAKE256":
46    //     Let result be the result of performing the TurboSHAKE256 function defined in Section 2
47    //     of [RFC9861] using message as the M input parameter, domainSeparation as the D input
48    //     parameter, and outputLength divided by 8 as the L input parameter.
49    // Step 6. If performing the operation results in an error, then throw an OperationError.
50    let mut result = vec![0u8; output_length as usize / 8];
51    match normalized_algorithm.name {
52        CryptoAlgorithm::TurboShake128 => {
53            let hasher = TurboShake::<168>::new(domain_separation)?;
54            hasher.hash(message, &mut result);
55        },
56        CryptoAlgorithm::TurboShake256 => {
57            let hasher = TurboShake::<136>::new(domain_separation)?;
58            hasher.hash(message, &mut result);
59        },
60        algorithm_name => {
61            return Err(Error::NotSupported(Some(format!(
62                "{} is not a TurboSHAKE algorithm",
63                algorithm_name.as_str()
64            ))));
65        },
66    }
67
68    // Step 7. Return result.
69    Ok(result)
70}
71
72/// Keccak rounds for TurboSHAKE
73const ROUNDS: usize = 12;
74
75/// TurboSHAKE hasher. RATE must be either 168 for TurboSHAKE128 or 136 for TurboSHAKE256.
76struct TurboShake<const RATE: usize> {
77    state: State1600,
78    cursor: SpongeCursor<RATE>,
79    keccak: Keccak,
80    domain_separation: u8,
81}
82
83impl<const RATE: usize> TurboShake<RATE> {
84    fn new(domain_separation: u8) -> Result<TurboShake<RATE>, Error> {
85        if RATE != 168 && RATE != 136 {
86            return Err(Error::NotSupported(Some(
87                "Invalid sponge cursor rate for TurboSHAKE".into(),
88            )));
89        }
90        if domain_separation == 0x00 || domain_separation > 0x7f {
91            return Err(Error::NotSupported(Some(
92                "Invalid TurboSHAKE domain separation".into(),
93            )));
94        }
95        Ok(TurboShake {
96            state: Default::default(),
97            cursor: Default::default(),
98            keccak: Default::default(),
99            domain_separation,
100        })
101    }
102
103    fn hash(mut self, message: &[u8], result: &mut [u8]) {
104        // Digest message
105        //
106        // Reference implementation from RustCrypto:
107        // <https://docs.rs/turboshake/0.7.1/src/turboshake/lib.rs.html#60-64>
108        self.keccak.with_p1600::<ROUNDS>(|p1600| {
109            self.cursor.absorb_u64_le(&mut self.state, p1600, message);
110        });
111
112        // Finalize
113        //
114        // Reference implementation from RustCrypto:
115        // <https://docs.rs/turboshake/0.7.1/src/turboshake/lib.rs.html#67-91>
116        let position = self.cursor.pos();
117        self.state[position / 8] ^= (self.domain_separation as u64) << (8 * (position % 8));
118        self.state[RATE / 8 - 1] ^= 1 << 63;
119
120        // Read result
121        //
122        // Reference implementation from RustCrypto:
123        // <https://docs.rs/turboshake/0.7.1/src/turboshake/lib.rs.html#161-165>
124        self.cursor = Default::default();
125        self.keccak.with_p1600::<ROUNDS>(|p1600| {
126            self.cursor
127                .squeeze_read_u64_le(&mut self.state, p1600, result);
128        });
129    }
130}