@serfab/cadre-core 1.16.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. package/README.md +4 -3
  2. package/dist/bootstrap-peer-store-file.js +1 -1
  3. package/dist/bootstrap-peer-store.d.ts +1 -1
  4. package/dist/bootstrap-peer-store.js +4 -4
  5. package/dist/cadre-invite-protocol.d.ts +81 -142
  6. package/dist/cadre-invite-protocol.js +168 -458
  7. package/dist/cadre-invite-protocol.js.map +1 -1
  8. package/dist/cadre-join-request.d.ts +15 -2
  9. package/dist/cadre-join-request.js +22 -3
  10. package/dist/cadre-join-request.js.map +1 -1
  11. package/dist/cadre-node.d.ts +1089 -475
  12. package/dist/cadre-node.js +2170 -1037
  13. package/dist/cadre-node.js.map +1 -1
  14. package/dist/control-authorization.d.ts +36 -14
  15. package/dist/control-authorization.js +20 -3
  16. package/dist/control-authorization.js.map +1 -1
  17. package/dist/control-cohort.d.ts +88 -32
  18. package/dist/control-cohort.js +91 -33
  19. package/dist/control-cohort.js.map +1 -1
  20. package/dist/control-database.d.ts +856 -318
  21. package/dist/control-database.js +1422 -701
  22. package/dist/control-database.js.map +1 -1
  23. package/dist/control-formation-recorder.d.ts +46 -43
  24. package/dist/control-formation-recorder.js +77 -78
  25. package/dist/control-formation-recorder.js.map +1 -1
  26. package/dist/control-protocol-guard.d.ts +11 -4
  27. package/dist/control-protocol-guard.js +20 -5
  28. package/dist/control-protocol-guard.js.map +1 -1
  29. package/dist/control-schema-version.d.ts +97 -0
  30. package/dist/control-schema-version.js +225 -0
  31. package/dist/control-schema-version.js.map +1 -0
  32. package/dist/control-schema.d.ts +45 -5
  33. package/dist/control-schema.js +262 -78
  34. package/dist/control-schema.js.map +1 -1
  35. package/dist/control-write-retry.d.ts +45 -10
  36. package/dist/control-write-retry.js +92 -22
  37. package/dist/control-write-retry.js.map +1 -1
  38. package/dist/delegate-admission.d.ts +13 -0
  39. package/dist/delegate-admission.js +27 -0
  40. package/dist/delegate-admission.js.map +1 -1
  41. package/dist/ed25519-key.d.ts +10 -7
  42. package/dist/ed25519-key.js +23 -20
  43. package/dist/ed25519-key.js.map +1 -1
  44. package/dist/edge-node/config.d.ts +122 -0
  45. package/dist/edge-node/config.js +199 -0
  46. package/dist/edge-node/config.js.map +1 -0
  47. package/dist/edge-node/dissolve.d.ts +95 -0
  48. package/dist/edge-node/dissolve.js +184 -0
  49. package/dist/edge-node/dissolve.js.map +1 -0
  50. package/dist/edge-node/edge-join.d.ts +44 -0
  51. package/dist/edge-node/edge-join.js +227 -0
  52. package/dist/edge-node/edge-join.js.map +1 -0
  53. package/dist/edge-node/edge-node.d.ts +295 -0
  54. package/dist/edge-node/edge-node.js +704 -0
  55. package/dist/edge-node/edge-node.js.map +1 -0
  56. package/dist/edge-node/index.d.ts +18 -0
  57. package/dist/edge-node/index.js +18 -0
  58. package/dist/edge-node/index.js.map +1 -0
  59. package/dist/edge-node/join-credential.d.ts +11 -0
  60. package/dist/edge-node/join-credential.js +11 -0
  61. package/dist/edge-node/join-credential.js.map +1 -0
  62. package/dist/edge-node/lazy-raw-storage.d.ts +40 -0
  63. package/dist/edge-node/lazy-raw-storage.js +134 -0
  64. package/dist/edge-node/lazy-raw-storage.js.map +1 -0
  65. package/dist/edge-node/node-local.d.ts +60 -0
  66. package/dist/edge-node/node-local.js +58 -0
  67. package/dist/edge-node/node-local.js.map +1 -0
  68. package/dist/edge-node/options.d.ts +83 -0
  69. package/dist/edge-node/options.js +110 -0
  70. package/dist/edge-node/options.js.map +1 -0
  71. package/dist/edge-node/owner-grant.d.ts +41 -0
  72. package/dist/edge-node/owner-grant.js +38 -0
  73. package/dist/edge-node/owner-grant.js.map +1 -0
  74. package/dist/edge-node/platform.d.ts +140 -0
  75. package/dist/edge-node/platform.js +12 -0
  76. package/dist/edge-node/platform.js.map +1 -0
  77. package/dist/edge-node/strand-move.d.ts +115 -0
  78. package/dist/edge-node/strand-move.js +223 -0
  79. package/dist/edge-node/strand-move.js.map +1 -0
  80. package/dist/edge-node/strands.d.ts +28 -0
  81. package/dist/edge-node/strands.js +60 -0
  82. package/dist/edge-node/strands.js.map +1 -0
  83. package/dist/enrolled-machine-store.d.ts +3 -3
  84. package/dist/enrolled-machine-store.js +3 -3
  85. package/dist/formation-token.d.ts +48 -0
  86. package/dist/formation-token.js +26 -0
  87. package/dist/formation-token.js.map +1 -0
  88. package/dist/index.d.ts +27 -17
  89. package/dist/index.js +39 -17
  90. package/dist/index.js.map +1 -1
  91. package/dist/joined-strand-store.d.ts +29 -12
  92. package/dist/joined-strand-store.js +66 -27
  93. package/dist/joined-strand-store.js.map +1 -1
  94. package/dist/key-store.d.ts +9 -4
  95. package/dist/key-store.js +9 -4
  96. package/dist/key-store.js.map +1 -1
  97. package/dist/link-budget.d.ts +9 -7
  98. package/dist/link-budget.js +9 -7
  99. package/dist/link-budget.js.map +1 -1
  100. package/dist/machine-removal.d.ts +19 -39
  101. package/dist/machine-removal.js +24 -98
  102. package/dist/machine-removal.js.map +1 -1
  103. package/dist/member-redemption.d.ts +256 -0
  104. package/dist/member-redemption.js +490 -0
  105. package/dist/member-redemption.js.map +1 -0
  106. package/dist/membership-view.d.ts +91 -0
  107. package/dist/membership-view.js +110 -0
  108. package/dist/membership-view.js.map +1 -0
  109. package/dist/owner-anchor-sync.d.ts +4 -4
  110. package/dist/owner-anchor-sync.js +16 -15
  111. package/dist/owner-anchor-sync.js.map +1 -1
  112. package/dist/owner-grant-protocol.d.ts +251 -0
  113. package/dist/owner-grant-protocol.js +344 -0
  114. package/dist/owner-grant-protocol.js.map +1 -0
  115. package/dist/owner-key-export.d.ts +70 -0
  116. package/dist/owner-key-export.js +279 -0
  117. package/dist/owner-key-export.js.map +1 -0
  118. package/dist/owner-key-flows.d.ts +55 -0
  119. package/dist/owner-key-flows.js +60 -0
  120. package/dist/owner-key-flows.js.map +1 -0
  121. package/dist/owner-key-store.d.ts +31 -0
  122. package/dist/owner-key-store.js +81 -0
  123. package/dist/owner-key-store.js.map +1 -0
  124. package/dist/owner-keys.d.ts +78 -0
  125. package/dist/owner-keys.js +133 -0
  126. package/dist/owner-keys.js.map +1 -0
  127. package/dist/owner-signer.d.ts +54 -0
  128. package/dist/owner-signer.js +51 -0
  129. package/dist/owner-signer.js.map +1 -0
  130. package/dist/peer-addr-book.d.ts +10 -1
  131. package/dist/peer-addr-book.js +4 -3
  132. package/dist/peer-addr-book.js.map +1 -1
  133. package/dist/peer-authorization.d.ts +83 -18
  134. package/dist/peer-authorization.js +123 -53
  135. package/dist/peer-authorization.js.map +1 -1
  136. package/dist/peer-join-backfill.d.ts +89 -20
  137. package/dist/peer-join-backfill.js +265 -108
  138. package/dist/peer-join-backfill.js.map +1 -1
  139. package/dist/pending-join-runner.d.ts +48 -18
  140. package/dist/pending-join-runner.js +74 -56
  141. package/dist/pending-join-runner.js.map +1 -1
  142. package/dist/provider/rent-node.js +1 -1
  143. package/dist/provider/rent-node.js.map +1 -1
  144. package/dist/seed-bootstrap.d.ts +162 -133
  145. package/dist/seed-bootstrap.js +300 -283
  146. package/dist/seed-bootstrap.js.map +1 -1
  147. package/dist/seed-trust-policy.js +4 -4
  148. package/dist/strand-addr-protocol.d.ts +4 -1
  149. package/dist/strand-addr-protocol.js +8 -2
  150. package/dist/strand-addr-protocol.js.map +1 -1
  151. package/dist/strand-cohort.d.ts +3 -0
  152. package/dist/strand-cohort.js.map +1 -1
  153. package/dist/strand-database.d.ts +24 -2
  154. package/dist/strand-database.js +29 -6
  155. package/dist/strand-database.js.map +1 -1
  156. package/dist/strand-first-sync-gate.d.ts +176 -88
  157. package/dist/strand-first-sync-gate.js +288 -121
  158. package/dist/strand-first-sync-gate.js.map +1 -1
  159. package/dist/strand-formation-deadlines.d.ts +9 -9
  160. package/dist/strand-formation-deadlines.js +9 -9
  161. package/dist/strand-formation-manager.d.ts +23 -17
  162. package/dist/strand-formation-manager.js +46 -44
  163. package/dist/strand-formation-manager.js.map +1 -1
  164. package/dist/strand-formation-protocol.d.ts +5 -8
  165. package/dist/strand-formation-protocol.js +101 -16
  166. package/dist/strand-formation-protocol.js.map +1 -1
  167. package/dist/strand-instance-manager.d.ts +27 -18
  168. package/dist/strand-instance-manager.js +119 -50
  169. package/dist/strand-instance-manager.js.map +1 -1
  170. package/dist/strand-membership-reconciler.d.ts +16 -13
  171. package/dist/strand-membership-reconciler.js +30 -22
  172. package/dist/strand-membership-reconciler.js.map +1 -1
  173. package/dist/strand-membership-writer.d.ts +45 -22
  174. package/dist/strand-membership-writer.js +226 -131
  175. package/dist/strand-membership-writer.js.map +1 -1
  176. package/dist/strand-move.d.ts +6 -5
  177. package/dist/strand-move.js +36 -32
  178. package/dist/strand-move.js.map +1 -1
  179. package/dist/strand-peer-redial.js +2 -3
  180. package/dist/strand-peer-redial.js.map +1 -1
  181. package/dist/strand-revocation-enforcer.d.ts +134 -60
  182. package/dist/strand-revocation-enforcer.js +144 -71
  183. package/dist/strand-revocation-enforcer.js.map +1 -1
  184. package/dist/strand-solicitation.d.ts +50 -39
  185. package/dist/strand-solicitation.js +10 -3
  186. package/dist/strand-solicitation.js.map +1 -1
  187. package/dist/strand-watcher.d.ts +80 -13
  188. package/dist/strand-watcher.js +152 -48
  189. package/dist/strand-watcher.js.map +1 -1
  190. package/dist/table-change-watch.d.ts +63 -0
  191. package/dist/table-change-watch.js +209 -0
  192. package/dist/table-change-watch.js.map +1 -0
  193. package/dist/timeout-scheduler.d.ts +4 -3
  194. package/dist/timeout-scheduler.js +4 -3
  195. package/dist/timeout-scheduler.js.map +1 -1
  196. package/dist/types.d.ts +374 -150
  197. package/dist/types.js +4 -4
  198. package/dist/types.js.map +1 -1
  199. package/package.json +17 -8
  200. package/src/bootstrap-peer-store-file.ts +44 -44
  201. package/src/bootstrap-peer-store.ts +214 -214
  202. package/src/cadre-invite-protocol.ts +740 -1052
  203. package/src/cadre-join-request.ts +24 -3
  204. package/src/cadre-node.ts +2402 -1079
  205. package/src/control-authorization.ts +47 -14
  206. package/src/control-cohort.ts +204 -128
  207. package/src/control-database.ts +1749 -820
  208. package/src/control-formation-recorder.ts +359 -353
  209. package/src/control-protocol-guard.ts +23 -6
  210. package/src/control-schema-version.ts +301 -0
  211. package/src/control-schema.ts +273 -78
  212. package/src/control-write-retry.ts +641 -567
  213. package/src/delegate-admission.ts +351 -314
  214. package/src/ed25519-key.ts +174 -171
  215. package/src/edge-node/config.ts +277 -0
  216. package/src/edge-node/dissolve.ts +251 -0
  217. package/src/edge-node/edge-join.ts +319 -0
  218. package/src/edge-node/edge-node.ts +1045 -0
  219. package/src/edge-node/index.ts +59 -0
  220. package/src/edge-node/join-credential.ts +16 -0
  221. package/src/edge-node/lazy-raw-storage.ts +178 -0
  222. package/src/edge-node/node-local.ts +84 -0
  223. package/src/edge-node/options.ts +179 -0
  224. package/src/edge-node/owner-grant.ts +63 -0
  225. package/src/edge-node/platform.ts +151 -0
  226. package/src/edge-node/strand-move.ts +270 -0
  227. package/src/edge-node/strands.ts +72 -0
  228. package/src/enrolled-machine-store.ts +3 -3
  229. package/src/formation-token.ts +63 -0
  230. package/src/index.ts +899 -789
  231. package/src/joined-strand-store.ts +73 -28
  232. package/src/key-store.ts +10 -4
  233. package/src/link-budget.ts +9 -7
  234. package/src/machine-removal.ts +26 -111
  235. package/src/member-redemption.ts +649 -0
  236. package/src/membership-view.ts +144 -0
  237. package/src/owner-anchor-sync.ts +19 -18
  238. package/src/owner-grant-protocol.ts +547 -0
  239. package/src/owner-key-export.ts +348 -0
  240. package/src/owner-key-flows.ts +109 -0
  241. package/src/owner-key-store.ts +85 -0
  242. package/src/owner-keys.ts +178 -0
  243. package/src/owner-signer.ts +94 -0
  244. package/src/peer-addr-book.ts +4 -3
  245. package/src/peer-authorization.ts +558 -488
  246. package/src/peer-join-backfill.ts +971 -761
  247. package/src/pending-join-runner.ts +76 -53
  248. package/src/provider/rent-node.ts +1 -1
  249. package/src/seed-bootstrap.ts +343 -349
  250. package/src/seed-trust-policy.ts +4 -4
  251. package/src/strand-addr-protocol.ts +547 -538
  252. package/src/strand-cohort.ts +4 -0
  253. package/src/strand-database.ts +352 -319
  254. package/src/strand-first-sync-gate.ts +334 -133
  255. package/src/strand-formation-deadlines.ts +227 -227
  256. package/src/strand-formation-manager.ts +731 -725
  257. package/src/strand-formation-protocol.ts +122 -24
  258. package/src/strand-instance-manager.ts +2092 -2026
  259. package/src/strand-membership-reconciler.ts +33 -25
  260. package/src/strand-membership-writer.ts +2093 -1974
  261. package/src/strand-move.ts +50 -38
  262. package/src/strand-peer-redial.ts +2 -3
  263. package/src/strand-revocation-enforcer.ts +740 -654
  264. package/src/strand-solicitation.ts +61 -34
  265. package/src/strand-watcher.ts +175 -56
  266. package/src/table-change-watch.ts +285 -0
  267. package/src/timeout-scheduler.ts +29 -28
  268. package/src/types.ts +391 -152
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Owner-key export and import: the text (or QR code) a user keeps as a backup of an owner key or
3
+ * carries to another device (`docs/architecture.md` → Owner Keys → Exporting and importing an
4
+ * owner key). Whoever holds a plain export is an owner.
5
+ *
6
+ * - Plain: {@link OWNER_KEY_EXPORT_PREFIX}, then base64url of the canonical JSON
7
+ * `{ key, label?, v: 1 }`, `key` being the 32-byte Ed25519 seed in base64url.
8
+ * - Locked: {@link LOCKED_OWNER_KEY_EXPORT_PREFIX}, then base64url of the canonical JSON
9
+ * `{ kdf: { N, name: 'scrypt', p, r, salt }, nonce, sealed, v: 1 }`, `sealed` being the plain
10
+ * JSON under XChaCha20-Poly1305 with the 32-byte key scrypt derives from the passphrase. The
11
+ * scrypt parameters ride in the text, so a later default still reads an old export.
12
+ *
13
+ * No error carries key material. `@noble/hashes` and `@noble/ciphers` are pure JS and random
14
+ * bytes come from `crypto.getRandomValues`, so this loads on React Native and in the browser.
15
+ */
16
+ import { xchacha20poly1305 } from '@noble/ciphers/chacha.js';
17
+ import { scryptAsync } from '@noble/hashes/scrypt.js';
18
+ import { randomBytes } from '@noble/hashes/utils.js';
19
+ import { fromString as uint8ArrayFromString, toString as uint8ArrayToString } from 'uint8arrays';
20
+ import { canonicalJson } from './canonical-json.js';
21
+ import { CADRE_INVITATION_PREFIX, CADRE_JOIN_REQUEST_PREFIX, OWNER_GRANT_PREFIX, OWNER_KEY_EXPORT_STEM } from './cadre-join-request.js';
22
+ import { NODE_CLAIM_PAYLOAD_PREFIX } from './node-claim-payload.js';
23
+ import { ed25519PublicKeyFromPrivate, requireEd25519PrivateKeyB64 } from './ed25519-key.js';
24
+ /** Version 1 of the plain export. */
25
+ export const OWNER_KEY_EXPORT_PREFIX = `${OWNER_KEY_EXPORT_STEM}:1.`;
26
+ /** Version 1 of the passphrase-locked export. */
27
+ export const LOCKED_OWNER_KEY_EXPORT_PREFIX = `${OWNER_KEY_EXPORT_STEM}-locked:1.`;
28
+ /** Longest label an owner key carries, the user's name for it ("phone", "paper backup"): in an export and on its `OwnerKey` row (the control schema's `LabelLength` check). */
29
+ export const OWNER_KEY_MAX_LABEL = 64;
30
+ /**
31
+ * The encoder's scrypt cost: about 140 ms and 64 MiB on Node 24 (x64, measured with
32
+ * `scryptAsync`).
33
+ * NOTE: unmeasured on Hermes; if a device run shows locking or unlocking is slow, lower N for the
34
+ * locked form or move the KDF to native.
35
+ */
36
+ const DEFAULT_SCRYPT_COST = { N: 2 ** 16, r: 8, p: 1 };
37
+ /**
38
+ * The most scrypt work the decoder runs, checked before deriving so a hostile file cannot keep a
39
+ * device busy for minutes: N at most 2^20 and r·p at most 8, sixteen times the default's work.
40
+ * At the cap scrypt takes about 2.5 s and 1 GiB on Node 24.
41
+ * NOTE: unmeasured on Hermes; if a device run shows the cap runs for minutes or exhausts memory,
42
+ * lower these two.
43
+ */
44
+ const MAX_SCRYPT_N = 2 ** 20;
45
+ const MAX_SCRYPT_R_TIMES_P = 8;
46
+ /** noble's `maxmem` for {@link MAX_SCRYPT_N}: its default (1 GiB + 1 KiB) is below what the cap itself needs, `128·r·(N + p + 1)` bytes. */
47
+ const SCRYPT_MAXMEM = 128 * MAX_SCRYPT_R_TIMES_P * (MAX_SCRYPT_N + MAX_SCRYPT_R_TIMES_P + 1);
48
+ const SCRYPT_SALT_BYTES = 16;
49
+ const MAX_SCRYPT_SALT_BYTES = 64;
50
+ const XCHACHA_NONCE_BYTES = 24;
51
+ const POLY1305_TAG_BYTES = 16;
52
+ /** A sealed plain export is under 200 bytes; this bounds what a hostile file makes the decoder decrypt. */
53
+ const MAX_SEALED_BYTES = 1024;
54
+ /** Every refusal from this module. The message is for the user and never includes key material. */
55
+ export class OwnerKeyExportError extends Error {
56
+ constructor(code, message, options) {
57
+ super(message, options);
58
+ this.name = 'OwnerKeyExportError';
59
+ this.code = code;
60
+ }
61
+ }
62
+ /**
63
+ * Encode an owner key as text, locked under `passphrase` when one is given.
64
+ *
65
+ * @throws {OwnerKeyExportError} `malformed` for a key that is not a 32-byte seed, an over-long
66
+ * label or an empty passphrase.
67
+ */
68
+ export async function encodeOwnerKeyExport(input) {
69
+ const plainJson = plainExportJson(input.privateKeyB64, input.label);
70
+ if (input.passphrase === undefined) {
71
+ return withPrefix(OWNER_KEY_EXPORT_PREFIX, plainJson);
72
+ }
73
+ return withPrefix(LOCKED_OWNER_KEY_EXPORT_PREFIX, await lockedExportJson(plainJson, input.passphrase));
74
+ }
75
+ /**
76
+ * Read owner-key text without a passphrase: a plain export's key, or `{ locked: true }` for a
77
+ * locked one whose envelope is well formed and whose scrypt cost is within the cap, so an app
78
+ * refuses a hostile file before asking for the passphrase.
79
+ *
80
+ * @throws {OwnerKeyExportError} `not-an-owner-key` (naming an invitation, an owner grant, a join
81
+ * request or a join code), `malformed` or `cost-too-high`.
82
+ */
83
+ export function decodeOwnerKeyExport(text) {
84
+ const trimmed = text.trim();
85
+ if (exportPrefix(trimmed) === LOCKED_OWNER_KEY_EXPORT_PREFIX) {
86
+ lockedEnvelope(decodeBody(trimmed, LOCKED_OWNER_KEY_EXPORT_PREFIX));
87
+ return { locked: true };
88
+ }
89
+ return { locked: false, ...exportContents(decodeBody(trimmed, OWNER_KEY_EXPORT_PREFIX)) };
90
+ }
91
+ /**
92
+ * Read owner-key text with its passphrase. A plain export is read as {@link decodeOwnerKeyExport}
93
+ * reads it, the passphrase unused.
94
+ *
95
+ * @throws {OwnerKeyExportError} as {@link decodeOwnerKeyExport}, and `wrong-passphrase` when the
96
+ * passphrase does not open it or the sealed part, nonce or scrypt parameters were altered.
97
+ */
98
+ export async function unlockOwnerKeyExport(text, passphrase) {
99
+ const trimmed = text.trim();
100
+ const prefix = exportPrefix(trimmed);
101
+ if (prefix === OWNER_KEY_EXPORT_PREFIX) {
102
+ return exportContents(decodeBody(trimmed, prefix));
103
+ }
104
+ const envelope = lockedEnvelope(decodeBody(trimmed, prefix));
105
+ const key = await deriveKey(passphrase, envelope, envelope.salt);
106
+ let plain;
107
+ try {
108
+ plain = xchacha20poly1305(key, envelope.nonce).decrypt(envelope.sealed);
109
+ }
110
+ catch (error) {
111
+ throw new OwnerKeyExportError('wrong-passphrase', 'The passphrase does not unlock this owner key, or the text was altered', { cause: error });
112
+ }
113
+ return exportContents(parseJsonObject(new TextDecoder().decode(plain)));
114
+ }
115
+ // ── Encoding ─────────────────────────────────────────────────────────────────
116
+ function plainExportJson(privateKeyB64, label) {
117
+ const key = requireKey(privateKeyB64);
118
+ const trimmedLabel = label?.trim();
119
+ return canonicalJson({ key, label: trimmedLabel ? requireLabel(trimmedLabel) : undefined, v: 1 });
120
+ }
121
+ async function lockedExportJson(plainJson, passphrase) {
122
+ if (passphrase.length === 0) {
123
+ throw new OwnerKeyExportError('malformed', 'A passphrase must not be empty; leave it out for an unlocked export');
124
+ }
125
+ const salt = randomBytes(SCRYPT_SALT_BYTES);
126
+ const nonce = randomBytes(XCHACHA_NONCE_BYTES);
127
+ const key = await deriveKey(passphrase, DEFAULT_SCRYPT_COST, salt);
128
+ const sealed = xchacha20poly1305(key, nonce).encrypt(new TextEncoder().encode(plainJson));
129
+ return canonicalJson({
130
+ kdf: { ...DEFAULT_SCRYPT_COST, name: 'scrypt', salt: toBase64url(salt) },
131
+ nonce: toBase64url(nonce),
132
+ sealed: toBase64url(sealed),
133
+ v: 1,
134
+ });
135
+ }
136
+ function withPrefix(prefix, json) {
137
+ return `${prefix}${toBase64url(new TextEncoder().encode(json))}`;
138
+ }
139
+ /** scrypt's async form yields to the event loop every few milliseconds, so a phone's UI keeps drawing while it runs. */
140
+ function deriveKey(passphrase, cost, salt) {
141
+ // NFKC, so one passphrase typed on keyboards that compose its characters differently derives one key.
142
+ const password = new TextEncoder().encode(passphrase.normalize('NFKC'));
143
+ return scryptAsync(password, salt, { N: cost.N, r: cost.r, p: cost.p, dkLen: 32, maxmem: SCRYPT_MAXMEM });
144
+ }
145
+ // ── Decoding ─────────────────────────────────────────────────────────────────
146
+ /** Which of the two prefixes `text` starts with; refuses, by name where it can, anything else. */
147
+ function exportPrefix(text) {
148
+ if (text.startsWith(LOCKED_OWNER_KEY_EXPORT_PREFIX))
149
+ return LOCKED_OWNER_KEY_EXPORT_PREFIX;
150
+ if (text.startsWith(OWNER_KEY_EXPORT_PREFIX))
151
+ return OWNER_KEY_EXPORT_PREFIX;
152
+ if (text.startsWith(OWNER_KEY_EXPORT_STEM)) {
153
+ throw new OwnerKeyExportError('malformed', 'This owner key was exported by a newer version of the app; update this app to import it');
154
+ }
155
+ throw new OwnerKeyExportError('not-an-owner-key', notAnOwnerKeyMessage(text));
156
+ }
157
+ /** No prefix of the text is echoed: whatever was pasted may be a key in some other form. */
158
+ function notAnOwnerKeyMessage(text) {
159
+ if (text.startsWith(CADRE_INVITATION_PREFIX)) {
160
+ return 'This is a cadre invitation, not an owner key; paste it where the app asks for an invitation';
161
+ }
162
+ if (text.startsWith(OWNER_GRANT_PREFIX)) {
163
+ return 'This is an owner grant, not an owner key; paste it where the app asks for an owner grant';
164
+ }
165
+ if (text.startsWith(CADRE_JOIN_REQUEST_PREFIX)) {
166
+ return 'This is a join request, not an owner key; paste it into the app that manages your cadre';
167
+ }
168
+ if (text.startsWith(NODE_CLAIM_PAYLOAD_PREFIX)) {
169
+ return 'This is a machine\'s join code, not an owner key; scan it where the app adds a machine';
170
+ }
171
+ return `An owner key must start with "${OWNER_KEY_EXPORT_PREFIX}" or "${LOCKED_OWNER_KEY_EXPORT_PREFIX}"`;
172
+ }
173
+ function decodeBody(text, prefix) {
174
+ let json;
175
+ try {
176
+ json = new TextDecoder().decode(uint8ArrayFromString(text.slice(prefix.length), 'base64url'));
177
+ }
178
+ catch (error) {
179
+ throw new OwnerKeyExportError('malformed', 'An owner key export must carry base64url after its prefix', { cause: error });
180
+ }
181
+ return parseJsonObject(json);
182
+ }
183
+ /** A version-1 JSON object, the shape of both the export body and a locked export's sealed plaintext. */
184
+ function parseJsonObject(json) {
185
+ let parsed;
186
+ try {
187
+ parsed = JSON.parse(json);
188
+ }
189
+ catch {
190
+ // No cause: JSON.parse's message can quote the input, which may hold the key.
191
+ throw new OwnerKeyExportError('malformed', 'An owner key export must carry JSON after its prefix');
192
+ }
193
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
194
+ throw new OwnerKeyExportError('malformed', 'An owner key export must decode to a JSON object');
195
+ }
196
+ const record = parsed;
197
+ if (record.v !== 1) {
198
+ throw new OwnerKeyExportError('malformed', `An owner key export must be version 1 (this one says ${JSON.stringify(record.v)})`);
199
+ }
200
+ return record;
201
+ }
202
+ function exportContents(record) {
203
+ const { key, label } = record;
204
+ if (typeof key !== 'string') {
205
+ throw new OwnerKeyExportError('malformed', 'An owner key export must carry its key');
206
+ }
207
+ if (label !== undefined && (typeof label !== 'string' || label.trim().length === 0)) {
208
+ throw new OwnerKeyExportError('malformed', 'An owner key export\'s label must be non-empty text');
209
+ }
210
+ const privateKeyB64 = requireKey(key);
211
+ return {
212
+ privateKeyB64,
213
+ publicKey: ed25519PublicKeyFromPrivate(privateKeyB64),
214
+ ...(label === undefined ? {} : { label: requireLabel(label.trim()) }),
215
+ };
216
+ }
217
+ function lockedEnvelope(record) {
218
+ const { kdf, nonce, sealed } = record;
219
+ if (typeof kdf !== 'object' || kdf === null || Array.isArray(kdf)) {
220
+ throw new OwnerKeyExportError('malformed', 'A locked owner key must name its key derivation');
221
+ }
222
+ const { name, N, r, p, salt } = kdf;
223
+ if (name !== 'scrypt') {
224
+ throw new OwnerKeyExportError('malformed', `A locked owner key must use scrypt (this one names ${JSON.stringify(name)})`);
225
+ }
226
+ const cost = requireScryptCost(N, r, p);
227
+ return {
228
+ ...cost,
229
+ salt: requireBytes(salt, 'salt', 1, MAX_SCRYPT_SALT_BYTES),
230
+ nonce: requireBytes(nonce, 'nonce', XCHACHA_NONCE_BYTES, XCHACHA_NONCE_BYTES),
231
+ sealed: requireBytes(sealed, 'sealed key', POLY1305_TAG_BYTES, MAX_SEALED_BYTES),
232
+ };
233
+ }
234
+ function requireScryptCost(N, r, p) {
235
+ if (!isPositiveInteger(N) || N < 2 || (N & (N - 1)) !== 0 || !isPositiveInteger(r) || !isPositiveInteger(p)) {
236
+ throw new OwnerKeyExportError('malformed', 'A locked owner key\'s scrypt parameters must be positive integers, N a power of two');
237
+ }
238
+ if (N > MAX_SCRYPT_N || r * p > MAX_SCRYPT_R_TIMES_P) {
239
+ throw new OwnerKeyExportError('cost-too-high', `This locked owner key asks for more work to unlock than this app allows (scrypt N ${N}, r ${r}, p ${p})`);
240
+ }
241
+ return { N, r, p };
242
+ }
243
+ function isPositiveInteger(value) {
244
+ return typeof value === 'number' && Number.isSafeInteger(value) && value > 0;
245
+ }
246
+ function requireBytes(value, field, minBytes, maxBytes) {
247
+ let bytes;
248
+ if (typeof value === 'string') {
249
+ try {
250
+ bytes = uint8ArrayFromString(value, 'base64url');
251
+ }
252
+ catch (error) {
253
+ throw new OwnerKeyExportError('malformed', `A locked owner key's ${field} must be base64url`, { cause: error });
254
+ }
255
+ }
256
+ if (bytes === undefined || bytes.length < minBytes || bytes.length > maxBytes) {
257
+ throw new OwnerKeyExportError('malformed', `A locked owner key's ${field} must be ${minBytes === maxBytes ? minBytes : `${minBytes} to ${maxBytes}`} bytes of base64url`);
258
+ }
259
+ return bytes;
260
+ }
261
+ // ── Shared field rules ───────────────────────────────────────────────────────
262
+ function requireKey(privateKeyB64) {
263
+ try {
264
+ return requireEd25519PrivateKeyB64(privateKeyB64, 'owner key');
265
+ }
266
+ catch (error) {
267
+ throw new OwnerKeyExportError('malformed', error instanceof Error ? error.message : 'The owner key must be a 32-byte Ed25519 private key', { cause: error });
268
+ }
269
+ }
270
+ function requireLabel(label) {
271
+ if (label.length > OWNER_KEY_MAX_LABEL) {
272
+ throw new OwnerKeyExportError('malformed', `An owner key's label must be at most ${OWNER_KEY_MAX_LABEL} characters (this one has ${label.length})`);
273
+ }
274
+ return label;
275
+ }
276
+ function toBase64url(bytes) {
277
+ return uint8ArrayToString(bytes, 'base64url');
278
+ }
279
+ //# sourceMappingURL=owner-key-export.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owner-key-export.js","sourceRoot":"","sources":["../src/owner-key-export.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,UAAU,IAAI,oBAAoB,EAAE,QAAQ,IAAI,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjG,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AACxI,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,2BAA2B,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAC;AAE5F,qCAAqC;AACrC,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,qBAAqB,KAAK,CAAC;AAErE,iDAAiD;AACjD,MAAM,CAAC,MAAM,8BAA8B,GAAG,GAAG,qBAAqB,YAAY,CAAC;AAEnF,+KAA+K;AAC/K,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAEtC;;;;;GAKG;AACH,MAAM,mBAAmB,GAAe,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;AAEnE;;;;;;GAMG;AACH,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,CAAC;AAC7B,MAAM,oBAAoB,GAAG,CAAC,CAAC;AAE/B,4IAA4I;AAC5I,MAAM,aAAa,GAAG,GAAG,GAAG,oBAAoB,GAAG,CAAC,YAAY,GAAG,oBAAoB,GAAG,CAAC,CAAC,CAAC;AAE7F,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAC7B,MAAM,qBAAqB,GAAG,EAAE,CAAC;AACjC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAC/B,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAC9B,2GAA2G;AAC3G,MAAM,gBAAgB,GAAG,IAAI,CAAC;AA0B9B,mGAAmG;AACnG,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAG7C,YAAY,IAA6B,EAAE,OAAe,EAAE,OAA6B;QACxF,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;QAClC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,CAAC;CACD;AAuBD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,KAA0B;IACpE,MAAM,SAAS,GAAG,eAAe,CAAC,KAAK,CAAC,aAAa,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IACpE,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACpC,OAAO,UAAU,CAAC,uBAAuB,EAAE,SAAS,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,UAAU,CAAC,8BAA8B,EAAE,MAAM,gBAAgB,CAAC,SAAS,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;AACxG,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAChD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,YAAY,CAAC,OAAO,CAAC,KAAK,8BAA8B,EAAE,CAAC;QAC9D,cAAc,CAAC,UAAU,CAAC,OAAO,EAAE,8BAA8B,CAAC,CAAC,CAAC;QACpE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACzB,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,cAAc,CAAC,UAAU,CAAC,OAAO,EAAE,uBAAuB,CAAC,CAAC,EAAE,CAAC;AAC3F,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,IAAY,EAAE,UAAkB;IAC1E,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IACrC,IAAI,MAAM,KAAK,uBAAuB,EAAE,CAAC;QACxC,OAAO,cAAc,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD,CAAC;IACD,MAAM,QAAQ,GAAG,cAAc,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;IAC7D,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,UAAU,EAAE,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjE,IAAI,KAAiB,CAAC;IACtB,IAAI,CAAC;QACJ,KAAK,GAAG,iBAAiB,CAAC,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACzE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,IAAI,mBAAmB,CAAC,kBAAkB,EAAE,wEAAwE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/I,CAAC;IACD,OAAO,cAAc,CAAC,eAAe,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACzE,CAAC;AAED,gFAAgF;AAEhF,SAAS,eAAe,CAAC,aAAqB,EAAE,KAAyB;IACxE,MAAM,GAAG,GAAG,UAAU,CAAC,aAAa,CAAC,CAAC;IACtC,MAAM,YAAY,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC;IACnC,OAAO,aAAa,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;AACnG,CAAC;AAED,KAAK,UAAU,gBAAgB,CAAC,SAAiB,EAAE,UAAkB;IACpE,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,mBAAmB,CAAC,WAAW,EAAE,qEAAqE,CAAC,CAAC;IACnH,CAAC;IACD,MAAM,IAAI,GAAG,WAAW,CAAC,iBAAiB,CAAC,CAAC;IAC5C,MAAM,KAAK,GAAG,WAAW,CAAC,mBAAmB,CAAC,CAAC;IAC/C,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,UAAU,EAAE,mBAAmB,EAAE,IAAI,CAAC,CAAC;IACnE,MAAM,MAAM,GAAG,iBAAiB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;IAC1F,OAAO,aAAa,CAAC;QACpB,GAAG,EAAE,EAAE,GAAG,mBAAmB,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE;QACxE,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC;QACzB,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC;QAC3B,CAAC,EAAE,CAAC;KACJ,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,MAAc,EAAE,IAAY;IAC/C,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;AAClE,CAAC;AAED,wHAAwH;AACxH,SAAS,SAAS,CAAC,UAAkB,EAAE,IAAgB,EAAE,IAAgB;IACxE,sGAAsG;IACtG,MAAM,QAAQ,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IACxE,OAAO,WAAW,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC;AAC3G,CAAC;AAED,gFAAgF;AAEhF,kGAAkG;AAClG,SAAS,YAAY,CAAC,IAAY;IACjC,IAAI,IAAI,CAAC,UAAU,CAAC,8BAA8B,CAAC;QAAE,OAAO,8BAA8B,CAAC;IAC3F,IAAI,IAAI,CAAC,UAAU,CAAC,uBAAuB,CAAC;QAAE,OAAO,uBAAuB,CAAC;IAC7E,IAAI,IAAI,CAAC,UAAU,CAAC,qBAAqB,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,mBAAmB,CAAC,WAAW,EAAE,yFAAyF,CAAC,CAAC;IACvI,CAAC;IACD,MAAM,IAAI,mBAAmB,CAAC,kBAAkB,EAAE,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC;AAC/E,CAAC;AAED,4FAA4F;AAC5F,SAAS,oBAAoB,CAAC,IAAY;IACzC,IAAI,IAAI,CAAC,UAAU,CAAC,uBAAuB,CAAC,EAAE,CAAC;QAC9C,OAAO,6FAA6F,CAAC;IACtG,CAAC;IACD,IAAI,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE,CAAC;QACzC,OAAO,0FAA0F,CAAC;IACnG,CAAC;IACD,IAAI,IAAI,CAAC,UAAU,CAAC,yBAAyB,CAAC,EAAE,CAAC;QAChD,OAAO,yFAAyF,CAAC;IAClG,CAAC;IACD,IAAI,IAAI,CAAC,UAAU,CAAC,yBAAyB,CAAC,EAAE,CAAC;QAChD,OAAO,wFAAwF,CAAC;IACjG,CAAC;IACD,OAAO,iCAAiC,uBAAuB,SAAS,8BAA8B,GAAG,CAAC;AAC3G,CAAC;AAED,SAAS,UAAU,CAAC,IAAY,EAAE,MAAc;IAC/C,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACJ,IAAI,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC;IAC/F,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,IAAI,mBAAmB,CAAC,WAAW,EAAE,2DAA2D,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAC3H,CAAC;IACD,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC;AAC9B,CAAC;AAED,yGAAyG;AACzG,SAAS,eAAe,CAAC,IAAY;IACpC,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACJ,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACR,8EAA8E;QAC9E,MAAM,IAAI,mBAAmB,CAAC,WAAW,EAAE,sDAAsD,CAAC,CAAC;IACpG,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5E,MAAM,IAAI,mBAAmB,CAAC,WAAW,EAAE,kDAAkD,CAAC,CAAC;IAChG,CAAC;IACD,MAAM,MAAM,GAAG,MAAiC,CAAC;IACjD,IAAI,MAAM,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QACpB,MAAM,IAAI,mBAAmB,CAAC,WAAW,EAAE,wDAAwD,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACjI,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC;AAED,SAAS,cAAc,CAAC,MAA+B;IACtD,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC;IAC9B,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC7B,MAAM,IAAI,mBAAmB,CAAC,WAAW,EAAE,wCAAwC,CAAC,CAAC;IACtF,CAAC;IACD,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC;QACrF,MAAM,IAAI,mBAAmB,CAAC,WAAW,EAAE,qDAAqD,CAAC,CAAC;IACnG,CAAC;IACD,MAAM,aAAa,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IACtC,OAAO;QACN,aAAa;QACb,SAAS,EAAE,2BAA2B,CAAC,aAAa,CAAC;QACrD,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;KACrE,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,MAA+B;IACtD,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IACtC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACnE,MAAM,IAAI,mBAAmB,CAAC,WAAW,EAAE,iDAAiD,CAAC,CAAC;IAC/F,CAAC;IACD,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,GAA8B,CAAC;IAC/D,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACvB,MAAM,IAAI,mBAAmB,CAAC,WAAW,EAAE,sDAAsD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3H,CAAC;IACD,MAAM,IAAI,GAAG,iBAAiB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACxC,OAAO;QACN,GAAG,IAAI;QACP,IAAI,EAAE,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,qBAAqB,CAAC;QAC1D,KAAK,EAAE,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,CAAC;QAC7E,MAAM,EAAE,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,kBAAkB,EAAE,gBAAgB,CAAC;KAChF,CAAC;AACH,CAAC;AAED,SAAS,iBAAiB,CAAC,CAAU,EAAE,CAAU,EAAE,CAAU;IAC5D,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7G,MAAM,IAAI,mBAAmB,CAAC,WAAW,EAAE,qFAAqF,CAAC,CAAC;IACnI,CAAC;IACD,IAAI,CAAC,GAAG,YAAY,IAAI,CAAC,GAAG,CAAC,GAAG,oBAAoB,EAAE,CAAC;QACtD,MAAM,IAAI,mBAAmB,CAAC,eAAe,EAAE,qFAAqF,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3J,CAAC;IACD,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;AACpB,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAc;IACxC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,YAAY,CAAC,KAAc,EAAE,KAAa,EAAE,QAAgB,EAAE,QAAgB;IACtF,IAAI,KAA6B,CAAC;IAClC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC/B,IAAI,CAAC;YACJ,KAAK,GAAG,oBAAoB,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;QAClD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,IAAI,mBAAmB,CAAC,WAAW,EAAE,wBAAwB,KAAK,oBAAoB,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QACjH,CAAC;IACF,CAAC;IACD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,GAAG,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC;QAC/E,MAAM,IAAI,mBAAmB,CAAC,WAAW,EAAE,wBAAwB,KAAK,YAAY,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,OAAO,QAAQ,EAAE,qBAAqB,CAAC,CAAC;IAC3K,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC;AAED,gFAAgF;AAEhF,SAAS,UAAU,CAAC,aAAqB;IACxC,IAAI,CAAC;QACJ,OAAO,2BAA2B,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;IAChE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,IAAI,mBAAmB,CAAC,WAAW,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,qDAAqD,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAC9J,CAAC;AACF,CAAC;AAED,SAAS,YAAY,CAAC,KAAa;IAClC,IAAI,KAAK,CAAC,MAAM,GAAG,mBAAmB,EAAE,CAAC;QACxC,MAAM,IAAI,mBAAmB,CAAC,WAAW,EAAE,wCAAwC,mBAAmB,6BAA6B,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IACrJ,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC;AAED,SAAS,WAAW,CAAC,KAAiB;IACrC,OAAO,kBAAkB,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * The owner-key actions an app's owner-key screen offers, built from the owner-key slot
3
+ * (`owner-key-store.ts`) and the export text (`owner-key-export.ts`): export the device's own key,
4
+ * and import an exported key into the device's owner slot (`docs/architecture.md` → Owner Keys →
5
+ * Exporting and importing an owner key). One copy for the phone kit and the web app.
6
+ *
7
+ * Over `CadreNode`'s public surface only; pure-JS crypto, so it loads on React Native and in the
8
+ * browser.
9
+ */
10
+ import type { CadreNode } from './cadre-node.js';
11
+ import type { KeyStore } from './key-store.js';
12
+ /** How an owner key is exported. */
13
+ export interface OwnerKeyExportOptions {
14
+ /** The user's name for the key, carried in the export. Trimmed, at most 64 characters, blank meaning none. */
15
+ label?: string;
16
+ /** Locks the export; must not be empty. Absent: a plain export. */
17
+ passphrase?: string;
18
+ }
19
+ /** An owner key's export and the key it holds: {@link exportOwnerKey}'s answer. */
20
+ export interface ExportedOwnerKey {
21
+ /** The exported key (base64url). */
22
+ publicKey: string;
23
+ /** The private key as export text. */
24
+ exportText: string;
25
+ }
26
+ /** {@link importOwnerKeyInto}'s answer. */
27
+ export interface ImportedOwnerKey {
28
+ /** The imported key (base64url), now in the owner slot. */
29
+ publicKey: string;
30
+ /** The node now signs with it: the key is a live owner of the node's party. */
31
+ wired: boolean;
32
+ }
33
+ /**
34
+ * Export the key in `keyStore`'s owner slot, the device's own owner key, which is the key the
35
+ * kits' nodes sign with. The answer names the key, for a caller that shows the export under it.
36
+ *
37
+ * @throws when the slot is empty: a member device holds no owner key of its own to export.
38
+ * @throws {OwnerKeyExportError} `malformed` for an over-long label or an empty passphrase.
39
+ */
40
+ export declare function exportOwnerKey(keyStore: KeyStore, options?: OwnerKeyExportOptions): Promise<ExportedOwnerKey>;
41
+ /**
42
+ * Import owner-key text into `keyStore`'s owner slot and, given a running node, wire the key when
43
+ * it is a live owner of that node's party. Importing the key the slot already holds changes
44
+ * nothing; a different one is refused.
45
+ *
46
+ * A node that already signs with another key keeps it, since a node takes one owner signer; the
47
+ * imported key is stored, `wired` is false, and the next start wires it. The kits' nodes sign
48
+ * only with the slot's key, so there the case arises only for an embedder that wired another.
49
+ *
50
+ * @throws {OwnerKeyExportError} `passphrase-required` for locked text with no passphrase,
51
+ * `wrong-passphrase`, or the decoder's refusals (`not-an-owner-key` names an invitation, join
52
+ * request or join code).
53
+ * @throws {OwnerKeySlotOccupiedError} when the slot holds a different key.
54
+ */
55
+ export declare function importOwnerKeyInto(keyStore: KeyStore, node: CadreNode | null, text: string, passphrase?: string): Promise<ImportedOwnerKey>;
@@ -0,0 +1,60 @@
1
+ import { ed25519PublicKeyFromPrivate } from './ed25519-key.js';
2
+ import { decodeOwnerKeyExport, encodeOwnerKeyExport, OwnerKeyExportError, unlockOwnerKeyExport, } from './owner-key-export.js';
3
+ import { importOwnerKey, readOwnerPrivateKey } from './owner-key-store.js';
4
+ /**
5
+ * Export the key in `keyStore`'s owner slot, the device's own owner key, which is the key the
6
+ * kits' nodes sign with. The answer names the key, for a caller that shows the export under it.
7
+ *
8
+ * @throws when the slot is empty: a member device holds no owner key of its own to export.
9
+ * @throws {OwnerKeyExportError} `malformed` for an over-long label or an empty passphrase.
10
+ */
11
+ export async function exportOwnerKey(keyStore, options = {}) {
12
+ const privateKeyB64 = await readOwnerPrivateKey(keyStore);
13
+ if (privateKeyB64 === undefined) {
14
+ throw new Error('This device holds no owner key of its own to export');
15
+ }
16
+ const exportText = await encodeOwnerKeyExport({ privateKeyB64, ...exportFields(options) });
17
+ return { publicKey: ed25519PublicKeyFromPrivate(privateKeyB64), exportText };
18
+ }
19
+ /**
20
+ * Import owner-key text into `keyStore`'s owner slot and, given a running node, wire the key when
21
+ * it is a live owner of that node's party. Importing the key the slot already holds changes
22
+ * nothing; a different one is refused.
23
+ *
24
+ * A node that already signs with another key keeps it, since a node takes one owner signer; the
25
+ * imported key is stored, `wired` is false, and the next start wires it. The kits' nodes sign
26
+ * only with the slot's key, so there the case arises only for an embedder that wired another.
27
+ *
28
+ * @throws {OwnerKeyExportError} `passphrase-required` for locked text with no passphrase,
29
+ * `wrong-passphrase`, or the decoder's refusals (`not-an-owner-key` names an invitation, join
30
+ * request or join code).
31
+ * @throws {OwnerKeySlotOccupiedError} when the slot holds a different key.
32
+ */
33
+ export async function importOwnerKeyInto(keyStore, node, text, passphrase) {
34
+ const contents = await readOwnerKeyExport(text, passphrase);
35
+ const signer = await importOwnerKey(keyStore, contents.privateKeyB64);
36
+ return { publicKey: signer.publicKey, wired: node !== null && (await wireIfOwner(node, signer)) };
37
+ }
38
+ async function readOwnerKeyExport(text, passphrase) {
39
+ const decoded = decodeOwnerKeyExport(text);
40
+ if (!decoded.locked)
41
+ return decoded;
42
+ if (passphrase === undefined) {
43
+ throw new OwnerKeyExportError('passphrase-required', 'This owner key is locked with a passphrase; enter it to import the key');
44
+ }
45
+ return await unlockOwnerKeyExport(text, passphrase);
46
+ }
47
+ /** `CadreNode.wireOwnKeyIfOwner(signer)`, unless the node already signs with another key, which it keeps. */
48
+ async function wireIfOwner(node, signer) {
49
+ const wired = node.getOwnerSigner();
50
+ if (wired !== null && wired.publicKey !== signer.publicKey)
51
+ return false;
52
+ return await node.wireOwnKeyIfOwner(signer);
53
+ }
54
+ function exportFields(options) {
55
+ return {
56
+ ...(options.label !== undefined && { label: options.label }),
57
+ ...(options.passphrase !== undefined && { passphrase: options.passphrase }),
58
+ };
59
+ }
60
+ //# sourceMappingURL=owner-key-flows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owner-key-flows.js","sourceRoot":"","sources":["../src/owner-key-flows.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAC;AAE/D,OAAO,EACN,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,GAEpB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AA2B3E;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,QAAkB,EAAE,UAAiC,EAAE;IAC3F,MAAM,aAAa,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IAC1D,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACxE,CAAC;IACD,MAAM,UAAU,GAAG,MAAM,oBAAoB,CAAC,EAAE,aAAa,EAAE,GAAG,YAAY,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC3F,OAAO,EAAE,SAAS,EAAE,2BAA2B,CAAC,aAAa,CAAC,EAAE,UAAU,EAAE,CAAC;AAC9E,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACvC,QAAkB,EAClB,IAAsB,EACtB,IAAY,EACZ,UAAmB;IAEnB,MAAM,QAAQ,GAAG,MAAM,kBAAkB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IAC5D,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,QAAQ,EAAE,QAAQ,CAAC,aAAa,CAAC,CAAC;IACtE,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,KAAK,EAAE,IAAI,KAAK,IAAI,IAAI,CAAC,MAAM,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;AACnG,CAAC;AAED,KAAK,UAAU,kBAAkB,CAAC,IAAY,EAAE,UAA8B;IAC7E,MAAM,OAAO,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IAC3C,IAAI,CAAC,OAAO,CAAC,MAAM;QAAE,OAAO,OAAO,CAAC;IACpC,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC9B,MAAM,IAAI,mBAAmB,CAAC,qBAAqB,EAAE,wEAAwE,CAAC,CAAC;IAChI,CAAC;IACD,OAAO,MAAM,oBAAoB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;AACrD,CAAC;AAED,6GAA6G;AAC7G,KAAK,UAAU,WAAW,CAAC,IAAe,EAAE,MAAmB;IAC9D,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;IACpC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,SAAS,KAAK,MAAM,CAAC,SAAS;QAAE,OAAO,KAAK,CAAC;IACzE,OAAO,MAAM,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;AAC7C,CAAC;AAED,SAAS,YAAY,CAAC,OAA8B;IACnD,OAAO;QACN,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;QAC5D,GAAG,CAAC,OAAO,CAAC,UAAU,KAAK,SAAS,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC;KAC3E,CAAC;AACH,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { type KeyId, type KeyStore } from './key-store.js';
2
+ import { type OwnerSigner } from './owner-signer.js';
3
+ /** A create or import found the owner slot already holding a key (a different one, for an import). Carries no key material. */
4
+ export declare class OwnerKeySlotOccupiedError extends Error {
5
+ readonly keyId: KeyId;
6
+ constructor(keyId: KeyId, message: string);
7
+ }
8
+ /**
9
+ * Generate a fresh owner key and keep it in the slot.
10
+ *
11
+ * @throws {OwnerKeySlotOccupiedError} when the slot holds anything, unreadable bytes included.
12
+ */
13
+ export declare function createOwnerKey(keyStore: KeyStore, keyId?: KeyId): Promise<OwnerSigner>;
14
+ /**
15
+ * A signer over the key in the slot, or `undefined` when the slot is empty. Never generates one.
16
+ * A rejected `get` (e.g. `KeyStoreAccessError`) propagates, and bytes that are not an Ed25519
17
+ * private key throw: neither means the slot is empty.
18
+ */
19
+ export declare function loadOwnerSigner(keyStore: KeyStore, keyId?: KeyId): Promise<OwnerSigner | undefined>;
20
+ /**
21
+ * Keep an imported owner key (its 32-byte Ed25519 seed, base64url) in the slot. Importing the key
22
+ * the slot already holds changes nothing.
23
+ *
24
+ * @throws {OwnerKeySlotOccupiedError} when the slot holds anything else, unreadable bytes included.
25
+ */
26
+ export declare function importOwnerKey(keyStore: KeyStore, privateKeyB64: string, keyId?: KeyId): Promise<OwnerSigner>;
27
+ /**
28
+ * The slot's private key as a base64url Ed25519 seed, or `undefined` when the slot is empty. For
29
+ * exporting the key (`owner-key-export.ts`); everything else signs through {@link loadOwnerSigner}.
30
+ */
31
+ export declare function readOwnerPrivateKey(keyStore: KeyStore, keyId?: KeyId): Promise<string | undefined>;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The owner-key slot: an owner key kept in a {@link KeyStore} slot of its own, apart from the
3
+ * node's identity slot (`docs/architecture.md` → Owner Keys).
4
+ *
5
+ * Unlike the identity slot (`identity-key.ts`), an empty owner slot is never filled on read: an
6
+ * owner key exists because the user made or imported one. Nothing here overwrites a slot that
7
+ * holds anything, since the key in it is what the user's cadres list as an owner.
8
+ *
9
+ * The stored form is the identity slot's: the libp2p protobuf bytes of an Ed25519 private key.
10
+ * Dependency-free beyond `@libp2p/crypto`, so this module is safe in every entry graph.
11
+ *
12
+ * NOTE: each write checks the slot and then sets it, which a `KeyStore` cannot do atomically; two
13
+ * concurrent creates or imports into one slot can both pass the check and the later write wins.
14
+ * Callers run one owner-key action at a time; if one ever can race another, serialize them here.
15
+ */
16
+ import { generateKeyPair, generateKeyPairFromSeed, privateKeyFromProtobuf, privateKeyToProtobuf } from '@libp2p/crypto/keys';
17
+ import { equals as uint8ArrayEquals, fromString as uint8ArrayFromString } from 'uint8arrays';
18
+ import { DEFAULT_OWNER_KEY_ID } from './key-store.js';
19
+ import { ed25519KeyPairFromLibp2p, requireEd25519PrivateKeyB64 } from './ed25519-key.js';
20
+ import { softwareOwnerSigner } from './owner-signer.js';
21
+ /** A create or import found the owner slot already holding a key (a different one, for an import). Carries no key material. */
22
+ export class OwnerKeySlotOccupiedError extends Error {
23
+ constructor(keyId, message) {
24
+ super(message);
25
+ this.name = 'OwnerKeySlotOccupiedError';
26
+ this.keyId = keyId;
27
+ }
28
+ }
29
+ /**
30
+ * Generate a fresh owner key and keep it in the slot.
31
+ *
32
+ * @throws {OwnerKeySlotOccupiedError} when the slot holds anything, unreadable bytes included.
33
+ */
34
+ export async function createOwnerKey(keyStore, keyId = DEFAULT_OWNER_KEY_ID) {
35
+ if (await keyStore.get(keyId) !== undefined) {
36
+ throw new OwnerKeySlotOccupiedError(keyId, `The owner-key slot "${keyId}" already holds a key`);
37
+ }
38
+ const generated = await generateKeyPair('Ed25519');
39
+ await keyStore.set(keyId, privateKeyToProtobuf(generated));
40
+ return softwareOwnerSigner(ed25519KeyPairFromLibp2p(generated).privateKeyB64);
41
+ }
42
+ /**
43
+ * A signer over the key in the slot, or `undefined` when the slot is empty. Never generates one.
44
+ * A rejected `get` (e.g. `KeyStoreAccessError`) propagates, and bytes that are not an Ed25519
45
+ * private key throw: neither means the slot is empty.
46
+ */
47
+ export async function loadOwnerSigner(keyStore, keyId = DEFAULT_OWNER_KEY_ID) {
48
+ const privateKeyB64 = await readOwnerPrivateKey(keyStore, keyId);
49
+ return privateKeyB64 === undefined ? undefined : softwareOwnerSigner(privateKeyB64);
50
+ }
51
+ /**
52
+ * Keep an imported owner key (its 32-byte Ed25519 seed, base64url) in the slot. Importing the key
53
+ * the slot already holds changes nothing.
54
+ *
55
+ * @throws {OwnerKeySlotOccupiedError} when the slot holds anything else, unreadable bytes included.
56
+ */
57
+ export async function importOwnerKey(keyStore, privateKeyB64, keyId = DEFAULT_OWNER_KEY_ID) {
58
+ const imported = requireEd25519PrivateKeyB64(privateKeyB64, 'owner private key');
59
+ // The stored form is deterministic for a given seed, so equal bytes mean the same key.
60
+ const importedBytes = privateKeyToProtobuf(await generateKeyPairFromSeed('Ed25519', uint8ArrayFromString(imported, 'base64url')));
61
+ const stored = await keyStore.get(keyId);
62
+ if (stored === undefined) {
63
+ await keyStore.set(keyId, importedBytes);
64
+ }
65
+ else if (!uint8ArrayEquals(stored, importedBytes)) {
66
+ throw new OwnerKeySlotOccupiedError(keyId, `The owner-key slot "${keyId}" already holds a different key`);
67
+ }
68
+ return softwareOwnerSigner(imported);
69
+ }
70
+ /**
71
+ * The slot's private key as a base64url Ed25519 seed, or `undefined` when the slot is empty. For
72
+ * exporting the key (`owner-key-export.ts`); everything else signs through {@link loadOwnerSigner}.
73
+ */
74
+ export async function readOwnerPrivateKey(keyStore, keyId = DEFAULT_OWNER_KEY_ID) {
75
+ const bytes = await keyStore.get(keyId);
76
+ if (bytes === undefined)
77
+ return undefined;
78
+ // A format conversion of the owner key's own stored bytes, not a derivation from the node identity.
79
+ return ed25519KeyPairFromLibp2p(privateKeyFromProtobuf(bytes)).privateKeyB64;
80
+ }
81
+ //# sourceMappingURL=owner-key-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owner-key-store.js","sourceRoot":"","sources":["../src/owner-key-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Owner keys as an app lists, adds and removes them (`CadreNode.listOwnerKeys`,
3
+ * `CadreNode.addOwner`, `CadreNode.planOwnerKeyRemoval`, `CadreNode.removeOwner`): the
4
+ * projection of the live `OwnerKey` rows an owner-key screen shows, the read-only plan behind a
5
+ * removal's confirmation step, the check that a requested re-vouch stays inside that plan, and
6
+ * the by-name refusals. Pure; the reads and writes themselves are `CadreNode`'s and
7
+ * `ControlDatabase`'s.
8
+ */
9
+ import type { CadreOwnerKey, CadrePeerRow, OwnerGrantStatus, OwnerKeyAddRefusal, OwnerKeyDependents, OwnerKeyRemovalPlan, OwnerKeyRemovalRefusal, OwnerKeyRow, RemoveOwnerOptions } from './types.js';
10
+ /**
11
+ * `CadreNode.addOwner` refused by name, before any write. `code` says which rule
12
+ * ({@link OwnerKeyAddRefusal}), for an app to switch on; the message is for a log.
13
+ */
14
+ export declare class OwnerKeyAddRefusedError extends Error {
15
+ readonly code: OwnerKeyAddRefusal;
16
+ readonly key: string;
17
+ constructor(key: string, code: OwnerKeyAddRefusal);
18
+ }
19
+ /**
20
+ * `CadreNode.removeOwner` refused by name, before any write. `code` says which rule
21
+ * ({@link OwnerKeyRemovalRefusal}), for an app to switch on; the message is for a log.
22
+ */
23
+ export declare class OwnerKeyRemovalRefusedError extends Error {
24
+ readonly code: OwnerKeyRemovalRefusal;
25
+ readonly key: string;
26
+ constructor(key: string, code: OwnerKeyRemovalRefusal);
27
+ }
28
+ /** An owner key as an approval prompt names it: `"label" (key)`, or the key alone when it has no label. */
29
+ export declare function namedOwnerKey(key: string, label: string | null): string;
30
+ /**
31
+ * One {@link CadreOwnerKey} per live row, in the rows' order. `heldKey` is the key of this
32
+ * node's owner signer, if one is wired; `anchor` is this node's trusted-owner anchor.
33
+ */
34
+ export declare function describeOwnerKeys(rows: readonly OwnerKeyRow[], heldKey: string | null, anchor: ReadonlySet<string>): CadreOwnerKey[];
35
+ /** What {@link planOwnerKeyRemoval} judges from: one node's live rows and its own standing. */
36
+ export interface OwnerKeyRemovalPlanInput {
37
+ key: string;
38
+ /** Live `CadrePeer` rows (`ControlDatabase.queryCadrePeers`). */
39
+ rows: readonly CadrePeerRow[];
40
+ /** Live `OwnerKey` rows (`ControlDatabase.queryOwnerKeyRows`). */
41
+ owners: readonly OwnerKeyRow[];
42
+ /** Every owner grant with its standing (`ControlDatabase.listOwnerGrantStatuses`); the live ones the key issued are the plan's `grants`. */
43
+ grants: readonly OwnerGrantStatus[];
44
+ selfPeerId: string | null;
45
+ /** The key of this node's owner signer when it is an enrolled owner (`CadreNode.ownerSignerEnrolledIn`), or null when it cannot sign owner writes. */
46
+ selfOwnerKey: string | null;
47
+ /** This node's trusted-owner anchor, for the listed entry's `trusted`. */
48
+ anchor: ReadonlySet<string>;
49
+ }
50
+ /**
51
+ * The plan for removing owner key `key` from these rows. The refusals come first and by name: a
52
+ * node that cannot sign; a key with no live row; the only live owner key (`MinOneOwner`), ahead
53
+ * of `self` so a sole owner asking to remove its own key hears the rule no other owner could
54
+ * get past either; and this node's own key, since the schema's remove branch wants a signer
55
+ * other than the removed key. With this node enrolled, a different live key always leaves two
56
+ * owners in one read, so `last-owner` reaches only that case, and concurrent removals elsewhere
57
+ * leave the schema to decide.
58
+ *
59
+ * `dependents` are the rows the key vouched, which every reader judges against its anchor
60
+ * through `VouchOwner`: once the key is removed they stop passing the membership predicate on
61
+ * every machine unless re-vouched first. This node's own rows are not among them. Its own
62
+ * `CadrePeer` row is re-vouched unasked (`revouchesOwnRow`): left on the removed key, every other
63
+ * machine would drop the remover. Its own `OwnerKey` row is reported as `ownDependency`, since
64
+ * re-vouching it would mean signing its own removal.
65
+ *
66
+ * `grants` are the live owner grants the key issued: a redemption wants a live issuer, so they
67
+ * stop working with the removal. They are listed, not withdrawn (`removeOwner` leaves them: a
68
+ * tombstone over a grant that can no longer be redeemed would add nothing).
69
+ */
70
+ export declare function planOwnerKeyRemoval(input: OwnerKeyRemovalPlanInput): OwnerKeyRemovalPlan;
71
+ /**
72
+ * The re-vouch `CadreNode.removeOwner` makes: what the caller asked for, checked against the
73
+ * plan, plus this node's own row (`selfPeerId`, first) when `plan.revouchesOwnRow`. Every
74
+ * requested key and peer must be in `plan.dependents`, so a caller cannot re-vouch a row the
75
+ * removed key never vouched. Throws naming the offenders, before anything is written.
76
+ * Duplicates are dropped.
77
+ */
78
+ export declare function revouchWithinPlan(plan: OwnerKeyRemovalPlan, revouch: RemoveOwnerOptions['revouch'], selfPeerId: string | null): OwnerKeyDependents;