太多事情需要做了,但是我太拖沓了,,继续翻译,赶紧这个系列翻完,写我自己的系列


等把这个系列做完了我自己搞个专题,分享跨链交易的技术核心和方案。

继续翻译,原帖地址:



前言

上篇文章中,我们实现了交易,在交易中,没有用户账户,同样不需要个人数据(名字,护照或者SSN)。但仍然有些信息确认身份,证明你是coin的所有者。比特币的地址正是这个证明。目前为止,我们可以使用用户定义的任意字符串作为地址,是时候生成真正的地址了,就像比特币比那样。

这部分引入大量的代码变更,没必要全解释,想看细致变更来这里this page

比特币地址

这里有个比特币地址示例:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa。这是比特币的第一个地址,是中本聪的地址。比特币地址是公开的。如果你想发送币给别人,你需要知道他的地址。但是地址(尽管唯一)并不证明你是“钱包”的所有者。事实上,地址是公钥的可读化。在比特币中,你的身份标识是一对保存在你电脑里的公钥和私钥。比特币依赖加密算法来创建公钥和私钥,并保证世界上除你之外的任何人都不能在没有私钥的情况下访问你的coin。我们来详细讨论。

公钥加密

算法使用一对key,公钥和私钥。公钥可以公开给任何人,相反地,私钥不可以公开。

本质上,比特币钱包仅仅时一些key对。当你安装了钱包应用或者使用btc的客户端产生心的地址时,即产生了一对key。控制私钥的人控制了发向这个key的比特币。

私钥和公钥仅仅是一串随机的bytes,所以他们是不可读的。因此,比特币系统使用算法将这些转换成可读的string。

如果你曾经使用过钱包应用,你很可能使用过助记词。这些助记忆词可用来生成私钥。这个机制成为BIP-039。

Ok,我们知道了btc系统中怎样进行身份认证。但是比特币怎样验证交易输出的拥有者并保存呢?



数字签名

在数学和密码学,有数字签名的概念,算法保证:

1、数据在由发送者到接收者的传递过程中不被修改

2、数据由确定发送者创建

3、数据发送者不能否定已经发送的数据

通过使用算法对数据签名,当某人收到一个后续可以被验证的数字签名时。使用私钥数据进行签名,使用公钥对其验证。

为了对数据进行签名我们需要如下信息:

1、需要被签名的数据

2、私钥

交易产生的签名存储于交易的输出中,为了验证签名,需要如下步骤:

1、需要被签名的数据

2、签名

3、公钥


简单地说,验证过程如下:从数据中获取签名并检查,签名使用私钥生成,公钥可以用来验证签名和数据地一致性。

数字签名并未加密, 你无法从数据中重构签名。这个过程类似于hash,你使用hashing算法对数据进行hash并得到唯一地hash值。签名和hash地区别是签名可以被验证。但是key对可以用来加密数据:私钥用来加密,公钥用来解密。尽管比特币不使用加密算法。


比特币输入中地每个交易是由交易地创建者来签名。在交易加入区块之前必须被验证:验证意味着:

1、检查输入有没有权限使用上个交易地输出

2、检查交易签名是否正确

签名和验证的过程如下:

现在我们来回顾整个交易生命期:

1、开始时,有一个包含创始区块的coinbase交易,coinbase交易没有真正的输入,因此没必要签名。coinbase交易的数据包含公钥的hash值(IPEMD16(SHA256(PubKey)算法)

2、当一个人发送coins,他创建交易。当前交易的输入引用上一个交易的输出。每个输入保存公钥(非hash)和整个交易的签名。

3、btc网络中的其他节点收到交易后对验证它。验证如下:若输入的公钥hash值匹配输出引用的hash值(这保证了发送者只能花费自己的钱),则数字签名正确时(这保证了交易由真正的coin的拥有者创建)

4、若一个矿工节点准备好了挖矿,它将交易加入区块中并开始挖矿。

5、当一个区块被挖出来时,网络中其他节点生成区块被挖出并且将区块加入链上。

6、在一个块加入链后,交易完成,其输出可以被下一个交易引用。

椭圆曲线加密

如上所述,公钥和私钥是随机的bytes。由于私钥用来验证coin的拥有者身份,这里需要一个条件,随机算法必须可以产生真的随机bytes。我们不想恰好产生一个别人用过的私钥。

比特币使用椭圆曲线加密算法来产生私钥。椭圆曲线是一个复杂的数学概念,这里不打算对其细节进行解释(好奇点这里 this gentle introduction to elliptic curves)。我们需要知道的是该算法可以产生真正的随机数。比特币使用的曲线可以随机产生0到2²⁵⁶ 之前的随机数(约为 10⁷⁷,宇宙中大概由10⁷⁸ 到 10⁸²个原子)。如此大的数意味着几乎不可能产生两个相同私钥。

此外,比特币(我们一样)使用ECDSA算法对交易进行签名


Base58

现在我们回到比特币地址:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa。这是一个公钥的可读版本。如果我们想对其进行16进制解码,它看起来是这样

比特币使用Base58算法来将公钥转换为人类可读版本。base58与base64非常类似,但它使用更少的字母集合:一些相似的符号从字幕集合中删除。入0和O,I和l,因为它们长得很像,此外,base58没有+和/

我们一起来看看由公钥产生地址的过程:

上述解码(译注:原文是decode,我觉得是编码才对)公钥包含三个部分:


由于hash 函数不可逆,从hash中获取公钥是不可能的,,但是我们可以用来检查是否是某个公钥得到的hash,通过使用hash函数并与hash值进行比对。

OK,现在我们有了所有细节,一起来码。在码的时候,一些概念会变得更加清晰。


地址的实现

我们首先看看钱包结构:


钱包只是一个key对。我们同样需要钱包类型来维护钱包集合并将其保存成文件加载。在钱包函数中生成key对。newKeyPair函数简单明了:使用ecdsa。下一步,使用曲线来生成私钥,通过私钥生成公钥。值得注意的是,在算法中,公钥是曲线上的点。因此,公钥是x,y坐标的结合。这比特币中,这些坐标连接到一起构成公钥。

我们来生成地址:

把公钥转为Base58地址的过程:
1、对公钥hash两次使用RIPEMD160(SHA256(PubKey))算法

2、将版本号置于公钥前面

3、对前两步的结果计算checksum,使用sha256。checksum是是哈希结果的头四位

4、将checksum附在第二步的后面

5、使用base58对第四步结果编码


这就得到了真正的比特币地址,你可以使用其在区块链浏览器上查询余额,但我保证肯定是0。这是公钥加密算法重要的原因:考虑私钥是随机数,得到相同结果的公钥基本不可能。

此外,注意到,无需连接比特币节点获取地址。地址可以使用很多语言和库得到。

我们现在需要修改输入和输出来使用地址:


注意到,我们不使用ScriptPubKey 和ScriptSig,因为我们不执行脚本语言。作为替代,ScriptSig被划分为Signature和PubKey位,并将ScriptPubKey重命名为PubKeyHash,我们采用与比特币相同的的输出锁定/解锁和输入签名逻辑,但是我们采用上述方法而不是脚本语言。

UsesKey方法检查输入是否使用专门的key来解锁输出。注意到输入存储着原始公钥,但是函数入参是经过hash变换后的公钥。IsLockedWithKey检查提供的公钥是否是用来解锁输出的。这是UsesKey的补充函数,它们共同在FindUnspentTransactions中用来建立交易之间的联系。

Lock只是简单的锁定输出,当我们需要发送coins给某个人时,我们仅仅知道t他的地址,因此,函数只接收地址作为唯一参数。public key从解码后的地址参数获得并存储在PubKeyHash位

现在我们来测试下:

ok,现在我们来实现数字签名


数字签名的实现

对交易签名是比特币中用来保证用户只能花自己钱的唯一办法。签名无效则交易无效,,交易不可上链。

我们已经实现了除了对数据签名的所有交易签名的代码。到底对交易中的哪一部分签名?亦或交易是作为一个整体被签名。签名的选择很重要。被签名的数据必须和签名一一对应。

例如,不考虑发送者和接收者,仅仅对输出中的value签名是没有意义的。

考虑交易解锁上一个输出,重分配value,锁定新的输出,下列数据需要被签名:

1、公钥hash存储解锁后的输出,用来做发送者身份验证。

2、公钥hash存储在新的,锁定后的输出中。用来做接收者身份验证。

3、输出的value

在比特币中,锁定和解锁逻辑存储于输入和输出的ScriptSigand 和ScriptPubKey。由于比特币允许不同类型的脚本,需要对ScriptPubKey整体签名。

正如所见,我们不需要对输入中的公钥进行签名。由于在比特币中,不是对交易签名,而是对被引用输出的齐带有保存有ScriptPubKey的输入的裁剪拷贝(trimmed copy)签名。

裁剪拷贝交易细节的获取在这里,当前版本可能已经更新,但是我找不到更可靠的资源。

它看起来比较复杂,让我们来开始编码,先看sign方法:


该方法以私钥和一个上次交易的映射为输入。如前所述,为了对交易签名。我们需要让问交易输入中引用的输出,所以我们需要保存这些输出的交易。

我们来一步一步看:

coinbase交易由于没有真正的输入不需要签名。

对交易的裁剪拷贝而不是交易签名。

这个拷贝包括了所有输入和输出,但是TXInput.Signature和TXInput.PubKey为nil。

接下来遍历拷贝中的每个输入

在每个输入中,Signature设置为nil,pubkey设置为引用输出中的pubkeyhash。这时,除了当前交易外所有的交易都为空。它们的Signature和PubKey都设置为空。这些输入的签名是独立的,尽管对我们的应用这是不必要的,但是比特币允许输入引用不同的地址。


hash方法对交易序列化并使用sha-256算法对其进行hash。我们部队hash结果进行签名,在hash后我们重置pubkey,所以其不影响遍历。

代码的核心部分如下:

我们使用privKey对txCopy.ID签名,一个ecdas签名是一对numbers,我们把它连起来存储到输入的Signature域。

验证函数:

方法很直观,首先 我们需要相同的交易拷贝:

然后我们需要产生key对的曲线:

然后我们对每个输入的签名进行检查:

由于我们需要与被签名数据相同的数据,这段代码和sign方法相同。

我们将value解包并存储于TXInput.Signature和TXInput.PubKey,由于签名是一个数对并且公钥是一组租表。我们用crypto/ecdsa函数解包。

我们创建使用从输入中提取的公钥创建ecdsa.PublicKey并使用从输入中提取的签名作为参数执行ecdsa.Verif。如果所有的输入都验证正确,则返回true,否则false。

现在,我们需要一个函数获取先前的交易。由于这需要和区块链交互,我们在Blockchain中加入一个方法。

这些函数很简单,FindTransactionfinds 用交易id找到交易(在区块链上遍历);SignTransactiontakes 输入交易,查找其引用并签名。VerifyTransactiondoes 对其进行验证。

现在我们需要真正的对交易签名和验证,签名发生在NewUTXOTransaction

验证在交易被加入区块前:

我们看看代码运行情况:

没问题,好棒

bc.SignTransaction(&tx, wallet.PrivateKey)NewUTXOTransaction

让我们注释掉NewUTXOTransaction中的bc.SignTransaction来保证交易不被挖矿:


结论:

目前我们实现了比特币的很多关键特性。除了网络部分基本搞定,下节我门补齐交易部分。