默克尔树课设

  1. 代码结构
    代码分为3个文件,Merkle_Tree.h(附录1)、Merkle_Tree.cpp(附录2)、Demo.cpp(附录3)。将类的构造及类函数的声明放在了Merkle_Tree.h文件中;Merkle_Tree.cpp中是对Merkle_Tree.h里定义类函数的具体实现;Demo.cpp是最终的测试文件,即主函数。

  2. 文件读取函数:
    void GetAllFiles(~),用于获取path路径下所有文件的相对路径及文件名。

  3. 类:
    (1) class Word,作为hash算法的基本数据处理类型(字),自带比较、复制、初始化、与加、求和及部分SHA1算法的操作。
    (2) class HashValue,用于存储及计算hash值,采用SHA1算法。类中重要函数说明如下:
    a. bool compare(~),与hash值hv比较
    b. void set_h(~),重载函数,对长为length的Word数组进行SHA1运算,生成对应Hash值;或将两个Hash值按字相加后生成新的Hash值。
    (3) class DataPackageRecord,用于存储一个数据包对应的文件名、数据包Hash值的默克尔树节点、数据包序号。
    (4) class DataPackage,用于读取文件,按字节读取,目前暂时只做了txt文件的读取验证。类中重要函数说明如下:
    a. bool readFromFile(~),以字节为基本单元读取文件。
    b. void pretreatment(),进行SHA1算法的填充处理,填充方法见2.2节。
    c. bool byteToWord(),将字节数据流变成Word格式数据流,方便后续处理。
    d. Word* getDataWord(),获取Word向量指针,即获取数据
    (5) class MerkleTreePoint,Merkle树节点类,存储节点对应哈希值,父节点指针,和两个子节点指针。
    (6) class MerkleTree,Merkle树类,存储构造好的Merkle树。重要函数说明如下:
    void setStructure(int n),将树初始化为n个叶子节点(数据包)的Merkle二叉树,由底层向上构造,每个父节点指向0~2个子节点,每个子节点指向一个父节点。
    bool buildMerkleTree(string floder),读取floder文件夹下的文件计算各节点Hash值。单子节点的Hash值为子节点的Hash值的SHA1运算结果。双子节点的hash值为两个子节点的Hash值按字节求和后的值的SHA1运算结果
    compareRoot(~),与另一棵Merkle树进行根节点比较
    getError(~),与另一棵Merkle树由根节点向下进行节点比较,若存在不同,则返回存在不同hash值的底层节点的序号及文件名。
    int getZeroKnowledgeProofHashSequence(~),Merkle树根据要验证的数据包序号,由底层向上依次返回需要参与零知识验证的hash值。

    4.测试文件
    文件夹“Alice”含有按序号00到19共20各大小为115KB的测试文件
    其中每个文件内容除第一个数字对应文件夹序号外,其它全部一致。文件夹“Bob”含有按序号00到19共20各大小为115KB的测试文件与Alex文件夹内容相同。

参考文献
[1]Nakamoto S. Bitcoin: A peer-to-peer electronic cash system, Available: http://bitcoin.org/bitcoin.pdf, 2008.
[2]周李京. 区块链隐私关键技术研究[D]. 北京: 北京邮电大学, 2019: 1-8
[3]Dwork C and Naor M. Pricing via processing or combatting junk mail[C]// In: Annual International Cryptology Conference, pp. 139-147. Berlin: Springer, 1992.
[4]Ding W. Block chain based instrument data management system[J]. China Instrumentation, 2015(10): 15-17.
[5]吴玲燕. 区块链技术在研究生教育教学管理中的应用与挑战[J]. 天津科技, 2019, 46(03): 6-10.
[6]付金华. 高效能区块链关键技术及应用研究[D]. 河南: 战略支援部队信息工程大学, 2020: 3-8, 45-47
[7]吴梦宇, 朱国胜, 吴善超. 基于Merkle树的区块链数据修改方法研究[J]. 信息通信, 2020(10): 10-12+16.
[8]Chelladurai U and Pandian S. Correction to: HARE: A new Hash-based Authenticated Reliable and Efficient Modified Merkle Tree Data Structure to Ensure Integrity of Data in the Healthcare Systems[J]. Journal of Ambient Intelligence and Humanized Computing, 2021, : 1-1.
[9]Secure Hash Standard[S]. Available: http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf
[10]庄上人家工作室, SHA1算法原理[DB/OL], CSDN, 2012.


```cpp
Merkle_Tree.h
#pragma once
#ifndef _MERKLE_TREE_H_
#define  _MERKLE_TREE_H_


#include<iostream>
#include<vector>
#include<io.h>
#include<windows.h>
#include<fstream>
#include<string>
#include<iostream>
using namespace std;

namespace MERKLETREE {
	//子类声明
	class Word               ; //字
	class HashValue          ; //哈希值
	class DataPackageRecord  ; //数据包记录
	class DataPackage        ; //数据包
	class MerkleTreePoint    ; //默克尔树节点
	class MerkleTree         ; //默克尔树

	class Word {
	public:
		int word;
		Word() { word = 0; }
		bool compare(Word& w);
		void s(int n);//循环左移n位
		void ft(int t, Word& b, Word& c, Word& d);

		void f(Word& a, Word& b, Word& c, Word& d);
		void copy(Word& w){word = w.word;}
		void setnum(int w) { word = w; }
		void add(Word& b) { word +=b.word; }
		void add(Word& b, Word& c) { word = b.word+c.word; }
	};
	class HashValue {
	public:
		Word h[5] ; //32*5;
		Word kt[4]; //常量字
		int length; //hash字长

		HashValue() { init(); length = 5; }
		HashValue(int n) {
			init();
			if (n == 0) {
				for (int i = 0; i < length; i++)
				{
					h[i].setnum(0x00);
				}
			}
		}
		HashValue(const HashValue& hv) {
			length = hv.length;
			for (int i = 0; i < 5; i++)
			{
				h[i] = hv.h[i];
			}
			for (int i = 0; i < 4; i++)
			{
			kt[i] = hv.kt[i];
			}
		}
		bool compare(HashValue& hv);

		//sha-1
		void init(); //初始化缓存、常量字
		void set_h(Word* data, int length);
		void set_h(HashValue& hv1, HashValue& hv2);
		void output();
	};

	class DataPackageRecord {
	public:
		MerkleTreePoint* mtp;//该包对应的节点
		string filename;//文件名//包名
		int serialNumber;//序号
	};
	class DataPackage {
		friend HashValue;
		friend MerkleTree;
	private:
		string filename;
		char* dataByte;
		char* dataByteFill;//填充
		Word* dataWord;
		int lengthByte;
		int lengthByteFill;
		int lengthWord;
	public:
		DataPackage() {
			dataByte = nullptr;
			dataWord = nullptr;
			dataByteFill = nullptr;
			lengthByte = 0;
			lengthWord = 0;
			lengthByteFill = 0;
		}
		~DataPackage() {
			if (dataByte != nullptr)
				delete[] dataByte;
			if (dataWord != nullptr)
				delete dataWord;
			if (dataByteFill != nullptr)
				delete dataByteFill;
		}

		void init();
		bool readFromFile(string fname); //文件读取为BYTE向量
		void pretreatment(); //hash-1的预处理,BYTE类型
		bool byteToWord();//byte转为word类型
		Word* getDataWord() { return dataWord; }
		int getLengthWord() { return lengthWord; }
	};

	class MerkleTreePoint {
		friend MerkleTree;
		DataPackageRecord* dpr;
	public:
		MerkleTreePoint() {
			dpr = nullptr;
			last = nullptr;
			next[0] = nullptr;
			next[1] = nullptr;
		}
	private:
		HashValue hv;
		MerkleTreePoint* last;//父节点
		MerkleTreePoint* next[2];//子节点
	};
	class MerkleTree {
		//严禁随意删除节点
	private:
		int deep;//深度
		int num;//数据包个数
		DataPackageRecord* datarecord;//数据包记录列表
		MerkleTreePoint* root;//根节点
	public:
		MerkleTree() {//从文件夹中建立默克尔树
			deep = 0;
			num = 0;
			datarecord = nullptr;
			root = nullptr;
		}
		~MerkleTree() {
			init();
		}
		void mtpDelete(MerkleTreePoint* mtp); //删除节点某节点及其后续节点
		void init(); //初始化树&&清理树
		void setStructure(int n); //依据n设置树结构
		bool buildMerkleTree(string floder); //依据文件生成树
		void treeHash(MerkleTreePoint* mtp);
		bool compareRoot(MerkleTree& mt); //比较两个树根hash

		int getError(MerkleTree& mt, vector<string>&errorfilenames, vector<int>&error); //返回error数量,返回error数组
		void catchError(MerkleTreePoint* mtp1, MerkleTreePoint* mtp2, vector<int>& error);
		int getZeroKnowledgeProofHashSequence(int serialNumber, vector<HashValue>&hashvalue); //返回验证序列个数
	};

}
#endif  _MERKLE_TREE_H_
 
附录2
Merkle_Tree.cpp
#include "Merkle_Tree.h"

using namespace MERKLETREE;

//文件读取函数声明
void GetAllFiles(string path, vector<string>& filenames, vector<string>& filepaths);

//Word类
bool
Word::compare(Word& w) {
	if (word != w.word)
		return false;
	return true;
}
void 
Word::s(int n) {
	unsigned int temp = word;
	word = (temp << n) | (temp >> (32 - n));
}
void
Word::ft(int t, Word& b, Word& c, Word& d) {
	if (t / 20 == 0) {
		word = ((b.word & c.word) | ((~b.word) & d.word));
	}
	else if (t / 20 == 1) {
		word = (b.word ^ c.word ^ d.word);
	}
	else if (t / 20 == 2) {
		word = ((b.word & c.word) | (b.word & d.word) | (c.word & d.word));
	}
	else if (t / 20 == 3) {
		word = (b.word ^ c.word ^ d.word);
	}
	else {
		word = 0;
	}
}
void
Word::f(Word& a, Word& b, Word& c, Word& d) {
	word = (a.word ^ b.word ^ c.word ^ d.word);
	s(1);
}


//HashValue类
bool
HashValue::compare(HashValue& hv) {
	if (length != hv.length)
		return false;
	for (int i = 0; i < length; i++)
	{
		if (!h[i].compare(hv.h[i]))
			return false;
	}
	return true;
}
void
HashValue::init() {
	length = 5;
	h[0].word = 0x67452301;
	h[1].word = 0xEFCDAB89;
	h[2].word = 0x98BADCFE;
	h[3].word = 0x10325476;
	h[4].word = 0xC3D2E1F0;

	kt[0].word = 0x5A827999;//  (0 <= t <= 19)
	kt[1].word = 0x6ED9EBA1;// (20 <= t <= 39)
	kt[2].word = 0x8F1BBCDC;//(40 <= t <= 59)
	kt[3].word = 0xCA62C1D6;// (60 <= t <= 79)
}
void
HashValue::set_h(Word* data, int length) {
	init();
	Word A, B, C, D, E;
	Word cache80[80];
	Word temp;
	int len = length / 16;
	for (int i = 0; i < len; i++)
	{
		//output();
		//1
		for (int t = 0; t < 16; t++)
			cache80[t].copy(data[i * 16 + t]);
		//2
		for (int t = 16; t < 80; t++)
			cache80[t].f(cache80[t - 3], cache80[t - 8], cache80[t - 14], cache80[t - 16]);
		//3
		A.copy(h[0]);
		B.copy(h[1]);
		C.copy(h[2]);
		D.copy(h[3]);
		E.copy(h[4]);
		//4
		for (int t = 0; t < 80; t++)
		{
			temp.copy(A);
			temp.s(5);
			Word temp2;
			temp2.ft(t, B, C, D);
			temp.add(temp2);
			temp.add(E);
			temp.add(cache80[t]);
			temp.add(kt[t / 20]);
		}
		E.copy(D);
		D.copy(C);
		C.copy(B);
		C.s(30);
		B.copy(A);
		A.copy(temp);
		//5
		h[0].add(A);
		h[1].add(B);
		h[2].add(C);
		h[3].add(D);
		h[4].add(E);
	}
}
void
HashValue::set_h(HashValue& hv1, HashValue& hv2) {
	Word w[16];
	for (int i = 0; i < 5; i++)
	{

		w[i].add(hv1.h[i], hv2.h[i]);
	}
	w[5].setnum(0x80000000);
	for (int i = 6; i < 15; i++)
	{
		w[i].setnum(0x00);
	}
	w[15].setnum(0x160);
	set_h(w, 16);
}
void
HashValue::output() {
	for (int i = 0; i < length; i++)
	{
		cout << h[i].word << " ";
	}
	cout << endl;
}


//DataPackage类
void
DataPackage::init() {
	if (dataByte != nullptr)
		delete[] dataByte;
	lengthByte = 0;
	if (dataByteFill != nullptr)
		delete[] dataByteFill;
	lengthByteFill = 0;
	if (dataWord != nullptr)
		delete dataWord;
	lengthWord = 0;
}
bool
DataPackage::readFromFile(string fname) {
	init();
	ifstream  file(fname, ios::in);
	char temp = 0;//byte
	int lengthtemp = 0;
	if (!file)
		return false;
	while (!file.eof())
	{
		file.get(temp);
		lengthtemp++;
	}
	file.close();
	file.open(fname, ios::in);
	dataByte = new char[lengthtemp];
	while (!file.eof())
	{
		file.get(dataByte[lengthByte]);
		lengthByte++;
	}
	if (lengthByte != lengthtemp)
	{
		file.close();
		return false;//防止读取途中文件长度变化(被篡改)
	}
	file.close();
	return true;
}
void
DataPackage::pretreatment() {
	int temp = lengthByte % 64;
	if (temp < 56)
		lengthByteFill = 64 - temp;
	else
		lengthByteFill = 128 - temp;
	dataByteFill = new char[lengthByteFill];
	dataByteFill[0] = 0x80;
	for (int i = 1; i < lengthByteFill - 4; i++)//int类型32bit,所以最多32位
		dataByteFill[i] = 0x00;
	for (int i = 0; i < 4; i++)
	{
		dataByteFill[lengthByteFill - 4 + i] = (char)((lengthByte * 8 << (i * 8)) >> 24);
	}
}
bool
DataPackage::byteToWord() {
	int len = lengthByte + lengthByteFill;
	if (len % 4 != 0)
		return false;
	lengthWord = len / 4;
	if (lengthWord % 16 != 0)
		return false;
	dataWord = new Word[lengthWord];
	int i = 0;
	for (i = 0; i < lengthByte; i++)
	{
		unsigned int temp = (unsigned int)dataByte[i];
		temp = (temp << 24) >> (((i) % 4) * 8);
		dataWord[(i) / 4].word += temp;
	}
	for (int j = 0; j < lengthByteFill; j++)
	{
		unsigned int temp = (unsigned int)dataByteFill[j];
		temp = (temp << 24) >> (((i + j) % 4) * 8);
		dataWord[(i + j) / 4].word += temp;
	}
}


//MerkleTree类
void
MerkleTree::mtpDelete(MerkleTreePoint* mtp)//删除节点某节点及其后续节点
{
	if (mtp->next[0] != nullptr) {
		mtpDelete(mtp->next[0]);
	}
	if (mtp->next[1] != nullptr) {
		mtpDelete(mtp->next[1]);
	}
	delete mtp;
}
void
MerkleTree::init()//初始化树&&清理树
{
	if (root != nullptr)
		mtpDelete(root);
	root = nullptr;
	if (datarecord != nullptr)
		delete[] datarecord;
	datarecord = nullptr;
	deep = 0;
	num = 0;
}
void
MerkleTree::setStructure(int n)//依据n设置树结构
{
	init();
	num = n;
	deep = 0;
	while (n > 1) {
		deep++;
		if (n % 2 == 0)
			n = n / 2;
		else
			n = n / 2 + 1;
	}
	deep++;
	MerkleTreePoint*** mtpList = new MerkleTreePoint * *[deep];//每层的节点列表
	int* mtpListLength = new int[deep];//每层节点数
	//空间申请
	for (int i = deep, n = num; i > 0; i--)
	{
		mtpListLength[i - 1] = n;
		mtpList[i - 1] = new MerkleTreePoint *[n];
		if (n % 2 == 0)
			n = n / 2;
		else
			n = n / 2 + 1;
	}
	for (int i = 0; i < deep; i++)
	{
		for (int j = 0; j < mtpListLength[i]; j++)
		{
			mtpList[i][j] = new MerkleTreePoint;
		}
	}
	//设置结构
	for (int i = 0; i < deep - 1; i++)
	{
		for (int j = 0; j < mtpListLength[i]; j++)
		{
			mtpList[i][j]->next[0] = mtpList[i + 1][j * 2];
			mtpList[i + 1][j * 2]->last = mtpList[i][j];
			if (j * 2 + 1 < mtpListLength[i + 1])
			{
				mtpList[i][j]->next[1] = mtpList[i + 1][j * 2 + 1];
				mtpList[i + 1][j * 2 + 1]->last = mtpList[i][j];
			}
		}
	}
	datarecord = new DataPackageRecord[num];
	for (int i = 0; i < num; i++)
	{
		datarecord[i].mtp = mtpList[deep - 1][i];
		datarecord[i].serialNumber = i;
	}
	//控制权移交;
	root = mtpList[0][0];
	//空间清理
	for (int i = 0; i < deep; i++)
	{
		delete[] mtpList[i];
		mtpList[i] = nullptr;
	}
	delete[] mtpList;
}
bool
MerkleTree::buildMerkleTree(string floder)//依据文件生成树
{
	vector<string>filenames;
	vector<string>filepaths;
	GetAllFiles(floder, filenames, filepaths);
	setStructure(filenames.size());
	DataPackage Toolman;//文件读取头
	//处理叶子节点
	for (int i = 0; i < num; i++)
	{
		if (!Toolman.readFromFile(filepaths[i]))
			return false;
		datarecord[i].filename = filenames[i];
		Toolman.pretreatment();
		if (!Toolman.byteToWord())
			return false;
		datarecord[i].mtp->hv.set_h(Toolman.getDataWord(), Toolman.getLengthWord());
		datarecord[i].mtp->dpr = &datarecord[i];
		//cout << filepaths[i] << endl;
		//datarecord[i].mtp->hv.output();
	}
	//处理枝干节点
	treeHash(root);
}
void
MerkleTree::treeHash(MerkleTreePoint* mtp) {
	if (mtp->next[0] != nullptr)
		treeHash(mtp->next[0]);
	if (mtp->next[1] != nullptr)
		treeHash(mtp->next[1]);
	if ((mtp->next[0] != nullptr) && (mtp->next[1] != nullptr))//非叶子节点进行hash计算
		mtp->hv.set_h(mtp->next[0]->hv, mtp->next[1]->hv);
	else if ((mtp->next[0] == nullptr) && (mtp->next[1] == nullptr))
		return;
	else//单分支hash节点
	{
		HashValue hvtemp(0);
		if (mtp->next[0] != nullptr)
		{
			mtp->hv.set_h(mtp->next[0]->hv, hvtemp);
		}
		else if (mtp->next[1] != nullptr)
		{
			mtp->hv.set_h(mtp->next[1]->hv, hvtemp);
		}
	}
}
bool
MerkleTree::compareRoot(MerkleTree& mt)//比较两个树根hash
{
	return root->hv.compare(mt.root->hv);
}
int
MerkleTree::getError(MerkleTree& mt, vector<string>&errorfilenames, vector<int>&error)//返回error数量,返回error数组
{
	if (compareRoot(mt))
		return 0;
	catchError(root, mt.root, error);
	for (int i = 0; i < error.size(); i++)
	{
		errorfilenames.push_back(datarecord[error[i]].filename);
	}
	return error.size();
}
void
MerkleTree::catchError(MerkleTreePoint* mtp1, MerkleTreePoint* mtp2, vector<int>& error)
{
	if ((mtp1->next[0] == nullptr) && (mtp2->next[0] == nullptr) && (mtp1->next[1] == nullptr) && (mtp2->next[1] == nullptr))
		if (!mtp1->hv.compare(mtp2->hv))
			error.push_back(mtp1->dpr->serialNumber);
	if ((mtp1->next[0] != nullptr) && (mtp2->next[0] != nullptr))
		if (!mtp1->next[0]->hv.compare(mtp2->next[0]->hv))
			catchError(mtp1->next[0], mtp2->next[0], error);
	if ((mtp1->next[1] != nullptr) && (mtp2->next[1] != nullptr))
		if (!mtp1->next[1]->hv.compare(mtp2->next[1]->hv))
			catchError(mtp1->next[1], mtp2->next[1], error);
}
int
MerkleTree::getZeroKnowledgeProofHashSequence(int serialNumber, vector<HashValue>&hashvalue)//返回验证序列个数
{
	MerkleTreePoint* mtptemp = datarecord[serialNumber].mtp;
	MerkleTreePoint* mtptemp2;
	//HashValue* hvtemp;
	while (mtptemp->last != nullptr) {
		mtptemp2 = mtptemp->last;
		if ((mtptemp2->next[0] != nullptr) && (mtptemp2->next[1] != nullptr))
		{
			if (mtptemp->hv.compare(mtptemp2->next[0]->hv))
			{
				//hvtemp = new HashValue();
				hashvalue.push_back(mtptemp2->next[1]->hv);
			}
			else
			{
				//hvtemp = new HashValue();
				hashvalue.push_back(mtptemp2->next[0]->hv);
			}
		}
		else
		{
			HashValue hvtemp(0);
			hashvalue.push_back(hvtemp);
		}
		mtptemp = mtptemp2;
		mtptemp2 = nullptr;
	}
	//hvtemp = new HashValue();
	hashvalue.push_back(root->hv);
	return hashvalue.size();
}


//文件读取--获取path路径下的所有文件
void
GetAllFiles(string path, vector<string>& filenames, vector<string>& filepaths)
{
	intptr_t hfile = 0;
	struct _finddata_t fileinfo;
	string p;
	if ((hfile = _findfirst(p.assign(path).append("/*").c_str(), &fileinfo)) == NO_ERROR);
	{
		do
		{
			if ((fileinfo.attrib & _A_SUBDIR))
			{
				if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != NO_ERROR)
					GetAllFiles(p.assign(path).append("/").append(fileinfo.name), filenames, filepaths);
			}
			else
			{
				filepaths.push_back(p.assign(path).append("/").append(fileinfo.name));
				filenames.push_back(fileinfo.name);
			}
		} while (_findnext(hfile, &fileinfo) == NO_ERROR);
		_findclose(hfile);
	}
}
 

Demo.cpp
//代码中一些函数的参考网址:
/*
获取路径下的所有文件:
https://www.cnblogs.com/fnlingnzb-learner/p/6424563.html
*/

#include"Merkle_Tree.h"
using namespace std;
using namespace MERKLETREE;
int main() {
	//准备,两个文件夹,各含20个文件
	//生成发送方默克尔树,接收方默克尔树
	MerkleTree Alice;
	MerkleTree Bob;

	Alice.buildMerkleTree("Alice");
	Bob.buildMerkleTree("Bob");

	cout << "数据比较实验:" << endl;
	cout << "数据比较结果(1为相同,0为相异):" << Bob.compareRoot(Alice) << endl;
	cout << endl;


	cout << "定位错误实验:" << endl;
	vector<int>error;
	vector<string>errorfiles;
	cout << "错误个数" << Alice.getError(Bob, errorfiles, error) << endl;
	for (int i = 0; i < error.size(); i++)
	{
		cout << "错误文件序号:" << error[i] << "错误文件名:" << errorfiles[i] << endl;
	}

	cout << "零知识证明实验:" << endl;
	vector<HashValue>hashvalue;
	int num_datapakage = 6;
	int n = Alice.getZeroKnowledgeProofHashSequence(num_datapakage, hashvalue);
	HashValue temp;
	DataPackage Carl;
	bool ishavedatapackage = false;
	string datapakage_filename = "";
	if (0 < num_datapakage && num_datapakage < 10)
		datapakage_filename += "Bob/0";
	else if (num_datapakage > 10 && num_datapakage < 100)
		datapakage_filename += "Bob/";
	else
		cout << "序号错误!" << endl;
	datapakage_filename += to_string(num_datapakage) + ".txt";
	Carl.readFromFile(datapakage_filename);
	Carl.pretreatment();
	Carl.byteToWord();
	temp.set_h(Carl.getDataWord(), Carl.getLengthWord());
	cout << "执行hash" << n - 1 << "次。" << endl;
	for (int i = 0; i < n - 1; i++)
	{
		temp.set_h(temp, hashvalue[i]);
	}
	ishavedatapackage = temp.compare(hashvalue[n - 1]);
	cout << "Alice 是否含有Carl文件(" << datapakage_filename << ")的内容(1为含有,0为不含有):" << ishavedatapackage << endl;
	//零知识证明
	//接收方发送一个数据包序号
	//发送方根据序号发送root值序列、由底层向上,
	//接收方依据本数据包生成哈希值,依次向上生成哈希值,
	return 0;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值