signify-ts 0.3.0 → 0.4.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.
@@ -7,6 +7,7 @@ import { b } from "./core.js";
7
7
  import { Cipher } from "./cipher.js";
8
8
  import { Diger } from "./diger.js";
9
9
  import { Prefixer } from "./prefixer.js";
10
+ import { Verfer } from "./verfer.js";
10
11
  /**
11
12
  * Creates IdentifierManager instances based on the algorithm and key indexes.
12
13
  */
@@ -155,7 +156,7 @@ export class SaltyIdentifierManager {
155
156
  const digers = nsigners.signers.map((nsigner) => new Diger({ code: this.dcode }, nsigner.verfer.qb64b).qb64);
156
157
  return [verfers, digers];
157
158
  }
158
- async sign(ser, indexed = true, indices = undefined, ondices = undefined) {
159
+ async sign(ser, indexed = true, indices = undefined, ondices = undefined, _rotated) {
159
160
  const signers = this.creator.create(this.icodes, this.ncount, this.ncode, this.transferable, this.pidx, 0, this.kidx, false);
160
161
  if (indexed) {
161
162
  const sigers = [];
@@ -173,17 +174,14 @@ export class SaltyIdentifierManager {
173
174
  let o = 0;
174
175
  if (ondices != undefined) {
175
176
  o = ondices[j];
176
- if ((o == undefined ||
177
- (typeof o == 'number' &&
178
- typeof o != 'number' &&
179
- o >= 0))) {
177
+ if (o !== undefined && (!Number.isInteger(o) || o < 0)) {
180
178
  throw new Error(`Invalid ondex = ${o}, not whole number.`);
181
179
  }
182
180
  }
183
181
  else {
184
182
  o = i;
185
183
  }
186
- sigers.push(signer.sign(ser, i, o == undefined ? true : false, o));
184
+ sigers.push(signer.sign(ser, i, o === undefined ? true : false, o));
187
185
  }
188
186
  return sigers.map((siger) => siger.qb64);
189
187
  }
@@ -251,7 +249,7 @@ export class RandyIdentifierManager {
251
249
  const digers = nsigners.signers.map((nsigner) => new Diger({ code: this.dcode }, nsigner.verfer.qb64b).qb64);
252
250
  return [verfers, digers];
253
251
  }
254
- async sign(ser, indexed = true, indices = undefined, ondices = undefined) {
252
+ async sign(ser, indexed = true, indices = undefined, ondices = undefined, _rotated) {
255
253
  const signers = this.prxs.map((prx) => this.decrypter.decrypt(new Cipher({ qb64: prx }).qb64b, undefined, this.transferable));
256
254
  if (indexed) {
257
255
  const sigers = [];
@@ -269,17 +267,14 @@ export class RandyIdentifierManager {
269
267
  let o = 0;
270
268
  if (ondices != undefined) {
271
269
  o = ondices[j];
272
- if ((o == undefined ||
273
- (typeof o == 'number' &&
274
- typeof o != 'number' &&
275
- o >= 0))) {
270
+ if (o !== undefined && (!Number.isInteger(o) || o < 0)) {
276
271
  throw new Error(`Invalid ondex = ${o}, not whole number.`);
277
272
  }
278
273
  }
279
274
  else {
280
275
  o = i;
281
276
  }
282
- sigers.push(signer.sign(ser, i, o == undefined ? true : false, o));
277
+ sigers.push(signer.sign(ser, i, o === undefined ? true : false, o));
283
278
  }
284
279
  return sigers.map((siger) => siger.qb64);
285
280
  }
@@ -292,11 +287,57 @@ export class RandyIdentifierManager {
292
287
  }
293
288
  }
294
289
  }
290
+ /**
291
+ * Identifier manager for group identifiers.
292
+ *
293
+ * A group identifier does not have its own private signing keys. Instead, this
294
+ * manager maps group event signing indexes onto the local member AID (`mhab`)
295
+ * and delegates actual signing to that member's key manager.
296
+ *
297
+ * The central invariant is that indexed rotation signatures carry two
298
+ * positions:
299
+ *
300
+ * - `index` points to the signer's current key in the event's current key list
301
+ * (`k`), which SignifyTS derives from `states`.
302
+ * - `ondex` points to the prior establishment event's next digest list (`n`),
303
+ * proving that the current key was a precommitted key.
304
+ *
305
+ * `rstates` are the proposed next digest source for the new event. They are not
306
+ * the signer authorization set for the event being signed.
307
+ */
295
308
  export class GroupIdentifierManager {
309
+ /**
310
+ * Creates a manager for either a new group event or an existing group AID.
311
+ *
312
+ * `states` and `rstates` are app-level key state records used when creating
313
+ * a new group event. `keys` and `ndigs` are persisted group manager params
314
+ * used when loading an existing group. For an existing group, `ndigs` is
315
+ * expected to be the current establishment event's next digest list.
316
+ *
317
+ * During group inception there is no separate prior establishment event, so
318
+ * `gpndigs` starts as the same digest list as `gdigs`. During rotation,
319
+ * `gdigs` changes to the proposed next digests, while `gpndigs` stays
320
+ * fixed for `ondex` derivation.
321
+ */
296
322
  constructor(manager, mhab, states = undefined, rstates = undefined, keys = [], ndigs = []) {
297
323
  var _a, _b;
324
+ /**
325
+ * Current group signing public keys for the event being built. During
326
+ * rotation these come from `states[*].k[0]` and define signature `index`.
327
+ */
298
328
  this.gkeys = [];
329
+ /**
330
+ * Proposed next digest list for the event being built. During rotation
331
+ * these come from `rstates[*].n[0]`, are returned from `rotate()`, and are
332
+ * serialized as `group.ndigs`.
333
+ */
299
334
  this.gdigs = [];
335
+ /**
336
+ * Prior next digest list from the current group establishment event. For
337
+ * rotations, this is the authorization commitment set used to derive
338
+ * `ondex`; it must remain separate from proposed next digests in `gdigs`.
339
+ */
340
+ this.gpndigs = [];
300
341
  this.algo = Algos.group;
301
342
  this.manager = manager;
302
343
  if (states != undefined) {
@@ -307,36 +348,92 @@ export class GroupIdentifierManager {
307
348
  }
308
349
  this.gkeys = (_a = states === null || states === void 0 ? void 0 : states.map((state) => state['k'][0])) !== null && _a !== void 0 ? _a : keys;
309
350
  this.gdigs = (_b = rstates === null || rstates === void 0 ? void 0 : rstates.map((state) => state['n'][0])) !== null && _b !== void 0 ? _b : ndigs;
351
+ this.gpndigs = this.gdigs;
310
352
  this.mhab = mhab;
311
353
  this.signers = [];
312
354
  }
355
+ /**
356
+ * Returns the inception key material for the group event.
357
+ *
358
+ * Inception has no prior group establishment event. The current keys and
359
+ * proposed next digests are therefore returned directly from the manager's
360
+ * configured `states` and `rstates`.
361
+ */
313
362
  async incept() {
314
363
  return [this.gkeys, this.gdigs];
315
364
  }
316
365
  /**
317
- * Performs a multisig rotation
366
+ * Performs a multisig rotation.
367
+ *
368
+ * `states` supplies the current group signing keys for the event being
369
+ * signed. `rstates` supplies the proposed next key digests to put in the
370
+ * new event. The prior next digests used for rotation `ondex` lookup stay
371
+ * unchanged until KERIA accepts the new establishment event and returns an
372
+ * updated group state.
373
+ *
318
374
  * @param _ncodes
319
375
  * @param _transferable
320
376
  * @param states
321
- * @param rstates key state records for the prior establishment event indicating next key digests.
322
- * You should pass in the current key
377
+ * @param rstates
323
378
  */
324
379
  async rotate(_ncodes, _transferable, states, rstates) {
325
380
  this.gkeys = states.map((state) => state['k'][0]);
326
381
  this.gdigs = rstates.map((state) => state['n'][0]);
327
382
  return [this.gkeys, this.gdigs];
328
383
  }
329
- async sign(ser, indexed = true) {
384
+ /**
385
+ * Signs for the group by delegating to the local member keeper.
386
+ */
387
+ async sign(ser, indexed = true, _indices = undefined, _ondices = undefined, rotated = false) {
330
388
  if (!this.mhab.state) {
331
389
  throw new Error(`No state in mhab`);
332
390
  }
333
391
  const key = this.mhab['state']['k'][0];
334
- const ndig = this.mhab['state']['n'][0];
392
+ // The event's current key list authorizes the signature's `index`.
335
393
  const csi = this.gkeys.indexOf(key); // csi = current signing index (from current rotation event)
336
- const pni = this.gdigs.indexOf(ndig); // pni = prior next index (from last establishment event)
394
+ if (csi < 0) {
395
+ throw new Error(`Group signing key ${key} is not present in current group signing keys.`);
396
+ }
397
+ let pni; // prior next index exposed as signature `ondex`
398
+ if (rotated) {
399
+ // Rotation call sites must explicitly request prior-next exposure,
400
+ // dual indexed signatures with both an index (csi) and an ondex (pni)
401
+ pni = this.priorNextIndexForKey(key);
402
+ }
403
+ else {
404
+ // Inception and non-rotation signatures satisfy only the current
405
+ // signing threshold. The event's `n` field is a precommitment for a
406
+ // future rotation, not an `ondex` source.
407
+ pni = undefined;
408
+ }
409
+ if (pni !== undefined && pni < 0) {
410
+ throw new Error(`Group signing key ${key} cannot derive ondex from prior next digest list.`);
411
+ }
412
+ // Delegate the actual signature to the local member AID keeper while
413
+ // supplying group-level index and ondex positions.
337
414
  const mkeeper = this.manager.get(this.mhab);
338
415
  return await mkeeper.sign(ser, indexed, [csi], [pni]);
339
416
  }
417
+ /**
418
+ * Finds the prior next digest position that precommitted the current key.
419
+ *
420
+ * This mirrors KERIpy's exposed-prior-next behavior: for each prior next
421
+ * digest, hash the current verifier key using that digest's code and compare
422
+ * the result to the digest at that position. A match means the signer has
423
+ * exposed that precommitted key, and the matching position is the `ondex`.
424
+ */
425
+ priorNextIndexForKey(key) {
426
+ const verfer = new Verfer({ qb64: key });
427
+ for (const [index, ndig] of this.gpndigs.entries()) {
428
+ const priorDiger = new Diger({ qb64: ndig });
429
+ const keyDigest = new Diger({ code: priorDiger.code }, verfer.qb64b)
430
+ .qb64;
431
+ if (keyDigest === priorDiger.qb64) {
432
+ return index;
433
+ }
434
+ }
435
+ return -1;
436
+ }
340
437
  params() {
341
438
  return {
342
439
  mhab: this.mhab,
@@ -96,3 +96,4 @@ export type FailedGroupOperation = components['schemas']['FailedGroupOperation']
96
96
  export type FailedDelegatorOperation = components['schemas']['FailedDelegatorOperation'];
97
97
  export type FailedOperation = FailedOOBIOperation | FailedQueryOperation | FailedEndRoleOperation | FailedWitnessOperation | FailedDelegationOperation | FailedRegistryOperation | FailedLocSchemeOperation | FailedChallengeOperation | FailedExchangeOperation | FailedSubmitOperation | FailedDoneOperation | FailedCredentialOperation | FailedGroupOperation | FailedDelegatorOperation;
98
98
  export type GenericOperation = OOBIOperation | QueryOperation | EndRoleOperation | WitnessOperation | DelegationOperation | RegistryOperation | LocSchemeOperation | ChallengeOperation | ExchangeOperation | SubmitOperation | DoneOperation | CredentialOperation | GroupOperation | DelegatorOperation;
99
+ export type EndRole = components['schemas']['EndRole'];
@@ -242,7 +242,7 @@ interface SignArgs {
242
242
  verfers?: Array<Verfer> | undefined;
243
243
  indexed?: boolean;
244
244
  indices?: Array<number> | undefined;
245
- ondices?: Array<number> | undefined;
245
+ ondices?: Array<number | undefined> | undefined;
246
246
  }
247
247
  /**
248
248
  * Manages key pair creation, retrieval, and message signing.
@@ -722,14 +722,14 @@ export class Manager {
722
722
  let o;
723
723
  if (ondices != undefined) {
724
724
  o = ondices[idx];
725
- if (o <= 0) {
726
- throw new Error(`Invalid other signing index = {o}, not None or not whole number.`);
725
+ if (o !== undefined && (!Number.isInteger(o) || o < 0)) {
726
+ throw new Error(`Invalid other signing index = ${o}, not None or not whole number.`);
727
727
  }
728
728
  }
729
729
  else {
730
730
  o = i;
731
731
  }
732
- const only = o == undefined;
732
+ const only = o === undefined;
733
733
  sigers.push(signer.sign(ser, i, only, o));
734
734
  });
735
735
  return sigers;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signify-ts",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Signing at the edge for KERI, ACDC, and KERIA",
5
5
  "keywords": [
6
6
  "keri",
@@ -62,7 +62,7 @@
62
62
  "@noble/curves": "^1.8.1",
63
63
  "@noble/hashes": "^1.3.2",
64
64
  "base64-js": "^1.5.1",
65
- "libsodium-wrappers-sumo": "0.8.4",
65
+ "libsodium-wrappers-sumo": "^0.8.4",
66
66
  "mathjs": "^15.2.0",
67
67
  "structured-headers": "^0.5.0"
68
68
  }