发明内容
针对上述存在的问题,本发明旨在提供一种具有通用性的方法,通过Monad转换,将这种非二叉树转为可认证数据结构,即Merkle树,减少程序员的重复工作。
为了实现上述目的,本发明所采用的技术方案如下:
基于可认证数据结构的区块链语义分析的方法,其特征在于,包括以下步骤:
S1:获取比特币区块中非二叉树结构的数据;
S2:基于Monad技术将非二叉树结构的数据转换为可认证数据结构;
S3:使用得出的可认证数据结构进行比特币交易验证。
进一步地,步骤S2的具体操作步骤包括:
S21:输入非二叉树结构的数据;
S22:从Merkle树中抽取出生成证明流和验证证明流的操作性语义,并写入编译器;
S23:将编译器中的所述操作性语义进行Monad转化,成为等价的指示性语义;
S24:将得到的所述指示性语义转化为目标编译器语言编码的库文件;
S25:调用所述目标编译器语言编码的库文件将输入的非二叉树结构转换为可认证数据结构。
进一步地,步骤S25中所述的可认证数据结构为基于Merkle树的二叉树数据结构。
进一步地,步骤S23中所述的将操作性语义包括纯函数、纯函数组合、非纯函数和非纯函数组合,将操作性语义Monad转换为等价的指示性语义的具体步骤包括:
S231:将操作性语义中的纯函数和纯函数组合用λ演算表示,并将其编码为λ表达式,再用let语句表示其指示性语义;
S232:将操作性语义中的非纯函数和非纯函数组合进行范畴转化;
S232:将转换后的非纯函数和非纯函数组合编码,得出指示性语义。
进一步地,步骤S231的具体操作步骤为:
步骤2311:将纯函数f(x)和g(x),用λ演算表示为:
f(x)=λx.e1和g(x)=λx.e2 (1),
其中,e1和e2表示任意表达式;
h(x)=(λx.e1)[e2/x],[e2/x] (2),
其中,[e2/x]表示用e2替换表达式e1中出现的所有非自由变量x;
步骤2313:将纯函数组合h(x)编码为λ表达式:
h(x)=(fun x→e1)e2 (3),
将其用let语句表示为:
h(x)=let y=e2in e1 (4)。
进一步地,步骤S232中的具体操作步骤包括:
S2321:将非纯函数的基本类型映射到Kleisli范畴中的对象;
S2322:将非纯函数映射到Kleisli范畴中的态射;
S2323:在Kleisli中采用bind操作将非纯函数转换为纯函数;
S2324:将转换后的纯函数进行组合;
S2325:将转换后的纯函数和纯函数组合进行编码表示。
本发明的有益效果是:
第一,通过Monad将编译器中一小部分操作性语义转化为等价的指示性语义。在实现时,Monad并未向编译器语法树引入新语法属性,基于OCaml自身的语法实现了添加新语言特性的目标。这种只使用源语言的语法就能产生新语言特性Monad方法普遍适用于函数式编程语言。不需要语言特性设计者深入了解修改编译器语法树,也不需要修改源语言的语法。
第二,使用Monad方法创建认证数据结构的方法具有通用性。只要程序语言编译器具有最一般性的类型推导系统,就能通过该方法将抽取出的证明流的语义写入到库文件中,形成新的语言特性。例如,Haskell是另外一种函数式程序语言编译器,同样也能通过Monad方法,在仅使用Haskell自身语言语法,便可实现在程序语言中引入可认证数据结构语言特性;
综上所述,本发明采用一种更为抽象和凝练的基于范畴论的monad技术,通过将编译器中一小部分操作性语义转化为等价的指示性语义(denotational semantics),可从编译器中抽取出需要的语义将其转化为目标编译器语言编码的库文件供程序员调用。因为Monad适用于所有基于Hindley-Milner类型推导系统函数式的编程语言,所以这种方法能推广到所有这类编程语言编译器。
具体实施方式
为了使本领域的普通技术人员能更好的理解本发明的技术方案,下面结合附图和实施例对本发明的技术方案做进一步的描述。
1、Merkle树
Merkle是一个带有Hash指针的二叉树,如附图1所示。其叶子节点存储数据,非叶子节点存储Hash指针。先计算两个叶子节点存储的数据的哈希值,然后将结果存储到父节点。如此反复计算,直到计算根节点左右孩子的哈希值,并将结果存储到根节点中。Merkle树最主要的优点是能提供节点与Merkle树关系的证明。
例如,如果客户向服务器提出数据d2的请求后,服务器向客户端返回<data,proof>,即:
proof=hash(h1,h2)+hash(h3,h4)+hash(d2)
path=<L;R>;
如果客户希望知道数据d2是否确实是Merkle树的成员,可以从叶节点沿路径<L;R>计算每个中间节点的哈希值,向上一直到根节点,依次与证明流proof提供的哈希值进行比对。
2、Monad理论框架与解释
(1)计算的一般化
通常程序被认为是函数。但计算机科学中的程序,与数学中的函数有很大区别。给定相同输入,每次运行程序可能会得到不同结果。例如,数学上的函数f(x)=x+1作为计算机中的程序运行时,可能会有以下几类结果。
1.如果输入x=1,结果为2;如果程序在运行时,突然断电则结果未知,记为⊥。结果集合B=f(A)+⊥。其中,+号表示OR的关系。
2.如果输入x=1,结果为2;如果得到计算结果,将结果打印到屏幕上。结果集合C=(f(A),S)=f(A)×S。其中,×表示AND关系,S表示输出到屏幕。
以上两个例子说明,数学上的函数变为计算机上的程序后,即便每次输入固定数值,也可能会得到不同的输出结果集合。为表示区别,将数学上函数称为纯函数,将程序表示的函数称为非纯函数,这两类函数都有“计算”的概念。很容易看出绝大多数程序都是非纯函数的计算。若能将计算概念一般化,即用一种数学公式统一数学上的纯函数和程序的非纯函数的表示,则能将数学和程序通过一般性计算连接起来。计算一般化的典型代表是范畴理论中的Monad,下面再Monad涉及到范畴、函子和自然转换的定义。
(2)范畴和对象
定义1范畴(Category)
同时,范畴由一组态射(Morphsim)构成,称为
通常写作
表示
因为注意到数学对象在映射时具有保留结构的特点,因此范畴论期望能抽象出数学结构映射关系。例如,将范畴论应用到编程语言的类型理论研究时,可将数据类型类比为对象,类型之间的映射类比为态射。函数f1:int→int,f2:char→char和f3:float→float都有相似的结构。果让所有简单数据类型从类型变量*={α,β,γ,...}中取值,那么上述三个函数具有统一的形式,即f:*→*。
事实上,在范畴论中还能对f:α→α进一步抽象结构,学术著作中一般称之为lift。例如,g
1:α→α,g
2:β→β和g
3:(α-β)→(α→β)分别表示三种不同函数,但他们之间有相似结构□→□,可以表示*→*,(*→*)→(*→*)等。如果令
(
是Kind首字母,表示一种超级类型),那么上述三个函数具有统一形式,即
不论是*→*还是
都形为·→·。·是对象,比集合概念更为抽象。上述示例中,·可以是*或
范畴论不关心*和
内部具体数学结构,关心的是对象与对象的态射间的具体属性。
(3)函子
定义2函子(Functor)
给定两个范畴
和
在
和
之间存在一个函子,它由以下部分组成:
并且满足以下条件:
2.F1(id(A))=id(F0(A))
函子
符合范畴论统一形式·→·,此时·表示的数学对象是范畴。因为函子在范畴间映射对象的同时还映射了态射,而态射可看成对象之间的结构,所以函子就是结构与结构之间映射的函数,并可实现结构之间的映射与组合。函子表示"(原始)结构→(映射后的)结构"的变换方法。
(4)自然转换
定义3自然转换(Natural Transformation)
给定两个范畴
和
并给定两个函子F和G,使得
自然变换是F和G之间的一组态射φ:F→G,满足:
2.对每个态射
满足F(f)·φ(B)=φ(A)·G(f)。
自然转换φ:F→G符合范畴论统一形式·→·,此时·表示的数学对象是函子。自然转换表示"(映射后的)结构1→(映射后的)结构2"的两个映射后结构之间的转换。如果将函子带入到自然转换中,会得到一个抽象关系"(原始结构→映射后的结构1)→(原始结构→映射后的结构2)"。
(5)态射、函子和自然转换的关系
态射、函子和自然转换之间的关系可以描述为,将范畴想象为一个平面
令平面中存在一个中间透明、顶点和四条边不透明的正方形。正方形的四个顶点如同对象,构成正方形的四条边如同态射。函子如同光源照射,照射正方形会在墙面
上留下投影。假设通过适当角度
使得正方形在墙面上留下的投影恰好为一个正三角形。又找另外一种适当角度
使得正方形在墙面上留下的投影恰好为一个倒三角形。这两种投影都源自于同一种数据源,即正方形,所以这两种投影方法F和G之间自然存在一种转换关系(自然转换)φ:F→G。
(6)纯函数组合需满足的属性
程序可由多个函数组成。程序的可组合性表示整个程序的行为由所构成的每个函数决定。假设,
构成一个程序,令
那么
表示的含义是程序P可像搭积木一样构成,先挑选功能f,再与功能P1组合的结果,与先挑选功能P2再与功能h组合的结果是一样的。因此,可组合的含义是功能可替换。在数学上纯函数满足可组合属性的集合M,就是含幺半群(monoid)代数结构
其中
表示某种组合操作,
表示单位元。
值得注意的是,含幺半群是非对称代数结构,即没有属性
这种特性恰好满足编程时函数调用顺序的要求,即一个函数序列不同调用顺序生成不同结果。如果将数据当做对象,函数当做态射,程序非常像一种含幺半群代数结构。
(7)非纯函数的组合性问题
任意程序都能当做是函数,从两个已有函数可以组合出新函数。函数组合的要求是前一个函数的值域等于后一个函数的定义域。实际情况是,并非每个函数的输入都能恰好映射到函数的输出中,会产生一些附加行为或数据。例如,将数据写入文件,数据输出到屏幕。这类附加行为称之为计算效果(computation effective),此类函数称为非纯函数。类比纯函数,非纯函数能够组合必须满足两个条件,一是要具备模拟纯函数的单位函数(identity function),二是要具备自定义的两个函数的组合规则。
Moggi观察到所有非纯函数都有一样的称为monad的计算结构。如果输入数据为A,将所有在此结构上函数操作的集合统称为T。那么输出结果要么就是TA,恰好是所有对应于输入为A的、纯函数集合为T的输出结果的集合。要么输出结果是TB,表示输出结果对应于所有输入为B的、所有纯函数集合为T的输出结果的集合(如附图2所示)。如果比较TA与TB输出结果的大小,TB比TA多出来的数据结果是TB具备的行为结果,而TA所没有的行为结果,就是所谓的计算效果。如果f:A→TB与g:B→TC进行组合,只需要将g的定义域B扩大为TB,同时将g的陪域扩大为TTB即可。准确的monad数学定义如下所述。
(8)Monad的数学定义与解释
首先,Monad有两种范畴论的解释性定义方式:
第一种,是Eilenberg-Moore从范畴论的角度对monad表现的行为进行解释(如附图3所示)。该定义指出,monad就是在范畴
上定义的一个三元组(T,η,μ),由一个函子(functor)
以及两个自然转换η:1
e→T和μ:T
2→T构成。η称为单位(unit)操作,μ称为组合(multiplication)操作。作为monad,它必须同时满足等式μ·Tη=1=μ·ηT和μ·Tμ=μ·μT。
如果输入数据为A,则ηA:A→TA,μA:T2A→TA。对应到函数式编程语言中的monad,η就应为return(ret)操作,而μ对应为join操作。第一种解释便于理解monad,但不便于具体编码实现,主要是因为join操作异于编程人员对函数组合的一般性理解。
非纯函数f:A→TB和g:B→TC内部是什么具体数学形式并不重要,重要的是前一个函数的值域与后一个函数的定义域相同。T是范畴论中的函子,表示计算,它可看做是把一种结构映射到到另外一种结构的函数。因此,Eilenberg-Moore的解决思路是:
1.先同时扩大g的定义域与值域,即T(g):T(B)→T(TC)。如果将T(g)简记为Tg,T(TC)记为T2C,则有Tg:TB→T2C。目的是保证前一个函数的值域与后一个函数的定义域相同。
2.然后,由于扩充g后值域变为了T2C,因此需要通过自然转换μ让T2C变为TC,即μC:T2→TC。
第二种是Keisli从范畴论角度对monad表现的行为进行解释。在之前提到过非纯函数组合问题,实际上是一个在范畴
上的monad(T,η,μ)问题。假如给出范畴
上的monad三元组(T,η,μ),就能有足够信息定义满足非纯函数的操作。因此,如果令三元组(T,η,μ)是范畴
上存在单位操作和组合操作的monad,需要把从定义域A到陪域B的非纯函数f:A→TB映射为纯函数f:A→B,那么就可以用Keisli范畴
解释Monad。Kleisli组合满足以下条件:
2.态射集合:
即在
中的态射为纯函数,而
中的态射为非纯函数,通过函子T就能将
中的非纯函数带到
的纯函数中计算。
3.当
时,单位操作η
A:A→TA。即η
A表示非纯函数的单位操作。η
A就是函数式编程语言中monad的return(ret)操作(如附图4所示)。
4.如果存在f*:TA→TB,必定存在f:A→TB。是Keisli的扩展推论,常称为lift(f)操作,记为f*。f*就是函数式编程语言中monad的bind(>>=)操作。
当满足Keisli条件时,在范畴
中的非纯函数f:A→TB和g:B→TC就能通过函子T将
中的态射f和g,映射到
中,在其中
和
表现为纯函数行为,那么就可以间接得到
中非纯函数的组合。
假设有函数f:A→TB和g:B→TC,Kleisli的定义下
其中f
*:A→B表示纯函数,f:A→TB表示非纯函数,f
*TA→TB表示lift就是函数式编程语言中的bind函数(如附图5所示)。如果要计算
就先要将f提升为f
*,g提升为g
*,因此实际计算的是
其次,从Monad计算的解释方面来阐述:
非纯函数的值域难以确切表示,因为不同计算方式会产生不同值域集合。一种较为容易理解的观点是,计算就是value-set形式,即输入一组数据集合生成结果集合。这种观点下,计算的结果是集合。但如果将计算理解为value-function的形式,即输入一组数据生成一种适合某种类型集合的计算,则计算的结果是另外某个计算。
例如,函数f(x,y)=x+y。当输入x=1时,结果是另外一个函数,即g(y)=f(1,y)=1+y,这是一种value-function的形式。由函数g(y)能产生结果集合,而一旦确定y的具体数值,结果必定在此集合中。因此,函数产生了结果集合,结果集合就能用函数表示。从这个角度来看,函数就是数据。
第二种计算value-functio的理解形式不但包含了第一种计算value-set的理解,更将计算一般化。计算就是先给定一个输入数据A,紧接着是一种生成某类数据的操作T。因为T生成的某类数据必须是世界上所有数据类型集合之集合,即超级数据集合,所以令
(对应到编程理论的
类型)。由此可看出,T必定是一个函子。这种定义域与陪域相同的函子,称为自函子(endofunctor)。
如图5所示,由于函数就是数据,因此生成的数据集合记为TB。如果B=A,生成的数据就是TA,表示A→TA。抽象出代数结构变为(1→T)(A),其中(1→T)就是抽象代数结构,A表示输入的数据集合。丢掉具体的A,保留下的(1→T)就是·→·形式。因为T是函子,所以1→T必定是自然转换,命名为η:1→T。可看出η唯一做的事情是将A原样输出为TA。此时η类似于编程中经常需要的空操作,例如已经计算出函数结果,再计算一次仍旧是原结果。
如果B≠A,生成数据就是TB,记为f:A→TB。因为η
A:A→TA可将A映射到TA。又因为
是函子,所以T(f):T(A)→T(TB),简记为Tf:TA→T
2B。
对于生成数据操作T如果能组合,就应该满足类似于含幺半群的代数结构。前面已经找到单位操作η:1→T,类似必须还要有组合操作满足μ:T×T→T,简记为μ:T
2→T。由于T是函子,所以μ必定是自然转换。考察其中的组合,μ
B:T
2B→TB。结合上面分析就有,
而因为f:A→TB,因此
上述代数结构就是Monad,记为(T,η,μ)。与含幺半群
对比,两者都具有·→·的结构。对于含幺半群来说,·对象是集合,→是函数。对于Monad来说,·对象是函数表示的集合(即将计算当做对象),而→则是从一种计算结构到另外一种计算结构的映射,即自然转换。Monoid的底层结构是集合,它可将纯函数组合;Monad通过一般化计算,将函数当做数据对待,可将非纯函数组合。因此,常称Monad是在自函子上的含幺半群代数结构,实现了一般性计算的函数组合操作。
(9)操作性语义到指示性语义的转换
为了将操作性语义转换为指示性语义,仍需借助一些类型理论和范畴论知识。在基于λ演算的类型理论看来,数学中的纯函数f(x)和g(x),用λ演算可表示为:f(x)=λx.e1和g(x)=λx.e2,其中e1和e2表示任意表达式。
数学中的纯函数组合
根据λ演算的β法则可表示为h(x)=(λx,e
1)[e
2/x],其中[e
2/x]表示用e
2替换表达式e
1中出现的所有非自由变量x。
在正式的ML系列编程语言编译器的实现中,λ表达式的语法为f(x)=fun x→e1,β法则一般用可读性较好的正式语法let x=e2 in e1表示。因此,数学中的纯函数组合h(x)就可以编码为λ表达式,即h(x)=(fun x→e1)e2,等价于用let...in...编码为了h(x)=lety=e2 in e1。
但非纯函数f:A→TB和g:B→TC无法直接组合为h:A→TC,因为f的陪域为TB,而g的定义域为B,TB≠B。所以,意味着非纯函数无法直接编码为λ表达式或let语句,因为两个非纯函数无法直接组合。解决问题的关键在于要先将类型理论中的类型映射到范畴论的对象上,将类型理论中的函数映射到范畴理论中的态射上,再通过范畴中的Kleisli范畴进行非纯函数的组合。
其主体思路是,将类型理论中的STLC(Simple-typedLambdaCalculus)用范畴论模型化,由于STLC主要数据类型就是
,即函数,因此STLC语言也称为λ
→。对应到范畴论中称之为CCC(CartesianClosedCategories)的范畴。即为:
1.将类型理论中的基本类型用范畴理论的对象解释。例如,[[τ]]=τ。
2.将类型理论中的函数(包括自然推导规则)用范畴理论的态射解释。如,
由于主要关心非纯函数的组合问题,因此简化为只考虑类型理论中的非纯函数如何用Kleisli范畴表示。如图所示,左边是先前的Kleisli范畴,右图是将操作性语义对应到指示性语义。其中类型α对应到范畴论的对象A,类型αt对应到范畴论的对象TA。类型理论中的表达式e1和e2对应到范畴论中的态射。在Klesili中非纯函数f转换为纯函数f*的操作就是bind(也称为lift,或>>=),即f*bindηAf。与之对应的,将操作性语义转换后的指示性语义后,就把原来将带有效果的函数λx.e1转化为不带效果可以组合的函数,用let语句表示就是[[let x=e1 in e2]]=bind[[x]][[λx.e1]](如附图6所示)。下面结合核心代码进行进一步分析。
3、核心代码分析
核心代码是把Miller等人定义的auth和unauth语义进行monad转换。以下分析过程中,其为了方便讨论,并未将x:TB写为准确的OCaml的语法类型x:βt,默认认为在范畴论CCC的理论框架下,TB等价于类型理论的βt。范畴论中表示对象的{A,B,C,...}符号等价于类型论类型变量{α,β,γ,...}的符号,两者在以下讨论中可以相互替代。
(1)Auth分析
Auth是将经过认证数据的证明流proof写入到磁盘上,因此auth程序的行为与write函数行为相似,属于write monad。假设proof为加密后的字符串。为了方便讨论,将其简化为∑*={a,b,c,...}形式,表示为所有字符组成的有限字符串序列。令∈表示空加密字符串,那么hash(a,b)就可以想象为两个字符串之间的某种组合操作(例如,字符串按位异或操作)。将此操作定义为·,那么v=s·t就表示v是s和t的一种组合结果。
auth程序的行为可描述为输入类型A的数据,输出结果为类型B的数据,同时生成代表证明流proof的字符串∑*,并将其写入到磁盘上。∑*是函数产生的效果(effect)。函数f可以表示为(假设,TB=B×∑*,TC=C×∑*):
f:A→TB=f:A→B×∑*
此时f(a)=(b,s)的含义是,如果输入数据为a,那么f(a)表示在返回数据b的同时,将生成写入磁盘的证明流s。为了能让非纯函数f(x)和g(x)可以组合,则必须满足下面两个条件才能构成monad。
当输入为a时,由单位函数定义可知,单位函数输入和输出结果应保持一致,而且不会产生证明流,因此id(a)=(a,∈)。由图3和图6可知,id就是monad中的ret。因此,ret(a)=(a,[])。与之对应的OCaml代码为let return a=(a,[])。
当输入为a时,令f(a)=(b,s)且g(b)=(b,t)。表示f产生结果b并生成证明流s。当两个函数组合时,g需要输入数据b,并产生证明流t,即
其函数组合,如下所示:
为了检查数据类型是否保持一致,第3行开始添加数据对应的类型。例如,x:TB表示变量x的数据类型是TB。从第6行到第7行后,数据s是否与数据t进行·操作由g(x)的内部实现确定,因此s应出现在g(x)的具体代码实现中。就具体示例来说,由于证明流是需要进行hash连接的,因此s·t确实进行了字符串连接操作。
(2)Unauth分析
Unauth从磁盘上读取经过加密的数据流,根据Merkle树从叶节点到根节点的路径逐个节点解密,如果解密后的证明流满足Hash值要求,则接收,否则报错。unauth程序的行为类似于parser monad。与上一小节类似,为了方便讨论引入∑*表示证明流。unauth程序的行为可描述为输入(A,∑*)数据是product类型,结果满足Hash运算要求时输出为(B,∑*),否则程序输出异常E。这表明输出数据类型是sum类型数据(B,∑*)+E。
f:A→TB=f:A×∑*→(B,∑*)+E
注意到左边公式只有一个输入参数A,而右边公式输入参数是A×∑*,也就是左右两边的输入参数不一样。根据Curring定理可知(Homset表示态射集合)
令A×B=A×∑*,C=(B,∑*)+E,代入Curring展开公式则有
其中CB={Z|Z:B→C}表示所有从B到C的函数组成的态射Z,因此
f:A→TB=f:A→(∑*→(B,∑*)+E)
即TB=∑*→(B,∑*)+E,将B代换为A结果不变,形式如下
f(A)=TA=∑*→(A,∑*)+E
以下为了便于讨论,仍旧使用f(a,s)的形式,而不采用Curring展开式。f(a,s1)=(b,s2)+failed含义是当输入数据类型a和对应的证明流s1后,如果成功解析数据则得到数据b及其证明流s2,即(b,s2);否则,引起异常,输出failed。为了能让非纯函数f(x)和g(x)可以组合,则必须满足下面两个条件才能构成monad。
当输入为a时,由单位函数定义可知,id(a,s1)=(a,s1)。因此,ret(a,s1)=(a,s1)。与之对应的OCaml代码let return a=fun proof→`Ok(a,proof)。注意,代码中使用了Uncurring形式,proof就是证明流。如果是两个函数的组合,当输入为(a,s1)时,令f(a,s1)=(b,s2)+E且f(b,s2)=(c,s3)+E,那么
形式为f(A)=A+B函数是sum类型,可用如下形式表示,语义等价于math...with...语句。函数组合可以如下进行化简:
最后一行通过match进行分支跳转,跳转后的代码分析与上节相似。
(3)实现Merkle树和关键接口
为了能将提取出的可认证数据结构的语义信息进行编码,首先要明确Functor和Monad在程序语言中的表现形式。在OCaml中Functor通过Modular实现。OCaml中Functor和Monad机制与Haskell有所区别,主要是因为OCaml中没有Higher-Kind数据类型,而Haskell有表现Higher-Kind数据类型的Type-Class。用Haskell实现代码时,要专门寻找在Haskell中对应于Type-Class概念而设计的Functor和Monad机制。通过Modular机制将可认证数据结构抽象为签名:
module type AUTHENTIKIT=sig
在创建表示新添加的可认证数据类型时,需通过类型构造器创建auth类型,并将该类型提交给OCaml编译器。按照编程方法的约定,此时只需要表现数据结构的形式,而不需要有具体代码实现,也就是在AUTHENTIKIT中写出所有要用到数据的抽象语法,auth类型的代码如下所示:
代码1可认证计算的Monad接口
可认证计算需要生成证明流以供数据验证方使用。在代码实践时,原来的可认证计算过程是运用Hack技术通过Campl4直接写入到OCaml编译器。但通过Monad可以将写入到编译器中的可认证语义提取出来,如代码片段1所示。第1行代码定义了抽象可认证计算类型,以参数多态形式表现。第2行定义了返回函数,本质就是η。第3行定义了bind操作,本质是定义了函数组合方式。
在上述代码中产生附加效果的数据类型用TA=′aτ表示,附加效果中含有需要的证明流和待验证数据,而其中τ=authenticated_computation。数据绑定过程就是Monad理论中的μ合并数据过程,其中bind就是符号>>=,用公式表示是:
bind:A→TB=(A→TA)→(B→TB)→(A→TB)
如果令A=()单位输入,则上述公式变为:
bind:A→TB=(()→TA)→(B→TB)→(()→TB)
由于根据范畴论,可以省略,所以公式变化为:
bind:A→TB=TA→(B→TB)→TB
这就是上述代码中第4行绑定数据的形式。由于最后计算结果是A→TA,因此需要η说明id函数的计算过程,这就是上述代码中return方法所起到的作用。实际计算是从A开始,通过lift操作得到TA,然后解析出TA中的A,再通过A→TB函数计算出带有效果的数值,最后将计算结果放到TB中。整个计算过程是两次扩展,首先将A的陪域扩展为TA,然后将B的定义域扩展为TB,最后将两个函数连接起来求出TB。
另外还需证明需要验证的数据流是“可认证的”。本质上相当于需要确保写入到磁盘上的证明流是连续的,如果在写入证明流时,写入过程被其他线程打断,那么所写入的证明流可能由于不连续而出错。在原论文中默认写入到磁盘的证明流是连续不会被打断的,以这种方式改写编译器内核后,可能会导致出现无法跟踪的错误情况。因此当假设写入证明流过程可能会被打断的时候,就需要采用更为正式的方式来验证证明流的完整性。只有当验证证明流是完整之后,才能继续后面的工作。所以代码所示,需要加入接口Authenticatable。
代码2防止证明流被打断代码接口
观察代码片段2可明显看出,这是创建树中节点的过程。通过pair就能合并两个叶子节点的哈希值,通过sum可将左或右叶子节点的数值合并到当前节点中。
由于在接口中添加了一层新的验证数据流完整性的接口,因此所有需要验证的数据都必须先经过此接口才能继续验证。也就是,创建证明路径和解析Merkle树的完整过程都可观察。对验证过程来说,要确保所有待验证或待写入数据都是完整不被打断的读取或写入的过程。
unauth函数返回Monad计算中验证的数据后,如果有证明路径信息,加上现在获取的验证信息,表示接下来就对可认证数据结构开展验证工作。代码片段3中最终定义了auth和unauth函数。从代码行可以看出,两者输入时都要求有Authenticatable类型数据,即要求数据是连续的完整的数据。
val auth:'aAuthenticatable.evidence->'a->'a authval unauth:'aAuthenticatable.evidence->'a auth->
'a authenticated_computation
代码3auth和unauth定义代码接口
在AUTHENTIKIT中需要添加基本Merkle树结构。树的实现有很多种方法,但每种实现方法都与具体要传输的信息结构密切相关。因为是在网络中传输结构化信息,所以最方便的方式是使用JSON数据格式。OCaml自身提供了多种JSON数据格式转换库以供调用,可以很方便用数组形式实现树形结构。基本Merkle树需要提供计算叶子节点哈希值功能,由make_leaf函数实现。通过哈希方式合并非叶子节点并计算出合并后哈希值功能的函数由make_branch函数实现。在构造Merkle树时,只需要上述两个函数即可。为了测试目的,提供了两个测试函数,分别是用于retrieve和update函数用于检索和更新Merkle上的节点。
由于Prover和Verifier都要通过同一个Merkle结构来访问可认证数据流,因此Merkle树应该是一个函子,如代码片段4所示。当需要使用Merkle树的是Prover时使用Prover提供的功能访问Merkle树,当需要使用Merkle树的是Verifier时就用Verifier提供的功能访问Merkle树。此处OCaml的Functor与Haskell中Functor在范畴论上是同一概念,但在具体编程实践上是两个不同的概念。前者属于类型理论中的*→*数据类型,后者属于类型理论中的Higher-kind数据类型,即属于□→□类型。
moduleMerkle:MERKLE=
functor(A:AUTHENTIKIT)->struct;openA;...
代码4Prover和Verifier通过函数子实例化
Merkle树接口的定义与实现部分与Monad无关,是完全抽象出来的一个数据层。完全可以将Merkle树的实现当作一个普通的数据结构。只有当Prover和Verifier这两个模块作为参数传递给Merkle树的时候,Monad接口才会与之发生松耦合的联系。在Merkle树看来,auth和unauth就像两个新的语法特性一样。
实施例:
1、数据获取:
a)通过Modular机制将可认证数据结构抽象为签名:
moduletypeAUTHENTIKIT=sig
b)描述抽象可认证计算unit、map、join和bind,具体可参见可认证计算的完整Monad接口代码片段:
type'a authenticated_computation
val return:'a->'a authenticated_computation
val(>>=):'a authenticated_computation->
('a->'b authenticated_computation)->
'b authenticated_computation
c)证明需要验证的数据流是是连续的,即要求数据是连续的完整的数据。具体参加Auth和Unauth的函数定义代码片段:
val auth:'aAuthenticatable.evidence->'a->'a auth
val unauth:'a Authenticatable.evidence->'a auth->
'a authenticated_computation
d)生成证明流。由于证明流在网络中传输,因此采用JSON形式。Ezjsonm是OCaml提供的一种JSON链接库。除此之外,还需要用到一种常用的哈希算法。选择OCaml中Cryptokit提供的Sha1算法接口进行哈希运算。使用JSON模拟网络数据的代码片段为:
let auth serialiser a=(a,hash_json(serialiser a)))
let unauth serialiser(a,h)=([serialiser a],a)
e)验证证明流,向服务器端提起查询请求。通过函数retrieve和update可分别对服务器端提出查询和更新数据请求。证明流验证方在验证数据时,有可能验证通过,也有可能验证失败。当验证通过时,会继续截取证明流首部中下一个哈希值进行验证,如此反复,直到证明流中所有Hash值都验证完毕。验证证明流需匹配检索路径的代码片段如下所示:
val hcode:string=hash(tree)
val proof:proof=proof_stream_of_somedata
Merkle_Verifier.retrieve[`L;`L]hcode proof
2、实施过程:
假设攻击者企图欺骗客户,通过伪造证明流和Merkle树,希望让客户端获得错误的数据,过程如下所述:
a)攻击者伪造一棵形状类似的Merkle树,但Merkle树叶节点数据是伪造的,如代码片段:
(攻击者伪造一棵Merkle树和数据节点的代码片段)
let other_tree=
Merkle_Prover.(make_branch
(make_branch(make_leaf"A")(make_leaf"B"))
(make_branch(make_leaf"C")(make_leaf"D")));;
b)当客户顺着原来的路径[L;L]进行查询时,实际上得到的是错误的数据,如代码片段(客户端查询到错误数据结果代码片段)
C)当将结果发送给客户端后,客户端会根据哈希值验证证明流,从而知道是否得到正确的数据。经过验证发现数据哈希值与证明流没有对应关系,因此报错,从而实现数据验证,避免损失。
以上显示和描述了本发明的基本原理、主要特征和本发明的优点。本行业的技术人员应该了解,本发明不受上述实施例的限制,上述实施例和说明书中描述的只是说明本发明的原理,在不脱离本发明精神和范围的前提下,本发明还会有各种变化和改进,这些变化和改进都落入要求保护的本发明范围内。本发明要求保护范围由所附的权利要求书及其等效物界定。