UNPKG

@iden3/js-iden3-auth

Version:

iden3-auth implementation in JavaScript

179 lines (178 loc) 7.49 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isGenesisStateId = exports.EthStateResolver = void 0; const js_iden3_core_1 = require("@iden3/js-iden3-core"); const ethers_1 = require("ethers"); const js_sdk_1 = require("@0xpolygonid/js-sdk"); const ethers_contracts_1 = require("../state/types/ethers-contracts"); const constants_1 = require("../constants"); const js_sdk_2 = require("@0xpolygonid/js-sdk"); /** * Ethereum-based state resolver that resolves identity states and GIST roots * from a smart contract deployed on an Ethereum-compatible blockchain. * * This resolver caches results with different TTL values: * - Latest states/roots: shorter TTL since they can transition to historical * - Historical states/roots: longer TTL since they are immutable once replaced */ class EthStateResolver { /** * Creates a new EthStateResolver instance * @param rpcUrl - The RPC URL for the Ethereum-compatible blockchain * @param contractAddress - The address of the state contract * @param options - Optional configuration for caching and provider setup */ constructor(rpcUrl, contractAddress, options) { const url = new URL(rpcUrl); const ethersProvider = new ethers_1.ethers.providers.JsonRpcProvider({ skipFetchSetup: options?.skipFetchSetup ?? false, url: url.href, user: url.username || undefined, password: url.password || undefined }); this._contract = ethers_contracts_1.Abi__factory.connect(contractAddress, ethersProvider); // Store cache options for later use this._stateCacheOptions = { notReplacedTtl: options?.stateCacheOptions?.notReplacedTtl ?? constants_1.CONSTANTS.ACCEPTED_STATE_TRANSITION_DELAY / 2, replacedTtl: options?.stateCacheOptions?.replacedTtl ?? constants_1.CONSTANTS.ACCEPTED_STATE_TRANSITION_DELAY, maxSize: options?.stateCacheOptions?.maxSize ?? constants_1.CONSTANTS.DEFAULT_CACHE_MAX_SIZE }; this._rootCacheOptions = { replacedTtl: options?.rootCacheOptions?.replacedTtl ?? constants_1.CONSTANTS.ACCEPTED_STATE_TRANSITION_DELAY, notReplacedTtl: options?.rootCacheOptions?.notReplacedTtl ?? constants_1.CONSTANTS.ACCEPTED_STATE_TRANSITION_DELAY / 2, maxSize: options?.rootCacheOptions?.maxSize ?? constants_1.CONSTANTS.DEFAULT_CACHE_MAX_SIZE }; // Initialize cache instances this._stateResolveCache = options?.stateCacheOptions?.cache ?? (0, js_sdk_1.createInMemoryCache)({ maxSize: this._stateCacheOptions.maxSize, ttl: this._stateCacheOptions.replacedTtl }); this._rootResolveCache = options?.rootCacheOptions?.cache ?? (0, js_sdk_1.createInMemoryCache)({ maxSize: this._rootCacheOptions.maxSize, ttl: this._rootCacheOptions.replacedTtl }); } getCacheKey(id, state) { return `${id.toString()}-${state.toString()}`; } getRootCacheKey(root) { return root.toString(); } async resolve(id, state) { const cacheKey = this.getCacheKey(id, state); // Check cache first const cachedResult = await this._stateResolveCache?.get(cacheKey); if (cachedResult) { return cachedResult; } // Perform the actual resolution const result = await this.performResolve(id, state); // Cache the result with appropriate TTL based on whether it's latest or historical const ttl = result.transitionTimestamp === 0 ? this._stateCacheOptions.notReplacedTtl : this._stateCacheOptions.replacedTtl; await this._stateResolveCache?.set(cacheKey, result, ttl); return result; } async performResolve(id, state) { // check if id is genesis const isGenesis = isGenesisStateId(id, state); let contractState; try { contractState = await this._contract.getStateInfoByIdAndState(id, state); } catch (e) { if ((0, js_sdk_2.isStateDoesNotExistError)(e)) { if (isGenesis) { return { latest: true, genesis: isGenesis, state, transitionTimestamp: 0 }; } throw new Error('State is not genesis and not registered in the smart contract'); } throw e; } if (!contractState.id.eq(id)) { throw new Error(`state was recorded for another identity`); } if (!contractState.state.eq(state)) { if (contractState.replacedAtTimestamp.eq(0n)) { throw new Error(`no information about state transition`); } return { latest: false, genesis: false, state, transitionTimestamp: contractState.replacedAtTimestamp.toNumber() }; } return { latest: contractState.replacedAtTimestamp.isZero(), genesis: isGenesis, state, transitionTimestamp: contractState.replacedAtTimestamp.toNumber() }; } async rootResolve(root) { const cacheKey = this.getRootCacheKey(root); // Check cache first const cachedResult = await this._rootResolveCache?.get(cacheKey); if (cachedResult) { return cachedResult; } // Perform the actual root resolution const result = await this.performRootResolve(root); // Cache the result with appropriate TTL based on whether it's latest or historical const ttl = result.latest ? this._rootCacheOptions.notReplacedTtl : this._rootCacheOptions.replacedTtl; await this._rootResolveCache?.set(cacheKey, result, ttl); return result; } async performRootResolve(root) { let globalStateInfo; try { globalStateInfo = await this._contract.getGISTRootInfo(root); } catch (e) { if ((0, js_sdk_2.isRootDoesNotExistError)(e)) { throw new Error('GIST root does not exist in the smart contract'); } throw e; } if (!globalStateInfo.root.eq(root)) { throw new Error(`gist info contains invalid state`); } if (!globalStateInfo.replacedByRoot.eq(0n)) { if (globalStateInfo.replacedAtTimestamp.eq(0n)) { throw new Error(`state was replaced, but replaced time unknown`); } return { latest: false, state: root, transitionTimestamp: globalStateInfo.replacedAtTimestamp.toString(), genesis: false }; } return { latest: true, state: root, transitionTimestamp: 0, genesis: false }; } } exports.EthStateResolver = EthStateResolver; function isGenesisStateId(id, state) { const userID = js_iden3_core_1.Id.fromBigInt(id); const identifier = js_iden3_core_1.Id.idGenesisFromIdenState(userID.type(), state); return userID.equal(identifier); } exports.isGenesisStateId = isGenesisStateId;