@digitaldefiance/node-ecies-lib 1.1.21 → 1.1.22

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 (211) hide show
  1. package/README.md +4 -0
  2. package/package.json +14 -35
  3. package/src/constants.ts +182 -0
  4. package/src/enumerations/index.ts +1 -0
  5. package/src/enumerations/pbkdf2-profile.ts +8 -0
  6. package/src/i18n/ecies-i18n-factory.ts +435 -0
  7. package/{dist/i18n/index.d.ts → src/i18n/index.ts} +0 -1
  8. package/{dist/index.d.ts → src/index.ts} +0 -1
  9. package/src/interfaces/authenticated-cipher.ts +9 -0
  10. package/src/interfaces/authenticated-decipher.ts +8 -0
  11. package/src/interfaces/checksum-config.ts +4 -0
  12. package/src/interfaces/checksum-consts.ts +13 -0
  13. package/src/interfaces/constants.ts +43 -0
  14. package/src/interfaces/ecies-consts.ts +99 -0
  15. package/src/interfaces/encryption-consts.ts +10 -0
  16. package/{dist/interfaces/index.d.ts → src/interfaces/index.ts} +0 -1
  17. package/src/interfaces/keypair-buffer-with-un-encrypted-private-key.ts +7 -0
  18. package/src/interfaces/keyring-consts.ts +5 -0
  19. package/src/interfaces/member-operational.ts +52 -0
  20. package/{dist/interfaces/member-with-mnemonic.d.ts → src/interfaces/member-with-mnemonic.ts} +3 -3
  21. package/{dist/interfaces/multi-encrypted-message.d.ts → src/interfaces/multi-encrypted-message.ts} +5 -5
  22. package/src/interfaces/multi-encrypted-parsed-header.ts +24 -0
  23. package/{dist/interfaces/pbkdf-profiles.d.ts → src/interfaces/pbkdf-profiles.ts} +2 -2
  24. package/src/interfaces/pbkdf2-result.ts +5 -0
  25. package/src/interfaces/signing-key-private-key-info.ts +12 -0
  26. package/{dist/interfaces/simple-keypair-buffer.d.ts → src/interfaces/simple-keypair-buffer.ts} +3 -3
  27. package/{dist/interfaces/simple-keypair.d.ts → src/interfaces/simple-keypair.ts} +3 -3
  28. package/src/interfaces/simple-public-key-only-buffer.ts +3 -0
  29. package/src/interfaces/simple-public-key-only.ts +3 -0
  30. package/src/interfaces/single-encrypted-parsed-header.ts +35 -0
  31. package/{dist/interfaces/wallet-seed.d.ts → src/interfaces/wallet-seed.ts} +3 -3
  32. package/src/interfaces/wrapped-key-consts.ts +6 -0
  33. package/src/member.ts +463 -0
  34. package/src/services/aes-gcm.ts +160 -0
  35. package/src/services/ecies/crypto-core.ts +213 -0
  36. package/src/services/ecies/file.ts +174 -0
  37. package/{dist/services/ecies/index.d.ts → src/services/ecies/index.ts} +0 -1
  38. package/src/services/ecies/multi-recipient.ts +583 -0
  39. package/src/services/ecies/service.ts +351 -0
  40. package/src/services/ecies/signature.ts +91 -0
  41. package/src/services/ecies/single-recipient.ts +676 -0
  42. package/src/services/ecies/utilities.ts +111 -0
  43. package/src/services/index.ts +3 -0
  44. package/src/services/pbkdf2.ts +307 -0
  45. package/{dist/types.d.ts → src/types.ts} +26 -9
  46. package/src/utils.ts +104 -0
  47. package/dist/constants.d.ts +0 -32
  48. package/dist/constants.d.ts.map +0 -1
  49. package/dist/constants.js +0 -137
  50. package/dist/constants.js.map +0 -1
  51. package/dist/enumerations/index.d.ts +0 -2
  52. package/dist/enumerations/index.d.ts.map +0 -1
  53. package/dist/enumerations/index.js +0 -18
  54. package/dist/enumerations/index.js.map +0 -1
  55. package/dist/enumerations/pbkdf2-profile.d.ts +0 -9
  56. package/dist/enumerations/pbkdf2-profile.d.ts.map +0 -1
  57. package/dist/enumerations/pbkdf2-profile.js +0 -13
  58. package/dist/enumerations/pbkdf2-profile.js.map +0 -1
  59. package/dist/i18n/ecies-i18n-factory.d.ts +0 -54
  60. package/dist/i18n/ecies-i18n-factory.d.ts.map +0 -1
  61. package/dist/i18n/ecies-i18n-factory.js +0 -333
  62. package/dist/i18n/ecies-i18n-factory.js.map +0 -1
  63. package/dist/i18n/index.d.ts.map +0 -1
  64. package/dist/i18n/index.js +0 -18
  65. package/dist/i18n/index.js.map +0 -1
  66. package/dist/index.d.ts.map +0 -1
  67. package/dist/index.js +0 -24
  68. package/dist/index.js.map +0 -1
  69. package/dist/interfaces/authenticated-cipher.d.ts +0 -10
  70. package/dist/interfaces/authenticated-cipher.d.ts.map +0 -1
  71. package/dist/interfaces/authenticated-cipher.js +0 -3
  72. package/dist/interfaces/authenticated-cipher.js.map +0 -1
  73. package/dist/interfaces/authenticated-decipher.d.ts +0 -9
  74. package/dist/interfaces/authenticated-decipher.d.ts.map +0 -1
  75. package/dist/interfaces/authenticated-decipher.js +0 -3
  76. package/dist/interfaces/authenticated-decipher.js.map +0 -1
  77. package/dist/interfaces/checksum-config.d.ts +0 -5
  78. package/dist/interfaces/checksum-config.d.ts.map +0 -1
  79. package/dist/interfaces/checksum-config.js +0 -3
  80. package/dist/interfaces/checksum-config.js.map +0 -1
  81. package/dist/interfaces/checksum-consts.d.ts +0 -11
  82. package/dist/interfaces/checksum-consts.d.ts.map +0 -1
  83. package/dist/interfaces/checksum-consts.js +0 -3
  84. package/dist/interfaces/checksum-consts.js.map +0 -1
  85. package/dist/interfaces/constants.d.ts +0 -43
  86. package/dist/interfaces/constants.d.ts.map +0 -1
  87. package/dist/interfaces/constants.js +0 -3
  88. package/dist/interfaces/constants.js.map +0 -1
  89. package/dist/interfaces/ecies-consts.d.ts +0 -88
  90. package/dist/interfaces/ecies-consts.d.ts.map +0 -1
  91. package/dist/interfaces/ecies-consts.js +0 -3
  92. package/dist/interfaces/ecies-consts.js.map +0 -1
  93. package/dist/interfaces/encryption-consts.d.ts +0 -11
  94. package/dist/interfaces/encryption-consts.d.ts.map +0 -1
  95. package/dist/interfaces/encryption-consts.js +0 -3
  96. package/dist/interfaces/encryption-consts.js.map +0 -1
  97. package/dist/interfaces/index.d.ts.map +0 -1
  98. package/dist/interfaces/index.js +0 -34
  99. package/dist/interfaces/index.js.map +0 -1
  100. package/dist/interfaces/keypair-buffer-with-un-encrypted-private-key.d.ts +0 -6
  101. package/dist/interfaces/keypair-buffer-with-un-encrypted-private-key.d.ts.map +0 -1
  102. package/dist/interfaces/keypair-buffer-with-un-encrypted-private-key.js +0 -3
  103. package/dist/interfaces/keypair-buffer-with-un-encrypted-private-key.js.map +0 -1
  104. package/dist/interfaces/keyring-consts.d.ts +0 -6
  105. package/dist/interfaces/keyring-consts.d.ts.map +0 -1
  106. package/dist/interfaces/keyring-consts.js +0 -3
  107. package/dist/interfaces/keyring-consts.js.map +0 -1
  108. package/dist/interfaces/member-operational.d.ts +0 -36
  109. package/dist/interfaces/member-operational.d.ts.map +0 -1
  110. package/dist/interfaces/member-operational.js +0 -3
  111. package/dist/interfaces/member-operational.js.map +0 -1
  112. package/dist/interfaces/member-with-mnemonic.d.ts.map +0 -1
  113. package/dist/interfaces/member-with-mnemonic.js +0 -3
  114. package/dist/interfaces/member-with-mnemonic.js.map +0 -1
  115. package/dist/interfaces/multi-encrypted-message.d.ts.map +0 -1
  116. package/dist/interfaces/multi-encrypted-message.js +0 -3
  117. package/dist/interfaces/multi-encrypted-message.js.map +0 -1
  118. package/dist/interfaces/multi-encrypted-parsed-header.d.ts +0 -24
  119. package/dist/interfaces/multi-encrypted-parsed-header.d.ts.map +0 -1
  120. package/dist/interfaces/multi-encrypted-parsed-header.js +0 -3
  121. package/dist/interfaces/multi-encrypted-parsed-header.js.map +0 -1
  122. package/dist/interfaces/pbkdf-profiles.d.ts.map +0 -1
  123. package/dist/interfaces/pbkdf-profiles.js +0 -3
  124. package/dist/interfaces/pbkdf-profiles.js.map +0 -1
  125. package/dist/interfaces/pbkdf2-result.d.ts +0 -6
  126. package/dist/interfaces/pbkdf2-result.d.ts.map +0 -1
  127. package/dist/interfaces/pbkdf2-result.js +0 -3
  128. package/dist/interfaces/pbkdf2-result.js.map +0 -1
  129. package/dist/interfaces/signing-key-private-key-info.d.ts +0 -11
  130. package/dist/interfaces/signing-key-private-key-info.d.ts.map +0 -1
  131. package/dist/interfaces/signing-key-private-key-info.js +0 -3
  132. package/dist/interfaces/signing-key-private-key-info.js.map +0 -1
  133. package/dist/interfaces/simple-keypair-buffer.d.ts.map +0 -1
  134. package/dist/interfaces/simple-keypair-buffer.js +0 -3
  135. package/dist/interfaces/simple-keypair-buffer.js.map +0 -1
  136. package/dist/interfaces/simple-keypair.d.ts.map +0 -1
  137. package/dist/interfaces/simple-keypair.js +0 -3
  138. package/dist/interfaces/simple-keypair.js.map +0 -1
  139. package/dist/interfaces/simple-public-key-only-buffer.d.ts +0 -4
  140. package/dist/interfaces/simple-public-key-only-buffer.d.ts.map +0 -1
  141. package/dist/interfaces/simple-public-key-only-buffer.js +0 -3
  142. package/dist/interfaces/simple-public-key-only-buffer.js.map +0 -1
  143. package/dist/interfaces/simple-public-key-only.d.ts +0 -4
  144. package/dist/interfaces/simple-public-key-only.d.ts.map +0 -1
  145. package/dist/interfaces/simple-public-key-only.js +0 -3
  146. package/dist/interfaces/simple-public-key-only.js.map +0 -1
  147. package/dist/interfaces/single-encrypted-parsed-header.d.ts +0 -35
  148. package/dist/interfaces/single-encrypted-parsed-header.d.ts.map +0 -1
  149. package/dist/interfaces/single-encrypted-parsed-header.js +0 -3
  150. package/dist/interfaces/single-encrypted-parsed-header.js.map +0 -1
  151. package/dist/interfaces/wallet-seed.d.ts.map +0 -1
  152. package/dist/interfaces/wallet-seed.js +0 -3
  153. package/dist/interfaces/wallet-seed.js.map +0 -1
  154. package/dist/interfaces/wrapped-key-consts.d.ts +0 -7
  155. package/dist/interfaces/wrapped-key-consts.d.ts.map +0 -1
  156. package/dist/interfaces/wrapped-key-consts.js +0 -3
  157. package/dist/interfaces/wrapped-key-consts.js.map +0 -1
  158. package/dist/member.d.ts +0 -74
  159. package/dist/member.d.ts.map +0 -1
  160. package/dist/member.js +0 -273
  161. package/dist/member.js.map +0 -1
  162. package/dist/services/aes-gcm.d.ts +0 -66
  163. package/dist/services/aes-gcm.d.ts.map +0 -1
  164. package/dist/services/aes-gcm.js +0 -115
  165. package/dist/services/aes-gcm.js.map +0 -1
  166. package/dist/services/ecies/crypto-core.d.ts +0 -83
  167. package/dist/services/ecies/crypto-core.d.ts.map +0 -1
  168. package/dist/services/ecies/crypto-core.js +0 -166
  169. package/dist/services/ecies/crypto-core.js.map +0 -1
  170. package/dist/services/ecies/file.d.ts +0 -30
  171. package/dist/services/ecies/file.d.ts.map +0 -1
  172. package/dist/services/ecies/file.js +0 -144
  173. package/dist/services/ecies/file.js.map +0 -1
  174. package/dist/services/ecies/index.d.ts.map +0 -1
  175. package/dist/services/ecies/index.js +0 -24
  176. package/dist/services/ecies/index.js.map +0 -1
  177. package/dist/services/ecies/multi-recipient.d.ts +0 -82
  178. package/dist/services/ecies/multi-recipient.d.ts.map +0 -1
  179. package/dist/services/ecies/multi-recipient.js +0 -360
  180. package/dist/services/ecies/multi-recipient.js.map +0 -1
  181. package/dist/services/ecies/service.d.ts +0 -70
  182. package/dist/services/ecies/service.d.ts.map +0 -1
  183. package/dist/services/ecies/service.js +0 -167
  184. package/dist/services/ecies/service.js.map +0 -1
  185. package/dist/services/ecies/signature.d.ts +0 -38
  186. package/dist/services/ecies/signature.d.ts.map +0 -1
  187. package/dist/services/ecies/signature.js +0 -69
  188. package/dist/services/ecies/signature.js.map +0 -1
  189. package/dist/services/ecies/single-recipient.d.ts +0 -85
  190. package/dist/services/ecies/single-recipient.d.ts.map +0 -1
  191. package/dist/services/ecies/single-recipient.js +0 -399
  192. package/dist/services/ecies/single-recipient.js.map +0 -1
  193. package/dist/services/ecies/utilities.d.ts +0 -22
  194. package/dist/services/ecies/utilities.d.ts.map +0 -1
  195. package/dist/services/ecies/utilities.js +0 -75
  196. package/dist/services/ecies/utilities.js.map +0 -1
  197. package/dist/services/index.d.ts +0 -4
  198. package/dist/services/index.d.ts.map +0 -1
  199. package/dist/services/index.js +0 -20
  200. package/dist/services/index.js.map +0 -1
  201. package/dist/services/pbkdf2.d.ts +0 -106
  202. package/dist/services/pbkdf2.d.ts.map +0 -1
  203. package/dist/services/pbkdf2.js +0 -195
  204. package/dist/services/pbkdf2.js.map +0 -1
  205. package/dist/types.d.ts.map +0 -1
  206. package/dist/types.js +0 -3
  207. package/dist/types.js.map +0 -1
  208. package/dist/utils.d.ts +0 -11
  209. package/dist/utils.d.ts.map +0 -1
  210. package/dist/utils.js +0 -82
  211. package/dist/utils.js.map +0 -1
@@ -0,0 +1,583 @@
1
+ import {
2
+ Constants as AppConstants,
3
+ ECIES,
4
+ EciesEncryptionTypeEnum,
5
+ ECIESError,
6
+ ECIESErrorTypeEnum,
7
+ } from '@digitaldefiance/ecies-lib';
8
+ import { PluginI18nEngine, CoreLanguageCode } from '@digitaldefiance/i18n-lib';
9
+ import { EciesStringKey } from '@digitaldefiance/ecies-lib';
10
+ import {
11
+ createCipheriv,
12
+ createDecipheriv,
13
+ createECDH,
14
+ randomBytes,
15
+ } from 'crypto';
16
+ import { ObjectId } from 'mongodb';
17
+ import { Types } from 'mongoose';
18
+ import { Constants } from '../../constants';
19
+ import { createEciesTranslationEngine } from '../../i18n/ecies-i18n-factory';
20
+ import { AuthenticatedCipher } from '../../interfaces/authenticated-cipher';
21
+ import { IMultiEncryptedMessage } from '../../interfaces/multi-encrypted-message';
22
+ import { IMultiEncryptedParsedHeader } from '../../interfaces/multi-encrypted-parsed-header';
23
+ import { Member } from '../../member';
24
+ import { EciesCryptoCore } from './crypto-core';
25
+ import { EciesSingleRecipientCore } from './single-recipient';
26
+
27
+ /**
28
+ * Multiple recipient encryption/decryption functions for ECIES
29
+ */
30
+ export class EciesMultiRecipient {
31
+ protected readonly cryptoCore: EciesCryptoCore;
32
+ protected readonly singleRecipientCore: EciesSingleRecipientCore;
33
+ protected readonly engine: PluginI18nEngine<CoreLanguageCode>;
34
+
35
+ constructor(
36
+ cryptoCore: EciesCryptoCore,
37
+ engine?: PluginI18nEngine<CoreLanguageCode>,
38
+ ) {
39
+ this.cryptoCore = cryptoCore;
40
+ this.engine = engine || createEciesTranslationEngine();
41
+ this.singleRecipientCore = new EciesSingleRecipientCore(
42
+ cryptoCore.config,
43
+ this.engine,
44
+ );
45
+ }
46
+
47
+ /**
48
+ * Get the size of the header for a given encryption type
49
+ * @param encryptionType The encryption type (single, simple, etc.)
50
+ * @param options Optional encryption options
51
+ * @param options.recipientCount The number of recipients
52
+ * @returns
53
+ */
54
+ public getHeaderSize(recipientCount: number): number {
55
+ return (
56
+ this.cryptoCore.consts.MULTIPLE.FIXED_OVERHEAD_SIZE +
57
+ recipientCount * this.cryptoCore.consts.MULTIPLE.ENCRYPTED_KEY_SIZE
58
+ );
59
+ }
60
+
61
+ /**
62
+ * Encrypt a message symmetric key with a public key
63
+ * @param receiverPublicKey The public key of the receiver
64
+ * @param messageSymmetricKey The message to encrypt
65
+ * @returns The encrypted message
66
+ */
67
+ public encryptKey(
68
+ receiverPublicKey: Buffer,
69
+ messageSymmetricKey: Buffer,
70
+ ): Buffer {
71
+ // Generate ephemeral ECDH key pair
72
+ const ecdh = createECDH(this.cryptoCore.config.curveName);
73
+ ecdh.generateKeys();
74
+
75
+ // Compute shared secret
76
+ let sharedSecret: Buffer;
77
+ try {
78
+ // Make sure we normalize the receiver's public key
79
+ const normalizedReceiverPublicKey =
80
+ this.cryptoCore.normalizePublicKey(receiverPublicKey);
81
+
82
+ // Ensure we're using the properly formatted public key (with 0x04 prefix)
83
+ // Our debugging shows only the full format with prefix works correctly
84
+ sharedSecret = ecdh.computeSecret(normalizedReceiverPublicKey);
85
+ } catch (error: unknown) {
86
+ console.error('[ERROR][encrypt] Failed to compute shared secret:', error);
87
+ if (error instanceof Error) {
88
+ if (
89
+ 'code' in error &&
90
+ error.code === 'ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY'
91
+ ) {
92
+ throw new ECIESError(
93
+ ECIESErrorTypeEnum.InvalidRecipientPublicKey,
94
+ this.engine,
95
+ undefined,
96
+ undefined,
97
+ {
98
+ nodeError: error.code,
99
+ },
100
+ );
101
+ }
102
+ throw new ECIESError(
103
+ ECIESErrorTypeEnum.SecretComputationFailed,
104
+ this.engine,
105
+ undefined,
106
+ undefined,
107
+ {
108
+ error: error.message,
109
+ },
110
+ );
111
+ }
112
+ throw new ECIESError(
113
+ ECIESErrorTypeEnum.SecretComputationFailed,
114
+ this.engine,
115
+ );
116
+ }
117
+
118
+ // Get the ephemeral public key and ensure it has the 0x04 prefix
119
+ let ephemeralPublicKey = ecdh.getPublicKey();
120
+ if (ephemeralPublicKey.length === this.cryptoCore.consts.RAW_PUBLIC_KEY_LENGTH) {
121
+ ephemeralPublicKey = Buffer.concat([
122
+ Buffer.from([this.cryptoCore.consts.PUBLIC_KEY_MAGIC]),
123
+ ephemeralPublicKey,
124
+ ]);
125
+ }
126
+
127
+ // Get the key from the shared secret (always use first 32 bytes)
128
+ const symKey = sharedSecret.subarray(0, this.cryptoCore.consts.SYMMETRIC.KEY_SIZE);
129
+
130
+ const iv = randomBytes(this.cryptoCore.consts.IV_SIZE);
131
+
132
+ // Create cipher with the derived symmetric key
133
+ const cipher = createCipheriv(
134
+ this.cryptoCore.consts.SYMMETRIC_ALGORITHM_CONFIGURATION,
135
+ symKey,
136
+ iv,
137
+ ) as unknown as AuthenticatedCipher;
138
+
139
+ // Ensure auto padding is enabled
140
+ cipher.setAutoPadding(true);
141
+
142
+ // Encrypt the message
143
+ let encrypted = cipher.update(messageSymmetricKey);
144
+ encrypted = Buffer.concat([encrypted, cipher.final()]);
145
+
146
+ // Get and explicitly set the authentication tag to max tag length for consistency
147
+ const authTag = cipher.getAuthTag();
148
+
149
+ // Format:ephemeralPublicKey (65) | iv (16) | authTag (16) | encryptedData (this.cryptoCore.consts.SYMMETRIC.KEY_SIZE = 32)
150
+ return Buffer.concat([ephemeralPublicKey, iv, authTag, encrypted]);
151
+ }
152
+
153
+ /**
154
+ * Decrypts symmetric key encrypted with ECIES using a header
155
+ * @param privateKey The private key to decrypt the data
156
+ * @param encryptedKey The data to decrypt
157
+ * @returns The decrypted data buffer
158
+ */
159
+ public decryptKey(privateKey: Buffer, encryptedKey: Buffer): Buffer {
160
+ if (encryptedKey.length !== this.cryptoCore.consts.MULTIPLE.ENCRYPTED_KEY_SIZE) {
161
+ throw new ECIESError(
162
+ ECIESErrorTypeEnum.InvalidEncryptedKeyLength,
163
+ this.engine,
164
+ undefined,
165
+ undefined,
166
+ {
167
+ expected: String(this.cryptoCore.consts.MULTIPLE.ENCRYPTED_KEY_SIZE),
168
+ actual: String(encryptedKey.length),
169
+ },
170
+ );
171
+ }
172
+ const ephemeralPublicKey = encryptedKey.subarray(
173
+ 0,
174
+ this.cryptoCore.consts.PUBLIC_KEY_LENGTH,
175
+ );
176
+ const iv = encryptedKey.subarray(
177
+ this.cryptoCore.consts.PUBLIC_KEY_LENGTH,
178
+ this.cryptoCore.consts.PUBLIC_KEY_LENGTH + this.cryptoCore.consts.IV_SIZE,
179
+ );
180
+ const authTag = encryptedKey.subarray(
181
+ this.cryptoCore.consts.PUBLIC_KEY_LENGTH + this.cryptoCore.consts.IV_SIZE,
182
+ this.cryptoCore.consts.PUBLIC_KEY_LENGTH + this.cryptoCore.consts.IV_SIZE + this.cryptoCore.consts.AUTH_TAG_SIZE,
183
+ );
184
+ const encrypted = encryptedKey.subarray(
185
+ this.cryptoCore.consts.PUBLIC_KEY_LENGTH + this.cryptoCore.consts.IV_SIZE + this.cryptoCore.consts.AUTH_TAG_SIZE,
186
+ this.cryptoCore.consts.PUBLIC_KEY_LENGTH +
187
+ this.cryptoCore.consts.IV_SIZE +
188
+ this.cryptoCore.consts.AUTH_TAG_SIZE +
189
+ this.cryptoCore.consts.SYMMETRIC.KEY_SIZE,
190
+ );
191
+ // Normalize the public key (ensuring 0x04 prefix)
192
+ const normalizedKey =
193
+ this.cryptoCore.normalizePublicKey(ephemeralPublicKey);
194
+
195
+ // Decrypt using components with the normalized key
196
+ const decrypted = this.singleRecipientCore.decryptWithComponents(
197
+ privateKey,
198
+ normalizedKey,
199
+ iv,
200
+ authTag,
201
+ encrypted,
202
+ );
203
+ if (decrypted.length !== this.cryptoCore.consts.SYMMETRIC.KEY_SIZE) {
204
+ throw new ECIESError(
205
+ ECIESErrorTypeEnum.InvalidDataLength,
206
+ this.engine,
207
+ undefined,
208
+ undefined,
209
+ {
210
+ expected: String(this.cryptoCore.consts.SYMMETRIC.KEY_SIZE),
211
+ actual: String(decrypted.length),
212
+ },
213
+ );
214
+ }
215
+ return decrypted;
216
+ }
217
+
218
+ /**
219
+ * Encrypts a message for multiple recipients.
220
+ * @param recipients The recipients to encrypt the message for.
221
+ * @param message The message to encrypt.
222
+ * @param preamble Optional preamble to include in the encrypted message.
223
+ * @returns The encrypted message.
224
+ * @throws EciesError if the number of recipients is greater than 65535.
225
+ */
226
+ public encryptMultiple(
227
+ recipients: Member[],
228
+ message: Buffer,
229
+ preamble?: Buffer,
230
+ ): IMultiEncryptedMessage {
231
+ if (recipients.length > AppConstants.UINT16_MAX) {
232
+ throw new ECIESError(ECIESErrorTypeEnum.TooManyRecipients, this.engine);
233
+ }
234
+
235
+ const messageTypeBuffer = Buffer.alloc(1);
236
+ messageTypeBuffer.writeUint8(EciesEncryptionTypeEnum.Multiple as number);
237
+
238
+ // Generate a random symmetric key
239
+ const symmetricKey = randomBytes(this.cryptoCore.consts.SYMMETRIC.KEY_SIZE);
240
+ const iv = randomBytes(this.cryptoCore.consts.IV_SIZE);
241
+
242
+ // Encrypt the message with the symmetric key
243
+ const cipher = createCipheriv(
244
+ this.cryptoCore.consts.SYMMETRIC_ALGORITHM_CONFIGURATION,
245
+ symmetricKey,
246
+ iv,
247
+ );
248
+
249
+ const encrypted = cipher.update(message);
250
+ const final = cipher.final();
251
+ const authTag = cipher.getAuthTag();
252
+
253
+ const encryptedMessage = Buffer.concat([encrypted, final]);
254
+
255
+ const storedMessage = Buffer.concat([
256
+ preamble ?? Buffer.alloc(0),
257
+ iv,
258
+ authTag,
259
+ encryptedMessage,
260
+ ]);
261
+
262
+ const encryptionResults = recipients.map((member) => ({
263
+ id: member.id,
264
+ encryptedKey: this.encryptKey(member.publicKey, symmetricKey),
265
+ }));
266
+
267
+ const recipientIds = encryptionResults.map(({ id }) => id);
268
+ const recipientKeys = encryptionResults.map(
269
+ ({ encryptedKey }) => encryptedKey,
270
+ );
271
+
272
+ // Verify the encrypted message size (just the encrypted content)
273
+ if (encryptedMessage.length !== message.length) {
274
+ throw new ECIESError(
275
+ ECIESErrorTypeEnum.MessageLengthMismatch,
276
+ this.engine,
277
+ );
278
+ }
279
+
280
+ const headerSize = this.calculateECIESMultipleRecipientOverhead(
281
+ recipients.length,
282
+ false,
283
+ recipientKeys,
284
+ );
285
+
286
+ return {
287
+ dataLength: message.length,
288
+ recipientCount: recipients.length,
289
+ recipientIds,
290
+ recipientKeys,
291
+ encryptedMessage: storedMessage,
292
+ headerSize,
293
+ };
294
+ }
295
+
296
+ /**
297
+ * Decrypts a message encrypted with multiple ECIE for a recipient.
298
+ * @param encryptedData The encrypted data.
299
+ * @param recipient The recipient.
300
+ * @returns The decrypted message.
301
+ */
302
+ public decryptMultipleECIEForRecipient(
303
+ encryptedData: IMultiEncryptedMessage,
304
+ recipient: Member,
305
+ ): Buffer {
306
+ if (recipient.privateKey === undefined) {
307
+ throw new ECIESError(ECIESErrorTypeEnum.PrivateKeyNotLoaded, this.engine);
308
+ }
309
+
310
+ // Find this recipient's encrypted key
311
+ const recipientIndex: number = encryptedData.recipientIds.findIndex(
312
+ (id: Types.ObjectId): boolean => id.equals(recipient.id),
313
+ );
314
+ if (recipientIndex === -1) {
315
+ throw new ECIESError(ECIESErrorTypeEnum.RecipientNotFound, this.engine);
316
+ }
317
+
318
+ const encryptedKey = encryptedData.recipientKeys[recipientIndex];
319
+
320
+ // Decrypt the symmetric key using the detected encryption type
321
+ const symmetricKey = this.decryptKey(
322
+ Buffer.from(recipient.privateKey.value),
323
+ encryptedKey,
324
+ );
325
+
326
+ // Extract the IV and auth tag from the encrypted message
327
+ const iv = encryptedData.encryptedMessage.subarray(0, this.cryptoCore.consts.IV_SIZE);
328
+ const authTag = encryptedData.encryptedMessage.subarray(
329
+ this.cryptoCore.consts.IV_SIZE,
330
+ this.cryptoCore.consts.IV_SIZE + this.cryptoCore.consts.AUTH_TAG_SIZE,
331
+ );
332
+
333
+ // Extract the encrypted content (no CRC, AES-GCM provides authentication)
334
+ const encrypted = encryptedData.encryptedMessage.subarray(
335
+ this.cryptoCore.consts.IV_SIZE + this.cryptoCore.consts.AUTH_TAG_SIZE,
336
+ );
337
+
338
+ // Decrypt the content with the symmetric key
339
+ const decipher = createDecipheriv(
340
+ this.cryptoCore.consts.SYMMETRIC_ALGORITHM_CONFIGURATION,
341
+ symmetricKey,
342
+ iv,
343
+ );
344
+ decipher.setAuthTag(authTag);
345
+
346
+ const decrypted = decipher.update(encrypted);
347
+ const final = decipher.final();
348
+ const decryptedMessage = Buffer.concat([decrypted, final]);
349
+
350
+ // AES-GCM provides authentication via auth tag (no separate CRC needed)
351
+
352
+ // The decrypted message should match the original data length
353
+ if (decryptedMessage.length !== encryptedData.dataLength) {
354
+ throw new ECIESError(ECIESErrorTypeEnum.InvalidDataLength, this.engine);
355
+ }
356
+
357
+ return decryptedMessage;
358
+ }
359
+
360
+ /**
361
+ * Calculate the overhead for a message encrypted for multiple recipients
362
+ * @param recipientCount number of recipients
363
+ * @param includeMessageOverhead whether to include the overhead for the encrypted message
364
+ * @param encryptedKeys optional array of encrypted keys to calculate actual size
365
+ * @returns the overhead size in bytes
366
+ */
367
+ public calculateECIESMultipleRecipientOverhead(
368
+ recipientCount: number,
369
+ includeMessageOverhead: boolean,
370
+ encryptedKeys?: Buffer[],
371
+ ): number {
372
+ if (recipientCount < 2) {
373
+ throw new ECIESError(
374
+ ECIESErrorTypeEnum.InvalidRecipientCount,
375
+ this.engine,
376
+ );
377
+ }
378
+
379
+ // Calculate encrypted keys size
380
+ let encryptedKeysSize: number;
381
+ if (encryptedKeys) {
382
+ encryptedKeysSize = encryptedKeys.reduce(
383
+ (total, key) => total + key.length,
384
+ 0,
385
+ );
386
+ } else {
387
+ // Default assumption: all keys use Simple encryption type (more efficient)
388
+ encryptedKeysSize = recipientCount * this.cryptoCore.consts.MULTIPLE.ENCRYPTED_KEY_SIZE;
389
+ }
390
+
391
+ const baseOverhead =
392
+ this.cryptoCore.consts.MULTIPLE.DATA_LENGTH_SIZE +
393
+ this.cryptoCore.consts.MULTIPLE.RECIPIENT_COUNT_SIZE +
394
+ recipientCount * Constants.OBJECT_ID_LENGTH + // recipient ids
395
+ encryptedKeysSize; // actual encrypted keys size
396
+
397
+ return includeMessageOverhead
398
+ ? baseOverhead + this.cryptoCore.consts.MULTIPLE.FIXED_OVERHEAD_SIZE
399
+ : baseOverhead;
400
+ }
401
+
402
+ /**
403
+ * Builds the header for a message encrypted for multiple recipients
404
+ * @param data The encrypted message data including recipients and encrypted keys
405
+ * @returns The header buffer for the message
406
+ * @throws EciesError if the number of recipients is greater than the maximum allowed
407
+ * @throws EciesError if the number of encrypted keys does not match the number of recipients
408
+ */
409
+ public buildECIESMultipleRecipientHeader(
410
+ data: IMultiEncryptedMessage,
411
+ ): Buffer {
412
+ if (data.recipientIds.length > this.cryptoCore.consts.MULTIPLE.MAX_RECIPIENTS) {
413
+ throw new ECIESError(ECIESErrorTypeEnum.TooManyRecipients, this.engine);
414
+ } else if (data.recipientIds.length !== data.recipientKeys.length) {
415
+ throw new ECIESError(
416
+ ECIESErrorTypeEnum.RecipientKeyCountMismatch,
417
+ this.engine,
418
+ );
419
+ } else if (
420
+ data.dataLength < 0 ||
421
+ data.dataLength > this.cryptoCore.consts.MAX_RAW_DATA_SIZE
422
+ ) {
423
+ throw new ECIESError(ECIESErrorTypeEnum.FileSizeTooLarge, this.engine);
424
+ }
425
+
426
+ // Create data length buffer
427
+ const dataLengthBuffer = Buffer.alloc(this.cryptoCore.consts.MULTIPLE.DATA_LENGTH_SIZE);
428
+ dataLengthBuffer.writeBigUInt64BE(BigInt(data.dataLength));
429
+
430
+ // Create recipient count buffer
431
+ const recipientCountBuffer = Buffer.alloc(
432
+ this.cryptoCore.consts.MULTIPLE.RECIPIENT_COUNT_SIZE,
433
+ );
434
+ recipientCountBuffer.writeUInt16BE(data.recipientIds.length);
435
+
436
+ // Create recipients buffer
437
+ const recipientsBuffer = Buffer.alloc(
438
+ data.recipientIds.length * Constants.OBJECT_ID_LENGTH,
439
+ );
440
+ data.recipientIds.forEach((recipientId: Types.ObjectId, index: number) => {
441
+ recipientsBuffer.set(
442
+ Buffer.from(recipientId.toHexString(), 'hex'),
443
+ index * Constants.OBJECT_ID_LENGTH,
444
+ );
445
+ });
446
+
447
+ // Validate encrypted key lengths based on their encryption type
448
+ data.recipientKeys.forEach((encryptedKey: Buffer) => {
449
+ if (encryptedKey.length === 0) {
450
+ throw new ECIESError(
451
+ ECIESErrorTypeEnum.InvalidEncryptedKeyLength,
452
+ this.engine,
453
+ );
454
+ }
455
+
456
+ if (encryptedKey.length !== this.cryptoCore.consts.MULTIPLE.ENCRYPTED_KEY_SIZE) {
457
+ throw new ECIESError(
458
+ ECIESErrorTypeEnum.InvalidEncryptedKeyLength,
459
+ this.engine,
460
+ undefined,
461
+ undefined,
462
+ {
463
+ expected: String(this.cryptoCore.consts.MULTIPLE.ENCRYPTED_KEY_SIZE),
464
+ actual: String(encryptedKey.length),
465
+ },
466
+ );
467
+ }
468
+ });
469
+
470
+ // Create encrypted keys buffer with variable-length keys
471
+ const encryptedKeysBuffer = Buffer.concat(data.recipientKeys);
472
+
473
+ // Combine all buffers to form the header
474
+ return Buffer.concat([
475
+ dataLengthBuffer,
476
+ recipientCountBuffer,
477
+ recipientsBuffer,
478
+ encryptedKeysBuffer,
479
+ ]);
480
+ }
481
+
482
+ /**
483
+ * Parses a multi-encrypted header.
484
+ * @param data - The data to parse.
485
+ * @returns The parsed header.
486
+ */
487
+ public parseMultiEncryptedHeader(data: Buffer): IMultiEncryptedParsedHeader {
488
+ // Ensure there's enough data to read headers
489
+ if (data.length < this.cryptoCore.consts.MULTIPLE.FIXED_OVERHEAD_SIZE) {
490
+ throw new ECIESError(ECIESErrorTypeEnum.InvalidDataLength, this.engine);
491
+ }
492
+
493
+ let offset = 0;
494
+
495
+ // Read data length
496
+ const dataLength = Number(data.readBigUInt64BE(offset));
497
+ if (dataLength <= 0 || dataLength > this.cryptoCore.consts.MAX_RAW_DATA_SIZE) {
498
+ throw new ECIESError(ECIESErrorTypeEnum.InvalidDataLength, this.engine);
499
+ }
500
+ offset += this.cryptoCore.consts.MULTIPLE.DATA_LENGTH_SIZE; // 8 bytes
501
+
502
+ // Read recipient count
503
+ const recipientCount = data.readUInt16BE(offset);
504
+ if (recipientCount <= 0 || recipientCount > this.cryptoCore.consts.MULTIPLE.MAX_RECIPIENTS) {
505
+ throw new ECIESError(
506
+ ECIESErrorTypeEnum.InvalidRecipientCount,
507
+ this.engine,
508
+ );
509
+ }
510
+ offset += this.cryptoCore.consts.MULTIPLE.RECIPIENT_COUNT_SIZE; // 2 bytes
511
+
512
+ // Ensure there's enough data for all recipients
513
+ const requiredLength = this.calculateECIESMultipleRecipientOverhead(
514
+ recipientCount,
515
+ false,
516
+ );
517
+ if (data.length < requiredLength) {
518
+ throw new ECIESError(ECIESErrorTypeEnum.InvalidDataLength, this.engine);
519
+ }
520
+
521
+ // Read recipient IDs
522
+ const recipientIds: Types.ObjectId[] = [];
523
+ for (let i = 0; i < recipientCount; i++) {
524
+ recipientIds.push(
525
+ new ObjectId(
526
+ data
527
+ .subarray(offset, offset + Constants.OBJECT_ID_LENGTH)
528
+ .toString('hex'),
529
+ ),
530
+ );
531
+ offset += Constants.OBJECT_ID_LENGTH;
532
+ }
533
+
534
+ // Read encrypted keys with variable lengths based on encryption type
535
+ const recipientKeys: Buffer[] = [];
536
+ for (let i = 0; i < recipientCount; i++) {
537
+ if (offset >= data.length) {
538
+ throw new ECIESError(ECIESErrorTypeEnum.InvalidDataLength, this.engine);
539
+ }
540
+
541
+ if (offset + this.cryptoCore.consts.MULTIPLE.ENCRYPTED_KEY_SIZE > data.length) {
542
+ throw new ECIESError(
543
+ ECIESErrorTypeEnum.InvalidDataLength,
544
+ this.engine,
545
+ undefined,
546
+ undefined,
547
+ {
548
+ required: String(this.cryptoCore.consts.MULTIPLE.ENCRYPTED_KEY_SIZE),
549
+ available: String(data.length - offset),
550
+ },
551
+ );
552
+ }
553
+
554
+ recipientKeys.push(
555
+ data.subarray(offset, offset + this.cryptoCore.consts.MULTIPLE.ENCRYPTED_KEY_SIZE),
556
+ );
557
+ offset += this.cryptoCore.consts.MULTIPLE.ENCRYPTED_KEY_SIZE;
558
+ }
559
+
560
+ return {
561
+ dataLength,
562
+ recipientCount,
563
+ recipientIds,
564
+ recipientKeys,
565
+ headerSize: offset,
566
+ };
567
+ }
568
+
569
+ /**
570
+ * Parses a multi-encrypted buffer into its components.
571
+ * @param data - The multi-encrypted buffer to parse.
572
+ * @returns The parsed multi-encrypted buffer.
573
+ */
574
+ public parseMultiEncryptedBuffer(data: Buffer): IMultiEncryptedMessage {
575
+ const header = this.parseMultiEncryptedHeader(data);
576
+ const encryptedMessage = data.subarray(header.headerSize);
577
+
578
+ return {
579
+ ...header,
580
+ encryptedMessage,
581
+ };
582
+ }
583
+ }