Skip to content

Commit d080da7

Browse files
feat: Blake3 Stateful Hash Object implementation (#13)
* feat: Draft Blake3 Stateful Hash Object implementation * chore: Draft theorem * chore: size_of_extract theorem * chore: mimic Rust version of the sponge implementation (#12) * chore: mimic Rust version of the sponge implementation * reset Sponge counter when ratcheting --------- Co-authored-by: Arthur Paulino <[email protected]>
1 parent acd8594 commit d080da7

File tree

2 files changed

+106
-23
lines changed

2 files changed

+106
-23
lines changed

Blake3.lean

+78-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
/-! Bindings to the BLAKE3 hashing library. -/
22

3+
theorem ByteArray.size_of_extract {hash : ByteArray} (hbe : b ≤ e) (h : e ≤ hash.data.size) :
4+
(hash.extract b e).size = e - b := by
5+
simp [ByteArray.size, ByteArray.extract, ByteArray.copySlice, ByteArray.empty, ByteArray.mkEmpty]
6+
rw [Nat.add_comm, Nat.sub_add_cancel hbe, Nat.min_eq_left h]
7+
38
namespace Blake3
49

510
/-- BLAKE3 constants -/
@@ -53,40 +58,97 @@ opaque initDeriveKey (context : @& ByteArray) : Hasher
5358
@[extern "lean_blake3_hasher_update"]
5459
opaque update (hasher : Hasher) (input : @& ByteArray) : Hasher
5560

61+
instance {length : USize} : Inhabited { r : ByteArray // r.size = length.toNat } where
62+
default := ⟨
63+
⟨⟨List.replicate length.toNat 0⟩⟩,
64+
by simp only [ByteArray.size, List.toArray_replicate, Array.size_mkArray]
65+
66+
5667
/-- Finalize the hasher and write the output to an array of a given length. -/
5768
@[extern "lean_blake3_hasher_finalize"]
58-
opaque finalize : (hasher : Hasher) → (length : USize) → ByteArray
69+
opaque finalize : (hasher : Hasher) → (length : USize) →
70+
{r : ByteArray // r.size = length.toNat}
71+
72+
def finalizeWithLength (hasher : Hasher) (length : Nat)
73+
(h : length % 2 ^ System.Platform.numBits = length := by native_decide) :
74+
{ r : ByteArray // r.size = length } :=
75+
let ⟨hash, h'⟩ := hasher.finalize length.toUSize
76+
have hash_size_eq_len : hash.size = length := by
77+
cases System.Platform.numBits_eq with
78+
| inl h32 => rw [h32] at h; simp [h', h32]; exact h
79+
| inr h64 => rw [h64] at h; simp [h', h64]; exact h
80+
⟨hash, hash_size_eq_len⟩
5981

6082
end Hasher
6183

6284
/-- Hash a ByteArray -/
6385
def hash (input : ByteArray) : Blake3Hash :=
6486
let hasher := Hasher.init ()
6587
let hasher := hasher.update input
66-
let output := hasher.finalize BLAKE3_OUT_LEN.toUSize
67-
if h : output.size = BLAKE3_OUT_LEN then
68-
⟨output, h⟩
69-
else
70-
panic! "Incorrect output size"
88+
hasher.finalizeWithLength BLAKE3_OUT_LEN
7189

7290
/-- Hash a ByteArray using keyed initializer -/
7391
def hashKeyed (input : @& ByteArray) (key : @& Blake3Key) : Blake3Hash :=
7492
let hasher := Hasher.initKeyed key
7593
let hasher := hasher.update input
76-
let output := hasher.finalize BLAKE3_OUT_LEN.toUSize
77-
if h : output.size = BLAKE3_OUT_LEN then
78-
⟨output, h⟩
79-
else
80-
panic! "Incorrect output size"
94+
hasher.finalizeWithLength BLAKE3_OUT_LEN
8195

8296
/-- Hash a ByteArray using initializer parameterized by some context -/
8397
def hashDeriveKey (input context : @& ByteArray) : Blake3Hash :=
8498
let hasher := Hasher.initDeriveKey context
8599
let hasher := hasher.update input
86-
let output := hasher.finalize BLAKE3_OUT_LEN.toUSize
87-
if h : output.size = BLAKE3_OUT_LEN then
88-
⟨output, h⟩
89-
else
90-
panic! "Incorrect output size"
100+
hasher.finalizeWithLength BLAKE3_OUT_LEN
101+
102+
structure Sponge where
103+
hasher : Hasher
104+
counter : Nat
105+
106+
namespace Sponge
107+
108+
abbrev ABSORB_MAX_BYTES := UInt32.size - 1
109+
abbrev DEFAULT_REKEYING_STAGE := UInt16.size - 1
110+
111+
def init (label : String) (_h : ¬label.isEmpty := by decide) : Sponge :=
112+
⟨Hasher.initDeriveKey label.toUTF8, 0
113+
114+
def ratchet (sponge : Sponge) : Sponge :=
115+
let key := sponge.hasher.finalizeWithLength BLAKE3_KEY_LEN
116+
{ sponge with hasher := Hasher.initKeyed key, counter := 0 }
117+
118+
def absorb (sponge : Sponge) (bytes : ByteArray)
119+
(_h : bytes.size < ABSORB_MAX_BYTES := by norm_cast) : Sponge :=
120+
let highCounter := sponge.counter >= DEFAULT_REKEYING_STAGE
121+
let sponge := if highCounter then sponge.ratchet else sponge
122+
⟨sponge.hasher.update bytes, sponge.counter + 1
123+
124+
def squeeze (sponge : Sponge) (length : USize)
125+
(h_len_bound : 2 * BLAKE3_OUT_LEN + length.toNat < 2 ^ System.Platform.numBits :=
126+
by native_decide) :
127+
{ r : ByteArray // r.size = length.toNat } :=
128+
let ⟨tmp, h⟩ := sponge.hasher.finalize (2 * BLAKE3_OUT_LEN.toUSize + length)
129+
let b := (2 * BLAKE3_OUT_LEN)
130+
let e := (2 * BLAKE3_OUT_LEN + length.toNat)
131+
let y := tmp.extract b e
132+
have sub_e_b_eq_length : e - b = length.toNat := by
133+
simp only [b, e]
134+
rw [Nat.add_comm _ length.toNat, Nat.add_sub_cancel]
135+
have le_b_e : b ≤ e := by simp only [Nat.le_add_right, e, b]
136+
have h_e_bound : e ≤ tmp.size := by
137+
simp [e, h]
138+
cases System.Platform.numBits_eq with
139+
| inl h32 =>
140+
have hbound : (2 * BLAKE3_OUT_LEN) + length.toNat < 2 ^ 32 := by
141+
rwa [h32, Nat.pow_succ] at h_len_bound
142+
rw [h32, BLAKE3_OUT_LEN]
143+
simp only [Nat.mod_eq_of_lt hbound, Nat.le_refl]
144+
| inr h64 =>
145+
have hbound : (2 * BLAKE3_OUT_LEN) + length.toNat < 2 ^ 64 := by
146+
rwa [h64, Nat.pow_succ] at h_len_bound
147+
rw [h64, BLAKE3_OUT_LEN]
148+
simp only [Nat.mod_eq_of_lt hbound, Nat.le_refl]
149+
have size_of_extract := ByteArray.size_of_extract le_b_e h_e_bound
150+
⟨y, by simp only [y, size_of_extract, sub_e_b_eq_length]⟩
151+
152+
end Sponge
91153

92154
end Blake3

Blake3Test.lean

+28-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Blake3
22

33
abbrev input : ByteArray := ⟨#[0]⟩
4-
abbrev expected_output_regular_hashing : ByteArray := ⟨#[
4+
abbrev expectedOutputRegularHashing : ByteArray := ⟨#[
55
45, 58, 222, 223, 241, 27, 97, 241,
66
76, 136, 110, 53, 175, 160, 54, 115,
77
109, 205, 135, 167, 77, 39, 181, 193,
@@ -15,7 +15,7 @@ abbrev key : Blake3.Blake3Key := .ofBytes ⟨#[
1515
213, 14, 159, 189, 82, 166, 91, 107,
1616
33, 78, 26, 226, 89, 65, 188, 92
1717
]⟩
18-
abbrev expected_output_keyed_hashing: ByteArray := ⟨#[
18+
abbrev expectedOutputKeyedHashing: ByteArray := ⟨#[
1919
145, 187, 220, 234, 206, 139, 205, 138,
2020
220, 103, 35, 65, 199, 96, 210, 18,
2121
145, 201, 131, 254, 79, 208, 229, 157,
@@ -24,16 +24,37 @@ abbrev expected_output_keyed_hashing: ByteArray := ⟨#[
2424

2525
-- Context (with "bad" randomness)
2626
abbrev context : ByteArray := ⟨#[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]⟩
27-
abbrev expected_output_derive_key_hashing: ByteArray := ⟨#[
27+
abbrev expectedOutputDeriveKeyHashing: ByteArray := ⟨#[
2828
34, 57, 22, 43, 164, 211, 65, 131,
2929
90, 55, 55, 92, 68, 90, 63, 136,
3030
3, 235, 52, 117, 143, 246, 158, 224,
31-
178, 170, 234, 211, 148, 21, 97, 183]⟩
31+
178, 170, 234, 211, 148, 21, 97, 183
32+
]⟩
33+
34+
abbrev expectedOutputSponge : ByteArray := ⟨#[
35+
21, 150, 121, 38, 177, 21, 33, 148, 213, 127,
36+
208, 231, 200, 18, 46, 115, 244, 188, 245, 188,
37+
176, 29, 43, 123, 135, 148, 132, 218, 45, 244,
38+
127, 103, 178, 82, 145, 67, 43, 106, 204, 137,
39+
154, 99, 175, 9, 196, 102, 126, 72, 27, 86
40+
]⟩
41+
42+
def hashingFails :=
43+
(Blake3.hash input).val.data != expectedOutputRegularHashing.data ||
44+
(Blake3.hashKeyed input key).val.data != expectedOutputKeyedHashing.data ||
45+
(Blake3.hashDeriveKey input context).val.data != expectedOutputDeriveKeyHashing.data
46+
47+
def spongeFails :=
48+
let sponge := Blake3.Sponge.init "ix 2025-01-01 16:18:03 content-addressing v1"
49+
let sponge := sponge.absorb ⟨#[1]⟩
50+
let sponge := sponge.absorb ⟨#[2]⟩
51+
let sponge := sponge.absorb ⟨#[3]⟩
52+
let sponge := sponge.absorb ⟨#[4, 5]⟩
53+
let output := sponge.squeeze 50
54+
output.val.data != expectedOutputSponge.data
3255

3356
def main : IO UInt32 := do
3457
println! s!"BLAKE3 version: {Blake3.version}"
35-
if (Blake3.hash input).val.data != expected_output_regular_hashing.data ||
36-
(Blake3.hashKeyed input key).val.data != expected_output_keyed_hashing.data ||
37-
(Blake3.hashDeriveKey input context).val.data != expected_output_derive_key_hashing.data
58+
if hashingFails || spongeFails
3859
then IO.eprintln "BLAKE3 test failed"; return 1
3960
else IO.println "BLAKE3 test succeeded"; return 0

0 commit comments

Comments
 (0)