@0xpolygonid/js-sdk 1.30.0 → 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 (44) hide show
  1. package/dist/browser/esm/index.js +972 -367
  2. package/dist/browser/esm/index.js.map +1 -1
  3. package/dist/browser/umd/index.js +975 -365
  4. package/dist/browser/umd/index.js.map +1 -1
  5. package/dist/node/cjs/credentials/credential-wallet.js +1 -1
  6. package/dist/node/cjs/credentials/status/on-chain-revocation.js +3 -10
  7. package/dist/node/cjs/iden3comm/handlers/common.js +51 -24
  8. package/dist/node/cjs/identity/identity-wallet.js +29 -16
  9. package/dist/node/cjs/proof/proof-service.js +5 -4
  10. package/dist/node/cjs/proof/verifiers/query.js +25 -1
  11. package/dist/node/cjs/schema-processor/json/validator.js +11 -0
  12. package/dist/node/cjs/storage/blockchain/onchain-zkp-verifier.js +0 -1
  13. package/dist/node/cjs/storage/blockchain/state.js +120 -3
  14. package/dist/node/cjs/storage/memory/cache-lru.js +25 -0
  15. package/dist/node/cjs/storage/memory/index.js +1 -0
  16. package/dist/node/cjs/storage/shared/identity-storage.js +7 -0
  17. package/dist/node/cjs/verifiable/constants.js +26 -2
  18. package/dist/node/cjs/verifiable/presentation.js +3 -3
  19. package/dist/node/esm/credentials/credential-wallet.js +1 -1
  20. package/dist/node/esm/credentials/status/on-chain-revocation.js +4 -11
  21. package/dist/node/esm/iden3comm/handlers/common.js +51 -24
  22. package/dist/node/esm/identity/identity-wallet.js +29 -16
  23. package/dist/node/esm/proof/proof-service.js +6 -5
  24. package/dist/node/esm/proof/verifiers/query.js +21 -0
  25. package/dist/node/esm/schema-processor/json/validator.js +11 -0
  26. package/dist/node/esm/storage/blockchain/onchain-zkp-verifier.js +0 -1
  27. package/dist/node/esm/storage/blockchain/state.js +120 -3
  28. package/dist/node/esm/storage/memory/cache-lru.js +18 -0
  29. package/dist/node/esm/storage/memory/index.js +1 -0
  30. package/dist/node/esm/storage/shared/identity-storage.js +7 -0
  31. package/dist/node/esm/verifiable/constants.js +25 -1
  32. package/dist/node/esm/verifiable/presentation.js +3 -3
  33. package/dist/types/credentials/status/on-chain-revocation.d.ts +6 -2
  34. package/dist/types/iden3comm/types/protocol/auth.d.ts +2 -2
  35. package/dist/types/identity/identity-wallet.d.ts +18 -3
  36. package/dist/types/proof/verifiers/query.d.ts +3 -0
  37. package/dist/types/storage/blockchain/state.d.ts +65 -1
  38. package/dist/types/storage/entities/identity.d.ts +1 -0
  39. package/dist/types/storage/interfaces/identity.d.ts +8 -1
  40. package/dist/types/storage/memory/cache-lru.d.ts +10 -0
  41. package/dist/types/storage/memory/index.d.ts +1 -0
  42. package/dist/types/storage/shared/identity-storage.d.ts +5 -0
  43. package/dist/types/verifiable/constants.d.ts +21 -0
  44. package/package.json +3 -2
@@ -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: {
@@ -8,7 +8,27 @@ exports.VerifiableConstants = Object.freeze({
8
8
  CONTEXT_TYPE_IS_EMPTY: 'ctxType is empty',
9
9
  // ErrStateNotFound issuer state is genesis state.
10
10
  IDENTITY_DOES_NOT_EXIST: 'Identity does not exist',
11
- NO_AUTH_CRED_FOUND: 'no auth credentials found'
11
+ NO_AUTH_CRED_FOUND: 'no auth credentials found',
12
+ // identity wallet
13
+ ID_WALLET_NO_CREDENTIAL_SATISFIED_QUERY: 'no credential satisfied query',
14
+ ID_WALLET_SIGNER_IS_REQUIRED: 'Ethereum signer is required to create Ethereum identities in order to transit state',
15
+ ID_WALLET_PROVER_IS_REQUIRED: 'prover is required to generate proofs for non ethereum identities',
16
+ ID_WALLET_CORE_CLAIM_REQUIRED_IN_SIG_PROOF: 'credential must have coreClaim representation in the signature proof',
17
+ ID_WALLET_CORE_CLAIM_REQUIRED_IN_ANY_PROOF: 'credential must have coreClaim representation in proofs',
18
+ ID_WALLET_CORE_CLAIM_MISMATCH: 'core claim representations is set in both proofs but they are not equal',
19
+ ID_WALLET_CORE_CLAIM_IS_NOT_SET: 'core claim is not set in credential proofs',
20
+ ID_WALLET_PROFILE_OR_IDENTITY_NOT_FOUND: 'profile or identity not found',
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',
23
+ ID_WALLET_ISSUER_AUTH_BJJ_CRED_MUST_HAVE_ANY_PROOF: 'issuer auth credential must have proof',
24
+ ID_WALLET_ISSUER_AUTH_BJJ_CRED_MUST_HAVE_MTP_PROOF: 'mtp is required for auth bjj key to issue new credentials',
25
+ // proof service
26
+ PROOF_SERVICE_NO_CREDENTIAL_FOR_IDENTITY_OR_PROFILE: 'no credentials belong to did or its profiles',
27
+ PROOF_SERVICE_NO_CREDENTIAL_FOR_QUERY: 'credential not found for query',
28
+ PROOF_SERVICE_PROFILE_GENESIS_DID_MISMATCH: 'subject and auth profiles are not derived from the same did',
29
+ PROOF_SERVICE_NO_QUERIES_IN_ZKP_REQUEST: 'no queries in zkp request',
30
+ // credential wallet
31
+ CREDENTIAL_WALLET_ALL_CREDENTIALS_ARE_REVOKED: 'all claims are revoked'
12
32
  },
13
33
  CREDENTIAL_TYPE: {
14
34
  // VerifiableCredential is a W3C verifiable credential type
@@ -181,3 +201,7 @@ var DisplayMethodType;
181
201
  (function (DisplayMethodType) {
182
202
  DisplayMethodType["Iden3BasicDisplayMethodV1"] = "Iden3BasicDisplayMethodV1";
183
203
  })(DisplayMethodType = exports.DisplayMethodType || (exports.DisplayMethodType = {}));
204
+ /**
205
+ * Default cache max size for in-memory cache
206
+ */
207
+ exports.DEFAULT_CACHE_MAX_SIZE = 10000;
@@ -47,12 +47,12 @@ const createVerifiablePresentation = (context, tp, credential, queries) => {
47
47
  }
48
48
  const skeleton = {
49
49
  '@context': baseContext,
50
- '@type': constants_1.VerifiableConstants.CREDENTIAL_TYPE.W3C_VERIFIABLE_PRESENTATION,
50
+ type: constants_1.VerifiableConstants.CREDENTIAL_TYPE.W3C_VERIFIABLE_PRESENTATION,
51
51
  verifiableCredential: {
52
52
  '@context': ldContext,
53
- '@type': vcTypes,
53
+ type: vcTypes,
54
54
  credentialSubject: {
55
- '@type': tp
55
+ type: tp
56
56
  }
57
57
  }
58
58
  };
@@ -170,6 +170,6 @@ export class CredentialWallet {
170
170
  }
171
171
  return { cred, revStatus };
172
172
  }
173
- throw new Error('all claims are revoked');
173
+ throw new Error(VerifiableConstants.ERRORS.CREDENTIAL_WALLET_ALL_CREDENTIALS_ARE_REVOKED);
174
174
  }
175
175
  }
@@ -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);
@@ -1,6 +1,7 @@
1
1
  import { getRandomBytes } from '@iden3/js-crypto';
2
2
  import { AuthMethod } from '../types';
3
3
  import { mergeObjects } from '../../utils';
4
+ import { VerifiableConstants } from '../../verifiable';
4
5
  import { getUnixTimestamp } from '@iden3/js-iden3-core';
5
6
  import { AcceptJwsAlgorithms, defaultAcceptProfile, MediaType } from '../constants';
6
7
  import { ethers } from 'ethers';
@@ -52,34 +53,60 @@ export const processZeroKnowledgeProofRequests = async (to, requests, from, proo
52
53
  const groupedCredentialsCache = new Map();
53
54
  const zkpResponses = [];
54
55
  for (const proofReq of requestScope) {
55
- if (!opts.supportedCircuits.includes(proofReq.circuitId)) {
56
- throw new Error(`Circuit ${proofReq.circuitId} is not allowed`);
57
- }
58
- const query = proofReq.query;
59
- const groupId = query.groupId;
60
- const combinedQueryData = combinedQueries.get(groupId);
61
- if (groupId) {
62
- if (!combinedQueryData) {
63
- throw new Error(`Invalid group id ${query.groupId}`);
56
+ let zkpRes;
57
+ try {
58
+ const isCircuitSupported = opts.supportedCircuits.includes(proofReq.circuitId);
59
+ if (!isCircuitSupported) {
60
+ if (proofReq.optional) {
61
+ continue;
62
+ }
63
+ throw new Error(`Circuit ${proofReq.circuitId} is not allowed`);
64
64
  }
65
- const combinedQuery = combinedQueryData.query;
66
- if (!groupedCredentialsCache.has(groupId)) {
67
- const credWithRevStatus = await proofService.findCredentialByProofQuery(to, combinedQueryData.query);
68
- if (!credWithRevStatus.cred) {
69
- throw new Error(`Credential not found for query ${JSON.stringify(combinedQuery)}`);
65
+ const query = proofReq.query;
66
+ const groupId = query.groupId;
67
+ const combinedQueryData = combinedQueries.get(groupId);
68
+ if (groupId) {
69
+ if (!combinedQueryData) {
70
+ throw new Error(`Invalid group id ${query.groupId}`);
71
+ }
72
+ const combinedQuery = combinedQueryData.query;
73
+ if (!groupedCredentialsCache.has(groupId)) {
74
+ const credWithRevStatus = await proofService.findCredentialByProofQuery(to, combinedQueryData.query);
75
+ if (!credWithRevStatus.cred) {
76
+ if (proofReq.optional) {
77
+ continue;
78
+ }
79
+ throw new Error(VerifiableConstants.ERRORS.PROOF_SERVICE_NO_CREDENTIAL_FOR_QUERY +
80
+ `${JSON.stringify(combinedQuery)}`);
81
+ }
82
+ groupedCredentialsCache.set(groupId, credWithRevStatus);
70
83
  }
71
- groupedCredentialsCache.set(groupId, credWithRevStatus);
72
84
  }
85
+ const credWithRevStatus = groupedCredentialsCache.get(groupId);
86
+ zkpRes = await proofService.generateProof(proofReq, to, {
87
+ verifierDid: from,
88
+ challenge: opts.challenge,
89
+ skipRevocation: Boolean(query.skipClaimRevocationCheck),
90
+ credential: credWithRevStatus?.cred,
91
+ credentialRevocationStatus: credWithRevStatus?.revStatus,
92
+ linkNonce: combinedQueryData?.linkNonce ? BigInt(combinedQueryData.linkNonce) : undefined
93
+ });
94
+ }
95
+ catch (error) {
96
+ const expectedErrors = [
97
+ VerifiableConstants.ERRORS.PROOF_SERVICE_NO_CREDENTIAL_FOR_IDENTITY_OR_PROFILE,
98
+ VerifiableConstants.ERRORS.ID_WALLET_NO_CREDENTIAL_SATISFIED_QUERY,
99
+ VerifiableConstants.ERRORS.CREDENTIAL_WALLET_ALL_CREDENTIALS_ARE_REVOKED
100
+ ];
101
+ // handle only errors in case credential is not found and it is optional proof request - otherwise throw
102
+ if (error instanceof Error &&
103
+ (expectedErrors.includes(error.message) ||
104
+ error.message.includes(VerifiableConstants.ERRORS.PROOF_SERVICE_NO_CREDENTIAL_FOR_QUERY)) &&
105
+ proofReq.optional) {
106
+ continue;
107
+ }
108
+ throw error;
73
109
  }
74
- const credWithRevStatus = groupedCredentialsCache.get(groupId);
75
- const zkpRes = await proofService.generateProof(proofReq, to, {
76
- verifierDid: from,
77
- challenge: opts.challenge,
78
- skipRevocation: Boolean(query.skipClaimRevocationCheck),
79
- credential: credWithRevStatus?.cred,
80
- credentialRevocationStatus: credWithRevStatus?.revStatus,
81
- linkNonce: combinedQueryData?.linkNonce ? BigInt(combinedQueryData.linkNonce) : undefined
82
- });
83
110
  zkpResponses.push(zkpRes);
84
111
  }
85
112
  return zkpResponses;
@@ -203,7 +203,7 @@ export class IdentityWallet {
203
203
  let credential;
204
204
  const ethSigner = opts.ethSigner;
205
205
  if (opts.createBjjCredential && !ethSigner) {
206
- throw new Error('Ethereum signer is required to create Ethereum identities in order to transit state');
206
+ throw new Error(VerifiableConstants.ERRORS.ID_WALLET_SIGNER_IS_REQUIRED);
207
207
  }
208
208
  const currentState = ZERO_HASH; // In Ethereum identities we don't have an initial state with the auth credential
209
209
  const didType = buildDIDType(opts.method || DidMethod.Iden3, opts.blockchain || Blockchain.Polygon, opts.networkId || NetworkId.Amoy);
@@ -241,23 +241,28 @@ export class IdentityWallet {
241
241
  }
242
242
  const profile = await this._storage.identity.getProfileById(did.string());
243
243
  if (!profile) {
244
- throw new Error('profile or identity not found');
244
+ throw new Error(VerifiableConstants.ERRORS.ID_WALLET_PROFILE_OR_IDENTITY_NOT_FOUND);
245
245
  }
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) {
254
- throw new Error('profile with given nonce or verifier already exists');
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) {
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();
@@ -400,11 +413,11 @@ export class IdentityWallet {
400
413
  const coreClaimHash = poseidon.hash([hi, hv]);
401
414
  const signature = await this.signChallenge(coreClaimHash, issuerAuthBJJCredential);
402
415
  if (!issuerAuthBJJCredential.proof) {
403
- throw new Error('issuer auth credential must have proof');
416
+ throw new Error(VerifiableConstants.ERRORS.ID_WALLET_ISSUER_AUTH_BJJ_CRED_MUST_HAVE_ANY_PROOF);
404
417
  }
405
418
  const mtpAuthBJJProof = issuerAuthBJJCredential.getIden3SparseMerkleTreeProof();
406
419
  if (!mtpAuthBJJProof) {
407
- throw new Error('mtp is required for auth bjj key to issue new credentials');
420
+ throw new Error(VerifiableConstants.ERRORS.ID_WALLET_ISSUER_AUTH_BJJ_CRED_MUST_HAVE_MTP_PROOF);
408
421
  }
409
422
  const sigProof = new BJJSignatureProof2021({
410
423
  issuerData: {
@@ -444,7 +457,7 @@ export class IdentityWallet {
444
457
  const issuerTree = await this.getDIDTreeModel(issuerDID);
445
458
  const coreClaim = await this.getCoreClaimFromCredential(credential);
446
459
  if (!coreClaim) {
447
- throw new Error('credential must have coreClaim representation in proofs');
460
+ throw new Error(VerifiableConstants.ERRORS.ID_WALLET_CORE_CLAIM_REQUIRED_IN_ANY_PROOF);
448
461
  }
449
462
  const nonce = coreClaim.getRevocationNonce();
450
463
  await issuerTree.revocationTree.add(nonce, BigInt(0));
@@ -467,7 +480,7 @@ export class IdentityWallet {
467
480
  // credential must have a bjj signature proof
468
481
  const coreClaim = credential.getCoreClaimFromProof(ProofType.BJJSignature);
469
482
  if (!coreClaim) {
470
- throw new Error('credential must have coreClaim representation in the signature proof');
483
+ throw new Error(VerifiableConstants.ERRORS.ID_WALLET_CORE_CLAIM_REQUIRED_IN_SIG_PROOF);
471
484
  }
472
485
  await this._storage.mt.addToMerkleTree(issuerDID.string(), MerkleTreeType.Claims, coreClaim.hIndex(), coreClaim.hValue());
473
486
  }
@@ -499,7 +512,7 @@ export class IdentityWallet {
499
512
  const coreClaim = credential.getCoreClaimFromProof(ProofType.BJJSignature) ||
500
513
  (await credential.toCoreClaim(opts));
501
514
  if (!coreClaim) {
502
- throw new Error('credential must have coreClaim representation in the signature proof');
515
+ throw new Error(VerifiableConstants.ERRORS.ID_WALLET_CORE_CLAIM_REQUIRED_IN_SIG_PROOF);
503
516
  }
504
517
  const mtpWithProof = await this.generateCoreClaimMtp(issuerDID, coreClaim, treeState);
505
518
  const mtpProof = new Iden3SparseMerkleTreeProof({
@@ -569,10 +582,10 @@ export class IdentityWallet {
569
582
  if (coreClaimFromMtpProof &&
570
583
  coreClaimFromSigProof &&
571
584
  coreClaimFromMtpProof.hex() !== coreClaimFromSigProof.hex()) {
572
- throw new Error('core claim representations is set in both proofs but they are not equal');
585
+ throw new Error(VerifiableConstants.ERRORS.ID_WALLET_CORE_CLAIM_MISMATCH);
573
586
  }
574
587
  if (!coreClaimFromMtpProof && !coreClaimFromSigProof) {
575
- throw new Error('core claim is not set in credential proofs');
588
+ throw new Error(VerifiableConstants.ERRORS.ID_WALLET_CORE_CLAIM_IS_NOT_SET);
576
589
  }
577
590
  //eslint-disable-next-line @typescript-eslint/no-non-null-assertion
578
591
  const coreClaim = coreClaimFromMtpProof ?? coreClaimFromSigProof;
@@ -581,7 +594,7 @@ export class IdentityWallet {
581
594
  async findOwnedCredentialsByDID(did, query) {
582
595
  const credentials = await this._credentialWallet.findByQuery(query);
583
596
  if (!credentials.length) {
584
- throw new Error(`no credential satisfied query`);
597
+ throw new Error(VerifiableConstants.ERRORS.ID_WALLET_NO_CREDENTIAL_SATISFIED_QUERY);
585
598
  }
586
599
  const { genesisDID } = await this.getGenesisDIDMetadata(did);
587
600
  const profiles = await this.getProfilesByDID(genesisDID);
@@ -621,7 +634,7 @@ export class IdentityWallet {
621
634
  let txId;
622
635
  if (!isEthIdentity) {
623
636
  if (!prover) {
624
- throw new Error('prover is required to generate proofs for non ethereum identities');
637
+ throw new Error(VerifiableConstants.ERRORS.ID_WALLET_PROVER_IS_REQUIRED);
625
638
  }
626
639
  // generate the proof
627
640
  const authInfo = await this._inputsGenerator.prepareAuthBJJCredential(did, oldTreeState);
@@ -1,6 +1,6 @@
1
1
  import { BytesHelper, DID, MerklizedRootPosition, getDateFromUnixTimestamp } from '@iden3/js-iden3-core';
2
2
  import { AuthV2Inputs, AuthV2PubSignals, CircuitId, Operators, Query, ValueProof } from '../circuits';
3
- import { createVerifiablePresentation } from '../verifiable';
3
+ import { createVerifiablePresentation, VerifiableConstants } from '../verifiable';
4
4
  import { parseCredentialSubject, parseQueryMetadata, toGISTProof, transformQueryValueToBigInts } from './common';
5
5
  import { NativeProver } from './provers/prover';
6
6
  import { getDocumentLoader } from '@iden3/js-jsonld-merklization';
@@ -73,7 +73,8 @@ export class ProofService {
73
73
  credentialWithRevStatus = { cred: opts.credential, revStatus };
74
74
  }
75
75
  if (!credentialWithRevStatus.cred) {
76
- throw new Error(`credential not found for query ${JSON.stringify(proofReq.query)}`);
76
+ throw new Error(VerifiableConstants.ERRORS.PROOF_SERVICE_NO_CREDENTIAL_FOR_QUERY +
77
+ ` ${JSON.stringify(proofReq.query)}`);
77
78
  }
78
79
  const credentialCoreClaim = await this._identityWallet.getCoreClaimFromCredential(credentialWithRevStatus.cred);
79
80
  const { nonce: authProfileNonce, genesisDID } = await this._identityWallet.getGenesisDIDMetadata(identifier);
@@ -85,11 +86,11 @@ export class ProofService {
85
86
  const subjectDID = DID.parse(preparedCredential.credential.credentialSubject['id']);
86
87
  const { nonce: credentialSubjectProfileNonce, genesisDID: subjectGenesisDID } = await this._identityWallet.getGenesisDIDMetadata(subjectDID);
87
88
  if (subjectGenesisDID.string() !== genesisDID.string()) {
88
- throw new Error('subject and auth profiles are not derived from the same did');
89
+ throw new Error(VerifiableConstants.ERRORS.PROOF_SERVICE_PROFILE_GENESIS_DID_MISMATCH);
89
90
  }
90
91
  const propertiesMetadata = parseCredentialSubject(proofReq.query.credentialSubject);
91
92
  if (!propertiesMetadata.length) {
92
- throw new Error('no queries in zkp request');
93
+ throw new Error(VerifiableConstants.ERRORS.PROOF_SERVICE_NO_QUERIES_IN_ZKP_REQUEST);
93
94
  }
94
95
  const mtPosition = preparedCredential.credentialCoreClaim.getMerklizedPosition();
95
96
  let mk;
@@ -273,7 +274,7 @@ export class ProofService {
273
274
  async findCredentialByProofQuery(did, query) {
274
275
  const credentials = await this._identityWallet.findOwnedCredentialsByDID(did, query);
275
276
  if (!credentials.length) {
276
- throw new Error(`no credentials belong to did or its profiles`);
277
+ throw new Error(VerifiableConstants.ERRORS.PROOF_SERVICE_NO_CREDENTIAL_FOR_IDENTITY_OR_PROFILE);
277
278
  }
278
279
  // For EQ / IN / NIN / LT / GT operations selective if credential satisfies query - we can get any.
279
280
  // TODO: choose credential for selective credentials
@@ -4,6 +4,7 @@ import { byteEncoder } from '../../utils';
4
4
  import { getOperatorNameByValue, Operators, QueryOperators } from '../../circuits/comparer';
5
5
  import { calculateCoreSchemaHash, VerifiableConstants } from '../../verifiable';
6
6
  import { circuitValidator } from '../provers';
7
+ import { ethers } from 'ethers';
7
8
  const defaultProofGenerationDelayOpts = 24 * 60 * 60 * 1000; // 24 hours
8
9
  export async function checkQueryRequest(query, queriesMetadata, ldContext, outputs, circuitId, schemaLoader, opts) {
9
10
  // validate issuer
@@ -197,3 +198,23 @@ export const fieldValueFromVerifiablePresentation = async (fieldName, verifiable
197
198
  }
198
199
  return await value.mtEntry();
199
200
  };
201
+ export function calculateGroupId(requestIds) {
202
+ const types = Array(requestIds.length).fill('uint256');
203
+ const groupID = BigInt(ethers.keccak256(ethers.solidityPacked(types, requestIds))) &
204
+ // It should fit in a field number in the circuit (max 253 bits). With this we truncate to 252 bits for the group ID
205
+ BigInt('0x0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF');
206
+ return groupID;
207
+ }
208
+ export function calculateRequestId(requestParams, creatorAddress) {
209
+ // 0x0000000000000000FFFF...FF. Reserved first 8 bytes for the request Id type and future use
210
+ // 0x00010000000000000000...00. First 2 bytes for the request Id type
211
+ // - 0x0000... for old request Ids with uint64
212
+ // - 0x0001... for new request Ids with uint256
213
+ const requestId = (BigInt(ethers.keccak256(ethers.solidityPacked(['bytes', 'address'], [requestParams, creatorAddress]))) &
214
+ BigInt('0x0000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF')) +
215
+ BigInt('0x0001000000000000000000000000000000000000000000000000000000000000');
216
+ return requestId;
217
+ }
218
+ export function calculateMultiRequestId(requestIds, groupIds, creatorAddress) {
219
+ return BigInt(ethers.keccak256(ethers.solidityPacked(['uint256[]', 'uint256[]', 'address'], [requestIds, groupIds, creatorAddress])));
220
+ }
@@ -38,6 +38,7 @@ export class JsonSchemaValidator {
38
38
  validator = ajv ?? defaultJSONSchemaValidator;
39
39
  if (validator.formats && !Object.keys(validator.formats).length) {
40
40
  addFormats(validator);
41
+ addCustomFormats(validator);
41
42
  }
42
43
  const validate = (schema.$id ? validator.getSchema(schema.$id) : undefined) || validator.compile(schema);
43
44
  const valid = validate(data);
@@ -48,3 +49,13 @@ export class JsonSchemaValidator {
48
49
  return true;
49
50
  }
50
51
  }
52
+ function addCustomFormats(validator) {
53
+ validator.addFormat('positive-integer', {
54
+ type: 'string',
55
+ validate: (positiveIntegerStr) => /^[1-9]\d*$/.test(positiveIntegerStr)
56
+ });
57
+ validator.addFormat('non-negative-integer', {
58
+ type: 'string',
59
+ validate: (nonNegativeIntegerStr) => /^(0|[1-9]\d*)$/.test(nonNegativeIntegerStr)
60
+ });
61
+ }
@@ -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
  }