MerKle Tree的建立
merkle tree 的基本概念:Merkle Tree 是由计算机科学家 Ralph Merkle 在很多年前提出的,并以他本人的名字来命名。不过,Merkle Tree 确实涉及到了很多有意思的实际应用。最近几年才有的一个例子是,比特币钱包服务用 Merkle Tree 的机制来作”百分百准备金证明“ ( http://blog.bifubao.com/2014/03/16/proof-of-reserves/ )。不过今天, Merkle Tree多出现在数据的“完整性校验”。
要在文件的基础上构建MerkleTree,首先是对文件进行分块,然后对每个文件分块进行hash计算,得到各块的hash value, 所有的hash value都为merkle tree的叶子节点,这些叶子节点两两成对(连接),再次hash计算,得到其父亲节点,其父亲节点也按照同样的方法,得到它的父亲节点,如此进行直到获得根节点。
图示:叶子节点数为4时,构造的merkleTree
以下是本人用C编写的构建文件MerkleTree的基本思路:
1.打开指定的文件;
2.获得文件长度,按照每块64Bytes大小对文件分块,块数n=len/64(这里测试的文件,其大小均能被64Bytes整除,eg1KB)
3.定义数据结构Path
typedef struct Path{
int * node;
}Path;
用于构建并保存Merkle Tree,图示
从底向上的建树过程,第i层对应path[i],第i层的第j个节点对应path[i].node[j];其子节点为path[i-1].node[j*2],path[i-1].node[j*2+1];
父亲节点是左右孩子节点的hashvalue连接后计算的hash值
测试代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct Path{
int * node;
}Path;
unsigned int SDBMHash(char *str)
{
unsigned int hash = 0;
while (*str)
{
// equivalent to: hash = 65599*hash + (*str++);
hash = (*str++) + (hash << 6) + (hash << 16) - hash;
}
return (hash & 0x7FFFFFFF);
}
int merkle_root(Path path[],int num){
int node_num=num,depth=0,i,j,k;
unsigned char temp[8];
char *p;
while(node_num/2!=0){
node_num/=2;
path[++depth].node=(int *)malloc(node_num*sizeof(int));
i=0;
while(i<node_num){
p=(char *)&path[depth-1].node[i*2];
for(j=0;j<8;j++){
temp[j]=*(p++);
}
/* for(k=0;k<8;k++){
printf("%02x ",temp[k]);
}
printf("\n");
*/
path[depth].node[i]=SDBMHash(temp);
i++;
}
}
printf("root=%x,%d\n",path[depth].node[0],path[depth].node[0]);
return depth;
}
/*在文件的基础上建立MKT*/
int merkle_overFile(char * filename){
FILE *fp;
int n,len,i,depth,k;
// filename[20];
unsigned char buffer[64];
Path path[30];
if((fp=fopen(filename,"rb"))==NULL){
printf("the file %s cannot be opened!\n",filename);
}
fseek(fp,0,SEEK_END);
if((len=ftell(fp))==-1L){
printf("Sorry! Can not calculate files which larger than 2GB!\n");
fclose(fp);
}
rewind(fp);
printf("len=%d\n",len);
n=len/64;
/*文件按64bytes大小分块,叶子节点初始化*/
path[0].node=(int *)malloc(n*sizeof(int));
i=0;
while(i<n){
fread(buffer,1,64,fp);
path[0].node[i]=SDBMHash(buffer);
i++;
}
fclose(fp);
depth=merkle_root(path,n);
return depth;
}
//测试输入文件file_1k.txt
int main(){
char filename[20];
int depth;
printf("please input the file:");
scanf("%s",filename);
depth=merkle_overFile(filename);
printf("depth=%d\n",depth);
return 0;
}
需要注意的地方:
windows下生成指定大小的文件,进入命令窗口,输入命令:
fsutil file createnew test.txt 104857600
则瞬间生成一个100MB大小的文件
其中104857600=100*1024*1024字节(Byte);
在C语言中用printf()函数打印字符型变量时,如果想采用”%x”的格式将字符型变量值以十六进制形式打印出来,会出现一个小问题
char buf[10] = {0};
buf[0] = 0xbf;
printf("%2x\n\n\n", buf[0]); /*在终端将会显示成:ffffffbf*/
buf[1] = 0x7f;
printf("%2x\n\n\n", buf[1]); /*在终端将会显示成:7f*/
研究发现,只要字符型变量值的二进制第一位是1,就会如buf[0]所显示的那样,出现6个f。
而格式输出函数printf中会对所输出的变量做有符号/无符号型的判断。如果是有符号型变量,且该值二进制首位为1(如我们定义的是char buf[100],且buf[0]中为0xbf),则会按照补码形式前面全置为1,也就是全为f。(因为终端显示的是8个十六机制数字;另外,我用Tobor C编译,因为Tobor C中int是2bytes,终端显示就成4个十六进制数字了:ffbf,因此我猜测,printf函数显示之前先将有符号型值转换成了一个4bytes的int。当然,这是函数内部的处理,跟我们所说的话题关系不大。)。
按照上面述说的,如果把buf声明为unsigned char型,则显示会变成:bf
而且,如果程序需要讲一个有符号型的变量以十六进制形式输出,且只显示两位,可以强制类型转换为无符号型变量,如上面的buf[0],可以转换成:(unsigned char)buf[0]
(这个是借鉴前人的经验,在测试中出现了类似问题,特写此处方便自己查看)
运行截图
如果有不妥之处,还请指教!(测试文件大小1KB,root是树根,前面是十六进制输出,后面是十进制输出结果,树深度为4)