Skip to content

Commit 0336925

Browse files
committed
fix(native): remove device share to reset state if private key reconstruction fails
1 parent 6a4b776 commit 0336925

File tree

4 files changed

+48
-12
lines changed

4 files changed

+48
-12
lines changed

packages/thirdweb/src/wallets/in-app/native/helpers/auth/middleware.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export async function postAuth({
8181
return storedToken;
8282
}
8383

84-
async function getRecoveryCode(args: {
84+
export async function getRecoveryCode(args: {
8585
storedToken: AuthStoredTokenWithCookieReturnType["storedToken"];
8686
client: ThirdwebClient;
8787
storage: ClientScopedStorage;

packages/thirdweb/src/wallets/in-app/native/helpers/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ export const AUTH_SHARE_INDEX = AUTH_SHARE_ID - 1;
66
const DEVICE_SHARE_ID = 1;
77
export const DEVICE_SHARE_INDEX = DEVICE_SHARE_ID - 1;
88
export const DEVICE_SHARE_MISSING_MESSAGE = "Missing device share.";
9+
export const INVALID_DEVICE_SHARE_MESSAGE =
10+
"Invalid private key reconstructed from shares";
911

1012
const RECOVERY_SHARE_ID = 2;
1113
export const RECOVERY_SHARE_INDEX = RECOVERY_SHARE_ID - 1;

packages/thirdweb/src/wallets/in-app/native/helpers/storage/local.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,22 @@ export async function setDeviceShare({
112112
return deviceShare;
113113
}
114114

115+
export async function removeDeviceShare({
116+
clientId,
117+
}: {
118+
clientId: string;
119+
}): Promise<void> {
120+
const userDetails = await getWalletUserDetails(clientId);
121+
122+
if (!userDetails) {
123+
throw new Error("Missing wallet user ID");
124+
}
125+
126+
const name = DEVICE_SHARE_LOCAL_STORAGE_NAME(clientId, userDetails.userId);
127+
await removeItemInAsyncStorage(name);
128+
return;
129+
}
130+
115131
export async function getDeviceShare(clientId: string) {
116132
const cachedWalletUserId = await getWalletUserDetails(clientId);
117133
if (!cachedWalletUserId) {

packages/thirdweb/src/wallets/in-app/native/helpers/wallet/retrieval.ts

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ import type { SetUpWalletRpcReturnType } from "../../../core/authentication/type
1111
import { getUserShares } from "../api/fetchers.js";
1212
import {
1313
DEVICE_SHARE_MISSING_MESSAGE,
14+
INVALID_DEVICE_SHARE_MESSAGE,
1415
ROUTE_GET_USER_SHARES,
1516
} from "../constants.js";
16-
import { getDeviceShare } from "../storage/local.js";
17+
import { getDeviceShare, removeDeviceShare } from "../storage/local.js";
1718
import { storeShares } from "./creation.js";
1819
import { decryptShareWeb } from "./encryption.js";
1920

@@ -47,7 +48,7 @@ async function getWalletPrivateKeyFromShares(shares: string[]) {
4748
}
4849
const prefixPrivateKey = hexToString(privateKeyHex as Hex);
4950
if (!prefixPrivateKey.startsWith("thirdweb_")) {
50-
throw new Error("Invalid private key reconstructed from shares");
51+
throw new Error(INVALID_DEVICE_SHARE_MESSAGE);
5152
}
5253
const privateKey = prefixPrivateKey.replace("thirdweb_", "");
5354
return privateKey;
@@ -58,9 +59,24 @@ async function getAccountFromShares(args: {
5859
shares: string[];
5960
}): Promise<Account> {
6061
const { client, shares } = args;
62+
const privateKey = await (async () => {
63+
try {
64+
return await getWalletPrivateKeyFromShares(shares);
65+
} catch (e) {
66+
// If the private key reconstruction fails, try to reset the device share and prompt the user to try again
67+
// This can happen if a user's account has been migrated or otherwise modified in the backend to use a new wallet. In that case, we need to reset their device state to get a new share
68+
if (e instanceof Error && e.message === INVALID_DEVICE_SHARE_MESSAGE) {
69+
await removeDeviceShare({ clientId: client.clientId });
70+
throw new Error("Invalid device state, please try again.");
71+
}
72+
// Otherwise this is a legitimate error, throw it
73+
throw e;
74+
}
75+
})();
76+
6177
return privateKeyToAccount({
6278
client,
63-
privateKey: await getWalletPrivateKeyFromShares(shares),
79+
privateKey,
6480
});
6581
}
6682

@@ -84,13 +100,13 @@ async function getShares<
84100
client: ThirdwebClient;
85101
authShare: { toRetrieve: A };
86102
recoveryShare: R extends true
87-
? {
88-
toRetrieve: R;
89-
recoveryCode: string;
90-
}
91-
: {
92-
toRetrieve: R;
93-
};
103+
? {
104+
toRetrieve: R;
105+
recoveryCode: string;
106+
}
107+
: {
108+
toRetrieve: R;
109+
};
94110
deviceShare: { toRetrieve: D };
95111
storage: ClientScopedStorage;
96112
}): Promise<{
@@ -147,7 +163,7 @@ async function getShares<
147163
}
148164
// if we get here, decryption was successful, so we stop trying
149165
break;
150-
} catch {}
166+
} catch { }
151167
}
152168
if (!recoverShareToReturn) {
153169
throw new Error("Invalid recovery code.");
@@ -179,6 +195,7 @@ async function getShares<
179195
async function getAccountAddressFromShares(args: {
180196
client: ThirdwebClient;
181197
shares: string[];
198+
storage: ClientScopedStorage;
182199
}) {
183200
const wallet = await getAccountFromShares(args);
184201
return wallet.address;
@@ -205,6 +222,7 @@ export async function setUpShareForNewDevice({
205222
const walletAddress = await getAccountAddressFromShares({
206223
client,
207224
shares: [recoveryShare, authShare],
225+
storage,
208226
});
209227

210228
const maybeDeviceShare = await storeShares({

0 commit comments

Comments
 (0)