区块链原理初步 | 《区块链与比特币》系列2

作者: bad6f946ebc4 | 来源:发表于2017-05-28 18:31 被阅读288次

    本文属于《区块链与比特币》系列文章中的第二篇,该系列文章正在撰写中,敬请期待!欢迎留言与我交流探讨区块链技术和前景,也可以发邮件到我邮箱:mywcyfl@163.com,我会回复的。

    另注,如果您认为本文大段大段的文字表述太过生硬,不够形象易读,您也可以在百度文库中搜索关键字:“比特币原理”,前排能看到一个名为“比特币原理_图文”且贡献者为“买洋火的小男孩”的PPT(由于质量上佳且通俗易懂,所以通常都是排第一位),点开后的标题为《疯狂者游戏——全网唯一不可伪造数据比特币原理》,这份PPT用较少的文字和恰当的图片,十分形象具体地讲清楚了比特币的原理,文档所获得的评价也十分之高,您可以去下载后看看,也可以下载下来后结合PPT的讲述来看此文,这样会容易理解许多。哦,对了,这份PPT也是我写的,目前在百度文库的比特币原理相关文档中排名第一,访问量过万、下载量近2000。

    上文说过,区块链是比特币的支撑技术,也是比特币整个体系中的核心。中本聪的白皮书,虽其自身的用意是发明比特币这种电子现金系统,但整篇论文其实都是在讨论如何实现——也即讨论区块链。细心的读者或许还注意到了上文中的一个表述:从关系上讲,比特币其实只是区块链在数字货币领域的一个应用(而且自然也是第一个应用)。既然这样说,那自然说明除了比特币、莱特币(及其他数字货币)外,还会在其他方面有很多应用空间。

    区块链整体包含较多的计算机、数学领域专业知识,因此如果我们逆向的来一一探讨其中每个技术点,那本篇将会十分的枯燥无味。这就如同我们读书时代一直学习的数学一样,虽然生活中的数学应用太多太多,但数学课的课本却从来都只是生硬的介绍这个公示、那个原理,然后再抛出一道道习题让你练习,这样知其然而不知其所以然的填鸭式教育,最后的结果就是让大部分人对数学敬而远之、而且毫无兴趣。但吴军老师的《数学之美》一书,带你从生活中来一点点找寻实际甚至是有趣的问题, 对其进行分析并提炼出数学模型,最终将其完美解决。这样的行文方式让人对数学的兴趣大增,读者们第一次发现,原来那些深奥、晦涩的数学公式,它们在生活中大有用处,它们居然可以实用于这些复杂的问题,让问题化繁为简,从而巧妙的将其解决。本文我们也将尝试这种表述方式,从需求上出发,分析揣摩每个技术点它为何需要出现、它是为解决什么问题而存在于这个电子现金系统中,希望以这种方式来让读者对区块链的理解更加直观、生动,而不只是停留在一堆堆数学原理和公式上。

    假设你当前对区块链技术一无所知,但信息论和密码学却掌握的登堂入室,但现在有一台时光机(Time Machine),它带你回到了2008年,也就是比特币发明之前。同时,你被赋予了一个艰巨的任务:抢在中本聪之前,把比特币给整出来,也即发明一种数字货币(好吧,高雅的概括叫“电子现金系统”),要满足的条件就是无需可信的第三方(因此不需要央行和金融中介,从而可以不受任何包括国家、公司在内的组织或个人所管控),但却可以自主且自由的发行和交易,而且还具备抗通货膨胀、紧缩能力。

    好,现在请开始的你表演。

    地址——解决身份标识问题

    这个么艰巨的任务交给了你,你一时有点手足无措,你完全不知道要干什么,更不知该从何处开始,你甚至有点懵逼了。

    但凡事都有方法论一说,面对这种初步看起来毫无头绪的系统性工程,最重要的就是化繁为简,将一个大问题分解为若干个小问题,并逐一击破。

    我们先把所有的其他问题抛开不管,现在只关注一个小细节:假设某人拥有一笔该数字货币,那么他如何存放这笔货币?也就是怎么表明某笔钱是某个人的。为了更有思路,我们先来分析其他形式的货币在该问题上的解决方案。

    首先是实体货币,不管是数千年前荒蛮时代的贝壳、或是后来内方外圆的各类铸造铜板、再到现代的纸币,存放问题都是一件几乎不需要思考的问题:直接揣兜里即可,又或者放在家里床底下也行。对于实体货币而言,货币在谁手上,就是谁的。比如大街上走着一个人,他钱包里放着厚厚的一叠钞票,由于钱包在他手中(而且你也没有提出有关钱包归属权的异议),那钞票的归属显而易见。但我们的数字货币没有现实实体,该方法并不可行。

    再看看银行里的金融,这些年,随着银行业的信息技术不断的深入,大部分的身家都是以银行账户里的余额(或股票、基金等)来体现,纵使部分富人身家亿万,但真正的现金却并不会太多。而要转账或者支付时,也只需登录银行账户并做一个简单的操作即可。这种方案似乎跟我们的需要恰好相符——你或许还会强调说这才是你的第一反应。

    那紧接着来深入思索一番,既然我们要发明的是数字货币,因此货币肯定是以数据的形式存放在计算机存储设备中——这一点是毋庸置疑的。银行业的模式可以简单的描述为:个人去银行开设账户,资金与账户对应,也即从抽象概念来讲,资金存放在个人的账户中,而这种存放则以银行数据库服务器中的数据为凭据。这里的核心与我们的需求格格不入:银行在这里的角色是中心节点,也即银行其实就是上述金融体系中可信的第三方。当一个人亮出他网银中的余额,你之所以会认可他这笔钱,不是因为你认可对方这个人,而是你认可网银背后的银行。

    思索后的结果是,开设账户的方式,并不直接满足我们的需求,因为没有中心节点能提供开设账户的权威。但你据此推演,没有中心节点就意味着整个网络是分布式的(这个分布式网络还必须不存在中央节点或一些提供特殊服务的节点,也就是每个节点都完全对等),分布式的网络体系有没有什么可行的账号分配方案呢?

    比特币分布式系统的网络拓扑图,所有节点完全对等,没有中心

    来得最快的灵感往往是最直接的,是否可以让分布式网络中的每个节点都具备账号分配权限呢?初步看起来似乎不可行,因为这样很难保证账号的唯一性,比如A节点可能产生账号“9527”,而几乎与此同时,B节点可能会产生“9527”这个账号。如果说制定一个规则,通过比较账号的时间戳来判定优先权,时间点较后产生重复账号无效。仔细想想,依然行不通,因为常规的商业分布式网络中,每个节点其实都是由商业机构所部署,节点的行为和权限受到该机构的约束。而在我们这个分布式网路中,由于不受任何组织或个人所管控,任何一个参与进来的节点都没有人知道是谁,更没有人有力量去保证该节点会遵守游戏规则,因此每个节点都可能作弊,它们可能会故意发一个比实际时间早很多的时间戳来保证账号更大可能性获得通过。

    进一步的,由于每个节点都可能不诚信,所以实际上任意一个节点所提供的信息,都不值得信赖(尤其是在利益驱动下),故而我们所提出的方案必须在节点可能不诚信的前提下依然奏效。明确此点,好让之后的思考少走弯路。

    你突然想到,之所以会有重复账号问题,那是因为账号产生时的值域太小。如果账号的取值域扩大到一个天文级别,甚至是天文级别的幂次方(一时没有形容词去描述这是什么级别),然后每个节点在产生账号时,随机在值域中找一落点,这样不就能避免重复问题了吗?

    具体来说,以一个位数足够宽的二进制数来代表此账号,由于位数足够宽——比如160位,使得值域的取值域大小为2160——这是一个极其浩瀚的数字,约等于1048(1后面跟着48个零),若当前地球人口以100亿计算(1后面跟着10个零),这意味着平均每个人可以分得约一百万亿亿亿亿个账号。每次产生新账号时,只需在此值域空间随机取一个数即可(工程上的具体做法为取一个160位的随机二进制数),由于取值域实在太大,重复的概率无限趋近零,因此可以忽略不计(实际工程领域可以认为一百万年内都难以出现一个重复的值)。

    选取这样的账号产生方案还有一个优点,那就是每个用户近似可以拥有无限多的账号,甚至是每一次交易都可以产生一个新的账号(因为不用担心账号资源枯竭),从而显著的提高交易的隐秘性(不过这到底是优点还是缺点呢?至少中本聪认为是优点)。

    账号格式和其产生的方式得到初步解决,但这显然不够完善。虽然现在能使得每个节点都可自由的产生账号,但如何确定账号的所有权又成了新的问题。计算机领域内账号所有权都是通过密码来确认,也即一个账号对应一个密码,谁能提供账号锁对应的密码,谁就具有该账号的所有权。可这套最经典的方案依然不能直接适用于此处,原因在于常规的账号-密码体系中,密码通常以哈希值(如MD5或SHA等)的形式存储于数据库中,在需要授权验证时,通过比对数据库中密码的哈希值和用户当次输入的密码的哈希值,如果匹配,则验证通过。但同样由于我们所规划的电子现金系统,其所有的节点都可能篡改现有账号的密码,因此并不直接适用。

    [TODO:配图说明常规的账号密码方案中,篡改密码来获得权限]

    我们同样需要寻找一种方案,使得账号-密码体系一旦产生,则无法被修改或者修改后也无效。直觉告诉你,这需要非对称密码学的帮助。

    非对称密码学由W.Diffie和M.Hellman于1976年提出,其具体的原理网络上由大量的介绍,为避免有凑字数的嫌疑,本文不做详述。但简要的概括起来,非对称加密算法需要两个密钥:公钥(publickey)和私钥(privatekey)。公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。 常见的非对称加密算法有RSA和椭圆曲线加密算法等。

    将上述引入我们的需求,如果我们产生一对公钥和私钥,然后令公钥作为我们的账号,私钥则自然作为我们账号的密码。这样的好处在于,公钥作为电子现金系统的账户,需要公开给交易方,私钥却无需保存在任何一个节点中,也就自然就不用担心被篡改。当需要做授权验证时,仅需让用户在本地用其私钥对某些上下文信息做加密,得到签名信息,然后其他节点利用该用户的公钥——也就是该用户的账号——对签名信息进行解密,比较和原始上下文是否一致,如一致则验证通过(这就是“数字签名”的大致原理)。

    整个解决方案已经出具轮廓,剩下的就是工程实践问题。实际上,中本聪所提出的方案比我们刚才讨论的,要复杂且细致不少。中本聪考虑到实际应用中的信息传输量和容错性等问题,并没有直接将公钥用作账号,而是对公钥进行若干次哈希操作以降低账号长度,并加入了校验位和版本号,然后再出于可读性的考虑,对组合后的账号进行了Base58编码,才得到了最终的账号——不过,中本聪将其称作”地址“,而不是”账号“,虽然这只是换了一个说法。

    我们现在来完整的看一下中本聪给出的解决方案,文字形式的表述如下:

    • Step1:利用随机数发生器生成一个32字节的随机数作为私钥,但受制于第二步所有的公钥生成算法,随机数数值需要介于0x01到0xFFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFE BAAE DCE6 AF48 A03B BFD2 5E8C D036 4140间(0x表示该数字是以16进制表示的,0x对数值本身无意义),私钥需要用户妥善保存。

    • Step2:将上一步生成的私钥作为输入,利用椭圆曲线加密算法(ECDSA-secp256k1)生成公钥,公钥的长度要长于私钥,为65字节,二进制表示时为520位。secp256k1算法保证了经由私钥计算得到公钥十分简便,但试图由公钥反算出私钥则极其困难,在当前科技水平下可以近似认为绝无可能。

    • Step3:考虑到公钥较长,为了节约信息传输量,中本聪并不直接将公钥用做地址。而是将公钥利用SHA-256哈希算法计算得到32字节的哈希值。

    • Step4:对于上一步得到的32位哈希值,再通过RIPEMD-160算法得到20字节的哈希值。20字节也即160位,RIPEMD-160算法后面的“160”本身也表示该算法的输出为160位。为了方便表述,暂且将本步骤生成的哈希值称作Hash160。

    • Step 5:将1字节的版本号附加到Hash160前面,组成21字节的二进制串。对该二进制串连续做两次SHA-256运算,并取结果的前4字节作为校验码,放在21字节的末尾,本步骤得到一个25字节的数字。

    • Step 6:考虑到地址的可读性,中本聪利用Base58编码算法对上步骤的结果进行编码,得到最终地址。

    上述过程中需要额外注意的有两点:
    其一,步骤5中的版本号是基于工程考虑,中本聪显然在工程实践方面的经验也十分丰富,他考虑到比特币体系不可能一蹴而就,生成地址的机制可能随着时间的变化而变化(如新需求诞生、或原机制暴露了问题),因此引入1字节的版本号,方便之后新版本的向前兼容。同样,校验码的引入也是出于工程考虑,这样使得就算用户手动输错了地址的某些位,系统也能立即发现(校验码校验失败,会提示地址无效),而不是就将错就错,让用户把钱打入到错误的地方。

    其二,步骤6中所使用的Base58仅仅是一种编码算法,而非加密算法,因此经由本步骤得到的地址,可以轻易的转换回上步骤得到的25字节数字。引入该步骤,纯粹为提高地址的可读性。而且相较于工业界常用的Base64算法(Web的URL地址就是Base64应用的一个典范),Base58算法不使用"+"和"/"符号(从而方便直接点击选取一整串地址)、不使用数字"0"和大些字母"O"(从而避免和小写字母"o"混淆)、不使用大些字母“I”和小写字母"l"(从而避免和数字“1”混淆)。

    考虑到本文的读者应当具备一定的编程能力,我们再给出上述流程的伪代码表述:

    GENERATE_KEY_ADDR()
        version ← 0x00;
        r_min ← 0x01; 
        r_max ← 0xFFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFE BAAE DCE6 AF48 A03B BFD2 5E8C D036 4140;
        key_s ← string(random(r_min, r_max));
        key_p ← secp256k1(key_s);
        addr ← Hash160(SHA256(key_p));
        addr ← strcat(version, addr);
        addr_hash ← SHA256(SHA256(addr));
        check ← (0xFFFF FFFF 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 & addr_hash) >> (256-32);
        addr ← strcat(addr, check);
        addr ← Base58(addr);
        return key_s, key_p, addr
    

    伪代码的返回结果中,key_s表示私钥,key_p表示公钥,addr则表示地址。

    经由上述步骤生成的最终地址类似于“1TMtxo9Z2YSykYqA1NkQSEgc75eMuekfT”,虽然比特币地址在未经Base58编码前长度以二进制计算是固定的25字节,但由于二进制到字母转换时位数的不确定性,编码后字符串的长度则介于26位-34位之间。地址的第一个字母通常是数字“1”,这是由于步骤5中放置在地址最前面的版本号通常是“0”所导致的。

    用户、私钥、公钥和地址的关系

    到现在我们明白,地址和公钥并不完全是一回事。公钥虽然可以直接作为地址使用,但最终成为地址的,是公钥的哈希值(当然还附加了版本号并进行了Base58编码,但为了表述方便,这里省去不说,下文也将如此表述)。而一旦私钥确定,那么公钥和地址也随之确定。用户只需要保密自己的私钥,地址则需要公开,实际上,公钥也需要公开,其原因我们很快会看到。

    钱包——让地址管理变得轻松

    经过了一阵头脑风暴和握笔演算,你轻松拿下账号问题。但你很快想到,相比于可以很容易存放在卡包里的一叠银行卡,你的电子现金系统的账号是一串长长的、毫无规律难以记忆的字符串——而且每个人几近于可以拥有无限多个,因此一个方便的管理方式也就成了一个问题。

    虽然实体银行卡方便存放,但人们想要使用它,必须同时保存好实体银行卡卡片和卡片对应的密码,只不过国内银行卡的密码通常是6位数字,默记于心相对比较容易。而在你刚才设计的账号密码提下中,由于一旦私钥确定,那么公钥和地址随之确定,因此用户只需要将私钥保存好即可——类比到银行卡的概念就是用户只需默记住密码,卡则不用保存。

    首先我们要摒弃掉让用户用笔将私钥记录在纸上这个办法,因为私钥可能经常增加,而每个又是长长的一串毫无规律的二进制,极易抄写错误,因此记录和保存起来都十分麻烦。而且用户还得担心万一这记录私钥用的小笔记本被人翻看了,那简直是灾难性的。

    让用户自行将私钥保存在他们自己的电子设备中也不会是个好办法,因为这样私钥太容易被盗,对于安全意识不高、或者没有提高计算机安全防御能力的用户而言,这简直是在给黑客送钱。而且为了防备电子设备的损坏,用户必须得自行做多个备份,这又进一步加剧了被盗的风险。

    最佳的做法显然还是依靠加密手段,而且虽说可以让用户自行完成加密,但如果这样做就注定了这个电子现金系统很难快速的为市场所接受,因为对普通用户而言,这门槛太高。

    思考到这,答案就呼之欲出了。你毫不犹豫的确定,需要在系统框架内,提供一种依赖于加密学的地址管理模块。由于需求是只允许用户本人拥有对私钥的加密、解密权限,没有密钥需要公开,因此合适的加密方案显然是对称加密。

    所谓对称加密,顾名思义,相对于上文我们提到的“非对称加密”而言,它只需要一个密钥,这个密钥既负责加密、又负责解密。加密过程和解密过程互为逆运算,因此称之为“对称加密”。对称加密同时也是我们千百年来最传统、最直接的加密思维。

    以AES为代表的对称加密算法简化模型

    上述简化模型中,X代表加密前的明文,Y代表加密后的密文,K则是密钥,Y=E(K, X)代表正向的加密运算,X=D(K, Y)代表反向的解密运算,函数E和函数D互为逆运算。该简化模型实际上也是对称加密学的通用模型。

    确定了这一点,具体的解决方案就可顺手拈出。你决定选取当前对称密码学中安全系数最高的AES作为具体加密方案,AES的英文全拼为“Advanced Encryption Standard”,直译过来是“高级加密标准”,它是由美国国家标准与技术研究院(NIST)历时近5年所甄选出来的对称加密算法,至今已成为对称加密领域的主流算法。AES的原型是Square算法,它的设计策略是宽轨迹策略(wide trail strategy),具备很好的抵抗差分密码分析及线性密码分析的能力,而在效率上又有不错的表现。

    引入我们的需求,我们利用AES来加密私钥,得到加密后的密文以文件的形式保存在用户电脑中。用户可能有多个私钥,那也只需将多个私钥以恰当的格式合并在一个文件中,利用AES将此文件整体加密即可。后续增加私钥的处理办法也很简便,只需把原先加密后的文件解密,将新的私钥添加进去,然后重新加密,用新生成的文件将原先的替换掉。(为了避免每次启用新的私钥后又需要重新备份钱包,实际工程中,比特币钱包在生成多个私钥时,通过设置私钥种子的方式,使得无论后续新增多少个私钥,你只需要在最开始保存一次钱包。采取这种私钥生成方式的钱包称之为确定性钱包,而采取每个私钥均随机生成、多个私钥间毫无关系的方式的钱包称之为非确定性钱包。关于确定性钱包的原理,我们会在后续章节继续深入讨论。)

    这些过程都可以很高效的在用户的电脑中完成,生成的加密文件可以任由用户保存多份以防丢失,而加密文件被他人盗取了也毫无所谓,因为盗取者盗取到的是加密后的私钥——也就是私钥的密文形式,这对盗取者而言毫无意义。

    依靠这种方式,用户就算有数百个私钥,他也不需要去记住这数百串长长的二进制数,而是只需要把一串AES密钥记在心里就行,记在心里自然不用担心被人看到(你该不会有说梦话的习惯吧?)。AES的密钥理论上可以为任何一串字符串,比如“12345678”或“88888888”(当然我们并不建议你这么做),这样的字符串相比于原先的32字节二进制数自然是好记忆得多。

    在比特币现有概念体系中,私钥加密后得到的密文文件被称作“钱包”,考虑到它是保存你一系列账号的容器,所以“钱包”这个称呼也算得上恰当。钱包以文件的形式被保存到用户电脑中,文件一般默认命名为“wallet.dat”。用户可以自行对钱包文件进行任何形式、任何次数的备份,为了防止电脑磁盘偶发性的损坏或电脑的丢失,通常建议用户将其备份到一些移动存储设备如U盘中,然后将U盘妥善保管。

    虽然有了私钥就可以快速计算出确定的公钥,但每次使用时都重新来一遍计算显然是不明智的,因此比特币钱包中,实际是同时保存了私钥和与之配对的公钥。生成的钱包文件的抽象格式可以简要的理解为:

    比特币钱包简要格式.png

    大家可能经常在一些数字货币咨询网站或交易网站上看到钱包“冷存储”的字样,这个“冷”显然不是指温度。“冷存储”指的是用户的私钥是在用户本地电脑上离线生成、且同样在离线的状态下被加密到钱包中,最后钱包被备份到类似U盘等的离线存储设备。由于整个过程都离线,纵使黑客再厉害,你物理上和网络隔断,他也是没办法窥探到你私钥的一丝一毫。

    相关文章

      网友评论

        本文标题:区块链原理初步 | 《区块链与比特币》系列2

        本文链接:https://www.haomeiwen.com/subject/bhuhfxtx.html