blob: 493963d3772382fe0b8d4f59907ca733636b88be [file] [edit]
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:typed_data';
const _iv = [
0x6a09e667,
0xbb67ae85,
0x3c6ef372,
0xa54ff53a,
0x510e527f,
0x9b05688c,
0x1f83d9ab,
0x5be0cd19,
];
const _permutation = [2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8];
/// Test-only, deliberately direct implementation of unkeyed BLAKE3-256.
///
/// Algorithm: https://github.com/BLAKE3-team/BLAKE3-specs/blob/master/blake3.pdf
///
/// This uses recursive tree construction, byte-wise input/output, and looped
/// compression. It shares no code, generated schedules, or tree machinery with
/// the optimized implementations. Official vectors validate this oracle too.
Uint8List referenceBlake3(Uint8List input) {
var output = _subtree(input, 0, input.length);
var words = _compress(
output.cv,
output.block,
0,
output.length,
output.flags | 8, // ROOT uses output-block counter zero.
);
return Uint8List.fromList([
for (var i = 0; i < 32; i++) (words[i ~/ 4] >>> (8 * (i % 4))) & 255,
]);
}
List<int> _compress(
List<int> cv,
List<int> block,
int counter,
int length,
int flags,
) {
var state = [
...cv,
..._iv.take(4),
counter & 0xffffffff,
counter ~/ 0x100000000,
length,
flags,
];
var message = block;
for (var round = 0; round < 7; round++) {
for (var column = 0; column < 4; column++) {
_mix(
state,
column,
column + 4,
column + 8,
column + 12,
message[column * 2],
message[column * 2 + 1],
);
}
for (var diagonal = 0; diagonal < 4; diagonal++) {
_mix(
state,
diagonal,
4 + (diagonal + 1) % 4,
8 + (diagonal + 2) % 4,
12 + (diagonal + 3) % 4,
message[8 + diagonal * 2],
message[9 + diagonal * 2],
);
}
message = [for (var index in _permutation) message[index]];
}
return [
for (var i = 0; i < 8; i++) state[i] ^ state[i + 8],
for (var i = 0; i < 8; i++) state[i + 8] ^ cv[i],
];
}
void _mix(List<int> v, int a, int b, int c, int d, int x, int y) {
v[a] = (v[a] + v[b] + x) & 0xffffffff;
v[d] = _rotate(v[d] ^ v[a], 16);
v[c] = (v[c] + v[d]) & 0xffffffff;
v[b] = _rotate(v[b] ^ v[c], 12);
v[a] = (v[a] + v[b] + y) & 0xffffffff;
v[d] = _rotate(v[d] ^ v[a], 8);
v[c] = (v[c] + v[d]) & 0xffffffff;
v[b] = _rotate(v[b] ^ v[c], 7);
}
int _rotate(int value, int amount) =>
((value >>> amount) | (value << (32 - amount))) & 0xffffffff;
_Output _subtree(Uint8List bytes, int start, int end) {
var length = end - start;
if (length > 1024) {
// The left child is the largest complete power-of-two subtree strictly
// smaller than this tree. This also handles an incomplete rightmost chunk.
var leftLength = 1024;
while (leftLength * 2 < length) {
leftLength *= 2;
}
var left = _subtree(bytes, start, start + leftLength).chainingValue();
var right = _subtree(bytes, start + leftLength, end).chainingValue();
return _Output(_iv, [...left, ...right], 0, 64, 4); // PARENT
}
var cv = _iv;
var offset = start;
while (true) {
var count = end - offset < 64 ? end - offset : 64;
var block = List<int>.filled(16, 0);
for (var i = 0; i < count; i++) {
block[i ~/ 4] |= bytes[offset + i] << (8 * (i % 4));
}
var last = offset + count == end;
var flags = (offset == start ? 1 : 0) | (last ? 2 : 0);
var output = _Output(cv, block, start ~/ 1024, count, flags);
if (last) return output;
cv = output.chainingValue();
offset += count;
}
}
class _Output {
final List<int> cv;
final List<int> block;
final int counter;
final int length;
final int flags;
new(this.cv, this.block, this.counter, this.length, this.flags);
List<int> chainingValue() =>
_compress(cv, block, counter, length, flags).sublist(0, 8);
}