datoms v1.3.0
datoms协议
Datoms是个人数据资产化协议堆栈中的最底层协议。
datom是最小的可交易数据资产单元
- 数据资产的最小单元是datom。
- 每一个datom都采用append-only log结构,具备很好的底层数据资产治理结构。
- 每一个datom都可以设置对应的权限、隐私保护以及其他安全使用要求。
- 每一个datom都是可交易的。
- 每一个datom都可以单独确权。
- 每一个datom都可以单独计量。
- 每一个datom都可以单独进行权限设置、访问控制。
1. datom的结构
一个datom是具有如下标准结构的文件集合。通常包括六个文件(datoms v1.3.0版本):
1.1 bitfield
采用bitfield对datom的一些状态进行二进制编码(es6标准,前缀为0b)。
state: { var1: fales, var2: true, var3: false, } //上述状态用es6的bitfield表示: const state = 0b010
1.2 data
采用wc3 VCs数据模型 的数据资产。一般包括原始数据,metadata以及(数据控制者)数字签名,可验证。VCs数据模型的结构示意图示例如下:
示例: Alice在JD的消费数据。
1. Alice的请求JD,复制其在JD在【2018/1/1-2019/1/1】的个人全部消费数据;
2. JD根据Alice的请求,按照peopledata的规范或行业规范,提供Alice上述数据。
3. 为保证复制到Alice后的数据可信,JD需要生成的数据格式示例如下:
{
"@context": [
"https://www.peopledata.org.cn/2022/data/cusmerdata/v1" //peopledata拟定的电子商务消费数据规范
],
"id": "metachain DID", //由metachain分配给Alice的DID.
//metadata
"type": ["cusumer e-commerce data"], //数据类型
"issuer": "https://www.jd.com/xxxxxxxx", //数据发布者:JD给Alice在JD的消费数据发布
"issuanceDate": "2022-01-01T19:23:24Z", //数据发布日期
"rawdata": { //原始数据
"id": "did:jd:ebfeb1f712ebc6f1c276e12ec21", // 原始数据的DID。
"cusumerdata": {
"startDate":2018/1/1 ,
"endDate"": 2019/1/1 ,
records:{
//JD 自定义的个人消费数据规范格式的数据。
“时间”:xxxxx;
.....
}
}
"proof": { //JD提供的签名proof,证明1)Alice的消费数据以及metadata是JD提供的;2)验证的方法;
"type": "RsaSignature2018",
"created": "2022-01-18T21:19:10Z",
"proofPurpose": "assertionMethod",
"verificationMethod": "https://www.jd.com/issuers/#key-1",
"jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..TCYt5X
sITJX1CxPCT8yAV-TVkIEq_PbChOMqsLfRoPsnsgw5WEuts01mq-pQy7UJiN5mgRxD-WUc
X16dUEMGlv50aqzpqh4Qktb3rk-BuQy72IFLOqV0G_zS245-kronKb78cPN25DGlcTwLtj
PAYuNzVBAh4vGHSrQyHUdBBPM"
}
//为防止中间传输过程中出现攻击/泄漏,提供的证明。
"proof": {
"type": "RsaSignature2018",
"created": "2022-01-18T21:19:10Z",
"proofPurpose": "authentication",
"verificationMethod": "did:JD:ebfeb1f712ebc6f1c276e12ec21#keys-1",
"challenge": "1f44d55f-f161-4938-a659-f8026467f126",
"domain": "4jt78h47fh47",
"jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..kTCYt5
XsITJX1CxPCT8yAV-TVIw5WEuts01mq-pQy7UJiN5mgREEMGlv50aqzpqh4Qq_PbChOMqs
LfRoPsnsgxD-WUcX16dUOqV0G_zS245-kronKb78cPktb3rk-BuQy72IFLN25DYuNzVBAh
4vGHSrQyHUGlcTwLtjPAnKb78"
}
}
对于dataom来说,这个data项是其“有效载荷”。
metadata主要来自数据发布者(控制者),提供metadata主要是对发布的个人数据提供可溯源、可验证的凭证。
proof主要有几种形式:
- 数据发布者数字签名;
数据发布者用自己专用的私钥进行数字签名。并提供验证的方法(methold)。
验证者可以参考数据发布者公开的url,对此签名进行验证。
- 中间人数字签名;
流通链条上经过的任何设备、实体的数字签名。
为溯源提供可验证的凭证。
- Witness 方签名;
witeness方对任何需要见证的事件进行签名。
- 交易签名;
对任何更新、更改事项(状态改变)的签名。
谁做的修改、更新。
- ZKP零知识证明【可选】
采用ZKP-SNACK进行的non-interactive的零知识证明。
针对特定场景,需要非交互式的ZKP。
- 1.3 key
由一个masterkey生成的private-key.
拥有此key,才能访问datom。
- 1.4 secret_key 右crypto.keyPair()生成的key。
const crypto = require('datom-crypto')
//生成一对新的keyPair: {keyPair.PublicKey, keyPair.secretKey}
const keyPair = crypto.keyPair()
console.log('publicKey is ',keyPair.publicKey.toString('hex'))
console.log('secretKey is',keyPair.secretKey.toString('hex'))
- 1.5 signatures
对数据块的签名。
signature = crypto.sign(data_block, secretKey)
- 1.6 tree 该datom对应的merkle tree的hash(root_node).
对datom的数据块计算merkle tree。并把root hash存储在此。以作为后续审计和验证。
hash = crypto.data(data)
hash = crypto.parent(left,right)
{
index: treeIndex,
hash: hashOfThisNode,
size: byteSizeOfThisTree
}
hash = crypto.tree(peaks)
1.7 可选项 (后续版本可以根据数据类型增加一些自定义添加项)。
可自定义的添加项。
2. datoms
若干个datom(最小数据资产块)组合构成datoms。 datoms按照数据资产类别进行分类。可以是同一种类的数据,也可以不是同一类的。
每一个datom的结构如下:
datom:
- bitfield
- data
- key
- name
- sign
- tree
不同datom在一个space中。一个示例:
开始使用
- 安装软件包 系统配置要求: 1) node 14.0以上
npm i datoms
- 编写代码(示例)
const datoms = require('datoms')
const datom = toPromises(datoms('存储地址', {
valueEncoding: 'json' // 数据采用json。也支持binary和其他函数对象。
}))
//添加一个datom。可以添加任意多个。
await datom.append({
name: 'name',
Company: 'company',
jobTitle: 'jobTitle',
Tel: 'phoneNumbe',
City: 'city',
Address: 'streetAddress',
emal: 'email',
zipcode: 'zipCode'
})
//读取datom
datom.createReadStream()
.on('data', console.log)
.on('end', console.log.bind(console, '\n(end)'))
API
var datom = datoms(storage, [key],[options])
创建一个新的datom. storage为存储数据或metadata的目录。
var datom = datoms('./dir') //数据存储到./dir目录中
key是datom的public key。如果不设置key,则默认从存储目录中的key文件中读取。如果没有key,则系统自动生成一个密钥对。
options包括:
{
createIfMissing: true, // 如果在存储中没有key pair,则创建一个新的。
overwrite: false, // 是否覆盖任何已经存在的datom
valueEncoding: 'json' | 'utf-8' | 'binary', // data的类型
sparse: false, // 不需要下载全部datom
eagerUpdate: true, // 始终更新
secretKey: buffer, // 自己传递secret key
storeSecretKey: true, // 是否存secret key
storageCacheSize: 65536, // 存储缓存最大entry的数量
onwrite: (index, data, peer, cb) // 在数据验证后写入(选项)
//
stats: true // 收集网络统计信息
// 自定义加密签名(可选)
crypto: {
sign (data, secretKey, cb(err, signature)),
verify (signature, data, key, cb(err, valid))
}
noiseKeyPair: { publicKey, secretKey } // 设置noise通信协议需要的key pair
}
注意: key和secretkey是node.js的buffer instances,而非浏览器的arraybuffer instance。在浏览器中使用,需要用Feross's buffer模式。
const storage = someRandomAccessStorage
const myPublicKey = someUint8Array
const Buffer = require('buffer').Buffer
const PublicKeyBuffer = Buffer.from(myPublicKey.buffer)
const datom = datoms(storage, PublicKeyBuffer)
datom.append(data,[callback])
添加一个数据块到datom。 callback回调(err,seq)。seq是数据添加后返回的序列号。
const id = datom.get(index, [options], callback)
从datom调取一个数据块。 options包括:
{
wait: true, // 等待索引下载完毕
onwait: () => {}, // 如果等待下载,则hook
timeout: 0, //
valueEncoding: 'json' | 'utf-8' | 'binary' //
}
datom.getBatch(start, end, [options], callback)
读取一个区间范围内的数据块。 options包括:
{
wait: sameAsAbove,
timeout: sameAsAbove,
valueEncoding: sameAsAbove
}
datom.cancel(getId)
取消一个正在pending的get操作。
datom.head([options], callback)
读取一个数据块的最新更新的。
const id = datom.download([range], [callback])
下载一个区间的数据。 range包含以下选项:
{
start: startIndex,
end: nonInclusiveEndIndex,
linear: false // download range linearly and not randomly
}
如果不指定区间,则下载全部数据。
也可以指定特点的区块。
{
blocks: [0, 1, 4, 10] // will download those 4 blocks as fast as possible
}
datom.undownload(downloadId)
取消一个正在penging的下载请求。
datom.signature([index], callback)
{
index: lastSignedBlock,
signature: Buffer
}
datom.verify(index, signature, callback)
验证一个index数据块的签名。
datom.rootHashes(index, callback)
取回一个index数据块的roothash。 回调返回(err,roots)。roots是merkle tree中的一组node。
Node {
index: merkle tree的根节点的index.
size: 这个root下的子node的总字节数
hash: root下的子node的hash(32-byte buffer)
}
var number = datom.downloaded([start], [end])
var bool = datom.has(index)
查询是否存在index的数据块。
var bool = datom.has(start, end)
var stream = datom.createReadStream([options])
创建一个可以读取数据的streamjs。
{
start: 0, // 开始的index
end: datom.length, // 得到结尾为止
snapshot: true, // 每次是否都读到结尾为止
tail: false, // 设定 `start` 到 `datom.length`
live: false, // 设定保持读状态
timeout: 0, // 每次事件的timeout (0 means no timeout)
wait: true, // 当数据下载是等待
batch: 1 // 每批次读取的message的数量
}
var stream = datom.replicate(isInitiator, [options])
创建一个replicate的stream。
// 假设有两个datoms, localdatom + remotedatom, 使用相同的key。
var net = require('net')
var server = net.createServer(function (socket) {
socket.pipe(remotedatom.replicate(false)).pipe(socket)
})
// 在客户端
var socket = net.connect(...)
socket.pipe(localdatom.replicate(true)).pipe(socket)
Options include:
{
live: false, // 当所有数据下载完后,是否保持replicate
ack: false, // 当设定为true时:一个peer写操作时,会被告知。
download: true, // 从peer下载数据吗?
upload: true, // 从peer上传数据吗?
encrypted: true, // 用key pair加密数据
noise: true, // 是否采用noise加密通信p2p协议
keyPair: { publicKey, secretKey }, // 用这个key pair为noise加密
onauthenticate (remotePublicKey, done) //当远程主机认证时,hook这个key
}
datom.close([callback])
关闭一个datom。
datom.destroyStorage([callback])
删除全部datom并关闭。
datom.audit([callback])
审计datom中的所有数据。与merkle tree中的hash比较,如果不一致,则clear bitfield。
{
valid: 10, // 有多少数据满足hash。
invalid: 0, // 有多少数据不满足hash。
}
datom.writable
datom是否可写?
datom.readable
datom是否可读?
datom.key
datom的publiic key
datom.discoveryKey
datom的discoverykey
datom.length
datom的长度
datom.byteLength
datom的子节长度
datom.stats
datom的统计信息。
{
totals: {
uploadedBytes: 100,
uploadedBlocks: 1,
downloadedBytes: 0,
downloadedBlocks: 0
},
peers: [
{
uploadedBytes: 100,
uploadedBlocks: 1,
downloadedBytes: 0,
downloadedBlocks: 0
},
...
]
}
datom.on('peer-add', peer)
当有新的peer加入时emmitted。
datom.on('peer-remove', peer)
当有peer退出时emmitted。
datom.on('peer-open', peer)
当有一个peer channel打开时emmitted。
datom.peers
所有peers的列表。
ext = datom.registerExtension(name, handlers)
注册一个replication extension。
{
encoding: 'json' | 'binary' | 'utf-8' | anyAbstractEncoding,
onmessage (message, peer) { //called 当收到peer发出的信息。
},
onerror (err) {
}
}
ext.send(message, peer)
发出一个extension message给一个特点的peer。
ext.broadcast(message)
广播一个extension message个所有链接的peer.
peer.publicKey
peer的key.
datom.on('ready')
当datom就绪emitted。
datom.on('error', err)
当遇到错误emitted.
datom.on('download', index, data)
下载index的数据emitted。
datom.on('upload', index, data)
当数据上传时emitted.
datom.on('append')
当有新的数据appned时emitted。
datom.on('sync')
当数据从0到datom.length全部下载完后emitted。
datom.on('close')
当datom全部关闭时emitted。
附录:
License
@jerry.zhangjerry.zhang.bill@gmail.com
MIT