@keetanetwork/anchor 0.0.37 → 0.0.39

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 (52) hide show
  1. package/lib/encrypted-container.d.ts +53 -3
  2. package/lib/encrypted-container.d.ts.map +1 -1
  3. package/lib/encrypted-container.js +549 -93
  4. package/lib/encrypted-container.js.map +1 -1
  5. package/lib/http-server/index.d.ts.map +1 -1
  6. package/lib/http-server/index.js +58 -5
  7. package/lib/http-server/index.js.map +1 -1
  8. package/lib/queue/drivers/queue_firestore.d.ts +29 -0
  9. package/lib/queue/drivers/queue_firestore.d.ts.map +1 -0
  10. package/lib/queue/drivers/queue_firestore.js +279 -0
  11. package/lib/queue/drivers/queue_firestore.js.map +1 -0
  12. package/lib/queue/index.d.ts +57 -0
  13. package/lib/queue/index.d.ts.map +1 -1
  14. package/lib/queue/index.js +127 -21
  15. package/lib/queue/index.js.map +1 -1
  16. package/lib/resolver.d.ts +4 -15
  17. package/lib/resolver.d.ts.map +1 -1
  18. package/lib/resolver.js +468 -636
  19. package/lib/resolver.js.map +1 -1
  20. package/lib/utils/signing.d.ts +12 -3
  21. package/lib/utils/signing.d.ts.map +1 -1
  22. package/lib/utils/signing.js +7 -13
  23. package/lib/utils/signing.js.map +1 -1
  24. package/lib/utils/types.d.ts +14 -2
  25. package/lib/utils/types.d.ts.map +1 -1
  26. package/lib/utils/types.js.map +1 -1
  27. package/npm-shrinkwrap.json +7 -7
  28. package/package.json +3 -2
  29. package/services/asset-movement/client.d.ts +2 -2
  30. package/services/asset-movement/client.d.ts.map +1 -1
  31. package/services/asset-movement/client.js +2 -2
  32. package/services/asset-movement/client.js.map +1 -1
  33. package/services/asset-movement/common.d.ts +201 -24
  34. package/services/asset-movement/common.d.ts.map +1 -1
  35. package/services/asset-movement/common.js +305 -80
  36. package/services/asset-movement/common.js.map +1 -1
  37. package/services/fx/client.d.ts +38 -11
  38. package/services/fx/client.d.ts.map +1 -1
  39. package/services/fx/client.js +187 -42
  40. package/services/fx/client.js.map +1 -1
  41. package/services/fx/common.d.ts +55 -6
  42. package/services/fx/common.d.ts.map +1 -1
  43. package/services/fx/common.js +142 -16
  44. package/services/fx/common.js.map +1 -1
  45. package/services/fx/server.d.ts +51 -7
  46. package/services/fx/server.d.ts.map +1 -1
  47. package/services/fx/server.js +333 -109
  48. package/services/fx/server.js.map +1 -1
  49. package/services/fx/util.d.ts +31 -0
  50. package/services/fx/util.d.ts.map +1 -0
  51. package/services/fx/util.js +132 -0
  52. package/services/fx/util.js.map +1 -0
@@ -1,37 +1,275 @@
1
1
  import crypto from './utils/crypto.js';
2
2
  import { lib as KeetaNetLib } from '@keetanetwork/keetanet-client';
3
- import { Buffer, arrayBufferToBuffer, bufferToArrayBuffer } from './utils/buffer.js';
3
+ import { Buffer, arrayBufferToBuffer, arrayBufferLikeToBuffer, bufferToArrayBuffer } from './utils/buffer.js';
4
4
  import { isArray } from './utils/array.js';
5
+ import { KeetaAnchorError } from './error.js';
6
+ // #region Error Handling
7
+ /**
8
+ * Error codes for EncryptedContainer operations
9
+ */
10
+ export const EncryptedContainerErrorCodes = [
11
+ // Parsing/Malformed data
12
+ 'MALFORMED_BASE_FORMAT',
13
+ 'MALFORMED_VERSION',
14
+ 'MALFORMED_DATA_STRUCTURE',
15
+ 'MALFORMED_KEY_INFO',
16
+ 'MALFORMED_SIGNER_INFO',
17
+ // Algorithm issues
18
+ 'UNSUPPORTED_VERSION',
19
+ 'UNSUPPORTED_CIPHER_ALGORITHM',
20
+ 'UNSUPPORTED_DIGEST_ALGORITHM',
21
+ 'UNSUPPORTED_SIGNATURE_ALGORITHM',
22
+ 'UNSUPPORTED_KEY_TYPE',
23
+ // Key/Decryption issues
24
+ 'NO_KEYS_PROVIDED',
25
+ 'NO_MATCHING_KEY',
26
+ 'DECRYPTION_FAILED',
27
+ 'DECOMPRESSION_FAILED',
28
+ // Signing issues
29
+ 'SIGNER_REQUIRES_PRIVATE_KEY',
30
+ 'NOT_SIGNED',
31
+ 'SIGNATURE_VERIFICATION_FAILED',
32
+ // State issues
33
+ 'NO_PLAINTEXT_AVAILABLE',
34
+ 'NO_ENCODED_DATA_AVAILABLE',
35
+ 'PLAINTEXT_DISABLED',
36
+ // Access management
37
+ 'ENCRYPTION_REQUIRED',
38
+ 'INVALID_PRINCIPALS',
39
+ 'ACCESS_MANAGEMENT_NOT_ALLOWED',
40
+ // Internal errors
41
+ 'INTERNAL_ERROR'
42
+ ];
43
+ /**
44
+ * Error class for EncryptedContainer operations
45
+ */
46
+ export class EncryptedContainerError extends KeetaAnchorError {
47
+ static name = 'EncryptedContainerError';
48
+ encryptedContainerErrorObjectTypeID;
49
+ static encryptedContainerErrorObjectTypeID = 'f4a8c2e1-7b3d-4f9a-8c5e-2d1f0a9b8c7e';
50
+ code;
51
+ /**
52
+ * Check if a string is a valid EncryptedContainerErrorCode
53
+ */
54
+ static isValidCode(code) {
55
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
56
+ return (EncryptedContainerErrorCodes.includes(code));
57
+ }
58
+ constructor(code, message) {
59
+ super(message);
60
+ this.code = code;
61
+ // EncryptedContainerError is not a user error
62
+ this.userError = false;
63
+ Object.defineProperty(this, 'encryptedContainerErrorObjectTypeID', {
64
+ value: EncryptedContainerError.encryptedContainerErrorObjectTypeID,
65
+ enumerable: false
66
+ });
67
+ }
68
+ static isInstance(input) {
69
+ return (this.hasPropWithValue(input, 'encryptedContainerErrorObjectTypeID', EncryptedContainerError.encryptedContainerErrorObjectTypeID));
70
+ }
71
+ toJSON() {
72
+ return ({
73
+ ...super.toJSON(),
74
+ code: this.code
75
+ });
76
+ }
77
+ static async fromJSON(input) {
78
+ const { message, other } = this.extractErrorProperties(input, this);
79
+ // Extract and validate code
80
+ if (!('code' in other) || typeof other.code !== 'string') {
81
+ throw (new Error('Invalid EncryptedContainerError JSON: missing code property'));
82
+ }
83
+ // Validate code is a valid EncryptedContainerErrorCode
84
+ const code = other.code;
85
+ if (!this.isValidCode(code)) {
86
+ throw (new Error(`Invalid EncryptedContainerError JSON: unknown code ${code}`));
87
+ }
88
+ const error = new this(code, message);
89
+ error.restoreFromJSON(other);
90
+ return (error);
91
+ }
92
+ }
93
+ // #endregion
5
94
  const zlibDeflateAsync = KeetaNetLib.Utils.Buffer.ZlibDeflateAsync;
6
95
  const zlibInflateAsync = KeetaNetLib.Utils.Buffer.ZlibInflateAsync;
7
96
  const ASN1toJS = KeetaNetLib.Utils.ASN1.ASN1toJS;
8
97
  const JStoASN1 = KeetaNetLib.Utils.ASN1.JStoASN1;
9
98
  const Account = KeetaNetLib.Account;
99
+ /*
100
+ * ASN.1 Schema
101
+ *
102
+ * EncryptedContainer DEFINITIONS ::=
103
+ * BEGIN
104
+ * Version ::= INTEGER { v2(1) }
105
+ *
106
+ * KeyStore ::= SEQUENCE {
107
+ * publicKey OCTET STRING,
108
+ * encryptedSymmetricKey OCTET STRING,
109
+ * ...
110
+ * }
111
+ *
112
+ * EncryptedContainerBox ::= SEQUENCE {
113
+ * keys SEQUENCE OF KeyStore,
114
+ * encryptionAlgorithm OBJECT IDENTIFIER,
115
+ * initializationVector OCTET STRING,
116
+ * encryptedValue OCTET STRING,
117
+ * ...
118
+ * }
119
+ *
120
+ * PlaintextContainerBox ::= SEQUENCE {
121
+ * plainValue OCTET STRING,
122
+ * ...
123
+ * }
124
+ *
125
+ * -- RFC 5652 Section 5.3 SignerInfo (adapted for KeetaNet)
126
+ * SignerInfo ::= SEQUENCE {
127
+ * version CMSVersion, -- INTEGER (3 for subjectKeyIdentifier)
128
+ * sid [0] SubjectKeyIdentifier, -- OCTET STRING (publicKeyAndType)
129
+ * digestAlgorithm OBJECT IDENTIFIER, -- SHA3-256: 2.16.840.1.101.3.4.2.8
130
+ * signatureAlgorithm OBJECT IDENTIFIER, -- Derived from account type
131
+ * signature OCTET STRING,
132
+ * ...
133
+ * }
134
+ *
135
+ * ContainerPackage ::= SEQUENCE {
136
+ * version Version,
137
+ * encryptedContainer [0] EXPLICIT EncryptedContainerBox OPTIONAL,
138
+ * plaintextContainer [1] EXPLICIT PlaintextContainerBox OPTIONAL,
139
+ * signerInfo [2] EXPLICIT SignerInfo OPTIONAL,
140
+ * ...
141
+ * } (WITH COMPONENTS {
142
+ * encryptedContainer PRESENT,
143
+ * plaintextContainer ABSENT
144
+ * } |
145
+ * WITH COMPONENTS {
146
+ * encryptedContainer ABSENT,
147
+ * plaintextContainer PRESENT
148
+ * })
149
+ * END
150
+ *
151
+ */
152
+ /**
153
+ * OID constants for cryptographic algorithms
154
+ * // XXX:TODO: We should standardize this somewhere centralized
155
+ */
10
156
  const oidDB = {
157
+ // Digest algorithms
158
+ 'sha3-256': '2.16.840.1.101.3.4.2.8',
159
+ // Signature algorithms
160
+ 'ed25519': '1.3.101.112',
161
+ 'secp256k1': '1.3.132.0.10',
162
+ 'secp256r1': '1.2.840.10045.3.1.7',
163
+ // Encryption algorithms
11
164
  'aes-256-cbc': '2.16.840.1.101.3.4.1.42'
12
165
  };
166
+ /**
167
+ * Supported algorithms
168
+ * These are the canonical names as defined in oidDB
169
+ */
170
+ const SUPPORTED_DIGEST_ALGORITHMS = ['sha3-256'];
171
+ const SUPPORTED_SIGNATURE_ALGORITHMS = ['ed25519', 'secp256k1', 'secp256r1'];
172
+ /**
173
+ * Known OID aliases that ASN1 parsers may return instead of our canonical names
174
+ */
175
+ const OID_ALIASES = {
176
+ 'prime256v1': 'secp256r1'
177
+ };
178
+ /**
179
+ * Build reverse lookup: name/alias -> numeric OID
180
+ */
181
+ const nameToNumericOID = {};
182
+ for (const [name, numericOID] of Object.entries(oidDB)) {
183
+ nameToNumericOID[name] = numericOID;
184
+ }
185
+ for (const [alias, canonicalName] of Object.entries(OID_ALIASES)) {
186
+ nameToNumericOID[alias] = oidDB[canonicalName];
187
+ }
188
+ /**
189
+ * Normalize an OID (name, alias, or already numeric) to its numeric form.
190
+ * Returns undefined if the OID is not recognized.
191
+ */
192
+ function normalizeToNumericOID(oid) {
193
+ if (/^\d+(\.\d+)*$/.test(oid)) {
194
+ return (oid);
195
+ }
196
+ // Otherwise look up the name/alias
197
+ return (nameToNumericOID[oid]);
198
+ }
199
+ // Pre-compute supported numeric OIDs for validation
200
+ const supportedDigestOIDs = new Set(SUPPORTED_DIGEST_ALGORITHMS.map(name => oidDB[name]));
201
+ const supportedSignatureOIDs = new Set(SUPPORTED_SIGNATURE_ALGORITHMS.map(name => oidDB[name]));
202
+ /**
203
+ * Map account key algorithm to signature algorithm OID
204
+ */
205
+ function getSignatureAlgorithmOID(account) {
206
+ const keyType = account.keyType;
207
+ const KeyAlgo = Account.AccountKeyAlgorithm;
208
+ if (keyType === KeyAlgo.ECDSA_SECP256K1) {
209
+ return (oidDB['secp256k1']);
210
+ }
211
+ else if (keyType === KeyAlgo.ED25519) {
212
+ return (oidDB['ed25519']);
213
+ }
214
+ else if (keyType === KeyAlgo.ECDSA_SECP256R1) {
215
+ return (oidDB['secp256r1']);
216
+ }
217
+ throw (new EncryptedContainerError('UNSUPPORTED_KEY_TYPE', `Unsupported key type for signing: ${keyType}`));
218
+ }
219
+ /**
220
+ * Build a typed encrypted container box with context tag [0].
221
+ */
222
+ function buildEncryptedBox(keys, algorithmOID, iv, encryptedData) {
223
+ return ({
224
+ type: 'context',
225
+ value: 0,
226
+ kind: 'explicit',
227
+ contains: [keys, { type: 'oid', oid: algorithmOID }, iv, encryptedData]
228
+ });
229
+ }
230
+ /**
231
+ * Build a typed plaintext container box with context tag [1].
232
+ */
233
+ function buildPlaintextBox(data) {
234
+ return ({
235
+ type: 'context',
236
+ value: 1,
237
+ kind: 'explicit',
238
+ contains: [data]
239
+ });
240
+ }
241
+ /**
242
+ * Build a typed signer info box with context tag [2].
243
+ */
244
+ function buildSignerInfoBox(signerInfo) {
245
+ return ({
246
+ type: 'context',
247
+ value: 2,
248
+ kind: 'explicit',
249
+ contains: signerInfo
250
+ });
251
+ }
13
252
  /**
14
253
  * Compiles the ASN.1 for the container
15
254
  *
255
+ * @param plaintext The plaintext data to encode
256
+ * @param encryptionOptions Optional encryption options
257
+ * @param signingOptions Optional signing options (will include SignerInfo)
16
258
  * @returns The ASN.1 DER data
17
259
  */
18
- async function buildASN1(plaintext, encryptionOptions) {
19
- const compressedPlaintext = Buffer.from(await zlibDeflateAsync(plaintext));
20
- const sequence = [];
21
- /*
22
- * Version v2 (1)
23
- */
24
- sequence[0] = 1;
260
+ async function buildASN1(plaintext, encryptionOptions, signingOptions) {
261
+ const compressedPlaintext = Buffer.from(await zlibDeflateAsync(bufferToArrayBuffer(plaintext)));
25
262
  /*
26
- * Encrypted container box
263
+ * Build the container box (encrypted or plaintext)
27
264
  */
265
+ let containerBox;
28
266
  if (encryptionOptions) {
29
267
  const { keys, cipherKey, cipherIV, cipherAlgo } = encryptionOptions;
30
268
  if (keys === undefined || keys.length === 0 || cipherKey === undefined || cipherIV === undefined || cipherAlgo === undefined) {
31
- throw (new Error('internal error: Unsupported method invocation'));
269
+ throw (new EncryptedContainerError('INTERNAL_ERROR', 'Unsupported method invocation'));
32
270
  }
33
271
  if (!(cipherAlgo in oidDB)) {
34
- throw (new Error(`Unsupported algorithm: ${cipherAlgo}`));
272
+ throw (new EncryptedContainerError('UNSUPPORTED_CIPHER_ALGORITHM', `Unsupported algorithm: ${cipherAlgo}`));
35
273
  }
36
274
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
37
275
  const algorithmOID = oidDB[cipherAlgo];
@@ -44,68 +282,97 @@ async function buildASN1(plaintext, encryptionOptions) {
44
282
  const encryptionKeysSequence = await Promise.all(keys.map(async function (key) {
45
283
  const encryptedSymmetricKey = Buffer.from(await key.encrypt(cipherKeyArrayBuffer));
46
284
  const retval = [
47
- key.publicKeyAndType,
48
- encryptedSymmetricKey
285
+ Buffer.from(key.publicKeyAndType),
286
+ Buffer.from(encryptedSymmetricKey)
49
287
  ];
50
288
  return (retval);
51
289
  }));
52
- sequence[1] = {
53
- type: 'context',
54
- value: 0,
55
- kind: 'explicit',
56
- contains: [
57
- encryptionKeysSequence,
58
- { type: 'oid', oid: algorithmOID },
59
- cipherIV,
60
- encryptedData
61
- ]
62
- };
290
+ containerBox = buildEncryptedBox(encryptionKeysSequence, algorithmOID, cipherIV, encryptedData);
63
291
  }
64
292
  else {
65
293
  /*
66
294
  * Otherwise we simply pass in the compressed data
67
295
  */
68
- sequence[1] = {
69
- type: 'context',
70
- value: 1,
71
- kind: 'explicit',
72
- contains: [compressedPlaintext]
73
- };
296
+ containerBox = buildPlaintextBox(Buffer.from(compressedPlaintext));
74
297
  }
75
- const outputASN1 = JStoASN1(sequence);
76
- const outputDER = Buffer.from(outputASN1.toBER(false));
298
+ /*
299
+ * Build the typed container package
300
+ */
301
+ const container = [1, containerBox];
302
+ if (signingOptions) {
303
+ /*
304
+ * Sign the compressed plaintext (before encryption) so signature is verifiable after decryption
305
+ */
306
+ const { signer } = signingOptions;
307
+ if (!signer.hasPrivateKey) {
308
+ throw (new EncryptedContainerError('SIGNER_REQUIRES_PRIVATE_KEY', 'Signer account must have a private key'));
309
+ }
310
+ // Hash the compressed plaintext with SHA3-256
311
+ const digestHash = crypto.createHash('sha3-256');
312
+ digestHash.update(compressedPlaintext);
313
+ const digest = digestHash.digest();
314
+ // Sign the digest
315
+ const signatureBuffer = await signer.sign(bufferToArrayBuffer(digest), { raw: true });
316
+ // Get signature as Buffer
317
+ let signature;
318
+ if (Buffer.isBuffer(signatureBuffer)) {
319
+ signature = Buffer.from(signatureBuffer);
320
+ }
321
+ else if ('get' in signatureBuffer && typeof signatureBuffer.get === 'function') {
322
+ signature = arrayBufferToBuffer(signatureBuffer.get());
323
+ }
324
+ else {
325
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
326
+ signature = arrayBufferToBuffer(signatureBuffer);
327
+ }
328
+ const signerInfoASN1 = [
329
+ 3, // CMSVersion 3 for subjectKeyIdentifier
330
+ {
331
+ type: 'context',
332
+ value: 0,
333
+ kind: 'implicit',
334
+ contains: Buffer.from(signer.publicKeyAndType)
335
+ },
336
+ { type: 'oid', oid: oidDB['sha3-256'] },
337
+ { type: 'oid', oid: getSignatureAlgorithmOID(signer) },
338
+ signature
339
+ ];
340
+ container.push(buildSignerInfoBox(signerInfoASN1));
341
+ }
342
+ const outputASN1 = JStoASN1(container);
343
+ const outputDER = arrayBufferToBuffer(outputASN1.toBER(false));
77
344
  return (outputDER);
78
345
  }
79
346
  function parseASN1Bare(input, acceptableEncryptionAlgorithms = ['aes-256-cbc', 'null']) {
80
347
  const inputSequence = ASN1toJS(bufferToArrayBuffer(input));
81
- if (!isArray(inputSequence, 2)) {
82
- throw (new Error('Malformed data detected (incorrect base format)'));
348
+ if (!isArray(inputSequence) || inputSequence.length < 2) {
349
+ throw (new EncryptedContainerError('MALFORMED_BASE_FORMAT', 'Malformed data detected (incorrect base format)'));
83
350
  }
84
351
  const version = inputSequence[0];
85
352
  if (typeof version !== 'bigint') {
86
- throw (new Error('Malformed data detected (version expected at position 0)'));
353
+ throw (new EncryptedContainerError('MALFORMED_VERSION', 'Malformed data detected (version expected at position 0)'));
87
354
  }
88
355
  if (version !== 1n) {
89
- throw (new Error('Malformed data detected (unsupported version)'));
356
+ throw (new EncryptedContainerError('UNSUPPORTED_VERSION', 'Malformed data detected (unsupported version)'));
90
357
  }
91
358
  const valueBox = inputSequence[1];
92
359
  if (typeof valueBox !== 'object' || valueBox === null) {
93
- throw (new Error('Malformed data detected (data expected at position 1)'));
360
+ throw (new EncryptedContainerError('MALFORMED_DATA_STRUCTURE', 'Malformed data detected (data expected at position 1)'));
94
361
  }
95
362
  if (!('type' in valueBox) || typeof valueBox.type !== 'string') {
96
- throw (new Error('Malformed data detected (expected type at position 1)'));
363
+ throw (new EncryptedContainerError('MALFORMED_DATA_STRUCTURE', 'Malformed data detected (expected type at position 1)'));
97
364
  }
98
365
  if (valueBox.type !== 'context') {
99
- throw (new Error('Malformed data detected (expected context at position 1)'));
366
+ throw (new EncryptedContainerError('MALFORMED_DATA_STRUCTURE', 'Malformed data detected (expected context at position 1)'));
100
367
  }
101
368
  if (!('value' in valueBox) || typeof valueBox.value !== 'number') {
102
- throw (new Error('Malformed data detected (expected context value at position 1)'));
369
+ throw (new EncryptedContainerError('MALFORMED_DATA_STRUCTURE', 'Malformed data detected (expected context value at position 1)'));
103
370
  }
104
371
  if (valueBox.value !== 0 && valueBox.value !== 1) {
105
- throw (new Error('Malformed data detected (expected context value of 0 or 1)'));
372
+ throw (new EncryptedContainerError('MALFORMED_DATA_STRUCTURE', 'Malformed data detected (expected context value of 0 or 1)'));
106
373
  }
107
374
  if (!('contains' in valueBox) || typeof valueBox.contains !== 'object' || valueBox.contains === null) {
108
- throw (new Error('Malformed data detected (expected contents at position 1)'));
375
+ throw (new EncryptedContainerError('MALFORMED_DATA_STRUCTURE', 'Malformed data detected (expected contents at position 1)'));
109
376
  }
110
377
  let isEncrypted;
111
378
  if (valueBox.value === 0) {
@@ -119,24 +386,24 @@ function parseASN1Bare(input, acceptableEncryptionAlgorithms = ['aes-256-cbc', '
119
386
  let cipherInfo;
120
387
  if (isEncrypted) {
121
388
  if (!isArray(value, 4)) {
122
- throw (new Error('Malformed data (incorrect number of elements within position 1 -- expected 4)'));
389
+ throw (new EncryptedContainerError('MALFORMED_DATA_STRUCTURE', 'Malformed data (incorrect number of elements within position 1 -- expected 4)'));
123
390
  }
124
391
  const keyInfoUnchecked = value[0];
125
392
  if (!isArray(keyInfoUnchecked)) {
126
- throw (new Error('Malformed data (expected sequence at position 2.0)'));
393
+ throw (new EncryptedContainerError('MALFORMED_DATA_STRUCTURE', 'Malformed data (expected sequence at position 2.0)'));
127
394
  }
128
395
  const keyInfo = keyInfoUnchecked.map(function (checkKeyInfo) {
129
396
  if (!isArray(checkKeyInfo, 2)) {
130
- throw (new Error('Malformed key information (expected sequence of 2 at position 1.0.x)'));
397
+ throw (new EncryptedContainerError('MALFORMED_KEY_INFO', 'Malformed key information (expected sequence of 2 at position 1.0.x)'));
131
398
  }
132
399
  const publicKeyBuffer = checkKeyInfo[0];
133
400
  if (!Buffer.isBuffer(publicKeyBuffer)) {
134
- throw (new Error('Malformed key information (expected octet string for public key at position 1.0.x)'));
401
+ throw (new EncryptedContainerError('MALFORMED_KEY_INFO', 'Malformed key information (expected octet string for public key at position 1.0.x)'));
135
402
  }
136
403
  const publicKey = Account.fromPublicKeyAndType(publicKeyBuffer);
137
404
  const encryptedSymmetricKey = checkKeyInfo[1];
138
405
  if (!Buffer.isBuffer(encryptedSymmetricKey)) {
139
- throw (new Error('Malformed key information (expected octet string for cipher key at position 1.0.x)'));
406
+ throw (new EncryptedContainerError('MALFORMED_KEY_INFO', 'Malformed key information (expected octet string for cipher key at position 1.0.x)'));
140
407
  }
141
408
  return ({
142
409
  publicKey,
@@ -149,11 +416,11 @@ function parseASN1Bare(input, acceptableEncryptionAlgorithms = ['aes-256-cbc', '
149
416
  const encryptionAlgorithm = 'aes-256-cbc';
150
417
  const cipherIV = value[2];
151
418
  if (!Buffer.isBuffer(cipherIV)) {
152
- throw (new Error('Malformed data (cipher IV expected at position 1.2)'));
419
+ throw (new EncryptedContainerError('MALFORMED_DATA_STRUCTURE', 'Malformed data (cipher IV expected at position 1.2)'));
153
420
  }
154
421
  const encryptedCompressedValue = value[3];
155
422
  if (!Buffer.isBuffer(encryptedCompressedValue)) {
156
- throw (new Error('Malformed data (encrypted compressed buffer expected at position 1.3)'));
423
+ throw (new EncryptedContainerError('MALFORMED_DATA_STRUCTURE', 'Malformed data (encrypted compressed buffer expected at position 1.3)'));
157
424
  }
158
425
  cipherInfo = {
159
426
  keys: keyInfo,
@@ -161,25 +428,100 @@ function parseASN1Bare(input, acceptableEncryptionAlgorithms = ['aes-256-cbc', '
161
428
  encryptedData: encryptedCompressedValue,
162
429
  encryptionAlgorithm: encryptionAlgorithm
163
430
  };
164
- containedCompressed = encryptedCompressedValue;
431
+ containedCompressed = Buffer.from(encryptedCompressedValue);
165
432
  }
166
433
  else {
167
434
  if (!isArray(value, 1)) {
168
- throw (new Error('Malformed data (incorrect number of elements within position 1 -- expected 1)'));
435
+ throw (new EncryptedContainerError('MALFORMED_DATA_STRUCTURE', 'Malformed data (incorrect number of elements within position 1 -- expected 1)'));
169
436
  }
170
437
  const containedCompressedUnchecked = value[0];
171
438
  if (!Buffer.isBuffer(containedCompressedUnchecked)) {
172
- throw (new Error('Malformed data (compressed buffer expected at position 1.0)'));
439
+ throw (new EncryptedContainerError('MALFORMED_DATA_STRUCTURE', 'Malformed data (compressed buffer expected at position 1.0)'));
173
440
  }
174
441
  if (!acceptableEncryptionAlgorithms.includes('null')) {
175
- throw (new Error('Malformed data (plaintext found but the null encryption algorithm is not acceptable)'));
442
+ throw (new EncryptedContainerError('ENCRYPTION_REQUIRED', 'Malformed data (plaintext found but the null encryption algorithm is not acceptable)'));
443
+ }
444
+ containedCompressed = Buffer.from(containedCompressedUnchecked);
445
+ }
446
+ // Parse SignerInfo if present
447
+ let signerInfo;
448
+ if (inputSequence.length >= 3) {
449
+ const signerInfoBox = inputSequence[2];
450
+ if (typeof signerInfoBox === 'object' && signerInfoBox !== null &&
451
+ 'type' in signerInfoBox && signerInfoBox.type === 'context' &&
452
+ 'value' in signerInfoBox && signerInfoBox.value === 2 &&
453
+ 'contains' in signerInfoBox && isArray(signerInfoBox.contains, 5)) {
454
+ const signerInfoData = signerInfoBox.contains;
455
+ // Parse version
456
+ const signerVersion = signerInfoData[0];
457
+ if (typeof signerVersion !== 'bigint') {
458
+ throw (new EncryptedContainerError('MALFORMED_SIGNER_INFO', 'Malformed SignerInfo (version expected at position 0)'));
459
+ }
460
+ // Parse sid (subjectKeyIdentifier in context tag [0])
461
+ const sidBox = signerInfoData[1];
462
+ let signerPublicKeyAndType;
463
+ if (typeof sidBox === 'object' && sidBox !== null &&
464
+ 'type' in sidBox && sidBox.type === 'context' &&
465
+ 'value' in sidBox && sidBox.value === 0 &&
466
+ 'contains' in sidBox && (Buffer.isBuffer(sidBox.contains) || sidBox.contains instanceof ArrayBuffer)) {
467
+ signerPublicKeyAndType = arrayBufferLikeToBuffer(sidBox.contains);
468
+ }
469
+ else if (Buffer.isBuffer(sidBox)) {
470
+ // Handle case where ASN.1 parser unwraps the context tag
471
+ signerPublicKeyAndType = Buffer.from(sidBox);
472
+ }
473
+ else {
474
+ throw (new EncryptedContainerError('MALFORMED_SIGNER_INFO', 'Malformed SignerInfo (sid expected at position 1)'));
475
+ }
476
+ // Parse digestAlgorithm
477
+ const digestAlgoRaw = signerInfoData[2];
478
+ let digestAlgorithmOID;
479
+ if (typeof digestAlgoRaw === 'object' && digestAlgoRaw !== null &&
480
+ 'type' in digestAlgoRaw && digestAlgoRaw.type === 'oid' &&
481
+ 'oid' in digestAlgoRaw && typeof digestAlgoRaw.oid === 'string') {
482
+ const normalized = normalizeToNumericOID(digestAlgoRaw.oid);
483
+ if (!normalized) {
484
+ throw (new EncryptedContainerError('MALFORMED_SIGNER_INFO', `Unknown digest algorithm: ${digestAlgoRaw.oid}`));
485
+ }
486
+ digestAlgorithmOID = normalized;
487
+ }
488
+ else {
489
+ throw (new EncryptedContainerError('MALFORMED_SIGNER_INFO', 'Malformed SignerInfo (digestAlgorithm expected at position 2)'));
490
+ }
491
+ // Parse signatureAlgorithm
492
+ const sigAlgoRaw = signerInfoData[3];
493
+ let signatureAlgorithmOID;
494
+ if (typeof sigAlgoRaw === 'object' && sigAlgoRaw !== null &&
495
+ 'type' in sigAlgoRaw && sigAlgoRaw.type === 'oid' &&
496
+ 'oid' in sigAlgoRaw && typeof sigAlgoRaw.oid === 'string') {
497
+ const normalized = normalizeToNumericOID(sigAlgoRaw.oid);
498
+ if (!normalized) {
499
+ throw (new EncryptedContainerError('MALFORMED_SIGNER_INFO', `Unknown signature algorithm: ${sigAlgoRaw.oid}`));
500
+ }
501
+ signatureAlgorithmOID = normalized;
502
+ }
503
+ else {
504
+ throw (new EncryptedContainerError('MALFORMED_SIGNER_INFO', 'Malformed SignerInfo (signatureAlgorithm expected at position 3)'));
505
+ }
506
+ // Parse signature
507
+ const signatureRaw = signerInfoData[4];
508
+ if (!Buffer.isBuffer(signatureRaw)) {
509
+ throw (new EncryptedContainerError('MALFORMED_SIGNER_INFO', 'Malformed SignerInfo (signature expected at position 4)'));
510
+ }
511
+ signerInfo = {
512
+ version: Number(signerVersion),
513
+ signerPublicKeyAndType,
514
+ digestAlgorithmOID,
515
+ signatureAlgorithmOID,
516
+ signature: Buffer.from(signatureRaw)
517
+ };
176
518
  }
177
- containedCompressed = containedCompressedUnchecked;
178
519
  }
179
520
  return ({
180
521
  version: version,
181
522
  isEncrypted: isEncrypted,
182
523
  innerValue: containedCompressed,
524
+ signerInfo,
183
525
  ...cipherInfo
184
526
  });
185
527
  }
@@ -188,15 +530,15 @@ async function parseASN1Decrypt(inputInfo, keys) {
188
530
  let cipherInfo;
189
531
  if (inputInfo.isEncrypted) {
190
532
  if (keys === undefined || keys.length === 0) {
191
- throw (new Error('Encrypted Container found with encryption but no keys for decryption supplied'));
533
+ throw (new EncryptedContainerError('NO_KEYS_PROVIDED', 'Encrypted Container found with encryption but no keys for decryption supplied'));
192
534
  }
193
535
  const algorithm = inputInfo.encryptionAlgorithm;
194
536
  if (algorithm === undefined) {
195
- throw (new Error('Encrypted Container found with encryption but no algorithm supplied'));
537
+ throw (new EncryptedContainerError('MALFORMED_DATA_STRUCTURE', 'Encrypted Container found with encryption but no algorithm supplied'));
196
538
  }
197
539
  const keyInfo = inputInfo.keys;
198
540
  if (keyInfo === undefined) {
199
- throw (new Error('internal error: Encrypted container found with missing keys'));
541
+ throw (new EncryptedContainerError('INTERNAL_ERROR', 'Encrypted container found with missing keys'));
200
542
  }
201
543
  let decryptionKeyInfo;
202
544
  for (const checkKeyInfo of keyInfo) {
@@ -214,19 +556,31 @@ async function parseASN1Decrypt(inputInfo, keys) {
214
556
  }
215
557
  }
216
558
  if (decryptionKeyInfo === undefined) {
217
- throw (new Error('No keys found which can perform decryption on the supplied encryption box'));
559
+ throw (new EncryptedContainerError('NO_MATCHING_KEY', 'No keys found which can perform decryption on the supplied encryption box'));
560
+ }
561
+ let cipherKey;
562
+ try {
563
+ const dataToDecrypt = bufferToArrayBuffer(decryptionKeyInfo.encryptedSymmetricKey);
564
+ cipherKey = arrayBufferLikeToBuffer(await decryptionKeyInfo.privateKey.decrypt(dataToDecrypt));
565
+ }
566
+ catch (err) {
567
+ throw (new EncryptedContainerError('DECRYPTION_FAILED', `Key decryption failed: ${err instanceof Error ? err.message : String(err)}`));
218
568
  }
219
- const cipherKey = Buffer.from(await decryptionKeyInfo.privateKey.decrypt(bufferToArrayBuffer(decryptionKeyInfo.encryptedSymmetricKey)));
220
569
  const cipherIV = inputInfo.cipherIV;
221
570
  if (cipherIV === undefined) {
222
- throw (new Error('internal error: No Cipher IV found'));
571
+ throw (new EncryptedContainerError('INTERNAL_ERROR', 'No Cipher IV found'));
223
572
  }
224
573
  const encryptedCompressedValue = inputInfo.innerValue;
225
574
  const decipher = crypto.createDecipheriv(algorithm, cipherKey, cipherIV);
226
- containedCompressed = Buffer.concat([
227
- decipher.update(encryptedCompressedValue),
228
- decipher.final()
229
- ]);
575
+ try {
576
+ containedCompressed = Buffer.concat([
577
+ decipher.update(encryptedCompressedValue),
578
+ decipher.final()
579
+ ]);
580
+ }
581
+ catch (err) {
582
+ throw (new EncryptedContainerError('DECRYPTION_FAILED', `Cipher decryption failed: ${err instanceof Error ? err.message : String(err)}`));
583
+ }
230
584
  cipherInfo = {
231
585
  isEncrypted: true,
232
586
  keys: keyInfo,
@@ -241,7 +595,14 @@ async function parseASN1Decrypt(inputInfo, keys) {
241
595
  isEncrypted: false
242
596
  };
243
597
  }
244
- const plaintext = Buffer.from(await zlibInflateAsync(containedCompressed));
598
+ let plaintext;
599
+ try {
600
+ const inflated = await zlibInflateAsync(bufferToArrayBuffer(containedCompressed));
601
+ plaintext = arrayBufferLikeToBuffer(inflated);
602
+ }
603
+ catch (err) {
604
+ throw (new EncryptedContainerError('DECOMPRESSION_FAILED', `Inflate failed: ${err instanceof Error ? err.message : String(err)}`));
605
+ }
245
606
  return ({
246
607
  version: inputInfo.version,
247
608
  plaintext: plaintext,
@@ -274,11 +635,15 @@ export class EncryptedContainer {
274
635
  * Encryption details
275
636
  */
276
637
  _internalState;
638
+ /**
639
+ * Signing information
640
+ */
641
+ #signingInfo = {};
277
642
  /**
278
643
  * The plaintext or encoded (and possibly encrypted) data
279
644
  */
280
645
  #data;
281
- constructor(principals) {
646
+ constructor(principals, signer) {
282
647
  if (principals === null) {
283
648
  this._internalState = {
284
649
  principals: null
@@ -292,6 +657,9 @@ export class EncryptedContainer {
292
657
  }
293
658
  ;
294
659
  this.#data = { plaintext: Buffer.alloc(0) };
660
+ if (signer) {
661
+ this.#signingInfo.signer = signer;
662
+ }
295
663
  }
296
664
  get encrypted() {
297
665
  return (this._internalState.principals !== null);
@@ -326,11 +694,22 @@ export class EncryptedContainer {
326
694
  *
327
695
  * @param data The plaintext data to encrypt or encode
328
696
  * @param principals The list of principals who can access the data if it is null then the data is not encrypted
329
- * @param locked If true, the plaintext data will not be accessible from this instance; otherwise it will be -- default depends on principals
697
+ * @param options Options including locked (plaintext accessibility) and signer (for RFC 5652 signing). For backward compatibility, can also be a boolean for locked.
330
698
  * @returns The EncryptedContainer instance with the plaintext data and principals set
331
699
  */
332
- static fromPlaintext(data, principals, locked) {
333
- const retval = new EncryptedContainer(principals);
700
+ static fromPlaintext(data, principals, options) {
701
+ // Handle backward compatibility - if options is a boolean, treat it as the locked flag
702
+ let lockedOpt;
703
+ let signer;
704
+ if (typeof options === 'boolean') {
705
+ lockedOpt = options;
706
+ }
707
+ else if (options !== undefined) {
708
+ lockedOpt = options.locked;
709
+ signer = options.signer;
710
+ }
711
+ const retval = new EncryptedContainer(principals, signer);
712
+ let locked = lockedOpt;
334
713
  if (locked === undefined) {
335
714
  locked = true;
336
715
  if (principals === null) {
@@ -373,16 +752,16 @@ export class EncryptedContainer {
373
752
  */
374
753
  #computeAndSetKeyInfo(mustBeEncrypted) {
375
754
  if (this._encoded === undefined) {
376
- throw (new Error('No encoded data available'));
755
+ throw (new EncryptedContainerError('NO_ENCODED_DATA_AVAILABLE', 'No encoded data available'));
377
756
  }
378
757
  const plaintextWrapper = parseASN1Bare(this._encoded);
379
758
  if (mustBeEncrypted && !plaintextWrapper.isEncrypted) {
380
- throw (new Error('Unable to set key information from plaintext -- it is not encrypted but that was required'));
759
+ throw (new EncryptedContainerError('ENCRYPTION_REQUIRED', 'Unable to set key information from plaintext -- it is not encrypted but that was required'));
381
760
  }
382
761
  if (plaintextWrapper.isEncrypted) {
383
762
  const principals = this._internalState.principals;
384
763
  if (principals === null) {
385
- throw (new Error('May not encrypt data with a null set of principals'));
764
+ throw (new EncryptedContainerError('INVALID_PRINCIPALS', 'May not encrypt data with a null set of principals'));
386
765
  }
387
766
  /*
388
767
  * Compute the new accounts by merging the input from the
@@ -395,7 +774,7 @@ export class EncryptedContainer {
395
774
  const blobPrincipals = (plaintextWrapper.keys ?? []).map((keyInfo) => {
396
775
  const currentPublicKey = keyInfo.publicKey;
397
776
  if (!currentPublicKey.isAccount()) {
398
- throw (new Error('internal error: Non-account found within the encryption key list'));
777
+ throw (new EncryptedContainerError('INTERNAL_ERROR', 'Non-account found within the encryption key list'));
399
778
  }
400
779
  for (const checkExistingKey of principals) {
401
780
  if (checkExistingKey.comparePublicKey(currentPublicKey)) {
@@ -407,15 +786,19 @@ export class EncryptedContainer {
407
786
  this._internalState.principals = blobPrincipals;
408
787
  // Confirm updated principals are populated correctly which sets container to encrypted
409
788
  if (!this.encrypted) {
410
- throw (new Error('internal error: Encrypted data found but not marked as encrypted'));
789
+ throw (new EncryptedContainerError('INTERNAL_ERROR', 'Encrypted data found but not marked as encrypted'));
411
790
  }
412
791
  }
413
792
  else {
414
793
  this._internalState.principals = null;
415
794
  if (this.encrypted) {
416
- throw (new Error('internal error: Plaintext data found but marked as encrypted'));
795
+ throw (new EncryptedContainerError('INTERNAL_ERROR', 'Plaintext data found but marked as encrypted'));
417
796
  }
418
797
  }
798
+ // Store parsed signer info if present
799
+ if (plaintextWrapper.signerInfo) {
800
+ this.#signingInfo.parsedSignerInfo = plaintextWrapper.signerInfo;
801
+ }
419
802
  return (plaintextWrapper);
420
803
  }
421
804
  /**
@@ -427,20 +810,20 @@ export class EncryptedContainer {
427
810
  return (this._plaintext);
428
811
  }
429
812
  if (this._encoded === undefined) {
430
- throw (new Error('No plaintext or encoded data available'));
813
+ throw (new EncryptedContainerError('NO_ENCODED_DATA_AVAILABLE', 'No plaintext or encoded data available'));
431
814
  }
432
815
  const info = this.#computeAndSetKeyInfo(this.encrypted);
433
816
  let principals = this._internalState.principals;
434
817
  if (info.isEncrypted) {
435
818
  if (principals === null) {
436
- throw (new Error('May not decrypt data with a null set of principals'));
819
+ throw (new EncryptedContainerError('INVALID_PRINCIPALS', 'May not decrypt data with a null set of principals'));
437
820
  }
438
821
  }
439
822
  else {
440
823
  principals = [];
441
824
  }
442
825
  const plaintextWrapper = await parseASN1Decrypt(info, principals);
443
- const plaintext = plaintextWrapper.plaintext;
826
+ const plaintext = Buffer.from(plaintextWrapper.plaintext);
444
827
  this.#data = { ...this.#data, plaintext };
445
828
  return (plaintext);
446
829
  }
@@ -449,9 +832,12 @@ export class EncryptedContainer {
449
832
  */
450
833
  async #computePlaintextEncoded() {
451
834
  if (this._plaintext === undefined) {
452
- throw (new Error('No plaintext data available'));
835
+ throw (new EncryptedContainerError('NO_PLAINTEXT_AVAILABLE', 'No plaintext data available'));
453
836
  }
454
- const structuredData = await buildASN1(this._plaintext);
837
+ const signingOptions = this.#signingInfo.signer
838
+ ? { signer: this.#signingInfo.signer }
839
+ : undefined;
840
+ const structuredData = await buildASN1(this._plaintext, undefined, signingOptions);
455
841
  return (structuredData);
456
842
  }
457
843
  /**
@@ -461,20 +847,23 @@ export class EncryptedContainer {
461
847
  */
462
848
  async #computeEncryptedEncoded() {
463
849
  if (this._plaintext === undefined) {
464
- throw (new Error('No encrypted nor plaintext data available'));
850
+ throw (new EncryptedContainerError('NO_PLAINTEXT_AVAILABLE', 'No encrypted nor plaintext data available'));
465
851
  }
466
852
  if (!this.#isEncrypted()) {
467
- throw (new Error('internal error: Asked to encrypt a plaintext buffer'));
853
+ throw (new EncryptedContainerError('INTERNAL_ERROR', 'Asked to encrypt a plaintext buffer'));
468
854
  }
855
+ const signingOptions = this.#signingInfo.signer
856
+ ? { signer: this.#signingInfo.signer }
857
+ : undefined;
469
858
  /**
470
859
  * structured data is the ASN.1 encoded structure
471
860
  */
472
861
  const structuredData = await buildASN1(this._plaintext, {
473
862
  keys: this._internalState.principals,
474
- cipherKey: crypto.randomBytes(32),
475
- cipherIV: crypto.randomBytes(16),
863
+ cipherKey: Buffer.from(crypto.randomBytes(32)),
864
+ cipherIV: Buffer.from(crypto.randomBytes(16)),
476
865
  cipherAlgo: this._internalState.cipherAlgo
477
- });
866
+ }, signingOptions);
478
867
  return (structuredData);
479
868
  }
480
869
  async #computeEncoded() {
@@ -498,10 +887,10 @@ export class EncryptedContainer {
498
887
  */
499
888
  grantAccessSync(accounts) {
500
889
  if (this._plaintext === undefined) {
501
- throw (new Error('Unable to grant access, plaintext not available'));
890
+ throw (new EncryptedContainerError('NO_PLAINTEXT_AVAILABLE', 'Unable to grant access, plaintext not available'));
502
891
  }
503
892
  if (!this.#isEncrypted()) {
504
- throw (new Error('May not manage access to a plaintext container'));
893
+ throw (new EncryptedContainerError('ACCESS_MANAGEMENT_NOT_ALLOWED', 'May not manage access to a plaintext container'));
505
894
  }
506
895
  if (!Array.isArray(accounts)) {
507
896
  accounts = [accounts];
@@ -526,10 +915,10 @@ export class EncryptedContainer {
526
915
  */
527
916
  revokeAccessSync(account) {
528
917
  if (this._plaintext === undefined) {
529
- throw (new Error('Unable to revoke access, plaintext not available'));
918
+ throw (new EncryptedContainerError('NO_PLAINTEXT_AVAILABLE', 'Unable to revoke access, plaintext not available'));
530
919
  }
531
920
  if (!this.#isEncrypted()) {
532
- throw (new Error('May not manage access to a plaintext container'));
921
+ throw (new EncryptedContainerError('ACCESS_MANAGEMENT_NOT_ALLOWED', 'May not manage access to a plaintext container'));
533
922
  }
534
923
  // Encoded data is invalidated with the new permissions so set only the plaintext data
535
924
  this.setPlaintext(this._plaintext);
@@ -558,11 +947,11 @@ export class EncryptedContainer {
558
947
  */
559
948
  async getPlaintext() {
560
949
  if (!this.#mayAccessPlaintext) {
561
- throw (new Error('May not access plaintext'));
950
+ throw (new EncryptedContainerError('PLAINTEXT_DISABLED', 'May not access plaintext'));
562
951
  }
563
952
  const plaintext = await this.#computePlaintext();
564
953
  if (plaintext === undefined) {
565
- throw (new Error('internal error: Plaintext could not be decoded'));
954
+ throw (new EncryptedContainerError('INTERNAL_ERROR', 'Plaintext could not be decoded'));
566
955
  }
567
956
  /*
568
957
  * Make a copy of our internal buffer so that any changes made
@@ -577,7 +966,7 @@ export class EncryptedContainer {
577
966
  async getEncodedBuffer() {
578
967
  const serialized = await this.#computeEncoded();
579
968
  if (serialized === undefined) {
580
- throw (new Error('internal error: Could not encode data'));
969
+ throw (new EncryptedContainerError('INTERNAL_ERROR', 'Could not encode data'));
581
970
  }
582
971
  /*
583
972
  * Make a copy of our internal buffer so that any changes made
@@ -592,10 +981,77 @@ export class EncryptedContainer {
592
981
  */
593
982
  get principals() {
594
983
  if (!this.#isEncrypted()) {
595
- throw (new Error('May not manage access to a plaintext container'));
984
+ throw (new EncryptedContainerError('ACCESS_MANAGEMENT_NOT_ALLOWED', 'May not manage access to a plaintext container'));
596
985
  }
597
986
  return (this._internalState.principals);
598
987
  }
988
+ /**
989
+ * Check if this container is signed
990
+ */
991
+ get isSigned() {
992
+ return (this.#signingInfo.signer !== undefined || this.#signingInfo.parsedSignerInfo !== undefined);
993
+ }
994
+ /**
995
+ * Get the signing account of this container.
996
+ */
997
+ getSigningAccount() {
998
+ // If we have a signer account set (for new containers), return it
999
+ if (this.#signingInfo.signer) {
1000
+ return (this.#signingInfo.signer);
1001
+ }
1002
+ // If we have parsed signer info (from encoded container), construct account from it
1003
+ if (this.#signingInfo.parsedSignerInfo) {
1004
+ const { signerPublicKeyAndType } = this.#signingInfo.parsedSignerInfo;
1005
+ return (Account.fromPublicKeyAndType(signerPublicKeyAndType));
1006
+ }
1007
+ return (undefined);
1008
+ }
1009
+ /**
1010
+ * Verify the signature on this container.
1011
+ * This requires decrypting the container first to access the compressed plaintext.
1012
+ *
1013
+ * @returns true if signature is valid, false if invalid, or throws if not signed or plaintext unavailable
1014
+ */
1015
+ async verifySignature() {
1016
+ // If we have a signer but no parsed info yet, encode first to produce the signed DER
1017
+ if (!this.#signingInfo.parsedSignerInfo && this.#signingInfo.signer) {
1018
+ const encoded = await this.#computeEncoded();
1019
+ if (encoded) {
1020
+ const parsed = parseASN1Bare(encoded);
1021
+ if (parsed.signerInfo) {
1022
+ this.#signingInfo.parsedSignerInfo = parsed.signerInfo;
1023
+ }
1024
+ }
1025
+ }
1026
+ if (!this.#signingInfo.parsedSignerInfo) {
1027
+ throw (new EncryptedContainerError('NOT_SIGNED', 'Container is not signed'));
1028
+ }
1029
+ const signerInfo = this.#signingInfo.parsedSignerInfo;
1030
+ // Validate digest algorithm OID
1031
+ if (!supportedDigestOIDs.has(signerInfo.digestAlgorithmOID)) {
1032
+ throw (new EncryptedContainerError('UNSUPPORTED_DIGEST_ALGORITHM', `Unsupported digest algorithm OID: ${signerInfo.digestAlgorithmOID}`));
1033
+ }
1034
+ // Validate signature algorithm OID
1035
+ if (!supportedSignatureOIDs.has(signerInfo.signatureAlgorithmOID)) {
1036
+ throw (new EncryptedContainerError('UNSUPPORTED_SIGNATURE_ALGORITHM', `Unsupported signature algorithm OID: ${signerInfo.signatureAlgorithmOID}`));
1037
+ }
1038
+ // We need the plaintext to verify the signature
1039
+ const plaintext = await this.#computePlaintext();
1040
+ if (!plaintext) {
1041
+ throw (new EncryptedContainerError('NO_PLAINTEXT_AVAILABLE', 'Unable to compute plaintext for signature verification'));
1042
+ }
1043
+ // Recompute the digest of the compressed plaintext
1044
+ const compressedPlaintext = Buffer.from(await zlibDeflateAsync(bufferToArrayBuffer(plaintext)));
1045
+ const digestHash = crypto.createHash('sha3-256');
1046
+ digestHash.update(compressedPlaintext);
1047
+ const digest = digestHash.digest();
1048
+ // Get the signer's account (public key only)
1049
+ const signerAccount = Account.fromPublicKeyAndType(signerInfo.signerPublicKeyAndType);
1050
+ // Verify the signature
1051
+ const signature = signerInfo.signature;
1052
+ const isValid = signerAccount.verify(bufferToArrayBuffer(digest), bufferToArrayBuffer(signature), { raw: true });
1053
+ return (isValid);
1054
+ }
599
1055
  }
600
1056
  /** @internal */
601
1057
  export const _Testing = {