vChain: Enabling Verifiable Boolean Range Queries over Blockchain Databases

vChain: Enabling Verifiable Boolean Range Queries
over Blockchain Databases

摘要

由于加密货币和去中心化应用的兴起,区块链最近备受关注。因此对查询存储在区块链数据库中数据的需求日益增加。为了保证数据查询的完整性,用户可以维护整个区块链数据库,并在本地查询数据。然而,由于区块链庞大的数据容量和巨大的维护成本,这种方法即使不是不可行,也是不经济的。我们提出了一种名为vChain的新颖框架,该框架可减轻用户的存储和计算成本,并采用可验证的查询来确保结果的完整性。为了支持可验证的布尔范围查询,我们提出了一种基于累加器的验证数据结构,该结构支持对任意查询属性进行动态聚合。进一步开发了两个新的索引来聚合块内和块间的数据记录,以实现高效的查询验证。同时我们还提出了反向前缀树结构,加速大量订阅查询的处理。安全分析和实证研究验证了所提出技术的鲁棒性和实用性。
关键词 :查询处理;数据完整性;区块链

1.说明

 由于比特币和以太网等加密货币的成功,区块链技术近年来获得了压倒性的发展势头。区块链是仅仅是一种追加数据时分布式的存储在网络的对等体中的结构。尽管网络中的对等方可能不会相互信任,但是区块链从两个方面确保了数据完整性。首先,借助哈希链技术,存储在区块链上的数据是不可变的。其次,由于其共识协议,区块链可确保所有对等方都维护相同的数据副本。这些加密保证的安全机制,加上去中心化和区块链的起源属性,使区块链成为一项潜在改变数据库系统的技术。
  从数据库的角度来看,区块链可以看作是存储大量带时间戳的数据记录的数据库。随着区块链技术在数据密集型应用中的广泛采用例如财务,供应链和知识产权管理,用户对查询存储在区块链数据库中的数据的需求与日俱增。比如,在比特币网络中,用户可能想要找到符合条件范围查询的事务。如“交易费≥50美元”、“0.99万美元≤总产量≤101万美元”。在基于区块链的专利管理系统中,用户可以使用布尔运算符来搜索关键字的组合,如“区块链”∧(“查询”∨“搜索”),在专利摘要中。许多公司,包括数据库巨头IBM、Oracle和SAP,以及初创公司如FlureeDB[10]、BigchainDB[11]和SwarmDB[12],都致力于开发区块链数据库解决方案以支持类似sql的查询,所有这些都假设存在一个受信任的方,该方可以忠实地执行基于区块链数据库的物化视图的用户查询。然而,这样的可信方可能并不总是存在,并且查询结果的完整性也不能得到保证。在区块链研究中,具有完整性保证的查询处理仍然是一个未探讨的问题。
  在典型的区块链网络中[1,2],如图1所示,有三种类型的节点,完整节点,挖矿节点,轻型节点。完整节点存储区块链中的所有数据,包括区块头和数据记录。矿工是一个具有强大计算能力的完整节点,负责建立共识机制证明。(例如,比特币区块链中的随机数)。一个轻型节点只存储区块头,其中包括共识证明和一个区块的加密哈希数。请注意,数据记录未存储在轻型结点中。

  为了确保区块链数据库上查询的完整性,查询用户可以作为完整节点加入区块链网络。然后,用户可以下载并验证整个数据库,并在本地处理查询,而不会影响查询的完整性。然而,对于普通用户来说,维护整个数据库的完整副本可能成本太高,因为它需要大量的存储、计算和带宽资源。例如,运行一个比特币完整节点的最低要求包括200GB的可用磁盘空间,不限流量的宽带连接,至少每秒50KB的上传速度以及每天6个小时的运行时间[13]。为了迎合资源有限的用户(尤其是移动用户)的需求,一种更具吸引力的替代方法是将存储和查询服务委派给功能强大的完整节点,而查询用户仅充当接收结果的轻型节点。然而,如何确保查询结果的完整性仍然是一个挑战,因为整个节点不受信任,这是区块链的固有假设。
  为了解决上述查询完整性问题,在本文中,我们提出了一个名为vChain的新颖框架,该框架采用可验证的查询处理来保证结果的完整性。更具体地,我们在每个块中增加一些额外的经过身份验证的数据结构(ADS),这个数据结构是基于一个(不可信的)能够构造并且返回加密认证的完整节点,也称为验证对象(VO)。供用户验证每个查询的结果。查询用户(轻节点)和完整节点之间的通信如图1所示,其中q表示查询请求,R表示结果集。
  值得注意的是,此vChain框架的灵感来自为外包数据库研究的查询身份验证技术。但是,存在一些关键差异,使常规技术不适用于区块链数据库。首先,传统技术依赖于数据所有者使用私钥对经过身份验证的数据结构(ADS)进行签名。相反,在区块链网络中没有数据所有者。只有矿工才能根据共识协议构造共识证明,将新数据追加到区块链。但是,它们不能充当数据所有者,因为它们无法持有私钥并对经过身份验证的数据结构(ADS)签名。其次,一个传统的经过身份验证的数据结构(ADS)是在固定的数据集中构建的,这种数据结构无法有效的使用于不限制数据的区块链数据库。最后,在传统的外包数据库中,根据需要,可以始终生成和附加新的经过身份验证的数据结构(ADS),以支持更多涉及不同属性集的查询。然而,由于区块链的不变性,这将是困难的,其中一个尺寸适合所有的数据结构(ADS)更适合支持动态查询属性。
  可见,经过身份验证的数据结构(ADS)的设计是vChain框架的关键问题。为了解决这个问题,本文着重于布尔范围查询,如前所述,这在区块链应用程序中很常见。我们提出了一种新的基于累加器的ADS方案,允许对任意查询属性(包括数值属性和集值属性)进行动态聚合。
这个新设计的ADS独立于共识协议,因此它与当前的区块链技术兼容。在此基础上,开发了高效的可验证查询处理算法。我们还分别为块内数据和块间数据提出了两种经过验证的索引结构,以实现批量验证。为了支持大规模的订阅查询,我们进一步提出了一个查询索引方案,可以对相似的查询请求进行分组。总之,我们在本文中的贡献如下:

  1. 据我们所知,这是第一个关于可验证查询处理的工作,它利用内置的ADS来实现区块链数据库的查询完整性。
  2. 我们提出了一个新的vChain框架,以及一个新的ADS方案和两个索引结构,可以聚合块内和块间的数据记录,从而实现高效的查询处理和验证。
  3. 我们开发了一种新的查询索引,可以同时处理大量的订阅查询。
  4. 我们进行安全分析以及实证研究以验证所提出的技术。我们还解决了实际实施问题。
      论文的其余部分组织如下。第2节回顾了现有的关于区块链和可验证查询处理的研究。第3节介绍了正式的问题定义,随后是第4节中的加密原语。第5节介绍了我们的基本解决方案,然后通过第6节中设计的两个索引结构对其进行了改进。第7节讨论了可验证的订阅查询。第8节介绍了安全性分析。第9节介绍了实验结果。最后,我们在第10节结束我们的论文。

2.相关工作

在这一部分,我们简要回顾了相关的研究,并讨论了相关的技术。
区块链. 自从比特币加密货币推出以来,区块链技术受到了学术界和产业界的极大关注。区块链本质上是梅儿根哈希树的一种特殊形式,由一系列的区块构成。每一个区块头由四个部分组成: (i) PreBkHash,前一个区块的哈希值。(ii) TS,创建块的时间戳;(iii) ConsProof,共识证明是有矿工构建并保证块的共识。(iv) MerkleRoot,梅尔根树的根结点哈希值。 ConsProof通常是基于PreBkHash 和MerkleRoot计算的,并根据共识协议而变化。在广泛使用工作量证明(Pow)的共识协议中,ConsProof 是由矿工计算的随机数,如下:

其中Z对应于开采难度。矿工找到随机数后,它将打包新的块,并将其广播到整个网络。其他矿工核实交易记录和新区块的随机数,一旦核实,将其附加到区块链。
  为了解决区块链系统的各种问题,已经做出了大量的努力,包括系统协议[20,21]、共识算法[22,23]、安全性[24,25]、存储[7]和性能基准[4]。最近,主要的数据库供应商,包括IBM[26]、Oracle[27]和SAP[28],都已经将区块链与他们的数据库管理系统集成在一起,并且他们允许用户通过数据库前端在区块链上执行查询。此外,许多初创公司,如FlureeDB[10]、BigchainDB[11]、SwarmDB[12],一直在为去中心化应用开发基于区块链的数据库解决方案。然而,它们通常将查询处理与底层区块链存储分开,并依赖可信的数据库服务器来保证查询完整性。相反,我们提出的vChain解决方案将经过验证的数据结构加入到区块链结构中,这样即使是不可信的服务器也可以提供具有数据完整性保证的查询服务。
可验证的查询处理. 已广泛研究可验证查询处理技术,以确保对不受信任的服务提供商提供结果的完整性。现有的大多数研究专注于外包数据库,有两种典型的方法:使用基于电路的可验证计算(VC)技术支持综合查询和使用经过身份验证的数据结构(ADS)支持特定查询。基于vc的方法(如:SNARKs[30])可以支持任意的计算任务,但代价是非常高的,有时是不切实际的开销。此外,这需要昂贵的预处理步骤,因为数据和查询程序都需要硬编码到证明密钥和验证密钥中。为了解决这个问题,本-萨森等人[31]开发了一种SNARKs变体,其中预处理步骤仅取决于数据库和查询程序的上限大小。最近,张等人[29]提出了一个vSQL系统,它利用一个交互式协议来支持可验证的SQL查询。但是,它仅限于具有固定模式的关系数据库。
  相比之下,基于ADS(经过身份验证的数据结构)的方法通常更有效,因为它针对特定的查询进行定制。我们提出的解决方案属于这种方法。通常有两种结构用来当做ADS:数字签名和梅尔根哈希树。数字签名基于非对称加密技术验证数字消息的内容。为了支持可验证的查询,它要求每个数据记录都要签名,因此无法扩展到大型数据集。另一方面,MHT是建立在等级树的基础上的。叶节点中的每个条目被分配一个数据记录的散列摘要,内部节点中的每个条目被分配一个从子节点派生的摘要。数据所有者签署MHT的根摘要,可用于验证数据记录的任何子集。MHT已被广泛应用于各种索引结构中。最近,对集值数据的可验证查询进行了研究。
  另一个密切相关的研究方向是数据流的可验证查询处理。然而,以前的研究[38,39]侧重于一次性查询,以检索最新版本的流式数据。要求数据所有者为所有数据记录维护一个MHT,并且存在长查询延迟,这不适用于实时流服务。换句话说,对数据流的订阅查询也进行了研究。到目前为止,还没有考虑过区块链数据库上订阅查询的完整性问题。

3.问题的定义

正如第1节所述的,本文提出了一个新的vChain框架,并研究了区块链数据库上的可验证查询处理。vCHain的系统模型图如图3所示,
该系统模型由三个部门组成(1)矿工(MINER)(2)服务提供者(SP)(3)查询用户(QUERY USER)。SP和MINER都是维护整个区块链数据库的完整节点。 查询用户是一个轻量级节点仅跟踪区块头。矿工负责构建共识证明并将新块附加到区块链。 SP为轻量级用户提供查询服务。
  存储在区块链中的数据可以被建模为一系列的时间对象块{O1,O2,…On}。每一个对象由<Ti,Vi,Wi>表示,Ti代表对象的时间戳,Vi是多维度的矢量表示着一个或多个数字属性,Wi是一个集值属性。为了验证查询过程,需要矿工将一个经过身份验证的数据结构嵌入到每一个区块中。我们考虑布尔范围查询的两种形式:(历史)时间窗口查询和订阅查询。
时间窗口查询. 用户可能希望搜索在特定时间段出现的记录。在这种情况下,可以发出时间窗口查询。具体来说时间窗口查询的形式是q = ⟨[ts,te],[α,β],ϒ⟩,其中[ts,te]是时间段的时间范围选择谓词,[α,β]是数字属性的多维范围选择谓词,ϒ是一个在集值属性的单调布尔函数。因此,SP返回这样的所有对象 {oi = ⟨ti,Vi,Wi⟩ | ti∈ [ts,te]∧Vi∈
[α,β] ∧ ϒ(Wi) = 1},我们假设ϒ处于合取范式。
 例3.1。在比特币交易搜索服务中,每个对象对应一个比特币转账交易。它由存储在Vi中的转账金额和存储在中的一组发件人/收件人Wi地址组成。用户可以发出查询q =⟨[2018-05,2018-06],[10,+∞],send:1ffyc∧receive:2daaf⟩,以查找从2018年5月到6月发生的转账金额大于10并且与地址“send:1FFYc”和“receive:2DAAf”相关联的所有交易。
订阅查询. 除了时间窗口查询外,用户还可以通过订阅查询注册他们的兴趣。具体来说,订阅查询的形式是 q = ⟨−,[α,β], ϒ⟩, 其中[α,β]和ϒ与时间窗口查询的查询条件相同。反过来,SP连续返回所有对象,使得{ oi =⟨ti,vi,wi⟩| vi∈α,β]∧ϒ(wi = 1 }直到查询被注销。
  例3.2。在基于区块链的汽车租赁系统中,每个租赁的客人的租赁价格存储了在Vi中和存储在Wi中的一组文本关键字。用户可以订阅查询q =⟨ - ,[200,250],“轿车”∧(“enbz”∨“bmw”)⟩要收到在[200,250]范围内的价格的所有租赁消息并包含关键词“轿车”和“奔驰”或“宝马”。
威胁模型 . 我们考虑sp是一个在区块链网络中不被信任的对等点,是潜在的对手。由于程序故障、安全漏洞、商业利益等各种问题,SP可能返回篡改或不完整的查询结果,从而违反区块链的预期安全性。具体来说,在查询处理过程中,SP检查嵌入在区块链中的ADS,并构造一个包含结果验证信息的验证对象(VO)。VO和结果一起返回给用户。使用VO,用户可以根据以下标准建立查询结果的可靠性和完整性。
可靠性: 作为结果返回的对象都没有被篡改,并且它们都满足查询条件。
完整性: 查询窗口或订阅期间没有缺少有效结果。
  当我们在第8节中进行安全性分析时,将正式地介绍上述安全性概念。
  这种模型的主要挑战是如何设计ADS,使其能够很容易地适应区块链结构同时可以为时间窗口查询和订阅查询有效地构建成本效益好的VOs(产生较小带宽开销和快速验证时间)。我们将在接下来的几节中解决这个难题。

4.前言

本节给出了一些在我们的算法设计中需要的密码构造的预备知识。
哈希加密函数. 加密哈希函数hash()接受任意长度的字符串作为其输入,并返回固定长度的位字符串。它是抗冲突的,并且很难找到两个不同的数值,m1和m2,使得hash(m1) = hash(m2)。经典的哈希加密函数包括SHA-1、SHA-2和SHA-3系列。
双线性配对. 设G和H是两个具有相同的素数阶p的循环乘法群。设g为G的生成器,双线性映射是函数e:G × G → H拥有以下属性:
双线性:If u,v ∈ G ande(u,v) ∈ H, then e(ua,vb) = e(u,v)ab for any u,v.
非退化性 e(g,g) != 1.
双线性配对是多重集累加器的基本操作,如本文后面所示。

加密多重集累加器. 多重集是允许元素出现多次的集合的概述。为了用常量大小表示它们,密码多集累加器是一个函数acc(·),它以抗冲突的方式将一个多集映射到某个循环乘法群中的一个元素。
  累加器的一个有用性质是它可以用来证明集合不相交。它由以下概率多项式时间算法组成。
• KeyGen(1λ) → (sk,pk): 在输入一个安全参数1λ时,它生成一个秘钥sk和一个公钥k。
• Setup(X,pk) → acc(X): 在输入多集X和公钥时,它计算累积值acc(X)。
• ProveDisjoint(X1,X2,pk) → π:输入两个多集X1, X2,其中X1∩X2=∅,公钥pk,输出一个证明π。
• VerifyDisjoint(acc(X1),acc(X2),π,pk) → {0,1}: 输入累积值acc(X1), acc(X2),证明π,公钥pk,当且仅当X1∩X2=∅时输出1。
  累加器和集合不相交证明的更详细的构造将在第5.2节中给出。

5. 基本解决方案

  为了在我们的框架vChain中实现可验证查询,一个简单的方案是构造一个传统的MHT(梅尔根树)作为每个块的ADS,并应用传统的基于MHT的认证方法。然而这个传统的方法有3个主要的缺陷: 1) 梅尔根树只支持梅尔根树自己构建的查询键。为了支持涉及任意一组属性的查询,需要为每个块构造指数数量的梅尔根树。2) 梅尔根树不适用于集值属性。3)不同块的梅尔根树不能有效地聚合,使其无法利用块间优化技术。为了克服这些缺点,在本节中,我们提出了基于新的基于累加器的ADS方案的新型身份验证技术,该方案将数值属性转换为集值属性,并支持对任意查询属性进行动态聚合。下面,我们从考虑单个对象开始,为了便于演示,我们将重点放在布尔时间窗口查询上(第5.1节和5.2节)。然后将其扩展到范围查询条件(第5.3节)。我们将在第6节讨论多对象的批处理查询处理和验证。订阅查询将在第7节中详细介绍。

5.1 ADS生成和查询处理

为简单起见,本节仅考虑集值属性Wi的布尔查询条件。我们假设每个块存储一个对象oi= ⟨ti,Wi⟩ ,然后用Objec-tHash来表示 MerkleRoot。
ADS生成. 回想一下,在所提出的vchain框架中,在采矿过程中为每个块生成ADS。它可以由SP使用,为每个查询构造验证对象(VO)。此,我们通过添加一个名为AttDigest的额外字段来扩展原始块结构,如图4中阴影部分所示。因此,区块头由PreBkHash, TS, ConsProof, Objec-tHash, 和AttDigest组成。
  想要作为ADS,Attdigest应该有三个所需的性质。首先,AttDigest应该能够以某种方式总结一个对象的属性(Wi)这个可以用来证明对象是否匹配查询条件。如果是不匹配,我们可以返回这个digest而不是整个对象。其次,无论Wi中的元素数量如何,Attdigest都应该是恒定的大小。最后,AttDigest应该是可聚合的,以支持一个块内甚至跨块的多个对象的批量验证(Section 6)。因此,我们建议使用多重集累加器作为AttDigest:

然后它支持很多功能,包括显示不相交和验证不相交,为了更好的可读性,我们将详细的结构推迟到第5.2节。
可验证的查询处理. 给定一个布尔查询条件和一个数据对象,只有两种可能的结果:匹配或不匹配。第一种情况的正确性可以通过返回对象作为结果来容易地验证,因为它的完整性可以通过存储在区块头中的ObjectHash来验证,这对于轻节点上的查询用户是可做的(回想图3)。挑战在于如何使用AttDigest有效地验证第二种情况。由于CNF是一个布尔函数,用“与”或“或”运算符的列表表示。例如,一个查询条件“轿车”∧(“奔驰”∨“宝马”) 相当于两个集合 {“Sedan”} and {“Benz”,“BMW”}。考虑一个不匹配的对象: {“Van”,“Benz”}。很容易观察到存在一个等价集合(i.e{“Sedan”}) 使得它与对象属性的交集为空。因此,我们可以应用ProveDisjoint({“Van “,” Benz”},{ " Sedan " },pk) 生成一个不相交的证明π作为不匹配对象的VO(验证对象)。相应地,用户可以从区块头中检索AttDigesti= acc({“Van”,“Benz”}),并使用VerifyDisjoint(AttDigesti,acc({“Sedan”}),π,pk)来验证不匹配。整个过程详见算法一。

5.2 多重集累加器的构造

我们现在讨论在5.1节中用作AttDigest的多集累加器的两种可能的构造。每种结构都有自己的优点和缺点,适合不同的应用场景,我们将在第9节中看到。

5.2.1 构造1

  我们首先提出一个在[32]中提出的构造,其基于双线性对和q-SDH假设。它由以下算法组成。

5.2.2 构造2

  受[35]的启发,第二种构造被提出来引入两个函数Sum(·) andProof-Sum(·),它允许多个累加值的聚合或设置不相交的证明。它是基于双线性对和q-DHE假设,由以下算法组成。


  与构造1相比,构造2支持多个累加值的聚合或集合不相交证明,这在6.3节中被在线批量验证方法所使用。但是,它会产生更大的密钥大小。特别是,构造1中的公钥大小与最大多重集大小成线性关系,而在构造2中,公钥大小与系统中属性的最大可能值成线性关系。在实际应用中,通常的做法是使用加密哈希函数将每个属性值编码为一个整数,然后由累加器接受。由于典型哈希函数返回的值为数百位,因此提前生成和发布这样规模的公钥代价很高。为了解决这个问题,我们可以引入一个可信的oracle,它拥有密钥,并负责响应公钥的请求。这种oracle可以由可信的第三方来执行,也可以利用安全硬件(如SGX)来实现。

5.3 扩展范围查询

前几节主要考虑对集值Wi的布尔查询。在许多情况下,用户还可能在数值属性Vi上应用范围条件。为了解决这一问题,我们提出了一种将数值属性转换为集值属性的方法。然后,范围查询可以相应地映射到布尔查询。
  这个想法是这样的。首先,我们用二进制格式表示每个数值。接下来,我们将一个数值转换为二进制前缀元素集合(用函数trans表示)。例如,数字4可以用二进制表示为100。因此,它可以转换成一个前缀集,比如trans(4) = {1∗,10∗,100},其中∗表示通配符匹配操作符。类似地,对于数值向量,我们可以对每个维度应用上述过程。例如,向量(4,2)具有二进制格式(100,010)。它转换的前缀集是{1∗1,10∗1,1001,0∗2,01∗2,0102}。请注意,这里每个元素都有一个下标符号(比如1,2)这是用于区分向量在不同维度中的二进制值。
  接下来,我们将范围查询条件转换为单调布尔函数,通过使用建立在整个二进制空间上的二叉树。例如,图5示出了一维空间[0,7]的树。具体来说,对于一维范围[α,β],我们首先以二进制格式表示α和β。接下来,我们将α和β视为树中的两个叶节点。最后,我们找到精确覆盖整个范围[α,β]的最小的树节点集。转换后的布尔函数是使用或(V)语义连接集合中每个元素的函数。例如,对于一个查询范围[0,6],我们可以找到它的转换布尔函数 0 ∗ ∨10 ∗ ∨110 (参见图五中的灰色结点)。如5.1节所讨论的,这个布尔函数的等价集是 {0∗,10∗,110}。类似地,在多维范围的情况下,转换后的布尔函数是一种使用AND (∧)语义连接每个维度的部分布尔函数。比如一个范围查询 [(0,3),(6,4)] 可以转换成(0∗1∨ 10∗1∨ 1101) ∧ (0112∨ 1002)相当于(0∗1∨ 10∗1∨ 1101) and (0112∨ 1002).

  通过上述变换,关于数值Vi是否在范围[α,β]内的查询变成了对前缀集[α,β]的布尔查询。在上面的例子中, 4 ∈ [0,6] 因为{1∗,10∗,100} ∩ {0∗,10∗,110} = {10∗} != ∅;(4,2) 不属于[(0,3),(6,4)] 因为 {0112,1002} ∩ {1∗1,10∗1, 1001,0∗2,01∗2,0102} = ∅。
 由于数据转换技术,在接下来我们将这两种类型的查询条件统一为一个关于集值属性的统一布尔查询条件。更具体地,对于每个数据对象⟨ti,Vi,Wi⟩转换成元组⟨ti,W′i⟩,其中W’i=trans(Vi)+Wi;and a query q = ⟨[ts,te],[α,β],ϒ⟩ is 转换成⟨[ts,te],ϒ′⟩,其中 ϒ′= trans([α,β]) ∧ ϒ。因此,查询结果如下 { Oi = ⟨ti,W′i⟩ | ti ∈ [ts,te] ∧ ϒ′(W′i) = 1}。

6 批量验证

  在本节中,我们将讨论如何通过批处理验证来提高查询性能。我们首先介绍两种经过验证的索引结构,即块内索引(第6.1节)和块间索引(第6.2节),随后是在线批量验证方法(第6.3节)。所有这些技术都允许SP可以批次证明不匹配的对象。

6.1 块内索引

  在上一篇讨论中,为了简单起见我们假设每个区块仅为存储一个对象。正常来说,每个区块通常存储多个对象。简单来说,我们可以对每个对象重复应用single-object algorithm算法来确保查询的完整性,然而,这会导致验证复杂度与对象数量成线性关系。此外,可以观察到,如果两个对象共享某些共同的属性值,它们可能由于相同的部分查询条件而对某些查询不匹配。因此,为了减少校对和验证开销,我们提出了一个块内索引,可以聚合多个对象并提高性能。
 图6显示了一个在区块链上带有块内索引的区块。它将每个对象的ObjectHash和AttDigest组织成一个二进制的Merkle树。区块头由以下组件组成:PreBkHash, TS, ConsProof, and MerkleRoot,其中MerkleRoot是hash of the binary Merkle tree的根结点。每一个树节点有三个字段:孩子结点的哈希值(表示为hashi,用来构成MHT),多重集属性(用Wi)表示,属性多重集的累加值(用AttDigesti表示)。它们是从孩子节点计算的,如下所示。
定义6.1(块内索引非叶节点). 用Hash(·) 表示哈希加密函数,’ | '是字符串连接操作符, acc(·)是多集累加器, nl和nr分别是结点n的左孩子和右孩子。非叶节点n的字段定义为:
• Wn=Wnl∪Wnr
• AttDigestn= acc(Wn)
hashn = hash(hash(hashnl| hashnr) | AttDigestn)
定义6.2 (块内索引叶子节点) . 叶节点的字段与底层对象的字段相同。
 当构建块内索引时,我们希望实现最大的验证效率。也就是说,我们的目标是在查询处理过程中最大化修剪不匹配对象的机会。一方面,这意味着我们应该找到一个集群策略,使得对于用户的查询,节点不匹配的机会最大。换句话说,我们努力使每个节点下的对象的相似性最大化。另一方面,平衡二叉树树是优选的,因为它可以提高查询效率。因此,我们建议以自下而上的方式基于区块的数据对象构建块内索引(由区块链矿工)。首先,块中的每个数据对象被分配给一个叶节点。接下来产生最大 Jaccard 系数(|Wnl∩Wnr| / |Wnl∪Wnr|)的叶子结点被迭代合并。这两个合并的树节点用于在上层创建一个新的非叶节点。这个过程在每个级别中重复,直到创建根节点。最后由hashr分配的Merkle-Root 被写作区块头的组件之一,算法二展示了详细的程序。

  使用上述块内索引,SP可以将查询处理为树搜索。从根节点开始,如果当前节点的属性multiset满足查询条件,则将进一步研究其子树。同样,相应的AttDigest被添加到VO中,它将在结果验证期间用于重建MerkleRoot。另一方面,如果multiset不满足查询条件,则意味着所有底层对象都不匹配。在这种情况下,SP将调用ProveDisjoint(·)和相应的AttDigest来生成不匹配的证明。直到到达叶子结点,multiset满足查询条件的对象是匹配对象,将作为查询结果返回。算法3展示了使用块内索引的VO构造。

  为了便于说明,我们使用5.1节中讨论的同一组对象。块内索引如图6所示。来自用户的布尔型查询是 “Sedan”∧ (“Benz”∨“BMW”) 。查询过程只是将索引从根节点遍历到叶节点。查询结果为{o1}。SP返回的VO包括{⟨AttDigestr⟩, ⟨AttDigest5⟩,⟨hash2, π2, {“Audi”}, AttDigest2⟩, ⟨hash6, π6, {“Van”},AttDigest6⟩}。 这里π2和π6分别是不匹配节点n2和N6的两个不相交证明(图6用阴影表示)。注意,AttDigestr和AttDigest5将在结果验证时用于MerkleRoot的构建。在用户方面,不匹配验证通过使用VO中的AttDigest、不相交集和证明π调用VerifyDisjoint(·)进行。此外,为了验证结果的可靠性和完整性,用户需要重新构造MerkleRoot,并将其与从块头读取的MerkleRoot进行比较。在事例中,首先,使用⟨π2, AttDigest2, {“Audi”}⟩ and ⟨π6, AttDigest6, {“Van”}⟩调用VerifyDisjoint(·) 来证明N2和N6确实不匹配查询。然后,用户使用返回的结果计算hash(O1), and hash5 = hash(hash(O1) | hash2 | AttDigest5 ),hashr = hash(hash5 | hash6 | AttDigestr ) based on the VO。最后,用户根据区块头中的MerkleRoot检查新计算的哈希值hashr

6.2 区块间的索引

  除了同一块内的相似对象之外,由于相同的原因,块间的对象也可能共享相似性并且不匹配查询。基于这一观察,我们构建利用跳过列表来进一步优化查询性能的块间索引。

  如图7所示,块间索引由多个跳过指数数量的先前块的区块组成。例如,列表可能跳过前面的2、4、8、···块。对于每个跳过,它维护三个组件。所有跳过块的哈希值(用PreSkippedHashLk表示),所有跳过块的属性multisets的总和(用WLk表示),和相应的w.r.t.的累加值(用AttDigestLk表示)。请注意,在此,我们使用属性multiSets的求和来在第7节中启用在线聚合身份验证。最后,使用额外的字段 SkipListRoot将块间索引写入区块,该字段定义为:
&esmp; 在查询处理期间,合格的跳过可以用于表示由于相同的不匹配原因而对查询结果没有贡献的多个区块。因为用户可以避免访问这些跳过的块,所以可以降低验证成本。

 算法4显示了带有块间索引的查询处理过程。我们从查询时间窗口中的最新块开始。我们将跳跃列表从最大跳过次数迭代到最小跳过次数。If the multiset of a skip WLi 不符合查询条件, 这意味着当前块和前一个第i块之间的所有跳过的区块不包含匹配结果。因此,调用ProveDisjoint(·) 然后输出不匹配证明πi。然后将 ⟨PreSkippedHashLi, πi, ϒi, AttDigestLi⟩ 加入到VO中。用户可以使用该证明来验证跳过的块确实与查询不匹配。与此同时,除hashLi之外的其他哈希值也都被添加到VO中。如果我们在迭代过程中没有发现不匹配的块,当前区块调用IntraIndexQuery(·) (Algorithm 3) 来检查前一个区块。如果我们成功找到不匹配跳过,则接下来将检查相应的前一块。递归调用函数interIndexquery(·),直到我们完成检查查询窗口中的所有区块。请注意,我们可以组合块内索引和块间索引来最大化性能,因为它们不冲突。

6.3在线批量验证

  回想一下,所提出的块内索引试图用某种方式将同一区块内的对象聚集在一起来最大化提高验证不匹配对象的效率。然而,在不同块或甚至同一块的不同子树中索引的一些对象/节点也可能有相同的不匹配原因。因此,在线聚合这样的对象/节点对于更有效的校对是有益的。为此,在第5.2节中由构造2引入的Sum(·) primitive ,Sum(·) primitive 在给定多个累加值时输出聚合多重集的累加值。
在图6所示的运行示例中,假设O2和O4共享查询条件不匹配的相同原因(“Benz”)。 Then, the SP can return π = ProveDisjoint(W2+W4,{“Benz”},pk) and AttDigest2,4= Sum(acc(W2),acc(W4))。And the user can applyVerifyDisjoint(AttDigest2,4,acc({“Benz”}),π,pk) to prove that these two objects mismatch in a batch。

7 可验证的订阅查询

订阅查询由查询用户注册,并持续处理,直到取消注册为止。当SP看到新确认的区块时,需要将结果与VOs一起发布给注册用户。在本节中,我们首先提出一个查询索引,以有效地处理大量订阅查询(第7.1节)。在那之后,我们开发了一个延迟认证优化,延迟不匹配的证明以减少查询验证成本(第7.2节)。

7.1 可扩展处理的查询索引

如前所述,大部分查询处理开销来自于Sp上的不匹配对象生成证据。幸运的是,对于不同的订阅查询,不匹配的对象可能有相同的不匹配原因。因此,这种查询可以共享不匹配证明。受[41]的启发,我们提议在订阅查询上构建一个反向前缀树,称为IP树。它本质上是一个前缀树,引用了数字范围条件和布尔集合条件的倒排文件。
前缀树组件. IP-Tree是基于每个树节点由CNF布尔函数表示的网格树构建的,是为了索引所有订阅查询的数字范围(见第5.3节)。例如,图8中对应于左上角单元格([0,2],[1,3])的网格节点N1由{ 0*1∧1*2 }表示。前缀树的根节点覆盖了所有订阅查询的整个范围空间。

倒排文件组件. IP-Tree的每个节点都与一个反向文件相关联,该反向文件是基于该节点下索引的订阅查询而构建的。每个反向文件有两个子组件:
范围条件倒排文件(RCIF). RCIF中的每个条目都有两个属性: 查询qi及其覆盖的类型(全部或者部分)。RCIF中的所有查询都与结点的数字空间S相交。覆盖的类型显示qi完全覆盖还是部分覆盖S。RCIF函数用于检查数值范围条件的不匹配。
布尔条件倒排文件(BCIF). BCIF函数只记录完全覆盖结点空间的查询。BCIF中的每个条目由两个属性组成:查询条件集ϒ和相应的查询。BCIF用于检查布尔设置条件的不匹配。
 我们以图8为例来说明如何构建IP-Tree。它是由SP以自上而下的方式构建的。我们首先创建根节点,并将所有查询作为部分覆盖查询添加到它的RCIF中。然后我们分割根节点并创建四个等距的子节点。对于每个子节点,如果一个查询完全或部分覆盖了该节点的空间,它将被添加到该节点的RCIF中。同样,一个全覆盖查询的等价集将被添加到节点的BCIF中。以N1为例。虽然查询q1和q2完全覆盖了这个节点,但查询q3仅部分覆盖了它。因此,RCIF包含三个交叉查询q1、q2和q3。q1和q2的涉及的类型是完整,q3的类型是部分。对于N1的BCIF,q1和q2共享同样的集合{“Van”},集合{“Benz”}和{“BMW”}分别对应着q1和q2的查询。接下来,由于q3只部分覆盖了N1,我们进一步将N1分裂成四个部分。由于q3全部覆盖了N7,它被添加到N7的RCIF和BCIF中。当在任何叶节点中没有找到部分查询时,算法终止。当一个查询被注册或注销时,我们更新IP-Tree的节点与查询的数值范围相对应。如果有必要,我们也可以拆分或合并树节点。请注意,为了防止树变得太深,当树的深度达到某个预定义的阈值时,我们切换回没有IP-Tree的情况。
  使用IP-Tree索引,可以将订阅查询作为树遍历处理。我们首先使用单个对象的例子来说明基本概念。当一个新的对象O到达时,IP树沿着从根到覆盖O的叶节点的路径被遍历。对于遍历路径上的任何节点nq,可以从nq的RCIF中找到相关的查询。这些查询可以分为三类:(1) 一个等价集在nq的BCIF上匹配对象O的全覆盖查询(因此,这个查询的结果是O被添加)。(2) 一个等价集在nq的BCIF上不匹配对象O的全覆盖查询(因此,将调用ProveDisjoint(·),并为该查询生成一个不相交证明)。(3)部分覆盖查询(无需进一步操作)。此外,我们识别出现在nq的父节点但不在nq的RCIF查询。接下来,nq的子节点将被处理,并且这个过程继续,直到我们到达一个叶节点或者所有查询都被分类为匹配或不匹配。考虑一个新的对象Oi = ⟨ti,(0,2), {“Van”,“Benz”}⟩ = ⟨ti, {001,102,“Van”,“Benz”}⟩ 在图8中展示。在N1,Q1被归类为匹配查询,q2和q4分别因为布尔集合条件和数值范围条件而不匹配。而q3直到我们检查N1的子节点N7才被确认为不匹配。
  思路能够很轻易的由区块内索引索引的对象的新块被推导出来。我们从块内索引的根开始,对于结点nb的任何索引,我们将其视为一个超级对象,并应用上述查询处理过程。唯一的区别是,如果一个全覆盖查询被归类为匹配,我们不能立即返回当前节点作为查询结果,而是进一步递归检查其子节点,直到到达叶节点。为了方便起见,附录a给出了详细算法的伪代码。

7.2 惰性认证

注意,在前面的部分中,新的区块被确认时,结果和证明被立即发布给注册用户。特别是,即使查询没有匹配结果,仍然计算并发送不匹配的证明。这种方法适用于实时应用程序。对于没有此类实时要求的应用程序,我们提出了一个延迟认证优化,其中SP在存在匹配对象时才会返回结果(或自上次结果以来的时间已通过阈值)。
  在这种方法中,VO应该证明当前对象是匹配的,并且自最后一个结果以来的所有其他对象都与查询不匹配。为了实现这一点,我们只需等待匹配结果,并调用一个时间窗口查询来动态计算不匹配的证据。然而,这种方法只能分别为每个查询生成不匹配的证明,不能利用不同订阅查询共享的证明。此外,这种方法将校对的负担全部留给有匹配结果的时候。为了解决这些问题,我们提出了一种新的方法,利用块间索引来增量生成不匹配证明。
  使用块间索引来回答订阅查询与使用时间窗口查询完全不同。原因是我们可以反向遍历区块链,并使用跳过列表在时间窗口查询中聚合证据。但是,对于订阅查询,我们不能这样做,因为新的块尚不可用,并且我们不知道未来的对象是否会共享相同的不匹配条件。因此,我们引入了一个堆栈,以便于跟踪共享相同不匹配条件的到达块。基本思想是使用跳跃列表来找到最大跳过距离Li,这样它就覆盖了m个堆栈顶部的元素。区块的AttDigest被AttDigestLi替代。得益于第5.2节中的构造2,不相交的证明可以通过调用ProofSum()在线聚合。例如,堆栈中有两个不匹配的blocki和blocki-1并且跳过距离为2。然后,SP可以用一个从ProofSum(πi,πi-1)计算的聚合证明来替换它们的证明。这样,当找到匹配结果时,SP不需要从头计算集合不相交证明。算法5中描述了详细的过程。

8 安全性分析

本节对多集累加器和查询验证算法进行安全性分析。

8.1 多重集累加器

我们首先给出多重集累加器和集合不相交证明的安全概念的正式定义。
定义8.1(不可伪造性[32]). 我们说,如果任何多项式时间恶意攻击的成功概率在下面的实验中可以忽略不计,那么多集累加器是不可伪造的:

此属性确保恶意SP伪造集合不相交证明的机会可以忽略不计,这为我们提出的查询认证算法的安全性奠定了基础。我们现在表明,我们的累加器的结构确实满足了所需的安全要求。
Theorem 8.1. 第5.2节中给出的多重集累加器的结构满足定义8.1中定义的不可伪造性的安全属性。
证明。详细证明见附录二。

8.2 查询认证分析

我们的查询认证算法的不可伪造性的正式定义如下:
定义8.2(不可伪造性)。 我们说我们提出的查询认证算法是不可伪造的,如果任何多项式时间恶意攻击的成功概率在下面的实验中是可以忽略的


  此属性可确保恶意SP的机会伪造不正确或不完整的结果可以忽略不计。我们可以表明我们提出的查询身份验证算法确实满足所需的安全要求.
Theorem 8.2. 我们提出的查询认证算法满足定义8.2中定义的不可识别性的安全性属性
证明。有关详细证明,请参阅附录C.

9. 性能评估

在本节中,我们将评估用于时间窗口查询和订阅查询的vChain框架的性能。实验中使用了三个数据集:
• Foursquare (4SQ)[46]: 4SQ数据集包含了1M的数据记录,这些是用户登记的信息。我们将30s间隔内的记录打包为一个块,每个对象的形式为 ⟨timestamp, [longitude,
latitude],{check-in place’s keywords}⟩ ,平均每个记录有2个关键字。
• Weather (WX): WX数据集包含2012-2017年间美国、加拿大和以色列36个城市的150万小时天气记录。对于每条记录,它包含七个数字属性(例如湿度和温度)和平均有两个关键字的天气描述属性。同一小时间隔内的记录和打包区块。
• Ethereum (ETH): 以太网交易数据集提取自2017年1月15日至2017年1月30日期间的区块链以太网。它包含90,000个具有112万条交易记录的块。每笔交易的形式是 ⟨timestamp, amount, {addresses}⟩, 其中,amount是以太网传输的数量,而{address}是发送方和接收方的地址。大多数交易都有两个地址。
  请注意,4SQ、WX和ETH中区块的时间间隔分别大约为30s、1小时和15s。
  查询用户设置在一台配备英特尔酷睿i5 CPU和8GB RAM的商用笔记本电脑上,运行在CentOS 7上,只有一个线程。SP和矿工设置在x64 blade服务器上,该服务器配有双英特尔至强2.67千兆赫、X5650中央处理器和32 GB内存,运行在CentOS 7上。实验是用C++写的,使用了以下库:MCL用于双线性对计算,Flint用于模块化算术运算, Crypto++用于160位SHA-1哈希操作,OpenMP进行并行计算。此外,SP运行24个超线程来加速查询处理。
&esmp; 为了评估vChain中可验证查询的性能,我们主要使用三个指标:(1) 以SP CPU时间计算的查询处理成本。(2)
以用户 CPU时间计算的结果验证成本。(3) SP传输给用户的VO的大小。对于每个实验,我们随机生成20个查询,并报告平均结果。默认情况下,我们将数值范围的选择性设置为10%(对于4SQ和WX)和50%(对于ETH),并使用大小为3(对于4SQ和WX)和9(对于ETH)的析取布尔函数。对于WX,每个范围谓词都包含两个属性。

9.1 安装成本

表1报告了矿工的安装成本,包括ADS的建设时间和ADS的

规模。实验中对三种方法进行了比较:(1) nil: 不使用索引。(2) intra: 仅使用块内索引;(3) both: 使用块内和块间索引,其中块间索引中的SkipList的大小设置为5。每种方法都采用第5.2节中介绍的两种不同的累加器结构(标记为acc1和acc2)。因此,在每个实验中总共评估了六个方案。不出所料,两者的ADS建设时间一般都比nil和intra长,但大多数情况下仍在2s以内。此外,与acc1相比,acc2显著减少了两者的构建时间,因为它支持在线聚合,因此可以在构建块间索引时重用前一个块的索引。关于ADS的大小,它独立于所使用的累加器,对于不同的索引和数据集,每个数据块的大小从2.6KB到11.1KB不等。
  我们还测量了用户运行轻量节点来维护区块头所需的空间。对于nil和intra,不考虑数据集和累加器,每个区块头的大小是800位。由于块间索引,两者的块头大小都略微增加到960位。

9.2 时间窗口查询性能

为了评估时间窗口查询的性能,对于4SQ和ETH,查询窗口的时间从2小时到10小时不等,对于WX,查询窗口的时间从20小时到100小时不等。三个数据集的结果分别如图9-11所示。我们做了一些有趣的观察。首先,正如预期的那



样,索引大大提高了几乎所有指标的性能。特别是对于4SQ和ETH数据集,使用索引的性能至少比使用相同累加器但不使用任何索引的性能好两倍。这是因为这两个数据集中的对象共享较少的相似性,因此从使用索引进行修剪中获益更多。第二,基于索引的方案的成本仅次于查询窗口的扩大而增加。对于使用acc2的基于索引的方案,这在用户CPU时间方面尤其如此,acc2支持批处理不匹配的验证(见6.3节)。第三,比较intra和both,除了4SQ数据集的处理器时间之外,both都不比intra差。一方面,这表明使用块间索引的有效性。另一方面,both比intra在SPU时间上表现差的主要原因是基于块间索引的方案中,较大的多重集被用作集合不相交证明的输入,这增加了处理器的处理时间。在附录D.3中提供了更多关于这方面的见解,在这里我们检查了SkipList大小的影响。在ETH数据集上观察到Both相对于intra的最大改进。理由如下。与4SQ相比,ETH中对象间的相似度较低;与WX相比,ETH每个区块包含的对象较少。对于这两种情况,通过在区块间索引中使用跳过列表,可以获得更多的性能改进。

9.3 订阅查询性能

我们接下来评估订阅查询的性能。首先,我们在启用块内和块间索引的默认设置下,使用或不使用ip树(表示为IP和nip)来检查SP的查询处理时间。我们随机生成不同数量的查询。我们将4SQ和ETH的默认订阅时间设置为2小时,WX为20小时。如图12所示,在所有测试案例中,知识产权树将服务点的开销减少了至少50%。在所有测试案例中。由于数据分布更稀疏,所以在ETH数据集中的性能增益(图12©)更大

  为了比较实时和惰性认证,我们考虑了两个实时方案(带有acc1和acc2)和一个惰性方案(仅使用acc2,因为acc1不支持累积集和证据的聚合)。我们将4SQ和ETH的订阅时间从2小时改为10小时,WX的订阅时间从20小时改为100小时。图13-15显示了改变订阅周期的结果。


显然,就用户CPU时间而言,惰性方案比实时方案性能好得多。此外,惰性方案中的CPU时间和VO大小仅随着订阅周期的增加而呈次线性增加。这是因为lazy方案可以跨块聚合不匹配对象的证明。相比之下,实时方案在新的块到达时立即计算所有的证明,导致更差的性能。就SP CPU时间而言,因为惰性方案需要牺牲SP的计算来聚集不匹配证明,当使用相同的累加器时,其性能通常比实时方案差。

10. 结论

本文在文献中首次研究了区块链数据库的可验证查询处理问题。我们提出了vChain框架来保证轻量级用户布尔范围查询的完整性。我们开发了一种新的基于累加器的自动数据挖掘方案,该方案将数字属性转换为集值属性,从而能够在任意查询属性上进行动态聚合。在此基础上,设计了两个数据索引,即基于树的块内索引和基于跳转列表的块间索引,以及一个基于前缀树的订阅查询索引,并进行了一系列优化。虽然我们提出的框架已经被证明是实际可实现的,但是所提出的技术的健壮性被安全性分析和经验结果所证实。
  本文为区块链研究开辟了一个新的方向。有许多有趣的研究问题值得进一步研究,例如,如何支持更复杂的分析查询;如何利用多核和众核等现代硬件来提升性能;以及如何解决查询处理中的隐私问题.

vChain: Enabling Verifiable Boolean Range Queries over Blockchain Databases相关推荐

  1. vChain: Enabling Verifiable Boolean Range Queries over Blockchain Databases(sigmod‘2019)

    目录 Abstract 1 Introduction 2 相关工作 问题定义 4 预赛 5 基本解决方案 5.1 ADS 生成和查询处理 5.2 多组蓄能器的结构 5.3 范围查询的扩展 6 批量验证 ...

  2. 论文翻译——vChain: Enabling Verifiable Boolean Range Queries over Blockchain Databases

    0-abstract 由于加密货币的兴起和去中心化应用的兴起,区块链近来备受关注.查询存储在区块链数据库中的数据的需求不断增长.为了确保查询的完整性,用户可以维护整个区块链数据库并在本地查询数据.但是 ...

  3. 论文笔记-vChain: Enabling Verifiable Boolean Range Queries over Blockchain Databases

    核心方法: 提出了一种基于累加器的可认证数据结构,可以动态聚合任意查询属性 提出块内和块间索引,聚合块内和块间数据,可以做高效查询验证 倒排前缀树结构,加速同时处理大量数据的订阅查询 提出问题: 1. ...

  4. 区块链可验证查询论文阅读(一)vChain: Enabling Verifiable Boolean Range Queriesover Blockchain Databases

    2019年7月发表在顶会SIGMOD上的论文<vChain: Enabling Verifiable Boolean Range Queries over Blockchain Database ...

  5. 区块链相关论文研读2 - vChain,关于可验证的查询

    这是在2019年7月发表在顶会SIGMOD上的论文,题目为<vChain: Enabling Verifiable Boolean Range Queries over Blockchain D ...

  6. Vchain:可验证的查询

    Vchain:可验证的查询 总体概述 论文核心内容 全文内容概述 密码学名词解释 传统的区块链模型 Vchain模型 从三大方向研读本文 一.查询的正确性 二.查询效率高--Boolean range ...

  7. access汇总_区块链或密码学相关论文汇总,持续更新中

    这里汇总区块链相关的论文,主要是顶会顶刊的. 温馨提示:没必要精读每一篇论文,因为时间和精力是有限的 下面的部分论文列表下面有知乎链接 ,它们是本人对该论文的解读. 同步更新本人Github,可通过& ...

  8. 【区块链论文整理】SIGMOD篇(三)

    SIGMOD(Special Interest Group On Management Of Data)是数据库三大顶会之一,近几年也发表了不少水平很高的文章.本文主要针对SIGMOD会议中区块链相关 ...

  9. 【区块链论文整理】SIGMOD 篇 (二)

    SIGMOD(Special Interest Group On Management Of Data)是数据库三大顶会之一,近几年也发表了不少水平很高的文章.本文主要针对SIGMOD会议中区块链相关 ...

最新文章

  1. c字符串函数实现(1)---strncpy
  2. 未定义的引用_Rust 引用和借阅
  3. Silverlight Dispatcher 类
  4. 探秘react,一文弄懂react的基本使用和高级特性
  5. python安装Scrapy踩过的坑以及安装指导
  6. SAP License:物料类型被误删除及解决办法
  7. 给定一个数组和一个数M,在数组中求一些数使它们的和最接近M------递归
  8. stl之multimap容器
  9. Docker之使用Dockerfile创建定制化镜像(四)--技术流ken
  10. HTML 打开新页面 关闭,javascript打开新窗口同时关闭旧窗口
  11. 知识图谱构建技术综述与实践
  12. LINQ TO XML 应用之 Win8 Metro 开发
  13. flac批量转mp3,详细步骤
  14. 操作系统页表进程调度Tips
  15. 1620:质因数分解
  16. python-tkinter使用方法——转载(二)
  17. java读取并导出多类型数据csv文件
  18. kotlin版贪吃蛇小游戏
  19. android 锯齿
  20. 前端|网页制作秘密武器 之发光边框

热门文章

  1. Ubuntu 上使用Dreamweaver 8
  2. Oracle中WITH ...... OPTION权限对于权限授予和收回的级联影响
  3. 计算机毕设Python+Vue在线答题系统(程序+LW+部署)
  4. Java实现 LeetCode 807 保持城市天际线 (暴力)
  5. 比 Xshell 还好用的 SSH 客户端神器!爱了
  6. 程序员进银行科技岗——简单总结
  7. 西门子S7-200 SMART/828d PLC数据采集、远程调试
  8. 数理逻辑蕴含_数理逻辑(1)——命题逻辑的基本概念
  9. linux开防火墙网速下降,关于linux网速提速的解决
  10. Java学习笔记:redis入门