///|
/// Constant-time byte-string comparison for secret material (key check
/// sums, MAC tags). Length mismatch returns `false` immediately; content
/// comparison itself leaks nothing beyond the length.
pub fn ct_equal(a : BytesView, b : BytesView) -> Bool {
  if a.length() != b.length() {
    return false
  }
  let mut diff = 0
  for i in 0.. Bytes {
  let pad = size - data.length()
  if pad <= 0 {
    return data.to_owned()
  }
  let out : Array[Byte] = Array(capacity=size)
  for _ in 0.. Bytes {
  if data.length() >= size {
    return data.to_owned()
  }
  let out : Array[Byte] = Array(capacity=size)
  for b in data {
    out.push(b)
  }
  for _ in data.length().. Bytes {
  a.to_owned().add(b.to_owned())
}

///|
/// Concatenate three byte strings.
pub fn concat3(a : BytesView, b : BytesView, c : BytesView) -> Bytes {
  concat2(a, concat2(b, c))
}

///|
/// Concatenate four byte strings.
pub fn concat4(
  a : BytesView,
  b : BytesView,
  c : BytesView,
  d : BytesView,
) -> Bytes {
  concat2(concat2(a, b), concat2(c, d))
}

///|
/// XOR two equally long byte strings.
pub fn xor_bytes(a : BytesView, b : BytesView) -> Bytes raise {
  guard a.length() == b.length() else {
    @error.fail("xor_bytes: length mismatch")
  }
  Bytes::makei(a.length(), i => a[i] ^ b[i])
}

///|
/// Lowercase hex encoding (test vectors, fingerprints, key ids).
pub fn to_hex(data : BytesView) -> String {
  @hex.encode(data)
}

///|
/// Parse a lowercase/uppercase hex string.
pub fn from_hex(s : String) -> Bytes raise {
  @hex.decode(s) catch {
    _ => @error.fail("invalid hex string")
  }
}

///|
/// UTF-8 encoding of a string for PGP wire use (User IDs, passphrases,
/// literal file names). `String::to_bytes`/`Bytes::of_string` are UTF-16LE
/// and must never be used on wire material.
pub fn utf8_bytes(s : String) -> Bytes {
  @utf8.encode(s)
}