@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.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
2
2
  import { ARID } from "@bcts/components";
3
3
  import { Envelope, Event, Request, Response } from "@bcts/envelope";
4
+ import { CborDate } from "@bcts/dcbor";
4
5
  import { ID, RECIPIENT_CONTINUATION, SENDER, SENDER_CONTINUATION, VALID_UNTIL } from "@bcts/known-values";
5
6
  import { XIDDocument } from "@bcts/xid";
6
7
  //#region src/error.ts
@@ -121,7 +122,22 @@ var GstpError = class GstpError extends Error {
121
122
  * eliminating the need for local state storage and enhancing security
122
123
  * for devices with limited storage or requiring distributed state management.
123
124
  *
124
- * Ported from gstp-rust/src/continuation.rs
125
+ * Ported from gstp-rust/src/continuation.rs.
126
+ *
127
+ * Wire shape — mirrors Rust:
128
+ * ```
129
+ * {
130
+ * <state envelope>
131
+ * } [
132
+ * 'id': ARID(...)
133
+ * 'validUntil': Date(...) ← CBOR tag 1, not ISO 8601 text
134
+ * ]
135
+ * ```
136
+ *
137
+ * The `state` envelope is **wrapped** before assertions are attached,
138
+ * matching Rust `self.state.wrap().add_optional_assertion(...)`. The
139
+ * earlier port attached the assertions directly to the un-wrapped state,
140
+ * producing a different digest tree.
125
141
  */
126
142
  /**
127
143
  * Represents an encrypted state continuation.
@@ -228,8 +244,10 @@ var Continuation = class Continuation {
228
244
  /**
229
245
  * Checks if the continuation is valid at the given time.
230
246
  *
231
- * If no valid_until is set, always returns true.
232
- * If no time is provided, always returns true.
247
+ * Mirrors Rust `is_valid_date(now)`: at the exact `valid_until`
248
+ * instant, the continuation is **expired** (returns `false`). The
249
+ * earlier port used `<=` here, which differed from Rust by one
250
+ * millisecond at the boundary.
233
251
  *
234
252
  * @param now - The time to check against, or undefined to skip time validation
235
253
  * @returns true if the continuation is valid at the given time
@@ -237,7 +255,7 @@ var Continuation = class Continuation {
237
255
  isValidDate(now) {
238
256
  if (this._validUntil === void 0) return true;
239
257
  if (now === void 0) return true;
240
- return now.getTime() <= this._validUntil.getTime();
258
+ return now.getTime() < this._validUntil.getTime();
241
259
  }
242
260
  /**
243
261
  * Checks if the continuation has the expected ID.
@@ -266,21 +284,39 @@ var Continuation = class Continuation {
266
284
  /**
267
285
  * Converts the continuation to an envelope.
268
286
  *
269
- * If a recipient is provided, the envelope is encrypted to that recipient.
287
+ * Mirrors Rust `Continuation::to_envelope`:
288
+ *
289
+ * ```rust
290
+ * self.state.wrap()
291
+ * .add_optional_assertion(ID, self.valid_id)
292
+ * .add_optional_assertion(VALID_UNTIL, self.valid_until)
293
+ * ```
294
+ *
295
+ * The state is wrapped first; the optional assertions then live on
296
+ * the wrap node. `valid_until` is encoded as a CBOR-tagged Date
297
+ * (tag 1) — never as a plain ISO 8601 string.
270
298
  *
271
299
  * @param recipient - Optional recipient to encrypt the envelope to
272
300
  * @returns The continuation as an envelope
273
301
  */
274
302
  toEnvelope(recipient) {
275
- let envelope = this._state;
303
+ let envelope = this._state.wrap();
276
304
  if (this._validId !== void 0) envelope = envelope.addAssertion(ID, this._validId);
277
- if (this._validUntil !== void 0) envelope = envelope.addAssertion(VALID_UNTIL, this._validUntil.toISOString());
305
+ if (this._validUntil !== void 0) envelope = envelope.addAssertion(VALID_UNTIL, CborDate.fromDatetime(this._validUntil));
278
306
  if (recipient !== void 0) envelope = envelope.encryptToRecipients([recipient]);
279
307
  return envelope;
280
308
  }
281
309
  /**
282
310
  * Parses a continuation from an envelope.
283
311
  *
312
+ * Mirrors Rust `Continuation::try_from_envelope`:
313
+ *
314
+ * ```rust
315
+ * state: envelope.try_unwrap()?, // unwrap
316
+ * valid_id: envelope.extract_optional_object_for_predicate(ID)?,
317
+ * valid_until: envelope.extract_optional_object_for_predicate(VALID_UNTIL)?,
318
+ * ```
319
+ *
284
320
  * @param encryptedEnvelope - The envelope to parse
285
321
  * @param expectedId - Optional ID to validate against
286
322
  * @param now - Optional time to validate against
@@ -295,23 +331,32 @@ var Continuation = class Continuation {
295
331
  } catch (e) {
296
332
  throw GstpError.envelope(e instanceof Error ? e : new Error(String(e)));
297
333
  }
298
- const state = envelope.subject();
299
- let validId;
334
+ let state;
300
335
  try {
301
- const idObj = envelope.objectForPredicate(ID);
302
- if (idObj !== void 0) {
303
- const leafCbor = idObj.asLeaf();
304
- if (leafCbor !== void 0) validId = ARID.fromTaggedCborData(leafCbor.toData());
336
+ state = envelope.tryUnwrap();
337
+ } catch (e) {
338
+ throw GstpError.envelope(e instanceof Error ? e : new Error(String(e)));
339
+ }
340
+ let validId;
341
+ const idObj = envelope.optionalObjectForPredicate(ID);
342
+ if (idObj !== void 0) {
343
+ const leafCbor = idObj.asLeaf();
344
+ if (leafCbor !== void 0) try {
345
+ validId = ARID.fromTaggedCbor(leafCbor);
346
+ } catch (e) {
347
+ throw GstpError.envelope(e instanceof Error ? e : new Error(String(e)));
305
348
  }
306
- } catch {}
349
+ }
307
350
  let validUntil;
308
- try {
309
- const validUntilObj = envelope.objectForPredicate(VALID_UNTIL);
310
- if (validUntilObj !== void 0) {
311
- const dateStr = validUntilObj.asText();
312
- if (dateStr !== void 0) validUntil = new Date(dateStr);
351
+ const validUntilObj = envelope.optionalObjectForPredicate(VALID_UNTIL);
352
+ if (validUntilObj !== void 0) {
353
+ const leafCbor = validUntilObj.asLeaf();
354
+ if (leafCbor !== void 0) try {
355
+ validUntil = CborDate.fromTaggedCbor(leafCbor).datetime();
356
+ } catch (e) {
357
+ throw GstpError.envelope(e instanceof Error ? e : new Error(String(e)));
313
358
  }
314
- } catch {}
359
+ }
315
360
  const continuation = new Continuation(state, validId, validUntil);
316
361
  if (!continuation.isValidDate(now)) throw GstpError.continuationExpired();
317
362
  if (!continuation.isValidId(expectedId)) throw GstpError.continuationIdInvalid();
@@ -348,6 +393,47 @@ var Continuation = class Continuation {
348
393
  //#endregion
349
394
  //#region src/sealed-request.ts
350
395
  /**
396
+ * Copyright © 2023-2026 Blockchain Commons, LLC
397
+ * Copyright © 2025-2026 Parity Technologies
398
+ *
399
+ *
400
+ * SealedRequest - Sealed request messages for GSTP
401
+ *
402
+ * A SealedRequest wraps a Request with sender information and state
403
+ * continuations for secure, authenticated request messages.
404
+ *
405
+ * Ported from gstp-rust/src/sealed_request.rs
406
+ */
407
+ /**
408
+ * Decode a CBOR value into a typed JS value.
409
+ *
410
+ * Mirrors the type-driven `T: TryFrom<CBOR>` dispatch Rust's
411
+ * `extract_object_for_parameter` relies on. TS lacks compile-time
412
+ * trait dispatch, so we hand-roll the most common cases:
413
+ * - tag 1 (`Date`) → JS `Date`,
414
+ * - tag 40012 (`ARID`) → `ARID`,
415
+ * - integer / text / bool / number / byte-string primitives,
416
+ * - everything else → the raw `Cbor` value.
417
+ *
418
+ * Callers needing other typed extraction should use
419
+ * `objectForParameter()` directly and decode the envelope themselves.
420
+ */
421
+ function extractCborAsT(cbor) {
422
+ const tagged = cbor.asTagged();
423
+ if (tagged !== void 0) {
424
+ const [tag] = tagged;
425
+ const tagNumber = Number(tag.value);
426
+ if (tagNumber === 1) return CborDate.fromTaggedCbor(cbor).datetime();
427
+ if (tagNumber === 40012) return ARID.fromTaggedCbor(cbor);
428
+ }
429
+ if (cbor.isInteger()) return cbor.toInteger();
430
+ if (cbor.isText()) return cbor.toText();
431
+ if (cbor.isBool()) return cbor.toBool();
432
+ if (cbor.isNumber()) return cbor.toNumber();
433
+ if (cbor.isByteString()) return cbor.toByteString();
434
+ return cbor;
435
+ }
436
+ /**
351
437
  * A sealed request that combines a Request with sender information and
352
438
  * state continuations for secure communication.
353
439
  *
@@ -439,25 +525,32 @@ var SealedRequest = class SealedRequest {
439
525
  }
440
526
  /**
441
527
  * Returns all objects for a parameter.
528
+ *
529
+ * Mirrors Rust `SealedRequest::objects_for_parameter` which delegates
530
+ * to `Expression::objects_for_parameter`. GSTP requests can carry
531
+ * multiple parameters with the same ID — e.g. a DKG invite has
532
+ * one `participant` per group member — and a decoder must see
533
+ * every one of them.
442
534
  */
443
535
  objectsForParameter(param) {
444
- const obj = this._request.body().getParameter(param);
445
- return obj !== void 0 ? [obj] : [];
536
+ return this._request.body().objectsForParameter(param);
446
537
  }
447
538
  /**
448
539
  * Extracts an object for a parameter as a specific type.
540
+ *
541
+ * Mirrors Rust `SealedRequest::extract_object_for_parameter` — Rust
542
+ * uses a `T: TryFrom<CBOR>` constraint and dispatches to whatever
543
+ * `From<CBOR> for T` impl is in scope (e.g. tag-1 CBOR decodes to
544
+ * `chrono::DateTime`, tag-40012 to `ARID`, etc.). TS lacks that
545
+ * trait dispatch, so we recognise the most common tagged types
546
+ * (`Date` via tag 1, `ARID` via tag 40012) plus the primitive
547
+ * fall-through. Callers needing other typed extraction should use
548
+ * `objectForParameter()` directly and decode the envelope themselves.
449
549
  */
450
550
  extractObjectForParameter(param) {
451
551
  const envelope = this.objectForParameter(param);
452
552
  if (envelope === void 0) throw GstpError.envelope(/* @__PURE__ */ new Error(`Parameter not found: ${param}`));
453
- return envelope.extractSubject((cbor) => {
454
- if (cbor.isInteger()) return cbor.toInteger();
455
- if (cbor.isText()) return cbor.toText();
456
- if (cbor.isBool()) return cbor.toBool();
457
- if (cbor.isNumber()) return cbor.toNumber();
458
- if (cbor.isByteString()) return cbor.toByteString();
459
- return cbor;
460
- });
553
+ return envelope.extractSubject((cbor) => extractCborAsT(cbor));
461
554
  }
462
555
  /**
463
556
  * Extracts an optional object for a parameter.
@@ -465,14 +558,7 @@ var SealedRequest = class SealedRequest {
465
558
  extractOptionalObjectForParameter(param) {
466
559
  const envelope = this.objectForParameter(param);
467
560
  if (envelope === void 0) return;
468
- return envelope.extractSubject((cbor) => {
469
- if (cbor.isInteger()) return cbor.toInteger();
470
- if (cbor.isText()) return cbor.toText();
471
- if (cbor.isBool()) return cbor.toBool();
472
- if (cbor.isNumber()) return cbor.toNumber();
473
- if (cbor.isByteString()) return cbor.toByteString();
474
- return cbor;
475
- });
561
+ return envelope.extractSubject((cbor) => extractCborAsT(cbor));
476
562
  }
477
563
  /**
478
564
  * Extracts all objects for a parameter as a specific type.
@@ -604,7 +690,7 @@ var SealedRequest = class SealedRequest {
604
690
  */
605
691
  toEnvelopeForRecipients(validUntil, signer, recipients) {
606
692
  const continuation = new Continuation(this._state ?? Envelope.new(null), this.id(), validUntil);
607
- const senderEncryptionKey = this._sender.inceptionKey()?.publicKeys()?.encapsulationPublicKey();
693
+ const senderEncryptionKey = this._sender.encryptionKey();
608
694
  if (senderEncryptionKey === void 0) throw GstpError.senderMissingEncryptionKey();
609
695
  const senderContinuation = continuation.toEnvelope(senderEncryptionKey);
610
696
  let result = this._request.toEnvelope();
@@ -646,7 +732,7 @@ var SealedRequest = class SealedRequest {
646
732
  } catch (e) {
647
733
  throw GstpError.xid(e instanceof Error ? e : new Error(String(e)));
648
734
  }
649
- const senderVerificationKey = sender.inceptionKey()?.publicKeys()?.signingPublicKey();
735
+ const senderVerificationKey = sender.verificationKey();
650
736
  if (senderVerificationKey === void 0) throw GstpError.senderMissingVerificationKey();
651
737
  let requestEnvelope;
652
738
  try {
@@ -908,7 +994,7 @@ var SealedResponse = class SealedResponse {
908
994
  let senderContinuation;
909
995
  if (this._state !== void 0) {
910
996
  const continuation = new Continuation(this._state, void 0, validUntil);
911
- const senderEncryptionKey = this._sender.inceptionKey()?.publicKeys()?.encapsulationPublicKey();
997
+ const senderEncryptionKey = this._sender.encryptionKey();
912
998
  if (senderEncryptionKey === void 0) throw GstpError.senderMissingEncryptionKey();
913
999
  senderContinuation = continuation.toEnvelope(senderEncryptionKey);
914
1000
  }
@@ -951,7 +1037,7 @@ var SealedResponse = class SealedResponse {
951
1037
  } catch (e) {
952
1038
  throw GstpError.xid(e instanceof Error ? e : new Error(String(e)));
953
1039
  }
954
- const senderVerificationKey = sender.inceptionKey()?.publicKeys()?.signingPublicKey();
1040
+ const senderVerificationKey = sender.verificationKey();
955
1041
  if (senderVerificationKey === void 0) throw GstpError.senderMissingVerificationKey();
956
1042
  let responseEnvelope;
957
1043
  try {
@@ -1170,7 +1256,7 @@ var SealedEvent = class SealedEvent {
1170
1256
  * @returns The sealed event as an envelope
1171
1257
  */
1172
1258
  toEnvelopeForRecipients(validUntil, signer, recipients) {
1173
- const senderEncryptionKey = this._sender.inceptionKey()?.publicKeys()?.encapsulationPublicKey();
1259
+ const senderEncryptionKey = this._sender.encryptionKey();
1174
1260
  if (senderEncryptionKey === void 0) throw GstpError.senderMissingEncryptionKey();
1175
1261
  let senderContinuation;
1176
1262
  if (this._state !== void 0) senderContinuation = new Continuation(this._state, void 0, validUntil).toEnvelope(senderEncryptionKey);
@@ -1215,7 +1301,7 @@ var SealedEvent = class SealedEvent {
1215
1301
  } catch (e) {
1216
1302
  throw GstpError.xid(e instanceof Error ? e : new Error(String(e)));
1217
1303
  }
1218
- const senderVerificationKey = sender.inceptionKey()?.publicKeys()?.signingPublicKey();
1304
+ const senderVerificationKey = sender.verificationKey();
1219
1305
  if (senderVerificationKey === void 0) throw GstpError.senderMissingVerificationKey();
1220
1306
  let eventEnvelope;
1221
1307
  try {