@oxy.so/core 1.0.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 (673) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +16 -0
  3. package/README.md +221 -0
  4. package/dist/cjs/.tsbuildinfo +1 -0
  5. package/dist/cjs/HttpService.js +1270 -0
  6. package/dist/cjs/OxyServices.base.js +390 -0
  7. package/dist/cjs/OxyServices.errors.js +95 -0
  8. package/dist/cjs/OxyServices.js +72 -0
  9. package/dist/cjs/boot/sessionColdBoot.js +395 -0
  10. package/dist/cjs/constants/version.js +16 -0
  11. package/dist/cjs/crypto/aead.js +79 -0
  12. package/dist/cjs/crypto/ecdh.js +35 -0
  13. package/dist/cjs/crypto/identityMarker.js +255 -0
  14. package/dist/cjs/crypto/kdf.js +39 -0
  15. package/dist/cjs/crypto/keyManager.js +2234 -0
  16. package/dist/cjs/crypto/polyfill.js +131 -0
  17. package/dist/cjs/crypto/recoveryPhrase.js +242 -0
  18. package/dist/cjs/crypto/signatureService.js +273 -0
  19. package/dist/cjs/i18n/accountCategoryLabels.js +44 -0
  20. package/dist/cjs/i18n/accountRoleLabels.js +27 -0
  21. package/dist/cjs/i18n/index.js +90 -0
  22. package/dist/cjs/i18n/locales/ar-SA.json +282 -0
  23. package/dist/cjs/i18n/locales/ca-ES.json +282 -0
  24. package/dist/cjs/i18n/locales/de-DE.json +282 -0
  25. package/dist/cjs/i18n/locales/en-US.json +2161 -0
  26. package/dist/cjs/i18n/locales/es-ES.json +2161 -0
  27. package/dist/cjs/i18n/locales/fr-FR.json +282 -0
  28. package/dist/cjs/i18n/locales/it-IT.json +282 -0
  29. package/dist/cjs/i18n/locales/ja-JP.json +282 -0
  30. package/dist/cjs/i18n/locales/ko-KR.json +282 -0
  31. package/dist/cjs/i18n/locales/locales/ar-SA.json +282 -0
  32. package/dist/cjs/i18n/locales/locales/ca-ES.json +282 -0
  33. package/dist/cjs/i18n/locales/locales/de-DE.json +282 -0
  34. package/dist/cjs/i18n/locales/locales/en-US.json +2161 -0
  35. package/dist/cjs/i18n/locales/locales/es-ES.json +2161 -0
  36. package/dist/cjs/i18n/locales/locales/fr-FR.json +282 -0
  37. package/dist/cjs/i18n/locales/locales/it-IT.json +282 -0
  38. package/dist/cjs/i18n/locales/locales/ja-JP.json +282 -0
  39. package/dist/cjs/i18n/locales/locales/ko-KR.json +282 -0
  40. package/dist/cjs/i18n/locales/locales/pt-PT.json +282 -0
  41. package/dist/cjs/i18n/locales/locales/zh-CN.json +282 -0
  42. package/dist/cjs/i18n/locales/pt-PT.json +282 -0
  43. package/dist/cjs/i18n/locales/zh-CN.json +282 -0
  44. package/dist/cjs/i18n/reputationCategoryLabels.js +20 -0
  45. package/dist/cjs/i18n/trustTierLabels.js +19 -0
  46. package/dist/cjs/index.js +498 -0
  47. package/dist/cjs/inference/OxyInferenceClient.js +544 -0
  48. package/dist/cjs/logger/index.js +149 -0
  49. package/dist/cjs/mixins/OxyServices.accounts.js +555 -0
  50. package/dist/cjs/mixins/OxyServices.analytics.js +49 -0
  51. package/dist/cjs/mixins/OxyServices.appData.js +108 -0
  52. package/dist/cjs/mixins/OxyServices.assets.js +764 -0
  53. package/dist/cjs/mixins/OxyServices.auth.js +1254 -0
  54. package/dist/cjs/mixins/OxyServices.chains.js +73 -0
  55. package/dist/cjs/mixins/OxyServices.civic.js +611 -0
  56. package/dist/cjs/mixins/OxyServices.connectedApps.js +78 -0
  57. package/dist/cjs/mixins/OxyServices.contacts.js +50 -0
  58. package/dist/cjs/mixins/OxyServices.deviceBoot.js +191 -0
  59. package/dist/cjs/mixins/OxyServices.deviceTransfer.js +314 -0
  60. package/dist/cjs/mixins/OxyServices.devices.js +116 -0
  61. package/dist/cjs/mixins/OxyServices.features.js +335 -0
  62. package/dist/cjs/mixins/OxyServices.followGraph.js +221 -0
  63. package/dist/cjs/mixins/OxyServices.identity.js +461 -0
  64. package/dist/cjs/mixins/OxyServices.identityBackup.js +172 -0
  65. package/dist/cjs/mixins/OxyServices.inference.js +59 -0
  66. package/dist/cjs/mixins/OxyServices.language.js +125 -0
  67. package/dist/cjs/mixins/OxyServices.links.js +72 -0
  68. package/dist/cjs/mixins/OxyServices.location.js +43 -0
  69. package/dist/cjs/mixins/OxyServices.nodes.js +175 -0
  70. package/dist/cjs/mixins/OxyServices.notifications.js +63 -0
  71. package/dist/cjs/mixins/OxyServices.payment.js +158 -0
  72. package/dist/cjs/mixins/OxyServices.privacy.js +199 -0
  73. package/dist/cjs/mixins/OxyServices.reputation.js +289 -0
  74. package/dist/cjs/mixins/OxyServices.security.js +89 -0
  75. package/dist/cjs/mixins/OxyServices.store.js +266 -0
  76. package/dist/cjs/mixins/OxyServices.topics.js +137 -0
  77. package/dist/cjs/mixins/OxyServices.user.js +902 -0
  78. package/dist/cjs/mixins/OxyServices.utility.js +1098 -0
  79. package/dist/cjs/mixins/index.js +135 -0
  80. package/dist/cjs/mixins/mixinHelpers.js +53 -0
  81. package/dist/cjs/models/Topic.js +16 -0
  82. package/dist/cjs/models/interfaces.js +20 -0
  83. package/dist/cjs/models/session.js +2 -0
  84. package/dist/cjs/server/auth.js +167 -0
  85. package/dist/cjs/server/capabilityTicket.js +192 -0
  86. package/dist/cjs/server/cors.js +225 -0
  87. package/dist/cjs/server/index.js +97 -0
  88. package/dist/cjs/server/rateLimit.js +207 -0
  89. package/dist/cjs/server/safeFetch.js +458 -0
  90. package/dist/cjs/server/securityHeaders.js +377 -0
  91. package/dist/cjs/server/userInvalidation.js +156 -0
  92. package/dist/cjs/server/verifySecret.js +50 -0
  93. package/dist/cjs/session/SessionClient.js +865 -0
  94. package/dist/cjs/session/accountDelegationAccess.js +48 -0
  95. package/dist/cjs/session/accountDialogController.js +1131 -0
  96. package/dist/cjs/session/accountSwitchTargets.js +81 -0
  97. package/dist/cjs/session/authStateStore.js +404 -0
  98. package/dist/cjs/session/createSessionClient.js +32 -0
  99. package/dist/cjs/session/deviceDirectory.js +143 -0
  100. package/dist/cjs/session/deviceSwitcherRows.js +76 -0
  101. package/dist/cjs/session/identityPin.js +256 -0
  102. package/dist/cjs/session/identitySession.js +135 -0
  103. package/dist/cjs/session/projectSessionState.js +121 -0
  104. package/dist/cjs/session/refresh.js +398 -0
  105. package/dist/cjs/session/sessionClientHost.js +34 -0
  106. package/dist/cjs/session/sharedDeviceCredential.js +247 -0
  107. package/dist/cjs/session/socketLoader.js +55 -0
  108. package/dist/cjs/shared/utils/colorUtils.js +153 -0
  109. package/dist/cjs/shared/utils/errorUtils.js +183 -0
  110. package/dist/cjs/shared/utils/index.js +49 -0
  111. package/dist/cjs/shared/utils/networkUtils.js +183 -0
  112. package/dist/cjs/shared/utils/themeUtils.js +106 -0
  113. package/dist/cjs/utils/accountCacheSweep.js +80 -0
  114. package/dist/cjs/utils/accountUtils.js +140 -0
  115. package/dist/cjs/utils/activityId.js +6 -0
  116. package/dist/cjs/utils/apiUtils.js +91 -0
  117. package/dist/cjs/utils/asyncUtils.js +222 -0
  118. package/dist/cjs/utils/authHelpers.js +120 -0
  119. package/dist/cjs/utils/authWebUrl.js +19 -0
  120. package/dist/cjs/utils/avatarUtils.js +29 -0
  121. package/dist/cjs/utils/cache.js +235 -0
  122. package/dist/cjs/utils/cacheKey.js +85 -0
  123. package/dist/cjs/utils/coldBoot.js +122 -0
  124. package/dist/cjs/utils/commonsApproval.js +31 -0
  125. package/dist/cjs/utils/commonsDelivery.js +59 -0
  126. package/dist/cjs/utils/deviceManager.js +174 -0
  127. package/dist/cjs/utils/displayNamePolicyRanges.generated.js +60 -0
  128. package/dist/cjs/utils/edgeRegion.js +6 -0
  129. package/dist/cjs/utils/errorUtils.js +243 -0
  130. package/dist/cjs/utils/identityCacheSweep.js +97 -0
  131. package/dist/cjs/utils/languageUtils.js +224 -0
  132. package/dist/cjs/utils/oauthPkce.js +311 -0
  133. package/dist/cjs/utils/officialOrigins.js +64 -0
  134. package/dist/cjs/utils/oxyServiceEnvironment.js +19 -0
  135. package/dist/cjs/utils/platform.js +119 -0
  136. package/dist/cjs/utils/profileLinks.js +63 -0
  137. package/dist/cjs/utils/redactUrl.js +29 -0
  138. package/dist/cjs/utils/registrableApex.js +45 -0
  139. package/dist/cjs/utils/requestUtils.js +209 -0
  140. package/dist/cjs/utils/sessionUtils.js +181 -0
  141. package/dist/cjs/utils/textNormalization.js +158 -0
  142. package/dist/cjs/utils/userHandle.js +37 -0
  143. package/dist/cjs/utils/userIdentity.js +50 -0
  144. package/dist/cjs/utils/validationUtils.js +370 -0
  145. package/dist/cjs/utils/webauthnOrigin.js +51 -0
  146. package/dist/esm/.tsbuildinfo +1 -0
  147. package/dist/esm/HttpService.js +1266 -0
  148. package/dist/esm/OxyServices.base.js +386 -0
  149. package/dist/esm/OxyServices.errors.js +88 -0
  150. package/dist/esm/OxyServices.js +66 -0
  151. package/dist/esm/boot/sessionColdBoot.js +392 -0
  152. package/dist/esm/constants/version.js +13 -0
  153. package/dist/esm/crypto/aead.js +74 -0
  154. package/dist/esm/crypto/ecdh.js +32 -0
  155. package/dist/esm/crypto/identityMarker.js +248 -0
  156. package/dist/esm/crypto/kdf.js +36 -0
  157. package/dist/esm/crypto/keyManager.js +2227 -0
  158. package/dist/esm/crypto/polyfill.js +129 -0
  159. package/dist/esm/crypto/recoveryPhrase.js +238 -0
  160. package/dist/esm/crypto/signatureService.js +269 -0
  161. package/dist/esm/i18n/accountCategoryLabels.js +37 -0
  162. package/dist/esm/i18n/accountRoleLabels.js +20 -0
  163. package/dist/esm/i18n/index.js +83 -0
  164. package/dist/esm/i18n/locales/ar-SA.json +282 -0
  165. package/dist/esm/i18n/locales/ca-ES.json +282 -0
  166. package/dist/esm/i18n/locales/de-DE.json +282 -0
  167. package/dist/esm/i18n/locales/en-US.json +2161 -0
  168. package/dist/esm/i18n/locales/es-ES.json +2161 -0
  169. package/dist/esm/i18n/locales/fr-FR.json +282 -0
  170. package/dist/esm/i18n/locales/it-IT.json +282 -0
  171. package/dist/esm/i18n/locales/ja-JP.json +282 -0
  172. package/dist/esm/i18n/locales/ko-KR.json +282 -0
  173. package/dist/esm/i18n/locales/locales/ar-SA.json +282 -0
  174. package/dist/esm/i18n/locales/locales/ca-ES.json +282 -0
  175. package/dist/esm/i18n/locales/locales/de-DE.json +282 -0
  176. package/dist/esm/i18n/locales/locales/en-US.json +2161 -0
  177. package/dist/esm/i18n/locales/locales/es-ES.json +2161 -0
  178. package/dist/esm/i18n/locales/locales/fr-FR.json +282 -0
  179. package/dist/esm/i18n/locales/locales/it-IT.json +282 -0
  180. package/dist/esm/i18n/locales/locales/ja-JP.json +282 -0
  181. package/dist/esm/i18n/locales/locales/ko-KR.json +282 -0
  182. package/dist/esm/i18n/locales/locales/pt-PT.json +282 -0
  183. package/dist/esm/i18n/locales/locales/zh-CN.json +282 -0
  184. package/dist/esm/i18n/locales/pt-PT.json +282 -0
  185. package/dist/esm/i18n/locales/zh-CN.json +282 -0
  186. package/dist/esm/i18n/reputationCategoryLabels.js +13 -0
  187. package/dist/esm/i18n/trustTierLabels.js +12 -0
  188. package/dist/esm/index.js +254 -0
  189. package/dist/esm/inference/OxyInferenceClient.js +538 -0
  190. package/dist/esm/logger/index.js +140 -0
  191. package/dist/esm/mixins/OxyServices.accounts.js +543 -0
  192. package/dist/esm/mixins/OxyServices.analytics.js +46 -0
  193. package/dist/esm/mixins/OxyServices.appData.js +103 -0
  194. package/dist/esm/mixins/OxyServices.assets.js +761 -0
  195. package/dist/esm/mixins/OxyServices.auth.js +1247 -0
  196. package/dist/esm/mixins/OxyServices.chains.js +70 -0
  197. package/dist/esm/mixins/OxyServices.civic.js +605 -0
  198. package/dist/esm/mixins/OxyServices.connectedApps.js +75 -0
  199. package/dist/esm/mixins/OxyServices.contacts.js +47 -0
  200. package/dist/esm/mixins/OxyServices.deviceBoot.js +186 -0
  201. package/dist/esm/mixins/OxyServices.deviceTransfer.js +311 -0
  202. package/dist/esm/mixins/OxyServices.devices.js +113 -0
  203. package/dist/esm/mixins/OxyServices.features.js +332 -0
  204. package/dist/esm/mixins/OxyServices.followGraph.js +218 -0
  205. package/dist/esm/mixins/OxyServices.identity.js +457 -0
  206. package/dist/esm/mixins/OxyServices.identityBackup.js +169 -0
  207. package/dist/esm/mixins/OxyServices.inference.js +56 -0
  208. package/dist/esm/mixins/OxyServices.language.js +122 -0
  209. package/dist/esm/mixins/OxyServices.links.js +69 -0
  210. package/dist/esm/mixins/OxyServices.location.js +40 -0
  211. package/dist/esm/mixins/OxyServices.nodes.js +172 -0
  212. package/dist/esm/mixins/OxyServices.notifications.js +60 -0
  213. package/dist/esm/mixins/OxyServices.payment.js +155 -0
  214. package/dist/esm/mixins/OxyServices.privacy.js +196 -0
  215. package/dist/esm/mixins/OxyServices.reputation.js +286 -0
  216. package/dist/esm/mixins/OxyServices.security.js +86 -0
  217. package/dist/esm/mixins/OxyServices.store.js +263 -0
  218. package/dist/esm/mixins/OxyServices.topics.js +134 -0
  219. package/dist/esm/mixins/OxyServices.user.js +899 -0
  220. package/dist/esm/mixins/OxyServices.utility.js +1095 -0
  221. package/dist/esm/mixins/index.js +132 -0
  222. package/dist/esm/mixins/mixinHelpers.js +48 -0
  223. package/dist/esm/models/Topic.js +13 -0
  224. package/dist/esm/models/interfaces.js +17 -0
  225. package/dist/esm/models/session.js +1 -0
  226. package/dist/esm/server/auth.js +154 -0
  227. package/dist/esm/server/capabilityTicket.js +183 -0
  228. package/dist/esm/server/cors.js +220 -0
  229. package/dist/esm/server/index.js +47 -0
  230. package/dist/esm/server/rateLimit.js +201 -0
  231. package/dist/esm/server/safeFetch.js +447 -0
  232. package/dist/esm/server/securityHeaders.js +364 -0
  233. package/dist/esm/server/userInvalidation.js +152 -0
  234. package/dist/esm/server/verifySecret.js +47 -0
  235. package/dist/esm/session/SessionClient.js +861 -0
  236. package/dist/esm/session/accountDelegationAccess.js +45 -0
  237. package/dist/esm/session/accountDialogController.js +1126 -0
  238. package/dist/esm/session/accountSwitchTargets.js +77 -0
  239. package/dist/esm/session/authStateStore.js +398 -0
  240. package/dist/esm/session/createSessionClient.js +29 -0
  241. package/dist/esm/session/deviceDirectory.js +135 -0
  242. package/dist/esm/session/deviceSwitcherRows.js +72 -0
  243. package/dist/esm/session/identityPin.js +249 -0
  244. package/dist/esm/session/identitySession.js +131 -0
  245. package/dist/esm/session/projectSessionState.js +114 -0
  246. package/dist/esm/session/refresh.js +390 -0
  247. package/dist/esm/session/sessionClientHost.js +31 -0
  248. package/dist/esm/session/sharedDeviceCredential.js +239 -0
  249. package/dist/esm/session/socketLoader.js +19 -0
  250. package/dist/esm/shared/utils/colorUtils.js +143 -0
  251. package/dist/esm/shared/utils/errorUtils.js +170 -0
  252. package/dist/esm/shared/utils/index.js +15 -0
  253. package/dist/esm/shared/utils/networkUtils.js +173 -0
  254. package/dist/esm/shared/utils/themeUtils.js +98 -0
  255. package/dist/esm/utils/accountCacheSweep.js +75 -0
  256. package/dist/esm/utils/accountUtils.js +131 -0
  257. package/dist/esm/utils/activityId.js +2 -0
  258. package/dist/esm/utils/apiUtils.js +84 -0
  259. package/dist/esm/utils/asyncUtils.js +207 -0
  260. package/dist/esm/utils/authHelpers.js +111 -0
  261. package/dist/esm/utils/authWebUrl.js +16 -0
  262. package/dist/esm/utils/avatarUtils.js +26 -0
  263. package/dist/esm/utils/cache.js +227 -0
  264. package/dist/esm/utils/cacheKey.js +80 -0
  265. package/dist/esm/utils/coldBoot.js +119 -0
  266. package/dist/esm/utils/commonsApproval.js +27 -0
  267. package/dist/esm/utils/commonsDelivery.js +54 -0
  268. package/dist/esm/utils/deviceManager.js +170 -0
  269. package/dist/esm/utils/displayNamePolicyRanges.generated.js +57 -0
  270. package/dist/esm/utils/edgeRegion.js +2 -0
  271. package/dist/esm/utils/errorUtils.js +232 -0
  272. package/dist/esm/utils/identityCacheSweep.js +92 -0
  273. package/dist/esm/utils/languageUtils.js +212 -0
  274. package/dist/esm/utils/oauthPkce.js +297 -0
  275. package/dist/esm/utils/officialOrigins.js +59 -0
  276. package/dist/esm/utils/oxyServiceEnvironment.js +16 -0
  277. package/dist/esm/utils/platform.js +110 -0
  278. package/dist/esm/utils/profileLinks.js +60 -0
  279. package/dist/esm/utils/redactUrl.js +26 -0
  280. package/dist/esm/utils/registrableApex.js +42 -0
  281. package/dist/esm/utils/requestUtils.js +203 -0
  282. package/dist/esm/utils/sessionUtils.js +171 -0
  283. package/dist/esm/utils/textNormalization.js +154 -0
  284. package/dist/esm/utils/userHandle.js +33 -0
  285. package/dist/esm/utils/userIdentity.js +45 -0
  286. package/dist/esm/utils/validationUtils.js +350 -0
  287. package/dist/esm/utils/webauthnOrigin.js +48 -0
  288. package/dist/types/.tsbuildinfo +1 -0
  289. package/dist/types/HttpService.d.ts +413 -0
  290. package/dist/types/OxyServices.base.d.ts +187 -0
  291. package/dist/types/OxyServices.d.ts +139 -0
  292. package/dist/types/OxyServices.errors.d.ts +84 -0
  293. package/dist/types/boot/sessionColdBoot.d.ts +140 -0
  294. package/dist/types/constants/version.d.ts +13 -0
  295. package/dist/types/crypto/aead.d.ts +56 -0
  296. package/dist/types/crypto/ecdh.d.ts +29 -0
  297. package/dist/types/crypto/identityMarker.d.ts +94 -0
  298. package/dist/types/crypto/kdf.d.ts +25 -0
  299. package/dist/types/crypto/keyManager.d.ts +582 -0
  300. package/dist/types/crypto/polyfill.d.ts +25 -0
  301. package/dist/types/crypto/recoveryPhrase.d.ts +173 -0
  302. package/dist/types/crypto/signatureService.d.ts +136 -0
  303. package/dist/types/i18n/accountCategoryLabels.d.ts +34 -0
  304. package/dist/types/i18n/accountRoleLabels.d.ts +10 -0
  305. package/dist/types/i18n/index.d.ts +3 -0
  306. package/dist/types/i18n/reputationCategoryLabels.d.ts +10 -0
  307. package/dist/types/i18n/trustTierLabels.d.ts +9 -0
  308. package/dist/types/index.d.ts +145 -0
  309. package/dist/types/inference/OxyInferenceClient.d.ts +342 -0
  310. package/dist/types/logger/index.d.ts +104 -0
  311. package/dist/types/mixins/OxyServices.accounts.d.ts +868 -0
  312. package/dist/types/mixins/OxyServices.analytics.d.ts +72 -0
  313. package/dist/types/mixins/OxyServices.appData.d.ts +108 -0
  314. package/dist/types/mixins/OxyServices.assets.d.ts +329 -0
  315. package/dist/types/mixins/OxyServices.auth.d.ts +908 -0
  316. package/dist/types/mixins/OxyServices.chains.d.ts +156 -0
  317. package/dist/types/mixins/OxyServices.civic.d.ts +510 -0
  318. package/dist/types/mixins/OxyServices.connectedApps.d.ts +169 -0
  319. package/dist/types/mixins/OxyServices.contacts.d.ts +100 -0
  320. package/dist/types/mixins/OxyServices.deviceBoot.d.ts +185 -0
  321. package/dist/types/mixins/OxyServices.deviceTransfer.d.ts +149 -0
  322. package/dist/types/mixins/OxyServices.devices.d.ts +103 -0
  323. package/dist/types/mixins/OxyServices.features.d.ts +250 -0
  324. package/dist/types/mixins/OxyServices.followGraph.d.ts +223 -0
  325. package/dist/types/mixins/OxyServices.identity.d.ts +351 -0
  326. package/dist/types/mixins/OxyServices.identityBackup.d.ts +129 -0
  327. package/dist/types/mixins/OxyServices.inference.d.ts +95 -0
  328. package/dist/types/mixins/OxyServices.language.d.ts +87 -0
  329. package/dist/types/mixins/OxyServices.links.d.ts +104 -0
  330. package/dist/types/mixins/OxyServices.location.d.ts +70 -0
  331. package/dist/types/mixins/OxyServices.nodes.d.ts +198 -0
  332. package/dist/types/mixins/OxyServices.notifications.d.ts +143 -0
  333. package/dist/types/mixins/OxyServices.payment.d.ts +117 -0
  334. package/dist/types/mixins/OxyServices.privacy.d.ts +146 -0
  335. package/dist/types/mixins/OxyServices.reputation.d.ts +202 -0
  336. package/dist/types/mixins/OxyServices.security.d.ts +84 -0
  337. package/dist/types/mixins/OxyServices.store.d.ts +334 -0
  338. package/dist/types/mixins/OxyServices.topics.d.ts +110 -0
  339. package/dist/types/mixins/OxyServices.user.d.ts +558 -0
  340. package/dist/types/mixins/OxyServices.utility.d.ts +393 -0
  341. package/dist/types/mixins/index.d.ts +87 -0
  342. package/dist/types/mixins/mixinHelpers.d.ts +31 -0
  343. package/dist/types/models/Topic.d.ts +42 -0
  344. package/dist/types/models/interfaces.d.ts +675 -0
  345. package/dist/types/models/session.d.ts +51 -0
  346. package/dist/types/server/auth.d.ts +130 -0
  347. package/dist/types/server/capabilityTicket.d.ts +41 -0
  348. package/dist/types/server/cors.d.ts +98 -0
  349. package/dist/types/server/index.d.ts +35 -0
  350. package/dist/types/server/rateLimit.d.ts +40 -0
  351. package/dist/types/server/safeFetch.d.ts +137 -0
  352. package/dist/types/server/securityHeaders.d.ts +221 -0
  353. package/dist/types/server/userInvalidation.d.ts +114 -0
  354. package/dist/types/server/verifySecret.d.ts +29 -0
  355. package/dist/types/session/SessionClient.d.ts +418 -0
  356. package/dist/types/session/accountDelegationAccess.d.ts +40 -0
  357. package/dist/types/session/accountDialogController.d.ts +648 -0
  358. package/dist/types/session/accountSwitchTargets.d.ts +70 -0
  359. package/dist/types/session/authStateStore.d.ts +153 -0
  360. package/dist/types/session/createSessionClient.d.ts +30 -0
  361. package/dist/types/session/deviceDirectory.d.ts +182 -0
  362. package/dist/types/session/deviceSwitcherRows.d.ts +92 -0
  363. package/dist/types/session/identityPin.d.ts +87 -0
  364. package/dist/types/session/identitySession.d.ts +105 -0
  365. package/dist/types/session/projectSessionState.d.ts +74 -0
  366. package/dist/types/session/refresh.d.ts +179 -0
  367. package/dist/types/session/sessionClientHost.d.ts +18 -0
  368. package/dist/types/session/sharedDeviceCredential.d.ts +202 -0
  369. package/dist/types/session/socketLoader.d.ts +11 -0
  370. package/dist/types/shared/utils/colorUtils.d.ts +104 -0
  371. package/dist/types/shared/utils/errorUtils.d.ts +97 -0
  372. package/dist/types/shared/utils/index.d.ts +13 -0
  373. package/dist/types/shared/utils/networkUtils.d.ts +139 -0
  374. package/dist/types/shared/utils/themeUtils.d.ts +90 -0
  375. package/dist/types/utils/accountCacheSweep.d.ts +75 -0
  376. package/dist/types/utils/accountUtils.d.ts +100 -0
  377. package/dist/types/utils/activityId.d.ts +2 -0
  378. package/dist/types/utils/apiUtils.d.ts +95 -0
  379. package/dist/types/utils/asyncUtils.d.ts +62 -0
  380. package/dist/types/utils/authHelpers.d.ts +58 -0
  381. package/dist/types/utils/authWebUrl.d.ts +16 -0
  382. package/dist/types/utils/avatarUtils.d.ts +16 -0
  383. package/dist/types/utils/cache.d.ts +130 -0
  384. package/dist/types/utils/cacheKey.d.ts +66 -0
  385. package/dist/types/utils/coldBoot.d.ts +131 -0
  386. package/dist/types/utils/commonsApproval.d.ts +13 -0
  387. package/dist/types/utils/commonsDelivery.d.ts +105 -0
  388. package/dist/types/utils/deviceManager.d.ts +65 -0
  389. package/dist/types/utils/displayNamePolicyRanges.generated.d.ts +57 -0
  390. package/dist/types/utils/edgeRegion.d.ts +2 -0
  391. package/dist/types/utils/errorUtils.d.ts +125 -0
  392. package/dist/types/utils/identityCacheSweep.d.ts +80 -0
  393. package/dist/types/utils/languageUtils.d.ts +100 -0
  394. package/dist/types/utils/oauthPkce.d.ts +157 -0
  395. package/dist/types/utils/officialOrigins.d.ts +13 -0
  396. package/dist/types/utils/oxyServiceEnvironment.d.ts +17 -0
  397. package/dist/types/utils/platform.d.ts +49 -0
  398. package/dist/types/utils/profileLinks.d.ts +44 -0
  399. package/dist/types/utils/redactUrl.d.ts +17 -0
  400. package/dist/types/utils/registrableApex.d.ts +27 -0
  401. package/dist/types/utils/requestUtils.d.ts +123 -0
  402. package/dist/types/utils/sessionUtils.d.ts +54 -0
  403. package/dist/types/utils/textNormalization.d.ts +103 -0
  404. package/dist/types/utils/userHandle.d.ts +26 -0
  405. package/dist/types/utils/userIdentity.d.ts +21 -0
  406. package/dist/types/utils/validationUtils.d.ts +268 -0
  407. package/dist/types/utils/webauthnOrigin.d.ts +32 -0
  408. package/package.json +168 -0
  409. package/src/HttpService.ts +1553 -0
  410. package/src/OxyServices.base.ts +478 -0
  411. package/src/OxyServices.errors.ts +122 -0
  412. package/src/OxyServices.ts +172 -0
  413. package/src/__tests__/authSocket.test.ts +96 -0
  414. package/src/__tests__/httpServiceActivityId.test.ts +49 -0
  415. package/src/__tests__/httpServiceAuthSelfAwait.test.ts +108 -0
  416. package/src/__tests__/httpServiceCache.test.ts +318 -0
  417. package/src/__tests__/httpServiceCsrf.test.ts +235 -0
  418. package/src/__tests__/httpServiceFormEncoded.test.ts +142 -0
  419. package/src/__tests__/httpServiceQueueDeadlock.test.ts +128 -0
  420. package/src/__tests__/httpServiceUnwrapEnvelope.test.ts +115 -0
  421. package/src/__tests__/inSessionRefresh.test.ts +260 -0
  422. package/src/__tests__/linkedClient.test.ts +370 -0
  423. package/src/__tests__/parseHttpErrorBody.test.ts +116 -0
  424. package/src/__tests__/serverValueImportsDeclared.test.ts +120 -0
  425. package/src/__tests__/userIdentity.test.ts +143 -0
  426. package/src/boot/__tests__/sessionColdBoot.identity.test.ts +393 -0
  427. package/src/boot/__tests__/sessionColdBoot.sharedDevice.test.ts +325 -0
  428. package/src/boot/__tests__/sessionColdBoot.test.ts +570 -0
  429. package/src/boot/sessionColdBoot.ts +529 -0
  430. package/src/constants/version.ts +15 -0
  431. package/src/crypto/__tests__/backupMaterial.test.ts +86 -0
  432. package/src/crypto/__tests__/cryptoPrimitives.test.ts +226 -0
  433. package/src/crypto/__tests__/identityMocks.ts +125 -0
  434. package/src/crypto/__tests__/keyManager.atomicity.test.ts +272 -0
  435. package/src/crypto/__tests__/keyManager.cacheSafety.test.ts +175 -0
  436. package/src/crypto/__tests__/keyManager.identityStatus.test.ts +217 -0
  437. package/src/crypto/__tests__/keyManager.recoveryLadder.test.ts +179 -0
  438. package/src/crypto/__tests__/keyManager.recoveryMnemonic.test.ts +138 -0
  439. package/src/crypto/__tests__/keyManager.storageMigration.test.ts +227 -0
  440. package/src/crypto/__tests__/keyManager.test.ts +329 -0
  441. package/src/crypto/__tests__/polyfill.test.ts +109 -0
  442. package/src/crypto/__tests__/recoveryPhrase.test.ts +61 -0
  443. package/src/crypto/__tests__/scopedSeed.test.ts +126 -0
  444. package/src/crypto/__tests__/signChallengeShared.test.ts +63 -0
  445. package/src/crypto/__tests__/signedRecord.test.ts +230 -0
  446. package/src/crypto/aead.ts +97 -0
  447. package/src/crypto/ecdh.ts +37 -0
  448. package/src/crypto/identityMarker.ts +291 -0
  449. package/src/crypto/kdf.ts +43 -0
  450. package/src/crypto/keyManager.ts +2607 -0
  451. package/src/crypto/polyfill.ts +143 -0
  452. package/src/crypto/recoveryPhrase.ts +337 -0
  453. package/src/crypto/signatureService.ts +350 -0
  454. package/src/i18n/__tests__/accountCategoryLabels.test.ts +62 -0
  455. package/src/i18n/__tests__/accountRoleLabels.test.ts +54 -0
  456. package/src/i18n/__tests__/reputationCategoryLabels.test.ts +56 -0
  457. package/src/i18n/__tests__/trustTierLabels.test.ts +47 -0
  458. package/src/i18n/accountCategoryLabels.ts +44 -0
  459. package/src/i18n/accountRoleLabels.ts +26 -0
  460. package/src/i18n/index.ts +88 -0
  461. package/src/i18n/locales/ar-SA.json +282 -0
  462. package/src/i18n/locales/ca-ES.json +282 -0
  463. package/src/i18n/locales/de-DE.json +282 -0
  464. package/src/i18n/locales/en-US.json +2161 -0
  465. package/src/i18n/locales/es-ES.json +2161 -0
  466. package/src/i18n/locales/fr-FR.json +282 -0
  467. package/src/i18n/locales/it-IT.json +282 -0
  468. package/src/i18n/locales/ja-JP.json +282 -0
  469. package/src/i18n/locales/ko-KR.json +282 -0
  470. package/src/i18n/locales/pt-PT.json +282 -0
  471. package/src/i18n/locales/zh-CN.json +282 -0
  472. package/src/i18n/reputationCategoryLabels.ts +20 -0
  473. package/src/i18n/trustTierLabels.ts +18 -0
  474. package/src/index.ts +881 -0
  475. package/src/inference/OxyInferenceClient.ts +888 -0
  476. package/src/inference/__tests__/OxyInferenceClient.test.ts +834 -0
  477. package/src/logger/__tests__/logger.test.ts +207 -0
  478. package/src/logger/index.ts +217 -0
  479. package/src/mixins/OxyServices.accounts.ts +1330 -0
  480. package/src/mixins/OxyServices.analytics.ts +53 -0
  481. package/src/mixins/OxyServices.appData.ts +158 -0
  482. package/src/mixins/OxyServices.assets.ts +861 -0
  483. package/src/mixins/OxyServices.auth.ts +1858 -0
  484. package/src/mixins/OxyServices.chains.ts +134 -0
  485. package/src/mixins/OxyServices.civic.ts +945 -0
  486. package/src/mixins/OxyServices.connectedApps.ts +174 -0
  487. package/src/mixins/OxyServices.contacts.ts +73 -0
  488. package/src/mixins/OxyServices.deviceBoot.ts +210 -0
  489. package/src/mixins/OxyServices.deviceTransfer.ts +393 -0
  490. package/src/mixins/OxyServices.devices.ts +120 -0
  491. package/src/mixins/OxyServices.features.ts +454 -0
  492. package/src/mixins/OxyServices.followGraph.ts +293 -0
  493. package/src/mixins/OxyServices.identity.ts +705 -0
  494. package/src/mixins/OxyServices.identityBackup.ts +250 -0
  495. package/src/mixins/OxyServices.inference.ts +57 -0
  496. package/src/mixins/OxyServices.language.ts +144 -0
  497. package/src/mixins/OxyServices.links.ts +107 -0
  498. package/src/mixins/OxyServices.location.ts +46 -0
  499. package/src/mixins/OxyServices.nodes.ts +337 -0
  500. package/src/mixins/OxyServices.notifications.ts +142 -0
  501. package/src/mixins/OxyServices.payment.ts +163 -0
  502. package/src/mixins/OxyServices.privacy.ts +219 -0
  503. package/src/mixins/OxyServices.reputation.ts +436 -0
  504. package/src/mixins/OxyServices.security.ts +110 -0
  505. package/src/mixins/OxyServices.store.ts +585 -0
  506. package/src/mixins/OxyServices.topics.ts +169 -0
  507. package/src/mixins/OxyServices.user.ts +1137 -0
  508. package/src/mixins/OxyServices.utility.ts +1370 -0
  509. package/src/mixins/__tests__/OxyServices.civic.test.ts +1095 -0
  510. package/src/mixins/__tests__/OxyServices.deviceBoot.test.ts +124 -0
  511. package/src/mixins/__tests__/OxyServices.deviceTransfer.test.ts +271 -0
  512. package/src/mixins/__tests__/OxyServices.identity.test.ts +376 -0
  513. package/src/mixins/__tests__/OxyServices.links.test.ts +154 -0
  514. package/src/mixins/__tests__/OxyServices.nodes.test.ts +341 -0
  515. package/src/mixins/__tests__/OxyServices.rotateKey.test.ts +276 -0
  516. package/src/mixins/__tests__/OxyServices.serviceAssetMetadata.test.ts +192 -0
  517. package/src/mixins/__tests__/OxyServices.serviceAssetMetadataBySha.test.ts +135 -0
  518. package/src/mixins/__tests__/accounts.test.ts +727 -0
  519. package/src/mixins/__tests__/appData.test.ts +203 -0
  520. package/src/mixins/__tests__/assetCredentials.test.ts +47 -0
  521. package/src/mixins/__tests__/assetUpload.test.ts +191 -0
  522. package/src/mixins/__tests__/backgroundCredential.httpIntegration.test.ts +135 -0
  523. package/src/mixins/__tests__/chains.test.ts +113 -0
  524. package/src/mixins/__tests__/commonsSignIn.test.ts +1144 -0
  525. package/src/mixins/__tests__/connectedApps.test.ts +123 -0
  526. package/src/mixins/__tests__/deviceTokenMintPinned.test.ts +113 -0
  527. package/src/mixins/__tests__/discoveryErrorHandling.test.ts +266 -0
  528. package/src/mixins/__tests__/followCacheInvalidation.test.ts +173 -0
  529. package/src/mixins/__tests__/followGraph.test.ts +147 -0
  530. package/src/mixins/__tests__/followGraphPagination.test.ts +271 -0
  531. package/src/mixins/__tests__/getFileDownloadUrl.test.ts +346 -0
  532. package/src/mixins/__tests__/getFollowStatuses.test.ts +95 -0
  533. package/src/mixins/__tests__/getUsersByIds.test.ts +149 -0
  534. package/src/mixins/__tests__/identityBackup.test.ts +315 -0
  535. package/src/mixins/__tests__/identityWriteCacheInvalidation.test.ts +407 -0
  536. package/src/mixins/__tests__/inferenceFactory.test.ts +58 -0
  537. package/src/mixins/__tests__/onTokensChanged.test.ts +129 -0
  538. package/src/mixins/__tests__/preSessionSkipAuth.test.ts +284 -0
  539. package/src/mixins/__tests__/privacyCacheInvalidation.test.ts +155 -0
  540. package/src/mixins/__tests__/reputation.test.ts +522 -0
  541. package/src/mixins/__tests__/serviceAuth.test.ts +832 -0
  542. package/src/mixins/__tests__/serviceTokenJwks.test.ts +208 -0
  543. package/src/mixins/__tests__/store.test.ts +304 -0
  544. package/src/mixins/__tests__/topics.test.ts +156 -0
  545. package/src/mixins/__tests__/userReadCacheBypass.test.ts +129 -0
  546. package/src/mixins/__tests__/userTokenAuth.test.ts +746 -0
  547. package/src/mixins/__tests__/verifyChallenge.test.ts +130 -0
  548. package/src/mixins/__tests__/verifyServiceActingAs.test.ts +167 -0
  549. package/src/mixins/__tests__/webauthnAuth.test.ts +205 -0
  550. package/src/mixins/index.ts +201 -0
  551. package/src/mixins/mixinHelpers.ts +69 -0
  552. package/src/models/Topic.ts +46 -0
  553. package/src/models/interfaces.ts +786 -0
  554. package/src/models/session.ts +55 -0
  555. package/src/server/__tests__/auth.test.ts +78 -0
  556. package/src/server/__tests__/capabilityTicket.test.ts +111 -0
  557. package/src/server/__tests__/cors.socket.test.ts +225 -0
  558. package/src/server/__tests__/cors.test.ts +158 -0
  559. package/src/server/__tests__/rateLimit.test.ts +257 -0
  560. package/src/server/__tests__/safeFetch.test.ts +232 -0
  561. package/src/server/__tests__/securityHeaders.test.ts +348 -0
  562. package/src/server/__tests__/serviceTokenAttribution.test.ts +396 -0
  563. package/src/server/__tests__/userInvalidation.test.ts +203 -0
  564. package/src/server/__tests__/verifySecret.test.ts +40 -0
  565. package/src/server/auth.ts +271 -0
  566. package/src/server/capabilityTicket.ts +258 -0
  567. package/src/server/cors.ts +277 -0
  568. package/src/server/index.ts +136 -0
  569. package/src/server/rateLimit.ts +313 -0
  570. package/src/server/safeFetch.ts +586 -0
  571. package/src/server/securityHeaders.ts +467 -0
  572. package/src/server/userInvalidation.ts +199 -0
  573. package/src/server/verifySecret.ts +52 -0
  574. package/src/session/SessionClient.ts +1001 -0
  575. package/src/session/__tests__/SessionClient.additive.test.ts +153 -0
  576. package/src/session/__tests__/SessionClient.broadcastChannel.test.ts +114 -0
  577. package/src/session/__tests__/SessionClient.diagnostics.test.ts +91 -0
  578. package/src/session/__tests__/SessionClient.directory.test.ts +688 -0
  579. package/src/session/__tests__/SessionClient.httpIntegration.test.ts +65 -0
  580. package/src/session/__tests__/SessionClient.identityPin.test.ts +175 -0
  581. package/src/session/__tests__/SessionClient.rest.test.ts +119 -0
  582. package/src/session/__tests__/SessionClient.serverEvents.test.ts +72 -0
  583. package/src/session/__tests__/SessionClient.socket.test.ts +202 -0
  584. package/src/session/__tests__/SessionClient.socketFactory.test.ts +81 -0
  585. package/src/session/__tests__/SessionClient.state.test.ts +78 -0
  586. package/src/session/__tests__/SessionClient.switchTokenOrder.test.ts +170 -0
  587. package/src/session/__tests__/accountDelegationAccess.test.ts +104 -0
  588. package/src/session/__tests__/accountDialogController.test.ts +1969 -0
  589. package/src/session/__tests__/accountDialogShape.test.ts +118 -0
  590. package/src/session/__tests__/accountSwitchTargets.test.ts +137 -0
  591. package/src/session/__tests__/authStateStore.test.ts +451 -0
  592. package/src/session/__tests__/deviceDirectory.test.ts +422 -0
  593. package/src/session/__tests__/deviceSwitcherRows.test.ts +223 -0
  594. package/src/session/__tests__/identityPin.test.ts +131 -0
  595. package/src/session/__tests__/identitySession.test.ts +194 -0
  596. package/src/session/__tests__/projectSessionState.test.ts +105 -0
  597. package/src/session/__tests__/refresh.identityPin.test.ts +307 -0
  598. package/src/session/__tests__/refresh.test.ts +443 -0
  599. package/src/session/__tests__/sessionIntegration.test.ts +209 -0
  600. package/src/session/__tests__/sharedDeviceCredential.test.ts +300 -0
  601. package/src/session/accountDelegationAccess.ts +72 -0
  602. package/src/session/accountDialogController.ts +1494 -0
  603. package/src/session/accountSwitchTargets.ts +93 -0
  604. package/src/session/authStateStore.ts +502 -0
  605. package/src/session/createSessionClient.ts +39 -0
  606. package/src/session/deviceDirectory.ts +269 -0
  607. package/src/session/deviceSwitcherRows.ts +145 -0
  608. package/src/session/identityPin.ts +310 -0
  609. package/src/session/identitySession.ts +206 -0
  610. package/src/session/projectSessionState.ts +137 -0
  611. package/src/session/refresh.ts +517 -0
  612. package/src/session/sessionClientHost.ts +39 -0
  613. package/src/session/sharedDeviceCredential.ts +349 -0
  614. package/src/session/socketLoader.ts +30 -0
  615. package/src/shared/utils/colorUtils.ts +155 -0
  616. package/src/shared/utils/errorUtils.ts +181 -0
  617. package/src/shared/utils/index.ts +59 -0
  618. package/src/shared/utils/networkUtils.ts +248 -0
  619. package/src/shared/utils/themeUtils.ts +115 -0
  620. package/src/types/buffer.d.ts +97 -0
  621. package/src/types/expo-crypto.d.ts +16 -0
  622. package/src/types/expo-secure-store.d.ts +17 -0
  623. package/src/utils/__tests__/accountUtils.test.ts +151 -0
  624. package/src/utils/__tests__/activityId.test.ts +45 -0
  625. package/src/utils/__tests__/asyncUtils.test.ts +187 -0
  626. package/src/utils/__tests__/authWebUrl.test.ts +15 -0
  627. package/src/utils/__tests__/cacheKey.test.ts +0 -0
  628. package/src/utils/__tests__/coldBoot.test.ts +496 -0
  629. package/src/utils/__tests__/commonsApproval.test.ts +60 -0
  630. package/src/utils/__tests__/identityCacheSweep.test.ts +151 -0
  631. package/src/utils/__tests__/languageUtils.test.ts +219 -0
  632. package/src/utils/__tests__/oauthPkce.test.ts +272 -0
  633. package/src/utils/__tests__/officialOrigins.test.ts +31 -0
  634. package/src/utils/__tests__/oxyServiceEnvironment.test.ts +7 -0
  635. package/src/utils/__tests__/profileLinks.test.ts +180 -0
  636. package/src/utils/__tests__/redactUrl.test.ts +33 -0
  637. package/src/utils/__tests__/registrableApex.test.ts +62 -0
  638. package/src/utils/__tests__/textNormalization.test.ts +205 -0
  639. package/src/utils/__tests__/validationUtils.test.ts +703 -0
  640. package/src/utils/__tests__/webauthnOrigin.test.ts +83 -0
  641. package/src/utils/accountCacheSweep.ts +93 -0
  642. package/src/utils/accountUtils.ts +194 -0
  643. package/src/utils/activityId.ts +2 -0
  644. package/src/utils/apiUtils.ts +137 -0
  645. package/src/utils/asyncUtils.ts +280 -0
  646. package/src/utils/authHelpers.ts +148 -0
  647. package/src/utils/authWebUrl.ts +17 -0
  648. package/src/utils/avatarUtils.ts +39 -0
  649. package/src/utils/cache.ts +273 -0
  650. package/src/utils/cacheKey.ts +93 -0
  651. package/src/utils/coldBoot.ts +218 -0
  652. package/src/utils/commonsApproval.ts +39 -0
  653. package/src/utils/commonsDelivery.ts +132 -0
  654. package/src/utils/deviceManager.ts +203 -0
  655. package/src/utils/displayNamePolicyRanges.generated.ts +69 -0
  656. package/src/utils/edgeRegion.ts +2 -0
  657. package/src/utils/errorUtils.ts +352 -0
  658. package/src/utils/identityCacheSweep.ts +104 -0
  659. package/src/utils/languageUtils.ts +256 -0
  660. package/src/utils/oauthPkce.ts +392 -0
  661. package/src/utils/officialOrigins.ts +62 -0
  662. package/src/utils/oxyServiceEnvironment.ts +17 -0
  663. package/src/utils/platform.ts +139 -0
  664. package/src/utils/profileLinks.ts +91 -0
  665. package/src/utils/redactUrl.ts +28 -0
  666. package/src/utils/registrableApex.ts +41 -0
  667. package/src/utils/requestUtils.ts +237 -0
  668. package/src/utils/sessionUtils.ts +206 -0
  669. package/src/utils/textNormalization.ts +162 -0
  670. package/src/utils/userHandle.ts +49 -0
  671. package/src/utils/userIdentity.ts +60 -0
  672. package/src/utils/validationUtils.ts +390 -0
  673. package/src/utils/webauthnOrigin.ts +52 -0
@@ -0,0 +1,226 @@
1
+ /**
2
+ * b3 Phase 0 crypto primitives — HKDF-SHA256, XChaCha20-Poly1305 AEAD, and
3
+ * secp256k1 ECDH. These are the pure, self-contained foundation for the Commons
4
+ * encrypted-backup and device-to-device transfer flows.
5
+ *
6
+ * - kdf: pinned against the published RFC 5869 test vectors (TC1/TC2/TC3) so an
7
+ * accidental algorithm/library swap is caught as a regression.
8
+ * - aead: round-trip plus exhaustive tamper coverage (flipped byte, wrong AAD,
9
+ * wrong key, wrong nonce) — every mismatch must throw.
10
+ * - ecdh: symmetry (`derive(a,pubB) === derive(b,pubA)`), fixed 32-byte width,
11
+ * and compressed/uncompressed public-key parity.
12
+ */
13
+
14
+ import {
15
+ generateSecp256k1KeyPair,
16
+ normalizeSecp256k1PublicKey,
17
+ } from '@oxy.so/protocol/secp256k1';
18
+ import { hkdfSha256 } from '../kdf';
19
+ import { encryptAead, decryptAead, AEAD_KEY_LENGTH, AEAD_NONCE_LENGTH } from '../aead';
20
+ import { deriveSharedSecret } from '../ecdh';
21
+
22
+ const fromHex = (hex: string): Uint8Array =>
23
+ Uint8Array.from(Buffer.from(hex.replace(/\s/g, ''), 'hex'));
24
+ const toHex = (bytes: Uint8Array): string => Buffer.from(bytes).toString('hex');
25
+ const range = (start: number, end: number): Uint8Array =>
26
+ Uint8Array.from(Array.from({ length: end - start + 1 }, (_, i) => start + i));
27
+
28
+ describe('hkdfSha256 (RFC 5869)', () => {
29
+ // RFC 5869 Appendix A — Test Case 1 (basic, with salt & info).
30
+ it('matches RFC 5869 Test Case 1', () => {
31
+ const okm = hkdfSha256(
32
+ fromHex('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b'),
33
+ fromHex('000102030405060708090a0b0c'),
34
+ fromHex('f0f1f2f3f4f5f6f7f8f9'),
35
+ 42,
36
+ );
37
+ expect(toHex(okm)).toBe(
38
+ '3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865',
39
+ );
40
+ });
41
+
42
+ // RFC 5869 Appendix A — Test Case 2 (longer inputs & output).
43
+ it('matches RFC 5869 Test Case 2', () => {
44
+ const okm = hkdfSha256(range(0x00, 0x4f), range(0x60, 0xaf), range(0xb0, 0xff), 82);
45
+ expect(toHex(okm)).toBe(
46
+ 'b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87',
47
+ );
48
+ });
49
+
50
+ // RFC 5869 Appendix A — Test Case 3 (zero-length salt & info).
51
+ it('matches RFC 5869 Test Case 3 (empty salt/info)', () => {
52
+ const okm = hkdfSha256(
53
+ fromHex('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b'),
54
+ new Uint8Array(0),
55
+ new Uint8Array(0),
56
+ 42,
57
+ );
58
+ expect(toHex(okm)).toBe(
59
+ '8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8',
60
+ );
61
+ });
62
+
63
+ it('returns exactly the requested number of bytes', () => {
64
+ const ikm = fromHex('deadbeef');
65
+ expect(hkdfSha256(ikm, new Uint8Array(0), new Uint8Array(0), 16).length).toBe(16);
66
+ expect(hkdfSha256(ikm, new Uint8Array(0), new Uint8Array(0), 32).length).toBe(32);
67
+ expect(hkdfSha256(ikm, new Uint8Array(0), new Uint8Array(0), 64).length).toBe(64);
68
+ });
69
+
70
+ it('derives independent keys for distinct info (context binding)', () => {
71
+ const ikm = fromHex('00112233445566778899aabbccddeeff');
72
+ const salt = fromHex('a1b2c3d4');
73
+ const backup = hkdfSha256(ikm, salt, new TextEncoder().encode('oxy.backup.v1'), 32);
74
+ const transfer = hkdfSha256(ikm, salt, new TextEncoder().encode('oxy.transfer.v1'), 32);
75
+ expect(toHex(backup)).not.toBe(toHex(transfer));
76
+ });
77
+
78
+ it('rejects invalid output lengths', () => {
79
+ const ikm = fromHex('deadbeef');
80
+ expect(() => hkdfSha256(ikm, new Uint8Array(0), new Uint8Array(0), 0)).toThrow();
81
+ expect(() => hkdfSha256(ikm, new Uint8Array(0), new Uint8Array(0), -1)).toThrow();
82
+ expect(() => hkdfSha256(ikm, new Uint8Array(0), new Uint8Array(0), 1.5)).toThrow();
83
+ expect(() => hkdfSha256(ikm, new Uint8Array(0), new Uint8Array(0), 255 * 32 + 1)).toThrow();
84
+ });
85
+ });
86
+
87
+ describe('encryptAead / decryptAead (XChaCha20-Poly1305)', () => {
88
+ const key = hkdfSha256(fromHex('00'), new Uint8Array(0), new TextEncoder().encode('aead-test'), AEAD_KEY_LENGTH);
89
+ const plaintext = new TextEncoder().encode('the quick brown fox jumps over the lazy dog');
90
+ const aad = new TextEncoder().encode('oxy.backup.v1:did:web:oxy.so:u:123');
91
+
92
+ it('round-trips without AAD', () => {
93
+ const { nonce, ciphertext } = encryptAead(key, plaintext);
94
+ expect(nonce.length).toBe(AEAD_NONCE_LENGTH);
95
+ // Ciphertext = plaintext length + 16-byte Poly1305 tag.
96
+ expect(ciphertext.length).toBe(plaintext.length + 16);
97
+ expect(toHex(decryptAead(key, nonce, ciphertext))).toBe(toHex(plaintext));
98
+ });
99
+
100
+ it('round-trips with AAD', () => {
101
+ const { nonce, ciphertext } = encryptAead(key, plaintext, aad);
102
+ expect(toHex(decryptAead(key, nonce, ciphertext, aad))).toBe(toHex(plaintext));
103
+ });
104
+
105
+ it('produces a distinct random nonce on each call', () => {
106
+ const a = encryptAead(key, plaintext);
107
+ const b = encryptAead(key, plaintext);
108
+ expect(toHex(a.nonce)).not.toBe(toHex(b.nonce));
109
+ // Random nonce => distinct ciphertext even for identical plaintext+key.
110
+ expect(toHex(a.ciphertext)).not.toBe(toHex(b.ciphertext));
111
+ });
112
+
113
+ it('throws when a ciphertext byte is flipped (tamper detection)', () => {
114
+ const { nonce, ciphertext } = encryptAead(key, plaintext, aad);
115
+ const tampered = Uint8Array.from(ciphertext);
116
+ tampered[0] ^= 0x01;
117
+ expect(() => decryptAead(key, nonce, tampered, aad)).toThrow();
118
+ });
119
+
120
+ it('throws when the authentication tag is flipped (tamper detection)', () => {
121
+ const { nonce, ciphertext } = encryptAead(key, plaintext, aad);
122
+ const tampered = Uint8Array.from(ciphertext);
123
+ tampered[tampered.length - 1] ^= 0x80;
124
+ expect(() => decryptAead(key, nonce, tampered, aad)).toThrow();
125
+ });
126
+
127
+ it('throws when AAD does not match', () => {
128
+ const { nonce, ciphertext } = encryptAead(key, plaintext, aad);
129
+ const wrongAad = new TextEncoder().encode('oxy.backup.v1:did:web:oxy.so:u:999');
130
+ expect(() => decryptAead(key, nonce, ciphertext, wrongAad)).toThrow();
131
+ // AAD present at encrypt, absent at decrypt must also fail.
132
+ expect(() => decryptAead(key, nonce, ciphertext)).toThrow();
133
+ });
134
+
135
+ it('throws when the key does not match', () => {
136
+ const { nonce, ciphertext } = encryptAead(key, plaintext, aad);
137
+ const wrongKey = hkdfSha256(fromHex('01'), new Uint8Array(0), new TextEncoder().encode('aead-test'), AEAD_KEY_LENGTH);
138
+ expect(() => decryptAead(wrongKey, nonce, ciphertext, aad)).toThrow();
139
+ });
140
+
141
+ it('throws when the nonce does not match', () => {
142
+ const { nonce, ciphertext } = encryptAead(key, plaintext, aad);
143
+ const wrongNonce = Uint8Array.from(nonce);
144
+ wrongNonce[0] ^= 0x01;
145
+ expect(() => decryptAead(key, wrongNonce, ciphertext, aad)).toThrow();
146
+ });
147
+
148
+ it('rejects keys of the wrong length', () => {
149
+ expect(() => encryptAead(new Uint8Array(16), plaintext)).toThrow();
150
+ const { nonce, ciphertext } = encryptAead(key, plaintext);
151
+ expect(() => decryptAead(new Uint8Array(31), nonce, ciphertext)).toThrow();
152
+ });
153
+
154
+ it('rejects a nonce of the wrong length on decrypt', () => {
155
+ const { ciphertext } = encryptAead(key, plaintext);
156
+ expect(() => decryptAead(key, new Uint8Array(12), ciphertext)).toThrow();
157
+ });
158
+
159
+ it('handles empty plaintext', () => {
160
+ const empty = new Uint8Array(0);
161
+ const { nonce, ciphertext } = encryptAead(key, empty, aad);
162
+ expect(ciphertext.length).toBe(16); // tag only
163
+ expect(decryptAead(key, nonce, ciphertext, aad).length).toBe(0);
164
+ });
165
+ });
166
+
167
+ describe('deriveSharedSecret (secp256k1 ECDH)', () => {
168
+ const alice = generateSecp256k1KeyPair();
169
+ const bob = generateSecp256k1KeyPair();
170
+ const alicePriv = alice.privateKey;
171
+ const bobPriv = bob.privateKey;
172
+ const alicePubUncompressed = alice.publicKey;
173
+ const bobPubUncompressed = bob.publicKey;
174
+
175
+ it('is symmetric: derive(a, pubB) === derive(b, pubA)', () => {
176
+ const ab = deriveSharedSecret(alicePriv, bobPubUncompressed);
177
+ const ba = deriveSharedSecret(bobPriv, alicePubUncompressed);
178
+ expect(toHex(ab)).toBe(toHex(ba));
179
+ });
180
+
181
+ it('returns exactly 32 bytes', () => {
182
+ expect(deriveSharedSecret(alicePriv, bobPubUncompressed).length).toBe(32);
183
+ });
184
+
185
+ it('agrees for compressed and uncompressed public-key encodings', () => {
186
+ const bobPubCompressed = normalizeSecp256k1PublicKey(bob.publicKey, true);
187
+ const fromUncompressed = deriveSharedSecret(alicePriv, bobPubUncompressed);
188
+ const fromCompressed = deriveSharedSecret(alicePriv, bobPubCompressed);
189
+ expect(toHex(fromCompressed)).toBe(toHex(fromUncompressed));
190
+ });
191
+
192
+ it('yields different secrets for different counterparties', () => {
193
+ const carol = generateSecp256k1KeyPair();
194
+ const withBob = deriveSharedSecret(alicePriv, bobPubUncompressed);
195
+ const withCarol = deriveSharedSecret(alicePriv, carol.publicKey);
196
+ expect(toHex(withBob)).not.toBe(toHex(withCarol));
197
+ });
198
+
199
+ it('is stable across repeated derivations', () => {
200
+ const first = deriveSharedSecret(alicePriv, bobPubUncompressed);
201
+ const second = deriveSharedSecret(alicePriv, bobPubUncompressed);
202
+ expect(toHex(first)).toBe(toHex(second));
203
+ });
204
+
205
+ it('composes with HKDF to yield a usable AEAD key round-trip', () => {
206
+ const shared = deriveSharedSecret(alicePriv, bobPubUncompressed);
207
+ const info = new TextEncoder().encode('oxy.transfer.v1');
208
+ const aliceKey = hkdfSha256(shared, new Uint8Array(0), info, AEAD_KEY_LENGTH);
209
+ const bobKey = hkdfSha256(
210
+ deriveSharedSecret(bobPriv, alicePubUncompressed),
211
+ new Uint8Array(0),
212
+ info,
213
+ AEAD_KEY_LENGTH,
214
+ );
215
+ expect(toHex(aliceKey)).toBe(toHex(bobKey));
216
+
217
+ const message = new TextEncoder().encode('device transfer payload');
218
+ const { nonce, ciphertext } = encryptAead(aliceKey, message);
219
+ expect(toHex(decryptAead(bobKey, nonce, ciphertext))).toBe(toHex(message));
220
+ });
221
+
222
+ it('rejects non-hex inputs', () => {
223
+ expect(() => deriveSharedSecret('zzzz', bobPubUncompressed)).toThrow();
224
+ expect(() => deriveSharedSecret(alicePriv, 'nothex')).toThrow();
225
+ });
226
+ });
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Shared in-memory mocks for KeyManager identity tests.
3
+ *
4
+ * The secure-store mock keys every entry by `(keychainService ?? 'default') + ' '
5
+ * + key` so the v2 slot layout — which gives the primary and backup DISTINCT
6
+ * keychain services — is faithfully modeled (a write under one service is
7
+ * invisible to a read under another, exactly like Android's per-service
8
+ * AndroidKeyStore keys).
9
+ *
10
+ * `__simulateKeystoreDeath__(service)` reproduces the SDK 57 Android failure mode
11
+ * this whole hardening defends against: an undecryptable entry is DELETED on the
12
+ * READ path and the read returns `null` (no throw). A subsequent write under that
13
+ * service creates a fresh, readable entry (a new keystore key), so recovery can
14
+ * re-persist afterwards.
15
+ *
16
+ * The AsyncStorage mock is a real in-memory map (the identity marker + the
17
+ * advisory migration flag live here — independent of the keychain).
18
+ */
19
+
20
+ type FailOp = 'set' | 'get';
21
+
22
+ export interface FailPlan {
23
+ failKey?: string;
24
+ failOp?: FailOp;
25
+ failTimes?: number;
26
+ /** Restrict the fault to one keychain service (`'default'` for unscoped keys). */
27
+ failService?: string;
28
+ }
29
+
30
+ const compositeKey = (service: string | undefined, key: string): string =>
31
+ `${service ?? 'default'} ${key}`;
32
+
33
+ export function createSecureStoreMock() {
34
+ const store = new Map<string, string>(); // keyed by `${service} ${key}`
35
+ const poisoned = new Set<string>(); // composite keys pending delete-on-read (keystore death)
36
+ const failPlan: FailPlan = {};
37
+
38
+ const maybeFail = (op: FailOp, key: string, service?: string): void => {
39
+ if (
40
+ failPlan.failOp === op &&
41
+ failPlan.failKey === key &&
42
+ (failPlan.failService === undefined || failPlan.failService === (service ?? 'default'))
43
+ ) {
44
+ if (failPlan.failTimes !== undefined) {
45
+ failPlan.failTimes -= 1;
46
+ if (failPlan.failTimes <= 0) {
47
+ failPlan.failKey = undefined;
48
+ failPlan.failOp = undefined;
49
+ failPlan.failTimes = undefined;
50
+ failPlan.failService = undefined;
51
+ }
52
+ }
53
+ throw new Error(`Simulated ${op} failure for ${key}`);
54
+ }
55
+ };
56
+
57
+ return {
58
+ __esModule: true,
59
+ WHEN_UNLOCKED_THIS_DEVICE_ONLY: 'WHEN_UNLOCKED_THIS_DEVICE_ONLY',
60
+ WHEN_UNLOCKED: 'WHEN_UNLOCKED',
61
+ setItemAsync: jest.fn(async (key: string, value: string, opts?: { keychainService?: string }) => {
62
+ maybeFail('set', key, opts?.keychainService);
63
+ const ck = compositeKey(opts?.keychainService, key);
64
+ poisoned.delete(ck); // a fresh write mints a new (readable) keystore entry
65
+ store.set(ck, value);
66
+ }),
67
+ getItemAsync: jest.fn(async (key: string, opts?: { keychainService?: string }) => {
68
+ maybeFail('get', key, opts?.keychainService);
69
+ const ck = compositeKey(opts?.keychainService, key);
70
+ if (poisoned.has(ck)) {
71
+ // Android SDK 57: undecryptable ciphertext is DELETED on read → null.
72
+ poisoned.delete(ck);
73
+ store.delete(ck);
74
+ return null;
75
+ }
76
+ return store.get(ck) ?? null;
77
+ }),
78
+ deleteItemAsync: jest.fn(async (key: string, opts?: { keychainService?: string }) => {
79
+ const ck = compositeKey(opts?.keychainService, key);
80
+ poisoned.delete(ck);
81
+ store.delete(ck);
82
+ }),
83
+ __resetStore__: () => {
84
+ store.clear();
85
+ poisoned.clear();
86
+ failPlan.failKey = undefined;
87
+ failPlan.failOp = undefined;
88
+ failPlan.failTimes = undefined;
89
+ failPlan.failService = undefined;
90
+ },
91
+ __getStore__: () => store,
92
+ __getRaw__: (key: string, service?: string): string | null => store.get(compositeKey(service, key)) ?? null,
93
+ __setRaw__: (key: string, value: string, service?: string): void => {
94
+ store.set(compositeKey(service, key), value);
95
+ },
96
+ __deleteRaw__: (key: string, service?: string): void => {
97
+ store.delete(compositeKey(service, key));
98
+ },
99
+ __simulateKeystoreDeath__: (service: string): void => {
100
+ // Mark every existing entry under this service as undecryptable; the next
101
+ // read of each deletes it and returns null.
102
+ for (const ck of store.keys()) {
103
+ if (ck.startsWith(`${service} `)) {
104
+ poisoned.add(ck);
105
+ }
106
+ }
107
+ },
108
+ __failPlan__: failPlan,
109
+ };
110
+ }
111
+
112
+ export function createAsyncStorageMock() {
113
+ const map = new Map<string, string>();
114
+ return {
115
+ getItem: async (key: string): Promise<string | null> => map.get(key) ?? null,
116
+ setItem: async (key: string, value: string): Promise<void> => {
117
+ map.set(key, value);
118
+ },
119
+ removeItem: async (key: string): Promise<void> => {
120
+ map.delete(key);
121
+ },
122
+ __map__: map,
123
+ __reset__: (): void => map.clear(),
124
+ };
125
+ }
@@ -0,0 +1,272 @@
1
+ /**
2
+ * Tests for KeyManager atomic-write & backup-restore safety under FLAKY storage.
3
+ *
4
+ * These pin the "never leave the device with zero recoverable copies of a
5
+ * healthy identity" invariant. Each scenario corresponds to a real
6
+ * account-loss / account-switch bug that a half-failed SecureStore write could
7
+ * otherwise cause:
8
+ *
9
+ * 1. A failed OVERWRITE must roll the primary back to the ORIGINAL identity
10
+ * and must keep a recoverable backup of it — never switch to the
11
+ * half-written new identity.
12
+ * 2. A transient read failure must not cause restoreIdentityFromBackup() to
13
+ * clobber a healthy-but-momentarily-locked primary with a stale backup.
14
+ * 3. restoreIdentityFromBackup() must refuse when the backup identifies a
15
+ * different account than a private key still present in the primary slot.
16
+ * 4. A first-time create still writes a backup (no regression).
17
+ *
18
+ * The identity now lives in the isolated v2 slots (primary service `oxy_identity`,
19
+ * backup service `oxy_identity_backup`), so assertions read/write via the
20
+ * service-scoped mock helpers.
21
+ */
22
+
23
+ import { setPlatformOS } from '../../utils/platform';
24
+
25
+ jest.mock(
26
+ 'expo-secure-store',
27
+ () => {
28
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
29
+ const { createSecureStoreMock } = require('./identityMocks');
30
+ return createSecureStoreMock();
31
+ },
32
+ { virtual: true },
33
+ );
34
+
35
+ jest.mock(
36
+ 'expo-crypto',
37
+ () => ({
38
+ __esModule: true,
39
+ getRandomBytes: (length: number) => {
40
+ const out = new Uint8Array(length);
41
+ for (let i = 0; i < length; i++) out[i] = (Math.random() * 256) & 0xff;
42
+ return out;
43
+ },
44
+ digestStringAsync: async () => '0'.repeat(64),
45
+ CryptoDigestAlgorithm: { SHA256: 'SHA-256' },
46
+ }),
47
+ { virtual: true },
48
+ );
49
+
50
+ jest.mock('@oxy.so/protocol', () => {
51
+ const actual = jest.requireActual('@oxy.so/protocol');
52
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
53
+ const { createAsyncStorageMock } = require('./identityMocks');
54
+ const asyncStorage = createAsyncStorageMock();
55
+ return {
56
+ __esModule: true,
57
+ ...actual,
58
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
59
+ loadExpoCrypto: async () => require('expo-crypto'),
60
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
61
+ loadSecureStore: async () => require('expo-secure-store'),
62
+ loadAsyncStorage: async () => ({ default: asyncStorage }),
63
+ loadSharedIdentityBridge: async () => null,
64
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
65
+ loadNodeCrypto: async () => require('crypto'),
66
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
67
+ getRandomBytesRN: (n: number) => require('expo-crypto').getRandomBytes(n),
68
+ };
69
+ });
70
+
71
+ // v2 slot key names + keychain services (must match keyManager.ts).
72
+ const PRIMARY_SVC = 'oxy_identity';
73
+ const BACKUP_SVC = 'oxy_identity_backup';
74
+ const V2_PRIV = 'oxy_identity_private_key_v2';
75
+ const V2_PUB = 'oxy_identity_public_key_v2';
76
+ const V2_BPRIV = 'oxy_identity_backup_private_key_v2';
77
+ const V2_BPUB = 'oxy_identity_backup_public_key_v2';
78
+
79
+ interface SecureStoreTestHandle {
80
+ __resetStore__: () => void;
81
+ __getRaw__: (key: string, service?: string) => string | null;
82
+ __setRaw__: (key: string, value: string, service?: string) => void;
83
+ __deleteRaw__: (key: string, service?: string) => void;
84
+ __failPlan__: { failKey?: string; failOp?: 'set' | 'get'; failTimes?: number; failService?: string };
85
+ }
86
+
87
+ describe('KeyManager atomicity & recoverability under flaky storage', () => {
88
+ let KeyManager: typeof import('../keyManager').KeyManager;
89
+
90
+ const resetCaches = () => {
91
+ const km = KeyManager as unknown as {
92
+ cachedPublicKey: unknown;
93
+ cachedHasIdentity: unknown;
94
+ cachedPublicKeyResolved: unknown;
95
+ };
96
+ km.cachedPublicKey = null;
97
+ km.cachedHasIdentity = null;
98
+ km.cachedPublicKeyResolved = false;
99
+ };
100
+
101
+ beforeAll(() => {
102
+ setPlatformOS('ios');
103
+ (globalThis as unknown as { navigator: unknown }).navigator = { product: 'ReactNative' };
104
+ });
105
+
106
+ beforeEach(async () => {
107
+ jest.resetModules();
108
+ setPlatformOS('ios');
109
+ const ss = (await import('expo-secure-store' as string)) as unknown as SecureStoreTestHandle;
110
+ ss.__resetStore__();
111
+ const km = await import('../keyManager');
112
+ KeyManager = km.KeyManager;
113
+ resetCaches();
114
+ });
115
+
116
+ it('a failed OVERWRITE leaves the ORIGINAL identity intact and recoverable (no silent switch)', async () => {
117
+ const originalPublic = await KeyManager.createIdentity();
118
+ const ss = (await import('expo-secure-store' as string)) as unknown as SecureStoreTestHandle;
119
+ const originalPriv = ss.__getRaw__(V2_PRIV, PRIMARY_SVC);
120
+ resetCaches();
121
+
122
+ // The new primary private write fails mid-overwrite.
123
+ ss.__failPlan__.failOp = 'set';
124
+ ss.__failPlan__.failKey = V2_PRIV;
125
+ ss.__failPlan__.failService = PRIMARY_SVC;
126
+ await expect(KeyManager.createIdentity({ overwrite: true })).rejects.toBeDefined();
127
+
128
+ // Recover from the simulated fault.
129
+ ss.__failPlan__.failKey = undefined;
130
+ ss.__failPlan__.failOp = undefined;
131
+ ss.__failPlan__.failService = undefined;
132
+ resetCaches();
133
+
134
+ // Primary must still be the original identity (rolled back).
135
+ expect(await KeyManager.hasIdentity()).toBe(true);
136
+ expect(await KeyManager.getPublicKey()).toBe(originalPublic);
137
+ expect(ss.__getRaw__(V2_PRIV, PRIMARY_SVC)).toBe(originalPriv);
138
+
139
+ // And the backup must still hold the ORIGINAL identity, never the new one.
140
+ expect(ss.__getRaw__(V2_BPRIV, BACKUP_SVC)).toBe(originalPriv);
141
+ expect(ss.__getRaw__(V2_BPUB, BACKUP_SVC)).toBe(originalPublic);
142
+ });
143
+
144
+ it('a failed final backup refresh rejects and rolls back instead of succeeding with a stale backup', async () => {
145
+ const originalPublic = await KeyManager.createIdentity();
146
+ const ss = (await import('expo-secure-store' as string)) as unknown as SecureStoreTestHandle;
147
+ const originalPriv = ss.__getRaw__(V2_PRIV, PRIMARY_SVC);
148
+ resetCaches();
149
+
150
+ // Let the new primary write and verify, then fail exactly once while
151
+ // refreshing the backup to the new identity. This used to return success
152
+ // with primary=B and backup=A, enabling a later absent-primary restore to
153
+ // silently switch back to A.
154
+ ss.__failPlan__.failOp = 'set';
155
+ ss.__failPlan__.failKey = V2_BPUB;
156
+ ss.__failPlan__.failService = BACKUP_SVC;
157
+ ss.__failPlan__.failTimes = 1;
158
+ await expect(KeyManager.createIdentity({ overwrite: true })).rejects.toBeDefined();
159
+
160
+ resetCaches();
161
+
162
+ // The operation failed atomically: primary and backup both still identify
163
+ // the original account, so callers cannot observe success with a stale
164
+ // cross-account backup.
165
+ expect(await KeyManager.hasIdentity()).toBe(true);
166
+ expect(await KeyManager.getPublicKey()).toBe(originalPublic);
167
+ expect(ss.__getRaw__(V2_PRIV, PRIMARY_SVC)).toBe(originalPriv);
168
+ expect(ss.__getRaw__(V2_PUB, PRIMARY_SVC)).toBe(originalPublic);
169
+ expect(ss.__getRaw__(V2_BPRIV, BACKUP_SVC)).toBe(originalPriv);
170
+ expect(ss.__getRaw__(V2_BPUB, BACKUP_SVC)).toBe(originalPublic);
171
+ });
172
+
173
+ it('restoreIdentityFromBackup does NOT clobber a healthy primary that is only transiently unreadable', async () => {
174
+ const original = await KeyManager.createIdentity();
175
+ const ss = (await import('expo-secure-store' as string)) as unknown as SecureStoreTestHandle;
176
+
177
+ // Put a DIFFERENT identity in the backup slot (simulates a stale backup
178
+ // from a previous account that a failed backup-refresh left behind).
179
+ const other = await KeyManager.generateKeyPair();
180
+ ss.__setRaw__(V2_BPRIV, other.privateKey, BACKUP_SVC);
181
+ ss.__setRaw__(V2_BPUB, other.publicKey, BACKUP_SVC);
182
+ resetCaches();
183
+
184
+ // Make the primary PRIVATE read throw (transient keychain lock).
185
+ ss.__failPlan__.failOp = 'get';
186
+ ss.__failPlan__.failKey = V2_PRIV;
187
+ ss.__failPlan__.failService = PRIMARY_SVC;
188
+
189
+ const restored = await KeyManager.restoreIdentityFromBackup();
190
+ expect(restored).toBe(false); // refused — transient read must not trigger a restore
191
+
192
+ // Clear the fault; the original primary must be intact and unchanged.
193
+ ss.__failPlan__.failKey = undefined;
194
+ ss.__failPlan__.failOp = undefined;
195
+ ss.__failPlan__.failService = undefined;
196
+ resetCaches();
197
+ expect(await KeyManager.getPublicKey()).toBe(original);
198
+ expect(ss.__getRaw__(V2_PUB, PRIMARY_SVC)).toBe(original);
199
+ });
200
+
201
+ it('restoreIdentityFromBackup refuses when a present primary private key identifies a different account than the backup', async () => {
202
+ // Primary holds identity A (valid). Corrupt ONLY the public key so the
203
+ // primary fails its consistency check but the private key still derives a
204
+ // real, different identity than the backup.
205
+ const a = await KeyManager.createIdentity();
206
+ const ss = (await import('expo-secure-store' as string)) as unknown as SecureStoreTestHandle;
207
+
208
+ // Backup holds a DIFFERENT identity B.
209
+ const b = await KeyManager.generateKeyPair();
210
+ ss.__setRaw__(V2_BPRIV, b.privateKey, BACKUP_SVC);
211
+ ss.__setRaw__(V2_BPUB, b.publicKey, BACKUP_SVC);
212
+ // Corrupt A's stored public key (private key A still present & valid).
213
+ ss.__setRaw__(V2_PUB, `04${'e'.repeat(128)}`, PRIMARY_SVC);
214
+ resetCaches();
215
+
216
+ const restored = await KeyManager.restoreIdentityFromBackup();
217
+ expect(restored).toBe(false);
218
+ // Must NOT have switched the primary to B.
219
+ expect(ss.__getRaw__(V2_PUB, PRIMARY_SVC)).not.toBe(b.publicKey);
220
+ expect(ss.__getRaw__(V2_PRIV, PRIMARY_SVC)).not.toBe(b.privateKey);
221
+ // The original A private key is still in place (untouched).
222
+ expect(KeyManager.derivePublicKey(ss.__getRaw__(V2_PRIV, PRIMARY_SVC) as string)).toBe(a);
223
+ });
224
+
225
+ it('a failed primary write during the post-rotation importKeyPair leaves the OLD key recoverable from backup', async () => {
226
+ // b3 key rotation: the server has already swapped to the NEW key; the device
227
+ // now commits it via importKeyPair({overwrite:true}). If that write fails
228
+ // mid-overwrite, the OLD key MUST remain intact and recoverable — never a
229
+ // half-written new identity that can't be decrypted or backed up.
230
+ const oldPublic = await KeyManager.createIdentity();
231
+ const ss = (await import('expo-secure-store' as string)) as unknown as SecureStoreTestHandle;
232
+ const oldPriv = ss.__getRaw__(V2_PRIV, PRIMARY_SVC);
233
+ resetCaches();
234
+
235
+ // The NEW rotated key material (generateKeyPair does NOT persist).
236
+ const rotated = await KeyManager.generateKeyPair();
237
+
238
+ // The new primary private write fails mid-commit.
239
+ ss.__failPlan__.failOp = 'set';
240
+ ss.__failPlan__.failKey = V2_PRIV;
241
+ ss.__failPlan__.failService = PRIMARY_SVC;
242
+ await expect(KeyManager.importKeyPair(rotated.privateKey, { overwrite: true })).rejects.toBeDefined();
243
+
244
+ // Recover from the simulated fault.
245
+ ss.__failPlan__.failKey = undefined;
246
+ ss.__failPlan__.failOp = undefined;
247
+ ss.__failPlan__.failService = undefined;
248
+ resetCaches();
249
+
250
+ // Primary is STILL the old identity (rolled back — never the new one).
251
+ expect(await KeyManager.hasIdentity()).toBe(true);
252
+ expect(await KeyManager.getPublicKey()).toBe(oldPublic);
253
+ expect(ss.__getRaw__(V2_PRIV, PRIMARY_SVC)).toBe(oldPriv);
254
+ // And the backup still holds the OLD identity, so the user can recover it.
255
+ expect(ss.__getRaw__(V2_BPRIV, BACKUP_SVC)).toBe(oldPriv);
256
+ expect(ss.__getRaw__(V2_BPUB, BACKUP_SVC)).toBe(oldPublic);
257
+ });
258
+
259
+ it('restores a provably-absent primary from a valid backup', async () => {
260
+ const original = await KeyManager.createIdentity();
261
+ const ss = (await import('expo-secure-store' as string)) as unknown as SecureStoreTestHandle;
262
+ // Wipe the primary entirely (backup remains).
263
+ ss.__deleteRaw__(V2_PRIV, PRIMARY_SVC);
264
+ ss.__deleteRaw__(V2_PUB, PRIMARY_SVC);
265
+ resetCaches();
266
+
267
+ const restored = await KeyManager.restoreIdentityFromBackup();
268
+ expect(restored).toBe(true);
269
+ expect(await KeyManager.getPublicKey()).toBe(original);
270
+ expect(await KeyManager.verifyIdentityIntegrity()).toBe(true);
271
+ });
272
+ });