///|
/// Encode a UTF-8 string as bytes for cryptographic operations.
fn crypto_utf8(value : String) -> Bytes {
  @utf8.encode(value)
}

///|
/// Return the lowercase SHA-256 digest of a UTF-8 string.
pub fn sha256_hex(message : String) -> String {
  @crypto.bytes_to_hex_string(@crypto.sha256(crypto_utf8(message)))
}

///|
/// Return the lowercase HMAC-SHA256 digest of a UTF-8 payload.
pub fn hmac_sha256_hex(secret : String, message : String) -> String {
  let key = crypto_utf8(secret)
  let payload = crypto_utf8(message)
  @crypto.bytes_to_hex_string(
    @crypto.hmac(@crypto.SHA256::new(), key.view(), payload.view()),
  )
}

///|
/// Compare signatures without returning early on the first different byte.
/// Length is folded into the accumulated difference to avoid prefix matches.
pub fn constant_time_equal(left : String, right : String) -> Bool {
  let left_bytes = crypto_utf8(left)
  let right_bytes = crypto_utf8(right)
  let limit = if left_bytes.length() > right_bytes.length() {
    left_bytes.length()
  } else {
    right_bytes.length()
  }
  let mut difference = left_bytes.length() ^ right_bytes.length()
  for index in 0.. String {
  "sha256=" + hmac_sha256_hex(secret, body)
}