blake3.bend source
blake3.bend on the hub · documented module
import Baseimport ./internal/core.bend as C# BLAKE3 (unkeyed hash mode)# =========================# Standard 32-byte digest. The core keeps the Output object so root finalization# follows the BLAKE3 specification rather than treating the root CV as a digest.type B3Output is Data: B3Output{cv: List<&2,U32>, block: List<&2,U32>, counter: C.W64, block_len: U32, flags: U32}def B3.iv() -> List<&2,U32>: [1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225]def B3.sigmas() -> List<&2,List<&2,U32>>: [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], [2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8], [3, 4, 10, 12, 13, 2, 7, 14, 6, 5, 9, 0, 11, 15, 8, 1], [10, 7, 12, 9, 14, 3, 13, 15, 4, 0, 11, 2, 5, 8, 1, 6], [12, 13, 9, 11, 15, 10, 14, 8, 7, 2, 5, 3, 0, 1, 6, 4], [9, 14, 11, 5, 8, 12, 15, 1, 13, 3, 0, 10, 2, 6, 4, 7], [11, 15, 5, 0, 1, 9, 8, 6, 14, 10, 2, 12, 3, 4, 7, 13]]def B3.msg(+m: List<&2,U32>, +sig: List<&2,U32>, pos: Nat) -> U32: C.Words32.get(m,U32.to_nat(C.Words32.get(sig,pos)))def B3.g(+v: List<&2,U32>, +a: Nat, +b: Nat, +c: Nat, +d: Nat, x: U32, y: U32) -> List<&2,U32>: +va0 = C.Words32.get(v,a) +vb0 = C.Words32.get(v,b) +vc0 = C.Words32.get(v,c) +vd0 = C.Words32.get(v,d) +va1 = C.Bits.add3(va0,vb0,x) +vd1 = C.Bits.rotr32(U32.xor(vd0,va1),16n) +vc1 = U32.add(vc0,vd1) +vb1 = C.Bits.rotr32(U32.xor(vb0,vc1),12n) +va2 = C.Bits.add3(va1,vb1,y) +vd2 = C.Bits.rotr32(U32.xor(vd1,va2),8n) +vc2 = U32.add(vc1,vd2) vb2 = C.Bits.rotr32(U32.xor(vb1,vc2),7n) v1 = List.set(&2,U32,v,a,va2) v2 = List.set(&2,U32,v1,b,vb2) v3 = List.set(&2,U32,v2,c,vc2) List.set(&2,U32,v3,d,vd2)def B3.round(+v: List<&2,U32>, +m: List<&2,U32>, +s: List<&2,U32>) -> List<&2,U32>: v0 = B3.g(v,0n,4n,8n,12n,B3.msg(m,s,0n),B3.msg(m,s,1n)) v1 = B3.g(v0,1n,5n,9n,13n,B3.msg(m,s,2n),B3.msg(m,s,3n)) v2 = B3.g(v1,2n,6n,10n,14n,B3.msg(m,s,4n),B3.msg(m,s,5n)) v3 = B3.g(v2,3n,7n,11n,15n,B3.msg(m,s,6n),B3.msg(m,s,7n)) v4 = B3.g(v3,0n,5n,10n,15n,B3.msg(m,s,8n),B3.msg(m,s,9n)) v5 = B3.g(v4,1n,6n,11n,12n,B3.msg(m,s,10n),B3.msg(m,s,11n)) v6 = B3.g(v5,2n,7n,8n,13n,B3.msg(m,s,12n),B3.msg(m,s,13n)) B3.g(v6,3n,4n,9n,14n,B3.msg(m,s,14n),B3.msg(m,s,15n))def B3.rounds(ss: List<&2,List<&2,U32>>, +m: List<&2,U32>, v: List<&2,U32>) -> List<&2,U32>: match ss: case Nil{}: v case s <> st: B3.rounds(st,m,B3.round(v,m,s))def B3.finish(+cv: List<&2,U32>, +v: List<&2,U32>) -> List<&2,U32>: [ U32.xor(C.Words32.get(v,0n),C.Words32.get(v,8n)), U32.xor(C.Words32.get(v,1n),C.Words32.get(v,9n)), U32.xor(C.Words32.get(v,2n),C.Words32.get(v,10n)), U32.xor(C.Words32.get(v,3n),C.Words32.get(v,11n)), U32.xor(C.Words32.get(v,4n),C.Words32.get(v,12n)), U32.xor(C.Words32.get(v,5n),C.Words32.get(v,13n)), U32.xor(C.Words32.get(v,6n),C.Words32.get(v,14n)), U32.xor(C.Words32.get(v,7n),C.Words32.get(v,15n)), U32.xor(C.Words32.get(v,8n),C.Words32.get(cv,0n)), U32.xor(C.Words32.get(v,9n),C.Words32.get(cv,1n)), U32.xor(C.Words32.get(v,10n),C.Words32.get(cv,2n)), U32.xor(C.Words32.get(v,11n),C.Words32.get(cv,3n)), U32.xor(C.Words32.get(v,12n),C.Words32.get(cv,4n)), U32.xor(C.Words32.get(v,13n),C.Words32.get(cv,5n)), U32.xor(C.Words32.get(v,14n),C.Words32.get(cv,6n)), U32.xor(C.Words32.get(v,15n),C.Words32.get(cv,7n)) ]def B3.compress.counter(+cv: List<&2,U32>, +block: List<&2,U32>, counter_hi: U32, counter_lo: U32, block_len: U32, flags: U32) -> List<&2,U32>: +iv = B3.iv() +m = block B3.finish(cv,B3.rounds(B3.sigmas(),m, [C.Words32.get(cv,0n),C.Words32.get(cv,1n),C.Words32.get(cv,2n),C.Words32.get(cv,3n), C.Words32.get(cv,4n),C.Words32.get(cv,5n),C.Words32.get(cv,6n),C.Words32.get(cv,7n), C.Words32.get(iv,0n),C.Words32.get(iv,1n),C.Words32.get(iv,2n),C.Words32.get(iv,3n), counter_lo,counter_hi,block_len,flags]))def B3.compress(+cv: List<&2,U32>, +block: List<&2,U32>, counter: C.W64, block_len: U32, flags: U32) -> List<&2,U32>: match counter: case C.W64{hi,lo}: B3.compress.counter(cv,block,hi,lo,block_len,flags)def B3.cv(words: List<&2,U32>) -> List<&2,U32>: List.take(&2,U32,words,8n)def B3.output_cv(out: B3Output) -> List<&2,U32>: match out: case B3Output{cv,block,counter,block_len,flags}: B3.cv(B3.compress(cv,block,counter,block_len,flags))def B3.start_flag(zero: Bool) -> U32: match zero: case True{}: 1 case False{}: 0def B3.nonfinal(+cv: List<&2,U32>, +block: List<&2,U32>, counter: C.W64, done: U32) -> List<&2,U32>: flag = B3.start_flag(U32.is_zero(done)) B3.cv(B3.compress(cv,C.Words32.block_le(block,16n),counter,64,flag))def B3.final_flags(zero: Bool) -> U32: match zero: case True{}: 3 case False{}: 2def B3.pad(+rest: List<&2,U32>, +n: U32) -> List<&2,U32>: # rest may still point at later chunks. Only the final n bytes belong to this # block; truncating here avoids rebuilding the entire remaining message in # List.append and keeps chunk finalization linear in input size. List.append( &2, U32, List.take(&2,U32,rest,U32.to_nat(n)), C.Bytes.zeros(U32.sub(64,n)) )def B3.chunk_blocks(n: Nat, +xs: List<&2,U32>, +total: U32, +done: U32, cv: List<&2,U32>, +counter: C.W64) -> B3Output: match n: case 0n: +left = U32.sub(total,done) flags = B3.final_flags(U32.is_zero(done)) B3Output{cv,C.Words32.block_le(B3.pad(xs,left),16n),counter,left,flags} case 1n+p: next = B3.nonfinal(cv,xs,counter,done) B3.chunk_blocks(p,List.drop(&2,U32,xs,64n),total,U32.add(done,64),next,counter)def B3.chunk_output.if(+xs: List<&2,U32>, +length: U32, counter: C.W64, empty: Bool) -> B3Output: match empty: case True{}: B3.chunk_blocks(0n,xs,length,0,B3.iv(),counter) case False{}: blocks = U32.div(U32.sub(length,1),64) B3.chunk_blocks(U32.to_nat(blocks),xs,length,0,B3.iv(),counter)def B3.chunk_output(+xs: List<&2,U32>, +length: U32, +counter: C.W64) -> B3Output: B3.chunk_output.if(xs,length,counter,U32.is_zero(length))def B3.parent_output(left: List<&2,U32>, right: List<&2,U32>) -> B3Output: B3Output{B3.iv(),List.append(&2,U32,left,right),C.W64.zero(),64,4}def B3.parent_cv(left: List<&2,U32>, right: List<&2,U32>) -> List<&2,U32>: B3.output_cv(B3.parent_output(left,right))# Build chunk CVs into a reversed accumulator. The old direct# cv <> B3.chunk_cvs(...)# form retained one JavaScript call frame per 1024-byte chunk and could exhaust# the Node stack on multi-megabyte inputs. This version is tail-recursive.def B3.chunk_cvs.go(n: Nat, +xs: List<&2,U32>, +index: Nat, acc: List<&2,List<&2,U32>>) -> List<&2,List<&2,U32>>: match n: case 0n: List.reverse(&2,List<&2,U32>,acc) case 1n: length = U32.from_nat(C.Bytes.length_nat(xs)) cv = B3.output_cv(B3.chunk_output(xs,length,C.W64.from_nat(index))) List.reverse(&2,List<&2,U32>,cv <> acc) case 1n+p: cv = B3.output_cv(B3.chunk_output(xs,1024,C.W64.from_nat(index))) B3.chunk_cvs.go(p,List.drop(&2,U32,xs,1024n),Nat.add(index,1n),cv <> acc)def B3.chunk_cvs(n: Nat, +xs: List<&2,U32>, +index: Nat) -> List<&2,List<&2,U32>>: B3.chunk_cvs.go(n,xs,index,Nil{})def B3.reduce_level(cvs: List<&2,List<&2,U32>>, acc: List<&2,List<&2,U32>>) -> List<&2,List<&2,U32>>: match cvs: case Nil{}: List.reverse(&2,List<&2,U32>,acc) case a <> Nil{}: # acc stores completed parents in reverse order. Prepending the odd CV and # reversing once yields the correct normal order without unbounded # non-tail List.append recursion on the JavaScript backend. List.reverse(&2,List<&2,U32>,a <> acc) case a <> b <> rest: B3.reduce_level(rest,B3.parent_cv(a,b) <> acc)def B3.reduce(fuel: Nat, cvs: List<&2,List<&2,U32>>) -> B3Output: match fuel cvs: case 1n+p +a <> +b <> Nil{}: B3.parent_output(a,b) case 1n+p +a <> +b <> +c <> rest: B3.reduce(p,B3.reduce_level(a <> b <> c <> rest,Nil{})) case 1n+p +a <> Nil{}: B3.parent_output(a,a) case 1n+p Nil{}: B3.parent_output(B3.iv(),B3.iv()) case 0n +xs: B3.parent_output(B3.iv(),B3.iv())def B3.chunk_count.if(n: Nat, zero: Bool) -> Nat: match zero: case True{}: 1n case False{}: Nat.add(Nat.div(Nat.sub(n,1n),1024n),1n)def B3.chunk_count(+n: Nat) -> Nat: B3.chunk_count.if(n,Nat.is_eq(n,0n))def B3.root.multi(+xs: List<&2,U32>, +chunks: Nat) -> B3Output: cvs = B3.chunk_cvs(chunks,xs,0n) B3.reduce(chunks,cvs)def B3.root.if(+xs: List<&2,U32>, +n: Nat, +chunks: Nat, one: Bool) -> B3Output: match one: case True{}: B3.chunk_output(xs,U32.from_nat(n),C.W64.zero()) case False{}: B3.root.multi(xs,chunks)def B3.root(+xs: List<&2,U32>) -> B3Output: +n = C.Bytes.length_nat(xs) +chunks = B3.chunk_count(n) B3.root.if(xs,n,chunks,Nat.is_eq(chunks,1n))def B3.words_le(words: List<&2,U32>) -> List<&2,U32>: match words: case Nil{}: Nil{} case h <> t: C.Bytes.u32le(h,B3.words_le(t))def B3.root_bytes(out: B3Output) -> List<&2,U32>: match out: case B3Output{cv,block,counter,block_len,flags}: all = B3.compress(cv,block,C.W64.zero(),block_len,U32.or(flags,8)) List.take(&2,U32,B3.words_le(all),32n)def BLAKE3.bytes(xs: List<&2,U32>) -> List<&2,U32>: B3.root_bytes(B3.root(xs))def BLAKE3.hex_bytes(xs: List<&2,U32>) -> String: C.Hex.bytes(BLAKE3.bytes(xs))def BLAKE3.text(s: String) -> String: BLAKE3.hex_bytes(C.Bytes.utf8(s))