观点

应该如何处理上链数据?

Ajian   |     |   701 次阅读

TokenScript 项目中,我们只会将靠得住(几十年来在密码工程学领域广泛应用且成果累累)的技术手段应用到公链上。

但糟糕的是,人们对区块链数据空间的使用往往并不切合区块链的设计目标。人们总在抱怨以太坊 “大塞车” ,同时又编写一些收发聊天信息的智能合约。不过在区块链上传播 “crypto 街头涂鸦” 还不是最大的问题。

我们认为,最大的问题在于没有站在互操作性、可拓展性以及持存性的角度设计数据结构。

换句话说,在各个独立的 Token 项目中,数据结构都随用例量身定制,难以和非区块链系统(Web/IoT)联动,在别的去中心化项目中使用时也容易出错,并且但凡需要改动,就得从头设计整个数据结构。

数据对象举例

我们先来从这个例子出发,然后再进行归纳推理。

假设某个活动的门票是数据对象,并以 JSON 编码:

 {
 “numero”: 24,
 “class”: 2,
 “start”: 2020010120
 }

1.这是主办方发行的第 24 张门票。

2.这张门票的级别为 “class 2”(类似于 VIP 席位的门票)

3.这个活动在 2020 年 1 月 1 号 20:00 开始。

上述数据对象可以在区块链交易中使用。假设我们有一个以太坊智能合约来转移门票的归属权:

function transfer_ticket(string ticket, address newOwner)

然而将数据编码成 JSON 格式会消耗大量的 gas ,因为这会增大交易体积并增加智能合约解析的负担。由于需要紧凑地打包数据对象,我们必须首先把数据从它的模式中分离。

数据(data)和模式(schema)的分离

我们把数据编码成了 20 个字节:0x3012020118020102180A32303230303130313230。

可以看到这 20 个字节包含了 3 条消息:

0x30 12 02 01 18 0A 01 02 18 0A 32 30 32 30 30 31 30 31 32 30
              --       --       +----------------------------
              ^        ^                     ^
              24       2         2  0  2  0  1  0  1  0  2  0

其中票号 24 被编码成了 0x18;票务级别 "VIP" 被编码成了 0x02;日期信息被编码成了 ASCII 字符串。这三条信息之间的结构数据是使用标准 DER 编码规则后产生的结果 †。

对数据的模式,我们用 ASN.X 语言描述(一种 schema 语言)。

<sequence>
 <element name=”numero” type=”asn:Integer”/>
 <element name=”class”>
   <type><enumerated>
     <enumeration name=”normal” number=”0"/>
     <enumeration name=”gift” number=”1"/>
     <enumeration name=”VIP” number=”2"/>
   </enumerated></type>
 </element>
 <element name=”start” type=”asn:UTCTime”/>
</sequence>

数据模式需要存储在智能合约之外。在我们的 TokenScript 项目中,数据模式直接由 TokenScript 存储,因此,兼容 TokenScript 的钱包和 dapp 浏览器能直接使用对应的数据模式来解析 20 字节的数据。你也可以用别的格式把数据模式存在应用程序中 ‡ ,但你不应该直接把它存到智能合约里,至少,你可以把数据模式编译成一块简洁的 solidity 字节码以方便智能合约解析那 20 字节的数据。

模式的使用

一旦我们能把数据和模式分离,以太坊上的智能合约函数就能变成这样:

function transferTicketing(bytes ticket, address newOwner)

可以看到现在门票对象从字符串类型变成了字节类型。让我们通过函数声明前后的几行代码来进一步分析:

struct Ticket {
  uint nomero; // the sequence number of the ticket
  uint class; // 0: normal, 1: gifted, 2: VIP
  string start; // start time of the event
}

function transferTicketing(bytes ticket, address newOwner)
{
  Ticket ticketObj = parse_ticket(ticket);
  …
}

parse_ticket 函数包含了由模式语言编译得来的代码。这要比 JSON 解析器的效率高出不少。

另一方面, dapp 是否需要构建 DER 编码后的字节数据供交易调用,用户端钱包是否需要可读化呈现一个已经构建好了的交易,都应取决于数据模式。

为什么要这么做?

所以 DER 或者 ASN 模糊化处理和以下直接了当、对新手友好的方法比,有什么好处呢?

function transferTicket(
          uint numero, uint class, string start, address newOwner)

或者跟这个更结构化一点的版本相比又如何?

function transferTicket(Ticket ticket, address newOnwer)

难道模糊化仅仅是为了减少交易大小吗?

上述问题的答案都是 “是的”,并且这个“对” 的程度超出你的想象。首先,经过 DER 或 ASN 处理后能把交易负荷减少 50% 以上,其次还有很多别的优势:

理由 1:用于制作签名证明

一串经 DER 编码的字节数据非常便于签名。不过,我们为什么需要签名呢?

签名是为了得到认证(attestation)。我们之所以把被签名过的数据对象称作 “认证”,因为它确实代表着签名者对某些事情的认可。

我们依然以上面的门票为例。一开始,你可能会认为门票合约控制了所有票务和归属权信息。比方说,当 “Alice” 想要把一张票转给 “Bob” 时, Alice 会发起一笔交易,将门票的所属权重新指认为 Bob 。

活动主办方往往会为一场活动发行数以万计的门票,或者少说也有几千张,其中绝大部分收到门票的用户并不会把门票转给别人。要不是有这种票券转让的需要,门票根本就不需要放到区块链上。活动主办方完全可以签署一份 attestation,将门票的所属权签署给特定的以太坊账户。账户私钥的持有者则可以通过一个 质询-应答 式的合约来证明门票的所有权。

另一方面,如果数据用 JSON 格式编码,它就无法被安全地签名,因为 JSON 并不是一种确定性的编码方式。

见证的用途非常广泛。以下列举几个例子。

  • 你可以编写智能合约,使得被认证为合格投资人的用户能参与 ICO 预售。
  • 汽车保险公司能证明你的车(用一个以太坊 token 实现)已经上了保险。
  • 如果你的车是个智能载具,你完全可以在不进行任何以太坊交易的前提下,利用认证消息把车借给你的朋友。

签名认证的设计模式只应根据现有的标准进行改造,而不是重新发明一种崭新的模式(这样会重新引起很多已被解决了的安全隐患)。TokenScript 已经在研究这样的改造方案,但革命尚未成功,同志仍需努力。比方说,我们目前需要用默克尔树,甚至零知识证明来实现一种可以做部分见证(partial attestation)的格式。

理由 2:数据互操作性

经过 DER 编码过后的数据有更好的互操作性。依然是以票务证明为例:哪些系统需要用到认证信息?我们目前知道以下几个。

1.智能合约:如果 Alice 想要出售她的门票(以认证消息的形式),智能合约需要具备查验活动主办方签名的能力。

2.钱包:用户钱包必须能正确显示认证消息的内容。每当有交易涉及到认证消息时,钱包都需要提醒用户交易的实际内容。(Dapp 浏览器也有这种需求)

不止上述两个端口。活动主办方的网站也需要具备读取见证消息的能力,因为门票持有者可能会利用见证消息来登录网站,获取活动的最新消息。检票员(或者更现代一些,自动门)也需要能读取这个数据(呈现方式可能是个二维码)。

使馆和边境警察也会用到这些证明。去年一整年,刻画成了认证消息形式的 FIFA 门票(称作 Fan ID)取代了 VISA ,被大规模应用到了俄罗斯边境过境的场景中。

不难看出很多系统都需要用到见证消息,而这些系统往往都是异构的。比方说,智能合约属于区块链,而钱包属于移动应用程序。在活动主办方的网站中,JSON 的表现方式是标准。而出入口的自动门作为一个物联网设备,通常认二维码。由于系统由可验证签名支撑,它不能随意转换——在这种场景下,签名过的数据必须有一种统一的呈现形式。

如果数据模式发生改变(比方说我们加入了一种 “VVIP” 等级的新门票),我们没有理由要求所有的去中心化系统一起升级。然而如果数据基于模式驱动(schema-driven),可以很轻易地对整个模式发起更新。

你可能会觉得自己的 token 不会被用到那么多其它系统上。但事实上谁也说不准。像以太坊这样去中心化平台的优势在于搭积木。许多系统不需要 MakerDAO 授权就使用了 DAI 。你可没法在美国运通这种传统中心化系统上看到那样的创造性活动。

理由 3:持存性

持存性(Extensibility)和互操作性紧密相关。要时刻牢记一旦数据被签名,如果没有把原有的签名无效化处理,它是没法被“转换”到一个新的系统中使用的。因此,所构建的系统必须同时理解旧的以及新的数据格式(即使那些格式慢慢都废弃了)。

假设你是一份用 crypto attestation 写就的遗嘱的继承人。当双亲过世后,你能兑现遗产了。此刻你肯定不希望那份多年来经过屡次升级的遗嘱合约要求你(仙逝了的)双亲用新的数据结构再进行一次签名吧!

X.509 数字证书是经过时间检验过了的一种数据结构。它的发明早于 SSL ,而目前依然服役状况良好。X.509 是以一个 ASN.1 模块设计的,它天生具备持存性。

今时今日区块链的数据对象也应该自豪地加上这个支持。

在此篇幅有限,我们无法涵盖整个实现过程,但一句话总结,那就是持存性依赖于设计模式。举例来讲,一个设计良好的设计模式能实现数组数据到所要求二维矩阵的转换。

那下一步呢

在 TokenScript 项目中,我们通过改造现有的标准来正确地处理数据。TokenScript 本身就在 OASIS (Open Document ISO 标准背后的标准化组织)的指导下实行了标准化。想要参与进来,你可以:

† DER 是密码工程学中编码数据的默认手段。举例来说,比特币签名以及 X.509 证书使用了这种编码。如果你想试验 DER 编码数据,你可以像下面这样使用 openssl :

$ echo -n 0x3012020118020102180A32303230303130313230 | xxd -r -p | openssl asn1parse -inform DER -i
    0:d=0  hl=2 l=  18 cons: SEQUENCE          
    2:d=1  hl=2 l=   1 prim:  INTEGER           :18
    5:d=1  hl=2 l=   1 prim:  INTEGER           :02
    8:d=1  hl=2 l=  10 prim:  GENERALIZEDTIME   :2020010120

‡ 同样的模式可以用一种被称作 ASM.1 的格式等价缩写:

 SEQUENCE {
 numero INTEGER,
 class ENUMERATED { normal(0), gifted(1), vip(2) },
 date UTCTime
 }

原文链接: https://medium.com/alphawallet/the-way-we-treat-data-on-the-blockchain-is-wrong-this-is-how-its-supposed-to-work-3368b7ceb343
作者: Weiwu Zhang
翻译&校对: 安仔 C1int & 阿剑


你可能还会喜欢:

代币经济学入门:什么是代币 ?
充分利用 CREATE2
Cosmos 区块链的工作原理,Part-1:比较 Cosmos 与比特币、以太坊

 
0 人喜欢