Module: Nuckle::Internals::Blake3

Defined in:
lib/nuckle/internals/blake3.rb

Overview

BLAKE3 cryptographic hash (unkeyed, keyed, and derive_key modes) with XOF.

References:

Defined Under Namespace

Classes: Hasher, Output

Constant Summary collapse

MASK32 =
0xFFFFFFFF
OUT_LEN =
32
KEY_LEN =
32
BLOCK_LEN =
64
CHUNK_LEN =
1024
CHUNK_START =

Domain-separation flags (per-compression, not global).

1
CHUNK_END =
2
PARENT =
4
ROOT =
8
KEYED_HASH =
16
DERIVE_KEY_CONTEXT =
32
DERIVE_KEY_MATERIAL =
64
IV =

Same constants as SHA-256 IV.

[
  0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A,
  0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19
].freeze
MSG_PERMUTATION =

Message-word permutation applied between rounds.

[2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8].freeze

Class Method Summary collapse

Class Method Details

.compress(cv, block_words, counter, block_len, flags) ⇒ Array<Integer>

BLAKE3 compression function.

Parameters:

  • cv (Array<Integer>)

    8-word chaining value

  • block_words (Array<Integer>)

    16-word message block

  • counter (Integer)

    64-bit counter (chunk index, XOF block index, or 0 for parents)

  • block_len (Integer)

    number of valid bytes in the block (1..64, or 0 for empty)

  • flags (Integer)

    domain-separation flags

Returns:

  • (Array<Integer>)

    16-word output (first 8 = new CV for non-XOF)



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

def compress(cv, block_words, counter, block_len, flags)
  state = [
    cv[0], cv[1], cv[2], cv[3],
    cv[4], cv[5], cv[6], cv[7],
    IV[0], IV[1], IV[2], IV[3],
    counter & MASK32, (counter >> 32) & MASK32, block_len, flags
  ]
  m = block_words.dup

  round(state, m)
  6.times do
    m = permute(m)
    round(state, m)
  end

  # Final xor: state[0..8] ^= state[8..16]; state[8..16] ^= cv[0..8]
  8.times do |i|
    state[i]     ^= state[i + 8]
    state[i + 8] ^= cv[i]
  end
  state
end

.derive_key(context, material, length = OUT_LEN) ⇒ Object

Derive a subkey from a context string and key material.



276
277
278
# File 'lib/nuckle/internals/blake3.rb', line 276

def derive_key(context, material, length = OUT_LEN)
  new_derive_key(context).update(material).finalize(length)
end

.g(state, a, b, c, d, mx, my) ⇒ Object

Quarter-round (G) on a 16-word state.



39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/nuckle/internals/blake3.rb', line 39

def g(state, a, b, c, d, mx, my)
  state[a] = (state[a] + state[b] + mx) & MASK32
  t = state[d] ^ state[a]
  state[d] = ((t >> 16) | (t << 16)) & MASK32
  state[c] = (state[c] + state[d]) & MASK32
  t = state[b] ^ state[c]
  state[b] = ((t >> 12) | (t << 20)) & MASK32
  state[a] = (state[a] + state[b] + my) & MASK32
  t = state[d] ^ state[a]
  state[d] = ((t >> 8) | (t << 24)) & MASK32
  state[c] = (state[c] + state[d]) & MASK32
  t = state[b] ^ state[c]
  state[b] = ((t >> 7) | (t << 25)) & MASK32
end

.hash(input, length = OUT_LEN) ⇒ Object

One-shot unkeyed hash.



255
256
257
# File 'lib/nuckle/internals/blake3.rb', line 255

def hash(input, length = OUT_LEN)
  Hasher.new(IV, 0).update(input).finalize(length)
end

.keyed_hash(key, input, length = OUT_LEN) ⇒ Object

One-shot keyed hash (32-byte key).



265
266
267
# File 'lib/nuckle/internals/blake3.rb', line 265

def keyed_hash(key, input, length = OUT_LEN)
  new_keyed(key).update(input).finalize(length)
end

.new_derive_key(context) ⇒ Object



280
281
282
283
# File 'lib/nuckle/internals/blake3.rb', line 280

def new_derive_key(context)
  ctx_key_bytes = Hasher.new(IV, DERIVE_KEY_CONTEXT).update(context).finalize(KEY_LEN)
  Hasher.new(ctx_key_bytes.unpack("V8"), DERIVE_KEY_MATERIAL)
end

.new_hasherObject

Build a Hasher for the unkeyed mode (for streaming use).



260
261
262
# File 'lib/nuckle/internals/blake3.rb', line 260

def new_hasher
  Hasher.new(IV, 0)
end

.new_keyed(key) ⇒ Object

Raises:

  • (ArgumentError)


269
270
271
272
273
# File 'lib/nuckle/internals/blake3.rb', line 269

def new_keyed(key)
  raise ArgumentError, "key must be 32 bytes" unless key.bytesize == KEY_LEN

  Hasher.new(key.b.unpack("V8"), KEYED_HASH)
end

.permute(m) ⇒ Object



67
68
69
# File 'lib/nuckle/internals/blake3.rb', line 67

def permute(m)
  MSG_PERMUTATION.map { |i| m[i] }
end

.round(state, m) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/nuckle/internals/blake3.rb', line 54

def round(state, m)
  # Columns
  g(state, 0, 4,  8, 12, m[0],  m[1])
  g(state, 1, 5,  9, 13, m[2],  m[3])
  g(state, 2, 6, 10, 14, m[4],  m[5])
  g(state, 3, 7, 11, 15, m[6],  m[7])
  # Diagonals
  g(state, 0, 5, 10, 15, m[8],  m[9])
  g(state, 1, 6, 11, 12, m[10], m[11])
  g(state, 2, 7,  8, 13, m[12], m[13])
  g(state, 3, 4,  9, 14, m[14], m[15])
end

.words_from_block(block) ⇒ Object

Parse a 64-byte string (or zero-padded short string) into 16 LE u32 words.



104
105
106
107
# File 'lib/nuckle/internals/blake3.rb', line 104

def words_from_block(block)
  block = block + ("\x00".b * (BLOCK_LEN - block.bytesize)) if block.bytesize < BLOCK_LEN
  block.unpack("V16")
end