// SHA-512 (FIPS 180-4) and HMAC-SHA512 (RFC 2104), in pure MoonBit.
//
// The toolchain ships no SHA-512, and the key derivation of the encryption
// needs one: HKDF-SHA512 is what RFC 5869 is written for, and the reference
// implementation this package follows uses it as well.
//
// `UInt64` has native 64-bit wrapping arithmetic in this toolchain, so the
// algorithm needs no masking the way the 32-bit SHA-256 above it does.
//
// A word of the message schedule is built from eight bytes in big-endian
// order, and the length that is appended at the end is a 128-bit big-endian
// bit count. Everything that fits into 64 bits has a zero high half, which is
// what the code below writes.
///|
/// The eighty round constants: the first 64 bits of the fractional parts of
/// the cube roots of the first eighty primes.
fn 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,
]
}
///|
/// The eight initial state words: the first 64 bits of the fractional parts
/// of the square roots of the first eight primes.
fn sha512_h() -> Array[UInt64] {
[
0x6a09e667f3bcc908UL, 0xbb67ae8584caa73bUL, 0x3c6ef372fe94f82bUL, 0xa54ff53a5f1d36f1UL,
0x510e527fade682d1UL, 0x9b05688c2b3e6c1fUL, 0x1f83d9abfb41bd6bUL, 0x5be0cd19137e2179UL,
]
}
///|
/// Rotate a 64-bit word right by `n` bits (1 <= n <= 63).
fn rotr64(x : UInt64, n : Int32) -> UInt64 {
(x >> n) | (x << (64 - n))
}
///|
/// The state of a SHA-512 that is fed in pieces.
pub struct Sha512 {
/// The eight state words.
state : Array[UInt64]
/// The bytes that did not fill a block yet.
pending : Array[Byte]
/// The number of bytes that were fed in.
mut length : UInt64
}
///|
/// A fresh SHA-512 computation.
pub fn Sha512::Sha512() -> Sha512 {
{ state: sha512_h(), pending: [], length: 0UL, }
}
///|
/// Compress one 128-byte block of `block` into `state`.
fn sha512_compress(
state : Array[UInt64],
block : Array[Byte],
off : Int32,
) -> Unit {
let k = sha512_k()
// The message schedule: sixteen words from the block, then sixty-four more.
let w : Array[UInt64] = Array(capacity=80)
let mut t = 0
while t < 16 {
let mut v : UInt64 = 0UL
let mut b = 0
while b < 8 {
v = (v << 8) | block[off + t * 8 + b].to_uint64()
b = b + 1
}
w.push(v)
t = t + 1
}
t = 16
while t < 80 {
let w15 = w[t - 15]
let w2 = w[t - 2]
// The small sigma is three rotations and a *shift*, not four rotations:
// sigma0(x) = ROTR1 ^ ROTR8 ^ SHR7, sigma1(x) = ROTR19 ^ ROTR61 ^ SHR6.
let s0 = rotr64(w15, 1) ^ rotr64(w15, 8) ^ (w15 >> 7)
let s1 = rotr64(w2, 19) ^ rotr64(w2, 61) ^ (w2 >> 6)
w.push(w[t - 16] + s0 + w[t - 7] + s1)
t = t + 1
}
let mut a = state[0]
let mut b = state[1]
let mut c = state[2]
let mut d = state[3]
let mut e = state[4]
let mut f = state[5]
let mut g = state[6]
let mut h = state[7]
t = 0
while t < 80 {
// The big sigma uses three rotations: SIGMA0 = ROTR28 ^ ROTR34 ^ ROTR39,
// SIGMA1 = ROTR14 ^ ROTR18 ^ ROTR41.
let big0 = rotr64(a, 28) ^ rotr64(a, 34) ^ rotr64(a, 39)
let big1 = rotr64(e, 14) ^ rotr64(e, 18) ^ rotr64(e, 41)
let ch = (e & f) ^ ((e ^ 0xffffffffffffffffUL) & g)
let maj = (a & b) ^ (a & c) ^ (b & c)
let t1 = h + big1 + ch + k[t] + w[t]
let t2 = big0 + maj
h = g
g = f
f = e
e = d + t1
d = c
c = b
b = a
a = t1 + t2
t = t + 1
}
state[0] = state[0] + a
state[1] = state[1] + b
state[2] = state[2] + c
state[3] = state[3] + d
state[4] = state[4] + e
state[5] = state[5] + f
state[6] = state[6] + g
state[7] = state[7] + h
}
///|
/// Feed `data` into the computation.
pub fn Sha512::update(self : Sha512, data : Bytes) -> Unit {
let n = data.length()
self.length = self.length + n.to_uint64()
let mut i = 0
while i < n {
self.pending.push(data.at(i))
i = i + 1
if self.pending.length() == 128 {
sha512_compress(self.state, self.pending, 0)
self.pending.clear()
}
}
}
///|
/// The digest of everything that was fed in: 64 raw bytes.
pub fn Sha512::finish(self : Sha512) -> Bytes {
let bitlen = self.length * 8UL
// The padded message: the pending bytes, then 0x80, then zeros, then the
// length as a 128-bit big-endian bit count. The high half is zero for
// everything that fits into 64 bits.
self.pending.push(b'\x80')
while self.pending.length() % 128 != 112 {
self.pending.push(b'\x00')
}
let mut j = 0
while j < 8 {
self.pending.push(b'\x00')
j = j + 1
}
j = 7
while j >= 0 {
self.pending.push(((bitlen >> (j * 8)) & 0xffUL).to_byte())
j = j - 1
}
let mut off = 0
while off < self.pending.length() {
sha512_compress(self.state, self.pending, off)
off = off + 128
}
// The digest is the eight state words in big-endian order.
let out = []
for v in self.state {
let mut b = 7
while b >= 0 {
out.push(((v >> (b * 8)) & 0xffUL).to_byte())
b = b - 1
}
}
Bytes::from_array(out[:])
}
///|
/// The SHA-512 digest of `data`: 64 raw bytes.
pub fn sha512(data : Bytes) -> Bytes {
let digest = Sha512::Sha512()
digest.update(data)
digest.finish()
}
///|
/// The SHA-512 digest of `data`, lowercase hex (128 characters).
pub fn sha512_hex(data : Bytes) -> String {
bytes_to_hex(sha512(data))
}
///|
/// HMAC-SHA512 (RFC 2104) of `data` under `key`: 64 raw bytes.
///
/// A key that is longer than the block size is hashed first, a key that is
/// shorter is padded with zeros.
pub fn hmac_sha512(key : Bytes, data : Bytes) -> Bytes {
let block = 128
let keylen = key.length()
let shortened = if keylen > block { sha512(key) } else { key }
let padded = FixedArray::make(block, b'\x00')
let mut i = 0
while i < shortened.length() {
padded[i] = shortened.at(i)
i = i + 1
}
let inner = FixedArray::make(block, b'\x00')
let outer = FixedArray::make(block, b'\x00')
i = 0
while i < block {
let b = padded[i]
inner[i] = b ^ 0x36
outer[i] = b ^ 0x5c
i = i + 1
}
let inner_hash = Sha512::Sha512()
inner_hash.update(Bytes::from_array(inner[:]))
inner_hash.update(data)
let inner_digest = inner_hash.finish()
let outer_hash = Sha512::Sha512()
outer_hash.update(Bytes::from_array(outer[:]))
outer_hash.update(inner_digest)
outer_hash.finish()
}