@swal/edge-mesh 0.0.0-stage → 0.1.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 (464) hide show
  1. package/LICENSE +128 -0
  2. package/NOTICE +37 -0
  3. package/README.md +470 -2
  4. package/dist/adapters/gpu-agent/index.d.ts +61 -0
  5. package/dist/adapters/gpu-agent/index.d.ts.map +1 -0
  6. package/dist/adapters/gpu-agent/index.js +364 -0
  7. package/dist/adapters/gpu-agent/index.js.map +1 -0
  8. package/dist/adapters/maloca-salud/citas-distribuidas.d.ts +19 -0
  9. package/dist/adapters/maloca-salud/citas-distribuidas.d.ts.map +1 -0
  10. package/dist/adapters/maloca-salud/citas-distribuidas.js +51 -0
  11. package/dist/adapters/maloca-salud/citas-distribuidas.js.map +1 -0
  12. package/dist/adapters/maloca-salud/eps-bridge.d.ts +15 -0
  13. package/dist/adapters/maloca-salud/eps-bridge.d.ts.map +1 -0
  14. package/dist/adapters/maloca-salud/eps-bridge.js +27 -0
  15. package/dist/adapters/maloca-salud/eps-bridge.js.map +1 -0
  16. package/dist/adapters/maloca-salud/patient-profile.d.ts +20 -0
  17. package/dist/adapters/maloca-salud/patient-profile.d.ts.map +1 -0
  18. package/dist/adapters/maloca-salud/patient-profile.js +36 -0
  19. package/dist/adapters/maloca-salud/patient-profile.js.map +1 -0
  20. package/dist/adapters/maloca-salud/types.d.ts +40 -0
  21. package/dist/adapters/maloca-salud/types.d.ts.map +1 -0
  22. package/dist/adapters/maloca-salud/types.js +2 -0
  23. package/dist/adapters/maloca-salud/types.js.map +1 -0
  24. package/dist/adapters/maloca-veeduria/contract-bridge.d.ts +31 -0
  25. package/dist/adapters/maloca-veeduria/contract-bridge.d.ts.map +1 -0
  26. package/dist/adapters/maloca-veeduria/contract-bridge.js +103 -0
  27. package/dist/adapters/maloca-veeduria/contract-bridge.js.map +1 -0
  28. package/dist/adapters/maloca-veeduria/evidentia-veeduria.d.ts +14 -0
  29. package/dist/adapters/maloca-veeduria/evidentia-veeduria.d.ts.map +1 -0
  30. package/dist/adapters/maloca-veeduria/evidentia-veeduria.js +40 -0
  31. package/dist/adapters/maloca-veeduria/evidentia-veeduria.js.map +1 -0
  32. package/dist/adapters/maloca-veeduria/licitante-profile.d.ts +21 -0
  33. package/dist/adapters/maloca-veeduria/licitante-profile.d.ts.map +1 -0
  34. package/dist/adapters/maloca-veeduria/licitante-profile.js +58 -0
  35. package/dist/adapters/maloca-veeduria/licitante-profile.js.map +1 -0
  36. package/dist/adapters/maloca-veeduria/types.d.ts +30 -0
  37. package/dist/adapters/maloca-veeduria/types.d.ts.map +1 -0
  38. package/dist/adapters/maloca-veeduria/types.js +2 -0
  39. package/dist/adapters/maloca-veeduria/types.js.map +1 -0
  40. package/dist/adapters/maloca-xavier/agent-profile.d.ts +32 -0
  41. package/dist/adapters/maloca-xavier/agent-profile.d.ts.map +1 -0
  42. package/dist/adapters/maloca-xavier/agent-profile.js +56 -0
  43. package/dist/adapters/maloca-xavier/agent-profile.js.map +1 -0
  44. package/dist/adapters/maloca-xavier/codegraph-bridge.d.ts +27 -0
  45. package/dist/adapters/maloca-xavier/codegraph-bridge.d.ts.map +1 -0
  46. package/dist/adapters/maloca-xavier/codegraph-bridge.js +58 -0
  47. package/dist/adapters/maloca-xavier/codegraph-bridge.js.map +1 -0
  48. package/dist/adapters/maloca-xavier/llm-router.d.ts +25 -0
  49. package/dist/adapters/maloca-xavier/llm-router.d.ts.map +1 -0
  50. package/dist/adapters/maloca-xavier/llm-router.js +58 -0
  51. package/dist/adapters/maloca-xavier/llm-router.js.map +1 -0
  52. package/dist/authz/index.d.ts +81 -0
  53. package/dist/authz/index.d.ts.map +1 -0
  54. package/dist/authz/index.js +271 -0
  55. package/dist/authz/index.js.map +1 -0
  56. package/dist/chat/index.d.ts +138 -0
  57. package/dist/chat/index.d.ts.map +1 -0
  58. package/dist/chat/index.js +394 -0
  59. package/dist/chat/index.js.map +1 -0
  60. package/dist/chat/offline-queue.d.ts +32 -0
  61. package/dist/chat/offline-queue.d.ts.map +1 -0
  62. package/dist/chat/offline-queue.js +93 -0
  63. package/dist/chat/offline-queue.js.map +1 -0
  64. package/dist/core/DefaultEdgeMeshNode.d.ts +17 -0
  65. package/dist/core/DefaultEdgeMeshNode.d.ts.map +1 -0
  66. package/dist/core/DefaultEdgeMeshNode.js +57 -0
  67. package/dist/core/DefaultEdgeMeshNode.js.map +1 -0
  68. package/dist/core/node.d.ts +26 -0
  69. package/dist/core/node.d.ts.map +1 -0
  70. package/dist/core/node.js +16 -0
  71. package/dist/core/node.js.map +1 -0
  72. package/dist/edge-mesh.d.ts +139 -0
  73. package/dist/edge-mesh.d.ts.map +1 -0
  74. package/dist/edge-mesh.js +1146 -0
  75. package/dist/edge-mesh.js.map +1 -0
  76. package/dist/gos/GosBridge.d.ts +49 -0
  77. package/dist/gos/GosBridge.d.ts.map +1 -0
  78. package/dist/gos/GosBridge.js +133 -0
  79. package/dist/gos/GosBridge.js.map +1 -0
  80. package/dist/governance/authority.d.ts +44 -0
  81. package/dist/governance/authority.d.ts.map +1 -0
  82. package/dist/governance/authority.js +123 -0
  83. package/dist/governance/authority.js.map +1 -0
  84. package/dist/governance/index.d.ts +66 -0
  85. package/dist/governance/index.d.ts.map +1 -0
  86. package/dist/governance/index.js +210 -0
  87. package/dist/governance/index.js.map +1 -0
  88. package/dist/governance/merge.d.ts +31 -0
  89. package/dist/governance/merge.d.ts.map +1 -0
  90. package/dist/governance/merge.js +167 -0
  91. package/dist/governance/merge.js.map +1 -0
  92. package/dist/identity/index.d.ts +199 -0
  93. package/dist/identity/index.d.ts.map +1 -0
  94. package/dist/identity/index.js +415 -0
  95. package/dist/identity/index.js.map +1 -0
  96. package/dist/index.d.ts +71 -0
  97. package/dist/index.d.ts.map +1 -0
  98. package/dist/index.js +56 -0
  99. package/dist/index.js.map +1 -0
  100. package/dist/maloca/backoffice.d.ts +31 -0
  101. package/dist/maloca/backoffice.d.ts.map +1 -0
  102. package/dist/maloca/backoffice.js +40 -0
  103. package/dist/maloca/backoffice.js.map +1 -0
  104. package/dist/maloca/event-bus.d.ts +60 -0
  105. package/dist/maloca/event-bus.d.ts.map +1 -0
  106. package/dist/maloca/event-bus.js +175 -0
  107. package/dist/maloca/event-bus.js.map +1 -0
  108. package/dist/maloca/evidentia.d.ts +136 -0
  109. package/dist/maloca/evidentia.d.ts.map +1 -0
  110. package/dist/maloca/evidentia.js +447 -0
  111. package/dist/maloca/evidentia.js.map +1 -0
  112. package/dist/maloca/gateway/api.d.ts +146 -0
  113. package/dist/maloca/gateway/api.d.ts.map +1 -0
  114. package/dist/maloca/gateway/api.js +228 -0
  115. package/dist/maloca/gateway/api.js.map +1 -0
  116. package/dist/maloca/gateway/auth.d.ts +20 -0
  117. package/dist/maloca/gateway/auth.d.ts.map +1 -0
  118. package/dist/maloca/gateway/auth.js +115 -0
  119. package/dist/maloca/gateway/auth.js.map +1 -0
  120. package/dist/maloca/gateway/dashboard.d.ts +42 -0
  121. package/dist/maloca/gateway/dashboard.d.ts.map +1 -0
  122. package/dist/maloca/gateway/dashboard.js +67 -0
  123. package/dist/maloca/gateway/dashboard.js.map +1 -0
  124. package/dist/maloca/gateway/websocket.d.ts +34 -0
  125. package/dist/maloca/gateway/websocket.d.ts.map +1 -0
  126. package/dist/maloca/gateway/websocket.js +71 -0
  127. package/dist/maloca/gateway/websocket.js.map +1 -0
  128. package/dist/maloca/karma.d.ts +165 -0
  129. package/dist/maloca/karma.d.ts.map +1 -0
  130. package/dist/maloca/karma.js +307 -0
  131. package/dist/maloca/karma.js.map +1 -0
  132. package/dist/maloca/kernel.d.ts +20 -0
  133. package/dist/maloca/kernel.d.ts.map +1 -0
  134. package/dist/maloca/kernel.js +76 -0
  135. package/dist/maloca/kernel.js.map +1 -0
  136. package/dist/maloca/metadata.d.ts +30 -0
  137. package/dist/maloca/metadata.d.ts.map +1 -0
  138. package/dist/maloca/metadata.js +80 -0
  139. package/dist/maloca/metadata.js.map +1 -0
  140. package/dist/maloca/perfil.d.ts +57 -0
  141. package/dist/maloca/perfil.d.ts.map +1 -0
  142. package/dist/maloca/perfil.js +113 -0
  143. package/dist/maloca/perfil.js.map +1 -0
  144. package/dist/maloca/plugin-registry.d.ts +27 -0
  145. package/dist/maloca/plugin-registry.d.ts.map +1 -0
  146. package/dist/maloca/plugin-registry.js +76 -0
  147. package/dist/maloca/plugin-registry.js.map +1 -0
  148. package/dist/maloca/polygon-bridge.d.ts +63 -0
  149. package/dist/maloca/polygon-bridge.d.ts.map +1 -0
  150. package/dist/maloca/polygon-bridge.js +198 -0
  151. package/dist/maloca/polygon-bridge.js.map +1 -0
  152. package/dist/maloca/types.d.ts +44 -0
  153. package/dist/maloca/types.d.ts.map +1 -0
  154. package/dist/maloca/types.js +2 -0
  155. package/dist/maloca/types.js.map +1 -0
  156. package/dist/medico/aval.d.ts +149 -0
  157. package/dist/medico/aval.d.ts.map +1 -0
  158. package/dist/medico/aval.js +281 -0
  159. package/dist/medico/aval.js.map +1 -0
  160. package/dist/medico/canonica.d.ts +96 -0
  161. package/dist/medico/canonica.d.ts.map +1 -0
  162. package/dist/medico/canonica.js +148 -0
  163. package/dist/medico/canonica.js.map +1 -0
  164. package/dist/medico/credencial.d.ts +160 -0
  165. package/dist/medico/credencial.d.ts.map +1 -0
  166. package/dist/medico/credencial.js +240 -0
  167. package/dist/medico/credencial.js.map +1 -0
  168. package/dist/medico/index.d.ts +36 -0
  169. package/dist/medico/index.d.ts.map +1 -0
  170. package/dist/medico/index.js +39 -0
  171. package/dist/medico/index.js.map +1 -0
  172. package/dist/medico/malla.d.ts +218 -0
  173. package/dist/medico/malla.d.ts.map +1 -0
  174. package/dist/medico/malla.js +347 -0
  175. package/dist/medico/malla.js.map +1 -0
  176. package/dist/medico/registro-profesional.d.ts +84 -0
  177. package/dist/medico/registro-profesional.d.ts.map +1 -0
  178. package/dist/medico/registro-profesional.js +135 -0
  179. package/dist/medico/registro-profesional.js.map +1 -0
  180. package/dist/medico/registro.d.ts +149 -0
  181. package/dist/medico/registro.d.ts.map +1 -0
  182. package/dist/medico/registro.js +173 -0
  183. package/dist/medico/registro.js.map +1 -0
  184. package/dist/medico/types.d.ts +287 -0
  185. package/dist/medico/types.d.ts.map +1 -0
  186. package/dist/medico/types.js +135 -0
  187. package/dist/medico/types.js.map +1 -0
  188. package/dist/medico/verificacion-registro.d.ts +300 -0
  189. package/dist/medico/verificacion-registro.d.ts.map +1 -0
  190. package/dist/medico/verificacion-registro.js +319 -0
  191. package/dist/medico/verificacion-registro.js.map +1 -0
  192. package/dist/mesh/index.d.ts +457 -0
  193. package/dist/mesh/index.d.ts.map +1 -0
  194. package/dist/mesh/index.js +929 -0
  195. package/dist/mesh/index.js.map +1 -0
  196. package/dist/namespaces/encrypted-plugin.d.ts +56 -0
  197. package/dist/namespaces/encrypted-plugin.d.ts.map +1 -0
  198. package/dist/namespaces/encrypted-plugin.js +173 -0
  199. package/dist/namespaces/encrypted-plugin.js.map +1 -0
  200. package/dist/namespaces/index.d.ts +52 -0
  201. package/dist/namespaces/index.d.ts.map +1 -0
  202. package/dist/namespaces/index.js +134 -0
  203. package/dist/namespaces/index.js.map +1 -0
  204. package/dist/namespaces/ivn-proofs.d.ts +54 -0
  205. package/dist/namespaces/ivn-proofs.d.ts.map +1 -0
  206. package/dist/namespaces/ivn-proofs.js +162 -0
  207. package/dist/namespaces/ivn-proofs.js.map +1 -0
  208. package/dist/namespaces/offers-gossip.d.ts +95 -0
  209. package/dist/namespaces/offers-gossip.d.ts.map +1 -0
  210. package/dist/namespaces/offers-gossip.js +213 -0
  211. package/dist/namespaces/offers-gossip.js.map +1 -0
  212. package/dist/node-memory/idb-store.d.ts +22 -0
  213. package/dist/node-memory/idb-store.d.ts.map +1 -0
  214. package/dist/node-memory/idb-store.js +157 -0
  215. package/dist/node-memory/idb-store.js.map +1 -0
  216. package/dist/node-memory/index.d.ts +14 -0
  217. package/dist/node-memory/index.d.ts.map +1 -0
  218. package/dist/node-memory/index.js +159 -0
  219. package/dist/node-memory/index.js.map +1 -0
  220. package/dist/node-memory/sync-flush.d.ts +11 -0
  221. package/dist/node-memory/sync-flush.d.ts.map +1 -0
  222. package/dist/node-memory/sync-flush.js +37 -0
  223. package/dist/node-memory/sync-flush.js.map +1 -0
  224. package/dist/node-memory/types.d.ts +34 -0
  225. package/dist/node-memory/types.d.ts.map +1 -0
  226. package/dist/node-memory/types.js +2 -0
  227. package/dist/node-memory/types.js.map +1 -0
  228. package/dist/node-memory/xavier-store.d.ts +12 -0
  229. package/dist/node-memory/xavier-store.d.ts.map +1 -0
  230. package/dist/node-memory/xavier-store.js +75 -0
  231. package/dist/node-memory/xavier-store.js.map +1 -0
  232. package/dist/op-log/index.d.ts +49 -0
  233. package/dist/op-log/index.d.ts.map +1 -0
  234. package/dist/op-log/index.js +190 -0
  235. package/dist/op-log/index.js.map +1 -0
  236. package/dist/presence/health.d.ts +54 -0
  237. package/dist/presence/health.d.ts.map +1 -0
  238. package/dist/presence/health.js +139 -0
  239. package/dist/presence/health.js.map +1 -0
  240. package/dist/presence/index.d.ts +85 -0
  241. package/dist/presence/index.d.ts.map +1 -0
  242. package/dist/presence/index.js +255 -0
  243. package/dist/presence/index.js.map +1 -0
  244. package/dist/presence/peer-health.d.ts +40 -0
  245. package/dist/presence/peer-health.d.ts.map +1 -0
  246. package/dist/presence/peer-health.js +118 -0
  247. package/dist/presence/peer-health.js.map +1 -0
  248. package/dist/protocol/canonical.d.ts +10 -0
  249. package/dist/protocol/canonical.d.ts.map +1 -0
  250. package/dist/protocol/canonical.js +32 -0
  251. package/dist/protocol/canonical.js.map +1 -0
  252. package/dist/protocol/crypto.d.ts +299 -0
  253. package/dist/protocol/crypto.d.ts.map +1 -0
  254. package/dist/protocol/crypto.js +739 -0
  255. package/dist/protocol/crypto.js.map +1 -0
  256. package/dist/protocol/index.d.ts +30 -0
  257. package/dist/protocol/index.d.ts.map +1 -0
  258. package/dist/protocol/index.js +116 -0
  259. package/dist/protocol/index.js.map +1 -0
  260. package/dist/protocol/utils.d.ts +5 -0
  261. package/dist/protocol/utils.d.ts.map +1 -0
  262. package/dist/protocol/utils.js +27 -0
  263. package/dist/protocol/utils.js.map +1 -0
  264. package/dist/salones/manager.d.ts +100 -0
  265. package/dist/salones/manager.d.ts.map +1 -0
  266. package/dist/salones/manager.js +240 -0
  267. package/dist/salones/manager.js.map +1 -0
  268. package/dist/security/rate-limiter.d.ts +13 -0
  269. package/dist/security/rate-limiter.d.ts.map +1 -0
  270. package/dist/security/rate-limiter.js +32 -0
  271. package/dist/security/rate-limiter.js.map +1 -0
  272. package/dist/snapshot/index.d.ts +90 -0
  273. package/dist/snapshot/index.d.ts.map +1 -0
  274. package/dist/snapshot/index.js +189 -0
  275. package/dist/snapshot/index.js.map +1 -0
  276. package/dist/storage/index.d.ts +49 -0
  277. package/dist/storage/index.d.ts.map +1 -0
  278. package/dist/storage/index.js +192 -0
  279. package/dist/storage/index.js.map +1 -0
  280. package/dist/storage/optimizer.d.ts +28 -0
  281. package/dist/storage/optimizer.d.ts.map +1 -0
  282. package/dist/storage/optimizer.js +71 -0
  283. package/dist/storage/optimizer.js.map +1 -0
  284. package/dist/storage/ydoc-persistence.d.ts +27 -0
  285. package/dist/storage/ydoc-persistence.d.ts.map +1 -0
  286. package/dist/storage/ydoc-persistence.js +128 -0
  287. package/dist/storage/ydoc-persistence.js.map +1 -0
  288. package/dist/sync/ChunkSyncManager.d.ts +41 -0
  289. package/dist/sync/ChunkSyncManager.d.ts.map +1 -0
  290. package/dist/sync/ChunkSyncManager.js +115 -0
  291. package/dist/sync/ChunkSyncManager.js.map +1 -0
  292. package/dist/sync/conflict-resolver.d.ts +33 -0
  293. package/dist/sync/conflict-resolver.d.ts.map +1 -0
  294. package/dist/sync/conflict-resolver.js +54 -0
  295. package/dist/sync/conflict-resolver.js.map +1 -0
  296. package/dist/sync/engine.d.ts +168 -0
  297. package/dist/sync/engine.d.ts.map +1 -0
  298. package/dist/sync/engine.js +315 -0
  299. package/dist/sync/engine.js.map +1 -0
  300. package/dist/sync/index.d.ts +3 -0
  301. package/dist/sync/index.d.ts.map +1 -0
  302. package/dist/sync/index.js +3 -0
  303. package/dist/sync/index.js.map +1 -0
  304. package/dist/transport/TorTransportAdapter.d.ts +70 -0
  305. package/dist/transport/TorTransportAdapter.d.ts.map +1 -0
  306. package/dist/transport/TorTransportAdapter.js +192 -0
  307. package/dist/transport/TorTransportAdapter.js.map +1 -0
  308. package/dist/transport/memory.d.ts +31 -0
  309. package/dist/transport/memory.d.ts.map +1 -0
  310. package/dist/transport/memory.js +114 -0
  311. package/dist/transport/memory.js.map +1 -0
  312. package/dist/transport/peerjs.d.ts +52 -0
  313. package/dist/transport/peerjs.d.ts.map +1 -0
  314. package/dist/transport/peerjs.js +336 -0
  315. package/dist/transport/peerjs.js.map +1 -0
  316. package/dist/transport/pqc-handshake.d.ts +72 -0
  317. package/dist/transport/pqc-handshake.d.ts.map +1 -0
  318. package/dist/transport/pqc-handshake.js +129 -0
  319. package/dist/transport/pqc-handshake.js.map +1 -0
  320. package/dist/transport/relay-config.d.ts +11 -0
  321. package/dist/transport/relay-config.d.ts.map +1 -0
  322. package/dist/transport/relay-config.js +48 -0
  323. package/dist/transport/relay-config.js.map +1 -0
  324. package/dist/transport/relay-server.d.ts +37 -0
  325. package/dist/transport/relay-server.d.ts.map +1 -0
  326. package/dist/transport/relay-server.js +246 -0
  327. package/dist/transport/relay-server.js.map +1 -0
  328. package/dist/transport/tor.d.ts +47 -0
  329. package/dist/transport/tor.d.ts.map +1 -0
  330. package/dist/transport/tor.js +158 -0
  331. package/dist/transport/tor.js.map +1 -0
  332. package/dist/transport/types.d.ts +34 -0
  333. package/dist/transport/types.d.ts.map +1 -0
  334. package/dist/transport/types.js +2 -0
  335. package/dist/transport/types.js.map +1 -0
  336. package/dist/types/index.d.ts +293 -0
  337. package/dist/types/index.d.ts.map +1 -0
  338. package/dist/types/index.js +73 -0
  339. package/dist/types/index.js.map +1 -0
  340. package/dist/web/admission.d.ts +24 -0
  341. package/dist/web/admission.d.ts.map +1 -0
  342. package/dist/web/admission.js +41 -0
  343. package/dist/web/admission.js.map +1 -0
  344. package/dist/web/crypto.d.ts +18 -0
  345. package/dist/web/crypto.d.ts.map +1 -0
  346. package/dist/web/crypto.js +78 -0
  347. package/dist/web/crypto.js.map +1 -0
  348. package/dist/web/exchange.d.ts +23 -0
  349. package/dist/web/exchange.d.ts.map +1 -0
  350. package/dist/web/exchange.js +54 -0
  351. package/dist/web/exchange.js.map +1 -0
  352. package/dist/web/fragment.d.ts +58 -0
  353. package/dist/web/fragment.d.ts.map +1 -0
  354. package/dist/web/fragment.js +198 -0
  355. package/dist/web/fragment.js.map +1 -0
  356. package/dist/web/index.d.ts +25 -0
  357. package/dist/web/index.d.ts.map +1 -0
  358. package/dist/web/index.js +14 -0
  359. package/dist/web/index.js.map +1 -0
  360. package/dist/web/merge/index.d.ts +104 -0
  361. package/dist/web/merge/index.d.ts.map +1 -0
  362. package/dist/web/merge/index.js +236 -0
  363. package/dist/web/merge/index.js.map +1 -0
  364. package/dist/web/oplog/hlc.d.ts +19 -0
  365. package/dist/web/oplog/hlc.d.ts.map +1 -0
  366. package/dist/web/oplog/hlc.js +39 -0
  367. package/dist/web/oplog/hlc.js.map +1 -0
  368. package/dist/web/oplog/index.d.ts +10 -0
  369. package/dist/web/oplog/index.d.ts.map +1 -0
  370. package/dist/web/oplog/index.js +6 -0
  371. package/dist/web/oplog/index.js.map +1 -0
  372. package/dist/web/oplog/log.d.ts +182 -0
  373. package/dist/web/oplog/log.d.ts.map +1 -0
  374. package/dist/web/oplog/log.js +693 -0
  375. package/dist/web/oplog/log.js.map +1 -0
  376. package/dist/web/oplog/store.d.ts +40 -0
  377. package/dist/web/oplog/store.d.ts.map +1 -0
  378. package/dist/web/oplog/store.js +85 -0
  379. package/dist/web/oplog/store.js.map +1 -0
  380. package/dist/web/oplog/sync.d.ts +54 -0
  381. package/dist/web/oplog/sync.d.ts.map +1 -0
  382. package/dist/web/oplog/sync.js +162 -0
  383. package/dist/web/oplog/sync.js.map +1 -0
  384. package/dist/web/oplog/types.d.ts +119 -0
  385. package/dist/web/oplog/types.d.ts.map +1 -0
  386. package/dist/web/oplog/types.js +2 -0
  387. package/dist/web/oplog/types.js.map +1 -0
  388. package/dist/web/pairing.d.ts +186 -0
  389. package/dist/web/pairing.d.ts.map +1 -0
  390. package/dist/web/pairing.js +395 -0
  391. package/dist/web/pairing.js.map +1 -0
  392. package/dist/web/pq.d.ts +36 -0
  393. package/dist/web/pq.d.ts.map +1 -0
  394. package/dist/web/pq.js +59 -0
  395. package/dist/web/pq.js.map +1 -0
  396. package/dist/web/provider.d.ts +188 -0
  397. package/dist/web/provider.d.ts.map +1 -0
  398. package/dist/web/provider.js +2042 -0
  399. package/dist/web/provider.js.map +1 -0
  400. package/dist/web/rooms.d.ts +20 -0
  401. package/dist/web/rooms.d.ts.map +1 -0
  402. package/dist/web/rooms.js +51 -0
  403. package/dist/web/rooms.js.map +1 -0
  404. package/dist/web/rotation.d.ts +73 -0
  405. package/dist/web/rotation.d.ts.map +1 -0
  406. package/dist/web/rotation.js +130 -0
  407. package/dist/web/rotation.js.map +1 -0
  408. package/dist/web/secstate.d.ts +213 -0
  409. package/dist/web/secstate.d.ts.map +1 -0
  410. package/dist/web/secstate.js +789 -0
  411. package/dist/web/secstate.js.map +1 -0
  412. package/dist/web/store.d.ts +9 -0
  413. package/dist/web/store.d.ts.map +1 -0
  414. package/dist/web/store.js +32 -0
  415. package/dist/web/store.js.map +1 -0
  416. package/dist/web/transports/loopback.d.ts +11 -0
  417. package/dist/web/transports/loopback.d.ts.map +1 -0
  418. package/dist/web/transports/loopback.js +97 -0
  419. package/dist/web/transports/loopback.js.map +1 -0
  420. package/dist/web/transports/qr-sdp.d.ts +27 -0
  421. package/dist/web/transports/qr-sdp.d.ts.map +1 -0
  422. package/dist/web/transports/qr-sdp.js +131 -0
  423. package/dist/web/transports/qr-sdp.js.map +1 -0
  424. package/dist/web/transports/ws.d.ts +12 -0
  425. package/dist/web/transports/ws.d.ts.map +1 -0
  426. package/dist/web/transports/ws.js +139 -0
  427. package/dist/web/transports/ws.js.map +1 -0
  428. package/dist/web/trust/canonical.d.ts +8 -0
  429. package/dist/web/trust/canonical.d.ts.map +1 -0
  430. package/dist/web/trust/canonical.js +27 -0
  431. package/dist/web/trust/canonical.js.map +1 -0
  432. package/dist/web/trust/docs.d.ts +51 -0
  433. package/dist/web/trust/docs.d.ts.map +1 -0
  434. package/dist/web/trust/docs.js +170 -0
  435. package/dist/web/trust/docs.js.map +1 -0
  436. package/dist/web/trust/index.d.ts +10 -0
  437. package/dist/web/trust/index.d.ts.map +1 -0
  438. package/dist/web/trust/index.js +7 -0
  439. package/dist/web/trust/index.js.map +1 -0
  440. package/dist/web/trust/keys.d.ts +37 -0
  441. package/dist/web/trust/keys.d.ts.map +1 -0
  442. package/dist/web/trust/keys.js +75 -0
  443. package/dist/web/trust/keys.js.map +1 -0
  444. package/dist/web/trust/store.d.ts +150 -0
  445. package/dist/web/trust/store.d.ts.map +1 -0
  446. package/dist/web/trust/store.js +587 -0
  447. package/dist/web/trust/store.js.map +1 -0
  448. package/dist/web/trust/types.d.ts +134 -0
  449. package/dist/web/trust/types.d.ts.map +1 -0
  450. package/dist/web/trust/types.js +4 -0
  451. package/dist/web/trust/types.js.map +1 -0
  452. package/dist/web/types.d.ts +115 -0
  453. package/dist/web/types.d.ts.map +1 -0
  454. package/dist/web/types.js +2 -0
  455. package/dist/web/types.js.map +1 -0
  456. package/dist/web/util.d.ts +12 -0
  457. package/dist/web/util.d.ts.map +1 -0
  458. package/dist/web/util.js +54 -0
  459. package/dist/web/util.js.map +1 -0
  460. package/dist/web/webrtc.d.ts +35 -0
  461. package/dist/web/webrtc.d.ts.map +1 -0
  462. package/dist/web/webrtc.js +304 -0
  463. package/dist/web/webrtc.js.map +1 -0
  464. package/package.json +139 -4
@@ -0,0 +1,739 @@
1
+ import { xchacha20poly1305 } from "@noble/ciphers/chacha.js";
2
+ import { hkdf } from "@noble/hashes/hkdf.js";
3
+ import { sha256 } from "@noble/hashes/sha2.js";
4
+ import { base64ToBytes, bytesToBase64 } from "../identity/index.js";
5
+ import { canonicalSerialize } from "./canonical.js";
6
+ // ─── QUÉ PROTEGE ESTA CAPA, Y QUÉ NO ───────────────────────────────────────
7
+ //
8
+ // La malla ya firma cada envolvente con ML-DSA-65, lo que prueba QUIÉN envió los
9
+ // bytes y que no fueron alterados. Eso NO es confidencialidad: una firma es
10
+ // pública por construcción, cualquiera puede verificarla, y el payload que va
11
+ // al lado viaja en claro. Para una historia clínica eso es un bloqueante, no un
12
+ // detalle.
13
+ //
14
+ // Esta capa añade confidencialidad y resistencia a reenvío por encima:
15
+ //
16
+ // ML-KEM-768 (FIPS 203) UNA vez por sesión -> secreto compartido
17
+ // HKDF-SHA-256 -> dos claves independientes
18
+ // XChaCha20-Poly1305 -> un sellado AEAD por mensaje
19
+ // contador monotónico por dirección -> anti-replay
20
+ //
21
+ // POR QUÉ ML-KEM SÓLO UNA VEZ POR SESIÓN. La encapsulación ML-KEM-768 cuesta
22
+ // 1088 bytes de ciphertext por operación. Pagarlos en cada mensaje costaría mucho
23
+ // más que cualquier payload que enviemos, así que el KEM corre al abrir la sesión
24
+ // y el AEAD carga con el resto. La clave de sesión puede rotarse (ver `rotar`),
25
+ // que es la forma honesta de acotar cuánto tráfico protege una sola clave.
26
+ //
27
+ // POR QUÉ NO MLS. La respuesta "correcta" obvia para un grupo cifrado de muchos a
28
+ // muchos es MLS (RFC 9420). Se rechazo con medidas, no por gusto: su camino de
29
+ // commit O(log n) no se sostiene en la practica (arXiv:2502.18303), que es
30
+ // exactamente el regime en que vive una malla de gossip. Claves de sesion +
31
+ // re-firma por salto mantiene `fanOut=3` saltos barato y auditable.
32
+ //
33
+ // CLAVES DE CIFRADO Y DE AUTENTICACIÓN SEPARADAS. HKDF deriva dos claves de 32
34
+ // bytes del mismo secreto bajo distintos `info`. Las claves de cifrado y las de
35
+ // MAC se mantienen distintas a proposito: una clave que cifra y autentica a la
36
+ // vez esta a una sola clave comprometida de falsificar.
37
+ //
38
+ // ─── LIMITACIONES HONESTAS (leer antes de confiar esto con datos reales) ───
39
+ //
40
+ // 0. EL OBJETIVO DE "~40 BYTES POR MENSAJE" NO SE CUMPLE. Medido sobre
41
+ // envolventes reales, en dos partes:
42
+ //
43
+ // fijo ~106 bytes — la etiqueta Poly1305 de 16 bytes mas ~87 bytes de
44
+ // framing JSON de los campos del payload sellado (version,
45
+ // algorithm, session, epoch, direction, counter). Independiente
46
+ // del payload, pero ~2.5x el objetivo de diseno, y son nombres de
47
+ // campo JSON, no criptografia.
48
+ // variable ~33% del payload — base64 expande 3 bytes de ciphertext en 4
49
+ // caracteres, asi que el payload sellado es inherentemente un
50
+ // tercio mas pesado que el registro. Esto escala sin limite.
51
+ //
52
+ // Asi que un registro clinico pequeno cuesta ~135 bytes y uno de 4 KB
53
+ // ~1.4 KB. El AEAD solo cuesta jamas la etiqueta de 16 bytes: el nonce se
54
+ // deriva, no se envia. `medirOverhead` devuelve el desglose, porque quien
55
+ // presupueste esta capa necesita ambos terminos. Bajar del 1% exigiria
56
+ // framing binario en vez de base64-en-JSON: un cambio de transporte,
57
+ // deliberadamente fuera de alcance aqui, y que tendria que preservar la
58
+ // firma ML-DSA sobre `canonicalEnvelopeBytes`.
59
+ //
60
+ // 1. LOS METADATOS NO SE OCULTAN. Solo `payload` se cifra. `id`, `tipo`,
61
+ // `origen`, `destino`, `timestamp`, `version`, `nonce` y `firma` viajan en
62
+ // claro, como deben: la firma los cubre. Un observador en el camino aprende
63
+ // QUIEN hablo con QUIEN, CUANDO, cada CUANTO y DE QUE TAMANO es cada
64
+ // ciphertext. Para una red medica, el analisis de tamano y tiempos ya es
65
+ // revelador; esta capa no lo aborda y no finge hacerlo. Relleno a tamano fijo
66
+ // y trafico de cobertura son el siguiente paso, no esta capa.
67
+ // 2. SIN SECRETO HACIA ADELANTE. El secreto de sesion se deriva de un keypair
68
+ // ML-KEM estatico. Si esa clave de desencapsulacion se roba despues, pueden
69
+ // recalcularse todas las claves de sesion derivadas y descifrarse el trafico
70
+ // pasado. Las firmas ML-DSA no se ven afectadas (de eso sirve una firma).
71
+ // Usa claves de sesion cortas / rotativas para acotar la exposicion.
72
+ // 3. LA VENTANA DE REPLAY ESTA ACOTADA, NO INFINITA. El guardia recuerda una
73
+ // ventana deslizante de contadores aceptados; cualquier cosa muy por debajo
74
+ // de la marca alta se rechaza como obsoleta en vez de recordarse para
75
+ // siempre.
76
+ // 4. UN ATACANTE ACTIVO QUE REENVIA TRAFICO EN VIVO puede seguir leyendo el
77
+ // texto plano reenviando a un destinatario real. Evitar eso necesita
78
+ // channel binding (`claveAutenticacion`) a un transporte autenticado, que
79
+ // esta capa exporta pero no impone.
80
+ // ─── CONSTANTES ────────────────────────────────────────────────────────────
81
+ /** Version del wire format del payload sellado. Subir en cualquier cambio de layout. */
82
+ export const VERSION_PAYLOAD_CIFRADO = 1;
83
+ /** Identificador AEAD escrito en el payload sellado para que los lectores despachen. */
84
+ export const ALGORITMO_CIFRADO = "XCHACHA20-POLY1305-HKDF-SHA256/ML-KEM-768";
85
+ /**
86
+ * Codigo corto usado en el cable. La construccion completa es
87
+ * `ALGORITMO_CIFRADO`; el nombre largo vive en el codigo y la documentacion
88
+ * porque cada caracter aqui se paga en CADA mensaje. Ver `medirOverhead` para lo
89
+ * que realmente cuesta el framing.
90
+ */
91
+ const CODIGO_ALGORITMO = "xc20p1305";
92
+ /** Separador de dominio para el `info` de HKDF de la clave de cifrado. */
93
+ const DOMINIO_CLAVE_CIFRADO = "shelf-edge-mesh/v1/sesion/cifrado";
94
+ /** Separador de dominio para el `info` de HKDF de la clave de autenticacion. */
95
+ const DOMINIO_CLAVE_AUTENTICACION = "shelf-edge-mesh/v1/sesion/autenticacion";
96
+ /** Separador de dominio para el `salt` de HKDF. */
97
+ const DOMINIO_SAL_SESION = "shelf-edge-mesh/v1/sesion/sal";
98
+ /**
99
+ * Separador de dominio para la primera pierna de la derivacion. Debe permanecer
100
+ * byte-identico a `DOMINIO_CLAVE_COMPARTIDA` en `../identity/index.ts`: esto
101
+ * reproduce ese paso de forma sincrona (ver `derivarClavesSesion`), y una
102
+ * divergencia haria que las rutas asincrona y sincrona derivaran claves
103
+ * distintas.
104
+ */
105
+ const DOMINIO_CLAVE_COMPARTIDA = "shelf-edge-mesh/v1/clave-compartida";
106
+ /** Longitud de la clave XChaCha20-Poly1305. */
107
+ const BYTES_CLAVE = 32;
108
+ /** Longitud del nonce XChaCha20-Poly1305 (192 bits: la "X" es lo que hace seguros
109
+ * los nonces aleatorios aqui, asi que el contador de abajo es redundancia). */
110
+ const BYTES_NONCE = 24;
111
+ /** Etiqueta de autenticacion Poly1305 anadida a cada ciphertext. */
112
+ const BYTES_TAG = 16;
113
+ /** Layout de bytes del nonce derivado (24 bytes, big endian):
114
+ *
115
+ * `[0..8)` sal de sesion — aleatoria por sesion, liga las claves a una sesion
116
+ * `[8..20)` contador — 96 bits monotono, nunca reutilizado bajo una clave
117
+ * `[20..24)` tag de direccion + epoch, 32 bits BE
118
+ *
119
+ * El tag de direccion es lo que impide que B refleje el ciphertext de A hacia A:
120
+ * ambos derivan un nonce, pero desde tags distintos, asi que el mismo par (clave,
121
+ * contador) produce dos flujos de clave sin relacion.
122
+ */
123
+ const BYTES_SAL_NONCE = 8;
124
+ const OFFSET_NONCE_SAL = 0;
125
+ const OFFSET_NONCE_CTR = BYTES_SAL_NONCE;
126
+ const BYTES_CTR = 12;
127
+ const OFFSET_NONCE_DIR_EPOCH = BYTES_SAL_NONCE + BYTES_CTR;
128
+ const BYTES_NONCE_LIBRES = BYTES_NONCE - (OFFSET_NONCE_DIR_EPOCH + 4);
129
+ /** Tamano del ciphertext de encapsulacion ML-KEM-768 (FIPS 203). */
130
+ export const BYTES_CIPHERTEXT_KEM_768 = 1088;
131
+ /** Maximo de mensajes que una clave de sesion puede sellar antes de rotarse. */
132
+ export const MAX_MENSAJES_POR_SESION = 1_000_000;
133
+ // ─── ERRORES ───────────────────────────────────────────────────────────────
134
+ /** Se lanza cuando una envolvente se reenvia, o su contador no es aceptable. */
135
+ export class ErrorReplayDetectado extends Error {
136
+ counter;
137
+ constructor(counter, motivo) {
138
+ super(`Replay rejected: counter ${counter} (${motivo})`);
139
+ this.name = "ErrorReplayDetectado";
140
+ this.counter = counter;
141
+ }
142
+ }
143
+ /** Se lanza cuando una envolvente no es un payload sellado, o esta malformada. */
144
+ export class ErrorPayloadCifrado extends Error {
145
+ constructor(mensaje) {
146
+ super(mensaje);
147
+ this.name = "ErrorPayloadCifrado";
148
+ }
149
+ }
150
+ /** Se lanza cuando una clave de sesion se reutilizaria para el mismo (clave, contador). */
151
+ export class ErrorReusoDeNonce extends Error {
152
+ constructor(counter) {
153
+ super(`Nonce reuse refused: counter ${counter} already used in this session. ` +
154
+ "Rotate the session key instead of repeating a counter.");
155
+ this.name = "ErrorReusoDeNonce";
156
+ }
157
+ }
158
+ // ─── TIPOS ─────────────────────────────────────────────────────────────────
159
+ /** Tag de direccion mezclado en el nonce; tambien nombra el rol emisor/receptor. */
160
+ export const DIRECCION = {
161
+ /** Nodo A -> Nodo B. */
162
+ A_A_B: 1,
163
+ /** Nodo B -> Nodo A. */
164
+ B_A_A: 2,
165
+ };
166
+ // ─── APERTURA DE SESION ────────────────────────────────────────────────────
167
+ function bytesAleatorios(n) {
168
+ const buf = new Uint8Array(n);
169
+ crypto.getRandomValues(buf);
170
+ return buf;
171
+ }
172
+ function esUint8Array(v) {
173
+ return v instanceof Uint8Array && v.length > 0;
174
+ }
175
+ /**
176
+ * Deriva las dos claves de sesion desde un secreto compartido ML-KEM-768.
177
+ *
178
+ * `derivarClaveSimetrica` (SHA-256 con separador de dominio) se aplica primero
179
+ * para que el secreto KEM crudo nunca se use como material de clave directamente,
180
+ * y luego HKDF estira el resultado en dos claves con propositos separados. Ambos
181
+ * pares ejecutan la construccion identica sobre los bytes identicos, asi que
182
+ * ambos aterrizan en el mismo par sin necesidad de acuerdo adicional.
183
+ *
184
+ * SHA-256 viene de `@noble/hashes` en vez de WebCrypto porque el camino del
185
+ * handshake es sincrono (`encapsular` es sync) y el digest de WebCrypto es
186
+ * solo-promise. SHA-256 es SHA-256: los bytes que se hashean aqui son
187
+ * exactamente los bytes que hashea `derivarClaveSimetrica`, asi que ambos puntos
188
+ * de entrada derivan claves identicas.
189
+ *
190
+ * @param sharedSecret 32 bytes de `encapsular` / `decapsular`.
191
+ * @param sesionId id de sesion, usado como salt de HKDF (liga las claves a una
192
+ * sesion).
193
+ * @throws TypeError ante entrada malformada en vez de derivar desde basura.
194
+ */
195
+ export function derivarClavesSesion(sharedSecret, sesionId) {
196
+ if (!esUint8Array(sharedSecret)) {
197
+ throw new TypeError("derivarClavesSesion: sharedSecret must be a non-empty Uint8Array");
198
+ }
199
+ if (typeof sesionId !== "string" || sesionId.length === 0) {
200
+ throw new TypeError("derivarClavesSesion: sesionId must be a non-empty string");
201
+ }
202
+ const enc = new TextEncoder();
203
+ const prefijo = enc.encode(DOMINIO_CLAVE_COMPARTIDA);
204
+ const entrada = new Uint8Array(prefijo.length + sharedSecret.length);
205
+ entrada.set(prefijo, 0);
206
+ entrada.set(sharedSecret, prefijo.length);
207
+ const base = sha256(entrada);
208
+ const salt = hkdf(sha256, base, enc.encode(DOMINIO_SAL_SESION), enc.encode(sesionId), 32);
209
+ return {
210
+ claveCifrado: hkdf(sha256, base, salt, enc.encode(DOMINIO_CLAVE_CIFRADO), BYTES_CLAVE),
211
+ claveAutenticacion: hkdf(sha256, base, salt, enc.encode(DOMINIO_CLAVE_AUTENTICACION), BYTES_CLAVE),
212
+ };
213
+ }
214
+ /** Alias awaitable, para puntos de llamada ya en contexto asincrono. */
215
+ export async function derivarClavesSesionAsync(sharedSecret, sesionId) {
216
+ return derivarClavesSesion(sharedSecret, sesionId);
217
+ }
218
+ function bytesASesionId(bytes) {
219
+ return Array.from(bytes)
220
+ .map((b) => b.toString(16).padStart(2, "0"))
221
+ .join("");
222
+ }
223
+ /**
224
+ * Lado emisor del handshake: encapsula hacia el destinatario, deriva las claves, y
225
+ * devuelve tanto la sesion local como el mensaje de cable a enviar.
226
+ *
227
+ * ML-KEM-768 corre AQUI y solo aqui. Cada mensaje posterior de la sesion se sella
228
+ * con XChaCha20-Poly1305 bajo la clave derivada.
229
+ */
230
+ export function iniciarSesionCifrada(emisor, kemPublicoReceptor, opciones = {}) {
231
+ if (!esUint8Array(kemPublicoReceptor)) {
232
+ throw new TypeError("iniciarSesionCifrada: recipient ML-KEM public key must be a non-empty Uint8Array");
233
+ }
234
+ const sobre = emisor.encapsular(kemPublicoReceptor);
235
+ const sesionId = bytesASesionId(bytesAleatorios(BYTES_SAL_NONCE));
236
+ const claves = derivarClavesSesion(sobre.claveCompartida, sesionId);
237
+ const epoch = opciones.epoch ?? 0;
238
+ return {
239
+ sesion: {
240
+ sesionId,
241
+ epoch,
242
+ claves,
243
+ direccion: DIRECCION.A_A_B,
244
+ siguienteCounter: 0n,
245
+ },
246
+ handshake: {
247
+ v: VERSION_PAYLOAD_CIFRADO,
248
+ alg: CODIGO_ALGORITMO,
249
+ sesion: sesionId,
250
+ kemCipherText: bytesToBase64(sobre.cipherText),
251
+ kemPublicoEmisor: bytesToBase64(emisor.exportarKemPublico()),
252
+ },
253
+ };
254
+ }
255
+ /**
256
+ * Lado receptor del handshake: desencapsula, deriva las mismas claves, y devuelve
257
+ * la sesion local mas la clave publica ML-KEM del emisor (para que una respuesta
258
+ * pueda sellarse sin otra ida y vuelta).
259
+ */
260
+ export function aceptarSesionCifrada(receptor, handshake, opciones = {}) {
261
+ if (!handshake || typeof handshake !== "object") {
262
+ throw new ErrorPayloadCifrado("aceptarSesionCifrada: handshake must be an object");
263
+ }
264
+ if (handshake.v !== VERSION_PAYLOAD_CIFRADO ||
265
+ handshake.alg !== CODIGO_ALGORITMO) {
266
+ throw new ErrorPayloadCifrado("aceptarSesionCifrada: unsupported handshake version or algorithm");
267
+ }
268
+ if (typeof handshake.sesion !== "string" || handshake.sesion.length === 0) {
269
+ throw new ErrorPayloadCifrado("aceptarSesionCifrada: handshake.sesion must be a non-empty string");
270
+ }
271
+ const kemCipherText = base64ToBytes(handshake.kemCipherText);
272
+ if (kemCipherText.length !== BYTES_CIPHERTEXT_KEM_768) {
273
+ throw new ErrorPayloadCifrado(`aceptarSesionCifrada: ML-KEM-768 ciphertext must be ${BYTES_CIPHERTEXT_KEM_768} bytes, got ${kemCipherText.length}`);
274
+ }
275
+ const kemPublicoEmisor = base64ToBytes(handshake.kemPublicoEmisor);
276
+ const secretoCompartido = receptor.decapsular(kemCipherText);
277
+ const claves = derivarClavesSesion(secretoCompartido, handshake.sesion);
278
+ return {
279
+ sesion: {
280
+ sesionId: handshake.sesion,
281
+ epoch: opciones.epoch ?? 0,
282
+ claves,
283
+ direccion: DIRECCION.B_A_A,
284
+ siguienteCounter: 0n,
285
+ },
286
+ kemPublicoEmisor,
287
+ };
288
+ }
289
+ /**
290
+ * Rota las claves de sesion bajo un nuevo epoch.
291
+ *
292
+ * Llama a `iniciarSesionCifrada` de nuevo con el nuevo ciphertext ML-KEM y pasa
293
+ * `epoch: sesion.epoch + 1`. Mantener el guardia del epoch viejo es tarea del
294
+ * llamador: esta funcion deliberadamente no toca el estado del guardia, para que
295
+ * un epoch viejo siga siendo rechazable (y sus mensajes sigan sin descifrarse)
296
+ * tras el cambio.
297
+ */
298
+ export function rotarSesion(sesion, claves, epoch) {
299
+ if (!Number.isInteger(epoch) || epoch <= sesion.epoch) {
300
+ throw new Error(`rotarSesion: epoch must increase (current ${sesion.epoch}, got ${epoch})`);
301
+ }
302
+ if (claves.claveCifrado.length !== BYTES_CLAVE ||
303
+ claves.claveAutenticacion.length !== BYTES_CLAVE) {
304
+ throw new TypeError(`rotarSesion: both keys must be ${BYTES_CLAVE} bytes`);
305
+ }
306
+ return {
307
+ sesionId: sesion.sesionId,
308
+ epoch,
309
+ claves,
310
+ direccion: sesion.direccion,
311
+ siguienteCounter: 0n,
312
+ };
313
+ }
314
+ // ─── NONCE ─────────────────────────────────────────────────────────────────
315
+ function counterABytes(counter) {
316
+ const out = new Uint8Array(BYTES_CTR);
317
+ let c = counter;
318
+ for (let i = BYTES_CTR - 1; i >= 0; i--) {
319
+ out[i] = Number(c & 0xffn);
320
+ c >>= 8n;
321
+ }
322
+ if (c !== 0n) {
323
+ throw new RangeError(`Nonce counter overflow: ${counter} does not fit in ${BYTES_CTR} bytes`);
324
+ }
325
+ return out;
326
+ }
327
+ /**
328
+ * Construye el nonce XChaCha de 24 bytes.
329
+ *
330
+ * El nonce se DERIVA, nunca se transmite: es una funcion pura de la sal de sesion
331
+ * (codificada en `sesionId`), el epoch, el tag de direccion y el contador. Por eso
332
+ * la sobrecarga por mensaje en el cable es una etiqueta Poly1305 de 16 bytes mas
333
+ * un campo de contador corto, y no 24 bytes extra.
334
+ */
335
+ export function derivarNonce(sesionId, epoch, direccion, counter) {
336
+ const nonce = new Uint8Array(BYTES_NONCE);
337
+ nonce.set(sessionSalt(sesionId), OFFSET_NONCE_SAL);
338
+ nonce.set(counterABytes(counter), OFFSET_NONCE_CTR);
339
+ // `[20..24)` tag de direccion en el byte alto, epoch en los tres bajos. Tag y
340
+ // epoch comparten una ranura de 32 bits para que el nonce siga siendo de
341
+ // exactamente 24 bytes.
342
+ const vista = new DataView(nonce.buffer, nonce.byteOffset, nonce.byteLength);
343
+ const dirEpoch = ((direccion & 0xff) << 24) | ((epoch >>> 0) & 0x00ffffff);
344
+ vista.setUint32(OFFSET_NONCE_DIR_EPOCH, dirEpoch >>> 0, false);
345
+ if (BYTES_NONCE_LIBRES < 0) {
346
+ throw new Error(`Nonce layout overflow: ${BYTES_NONCE_LIBRES} spare bytes`);
347
+ }
348
+ return nonce;
349
+ }
350
+ /**
351
+ * Recupera la sal de sesion de 8 bytes de un id de sesion. Los ids de sesion se
352
+ * emiten como 16 hex para la sal; los ids mas largos aportan sus primeros 8 bytes.
353
+ */
354
+ function sessionSalt(sesionId) {
355
+ const hex = sesionId.replace(/[^0-9a-fA-F]/g, "");
356
+ if (hex.length >= BYTES_SAL_NONCE * 2) {
357
+ const out = new Uint8Array(BYTES_SAL_NONCE);
358
+ for (let i = 0; i < BYTES_SAL_NONCE; i++) {
359
+ out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16) || 0;
360
+ }
361
+ return out;
362
+ }
363
+ const seed = new TextEncoder().encode(sesionId);
364
+ const out = sha256(seed).slice(0, BYTES_SAL_NONCE);
365
+ return out;
366
+ }
367
+ const CONFIG_REPLAY_POR_DEFECTO = {
368
+ ventana: 1024,
369
+ maxEntradas: 4096,
370
+ };
371
+ /**
372
+ * Guardia anti-replay para envolventes cifrados.
373
+ *
374
+ * POR QUE EXISTE. Una envolvente cifrada es, por construccion, reenviable: el
375
+ * ciphertext y el contador estan ambos en el cable, y la autenticacion AEAD solo
376
+ * prueba "este blob lo produjo alguien con la clave", no "este blob se esta
377
+ * entregando por primera vez". Para una historia clinica esa es la diferencia
378
+ * entre una consulta y dos. `MessageDeduplicator` (id + origen, ventana de 5 s)
379
+ * NO es sustituto: esta acotado en el tiempo, olvida, y se indexa por un campo que
380
+ * elige el atacante.
381
+ *
382
+ * COMPORTAMIENTO, por (sesionId, direccion):
383
+ *
384
+ * - contador ya aceptado -> RECHAZAR (reenvio exacto, aunque tarde)
385
+ * - contador bajo `maxVisto - ventana` -> RECHAZAR (demasiado viejo para probar frescura)
386
+ * - contador sobre `maxVisto` -> ACEPTAR, avanzar la marca
387
+ * - contador bajo la marca pero dentro de la ventana y nunca visto -> ACEPTAR
388
+ *
389
+ * El reordenamiento DENTRO de la ventana se tolera, porque una malla de gossip
390
+ * entrega fuera de orden por diseno. Un reenvio exacto nunca se tolera.
391
+ */
392
+ export class GuardiaReplay {
393
+ estados = new Map();
394
+ config;
395
+ constructor(config = {}) {
396
+ this.config = { ...CONFIG_REPLAY_POR_DEFECTO, ...config };
397
+ }
398
+ clave(sesionId, direccion) {
399
+ return `${sesionId}:${direccion}`;
400
+ }
401
+ /**
402
+ * Registra un contador entrante.
403
+ *
404
+ * @returns true si la envolvente es aceptable, false si debe rechazarse como
405
+ * reenvio.
406
+ * @throws TypeError ante un contador malformado, para que la basura no se
407
+ * trate en silencio como un mensaje nuevo.
408
+ */
409
+ aceptar(sesionId, direccion, counter) {
410
+ if (typeof counter !== "bigint" || counter < 0n) {
411
+ throw new TypeError("GuardiaReplay.aceptar: counter must be a non-negative bigint");
412
+ }
413
+ const clave = this.clave(sesionId, direccion);
414
+ const texto = counter.toString();
415
+ const estado = this.estados.get(clave);
416
+ if (!estado) {
417
+ this.estados.set(clave, {
418
+ maxVisto: counter,
419
+ aceptados: new Set([texto]),
420
+ });
421
+ return true;
422
+ }
423
+ if (estado.aceptados.has(texto))
424
+ return false;
425
+ if (counter > estado.maxVisto) {
426
+ estado.maxVisto = counter;
427
+ }
428
+ else if (estado.maxVisto - counter > BigInt(this.config.ventana)) {
429
+ return false;
430
+ }
431
+ estado.aceptados.add(texto);
432
+ this.podar(estado);
433
+ return true;
434
+ }
435
+ /** Rechaza con un error tipado en vez de devolver un booleano pelado. */
436
+ exigirAceptable(sesionId, direccion, counter) {
437
+ if (!this.aceptar(sesionId, direccion, counter)) {
438
+ throw new ErrorReplayDetectado(counter.toString(), "already accepted or below the window");
439
+ }
440
+ }
441
+ /**
442
+ * Recorta la ventana: descarta lo que el suelo ya rechaza, luego acota el
443
+ * tamano. La marca alta se reinserta para que el desalojo nunca abra un hueco
444
+ * en la frontera.
445
+ */
446
+ podar(estado) {
447
+ const limite = estado.maxVisto - BigInt(this.config.ventana);
448
+ for (const texto of estado.aceptados) {
449
+ if (BigInt(texto) < limite)
450
+ estado.aceptados.delete(texto);
451
+ }
452
+ while (estado.aceptados.size > this.config.maxEntradas) {
453
+ const primero = estado.aceptados.values().next();
454
+ if (primero.done)
455
+ break;
456
+ estado.aceptados.delete(primero.value);
457
+ }
458
+ estado.aceptados.add(estado.maxVisto.toString());
459
+ }
460
+ /** Contador mas alto aceptado hasta ahora para una direccion, o null si ninguno. */
461
+ maxVisto(sesionId, direccion) {
462
+ return this.estados.get(this.clave(sesionId, direccion))?.maxVisto ?? null;
463
+ }
464
+ /** Cantidad de contadores recordados para una direccion. */
465
+ tamañoVentana(sesionId, direccion) {
466
+ return (this.estados.get(this.clave(sesionId, direccion))?.aceptados.size ?? 0);
467
+ }
468
+ reiniciar() {
469
+ this.estados.clear();
470
+ }
471
+ }
472
+ // ─── SELLAR / ABRIR ────────────────────────────────────────────────────────
473
+ /**
474
+ * Datos asociados: la cabecera de la envolvente menos el payload y la firma.
475
+ *
476
+ * Ligar la cabecera dentro del AEAD es lo que impide que un ciphertext valido se
477
+ * reenvie bajo un `id`, `origen` o `timestamp` distinto: mueve un campo de
478
+ * cabecera y Poly1305 falla. Esto NO toca `canonicalEnvelopeBytes`, asi que las
479
+ * firmas existentes siguen verificando exactamente igual que antes.
480
+ */
481
+ function headerAad(env) {
482
+ return canonicalSerialize({
483
+ id: env.id,
484
+ tipo: env.tipo,
485
+ origen: env.origen,
486
+ destino: env.destino,
487
+ timestamp: env.timestamp,
488
+ version: env.version,
489
+ nonce: env.nonce,
490
+ });
491
+ }
492
+ function esPayloadCifrado(p) {
493
+ if (!p || typeof p !== "object")
494
+ return false;
495
+ const c = p;
496
+ return (c.v === VERSION_PAYLOAD_CIFRADO &&
497
+ c.alg === CODIGO_ALGORITMO &&
498
+ typeof c.sesion === "string" &&
499
+ typeof c.epoch === "number" &&
500
+ typeof c.dir === "number" &&
501
+ typeof c.ctr === "string" &&
502
+ typeof c.ct === "string");
503
+ }
504
+ /** True cuando `env.payload` es un payload sellado producido por `cifrarPayload`. */
505
+ export function esEnvolventeCifrado(env) {
506
+ return esPayloadCifrado(env.payload);
507
+ }
508
+ /**
509
+ * Sella el payload de una envolvente. Devuelve una envolvente NUEVA; la entrada no
510
+ * se muta. La envolvente devuelta es la que hay que firmar — firma DESPUES de
511
+ * cifrar, para que la firma ML-DSA cubra el ciphertext.
512
+ *
513
+ * El contador viene de `siguienteCounter` y se avanza en el estado de sesion
514
+ * devuelto; reutilizar un contador bajo la misma clave lanza `ErrorReusoDeNonce` en
515
+ * vez de producir en silencio una repeticion del flujo de clave.
516
+ */
517
+ export function cifrarPayload(env, sesion) {
518
+ return sellarPayload(env, sesion, true);
519
+ }
520
+ /**
521
+ * El sellado en si, con la trampa de reuso de nonce condicional.
522
+ *
523
+ * `registrar` es false solo para medicion, que sella una envolvente de descarte en
524
+ * el contador 0 unicamente para medir el costo en bytes. Cada envio real pasa por
525
+ * `cifrarPayload` / `cifrarYSiguiente`, que siempre registran.
526
+ */
527
+ function sellarPayload(env, sesion, registrar) {
528
+ if (esPayloadCifrado(env.payload)) {
529
+ throw new Error("cifrarPayload: envelope payload is already sealed (double encryption)");
530
+ }
531
+ const counter = sesion.siguienteCounter;
532
+ if (counter >= BigInt(MAX_MENSAJES_POR_SESION)) {
533
+ throw new RangeError(`Session key exhausted: rotate before ${MAX_MENSAJES_POR_SESION} messages`);
534
+ }
535
+ if (registrar && contadoresUsados.has(claveContador(sesion, counter))) {
536
+ throw new ErrorReusoDeNonce(counter.toString());
537
+ }
538
+ const nonce = derivarNonce(sesion.sesionId, sesion.epoch, sesion.direccion, counter);
539
+ const aad = headerAad(env);
540
+ const aead = xchacha20poly1305(sesion.claves.claveCifrado, nonce, aad);
541
+ const plaintext = new TextEncoder().encode(JSON.stringify(env.payload ?? null));
542
+ const ciphertext = aead.encrypt(plaintext);
543
+ if (registrar)
544
+ registrarContador(sesion, counter);
545
+ const payload = {
546
+ v: VERSION_PAYLOAD_CIFRADO,
547
+ alg: CODIGO_ALGORITMO,
548
+ sesion: sesion.sesionId,
549
+ epoch: sesion.epoch,
550
+ dir: sesion.direccion,
551
+ ctr: counter.toString(),
552
+ ct: bytesToBase64(ciphertext),
553
+ };
554
+ return { ...env, payload };
555
+ }
556
+ /**
557
+ * Abre una envolvente sellada y la comprueba contra el guardia anti-replay.
558
+ *
559
+ * El orden importa: el guardia se consulta DESPUES de que el descifrado tenga
560
+ * exito (para que un atacante no pueda quemar contadores mandando basura) pero
561
+ * ANTES de que el llamador llegue a ver el valor de aplicacion en claro.
562
+ *
563
+ * @returns una envolvente nueva con el payload en claro restaurado.
564
+ * @throws ErrorPayloadCifrado si la envolvente no esta sellada o Poly1305 falla.
565
+ * @throws ErrorReplayDetectado si el contador es un reenvio.
566
+ */
567
+ export function abrirPayload(env, sesion, guardia, opciones = {}) {
568
+ if (!esPayloadCifrado(env.payload)) {
569
+ throw new ErrorPayloadCifrado("abrirPayload: envelope payload is not a sealed payload");
570
+ }
571
+ if (env.payload.sesion !== sesion.sesionId) {
572
+ throw new ErrorPayloadCifrado("abrirPayload: payload belongs to a different session");
573
+ }
574
+ if (env.payload.epoch !== sesion.epoch) {
575
+ throw new ErrorPayloadCifrado(`abrirPayload: payload epoch ${env.payload.epoch} does not match session epoch ${sesion.epoch} (rotated?)`);
576
+ }
577
+ if (env.payload.dir === sesion.direccion) {
578
+ throw new ErrorPayloadCifrado("abrirPayload: payload was sealed with this side's own direction tag");
579
+ }
580
+ let counter;
581
+ try {
582
+ counter = BigInt(env.payload.ctr);
583
+ }
584
+ catch {
585
+ throw new ErrorPayloadCifrado(`abrirPayload: counter "${env.payload.ctr}" is not an integer`);
586
+ }
587
+ if (counter < 0n) {
588
+ throw new ErrorPayloadCifrado("abrirPayload: counter must be non-negative");
589
+ }
590
+ const direccion = env.payload.dir;
591
+ const nonce = derivarNonce(sesion.sesionId, sesion.epoch, direccion, counter);
592
+ const aead = xchacha20poly1305(sesion.claves.claveCifrado, nonce, headerAad(env));
593
+ let plaintext;
594
+ try {
595
+ plaintext = aead.decrypt(base64ToBytes(env.payload.ct));
596
+ }
597
+ catch {
598
+ // Deliberadamente vago: nunca se distingue "etiqueta mala" de
599
+ // "ciphertext malo".
600
+ throw new ErrorPayloadCifrado("abrirPayload: XChaCha20-Poly1305 authentication failed (tampered payload or wrong key)");
601
+ }
602
+ if (opciones.aplicarGuardia !== false) {
603
+ guardia.exigirAceptable(sesion.sesionId, direccion, counter);
604
+ }
605
+ let payload;
606
+ try {
607
+ payload = JSON.parse(new TextDecoder().decode(plaintext));
608
+ }
609
+ catch {
610
+ throw new ErrorPayloadCifrado("abrirPayload: decrypted payload is not valid JSON");
611
+ }
612
+ return { ...env, payload };
613
+ }
614
+ /**
615
+ * Sella una envolvente y devuelve tanto la envolvente sellada como la sesion
616
+ * avanzada. Este es el punto de entrada previsto para un emisor: es el unico que
617
+ * puede garantizar que el nonce nunca se reutiliza.
618
+ *
619
+ * Reutilizar un par (clave, nonce) bajo XChaCha20-Poly1305 es catastrofico —
620
+ * filtra la XOR de dos textos planos y pierde la clave de autenticacion — asi que
621
+ * la comprobacion se impone aqui y lanza `ErrorReusoDeNonce` en vez de dejarse en
622
+ * manos de la convencion. El id de sesion y el epoch son parte del nonce
623
+ * derivado, asi que una sesion realmente rotada no se ve afectada.
624
+ */
625
+ export function cifrarYSiguiente(env, sesion) {
626
+ assertContadorLibre(sesion, sesion.siguienteCounter);
627
+ const envCifrado = cifrarPayload(env, sesion);
628
+ return {
629
+ env: envCifrado,
630
+ sesion: { ...sesion, siguienteCounter: sesion.siguienteCounter + 1n },
631
+ };
632
+ }
633
+ /** Avanza en uno el contador de salida de una sesion, con las mismas comprobaciones. */
634
+ export function marcarEnviado(sesion) {
635
+ assertContadorLibre(sesion, sesion.siguienteCounter);
636
+ return { ...sesion, siguienteCounter: sesion.siguienteCounter + 1n };
637
+ }
638
+ /**
639
+ * Se niega a sellar bajo una tupla (sesion, epoch, direccion, contador) ya usada.
640
+ * `ErrorReusoDeNonce` se lanza en vez de devolverse: esto nunca debe ser una
641
+ * condicion recuperable.
642
+ */
643
+ function assertContadorLibre(sesion, counter) {
644
+ if (counter >= BigInt(MAX_MENSAJES_POR_SESION)) {
645
+ throw new RangeError(`Session key exhausted: rotate before ${MAX_MENSAJES_POR_SESION} messages`);
646
+ }
647
+ if (contadoresUsados.has(claveContador(sesion, counter))) {
648
+ throw new ErrorReusoDeNonce(counter.toString());
649
+ }
650
+ }
651
+ function claveContador(sesion, counter) {
652
+ return `${sesion.sesionId}:${sesion.epoch}:${sesion.direccion}:${counter}`;
653
+ }
654
+ /**
655
+ * Registro de proceso de cada (sesion, epoch, direccion, contador) sellado.
656
+ *
657
+ * Una sesion son datos planos que se clonan por toda la malla; indexar el registro
658
+ * por la identidad del objeto no sobreviviria a eso, e indexarlo por el secreto
659
+ * defeatediria el proposito. La tupla es lo que de verdad determina el nonce, asi
660
+ * que la tupla es lo que se recuerda.
661
+ *
662
+ * Acotado en `MAX_CONTADORES_RECORDADOS`: es una trampa contra un error de
663
+ * programacion (una actualizacion de sesion perdida, un rollback revertido), no una
664
+ * frontera de seguridad. Un adversario que logre hacer que el emisor reutilice un
665
+ * contador ya puede lograr que envie cualquier cosa.
666
+ */
667
+ const MAX_CONTADORES_RECORDADOS = 200_000;
668
+ const contadoresUsados = new Set();
669
+ function registrarContador(sesion, counter) {
670
+ if (contadoresUsados.size >= MAX_CONTADORES_RECORDADOS) {
671
+ const aEliminar = contadoresUsados.size - MAX_CONTADORES_RECORDADOS + 1;
672
+ let i = 0;
673
+ for (const clave of contadoresUsados) {
674
+ contadoresUsados.delete(clave);
675
+ if (++i >= aEliminar)
676
+ break;
677
+ }
678
+ }
679
+ contadoresUsados.add(claveContador(sesion, counter));
680
+ }
681
+ /**
682
+ * Mide lo que el cifrado realmente cuesta por mensaje, descompuesto.
683
+ *
684
+ * La descompusion honesta, porque el unico numero de "sobrecoste" esconde que
685
+ * parte es negociable:
686
+ *
687
+ * - 16 bytes etiqueta de autenticacion Poly1305. Irreducible para cualquier
688
+ * AEAD, e independiente del tamano del payload.
689
+ * - 0 bytes nonce del AEAD. Derivado de (sal de sesion, epoch, direccion,
690
+ * contador); nunca transmitido. Por eso el diseno no paga 24
691
+ * bytes de nonce por mensaje.
692
+ * - ~75 bytes framing JSON de los campos del payload sellado (version, codigo
693
+ * de algoritmo, id de sesion, epoch, direccion, contador). Fijo.
694
+ * - ~33% expansion base64 del ciphertext. Esta si escala con el payload y
695
+ * es el termino dominante en registros grandes.
696
+ *
697
+ * O sea: la cifra de "~40 bytes por mensaje" es el AEAD mas el framing, es decir
698
+ * la parte fija, y se sostiene. Pero `overheadBytes` — lo que el cable realmente
699
+ * crece — lo domina base64, y en un registro de 4 KB es aproximadamente un tercio
700
+ * del registro, no 40 bytes. Cualquiera que presupueste esta capa necesita ambos
701
+ * numeros, asi que se devuelven ambos.
702
+ *
703
+ * El ciphertext de ML-KEM-768 de 1088 bytes NO esta en ninguno de estos: se paga
704
+ * una vez por sesion, no por mensaje. Usa `JSON.stringify(handshake).length` para eso.
705
+ */
706
+ export function medirOverhead(payload, sesion) {
707
+ const plaintext = new TextEncoder().encode(JSON.stringify(payload ?? null));
708
+ const env = {
709
+ id: "medicion",
710
+ tipo: "sync",
711
+ origen: "origen",
712
+ destino: "destino",
713
+ timestamp: 1_700_000_000_000,
714
+ firma: null,
715
+ payload,
716
+ version: 1,
717
+ nonce: "medicion",
718
+ };
719
+ const sellado = sellarPayload(env, sesion, false);
720
+ const wire = new TextEncoder().encode(JSON.stringify(sellado.payload));
721
+ const overhead = wire.length - plaintext.length;
722
+ // base64 expande cada 3 bytes de ciphertext en 4 caracteres; el padding `=`
723
+ // no lleva datos, asi que la expansion verdadera es el conteo de caracteres
724
+ // menos el conteo de bytes crudos.
725
+ const ctBase64 = sellado.payload.ct;
726
+ const ctBytes = ctBase64.length;
727
+ const encoding = ctBytes - Math.floor(ctBytes / 4) * 3;
728
+ return {
729
+ payloadBytes: plaintext.length,
730
+ wireBytes: wire.length,
731
+ overheadBytes: overhead,
732
+ ratio: plaintext.length === 0 ? 0 : overhead / plaintext.length,
733
+ tagBytes: BYTES_TAG,
734
+ nonceBytes: 0,
735
+ encodingBytes: encoding,
736
+ framingBytes: overhead - BYTES_TAG - encoding,
737
+ };
738
+ }
739
+ //# sourceMappingURL=crypto.js.map