@meshline/sdk 0.0.0-stage → 0.1.0-alpha.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 (338) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +43 -2
  3. package/dist/channels/repository.d.ts +73 -0
  4. package/dist/channels/repository.d.ts.map +1 -0
  5. package/dist/channels/repository.js +367 -0
  6. package/dist/channels/repository.js.map +1 -0
  7. package/dist/client/account-operations.d.ts +41 -0
  8. package/dist/client/account-operations.d.ts.map +1 -0
  9. package/dist/client/account-operations.js +304 -0
  10. package/dist/client/account-operations.js.map +1 -0
  11. package/dist/client/conversations.d.ts +36 -0
  12. package/dist/client/conversations.d.ts.map +1 -0
  13. package/dist/client/conversations.js +144 -0
  14. package/dist/client/conversations.js.map +1 -0
  15. package/dist/client.d.ts +53 -0
  16. package/dist/client.d.ts.map +1 -0
  17. package/dist/client.js +162 -0
  18. package/dist/client.js.map +1 -0
  19. package/dist/components/account.d.ts +38 -0
  20. package/dist/components/account.d.ts.map +1 -0
  21. package/dist/components/account.js +243 -0
  22. package/dist/components/account.js.map +1 -0
  23. package/dist/components/channel.d.ts +78 -0
  24. package/dist/components/channel.d.ts.map +1 -0
  25. package/dist/components/channel.js +475 -0
  26. package/dist/components/channel.js.map +1 -0
  27. package/dist/components/component.d.ts +49 -0
  28. package/dist/components/component.d.ts.map +1 -0
  29. package/dist/components/component.js +194 -0
  30. package/dist/components/component.js.map +1 -0
  31. package/dist/components/device.d.ts +60 -0
  32. package/dist/components/device.d.ts.map +1 -0
  33. package/dist/components/device.js +648 -0
  34. package/dist/components/device.js.map +1 -0
  35. package/dist/components/group.d.ts +141 -0
  36. package/dist/components/group.d.ts.map +1 -0
  37. package/dist/components/group.js +933 -0
  38. package/dist/components/group.js.map +1 -0
  39. package/dist/components/message.d.ts +63 -0
  40. package/dist/components/message.d.ts.map +1 -0
  41. package/dist/components/message.js +439 -0
  42. package/dist/components/message.js.map +1 -0
  43. package/dist/components/profile.d.ts +37 -0
  44. package/dist/components/profile.d.ts.map +1 -0
  45. package/dist/components/profile.js +232 -0
  46. package/dist/components/profile.js.map +1 -0
  47. package/dist/crypto/groups.d.ts +56 -0
  48. package/dist/crypto/groups.d.ts.map +1 -0
  49. package/dist/crypto/groups.js +163 -0
  50. package/dist/crypto/groups.js.map +1 -0
  51. package/dist/crypto/messages.d.ts +52 -0
  52. package/dist/crypto/messages.d.ts.map +1 -0
  53. package/dist/crypto/messages.js +180 -0
  54. package/dist/crypto/messages.js.map +1 -0
  55. package/dist/crypto/primitives.d.ts +30 -0
  56. package/dist/crypto/primitives.d.ts.map +1 -0
  57. package/dist/crypto/primitives.js +102 -0
  58. package/dist/crypto/primitives.js.map +1 -0
  59. package/dist/errors.d.ts +11 -0
  60. package/dist/errors.d.ts.map +1 -0
  61. package/dist/errors.js +14 -0
  62. package/dist/errors.js.map +1 -0
  63. package/dist/groups/account-sync.d.ts +28 -0
  64. package/dist/groups/account-sync.d.ts.map +1 -0
  65. package/dist/groups/account-sync.js +235 -0
  66. package/dist/groups/account-sync.js.map +1 -0
  67. package/dist/groups/messages.d.ts +39 -0
  68. package/dist/groups/messages.d.ts.map +1 -0
  69. package/dist/groups/messages.js +141 -0
  70. package/dist/groups/messages.js.map +1 -0
  71. package/dist/groups/operations.d.ts +57 -0
  72. package/dist/groups/operations.d.ts.map +1 -0
  73. package/dist/groups/operations.js +225 -0
  74. package/dist/groups/operations.js.map +1 -0
  75. package/dist/groups/repository.d.ts +56 -0
  76. package/dist/groups/repository.d.ts.map +1 -0
  77. package/dist/groups/repository.js +254 -0
  78. package/dist/groups/repository.js.map +1 -0
  79. package/dist/groups/secrets.d.ts +67 -0
  80. package/dist/groups/secrets.d.ts.map +1 -0
  81. package/dist/groups/secrets.js +452 -0
  82. package/dist/groups/secrets.js.map +1 -0
  83. package/dist/groups/state.d.ts +39 -0
  84. package/dist/groups/state.d.ts.map +1 -0
  85. package/dist/groups/state.js +201 -0
  86. package/dist/groups/state.js.map +1 -0
  87. package/dist/identity/identifiers.d.ts +9 -0
  88. package/dist/identity/identifiers.d.ts.map +1 -0
  89. package/dist/identity/identifiers.js +37 -0
  90. package/dist/identity/identifiers.js.map +1 -0
  91. package/dist/identity/neo.d.ts +11 -0
  92. package/dist/identity/neo.d.ts.map +1 -0
  93. package/dist/identity/neo.js +77 -0
  94. package/dist/identity/neo.js.map +1 -0
  95. package/dist/index.d.ts +53 -0
  96. package/dist/index.d.ts.map +1 -0
  97. package/dist/index.js +46 -0
  98. package/dist/index.js.map +1 -0
  99. package/dist/interactions.d.ts +31 -0
  100. package/dist/interactions.d.ts.map +1 -0
  101. package/dist/interactions.js +2 -0
  102. package/dist/interactions.js.map +1 -0
  103. package/dist/messages/account-sender.d.ts +9 -0
  104. package/dist/messages/account-sender.d.ts.map +1 -0
  105. package/dist/messages/account-sender.js +46 -0
  106. package/dist/messages/account-sender.js.map +1 -0
  107. package/dist/messages/contacts.d.ts +103 -0
  108. package/dist/messages/contacts.d.ts.map +1 -0
  109. package/dist/messages/contacts.js +525 -0
  110. package/dist/messages/contacts.js.map +1 -0
  111. package/dist/messages/outbox.d.ts +66 -0
  112. package/dist/messages/outbox.d.ts.map +1 -0
  113. package/dist/messages/outbox.js +167 -0
  114. package/dist/messages/outbox.js.map +1 -0
  115. package/dist/messages/receiver.d.ts +35 -0
  116. package/dist/messages/receiver.d.ts.map +1 -0
  117. package/dist/messages/receiver.js +113 -0
  118. package/dist/messages/receiver.js.map +1 -0
  119. package/dist/messages/repository.d.ts +89 -0
  120. package/dist/messages/repository.d.ts.map +1 -0
  121. package/dist/messages/repository.js +295 -0
  122. package/dist/messages/repository.js.map +1 -0
  123. package/dist/models/authentication.d.ts +37 -0
  124. package/dist/models/authentication.d.ts.map +1 -0
  125. package/dist/models/authentication.js +53 -0
  126. package/dist/models/authentication.js.map +1 -0
  127. package/dist/models/channels.d.ts +141 -0
  128. package/dist/models/channels.d.ts.map +1 -0
  129. package/dist/models/channels.js +258 -0
  130. package/dist/models/channels.js.map +1 -0
  131. package/dist/models/contacts.d.ts +68 -0
  132. package/dist/models/contacts.d.ts.map +1 -0
  133. package/dist/models/contacts.js +186 -0
  134. package/dist/models/contacts.js.map +1 -0
  135. package/dist/models/content.d.ts +37 -0
  136. package/dist/models/content.d.ts.map +1 -0
  137. package/dist/models/content.js +137 -0
  138. package/dist/models/content.js.map +1 -0
  139. package/dist/models/device-state.d.ts +8 -0
  140. package/dist/models/device-state.d.ts.map +1 -0
  141. package/dist/models/device-state.js +22 -0
  142. package/dist/models/device-state.js.map +1 -0
  143. package/dist/models/group-admission.d.ts +111 -0
  144. package/dist/models/group-admission.d.ts.map +1 -0
  145. package/dist/models/group-admission.js +153 -0
  146. package/dist/models/group-admission.js.map +1 -0
  147. package/dist/models/group-keys.d.ts +30 -0
  148. package/dist/models/group-keys.d.ts.map +1 -0
  149. package/dist/models/group-keys.js +52 -0
  150. package/dist/models/group-keys.js.map +1 -0
  151. package/dist/models/group-management.d.ts +190 -0
  152. package/dist/models/group-management.d.ts.map +1 -0
  153. package/dist/models/group-management.js +263 -0
  154. package/dist/models/group-management.js.map +1 -0
  155. package/dist/models/groups.d.ts +81 -0
  156. package/dist/models/groups.d.ts.map +1 -0
  157. package/dist/models/groups.js +127 -0
  158. package/dist/models/groups.js.map +1 -0
  159. package/dist/models/identity.d.ts +47 -0
  160. package/dist/models/identity.d.ts.map +1 -0
  161. package/dist/models/identity.js +136 -0
  162. package/dist/models/identity.js.map +1 -0
  163. package/dist/models/messages.d.ts +104 -0
  164. package/dist/models/messages.d.ts.map +1 -0
  165. package/dist/models/messages.js +168 -0
  166. package/dist/models/messages.js.map +1 -0
  167. package/dist/models/profile.d.ts +24 -0
  168. package/dist/models/profile.d.ts.map +1 -0
  169. package/dist/models/profile.js +47 -0
  170. package/dist/models/profile.js.map +1 -0
  171. package/dist/models/relay.d.ts +39 -0
  172. package/dist/models/relay.d.ts.map +1 -0
  173. package/dist/models/relay.js +160 -0
  174. package/dist/models/relay.js.map +1 -0
  175. package/dist/protocol/codec.d.ts +36 -0
  176. package/dist/protocol/codec.d.ts.map +1 -0
  177. package/dist/protocol/codec.js +144 -0
  178. package/dist/protocol/codec.js.map +1 -0
  179. package/dist/protocol/context.d.ts +14 -0
  180. package/dist/protocol/context.d.ts.map +1 -0
  181. package/dist/protocol/context.js +43 -0
  182. package/dist/protocol/context.js.map +1 -0
  183. package/dist/protocol/encoding.d.ts +15 -0
  184. package/dist/protocol/encoding.d.ts.map +1 -0
  185. package/dist/protocol/encoding.js +133 -0
  186. package/dist/protocol/encoding.js.map +1 -0
  187. package/dist/protocol/json.d.ts +16 -0
  188. package/dist/protocol/json.d.ts.map +1 -0
  189. package/dist/protocol/json.js +172 -0
  190. package/dist/protocol/json.js.map +1 -0
  191. package/dist/protocol/validation.d.ts +4 -0
  192. package/dist/protocol/validation.d.ts.map +1 -0
  193. package/dist/protocol/validation.js +18 -0
  194. package/dist/protocol/validation.js.map +1 -0
  195. package/dist/runtime/abort.d.ts +6 -0
  196. package/dist/runtime/abort.d.ts.map +1 -0
  197. package/dist/runtime/abort.js +32 -0
  198. package/dist/runtime/abort.js.map +1 -0
  199. package/dist/runtime/async-gate.d.ts +6 -0
  200. package/dist/runtime/async-gate.d.ts.map +1 -0
  201. package/dist/runtime/async-gate.js +15 -0
  202. package/dist/runtime/async-gate.js.map +1 -0
  203. package/dist/runtime/async-pulse.d.ts +7 -0
  204. package/dist/runtime/async-pulse.d.ts.map +1 -0
  205. package/dist/runtime/async-pulse.js +23 -0
  206. package/dist/runtime/async-pulse.js.map +1 -0
  207. package/dist/runtime/clock.d.ts +16 -0
  208. package/dist/runtime/clock.d.ts.map +1 -0
  209. package/dist/runtime/clock.js +62 -0
  210. package/dist/runtime/clock.js.map +1 -0
  211. package/dist/runtime/events.d.ts +11 -0
  212. package/dist/runtime/events.d.ts.map +1 -0
  213. package/dist/runtime/events.js +45 -0
  214. package/dist/runtime/events.js.map +1 -0
  215. package/dist/runtime/url.d.ts +3 -0
  216. package/dist/runtime/url.d.ts.map +1 -0
  217. package/dist/runtime/url.js +23 -0
  218. package/dist/runtime/url.js.map +1 -0
  219. package/dist/storage/signed-request.d.ts +14 -0
  220. package/dist/storage/signed-request.d.ts.map +1 -0
  221. package/dist/storage/signed-request.js +26 -0
  222. package/dist/storage/signed-request.js.map +1 -0
  223. package/dist/storage/store.d.ts +58 -0
  224. package/dist/storage/store.d.ts.map +1 -0
  225. package/dist/storage/store.js +52 -0
  226. package/dist/storage/store.js.map +1 -0
  227. package/dist/storage/transaction.d.ts +7 -0
  228. package/dist/storage/transaction.d.ts.map +1 -0
  229. package/dist/storage/transaction.js +22 -0
  230. package/dist/storage/transaction.js.map +1 -0
  231. package/dist/transport/client.d.ts +61 -0
  232. package/dist/transport/client.d.ts.map +1 -0
  233. package/dist/transport/client.js +392 -0
  234. package/dist/transport/client.js.map +1 -0
  235. package/dist/transport/discovery.d.ts +19 -0
  236. package/dist/transport/discovery.d.ts.map +1 -0
  237. package/dist/transport/discovery.js +75 -0
  238. package/dist/transport/discovery.js.map +1 -0
  239. package/dist/transport/endpoint.d.ts +6 -0
  240. package/dist/transport/endpoint.d.ts.map +1 -0
  241. package/dist/transport/endpoint.js +35 -0
  242. package/dist/transport/endpoint.js.map +1 -0
  243. package/dist/transport/http.d.ts +52 -0
  244. package/dist/transport/http.d.ts.map +1 -0
  245. package/dist/transport/http.js +128 -0
  246. package/dist/transport/http.js.map +1 -0
  247. package/dist/transport/pool.d.ts +31 -0
  248. package/dist/transport/pool.d.ts.map +1 -0
  249. package/dist/transport/pool.js +137 -0
  250. package/dist/transport/pool.js.map +1 -0
  251. package/dist/transport/relay-error.d.ts +23 -0
  252. package/dist/transport/relay-error.d.ts.map +1 -0
  253. package/dist/transport/relay-error.js +47 -0
  254. package/dist/transport/relay-error.js.map +1 -0
  255. package/dist/transport/rpc.d.ts +31 -0
  256. package/dist/transport/rpc.d.ts.map +1 -0
  257. package/dist/transport/rpc.js +56 -0
  258. package/dist/transport/rpc.js.map +1 -0
  259. package/dist/transport/session.d.ts +49 -0
  260. package/dist/transport/session.d.ts.map +1 -0
  261. package/dist/transport/session.js +169 -0
  262. package/dist/transport/session.js.map +1 -0
  263. package/dist/transport/socket.d.ts +55 -0
  264. package/dist/transport/socket.d.ts.map +1 -0
  265. package/dist/transport/socket.js +266 -0
  266. package/dist/transport/socket.js.map +1 -0
  267. package/dist/transport/subscription.d.ts +36 -0
  268. package/dist/transport/subscription.d.ts.map +1 -0
  269. package/dist/transport/subscription.js +169 -0
  270. package/dist/transport/subscription.js.map +1 -0
  271. package/package.json +35 -4
  272. package/src/channels/repository.ts +244 -0
  273. package/src/client/account-operations.ts +183 -0
  274. package/src/client/conversations.ts +95 -0
  275. package/src/client.ts +108 -0
  276. package/src/components/account.ts +206 -0
  277. package/src/components/channel.ts +253 -0
  278. package/src/components/component.ts +151 -0
  279. package/src/components/device.ts +455 -0
  280. package/src/components/group.ts +546 -0
  281. package/src/components/message.ts +253 -0
  282. package/src/components/profile.ts +179 -0
  283. package/src/crypto/groups.ts +107 -0
  284. package/src/crypto/messages.ts +129 -0
  285. package/src/crypto/primitives.ts +103 -0
  286. package/src/errors.ts +13 -0
  287. package/src/groups/account-sync.ts +144 -0
  288. package/src/groups/messages.ts +89 -0
  289. package/src/groups/operations.ts +145 -0
  290. package/src/groups/repository.ts +161 -0
  291. package/src/groups/secrets.ts +256 -0
  292. package/src/groups/state.ts +138 -0
  293. package/src/identity/identifiers.ts +41 -0
  294. package/src/identity/neo.ts +72 -0
  295. package/src/index.ts +52 -0
  296. package/src/interactions.ts +35 -0
  297. package/src/messages/account-sender.ts +33 -0
  298. package/src/messages/contacts.ts +379 -0
  299. package/src/messages/outbox.ts +152 -0
  300. package/src/messages/receiver.ts +86 -0
  301. package/src/messages/repository.ts +230 -0
  302. package/src/models/authentication.ts +63 -0
  303. package/src/models/channels.ts +155 -0
  304. package/src/models/contacts.ts +141 -0
  305. package/src/models/content.ts +105 -0
  306. package/src/models/device-state.ts +18 -0
  307. package/src/models/group-admission.ts +110 -0
  308. package/src/models/group-keys.ts +34 -0
  309. package/src/models/group-management.ts +182 -0
  310. package/src/models/groups.ts +100 -0
  311. package/src/models/identity.ts +164 -0
  312. package/src/models/messages.ts +139 -0
  313. package/src/models/profile.ts +46 -0
  314. package/src/models/relay.ts +151 -0
  315. package/src/protocol/codec.ts +144 -0
  316. package/src/protocol/context.ts +39 -0
  317. package/src/protocol/encoding.ts +106 -0
  318. package/src/protocol/json.ts +140 -0
  319. package/src/protocol/validation.ts +13 -0
  320. package/src/runtime/abort.ts +27 -0
  321. package/src/runtime/async-gate.ts +16 -0
  322. package/src/runtime/async-pulse.ts +20 -0
  323. package/src/runtime/clock.ts +63 -0
  324. package/src/runtime/events.ts +31 -0
  325. package/src/runtime/url.ts +17 -0
  326. package/src/storage/signed-request.ts +26 -0
  327. package/src/storage/store.ts +90 -0
  328. package/src/storage/transaction.ts +17 -0
  329. package/src/transport/client.ts +328 -0
  330. package/src/transport/discovery.ts +64 -0
  331. package/src/transport/endpoint.ts +32 -0
  332. package/src/transport/http.ts +129 -0
  333. package/src/transport/pool.ts +115 -0
  334. package/src/transport/relay-error.ts +46 -0
  335. package/src/transport/rpc.ts +56 -0
  336. package/src/transport/session.ts +154 -0
  337. package/src/transport/socket.ts +248 -0
  338. package/src/transport/subscription.ts +117 -0
@@ -0,0 +1,138 @@
1
+ import { groupManagementHash } from '../crypto/groups.js';
2
+ import { ProtocolError } from '../errors.js';
3
+ import { deriveResourceId } from '../identity/identifiers.js';
4
+ import { groupEventCodec, groupManagementPayloadCodec, validateGroupEvent, verifyGroupManagementPayload,
5
+ type GroupEvent, type GroupManagementPayload, type GroupMember, type GroupState } from '../models/group-management.js';
6
+ import { groupMessageEnvelopeCodec, validateGroupRef, verifyGroupMessageEnvelope, type GroupMemberKey, type GroupMessageEnvelope, type GroupRef } from '../models/groups.js';
7
+ import { certificateId, validateCertificate, type DeviceCertificate } from '../models/identity.js';
8
+ import type { NetworkContext } from '../protocol/context.js';
9
+ import { equalBytes } from '../protocol/encoding.js';
10
+ import { requireObject } from '../protocol/json.js';
11
+
12
+ export interface GroupProjectedMember extends GroupMember { readonly joinedAtSequence: number; readonly nickname?: string; readonly nicknameSequence?: number }
13
+ export type GroupDeparture = 'left' | 'removed' | 'banned' | 'not_member';
14
+ /** Local verified state. Relay previews must never be deserialized into this type. */
15
+ export interface GroupProjection extends GroupRef {
16
+ readonly sequence: number; readonly epoch: number; readonly managementHash: string; readonly clientSecretCommitment: string;
17
+ readonly state: GroupState; readonly members: readonly GroupProjectedMember[]; readonly bans: readonly string[]; readonly departures: Readonly<Record<string, GroupDeparture>>;
18
+ }
19
+ export interface VerifiedGroupMessage { readonly envelope: GroupMessageEnvelope; readonly sender: DeviceCertificate; readonly signerDeviceId: string; readonly joinedAtSequence: number }
20
+ export type GroupChangeKind = 'properties' | 'members' | 'roles' | 'bans' | 'nickname' | 'status';
21
+ export const groupChangeKinds: readonly GroupChangeKind[] = ['properties', 'members', 'roles', 'bans', 'nickname', 'status'];
22
+ const managementChanges: Readonly<Record<GroupManagementPayload['kind'], readonly GroupChangeKind[]>> = {
23
+ create: ['properties', 'members', 'roles', 'status'], update: ['properties'], applicationApproval: ['members'], roleUpdate: ['roles'], ownerTransfer: ['roles'],
24
+ memberLeave: ['members'], memberRemoval: ['members'], memberBan: ['members', 'bans'], memberUnban: ['bans'], memberRecoveryApproval: ['members'], secretRotation: ['members'], close: ['status'],
25
+ };
26
+ export interface GroupEventResult { readonly projection: GroupProjection; readonly message?: VerifiedGroupMessage; readonly rejection?: ProtocolError; readonly changeKinds?: readonly GroupChangeKind[] }
27
+
28
+ /** Applies one timeline event without mutating the preceding projection. Invalid administration stops the chain; invalid ordinary messages retain an explicit rejection. */
29
+ export function applyGroupEvent(previous: GroupProjection | undefined, source: GroupEvent, certificate: DeviceCertificate | undefined, group: GroupRef, context: NetworkContext): GroupEventResult {
30
+ validateGroupRef(group); const entry = groupEventCodec.decode(groupEventCodec.encode(source)); validateGroupEvent(entry);
31
+ if (previous && (previous.groupId !== group.groupId || previous.relayId !== group.relayId)) throw new ProtocolError('invalid_binding', 'Verified group state belongs to another group or relay.');
32
+ if (entry.sequence <= (previous?.sequence ?? -1)) throw new ProtocolError('invalid_sequence', 'A group event must advance the verified sequence.');
33
+ if (previous?.state.status === 'closed') throw new ProtocolError('group_closed', 'A closed group cannot append events.');
34
+ if (entry.signerDeviceId !== undefined) {
35
+ if (!certificate) throw new ProtocolError('missing_certificate', 'Group event signing certificate is missing.');
36
+ validateCertificate(certificate, context); if (certificateId(certificate, context) !== entry.signerDeviceId) throw new ProtocolError('invalid_identity', 'Group event signing certificate belongs to another device.');
37
+ }
38
+ if (entry.payload.$type === 'meshline.group.key.rotated') {
39
+ if (!previous || certificate !== undefined || entry.epoch <= previous.epoch) throw new ProtocolError('invalid_epoch', 'Relay key rotation requires an established group and a newer epoch.');
40
+ return { projection: { ...previous, sequence: entry.sequence, epoch: entry.epoch } };
41
+ }
42
+ if (entry.payload.$type === 'meshline.group.message') {
43
+ if (!previous || entry.epoch !== previous.epoch) throw new ProtocolError('invalid_epoch', 'Group message must use the verified current epoch.');
44
+ const projection = { ...previous, sequence: entry.sequence };
45
+ try {
46
+ const member = previous.members.find(value => value.account === certificate!.account);
47
+ if (!member || previous.bans.includes(certificate!.account)) throw new ProtocolError('unauthorized_member', 'Group message sender is not a current unbanned member.');
48
+ const envelope = groupMessageEnvelopeCodec.decode(entry.payload);
49
+ if (envelope.groupId !== group.groupId || envelope.epoch !== entry.epoch) throw new ProtocolError('invalid_binding', 'Group envelope and event have inconsistent group or epoch bindings.');
50
+ verifyGroupMessageEnvelope(envelope, certificate!, entry.signerDeviceId!, context);
51
+ return { projection, message: { envelope, sender: certificate!, signerDeviceId: entry.signerDeviceId!, joinedAtSequence: member.joinedAtSequence } };
52
+ } catch (error) {
53
+ if (!(error instanceof ProtocolError)) throw error;
54
+ return { projection, rejection: error };
55
+ }
56
+ }
57
+ const payload = groupManagementPayloadCodec.decode(entry.payload); verifyGroupManagementPayload(payload, certificate!, context);
58
+ const actor = certificate!.account;
59
+ if (payload.kind === 'create') {
60
+ const value = payload.value;
61
+ if (previous || entry.sequence !== 0 || entry.epoch !== 0 || value.owner.account !== actor || value.groupId !== group.groupId
62
+ || deriveResourceId('group', actor, group.relayId, value.nonce, context) !== group.groupId) throw new ProtocolError('invalid_creation', 'Group creation does not establish the expected owner, group and hosting relay.');
63
+ return { changeKinds: managementChanges.create, projection: { ...group, sequence: 0, epoch: 0, managementHash: groupManagementHash(requireObject(groupManagementPayloadCodec.encode(payload)), context), clientSecretCommitment: value.clientSecretCommitment,
64
+ state: { groupId: group.groupId, name: value.name, ...(value.description === undefined ? {} : { description: value.description }), status: 'active', owner: actor, memberCapacity: value.memberCapacity, memberCount: 1, invitePolicy: value.invitePolicy },
65
+ members: [{ account: actor, role: 'owner', memberEncryptionPublicKey: value.owner.memberEncryptionPublicKey.slice(), joinedAtSequence: 0 }], bans: [], departures: {} } };
66
+ }
67
+ if (!previous || payload.value.groupId !== group.groupId || payload.value.prevHash !== previous.managementHash) throw new ProtocolError('invalid_management_chain', 'Group management predecessor does not match the verified head.');
68
+ const members = new Map<string, GroupProjectedMember>(previous.members.map(value => [value.account, { ...value, memberEncryptionPublicKey: value.memberEncryptionPublicKey.slice() }]));
69
+ const bans = new Set(previous.bans); const departures = { ...previous.departures };
70
+ const author = members.get(actor); if (!author || bans.has(actor)) throw new ProtocolError('unauthorized_member', 'Group administrator is not a current unbanned member.');
71
+ const owner = author.role === 'owner'; const administrator = owner || author.role === 'administrator';
72
+ let state = { ...previous.state }; let commitment = previous.clientSecretCommitment; let advances = false;
73
+ const member = (account: string): GroupProjectedMember => { const value = members.get(account); if (!value) throw new ProtocolError('missing_member', 'Group management target is not a current member.'); return value; };
74
+ const authorize = (allowed: boolean): void => { if (!allowed) throw new ProtocolError('unauthorized_role', 'Group actor is not authorized for this operation.'); };
75
+ const add = (value: GroupMemberKey): void => { members.set(value.account, { account: value.account, role: 'member', memberEncryptionPublicKey: value.memberEncryptionPublicKey.slice(), joinedAtSequence: entry.sequence }); delete departures[value.account]; };
76
+ const remove = (account: string, departure: GroupDeparture): void => { members.delete(account); departures[account] = departure; };
77
+ switch (payload.kind) {
78
+ case 'update': {
79
+ authorize(owner); const value = payload.value;
80
+ if (value.name !== undefined) state = { ...state, name: value.name };
81
+ if (value.description === null) delete state.description; else if (value.description !== undefined) state = { ...state, description: value.description };
82
+ if (value.memberCapacity !== undefined) state = { ...state, memberCapacity: value.memberCapacity };
83
+ if (value.invitePolicy !== undefined) state = { ...state, invitePolicy: value.invitePolicy };
84
+ break;
85
+ }
86
+ case 'applicationApproval':
87
+ authorize(administrator);
88
+ if (members.size + payload.value.members.length > state.memberCapacity) throw new ProtocolError('group_capacity', 'Group admission exceeds the current capacity.');
89
+ for (const value of payload.value.members) { if (members.has(value.account) || bans.has(value.account)) throw new ProtocolError('invalid_admission', 'An existing or banned account cannot be admitted.'); add(value); }
90
+ advances = true; break;
91
+ case 'roleUpdate': {
92
+ authorize(owner); const target = member(payload.value.account); authorize(target.role !== 'owner'); members.set(target.account, { ...target, role: payload.value.role }); break;
93
+ }
94
+ case 'ownerTransfer': {
95
+ authorize(owner); const target = member(payload.value.newOwnerAccount); authorize(target.account !== actor && !bans.has(target.account));
96
+ members.set(actor, { ...author, role: 'member' }); members.set(target.account, { ...target, role: 'owner' }); state = { ...state, owner: target.account }; break;
97
+ }
98
+ case 'memberLeave': authorize(!owner && payload.value.account === actor); remove(actor, 'left'); advances = true; break;
99
+ case 'memberRemoval':
100
+ authorize(administrator);
101
+ for (const account of payload.value.accounts) { const target = member(account); authorize(account !== actor && target.role !== 'owner' && (owner || target.role === 'member')); remove(account, 'removed'); }
102
+ advances = true; break;
103
+ case 'memberBan':
104
+ authorize(administrator);
105
+ for (const account of payload.value.accounts) {
106
+ const target = members.get(account); authorize(account !== actor && (!target || target.role !== 'owner' && (owner || target.role === 'member')));
107
+ if (target) { remove(account, 'banned'); advances = true; } else departures[account] = 'banned';
108
+ bans.add(account);
109
+ }
110
+ break;
111
+ case 'memberUnban':
112
+ authorize(administrator);
113
+ for (const account of payload.value.accounts) { bans.delete(account); if (departures[account] === 'banned') departures[account] = 'not_member'; }
114
+ break;
115
+ case 'memberRecoveryApproval':
116
+ authorize(administrator);
117
+ for (const value of payload.value.members) {
118
+ const target = member(value.account); authorize(owner || target.role === 'member' && target.account !== actor);
119
+ if (equalBytes(target.memberEncryptionPublicKey, value.memberEncryptionPublicKey)) throw new ProtocolError('unchanged_key', 'Recovered member public key must change.');
120
+ members.set(target.account, { ...target, memberEncryptionPublicKey: value.memberEncryptionPublicKey.slice() });
121
+ }
122
+ advances = true; break;
123
+ case 'secretRotation':
124
+ authorize(owner);
125
+ if (payload.value.clientSecretCommitment === commitment) throw new ProtocolError('unchanged_secret', 'Client secret rotation must change its commitment.');
126
+ commitment = payload.value.clientSecretCommitment;
127
+ if (payload.value.ownerEncryptionPublicKey) {
128
+ if (equalBytes(author.memberEncryptionPublicKey, payload.value.ownerEncryptionPublicKey)) throw new ProtocolError('unchanged_key', 'Specified owner public key must change.');
129
+ members.set(actor, { ...author, memberEncryptionPublicKey: payload.value.ownerEncryptionPublicKey.slice() });
130
+ }
131
+ advances = true; break;
132
+ case 'close': authorize(owner); state = { ...state, status: 'closed' }; break;
133
+ }
134
+ if (advances ? entry.epoch <= previous.epoch : entry.epoch !== previous.epoch) throw new ProtocolError('invalid_epoch', 'Group management event has an invalid epoch transition.');
135
+ state = { ...state, memberCount: members.size };
136
+ return { changeKinds: managementChanges[payload.kind], projection: { ...group, sequence: entry.sequence, epoch: entry.epoch, state, members: [...members.values()], bans: [...bans], departures,
137
+ clientSecretCommitment: commitment, managementHash: groupManagementHash(requireObject(groupManagementPayloadCodec.encode(payload)), context) } };
138
+ }
@@ -0,0 +1,41 @@
1
+ import { sha256, requireLength, systemRandom, type RandomSource } from '../crypto/primitives.js';
2
+ import { ProtocolError } from '../errors.js';
3
+ import { NetworkContext, signingInput } from '../protocol/context.js';
4
+ import { decodeBase64Url, encodeBase64Url } from '../protocol/encoding.js';
5
+ import { validateAccountId, validateRelayId } from './neo.js';
6
+
7
+ export type IdentifierKind = 'device' | 'message' | 'channel' | 'group' | 'invite';
8
+ const prefixes: Record<IdentifierKind, string> = { device: 'dev_', message: 'msg_', channel: 'chan_', group: 'grp_', invite: 'inv_' };
9
+
10
+ /** Checks canonical representation only, not existence or authorization. */
11
+ export function validateIdentifier(kind: IdentifierKind, value: string): void {
12
+ const prefix = prefixes[kind];
13
+ if (!value.startsWith(prefix)) throw new ProtocolError('invalid_identifier', `Expected ${prefix} identifier.`);
14
+ decodeBase64Url(value.slice(prefix.length), 16);
15
+ }
16
+
17
+ export function createIdentifier(kind: 'message' | 'invite', random: RandomSource = systemRandom): string {
18
+ const bytes = random.bytes(16);
19
+ requireLength(bytes, 16, 'Identifier randomness');
20
+ return prefixes[kind] + encodeBase64Url(bytes);
21
+ }
22
+
23
+ export function deriveDeviceId(account: string, signingPublicKey: Uint8Array, encryptionPublicKey: Uint8Array, context: NetworkContext): string {
24
+ validateAccountId(account);
25
+ requireLength(signingPublicKey, 32, 'Signing public key');
26
+ requireLength(encryptionPublicKey, 32, 'Encryption public key');
27
+ const input = signingInput({
28
+ $type: 'meshline.device.identity', account,
29
+ signing_public_key: encodeBase64Url(signingPublicKey),
30
+ encryption_public_key: encodeBase64Url(encryptionPublicKey),
31
+ }, context);
32
+ return 'dev_' + encodeBase64Url(sha256(input).subarray(0, 16));
33
+ }
34
+
35
+ export function deriveResourceId(kind: 'channel' | 'group', creator: string, relayId: string, nonce: Uint8Array, context: NetworkContext): string {
36
+ validateAccountId(creator);
37
+ validateRelayId(relayId);
38
+ requireLength(nonce, 16, 'Resource nonce');
39
+ const input = signingInput({ $type: `meshline.${kind}.identity`, creator, relay_id: relayId, nonce: encodeBase64Url(nonce) }, context);
40
+ return prefixes[kind] + encodeBase64Url(sha256(input).subarray(0, 16));
41
+ }
@@ -0,0 +1,72 @@
1
+ import { p256 } from '@noble/curves/nist.js';
2
+ import { hash160, sha256, verifyAccount } from '../crypto/primitives.js';
3
+ import { concatBytes, equalBytes } from '../protocol/encoding.js';
4
+ import { ProtocolError } from '../errors.js';
5
+
6
+ const base58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
7
+
8
+ function scriptHash(publicKey: Uint8Array): Uint8Array {
9
+ if (publicKey.length !== 33 || (publicKey[0] !== 2 && publicKey[0] !== 3))
10
+ throw new ProtocolError('invalid_key', 'Expected a compressed P-256 key.');
11
+ try { p256.Point.fromBytes(publicKey).assertValidity(); }
12
+ catch (cause) { throw new ProtocolError('invalid_key', 'The account key is not a valid P-256 point.', { cause }); }
13
+ return hash160(concatBytes(new Uint8Array([0x0c, 0x21]), publicKey, new Uint8Array([0x41, 0x56, 0xe7, 0xb3, 0x27])));
14
+ }
15
+
16
+ function chainReference(chainId: string): number {
17
+ const match = /^neo:(0|[1-9][0-9]{0,9})$/.exec(chainId);
18
+ if (!match || match[0] !== chainId || Number(match[1]) > 0xffff_ffff)
19
+ throw new ProtocolError('invalid_account', 'Expected a canonical Neo chain identifier.');
20
+ return Number(match[1]);
21
+ }
22
+
23
+ /** Derives a canonical CAIP-10 Neo N3 account identifier. */
24
+ export function getAccountId(chainId: string, publicKey: Uint8Array): string {
25
+ chainReference(chainId);
26
+ const payload = concatBytes(new Uint8Array([0x35]), scriptHash(publicKey));
27
+ const encoded = concatBytes(payload, sha256(sha256(payload)).subarray(0, 4));
28
+ let integer = 0n;
29
+ for (const byte of encoded) integer = (integer << 8n) | BigInt(byte);
30
+ let address = '';
31
+ while (integer > 0n) { address = base58[Number(integer % 58n)]! + address; integer /= 58n; }
32
+ return `${chainId}:${address}`;
33
+ }
34
+
35
+ /** Checks account format and address checksum without normalizing the input. */
36
+ export function validateAccountId(accountId: string): void {
37
+ const split = accountId.lastIndexOf(':');
38
+ chainReference(accountId.slice(0, split));
39
+ const address = accountId.slice(split + 1);
40
+ if (address.length !== 34) throw new ProtocolError('invalid_account', 'A Neo N3 address contains 34 characters.');
41
+ let integer = 0n;
42
+ for (const character of address) {
43
+ const digit = base58.indexOf(character);
44
+ if (digit < 0) throw new ProtocolError('invalid_account', 'Invalid Base58 address character.');
45
+ integer = integer * 58n + BigInt(digit);
46
+ }
47
+ const payload = new Uint8Array(25);
48
+ for (let index = 24; index >= 0; index--) { payload[index] = Number(integer & 255n); integer >>= 8n; }
49
+ if (integer !== 0n || payload[0] !== 0x35 || !equalBytes(payload.subarray(21), sha256(sha256(payload.subarray(0, 21))).subarray(0, 4)))
50
+ throw new ProtocolError('invalid_account', 'Invalid Neo address version, length, or checksum.');
51
+ }
52
+
53
+ /** Verifies that the public key identifies the account, including its chain reference. */
54
+ export function matchesAccount(accountId: string, publicKey: Uint8Array): boolean {
55
+ validateAccountId(accountId);
56
+ return getAccountId(accountId.slice(0, accountId.lastIndexOf(':')), publicKey) === accountId;
57
+ }
58
+
59
+ /** Derives a lowercase display-order Neo script hash for a relay. */
60
+ export function getRelayId(publicKey: Uint8Array): string {
61
+ return '0x' + Array.from(scriptHash(publicKey).reverse(), byte => byte.toString(16).padStart(2, '0')).join('');
62
+ }
63
+
64
+ export function validateRelayId(relayId: string): void {
65
+ if (!/^0x[0-9a-f]{40}$/.test(relayId) || relayId.length !== 42)
66
+ throw new ProtocolError('invalid_relay', 'Expected a canonical lowercase Neo script hash.');
67
+ }
68
+
69
+ export function verifyRelay(relayId: string, publicKey: Uint8Array, input: Uint8Array, signature: Uint8Array): boolean {
70
+ validateRelayId(relayId);
71
+ return verifyAccount(input, signature, publicKey) && getRelayId(publicKey) === relayId;
72
+ }
package/src/index.ts ADDED
@@ -0,0 +1,52 @@
1
+ export * from './errors.js';
2
+ export * from './protocol/encoding.js';
3
+ export * from './protocol/json.js';
4
+ export * from './protocol/context.js';
5
+ export * from './crypto/primitives.js';
6
+ export * from './identity/neo.js';
7
+ export * from './identity/identifiers.js';
8
+ export type { ExtensibleModel, ProtocolCodec } from './protocol/codec.js';
9
+ export * from './models/identity.js';
10
+ export * from './storage/store.js';
11
+ export * from './runtime/async-gate.js';
12
+ export * from './runtime/clock.js';
13
+ export * from './models/authentication.js';
14
+ export * from './transport/endpoint.js';
15
+ export * from './transport/relay-error.js';
16
+ export * from './transport/http.js';
17
+ export * from './interactions.js';
18
+ export * from './transport/session.js';
19
+ export * from './transport/rpc.js';
20
+ export * from './transport/socket.js';
21
+ export * from './models/relay.js';
22
+ export * from './transport/discovery.js';
23
+ export * from './runtime/events.js';
24
+ export * from './transport/client.js';
25
+ export type { RelaySubscription, RelaySubscriptionOptions, SubscriptionMethod } from './transport/subscription.js';
26
+ export * from './transport/pool.js';
27
+ export * from './components/component.js';
28
+ export * from './components/account.js';
29
+ export * from './models/content.js';
30
+ export * from './models/profile.js';
31
+ export * from './components/profile.js';
32
+ export * from './models/device-state.js';
33
+ export * from './components/device.js';
34
+ export * from './models/contacts.js';
35
+ export * from './protocol/validation.js';
36
+ export * from './models/messages.js';
37
+ export * from './models/channels.js';
38
+ export * from './crypto/messages.js';
39
+ export * from './models/groups.js';
40
+ export * from './models/group-management.js';
41
+ export * from './models/group-keys.js';
42
+ export * from './models/group-admission.js';
43
+ export * from './crypto/groups.js';
44
+ export * from './components/message.js';
45
+ export * from './components/channel.js';
46
+ export type { ChannelInfo, ChannelPostInfo, ChannelPostChange } from './channels/repository.js';
47
+ export type { MessageKey, MessageInfo, AccountMessage } from './messages/repository.js';
48
+ export type { MessageSendState, MessageSendStatus } from './messages/outbox.js';
49
+ export type { ContactInfo, ContactGrantState, ContactRequestInfo, ContactRequestDirection, ContactChange, ContactChangeKind, ContactRequestChange, ContactRequestChangeKind } from './messages/contacts.js';
50
+ export * from './components/group.js';
51
+ export type { GroupMessageInfo } from './groups/messages.js';
52
+ export * from './client.js';
@@ -0,0 +1,35 @@
1
+ import type { DeviceCertificate } from './models/identity.js';
2
+ import type { NetworkContext } from './protocol/context.js';
3
+
4
+ /** The application retains its account private key and signs the exact input using Neo P-256/SHA-256. */
5
+ export interface AccountSigner {
6
+ readonly accountId: string;
7
+ readonly publicKey: Uint8Array;
8
+ sign(input: Uint8Array, signal?: AbortSignal): Promise<Uint8Array>;
9
+ }
10
+
11
+ /** Device signatures are Ed25519 over the exact supplied bytes. */
12
+ export interface DeviceSigner {
13
+ readonly certificate: DeviceCertificate;
14
+ sign(input: Uint8Array, signal?: AbortSignal): Promise<Uint8Array>;
15
+ }
16
+
17
+ export interface RelayEntry {
18
+ readonly relayId: string;
19
+ readonly endpoint: string;
20
+ readonly status: 'active' | 'disabled' | 'suspended';
21
+ /** Registry UInt64 timestamp, in Unix milliseconds; this is not a protocol JSON number. */
22
+ readonly updatedAt: bigint;
23
+ }
24
+
25
+ export interface RelayRegistry {
26
+ readonly context: NetworkContext;
27
+ getRelay(relayId: string, signal?: AbortSignal): Promise<RelayEntry | undefined>;
28
+ getRelays(signal?: AbortSignal): AsyncIterable<RelayEntry>;
29
+ }
30
+
31
+ /** Use OS-backed protection in the application adapter; never persist unprotected device secrets. */
32
+ export interface SecretProtector {
33
+ protect(plaintext: Uint8Array, purpose: string, signal?: AbortSignal): Promise<Uint8Array>;
34
+ unprotect(protectedData: Uint8Array, purpose: string, signal?: AbortSignal): Promise<Uint8Array>;
35
+ }
@@ -0,0 +1,33 @@
1
+ import type { MessageManager } from '../components/message.js';
2
+ import { ProtocolError } from '../errors.js';
3
+ import { accountGroupHistorySecretSyncCodec, accountGroupPrivateStateRequestCodec, accountGroupPrivateStateSyncCodec,
4
+ validateAccountGroupHistorySecretSync, validateAccountGroupPrivateStateRequest, validateAccountGroupPrivateStateSync } from '../models/groups.js';
5
+ import type { JsonObject } from '../protocol/json.js';
6
+ import type { MessageEffects } from './repository.js';
7
+
8
+ type AccountSender = (payload: JsonObject, devices: readonly string[] | undefined, signal?: AbortSignal, effects?: MessageEffects) => Promise<void>;
9
+ const senders = new WeakMap<MessageManager, AccountSender>();
10
+
11
+ /** Internal capability: managers can enqueue only recognized self-account group payloads. */
12
+ export function registerAccountSender(manager: MessageManager, sender: AccountSender): void { senders.set(manager, sender); }
13
+ export function sendGroupAccountPayload(manager: MessageManager, source: JsonObject, devices?: readonly string[], signal?: AbortSignal, effects?: MessageEffects): Promise<void> {
14
+ const sender = senders.get(manager); if (!sender) throw new ProtocolError('not_initialized', 'Message manager has no account sender.');
15
+ let payload: JsonObject;
16
+ switch (source['$type']) {
17
+ case 'meshline.account.group.state.request': {
18
+ const value = accountGroupPrivateStateRequestCodec.decode(source); validateAccountGroupPrivateStateRequest(value); payload = accountGroupPrivateStateRequestCodec.encode(value); break;
19
+ }
20
+ case 'meshline.account.group.state.sync': {
21
+ const value = accountGroupPrivateStateSyncCodec.decode(source);
22
+ try { validateAccountGroupPrivateStateSync(value); payload = accountGroupPrivateStateSyncCodec.encode(value); }
23
+ finally { for (const item of value.states) item.memberEncryptionPrivateKey.fill(0); } break;
24
+ }
25
+ case 'meshline.account.group.history_secret.sync': {
26
+ const value = accountGroupHistorySecretSyncCodec.decode(source);
27
+ try { validateAccountGroupHistorySecretSync(value); payload = accountGroupHistorySecretSyncCodec.encode(value); }
28
+ finally { for (const item of value.secrets) item.applicationSecret.fill(0); } break;
29
+ }
30
+ default: throw new ProtocolError('invalid_payload', 'Unsupported group account payload.');
31
+ }
32
+ return sender(payload, devices && [...devices], signal, effects);
33
+ }