Private/Blake2b.psm1

#!/usr/bin/env pwsh
using namespace System
using namespace System.Collections.Generic
using namespace System.Text

using module ./Utilities.psm1

class Blake2b : CryptobaseUtils {
  static [uint64[]] $Blake2bIv = @(
    [Convert]::ToUInt64('6a09e667f3bcc908',16), [Convert]::ToUInt64('bb67ae8584caa73b',16), [Convert]::ToUInt64('3c6ef372fe94f82b',16), [Convert]::ToUInt64('a54ff53a5f1d36f1',16),
    [Convert]::ToUInt64('510e527fade682d1',16), [Convert]::ToUInt64('9b05688c2b3e6c1f',16), [Convert]::ToUInt64('1f83d9abfb41bd6b',16), [Convert]::ToUInt64('5be0cd19137e2179',16)
  )

  static [byte[]] $Blake2bSigma = @(
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
    14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3,
    11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4,
    7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8,
    9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13,
    2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9,
    12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11,
    13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10,
    6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5,
    10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0
  )

  static [byte[]] ComputeHash([byte[]]$inputbytes) {
    return [Blake2b]::ComputeHash($inputbytes, 64)
  }

  static [byte[]] ComputeHash([byte[]]$inputbytes, [int]$outputLength) {
    return [Blake2b]::ComputeHash($inputbytes, $outputLength, $null)
  }

  static [byte[]] ComputeHash([byte[]]$inputbytes, [int]$outputLength, [byte[]]$key) {
    return [Blake2b]::ComputeHash($inputbytes, $outputLength, $key, $null, $null)
  }

  static [byte[]] ComputeHash([byte[]]$inputbytes, [int]$outputLength, [byte[]]$key, [byte[]]$salt, [byte[]]$personalization) {
    if ($outputLength -lt 1 -or $outputLength -gt 64) { throw [ArgumentException]::new("Output length must be between 1 and 64 bytes") }
    if ($null -ne $key -and $key.Length -gt 64) { throw [ArgumentException]::new("Key length must not exceed 64 bytes") }

    $h = [uint64[]]::new(8)
    [Array]::Copy([Blake2b]::Blake2bIv, $h, 8)

    $paramWords = [Blake2b]::GetParamWords($outputLength, ($null -eq $key ? 0 : $key.Length), $salt, $personalization)
    for ($i = 0; $i -lt 8; $i++) {
      $h[$i] = $h[$i] -bxor $paramWords[$i]
    }

    $buffer = [byte[]]::new(128)
    $bufferLength = 0
    $bytesCompressed = [uint64]0

    if ($null -ne $key -and $key.Length -gt 0) {
      [Array]::Copy($key, 0, $buffer, 0, $key.Length)
      $bufferLength = 128
    }

    $m = [uint64[]]::new(16)
    $v = [uint64[]]::new(16)

    for ($i = 0; $i -lt $inputbytes.Length; $i++) {
      if ($bufferLength -eq 128) {
        $bytesCompressed = [uint64](([bigint]$bytesCompressed + 128) -band [uint64]::MaxValue)
        [Blake2b]::Compress($h, $buffer, $bytesCompressed, $false, $m, $v)
        $bufferLength = 0
        [Array]::Clear($buffer, 0, 128)
      }
      $buffer[$bufferLength++] = $inputbytes[$i]
    }

    $bytesCompressed = [uint64](([bigint]$bytesCompressed + $bufferLength) -band [uint64]::MaxValue)
    [Blake2b]::Compress($h, $buffer, $bytesCompressed, $true, $m, $v)

    $output = [byte[]]::new($outputLength)
    for ($i = 0; $i -lt [int]($outputLength / 8); $i++) {
      [Blake2b]::WriteUInt64LE($output, $i * 8, $h[$i])
    }
    if ($outputLength % 8 -ne 0) {
      $lastBytes = [byte[]]::new(8)
      [Blake2b]::WriteUInt64LE($lastBytes, 0, $h[[int]($outputLength / 8)])
      [Array]::Copy($lastBytes, 0, $output, [int]($outputLength / 8) * 8, $outputLength % 8)
    }
    return $output
  }

  hidden static [uint64[]] GetParamWords([int]$digestSize, [int]$keyLength, [byte[]]$salt, [byte[]]$personalization) {
    $paramBytes = [byte[]]::new(64)
    $paramBytes[0] = [byte]$digestSize
    $paramBytes[1] = [byte]$keyLength
    $paramBytes[2] = 1 # FanOut
    $paramBytes[3] = 1 # Depth

    if ($null -ne $salt -and $salt.Length -gt 0) {
      [Array]::Copy($salt, 0, $paramBytes, 32, [Math]::Min(16, $salt.Length))
    }
    if ($null -ne $personalization -and $personalization.Length -gt 0) {
      [Array]::Copy($personalization, 0, $paramBytes, 48, [Math]::Min(16, $personalization.Length))
    }

    $words = [uint64[]]::new(8)
    for ($i = 0; $i -lt 8; $i++) {
      $words[$i] = [Blake2b]::ReadUInt64LE($paramBytes, $i * 8)
    }
    return $words
  }

  hidden static [void] Compress([uint64[]]$h, [byte[]]$buffer, [uint64]$bytesCompressed, [bool]$isLastBlock, [uint64[]]$m, [uint64[]]$v) {
    for ($i = 0; $i -lt 16; $i++) {
      $m[$i] = [Blake2b]::ReadUInt64LE($buffer, $i * 8)
    }

    [Array]::Copy($h, $v, 8)
    [Array]::Copy([Blake2b]::Blake2bIv, 0, $v, 8, 8)

    $v[12] = $v[12] -bxor $bytesCompressed
    # v[13] counter high bits not used here
    if ($isLastBlock) {
      $v[14] = $v[14] -bxor [uint64]::MaxValue
    }

    for ($round = 0; $round -lt 12; $round++) {
      $rowOff = ($round % 10) * 16
      [Blake2b]::G($v, 0, 4, 8, 12, $m[[Blake2b]::Blake2bSigma[$rowOff + 0]], $m[[Blake2b]::Blake2bSigma[$rowOff + 1]])
      [Blake2b]::G($v, 1, 5, 9, 13, $m[[Blake2b]::Blake2bSigma[$rowOff + 2]], $m[[Blake2b]::Blake2bSigma[$rowOff + 3]])
      [Blake2b]::G($v, 2, 6, 10, 14, $m[[Blake2b]::Blake2bSigma[$rowOff + 4]], $m[[Blake2b]::Blake2bSigma[$rowOff + 5]])
      [Blake2b]::G($v, 3, 7, 11, 15, $m[[Blake2b]::Blake2bSigma[$rowOff + 6]], $m[[Blake2b]::Blake2bSigma[$rowOff + 7]])

      [Blake2b]::G($v, 0, 5, 10, 15, $m[[Blake2b]::Blake2bSigma[$rowOff + 8]], $m[[Blake2b]::Blake2bSigma[$rowOff + 9]])
      [Blake2b]::G($v, 1, 6, 11, 12, $m[[Blake2b]::Blake2bSigma[$rowOff + 10]], $m[[Blake2b]::Blake2bSigma[$rowOff + 11]])
      [Blake2b]::G($v, 2, 7, 8, 13, $m[[Blake2b]::Blake2bSigma[$rowOff + 12]], $m[[Blake2b]::Blake2bSigma[$rowOff + 13]])
      [Blake2b]::G($v, 3, 4, 9, 14, $m[[Blake2b]::Blake2bSigma[$rowOff + 14]], $m[[Blake2b]::Blake2bSigma[$rowOff + 15]])
    }

    for ($i = 0; $i -lt 8; $i++) {
      $h[$i] = $h[$i] -bxor $v[$i] -bxor $v[$i + 8]
    }
  }

  hidden static [void] G([uint64[]]$v, [int]$a, [int]$b, [int]$c, [int]$d, [uint64]$x, [uint64]$y) {
    $v[$a] = [uint64](([bigint]$v[$a] + $v[$b] + $x) -band [uint64]::MaxValue)
    $v[$d] = [Blake2b]::RotateRight($v[$d] -xor $v[$a], 32)
    $v[$c] = [uint64](([bigint]$v[$c] + $v[$d]) -band [uint64]::MaxValue)
    $v[$b] = [Blake2b]::RotateRight($v[$b] -xor $v[$c], 24)
    $v[$a] = [uint64](([bigint]$v[$a] + $v[$b] + $y) -band [uint64]::MaxValue)
    $v[$d] = [Blake2b]::RotateRight($v[$d] -xor $v[$a], 16)
    $v[$c] = [uint64](([bigint]$v[$c] + $v[$d]) -band [uint64]::MaxValue)
    $v[$b] = [Blake2b]::RotateRight($v[$b] -xor $v[$c], 63)
  }

  hidden static [uint64] RotateRight([uint64]$value, [int]$offset) {
    return ($value -shr $offset) -bor ($value -shl (64 - $offset))
  }

  hidden static [uint64] ReadUInt64LE([byte[]]$buf, [int]$off) {
    return ([uint64]$buf[$off]) -bor
    ([uint64]$buf[$off + 1] -shl 8) -bor
    ([uint64]$buf[$off + 2] -shl 16) -bor
    ([uint64]$buf[$off + 3] -shl 24) -bor
    ([uint64]$buf[$off + 4] -shl 32) -bor
    ([uint64]$buf[$off + 5] -shl 40) -bor
    ([uint64]$buf[$off + 6] -shl 48) -bor
    ([uint64]$buf[$off + 7] -shl 56)
  }

  hidden static [void] WriteUInt64LE([byte[]]$buf, [int]$off, [uint64]$val) {
    $buf[$off] = [byte]($val -band 0xFF)
    $buf[$off + 1] = [byte](($val -shr 8) -band 0xFF)
    $buf[$off + 2] = [byte](($val -shr 16) -band 0xFF)
    $buf[$off + 3] = [byte](($val -shr 24) -band 0xFF)
    $buf[$off + 4] = [byte](($val -shr 32) -band 0xFF)
    $buf[$off + 5] = [byte](($val -shr 40) -band 0xFF)
    $buf[$off + 6] = [byte](($val -shr 48) -band 0xFF)
    $buf[$off + 7] = [byte](($val -shr 56) -band 0xFF)
  }
}