@coordination-network/revocation-registry 0.0.2 → 0.1.1

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
@@ -93,9 +93,9 @@ const rebuilt = RevocationRegistry.fromLeaves(slots)
93
93
 
94
94
  ### Snapshots
95
95
 
96
- Replaying every revocation since genesis gets slow. `export()` serializes the whole tree, hashes included, and `import()` rebuilds it without rehashing, so a service can publish periodic snapshots and a holder can sync from the latest one plus the few revocations after it.
96
+ Replaying every revocation since genesis gets slow. `export()` serializes the whole tree, hashes included, and `import()` rebuilds it without recomputing every node, so a service can publish periodic snapshots and a holder can sync from the latest one plus the few revocations after it.
97
97
 
98
- `import()` validates by default that the serialized leaves and hashes agree with each other, which rejects a tampered snapshot. That is an internal-consistency check, not proof of authenticity: still compare `root` against the authority.
98
+ `import()` validates by default: it rehashes the leaves against the serialized hashes and requires every leaf value to be a field element, which rejects a tampered snapshot. That is not proof of authenticity. Import a served snapshot with validation on, and compare `root` against the authority's.
99
99
 
100
100
  A snapshot carries the node hashes, so it is larger than the leaf slots and lets a publisher hand you hashes you did not derive. `fromLeaves` is the leaner and more suspicious option; `import` is the faster one when you produced the snapshot yourself.
101
101
 
@@ -141,7 +141,6 @@ An index is only valid against the tree state the write applies to. If another r
141
141
  ## Notes
142
142
 
143
143
  - Commitments must be non-zero and below the BN254 scalar field order. Zero is reserved for the sentinel leaf and for tombstones. Above the field order, two distinct values can reduce to the same field element, which would let one revocation silently cover an unrelated credential and would break the sorted ordering a non-membership proof relies on.
144
- - An empty registry cannot produce proofs. Nothing has been revoked, so a verifier should treat that as "no revocations", not as a failure.
145
144
  - Revocation is normally permanent. `unrevoke` exists because the structure supports it, but an un-revoked credential becomes valid again for every verifier still accepting an older root, and it leaves a tombstone that rules out rebuilding from `commitments` alone.
146
145
  - `indexOf` returns a physical slot, never a position in `commitments`. The two always differ by at least one, because slot 0 is the sentinel.
147
146
  - `revocationHashes` is the Poseidon pair every implementation of a registry must agree on: 3-input for leaves, 2-input for internal nodes. `tests/cross-language.test.ts` pins the resulting roots against the Solidity implementation.
package/dist/index.cjs CHANGED
@@ -387,12 +387,20 @@ class RevocationRegistry {
387
387
  *
388
388
  * @param snapshot The serialized tree.
389
389
  * @param validate Recompute every commitment and check it against the
390
- * serialized hashes, rejecting a tampered snapshot. Defaults to `true`; pass
391
- * `false` only for data you produced yourself.
390
+ * serialized hashes and require every leaf value to be a field element,
391
+ * rejecting a tampered snapshot. Defaults to `true`; pass `false` only for
392
+ * data you produced yourself.
392
393
  */
393
394
  static import(snapshot, validate = true) {
394
395
  const tree = new RevocationRegistry();
395
396
  tree.leanIMTPlus = leanImtPlus.LeanIMTPlus.import(revocationHashes, snapshot, { validate });
397
+ // Hashing reduces mod p, so a leaf holding `c + p` matches the honest root while `isRevoked(c)` is false.
398
+ if (validate) {
399
+ for (const { value, nextValue } of tree.leafSlots) {
400
+ toFieldElement(value);
401
+ toFieldElement(nextValue);
402
+ }
403
+ }
396
404
  return tree;
397
405
  }
398
406
  /**
package/dist/index.js CHANGED
@@ -385,12 +385,20 @@ class RevocationRegistry {
385
385
  *
386
386
  * @param snapshot The serialized tree.
387
387
  * @param validate Recompute every commitment and check it against the
388
- * serialized hashes, rejecting a tampered snapshot. Defaults to `true`; pass
389
- * `false` only for data you produced yourself.
388
+ * serialized hashes and require every leaf value to be a field element,
389
+ * rejecting a tampered snapshot. Defaults to `true`; pass `false` only for
390
+ * data you produced yourself.
390
391
  */
391
392
  static import(snapshot, validate = true) {
392
393
  const tree = new RevocationRegistry();
393
394
  tree.leanIMTPlus = LeanIMTPlus.import(revocationHashes, snapshot, { validate });
395
+ // Hashing reduces mod p, so a leaf holding `c + p` matches the honest root while `isRevoked(c)` is false.
396
+ if (validate) {
397
+ for (const { value, nextValue } of tree.leafSlots) {
398
+ toFieldElement(value);
399
+ toFieldElement(nextValue);
400
+ }
401
+ }
394
402
  return tree;
395
403
  }
396
404
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coordination-network/revocation-registry",
3
- "version": "0.0.2",
3
+ "version": "0.1.1",
4
4
  "description": "LeanIMT+ revocation registry in TypeScript: revoke credential commitments and generate membership and non-membership proofs, as the registry itself or as a mirror of one held elsewhere.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -318,14 +318,23 @@ export class RevocationRegistry {
318
318
  *
319
319
  * @param snapshot The serialized tree.
320
320
  * @param validate Recompute every commitment and check it against the
321
- * serialized hashes, rejecting a tampered snapshot. Defaults to `true`; pass
322
- * `false` only for data you produced yourself.
321
+ * serialized hashes and require every leaf value to be a field element,
322
+ * rejecting a tampered snapshot. Defaults to `true`; pass `false` only for
323
+ * data you produced yourself.
323
324
  */
324
325
  public static import(snapshot: string, validate = true): RevocationRegistry {
325
326
  const tree = new RevocationRegistry()
326
327
 
327
328
  tree.leanIMTPlus = LeanIMTPlus.import(revocationHashes, snapshot, { validate })
328
329
 
330
+ // Hashing reduces mod p, so a leaf holding `c + p` matches the honest root while `isRevoked(c)` is false.
331
+ if (validate) {
332
+ for (const { value, nextValue } of tree.leafSlots) {
333
+ toFieldElement(value)
334
+ toFieldElement(nextValue)
335
+ }
336
+ }
337
+
329
338
  return tree
330
339
  }
331
340
 
package/types/index.d.ts CHANGED
@@ -282,8 +282,9 @@ declare class RevocationRegistry {
282
282
  *
283
283
  * @param snapshot The serialized tree.
284
284
  * @param validate Recompute every commitment and check it against the
285
- * serialized hashes, rejecting a tampered snapshot. Defaults to `true`; pass
286
- * `false` only for data you produced yourself.
285
+ * serialized hashes and require every leaf value to be a field element,
286
+ * rejecting a tampered snapshot. Defaults to `true`; pass `false` only for
287
+ * data you produced yourself.
287
288
  */
288
289
  static import(snapshot: string, validate?: boolean): RevocationRegistry;
289
290
  /**