文件与格式
应用与命令行读写的各类文件。带版本号的格式,版本号写在文件里。
条目
条目是一个规范 JSON 对象,恰有七个成员:
| 成员 | 内容 |
|---|---|
spec | 规格名,zikaron/1 |
entryType | 类型:genesis、history、grant、revocation、adoption、succession、annotation,或任何约定的新类型 |
author | 作者地址,小写十六进制 |
seq | 序号,从 0 起 |
prev | 前一条的编号;第一条为 null |
body | 正文,按类型而定 |
sig | 65 字节的 EIP-191 签名 |
条目编号是它全部字节的 SHA-256。规范格式:紧凑书写,成员按字节序排列,整数按十进制书写,十六进制一律小写。详见法 §3 与 §4。
命令行的账本目录
一个明文文件夹,每条条目一个文件,文件名为条目编号的 64 位十六进制加 .entry,从建起就只有本人能读写。zikaron 写的就是这种目录;读的子命令另外也收桌面数据目录里的账本(经凭据)、账本镜像与记录包(.zkit 文件或文件夹),见命令行。严格读取时,长度为零的条目文件具名报出。
机器目录
每台机器一个。在 macOS 与 Linux 上,默认位置是 ~/.zikaron-desk.d/,指针文件 ~/.zikaron-desk 可以把它指到别处:一行绝对路径,以一个换行结尾,只本人可读写。在 Windows 上,指针文件与默认机器目录都在 %LOCALAPPDATA%\ZIKARON\ 里:.zikaron-desk 与 .zikaron-desk.d\。应用每次启动时把指针写成所用的机器目录,已指向它时原样留着。机器目录只在这两处找:先看指针,再看默认位置;命令行按同样的次序找。
| 文件 | 内容 |
|---|---|
keys-anchor.json | 密钥库,格式 zikaron-desk/keybox/3,只本人可读写;错误次数也记在这里 |
identities-anchor.json | 身份登记,加密;每个身份选的网络也记在这里 |
machine.json | 机器设置,格式 zikaron-desk/machine/1,明文,开锁前要读,只有语言 lang 与外观 appearance 两项;别的成员不读,下次保存即去掉 |
machine-anchor.sealed | 加密的机器设置(种类 machine-sealed,格式 zikaron-desk/machine/1),开锁后才读:自动锁定、新建身份时默认选中的网络 network、代理 proxy(system、none 或一处代理地址,连同用户名与密码)、命令行上链 cli_anchor(queue 为仅加入队列,缺省即自动发送)、经桌面 App 操作 cli_desktop(缺省开)、经凭据操作 cli_alone(凭据的有效秒数或 never,缺省关)、用过的数据目录 homes、上次整机备份 backup 与上次备份失败的时刻 backup_failed;其余成员原样保留 |
machine-mark | 本机的机器标,32 位小写十六进制加一个换行,明文,首次用到时生成;数据目录的写者标拿它比对 |
read-networks.json | 只读网络,格式 zikaron.read-networks/1,明文,所有身份共用,一个也没有时不留文件;命令行经凭据找接收方公钥时也读它 |
checked/facts.json | 已核对事实,格式 zikaron.checked-facts/1,加密,不进任何镜像、备份或导出件 |
kits/index.json | 记录包索引,明文,见下文 |
kits/links.json | 手填的记录包取处,加密 |
kits/verified/ | 核验结果档,明文,见下文 |
records/index.json | 签入时的文件名与位置,加密;命令行经凭据 history --file 写进来的文件也记在这里 |
backup/index.json | 上次整机备份收了哪些条目与授权,格式 zikaron-desk/backup-index/1,加密;备份灯据它数出备份之后新增的条数;不进备份,恢复时另写一份 |
cli-sent/ | 命令行自持私钥运行 anchor 时签过的交易,明文:每次锚定一个文件夹,名为链号、发送账户、去处(登记合约,自转账时即发送账户本身)与调用数据合起来的 SHA-256 前 16 字节(32 位十六进制);里面按 0、1、2 编号,每个文件是 {"nonce","tx"},或标记作废的 {"void"};只记 nonce 与交易哈希,文件只增 |
cli-credential-anchor.json | 命令行的凭据,「经凭据操作」开着时才有,只本人可读写;规范 JSON,恰有四个成员:expires(到期的 Unix 秒,不过期为 null)、issued(签发的 Unix 秒)、key(一把随机的 32 字节钥)、master(随机数加以这把钥经 XChaCha20-Poly1305 封起来的主钥,附加数据 zikaron/cli-credential/1);有效期一分钟到一年或不过期,每次用都核期限;关掉开关、到期、换主钥即删,改口令照用,不进备份 |
adopted-salt、adopted-list | 收编清单,明文:盐文件是 64 位小写十六进制加一个换行;清单每本收编的账一行,内容是盐加根条目号的 SHA-256,不含账本名、路径或条目号;命令行写明文账本目录之前据它判 E_ADOPTED |
handed-pens | 交接清单,明文:每本交出或收下的账一行,以同一把盐盲化的根条目号为键;命令行与桌面写之前都先读它 |
zikaron-door-0x… | 桌面应用为打开的数据目录开的本机通道,名字取机器目录与数据目录两条规范路径的 SHA-256 前 8 字节(0x 加 16 位十六进制);macOS 与 Linux 上是机器目录里的本机套接字文件,只本人可读写;Windows 上是命名管道 \\.\pipe\zikaron-door-0x…,只本用户可连、拒收别的机器;锁定、关闭数据目录或退出时关上,「经凭据操作」开着时不开 |
set-aside/ | 主钥已失、永久封存的文件,字节原样,按 1、2、3 编号平铺;目录已有时为 set-aside-2、-3 |
在 macOS 与 Linux 上,机器目录从 HOME 算起;在 Windows 上从 USERPROFILE 算起(未设时取系统的用户配置文件夹):它就是系统的用户配置文件夹时在 %LOCALAPPDATA%\ZIKARON\,否则在它下面的 AppData\Local\ZIKARON\。应用只用自己的密钥库,系统钥匙串保持原样。
数据目录
每个身份的每种角色一个,默认放在机器目录下,角色子目录为 author(记录者)或 grantee(使用方)。数据目录里不存任何绝对路径,整个复制到别处或别台机器,原样读回。里面四个子目录:
ledger/- 账本,每条条目一个加密文件。
kits/- 导出的记录包、授权文件、凭证、快照,以及签发时留存的条款文件(
kits/terms/)。 grants-held/- 收到的授权,每份附上次复核的结论;收下的授权文件在
grants-held/files/。 settings/- 设置
desk.json(含这个数据目录的网络、自动上链与「隐藏本机删除的条目」)、待上链队列queue.json、上次账本检查last-audit.json、加密的目录标签home-label.json(这个数据目录的随机编号,以及它属于哪个身份的哪种角色)、明文的写者标writer-mark(一行,写着由哪台机器写入),以及只让一处写入的锁writer.lock(「经凭据操作」开着时,桌面与命令行每写一笔各拿一次,写完即放)。
数据目录的文件夹名和账本里的文件名,都由一把本机名钥转写,地址与条目编号由此从文件名上隐去;内容逐文件加密。要给别的工具读,导出账本镜像。
写入锁由系统内核持有,进程一结束即放开,断电与强行结束也一样;它只归应用自己的进程,从应用里启动的程序另起自己的进程。内核锁只管一台机器,所以另有写者标:打开一个数据目录时,写者标写着别台机器,或这一版读不成,这个窗口就只读,等你选「改由本机写」才改写写者标并接管。打开时还先读目录标签,标签属于别的身份或角色即拒,这个数据目录原样留着。数据目录的标签由第一个以写者打开或写进它的窗口写下,只读打开不写。
本机数据的加密
本机数据钥由主钥经 HKDF-SHA256 派生(信息串 zikaron/local/v1),文件名钥另用信息串 zikaron/names/v1。每个文件以 XChaCha20-Poly1305 单独加密,每次封装都用新的随机数。完整的规格与测试向量在源码的 crates/app/local-data-standard.md。
"zikaron-local/2\n" · 算法(1 字节) · 种类长度(1 字节) · 种类 · 版本(u16 大端) · 身份摘要(16 字节) · 随机数(24 字节) · 密文与标签- 算法
- 只认
2:身份摘要的后半由钥算出。低于2的算法号与魔数zikaron-local/1读作not-sealed,高于2的读作newer-version。 - 身份摘要
- 一个文件的身份是它的主人与位置。机器目录里的文件主人是
machine;数据目录里的文件主人是这个目录的随机编号,记在它的目录标签里。位置是种类加上文件在目录里的逻辑名(哪一格、哪个名字),与磁盘上的路径和文件名分开,所以挪动数据目录、换主钥后整批改名、恢复到别台机器之后,每个文件照旧打开。前 8 字节是sha256("zikaron-local/owner" ‖ 0 ‖ 主人)的开头;后 8 字节是HMAC-SHA256(K, "zikaron-local/place" ‖ 0 ‖ 逻辑名)的开头,K由本机数据钥经 HKDF-SHA256 以信息串zikaron-local/place-key派生。这样,文件头与条目编号、授权编号之间的对应,只有持钥的人算得出。 - 附加数据
- 整个文件头,连同随机数。头里任一字节改动,或文件被挪到别的位置、别的数据目录,都打不开。明文里再写一遍主人与逻辑名,打开后逐项核对。
读与写都只用现行这一种封装:明文文件与别的封装读作 not-sealed,点名原样留着,不转换。本版与 0.1.3 写的是同一种封装;0.1.2 及更早版本读不了。早先版本的数据怎样带过来,见兼容与演进。
读不成的文件统一报 LOCAL_SEAL,详情写明六种原因之一:not-sealed(缺魔数,或别的封装、算法号)、truncated(头或标签被截断)、other-kind(封的是别的种类)、newer-version(更新的版本或算法写的)、swapped(别的文件:摘要与所求的不符,或数据目录缺标签)、unopenable(别的钥,或有字节被改)。读不成的文件原样留着:写到它时具名拒绝,把它挪开,下次写入即重建。只写一次的文件(收到的授权、留存的授权文件、条款文件与签发记录、账本条目)只要已在,一律拒写。
写入时先封装,再经临时文件改名到位,权限 0600;改名在 Windows 上遇到别的程序一时占着文件,在一秒内重试。在 FAT 与 exFAT 卷上也能落条目。写不成的写入随即删掉自己的临时文件,写者打开数据目录时扫掉残留的,残留的临时文件不挡读账本。同一文件的较早副本放回原处,封装认不出来;账本有自己的签名链。
密钥库
主钥与各身份的密钥封在 keys-anchor.json 里:口令经 scrypt(N=262144、r=8、p=1)派生密钥,以 AES-128-CTR 加密,keccak MAC 校验。口令为 8 位 ASCII 字母或数字,连错 5 次锁定,只能走恢复。
整机备份
文件名 zikaron-backup-YYYY-MM-DD.zikaron(UTC 日期),同一天再导出编号为 -2、-3。第一行为 zikaron-backup/1,第二行是规范 JSON 文件头:app、created、format、kdf(scrypt 的 N、r、p 与 32 字节盐)、nonce。正文用 scrypt(N=262144、r=8、p=1)从备份密码派生密钥,以 XChaCha20-Poly1305 加密,两行文件头作为附加数据。
备份里有:身份登记、每个身份的密钥、每一份加密的本机文件(解开后放入)、主身份的记录、机器设置(machine.json 与加密的机器设置合成一份)与只读网络列表(成员 readNetworks)。命令行的凭据不进备份,恢复之后「经凭据操作」是关的。主钥、口令与错误次数留在本机;已核对事实、备份索引、已删除身份的数据目录,以及明文的导出件(记录包、索引、核验结果档、凭证、授权文件、镜像、密钥文件)也在备份之外。
备份只写进空着的名字:要用的名字已被占用时,顺延取下一个编号。写出后读回并打开,才算备份成功;任何一步失败,加密的机器设置都记下 backup_failed,直到下一次备份成功。恢复时逐个检查成员的路径,带路径穿越的成员一概拒收;恢复生成新的主钥;备份带着只读网络列表时,替换本机的列表;旧版的备份缺这一项,本机的列表保持原样。
密钥文件
标准以太坊 keystore(V3):scrypt(N=262144、r=8、p=1)、aes-128-ctr,文件名为 UTC--<时间>--<40 位十六进制地址>,权限 0600,以导出时设定的文件密码(至少 8 位)加密。写出后应用读回核对格式与地址,相符才算导出成功。
导入时两种派生都认:scrypt,以及 pbkdf2(hmac-sha256,轮数 1 至 224)。scrypt 参数须在界内:内存 128·r·N 至多 230 字节,p 为 1 至 16,总工作量 128·r·N·p 至多 232 字节;密钥库与整机备份读参数时用同一组界限。坏的或超界的文件在派生之前即拒。
记录包
格式由 zikaron.kit/1 §7 规定。导出缺省是一个 .zkit 文件,要发布到网址时导出为文件夹;核验时两种都收。展开后是:
kit-3fa9c2e1/
manifest.json 清单
entries/<64 位十六进制>.zk1 条目,每条一个文件
files/<路径> 原文件与附件
files/verify.md 给收件方的核验说明
proofs/<路径> 上链证明清单列出每个条目的编号,每个文件的路径、大小与 SHA-256,以及每份证明对应的交易;包里出现清单之外的文件,核验即失败。记录包编号是清单字节的 SHA-256。包内路径只用 a-z、0-9、.、_、-;有附件改过名时,files/zikaron-names.json 记下原名与包内名的对照,改名规则为 <转写名>-<原名 SHA-256 前八位>.<扩展名>。
导出的记录包名为 kit- 加所选里序号最小的那件记录的内容指纹前八位,单个文件再加 .zkit;所选只含其他条目时为 kit,同名已有即编号为 -2、-3。加密记录包缺省名为 kit.zkit,不取包里的任何内容,同名已有即顺延编号。
导出时,清单里附言 note_md 的末尾自动加一行,写明锚在哪条链:
anchored-on: eip155:<链号> · registry <小写合约地址> · from <起始区块>分隔符是「空格、·(U+00B7)、空格」,不随界面语言变,取自导出那一刻这个数据目录的网络;没配网络就不加,附言原本以这样一行结尾时先去掉再加。这一行属工具层的约定,法照旧只读链上的锚:核验时据它提示要添加的网络。
加密记录包
加密给一个接收方的记录包是单个文件,扩展名照用 .zkit,里面是逐字节原样的普通单文件记录包:
"zikaron-kit-addressed\n" · 版本(1 字节,现为 1) · 一次性公钥(33 字节,压缩)
段 0 · 段 1 · … 每段 64 KiB 明文加 16 字节标签,末段较短- 密钥
- 每个包新取一把一次性私钥
e,与接收方公钥P在 secp256k1 上做 ECDH,取共享点的 x 坐标;HKDF-SHA256 以一次性公钥与P(都压缩)相接为盐、信息串zikaron-kit-addressed/1,导出 32 字节加密钥与 19 字节随机数前缀。一次性私钥用完即抹,发送方自己留不下可解的钥。 - 分段
- 第
i段以 XChaCha20-Poly1305 加密,随机数为「前缀 · i(4 字节大端) · 是否末段(1 字节)」,整个文件头作附加数据;改一个字节、删段、补段、换段次序,都过不了标签。 - 文件头
- 只有格式标记、版本与一次性公钥,不写接收方的地址或公钥;外人只看得出它是加密记录包、大小与文件名。
打开时,每把候选钥匙与一次性公钥协商后试第一段,标签过得去的那把就是接收方;之后逐段解进系统临时目录里专用的文件夹,有一段不过即删掉整个文件夹再报拒,所以不留明文。导出时,明文包先排在系统临时目录里专用的文件夹,加密写到目标位置,明文副本随即删掉。
记录包索引
机器目录下的 kits/index.json,格式名 zikaron.kits-index/1,明文保存,供同一台机器上的其他应用只读访问,见接入。每导出一个记录包记一行,同一路径再导出时替换原行:
| 字段 | 内容 |
|---|---|
id | 记录包编号 |
created | 导出时刻,Unix 秒 |
root | 账本创建条目的编号 |
path | 包的绝对路径 |
contents | 包内原件的内容指纹 |
note_md | 附言,作者写的原文,不含 anchored-on 那一行 |
anchoredOn | 锚在哪条链:{chainId, fromBlock, registry},可选 |
link | 取处(这个包在网上的 https 地址),可选 |
oneTimeKey | 加密记录包的一次性公钥(十六进制),用来认出本机那一份;普通包没有这一项。行里不记接收方,其余各项读自加密前的明文包 |
核验结果档
每核验一个记录包、授权文件或发布地址,应用在机器目录的 kits/verified/ 里写一份结果,文件名是 0x<包清单文件的 sha256>.json,再核整份替换。格式名 zikaron.kit-verification/1,明文、只本人可读写,规范 JSON:
{"anchors":[…],"at":<秒>,"basis":{…}?,"core":<hex32>,"form":"zikaron.kit-verification/1",
"kit":<hex32>?,"kitVerdict":{"subject":"…"?,"verdict":"…"},"manifestSha256":<hex32>,
"missed":[{"chainId":…,"reading":"down"|"fingerprint","registry":"0x…"}],
"records":[{"anchor":{"blockNumber","blockTimestamp","chainId","registry","tx"}|null,"content":<hex32>}]}records- 包里每件记录一行:内容指纹,以及最早计入的那一枚锚;没上链的,或有链没读成而读成的链上找不到锚的,写
null。 anchors- 这一次扫到的锚,各带它的登记合约。
basis、missed- 这一次读成的链,与没读成的网络及其读法代码:
down不通,fingerprint指纹不符。 core- 判这一次的核的摘要。
包不成立也写,只写判定;包成立而这一次一条链也没读成时不写。应用自己只写不读,这份档也不改任何判定,见接入。
授权码与凭证
文本 zikaron-grant: 后接一段或多段,段间以 . 连接。每段是一条授权条目的规范字节,经 base64url 编码(省去填充);多段时,从最初作者的授权依次排到当前这一份。全长上限 2,953 字节,恰为一个二维码的容量(版本 40、字节模式、纠错等级 L)。格式见 zikaron.kit/1 §6。粘贴进来的授权码,先去掉换行、空格与看不见的字符(零宽字符、软连字符、字节序标记),再判形:形不对的具名拒收。
凭证文件夹名为 badge- 加授权编号前十位,里面的 badge.txt 是原样的文本,badge.svg 是它的二维码。
授权文件
扩展名 .zkgrant,文件名为 grant- 加授权编号前十位。它是一个容器:第一行 zikaron-kit-file/1,之后每一项写成「包内路径、换行、十进制长度、换行、字节、换行」,清单在前,其余按字节序。长度只认编码器的写法:十进制数字,零写作 0,其余以 1 至 9 开头。读进内存的读法(授权文件、从发布地址取来的记录包)上限为总长 64 MiB、4096 项;记录包文件逐项写、逐项读,总长不设上限。它的读法只有一处(zikaron_glue::grantfile),应用与命令行的 check-grant 共用。里面装着:
manifest.json;entries/*.zk1:授权链各跳的条目,授权在本角色账本里时,连同签发者账本;files/zikaron-grant.txt(授权码)、files/terms/…(条款文件)、files/verify.md,以及可选的files/publish.txt(导出这份授权文件的数据目录所设的发布地址)。
交接件
扩展名 .zkhand,由 hand-over 写出:与授权文件同一个容器的单文件记录包,装着整本账的条目与 files/zikaron-handoff.txt。后者三行:handoff/1、id 加 32 位十六进制的交接编号、after 加交出方当初收下这本账所凭的那份交接件的编号(一直自己持有时写 -)。take-over 先按记录包的法核一遍,再读其中任何一项。
账本镜像
<所选文件夹>/ZIKARON-backup/<40 位十六进制地址>/<author|grantee>/
mirror.json 种类 desk-mirror,版本 1
entries/<64 位十六进制> 条目原文,文件名只有编号
held/<路径> 收到的授权等同一文件夹里已有较早的镜像时,只补上新增的条目。命令行读的子命令可以直接指向含 mirror.json 的那一层。镜像里的 author 文件夹可以在桌面上用「收编已有账本目录」「原位收编」收进数据目录。
派生路径
约定的字面量
bytes-sha256/1- 存证的方式标记,文件、文件夹与 Git 仓库共用这一个,表示内容指纹是一串字节的 SHA-256。对文件,这串字节是文件的全部字节;对文件夹,是按相对路径排序的规范 JSON
{"files":[{"path":…,"sha256":…}…]}(符号链接与特殊文件略过并计数,空文件夹拒收);路径里的各级名字在任何系统上都以/连接,同一棵树在哪里算都是同一个指纹;核对时也认早先版本在 Windows 上以\连接写下的记录,签名只写/这一种;对 Git 仓库,是当前提交对象去掉commit <长度>\0头之后的字节,与git cat-file commit HEAD | shasum -a 256相同。与之配对的toolchain是这个字面量本身的 SHA-256:0xb7f3c4a226684d6da684b0ac82d36ed5bcba7bb61d09c955bff9437f84a52a4d。 retraction- 删除条目的类型名。正文中
subject必填,为本账本里一条存证的编号,0x加 64 位小写十六进制;note_md可选。别的工具把它列为未知类型(UNKNOWN_TYPE),账本结论照旧;本应用的读法写在zikaron_glue::retraction,应用与命令行共用。 for- 存证正文里的附加成员:
app、identity(hex20)、ref,另有可选的seat。填了任一项,前三项都要有。 upstream- 授权正文里的附加成员,值为上游授权的编号,zikaron.kit/1 把它读作转授权。