@bcts/gstp 1.0.0-alpha.23 → 1.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +130 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +38 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.iife.js +131 -46
- package/dist/index.iife.js.map +1 -1
- package/dist/index.mjs +130 -44
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -7
- package/src/continuation.ts +81 -41
- package/src/sealed-event.ts +9 -6
- package/src/sealed-request.ts +69 -27
- package/src/sealed-response.ts +7 -6
package/src/continuation.ts
CHANGED
|
@@ -9,11 +9,28 @@
|
|
|
9
9
|
* eliminating the need for local state storage and enhancing security
|
|
10
10
|
* for devices with limited storage or requiring distributed state management.
|
|
11
11
|
*
|
|
12
|
-
* Ported from gstp-rust/src/continuation.rs
|
|
12
|
+
* Ported from gstp-rust/src/continuation.rs.
|
|
13
|
+
*
|
|
14
|
+
* Wire shape — mirrors Rust:
|
|
15
|
+
* ```
|
|
16
|
+
* {
|
|
17
|
+
* <state envelope>
|
|
18
|
+
* } [
|
|
19
|
+
* 'id': ARID(...)
|
|
20
|
+
* 'validUntil': Date(...) ← CBOR tag 1, not ISO 8601 text
|
|
21
|
+
* ]
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* The `state` envelope is **wrapped** before assertions are attached,
|
|
25
|
+
* matching Rust `self.state.wrap().add_optional_assertion(...)`. The
|
|
26
|
+
* earlier port attached the assertions directly to the un-wrapped state,
|
|
27
|
+
* producing a different digest tree.
|
|
13
28
|
*/
|
|
14
29
|
|
|
15
30
|
import { ARID, type Encrypter, type PrivateKeys } from "@bcts/components";
|
|
16
31
|
import { Envelope, type EnvelopeEncodableValue } from "@bcts/envelope";
|
|
32
|
+
import { CborDate } from "@bcts/dcbor";
|
|
33
|
+
import type { Cbor } from "@bcts/dcbor";
|
|
17
34
|
import { ID, VALID_UNTIL } from "@bcts/known-values";
|
|
18
35
|
import { GstpError } from "./error";
|
|
19
36
|
|
|
@@ -132,8 +149,10 @@ export class Continuation {
|
|
|
132
149
|
/**
|
|
133
150
|
* Checks if the continuation is valid at the given time.
|
|
134
151
|
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
152
|
+
* Mirrors Rust `is_valid_date(now)`: at the exact `valid_until`
|
|
153
|
+
* instant, the continuation is **expired** (returns `false`). The
|
|
154
|
+
* earlier port used `<=` here, which differed from Rust by one
|
|
155
|
+
* millisecond at the boundary.
|
|
137
156
|
*
|
|
138
157
|
* @param now - The time to check against, or undefined to skip time validation
|
|
139
158
|
* @returns true if the continuation is valid at the given time
|
|
@@ -145,7 +164,8 @@ export class Continuation {
|
|
|
145
164
|
if (now === undefined) {
|
|
146
165
|
return true;
|
|
147
166
|
}
|
|
148
|
-
|
|
167
|
+
// Strict `<` mirrors Rust `valid_until > now`.
|
|
168
|
+
return now.getTime() < this._validUntil.getTime();
|
|
149
169
|
}
|
|
150
170
|
|
|
151
171
|
/**
|
|
@@ -181,25 +201,33 @@ export class Continuation {
|
|
|
181
201
|
/**
|
|
182
202
|
* Converts the continuation to an envelope.
|
|
183
203
|
*
|
|
184
|
-
*
|
|
204
|
+
* Mirrors Rust `Continuation::to_envelope`:
|
|
205
|
+
*
|
|
206
|
+
* ```rust
|
|
207
|
+
* self.state.wrap()
|
|
208
|
+
* .add_optional_assertion(ID, self.valid_id)
|
|
209
|
+
* .add_optional_assertion(VALID_UNTIL, self.valid_until)
|
|
210
|
+
* ```
|
|
211
|
+
*
|
|
212
|
+
* The state is wrapped first; the optional assertions then live on
|
|
213
|
+
* the wrap node. `valid_until` is encoded as a CBOR-tagged Date
|
|
214
|
+
* (tag 1) — never as a plain ISO 8601 string.
|
|
185
215
|
*
|
|
186
216
|
* @param recipient - Optional recipient to encrypt the envelope to
|
|
187
217
|
* @returns The continuation as an envelope
|
|
188
218
|
*/
|
|
189
219
|
toEnvelope(recipient?: Encrypter): Envelope {
|
|
190
|
-
let envelope = this._state;
|
|
220
|
+
let envelope = this._state.wrap();
|
|
191
221
|
|
|
192
|
-
// Add ID assertion if set
|
|
193
222
|
if (this._validId !== undefined) {
|
|
194
223
|
envelope = envelope.addAssertion(ID, this._validId);
|
|
195
224
|
}
|
|
196
225
|
|
|
197
|
-
// Add valid_until assertion if set
|
|
198
226
|
if (this._validUntil !== undefined) {
|
|
199
|
-
|
|
227
|
+
// Pass a tagged-CBOR Date; mirrors Rust `Date → CBOR` (tag 1).
|
|
228
|
+
envelope = envelope.addAssertion(VALID_UNTIL, CborDate.fromDatetime(this._validUntil));
|
|
200
229
|
}
|
|
201
230
|
|
|
202
|
-
// Encrypt to recipient if provided
|
|
203
231
|
if (recipient !== undefined) {
|
|
204
232
|
envelope = envelope.encryptToRecipients([recipient]);
|
|
205
233
|
}
|
|
@@ -210,6 +238,14 @@ export class Continuation {
|
|
|
210
238
|
/**
|
|
211
239
|
* Parses a continuation from an envelope.
|
|
212
240
|
*
|
|
241
|
+
* Mirrors Rust `Continuation::try_from_envelope`:
|
|
242
|
+
*
|
|
243
|
+
* ```rust
|
|
244
|
+
* state: envelope.try_unwrap()?, // unwrap
|
|
245
|
+
* valid_id: envelope.extract_optional_object_for_predicate(ID)?,
|
|
246
|
+
* valid_until: envelope.extract_optional_object_for_predicate(VALID_UNTIL)?,
|
|
247
|
+
* ```
|
|
248
|
+
*
|
|
213
249
|
* @param encryptedEnvelope - The envelope to parse
|
|
214
250
|
* @param expectedId - Optional ID to validate against
|
|
215
251
|
* @param now - Optional time to validate against
|
|
@@ -223,8 +259,15 @@ export class Continuation {
|
|
|
223
259
|
now?: Date,
|
|
224
260
|
recipient?: PrivateKeys,
|
|
225
261
|
): Continuation {
|
|
226
|
-
|
|
227
|
-
|
|
262
|
+
type EnvelopeExt = Envelope & {
|
|
263
|
+
decryptToRecipient(p: PrivateKeys): Envelope;
|
|
264
|
+
tryUnwrap(): Envelope;
|
|
265
|
+
objectForPredicate(p: unknown): Envelope;
|
|
266
|
+
optionalObjectForPredicate(p: unknown): Envelope | undefined;
|
|
267
|
+
asLeaf(): Cbor | undefined;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
let envelope = encryptedEnvelope as EnvelopeExt;
|
|
228
271
|
if (recipient !== undefined) {
|
|
229
272
|
try {
|
|
230
273
|
envelope = encryptedEnvelope.decryptToRecipient(recipient);
|
|
@@ -233,47 +276,49 @@ export class Continuation {
|
|
|
233
276
|
}
|
|
234
277
|
}
|
|
235
278
|
|
|
236
|
-
//
|
|
237
|
-
|
|
279
|
+
// Mirrors Rust `envelope.try_unwrap()?` — peel off the
|
|
280
|
+
// `state.wrap()` introduced in `to_envelope`.
|
|
281
|
+
let state: Envelope;
|
|
282
|
+
try {
|
|
283
|
+
state = envelope.tryUnwrap();
|
|
284
|
+
} catch (e) {
|
|
285
|
+
throw GstpError.envelope(e instanceof Error ? e : new Error(String(e)));
|
|
286
|
+
}
|
|
238
287
|
|
|
239
|
-
// Extract optional ID
|
|
240
288
|
let validId: ARID | undefined;
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
289
|
+
const idObj = envelope.optionalObjectForPredicate(ID) as EnvelopeExt | undefined;
|
|
290
|
+
if (idObj !== undefined) {
|
|
291
|
+
const leafCbor = idObj.asLeaf();
|
|
292
|
+
if (leafCbor !== undefined) {
|
|
293
|
+
try {
|
|
294
|
+
validId = ARID.fromTaggedCbor(leafCbor);
|
|
295
|
+
} catch (e) {
|
|
296
|
+
throw GstpError.envelope(e instanceof Error ? e : new Error(String(e)));
|
|
248
297
|
}
|
|
249
298
|
}
|
|
250
|
-
} catch {
|
|
251
|
-
// ID is optional
|
|
252
299
|
}
|
|
253
300
|
|
|
254
|
-
// Extract optional valid_until
|
|
255
301
|
let validUntil: Date | undefined;
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
302
|
+
const validUntilObj = envelope.optionalObjectForPredicate(VALID_UNTIL) as
|
|
303
|
+
| EnvelopeExt
|
|
304
|
+
| undefined;
|
|
305
|
+
if (validUntilObj !== undefined) {
|
|
306
|
+
const leafCbor = validUntilObj.asLeaf();
|
|
307
|
+
if (leafCbor !== undefined) {
|
|
308
|
+
try {
|
|
309
|
+
validUntil = CborDate.fromTaggedCbor(leafCbor).datetime();
|
|
310
|
+
} catch (e) {
|
|
311
|
+
throw GstpError.envelope(e instanceof Error ? e : new Error(String(e)));
|
|
262
312
|
}
|
|
263
313
|
}
|
|
264
|
-
} catch {
|
|
265
|
-
// valid_until is optional
|
|
266
314
|
}
|
|
267
315
|
|
|
268
|
-
// Create the continuation
|
|
269
316
|
const continuation = new Continuation(state, validId, validUntil);
|
|
270
317
|
|
|
271
|
-
// Validate date
|
|
272
318
|
if (!continuation.isValidDate(now)) {
|
|
273
319
|
throw GstpError.continuationExpired();
|
|
274
320
|
}
|
|
275
321
|
|
|
276
|
-
// Validate ID
|
|
277
322
|
if (!continuation.isValidId(expectedId)) {
|
|
278
323
|
throw GstpError.continuationIdInvalid();
|
|
279
324
|
}
|
|
@@ -290,12 +335,10 @@ export class Continuation {
|
|
|
290
335
|
* @returns true if the continuations are equal
|
|
291
336
|
*/
|
|
292
337
|
equals(other: Continuation): boolean {
|
|
293
|
-
// Compare state envelopes by their digests
|
|
294
338
|
if (!this._state.digest().equals(other._state.digest())) {
|
|
295
339
|
return false;
|
|
296
340
|
}
|
|
297
341
|
|
|
298
|
-
// Compare IDs
|
|
299
342
|
if (this._validId === undefined && other._validId === undefined) {
|
|
300
343
|
// Both undefined, equal
|
|
301
344
|
} else if (this._validId !== undefined && other._validId !== undefined) {
|
|
@@ -303,11 +346,9 @@ export class Continuation {
|
|
|
303
346
|
return false;
|
|
304
347
|
}
|
|
305
348
|
} else {
|
|
306
|
-
// One is undefined, one is not
|
|
307
349
|
return false;
|
|
308
350
|
}
|
|
309
351
|
|
|
310
|
-
// Compare valid_until
|
|
311
352
|
if (this._validUntil === undefined && other._validUntil === undefined) {
|
|
312
353
|
// Both undefined, equal
|
|
313
354
|
} else if (this._validUntil !== undefined && other._validUntil !== undefined) {
|
|
@@ -315,7 +356,6 @@ export class Continuation {
|
|
|
315
356
|
return false;
|
|
316
357
|
}
|
|
317
358
|
} else {
|
|
318
|
-
// One is undefined, one is not
|
|
319
359
|
return false;
|
|
320
360
|
}
|
|
321
361
|
|
package/src/sealed-event.ts
CHANGED
|
@@ -287,9 +287,12 @@ export class SealedEvent<T extends EnvelopeEncodableValue> implements SealedEven
|
|
|
287
287
|
signer?: Signer,
|
|
288
288
|
recipients?: XIDDocument[],
|
|
289
289
|
): Envelope {
|
|
290
|
-
//
|
|
291
|
-
|
|
292
|
-
|
|
290
|
+
// Mirrors Rust `self.sender.encryption_key()` — uses the
|
|
291
|
+
// inception key's encapsulation public key when available, falls
|
|
292
|
+
// back to the first key in the document's key set. Eagerly fetched
|
|
293
|
+
// (matching Rust `sealed_event.rs:247–250`) so the error is raised
|
|
294
|
+
// even when neither state nor validUntil is present.
|
|
295
|
+
const senderEncryptionKey = this._sender.encryptionKey();
|
|
293
296
|
if (senderEncryptionKey === undefined) {
|
|
294
297
|
throw GstpError.senderMissingEncryptionKey();
|
|
295
298
|
}
|
|
@@ -381,9 +384,9 @@ export class SealedEvent<T extends EnvelopeEncodableValue> implements SealedEven
|
|
|
381
384
|
throw GstpError.xid(e instanceof Error ? e : new Error(String(e)));
|
|
382
385
|
}
|
|
383
386
|
|
|
384
|
-
//
|
|
385
|
-
|
|
386
|
-
const senderVerificationKey =
|
|
387
|
+
// Mirrors Rust `sender.verification_key()` (with first-key
|
|
388
|
+
// fallback).
|
|
389
|
+
const senderVerificationKey = sender.verificationKey();
|
|
387
390
|
if (senderVerificationKey === undefined) {
|
|
388
391
|
throw GstpError.senderMissingVerificationKey();
|
|
389
392
|
}
|
package/src/sealed-request.ts
CHANGED
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* Ported from gstp-rust/src/sealed_request.rs
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import
|
|
14
|
+
import { ARID, type PrivateKeys, type Signer } from "@bcts/components";
|
|
15
|
+
import { CborDate, type Cbor } from "@bcts/dcbor";
|
|
15
16
|
import {
|
|
16
17
|
Envelope,
|
|
17
18
|
Request,
|
|
@@ -25,6 +26,45 @@ import { XIDDocument } from "@bcts/xid";
|
|
|
25
26
|
import { Continuation } from "./continuation";
|
|
26
27
|
import { GstpError } from "./error";
|
|
27
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Decode a CBOR value into a typed JS value.
|
|
31
|
+
*
|
|
32
|
+
* Mirrors the type-driven `T: TryFrom<CBOR>` dispatch Rust's
|
|
33
|
+
* `extract_object_for_parameter` relies on. TS lacks compile-time
|
|
34
|
+
* trait dispatch, so we hand-roll the most common cases:
|
|
35
|
+
* - tag 1 (`Date`) → JS `Date`,
|
|
36
|
+
* - tag 40012 (`ARID`) → `ARID`,
|
|
37
|
+
* - integer / text / bool / number / byte-string primitives,
|
|
38
|
+
* - everything else → the raw `Cbor` value.
|
|
39
|
+
*
|
|
40
|
+
* Callers needing other typed extraction should use
|
|
41
|
+
* `objectForParameter()` directly and decode the envelope themselves.
|
|
42
|
+
*/
|
|
43
|
+
function extractCborAsT<T>(cbor: Cbor): T {
|
|
44
|
+
const tagged = cbor.asTagged();
|
|
45
|
+
if (tagged !== undefined) {
|
|
46
|
+
const [tag] = tagged;
|
|
47
|
+
const tagNumber = Number(tag.value);
|
|
48
|
+
// Tag 1 — Standard date/time (RFC 8949 §3.4.2). Rust impls
|
|
49
|
+
// `TryFrom<CBOR> for chrono::DateTime`. We surface the JS
|
|
50
|
+
// `Date` to mirror what frost-hubert's typed callers expect.
|
|
51
|
+
if (tagNumber === 1) {
|
|
52
|
+
return CborDate.fromTaggedCbor(cbor).datetime() as T;
|
|
53
|
+
}
|
|
54
|
+
// Tag 40012 — ARID (`bc-tags::TAG_ARID`). Rust impls
|
|
55
|
+
// `TryFrom<CBOR> for ARID`.
|
|
56
|
+
if (tagNumber === 40012) {
|
|
57
|
+
return ARID.fromTaggedCbor(cbor) as T;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (cbor.isInteger()) return cbor.toInteger() as T;
|
|
61
|
+
if (cbor.isText()) return cbor.toText() as T;
|
|
62
|
+
if (cbor.isBool()) return cbor.toBool() as T;
|
|
63
|
+
if (cbor.isNumber()) return cbor.toNumber() as T;
|
|
64
|
+
if (cbor.isByteString()) return cbor.toByteString() as T;
|
|
65
|
+
return cbor as T;
|
|
66
|
+
}
|
|
67
|
+
|
|
28
68
|
/**
|
|
29
69
|
* Interface that defines the behavior of a sealed request.
|
|
30
70
|
*
|
|
@@ -188,29 +228,35 @@ export class SealedRequest implements SealedRequestBehavior {
|
|
|
188
228
|
|
|
189
229
|
/**
|
|
190
230
|
* Returns all objects for a parameter.
|
|
231
|
+
*
|
|
232
|
+
* Mirrors Rust `SealedRequest::objects_for_parameter` which delegates
|
|
233
|
+
* to `Expression::objects_for_parameter`. GSTP requests can carry
|
|
234
|
+
* multiple parameters with the same ID — e.g. a DKG invite has
|
|
235
|
+
* one `participant` per group member — and a decoder must see
|
|
236
|
+
* every one of them.
|
|
191
237
|
*/
|
|
192
238
|
objectsForParameter(param: ParameterID): Envelope[] {
|
|
193
|
-
|
|
194
|
-
return obj !== undefined ? [obj] : [];
|
|
239
|
+
return this._request.body().objectsForParameter(param);
|
|
195
240
|
}
|
|
196
241
|
|
|
197
242
|
/**
|
|
198
243
|
* Extracts an object for a parameter as a specific type.
|
|
244
|
+
*
|
|
245
|
+
* Mirrors Rust `SealedRequest::extract_object_for_parameter` — Rust
|
|
246
|
+
* uses a `T: TryFrom<CBOR>` constraint and dispatches to whatever
|
|
247
|
+
* `From<CBOR> for T` impl is in scope (e.g. tag-1 CBOR decodes to
|
|
248
|
+
* `chrono::DateTime`, tag-40012 to `ARID`, etc.). TS lacks that
|
|
249
|
+
* trait dispatch, so we recognise the most common tagged types
|
|
250
|
+
* (`Date` via tag 1, `ARID` via tag 40012) plus the primitive
|
|
251
|
+
* fall-through. Callers needing other typed extraction should use
|
|
252
|
+
* `objectForParameter()` directly and decode the envelope themselves.
|
|
199
253
|
*/
|
|
200
254
|
extractObjectForParameter<T>(param: ParameterID): T {
|
|
201
255
|
const envelope = this.objectForParameter(param);
|
|
202
256
|
if (envelope === undefined) {
|
|
203
257
|
throw GstpError.envelope(new Error(`Parameter not found: ${param}`));
|
|
204
258
|
}
|
|
205
|
-
return envelope.extractSubject((cbor) =>
|
|
206
|
-
// Extract primitive value from CBOR
|
|
207
|
-
if (cbor.isInteger()) return cbor.toInteger() as T;
|
|
208
|
-
if (cbor.isText()) return cbor.toText() as T;
|
|
209
|
-
if (cbor.isBool()) return cbor.toBool() as T;
|
|
210
|
-
if (cbor.isNumber()) return cbor.toNumber() as T;
|
|
211
|
-
if (cbor.isByteString()) return cbor.toByteString() as T;
|
|
212
|
-
return cbor as T;
|
|
213
|
-
});
|
|
259
|
+
return envelope.extractSubject((cbor) => extractCborAsT<T>(cbor));
|
|
214
260
|
}
|
|
215
261
|
|
|
216
262
|
/**
|
|
@@ -221,15 +267,7 @@ export class SealedRequest implements SealedRequestBehavior {
|
|
|
221
267
|
if (envelope === undefined) {
|
|
222
268
|
return undefined;
|
|
223
269
|
}
|
|
224
|
-
return envelope.extractSubject((cbor) =>
|
|
225
|
-
// Extract primitive value from CBOR
|
|
226
|
-
if (cbor.isInteger()) return cbor.toInteger() as T;
|
|
227
|
-
if (cbor.isText()) return cbor.toText() as T;
|
|
228
|
-
if (cbor.isBool()) return cbor.toBool() as T;
|
|
229
|
-
if (cbor.isNumber()) return cbor.toNumber() as T;
|
|
230
|
-
if (cbor.isByteString()) return cbor.toByteString() as T;
|
|
231
|
-
return cbor as T;
|
|
232
|
-
});
|
|
270
|
+
return envelope.extractSubject((cbor) => extractCborAsT<T>(cbor));
|
|
233
271
|
}
|
|
234
272
|
|
|
235
273
|
/**
|
|
@@ -404,9 +442,12 @@ export class SealedRequest implements SealedRequestBehavior {
|
|
|
404
442
|
const stateEnvelope = this._state ?? Envelope.new(null);
|
|
405
443
|
const continuation = new Continuation(stateEnvelope, this.id(), validUntil);
|
|
406
444
|
|
|
407
|
-
//
|
|
408
|
-
|
|
409
|
-
|
|
445
|
+
// Mirrors Rust `self.sender.encryption_key()` which prefers the
|
|
446
|
+
// inception key's encapsulation public key but falls back to the
|
|
447
|
+
// first key in the document's key set. The earlier port called
|
|
448
|
+
// `inceptionKey()?.publicKeys()?.encapsulationPublicKey()` directly
|
|
449
|
+
// and skipped that fallback.
|
|
450
|
+
const senderEncryptionKey = this._sender.encryptionKey();
|
|
410
451
|
if (senderEncryptionKey === undefined) {
|
|
411
452
|
throw GstpError.senderMissingEncryptionKey();
|
|
412
453
|
}
|
|
@@ -485,9 +526,10 @@ export class SealedRequest implements SealedRequestBehavior {
|
|
|
485
526
|
throw GstpError.xid(e instanceof Error ? e : new Error(String(e)));
|
|
486
527
|
}
|
|
487
528
|
|
|
488
|
-
//
|
|
489
|
-
|
|
490
|
-
|
|
529
|
+
// Mirrors Rust `sender.verification_key()` (with first-key
|
|
530
|
+
// fallback). The earlier port read the inception signing key
|
|
531
|
+
// directly and skipped that fallback.
|
|
532
|
+
const senderVerificationKey = sender.verificationKey();
|
|
491
533
|
if (senderVerificationKey === undefined) {
|
|
492
534
|
throw GstpError.senderMissingVerificationKey();
|
|
493
535
|
}
|
package/src/sealed-response.ts
CHANGED
|
@@ -325,9 +325,10 @@ export class SealedResponse implements SealedResponseBehavior {
|
|
|
325
325
|
if (this._state !== undefined) {
|
|
326
326
|
const continuation = new Continuation(this._state, undefined, validUntil);
|
|
327
327
|
|
|
328
|
-
//
|
|
329
|
-
|
|
330
|
-
|
|
328
|
+
// Mirrors Rust `self.sender.encryption_key()` — uses the
|
|
329
|
+
// inception key's encapsulation public key when available, falls
|
|
330
|
+
// back to the first key in the document's key set.
|
|
331
|
+
const senderEncryptionKey = this._sender.encryptionKey();
|
|
331
332
|
if (senderEncryptionKey === undefined) {
|
|
332
333
|
throw GstpError.senderMissingEncryptionKey();
|
|
333
334
|
}
|
|
@@ -408,9 +409,9 @@ export class SealedResponse implements SealedResponseBehavior {
|
|
|
408
409
|
throw GstpError.xid(e instanceof Error ? e : new Error(String(e)));
|
|
409
410
|
}
|
|
410
411
|
|
|
411
|
-
//
|
|
412
|
-
|
|
413
|
-
const senderVerificationKey =
|
|
412
|
+
// Mirrors Rust `sender.verification_key()` (with first-key
|
|
413
|
+
// fallback).
|
|
414
|
+
const senderVerificationKey = sender.verificationKey();
|
|
414
415
|
if (senderVerificationKey === undefined) {
|
|
415
416
|
throw GstpError.senderMissingVerificationKey();
|
|
416
417
|
}
|