每个区块链都必须要有唯一标示每笔交易 (TX) 的方法,否则它将很容易受到交易重放攻击。简单来说,重放攻击指的是攻击者通过使用账本中已有的交易来欺骗系统。

原文标题:《关于唯链雷神区块链,你可能还不知道那些事儿(第一部分)- 交易唯一性》
作者:Peter Zhou,唯链首席科学家,英国南安普顿大学计算机博士,于 2017 年加入唯链,主要负责唯链雷神区块链的科研研发及知识产权保护等工作

自 2018 年 6 月开始,唯链雷神区块链已经正式上线一年了。作为其 白皮书 的作者之一,我仍然觉得有一些为了区块链技术的大规模应用而设计的重要特性,还没有被大家所了解。本篇是「关于唯链雷神区块链,你可能还不知道的那些事儿」系列的第一篇。在这个系列里我将详细阐述这些非常有用,但尚未普及的唯链雷神区块链的特性。另外,如果你还没有了解过唯链雷神区块链的多方支付协议(MPP)和多任务交易(MTT)特性,我强烈建议你可以先读一下相关的文章。

区块链如何对抗重放攻击?硬核详解唯链 TXID 方案Peter Zhou,唯链首席科学家,英国南安普顿大学计算机博士,先后以研究员和资深研究员的身份任职于英国肯特大学和芬兰奥卢大学,参与了欧盟和芬兰科学院重要科研项目,拥有十年计算机科研研发以及在国际一流学术杂志和会议上发表科研成果之经验,于 2017 年加入唯链,主要负责唯链雷神区块链的科研研发及知识产权保护等工作

交易唯一性

每个区块链都必须要有唯一标示每笔交易 (TX) 的方法,否则它将很容易受到交易重放攻击。简单来说,重放攻击指的是攻击者通过使用账本中已有的交易来欺骗系统。

对于一个类似比特币的基于 UTXO 模型的区块链, 交易之间是相互关联的,我们可通过历史花费记录来验证交易。然而,对于像太坊这种基于账户体系的区块链来说,这种验证唯一性地方法不再适用。对于这类系统,我们需要在交易中加入一些额外的信息来达到交易的唯一性。

以太坊的解决方案

在以太坊上交易的唯一性是这样实现的,以太坊在每笔交易中加入变量 AccountNonce,并且规定只有在 AccounceNonce 和发起交易的账户里的 Nonce 变量的最新值一致的情况下,交易才会被处理。请注意,Nonce 的数值记录了该账户目前已经发送的交易数量。这样一来,一笔交易就能通过这个 Nonce 的值和发起方的账户地址唯一来标示。

不过这种设计存在不足之处,那就是从同一个账户发送的交易将被迫组成一个序列。在这个序列里,任何一笔交易的失败,都将会导致后续交易不能被以太坊系统接受。这是因为后续的交易里包含的 Nonce 的数值都比交易发起账户当前的 Nonce 数值要大,所以必然会被系统排除在外。大家可以想象一下,当你有多笔交易正在等待系统确认,而你又想发新的交易的时候,你将面临这样的风险:一旦之前的任一交易失败,新交易将会被无限延迟。

唯链雷神区块链解决方案

唯链雷神区块链是一个基于账户的区块链系统,它通过以下方法来实现交易的唯一性。首先,它重新定义了交易里的 Nonce 变量,使它成为一个完全由交易发起者决定的 64 位无符号整数。对于一笔交易,我们将计算两个哈希值,一个是不带签名的交易数据 RLP 编码的哈希值,然后我们把计算出来的第一个哈希值串联上交易发起方的账户地址,并对其做哈希运算,得到第二个哈希值。第二个哈希值的长度为 256 位,在唯链雷神区块链上用来作为这笔交易的唯一标示(TXID)。值得注意的是,TXID 的计算是不需要私钥来为交易签名的。

为什么这个设计很重要?

首先,一笔交易的唯一性完全取决于它的内容。它是否会被节点处理完全取决于计算出来的 TXID 是否已经在区块链上存在,而不是取决于发起方账户的状态(例如它的 Nonce 值)以及由这个账户发起的其他待确认的交易。显然这样的改变对于 dApps 开发者是很大的利好,因为当他们编写涉及与区块链交互以及处理交易失败的代码时,他们所要考虑的问题被大大简化了。

这一设计对企业用户来说具有同样重要的意义。假设你是一个工厂的负责人,你想要在区块链上注册产品,从而实现产品的追溯以及物流信息的可被验证性。如果是基于以太坊的交易唯一性方案,当你的产品被源源不断地生产出来,并通过发送交易到区块链来试图注册它们的时候,任何一个交易的失败将导致后续所有交易被系统拒绝,产品注册失败。这对于你的工厂来说是灾难性的,是不可接受的。如果是基于唯链雷神区块链的方案,整个流程将会变的流畅而且方便管理。因为你所需要做的仅仅是在发送一笔交易之前,确保它 TXID 没有被使用过。其余的就是单独处理那些失败的交易即可(比如重新发送它们),这是因为失败的交易不会影响到其他的交易。

一个简单的演示

为了大家能够更好的理解,我编写了一小段代码来展示 TXID 是如何计算的。在展示代码之前,我首先到类似 veforge 这样的唯链雷神区块链浏览器上随机找一笔交易。我的代码会在本地重构这笔交易并计算它的 TXID。最后,我会把计算出来的 TXID 值和浏览器上的值进行对比来验证我得出的计算结果。

以下是我随机选取的一笔交易的 TXID:
0x38fac25309ab5395f1725284af46af585988983945e67b77cf9916b1ddf13d4b

这里有个问题:由于 veforge 会四舍五入交易转账值,我会需要用其他工具来找到精确的数值,来重构这笔交易。为此我打开了 Sync(一个专为唯链雷神区块链打造的功能强大的 dApp 运行环境),点击了 「Toggle Developer Tools」 以及 「Console」(如下图所示)。

区块链如何对抗重放攻击?硬核详解唯链 TXID 方案区块链如何对抗重放攻击?硬核详解唯链 TXID 方案

然后在 console 里输入以下命令:

connex.thor.transaction('0x38fac25309ab5395f1725284af46af585988983945e67b77cf9916b1ddf13d4b').get().then(tx => console.log(tx))

下图展示了我随后得到的结果:

区块链如何对抗重放攻击?硬核详解唯链 TXID 方案

为了运行我的代码,你需要安装 thor-devkit.js,这是一个帮助开发者开发 dApp 的 Typescript 开源库。你可以通过这个 链接,找到我的代码。

好了,现在我来给大家讲一下代码。这里我们第一步做的是构建一个交易子句。对于那些不熟悉唯链雷声区块链交易模型的同学,我简单说一下。每个唯链雷神区块链的交易都可以包含多个子句(Clause),每个子句包含变量 tovaluedata,可以做通常我们定义的交易的同样的事情。有了多子句结构,我们可以让一笔交易做跟多的事情,只要是这些事情都是由同一个地址发起的。这样会大大提高效率,并且保证这些子句操作的原子性。

let clauses =  [{
    to: '0x564B08C9e249B563903E06D461824b5d6b7F2968',
    value: "0x2a7ee2750fca8ea00000",
    data: '0x'
}]

在构造完子句之后,我们需要给交易的其他变量赋值。这里我们用之前在 console 中得到的系统返回值来赋值:

let body: Transaction.Body = {
    chainTag: 74,
    blockRef: '0x002e3040a9ade438',
    expiration: 720,
    clauses: clauses,
    gasPriceCoef: 0,
    gas: 21000,
    dependsOn: null,
    nonce: '0x73541be64e72817c'
}

之后,我们做的是构建交易:

let tx = new Transaction(body)

并且计算交易 TXID。这个计算过程分两步:1)首先计算不带签名的交易数据 RLP 编码的哈希值:

let signingHash = cry.blake2b256(tx.encode())

2)其次把步骤一中计算出的哈希值和发送账户地址串联起来,然后再对其计算哈希值,得到最终的交易 TXID:

const origin = '0xa4d2050f24ed7EfF313B7E912D6e5BF96ce57B95'
let id = '0x' + cry.blake2b256(signingHash, Buffer.from(origin.slice(2), 'hex')).toString('hex')

好了,现在我们已经计算出了该笔交易的 TXID,可以把它打印出来,与之前在 veforge 浏览器上找到的 TXID 进行比较了。

最后,我要感谢 @rogake 帮助我一起翻译了 我的英文原文

来源链接:bbs.vechainworld.io