diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py index cd9cbe9bff..12a7a49894 100644 --- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py @@ -487,8 +487,6 @@ def check_sha3(self, name, capacity, rate, suffix): self.assertEqual(m._rate_bits, rate) self.assertEqual(m._suffix, suffix) - # TODO: RUSTPYTHON - @unittest.expectedFailure @requires_sha3 def test_extra_sha3(self): self.check_sha3('sha3_224', 448, 1152, b'\x06') diff --git a/crates/stdlib/src/hashlib.rs b/crates/stdlib/src/hashlib.rs index c9902eff60..08a9b86a0c 100644 --- a/crates/stdlib/src/hashlib.rs +++ b/crates/stdlib/src/hashlib.rs @@ -75,6 +75,28 @@ pub mod _hashlib { } } + const KECCAK_WIDTH_BITS: usize = 1600; + + fn keccak_suffix(name: &str) -> Option { + match name { + "sha3_224" | "sha3_256" | "sha3_384" | "sha3_512" => Some(0x06), + "shake_128" | "shake_256" => Some(0x1f), + _ => None, + } + } + + fn keccak_rate_bits(name: &str, block_size: usize) -> Option { + keccak_suffix(name).map(|_| block_size * 8) + } + + fn keccak_capacity_bits(name: &str, block_size: usize) -> Option { + keccak_rate_bits(name, block_size).map(|rate| KECCAK_WIDTH_BITS - rate) + } + + fn missing_hash_attribute(vm: &VirtualMachine, class_name: &str, attr: &str) -> PyResult { + Err(vm.new_attribute_error(format!("'{class_name}' object has no attribute '{attr}'"))) + } + #[derive(FromArgs)] #[allow(unused)] struct XofDigestArgs { @@ -132,6 +154,32 @@ pub mod _hashlib { self.ctx.read().block_size() } + #[pygetset] + fn _capacity_bits(&self, vm: &VirtualMachine) -> PyResult { + let block_size = self.ctx.read().block_size(); + match keccak_capacity_bits(&self.name, block_size) { + Some(capacity) => Ok(capacity), + None => missing_hash_attribute(vm, "HASH", "_capacity_bits"), + } + } + + #[pygetset] + fn _rate_bits(&self, vm: &VirtualMachine) -> PyResult { + let block_size = self.ctx.read().block_size(); + match keccak_rate_bits(&self.name, block_size) { + Some(rate) => Ok(rate), + None => missing_hash_attribute(vm, "HASH", "_rate_bits"), + } + } + + #[pygetset] + fn _suffix(&self, vm: &VirtualMachine) -> PyResult { + match keccak_suffix(&self.name) { + Some(suffix) => Ok(vec![suffix].into()), + None => missing_hash_attribute(vm, "HASH", "_suffix"), + } + } + #[pymethod] fn update(&self, data: ArgBytesLike) { data.with_ref(|bytes| self.ctx.write().update(bytes)); @@ -205,6 +253,32 @@ pub mod _hashlib { self.ctx.read().block_size() } + #[pygetset] + fn _capacity_bits(&self, vm: &VirtualMachine) -> PyResult { + let block_size = self.ctx.read().block_size(); + match keccak_capacity_bits(&self.name, block_size) { + Some(capacity) => Ok(capacity), + None => missing_hash_attribute(vm, "HASHXOF", "_capacity_bits"), + } + } + + #[pygetset] + fn _rate_bits(&self, vm: &VirtualMachine) -> PyResult { + let block_size = self.ctx.read().block_size(); + match keccak_rate_bits(&self.name, block_size) { + Some(rate) => Ok(rate), + None => missing_hash_attribute(vm, "HASHXOF", "_rate_bits"), + } + } + + #[pygetset] + fn _suffix(&self, vm: &VirtualMachine) -> PyResult { + match keccak_suffix(&self.name) { + Some(suffix) => Ok(vec![suffix].into()), + None => missing_hash_attribute(vm, "HASHXOF", "_suffix"), + } + } + #[pymethod] fn update(&self, data: ArgBytesLike) { data.with_ref(|bytes| self.ctx.write().update(bytes));