@0xpolygonid/js-sdk 1.31.1 → 1.31.3

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.
Files changed (30) hide show
  1. package/dist/browser/esm/index.js +773 -321
  2. package/dist/browser/esm/index.js.map +1 -1
  3. package/dist/browser/umd/index.js +773 -319
  4. package/dist/browser/umd/index.js.map +1 -1
  5. package/dist/node/cjs/credentials/status/on-chain-revocation.js +3 -10
  6. package/dist/node/cjs/identity/identity-wallet.js +17 -4
  7. package/dist/node/cjs/storage/blockchain/onchain-zkp-verifier.js +0 -1
  8. package/dist/node/cjs/storage/blockchain/state.js +120 -3
  9. package/dist/node/cjs/storage/memory/cache-lru.js +25 -0
  10. package/dist/node/cjs/storage/memory/index.js +1 -0
  11. package/dist/node/cjs/storage/shared/identity-storage.js +7 -0
  12. package/dist/node/cjs/verifiable/constants.js +6 -1
  13. package/dist/node/esm/credentials/status/on-chain-revocation.js +4 -11
  14. package/dist/node/esm/identity/identity-wallet.js +17 -4
  15. package/dist/node/esm/storage/blockchain/onchain-zkp-verifier.js +0 -1
  16. package/dist/node/esm/storage/blockchain/state.js +120 -3
  17. package/dist/node/esm/storage/memory/cache-lru.js +18 -0
  18. package/dist/node/esm/storage/memory/index.js +1 -0
  19. package/dist/node/esm/storage/shared/identity-storage.js +7 -0
  20. package/dist/node/esm/verifiable/constants.js +5 -0
  21. package/dist/types/credentials/status/on-chain-revocation.d.ts +6 -2
  22. package/dist/types/identity/identity-wallet.d.ts +18 -3
  23. package/dist/types/storage/blockchain/state.d.ts +65 -1
  24. package/dist/types/storage/entities/identity.d.ts +1 -0
  25. package/dist/types/storage/interfaces/identity.d.ts +8 -1
  26. package/dist/types/storage/memory/cache-lru.d.ts +10 -0
  27. package/dist/types/storage/memory/index.d.ts +1 -0
  28. package/dist/types/storage/shared/identity-storage.d.ts +5 -0
  29. package/dist/types/verifiable/constants.d.ts +5 -0
  30. package/package.json +3 -2
@@ -21,8 +21,9 @@ class OnChainResolver {
21
21
  * @public
22
22
  * @param {Array<EthConnectionConfig>} _configs - list of ethereum network connections
23
23
  */
24
- constructor(_configs) {
24
+ constructor(_configs, _opts) {
25
25
  this._configs = _configs;
26
+ this._stateStorage = new state_1.EthStateStorage(_configs, _opts?.stateStorageOptions);
26
27
  }
27
28
  /**
28
29
  * resolve is a method to resolve a credential status from the blockchain.
@@ -53,8 +54,7 @@ class OnChainResolver {
53
54
  const issuerId = js_iden3_core_1.DID.idFromDID(issuer);
54
55
  let latestIssuerState;
55
56
  try {
56
- const ethStorage = this._getStateStorageForIssuer(issuerId);
57
- const latestStateInfo = await ethStorage.getLatestStateById(issuerId.bigInt());
57
+ const latestStateInfo = await this._stateStorage.getLatestStateById(issuerId.bigInt());
58
58
  if (!latestStateInfo.state) {
59
59
  throw new Error('state contract returned empty state');
60
60
  }
@@ -120,13 +120,6 @@ class OnChainResolver {
120
120
  }
121
121
  return network;
122
122
  }
123
- // TODO: is dirty hack for mock in tests.
124
- // need to pass to constructor list of state stores not list of network configs
125
- _getStateStorageForIssuer(issuerId) {
126
- const issuerChainId = (0, js_iden3_core_1.getChainId)(js_iden3_core_1.DID.blockchainFromId(issuerId), js_iden3_core_1.DID.networkIdFromId(issuerId));
127
- const ethStorage = new state_1.EthStateStorage(this.networkByChainId(issuerChainId));
128
- return ethStorage;
129
- }
130
123
  _getOnChainRevocationStorageForIssuer(chainId, contractAddress) {
131
124
  const networkConfig = this.networkByChainId(chainId);
132
125
  const onChainCaller = new onchain_revocation_1.OnChainRevocationStorage(networkConfig, contractAddress);
@@ -272,18 +272,23 @@ class IdentityWallet {
272
272
  return { nonce: profile.nonce, genesisDID: js_iden3_core_1.DID.parse(profile.genesisIdentifier) };
273
273
  }
274
274
  /** {@inheritDoc IIdentityWallet.createProfile} */
275
- async createProfile(did, nonce, verifier) {
275
+ async createProfile(did, nonce, verifier, tags) {
276
276
  const profileDID = (0, common_1.generateProfileDID)(did, nonce);
277
277
  const identityProfiles = await this._storage.identity.getProfilesByGenesisIdentifier(did.string());
278
- const existingProfile = identityProfiles.find((p) => p.nonce == nonce || p.verifier == verifier);
279
- if (existingProfile) {
278
+ const profilesForTagAndVerifier = await this._storage.identity.getProfilesByVerifier(verifier, tags);
279
+ if (profilesForTagAndVerifier.length) {
280
+ throw new Error(verifiable_1.VerifiableConstants.ERRORS.ID_WALLET_PROFILE_ALREADY_EXISTS_VERIFIER_TAGS);
281
+ }
282
+ const existingProfileWithNonce = identityProfiles.find((p) => p.nonce == nonce);
283
+ if (existingProfileWithNonce) {
280
284
  throw new Error(verifiable_1.VerifiableConstants.ERRORS.ID_WALLET_PROFILE_ALREADY_EXISTS);
281
285
  }
282
286
  await this._storage.identity.saveProfile({
283
287
  id: profileDID.string(),
284
288
  nonce,
285
289
  genesisIdentifier: did.string(),
286
- verifier
290
+ verifier,
291
+ tags
287
292
  });
288
293
  return profileDID;
289
294
  }
@@ -302,9 +307,17 @@ class IdentityWallet {
302
307
  const key = await this._kms.createKeyFromSeed(keyType, (0, js_crypto_1.getRandomBytes)(32));
303
308
  return key;
304
309
  }
310
+ /**
311
+ * @deprecated The method should not be used. It returns only one profile per verifier, which can potentially restrict business use cases
312
+ * {@inheritDoc IIdentityWallet.getProfileByVerifier}
313
+ */
305
314
  async getProfileByVerifier(verifier) {
306
315
  return this._storage.identity.getProfileByVerifier(verifier);
307
316
  }
317
+ /** {@inheritDoc IIdentityWallet.getProfilesByVerifier} */
318
+ async getProfilesByVerifier(verifier, tags) {
319
+ return this._storage.identity.getProfilesByVerifier(verifier, tags);
320
+ }
308
321
  /** {@inheritDoc IIdentityWallet.getDIDTreeModel} */
309
322
  async getDIDTreeModel(did) {
310
323
  const didStr = did.string();
@@ -7,7 +7,6 @@ exports.packEthIdentityProof = exports.OnChainZKPVerifier = exports.toTxDataArgs
7
7
  const ethers_1 = require("ethers");
8
8
  const iden3comm_1 = require("../../iden3comm");
9
9
  const ZkpVerifier_json_1 = __importDefault(require("./abi/ZkpVerifier.json"));
10
- //import { UniversalVerifier } from '@iden3/universal-verifier-v2-abi';
11
10
  const universal_verifier_v2_abi_1 = require("@iden3/universal-verifier-v2-abi");
12
11
  const blockchain_1 = require("../../blockchain");
13
12
  const js_iden3_core_1 = require("@iden3/js-iden3-core");
@@ -11,6 +11,9 @@ const State_json_1 = __importDefault(require("./abi/State.json"));
11
11
  const js_iden3_core_1 = require("@iden3/js-iden3-core");
12
12
  const blockchain_1 = require("../../blockchain");
13
13
  const common_1 = require("./common");
14
+ const memory_1 = require("../memory");
15
+ const iden3comm_1 = require("../../iden3comm");
16
+ const verifiable_1 = require("../../verifiable");
14
17
  exports.defaultEthConnectionConfig = {
15
18
  url: 'http://localhost:8545',
16
19
  defaultGasLimit: 600000,
@@ -36,15 +39,71 @@ class EthStateStorage {
36
39
  * Creates an instance of EthStateStorage.
37
40
  * @param {EthConnectionConfig} [ethConfig=defaultEthConnectionConfig]
38
41
  */
39
- constructor(ethConfig) {
42
+ constructor(ethConfig, options) {
40
43
  this.ethConfig = ethConfig;
44
+ this._disableCache = false;
41
45
  const config = Array.isArray(ethConfig) ? ethConfig[0] : ethConfig;
42
46
  this.provider = new ethers_1.JsonRpcProvider(config.url);
43
47
  this.stateContract = new ethers_1.Contract(config.contractAddress, State_json_1.default, this.provider);
44
48
  this._transactionService = new blockchain_1.TransactionService(this.getRpcProvider());
49
+ // Store cache options for later use
50
+ this._latestStateCacheOptions = {
51
+ ttl: options?.latestStateCacheOptions?.ttl ?? iden3comm_1.PROTOCOL_CONSTANTS.DEFAULT_PROOF_VERIFY_DELAY / 2,
52
+ maxSize: options?.latestStateCacheOptions?.maxSize ?? verifiable_1.DEFAULT_CACHE_MAX_SIZE
53
+ };
54
+ this._stateCacheOptions = {
55
+ notReplacedTtl: options?.stateCacheOptions?.notReplacedTtl ??
56
+ iden3comm_1.PROTOCOL_CONSTANTS.DEFAULT_PROOF_VERIFY_DELAY / 2,
57
+ replacedTtl: options?.stateCacheOptions?.replacedTtl ?? iden3comm_1.PROTOCOL_CONSTANTS.DEFAULT_PROOF_VERIFY_DELAY,
58
+ maxSize: options?.stateCacheOptions?.maxSize ?? verifiable_1.DEFAULT_CACHE_MAX_SIZE
59
+ };
60
+ this._rootCacheOptions = {
61
+ replacedTtl: options?.rootCacheOptions?.replacedTtl ?? iden3comm_1.PROTOCOL_CONSTANTS.DEFAULT_AUTH_VERIFY_DELAY,
62
+ notReplacedTtl: options?.rootCacheOptions?.notReplacedTtl ??
63
+ iden3comm_1.PROTOCOL_CONSTANTS.DEFAULT_AUTH_VERIFY_DELAY / 2,
64
+ maxSize: options?.rootCacheOptions?.maxSize ?? verifiable_1.DEFAULT_CACHE_MAX_SIZE
65
+ };
66
+ this._gistProofCacheOptions = {
67
+ ttl: iden3comm_1.PROTOCOL_CONSTANTS.DEFAULT_AUTH_VERIFY_DELAY / 2,
68
+ maxSize: options?.gistProofCacheOptions?.maxSize ?? verifiable_1.DEFAULT_CACHE_MAX_SIZE
69
+ };
70
+ // Initialize cache instances
71
+ this._latestStateResolveCache =
72
+ options?.latestStateCacheOptions?.cache ??
73
+ (0, memory_1.createInMemoryCache)({
74
+ maxSize: this._latestStateCacheOptions.maxSize,
75
+ ttl: this._latestStateCacheOptions.ttl
76
+ });
77
+ this._stateResolveCache =
78
+ options?.stateCacheOptions?.cache ??
79
+ (0, memory_1.createInMemoryCache)({
80
+ maxSize: this._stateCacheOptions.maxSize,
81
+ ttl: this._stateCacheOptions.replacedTtl
82
+ });
83
+ this._rootResolveCache =
84
+ options?.rootCacheOptions?.cache ??
85
+ (0, memory_1.createInMemoryCache)({
86
+ maxSize: this._rootCacheOptions.maxSize,
87
+ ttl: this._rootCacheOptions.replacedTtl
88
+ });
89
+ this._gistProofResolveCache =
90
+ options?.gistProofCacheOptions?.cache ??
91
+ (0, memory_1.createInMemoryCache)({
92
+ maxSize: this._gistProofCacheOptions.maxSize,
93
+ ttl: this._gistProofCacheOptions.ttl
94
+ });
95
+ this._disableCache = options?.disableCache ?? false;
45
96
  }
46
97
  /** {@inheritdoc IStateStorage.getLatestStateById} */
47
98
  async getLatestStateById(id) {
99
+ const cacheKey = this.getLatestStateCacheKey(id);
100
+ if (!this._disableCache) {
101
+ // Check cache first
102
+ const cachedResult = await this._latestStateResolveCache?.get(cacheKey);
103
+ if (cachedResult) {
104
+ return cachedResult;
105
+ }
106
+ }
48
107
  const { stateContract } = this.getStateContractAndProviderForId(id);
49
108
  const rawData = await stateContract.getStateInfoById(id);
50
109
  const stateInfo = {
@@ -56,10 +115,20 @@ class EthStateStorage {
56
115
  createdAtBlock: BigInt(rawData[5]),
57
116
  replacedAtBlock: BigInt(rawData[6])
58
117
  };
118
+ !this._disableCache &&
119
+ (await this._latestStateResolveCache?.set(cacheKey, stateInfo, this._latestStateCacheOptions.ttl));
59
120
  return stateInfo;
60
121
  }
61
122
  /** {@inheritdoc IStateStorage.getStateInfoByIdAndState} */
62
123
  async getStateInfoByIdAndState(id, state) {
124
+ const cacheKey = this.getStateCacheKey(id, state);
125
+ if (!this._disableCache) {
126
+ // Check cache first
127
+ const cachedResult = await this._stateResolveCache?.get(cacheKey);
128
+ if (cachedResult) {
129
+ return cachedResult;
130
+ }
131
+ }
63
132
  const { stateContract } = this.getStateContractAndProviderForId(id);
64
133
  const rawData = await stateContract.getStateInfoByIdAndState(id, state);
65
134
  const stateInfo = {
@@ -71,6 +140,10 @@ class EthStateStorage {
71
140
  createdAtBlock: BigInt(rawData[5]),
72
141
  replacedAtBlock: BigInt(rawData[6])
73
142
  };
143
+ const ttl = stateInfo.replacedAtTimestamp === 0n
144
+ ? this._stateCacheOptions.notReplacedTtl
145
+ : this._stateCacheOptions.replacedTtl;
146
+ !this._disableCache && (await this._stateResolveCache?.set(cacheKey, stateInfo, ttl));
74
147
  return stateInfo;
75
148
  }
76
149
  /** {@inheritdoc IStateStorage.publishState} */
@@ -143,9 +216,17 @@ class EthStateStorage {
143
216
  }
144
217
  /** {@inheritdoc IStateStorage.getGISTProof} */
145
218
  async getGISTProof(id) {
219
+ const cacheKey = this.getGistProofCacheKey(id);
220
+ if (!this._disableCache) {
221
+ // Check cache first
222
+ const cachedResult = await this._gistProofResolveCache?.get(cacheKey);
223
+ if (cachedResult) {
224
+ return cachedResult;
225
+ }
226
+ }
146
227
  const { stateContract } = this.getStateContractAndProviderForId(id);
147
228
  const data = await stateContract.getGISTProof(id);
148
- return {
229
+ const stateProof = {
149
230
  root: BigInt(data.root.toString()),
150
231
  existence: data.existence,
151
232
  siblings: data.siblings?.map((sibling) => BigInt(sibling.toString())),
@@ -155,12 +236,23 @@ class EthStateStorage {
155
236
  auxIndex: BigInt(data.auxIndex.toString()),
156
237
  auxValue: BigInt(data.auxValue.toString())
157
238
  };
239
+ !this._disableCache &&
240
+ (await this._gistProofResolveCache?.set(cacheKey, stateProof, this._gistProofCacheOptions.ttl));
241
+ return stateProof;
158
242
  }
159
243
  /** {@inheritdoc IStateStorage.getGISTRootInfo} */
160
244
  async getGISTRootInfo(root, id) {
245
+ const cacheKey = this.getRootCacheKey(root);
246
+ if (!this._disableCache) {
247
+ // Check cache first
248
+ const cachedResult = await this._rootResolveCache?.get(cacheKey);
249
+ if (cachedResult) {
250
+ return cachedResult;
251
+ }
252
+ }
161
253
  const { stateContract } = this.getStateContractAndProviderForId(id);
162
254
  const data = await stateContract.getGISTRootInfo(root);
163
- return {
255
+ const rootInfo = {
164
256
  root: BigInt(data.root.toString()),
165
257
  replacedByRoot: BigInt(data.replacedByRoot.toString()),
166
258
  createdAtTimestamp: BigInt(data.createdAtTimestamp.toString()),
@@ -168,11 +260,24 @@ class EthStateStorage {
168
260
  createdAtBlock: BigInt(data.createdAtBlock.toString()),
169
261
  replacedAtBlock: BigInt(data.replacedAtBlock.toString())
170
262
  };
263
+ const ttl = rootInfo.replacedAtTimestamp == 0n
264
+ ? this._rootCacheOptions.notReplacedTtl
265
+ : this._rootCacheOptions.replacedTtl;
266
+ !this._disableCache && (await this._rootResolveCache?.set(cacheKey, rootInfo, ttl));
267
+ return rootInfo;
171
268
  }
172
269
  /** {@inheritdoc IStateStorage.getRpcProvider} */
173
270
  getRpcProvider() {
174
271
  return this.provider;
175
272
  }
273
+ /** enable caching */
274
+ enableCache() {
275
+ this._disableCache = false;
276
+ }
277
+ /** disable caching */
278
+ disableCache() {
279
+ this._disableCache = true;
280
+ }
176
281
  getStateContractAndProviderForId(id) {
177
282
  const idTyped = js_iden3_core_1.Id.fromBigInt(id);
178
283
  const chainId = (0, js_iden3_core_1.getChainId)(js_iden3_core_1.DID.blockchainFromId(idTyped), js_iden3_core_1.DID.networkIdFromId(idTyped));
@@ -189,5 +294,17 @@ class EthStateStorage {
189
294
  }
190
295
  return network;
191
296
  }
297
+ getGistProofCacheKey(id) {
298
+ return `gist-${id.toString()}`;
299
+ }
300
+ getLatestStateCacheKey(id) {
301
+ return `latest-${id.toString()}`;
302
+ }
303
+ getStateCacheKey(id, state) {
304
+ return `${id.toString()}-${state.toString()}`;
305
+ }
306
+ getRootCacheKey(root) {
307
+ return root.toString();
308
+ }
192
309
  }
193
310
  exports.EthStateStorage = EthStateStorage;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createInMemoryCache = void 0;
7
+ const quick_lru_1 = __importDefault(require("quick-lru"));
8
+ function createInMemoryCache(params) {
9
+ const cache = new quick_lru_1.default({ maxSize: params.maxSize, maxAge: params.ttl });
10
+ return {
11
+ get: async (key) => {
12
+ return cache.get(key);
13
+ },
14
+ set: async (key, value, ttl) => {
15
+ cache.set(key, value, { maxAge: ttl ?? params.ttl });
16
+ },
17
+ clear: async () => {
18
+ cache.clear();
19
+ },
20
+ delete: async (key) => {
21
+ cache.delete(key);
22
+ }
23
+ };
24
+ }
25
+ exports.createInMemoryCache = createInMemoryCache;
@@ -16,3 +16,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./merkletree"), exports);
18
18
  __exportStar(require("./data-source"), exports);
19
+ __exportStar(require("./cache-lru"), exports);
@@ -27,9 +27,16 @@ class IdentityStorage {
27
27
  await this._profileDataSource.save(element.id, element);
28
28
  }
29
29
  }
30
+ /**
31
+ * @deprecated The method should not be used. It returns only one profile per verifier, which can potentially restrict business use cases
32
+ * Use getProfilesByVerifier instead.
33
+ */
30
34
  async getProfileByVerifier(verifier) {
31
35
  return this._profileDataSource.get(verifier, 'verifier');
32
36
  }
37
+ async getProfilesByVerifier(verifier, tags) {
38
+ return (await this._profileDataSource.load()).filter((p) => p.verifier === verifier && (!tags || tags.every((tag) => p.tags?.includes(tag))));
39
+ }
33
40
  async getProfileById(profileId) {
34
41
  return this._profileDataSource.get(profileId);
35
42
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DisplayMethodType = exports.PaymentFeatures = exports.SupportedCurrencies = exports.SupportedPaymentProofType = exports.PaymentType = exports.PaymentRequestDataType = exports.RefreshServiceType = exports.SubjectPosition = exports.MerklizedRootPosition = exports.ProofPurpose = exports.CredentialStatusType = exports.ProofType = exports.VerifiableConstants = void 0;
3
+ exports.DEFAULT_CACHE_MAX_SIZE = exports.DisplayMethodType = exports.PaymentFeatures = exports.SupportedCurrencies = exports.SupportedPaymentProofType = exports.PaymentType = exports.PaymentRequestDataType = exports.RefreshServiceType = exports.SubjectPosition = exports.MerklizedRootPosition = exports.ProofPurpose = exports.CredentialStatusType = exports.ProofType = exports.VerifiableConstants = void 0;
4
4
  /** Verifiable constants */
5
5
  exports.VerifiableConstants = Object.freeze({
6
6
  ERRORS: {
@@ -19,6 +19,7 @@ exports.VerifiableConstants = Object.freeze({
19
19
  ID_WALLET_CORE_CLAIM_IS_NOT_SET: 'core claim is not set in credential proofs',
20
20
  ID_WALLET_PROFILE_OR_IDENTITY_NOT_FOUND: 'profile or identity not found',
21
21
  ID_WALLET_PROFILE_ALREADY_EXISTS: 'profile with given nonce or verifier already exists',
22
+ ID_WALLET_PROFILE_ALREADY_EXISTS_VERIFIER_TAGS: 'profile with given verifier and tags already exists',
22
23
  ID_WALLET_ISSUER_AUTH_BJJ_CRED_MUST_HAVE_ANY_PROOF: 'issuer auth credential must have proof',
23
24
  ID_WALLET_ISSUER_AUTH_BJJ_CRED_MUST_HAVE_MTP_PROOF: 'mtp is required for auth bjj key to issue new credentials',
24
25
  // proof service
@@ -200,3 +201,7 @@ var DisplayMethodType;
200
201
  (function (DisplayMethodType) {
201
202
  DisplayMethodType["Iden3BasicDisplayMethodV1"] = "Iden3BasicDisplayMethodV1";
202
203
  })(DisplayMethodType = exports.DisplayMethodType || (exports.DisplayMethodType = {}));
204
+ /**
205
+ * Default cache max size for in-memory cache
206
+ */
207
+ exports.DEFAULT_CACHE_MAX_SIZE = 10000;
@@ -1,5 +1,5 @@
1
1
  import { OnChainRevocationStorage } from '../../storage/blockchain/onchain-revocation';
2
- import { DID, getChainId } from '@iden3/js-iden3-core';
2
+ import { DID } from '@iden3/js-iden3-core';
3
3
  import { VerifiableConstants } from '../../verifiable/constants';
4
4
  import { isGenesisState } from '../../utils';
5
5
  import { EthStateStorage } from '../../storage/blockchain/state';
@@ -18,8 +18,9 @@ export class OnChainResolver {
18
18
  * @public
19
19
  * @param {Array<EthConnectionConfig>} _configs - list of ethereum network connections
20
20
  */
21
- constructor(_configs) {
21
+ constructor(_configs, _opts) {
22
22
  this._configs = _configs;
23
+ this._stateStorage = new EthStateStorage(_configs, _opts?.stateStorageOptions);
23
24
  }
24
25
  /**
25
26
  * resolve is a method to resolve a credential status from the blockchain.
@@ -50,8 +51,7 @@ export class OnChainResolver {
50
51
  const issuerId = DID.idFromDID(issuer);
51
52
  let latestIssuerState;
52
53
  try {
53
- const ethStorage = this._getStateStorageForIssuer(issuerId);
54
- const latestStateInfo = await ethStorage.getLatestStateById(issuerId.bigInt());
54
+ const latestStateInfo = await this._stateStorage.getLatestStateById(issuerId.bigInt());
55
55
  if (!latestStateInfo.state) {
56
56
  throw new Error('state contract returned empty state');
57
57
  }
@@ -117,13 +117,6 @@ export class OnChainResolver {
117
117
  }
118
118
  return network;
119
119
  }
120
- // TODO: is dirty hack for mock in tests.
121
- // need to pass to constructor list of state stores not list of network configs
122
- _getStateStorageForIssuer(issuerId) {
123
- const issuerChainId = getChainId(DID.blockchainFromId(issuerId), DID.networkIdFromId(issuerId));
124
- const ethStorage = new EthStateStorage(this.networkByChainId(issuerChainId));
125
- return ethStorage;
126
- }
127
120
  _getOnChainRevocationStorageForIssuer(chainId, contractAddress) {
128
121
  const networkConfig = this.networkByChainId(chainId);
129
122
  const onChainCaller = new OnChainRevocationStorage(networkConfig, contractAddress);
@@ -246,18 +246,23 @@ export class IdentityWallet {
246
246
  return { nonce: profile.nonce, genesisDID: DID.parse(profile.genesisIdentifier) };
247
247
  }
248
248
  /** {@inheritDoc IIdentityWallet.createProfile} */
249
- async createProfile(did, nonce, verifier) {
249
+ async createProfile(did, nonce, verifier, tags) {
250
250
  const profileDID = generateProfileDID(did, nonce);
251
251
  const identityProfiles = await this._storage.identity.getProfilesByGenesisIdentifier(did.string());
252
- const existingProfile = identityProfiles.find((p) => p.nonce == nonce || p.verifier == verifier);
253
- if (existingProfile) {
252
+ const profilesForTagAndVerifier = await this._storage.identity.getProfilesByVerifier(verifier, tags);
253
+ if (profilesForTagAndVerifier.length) {
254
+ throw new Error(VerifiableConstants.ERRORS.ID_WALLET_PROFILE_ALREADY_EXISTS_VERIFIER_TAGS);
255
+ }
256
+ const existingProfileWithNonce = identityProfiles.find((p) => p.nonce == nonce);
257
+ if (existingProfileWithNonce) {
254
258
  throw new Error(VerifiableConstants.ERRORS.ID_WALLET_PROFILE_ALREADY_EXISTS);
255
259
  }
256
260
  await this._storage.identity.saveProfile({
257
261
  id: profileDID.string(),
258
262
  nonce,
259
263
  genesisIdentifier: did.string(),
260
- verifier
264
+ verifier,
265
+ tags
261
266
  });
262
267
  return profileDID;
263
268
  }
@@ -276,9 +281,17 @@ export class IdentityWallet {
276
281
  const key = await this._kms.createKeyFromSeed(keyType, getRandomBytes(32));
277
282
  return key;
278
283
  }
284
+ /**
285
+ * @deprecated The method should not be used. It returns only one profile per verifier, which can potentially restrict business use cases
286
+ * {@inheritDoc IIdentityWallet.getProfileByVerifier}
287
+ */
279
288
  async getProfileByVerifier(verifier) {
280
289
  return this._storage.identity.getProfileByVerifier(verifier);
281
290
  }
291
+ /** {@inheritDoc IIdentityWallet.getProfilesByVerifier} */
292
+ async getProfilesByVerifier(verifier, tags) {
293
+ return this._storage.identity.getProfilesByVerifier(verifier, tags);
294
+ }
282
295
  /** {@inheritDoc IIdentityWallet.getDIDTreeModel} */
283
296
  async getDIDTreeModel(did) {
284
297
  const didStr = did.string();
@@ -1,7 +1,6 @@
1
1
  import { JsonRpcProvider, Contract, ethers } from 'ethers';
2
2
  import { AuthMethod, processProofResponse } from '../../iden3comm';
3
3
  import abi from './abi/ZkpVerifier.json';
4
- //import { UniversalVerifier } from '@iden3/universal-verifier-v2-abi';
5
4
  import { IVerifierABI } from '@iden3/universal-verifier-v2-abi';
6
5
  import { TransactionService } from '../../blockchain';
7
6
  import { BytesHelper, chainIDfromDID, DID, Id } from '@iden3/js-iden3-core';
@@ -5,6 +5,9 @@ import abi from './abi/State.json';
5
5
  import { DID, getChainId, Id } from '@iden3/js-iden3-core';
6
6
  import { TransactionService } from '../../blockchain';
7
7
  import { prepareZkpProof } from './common';
8
+ import { createInMemoryCache } from '../memory';
9
+ import { PROTOCOL_CONSTANTS } from '../../iden3comm';
10
+ import { DEFAULT_CACHE_MAX_SIZE } from '../../verifiable';
8
11
  export /** @type {EthConnectionConfig} - default configuration for EthConnectionConfig */ const defaultEthConnectionConfig = {
9
12
  url: 'http://localhost:8545',
10
13
  defaultGasLimit: 600000,
@@ -30,15 +33,71 @@ export class EthStateStorage {
30
33
  * Creates an instance of EthStateStorage.
31
34
  * @param {EthConnectionConfig} [ethConfig=defaultEthConnectionConfig]
32
35
  */
33
- constructor(ethConfig) {
36
+ constructor(ethConfig, options) {
34
37
  this.ethConfig = ethConfig;
38
+ this._disableCache = false;
35
39
  const config = Array.isArray(ethConfig) ? ethConfig[0] : ethConfig;
36
40
  this.provider = new JsonRpcProvider(config.url);
37
41
  this.stateContract = new Contract(config.contractAddress, abi, this.provider);
38
42
  this._transactionService = new TransactionService(this.getRpcProvider());
43
+ // Store cache options for later use
44
+ this._latestStateCacheOptions = {
45
+ ttl: options?.latestStateCacheOptions?.ttl ?? PROTOCOL_CONSTANTS.DEFAULT_PROOF_VERIFY_DELAY / 2,
46
+ maxSize: options?.latestStateCacheOptions?.maxSize ?? DEFAULT_CACHE_MAX_SIZE
47
+ };
48
+ this._stateCacheOptions = {
49
+ notReplacedTtl: options?.stateCacheOptions?.notReplacedTtl ??
50
+ PROTOCOL_CONSTANTS.DEFAULT_PROOF_VERIFY_DELAY / 2,
51
+ replacedTtl: options?.stateCacheOptions?.replacedTtl ?? PROTOCOL_CONSTANTS.DEFAULT_PROOF_VERIFY_DELAY,
52
+ maxSize: options?.stateCacheOptions?.maxSize ?? DEFAULT_CACHE_MAX_SIZE
53
+ };
54
+ this._rootCacheOptions = {
55
+ replacedTtl: options?.rootCacheOptions?.replacedTtl ?? PROTOCOL_CONSTANTS.DEFAULT_AUTH_VERIFY_DELAY,
56
+ notReplacedTtl: options?.rootCacheOptions?.notReplacedTtl ??
57
+ PROTOCOL_CONSTANTS.DEFAULT_AUTH_VERIFY_DELAY / 2,
58
+ maxSize: options?.rootCacheOptions?.maxSize ?? DEFAULT_CACHE_MAX_SIZE
59
+ };
60
+ this._gistProofCacheOptions = {
61
+ ttl: PROTOCOL_CONSTANTS.DEFAULT_AUTH_VERIFY_DELAY / 2,
62
+ maxSize: options?.gistProofCacheOptions?.maxSize ?? DEFAULT_CACHE_MAX_SIZE
63
+ };
64
+ // Initialize cache instances
65
+ this._latestStateResolveCache =
66
+ options?.latestStateCacheOptions?.cache ??
67
+ createInMemoryCache({
68
+ maxSize: this._latestStateCacheOptions.maxSize,
69
+ ttl: this._latestStateCacheOptions.ttl
70
+ });
71
+ this._stateResolveCache =
72
+ options?.stateCacheOptions?.cache ??
73
+ createInMemoryCache({
74
+ maxSize: this._stateCacheOptions.maxSize,
75
+ ttl: this._stateCacheOptions.replacedTtl
76
+ });
77
+ this._rootResolveCache =
78
+ options?.rootCacheOptions?.cache ??
79
+ createInMemoryCache({
80
+ maxSize: this._rootCacheOptions.maxSize,
81
+ ttl: this._rootCacheOptions.replacedTtl
82
+ });
83
+ this._gistProofResolveCache =
84
+ options?.gistProofCacheOptions?.cache ??
85
+ createInMemoryCache({
86
+ maxSize: this._gistProofCacheOptions.maxSize,
87
+ ttl: this._gistProofCacheOptions.ttl
88
+ });
89
+ this._disableCache = options?.disableCache ?? false;
39
90
  }
40
91
  /** {@inheritdoc IStateStorage.getLatestStateById} */
41
92
  async getLatestStateById(id) {
93
+ const cacheKey = this.getLatestStateCacheKey(id);
94
+ if (!this._disableCache) {
95
+ // Check cache first
96
+ const cachedResult = await this._latestStateResolveCache?.get(cacheKey);
97
+ if (cachedResult) {
98
+ return cachedResult;
99
+ }
100
+ }
42
101
  const { stateContract } = this.getStateContractAndProviderForId(id);
43
102
  const rawData = await stateContract.getStateInfoById(id);
44
103
  const stateInfo = {
@@ -50,10 +109,20 @@ export class EthStateStorage {
50
109
  createdAtBlock: BigInt(rawData[5]),
51
110
  replacedAtBlock: BigInt(rawData[6])
52
111
  };
112
+ !this._disableCache &&
113
+ (await this._latestStateResolveCache?.set(cacheKey, stateInfo, this._latestStateCacheOptions.ttl));
53
114
  return stateInfo;
54
115
  }
55
116
  /** {@inheritdoc IStateStorage.getStateInfoByIdAndState} */
56
117
  async getStateInfoByIdAndState(id, state) {
118
+ const cacheKey = this.getStateCacheKey(id, state);
119
+ if (!this._disableCache) {
120
+ // Check cache first
121
+ const cachedResult = await this._stateResolveCache?.get(cacheKey);
122
+ if (cachedResult) {
123
+ return cachedResult;
124
+ }
125
+ }
57
126
  const { stateContract } = this.getStateContractAndProviderForId(id);
58
127
  const rawData = await stateContract.getStateInfoByIdAndState(id, state);
59
128
  const stateInfo = {
@@ -65,6 +134,10 @@ export class EthStateStorage {
65
134
  createdAtBlock: BigInt(rawData[5]),
66
135
  replacedAtBlock: BigInt(rawData[6])
67
136
  };
137
+ const ttl = stateInfo.replacedAtTimestamp === 0n
138
+ ? this._stateCacheOptions.notReplacedTtl
139
+ : this._stateCacheOptions.replacedTtl;
140
+ !this._disableCache && (await this._stateResolveCache?.set(cacheKey, stateInfo, ttl));
68
141
  return stateInfo;
69
142
  }
70
143
  /** {@inheritdoc IStateStorage.publishState} */
@@ -137,9 +210,17 @@ export class EthStateStorage {
137
210
  }
138
211
  /** {@inheritdoc IStateStorage.getGISTProof} */
139
212
  async getGISTProof(id) {
213
+ const cacheKey = this.getGistProofCacheKey(id);
214
+ if (!this._disableCache) {
215
+ // Check cache first
216
+ const cachedResult = await this._gistProofResolveCache?.get(cacheKey);
217
+ if (cachedResult) {
218
+ return cachedResult;
219
+ }
220
+ }
140
221
  const { stateContract } = this.getStateContractAndProviderForId(id);
141
222
  const data = await stateContract.getGISTProof(id);
142
- return {
223
+ const stateProof = {
143
224
  root: BigInt(data.root.toString()),
144
225
  existence: data.existence,
145
226
  siblings: data.siblings?.map((sibling) => BigInt(sibling.toString())),
@@ -149,12 +230,23 @@ export class EthStateStorage {
149
230
  auxIndex: BigInt(data.auxIndex.toString()),
150
231
  auxValue: BigInt(data.auxValue.toString())
151
232
  };
233
+ !this._disableCache &&
234
+ (await this._gistProofResolveCache?.set(cacheKey, stateProof, this._gistProofCacheOptions.ttl));
235
+ return stateProof;
152
236
  }
153
237
  /** {@inheritdoc IStateStorage.getGISTRootInfo} */
154
238
  async getGISTRootInfo(root, id) {
239
+ const cacheKey = this.getRootCacheKey(root);
240
+ if (!this._disableCache) {
241
+ // Check cache first
242
+ const cachedResult = await this._rootResolveCache?.get(cacheKey);
243
+ if (cachedResult) {
244
+ return cachedResult;
245
+ }
246
+ }
155
247
  const { stateContract } = this.getStateContractAndProviderForId(id);
156
248
  const data = await stateContract.getGISTRootInfo(root);
157
- return {
249
+ const rootInfo = {
158
250
  root: BigInt(data.root.toString()),
159
251
  replacedByRoot: BigInt(data.replacedByRoot.toString()),
160
252
  createdAtTimestamp: BigInt(data.createdAtTimestamp.toString()),
@@ -162,11 +254,24 @@ export class EthStateStorage {
162
254
  createdAtBlock: BigInt(data.createdAtBlock.toString()),
163
255
  replacedAtBlock: BigInt(data.replacedAtBlock.toString())
164
256
  };
257
+ const ttl = rootInfo.replacedAtTimestamp == 0n
258
+ ? this._rootCacheOptions.notReplacedTtl
259
+ : this._rootCacheOptions.replacedTtl;
260
+ !this._disableCache && (await this._rootResolveCache?.set(cacheKey, rootInfo, ttl));
261
+ return rootInfo;
165
262
  }
166
263
  /** {@inheritdoc IStateStorage.getRpcProvider} */
167
264
  getRpcProvider() {
168
265
  return this.provider;
169
266
  }
267
+ /** enable caching */
268
+ enableCache() {
269
+ this._disableCache = false;
270
+ }
271
+ /** disable caching */
272
+ disableCache() {
273
+ this._disableCache = true;
274
+ }
170
275
  getStateContractAndProviderForId(id) {
171
276
  const idTyped = Id.fromBigInt(id);
172
277
  const chainId = getChainId(DID.blockchainFromId(idTyped), DID.networkIdFromId(idTyped));
@@ -183,4 +288,16 @@ export class EthStateStorage {
183
288
  }
184
289
  return network;
185
290
  }
291
+ getGistProofCacheKey(id) {
292
+ return `gist-${id.toString()}`;
293
+ }
294
+ getLatestStateCacheKey(id) {
295
+ return `latest-${id.toString()}`;
296
+ }
297
+ getStateCacheKey(id, state) {
298
+ return `${id.toString()}-${state.toString()}`;
299
+ }
300
+ getRootCacheKey(root) {
301
+ return root.toString();
302
+ }
186
303
  }