C语言简易区块链数据结构

哈希简谈(sha_256)

就是对任意长度字符数据都会生成唯一的长度为256bit长的哈希值,且不可逆的一种加密方式
哈希函数,又称散列算法,是一种从任何一种数据中创建小的数字“指纹”的方法。散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。该函数将数据打乱混合,重新创建一个叫做散列值(或哈希值)的指纹。散列值通常用一个短的随机字母和数字组成的字符串来代表。

概要

主要参照:简易区块链C语言实现
博主讲得很清楚,简洁易懂,适合初学区块链

区块链数据结构

1633843973844
具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typedef union{
unsigned char byte[104];//与以下结构体等长,利用了联合的特性
struct{
unsigned char sha_all[32];//对本区块head、body进行SHA256运算(且最终保证满足前导0要求)的结果
unsigned long nonce;//随机数,目的是在不改变其它有意义数据的情况下使每次哈希结果改变直到最后能够生成满足要求前导0
unsigned char sha_prev[32];//保存上一个区块的sha_all
unsigned char sha_block[32];//对本区块body进行SHA256运算的结果,可视为经过不可逆加密后的数据
};
}block_head;//区块链核心,目的是保证不可篡改性

typedef struct{
char body[1000];
}block_body;//实际数据

typedef struct{
block_head head;
block_body body;
}block;

总之HEAD是核心,nonce的意义就在满足前导0要求,而前导0(不一定要求为0)的位数某种程度上控制了哈希运算的加密难度(复杂度)(比特币要靠挖就是算这个费算力),正因为有此不定的数据存在,使得哈希运算次数不定,则即使哈希加密可逆也无法解密出整个区块链保存的数据。又由于sha_all关联于sha_prev与sha_block,则任意修改区块链中的body数据,会使得sha_block不同,进而sha_all不同,直接影响了sha_prev无法衔接,可轻易识别非法修改数据。

这里前导0只要求两位(不然算的慢):

1
2
3
4
5
int check_sha(BYTE text[32]){  // 检验是否为前导0
if(text[0]==0x00)
return 1;
return 0;
}

初始化程序

基本上掌握初始化原理就差不多弄得懂了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
void init(){
block_head* gen=malloc(104);
block_body gbody;
strcpy(gbody.body,"helloworldjin");//创世信息,后面的区块就在此存数据了
memset(gen->byte,0,104);//初始化0
sha256_main(gbody.body,strlen(gbody.body),gen->sha_block);//将body信息哈希一次

while(gen->nonce<0xffffffffffffffff){//一直哈希直到满足前导
BYTE text[10200];//暂存块头块体
read_head_body(text,gen,gbody);//块头快体写入

BYTE buf[32];//暂存加密后的块头块体
size_t textsize=104-32+strlen(gbody.body);//(注意要减去32 sha_all)
sha256_main(text,textsize,buf);//text存的块头快体一起哈希

printf("当前nonce为: %lu ",gen->nonce);
printf("前8bit为:%02x\n",buf[0]);

if(check_sha(buf)){// 检查是否为前导0
//成功则记录区块
printf("创世块nonce为:%lu\n",gen->nonce);

//文件写入处理
const char* filename = "genesis.block";
FILE *fp = fopen(filename,"w");
int i;
for (i=0;i<32;i++) {
gen->sha_all[i] = buf[i];//加密后的块头块体哈希值
}
for (i=0;i<104;i++) { // 写入区块头(包含加密后的本块块头块体哈希值,nonce随机数,sha_prev上个区块加密后的块头块体哈希值,sha_block本块数据哈希值)
fprintf(fp,"%c",gen->byte[i]);
}
for (i=0;i<strlen(gbody.body);i++) { // 写入区块体(包含直接的数据)
fprintf(fp,"%c",gbody.body[i]);
}
fclose(fp);
break;
}
gen->nonce++;
}
}

考虑加入时间戳是否就是在最后通过前导检测后,写入时多写一个时间关联数据,意义类似nonce随机数?

再分析一个函数

找最后一个块的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
block find_last_block(){
block block_list[100];//这个可以随便扩
const char *filePath = ".";
int size = read_dir_block(block_list,filePath); // 一共size个区块(简单根据文件名数),这里不只是算了个数字,还将每个文件数据导入到了block_list[i]
if (size <= 0) {
printf("no block file\n");//这个随便了
block tmp;
memset(tmp.head.byte, 0, 104);
return tmp;
}
// 然后给所有区块弄一个拓扑排序
// 先遍历找第一个 block
block topo[size];
BYTE hash[32] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};//创世区块的前导(这里如果确定中间某一个块的前导值也可以中途开找)
int i,j;
for (i=0;i<size;i++) {
int cmp = memcmp(hash,block_list[i].head.sha_prev,32);
if(cmp == 0){ // equal, this is the first block
copy(&topo[0],&block_list[i]);
break;
}
}// topo[0] 为创世纪块

//双循环链接区块
for (i=1;i<size;i++) {
for (j=0;j<size;j++) {
int cmp = memcmp(topo[i-1].head.sha_all,block_list[j].head.sha_prev,32);
// 遍历所有区块,prev == topo[i-1] 的便是对应 topo[i]
if (cmp == 0) {
copy(&topo[i],&block_list[j]);
break;
}
}
}
return topo[size-1];//返回最后一个
}

自然可以简单修改为找对应需要数据区块的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
block find_block(char* content){        // 读各个区块
block block_list[100];
const char *filePath = ".";
int size = read_dir_block(block_list,filePath); // 一共 size 个区块
// 给所有区块弄一个拓扑排序
if (size <= 0) {
printf("no block file\n");
block tmp;
memset(tmp.head.byte, 0, 104);
return tmp;
}
// 先找第一个 block
block topo[size];
BYTE hash[32] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
int i,j;
for (i=0;i<size;i++) {
int cmp = memcmp(hash,block_list[i].head.sha_prev,32);
if(cmp == 0){ // equal, this is the first block
copy(&topo[0],&block_list[i]);
break;
}
}
// topo[0] 为创世纪块
for (i=1;i<size;i++) {
for (j=0;j<size;j++) {
int cmp = memcmp(topo[i-1].head.sha_all,block_list[j].head.sha_prev,32);
// 遍历所有区块,找到 prev == topo[i-1] 的,以此来确定 topo[i]
if (cmp == 0) {
copy(&topo[i],&block_list[j]);
break;
}
}
}
//从链接完的区块链topo排序中找
printf("start find *****************\n");
for(i=0;i<size;i++){
printf("\nbody:%s\n",topo[i].body.body);
if(strstr(topo[i].body.body,content)){
printf("\n****finded****\n");
break;
}
}
return topo[i];
}

可能复杂了,但是改的简单)

---------------THEEND---------------