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}