/*
	多数決関数を利用した鍵管理

		Created by Osaka(2013/4/24)

多数決関数で分散した鍵x、y、zについて
それらを2つづつを連結し、新たな鍵列(連結鍵)xy、yz、zxを作成
この鍵列の先頭に次のような16バイトを付加する
第1バイトのビット列はnnnnnxxxで、
nは任意のビット、xxxは、3つの鍵の組み合わせ
第2バイトからは鍵列16進列のMD5を示す

課題
	任意長の主となる副鍵(サーバー管理)への対応

乱数列生成
	http://eddie.keddy.ne.jp/change/random.htm
*/
var BitArray=require('bit-array');
var Stochator=require('./stochator');
var crypto=require('crypto');

//256個の乱数、key128文字に対応
var rd=new Array(209,192,123,68,26,25,188,235,79,116,12,154,107,30,174,106,134,49,187,248,5,29,210,100,230,111,179,153,78,135,48,197,183,253,39,145,43,200,16,60,99,15,92,37,101,115,176,242,59,160,19,250,207,93,241,141,212,220,118,7,173,103,0,87,244,75,3,41,72,226,58,112,89,84,157,28,238,71,131,201,129,249,80,62,8,181,86,205,224,17,162,85,255,70,185,54,105,61,55,219,74,6,195,27,38,56,137,91,214,148,1,245,110,204,42,218,65,23,125,182,52,50,166,152,2,46,136,32,223,189,158,171,178,169,128,234,104,217,35,51,202,18,232,151,20,9,130,243,95,186,53,57,150,227,194,191,172,47,221,117,156,31,236,64,159,67,231,233,132,120,180,147,33,216,251,69,161,167,143,199,228,122,82,11,4,21,140,155,83,73,170,36,203,121,133,77,146,239,184,109,88,24,196,213,211,215,13,138,126,247,81,252,127,76,124,144,165,168,177,98,119,240,175,198,225,190,66,222,149,102,45,193,22,246,108,113,97,44,164,14,208,63,34,229,10,114,139,254,206,40,90,96,94,163,142,237);

/*
var rr="";
for(var i=0;i<rd.length;i++){
	var v=rd[i].toString(16);
	if(v.length==1)v="0"+v;
	rr+=v;
}
console.log(rd.length+"\n"+rr+"\n"+rr.length);
*/

var HEX_DIGIT=new Array("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f");
var BIT_DIGIT=new Array("0000","0001","0010","0011","0100","0101","0110","0111","1000","1001","1010","1011","1100","1101","1110","1111");

var keybits=64;

var rand2=new Stochator({
	kind: "integer",
	min: 0,
	max: 1
}, Boolean);

var rand3=new Stochator({
	kind: "integer",
	min: 0,
	max: 2
}, "triple");


var keystr="大坂哲司fujimic江東区青海1-1-20ダイバーシティ19F";

var d1=string2hex(keystr);//主鍵
var keybits=d1.length*4;//キーのビット長

console.log(keystr+"\nstring2hex:"+d1+"\nlength="+keybits+" "+keystr.length);
d1=hex2bit(d1);
console.log("bit="+d1)
D1=new BitArray(keybits);
for(var i=0;i<keybits;i++)if(d1.charAt(i)=='1')D1.set(i,true);

console.log("\n")

/*
var d2=string2hex("多数決関");
console.log("string2hex:"+d2);
d2=hex2bit(d2);
*/

//var D2=getRandBitset(keybits);//ランダムな主となる副鍵
var D2=getRandData(keystr.length);//サーバーが管理している主となる副鍵
console.log("set="+D2.toString());

	var x=generateKey(D1, D2);//主鍵bs(ユーザー管理)、副鍵a1(サーバー管理)から多数決関数に合致する2鍵a2、a3を求める
	var a2=x[0],a3=x[1];

console.log("k:" +D1.toString());
console.log("x:" +D2.toString());
console.log("y:" +a2.toString());
console.log("z:" +a3.toString());
	b123=majority(D2, a2, a3);//多数決関数で、副鍵から主鍵を求める
console.log("f:" +b123.toString());

console.log("\n")

st=hex2string(bit2hex(b123.toString()));
console.log("st:"+st);

//秘密分散用に、鍵の連結を行う(連結鍵)
var xy=keyjoin("110",D2,a2);
var yz=keyjoin("011",a2,a3);
var zx=keyjoin("101",a3,D2);

console.log("\nxy:"+xy);
console.log("yz:"+yz);

//連結鍵2組から副鍵3個を求める
var data1=getKeyData(xy);
var data2=getKeyData(yz);
var data=new Array(3);
if(data1[0])data[0]=data1[0];
if(data1[1])data[1]=data1[1];
if(data1[2])data[2]=data1[2];
if(data2[0])data[0]=data2[0];
if(data2[1])data[1]=data2[1];
if(data2[2])data[2]=data2[2];

	b123=majority(data[0], data[1], data[2]);//多数決関数で、副鍵から主鍵を求める
console.log("F:" +b123.toString());
st=hex2string(bit2hex(b123.toString()));
console.log("ST:"+st);

console.log("\nyz:"+yz);
console.log("zx:"+zx);

var data1=getKeyData(yz);
var data2=getKeyData(zx);
var data=new Array(3);
if(data1[0])data[0]=data1[0];
if(data1[1])data[1]=data1[1];
if(data1[2])data[2]=data1[2];
if(data2[0])data[0]=data2[0];
if(data2[1])data[1]=data2[1];
if(data2[2])data[2]=data2[2];

	b123=majority(data[0], data[1], data[2]);//多数決関数で、副鍵から主鍵を求める
console.log("F:" +b123.toString());
st=hex2string(bit2hex(b123.toString()));
console.log("ST:"+st);



//-----------------------------------------------------------------------------

//バイト数値を16進、2進で現わす
function byte2bit(num){
//console.log("byte2bit "+num);
	var h=HEX_DIGIT[((num>>8)>>4) & 0xF] + HEX_DIGIT[(num>>8) & 0xF];
	h += HEX_DIGIT[(num>>4) & 0xF] + HEX_DIGIT[num & 0xF];
	var b=BIT_DIGIT[((num>>8)>>4) & 0xF] + BIT_DIGIT[(num>>8) & 0xF];
	b += BIT_DIGIT[(num>>4) & 0xF] + BIT_DIGIT[num & 0xF];
console.log(num + "h="+h+":b="+b);
}

//ユニコード16進コード列をビット列に変換
function hex2bit(d){
//console.log("hex2="+d);
	d=d.toLowerCase();
	var s="";
	for(var i=0;i<d.length;i++){
		var c=d.charAt(i);
		s+=BIT_DIGIT[getCNUM(c)];
	}
	return s;
}

//ユニコード16進コード列をビットセットに変換
function hex2bitset(d){
//console.log("hex2="+d);
	d=d.toLowerCase();
	var s="";
	for(var i=0;i<d.length;i++){
		var c=d.charAt(i);
		s+=BIT_DIGIT[getCNUM(c)];
	}

	var D=new BitArray(s.length);
	for(var i=0;i<s.length;i++)if(s.charAt(i)=='1')D.set(i,true);

	return D;
}

//ビット列をユニコード16進コード列に変換
function bit2hex(d){
//console.log("bit2="+d);
	var s="";
	for(var i=0;i<d.length;i+=8){
		var c1=d.charAt(i+0)=='1'?8:0;
		var c2=d.charAt(i+1)=='1'?4:0;
		var c3=d.charAt(i+2)=='1'?2:0;
		var c4=d.charAt(i+3)=='1'?1:0;
		var c5=d.charAt(i+4)=='1'?8:0;
		var c6=d.charAt(i+5)=='1'?4:0;
		var c7=d.charAt(i+6)=='1'?2:0;
		var c8=d.charAt(i+7)=='1'?1:0;
		c1=c1+c2+c3+c4;if(c1<10)c1=""+c1;else{c1+=87;c1=String.fromCharCode(c1);}
		c2=c5+c6+c7+c8;if(c2<10)c2=""+c2;else{c2+=87;c2=String.fromCharCode(c2);}
		s+=c1+c2;
	}
	return s;
}

//文字列をユニコード16進コード列に変換
function string2hex(s){
	var d="";
	for(var i=0;i<s.length;i++){
		D=s.charCodeAt(i).toString(16);
		if(D.length==2)d+="00"+D;
		else d+=D;
	}
	return d;
}

//ユニコード16進コード列を文字列に変換
function hex2string(s){
	s=s.toLowerCase();
	var d="";
	var st="";
	for(var i=0;i<s.length;i+=4){
		var c1=getCNUM(s.charAt(i+0));
		var c2=getCNUM(s.charAt(i+1));
		var c3=getCNUM(s.charAt(i+2));
		var c4=getCNUM(s.charAt(i+3));
//		var cc=c1*(16*16*16)+c2*(16*16)+c3*16+c4;
		var cc=c1*4096+c2*256+c3*16+c4;
		st+=String.fromCharCode(cc);
	}
	return st;
}

//0-9a-fを数字にする
function getCNUM(s){
	if(s>="0"&&s<="9")return s-"0";
	else{
		var c=s.charCodeAt(0);
		return (c-87);//c-97+10
	}
}

//2つの鍵b1(主鍵)、b2(副鍵)から多数決関数に合致する2つの副鍵a2、a3を生成
function generateKey(b1, b2){
	var a2=new BitArray(keybits);
	var a3=new BitArray(keybits);

	for(var i=0;i<keybits;i++){
//b1(0) = majority(b2(1),a2(0),a3(0))
		if(!b1.get(i)&&b2.get(i)){
			a2.set(i,false);
			a3.set(i,false);
		}
//b1(1) = majority(b2(0),a2(1),a3(1))
		else if(b1.get(i)&&!b2.get(i)){
			a2.set(i,true);
			a3.set(i,true);
		}
//b1(0) = majority(b2(0),a2(0),a3(0)), majority(b2(0),a2(0),a3(1)), majority(b2(0),a2(1),a3(0))
		else if(!b1.get(i)&&!b2.get(i)){
			var v=rand3.triple();
			switch (v){
				case 0 :
					a2.set(i,false);
					a3.set(i,false);
					break;
				case 1 :
					a2.set(i,false);
					a3.set(i,true);
					break;
				case 2 :
					a2.set(i,true);
					a3.set(i,false);
					break;
			}
		}
//b1(1) = majority(b2(1),a2(0),a3(1)), majority(b2(1),a2(1),a3(0)), majority(b2(1),a2(1),a3(1))
		else if(b1.get(i)&&b2.get(i)){
			var v=rand3.triple();
			switch (v){
				case 0 :
					a2.set(i,false);
					a3.set(i,true);
					break;
				case 1 :
					a2.set(i,true);
					a3.set(i,false);
					break;
				case 2 :
					a2.set(i,true);
					a3.set(i,true);
					break;
			}
		}
	}
	return [a2,a3];
}

//BitSetの多数決関数 majority(x, y, z)=((x&y) | (y&z) | (z&x))
function majority(x, y, z){
	b12=x.copy();
		b12.and (y);
	b23=y.copy();
		b23.and (z);
	b31=z.copy();
		b31.and (x);

		b23.or(b31);
	b123=b12.copy();
		b123.or(b23);
	return b123;
}

//指定したビット数のビット列を求める
function getRandBit(num){
	var s="";
	for(var i=0;i<num;i++){
		if(rand2.next())s+="1";
		else s+="0";
	}
	return s;
}

//サーバー管理の乱数列から指定した長さの乱数列を得る
function getRandData(num){
	num=num+num;
	var rr="";
	for(var i=0;i<num;i++){
		var v=rd[i].toString(16);
		if(v.length==1)v="0"+v;
		rr+=v;
	}
	return hex2bitset(rr);
}

//指定したビット数のビット列を求める
function getRandBitset(num){
	var s="";
	for(var i=0;i<num;i++){
		if(rand2.next())s+="1";
		else s+="0";
	}

	var D=new BitArray(num);
	for(var i=0;i<num;i++)if(s.charAt(i)=='1')D.set(i,true);
	return D;
}

//鍵a、bの連結+付加情報(連結鍵と言う)
function keyjoin(mode,a,b){
	a=bit2hex(a.toString());
	b=bit2hex(b.toString());
	var info=getRandBitset(8);
	if(mode.charAt(0)=='1') info.set(0,true);
	else					info.set(0,false);
	if(mode.charAt(1)=='1') info.set(1,true);
	else					info.set(1,false);
	if(mode.charAt(2)=='1') info.set(2,true);
	else					info.set(2,false);
	info=bit2hex(info.toString());
//console.log("info="+info);
	info=a+info+b;
	var digest=md5_hex(info);
//console.log("digest="+digest);
	return info+digest;
}

//文字列のmd5を計算
function md5_hex(src){
	var md5=crypto.createHash("md5");
	md5.update(src, 'utf8');
	return md5.digest('hex');
}

//連結鍵から鍵を求める
function getKeyData(d){
	var k=keybits/8*2;
	var s=d.substring(0,k*2+2);
	var c=d.substring(k*2+2);
	var digest=md5_hex(s);
//console.log("Digest="+digest);
	if(digest==c){
		var s1=s.substring(0,k);	s1=hex2bitset(s1);
		var s2=s.substring(k+2);	s2=hex2bitset(s2);
		var info=s.substring(k,k+2);
		info=hex2bit(info);
		if(info.indexOf("110")==0)return [s1,s2,null];
		else if(info.indexOf("011")==0)return [null,s1,s2];
		else if(info.indexOf("101")==0)return [s2,null,s1];
		else{
console.log("infodata is broken!!");
			return [null,null,null];
		}
	}
	else{
console.log("key is broken!!");
		return [null,null,null];
	}
}

function bin2hex(s){
	// http://kevin.vanzonneveld.net
	// +	 original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
	// +	 bugfixed by: Onno Marsman
	// +	 bugfixed by: Linuxworld
	// +	 improved by: ntoniazzi (http://phpjs.org/functions/bin2hex:361#comment_177616)
	// *		 example 1: bin2hex('Kev');
	// *		 returns 1: '4b6576'
	// *		 example 2: bin2hex(String.fromCharCode(0x00));
	// *		 returns 2: '00'
	var i, l, o = "", n;

	s += "";
	for (i = 0, l = s.length; i < l; i++) {
		n = s.charCodeAt(i).toString(16)
		o += n.length < 2 ? "0" + n : n;
	}
	return o;
}

/*
	How to program hex2bin in Javascript?
		http://stackoverflow.com/questions/7695450/how-to-program-hex2bin-in-javascript
*/
function hex2bin(hex){
	var bytes = [], str;
	for(var i=0; i< hex.length-1; i+=2)
		bytes.push(parseInt(hex.substr(i, 2), 16));
	return String.fromCharCode.apply(String, bytes);
}

function fileSaveContents( filename , str ){
	var fs = require("fs");
	var fileContent = "";

	var fd = fs.openSync(filename, "w");
	fs.writeSync(fd, str, 0, "ascii");
	fs.closeSync(fd);

	return fileContent;
}

function fileGetContents( filename ){
	var fs = require("fs");
	var fileContent = "";
	var stat = fs.statSync(filename);

	var fd = fs.openSync(filename, "r");
	var bytes = fs.readSync(fd, stat.size, 0, "ascii");
	fileContent += bytes[0];
	fs.closeSync(fd);

	return fileContent;
}