Skip to content

Latest commit

 

History

History
154 lines (102 loc) · 25.8 KB

account_root_zh.md

File metadata and controls

154 lines (102 loc) · 25.8 KB

账户控制与根封装合同

English | 简体中文

核对日期:2026-10-24,公开源码 9fd70dca。本文描述桌面客户端的认证绑定、按账户分开的工作区和全设备丢失恢复;发布工件与具体部署验收分别核对。 此文描述公开客户端实现,不代表生产 key 或生产部署已验收。

账户控制

dmsg_client::account 直接使用 dmsg_types 的命令类型,签名消息与 canister 用同一个 dmsg_protocol 编码为确定性 CBOR:Principal、AccountId 和固定字节字段为 byte string,unit variant 为文本。账户创建使用 dmsg/create-account/v1,home 配置了准入公钥时另附云端对 dmsg/account-admission/v1(home、caller、期限)签发的票据;账户变更使用 dmsg/device-approval/v2、dmsg/account/v2 和 dmsg/account-operation/v2;文档认证使用 dmsg/attest/v1,根派生使用 dmsg/derive-root/v1,恢复设备 PoP 使用 dmsg/recovery-device/v1,controller 注册 PoP 使用 dmsg/controller-pop/v1。签名覆盖实际 home/caller、账户、命令、版本、设备序号、请求 ID 和期限。

user home 不在客户端配置中固定。客户端读取 dmsg_handle.get_handle_config() 的 user_homes(权威列表;config.toml 的 user_homes 只作钉定,必须都在列表中)和 registration_homes(当前接收新账户的子集,随机选择一个注册)。登录后并行对列表中的每个 home 调用 my_account,全部成功后选取列表中首个命中的账户;任一查询失败视为未知,不落到注册。账户的 home_user 随账户固定。

客户端在联网前把完整请求写入工作区的加密日志。响应丢失时通过 my_account / get_operation 查询原操作;不会把认证成功或返回账户 ID 当成设备批准。当前账户安全叶(显式恢复策略为 schema 5,旧账户保留 schema 4)、完整设备 map、版本及根摘要先经 IC 证书校验;本机已经看到的更高版本和已安装根不得被旧响应覆盖。

新设备请求绑定目标账户、home、设备公钥、角色、能力、预期版本、请求 ID;管理员核对后批准。默认成员为 ContentSign/VaultUnlock,默认管理员另含 RootManage,不默认开启 FormalApprove/PaymentOffer。根包只封装给持有 VaultUnlock 的活跃设备:新设备持有它时,管理员换根后它才能读到根;没有它的设备不在根包中,客户端也不尝试打开根。云端同样按 VaultUnlock 放行账户密文读取。账户的初始设备和登录恢复的替换设备必须同时持有 RootManage 与 VaultUnlock。认证绑定分三步:新登录在本机生成 nonce 并把请求(home、账户、Principal、nonce)交给管理员设备;管理员批准 BindAuth { principal, nonce },账户内登记 10 分钟有效的待接受项;新登录用同一 nonce 调用 accept_auth_binding 后才绑定。登录身份不等同于设备授权。

客户端的认证绑定(dmsg_client::security)有两种走法,管理员一侧都经审批中心批准:

  • 跨设备:没有账户的新登录生成请求令牌(base64url 的确定性 CBOR {format: "dmsg-auth-request/1", home, account, principal, nonce, expires_at},5 分钟有效),nonce 记在该登录的工作区;管理员设备核对令牌的 home、账户与期限,并由人输入对方显示的登录 Principal 后创建审批;批准后新登录调用 accept_auth_binding,随即进入该账户的工作区(本机已有该账户的已绑定工作区时直接解锁,否则等待配对或恢复)。
  • 同一设备:已解锁的设备发起一次“用于绑定”的登录,daemon 收到第二个登录的 delegation 后不切换账户,而是先确认它没有账户,再生成 nonce 并创建审批;批准即提交 BindAuth,随后用第二个登录的身份调用 accept_auth_binding。该身份只留在内存,至多等待 10 分钟。

RemoveAuth(不能移除当前使用的登录)、SetPolicy(冻结、每日执行次数、允许的用途)、SetRecoveryDelay 与 DisputeRecovery 同样经审批中心。审批记录里登录 Principal 以文本保存,签名前才转换为命令。

设备权益规则(2026-10-28):Free / Plus / Pro / Max 最多分别有 2 / 3 / 5 / 10 台活跃设备;已撤销设备不占名额。存量超额或会员到期降级只限制继续新增,不撤销设备或阻止安全维护。AddDevice 超限返回现有 QuotaExceeded,会员状态失效返回 MembershipStale;已提交请求仍幂等返回原回执。额度由现有 commerce 投影的套餐标识推导,账户/设备结构、Candid、签名编码和安全叶格式均不变。

设备继任

已满额的账户换设备时不必先让出名额:由被替换的设备为继任设备批准 AddSuccessor { device, proof }。继任设备签 successor_device_message = digest("dmsg/add-successor/v1", (home, account_id, device, expected_version, request_id, predecessor)),其中 predecessor 是批准的设备,所以其他设备无法批准它。批准者需要的权限与 AddDevice 相同(持 RootManage 的管理员;启用硬件政策时还要它的批准钥匙),继任设备的能力不能超出批准者。继任不受档位的设备上限约束,但账户同时只能有一次继任;设备表 16 条上限与淘汰规则不变,因此降级后超额的账户也能逐台替换。与 AddDevice 一样,它递增 security_epoch,继任设备持有 VaultUnlock 时需要换根。

继任结束前,批准的设备只能批准交接:为继任设备 SetApprovalKey、ReserveRoot 与 CommitRoot(与配对一样,批准后由它换根)、DisputeRecovery,以及撤销自己(继任完成)或撤销继任设备(继任取消)的 RevokeDevice。它批准的其他账户命令、应用批准、第三方认证与证明一律返回 Forbidden。继任设备加入满 7 天后,unlock_secret 以 DeviceNotApproved 拒绝批准的设备。撤销任一台设备即结束继任,完成登录恢复时清空。继任只记在 user home 的账户状态中,不进入 Device、devices_root、安全叶或 AccountInfo。

批准钥匙

账户可以要求某些设备批准另带一把“批准钥匙”的签名。批准钥匙是设备硬件保管的 P-256 钥匙(macOS 上为 Secure Enclave,.userPresence,可选仅当前指纹 .biometryCurrentSet),每台设备至多一把,登记在 user home 的账户状态中,不进入 Device、devices_root 或安全叶,dmsg_directory 与云端的安全证据因此不变。

key_id     = digest("dmsg/hardware-key/v1", public_key)            // SEC1 压缩点,33 字节
possession = P256(key, digest("dmsg/approval-key/v1", (home, account_id, device_id, public_key)))
M          = approval_message(home, account_id, domain, command, approval)   // 设备 Ed25519 所签
H          = digest("dmsg/hardware-approval/v1", (M, key_id))
Approval.hardware = { key_id, signature = P256(key, H) }          // r || s,低 s;不进入 M

P-256 签名按 ECDSA-SHA256 处理 32 字节的 H(即 CryptoKit signature(for: Data(H))),user home 拒绝高 s。AccountInfo 返回各设备的 approval_keys 与账户的 hardware_policy。

  • SetApprovalKey { device_id, key }:登记、替换(key 带新公钥与持有证明)或退役(None)。任何有效设备可设自己的钥匙,为其他设备设钥匙需要管理员;同一把钥匙不能登记给两台设备。撤销设备时其钥匙一并退役。
  • SetHardwarePolicy { purposes }:空集合关闭;非空时必须包含三项控制用途。开启需要批准设备本身的有效证明。
  • 用途:AccountControl(批准钥匙、两种政策、恢复延迟、登录绑定)、DeviceControl(AddDevice、AddSuccessor、RevokeDevice、SetDeviceCapabilities)、PrincipalControl(EnablePrincipal、RegisterController、RetireController)为启用即必含;Payment(CashCheckout、PandaSubscription 的 approve_application,AuthorizeHandle)与 Attestation(attest、attest_app_action、approve_authentication)可选。换根、争议恢复、principal 展示文档、controller 改名与标记失陷、多签钱包批准和恢复派生不在其列。
  • 核验只在 user home 的设备批准检查中进行:账户政策要求的用途必须带有当前登记钥匙的有效证明;未要求时请求带来的证明同样核验。是否要求只由账户状态决定,旧 Candid(没有 hardware 字段)在启用后被拒绝。登记、替换、退役钥匙和修改政策都递增 security_epoch,在途的旧批准随之失效;commerce、membership 与多签钱包仍经 verify_application_authorization 复核记录与 epoch,不需改动。
  • 登录恢复不需要批准钥匙:complete_recovery 在同一提交点退役全部钥匙并清空政策,替换设备再登记、再启用。开启硬件批准后,账户控制从“设备软件钥匙与登录同时失窃即可立即接管”变为“链上恢复开启时仍可经可被争议的延迟恢复接管”。

客户端(dmsg_client::approval_key)把钥匙句柄用 LDK 封存,不出现在任何视图或日志中。账户保护的高风险请求在本机有登记钥匙时,人只在审批开始前回答一次:daemon 在不持客户端锁时取得这次在场授权,随后组装批准、签 Ed25519,并以同一授权签 H,因此等待人时不占设备序号,也不弹第二次在场确认。启用政策后没有可用钥匙的设备,可在本机生成钥匙并当场以持有证明签出请求令牌(base64url 确定性 CBOR {format: "dmsg-approval-key/1", home, account, device_id, key}),由一台有钥匙的管理员设备批准登记。

本机工作区

每个账户在本机有自己的工作区,账户之间互不可见:<home>/<environment>/accounts/<account>/ 存放该账户的 workspace.sqlite 与 Agent 连接器状态 agents.sqlite;还没有账户的登录用 logins/<principal>/,等它有了账户(创建、被绑定)后整个目录改名为 accounts/<account>/。登录时客户端用 my_account 找到该登录的账户并选择对应工作区,没有时选择该登录自己的工作区;同一账户的不同登录共用一个工作区。current 记录最近选择的账户,daemon 重启和快速解锁使用它;切换到另一个账户的工作区时先锁定,其驻留密钥一并丢弃。每个工作区有自己的设备 ID 与密钥,所以同一台电脑上的不同账户是不同的设备。

每个工作区还有一把硬件锚:macOS 或 iOS Secure Enclave 中的 P-256 签名钥匙,开机后首次解锁即可使用,仅限本机,使用时不询问人。因为判定发生在解锁之前,它的句柄与公钥以明文记在工作区元数据中。客户端在启动、注入硬件和选择工作区时用它签 digest("dmsg/hardware-anchor/v1", (environment, deviceId, challenge)) 并验签。本机硬件打不开句柄(例如系统迁移或恢复备份把工作区复制到了另一台设备)即判为迁入;硬件暂时不能回答时不改变任何状态,稍后再判。迁入的工作区不解锁、不做服务端写入、不接收推送,也不会经撤销检查被清空。人确认后,整个目录改名为 moved/<account>-<设备 ID 前 8 位>/(附明文 moved.json),本机随后以新设备加入账户;副本保留供导入,只有人确认后才删除。锚出现之前就已持有硬件钥匙(在场快速解锁钥匙或设备绑定槽位)的工作区,补建的锚标为 late:此后第一把打开的硬件钥匙确认原机,本机无法使用的则判为迁入。没有 Secure Enclave 的平台不做此判定。

本机解锁

没有本地解锁口令、恢复码或服务外离线恢复包;文件导出不属于账户恢复。本机数据密钥 LDK 只在以下两种钥匙下封装:

name        = "dmsg:" + environment + ":" + account + ":" + deviceId
LUK         = HKDF(unlock_secret, ["dmsg/local-unlock/2", name])
wrappedKey  = E(LUK, LDK, ["dmsg/local-key/2", name])
fastWrapped = E(K_fast, LDK, ["dmsg/local-key/2", "fast:" + name])   // 可选
  • unlock_secret(account_id, device_id) 是 user home 的认证 query:caller 必须是账户的登录 Principal,设备必须存在且未撤销,返回 32 字节 digest("dmsg/unlock-secret/v1", (master_secret, account_id, device_id)),其中 digest(domain, value) = SHA256(CBOR([1, domain, value]))。master_secret 在 home 初始化的 timer 中由 raw_rand 生成一次,升级保留。撤销设备后该设备的解锁材料不再发放。

  • 新工作台在绑定账户前只用一把明文保存的临时随机键封装 LDK;此时本机只有新生成的设备密钥,没有任何内容,写入被拒绝。账户创建或配对批准后,客户端取得 unlock_secret,用 LUK 重封 LDK 并删除临时键。

  • 日常解锁:经 dmsg.net 登录页的 II 登录(dmsg-login/1,30 天 delegation,会话私钥由 daemon 持有)→ unlock_secret → LUK → LDK → Bundle,全部只在内存。login_unlocked_at 记录最近一次登录解锁。

  • 快速解锁:登录解锁后默认开启,人关闭后保持关闭;secure_storage = "file" 时没有快速解锁。K_fast 只封装 LDK 的第二份副本:macOS 上由 Secure Enclave 的密钥协商恢复,每次解锁要求 Touch ID 或 Mac 登录密码,并恢复以 LDK 封存的 30 天登录;没有硬件在场验证的平台使用存于凭据库的随机 32 字节,没有用户在场验证,也不保存登录。硬件在场验证保护的快速解锁(含 macOS/iOS)不受 II 登录期限限制;只有无需在场验证的普通凭据库密钥在距上次登录解锁满 30 天后拒绝。II delegation 仍按自身期限失效,过期的封存登录删除,不阻止本机解锁与读取;快速解锁后一旦联网就核对认证设备表,已撤销则清空本机数据(封存的登录随之删除)。Secure Enclave 报告“暂时不能询问”(无 Touch ID 键盘、锁定等)时保留快速解锁,只有钥匙确定无法重建时才关闭它。

  • 设备绑定(具备系统凭据库与硬件能力时默认):保留同一个 LDK,只换封装。凭据库中另有独立槽位 <name>:device-bound,保存一把 Secure Enclave 密钥协商钥匙的句柄与软件对端;其秘密 S 每次解锁取得一次(要求在场):

    slot     = digest("dmsg/device-bound-key/v1", peer)
    context  = (1, environment, account, deviceId, slot)
    K_login  = HKDF(LUK || S, ["dmsg/local-login-bound/v1", context])
    K_fast   = HKDF(S, ["dmsg/local-fast-bound/v1", context])
    ldk-bound = E(K_login, LDK, ["login", context]);  ldk-fast = E(K_fast, LDK, ["fast", context])
    

    开启时先用新钥匙实际解出一次 S,再在同一事务中写入 ldk-bound、新的 ldk-fast 并删除只靠 LUK 的封装;以后登录解锁和快速解锁都需要这台 Mac 的硬件。关闭快速解锁只删 ldk-fast,退出登录与任何错误都不删除槽位,只有重置工作区或解除绑定才删除;仅当前指纹的槽位在指纹集合变化后报告失效,硬件暂时不可用时报告不可用,均不回退旧封装。解除绑定重新以 LUK 封装 LDK。它只防开启之后复制的工作区:之前的副本仍可凭登录打开同一个 LDK;后台在线时内存中的设备签名钥匙与常驻 Agent ID 在凭据库中的副本不受它保护,开启时如实列出。

2026-10-31 默认绑定规则:注册、配对、恢复后的加入与普通登录共用本机绑定流程,复用已有 unlock_secret query 的结果,不增加 update 或日常 vetKD 调用。系统确认在客户端执行器之外;确认前不释放内容密钥,取消或失败不回退,确认后重新校验会话与设备授权。旧的未绑定快速解锁返回 AuthRequired,先经登录迁移。工作区元数据 bound_declined 缺省为 false,明确解除绑定时设为 true,手动重新绑定时清除;关闭快速解锁不改变它。没有系统凭据库或硬件能力时保留兼容路径,已有硬件锚但暂时不能询问时不能按无硬件降级。既有副本仍受此前的保护边界约束。

换机通过旧机批准新设备与 dmsg-device-transfer/1 传输本机数据;新设备不继承旧设备身份或硬件槽位。旧机不可用且事先开启链上恢复时,只承诺恢复已上传的云端内容,硬件绑定副本中的未上传文件不能仅凭登录恢复。已有硬件快速解锁钥匙优先用于默认绑定;新绑定槽位记录 created_at(旧槽位缺省为 0),只有早于硬件锚的钥匙才能确认旧锚的归属,避免用新机生成的钥匙误认旧机副本。

硬件快速解锁允许长期离线读取本机已有副本;远程撤销须在客户端取得认证撤销状态后才能清空该副本。首次绑定、关闭快速解锁、遗失硬件密钥和旧工作区的默认绑定迁移仍按原路径处理,不因 II 到期而自动降级保护。

不可变 RootBundle v3

字节为确定性 CBOR:

{ body: {
    format: "dmsg-root-bundle/3",
    context: { account, environment, generation, opId, securityEpoch },
    commitment: hex(SHA256(HKDF(VRK, ["dmsg/root-commitment/1"]))),
    envelopes: [ { device, enc, ciphertext } ... ],      // 按 device 升序,每台持有 VaultUnlock 的活跃设备一项
    recoveryKey?: { homeCose, keyName, publicKey },        // COSE 内容根 vetKD 公钥
    recovery?: base64url(IbeCiphertext),                   // IBE 到身份 CBOR([AccountId, generation])
    previous: null | { digest, uploadId, generation, envelope },
    device, signingPublic
  }, signature: bytes64 }

v3 的 recoveryKey 与 recovery 必须同时存在或同时省略,关闭时省略。ContentRootRef.recovery_enabled 必须与账户策略及接收者摘要一致。旧 v2 包仍可读取,未完成的 v2 任务保持原套件和摘要;v2 新提交只允许恢复已开启的账户。签名域与 bundle digest 域保持版本 2,body 内的 format 区分格式。

认证状态仍为 schema 4、缺少 recovery_enabled 的账户继续生成 v2,保留原有恢复能力并兼容尚未升级的 home;明确的 schema 5 策略才选择 v3。格式选择依据已验证的账户状态,不通过完整性错误触发降级或放宽校验。旧账户未提交的 v3 草稿在对账账户日志后重建:保留仍有效的预留和候选根,为新的不可变上传字节生成新计划;已经提交的包原样打开。

设备加入/撤销与换根是两次提交。设备变更成功后,换根失败继续保持可对账,不能要求重复加入。旧版本已标为失败的审批,仅当它已经获批、开始执行且仍保存上述不兼容草稿时恢复执行。新设备等待自己的根信封;登录已失效时需要重新登录。

公钥、COSE_Encrypt0 与 HPKE envelope 字节使用无填充 base64url;摘要、设备/操作/上传 ID 使用小写 hex。account 为规范的 20 字符 Xid,代次为安全正整数,securityEpoch 为预留槽的安全版本。

  • signature 是设备 Ed25519 对 digest("dmsg/root-bundle/2", body) 的签名。签名数学正确不单独证明设备权利;读取当前根还须核对 user 的认证根承诺。
  • 设备信封:HPKE.Seal(device.hpke_pub, VRK, info = aad = CBOR(["dmsg/device-root/1", environment, account, generation, device_id])),复用频道 epoch 密钥的 RFC 9180 X25519/HKDF-SHA256/AES-256-GCM 原语。读取方解开后核对 commitment,保证所有设备拿到同一个根。
  • 恢复信封:ic-vetkeys 的 IbeCiphertext::encrypt(dpk, identity, VRK, seed),dpk 是 COSE root_public_key(account_id, generation) 返回的 96 字节派生公钥(所有账户共享同一 context 公钥),identity = CBOR([AccountId bytes12, generation])。客户端核对描述中的 home、环境、代次、派生版本 2、账户、SHA256(public_key) 指纹与 key 名(生产为 key_1)。
  • previous.envelope 以新 VRK 包装上一代 VRK,AAD 为 ["dmsg/previous-root/1", account, generation, previous.generation, previous.digest]。previous.digest 是上一份 bundle 字节的 SHA-256(即云端 manifest digest),uploadId 定位它;读者验证摘要并要求代次严格递减,至多读取 256 代。
  • 链上 ContentRootRef { generation, suite: "dmsg-root-v3", recovery_enabled, recipients_digest, body_digest, bundle_digest }:recipients_digest = digest("dmsg/root-recipients/2", [sorted device_id bytes, generation, recovery_enabled]),body_digest = SHA256(CBOR(body)),bundle_digest = digest("dmsg/root-bundle-digest/2", [recipients_digest, body_digest])。CommitRoot 用持有 VaultUnlock 的当前活跃设备集合重算 recipients_digest,并要求批准设备本身在其中,任一不符则拒绝,因此已撤销或没有 VaultUnlock 的设备收不到新根,未批准设备也不能被塞进根包。

单份 bundle 最多 64,000 字节。通过 cloud upload 的 kind=root 保存:manifest 是原始 bundle 字节;必须存在的一块为确定性 CBOR 空数组 0x80。云端 ObjectRef 的 digest 等于 manifest SHA-256,客户端据此下载并按链上 bundle_digest 验证。

部署先更新认证状态读取端,再升级 user canister 与客户端;旧客户端读取 v3 根或 schema 5 账户时须升级。

初始化与换根顺序

  1. 验证账户与设备能力;用单独 op_id 预留根代次(ReserveRoot)。丢弃/过期预留可能产生代次间隙。
  2. 本地生成随机 VRK(换根任务连同候选根用 LDK 封存为本机记录 root-job,重试复用),按认证设备表封装给每台持有 VaultUnlock 的活跃设备,仅在链上恢复开启时查询并核对 COSE 恢复公钥后生成 IBE 恢复信封,生成唯一 bundle 字节。
  3. 上传计划与 upload_id 持久化;重试查询同一上传,复用计划和密文。上传并 finalize 后,下载 manifest 再核对 SHA-256。
  4. 执行唯一的 user CommitRoot CAS。上传成功不能替代链上提交成功。
  5. 读回认证承诺后启用新根:首次绑定时工作区才归属账户(此前没有内容);换根后,旧根经新根包的 previous 链读取。

全程没有链上密钥调用。只有根包接收者变化(新增或撤销持有 VaultUnlock 的设备,或为设备增减这项能力)、链上恢复开关变化和登录恢复会让 vault_write_state 变为 RekeyRequired,管理员换根即可;认证绑定和其他能力的变化只递增 security epoch。其他接收者下载当前根包、解开自己的信封即可读取。

全设备丢失恢复

2026-10-31:新账户默认关闭链上恢复。SetRecoveryPolicy { enabled, delay_ms } 由管理员设备批准,受 AccountControl 硬件批准政策约束。桌面安全设置只提交一次审批,批准后客户端自动换根;不要求用户另行操作根密钥。开启前必须仍有可解锁的授权设备。

模式变化取消待恢复、清除恢复派生资格并使已有根进入 RekeyRequired;没有结束的已授权派生先完成再改政策(返回 Pending)。关闭时 request_recovery、complete_recovery、derive_root 均拒绝;开启后须先提交包含恢复信封的根,才能申请恢复。日常 unlock_secret 仍是 query。旧根、已交付的密钥及已复制内容不能追回。

稳定存储新增可选 recovery_enabled:缺省保留旧账户的开启状态,认证叶仍为 schema 4,升级不批量改写;新账户或显式设置后为 schema 5,并必须包含布尔值。get_account 与认证叶须一致。以下恢复步骤仅适用于已开启的账户;关闭时即使找回登录也不能恢复全失后的内容。

客户端实现见 dmsg_client::recovery:申请封存在新设备的工作区,回复丢失时原样重发(同一申请不会重新开始等待);派生的批准与传输密钥种子按承诺的根摘要封存为 recover-root:<digest>,结果先按执行 ID 读取,原批准过期且认证时间与设备序号证明它不可能再执行时才重新批准;max_cycles 固定为 700 亿。完成后客户端解开恢复信封、沿 previous 链取得历史根,再换根。

  1. 新设备用绑定过的 II 账户登录,提交 request_recovery(account_id, RecoveryRequest { op_id, new_auth = caller, device, expires_at }, device_proof),设备 PoP 为 dmsg/recovery-device/v1。II 也丢了就不能恢复。
  2. 等待 recovery_delay_ms(默认 3 天,SetRecoveryDelay 可设 1–7 天)。等待期间已有设备从认证叶 pending_recovery_digest 看到申请,任意有效设备提交 DisputeRecovery { op_id } 即取消;用 RemoveAuth 解除发起申请的登录同样作废申请。没有再确认流程。
  3. 到期后 complete_recovery(account_id, op_id):替换全部设备与登录绑定,退役全部批准钥匙并关闭硬件批准政策,security_epoch +1,已有根置 RekeyRequired,记录 recovered_device = (device_id, generation)。
  4. 恢复设备取得 unlock_secret 完成本机绑定,然后 derive_root(DeriveRootRequest { generation, transport_public_key, max_cycles, approval }):只允许 recovered_device 对当前代次派生(同一 request_id 重试返回原结果,换根前也可凭新批准再次派生),COSE 派生该身份的 vetKey 并加密给传输公钥,客户端解密并核对 vetKey 后用它解开根包的 IBE 恢复信封,随即换根到下一代(只封装给自己)。换根后派生权消失。

恢复派生的批准域为 dmsg/derive-root/v1,命令为 [generation, transport_public_key, max_cycles];max_cycles 是客户端在批准中写定的上限。派生与正式认证共用账户政策的每日执行次数(默认 20,SetPolicy 最高 64)和 64 条执行保留窗口。开启链上恢复时,II 被盗且延迟期内所有设备都没响应仍可能导致内容泄露;关闭时拒绝这条恢复路径。