Getting errors while creating and reconstructing user share

While on-boarding the user on the our platform, we are facing the below error intermittently for the some users and the error frequency rate is quite high, Could someone please help us for the same.

Let me know if any additional information is required:

  • SDK Version:
    @tkey/common-types”: “^9.0.0”,
    @tkey/default”: “^9.0.1”,
    @tkey/security-questions”: “^9.0.0”,
    @tkey/service-provider-torus”: “^9.0.0”,
    @tkey/storage-layer-torus”: “^9.0.0”,
    @tkey/web-storage”: “^9.0.0”,
    @toruslabs/customauth”: “^13.0.0”,
    @web3auth/base”: “^6.1.1”,
    @web3auth/ethereum-provider”: “^6.1.5”,
  • Platform: Web browser/JS
  • Browser Console Screenshots:

Different intermittent errors for some users:

 'TypeError: Cannot convert undefined or null to object\n' +
    '    at Function.keys (<anonymous>)\n' +
    '    at V._refreshShares (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:4:11473)\n' +
    '    at V.generateNewShare (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:4:11023)\n' +
    '    at m.generateNewShareWithSecurityQuestions (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:9:37093)\n' +
    '    at r.Web3AuthProvider.generateNewShareWithSecurityPassphrase (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:15:171034)\n' +
    '    at async r.Web3AuthProvider.connect (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:15:169455)\n' +
    '    at async f.connect (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:15:181040)\n' +
    '    at async tY (https://wallet.zblocks.io/_next/static/chunks/pages/_app-3a54ad13a186fad3.js:86:546801)\n' +
    '    at async tG (https://wallet.zblocks.io/_next/static/chunks/pages/_app-3a54ad13a186fad3.js:86:548233)'
}
'WalletError: Failed to fetch the key provider.\n' +
    '    at r.Web3AuthProvider.getKeyProvider (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:15:167783)\n' +
    '    at async f.getKeyProvider (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:15:180415)\n' +
    '    at async f.getAddress (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:15:181223)\n'
stack: 'CoreError: setMetadata errored {"error":{"index":0,"timestamp":"Message has been signed more than 90s ago"},"success":false}\n' +
    '    at f.fromCode (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:2:165362)\n' +
    '    at f.metadataPostFailed (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:2:165982)\n' +
    '    at V.syncLocalMetadataTransitions (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:4:14838)\n' +
    '    at async V.addLocalMetadataTransitions (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:4:14337)\n' +
    '    at async V._initializeNewKey (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:4:13502)\n' +
    '    at async V.initialize (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:4:6200)\n' +
    '    at async r.Web3AuthProvider.initializeTkey (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:15:170199)\n' +
    '    at async r.Web3AuthProvider.connect (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:15:169062)\n' +
    '    at async f.connect (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:15:181040)\n' +
    '    at async tY (https://wallet.zblocks.io/_next/static/chunks/pages/_app-3a54ad13a186fad3.js:86:546801)'
CoreError: getMetadata errored Failed to fetch\n' +
    '    at Function.fromCode (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:2:165362)\n' +
    '    at Function.metadataGetFailed (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:2:165864)\n' +
    '    at V.getGenericMetadataWithTransitionStates (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:4:19157)\n' +
    '    at async V.initialize (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:4:5920)\n' +
    '    at async r.Web3AuthProvider.initializeTkey (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:15:170199)\n' +
    '    at async r.Web3AuthProvider.connect (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:15:169062)\n' +
    '    at async f.connect (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:15:181040)\n'
Error: Please serve redirect.html present in serviceworker folder of this package on https://wallet.zblocks.io/serviceworker/redirect\n' +
    '    at HTMLLinkElement.n.relList.n.relList.supports.n.relList.supports.n.onerror (https://wallet.zblocks.io/_next/static/chunks/5dfdcea7.355dbec6430e121c.js:10:659)'

Code Implementation using the tkey/SDK:

export class Web3AuthProvider implements IWalletProvider {
	private tKey: ThresholdKey;
	private modules: ModuleMap;
	private keyDetails: KeyDetails | undefined;
	private loginParams: Web3AuthLoginParams;
	private loginResponse: TorusLoginResponse | undefined;
	private provider: any;
	private isActive: boolean;
	private isPopUp: boolean;

	constructor(config: Web3AuthConfig) {
		this.keyDetails = undefined;
		this.isActive = false;
		this.isPopUp = false;
		this.loginParams = {
			accessToken: "",
			typeOfToken: DEFAULT_TOKEN_TYPE,
			tokenExpiry: DEFAULT_TOKEN_EXPIRY,
			clientId: config.clientId ?? "",
			domain: config.domain ?? "",
			typeOfLogin: DEFAULT_LOGIN_TYPE,
			verifier: config.verifierName ?? "",
		};
		this.modules = {
			webStorage: new WebStorageModule(),
			securityQuestions: new SecurityQuestionsModule(),
		};

		directParams.network = config.networkType;
		directParams.enableLogging = config?.enableLogging;
		directParams.web3AuthClientId = config.web3AuthClientId ?? directParams.web3AuthClientId;
		const serviceProvider = new TorusServiceProvider({ customAuthArgs: directParams });
		const storageLayer = new TorusStorageLayer({
			hostUrl: TORUS_METADATA_URL,
			enableLogging: config?.enableLogging,
		});

		this.tKey = new ThresholdKey({
			serviceProvider: serviceProvider,
			storageLayer: storageLayer,
			modules: this.modules,
		});
	}

	isConnected(): boolean {
		return this.isActive;
	}

	get getKeyDetails(): KeyDetails | undefined {
		return this.keyDetails;
	}

	injectLoginParams(loginParams: Web3AuthLoginInput, isPopUp: boolean) {
		this.loginParams.accessToken = loginParams.accessToken;
		this.loginParams.tokenExpiry = loginParams.tokenExpiry ?? this.loginParams.tokenExpiry;
		this.loginParams.typeOfToken = loginParams.typeOfToken ?? this.loginParams.typeOfToken;
		this.loginParams.typeOfLogin = loginParams.typeOfLogin ?? this.loginParams.typeOfLogin;
		this.isPopUp = isPopUp;
	}

	async getKeyProvider(networkConfig: NetworkConfig): Promise<IKeyProvider> {
		try {
			console.log("using rpc-url:", networkConfig.networkRpcUrl);
			const ethereumPrivateKeyProvider = new EthereumPrivateKeyProvider({
				config: {
					chainConfig: convertToCustomChainConfig(networkConfig),
				},
			});
			const reconstructedKey = (await this.tKey.reconstructKey()).privKey;
			let privKey = reconstructedKey.toString("hex");
			if (reconstructedKey.byteLength() !== 32) {
				console.error("Key size is not 32 bytes : ", privKey);
				throw new WalletError(ErrorCode.INVALID_PRIVATE_KEY);
			}
			if (privKey.length < 64) {
				privKey = privKey.padStart(64, "0");
			}
			await ethereumPrivateKeyProvider.setupProvider(privKey);
			console.log("setup provider created");
			return new Web3AuthKeyProvider(ethereumPrivateKeyProvider);
		} catch (error) {
			throw new WalletError(ErrorCode.GET_KEY_PROVIDER_FAILED, error as Error);
		}
	}

	public async connect(): Promise<any> {
		this.loginResponse = await this.login();
		await this.initializeTkey();
		try {
			await this.getLocalShareFromWeb();
		} catch (error) {
			const response: RecoveryShareResponse = await fetchPassphrase(
				this.loginResponse.publicAddress,
				this.loginParams.accessToken
			);
			response.data.passphrase == ""
				? await this.triggerSetPassphraseEvent()
				: await this.recoverUsingPassphrase(response.data.passphrase);

			this.resetLocalDeviceShare();
			console.info("key: ", this.tKey.getMetadata().pubKey.x.toString("hex"));
		}
		await this.generateNewShareWithSecurityPassphrase();

		this.isActive = true;
		return this.loginResponse?.userInfo;
	}

	private createLoginParam() {
		const params: SubVerifierDetails = {
			clientId: this.loginParams.clientId,
			verifier: this.loginParams.verifier,
			typeOfLogin: this.loginParams.typeOfLogin ?? DEFAULT_LOGIN_TYPE,
			jwtParams: {
				domain: this.loginParams.domain,
				verifierIdField: VERIFIER_ID_FIELD,
				response_type: TOKEN,
				scope: "",
			},
			hash: `access_token=${this.loginParams.accessToken}&token_type=${this.loginParams.typeOfToken}&expires_in=${this.loginParams.tokenExpiry}`,
			queryParameters: {},
		};
		if (this.isPopUp) {
			delete params["hash"];
			delete params["queryParameters"];
		}
		return params;
	}

	async login(): Promise<TorusLoginResponse> {
		const torusProvider = this.tKey.serviceProvider as TorusServiceProvider;
		await torusProvider.init({});
		const response = await torusProvider.triggerLogin(this.createLoginParam());
		return response;
	}

	public async initializeTkey() {
		this.keyDetails = await this.tKey.initialize();
	}

	public async getLocalShareFromWeb() {
		try {
			const webStorageModule = this.modules.webStorage as WebStorageModule;
			await webStorageModule.inputShareFromWebStorage();
		} catch (error) {
			console.info(
				"Seems like local share is missing in this browser, trying to use recovery share"
			);
			throw error;
		}
	}

	private async recoverUsingPassphrase(passphrase: string) {
		const SecurityModule = this.tKey.modules.securityQuestions as SecurityQuestionsModule;
		await SecurityModule.inputShareFromSecurityQuestions(passphrase);
	}

	public async changeSecurityShareInfo(
		answer: string,
		question = SECURITY_QUESTION,
		isStore = true
	) {
		const SecurityModule = this.tKey.modules.securityQuestions as SecurityQuestionsModule;
		if (isStore) {
			await this.updatePassphrase(answer, question);
		} else {
			await this.updatePassphrase("", "");
		}
		await SecurityModule.changeSecurityQuestionAndAnswer(answer, question);
	}

	public async generateNewShareWithSecurityPassphrase() {
		try {
			(this.tKey.modules.securityQuestions as SecurityQuestionsModule).getSecurityQuestions();
		} catch (error) {
			const reconstruct = this.tKey.reconstructKey();
			const result = this.createPassphrase();
			for (let retry = 0; retry < 4; retry++) {
				try {
					const SecurityModule = this.tKey.modules.securityQuestions as SecurityQuestionsModule;
					await reconstruct;
					await SecurityModule.generateNewShareWithSecurityQuestions(
						(
							await result
						).data.passphrase,
						SECURITY_QUESTION
					);
					break;
				} catch (error) {
					console.error(error);
					if (retry < 3) {
						// Added exponential delay
						// 0.5, 1, 1.5 secs.
						sleep((retry + 1) * 500);
						continue;
					} else {
						throw error;
					}
				}
			}
		}
	}

	public setModules(modules: ModuleMap) {
		this.modules = modules;
		this.tKey.modules = this.modules;
	}
}

@kshitij.dean.sam Thanks for your recent communication.

Your issue has been forwarded to our Dev team and we will get back with further updates.

Hi,
It is very possible that this user created too much shares as it set the device share if share not found

this.resetLocalDeviceShare();

You can try to comment out this line of code.
You will need to delete old device share if you want to create new share for the user.

Suggestion
please only set to local device if user requested ( eg. user click button to set local device)

Hi @cherngwoei ,

Thanks for the quick response, but here resetLocalDeviceShare is not creating a new share but reset the local share in the current browser, so that it don’t need to use QuestionAnswer share again and again.

And mostly failure occurred while creating the third share which is QuestionAnswer share, and we only generate it once per user at a time of on-boarding.

Can you please look into the individual errors, and different error coming for different users. All errors doesn’t seem related to me.

Let me know, if you require any additional info.

Below is the implementation of resetLocalDeviceShare.

     private resetLocalDeviceShare() {
		const shareIndex = this.getDeviceStorageShareIndex();
		if (shareIndex == "") {
			throw new Error("Local web-storage device share is not found");
		}
		const shareStores = this.tKey.getAllShareStoresForLatestPolynomial();
		shareStores.forEach((shareStore) => {
			const localShareIndex = shareStore.share.shareIndex.toString("hex");
			if (localShareIndex == shareIndex) {
				this.constructAndSetShareOnLocalStorage(shareStore);
			}
		});
	}

    private getDeviceStorageShareIndex() {
		const shareDescriptionMap = this.tKey.getMetadata().getShareDescription();
		for (const shareIndexStr in shareDescriptionMap) {
			if (Object.hasOwnProperty.call(shareDescriptionMap, shareIndexStr)) {
				const descriptions = shareDescriptionMap[shareIndexStr];
				for (const element of descriptions) {
					const shareDes = JSON.parse(element);
					if (shareDes?.module == "webStorage") {
						console.info("Found local device share in metadata");
						return shareIndexStr;
					}
				}
			}
		}
		return "";
	}

	private constructAndSetShareOnLocalStorage(shareStore: ShareStore) {
		const shareJson = {
			share: {
				share: shareStore.share.share.toString("hex"),
				shareIndex: shareStore.share.shareIndex.toString("hex"),
			},
			polynomialID: shareStore.polynomialID,
		};
		localStorage.setItem(
			this.tKey.getMetadata().pubKey.x.toString("hex"),
			JSON.stringify(shareJson)
		);
	}

Noted.
From the error message, it has called the generateNewShare from the generateNewShareWithSecurityQuestions

This is creating a new share every time it is called.
could you try comment it out in the connect ?

Hi @cherngwoei,

you are right, but as per logic it will only be created once per user. And that is what we need as well, if you check the below code, we added the code in such a way if the question for that share is not present( assuming the share is not yet created for that user) then only we are creating the new share with Security Question.

Now the problem seems to be coming from generateNewShareWithSecurityQuestions, for some users.

If I comment this code then we won’t able to create the backup share for the user, which we don’t want right now. And its occurring intermittently.

public async generateNewShareWithSecurityPassphrase() {
		try {
			(this.tKey.modules.securityQuestions as SecurityQuestionsModule).getSecurityQuestions();
		} catch (error) {
			const reconstruct = this.tKey.reconstructKey();
			const result = this.createPassphrase();
			for (let retry = 0; retry < 4; retry++) {
				try {
					const SecurityModule = this.tKey.modules.securityQuestions as SecurityQuestionsModule;
					await reconstruct;
					await SecurityModule.generateNewShareWithSecurityQuestions(
						(
							await result
						).data.passphrase,
						SECURITY_QUESTION
					);
					break;
				} catch (error) {
					console.error(error);
					if (retry < 3) {
						// Added exponential delay
						// 0.5, 1, 1.5 secs.
						sleep((retry + 1) * 500);
						continue;
					} else {
						throw error;
					}
				}
			}

Hi @cherngwoei

Is there any update on the above thread? or any possible reason for the given errors?

May I know which region you a testing from ?
which web3auth network you are using ?

We are doing testing in India region and web3auth network is AQUA.