打开sCrypt的盒子(6)数据序列化

数据序列化,就是将一个数据结构或者对象,按照某种规则组合成一个输出,可以是字节数组,也可以是一个格式文件或者字符串。序列化的目的是为了对象的网络传输,数据存储。

序列化后数据还可以反序列化形成对象。
在这里插入图片描述

举个例子,表示状态的对象

let state = {'counter': 11, 'bytes': '1234', 'flag': true}

如果序列化成JSONhttps://www.json.org/格式则为:

{
"counter":11,
"bytes":"1234",
"flag":true
}

如果序列化成msgpackhttps://msgpack.org/

83 A7 63 6F 75 6E 74 65 72 0B A5 62 79 74 65 73 A4 31 32 33 34 A4 66 6C 61 67 C3

如果序列化为YAMLhttps://yaml.org/

---
counter: 11
bytes: '1234'
flag: true

无论是JSON还是msgpack,yaml都有程序库可以反序列化数据为对象。

在比特币脚本中如何做这种序列化呢?从比特币脚本操作符https://wiki.bsv.info/op-codes知道下面几个命令

符号值 (十六进制)输入输出描述
1-750x01-0x4b(special)data把接下来的N 个字节压入堆栈中,N 的取值在1 到75 之间
OP_PUSHDATA10x4c(special)data下一个字节包括数字N,会将接下来的N 个字节压入堆栈
OP_PUSHDATA20x4d(special)data下面两个字节包括数字N,会将接下来的N 个字节压入堆栈
OP_PUSHDATA40x4e(special)data下面四个字节包括数字N,会将接下来的N 个字节压入堆栈

sCrypt最新版https://github.com/scrypt-sv/scryptlib中使用上面的命令做了一个序列化的初步实现。

先看一下下面的代码片段

OP_RETURN 0b 1234 01 0700

这就是上面state对象的序列化结果。按照顺序

'counter': 11对应对应0b

'bytes': '1234'对应1234

'flag': true对应01

注意这里都是hex,而不是整数。

上面代码片断在脚本HEX的表现是

6a010b0212340101020700

我们分割一下,解释

6a 010b 021234 0101 020700

6aOP_RETURN
010b01是表示后面有一个字节的数据,数据是0b

02123402是表示后面有两个字节的数据,数据是1234

010101是表示后面有一个字节的数据,数据是01

02070002是表示后面有两个字节的数据,数据是0700

0700是little endian的数据7,表示前面有多少个字节的数据,010b 021234 0101合计刚好是7个字节。

可以看出来这个序列化的方式是

  • 整体格式OP_RETURN + 数据 + 数据长度
  • 每个数据片断是脚本操作符1-75或者OP_PUSHDATAx + 数据值
  • 数据片断不包含名称’counter’,‘flag’,只有数据值

序列化的原理就这么多,关键是要理解脚本是如何压数据入栈的。

合约代码和测试代码见GitHub:

https://github.com/scrypt-sv/boilerplate/blob/master/contracts/stateSerializer.scrypt

注意:在这篇文章写作期间,“scryptlib”:"^0.2.10"还不支持超过75个字节的字节数组入栈。
另外:Varint和OP_PUSHDATA是不同的,不可混淆。

因为这种序列化之后不包括key,所以顺序就变得非常重要。否则反序列化的时候就不正确了。
这篇文章写的很好: http://jartto.wang/2016/10/25/does-js-guarantee-object-property-order/
下面的对象,序列化顺序的规律你能掌握吗?

> data = {'1':'aaa','2':'bbb','3':'ccc','测试':'000'}
{ '1': 'aaa', '2': 'bbb', '3': 'ccc', '测试': '000' }
> data = {'测试':'000','1':'aaa','2':'bbb','3':'ccc'}
{ '1': 'aaa', '2': 'bbb', '3': 'ccc', '测试': '000' }
> data = {'a':'000','1':'aaa','2':'bbb','3':'ccc'}
{ '1': 'aaa', '2': 'bbb', '3': 'ccc', a: '000' }

感兴趣的人看一看更深入的讨论 https://stackoverflow.com/questions/5525795/does-javascript-guarantee-object-property-order

直接说结论,序列化的对象要保证没有数字作为key,比如不要有 '1':'aaa'
这样序列化key的顺序就是定义顺序,先写出来的在前面,后写出来的在后面。


(后记)

既然知道初步实现的序列化有一些问题,那么就贡献自己的代码好了。
去Fork一个sCryptLib的代码,先写BDD驱动的测试代码,然后再做具体实现。

我们希望下面一些用法,把想法都写在BDD测试代码里

  • 不只是对象,也支持数组的序列化
  • 支持反序列化,反序列化的时候可以识别出类型
  • 支持BigInt
  • 支持比特币脚本数字 OP_0 OP_1 OP_1NEGATE OP_2 … … OP_16
  • Bool类型使用OP_TRUE和OP_FALSE
  • 支持OP_Pushdata1,2,4,也就是支持大内容

写好的测试代码参考
https://github.com/scrypt-sv/scryptlib/blob/master/test/serializer.test.ts

然后编写代码,让代码通过测试。3天期间做了多次修改,修改了两个库,完美实现,提交给官方,获得review,再次修改,最终代码合并。新的序列化使用方法请参考测试代码。

同时对合约代码也做了修改
https://github.com/scrypt-sv/boilerplate/blob/master/contracts/serializer.scrypt

谢谢大家为更好的比特币做出的贡献
享受比特币带来的安全自由, 关注使用NoteSV

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值