@ternent/core 0.0.1

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 (313) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +17 -0
  3. package/.github/workflows/deploy-armour.yml +42 -0
  4. package/.github/workflows/deploy-identity.yml +42 -0
  5. package/.github/workflows/deploy-seal.yml +42 -0
  6. package/.github/workflows/deploy-ui.yml +42 -0
  7. package/.github/workflows/deploy-utils.yml +42 -0
  8. package/.github/workflows/release-create.yml +59 -0
  9. package/.github/workflows/release-publish.yml +54 -0
  10. package/.nvmrc +1 -0
  11. package/.ops/publish.mjs +31 -0
  12. package/package.json +16 -0
  13. package/packages/README.md +0 -0
  14. package/packages/armour/CHANGELOG.md +66 -0
  15. package/packages/armour/CLAUDE.md +8 -0
  16. package/packages/armour/README.md +103 -0
  17. package/packages/armour/SPEC.md +92 -0
  18. package/packages/armour/package.json +45 -0
  19. package/packages/armour/src/constants.ts +5 -0
  20. package/packages/armour/src/deps.d.ts +56 -0
  21. package/packages/armour/src/errors.ts +172 -0
  22. package/packages/armour/src/files.ts +73 -0
  23. package/packages/armour/src/identity.ts +72 -0
  24. package/packages/armour/src/index.ts +56 -0
  25. package/packages/armour/src/init.ts +10 -0
  26. package/packages/armour/src/passphrase.ts +33 -0
  27. package/packages/armour/src/recipients.ts +73 -0
  28. package/packages/armour/src/text.ts +68 -0
  29. package/packages/armour/src/types.ts +93 -0
  30. package/packages/armour/test/armour.test.ts +270 -0
  31. package/packages/armour/tsconfig.build.json +12 -0
  32. package/packages/armour/tsconfig.json +12 -0
  33. package/packages/armour/vite.config.ts +29 -0
  34. package/packages/concord/CHANGELOG.md +83 -0
  35. package/packages/concord/CLAUDE.md +9 -0
  36. package/packages/concord/README.md +146 -0
  37. package/packages/concord/SPEC.md +287 -0
  38. package/packages/concord/package.json +51 -0
  39. package/packages/concord/src/app.ts +717 -0
  40. package/packages/concord/src/errors.ts +9 -0
  41. package/packages/concord/src/index.ts +20 -0
  42. package/packages/concord/src/types.ts +127 -0
  43. package/packages/concord/test/concord.test.ts +978 -0
  44. package/packages/concord/tsconfig.json +12 -0
  45. package/packages/concord/vite.browser.config.ts +27 -0
  46. package/packages/concord/vite.config.ts +35 -0
  47. package/packages/concord/vite.config.ts.timestamp-1774262297922-ffd76e35ea668.mjs +83 -0
  48. package/packages/identity/CHANGELOG.md +47 -0
  49. package/packages/identity/README.md +236 -0
  50. package/packages/identity/package.json +41 -0
  51. package/packages/identity/src/index.ts +538 -0
  52. package/packages/identity/test/identity.test.ts +172 -0
  53. package/packages/identity/tsconfig.build.json +12 -0
  54. package/packages/identity/vite.config.ts +17 -0
  55. package/packages/ledger/CHANGELOG.md +69 -0
  56. package/packages/ledger/CLAUDE.md +9 -0
  57. package/packages/ledger/SPEC.md +304 -0
  58. package/packages/ledger/package.json +48 -0
  59. package/packages/ledger/src/index.ts +2 -0
  60. package/packages/ledger/src/ledger.ts +1286 -0
  61. package/packages/ledger/src/seal-cli.d.ts +25 -0
  62. package/packages/ledger/src/types.ts +294 -0
  63. package/packages/ledger/test/ledger.test.ts +838 -0
  64. package/packages/ledger/tsconfig.json +12 -0
  65. package/packages/ledger/vite.browser.config.ts +27 -0
  66. package/packages/ledger/vite.config.ts +39 -0
  67. package/packages/seal/CHANGELOG.md +137 -0
  68. package/packages/seal/CLAUDE.md +8 -0
  69. package/packages/seal/README.md +258 -0
  70. package/packages/seal/bin/seal +6 -0
  71. package/packages/seal/package.json +59 -0
  72. package/packages/seal/src/artifact.ts +380 -0
  73. package/packages/seal/src/cli.ts +372 -0
  74. package/packages/seal/src/commands/identity.ts +52 -0
  75. package/packages/seal/src/commands/manifest.ts +71 -0
  76. package/packages/seal/src/commands/publicKey.ts +7 -0
  77. package/packages/seal/src/commands/sign.ts +56 -0
  78. package/packages/seal/src/commands/verify.ts +54 -0
  79. package/packages/seal/src/crypto.ts +85 -0
  80. package/packages/seal/src/errors.ts +88 -0
  81. package/packages/seal/src/index.ts +5 -0
  82. package/packages/seal/src/manifest.ts +114 -0
  83. package/packages/seal/src/node.ts +18 -0
  84. package/packages/seal/src/proof.ts +344 -0
  85. package/packages/seal/test/artifact.test.ts +86 -0
  86. package/packages/seal/test/cli.test.ts +208 -0
  87. package/packages/seal/test/crypto.test.ts +21 -0
  88. package/packages/seal/test/manifest.test.ts +32 -0
  89. package/packages/seal/test/proof.test.ts +60 -0
  90. package/packages/seal/tsconfig.json +12 -0
  91. package/packages/seal/vite.config.ts +54 -0
  92. package/packages/ui/CHANGELOG.md +393 -0
  93. package/packages/ui/README.md +57 -0
  94. package/packages/ui/jsconfig.json +19 -0
  95. package/packages/ui/package.json +64 -0
  96. package/packages/ui/scripts/check-tokens.js +56 -0
  97. package/packages/ui/scripts/generate-theme-css.mjs +85 -0
  98. package/packages/ui/src/design-system/base.css +8 -0
  99. package/packages/ui/src/design-system/docs/ACCESSIBILITY_RULES.md +186 -0
  100. package/packages/ui/src/design-system/docs/AI_SYSTEM.md +281 -0
  101. package/packages/ui/src/design-system/docs/PATTERN_RULES.md +83 -0
  102. package/packages/ui/src/design-system/docs/PRIMITIVE_RULES.md +258 -0
  103. package/packages/ui/src/design-system/docs/TOKEN_RULES.md +235 -0
  104. package/packages/ui/src/design-system/docs/VISUAL_DIRECTION.md +68 -0
  105. package/packages/ui/src/design-system/foundation.js +420 -0
  106. package/packages/ui/src/design-system/tokens.css +140 -0
  107. package/packages/ui/src/design-system/tokens.js +327 -0
  108. package/packages/ui/src/design-system/utils.js +246 -0
  109. package/packages/ui/src/main.js +4 -0
  110. package/packages/ui/src/patterns/FeatureCard/FeatureCard.spec.md +24 -0
  111. package/packages/ui/src/patterns/FeatureCard/FeatureCard.types.ts +8 -0
  112. package/packages/ui/src/patterns/FeatureCard/FeatureCard.vue +175 -0
  113. package/packages/ui/src/patterns/FormField/FormField.spec.md +65 -0
  114. package/packages/ui/src/patterns/FormField/FormField.types.ts +11 -0
  115. package/packages/ui/src/patterns/FormField/FormField.vue +87 -0
  116. package/packages/ui/src/patterns/IdentityGlyph/IdentityGlyph.vue +61 -0
  117. package/packages/ui/src/patterns/IdentityGlyph/IdentityHandle.vue +58 -0
  118. package/packages/ui/src/patterns/IdentityGlyph/identityGlyph.types.ts +36 -0
  119. package/packages/ui/src/patterns/IdentityGlyph/identityGlyph.utils.ts +585 -0
  120. package/packages/ui/src/patterns/IdentityGlyph/index.ts +5 -0
  121. package/packages/ui/src/patterns/KeyValueList/KeyValueList.spec.md +28 -0
  122. package/packages/ui/src/patterns/KeyValueList/KeyValueList.types.ts +16 -0
  123. package/packages/ui/src/patterns/KeyValueList/KeyValueList.vue +50 -0
  124. package/packages/ui/src/patterns/LandingPage/LandingIcon.vue +90 -0
  125. package/packages/ui/src/patterns/LandingPage/LandingPage.spec.md +24 -0
  126. package/packages/ui/src/patterns/LandingPage/LandingPage.types.ts +212 -0
  127. package/packages/ui/src/patterns/LandingPage/LandingPage.vue +599 -0
  128. package/packages/ui/src/patterns/ListWorkspaceLayout/ListWorkspaceLayout.test.ts +33 -0
  129. package/packages/ui/src/patterns/ListWorkspaceLayout/ListWorkspaceLayout.vue +44 -0
  130. package/packages/ui/src/patterns/Logo/Logo.spec.md +22 -0
  131. package/packages/ui/src/patterns/Logo/Logo.vue +160 -0
  132. package/packages/ui/src/patterns/PageSurface/PageSurface.spec.md +15 -0
  133. package/packages/ui/src/patterns/PageSurface/PageSurface.vue +85 -0
  134. package/packages/ui/src/patterns/PanelChrome/PanelChrome.spec.md +39 -0
  135. package/packages/ui/src/patterns/PanelChrome/PanelChrome.types.ts +1 -0
  136. package/packages/ui/src/patterns/PanelChrome/PanelChrome.vue +187 -0
  137. package/packages/ui/src/patterns/PreviewPanel/PreviewPanel.spec.md +31 -0
  138. package/packages/ui/src/patterns/PreviewPanel/PreviewPanel.types.ts +23 -0
  139. package/packages/ui/src/patterns/PreviewPanel/PreviewPanel.vue +354 -0
  140. package/packages/ui/src/patterns/RecordList/RecordList.spec.md +35 -0
  141. package/packages/ui/src/patterns/RecordList/RecordList.test.ts +42 -0
  142. package/packages/ui/src/patterns/RecordList/RecordList.types.ts +9 -0
  143. package/packages/ui/src/patterns/RecordList/RecordList.utils.ts +5 -0
  144. package/packages/ui/src/patterns/RecordList/RecordList.vue +134 -0
  145. package/packages/ui/src/patterns/SectionClarifier/SectionClarifier.vue +85 -0
  146. package/packages/ui/src/patterns/SectionIntro/SectionIntro.spec.md +25 -0
  147. package/packages/ui/src/patterns/SectionIntro/SectionIntro.types.ts +7 -0
  148. package/packages/ui/src/patterns/SectionIntro/SectionIntro.vue +141 -0
  149. package/packages/ui/src/patterns/SidebarNav/SidebarNav.spec.md +34 -0
  150. package/packages/ui/src/patterns/SidebarNav/SidebarNav.types.ts +17 -0
  151. package/packages/ui/src/patterns/SidebarNav/SidebarNav.vue +110 -0
  152. package/packages/ui/src/patterns/SplitView/SplitView.spec.md +28 -0
  153. package/packages/ui/src/patterns/SplitView/SplitView.test.ts +22 -0
  154. package/packages/ui/src/patterns/SplitView/SplitView.types.ts +3 -0
  155. package/packages/ui/src/patterns/SplitView/SplitView.utils.ts +13 -0
  156. package/packages/ui/src/patterns/SplitView/SplitView.vue +39 -0
  157. package/packages/ui/src/patterns/StepList/StepList.spec.md +15 -0
  158. package/packages/ui/src/patterns/StepList/StepList.types.ts +4 -0
  159. package/packages/ui/src/patterns/StepList/StepList.vue +91 -0
  160. package/packages/ui/src/patterns/Verification/VerificationBadge.vue +97 -0
  161. package/packages/ui/src/patterns/Verification/VerificationComponents.test.ts +153 -0
  162. package/packages/ui/src/patterns/Verification/VerificationDetailsPanel.vue +270 -0
  163. package/packages/ui/src/patterns/Verification/VerificationSummary.vue +171 -0
  164. package/packages/ui/src/patterns/Verification/index.ts +6 -0
  165. package/packages/ui/src/patterns/Verification/verification.types.ts +8 -0
  166. package/packages/ui/src/patterns/Verification/verification.utils.test.ts +37 -0
  167. package/packages/ui/src/patterns/Verification/verification.utils.ts +75 -0
  168. package/packages/ui/src/patterns/index.ts +25 -0
  169. package/packages/ui/src/primitives/Accordian/Accordian.vue +11 -0
  170. package/packages/ui/src/primitives/Accordian/AccordianItem.vue +14 -0
  171. package/packages/ui/src/primitives/Accordion/Accordion.props.ts +21 -0
  172. package/packages/ui/src/primitives/Accordion/Accordion.spec.md +50 -0
  173. package/packages/ui/src/primitives/Accordion/Accordion.types.ts +4 -0
  174. package/packages/ui/src/primitives/Accordion/Accordion.variants.ts +12 -0
  175. package/packages/ui/src/primitives/Accordion/Accordion.vue +71 -0
  176. package/packages/ui/src/primitives/Accordion/AccordionItem.props.ts +14 -0
  177. package/packages/ui/src/primitives/Accordion/AccordionItem.vue +40 -0
  178. package/packages/ui/src/primitives/Badge/Badge.props.ts +17 -0
  179. package/packages/ui/src/primitives/Badge/Badge.spec.md +17 -0
  180. package/packages/ui/src/primitives/Badge/Badge.types.ts +15 -0
  181. package/packages/ui/src/primitives/Badge/Badge.variants.ts +48 -0
  182. package/packages/ui/src/primitives/Badge/Badge.vue +31 -0
  183. package/packages/ui/src/primitives/Button/Button.props.ts +29 -0
  184. package/packages/ui/src/primitives/Button/Button.spec.md +139 -0
  185. package/packages/ui/src/primitives/Button/Button.types.ts +19 -0
  186. package/packages/ui/src/primitives/Button/Button.variants.ts +72 -0
  187. package/packages/ui/src/primitives/Button/Button.vue +90 -0
  188. package/packages/ui/src/primitives/Card/Card.props.ts +17 -0
  189. package/packages/ui/src/primitives/Card/Card.spec.md +29 -0
  190. package/packages/ui/src/primitives/Card/Card.types.ts +12 -0
  191. package/packages/ui/src/primitives/Card/Card.variants.ts +27 -0
  192. package/packages/ui/src/primitives/Card/Card.vue +37 -0
  193. package/packages/ui/src/primitives/Checkbox/Checkbox.props.ts +21 -0
  194. package/packages/ui/src/primitives/Checkbox/Checkbox.spec.md +51 -0
  195. package/packages/ui/src/primitives/Checkbox/Checkbox.types.ts +4 -0
  196. package/packages/ui/src/primitives/Checkbox/Checkbox.variants.ts +34 -0
  197. package/packages/ui/src/primitives/Checkbox/Checkbox.vue +92 -0
  198. package/packages/ui/src/primitives/Dialog/Dialog.props.ts +29 -0
  199. package/packages/ui/src/primitives/Dialog/Dialog.spec.md +52 -0
  200. package/packages/ui/src/primitives/Dialog/Dialog.types.ts +3 -0
  201. package/packages/ui/src/primitives/Dialog/Dialog.variants.ts +27 -0
  202. package/packages/ui/src/primitives/Dialog/Dialog.vue +78 -0
  203. package/packages/ui/src/primitives/Drawer/Drawer.props.ts +33 -0
  204. package/packages/ui/src/primitives/Drawer/Drawer.spec.md +50 -0
  205. package/packages/ui/src/primitives/Drawer/Drawer.types.ts +5 -0
  206. package/packages/ui/src/primitives/Drawer/Drawer.variants.ts +35 -0
  207. package/packages/ui/src/primitives/Drawer/Drawer.vue +88 -0
  208. package/packages/ui/src/primitives/FieldMessage/FieldMessage.props.ts +17 -0
  209. package/packages/ui/src/primitives/FieldMessage/FieldMessage.spec.md +35 -0
  210. package/packages/ui/src/primitives/FieldMessage/FieldMessage.types.ts +5 -0
  211. package/packages/ui/src/primitives/FieldMessage/FieldMessage.variants.ts +14 -0
  212. package/packages/ui/src/primitives/FieldMessage/FieldMessage.vue +40 -0
  213. package/packages/ui/src/primitives/FileInput/FileInput.props.ts +41 -0
  214. package/packages/ui/src/primitives/FileInput/FileInput.types.ts +6 -0
  215. package/packages/ui/src/primitives/FileInput/FileInput.variants.ts +46 -0
  216. package/packages/ui/src/primitives/FileInput/FileInput.vue +163 -0
  217. package/packages/ui/src/primitives/Input/Input.props.ts +29 -0
  218. package/packages/ui/src/primitives/Input/Input.spec.md +79 -0
  219. package/packages/ui/src/primitives/Input/Input.types.ts +13 -0
  220. package/packages/ui/src/primitives/Input/Input.variants.ts +54 -0
  221. package/packages/ui/src/primitives/Input/Input.vue +99 -0
  222. package/packages/ui/src/primitives/Label/Label.props.ts +25 -0
  223. package/packages/ui/src/primitives/Label/Label.spec.md +31 -0
  224. package/packages/ui/src/primitives/Label/Label.types.ts +3 -0
  225. package/packages/ui/src/primitives/Label/Label.variants.ts +17 -0
  226. package/packages/ui/src/primitives/Label/Label.vue +38 -0
  227. package/packages/ui/src/primitives/Menu/Menu.props.ts +17 -0
  228. package/packages/ui/src/primitives/Menu/Menu.spec.md +38 -0
  229. package/packages/ui/src/primitives/Menu/Menu.types.ts +10 -0
  230. package/packages/ui/src/primitives/Menu/Menu.variants.ts +10 -0
  231. package/packages/ui/src/primitives/Menu/Menu.vue +57 -0
  232. package/packages/ui/src/primitives/Popover/Popover.props.ts +25 -0
  233. package/packages/ui/src/primitives/Popover/Popover.spec.md +49 -0
  234. package/packages/ui/src/primitives/Popover/Popover.types.ts +3 -0
  235. package/packages/ui/src/primitives/Popover/Popover.variants.ts +18 -0
  236. package/packages/ui/src/primitives/Popover/Popover.vue +74 -0
  237. package/packages/ui/src/primitives/RadioGroup/RadioGroup.props.ts +29 -0
  238. package/packages/ui/src/primitives/RadioGroup/RadioGroup.spec.md +50 -0
  239. package/packages/ui/src/primitives/RadioGroup/RadioGroup.types.ts +12 -0
  240. package/packages/ui/src/primitives/RadioGroup/RadioGroup.variants.ts +48 -0
  241. package/packages/ui/src/primitives/RadioGroup/RadioGroup.vue +87 -0
  242. package/packages/ui/src/primitives/Separator/Separator.props.ts +9 -0
  243. package/packages/ui/src/primitives/Separator/Separator.spec.md +15 -0
  244. package/packages/ui/src/primitives/Separator/Separator.types.ts +3 -0
  245. package/packages/ui/src/primitives/Separator/Separator.variants.ts +8 -0
  246. package/packages/ui/src/primitives/Separator/Separator.vue +23 -0
  247. package/packages/ui/src/primitives/Skeleton/Skeleton.props.ts +21 -0
  248. package/packages/ui/src/primitives/Skeleton/Skeleton.spec.md +18 -0
  249. package/packages/ui/src/primitives/Skeleton/Skeleton.types.ts +5 -0
  250. package/packages/ui/src/primitives/Skeleton/Skeleton.variants.ts +18 -0
  251. package/packages/ui/src/primitives/Skeleton/Skeleton.vue +37 -0
  252. package/packages/ui/src/primitives/Spinner/Spinner.props.ts +13 -0
  253. package/packages/ui/src/primitives/Spinner/Spinner.spec.md +16 -0
  254. package/packages/ui/src/primitives/Spinner/Spinner.types.ts +5 -0
  255. package/packages/ui/src/primitives/Spinner/Spinner.variants.ts +15 -0
  256. package/packages/ui/src/primitives/Spinner/Spinner.vue +33 -0
  257. package/packages/ui/src/primitives/SplitButton/SplitButton.vue +108 -0
  258. package/packages/ui/src/primitives/Switch/Switch.props.ts +21 -0
  259. package/packages/ui/src/primitives/Switch/Switch.spec.md +49 -0
  260. package/packages/ui/src/primitives/Switch/Switch.types.ts +3 -0
  261. package/packages/ui/src/primitives/Switch/Switch.variants.ts +34 -0
  262. package/packages/ui/src/primitives/Switch/Switch.vue +71 -0
  263. package/packages/ui/src/primitives/Tabs/Tabs.props.ts +25 -0
  264. package/packages/ui/src/primitives/Tabs/Tabs.spec.md +48 -0
  265. package/packages/ui/src/primitives/Tabs/Tabs.types.ts +11 -0
  266. package/packages/ui/src/primitives/Tabs/Tabs.variants.ts +28 -0
  267. package/packages/ui/src/primitives/Tabs/Tabs.vue +59 -0
  268. package/packages/ui/src/primitives/Textarea/Textarea.props.ts +33 -0
  269. package/packages/ui/src/primitives/Textarea/Textarea.spec.md +59 -0
  270. package/packages/ui/src/primitives/Textarea/Textarea.types.ts +5 -0
  271. package/packages/ui/src/primitives/Textarea/Textarea.variants.ts +27 -0
  272. package/packages/ui/src/primitives/Textarea/Textarea.vue +74 -0
  273. package/packages/ui/src/primitives/Tooltip/Tooltip.props.ts +21 -0
  274. package/packages/ui/src/primitives/Tooltip/Tooltip.spec.md +45 -0
  275. package/packages/ui/src/primitives/Tooltip/Tooltip.types.ts +3 -0
  276. package/packages/ui/src/primitives/Tooltip/Tooltip.variants.ts +4 -0
  277. package/packages/ui/src/primitives/Tooltip/Tooltip.vue +31 -0
  278. package/packages/ui/src/primitives/TreeView/TreeView.types.ts +10 -0
  279. package/packages/ui/src/primitives/TreeView/TreeView.vue +113 -0
  280. package/packages/ui/src/primitives/TreeView/TreeViewNode.vue +190 -0
  281. package/packages/ui/src/primitives/index.ts +29 -0
  282. package/packages/ui/src/style.css +7 -0
  283. package/packages/ui/src/style.js +1 -0
  284. package/packages/ui/src/themes/armour.css +147 -0
  285. package/packages/ui/src/themes/aurora.css +147 -0
  286. package/packages/ui/src/themes/citrine-ash.css +147 -0
  287. package/packages/ui/src/themes/concord.css +147 -0
  288. package/packages/ui/src/themes/garnet-honey.css +147 -0
  289. package/packages/ui/src/themes/harbor-rose.css +147 -0
  290. package/packages/ui/src/themes/ledger.css +147 -0
  291. package/packages/ui/src/themes/neon-noir.css +74 -0
  292. package/packages/ui/src/themes/obsidian-iris.css +147 -0
  293. package/packages/ui/src/themes/pixpax.css +147 -0
  294. package/packages/ui/src/themes/print.css +147 -0
  295. package/packages/ui/src/themes/prism.css +147 -0
  296. package/packages/ui/src/themes/proof.css +145 -0
  297. package/packages/ui/src/themes/semanticThemeContract.js +2256 -0
  298. package/packages/ui/src/themes/spruce-ink.css +147 -0
  299. package/packages/ui/src/themes/sunset.css +147 -0
  300. package/packages/ui/tailwind.config.js +64 -0
  301. package/packages/ui/vite.config.js +35 -0
  302. package/packages/ui/vite.config.js.timestamp-1780697224943-89fbc929987bc.mjs +38 -0
  303. package/packages/utils/CHANGELOG.md +111 -0
  304. package/packages/utils/README.md +3 -0
  305. package/packages/utils/package.json +46 -0
  306. package/packages/utils/src/index.test.js +39 -0
  307. package/packages/utils/src/index.ts +289 -0
  308. package/packages/utils/tsconfig.build.json +12 -0
  309. package/packages/utils/vite.config.js +28 -0
  310. package/pnpm-workspace.yaml +8 -0
  311. package/scripts/vite/package-lib-config.ts +59 -0
  312. package/tsconfig.json +24 -0
  313. package/tsconfig.node.json +9 -0
@@ -0,0 +1,538 @@
1
+ import { ed25519, x25519 } from "@noble/curves/ed25519.js";
2
+ import { hmac } from "@noble/hashes/hmac.js";
3
+ import { sha256, sha512 } from "@noble/hashes/sha2.js";
4
+ import {
5
+ generateMnemonic as bip39GenerateMnemonic,
6
+ mnemonicToSeedSync,
7
+ validateMnemonic as bip39ValidateMnemonic,
8
+ } from "@scure/bip39";
9
+ import { wordlist as englishWordlist } from "@scure/bip39/wordlists/english.js";
10
+
11
+ const IDENTITY_FORMAT = "ternent-identity" as const;
12
+ const IDENTITY_VERSION = "2" as const;
13
+ const IDENTITY_ALGORITHM = "Ed25519" as const;
14
+ const ED25519_CONTEXT = "ternent-seal/v2";
15
+ const IDENTITY_MATERIAL_KIND = "seed" as const;
16
+ const IDENTITY_DERIVATION_PATH = "m/101010'/25519'/0'" as const;
17
+ const BIP39_LANGUAGE = "english" as const;
18
+ const SLIP10_HARDENED_OFFSET = 0x80000000;
19
+ const SLIP10_KEY = new TextEncoder().encode("ed25519 seed");
20
+ const IDENTITY_PATH_SEGMENTS = [101010, 25519, 0] as const;
21
+ const BECH32_GENERATORS = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
22
+
23
+ export type MnemonicWordCount = 12 | 24;
24
+
25
+ export type IdentityMaterial = {
26
+ kind: typeof IDENTITY_MATERIAL_KIND;
27
+ seed: string;
28
+ };
29
+
30
+ export type SerializedIdentity = {
31
+ format: typeof IDENTITY_FORMAT;
32
+ version: typeof IDENTITY_VERSION;
33
+ algorithm: typeof IDENTITY_ALGORITHM;
34
+ createdAt: string;
35
+ publicKey: string;
36
+ keyId: string;
37
+ material: IdentityMaterial;
38
+ };
39
+
40
+ type SeedLike = SerializedIdentity | Uint8Array | ArrayBuffer | string;
41
+ type PublicKeyLike =
42
+ | SerializedIdentity
43
+ | Uint8Array
44
+ | ArrayBuffer
45
+ | string
46
+ | { publicKey: string | Uint8Array | ArrayBuffer }
47
+ | { seed: string | Uint8Array | ArrayBuffer };
48
+
49
+ function isRecord(value: unknown): value is Record<string, unknown> {
50
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
51
+ }
52
+
53
+ function concatBytes(...parts: Uint8Array[]): Uint8Array {
54
+ const length = parts.reduce((sum, part) => sum + part.length, 0);
55
+ const output = new Uint8Array(length);
56
+ let offset = 0;
57
+ for (const part of parts) {
58
+ output.set(part, offset);
59
+ offset += part.length;
60
+ }
61
+ return output;
62
+ }
63
+
64
+ function ensureBytes(value: Uint8Array | ArrayBuffer, label: string): Uint8Array {
65
+ const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);
66
+ if (bytes.length === 0) {
67
+ throw new Error(`${label} must not be empty.`);
68
+ }
69
+ return bytes;
70
+ }
71
+
72
+ function bytesToHex(bytes: Uint8Array): string {
73
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
74
+ }
75
+
76
+ function normalizeBase64Url(value: string): string {
77
+ return String(value || "")
78
+ .trim()
79
+ .replace(/\+/g, "-")
80
+ .replace(/\//g, "_")
81
+ .replace(/=+$/g, "");
82
+ }
83
+
84
+ function base64Encode(bytes: Uint8Array): string {
85
+ const buffer = (
86
+ globalThis as typeof globalThis & {
87
+ Buffer?: {
88
+ from(input: Uint8Array): { toString(encoding: "base64"): string };
89
+ from(input: string, encoding: "base64"): Uint8Array;
90
+ };
91
+ }
92
+ ).Buffer;
93
+
94
+ if (buffer) {
95
+ return buffer.from(bytes).toString("base64");
96
+ }
97
+
98
+ let binary = "";
99
+ for (const byte of bytes) {
100
+ binary += String.fromCharCode(byte);
101
+ }
102
+ if (typeof btoa !== "undefined") {
103
+ return btoa(binary);
104
+ }
105
+ throw new Error("Base64 encoding is not available in this runtime.");
106
+ }
107
+
108
+ function base64Decode(value: string): Uint8Array {
109
+ const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
110
+ const buffer = (
111
+ globalThis as typeof globalThis & {
112
+ Buffer?: {
113
+ from(input: Uint8Array): { toString(encoding: "base64"): string };
114
+ from(input: string, encoding: "base64"): Uint8Array;
115
+ };
116
+ }
117
+ ).Buffer;
118
+
119
+ if (buffer) {
120
+ return new Uint8Array(buffer.from(normalized, "base64"));
121
+ }
122
+
123
+ if (typeof atob !== "undefined") {
124
+ const binary = atob(normalized);
125
+ const bytes = new Uint8Array(binary.length);
126
+ for (let index = 0; index < binary.length; index += 1) {
127
+ bytes[index] = binary.charCodeAt(index);
128
+ }
129
+ return bytes;
130
+ }
131
+
132
+ throw new Error("Base64 decoding is not available in this runtime.");
133
+ }
134
+
135
+ function base64UrlEncode(bytes: Uint8Array): string {
136
+ return base64Encode(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
137
+ }
138
+
139
+ function base64UrlDecode(value: string): Uint8Array {
140
+ const normalized = normalizeBase64Url(value);
141
+ if (!normalized) {
142
+ throw new Error("Base64url value is required.");
143
+ }
144
+ const pad = normalized.length % 4 === 0 ? "" : "=".repeat(4 - (normalized.length % 4));
145
+ return base64Decode(`${normalized}${pad}`);
146
+ }
147
+
148
+ function utf8Bytes(value: string): Uint8Array {
149
+ return new TextEncoder().encode(value);
150
+ }
151
+
152
+ function combineContext(payload: Uint8Array, context = ED25519_CONTEXT): Uint8Array {
153
+ return concatBytes(utf8Bytes(context), new Uint8Array([0]), payload);
154
+ }
155
+
156
+ function assertWordCount(words: number): MnemonicWordCount {
157
+ if (words === 12 || words === 24) {
158
+ return words;
159
+ }
160
+ throw new Error("Mnemonic word count must be 12 or 24.");
161
+ }
162
+
163
+ function strengthFromWordCount(words: MnemonicWordCount): 128 | 256 {
164
+ return words === 24 ? 256 : 128;
165
+ }
166
+
167
+ function serializeUint32(value: number): Uint8Array {
168
+ return new Uint8Array([
169
+ (value >>> 24) & 0xff,
170
+ (value >>> 16) & 0xff,
171
+ (value >>> 8) & 0xff,
172
+ value & 0xff,
173
+ ]);
174
+ }
175
+
176
+ function deriveSlip10Seed(seedBytes: Uint8Array): Uint8Array {
177
+ let chain = hmac(sha512, SLIP10_KEY, seedBytes);
178
+ let key = chain.slice(0, 32);
179
+ let chainCode = chain.slice(32);
180
+
181
+ for (const segment of IDENTITY_PATH_SEGMENTS) {
182
+ const index = segment + SLIP10_HARDENED_OFFSET;
183
+ chain = hmac(sha512, chainCode, concatBytes(new Uint8Array([0]), key, serializeUint32(index)));
184
+ key = chain.slice(0, 32);
185
+ chainCode = chain.slice(32);
186
+ }
187
+
188
+ return key;
189
+ }
190
+
191
+ function isSerializedIdentity(value: unknown): value is SerializedIdentity {
192
+ return (
193
+ isRecord(value) &&
194
+ value.format === IDENTITY_FORMAT &&
195
+ value.version === IDENTITY_VERSION &&
196
+ value.algorithm === IDENTITY_ALGORITHM &&
197
+ typeof value.createdAt === "string" &&
198
+ typeof value.publicKey === "string" &&
199
+ typeof value.keyId === "string" &&
200
+ isRecord(value.material) &&
201
+ value.material.kind === IDENTITY_MATERIAL_KIND &&
202
+ typeof value.material.seed === "string"
203
+ );
204
+ }
205
+
206
+ function toCanonicalIdentity(identity: SerializedIdentity): SerializedIdentity {
207
+ return {
208
+ format: identity.format,
209
+ version: identity.version,
210
+ algorithm: identity.algorithm,
211
+ createdAt: identity.createdAt,
212
+ publicKey: identity.publicKey,
213
+ keyId: identity.keyId,
214
+ material: {
215
+ kind: identity.material.kind,
216
+ seed: identity.material.seed,
217
+ },
218
+ };
219
+ }
220
+
221
+ function resolveSeedBytes(input: SeedLike): Uint8Array {
222
+ if (input instanceof Uint8Array || input instanceof ArrayBuffer) {
223
+ const bytes = ensureBytes(input, "Seed");
224
+ if (bytes.length !== 32) {
225
+ throw new Error("Seed must be 32 bytes.");
226
+ }
227
+ return bytes;
228
+ }
229
+ if (typeof input === "string") {
230
+ const bytes = base64UrlDecode(input);
231
+ if (bytes.length !== 32) {
232
+ throw new Error("Seed must be 32 bytes.");
233
+ }
234
+ return bytes;
235
+ }
236
+ if (isSerializedIdentity(input)) {
237
+ return resolveSeedBytes(input.material.seed);
238
+ }
239
+ throw new Error("A valid identity or 32-byte seed is required.");
240
+ }
241
+
242
+ function resolvePublicKeyBytes(input: PublicKeyLike): Uint8Array {
243
+ if (input instanceof Uint8Array || input instanceof ArrayBuffer) {
244
+ const bytes = ensureBytes(input, "Public key");
245
+ if (bytes.length !== 32) {
246
+ throw new Error("Public key must be 32 bytes.");
247
+ }
248
+ return bytes;
249
+ }
250
+ if (typeof input === "string") {
251
+ const bytes = base64UrlDecode(input);
252
+ if (bytes.length !== 32) {
253
+ throw new Error("Public key must be 32 bytes.");
254
+ }
255
+ return bytes;
256
+ }
257
+ if (isSerializedIdentity(input)) {
258
+ return resolvePublicKeyBytes(input.publicKey);
259
+ }
260
+ if (isRecord(input) && "publicKey" in input) {
261
+ return resolvePublicKeyBytes(input.publicKey as string | Uint8Array | ArrayBuffer);
262
+ }
263
+ throw new Error("A valid 32-byte public key is required.");
264
+ }
265
+
266
+ function resolveX25519PrivateKeyBytes(input: SeedLike): Uint8Array {
267
+ return ed25519.utils.toMontgomerySecret(resolveSeedBytes(input));
268
+ }
269
+
270
+ function convertEd25519PublicKeyToX25519PublicKeyBytes(publicKeyBytes: Uint8Array): Uint8Array {
271
+ return ed25519.utils.toMontgomery(publicKeyBytes);
272
+ }
273
+
274
+ function resolveX25519PublicKeyBytes(input: PublicKeyLike): Uint8Array {
275
+ if (isRecord(input) && "seed" in input) {
276
+ return x25519.getPublicKey(
277
+ resolveX25519PrivateKeyBytes(input.seed as string | Uint8Array | ArrayBuffer),
278
+ );
279
+ }
280
+ if (isSerializedIdentity(input)) {
281
+ return x25519.getPublicKey(resolveX25519PrivateKeyBytes(input));
282
+ }
283
+ if (typeof input === "string" || input instanceof Uint8Array || input instanceof ArrayBuffer) {
284
+ return convertEd25519PublicKeyToX25519PublicKeyBytes(resolvePublicKeyBytes(input));
285
+ }
286
+ if (isRecord(input) && "publicKey" in input) {
287
+ return convertEd25519PublicKeyToX25519PublicKeyBytes(
288
+ resolvePublicKeyBytes(input.publicKey as string | Uint8Array | ArrayBuffer),
289
+ );
290
+ }
291
+ throw new Error("A valid identity seed or Ed25519 public key is required.");
292
+ }
293
+
294
+ function bech32Polymod(values: number[]): number {
295
+ let checksum = 1;
296
+ for (const value of values) {
297
+ const top = checksum >>> 25;
298
+ checksum = ((checksum & 0x1ffffff) << 5) ^ value;
299
+ for (let bit = 0; bit < 5; bit += 1) {
300
+ if ((top >>> bit) & 1) {
301
+ checksum ^= BECH32_GENERATORS[bit];
302
+ }
303
+ }
304
+ }
305
+ return checksum;
306
+ }
307
+
308
+ function bech32HrpExpand(hrp: string): number[] {
309
+ const output: number[] = [];
310
+ for (let index = 0; index < hrp.length; index += 1) {
311
+ output.push(hrp.charCodeAt(index) >> 5);
312
+ }
313
+ output.push(0);
314
+ for (let index = 0; index < hrp.length; index += 1) {
315
+ output.push(hrp.charCodeAt(index) & 31);
316
+ }
317
+ return output;
318
+ }
319
+
320
+ function convertBits(data: Uint8Array, fromBits: number, toBits: number): number[] {
321
+ let value = 0;
322
+ let bits = 0;
323
+ const maxValue = (1 << toBits) - 1;
324
+ const output: number[] = [];
325
+ for (const byte of data) {
326
+ value = (value << fromBits) | byte;
327
+ bits += fromBits;
328
+ while (bits >= toBits) {
329
+ bits -= toBits;
330
+ output.push((value >> bits) & maxValue);
331
+ }
332
+ }
333
+ if (bits > 0) {
334
+ output.push((value << (toBits - bits)) & maxValue);
335
+ }
336
+ return output;
337
+ }
338
+
339
+ function bech32CreateChecksum(hrp: string, data: number[]): number[] {
340
+ const values = [...bech32HrpExpand(hrp), ...data, 0, 0, 0, 0, 0, 0];
341
+ const polymod = bech32Polymod(values) ^ 1;
342
+ const checksum: number[] = [];
343
+ for (let index = 0; index < 6; index += 1) {
344
+ checksum.push((polymod >> (5 * (5 - index))) & 31);
345
+ }
346
+ return checksum;
347
+ }
348
+
349
+ function bech32Encode(hrp: string, data: Uint8Array): string {
350
+ const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
351
+ const words = convertBits(data, 8, 5);
352
+ const combined = [...words, ...bech32CreateChecksum(hrp, words)];
353
+ return `${hrp}1${combined.map((value) => charset[value]).join("")}`;
354
+ }
355
+
356
+ async function createIdentityFromSeedBytes(
357
+ seedBytes: Uint8Array,
358
+ createdAt: string,
359
+ ): Promise<SerializedIdentity> {
360
+ const publicKeyBytes = ed25519.getPublicKey(seedBytes);
361
+ return {
362
+ format: IDENTITY_FORMAT,
363
+ version: IDENTITY_VERSION,
364
+ algorithm: IDENTITY_ALGORITHM,
365
+ createdAt,
366
+ publicKey: base64UrlEncode(publicKeyBytes),
367
+ keyId: bytesToHex(sha256(publicKeyBytes)),
368
+ material: {
369
+ kind: IDENTITY_MATERIAL_KIND,
370
+ seed: base64UrlEncode(seedBytes),
371
+ },
372
+ };
373
+ }
374
+
375
+ export function getDefaultSignatureContext(): string {
376
+ return ED25519_CONTEXT;
377
+ }
378
+
379
+ export function getIdentityDerivationPath(): string {
380
+ return IDENTITY_DERIVATION_PATH;
381
+ }
382
+
383
+ export function generateMnemonic(
384
+ input: {
385
+ words?: MnemonicWordCount;
386
+ } = {},
387
+ ): string {
388
+ const words = assertWordCount(input.words ?? 12);
389
+ return bip39GenerateMnemonic(englishWordlist, strengthFromWordCount(words));
390
+ }
391
+
392
+ export function validateMnemonic(
393
+ mnemonic: string,
394
+ options: { wordlist?: typeof BIP39_LANGUAGE } = {},
395
+ ): boolean {
396
+ if (options.wordlist && options.wordlist !== BIP39_LANGUAGE) {
397
+ throw new Error("Only the English BIP-39 wordlist is supported.");
398
+ }
399
+ return bip39ValidateMnemonic(String(mnemonic || "").trim(), englishWordlist);
400
+ }
401
+
402
+ export async function createIdentity(
403
+ createdAt = new Date().toISOString(),
404
+ ): Promise<SerializedIdentity> {
405
+ return createIdentityFromSeedBytes(ed25519.utils.randomSecretKey(), createdAt);
406
+ }
407
+
408
+ export async function createIdentityFromMnemonic(input: {
409
+ mnemonic: string;
410
+ passphrase?: string;
411
+ createdAt?: string;
412
+ }): Promise<SerializedIdentity> {
413
+ const mnemonic = String(input.mnemonic || "").trim();
414
+ if (!validateMnemonic(mnemonic)) {
415
+ throw new Error("Mnemonic phrase is not a valid English BIP-39 phrase.");
416
+ }
417
+ const mnemonicSeed = mnemonicToSeedSync(mnemonic, input.passphrase || "");
418
+ const derivedSeed = deriveSlip10Seed(mnemonicSeed);
419
+ return createIdentityFromSeedBytes(derivedSeed, input.createdAt ?? new Date().toISOString());
420
+ }
421
+
422
+ export async function createMnemonicIdentity(
423
+ input: {
424
+ words?: MnemonicWordCount;
425
+ passphrase?: string;
426
+ createdAt?: string;
427
+ } = {},
428
+ ): Promise<{ identity: SerializedIdentity; mnemonic: string }> {
429
+ const mnemonic = generateMnemonic({ words: input.words ?? 12 });
430
+ return {
431
+ mnemonic,
432
+ identity: await createIdentityFromMnemonic({
433
+ mnemonic,
434
+ passphrase: input.passphrase,
435
+ createdAt: input.createdAt,
436
+ }),
437
+ };
438
+ }
439
+
440
+ export async function derivePublicKey(seed: string | Uint8Array | ArrayBuffer): Promise<string> {
441
+ return base64UrlEncode(ed25519.getPublicKey(resolveSeedBytes(seed)));
442
+ }
443
+
444
+ export async function deriveKeyId(publicKey: string | Uint8Array | ArrayBuffer): Promise<string> {
445
+ return bytesToHex(sha256(resolvePublicKeyBytes(publicKey)));
446
+ }
447
+
448
+ export function parseIdentity(input: string | SerializedIdentity): SerializedIdentity {
449
+ const parsed = typeof input === "string" ? JSON.parse(input) : input;
450
+ if (!isSerializedIdentity(parsed)) {
451
+ throw new Error("Identity payload must be a ternent-identity v2 object.");
452
+ }
453
+ return toCanonicalIdentity(parsed);
454
+ }
455
+
456
+ export function serializeIdentity(identity: SerializedIdentity, pretty = true): string {
457
+ return `${JSON.stringify(parseIdentity(identity), null, pretty ? 2 : 0)}\n`;
458
+ }
459
+
460
+ export async function validateIdentity(identity: SerializedIdentity): Promise<SerializedIdentity> {
461
+ const parsed = parseIdentity(identity);
462
+ const derivedPublicKey = await derivePublicKey(parsed.material.seed);
463
+ if (derivedPublicKey !== normalizeBase64Url(parsed.publicKey)) {
464
+ throw new Error("Identity publicKey does not match the stored seed.");
465
+ }
466
+ const derivedKeyId = await deriveKeyId(parsed.publicKey);
467
+ if (derivedKeyId !== parsed.keyId) {
468
+ throw new Error("Identity keyId does not match the public key.");
469
+ }
470
+ return parsed;
471
+ }
472
+
473
+ export async function signBytes(
474
+ identityOrSeed: SeedLike,
475
+ payload: Uint8Array | ArrayBuffer,
476
+ options: { context?: string } = {},
477
+ ): Promise<string> {
478
+ return base64UrlEncode(
479
+ ed25519.sign(
480
+ combineContext(ensureBytes(payload, "Payload"), options.context),
481
+ resolveSeedBytes(identityOrSeed),
482
+ ),
483
+ );
484
+ }
485
+
486
+ export async function verifyBytes(
487
+ publicKey: string | Uint8Array | ArrayBuffer,
488
+ payload: Uint8Array | ArrayBuffer,
489
+ signature: string,
490
+ options: { context?: string } = {},
491
+ ): Promise<boolean> {
492
+ return ed25519.verify(
493
+ base64UrlDecode(signature),
494
+ combineContext(ensureBytes(payload, "Payload"), options.context),
495
+ resolvePublicKeyBytes(publicKey),
496
+ );
497
+ }
498
+
499
+ export async function signUtf8(
500
+ identityOrSeed: SeedLike,
501
+ value: string,
502
+ options: { context?: string } = {},
503
+ ): Promise<string> {
504
+ return signBytes(identityOrSeed, utf8Bytes(value), options);
505
+ }
506
+
507
+ export async function verifyUtf8(
508
+ publicKey: string | Uint8Array | ArrayBuffer,
509
+ value: string,
510
+ signature: string,
511
+ options: { context?: string } = {},
512
+ ): Promise<boolean> {
513
+ return verifyBytes(publicKey, utf8Bytes(value), signature, options);
514
+ }
515
+
516
+ export async function deriveX25519PrivateKey(input: SeedLike): Promise<string> {
517
+ return base64UrlEncode(resolveX25519PrivateKeyBytes(input));
518
+ }
519
+
520
+ export async function deriveX25519PublicKey(input: PublicKeyLike): Promise<string> {
521
+ return base64UrlEncode(resolveX25519PublicKeyBytes(input));
522
+ }
523
+
524
+ export async function convertEd25519PublicKeyToX25519PublicKey(
525
+ publicKey: string | Uint8Array | ArrayBuffer,
526
+ ): Promise<string> {
527
+ return base64UrlEncode(
528
+ convertEd25519PublicKeyToX25519PublicKeyBytes(resolvePublicKeyBytes(publicKey)),
529
+ );
530
+ }
531
+
532
+ export async function deriveAgeRecipient(input: PublicKeyLike): Promise<string> {
533
+ return bech32Encode("age", resolveX25519PublicKeyBytes(input));
534
+ }
535
+
536
+ export async function deriveAgeSecretKey(input: SeedLike): Promise<string> {
537
+ return bech32Encode("age-secret-key-", resolveX25519PrivateKeyBytes(input)).toUpperCase();
538
+ }
@@ -0,0 +1,172 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ convertEd25519PublicKeyToX25519PublicKey,
4
+ createIdentity,
5
+ createIdentityFromMnemonic,
6
+ createMnemonicIdentity,
7
+ deriveAgeRecipient,
8
+ deriveAgeSecretKey,
9
+ deriveKeyId,
10
+ derivePublicKey,
11
+ deriveX25519PublicKey,
12
+ getIdentityDerivationPath,
13
+ generateMnemonic,
14
+ parseIdentity,
15
+ serializeIdentity,
16
+ signUtf8,
17
+ validateMnemonic,
18
+ verifyUtf8,
19
+ } from "../src/index";
20
+
21
+ describe("@ternent/identity", () => {
22
+ it("round-trips a serialized identity", async () => {
23
+ const identity = await createIdentity("2026-03-16T00:00:00.000Z");
24
+ const parsed = parseIdentity(serializeIdentity(identity));
25
+
26
+ expect(parsed).toEqual(identity);
27
+ });
28
+
29
+ it("serializes a canonical identity payload without UI-only fields", async () => {
30
+ const identity = await createIdentity("2026-03-16T00:00:00.000Z");
31
+ const withUiState = {
32
+ ...identity,
33
+ id: `identity-${identity.keyId.slice(0, 12)}`,
34
+ };
35
+
36
+ expect(JSON.parse(serializeIdentity(withUiState as typeof identity))).toEqual(identity);
37
+ });
38
+
39
+ it("derives deterministic public keys and key ids from the seed", async () => {
40
+ const identity = await createIdentity();
41
+
42
+ expect(await derivePublicKey(identity.material.seed)).toBe(identity.publicKey);
43
+ expect(await deriveKeyId(identity.publicKey)).toBe(identity.keyId);
44
+ });
45
+
46
+ it("creates deterministic identities from a mnemonic phrase", async () => {
47
+ const input = {
48
+ mnemonic:
49
+ "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
50
+ passphrase: "TREZOR",
51
+ createdAt: "2026-03-16T00:00:00.000Z",
52
+ };
53
+
54
+ const identityA = await createIdentityFromMnemonic(input);
55
+ const identityB = await createIdentityFromMnemonic(input);
56
+
57
+ expect(identityA).toEqual(identityB);
58
+ expect(identityA).toEqual({
59
+ format: "ternent-identity",
60
+ version: "2",
61
+ algorithm: "Ed25519",
62
+ createdAt: "2026-03-16T00:00:00.000Z",
63
+ publicKey: "6IBjlSqvqPfmXohZXC-oqbjX71wHutV9vvrRtw5VrUE",
64
+ keyId: "f34b06f6ffee32599d0e3f49bbee43baf4018ab07378b505da8c778f766096b8",
65
+ material: {
66
+ kind: "seed",
67
+ seed: "u7XruZ6e2gC5owNbLcZEqRFbgD8GPrqjyn-l5-SIz64",
68
+ },
69
+ });
70
+ expect(getIdentityDerivationPath()).toBe("m/101010'/25519'/0'");
71
+ });
72
+
73
+ it("generates and validates 12- and 24-word mnemonics", async () => {
74
+ const mnemonic12 = generateMnemonic({ words: 12 });
75
+ const mnemonic24 =
76
+ "liberty bag shell level tip galaxy glow shrimp cram hood lawsuit error waste zoo wash rough cinnamon firm sister mistake awful seven nurse hawk";
77
+ const created = await createMnemonicIdentity({
78
+ words: 12,
79
+ passphrase: "optional",
80
+ createdAt: "2026-03-16T00:00:00.000Z",
81
+ });
82
+ const twentyFourWordIdentity = await createIdentityFromMnemonic({
83
+ mnemonic: mnemonic24,
84
+ createdAt: "2026-03-16T00:00:00.000Z",
85
+ });
86
+ const noPassphraseIdentity = await createIdentityFromMnemonic({
87
+ mnemonic: "legal winner thank year wave sausage worth useful legal winner thank yellow",
88
+ createdAt: "2026-03-16T00:00:00.000Z",
89
+ });
90
+
91
+ expect(mnemonic12.split(" ")).toHaveLength(12);
92
+ expect(mnemonic24.split(" ")).toHaveLength(24);
93
+ expect(validateMnemonic(mnemonic12)).toBe(true);
94
+ expect(validateMnemonic(mnemonic24)).toBe(true);
95
+ expect(created.mnemonic.split(" ")).toHaveLength(12);
96
+ expect(created.identity.material.kind).toBe("seed");
97
+ expect(twentyFourWordIdentity).toEqual({
98
+ format: "ternent-identity",
99
+ version: "2",
100
+ algorithm: "Ed25519",
101
+ createdAt: "2026-03-16T00:00:00.000Z",
102
+ publicKey: "E71yH7ReOw9yPR0rACZpa_ZU1D8xotD0uYhv5-bDih0",
103
+ keyId: "41db29a5d32f112d1cec0511120309e9e02e7f15efda8a1890d269060e120839",
104
+ material: {
105
+ kind: "seed",
106
+ seed: "GXKurndvf5UYhth7C_uuVjUf84V_17xe0kpIyVS12WI",
107
+ },
108
+ });
109
+ expect(noPassphraseIdentity).toEqual({
110
+ format: "ternent-identity",
111
+ version: "2",
112
+ algorithm: "Ed25519",
113
+ createdAt: "2026-03-16T00:00:00.000Z",
114
+ publicKey: "8gb5_L0cJPiga567IOtUgTydVa_2361Al17RE0v6sfA",
115
+ keyId: "9bcf8f0f872020e588995f39b82e3ceb454e415f7edef4ecf7a5007dc50f8824",
116
+ material: {
117
+ kind: "seed",
118
+ seed: "9OLA_8sUT8OZ0ILgUUzjI3Bq_uBKGa9Z5fmAPhf1RmE",
119
+ },
120
+ });
121
+ });
122
+
123
+ it("uses signature context separation", async () => {
124
+ const identity = await createIdentity();
125
+ const signature = await signUtf8(identity, "hello", { context: "one" });
126
+
127
+ expect(
128
+ await verifyUtf8(identity.publicKey, "hello", signature, {
129
+ context: "one",
130
+ }),
131
+ ).toBe(true);
132
+ expect(
133
+ await verifyUtf8(identity.publicKey, "hello", signature, {
134
+ context: "two",
135
+ }),
136
+ ).toBe(false);
137
+ });
138
+
139
+ it("matches X25519 public derivation from seed and public conversion", async () => {
140
+ const identity = await createIdentity();
141
+
142
+ expect(await deriveX25519PublicKey(identity)).toBe(
143
+ await convertEd25519PublicKeyToX25519PublicKey(identity.publicKey),
144
+ );
145
+ });
146
+
147
+ it("derives stable age-compatible recipient and secret strings", async () => {
148
+ const identity = await createIdentity();
149
+
150
+ const recipient = await deriveAgeRecipient(identity);
151
+ const secret = await deriveAgeSecretKey(identity);
152
+
153
+ expect(recipient.startsWith("age1")).toBe(true);
154
+ expect(secret.startsWith("AGE-SECRET-KEY-1")).toBe(true);
155
+ expect(await deriveAgeRecipient(identity)).toBe(recipient);
156
+ expect(await deriveAgeSecretKey(identity)).toBe(secret);
157
+ });
158
+
159
+ it("never serializes mnemonic material into the exported identity", async () => {
160
+ const identity = await createIdentityFromMnemonic({
161
+ mnemonic:
162
+ "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
163
+ passphrase: "TREZOR",
164
+ createdAt: "2026-03-16T00:00:00.000Z",
165
+ });
166
+ const exported = JSON.parse(serializeIdentity(identity)) as Record<string, unknown>;
167
+
168
+ expect(exported.material).toEqual(identity.material);
169
+ expect(exported.mnemonic).toBeUndefined();
170
+ expect(exported.passphrase).toBeUndefined();
171
+ });
172
+ });