UNPKG

@unirep/core

Version:

Client library for protocol related functions which are used in UniRep protocol.

820 lines (819 loc) 39.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.UserState = void 0; const anondb_1 = require("anondb"); const utils_1 = require("@unirep/utils"); const poseidon_lite_1 = require("poseidon-lite"); const circuits_1 = require("@unirep/circuits"); const Synchronizer_1 = require("./Synchronizer"); const userSchema_1 = require("./userSchema"); const web_1 = require("anondb/web"); /** * The user state object is used to manage user state for an attester. * The state is backed by an [anondb](https://github.com/vimwitch/anondb) instance. * @example * ```ts * import { UserState } from '@unirep/core' * import { defaultProver } from '@unirep/circuits/provers/defaultProver' * import { Identity } from '@semaphore-protocol/identity' * * const id = new Identity() * const state = new UserState({ * prover: defaultProver, // a circuit prover * unirepAddress: '0xaabbccaabbccaabbccaabbccaabbccaabbccaaaa', * provider, // an ethers.js provider * id, * }) * * // or, initialize with an existing synchronizer object * const state = new UserState({ * synchronizer, * id, * prover: defaultProver, // a circuit prover * }) * * // start the synchoronizer deamon * await state.start() * await state.waitForSync() * * // stop the synchronizer deamon * state.stop() * ``` */ class UserState { /** * The [Semaphore](https://semaphore.pse.dev/) identity commitment of the user. */ get commitment() { return this.id.commitment; } /** * The [Semaphore](https://semaphore.pse.dev/) identity of the user. */ get id() { return this._id; } /** * The underlying synchronizer object. */ get sync() { return this._sync; } /** * The prover object. */ get prover() { return this._prover; } /** * The current chain ID of UniRep contract. */ get chainId() { return this._chainId; } /** * Read the database object. */ get db() { return this._db; } constructor(config) { var _a; /** * @private * Update user state data. * @param data The latest transitioned data. * @param latestTransitionedEpoch The latest found transitioned epoch. * @param attesterId The attester ID. */ this._updateData = async (data, latestTransitionedEpoch, attesterId) => { const _attesterId = (0, Synchronizer_1.toDecString)(attesterId); const leaf = (0, utils_1.genStateTreeLeaf)(this.id.secret, _attesterId, latestTransitionedEpoch, data, this.chainId); const foundLeaf = await this.db.findOne('StateTreeLeaf', { where: { epoch: latestTransitionedEpoch, hash: leaf.toString(), }, }); if (!foundLeaf) throw new Error('@unirep/core:UserState unable to find state tree leaf index'); const parsedData = JSON.parse(`{${data.map((v, i) => `"${i}": "${v}"`).join(',')}}`); await this.db.upsert('UserState', { where: { attesterId: _attesterId, }, update: { latestTransitionedEpoch, data: parsedData, latestTransitionedIndex: foundLeaf.index, }, create: { attesterId: _attesterId, latestTransitionedEpoch, data: parsedData, latestTransitionedIndex: foundLeaf.index, }, }); }; /** * Get the data for a user up to and including the provided epoch. * By default data up to and including the current epoch is returned. * * :::tip * If you want to make a proof of data make sure to use `getProvableData`. * Data can only be proven once it has been included in a state tree leaf. * Learn more about reputation proofs [here](https://developer.unirep.io/docs/circuits-api/classes/src.ReputationProof.md). * ::: * @param toEpoch The latest epoch that the reputation is accumulated. Default: current epoch. * @param attesterId The attester to be queried. Default: `this.sync.attesterId` * @param fromEpoch The epoch to start querying from. Default: `0`. * @returns The data object */ this.getData = async (toEpoch, attesterId = this.sync.attesterId) => { var _a; const _attesterId = (0, Synchronizer_1.toDecString)(attesterId); const _toEpoch = toEpoch !== null && toEpoch !== void 0 ? toEpoch : (await this.latestTransitionedEpoch(_attesterId)); let savedData = undefined; try { savedData = await this.db.findOne('UserState', { where: { attesterId: _attesterId, }, }); } catch (_) { } const data = new Array(this.sync.settings.fieldCount).fill(BigInt(0)); if (savedData) { for (let i = 0; i < data.length; i++) { data[i] = BigInt(savedData.data[`${i}`]); } } const orClauses = []; const signup = await this.db.findOne('UserSignUp', { where: { commitment: this.commitment.toString(), attesterId: _attesterId, }, }); if (signup) { orClauses.push({ epochKey: signup.commitment, epoch: utils_1.MAX_EPOCH, }); } const transitionedEpoch = (_a = savedData === null || savedData === void 0 ? void 0 : savedData.latestTransitionedEpoch) !== null && _a !== void 0 ? _a : signup === null || signup === void 0 ? void 0 : signup.epoch; const allNullifiers = []; const epochs = await this.db.findMany('StateTreeLeaf', { where: { AND: [ { epoch: { gte: signup === null || signup === void 0 ? void 0 : signup.epoch }, }, { epoch: { lte: _toEpoch }, }, ], }, orderBy: { epoch: 'asc', }, }); for (const { epoch: x } of epochs) { allNullifiers.push(...[0, this.sync.settings.numEpochKeyNoncePerEpoch].map((v) => (0, utils_1.genEpochKey)(this.id.secret, _attesterId, x, v, this.chainId).toString())); } const sortedNullifiers = await this.db.findMany('Nullifier', { where: { attesterId: _attesterId, nullifier: allNullifiers, }, orderBy: { epoch: 'asc', }, }); const validEpochs = await this.db.findMany('StateTreeLeaf', { where: { AND: [ { epoch: { gte: transitionedEpoch }, }, { epoch: { lte: _toEpoch }, }, ], }, orderBy: { epoch: 'asc', }, }); let latestTransitionedEpoch = transitionedEpoch; for (const { epoch: x } of validEpochs) { const epks = Array(this.sync.settings.numEpochKeyNoncePerEpoch) .fill(null) .map((_, i) => (0, utils_1.genEpochKey)(this.id.secret, _attesterId, x, i, this.chainId).toString()); let usted = false; for (const { epoch } of sortedNullifiers) { if (epoch > x) { break; } if (epoch === x) { usted = true; break; } } if (!usted && x !== (signup === null || signup === void 0 ? void 0 : signup.epoch)) continue; orClauses.push({ epochKey: epks, epoch: x, }); latestTransitionedEpoch = x; } if (orClauses.length === 0) return data; const attestations = await this.db.findMany('Attestation', { where: { OR: orClauses, attesterId: _attesterId, }, orderBy: { index: 'asc', }, }); let transitionedData = [...data]; for (const a of attestations) { const { fieldIndex } = a; let currentNonce = BigInt(-1); if (fieldIndex < this.sync.settings.sumFieldCount) { data[fieldIndex] = (data[fieldIndex] + BigInt(a.change)) % utils_1.F; } else { const { nonce } = this.parseReplData(BigInt(a.change)); if (nonce > currentNonce) { data[fieldIndex] = BigInt(a.change); currentNonce = nonce; } } const { epoch } = a; if (epoch < latestTransitionedEpoch) { transitionedData = [...data]; } } if (latestTransitionedEpoch !== (signup === null || signup === void 0 ? void 0 : signup.epoch)) { await this._updateData(transitionedData, latestTransitionedEpoch, _attesterId); } return data; }; /** * Get the pending changes to the data owned by an epoch key. * @param epochKey The epoch key to be queried. * @param epoch The epoch of the epoch key to be queried. * @param attesterId The attester to be queried. Default: `this.attesterId` * @returns The data object. */ this.getDataByEpochKey = async (epochKey, epoch, attesterId = this.sync.attesterId) => { this._checkSync(); const _attesterId = (0, Synchronizer_1.toDecString)(attesterId); this.sync.checkAttesterId(_attesterId); const data = Array(this.sync.settings.fieldCount).fill(BigInt(0)); if (typeof epoch !== 'number') throw new Error('epoch must be number'); const attestations = await this.db.findMany('Attestation', { where: { epoch, epochKey: epochKey.toString(), attesterId: _attesterId, }, orderBy: { index: 'asc', }, }); for (const a of attestations) { const { fieldIndex } = a; let currentNonce = BigInt(-1); if (fieldIndex < this.sync.settings.sumFieldCount) { data[fieldIndex] = (data[fieldIndex] + BigInt(a.change)) % utils_1.F; } else { const { nonce } = this.parseReplData(BigInt(a.change)); if (nonce > currentNonce) { data[fieldIndex] = BigInt(a.change); currentNonce = nonce; } } } return data; }; /** * @private * Check if epoch key nonce is valid. Throws an error if the epoch key nonce is invalid. * @param epochKeyNonce The input epoch key nonce to be checked. */ this._checkEpkNonce = (epochKeyNonce) => { if (epochKeyNonce >= this.sync.settings.numEpochKeyNoncePerEpoch || epochKeyNonce < 0) throw new Error(`@unirep/core:UserState: epochKeyNonce (${epochKeyNonce}) must be less than max epoch nonce and not negative`); }; /** * @private * Check if a synchronizer is set. Throws an error if a synchronizer is not set. */ this._checkSync = () => { if (!this.sync) throw new Error('@unirep/core:UserState: no synchronizer is set'); }; /** * @private * Check if a chain ID is set. If a chain ID is not set, it queries the provider and sets chain ID. */ this._checkChainId = async () => { if (this.chainId === -1) { const { chainId } = await this.sync.provider.getNetwork(); this._chainId = chainId; } }; /** * @private * Check if the input exceeds the maximum bit * @param input The input value * @param bit The bit number */ this._checkBit = (input, bit) => { if (input >= BigInt(1) << BigInt(bit)) { throw new Error(`@unirep/core:UserState: input ${input} exceeds the maximum bit ${bit}`); } }; /** * Get the index of epoch key among all attestations. * @param epoch The epoch of the epoch key to be queried. * @param epochKey The epoch key to be queried. * @param attesterId The attester to be queried. * @returns The index of the epoch key. */ this.getEpochKeyIndex = async (epoch, epochKey, attesterId) => { this._checkSync(); const attestations = await this.db.findMany('Attestation', { where: { epoch, attesterId: (0, Synchronizer_1.toDecString)(attesterId), }, orderBy: { index: 'asc', }, }); let index = 0; const seenEpochKeys = {}; const inputEpochKey = epochKey; for (const { epochKey } of attestations) { if (seenEpochKeys[epochKey]) continue; if (BigInt(epochKey) === BigInt(inputEpochKey)) { return index; } seenEpochKeys[epochKey] = true; index++; } return -1; }; /** * Generate a user state transition proof. Returns a [`UserStateTransitionProof`](https://developer.unirep.io/docs/circuits-api/classes/src.UserStateTransitionProof.md). * @param options.toEpoch `toEpoch` is used to indicate in which epoch the proof will be used. Default: current epoch. * @param options.attesterId `attesterId` is used to generate proof for certain attester. Default: `this.attesterId`. * @returns The `UserStateTransitionProof` object. * @example * ```ts * const { publicSignals, proof } = await userState.genUserStateTransitionProof() * ``` */ this.genUserStateTransitionProof = async (options = {}) => { var _a; await this._checkChainId(); const { toEpoch: _toEpoch } = options; const attesterId = (0, Synchronizer_1.toDecString)((_a = options.attesterId) !== null && _a !== void 0 ? _a : this.sync.attesterId); const fromEpoch = await this.latestTransitionedEpoch(attesterId); const data = await this.getData(fromEpoch - 1, attesterId); const toEpoch = _toEpoch !== null && _toEpoch !== void 0 ? _toEpoch : this.sync.calcCurrentEpoch(attesterId); if (fromEpoch === toEpoch) { throw new Error('@unirep/core:UserState: Cannot transition to same epoch'); } const epochTree = await this.sync.genEpochTree(fromEpoch, attesterId); const stateTree = await this.sync.genStateTree(fromEpoch, attesterId); const epochKeys = Array(this.sync.settings.numEpochKeyNoncePerEpoch) .fill(null) .map((_, i) => (0, utils_1.genEpochKey)(this.id.secret, attesterId, fromEpoch, i, this.chainId)); const historyTree = await this.sync.genHistoryTree(attesterId); const leafHash = (0, poseidon_lite_1.poseidon2)([stateTree.root, epochTree.root]); const leaf = await this.db.findOne('HistoryTreeLeaf', { where: { attesterId, leaf: leafHash.toString(), }, }); let historyTreeProof; if (leaf) { historyTreeProof = historyTree.createProof(leaf.index); } else { // the epoch hasn't been ended onchain yet // add the leaf offchain to make the proof const leafCount = await this.db.count('HistoryTreeLeaf', { attesterId, }); historyTree.insert(leafHash); historyTreeProof = historyTree.createProof(leafCount); } const epochKeyLeafIndices = await Promise.all(epochKeys.map(async (epk) => this.getEpochKeyIndex(fromEpoch, epk, attesterId))); const epochKeyRep = await Promise.all(epochKeys.map(async (epochKey, i) => { const newData = await this.getDataByEpochKey(epochKey, fromEpoch, attesterId); const hasChanges = newData.reduce((acc, obj) => { return acc || obj != BigInt(0); }, false); const proof = epochKeyLeafIndices[i] !== -1 ? epochTree.createProof(epochKeyLeafIndices[i]) : { pathIndices: Array(this.sync.settings.epochTreeDepth).fill(0), siblings: Array(this.sync.settings.epochTreeDepth).fill(0), }; return { epochKey, hasChanges, newData, proof }; })); const latestLeafIndex = await this.latestStateTreeLeafIndex(fromEpoch, attesterId); const stateTreeProof = stateTree.createProof(latestLeafIndex); const circuitInputs = { from_epoch: fromEpoch, to_epoch: toEpoch, identity_secret: this.id.secret, state_tree_indices: stateTreeProof.pathIndices, state_tree_elements: stateTreeProof.siblings, attester_id: attesterId.toString(), history_tree_indices: historyTreeProof.pathIndices, history_tree_elements: historyTreeProof.siblings, data, new_data: epochKeyRep.map(({ newData }) => newData), epoch_tree_elements: epochKeyRep.map(({ proof }) => proof.siblings), epoch_tree_indices: epochKeyRep.map(({ proof }) => proof.pathIndices), epoch_tree_root: epochTree.root, chain_id: this.chainId, }; const results = await this.prover.genProofAndPublicSignals(circuits_1.Circuit.userStateTransition, (0, utils_1.stringifyBigInts)(circuitInputs)); return new circuits_1.UserStateTransitionProof(results.publicSignals, results.proof, this.prover); }; /** * Generate a proof of reputation. Returns a [`ReputationProof`](https://developer.unirep.io/docs/circuits-api/classes/src.ReputationProof.md). * :::danger * **Please avoid assigning the `minRep = data[0] - data[1]` or `maxRep = data[1] - data[0]`.**<br/> * The proof could allow a user to accidentally publish their overall reputation (i.e. `data[0]-data[1]`). * Depending on the circumstances (such as the length of the attestation history) this could reveal a user’s epoch key(s) as well. * ::: * @param options.epkNonce The nonce determines the output of the epoch key. Default: `0`. * @param options.minRep The amount of reputation that user wants to prove. It should satisfy: `posRep - negRep >= minRep`. Default: `0` * @param options.maxRep The amount of reputation that user wants to prove. It should satisfy: `negRep - posRep >= maxRep`. Default: `0` * @param options.graffiti The graffiti that user wants to prove. It should satisfy: `graffiti == (data[SUM_FIELD_COUNT] / (2 ** REPL_NONCE_BITS))`. Default: `0`. * @param options.proveZeroRep Indicates if user wants to prove `posRep - negRep == 0`. Default: `0`. * @param options.revealNonce Indicates if user wants to reveal epoch key nonce. Default: `false`. * @param options.data Indicates if user wants to endorse a 253-bits data. Default: `0`. * @param options.attesterId `attesterId` is used to generate proof for certain attester. Default: `this.attesterId` * @returns The reputation proof of type `ReputationProof`. * @example * ```ts * const {publicSignals, proof} = await userState.genProveReputationProof({ * minRep: 3, * graffiti: '1234', * }) * ``` */ this.genProveReputationProof = async (options) => { var _a, _b, _c; await this._checkChainId(); const { minRep, maxRep, graffiti, proveZeroRep, revealNonce } = options; this._checkBit(BigInt(minRep !== null && minRep !== void 0 ? minRep : 0), Number(utils_1.REP_BITS)); this._checkBit(BigInt(maxRep !== null && maxRep !== void 0 ? maxRep : 0), Number(utils_1.REP_BITS)); const nonce = (_a = options.epkNonce) !== null && _a !== void 0 ? _a : 0; const attesterId = (0, Synchronizer_1.toDecString)((_b = options.attesterId) !== null && _b !== void 0 ? _b : this.sync.attesterId); this._checkEpkNonce(nonce); const epoch = await this.latestTransitionedEpoch(attesterId); const leafIndex = await this.latestStateTreeLeafIndex(epoch, attesterId); const data = await this.getData(epoch - 1, attesterId); const stateTree = await this.sync.genStateTree(epoch, attesterId); const stateTreeProof = stateTree.createProof(leafIndex); const circuitInputs = { identity_secret: this.id.secret, state_tree_indices: stateTreeProof.pathIndices, state_tree_elements: stateTreeProof.siblings, data, prove_graffiti: graffiti ? 1 : 0, graffiti: BigInt(graffiti !== null && graffiti !== void 0 ? graffiti : 0), reveal_nonce: revealNonce !== null && revealNonce !== void 0 ? revealNonce : 0, attester_id: attesterId, epoch, nonce, min_rep: minRep !== null && minRep !== void 0 ? minRep : 0, max_rep: maxRep !== null && maxRep !== void 0 ? maxRep : 0, prove_min_rep: !!(minRep !== null && minRep !== void 0 ? minRep : 0) ? 1 : 0, prove_max_rep: !!(maxRep !== null && maxRep !== void 0 ? maxRep : 0) ? 1 : 0, prove_zero_rep: proveZeroRep !== null && proveZeroRep !== void 0 ? proveZeroRep : 0, sig_data: (_c = options.data) !== null && _c !== void 0 ? _c : 0, chain_id: this.chainId, }; const results = await this.prover.genProofAndPublicSignals(circuits_1.Circuit.reputation, (0, utils_1.stringifyBigInts)(circuitInputs)); return new circuits_1.ReputationProof(results.publicSignals, results.proof, this.prover); }; /** * Generate a proof that can be used to signup. Returns a [`SignupProof`](https://developer.unirep.io/docs/circuits-api/classes/src.SignupProof.md) * @param options.epoch Indicates in which epoch the proof will be used. Default: current epoch. * @param options.attesterId Indicates for which attester the proof will be used. Default: `this.attesterId` * @returns The sign up proof of type `SignUpProof`. * @example * ```ts * const { publicSignals, proof } = await userState.genUserSignUpProof() * ``` */ this.genUserSignUpProof = async (options = {}) => { var _a, _b; await this._checkChainId(); const attesterId = (0, Synchronizer_1.toDecString)((_a = options.attesterId) !== null && _a !== void 0 ? _a : this.sync.attesterId); const epoch = (_b = options.epoch) !== null && _b !== void 0 ? _b : this.sync.calcCurrentEpoch(attesterId); const circuitInputs = { epoch, identity_secret: this.id.secret, attester_id: attesterId, chain_id: this.chainId, }; const results = await this.prover.genProofAndPublicSignals(circuits_1.Circuit.signup, (0, utils_1.stringifyBigInts)(circuitInputs)); return new circuits_1.SignupProof(results.publicSignals, results.proof, this.prover); }; /** * Generate a proof that a user controls an epoch key in a certain epoch. * Optionally provide a data value to sign. * Returns an [`EpochKeyProof`](https://developer.unirep.io/docs/circuits-api/classes/src.EpochKeyProof.md). * @param options.nonce The specified epoch key nonce. Default: `0`. * @param options.epoch The specified epoch. Default: current epoch. * @param options.data Indicates if user wants to endorse a 253-bits data. Default: `0` * @param options.revealNonce Indicates if user wants to reveal epoch key nonce. Default: `false`. * @param options.attesterId Indicates for which attester the proof will be used. Default: `this.attesterId` * @returns The epoch key proof of type `EpochKeyProof`. * @example * ```ts * const { publicSignals, proof } = await userState.genEpochKeyProof({ * nonce: 1 * }) * ``` */ this.genEpochKeyProof = async (options = {}) => { var _a, _b, _c, _d; await this._checkChainId(); const nonce = (_a = options.nonce) !== null && _a !== void 0 ? _a : 0; const attesterId = (0, Synchronizer_1.toDecString)((_b = options.attesterId) !== null && _b !== void 0 ? _b : this.sync.attesterId); const epoch = (_c = options.epoch) !== null && _c !== void 0 ? _c : (await this.latestTransitionedEpoch(attesterId)); const tree = await this.sync.genStateTree(epoch, attesterId); const leafIndex = await this.latestStateTreeLeafIndex(epoch, attesterId); const data = await this.getData(epoch - 1, attesterId); const proof = tree.createProof(leafIndex); const circuitInputs = { identity_secret: this.id.secret, data, sig_data: (_d = options.data) !== null && _d !== void 0 ? _d : BigInt(0), state_tree_elements: proof.siblings, state_tree_indices: proof.pathIndices, epoch, nonce, attester_id: attesterId, reveal_nonce: options.revealNonce ? 1 : 0, chain_id: this.chainId, }; const results = await this.prover.genProofAndPublicSignals(circuits_1.Circuit.epochKey, (0, utils_1.stringifyBigInts)(circuitInputs)); return new circuits_1.EpochKeyProof(results.publicSignals, results.proof, this.prover); }; /** * Generate a proof that a user controls an epoch key in a certain epoch. * Optionally provide a data value to sign. * Returns an [`EpochKeyLiteProof`](https://developer.unirep.io/docs/circuits-api/classes/src.EpochKeyLiteProof.md). * * This proof **will not include a merkle tree proof** which makes the proof size smaller than an [`EpochKeyProof`](https://developer.unirep.io/docs/circuits-api/classes/src.EpochKeyProof.md). * It can be used to prove a seen and valid epoch key. * @param options.nonce The specified epoch key nonce. Default: `0`. * @param options.epoch The specified epoch. Default: current epoch. * @param options.data Indicates if user wants to endorse a 253-bits data. Default: `0`. * @param options.revealNonce Indicates if user wants to reveal epoch key nonce. Default: `false`. * @param options.attesterId Indicates for which attester the proof will be used. Default: `this.attesterId` * @returns The epoch key lite proof of type `EpochKeyLiteProof`. * @example * ```ts * const { publicSignals, proof } = await userState.genEpochKeyLiteProof({ * nonce: 1 * }) * ``` */ this.genEpochKeyLiteProof = async (options = {}) => { var _a, _b, _c, _d; await this._checkChainId(); const nonce = (_a = options.nonce) !== null && _a !== void 0 ? _a : 0; const attesterId = (0, Synchronizer_1.toDecString)((_b = options.attesterId) !== null && _b !== void 0 ? _b : this.sync.attesterId); const epoch = (_c = options.epoch) !== null && _c !== void 0 ? _c : (await this.latestTransitionedEpoch(attesterId)); const circuitInputs = { identity_secret: this.id.secret, sig_data: (_d = options.data) !== null && _d !== void 0 ? _d : BigInt(0), epoch, nonce, attester_id: attesterId, reveal_nonce: options.revealNonce ? 1 : 0, chain_id: this.chainId, }; const results = await this.prover.genProofAndPublicSignals(circuits_1.Circuit.epochKeyLite, (0, utils_1.stringifyBigInts)(circuitInputs)); return new circuits_1.EpochKeyLiteProof(results.publicSignals, results.proof, this.prover); }; const db = (_a = config.db) !== null && _a !== void 0 ? _a : new web_1.MemoryConnector((0, anondb_1.constructSchema)(userSchema_1.userSchema)); const { id, synchronizer, attesterId, unirepAddress, provider, prover, } = config; if (!id) { throw new Error('@unirep/core:UserState: id must be supplied as an argument when initialized with a sync'); } if (!prover) { throw new Error('@unirep/core:UserState: prover must be supplied as an argument when initialized with a sync'); } if (synchronizer) { this._sync = synchronizer; } else { if (!provider || !unirepAddress) { throw new Error('@unirep/core:UserState: provider and Unirep address must be supplied as an argument when initialized with a sync'); } this._sync = new Synchronizer_1.Synchronizer({ db, attesterId, provider, unirepAddress, }); } this._id = id; this._prover = prover; this._chainId = -1; // need to be setup in async function this._db = db; } /** * Start the synchronizer daemon. * Start polling the blockchain for new events. If we're behind the HEAD block we'll poll many times quickly */ async start() { await this.sync.start(); await this._checkChainId(); } /** * Wait for the synchronizer to sync up to a certain block. * By default this will wait until the current latest known block (according to the provider). * @param blockNumber The block number to be synced to. */ async waitForSync(blockNumber) { await this.sync.waitForSync(blockNumber); } /** * Stop synchronizing with Unirep contract. */ stop() { this.sync.stop(); } /** * Query the current database if the [Semaphore](https://semaphore.pse.dev/) identity commitment is stored. * @param attesterId The attester to be queried. Default: `this.attesterId`. * @returns True if user has signed up in unirep contract, false otherwise. */ async hasSignedUp(attesterId = this.sync.attesterId) { this._checkSync(); this.sync.checkAttesterId(attesterId); const signup = await this.db.findOne('UserSignUp', { where: { commitment: this.commitment.toString(), attesterId: (0, Synchronizer_1.toDecString)(attesterId), }, }); return !!signup; } /** * Query the current database for a user's signup event or latest user state transition [nullifier](https://developer.unirep.io/docs/protocol/nullifiers). * @param attesterId The attester to be queried. Default: `this.attesterId` * @returns The latest epoch where a user performed a user state transition. */ async latestTransitionedEpoch(attesterId = this.sync.attesterId) { var _a; this._checkSync(); const _attesterId = (0, Synchronizer_1.toDecString)(attesterId); this.sync.checkAttesterId(attesterId); let savedData = undefined; try { savedData = await this.db.findOne('UserState', { where: { attesterId: _attesterId, }, }); } catch (_) { } let earlistEpoch = (_a = savedData === null || savedData === void 0 ? void 0 : savedData.latestTransitionedEpoch) !== null && _a !== void 0 ? _a : 0; if (!savedData) { const signup = await this.db.findOne('UserSignUp', { where: { commitment: this.commitment.toString(), attesterId: _attesterId, }, }); if (!signup) throw new Error('@unirep/core:UserState user is not signed up'); earlistEpoch = signup.epoch; } const currentEpoch = await this.sync.loadCurrentEpoch(_attesterId); for (let x = currentEpoch; x >= earlistEpoch; x--) { const nullifiers = [ 0, this.sync.settings.numEpochKeyNoncePerEpoch, ].map((v) => (0, utils_1.genEpochKey)(this.id.secret, _attesterId, x, v, this.chainId).toString()); const n = await this.db.findOne('Nullifier', { where: { nullifier: nullifiers, }, }); if (n) { return n.epoch; } } return earlistEpoch; } /** * Get the latest global state tree leaf index for an epoch. * @param epoch Get the global state tree leaf index of the given epoch. Default: current epoch. * @param attesterId The attester to be queried. Default: `this.attesterId` * @returns The the latest state tree leaf index for an epoch. */ async latestStateTreeLeafIndex(epoch, attesterId = this.sync.attesterId) { const _attesterId = (0, Synchronizer_1.toDecString)(attesterId); const currentEpoch = epoch !== null && epoch !== void 0 ? epoch : this.sync.calcCurrentEpoch(_attesterId); const latestTransitionedEpoch = await this.latestTransitionedEpoch(_attesterId); let savedData = undefined; try { savedData = await this.db.findOne('UserState', { where: { attesterId: _attesterId, }, }); } catch (_) { } if ((savedData === null || savedData === void 0 ? void 0 : savedData.latestTransitionedEpoch) === currentEpoch) return savedData.latestTransitionedIndex; if (latestTransitionedEpoch !== currentEpoch) throw new Error('@unirep/core:UserState user has not transitioned to epoch'); const data = await this.getData(latestTransitionedEpoch - 1, _attesterId); const leaf = (0, utils_1.genStateTreeLeaf)(this.id.secret, _attesterId, latestTransitionedEpoch, data, this.chainId); const foundLeaf = await this.db.findOne('StateTreeLeaf', { where: { epoch: currentEpoch, hash: leaf.toString(), }, }); if (!foundLeaf) throw new Error('@unirep/core:UserState unable to find state tree leaf index'); return foundLeaf.index; } /** * Get epoch keys for the current user, for an epoch. * If a `nonce` value is supplied the return value will be a single epoch key. * Otherwise an array of all epoch keys will be returned. * * If no `epoch` is supplied the current epoch will be used (as determined by `synchronizer.calcCurrentEpoch`). * @param epoch The epoch to be queried. Default: current epoch. * @param nonce The specified epoch key nonce. Default: `0`. * @param attesterId The attester to be queried. Default: `this.attesterId` * @returns An epoch key or an array of epoch keys. */ getEpochKeys(epoch, nonce, attesterId = this.sync.attesterId) { this._checkSync(); const _attesterId = (0, Synchronizer_1.toDecString)(attesterId); const _epoch = epoch !== null && epoch !== void 0 ? epoch : this.sync.calcCurrentEpoch(attesterId); this._checkEpkNonce(nonce !== null && nonce !== void 0 ? nonce : 0); if (typeof nonce === 'number') { return (0, utils_1.genEpochKey)(this.id.secret, _attesterId, _epoch, nonce, this.chainId); } return Array(this.sync.settings.numEpochKeyNoncePerEpoch) .fill(null) .map((_, i) => (0, utils_1.genEpochKey)(this.id.secret, _attesterId, _epoch, i, this.chainId)); } /** * This function is used to parse replacement data field to be `index` and `data`. See [replacement data field](https://developer.unirep.io/docs/protocol/data#replacement-field). * @param replData The raw data which is processed on-chain. * @returns The parsed data and the data index (nonce). */ parseReplData(replData) { const data = replData / (BigInt(1) << BigInt(this.sync.settings.replNonceBits)); const nonce = replData % (BigInt(1) << BigInt(this.sync.settings.replNonceBits)); return { data, nonce, }; } /** * Get the data that can be proven by the user using a state tree leaf. * This is the data up to, but not including, the epoch the user has transitioned into. * @param attesterId The attester to be queried. Default: `this.attesterId` * @returns The data object */ async getProvableData(attesterId = this.sync.attesterId) { const epoch = await this.latestTransitionedEpoch(attesterId); return this.getData(epoch - 1, attesterId); } } exports.default = UserState; exports.UserState = UserState;