forgesworn/nsec-tree

GitHub: forgesworn/nsec-tree

基于单一主密钥确定性派生无限不可关联的 Nostr 层级子身份,支持用途标签、盲关联证明和助记词一键恢复。

Stars: 0 | Forks: 0

# nsec-tree **Nostr:** [`npub1mgvlrnf5hm9yf0n5mf9nqmvarhvxkc6remu5ec3vf8r0txqkuk7su0e7q2`](https://njump.me/npub1mgvlrnf5hm9yf0n5mf9nqmvarhvxkc6remu5ec3vf8r0txqkuk7su0e7q2) 确定性 Nostr 身份层级结构。一个主密钥,无限身份。 ``` npm install nsec-tree ``` 仅支持 ESM。零自定义加密——所有原语均来自 @noble/@scure。 ## 为什么使用 nsec-tree? NIP-06 标准化了基于助记词的密钥派生,但大多数客户端只展示一个主密钥。nsec-tree 为你提供了一个带有用途标签的身份树。 - **默认不可关联** — 没有观察者能证明两个子 npub 共属于同一个主密钥 - **可恢复** — 12 个单词即可重建你的整个身份树 - **带用途标签** — 人类可读的派生(`"social"`、`"commerce"`、`"trott:rider"`) - **可组合层级** — 建立类似 `work -> company:a -> signing` 的树结构 子密钥是普通的 Nostr 密钥对。不理解关联证明的客户端会将它们视为独立的身份。 这不仅仅是“从一个种子生成多个账户”。你可以为人设、组织、应用、环境和轮换替换密钥派生出结构化的子树: - `personal -> social -> main` - `personal -> social -> alt` - `work -> company:a -> payroll` - `work -> company:b -> ops` - `work -> company:b -> ops -> emergency` ## 快速开始 ### 从助记词开始(全新用户) ``` import { fromMnemonic, derive } from 'nsec-tree' const root = fromMnemonic('abandon abandon ... about') const social = derive(root, 'social') const commerce = derive(root, 'commerce') console.log(social.npub) // npub1... console.log(commerce.npub) // npub1... (different, unlinkable) root.destroy() ``` ### 从现有的 nsec 开始(现有用户) ``` import { fromNsec, derive } from 'nsec-tree/core' // no BIP deps const root = fromNsec('nsec1...') const throwaway = derive(root, 'throwaway', 42) ``` ### 构建层级 ``` import { fromMnemonic, deriveFromIdentity } from 'nsec-tree' import { derivePersona } from 'nsec-tree/persona' const root = fromMnemonic('abandon abandon ... about') const work = derivePersona(root, 'work') const companyA = deriveFromIdentity(work.identity, 'company:a') const payroll = deriveFromIdentity(companyA, 'payroll') const companyB = deriveFromIdentity(work.identity, 'company:b') const ops = deriveFromIdentity(companyB, 'ops') console.log(work.identity.npub) // master -> work console.log(payroll.npub) // master -> work -> company:a -> payroll console.log(ops.npub) // master -> work -> company:b -> ops ``` 每一级都是确定且隔离的。泄露 `company:a -> payroll` 不会暴露兄弟分支 `company:b -> ops`。 ### 证明所有权(关联证明) ``` import { createBlindProof, verifyProof } from 'nsec-tree/proof' const proof = createBlindProof(root, child) // Send proof to verifier... const valid = verifyProof(proof) // true ``` ### 将证明发布到 Nostr (NIP-78) ``` import { toUnsignedEvent } from 'nsec-tree/event' const unsigned = toUnsignedEvent(proof) // Sign with your Nostr library, then publish to relays ``` ## API ### `fromMnemonic(mnemonic, passphrase?)` 从 BIP-39 助记词创建一个 `TreeRoot`。在 `m/44'/1237'/727'/0'/0'` 路径派生树根。 ### `fromNsec(nsec)` 从 bech32 编码的 nsec 字符串或原始的 32 字节密钥创建一个 `TreeRoot`。中间的 HMAC 会将签名密钥与派生密钥分离。 ### `derive(root, purpose, index?)` 从 `TreeRoot` 派生一个子 `Identity`。返回 `{ nsec, npub, privateKey, publicKey, purpose, index }`。`index` 默认为 `0`。 ### `deriveFromIdentity(identity, purpose, index?)` 从任何现有的 `Identity` 派生子 `Identity`,支持任意深度的层级,例如 `work -> company:a -> payroll -> hot-wallet`。 ### `recover(root, purposes, scanRange?)` 扫描多个用途和索引,返回 `Map`。默认扫描范围:20(BIP-44 间隙限制)。 ### `zeroise(identity)` 将派生身份的私钥字节归零。 ### `createBlindProof(root, child)` BIP-340 Schnorr 证明,证明主密钥拥有该子密钥,但不泄露派生槽位。 ### `createFullProof(root, child)` 类似盲证明,但同时会揭示用途和索引。 ### `verifyProof(proof)` 验证 `LinkageProof`。返回 `boolean`。 ### `toUnsignedEvent(proof)` 将 `LinkageProof` 转换为未签名的 NIP-78 Kind 30078 Nostr 事件。应用程序需对其进行签名并发布。 ### `fromEvent(event)` 从 NIP-78 事件的标签中提取 `LinkageProof`。将结果传递给 `verifyProof()` 以检查其加密有效性。 ## 子路径导出 | 导入项 | 内容 | 依赖 BIP? | |--------|------|-----------| | `nsec-tree` | 完整 API | 是 | | `nsec-tree/core` | fromNsec, derive, recover, zeroise | 否 | | `nsec-tree/mnemonic` | fromMnemonic | 是 | | `nsec-tree/proof` | 关联证明 | 否 | | `nsec-tree/persona` | 人设派生,二级层级结构,恢复 | 否 | | `nsec-tree/event` | NIP-78 事件转换 (toUnsignedEvent, fromEvent) | 否 | | `nsec-tree/encoding` | NIP-19 bech32 助手 (encodeNsec, decodeNsec, encodeNpub, decodeNpub) | 否 | 如果你只需要基于 nsec 的派生,请使用 `nsec-tree/core`——它可以避免引入 BIP-32/39 依赖。 ## 工作原理 - **树根** 来自助记词(BIP-32 路径 `m/44'/1237'/727'/0'/0'`)或 nsec(中间 HMAC) - **子密钥:** `HMAC-SHA256(tree_root, "nsec-tree\0" || purpose || "\0" || index_be32)` - **关联证明:** 基于证明字符串的 BIP-340 Schnorr 签名 - **层级结构:** 任何派生的身份都可以通过 `deriveFromIdentity(...)` 成为子树的根 - 请参阅 `PROTOCOL.md` 获取包含测试向量的完整派生规范 ## 人设 (Personas) 人设是一个使用 `nostr:persona:{name}` 约定从你的主密钥派生出的具名 Nostr 身份。每个人设都有自己的密钥对——适合作为独立的 kind-0 配置文件——并且默认情况下与其他人设不可关联。 ### 派生人设 ``` import { fromMnemonic } from 'nsec-tree' import { derivePersona } from 'nsec-tree/persona' const root = fromMnemonic('abandon abandon ... about') const personal = derivePersona(root, 'personal') const bitcoiner = derivePersona(root, 'bitcoiner') const work = derivePersona(root, 'work') console.log(personal.identity.npub) // npub1... console.log(bitcoiner.identity.npub) // npub1... (different, unlinkable) ``` ### 二级层级结构 `deriveFromPersona` 在人设之下创建子身份。这非常适合用于群组签名密钥——每个群组都获得一个从人设(而非主密钥)派生的隔离密钥对。 ``` import { deriveFromPersona } from 'nsec-tree/persona' const meetup = deriveFromPersona(bitcoiner, 'canary:group:local-meetup') const conference = deriveFromPersona(bitcoiner, 'canary:group:btcpp-2026') ``` 层级为:**master → persona → group identity**。泄露群组密钥不会暴露人设密钥,而泄露人设也不会暴露主密钥。 ### 任意深度层级 `deriveFromPersona(...)` 只是一个便捷的辅助函数。更通用的 `deriveFromIdentity(...)` API 允许你根据需要进行无限深度的分支。 ``` import { deriveFromIdentity } from 'nsec-tree' const companyA = deriveFromIdentity(work.identity, 'company:a') const payroll = deriveFromIdentity(companyA, 'payroll') const hotWallet = deriveFromIdentity(payroll, 'hot-wallet') const companyB = deriveFromIdentity(work.identity, 'company:b') const ops = deriveFromIdentity(companyB, 'ops') ``` 这会生成如下路径: - `master -> work -> company:a -> payroll -> hot-wallet` - `master -> work -> company:b -> ops` 这就是 nsec-tree 不仅仅是一个“多账户”库的地方。它让你能够直接在确定性密钥中建模真实的运营结构:人员、团队、客户、设备、环境或服务。一次备份即可重建整个树。 ### 恢复 `recoverPersonas` 通过扫描已知名称列表,从助记词重新派生所有人设。未提供名称时,它将使用 `DEFAULT_PERSONA_NAMES`: `personal`、`bitcoiner`、`work`、`social`、`anonymous`。 ``` import { recoverPersonas, DEFAULT_PERSONA_NAMES } from 'nsec-tree/persona' const root = fromMnemonic('abandon abandon ... about') const recovered = recoverPersonas(root, DEFAULT_PERSONA_NAMES, 2) for (const [name, personas] of recovered) { console.log(`${name}: ${personas.length} indices scanned`) } ``` 恢复是确定性的——相同的助记词总会生成相同的人设。 你只需知道(或约定)要扫描的人设名称即可。 同样的原则也适用于更深的树:当分支名称和轮换约定保持稳定时,恢复非常容易。你使用的层级结构越多,命名规范就越重要。 ### 轮换 如果某个人设被泄露,可以在更高的索引处派生它: ``` const bitcoinerV0 = derivePersona(root, 'bitcoiner', 0) // compromised const bitcoinerV1 = derivePersona(root, 'bitcoiner', 1) // replacement ``` 使用盲关联证明来证明连续性——新的人设由同一个主密钥控制——而不暴露使用了哪个派生槽位: ``` import { createBlindProof, verifyProof } from 'nsec-tree/proof' const proof = createBlindProof(root, bitcoinerV1.identity) verifyProof(proof) // true — same master, new identity ``` ### 生态系统集成 nsec-tree 的人设旨在与其他库组合使用: - **canary-kit** — `deriveFromPersona(persona, 'canary:group:...')` 生成 canary-kit 用于加密位置信标、紧急警报和存活检查的群组签名密钥。 - **spoken-token** — 将口头验证令牌绑定到人设的公钥,以便在语音通话中进行身份确认。 ### 安全模型 不同级别的泄露会产生不同的影响范围: | 被泄露内容 | 影响 | 缓解措施 | |-------------|--------|------------| | 群组密钥 | 暴露一个群组身份 | 轮换群组密钥(新 purpose 或 index) | | 人设密钥 | 可派生该人设下的所有群组密钥 | 轮换人设(递增 index),发布关联证明 | | 主密钥 | 可派生所有的人设和群组密钥 | 轮换助记词,迁移所有身份 | 二级层级结构确保群组密钥的泄露不会升级到人设,而人设的泄露也不会升级到主密钥。 ## 示例 [`examples/`](examples/) 目录中包含可运行的示例: | 示例 | 展示内容 | |---------|---------------| | [basic-derivation.ts](examples/basic-derivation.ts) | 派生 social + commerce 身份 | | [existing-nsec.ts](examples/existing-nsec.ts) | 使用现有的 nsec,无需助记词 | | [recovery.ts](examples/recovery.ts) | 从助记词恢复所有身份 | | [linkage-proofs.ts](examples/linkage-proofs.ts) | 盲证明和完全所有权证明 | | [bot-fleet.ts](examples/bot-fleet.ts) | 一个种子生成 10 个机器人 | | [nostr-event-signing.ts](examples/nostr-event-signing.ts) | 使用 nostr-tools 签名 kind-1 事件 | | [persona.ts](examples/persona.ts) | 人设派生、深层层级、轮换、恢复 | 运行任何示例:`npx tsx examples/.ts` ## 延伸阅读 - [常见问题](docs/faq.md) — 常见问题和异议 - [对比](docs/comparison.md) — nsec-tree 对比 NIP-06、NIP-26、已链接的子密钥 - [NIP 草案](docs/NIP-XXXX.md) — NIP 格式的正式规范 - [PROTOCOL.md](PROTOCOL.md) — 包含测试向量的完整派生规范 ## 安全 - **零自定义加密** — HMAC-SHA256 (RFC 2104)、BIP-32、BIP-340 Schnorr。全部来自 @noble/@scure。 - **默认不可关联** — 仅通过关联证明进行选择性披露 - **数据清零** — 完成后调用 `root.destroy()` 和 `zeroise(identity)`。如果你忘记清理,`FinalizationRegistry` 会提供尽力而为的清理。 - **主密钥泄露** — 如果主密钥泄漏,所有子密钥都将是可派生的。请像保护任何 nsec 一样严密保护它。 - 完整威胁模型请参阅 `PROTOCOL.md` ## 许可证 MIT 如果你觉得 nsec-tree 有用,可以考虑打赏: - **Lightning:** `thedonkey@strike.me` - **Nostr zaps:** `npub1mgvlrnf5hm9yf0n5mf9nqmvarhvxkc6remu5ec3vf8r0txqkuk7su0e7q2`
标签:BIP39, CMS安全, ESM, GNU通用公共许可证, HD Wallet, JavaScript, MITM代理, NIP-06, Node.js, Nostr, npub, nsec, nsec-tree, TypeScript, Web3, 不可链接性, 分层确定性钱包, 助记词, 区块链, 匿名性, 去中心化身份, 子身份管理, 安全插件, 密码学, 密钥派生, 手动系统调用, 数字身份, 网络安全, 自动化攻击, 账户恢复, 身份隔离, 隐私保护, 零信任定制加密