Viewing files at commit f08b4a249972Commit detailsView latest

scripts/oakbench/pureblake3.py 127 lines · 6.0 KB · python
1
"""Pure-Python BLAKE3 (default 32-byte hash mode only), stdlib-only (ADR-0001).
2
3
Independently written from the BLAKE3 specification (Aumasson, Neves,
4
O'Connor, Wilcox-O'Hearn, 2020), section 2 (compression function, chunk
5
chaining and binary tree). It is deliberately a separate implementation from
6
the Rust ``blake3`` crate Oak links, so a hash verified here is not a hash the
7
subject checked with its own code. Throughput is roughly 1-3 MB/s; callers
8
bound the input size.
9
"""
10
from __future__ import annotations
11
12
import struct
13
14
IV = (0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A,
15
      0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19)
16
CHUNK_START, CHUNK_END, PARENT, ROOT = 1, 2, 4, 8
17
CHUNK_LEN = 1024
18
BLOCK_LEN = 64
19
_M = 0xFFFFFFFF
20
# Message word schedule per round (the spec's permutation applied r times).
21
_PERM = (2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8)
22
23
24
def _schedules():
25
    order = list(range(16))
26
    result = []
27
    for _ in range(7):
28
        result.append(tuple(order))
29
        order = [order[p] for p in _PERM]
30
    return tuple(result)
31
32
33
_SCHEDULE = _schedules()
34
_WORDS = struct.Struct('<16I')
35
36
37
def _compress(cv, words, counter, block_len, flags):
38
    """Return the full 16-word output state of the compression function."""
39
    s0, s1, s2, s3, s4, s5, s6, s7 = cv
40
    s8, s9, s10, s11 = IV[0], IV[1], IV[2], IV[3]
41
    s12, s13, s14, s15 = counter & _M, (counter >> 32) & _M, block_len, flags
42
    for schedule in _SCHEDULE:
43
        m = [words[i] for i in schedule]
44
        # Column step.
45
        s0 = (s0 + s4 + m[0]) & _M; s12 ^= s0; s12 = ((s12 >> 16) | (s12 << 16)) & _M
46
        s8 = (s8 + s12) & _M; s4 ^= s8; s4 = ((s4 >> 12) | (s4 << 20)) & _M
47
        s0 = (s0 + s4 + m[1]) & _M; s12 ^= s0; s12 = ((s12 >> 8) | (s12 << 24)) & _M
48
        s8 = (s8 + s12) & _M; s4 ^= s8; s4 = ((s4 >> 7) | (s4 << 25)) & _M
49
        s1 = (s1 + s5 + m[2]) & _M; s13 ^= s1; s13 = ((s13 >> 16) | (s13 << 16)) & _M
50
        s9 = (s9 + s13) & _M; s5 ^= s9; s5 = ((s5 >> 12) | (s5 << 20)) & _M
51
        s1 = (s1 + s5 + m[3]) & _M; s13 ^= s1; s13 = ((s13 >> 8) | (s13 << 24)) & _M
52
        s9 = (s9 + s13) & _M; s5 ^= s9; s5 = ((s5 >> 7) | (s5 << 25)) & _M
53
        s2 = (s2 + s6 + m[4]) & _M; s14 ^= s2; s14 = ((s14 >> 16) | (s14 << 16)) & _M
54
        s10 = (s10 + s14) & _M; s6 ^= s10; s6 = ((s6 >> 12) | (s6 << 20)) & _M
55
        s2 = (s2 + s6 + m[5]) & _M; s14 ^= s2; s14 = ((s14 >> 8) | (s14 << 24)) & _M
56
        s10 = (s10 + s14) & _M; s6 ^= s10; s6 = ((s6 >> 7) | (s6 << 25)) & _M
57
        s3 = (s3 + s7 + m[6]) & _M; s15 ^= s3; s15 = ((s15 >> 16) | (s15 << 16)) & _M
58
        s11 = (s11 + s15) & _M; s7 ^= s11; s7 = ((s7 >> 12) | (s7 << 20)) & _M
59
        s3 = (s3 + s7 + m[7]) & _M; s15 ^= s3; s15 = ((s15 >> 8) | (s15 << 24)) & _M
60
        s11 = (s11 + s15) & _M; s7 ^= s11; s7 = ((s7 >> 7) | (s7 << 25)) & _M
61
        # Diagonal step.
62
        s0 = (s0 + s5 + m[8]) & _M; s15 ^= s0; s15 = ((s15 >> 16) | (s15 << 16)) & _M
63
        s10 = (s10 + s15) & _M; s5 ^= s10; s5 = ((s5 >> 12) | (s5 << 20)) & _M
64
        s0 = (s0 + s5 + m[9]) & _M; s15 ^= s0; s15 = ((s15 >> 8) | (s15 << 24)) & _M
65
        s10 = (s10 + s15) & _M; s5 ^= s10; s5 = ((s5 >> 7) | (s5 << 25)) & _M
66
        s1 = (s1 + s6 + m[10]) & _M; s12 ^= s1; s12 = ((s12 >> 16) | (s12 << 16)) & _M
67
        s11 = (s11 + s12) & _M; s6 ^= s11; s6 = ((s6 >> 12) | (s6 << 20)) & _M
68
        s1 = (s1 + s6 + m[11]) & _M; s12 ^= s1; s12 = ((s12 >> 8) | (s12 << 24)) & _M
69
        s11 = (s11 + s12) & _M; s6 ^= s11; s6 = ((s6 >> 7) | (s6 << 25)) & _M
70
        s2 = (s2 + s7 + m[12]) & _M; s13 ^= s2; s13 = ((s13 >> 16) | (s13 << 16)) & _M
71
        s8 = (s8 + s13) & _M; s7 ^= s8; s7 = ((s7 >> 12) | (s7 << 20)) & _M
72
        s2 = (s2 + s7 + m[13]) & _M; s13 ^= s2; s13 = ((s13 >> 8) | (s13 << 24)) & _M
73
        s8 = (s8 + s13) & _M; s7 ^= s8; s7 = ((s7 >> 7) | (s7 << 25)) & _M
74
        s3 = (s3 + s4 + m[14]) & _M; s14 ^= s3; s14 = ((s14 >> 16) | (s14 << 16)) & _M
75
        s9 = (s9 + s14) & _M; s4 ^= s9; s4 = ((s4 >> 12) | (s4 << 20)) & _M
76
        s3 = (s3 + s4 + m[15]) & _M; s14 ^= s3; s14 = ((s14 >> 8) | (s14 << 24)) & _M
77
        s9 = (s9 + s14) & _M; s4 ^= s9; s4 = ((s4 >> 7) | (s4 << 25)) & _M
78
    return (s0 ^ s8, s1 ^ s9, s2 ^ s10, s3 ^ s11, s4 ^ s12, s5 ^ s13, s6 ^ s14, s7 ^ s15,
79
            s8 ^ cv[0], s9 ^ cv[1], s10 ^ cv[2], s11 ^ cv[3],
80
            s12 ^ cv[4], s13 ^ cv[5], s14 ^ cv[6], s15 ^ cv[7])
81
82
83
def _block_words(block: bytes):
84
    if len(block) < BLOCK_LEN:
85
        block = block + bytes(BLOCK_LEN - len(block))
86
    return _WORDS.unpack(block)
87
88
89
def _chunk_output(chunk: bytes, counter: int):
90
    """(cv, words, counter, block_len, flags) of a chunk's final block, not yet finalized."""
91
    cv = IV
92
    blocks = max(1, (len(chunk) + BLOCK_LEN - 1) // BLOCK_LEN)
93
    for index in range(blocks - 1):
94
        flags = CHUNK_START if index == 0 else 0
95
        cv = _compress(cv, _block_words(chunk[index * BLOCK_LEN:(index + 1) * BLOCK_LEN]),
96
                       counter, BLOCK_LEN, flags)[:8]
97
    last = chunk[(blocks - 1) * BLOCK_LEN:]
98
    flags = CHUNK_END | (CHUNK_START if blocks == 1 else 0)
99
    return cv, _block_words(last), counter, len(last), flags
100
101
102
def _parent_output(left, right):
103
    return IV, tuple(left) + tuple(right), 0, BLOCK_LEN, PARENT
104
105
106
def digest(data: bytes) -> bytes:
107
    """BLAKE3 hash (32 bytes) of ``data``."""
108
    data = bytes(data)
109
    chunks = max(1, (len(data) + CHUNK_LEN - 1) // CHUNK_LEN)
110
    stack = []
111
    for index in range(chunks - 1):
112
        output = _chunk_output(data[index * CHUNK_LEN:(index + 1) * CHUNK_LEN], index)
113
        cv = _compress(*output)[:8]
114
        total = index + 1
115
        while total & 1 == 0:
116
            cv = _compress(*_parent_output(stack.pop(), cv))[:8]
117
            total >>= 1
118
        stack.append(cv)
119
    output = _chunk_output(data[(chunks - 1) * CHUNK_LEN:], chunks - 1)
120
    while stack:
121
        output = _parent_output(stack.pop(), _compress(*output)[:8])
122
    cv, words, counter, block_len, flags = output
123
    return struct.pack('<8I', *_compress(cv, words, counter, block_len, flags | ROOT)[:8])
124
125
126
def hexdigest(data: bytes) -> str:
127
    return digest(data).hex()