@coordination-network/issuer-set 0.0.1 → 0.0.2

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/README.md CHANGED
@@ -44,7 +44,7 @@ const set = await ApprovedIssuerSet.fromContract({
44
44
  ### Publishing and retiring keys
45
45
 
46
46
  ```ts
47
- const leaf = set.publish(issuerKey) // curve-validated, unlike the constructor
47
+ const leaf = set.publish(issuerKey) // must be in the prime-order subgroup, unlike the constructor
48
48
  set.publishMany(issuerKeys)
49
49
  set.retire(issuerKey) // tombstones the slot; leafCount never shrinks
50
50
  ```
package/dist/index.cjs CHANGED
@@ -69,8 +69,24 @@ function toFieldElement(value) {
69
69
  return element;
70
70
  }
71
71
  /**
72
- * Coerces a pair to a Baby Jubjub public key of full order, rejecting a point
73
- * that is not on the curve or that lies in the small-order subgroup.
72
+ * Whether a point is a non-identity element of Baby Jubjub's prime-order
73
+ * subgroup: canonical coordinates, on the curve, and `subOrder` times it is the
74
+ * identity. Coordinates are checked because zk-kit reduces them mod the field.
75
+ *
76
+ * @param point The point as `[x, y]`.
77
+ */
78
+ function inPrimeOrderSubgroup(point) {
79
+ if (point.some((c) => c < 0n || c >= SNARK_SCALAR_FIELD))
80
+ return false;
81
+ if (!babyJubjub.inCurve(point) || (point[0] === 0n && point[1] === 1n))
82
+ return false;
83
+ const [x, y] = babyJubjub.mulPointEscalar(point, babyJubjub.subOrder);
84
+ return x === 0n && y === 1n;
85
+ }
86
+ /**
87
+ * Coerces a pair to a Baby Jubjub public key of prime order, rejecting a point
88
+ * that is not on the curve, that lies in the small-order subgroup, or that
89
+ * mixes a prime-order part with a torsion part.
74
90
  *
75
91
  * The set would happily commit to any two field elements, so without the curve
76
92
  * check a caller could publish a commitment to a point that does not exist.
@@ -79,8 +95,12 @@ function toFieldElement(value) {
79
95
  * in EdDSA verification takes at most eight values whatever the message, so
80
96
  * anyone can solve `S·B = R + h·A` for a signature over a claim tree they chose;
81
97
  * at the identity it is immediate. Publishing such a key would hand every
82
- * stranger the member's issuing power. Mirrors the same rejection in
83
- * `ApprovedIssuerSet.sol`.
98
+ * stranger the member's issuing power. `ApprovedIssuerSet.sol` rejects the same
99
+ * low-order points.
100
+ *
101
+ * Under the circuit's cofactored EdDSA only the holder of `A`'s secret can sign
102
+ * under a mixed-order key `A + T`, so it would only let one secret occupy several
103
+ * leaves. The contract does not reject it; this function is stricter.
84
104
  *
85
105
  * @param publicKey The signing key as `[x, y]`.
86
106
  * @returns The key with both coordinates as bigints.
@@ -96,6 +116,9 @@ function toPublicKey(publicKey) {
96
116
  if (x === 0n && y === 1n) {
97
117
  throw new Error("ApprovedIssuerSet: the public key has low order");
98
118
  }
119
+ if (!inPrimeOrderSubgroup(point)) {
120
+ throw new Error("ApprovedIssuerSet: the public key is not in the prime-order subgroup");
121
+ }
99
122
  return point;
100
123
  }
101
124
 
@@ -215,7 +238,7 @@ class ApprovedIssuerSet {
215
238
  /**
216
239
  * Publishes a signing key, appended at the next free slot.
217
240
  * @returns The leaf commitment inserted.
218
- * @throws if the key is off-curve, low-order, or already approved.
241
+ * @throws if the key is off-curve, not in the prime-order subgroup, or already approved.
219
242
  */
220
243
  publish(publicKey) {
221
244
  const leaf = issuerKeyLeaf(toPublicKey(publicKey));
@@ -248,7 +271,7 @@ class ApprovedIssuerSet {
248
271
  /**
249
272
  * Tombstones a key's slot to `0`, ending its ability to verify. The slot
250
273
  * stays allocated; a republished key gets a new one.
251
- * @throws if the key is off-curve, low-order, or not currently approved.
274
+ * @throws if the key is off-curve, not in the prime-order subgroup, or not currently approved.
252
275
  */
253
276
  retire(publicKey) {
254
277
  const index = this.indexOf(toPublicKey(publicKey));
@@ -258,7 +281,7 @@ class ApprovedIssuerSet {
258
281
  }
259
282
  /**
260
283
  * Generates the membership witness the credential circuit consumes.
261
- * @throws if the key is off-curve, low-order, or not currently approved.
284
+ * @throws if the key is off-curve, not in the prime-order subgroup, or not currently approved.
262
285
  */
263
286
  generateProof(publicKey) {
264
287
  const leaf = issuerKeyLeaf(toPublicKey(publicKey));
@@ -280,6 +303,7 @@ exports.COFACTOR = COFACTOR;
280
303
  exports.ISSUER_LEAF_DOMAIN = ISSUER_LEAF_DOMAIN;
281
304
  exports.ISSUER_SET_MAX_DEPTH = ISSUER_SET_MAX_DEPTH;
282
305
  exports.SNARK_SCALAR_FIELD = SNARK_SCALAR_FIELD;
306
+ exports.inPrimeOrderSubgroup = inPrimeOrderSubgroup;
283
307
  exports.internalNode = internalNode;
284
308
  exports.issuerKeyLeaf = issuerKeyLeaf;
285
309
  exports.issuerLeaf = issuerLeaf;
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { LeanIMT } from '@zk-kit/lean-imt';
2
2
  import { poseidon2 } from 'poseidon-lite/poseidon2';
3
3
  import { poseidon3 } from 'poseidon-lite/poseidon3';
4
- import { inCurve, mulPointEscalar } from '@zk-kit/baby-jubjub';
4
+ import { inCurve, mulPointEscalar, subOrder } from '@zk-kit/baby-jubjub';
5
5
 
6
6
  /**
7
7
  * The BN254 (alt_bn128) scalar field order. Every leaf and internal-node hash,
@@ -67,8 +67,24 @@ function toFieldElement(value) {
67
67
  return element;
68
68
  }
69
69
  /**
70
- * Coerces a pair to a Baby Jubjub public key of full order, rejecting a point
71
- * that is not on the curve or that lies in the small-order subgroup.
70
+ * Whether a point is a non-identity element of Baby Jubjub's prime-order
71
+ * subgroup: canonical coordinates, on the curve, and `subOrder` times it is the
72
+ * identity. Coordinates are checked because zk-kit reduces them mod the field.
73
+ *
74
+ * @param point The point as `[x, y]`.
75
+ */
76
+ function inPrimeOrderSubgroup(point) {
77
+ if (point.some((c) => c < 0n || c >= SNARK_SCALAR_FIELD))
78
+ return false;
79
+ if (!inCurve(point) || (point[0] === 0n && point[1] === 1n))
80
+ return false;
81
+ const [x, y] = mulPointEscalar(point, subOrder);
82
+ return x === 0n && y === 1n;
83
+ }
84
+ /**
85
+ * Coerces a pair to a Baby Jubjub public key of prime order, rejecting a point
86
+ * that is not on the curve, that lies in the small-order subgroup, or that
87
+ * mixes a prime-order part with a torsion part.
72
88
  *
73
89
  * The set would happily commit to any two field elements, so without the curve
74
90
  * check a caller could publish a commitment to a point that does not exist.
@@ -77,8 +93,12 @@ function toFieldElement(value) {
77
93
  * in EdDSA verification takes at most eight values whatever the message, so
78
94
  * anyone can solve `S·B = R + h·A` for a signature over a claim tree they chose;
79
95
  * at the identity it is immediate. Publishing such a key would hand every
80
- * stranger the member's issuing power. Mirrors the same rejection in
81
- * `ApprovedIssuerSet.sol`.
96
+ * stranger the member's issuing power. `ApprovedIssuerSet.sol` rejects the same
97
+ * low-order points.
98
+ *
99
+ * Under the circuit's cofactored EdDSA only the holder of `A`'s secret can sign
100
+ * under a mixed-order key `A + T`, so it would only let one secret occupy several
101
+ * leaves. The contract does not reject it; this function is stricter.
82
102
  *
83
103
  * @param publicKey The signing key as `[x, y]`.
84
104
  * @returns The key with both coordinates as bigints.
@@ -94,6 +114,9 @@ function toPublicKey(publicKey) {
94
114
  if (x === 0n && y === 1n) {
95
115
  throw new Error("ApprovedIssuerSet: the public key has low order");
96
116
  }
117
+ if (!inPrimeOrderSubgroup(point)) {
118
+ throw new Error("ApprovedIssuerSet: the public key is not in the prime-order subgroup");
119
+ }
97
120
  return point;
98
121
  }
99
122
 
@@ -213,7 +236,7 @@ class ApprovedIssuerSet {
213
236
  /**
214
237
  * Publishes a signing key, appended at the next free slot.
215
238
  * @returns The leaf commitment inserted.
216
- * @throws if the key is off-curve, low-order, or already approved.
239
+ * @throws if the key is off-curve, not in the prime-order subgroup, or already approved.
217
240
  */
218
241
  publish(publicKey) {
219
242
  const leaf = issuerKeyLeaf(toPublicKey(publicKey));
@@ -246,7 +269,7 @@ class ApprovedIssuerSet {
246
269
  /**
247
270
  * Tombstones a key's slot to `0`, ending its ability to verify. The slot
248
271
  * stays allocated; a republished key gets a new one.
249
- * @throws if the key is off-curve, low-order, or not currently approved.
272
+ * @throws if the key is off-curve, not in the prime-order subgroup, or not currently approved.
250
273
  */
251
274
  retire(publicKey) {
252
275
  const index = this.indexOf(toPublicKey(publicKey));
@@ -256,7 +279,7 @@ class ApprovedIssuerSet {
256
279
  }
257
280
  /**
258
281
  * Generates the membership witness the credential circuit consumes.
259
- * @throws if the key is off-curve, low-order, or not currently approved.
282
+ * @throws if the key is off-curve, not in the prime-order subgroup, or not currently approved.
260
283
  */
261
284
  generateProof(publicKey) {
262
285
  const leaf = issuerKeyLeaf(toPublicKey(publicKey));
@@ -273,4 +296,4 @@ class ApprovedIssuerSet {
273
296
  }
274
297
  }
275
298
 
276
- export { ApprovedIssuerSet, COFACTOR, ISSUER_LEAF_DOMAIN, ISSUER_SET_MAX_DEPTH, SNARK_SCALAR_FIELD, internalNode, issuerKeyLeaf, issuerLeaf, toFieldElement, toPublicKey };
299
+ export { ApprovedIssuerSet, COFACTOR, ISSUER_LEAF_DOMAIN, ISSUER_SET_MAX_DEPTH, SNARK_SCALAR_FIELD, inPrimeOrderSubgroup, internalNode, issuerKeyLeaf, issuerLeaf, toFieldElement, toPublicKey };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coordination-network/issuer-set",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Approved-issuer set in TypeScript: publish network members' credential signing keys into the LeanIMT accumulator (dynamic depth, capacity 2^20) spec 03 defines, and generate the membership proofs a credential circuit consumes, as the set itself or as a mirror of one held on chain.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -143,7 +143,7 @@ export class ApprovedIssuerSet implements IssuerSet {
143
143
  /**
144
144
  * Publishes a signing key, appended at the next free slot.
145
145
  * @returns The leaf commitment inserted.
146
- * @throws if the key is off-curve, low-order, or already approved.
146
+ * @throws if the key is off-curve, not in the prime-order subgroup, or already approved.
147
147
  */
148
148
  publish(publicKey: IssuerPublicKey): bigint {
149
149
  const leaf = issuerKeyLeaf(toPublicKey(publicKey))
@@ -186,7 +186,7 @@ export class ApprovedIssuerSet implements IssuerSet {
186
186
  /**
187
187
  * Tombstones a key's slot to `0`, ending its ability to verify. The slot
188
188
  * stays allocated; a republished key gets a new one.
189
- * @throws if the key is off-curve, low-order, or not currently approved.
189
+ * @throws if the key is off-curve, not in the prime-order subgroup, or not currently approved.
190
190
  */
191
191
  retire(publicKey: IssuerPublicKey): void {
192
192
  const index = this.indexOf(toPublicKey(publicKey))
@@ -197,7 +197,7 @@ export class ApprovedIssuerSet implements IssuerSet {
197
197
 
198
198
  /**
199
199
  * Generates the membership witness the credential circuit consumes.
200
- * @throws if the key is off-curve, low-order, or not currently approved.
200
+ * @throws if the key is off-curve, not in the prime-order subgroup, or not currently approved.
201
201
  */
202
202
  generateProof(publicKey: IssuerPublicKey): IssuerMembershipWitness {
203
203
  const leaf = issuerKeyLeaf(toPublicKey(publicKey))
package/src/index.ts CHANGED
@@ -9,4 +9,4 @@ export type {
9
9
  IssuerPublicKey,
10
10
  IssuerSet
11
11
  } from "./types"
12
- export { toFieldElement, toPublicKey } from "./utils"
12
+ export { inPrimeOrderSubgroup, toFieldElement, toPublicKey } from "./utils"
package/src/utils.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { inCurve, mulPointEscalar } from "@zk-kit/baby-jubjub"
1
+ import { inCurve, mulPointEscalar, subOrder } from "@zk-kit/baby-jubjub"
2
2
  import { COFACTOR, SNARK_SCALAR_FIELD } from "./constants"
3
3
  import type { BigNumberish, IssuerPublicKey } from "./types"
4
4
 
@@ -30,8 +30,25 @@ export function toFieldElement(value: BigNumberish): bigint {
30
30
  }
31
31
 
32
32
  /**
33
- * Coerces a pair to a Baby Jubjub public key of full order, rejecting a point
34
- * that is not on the curve or that lies in the small-order subgroup.
33
+ * Whether a point is a non-identity element of Baby Jubjub's prime-order
34
+ * subgroup: canonical coordinates, on the curve, and `subOrder` times it is the
35
+ * identity. Coordinates are checked because zk-kit reduces them mod the field.
36
+ *
37
+ * @param point The point as `[x, y]`.
38
+ */
39
+ export function inPrimeOrderSubgroup(point: [bigint, bigint]): boolean {
40
+ if (point.some((c) => c < 0n || c >= SNARK_SCALAR_FIELD)) return false
41
+ if (!inCurve(point) || (point[0] === 0n && point[1] === 1n)) return false
42
+
43
+ const [x, y] = mulPointEscalar(point, subOrder)
44
+
45
+ return x === 0n && y === 1n
46
+ }
47
+
48
+ /**
49
+ * Coerces a pair to a Baby Jubjub public key of prime order, rejecting a point
50
+ * that is not on the curve, that lies in the small-order subgroup, or that
51
+ * mixes a prime-order part with a torsion part.
35
52
  *
36
53
  * The set would happily commit to any two field elements, so without the curve
37
54
  * check a caller could publish a commitment to a point that does not exist.
@@ -40,8 +57,12 @@ export function toFieldElement(value: BigNumberish): bigint {
40
57
  * in EdDSA verification takes at most eight values whatever the message, so
41
58
  * anyone can solve `S·B = R + h·A` for a signature over a claim tree they chose;
42
59
  * at the identity it is immediate. Publishing such a key would hand every
43
- * stranger the member's issuing power. Mirrors the same rejection in
44
- * `ApprovedIssuerSet.sol`.
60
+ * stranger the member's issuing power. `ApprovedIssuerSet.sol` rejects the same
61
+ * low-order points.
62
+ *
63
+ * Under the circuit's cofactored EdDSA only the holder of `A`'s secret can sign
64
+ * under a mixed-order key `A + T`, so it would only let one secret occupy several
65
+ * leaves. The contract does not reject it; this function is stricter.
45
66
  *
46
67
  * @param publicKey The signing key as `[x, y]`.
47
68
  * @returns The key with both coordinates as bigints.
@@ -61,5 +82,9 @@ export function toPublicKey(publicKey: IssuerPublicKey): [bigint, bigint] {
61
82
  throw new Error("ApprovedIssuerSet: the public key has low order")
62
83
  }
63
84
 
85
+ if (!inPrimeOrderSubgroup(point)) {
86
+ throw new Error("ApprovedIssuerSet: the public key is not in the prime-order subgroup")
87
+ }
88
+
64
89
  return point
65
90
  }
package/types/index.d.ts CHANGED
@@ -106,7 +106,7 @@ declare class ApprovedIssuerSet implements IssuerSet {
106
106
  /**
107
107
  * Publishes a signing key, appended at the next free slot.
108
108
  * @returns The leaf commitment inserted.
109
- * @throws if the key is off-curve, low-order, or already approved.
109
+ * @throws if the key is off-curve, not in the prime-order subgroup, or already approved.
110
110
  */
111
111
  publish(publicKey: IssuerPublicKey): bigint;
112
112
  /**
@@ -119,12 +119,12 @@ declare class ApprovedIssuerSet implements IssuerSet {
119
119
  /**
120
120
  * Tombstones a key's slot to `0`, ending its ability to verify. The slot
121
121
  * stays allocated; a republished key gets a new one.
122
- * @throws if the key is off-curve, low-order, or not currently approved.
122
+ * @throws if the key is off-curve, not in the prime-order subgroup, or not currently approved.
123
123
  */
124
124
  retire(publicKey: IssuerPublicKey): void;
125
125
  /**
126
126
  * Generates the membership witness the credential circuit consumes.
127
- * @throws if the key is off-curve, low-order, or not currently approved.
127
+ * @throws if the key is off-curve, not in the prime-order subgroup, or not currently approved.
128
128
  */
129
129
  generateProof(publicKey: IssuerPublicKey): IssuerMembershipWitness;
130
130
  /** Instance form of {@link ApprovedIssuerSet.verifyProof}, additionally bound to this set's current root. */
@@ -182,8 +182,17 @@ declare function internalNode(left: bigint, right: bigint): bigint;
182
182
  */
183
183
  declare function toFieldElement(value: BigNumberish): bigint;
184
184
  /**
185
- * Coerces a pair to a Baby Jubjub public key of full order, rejecting a point
186
- * that is not on the curve or that lies in the small-order subgroup.
185
+ * Whether a point is a non-identity element of Baby Jubjub's prime-order
186
+ * subgroup: canonical coordinates, on the curve, and `subOrder` times it is the
187
+ * identity. Coordinates are checked because zk-kit reduces them mod the field.
188
+ *
189
+ * @param point The point as `[x, y]`.
190
+ */
191
+ declare function inPrimeOrderSubgroup(point: [bigint, bigint]): boolean;
192
+ /**
193
+ * Coerces a pair to a Baby Jubjub public key of prime order, rejecting a point
194
+ * that is not on the curve, that lies in the small-order subgroup, or that
195
+ * mixes a prime-order part with a torsion part.
187
196
  *
188
197
  * The set would happily commit to any two field elements, so without the curve
189
198
  * check a caller could publish a commitment to a point that does not exist.
@@ -192,13 +201,17 @@ declare function toFieldElement(value: BigNumberish): bigint;
192
201
  * in EdDSA verification takes at most eight values whatever the message, so
193
202
  * anyone can solve `S·B = R + h·A` for a signature over a claim tree they chose;
194
203
  * at the identity it is immediate. Publishing such a key would hand every
195
- * stranger the member's issuing power. Mirrors the same rejection in
196
- * `ApprovedIssuerSet.sol`.
204
+ * stranger the member's issuing power. `ApprovedIssuerSet.sol` rejects the same
205
+ * low-order points.
206
+ *
207
+ * Under the circuit's cofactored EdDSA only the holder of `A`'s secret can sign
208
+ * under a mixed-order key `A + T`, so it would only let one secret occupy several
209
+ * leaves. The contract does not reject it; this function is stricter.
197
210
  *
198
211
  * @param publicKey The signing key as `[x, y]`.
199
212
  * @returns The key with both coordinates as bigints.
200
213
  */
201
214
  declare function toPublicKey(publicKey: IssuerPublicKey): [bigint, bigint];
202
215
 
203
- export { ApprovedIssuerSet, COFACTOR, ISSUER_LEAF_DOMAIN, ISSUER_SET_MAX_DEPTH, SNARK_SCALAR_FIELD, internalNode, issuerKeyLeaf, issuerLeaf, toFieldElement, toPublicKey };
216
+ export { ApprovedIssuerSet, COFACTOR, ISSUER_LEAF_DOMAIN, ISSUER_SET_MAX_DEPTH, SNARK_SCALAR_FIELD, inPrimeOrderSubgroup, internalNode, issuerKeyLeaf, issuerLeaf, toFieldElement, toPublicKey };
204
217
  export type { BigNumberish, ContractLeafReader, IssuerMembershipProof, IssuerMembershipWitness, IssuerPublicKey, IssuerSet };