// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

// SHA-512 (FIPS 180-4). MariaDB's client_ed25519 signs the challenge with
// Ed25519, whose PRF is SHA-512; the driver otherwise only had SHA-1/SHA-256.

///|
/// The 80 SHA-512 round constants (FIPS 180-4): the cube-root fractional parts of
/// the first 80 primes, generated not transcribed.
let sha512_k : Array[UInt64] = [
  0x428A2F98D728AE22UL, 0x7137449123EF65CDUL, 0xB5C0FBCFEC4D3B2FUL, 0xE9B5DBA58189DBBCUL,
  0x3956C25BF348B538UL, 0x59F111F1B605D019UL, 0x923F82A4AF194F9BUL, 0xAB1C5ED5DA6D8118UL,
  0xD807AA98A3030242UL, 0x12835B0145706FBEUL, 0x243185BE4EE4B28CUL, 0x550C7DC3D5FFB4E2UL,
  0x72BE5D74F27B896FUL, 0x80DEB1FE3B1696B1UL, 0x9BDC06A725C71235UL, 0xC19BF174CF692694UL,
  0xE49B69C19EF14AD2UL, 0xEFBE4786384F25E3UL, 0x0FC19DC68B8CD5B5UL, 0x240CA1CC77AC9C65UL,
  0x2DE92C6F592B0275UL, 0x4A7484AA6EA6E483UL, 0x5CB0A9DCBD41FBD4UL, 0x76F988DA831153B5UL,
  0x983E5152EE66DFABUL, 0xA831C66D2DB43210UL, 0xB00327C898FB213FUL, 0xBF597FC7BEEF0EE4UL,
  0xC6E00BF33DA88FC2UL, 0xD5A79147930AA725UL, 0x06CA6351E003826FUL, 0x142929670A0E6E70UL,
  0x27B70A8546D22FFCUL, 0x2E1B21385C26C926UL, 0x4D2C6DFC5AC42AEDUL, 0x53380D139D95B3DFUL,
  0x650A73548BAF63DEUL, 0x766A0ABB3C77B2A8UL, 0x81C2C92E47EDAEE6UL, 0x92722C851482353BUL,
  0xA2BFE8A14CF10364UL, 0xA81A664BBC423001UL, 0xC24B8B70D0F89791UL, 0xC76C51A30654BE30UL,
  0xD192E819D6EF5218UL, 0xD69906245565A910UL, 0xF40E35855771202AUL, 0x106AA07032BBD1B8UL,
  0x19A4C116B8D2D0C8UL, 0x1E376C085141AB53UL, 0x2748774CDF8EEB99UL, 0x34B0BCB5E19B48A8UL,
  0x391C0CB3C5C95A63UL, 0x4ED8AA4AE3418ACBUL, 0x5B9CCA4F7763E373UL, 0x682E6FF3D6B2B8A3UL,
  0x748F82EE5DEFB2FCUL, 0x78A5636F43172F60UL, 0x84C87814A1F0AB72UL, 0x8CC702081A6439ECUL,
  0x90BEFFFA23631E28UL, 0xA4506CEBDE82BDE9UL, 0xBEF9A3F7B2C67915UL, 0xC67178F2E372532BUL,
  0xCA273ECEEA26619CUL, 0xD186B8C721C0C207UL, 0xEADA7DD6CDE0EB1EUL, 0xF57D4F7FEE6ED178UL,
  0x06F067AA72176FBAUL, 0x0A637DC5A2C898A6UL, 0x113F9804BEF90DAEUL, 0x1B710B35131C471BUL,
  0x28DB77F523047D84UL, 0x32CAAB7B40C72493UL, 0x3C9EBE0A15C9BEBCUL, 0x431D67C49C100D4CUL,
  0x4CC5D4BECB3E42B6UL, 0x597F299CFC657E2AUL, 0x5FCB6FAB3AD6FAECUL, 0x6C44198C4A475817UL,
]

///|
fn rotr64(x : UInt64, n : Int) -> UInt64 {
  (x >> n) | (x << (64 - n))
}

///|
/// SHA-512 (FIPS 180-4), 64-bit words over 80 rounds. Messages here are far under
/// 2^64 bits, so the 128-bit length field's high half is always zero.
pub fn sha512(msg : Bytes) -> Bytes {
  let mut h0 : UInt64 = 0x6A09E667F3BCC908UL
  let mut h1 : UInt64 = 0xBB67AE8584CAA73BUL
  let mut h2 : UInt64 = 0x3C6EF372FE94F82BUL
  let mut h3 : UInt64 = 0xA54FF53A5F1D36F1UL
  let mut h4 : UInt64 = 0x510E527FADE682D1UL
  let mut h5 : UInt64 = 0x9B05688C2B3E6C1FUL
  let mut h6 : UInt64 = 0x1F83D9ABFB41BD6BUL
  let mut h7 : UInt64 = 0x5BE0CD19137E2179UL
  let bitlen = msg.length().to_uint64() * 8UL
  let buf = Buffer()
  buf.write_bytes(msg[:])
  buf.write_byte(b'\x80')
  while buf.length() % 128 != 112 {
    buf.write_byte(b'\x00')
  }
  for _high = 0; _high < 8; _high = _high + 1 {
    buf.write_byte(b'\x00')
  }
  for i = 7; i >= 0; i = i - 1 {
    buf.write_byte(((bitlen >> (i * 8)) & 0xFF).to_byte())
  }
  let data = buf.to_bytes()
  let w : Array[UInt64] = Array::make(80, 0UL)
  let nblocks = data.length() / 128
  for b = 0; b < nblocks; b = b + 1 {
    let off = b * 128
    for i = 0; i < 16; i = i + 1 {
      let j = off + i * 8
      w[i] = (data[j].to_int().to_uint64() << 56) |
        (data[j + 1].to_int().to_uint64() << 48) |
        (data[j + 2].to_int().to_uint64() << 40) |
        (data[j + 3].to_int().to_uint64() << 32) |
        (data[j + 4].to_int().to_uint64() << 24) |
        (data[j + 5].to_int().to_uint64() << 16) |
        (data[j + 6].to_int().to_uint64() << 8) |
        data[j + 7].to_int().to_uint64()
    }
    for i = 16; i < 80; i = i + 1 {
      let s0 = rotr64(w[i - 15], 1) ^ rotr64(w[i - 15], 8) ^ (w[i - 15] >> 7)
      let s1 = rotr64(w[i - 2], 19) ^ rotr64(w[i - 2], 61) ^ (w[i - 2] >> 6)
      w[i] = w[i - 16] + s0 + w[i - 7] + s1
    }
    let mut a = h0
    let mut bb = h1
    let mut c = h2
    let mut d = h3
    let mut e = h4
    let mut f = h5
    let mut g = h6
    let mut hh = h7
    for i = 0; i < 80; i = i + 1 {
      let s1 = rotr64(e, 14) ^ rotr64(e, 18) ^ rotr64(e, 41)
      let ch = (e & f) ^ (e.lnot() & g)
      let t1 = hh + s1 + ch + sha512_k[i] + w[i]
      let s0 = rotr64(a, 28) ^ rotr64(a, 34) ^ rotr64(a, 39)
      let maj = (a & bb) ^ (a & c) ^ (bb & c)
      let t2 = s0 + maj
      hh = g
      g = f
      f = e
      e = d + t1
      d = c
      c = bb
      bb = a
      a = t1 + t2
    }
    h0 = h0 + a
    h1 = h1 + bb
    h2 = h2 + c
    h3 = h3 + d
    h4 = h4 + e
    h5 = h5 + f
    h6 = h6 + g
    h7 = h7 + hh
  }
  let out = Buffer()
  for hv in [h0, h1, h2, h3, h4, h5, h6, h7] {
    for k = 7; k >= 0; k = k - 1 {
      out.write_byte(((hv >> (k * 8)) & 0xFF).to_byte())
    }
  }
  out.to_bytes()
}