开发者指南 · Zikaron

安全

本软件保管的是密钥与证据。本页说明它防范什么、依靠什么防范,以及维护者须遵守的规范。

报告漏洞

请私下报告:发邮件到 kaptonia@tuta.io,或在 Session 上找 05208a1c6901322420185a1f72ef691ac749fb86ca74c129cf65cb5ec8972a5f22。写明受影响的版本、复现步骤、观察到的结果以及你判断的影响。修复发布之前,请把细节限于私下往来。

以下几类优先处理:签名绕过用户确认;核验对伪造的输入答「全部通过」;读出锁定状态下的本机数据或密钥;两份合规实现对同一输入给出相异的结论。

威胁模型

对手防护
趁你离开时坐到机器前的人口令门;输错五次锁定;闲置自动锁定;锁定时从内存清除主钥。
取得磁盘或备份文件的人账本、设置、队列、身份登记、已核对事实、备份索引、收到的授权,以及开锁后才读的机器设置(含代理的用户名与密码)都加密存放,密钥库另以口令加密;文件名与文件头里标明「这是哪一份」的一段都由本机的钥算出,文件与条目编号、授权编号的对应只有持钥的人算得出。明文的有:开锁前要读的 machine.json(只有语言与外观)、只读网络列表、机器标与写者标、盲化的收编清单与交接清单(随机盐加根条目号的 SHA-256,不含账本名、路径或条目号)、命令行签过的交易哈希(cli-sent/,由它可以查到这台机器发过哪些交易),以及给别人读的导出件(含核验结果档;记录包索引里加密包的一行只多一枚一次性公钥,不记接收方)。「经凭据操作」开着时,机器目录里另有一份只本人可读写的凭据:一把随机钥和用它封起来的主钥。拿到这个文件就能像命令行一样读写封存的数据、以这个身份签名并发交易,所以它带期限,关掉开关即删。抵御离线穷举的,是口令与备份密码本身的强度,以及每次尝试都要跑的 scrypt(N=262144、r=8、p=1)。
伪造记录交给你的人签名、账本链条与链上记录相互印证,核验逐项给出结论。
事后补造证据的人上链给出时间上限;连续的账本占满了每一个位置。
说谎的节点自己的账本与授权核验对照多个节点的读数,按问法只比判定用得到的事实,相异即报分歧,结论要各节点一致;链头取各节点都已到的最低一块,相差超过 64 块即具名报出落后的节点;只计答的是这条链的节点;核验记录与查看他人账本每条链问一个节点,单一来源时一律明确标注。只读网络每次读之前逐个节点核登记合约的代码哈希,只在给出钉定代码的节点上读,有节点给出别的代码即整条网络这一次不用。由地址找接收方公钥时,一个节点给出的交易就够:由签名恢复出的公钥须推出所给的地址,自己证明自己;只有每条网络的每个节点都从起始区块读到链头而没有这个地址签的锚,才答未锚定(ADDRESS_NOT_ANCHORED),有网络没读成即答缺席。只配一个节点时,日志与回执无从交叉核对,这一点如实标明。gas 估算是各节点自己算出的量,取其中最大的(多给的额度不多扣费),仍受 200,000 的上限。
诱导你签名的网页或应用身份密钥只在 Zikaron 中使用;应用只签四个域里的两个:zikaron/1 与 zikaron/1-adoption。
本机上别的用户桌面的本机通道只收同一用户:macOS 与 Linux 上是只本人可读写的套接字文件,Windows 上是只本用户可连、拒收别的机器的命名管道;连上后两端各自向系统核对对方是谁。通道只在「经桌面 App 操作」开着、桌面已解锁并以写者打开数据目录时开着,锁定、关闭、退出或打开「经凭据操作」即关;它能写条目、按「命令行上链」的设置上链;私钥与口令留在桌面里,要口令的连署与移交账本只在桌面上做。命令行的凭据只本人可读写。

以下情形在防护范围之外:已解锁机器上运行的恶意软件(它以你的身份运行,能经通道请桌面写条目);「经凭据操作」开着时以你的身份运行的程序(它读得到凭据,桌面锁着也能读写封存的数据,「命令行上链」为「自动发送」时还能花这个身份的 gas);接入的 AI 应用也以你的身份运行,经 zikaron mcp 做的事与命令行相同,照同样两个开关走;内存读取;操作系统或硬件层面的攻破。对此,软件如实声明自身的边界:打开「经凭据操作」的卡上写明命令行可花 gas,凭据带期限,随时可关。

密钥规范

签名确定
采用 RFC 6979 确定性随机数与低 s 值。同一把密钥对同一份字节,在任何合规工具上都签出同一个签名。
四个域相互独立
域的字面量写在被签消息中,一个域下的签名只在该域内有效。新增域时,选用与现有字面量全然相异的字面量。
导入的密钥先只读
导入的身份可能在别处写过账本,所以在本机账本与链上这把密钥的上链记录核对相符之前只读。首次启动导入私钥作主身份时,同时导出它的密钥文件,读回核对过才建成身份。坏的或参数超界的密钥文件在派生之前即拒,不白白占住机器。
只签自己组装的字节
应用只对自己组装的内容签名:在窗口里,是用户已在确认卡上看过的;经桌面通道,是本用户的命令行请求的,桌面直接照做;经凭据时,命令行用钥库里这个身份的钥,只签它自己组装、过了写入前审核与写闸的条目与交易。签名入口仅对此类内容开放。
解密只做协商
打开加密给本机身份的记录包,私钥只借给封存层做一次 ECDH,得出一个共享值,不签任何东西,不写任何文件。钥库里每一槽以它那把钥的地址命名;槽里的钥推出的地址不是这一槽的,桌面与命令行都拒用这一槽,别的槽照读。
加密记录包的边界
外层加密只管投递:接收方打开后仍可转出明文;它证明不了是谁发的,任何人拿到接收方公钥都能做一份,记录真不真照旧由包内的签名与链上的锚判定;没有前向保密,接收方的身份密钥日后泄露,先前发给他的包都打得开;签名钥兼作解密钥,因为由链上交易恢复出的正是这把钥的公钥。
密钥在内存中短暂停留
用后即清除。显示过的助记词在收起、锁定、退出时从内存抹掉;输入的口令交出后即抹。日志与错误信息中只写地址。
密码学调用集中一处
椭圆曲线、哈希、密钥派生的调用集中在指定模块,其他代码经由该模块使用。

读链规范

失败即答案缺席
节点连接失败、读数相异、扫描中断时,结论记为缺席,界面照此显示。网络故障与伪造记录,须让用户一眼分清。节点答回读不成的链高,具名拒绝;余额要有节点答出才算数。
绿灯只来自完整的检查
交易进入区块只算「确认中」;账本检查的报告读到它,才点亮绿灯。
回执成功才出队
条目在回执确认成功后离开队列;发送失败、等待超时的条目留在队列中。删除尚未发出的存证时,它也随之出队。
重发前先查
已上链的条目挡在队列之外;签名之前先问链上这把钥是否已有这几个哈希的锚,已有即一笔不发。一批签成之后、广播之前先在队列里记作已提交(带交易哈希与 nonce),重启后按哈希接着问原交易的回执;中途节点一时不通不算失败,照样接着问。广播只送一次,「加价重发」由用户在桌面上按;经凭据的 anchor --ledger 在「自动发送」时,只把各节点池都没有而 nonce 仍空着的那一批在原 nonce 上照原样重签再发。
同一个 nonce 只付一次
卡住的一批由用户发起「加价重发」:同一个 nonce,各项费用按现价且至少比原来高 10%,原来的与新的都继续盯,先入块的作数,一批至多三次。一批不在任何节点池里时,nonce 还没被用掉才按现价重发;已被别的交易用掉即作废,条目退回队列。命令行自持私钥时,签过的交易先记进 cli-sent/ 再广播;同一条命令重跑先按哈希问下落,所以一次锚定只付一次费。
写出之前现读一次链
上链、导出记录包、导出授权文件、生成凭证之前,都先现读一次链;链上有本机没有的记录时拒绝,提示「账本不是最新」。
核对过的才记下
几个节点一致确认过、判词已定的锚,本机记下它的区块时间与判词,下次直接用这份记录;区块被替换或日志不同即重新问。只一个节点读到的、判词未证的一概不记。「全部重新核对」随时清掉重来。
逐次核对证书
每次连接都核对 https 节点的证书链与主机名;经代理时 TLS 仍与节点端到端,按节点的名字核对。TLS 集中在 zikaron-net 一处,用 rustls,根证书编进每个程序,与系统的证书库分开;应用与命令行走的是同一条路。经 HTTPS 代理时,到代理一段另走 TLS,按代理的名字核对它的证书。
输入设上限
每一问从开始算起 30 秒为限,其中连接至多 10 秒,应答至多 64 MiB;节点地址解析出几个 IP 时逐个试。从发布地址取记录包只走 https,每个文件 20 秒、32 MiB,一个包至多 64 MiB,同源跳转至多 3 次。退出应用时,进行中的一问在四分之一秒内切断。TLS 握手、代理的答复与节点的应答一个字节一个字节地慢慢来时,照样到时限即切断。
按表等待,不加急
节点限流时等 0.3 秒、0.9 秒各再问一次;服务器出错等 0.3 秒再问一次;读的请求在应答到达之前断开,换新连接再问一次;超时与其他拒绝即报。每一类至多再问两次,间隔都在一秒之内。
代理脚本关在沙箱里
跟随系统时,系统指定的自动配置脚本由应用直接去取,不经代理:https 地址哪里都取,http 只取本机的(字面回环地址与 localhost),file: 不取;跳转至多 5 次,每一跳同样判;脚本至多 1 MiB,5 秒内取完。脚本在 QuickJS 里跑,每次一个新引擎,只有语言自带的对象与标准的 PAC 函数,没有网络、文件、模块与定时器;每次至多 2 秒、内存 8 MiB、栈 512 KiB;它要的域名解析由应用在外面答,每次至多 1 秒。交给脚本的只有节点的协议、主机与端口,路径与查询留在外面。脚本给的几条路逐条试,SOCKS 先试 SOCKS5 再试 SOCKS4。脚本取不到、跑不成或答不对时照没有脚本走,原因具名:PAC_UNFETCHED、PAC_SCHEME、PAC_REMOTE_HTTP、PAC_TOO_LARGE、PAC_FAILED、PAC_TIMEOUT、PAC_MEMORY、PAC_NO_FUNCTION、PAC_ANSWER。
代理凭据
HTTP 代理用 Basic,SOCKS5 用用户名口令,SOCKS4 只带用户 ID;密码存在加密的机器设置里,交给代理之后即从内存抹掉,设置页只显示「已设密码」。代理要凭据而没给,或不收所给的,具名报出:PROXY_CREDENTIALS_MISSING、PROXY_CREDENTIALS_REFUSED。

隐私

  • 应用的网络请求只有这几个去处:用户配置的节点、用户填写的发布地址、核验时用户给出的 https 地址,以及收到的授权文件里带着的发布地址(定期复核时也会去取)、系统设置指定的代理自动配置脚本地址(由应用直接去取,不经代理)。除此之外,应用零更新检查、零遥测。
  • 公共节点能看到查询的地址与合约,文档与界面如实告知。按地址导出加密记录包时,节点看得到被查的那个接收方地址;只给公钥时不读链。
  • 代理缺省跟随系统设置:系统指定了自动配置脚本时照脚本走,再是手填的代理;系统的「自动探测代理」不读,只开了它而别无设定的当作直连;在别的机器上、走 http 的脚本不取,照没有脚本走,设置页写明原因。手填的代理不碰本地网:私网、链路本地与 .local 的节点一律直连;本机上的节点任何时候都直连。请求的 User-Agent 只写产品名 ZIKARON。
  • 节点地址里带的访问密钥,在一切提示、详情与日志里都隐去,地址写成哪种形都一样。
  • 条目中的文字随账本流出(记录包、授权文件、镜像),永久可读;文档如实告知。
  • 短小、易猜的内容,其指纹能承受的穷举有限,文档提醒用户加入随机内容。
  • 签入时的文件名与路径留在本机,与账本分开存放;记录名称写进条目,随条目公开。

依赖与供应链

  • Cargo.lock 随仓库提交,发布构建使用 --locked;工具链由 rust-toolchain.toml 钉定。
  • 发布的二进制里只有中性的路径名,构建时由 zikaron-pack no-paths 核对。Linux 的 AppImage 运行时按字节钉定,下载来的不符即停。代理脚本引擎 QuickJS 的 C 源照它自己的发布构建去掉断言编译,二进制里不带编译目录的路径,由 crates/zikaron-net/tests/engine_sources.rs 复核。
  • 依赖保持精简,核与存储尽量只用标准库;传输层直接依赖的第三方有 rustls(以 ring 为加密后端)、webpki-roots,以及跑代理自动配置脚本的 QuickJS(经 rquickjs,钉定 0.14.0,只取核心引擎);zikaron-os 与 zikaron-pack 只用标准库。封存层的密码学依赖(chacha20poly1305、hkdf、hmac、sha2、scrypt、aes、ctr,以及只取曲线运算与 ECDH 的 k256)只经 zikaron-seal 的 crypto 模块使用;界面用 egui/eframe 0.34,macOS 上另经 wgpu 走 Metal。
  • 升级密码学相关依赖时单独提交合并请求,附变更说明,并重跑全部一致性比对。
  • 新增依赖须说明用途、维护状况与许可。
  • 内嵌字体与其许可文本一并发布。每个平台的包都附按 Cargo.lock 现生成的第三方许可清单,应用的「关于」里也能看到;清单生成不了,构建即失败。

文件写入规范

  • 先写临时文件并同步落盘,再重命名到位;在 FAT 与 exFAT 卷上同样可行。Windows 上改名遇到别的程序(如杀毒软件)一时占着文件,在一秒内重试。
  • 读不成的本机文件具名报出,原样留着。
  • 写到一半出错或断电留下的临时文件,写不成的写入随即删掉;写者打开数据目录时扫掉残留的,它们不挡读账本。
  • 一处数据只有一个写者:同一台机器上靠内核锁,进程一结束即放开;「经凭据操作」开着时,桌面与命令行每写一笔各拿一次这把锁、写完即放,拿到时若对方写过先重读,撞上对方在写即这一笔不做并说明。跨机器靠写者标,后开的一台只读,命令行遇别台机器的写者标即拒(E_HOME_OTHER_MACHINE)。属于别的身份或角色的数据目录,打开前即拒。
  • 已有文件一律保留:备份、记录包、授权文件遇到同名时自动编号另存,并告知存放位置;密钥文件与命令行遇到同名即停止。
  • 写完读回核对,密钥文件与备份尤其如此:读回相符才向用户报告成功。
  • 恢复备份、更换主身份这类改动整步完成:要么全部生效,要么保持原状。批量签名在第一处失败停下,已签的保留。
  • 主钥已失、永久封存的旧数据,字节原样移入 set-aside 目录。
  • 取出原件要么全取、要么一个字节不写:所选文件夹里已有同名即整批不取;遇到链接即拒,链接一概不跟;先排进所选文件夹里的暂存文件夹、逐份核过指纹再逐个就位,绝不替换;任何一步失败,已放下的撤回、暂存清掉;绝不写到所选文件夹之外。
  • 恢复备份时逐个检查成员的路径,带路径穿越的成员一概拒收。「更换数据目录」只收已有的数据目录或空文件夹,迁移的新位置要在原目录之外。
  • 命令行写条目之前,先离线审「账本加上这一条」,会把账本写断的一概拒收(E_WOULD_BREAK);写桌面的数据目录还先过写闸,落着只读标记、账本断了链或已交出,一个字节不写(E_READ_ONLY、E_HANDED_OFF);桌面收编过的明文账本目录,命令行一律拒写(E_ADOPTED)。
  • 密钥文件、命令行的凭据与账本的条目文件都只本人可读写:macOS 与 Linux 上权限 0600,Windows 上只授所有者,由 zikaron-os 在建文件那一步设好。改名覆盖统一经由 zikaron-os。

失败必须可见

每次操作落空,界面都给出明确提示。这类软件中代价最高的缺陷,是被用户当作成功的失败:一份以为已经上链的证据,到要用时才发现缺失。评审合并请求时,对每个新操作都问一句:它失败时,用户看到什么?