use super::util::hex_encode;
use crate::compiler::function::EnumVariant;
use crate::compiler::prelude::*;
use crate::value;
use sha3::{Digest, Sha3_224, Sha3_256, Sha3_384, Sha3_512};
static DEFAULT_VARIANT: Value = Value::from_static_str("SHA3-512");
static VARIANT_ENUM: &[EnumVariant] = &[
EnumVariant {
value: "SHA3-224",
description: "SHA3-224 algorithm",
},
EnumVariant {
value: "SHA3-256",
description: "SHA3-256 algorithm",
},
EnumVariant {
value: "SHA3-384",
description: "SHA3-384 algorithm",
},
EnumVariant {
value: "SHA3-512",
description: "SHA3-512 algorithm",
},
];
const PARAMETERS: &[Parameter] = &[
Parameter::required(
"value",
kind::BYTES,
"The string to calculate the hash for.",
),
Parameter::optional(
"variant",
kind::BYTES,
"The variant of the algorithm to use.",
)
.default(&DEFAULT_VARIANT)
.enum_variants(VARIANT_ENUM),
];
fn sha3_hex(value: &[u8], variant: &[u8]) -> bytestring::ByteString {
match variant {
b"SHA3-224" => hex_encode::<56>(Sha3_224::digest(value)),
b"SHA3-256" => hex_encode::<64>(Sha3_256::digest(value)),
b"SHA3-384" => hex_encode::<96>(Sha3_384::digest(value)),
b"SHA3-512" => hex_encode::<128>(Sha3_512::digest(value)),
_ => unreachable!("enum invariant"),
}
}
fn sha3(value: Value, variant: &Bytes) -> Resolved {
let value = value.try_bytes()?;
Ok(Value::String(sha3_hex(&value, variant)))
}
fn variants() -> Vec<Value> {
vec![
value!("SHA3-224"),
value!("SHA3-256"),
value!("SHA3-384"),
value!("SHA3-512"),
]
}
#[derive(Clone, Copy, Debug)]
pub struct Sha3;
impl Function for Sha3 {
fn identifier(&self) -> &'static str {
"sha3"
}
fn usage(&self) -> &'static str {
"Calculates a [SHA-3](https://en.wikipedia.org/wiki/SHA-3) hash of the `value`."
}
fn category(&self) -> &'static str {
Category::Cryptography.as_ref()
}
fn return_kind(&self) -> u16 {
kind::BYTES
}
fn parameters(&self) -> &'static [Parameter] {
PARAMETERS
}
fn examples(&self) -> &'static [Example] {
&[
example! {
title: "Calculate sha3 hash using default variant",
source: r#"sha3("foobar")"#,
result: Ok(
"ff32a30c3af5012ea395827a3e99a13073c3a8d8410a708568ff7e6eb85968fccfebaea039bc21411e9d43fdb9a851b529b9960ffea8679199781b8f45ca85e2",
),
},
example! {
title: "Calculate sha3 hash with SHA3-224",
source: r#"sha3("foo", variant: "SHA3-224")"#,
result: Ok("f4f6779e153c391bbd29c95e72b0708e39d9166c7cea51d1f10ef58a"),
},
example! {
title: "Calculate sha3 hash with SHA3-384",
source: r#"sha3("foobar", "SHA3-384")"#,
result: Ok(
"0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8",
),
},
]
}
fn compile(
&self,
state: &state::TypeState,
_ctx: &mut FunctionCompileContext,
arguments: ArgumentList,
) -> Compiled {
let value = arguments.required("value");
let variant = arguments
.optional_enum("variant", &variants(), state)?
.unwrap_or_else(|| DEFAULT_VARIANT.clone())
.try_bytes()
.expect("variant not bytes");
if let Some(val) = value.resolve_constant(state)
&& let Ok(bytes) = val.try_bytes()
{
Ok(Box::new(crate::compiler::expression::Literal::from(
sha3_hex(&bytes, &variant),
)))
} else {
Ok(Sha3Fn { value, variant }.as_expr())
}
}
}
#[derive(Debug, Clone)]
struct Sha3Fn {
value: Box<dyn Expression>,
variant: Bytes,
}
impl FunctionExpression for Sha3Fn {
fn resolve(&self, ctx: &mut Context) -> Resolved {
let value = self.value.resolve(ctx)?;
let variant = &self.variant;
sha3(value, variant)
}
fn type_def(&self, _: &state::TypeState) -> TypeDef {
TypeDef::bytes().infallible()
}
}
#[cfg(test)]
mod tests {
use super::*;
test_function![
sha3 => Sha3;
sha3 {
args: func_args![value: "foo"],
want: Ok("4bca2b137edc580fe50a88983ef860ebaca36c857b1f492839d6d7392452a63c82cbebc68e3b70a2a1480b4bb5d437a7cba6ecf9d89f9ff3ccd14cd6146ea7e7"),
tdef: TypeDef::bytes().infallible(),
}
sha3_224 {
args: func_args![value: "foo"],
want: Ok("4bca2b137edc580fe50a88983ef860ebaca36c857b1f492839d6d7392452a63c82cbebc68e3b70a2a1480b4bb5d437a7cba6ecf9d89f9ff3ccd14cd6146ea7e7"),
tdef: TypeDef::bytes().infallible(),
}
sha3_256 {
args: func_args![value: "foo",
variant: "SHA3-256"
],
want: Ok("76d3bc41c9f588f7fcd0d5bf4718f8f84b1c41b20882703100b9eb9413807c01"),
tdef: TypeDef::bytes().infallible(),
}
sha3_384 {
args: func_args![value: "foo",
variant: "SHA3-384"
],
want: Ok("665551928d13b7d84ee02734502b018d896a0fb87eed5adb4c87ba91bbd6489410e11b0fbcc06ed7d0ebad559e5d3bb5"),
tdef: TypeDef::bytes().infallible(),
}
sha3_512 {
args: func_args![value: "foo",
variant: "SHA3-512"
],
want: Ok("4bca2b137edc580fe50a88983ef860ebaca36c857b1f492839d6d7392452a63c82cbebc68e3b70a2a1480b4bb5d437a7cba6ecf9d89f9ff3ccd14cd6146ea7e7"),
tdef: TypeDef::bytes().infallible(),
}
];
#[test]
fn test_sha3_compiles_to_literal() {
use crate::compiler::CompileConfig;
let state = state::TypeState::default();
let mut ctx = FunctionCompileContext::new(Span::default(), CompileConfig::default());
let mut args = ArgumentList::default();
args.insert("value", Value::from("foo").into());
let expr = Sha3.compile(&state, &mut ctx, args).unwrap();
assert_eq!(
expr.resolve_constant(&state),
Some(Value::from(
"4bca2b137edc580fe50a88983ef860ebaca36c857b1f492839d6d7392452a63c82cbebc68e3b70a2a1480b4bb5d437a7cba6ecf9d89f9ff3ccd14cd6146ea7e7"
))
);
}
#[test]
fn test_sha3_compiles_dynamic() {
use crate::compiler::CompileConfig;
use crate::compiler::expression::Variable;
use crate::compiler::parser::Ident;
let mut state = state::TypeState::default();
state.local.insert_variable(
Ident::new("foo"),
type_def::Details {
type_def: TypeDef::bytes(),
value: None,
},
);
let mut ctx = FunctionCompileContext::new(Span::default(), CompileConfig::default());
let var = Variable::new((0, 0).into(), Ident::new("foo"), &state.local).unwrap();
let mut args = ArgumentList::default();
args.insert("value", var.into());
let expr = Sha3.compile(&state, &mut ctx, args).unwrap();
assert!(expr.resolve_constant(&state).is_none());
}
}