diff --git a/blake.js b/blake.js
--- a/blake.js
+++ b/blake.js
@@ -1,108 +1,100 @@
-import {str2utf8} from "./util.js";
+// https://tools.ietf.org/html/rfc7693
 
 const MASK=0xffffffff;
 const BLOCK_LEN=64;
 
 const IV=[0x6A09E667,0xBB67AE85,0x3C6EF372,0xA54FF53A,0x510E527F,0x9B05688C,0x1F83D9AB,0x5BE0CD19];
 
-const SIGMA=[
-	[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],
-	[14,10,4,8,9,15,13,6,1,12,0,2,11,7,5,3],
-	[11,8,12,0,5,2,15,13,10,14,3,6,7,1,9,4],
-	[7,9,3,1,13,12,11,14,2,6,5,10,4,0,15,8],
-	[9,0,5,7,2,4,10,15,14,1,11,12,6,8,3,13],
-	[2,12,6,10,0,11,8,3,4,13,7,5,15,14,1,9],
-	[12,5,1,15,14,13,4,10,0,7,6,3,9,2,8,11],
-	[13,11,7,14,12,1,3,9,5,0,15,4,8,6,2,10],
-	[6,15,14,9,11,3,0,8,12,2,13,7,1,4,10,5],
-	[10,2,8,4,7,6,1,5,15,11,9,14,3,12,13,0]
-];
+function padEnd(arr,length,val=0){
+	return arr.concat((new Array(length-arr.length)).fill(0));
+}
 
 function rrot(x,shift){
 	return ((x>>>shift)|(x<<(32-shift)))&MASK;
 }
 
-function g(arr,ia,ib,ic,id,x,y){
-	let a=arr[ia], b=arr[ib], c=arr[ic], d=arr[id];
-	// console.log(">> "+[a,b,c,d,x,y].map(x=>(x>>>0).toString(16)));
-	a=(a+b+x)&MASK; d=rrot(d^a,16);
-	// console.log([a,d].map(x=>(x>>>0).toString(16)));
-	c=(c+d)&MASK; b=rrot(b^c,12);
-	// console.log([c,b].map(x=>(x>>>0).toString(16)));
-	a=(a+b+y)&MASK; d=rrot(d^a,8);
-	// console.log([a,d].map(x=>(x>>>0).toString(16)));
-	c=(c+d)&MASK; b=rrot(b^c,7);
-	// console.log([c,b].map(x=>(x>>>0).toString(16)));
-	// console.log("<< "+[a,b,c,d].map(x=>(x>>>0).toString(16)));
-	arr[ia]=a; arr[ib]=b; arr[ic]=c; arr[id]=d;
+function BLAKE2S(outputLen=32,key=[]){
+	this._buffer=[];
+	this._dataLen=0;
+	this._outputLen=outputLen;
+	
+	this._state=IV.slice();
+	this._state[0]^=0x01010000^(key.length<<8)^this._outputLen;
+	
+	if(key.length>0){this.update(padEnd(key,BLOCK_LEN,0));}
 }
 
-function f(state,data,offset,last){
-	console.log("... "+data.map(x=>(x>>>0).toString(16)));
-	let v=state.concat(IV);
-	v[12]^=offset&MASK;
-	v[13]^=0; // !! offset>>>32
+BLAKE2S.prototype.update=function(data){
+	for(let i=0;i<data.length;i++){
+		if(this._buffer.length<BLOCK_LEN){
+			this._buffer.push(data[i]);
+			this._dataLen++;
+		}
+		else{
+			this._compress(false);
+			this._buffer=[];
+		}
+	}
+};
+
+BLAKE2S.prototype.digest=function(){
+	this._buffer=padEnd(this._buffer,BLOCK_LEN,0);
+	this._compress(true);
+	return int32s2bytes(this._state).slice(0,this._outputLen);
+};
+
+BLAKE2S.prototype._compress=function(last){
+	const SIGMA=[
+		[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],
+		[14,10,4,8,9,15,13,6,1,12,0,2,11,7,5,3],
+		[11,8,12,0,5,2,15,13,10,14,3,6,7,1,9,4],
+		[7,9,3,1,13,12,11,14,2,6,5,10,4,0,15,8],
+		[9,0,5,7,2,4,10,15,14,1,11,12,6,8,3,13],
+		[2,12,6,10,0,11,8,3,4,13,7,5,15,14,1,9],
+		[12,5,1,15,14,13,4,10,0,7,6,3,9,2,8,11],
+		[13,11,7,14,12,1,3,9,5,0,15,4,8,6,2,10],
+		[6,15,14,9,11,3,0,8,12,2,13,7,1,4,10,5],
+		[10,2,8,4,7,6,1,5,15,11,9,14,3,12,13,0]
+	];
+	let v=this._state.concat(IV);
+	v[12]^=this._dataLen&MASK;
+	v[13]^=(this._dataLen/0x100000000)&MASK;
 	if(last){v[14]^=MASK;}
+	let data=bytes2int32s(this._buffer);
 
 	for(let i=0;i<10;i++){
-		console.log(i+": "+(v.map(x=>(x>>>0).toString(16))).join(" "));
 		let perm=SIGMA[i%10];
-		g(v,0,4,8,12,data[perm[0]],data[perm[1]]);
-		g(v,1,5,9,13,data[perm[2]],data[perm[3]]);
-		g(v,2,6,10,14,data[perm[4]],data[perm[5]]);
-		g(v,3,7,11,15,data[perm[6]],data[perm[7]]);
+		this._mix(v,0,4,8,12,data[perm[0]],data[perm[1]]);
+		this._mix(v,1,5,9,13,data[perm[2]],data[perm[3]]);
+		this._mix(v,2,6,10,14,data[perm[4]],data[perm[5]]);
+		this._mix(v,3,7,11,15,data[perm[6]],data[perm[7]]);
 
-		g(v,0,5,10,15,data[perm[8]],data[perm[9]]);
-		g(v,1,6,11,12,data[perm[10]],data[perm[11]]);
-		g(v,2,7,8,13,data[perm[12]],data[perm[13]]);
-		g(v,3,4,9,14,data[perm[14]],data[perm[15]]);
+		this._mix(v,0,5,10,15,data[perm[8]],data[perm[9]]);
+		this._mix(v,1,6,11,12,data[perm[10]],data[perm[11]]);
+		this._mix(v,2,7,8,13,data[perm[12]],data[perm[13]]);
+		this._mix(v,3,4,9,14,data[perm[14]],data[perm[15]]);
 	}
 
-	for(let i=0;i<8;i++){
-		state[i]^=v[i]^v[i+8];
-	}
-}
-
-function blake2s(data,dataLen,keyLen=0,outputLen=32){
-	let state=IV.slice();
-	state[0]^=0x01010000^(keyLen<<8)^outputLen;
-
-	if(data.length>1){
-		for(let i=0;i<data.length-1;i++){f(state,data[i],(i+1)*BLOCK_LEN,false);}
-	}
-
-	if(keyLen==0){f(state,data[data.length-1],dataLen,true);}
-	else{f(state,data[data.length-1],dataLen+BLOCK_LEN,true);}
-
-	return state.slice(0,outputLen);
+	this._state=this._state.map((x,i)=>x^v[i]^v[i+8]);
 }
 
-function bytes2int32(arr){
-	return arr.reduce((acc,b,i)=>acc|b<<(i*8));
-}
-
-function bytes2int32s(arr){
-	let res=[];
-	for(let i=0;i<arr.length;i+=4){
-		res.push(bytes2int32(arr.slice(i,4)));
-	}
-	return res;
-}
+BLAKE2S.prototype._mix=function(arr,ia,ib,ic,id,x,y){
+	let a=arr[ia], b=arr[ib], c=arr[ic], d=arr[id];
+	a=(a+b+x)&MASK; d=rrot(d^a,16);
+	c=(c+d)&MASK; b=rrot(b^c,12);
+	a=(a+b+y)&MASK; d=rrot(d^a,8);
+	c=(c+d)&MASK; b=rrot(b^c,7);
+	arr[ia]=a; arr[ib]=b; arr[ic]=c; arr[id]=d;
+};
 
-function int322bytes(x){
-	let res=[];
-	for(let i=0;i<4;i++){
-		res.push(x&0xff);
-		x>>>=8;
-	}
-	return res;
-}
-
-function int32s2bytes(arr){
-	return arr.map(int322bytes).reduce((acc,bytes)=>acc.concat(bytes));
+function blake2s(data,outputLen=32,key=[]){
+	let h=new BLAKE2S(outputLen,key);
+	for(let i=0;i<data.length;i+=BLOCK_LEN){h.update(data.slice(i,i+BLOCK_LEN));}
+	return h.digest();
 }
 
 let msg=[97,98,99];
-let data=[bytes2int32s(msg).concat(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)];
-//console.log(int32s2bytes(blake2s(data,msg.length)).map(x=>x.toString(16)).join(" "));
-console.log(int32s2bytes(blake2s(data,msg.length,outputLen=16)).map(x=>x.toString(16)).join(" "));
+console.log(bytes2hex(blake2s(msg,msg.length,0,16))=="aa4938119b1dc7b87cbad0ffd200d0ae");
+console.log(bytes2hex(blake2s(msg,msg.length,0,20))=="5ae3b99be29b01834c3b508521ede60438f8de17");
+console.log(bytes2hex(blake2s(msg,msg.length,0,28))=="0b033fc226df7abde29f67a05d3dc62cf271ef3dfea4d387407fbd55");
+console.log(bytes2hex(blake2s(msg,msg.length))=="508c5e8c327c14e2e1a72ba34eeb452f37458b209ed63a294d999b4c86675982");
diff --git a/chacha.js b/chacha.js
--- a/chacha.js
+++ b/chacha.js
@@ -1,5 +1,3 @@
-// https://tools.ietf.org/html/rfc7693
-
 MASK=0xffffffff;
 
 function int2bytes(x){
diff --git a/crypto.html b/crypto.html
--- a/crypto.html
+++ b/crypto.html
@@ -1,8 +1,8 @@
 <!DOCTYPE html>
 <html>
 <head>
-	<!--<script type="text/javascript" src="util.js" />-->
-	<script type="module" src="blake.js" />
+	<script type="text/javascript" src="util.js"></script>
+	<script type="text/javascript" src="blake.js"></script>
 </head>
 <body>
 	<script type="text/javascript">
diff --git a/util.js b/util.js
--- a/util.js
+++ b/util.js
@@ -1,4 +1,33 @@
-export function str2utf8(s){
+function bytes2int32(arr){
+	return arr.reduce((acc,b,i)=>acc|b<<(i*8));
+}
+
+function bytes2int32s(arr){
+	let res=[];
+	for(let i=0;i<arr.length;i+=4){
+		res.push(bytes2int32(arr.slice(i,i+4)));
+	}
+	return res;
+}
+
+function int322bytes(x){
+	let res=[];
+	for(let i=0;i<4;i++){
+		res.push(x&0xff);
+		x>>>=8;
+	}
+	return res;
+}
+
+function int32s2bytes(arr){
+	return arr.map(int322bytes).reduce((acc,bytes)=>acc.concat(bytes));
+}
+
+function bytes2hex(arr){
+	return arr.map(x=>x.toString(16).padStart(2,"0")).join("");
+}
+
+function str2utf8(s){
 	let res=[];
 	let c=s.codePointAt(0);
 	for(let i=0;c!==undefined;i++,c=s.codePointAt(i)){
@@ -23,7 +52,7 @@ export function str2utf8(s){
 	return res;
 }
 
-console.log(str2utf8("$").map(x=>x.toString(16)));
+/*console.log(str2utf8("$").map(x=>x.toString(16)));
 console.log(str2utf8("ยข").map(x=>x.toString(16)));
 console.log(str2utf8("โ‚ฌ").map(x=>x.toString(16)));
-console.log(str2utf8("๐ˆ").map(x=>x.toString(16)));
+console.log(str2utf8("๐ˆ").map(x=>x.toString(16)));*/