交易是如何产生的,以及钱包是如何同步的

交易是如何产生的,以及钱包是如何同步的

How a transaction is born, and how wallet syncing works

交易是如何产生的,以及钱包是如何同步的

Zpalmtree,于2018年11月13日发布
安德猴,于2019年1月31日翻译
原文链接:https://blog.turtlecoin.lol/archives/how-a-transaction-is-born-and-how-wallet-syncing-works/

译者注:文中有些专业名词属于门罗系币种独有特性,尚无对应的中文翻译。
本篇文章真·高度烧脑。大家如果有心要弄懂乌龟币工作原理的话,请一定配合这篇门罗币入门指南阅读。

很久很久以前,有一对创币交易和融合交易恋人,他们深爱着彼此……

咳咳,开个玩笑。拿杯饮料边喝边看吧,这是一篇很长且让人困惑的文章。

首先,让我们先看一下交易里的钱最初是从哪里来的吧。所有的币都来自网络的挖矿行为,通过coinbase交易(注:coinbase transaction,又叫创币交易)产生新的币。

正如之前的文章提到过的那样,coinbase交易是一种特殊的交易,是对挖出区块矿工的奖励。这笔钱是“神奇地”出现的,并且没有发起者。

每个区块都有其对应的区块奖励

这可能会让你认为矿工可以像印钞一般获取他们想要的任何数量的币。然而,coinbase交易的金额不能大于“当前区块奖励”,否则包含这笔交易的区块就会被其他矿池和程序拒绝。“当前区块奖励”是由上图的供应量曲线以及全网持币总量所决定的。

假设Bob在solo挖矿,并且有一天很幸运挖出了一个块,就900,000高度的区块吧。

Bob的幸运日

Bob通过挖出这个块获得了29,013.99TRTL的区块奖励。你可以看到,这笔交易是没有输入inputs)的,因为这是一笔coinbase交易。我们真正感兴趣的是输出outputs)部分。有趣的是,Bob并没有一次性收到29,013.99TRTL。我们可以看到他收到了6笔输出,每笔的金额数都不同,加起来的总额为29,013.99TRTL。

每发送一笔交易时,这笔交易都必须被分成不同的面额。这做起来很简单——只需要分成不同的单位(万,千,百,十等)就可以了。

这么做是为了保证混淆/隐秘mixin / privacy)的特性。当发起一笔交易时,我们需要确保我们发送的金额和其他用户已经发送过的金额一致。如果我们不把我们的输出outputs)分割为标准化的大小,我们就需要之前有人发送过与29,013.99TRTL完全一致的金额,而这明显是不可能的。这些标准化的金额可以组成任何交易所需的金额大小。

好,现在Bob已经有了一笔由6项输出outputs)组成的TRTL,他要怎么花呢?

找出哪些交易输出属于我们

首先,Bob需要确认这笔交易是否属于他。由于CryptoNight系列的币种都是匿名的,没有与接收方地址对应的私钥private keys),我们就无法确认一笔交易被发送给了哪个地址。

接下来就不可避免地会谈到代码部分了。我会尽量解释得简单易懂。如果你想要Follow我们的代码的话,请访问这里

每笔交易都伴随着一份交易公钥transaction public key)。通过使用公钥public key),和private view key,并施加一些加密魔法,我们就可以进行密钥推导key derivation

嘿哥们儿,你交易掉了

接下来,我们看向交易中的输出部分,这次我们先从0.09TRTL的输出开始。

对于交易中的每项输出(outputs),我们都需要用到密钥推导key derivation),输出索引output index)(交易中输出的排列),和输出密钥output key)(长十六进制字符串,看起来像是镜像中的哈希)

再施加一些加密魔法,我们就得到了public spend key。这就是交易发送的 public spend key

如果你没搞明白的话,乌龟币地址(实际上是所有的CryptoNight币种的地址)是由public spend keypublic view key变来的。

比如,这个地址:

TRTLuyXrvesGMqGTQvHhUUjcvhmL6w82fVtavT2tyELWfPrCwzGTaxb6FVEeLCeyJK4DXiYGWWii8NFZK11bZR36XkjzDpWRwPd

对应着public spend key

506b84105bd39634e48b9009fece08847a8b9efcacb7e92f56f545cfe58ad7c5

public view key

ee3144dc1f62e72e19ac21e4133a35061995ac0d7f0d241e4672b9f9b7ddf9f8

好,我们已经拿到了交易发送到的public spend key,接下来做什么呢?我们只需要验证这个public spend key是不是和我们钱包的public spend key相同就可以了。

如果两份public spend key吻合,那这笔交易就是属于我们的!

另外——如果这笔交易不是属于我们的,我们不会得到交易发送的实际public spend key-我们只会得到一些无意义的字符。所以,你的隐私依然是完整且安全的。

我们接着对交易里的每项支出重复这一过程。因为我们不能简单地认为一项输出属于我们,所有的输出就都是我们的。记住,你可以一次性给很多人发送一笔交易。

一旦我们把属于我们的输出都加起来,我们就能弄清楚这笔交易中有多少是发送给我们的了。这次Bob收到的总额是29,013.99TRTL,干得好Bob!

存储那些能让你发送交易的神奇玩意

Bob已经同步synced)了他的钱包,他想要从Alice那里购买一件乌龟币T恤。他要如何发送这笔钱呢?

T恤不错

如果我们发现了一项属于我们的交易输出outputs),我们就可以通过它创建一项输入inputs),用到我们的private spend key。之后我们就可以花费这些输入inputs),生成新的输出outputs),和其他用户继续这一循环。有点绕哈。

我们需要存储一些包含新输入的信息。我们需要:

交易公钥transaction public key)——包含在交易内

输出索引output index)——之前提到的输出索引

总量amount)——可以从输出数据中获取

密钥key)——我们之前用到的输出密钥output key

我们还需要全网输出索引global output index),这是输出数据的一部分,用于追踪所有的输出密钥output key)。每个不同的金额有不同的全网输出索引存储。因此,在交易中使用的第一个“1”数量的全网输出索引为0,第二个“1”数量的全网输出索引为1,依此类推。如果有人发送了数量为“2”的金额,则会得到索引0,因为不同金额的索引互不相关。

这可能有些难以理解,不过即使不懂也别担心——这其实并不重要。

最后,我们需要生成输入的密钥镜像key image)。我们需要用到之前的密钥推导key derivation),和我们的private spend key,再施加一些加密货币的魔法,Bingo!我们就得到了密钥镜像key image)。

因为我们需要生成一个密钥镜像来使用我们的币,并且我们需要private spend key来完成这一步骤,这允许我们生成只读view only)钱包,这种钱包只能用来接收交易,而不能发送交易,因为它并没有private spend key

如果上面的解释让你感到很困惑,下面的图解可能会帮助你理解。当然也有可能让你更加云里雾里。

箭头,全都是箭头

花费我们的交易输入

Bob的交易输入transaction inputs)已经全部存进了他的钱包。那么他现在就可以发起一笔交易咯?让我来解释一下他需要进行的步骤。

选择输入

相关代码在这里

首先,我们需要由足够的输入来让Bob发起他所需金额的交易。Alice一件T恤卖25,000TRTL,所以我们至少需要这么多。Bob还需要额外支付0.1TRTL的网络手续费。

输入inputs)是从用户的钱包里随机选择的,以确保支出的模式难以被侦测到。我们不断随机添加输入,直到总输入满足转账所需金额。

假设说随机被选中的输入为3TRTL,0.09TRTL,20,000TRTL和9,000TRTL。这些加起来总共为29003.09TRTL,所以我们的输入加起来已经足够发起交易了。(译者注:此处看不懂的读者可以返回到开始时的第二张区块浏览器截图,图中有6项输出,被转化为输入进入了Bob的钱包。输入就是从这些里面随机选择。)

由于Bob只想发送给Alice 25,000TRTL,但他钱包里并没有恰好为这个数字的输入,因此他还需要给自己发送“找零”。他想要发送25,000TRTL+0.1TRTL(网络手续费),而他的输入为29003.09TRTL,因此他还需要给自己发送4002.99TRTL。(译者注:“找零”机制在另一篇文章《交易输入与融合交易》有提到)

Bob的找零

准备目的地

相关代码在这里

现在我们需要把29,003.09TRTL分割成原子般大小的部分,并表明我们要实际把每部分要发送给谁。

Alice给了Bob她的地址,我们会从这个地址中解得public spend keypublic view key。随后我们将发送给她的25,000TRTL分成20,000TRTL和5,000TRTL,这两部分最终会变成供Alice扫描的两项输出outputs),就像我们在文章开头做的那样。

接下来,我们需要定好Bob要发送给自己的金额的地址。我们用我们自己的地址解得public spend keypublic view key,再将总金额分割成更小的单位,这次的各项为4,000TRTL+2TRTL+0.9TRTL+0.09TRTL。

因此,这笔交易会有6项输出——2项属于Alice的,以及4项属于Bob的。

Outputs
= 20000 TRTL + 5000 TRTL + 4000 TRTL + 2 TRTL + 0.9 TRTL + 0.09 TRTL

准备交易输入

相关代码在这里

接下来我们需要将我们真实的输入inputs)藏进其他输入中,以隐藏哪些输入时真正用于生成交易的。我们向程序请求与我们mixin value相当的虚假输出,并且我们的每条发送都需要这么多数量。如果我们使用数值为3的mixin,则我们需要各3个虚假输出给20,000TRTL和5,000TRTL。依此类推。

接下来,我们看看每个真实的输入和虚假的输出。我们用真实的输入的交易公钥transaction public key),我们的private view key以及private spend key生成一个密钥镜像key image)。这也会给我们每项交易输入对应的临时密钥对key pair),我们稍后会在环签名的时候用到。

现在我们终于可以生成一项交易输入了,它包含了交易总额、先前生成的密钥镜像,以及包含虚假输出和我们真实输入的全网输出索引。啊,真让人头大。

这张动图不错吧

准备输出

相关代码在这里

现在我们可以生成交易的输出了。首先,我们生成随机的密钥对。通过这个,发送给同一地址的交易才不会混淆到一起。

接下来,我们看向我们之前准备好的目的地。通过接收方的public view keypublic spend key,以及通过随机的密钥对生成的私钥,我们生成了输出密钥output key)。

随后,我们生成输出,此输出包含了我们刚刚生成的输出密钥以及此项输出的总量(amount)。

我们把用随机密钥对生成的公钥保存起来,它在稍后会成为交易公钥transaction public key)。

将交易组装起来

相关代码在这里

将交易元素组合起来

到现在我们已经完成了大部分工作,我们可以把这笔交易组装起来了。我们首先通过创建tx_extra开始——这包含了一些数据,但其中最重要的是交易公钥transaction public key)。如果我们使用了Payment ID,也会包含在这里面。

接下来,我们拷贝好之前准备好的输入和输出、以及一些其他的信息(例如交易版本,解锁时间)。 最后,我们对这些数据进行哈希,得到transaction prefix hash

生成环签名

相关代码在这里

最后,我们来生成交易环签名ring signatures)。这用来验证我们拥有发送的那些币。我们对每一项交易输入都生成环签名,通过使用我们之前为每项输入用临时密钥对生成的私钥private key)、transaction prefix hash、虚假输出和真实输入拥有的公钥,以及每项交易输入的密钥镜像。这里就又要用到一些加密魔法了。

发送交易

终于,我们完成了!我们将交易发送到了程序中,如果程序验证此交易有效,Alice稍后就能收到他的乌龟币了。解释这里的时候我还漏讲了一点:检测你何时花费了龟币,而不仅是当你收到龟币时。

Bob的交易飞遍全网络

找出哪些交易输出属于我们

幸运的是,此部分会简单很多。还记得当我们找到一笔属于我们的交易时,我们了生成密钥镜像吗?因为我们发送交易的时候使用了密钥镜像,我们只需要查看每项交易输入,如果他使用我们创建的密钥镜像,那就是我们发送的。

因此,当Bob扫描到了他发送给Alice的交易时,他会看到4笔用于生成此笔交易的输入,并发现这些输入用的是他之前生成的密钥镜像。它会将这些密钥镜像标记为已花费spent),这样他就不会在另一个交易中意外使用他们了。否则有可能出现双花,这会导致失败,一位一个密钥镜像只能使用一次。

当Bob将交易输入加起来时,他会算出3TRTL,0.09TRTL,20,000TRTL+9,000TRTL——正好是他发送的总额。他随后会扫描交易的输出,然后发现4项输出属于他自己——4000 TRTL + 2 TRTL + 0.9 TRTL + 0.09 TRTL,这些找零会返还到他的账户。

这样,Bob就得出了他在这笔交易里的花费:25,000TRTL。

你可能还记得Bob花费了0.1TRTL的网络手续费,但我们并没有把这个手续费发送给任意一个地址。网络手续费只是输入总和和输出总和之间的差——挖出了包含这笔交易的区块的矿工可以把这笔手续费作为奖励添加到coinbase交易中。

总结:

总结,一笔交易是由输入inputs)、输出outputs)、以及环签名ring signatures)组成的。

交易输入transaction inputs)是我们之前收到的资金,加上一些用来生成密钥的数据。它们也隐藏在其他人的输入中。

交易输出transaction outputs)是接收方获得的金钱,这可能还包括一些返还给发送方的找零。

环签名ring signatures)证明交易有效,并且我们拥有对应的私钥。这是一个零知识证明zero knowledge proof)的例子:第三方可以证明签名是合法有效的,但是它没有给出发送方或者接收方是谁的信息,只表明了交易是合法的。

如果交易输入拥有和我们生成的相同的密钥镜像,那就说明这是我们对外发送的交易。

如果交易输出在解密后拥有与我们相同的public spend key,那么这就是发送给我们的交易。

如果你没有理解的话,我们抱歉。这相当的复杂,并且很难清楚地解释。我得承认,这一整个输入、输出、虚假输出+实际输入的东西把我也弄晕了。

感谢阅读

感谢阅读。希望你学到了一些东西。

(全文完)

译者感言:

终于把这个超级大坑填完了,前前后后耗费近一周时间,为了初步搞懂门罗系的工作原理,以给大家呈现准确的译文,光是文献就查阅了上万字。旧坑填完了,是时候挖个新坑了,嗯。(反正我是翻出来了,你们能不能读懂就不关我事咯)

发表评论

电子邮件地址不会被公开。 必填项已用*标注