Module: Everywhere::Blake2b

Defined in:
lib/everywhere/blake2b.rb

Overview

Pure-Ruby BLAKE2b (RFC 7693), unkeyed, variable digest length.

OpenSSL only exposes the fixed BLAKE2b512 digest, but minisign's secret-key checksum is BLAKE2b-256 — and a 2b-256 digest is NOT a truncation of 2b-512 (the output length is part of the parameter block). This implementation covers any length; hot paths (hashing a whole artifact) should still prefer Blake2b.digest64, which uses OpenSSL when available and only falls back here.

Constant Summary collapse

MASK =
0xFFFFFFFFFFFFFFFF
IV =
[
  0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1,
  0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179
].freeze
SIGMA =
[
  [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]
].freeze

Class Method Summary collapse

Class Method Details

.compress(h, block, t, last) ⇒ Object



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/everywhere/blake2b.rb', line 81

def compress(h, block, t, last)
  m = block.unpack("Q<16")
  v = h + IV
  v[12] ^= t & MASK
  v[13] ^= (t >> 64) & MASK
  v[14] ^= MASK if last

  12.times do |round|
    s = SIGMA[round % 10]
    mix(v, 0, 4, 8, 12, m[s[0]], m[s[1]])
    mix(v, 1, 5, 9, 13, m[s[2]], m[s[3]])
    mix(v, 2, 6, 10, 14, m[s[4]], m[s[5]])
    mix(v, 3, 7, 11, 15, m[s[6]], m[s[7]])
    mix(v, 0, 5, 10, 15, m[s[8]], m[s[9]])
    mix(v, 1, 6, 11, 12, m[s[10]], m[s[11]])
    mix(v, 2, 7, 8, 13, m[s[12]], m[s[13]])
    mix(v, 3, 4, 9, 14, m[s[14]], m[s[15]])
  end

  8.times { |i| h[i] ^= v[i] ^ v[i + 8] }
end

.digest(data, outlen) ⇒ Object

Raises:

  • (ArgumentError)


35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/everywhere/blake2b.rb', line 35

def digest(data, outlen)
  raise ArgumentError, "digest length must be 1..64" unless (1..64).cover?(outlen)

  data = data.to_s.b
  h = IV.dup
  h[0] ^= 0x01010000 ^ outlen # param block word 0: digest_length, fanout=1, depth=1

  if data.empty?
    compress(h, ("\0" * 128).b, 0, true)
  else
    blocks = (data.bytesize + 127) / 128
    blocks.times do |i|
      block = data.byteslice(i * 128, 128)
      if i == blocks - 1
        compress(h, block.ljust(128, "\0"), data.bytesize, true)
      else
        compress(h, block, (i + 1) * 128, false)
      end
    end
  end

  h.pack("Q<8").byteslice(0, outlen)
end

.digest64_file(path) ⇒ Object

64-byte BLAKE2b of a file — minisign's prehash. Streams through OpenSSL's BLAKE2b512 when the linked OpenSSL provides it (it matches RFC 7693 with outlen 64), else falls back to the pure-Ruby path.



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/everywhere/blake2b.rb', line 64

def digest64_file(path)
  require "openssl"
  md = begin
    OpenSSL::Digest.new("BLAKE2b512")
  rescue StandardError
    nil
  end
  return digest(File.binread(path), 64) unless md

  File.open(path, "rb") do |io|
    while (chunk = io.read(1 << 20))
      md << chunk
    end
  end
  md.digest
end

.hexdigest(data, outlen) ⇒ Object



59
# File 'lib/everywhere/blake2b.rb', line 59

def hexdigest(data, outlen) = digest(data, outlen).unpack1("H*")

.mix(v, a, b, c, d, x, y) ⇒ Object



103
104
105
106
107
108
109
110
111
112
# File 'lib/everywhere/blake2b.rb', line 103

def mix(v, a, b, c, d, x, y)
  v[a] = (v[a] + v[b] + x) & MASK
  v[d] = rotr(v[d] ^ v[a], 32)
  v[c] = (v[c] + v[d]) & MASK
  v[b] = rotr(v[b] ^ v[c], 24)
  v[a] = (v[a] + v[b] + y) & MASK
  v[d] = rotr(v[d] ^ v[a], 16)
  v[c] = (v[c] + v[d]) & MASK
  v[b] = rotr(v[b] ^ v[c], 63)
end

.rotr(x, n) ⇒ Object



114
# File 'lib/everywhere/blake2b.rb', line 114

def rotr(x, n) = ((x >> n) | ((x << (64 - n)) & MASK)) & MASK