@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 CHANGED
@@ -13,6 +13,7 @@ var __exportAll = (all, no_symbols) => {
13
13
  //#endregion
14
14
  let _bcts_components = require("@bcts/components");
15
15
  let _bcts_envelope = require("@bcts/envelope");
16
+ let _bcts_dcbor = require("@bcts/dcbor");
16
17
  let _bcts_known_values = require("@bcts/known-values");
17
18
  let _bcts_xid = require("@bcts/xid");
18
19
  //#region src/error.ts
@@ -133,7 +134,22 @@ var GstpError = class GstpError extends Error {
133
134
  * eliminating the need for local state storage and enhancing security
134
135
  * for devices with limited storage or requiring distributed state management.
135
136
  *
136
- * Ported from gstp-rust/src/continuation.rs
137
+ * Ported from gstp-rust/src/continuation.rs.
138
+ *
139
+ * Wire shape — mirrors Rust:
140
+ * ```
141
+ * {
142
+ * <state envelope>
143
+ * } [
144
+ * 'id': ARID(...)
145
+ * 'validUntil': Date(...) ← CBOR tag 1, not ISO 8601 text
146
+ * ]
147
+ * ```
148
+ *
149
+ * The `state` envelope is **wrapped** before assertions are attached,
150
+ * matching Rust `self.state.wrap().add_optional_assertion(...)`. The
151
+ * earlier port attached the assertions directly to the un-wrapped state,
152
+ * producing a different digest tree.
137
153
  */
138
154
  /**
139
155
  * Represents an encrypted state continuation.
@@ -240,8 +256,10 @@ var Continuation = class Continuation {
240
256
  /**
241
257
  * Checks if the continuation is valid at the given time.
242
258
  *
243
- * If no valid_until is set, always returns true.
244
- * If no time is provided, always returns true.
259
+ * Mirrors Rust `is_valid_date(now)`: at the exact `valid_until`
260
+ * instant, the continuation is **expired** (returns `false`). The
261
+ * earlier port used `<=` here, which differed from Rust by one
262
+ * millisecond at the boundary.
245
263
  *
246
264
  * @param now - The time to check against, or undefined to skip time validation
247
265
  * @returns true if the continuation is valid at the given time
@@ -249,7 +267,7 @@ var Continuation = class Continuation {
249
267
  isValidDate(now) {
250
268
  if (this._validUntil === void 0) return true;
251
269
  if (now === void 0) return true;
252
- return now.getTime() <= this._validUntil.getTime();
270
+ return now.getTime() < this._validUntil.getTime();
253
271
  }
254
272
  /**
255
273
  * Checks if the continuation has the expected ID.
@@ -278,21 +296,39 @@ var Continuation = class Continuation {
278
296
  /**
279
297
  * Converts the continuation to an envelope.
280
298
  *
281
- * If a recipient is provided, the envelope is encrypted to that recipient.
299
+ * Mirrors Rust `Continuation::to_envelope`:
300
+ *
301
+ * ```rust
302
+ * self.state.wrap()
303
+ * .add_optional_assertion(ID, self.valid_id)
304
+ * .add_optional_assertion(VALID_UNTIL, self.valid_until)
305
+ * ```
306
+ *
307
+ * The state is wrapped first; the optional assertions then live on
308
+ * the wrap node. `valid_until` is encoded as a CBOR-tagged Date
309
+ * (tag 1) — never as a plain ISO 8601 string.
282
310
  *
283
311
  * @param recipient - Optional recipient to encrypt the envelope to
284
312
  * @returns The continuation as an envelope
285
313
  */
286
314
  toEnvelope(recipient) {
287
- let envelope = this._state;
315
+ let envelope = this._state.wrap();
288
316
  if (this._validId !== void 0) envelope = envelope.addAssertion(_bcts_known_values.ID, this._validId);
289
- if (this._validUntil !== void 0) envelope = envelope.addAssertion(_bcts_known_values.VALID_UNTIL, this._validUntil.toISOString());
317
+ if (this._validUntil !== void 0) envelope = envelope.addAssertion(_bcts_known_values.VALID_UNTIL, _bcts_dcbor.CborDate.fromDatetime(this._validUntil));
290
318
  if (recipient !== void 0) envelope = envelope.encryptToRecipients([recipient]);
291
319
  return envelope;
292
320
  }
293
321
  /**
294
322
  * Parses a continuation from an envelope.
295
323
  *
324
+ * Mirrors Rust `Continuation::try_from_envelope`:
325
+ *
326
+ * ```rust
327
+ * state: envelope.try_unwrap()?, // unwrap
328
+ * valid_id: envelope.extract_optional_object_for_predicate(ID)?,
329
+ * valid_until: envelope.extract_optional_object_for_predicate(VALID_UNTIL)?,
330
+ * ```
331
+ *
296
332
  * @param encryptedEnvelope - The envelope to parse
297
333
  * @param expectedId - Optional ID to validate against
298
334
  * @param now - Optional time to validate against
@@ -307,23 +343,32 @@ var Continuation = class Continuation {
307
343
  } catch (e) {
308
344
  throw GstpError.envelope(e instanceof Error ? e : new Error(String(e)));
309
345
  }
310
- const state = envelope.subject();
311
- let validId;
346
+ let state;
312
347
  try {
313
- const idObj = envelope.objectForPredicate(_bcts_known_values.ID);
314
- if (idObj !== void 0) {
315
- const leafCbor = idObj.asLeaf();
316
- if (leafCbor !== void 0) validId = _bcts_components.ARID.fromTaggedCborData(leafCbor.toData());
348
+ state = envelope.tryUnwrap();
349
+ } catch (e) {
350
+ throw GstpError.envelope(e instanceof Error ? e : new Error(String(e)));
351
+ }
352
+ let validId;
353
+ const idObj = envelope.optionalObjectForPredicate(_bcts_known_values.ID);
354
+ if (idObj !== void 0) {
355
+ const leafCbor = idObj.asLeaf();
356
+ if (leafCbor !== void 0) try {
357
+ validId = _bcts_components.ARID.fromTaggedCbor(leafCbor);
358
+ } catch (e) {
359
+ throw GstpError.envelope(e instanceof Error ? e : new Error(String(e)));
317
360
  }
318
- } catch {}
361
+ }
319
362
  let validUntil;
320
- try {
321
- const validUntilObj = envelope.objectForPredicate(_bcts_known_values.VALID_UNTIL);
322
- if (validUntilObj !== void 0) {
323
- const dateStr = validUntilObj.asText();
324
- if (dateStr !== void 0) validUntil = new Date(dateStr);
363
+ const validUntilObj = envelope.optionalObjectForPredicate(_bcts_known_values.VALID_UNTIL);
364
+ if (validUntilObj !== void 0) {
365
+ const leafCbor = validUntilObj.asLeaf();
366
+ if (leafCbor !== void 0) try {
367
+ validUntil = _bcts_dcbor.CborDate.fromTaggedCbor(leafCbor).datetime();
368
+ } catch (e) {
369
+ throw GstpError.envelope(e instanceof Error ? e : new Error(String(e)));
325
370
  }
326
- } catch {}
371
+ }
327
372
  const continuation = new Continuation(state, validId, validUntil);
328
373
  if (!continuation.isValidDate(now)) throw GstpError.continuationExpired();
329
374
  if (!continuation.isValidId(expectedId)) throw GstpError.continuationIdInvalid();
@@ -360,6 +405,47 @@ var Continuation = class Continuation {
360
405
  //#endregion
361
406
  //#region src/sealed-request.ts
362
407
  /**
408
+ * Copyright © 2023-2026 Blockchain Commons, LLC
409
+ * Copyright © 2025-2026 Parity Technologies
410
+ *
411
+ *
412
+ * SealedRequest - Sealed request messages for GSTP
413
+ *
414
+ * A SealedRequest wraps a Request with sender information and state
415
+ * continuations for secure, authenticated request messages.
416
+ *
417
+ * Ported from gstp-rust/src/sealed_request.rs
418
+ */
419
+ /**
420
+ * Decode a CBOR value into a typed JS value.
421
+ *
422
+ * Mirrors the type-driven `T: TryFrom<CBOR>` dispatch Rust's
423
+ * `extract_object_for_parameter` relies on. TS lacks compile-time
424
+ * trait dispatch, so we hand-roll the most common cases:
425
+ * - tag 1 (`Date`) → JS `Date`,
426
+ * - tag 40012 (`ARID`) → `ARID`,
427
+ * - integer / text / bool / number / byte-string primitives,
428
+ * - everything else → the raw `Cbor` value.
429
+ *
430
+ * Callers needing other typed extraction should use
431
+ * `objectForParameter()` directly and decode the envelope themselves.
432
+ */
433
+ function extractCborAsT(cbor) {
434
+ const tagged = cbor.asTagged();
435
+ if (tagged !== void 0) {
436
+ const [tag] = tagged;
437
+ const tagNumber = Number(tag.value);
438
+ if (tagNumber === 1) return _bcts_dcbor.CborDate.fromTaggedCbor(cbor).datetime();
439
+ if (tagNumber === 40012) return _bcts_components.ARID.fromTaggedCbor(cbor);
440
+ }
441
+ if (cbor.isInteger()) return cbor.toInteger();
442
+ if (cbor.isText()) return cbor.toText();
443
+ if (cbor.isBool()) return cbor.toBool();
444
+ if (cbor.isNumber()) return cbor.toNumber();
445
+ if (cbor.isByteString()) return cbor.toByteString();
446
+ return cbor;
447
+ }
448
+ /**
363
449
  * A sealed request that combines a Request with sender information and
364
450
  * state continuations for secure communication.
365
451
  *
@@ -451,25 +537,32 @@ var SealedRequest = class SealedRequest {
451
537
  }
452
538
  /**
453
539
  * Returns all objects for a parameter.
540
+ *
541
+ * Mirrors Rust `SealedRequest::objects_for_parameter` which delegates
542
+ * to `Expression::objects_for_parameter`. GSTP requests can carry
543
+ * multiple parameters with the same ID — e.g. a DKG invite has
544
+ * one `participant` per group member — and a decoder must see
545
+ * every one of them.
454
546
  */
455
547
  objectsForParameter(param) {
456
- const obj = this._request.body().getParameter(param);
457
- return obj !== void 0 ? [obj] : [];
548
+ return this._request.body().objectsForParameter(param);
458
549
  }
459
550
  /**
460
551
  * Extracts an object for a parameter as a specific type.
552
+ *
553
+ * Mirrors Rust `SealedRequest::extract_object_for_parameter` — Rust
554
+ * uses a `T: TryFrom<CBOR>` constraint and dispatches to whatever
555
+ * `From<CBOR> for T` impl is in scope (e.g. tag-1 CBOR decodes to
556
+ * `chrono::DateTime`, tag-40012 to `ARID`, etc.). TS lacks that
557
+ * trait dispatch, so we recognise the most common tagged types
558
+ * (`Date` via tag 1, `ARID` via tag 40012) plus the primitive
559
+ * fall-through. Callers needing other typed extraction should use
560
+ * `objectForParameter()` directly and decode the envelope themselves.
461
561
  */
462
562
  extractObjectForParameter(param) {
463
563
  const envelope = this.objectForParameter(param);
464
564
  if (envelope === void 0) throw GstpError.envelope(/* @__PURE__ */ new Error(`Parameter not found: ${param}`));
465
- return envelope.extractSubject((cbor) => {
466
- if (cbor.isInteger()) return cbor.toInteger();
467
- if (cbor.isText()) return cbor.toText();
468
- if (cbor.isBool()) return cbor.toBool();
469
- if (cbor.isNumber()) return cbor.toNumber();
470
- if (cbor.isByteString()) return cbor.toByteString();
471
- return cbor;
472
- });
565
+ return envelope.extractSubject((cbor) => extractCborAsT(cbor));
473
566
  }
474
567
  /**
475
568
  * Extracts an optional object for a parameter.
@@ -477,14 +570,7 @@ var SealedRequest = class SealedRequest {
477
570
  extractOptionalObjectForParameter(param) {
478
571
  const envelope = this.objectForParameter(param);
479
572
  if (envelope === void 0) return;
480
- return envelope.extractSubject((cbor) => {
481
- if (cbor.isInteger()) return cbor.toInteger();
482
- if (cbor.isText()) return cbor.toText();
483
- if (cbor.isBool()) return cbor.toBool();
484
- if (cbor.isNumber()) return cbor.toNumber();
485
- if (cbor.isByteString()) return cbor.toByteString();
486
- return cbor;
487
- });
573
+ return envelope.extractSubject((cbor) => extractCborAsT(cbor));
488
574
  }
489
575
  /**
490
576
  * Extracts all objects for a parameter as a specific type.
@@ -616,7 +702,7 @@ var SealedRequest = class SealedRequest {
616
702
  */
617
703
  toEnvelopeForRecipients(validUntil, signer, recipients) {
618
704
  const continuation = new Continuation(this._state ?? _bcts_envelope.Envelope.new(null), this.id(), validUntil);
619
- const senderEncryptionKey = this._sender.inceptionKey()?.publicKeys()?.encapsulationPublicKey();
705
+ const senderEncryptionKey = this._sender.encryptionKey();
620
706
  if (senderEncryptionKey === void 0) throw GstpError.senderMissingEncryptionKey();
621
707
  const senderContinuation = continuation.toEnvelope(senderEncryptionKey);
622
708
  let result = this._request.toEnvelope();
@@ -658,7 +744,7 @@ var SealedRequest = class SealedRequest {
658
744
  } catch (e) {
659
745
  throw GstpError.xid(e instanceof Error ? e : new Error(String(e)));
660
746
  }
661
- const senderVerificationKey = sender.inceptionKey()?.publicKeys()?.signingPublicKey();
747
+ const senderVerificationKey = sender.verificationKey();
662
748
  if (senderVerificationKey === void 0) throw GstpError.senderMissingVerificationKey();
663
749
  let requestEnvelope;
664
750
  try {
@@ -920,7 +1006,7 @@ var SealedResponse = class SealedResponse {
920
1006
  let senderContinuation;
921
1007
  if (this._state !== void 0) {
922
1008
  const continuation = new Continuation(this._state, void 0, validUntil);
923
- const senderEncryptionKey = this._sender.inceptionKey()?.publicKeys()?.encapsulationPublicKey();
1009
+ const senderEncryptionKey = this._sender.encryptionKey();
924
1010
  if (senderEncryptionKey === void 0) throw GstpError.senderMissingEncryptionKey();
925
1011
  senderContinuation = continuation.toEnvelope(senderEncryptionKey);
926
1012
  }
@@ -963,7 +1049,7 @@ var SealedResponse = class SealedResponse {
963
1049
  } catch (e) {
964
1050
  throw GstpError.xid(e instanceof Error ? e : new Error(String(e)));
965
1051
  }
966
- const senderVerificationKey = sender.inceptionKey()?.publicKeys()?.signingPublicKey();
1052
+ const senderVerificationKey = sender.verificationKey();
967
1053
  if (senderVerificationKey === void 0) throw GstpError.senderMissingVerificationKey();
968
1054
  let responseEnvelope;
969
1055
  try {
@@ -1182,7 +1268,7 @@ var SealedEvent = class SealedEvent {
1182
1268
  * @returns The sealed event as an envelope
1183
1269
  */
1184
1270
  toEnvelopeForRecipients(validUntil, signer, recipients) {
1185
- const senderEncryptionKey = this._sender.inceptionKey()?.publicKeys()?.encapsulationPublicKey();
1271
+ const senderEncryptionKey = this._sender.encryptionKey();
1186
1272
  if (senderEncryptionKey === void 0) throw GstpError.senderMissingEncryptionKey();
1187
1273
  let senderContinuation;
1188
1274
  if (this._state !== void 0) senderContinuation = new Continuation(this._state, void 0, validUntil).toEnvelope(senderEncryptionKey);
@@ -1227,7 +1313,7 @@ var SealedEvent = class SealedEvent {
1227
1313
  } catch (e) {
1228
1314
  throw GstpError.xid(e instanceof Error ? e : new Error(String(e)));
1229
1315
  }
1230
- const senderVerificationKey = sender.inceptionKey()?.publicKeys()?.signingPublicKey();
1316
+ const senderVerificationKey = sender.verificationKey();
1231
1317
  if (senderVerificationKey === void 0) throw GstpError.senderMissingVerificationKey();
1232
1318
  let eventEnvelope;
1233
1319
  try {