@chipi-stack/chipi-expo 13.10.0 → 13.12.0
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.
- package/dist/index.d.mts +114 -1
- package/dist/index.d.ts +114 -1
- package/dist/index.js +287 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +181 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +19 -5
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,116 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { ChipiProvider, ChipiWalletData, SessionState, SessionWallet, UseChipiSessionConfig, UseChipiSessionReturn, UseChipiWalletConfig, UseChipiWalletReturn, useAddSessionKeyToContract, useApprove, useCallAnyContract, useChipiContext, useChipiSession, useChipiWallet, useCreateSessionKey, useExecuteWithSession, useGetSessionData, useGetSku, useGetSkuList, useGetSkuPurchase, useGetTokenBalance, useGetTransactionList, useGetWallet, usePurchaseSku, useRecordSendTransaction, useRevokeSessionKey, useTransfer } from '@chipi-stack/chipi-react';
|
|
2
|
+
import { CreateWalletParams, CreateWalletResponse, MigrateWalletToPasskeyParams, WalletData } from '@chipi-stack/types';
|
|
2
3
|
export * from '@chipi-stack/types';
|
|
3
4
|
export { Chain, ChainToken } from '@chipi-stack/types';
|
|
5
|
+
|
|
6
|
+
type CreateWalletInput = {
|
|
7
|
+
params: CreateWalletParams;
|
|
8
|
+
bearerToken: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Expo-native override of useCreateWallet.
|
|
12
|
+
*
|
|
13
|
+
* When usePasskey: true is passed, this uses expo-local-authentication +
|
|
14
|
+
* expo-secure-store instead of the browser-only @simplewebauthn/browser,
|
|
15
|
+
* so it works on real iOS and Android devices.
|
|
16
|
+
*/
|
|
17
|
+
declare function useCreateWallet(): {
|
|
18
|
+
createWallet: (input: CreateWalletInput) => void;
|
|
19
|
+
createWalletAsync: (input: CreateWalletInput) => Promise<CreateWalletResponse>;
|
|
20
|
+
data: CreateWalletResponse | undefined;
|
|
21
|
+
isLoading: boolean;
|
|
22
|
+
isError: boolean;
|
|
23
|
+
error: Error | null;
|
|
24
|
+
isSuccess: boolean;
|
|
25
|
+
reset: () => void;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type MigrateWalletToPasskeyInput = MigrateWalletToPasskeyParams & {
|
|
29
|
+
bearerToken: string;
|
|
30
|
+
};
|
|
31
|
+
interface MigrateWalletToPasskeyResult {
|
|
32
|
+
success: boolean;
|
|
33
|
+
wallet: WalletData;
|
|
34
|
+
credentialId: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Expo-native override of useMigrateWalletToPasskey.
|
|
38
|
+
*
|
|
39
|
+
* Migrates a PIN-encrypted wallet to biometric (Face ID / Touch ID) protection.
|
|
40
|
+
* Uses expo-local-authentication + expo-secure-store instead of browser WebAuthn.
|
|
41
|
+
*/
|
|
42
|
+
declare function useMigrateWalletToPasskey(): {
|
|
43
|
+
migrateWalletToPasskey: (input: MigrateWalletToPasskeyInput) => void;
|
|
44
|
+
migrateWalletToPasskeyAsync: (input: MigrateWalletToPasskeyInput) => Promise<MigrateWalletToPasskeyResult>;
|
|
45
|
+
data: MigrateWalletToPasskeyResult | undefined;
|
|
46
|
+
isLoading: boolean;
|
|
47
|
+
isError: boolean;
|
|
48
|
+
error: Error | null;
|
|
49
|
+
isSuccess: boolean;
|
|
50
|
+
reset: () => void;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Native Passkey for Expo/React Native
|
|
55
|
+
*
|
|
56
|
+
* Replaces the browser-only @simplewebauthn/browser implementation with a
|
|
57
|
+
* native equivalent using:
|
|
58
|
+
* - expo-local-authentication → biometric gate (Face ID / Touch ID / fingerprint)
|
|
59
|
+
* - expo-secure-store → iOS Keychain / Android Keystore protected storage
|
|
60
|
+
* - crypto-es → cryptographically secure random bytes (no Web Crypto in RN)
|
|
61
|
+
*
|
|
62
|
+
* Security model: a random 32-byte key is generated at wallet creation time,
|
|
63
|
+
* stored in the device's secure enclave behind biometric authentication,
|
|
64
|
+
* and retrieved later by prompting the user again. This is functionally
|
|
65
|
+
* equivalent to the WebAuthn PRF approach used on the web.
|
|
66
|
+
*/
|
|
67
|
+
interface NativeWalletCredential {
|
|
68
|
+
credentialId: string;
|
|
69
|
+
userId: string;
|
|
70
|
+
createdAt: string;
|
|
71
|
+
}
|
|
72
|
+
interface NativeCreateWalletPasskeyResult {
|
|
73
|
+
encryptKey: string;
|
|
74
|
+
credentialId: string;
|
|
75
|
+
prfSupported: false;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Returns true if the device has biometric hardware AND the user has enrolled.
|
|
79
|
+
* Must be true before calling createNativeWalletPasskey or getNativeWalletEncryptKey.
|
|
80
|
+
*/
|
|
81
|
+
declare function isNativeBiometricSupported(): Promise<boolean>;
|
|
82
|
+
/**
|
|
83
|
+
* Create a new native wallet passkey.
|
|
84
|
+
*
|
|
85
|
+
* 1. Verifies biometric support.
|
|
86
|
+
* 2. Prompts the user with Face ID / Touch ID to confirm intent.
|
|
87
|
+
* 3. Generates a random encryption key.
|
|
88
|
+
* 4. Stores the key in the device's Keychain/Keystore, protected by biometrics.
|
|
89
|
+
* 5. Returns the encryption key so the wallet can be created immediately.
|
|
90
|
+
*
|
|
91
|
+
* The key is NEVER stored anywhere else — it lives only in the secure enclave.
|
|
92
|
+
*/
|
|
93
|
+
declare function createNativeWalletPasskey(userId: string, _userName: string): Promise<NativeCreateWalletPasskeyResult>;
|
|
94
|
+
/**
|
|
95
|
+
* Retrieve the stored encryption key by authenticating with biometrics.
|
|
96
|
+
* expo-secure-store automatically triggers the Face ID / Touch ID prompt
|
|
97
|
+
* when requireAuthentication: true was used during storage.
|
|
98
|
+
*
|
|
99
|
+
* Returns null if no key is stored for the given userId.
|
|
100
|
+
*/
|
|
101
|
+
declare function getNativeWalletEncryptKey(userId: string): Promise<string | null>;
|
|
102
|
+
/**
|
|
103
|
+
* Returns true if a native wallet passkey has been created on this device.
|
|
104
|
+
*/
|
|
105
|
+
declare function hasNativeWalletPasskey(): Promise<boolean>;
|
|
106
|
+
/**
|
|
107
|
+
* Returns the stored credential metadata, or null if none exists.
|
|
108
|
+
*/
|
|
109
|
+
declare function getNativeWalletCredential(): Promise<NativeWalletCredential | null>;
|
|
110
|
+
/**
|
|
111
|
+
* Removes the stored encryption key and credential metadata from this device.
|
|
112
|
+
* Use with caution — if the wallet has no other recovery mechanism, this is destructive.
|
|
113
|
+
*/
|
|
114
|
+
declare function removeNativeWalletPasskey(userId: string): Promise<void>;
|
|
115
|
+
|
|
116
|
+
export { type NativeCreateWalletPasskeyResult, type NativeWalletCredential, createNativeWalletPasskey, getNativeWalletCredential, getNativeWalletEncryptKey, hasNativeWalletPasskey, isNativeBiometricSupported, removeNativeWalletPasskey, useCreateWallet, useMigrateWalletToPasskey };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,116 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { ChipiProvider, ChipiWalletData, SessionState, SessionWallet, UseChipiSessionConfig, UseChipiSessionReturn, UseChipiWalletConfig, UseChipiWalletReturn, useAddSessionKeyToContract, useApprove, useCallAnyContract, useChipiContext, useChipiSession, useChipiWallet, useCreateSessionKey, useExecuteWithSession, useGetSessionData, useGetSku, useGetSkuList, useGetSkuPurchase, useGetTokenBalance, useGetTransactionList, useGetWallet, usePurchaseSku, useRecordSendTransaction, useRevokeSessionKey, useTransfer } from '@chipi-stack/chipi-react';
|
|
2
|
+
import { CreateWalletParams, CreateWalletResponse, MigrateWalletToPasskeyParams, WalletData } from '@chipi-stack/types';
|
|
2
3
|
export * from '@chipi-stack/types';
|
|
3
4
|
export { Chain, ChainToken } from '@chipi-stack/types';
|
|
5
|
+
|
|
6
|
+
type CreateWalletInput = {
|
|
7
|
+
params: CreateWalletParams;
|
|
8
|
+
bearerToken: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Expo-native override of useCreateWallet.
|
|
12
|
+
*
|
|
13
|
+
* When usePasskey: true is passed, this uses expo-local-authentication +
|
|
14
|
+
* expo-secure-store instead of the browser-only @simplewebauthn/browser,
|
|
15
|
+
* so it works on real iOS and Android devices.
|
|
16
|
+
*/
|
|
17
|
+
declare function useCreateWallet(): {
|
|
18
|
+
createWallet: (input: CreateWalletInput) => void;
|
|
19
|
+
createWalletAsync: (input: CreateWalletInput) => Promise<CreateWalletResponse>;
|
|
20
|
+
data: CreateWalletResponse | undefined;
|
|
21
|
+
isLoading: boolean;
|
|
22
|
+
isError: boolean;
|
|
23
|
+
error: Error | null;
|
|
24
|
+
isSuccess: boolean;
|
|
25
|
+
reset: () => void;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type MigrateWalletToPasskeyInput = MigrateWalletToPasskeyParams & {
|
|
29
|
+
bearerToken: string;
|
|
30
|
+
};
|
|
31
|
+
interface MigrateWalletToPasskeyResult {
|
|
32
|
+
success: boolean;
|
|
33
|
+
wallet: WalletData;
|
|
34
|
+
credentialId: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Expo-native override of useMigrateWalletToPasskey.
|
|
38
|
+
*
|
|
39
|
+
* Migrates a PIN-encrypted wallet to biometric (Face ID / Touch ID) protection.
|
|
40
|
+
* Uses expo-local-authentication + expo-secure-store instead of browser WebAuthn.
|
|
41
|
+
*/
|
|
42
|
+
declare function useMigrateWalletToPasskey(): {
|
|
43
|
+
migrateWalletToPasskey: (input: MigrateWalletToPasskeyInput) => void;
|
|
44
|
+
migrateWalletToPasskeyAsync: (input: MigrateWalletToPasskeyInput) => Promise<MigrateWalletToPasskeyResult>;
|
|
45
|
+
data: MigrateWalletToPasskeyResult | undefined;
|
|
46
|
+
isLoading: boolean;
|
|
47
|
+
isError: boolean;
|
|
48
|
+
error: Error | null;
|
|
49
|
+
isSuccess: boolean;
|
|
50
|
+
reset: () => void;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Native Passkey for Expo/React Native
|
|
55
|
+
*
|
|
56
|
+
* Replaces the browser-only @simplewebauthn/browser implementation with a
|
|
57
|
+
* native equivalent using:
|
|
58
|
+
* - expo-local-authentication → biometric gate (Face ID / Touch ID / fingerprint)
|
|
59
|
+
* - expo-secure-store → iOS Keychain / Android Keystore protected storage
|
|
60
|
+
* - crypto-es → cryptographically secure random bytes (no Web Crypto in RN)
|
|
61
|
+
*
|
|
62
|
+
* Security model: a random 32-byte key is generated at wallet creation time,
|
|
63
|
+
* stored in the device's secure enclave behind biometric authentication,
|
|
64
|
+
* and retrieved later by prompting the user again. This is functionally
|
|
65
|
+
* equivalent to the WebAuthn PRF approach used on the web.
|
|
66
|
+
*/
|
|
67
|
+
interface NativeWalletCredential {
|
|
68
|
+
credentialId: string;
|
|
69
|
+
userId: string;
|
|
70
|
+
createdAt: string;
|
|
71
|
+
}
|
|
72
|
+
interface NativeCreateWalletPasskeyResult {
|
|
73
|
+
encryptKey: string;
|
|
74
|
+
credentialId: string;
|
|
75
|
+
prfSupported: false;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Returns true if the device has biometric hardware AND the user has enrolled.
|
|
79
|
+
* Must be true before calling createNativeWalletPasskey or getNativeWalletEncryptKey.
|
|
80
|
+
*/
|
|
81
|
+
declare function isNativeBiometricSupported(): Promise<boolean>;
|
|
82
|
+
/**
|
|
83
|
+
* Create a new native wallet passkey.
|
|
84
|
+
*
|
|
85
|
+
* 1. Verifies biometric support.
|
|
86
|
+
* 2. Prompts the user with Face ID / Touch ID to confirm intent.
|
|
87
|
+
* 3. Generates a random encryption key.
|
|
88
|
+
* 4. Stores the key in the device's Keychain/Keystore, protected by biometrics.
|
|
89
|
+
* 5. Returns the encryption key so the wallet can be created immediately.
|
|
90
|
+
*
|
|
91
|
+
* The key is NEVER stored anywhere else — it lives only in the secure enclave.
|
|
92
|
+
*/
|
|
93
|
+
declare function createNativeWalletPasskey(userId: string, _userName: string): Promise<NativeCreateWalletPasskeyResult>;
|
|
94
|
+
/**
|
|
95
|
+
* Retrieve the stored encryption key by authenticating with biometrics.
|
|
96
|
+
* expo-secure-store automatically triggers the Face ID / Touch ID prompt
|
|
97
|
+
* when requireAuthentication: true was used during storage.
|
|
98
|
+
*
|
|
99
|
+
* Returns null if no key is stored for the given userId.
|
|
100
|
+
*/
|
|
101
|
+
declare function getNativeWalletEncryptKey(userId: string): Promise<string | null>;
|
|
102
|
+
/**
|
|
103
|
+
* Returns true if a native wallet passkey has been created on this device.
|
|
104
|
+
*/
|
|
105
|
+
declare function hasNativeWalletPasskey(): Promise<boolean>;
|
|
106
|
+
/**
|
|
107
|
+
* Returns the stored credential metadata, or null if none exists.
|
|
108
|
+
*/
|
|
109
|
+
declare function getNativeWalletCredential(): Promise<NativeWalletCredential | null>;
|
|
110
|
+
/**
|
|
111
|
+
* Removes the stored encryption key and credential metadata from this device.
|
|
112
|
+
* Use with caution — if the wallet has no other recovery mechanism, this is destructive.
|
|
113
|
+
*/
|
|
114
|
+
declare function removeNativeWalletPasskey(userId: string): Promise<void>;
|
|
115
|
+
|
|
116
|
+
export { type NativeCreateWalletPasskeyResult, type NativeWalletCredential, createNativeWalletPasskey, getNativeWalletCredential, getNativeWalletEncryptKey, hasNativeWalletPasskey, isNativeBiometricSupported, removeNativeWalletPasskey, useCreateWallet, useMigrateWalletToPasskey };
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,289 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var chipiReact = require('@chipi-stack/chipi-react');
|
|
4
|
+
var reactQuery = require('@tanstack/react-query');
|
|
5
|
+
var LocalAuthentication = require('expo-local-authentication');
|
|
6
|
+
var SecureStore = require('expo-secure-store');
|
|
7
|
+
var CryptoES = require('crypto-es');
|
|
8
|
+
var backend = require('@chipi-stack/backend');
|
|
4
9
|
var types = require('@chipi-stack/types');
|
|
5
10
|
|
|
11
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
6
12
|
|
|
13
|
+
function _interopNamespace(e) {
|
|
14
|
+
if (e && e.__esModule) return e;
|
|
15
|
+
var n = Object.create(null);
|
|
16
|
+
if (e) {
|
|
17
|
+
Object.keys(e).forEach(function (k) {
|
|
18
|
+
if (k !== 'default') {
|
|
19
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
20
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
get: function () { return e[k]; }
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
n.default = e;
|
|
28
|
+
return Object.freeze(n);
|
|
29
|
+
}
|
|
7
30
|
|
|
31
|
+
var LocalAuthentication__namespace = /*#__PURE__*/_interopNamespace(LocalAuthentication);
|
|
32
|
+
var SecureStore__namespace = /*#__PURE__*/_interopNamespace(SecureStore);
|
|
33
|
+
var CryptoES__default = /*#__PURE__*/_interopDefault(CryptoES);
|
|
34
|
+
|
|
35
|
+
// src/index.ts
|
|
36
|
+
var ENCRYPT_KEY_PREFIX = "chipi_wallet_key_";
|
|
37
|
+
var CREDENTIAL_META_KEY = "chipi_wallet_credential";
|
|
38
|
+
function generateRandomHex(byteCount) {
|
|
39
|
+
const wordArray = CryptoES__default.default.lib.WordArray.random(byteCount);
|
|
40
|
+
return wordArray.toString(CryptoES__default.default.enc.Hex);
|
|
41
|
+
}
|
|
42
|
+
async function isNativeBiometricSupported() {
|
|
43
|
+
const hasHardware = await LocalAuthentication__namespace.hasHardwareAsync();
|
|
44
|
+
if (!hasHardware) return false;
|
|
45
|
+
const isEnrolled = await LocalAuthentication__namespace.isEnrolledAsync();
|
|
46
|
+
return isEnrolled;
|
|
47
|
+
}
|
|
48
|
+
async function createNativeWalletPasskey(userId, _userName) {
|
|
49
|
+
const supported = await isNativeBiometricSupported();
|
|
50
|
+
if (!supported) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
"Biometric authentication is not available or not enrolled on this device. Please enroll Face ID, Touch ID, or a fingerprint in your device settings."
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
const authResult = await LocalAuthentication__namespace.authenticateAsync({
|
|
56
|
+
promptMessage: "Authenticate to create your wallet",
|
|
57
|
+
cancelLabel: "Cancel",
|
|
58
|
+
disableDeviceFallback: false
|
|
59
|
+
});
|
|
60
|
+
if (!authResult.success) {
|
|
61
|
+
const reason = "error" in authResult ? authResult.error : "unknown";
|
|
62
|
+
if (reason === "user_cancel" || reason === "app_cancel") {
|
|
63
|
+
throw new Error("Biometric authentication was cancelled");
|
|
64
|
+
}
|
|
65
|
+
throw new Error(`Biometric authentication failed: ${reason}`);
|
|
66
|
+
}
|
|
67
|
+
const encryptKey = generateRandomHex(32);
|
|
68
|
+
const credentialId = `native_biometric_${userId}_${Date.now()}`;
|
|
69
|
+
await SecureStore__namespace.setItemAsync(
|
|
70
|
+
`${ENCRYPT_KEY_PREFIX}${userId}`,
|
|
71
|
+
encryptKey,
|
|
72
|
+
{ requireAuthentication: true }
|
|
73
|
+
);
|
|
74
|
+
const meta = {
|
|
75
|
+
credentialId,
|
|
76
|
+
userId,
|
|
77
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
78
|
+
};
|
|
79
|
+
await SecureStore__namespace.setItemAsync(CREDENTIAL_META_KEY, JSON.stringify(meta));
|
|
80
|
+
return { encryptKey, credentialId, prfSupported: false };
|
|
81
|
+
}
|
|
82
|
+
async function getNativeWalletEncryptKey(userId) {
|
|
83
|
+
const supported = await isNativeBiometricSupported();
|
|
84
|
+
if (!supported) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
"Biometric authentication is not available or not enrolled on this device."
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return SecureStore__namespace.getItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`, {
|
|
90
|
+
requireAuthentication: true
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async function hasNativeWalletPasskey() {
|
|
94
|
+
const stored = await SecureStore__namespace.getItemAsync(CREDENTIAL_META_KEY);
|
|
95
|
+
return stored !== null;
|
|
96
|
+
}
|
|
97
|
+
async function getNativeWalletCredential() {
|
|
98
|
+
const stored = await SecureStore__namespace.getItemAsync(CREDENTIAL_META_KEY);
|
|
99
|
+
if (!stored) return null;
|
|
100
|
+
try {
|
|
101
|
+
return JSON.parse(stored);
|
|
102
|
+
} catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async function removeNativeWalletPasskey(userId) {
|
|
107
|
+
await SecureStore__namespace.deleteItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`);
|
|
108
|
+
await SecureStore__namespace.deleteItemAsync(CREDENTIAL_META_KEY);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/hooks/useCreateWallet.ts
|
|
112
|
+
function useCreateWallet() {
|
|
113
|
+
const { chipiSDK } = chipiReact.useChipiContext();
|
|
114
|
+
const mutation = reactQuery.useMutation({
|
|
115
|
+
mutationFn: async (input) => {
|
|
116
|
+
let encryptKey = input.params.encryptKey;
|
|
117
|
+
if (input.params.usePasskey) {
|
|
118
|
+
if (!input.params.externalUserId) {
|
|
119
|
+
throw new Error("externalUserId is required when using passkey");
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const passkeyResult = await createNativeWalletPasskey(
|
|
123
|
+
input.params.externalUserId,
|
|
124
|
+
input.params.externalUserId
|
|
125
|
+
);
|
|
126
|
+
encryptKey = passkeyResult.encryptKey;
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (error instanceof Error) {
|
|
129
|
+
throw new Error(`Passkey creation failed: ${error.message}`);
|
|
130
|
+
}
|
|
131
|
+
throw new Error("Failed to create passkey for wallet");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return chipiSDK.createWallet({
|
|
135
|
+
params: {
|
|
136
|
+
...input.params,
|
|
137
|
+
encryptKey
|
|
138
|
+
},
|
|
139
|
+
bearerToken: input.bearerToken
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
return {
|
|
144
|
+
createWallet: mutation.mutate,
|
|
145
|
+
createWalletAsync: mutation.mutateAsync,
|
|
146
|
+
data: mutation.data,
|
|
147
|
+
isLoading: mutation.isPending,
|
|
148
|
+
isError: mutation.isError,
|
|
149
|
+
error: mutation.error,
|
|
150
|
+
isSuccess: mutation.isSuccess,
|
|
151
|
+
reset: mutation.reset
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function useMigrateWalletToPasskey() {
|
|
155
|
+
const mutation = reactQuery.useMutation({
|
|
156
|
+
mutationFn: async (input) => {
|
|
157
|
+
const { wallet, oldEncryptKey, externalUserId } = input;
|
|
158
|
+
try {
|
|
159
|
+
const passkeyResult = await createNativeWalletPasskey(
|
|
160
|
+
externalUserId,
|
|
161
|
+
externalUserId
|
|
162
|
+
);
|
|
163
|
+
let decryptedPrivateKey;
|
|
164
|
+
try {
|
|
165
|
+
decryptedPrivateKey = backend.decryptPrivateKey(
|
|
166
|
+
wallet.encryptedPrivateKey,
|
|
167
|
+
oldEncryptKey
|
|
168
|
+
);
|
|
169
|
+
} catch {
|
|
170
|
+
throw new Error(
|
|
171
|
+
"Failed to decrypt wallet with provided encryptKey. Please verify your PIN/password is correct."
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
const newEncryptedPrivateKey = backend.encryptPrivateKey(
|
|
175
|
+
decryptedPrivateKey,
|
|
176
|
+
passkeyResult.encryptKey
|
|
177
|
+
);
|
|
178
|
+
const updatedWallet = {
|
|
179
|
+
...wallet,
|
|
180
|
+
encryptedPrivateKey: newEncryptedPrivateKey
|
|
181
|
+
};
|
|
182
|
+
return {
|
|
183
|
+
success: true,
|
|
184
|
+
wallet: updatedWallet,
|
|
185
|
+
credentialId: passkeyResult.credentialId
|
|
186
|
+
};
|
|
187
|
+
} catch (error) {
|
|
188
|
+
if (error instanceof Error) {
|
|
189
|
+
throw new Error(`Migration failed: ${error.message}`);
|
|
190
|
+
}
|
|
191
|
+
throw new Error("Failed to migrate wallet to passkey");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
return {
|
|
196
|
+
migrateWalletToPasskey: mutation.mutate,
|
|
197
|
+
migrateWalletToPasskeyAsync: mutation.mutateAsync,
|
|
198
|
+
data: mutation.data,
|
|
199
|
+
isLoading: mutation.isPending,
|
|
200
|
+
isError: mutation.isError,
|
|
201
|
+
error: mutation.error,
|
|
202
|
+
isSuccess: mutation.isSuccess,
|
|
203
|
+
reset: mutation.reset
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
Object.defineProperty(exports, "ChipiProvider", {
|
|
208
|
+
enumerable: true,
|
|
209
|
+
get: function () { return chipiReact.ChipiProvider; }
|
|
210
|
+
});
|
|
211
|
+
Object.defineProperty(exports, "useAddSessionKeyToContract", {
|
|
212
|
+
enumerable: true,
|
|
213
|
+
get: function () { return chipiReact.useAddSessionKeyToContract; }
|
|
214
|
+
});
|
|
215
|
+
Object.defineProperty(exports, "useApprove", {
|
|
216
|
+
enumerable: true,
|
|
217
|
+
get: function () { return chipiReact.useApprove; }
|
|
218
|
+
});
|
|
219
|
+
Object.defineProperty(exports, "useCallAnyContract", {
|
|
220
|
+
enumerable: true,
|
|
221
|
+
get: function () { return chipiReact.useCallAnyContract; }
|
|
222
|
+
});
|
|
223
|
+
Object.defineProperty(exports, "useChipiContext", {
|
|
224
|
+
enumerable: true,
|
|
225
|
+
get: function () { return chipiReact.useChipiContext; }
|
|
226
|
+
});
|
|
227
|
+
Object.defineProperty(exports, "useChipiSession", {
|
|
228
|
+
enumerable: true,
|
|
229
|
+
get: function () { return chipiReact.useChipiSession; }
|
|
230
|
+
});
|
|
231
|
+
Object.defineProperty(exports, "useChipiWallet", {
|
|
232
|
+
enumerable: true,
|
|
233
|
+
get: function () { return chipiReact.useChipiWallet; }
|
|
234
|
+
});
|
|
235
|
+
Object.defineProperty(exports, "useCreateSessionKey", {
|
|
236
|
+
enumerable: true,
|
|
237
|
+
get: function () { return chipiReact.useCreateSessionKey; }
|
|
238
|
+
});
|
|
239
|
+
Object.defineProperty(exports, "useExecuteWithSession", {
|
|
240
|
+
enumerable: true,
|
|
241
|
+
get: function () { return chipiReact.useExecuteWithSession; }
|
|
242
|
+
});
|
|
243
|
+
Object.defineProperty(exports, "useGetSessionData", {
|
|
244
|
+
enumerable: true,
|
|
245
|
+
get: function () { return chipiReact.useGetSessionData; }
|
|
246
|
+
});
|
|
247
|
+
Object.defineProperty(exports, "useGetSku", {
|
|
248
|
+
enumerable: true,
|
|
249
|
+
get: function () { return chipiReact.useGetSku; }
|
|
250
|
+
});
|
|
251
|
+
Object.defineProperty(exports, "useGetSkuList", {
|
|
252
|
+
enumerable: true,
|
|
253
|
+
get: function () { return chipiReact.useGetSkuList; }
|
|
254
|
+
});
|
|
255
|
+
Object.defineProperty(exports, "useGetSkuPurchase", {
|
|
256
|
+
enumerable: true,
|
|
257
|
+
get: function () { return chipiReact.useGetSkuPurchase; }
|
|
258
|
+
});
|
|
259
|
+
Object.defineProperty(exports, "useGetTokenBalance", {
|
|
260
|
+
enumerable: true,
|
|
261
|
+
get: function () { return chipiReact.useGetTokenBalance; }
|
|
262
|
+
});
|
|
263
|
+
Object.defineProperty(exports, "useGetTransactionList", {
|
|
264
|
+
enumerable: true,
|
|
265
|
+
get: function () { return chipiReact.useGetTransactionList; }
|
|
266
|
+
});
|
|
267
|
+
Object.defineProperty(exports, "useGetWallet", {
|
|
268
|
+
enumerable: true,
|
|
269
|
+
get: function () { return chipiReact.useGetWallet; }
|
|
270
|
+
});
|
|
271
|
+
Object.defineProperty(exports, "usePurchaseSku", {
|
|
272
|
+
enumerable: true,
|
|
273
|
+
get: function () { return chipiReact.usePurchaseSku; }
|
|
274
|
+
});
|
|
275
|
+
Object.defineProperty(exports, "useRecordSendTransaction", {
|
|
276
|
+
enumerable: true,
|
|
277
|
+
get: function () { return chipiReact.useRecordSendTransaction; }
|
|
278
|
+
});
|
|
279
|
+
Object.defineProperty(exports, "useRevokeSessionKey", {
|
|
280
|
+
enumerable: true,
|
|
281
|
+
get: function () { return chipiReact.useRevokeSessionKey; }
|
|
282
|
+
});
|
|
283
|
+
Object.defineProperty(exports, "useTransfer", {
|
|
284
|
+
enumerable: true,
|
|
285
|
+
get: function () { return chipiReact.useTransfer; }
|
|
286
|
+
});
|
|
8
287
|
Object.defineProperty(exports, "Chain", {
|
|
9
288
|
enumerable: true,
|
|
10
289
|
get: function () { return types.Chain; }
|
|
@@ -13,17 +292,13 @@ Object.defineProperty(exports, "ChainToken", {
|
|
|
13
292
|
enumerable: true,
|
|
14
293
|
get: function () { return types.ChainToken; }
|
|
15
294
|
});
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
enumerable: true,
|
|
25
|
-
get: function () { return types[k]; }
|
|
26
|
-
});
|
|
27
|
-
});
|
|
295
|
+
exports.createNativeWalletPasskey = createNativeWalletPasskey;
|
|
296
|
+
exports.getNativeWalletCredential = getNativeWalletCredential;
|
|
297
|
+
exports.getNativeWalletEncryptKey = getNativeWalletEncryptKey;
|
|
298
|
+
exports.hasNativeWalletPasskey = hasNativeWalletPasskey;
|
|
299
|
+
exports.isNativeBiometricSupported = isNativeBiometricSupported;
|
|
300
|
+
exports.removeNativeWalletPasskey = removeNativeWalletPasskey;
|
|
301
|
+
exports.useCreateWallet = useCreateWallet;
|
|
302
|
+
exports.useMigrateWalletToPasskey = useMigrateWalletToPasskey;
|
|
28
303
|
//# sourceMappingURL=index.js.map
|
|
29
304
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js","sourcesContent":[]}
|
|
1
|
+
{"version":3,"sources":["../src/native-passkey.ts","../src/hooks/useCreateWallet.ts","../src/hooks/useMigrateWalletToPasskey.ts"],"names":["CryptoES","LocalAuthentication","SecureStore","useChipiContext","useMutation","decryptPrivateKey","encryptPrivateKey"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Native Passkey for Expo/React Native\n *\n * Replaces the browser-only @simplewebauthn/browser implementation with a\n * native equivalent using:\n * - expo-local-authentication → biometric gate (Face ID / Touch ID / fingerprint)\n * - expo-secure-store → iOS Keychain / Android Keystore protected storage\n * - crypto-es → cryptographically secure random bytes (no Web Crypto in RN)\n *\n * Security model: a random 32-byte key is generated at wallet creation time,\n * stored in the device's secure enclave behind biometric authentication,\n * and retrieved later by prompting the user again. This is functionally\n * equivalent to the WebAuthn PRF approach used on the web.\n */\n\nimport * as LocalAuthentication from \"expo-local-authentication\";\nimport * as SecureStore from \"expo-secure-store\";\nimport CryptoES from \"crypto-es\";\n\nconst ENCRYPT_KEY_PREFIX = \"chipi_wallet_key_\";\nconst CREDENTIAL_META_KEY = \"chipi_wallet_credential\";\n\nexport interface NativeWalletCredential {\n credentialId: string;\n userId: string;\n createdAt: string;\n}\n\nexport interface NativeCreateWalletPasskeyResult {\n encryptKey: string;\n credentialId: string;\n prfSupported: false;\n}\n\n/**\n * Generate a cryptographically random hex string (32 bytes = 64 hex chars).\n * Uses crypto-es because React Native has no Web Crypto API (crypto is undefined).\n * Same approach as @chipi-stack/backend for consistency.\n */\nfunction generateRandomHex(byteCount: number): string {\n const wordArray = CryptoES.lib.WordArray.random(byteCount);\n return wordArray.toString(CryptoES.enc.Hex);\n}\n\n/**\n * Returns true if the device has biometric hardware AND the user has enrolled.\n * Must be true before calling createNativeWalletPasskey or getNativeWalletEncryptKey.\n */\nexport async function isNativeBiometricSupported(): Promise<boolean> {\n const hasHardware = await LocalAuthentication.hasHardwareAsync();\n if (!hasHardware) return false;\n const isEnrolled = await LocalAuthentication.isEnrolledAsync();\n return isEnrolled;\n}\n\n/**\n * Create a new native wallet passkey.\n *\n * 1. Verifies biometric support.\n * 2. Prompts the user with Face ID / Touch ID to confirm intent.\n * 3. Generates a random encryption key.\n * 4. Stores the key in the device's Keychain/Keystore, protected by biometrics.\n * 5. Returns the encryption key so the wallet can be created immediately.\n *\n * The key is NEVER stored anywhere else — it lives only in the secure enclave.\n */\nexport async function createNativeWalletPasskey(\n userId: string,\n _userName: string\n): Promise<NativeCreateWalletPasskeyResult> {\n const supported = await isNativeBiometricSupported();\n if (!supported) {\n throw new Error(\n \"Biometric authentication is not available or not enrolled on this device. \" +\n \"Please enroll Face ID, Touch ID, or a fingerprint in your device settings.\"\n );\n }\n\n // Prompt biometrics to confirm user intent before generating/storing the key\n const authResult = await LocalAuthentication.authenticateAsync({\n promptMessage: \"Authenticate to create your wallet\",\n cancelLabel: \"Cancel\",\n disableDeviceFallback: false,\n });\n\n if (!authResult.success) {\n const reason = \"error\" in authResult ? authResult.error : \"unknown\";\n if (reason === \"user_cancel\" || reason === \"app_cancel\") {\n throw new Error(\"Biometric authentication was cancelled\");\n }\n throw new Error(`Biometric authentication failed: ${reason}`);\n }\n\n const encryptKey = generateRandomHex(32);\n const credentialId = `native_biometric_${userId}_${Date.now()}`;\n\n // Store the encryption key — requireAuthentication means future reads need biometrics\n await SecureStore.setItemAsync(\n `${ENCRYPT_KEY_PREFIX}${userId}`,\n encryptKey,\n { requireAuthentication: true }\n );\n\n // Store lightweight credential metadata (not sensitive — no requireAuthentication needed)\n const meta: NativeWalletCredential = {\n credentialId,\n userId,\n createdAt: new Date().toISOString(),\n };\n await SecureStore.setItemAsync(CREDENTIAL_META_KEY, JSON.stringify(meta));\n\n return { encryptKey, credentialId, prfSupported: false };\n}\n\n/**\n * Retrieve the stored encryption key by authenticating with biometrics.\n * expo-secure-store automatically triggers the Face ID / Touch ID prompt\n * when requireAuthentication: true was used during storage.\n *\n * Returns null if no key is stored for the given userId.\n */\nexport async function getNativeWalletEncryptKey(\n userId: string\n): Promise<string | null> {\n const supported = await isNativeBiometricSupported();\n if (!supported) {\n throw new Error(\n \"Biometric authentication is not available or not enrolled on this device.\"\n );\n }\n\n return SecureStore.getItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`, {\n requireAuthentication: true,\n });\n}\n\n/**\n * Returns true if a native wallet passkey has been created on this device.\n */\nexport async function hasNativeWalletPasskey(): Promise<boolean> {\n const stored = await SecureStore.getItemAsync(CREDENTIAL_META_KEY);\n return stored !== null;\n}\n\n/**\n * Returns the stored credential metadata, or null if none exists.\n */\nexport async function getNativeWalletCredential(): Promise<NativeWalletCredential | null> {\n const stored = await SecureStore.getItemAsync(CREDENTIAL_META_KEY);\n if (!stored) return null;\n try {\n return JSON.parse(stored) as NativeWalletCredential;\n } catch {\n return null;\n }\n}\n\n/**\n * Removes the stored encryption key and credential metadata from this device.\n * Use with caution — if the wallet has no other recovery mechanism, this is destructive.\n */\nexport async function removeNativeWalletPasskey(userId: string): Promise<void> {\n await SecureStore.deleteItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`);\n await SecureStore.deleteItemAsync(CREDENTIAL_META_KEY);\n}\n","import { useMutation, type UseMutationResult } from \"@tanstack/react-query\";\nimport { useChipiContext } from \"@chipi-stack/chipi-react\";\nimport type {\n CreateWalletParams,\n CreateWalletResponse,\n} from \"@chipi-stack/types\";\nimport { createNativeWalletPasskey } from \"../native-passkey\";\n\ntype CreateWalletInput = {\n params: CreateWalletParams;\n bearerToken: string;\n};\n\n/**\n * Expo-native override of useCreateWallet.\n *\n * When usePasskey: true is passed, this uses expo-local-authentication +\n * expo-secure-store instead of the browser-only @simplewebauthn/browser,\n * so it works on real iOS and Android devices.\n */\nexport function useCreateWallet(): {\n createWallet: (input: CreateWalletInput) => void;\n createWalletAsync: (\n input: CreateWalletInput\n ) => Promise<CreateWalletResponse>;\n data: CreateWalletResponse | undefined;\n isLoading: boolean;\n isError: boolean;\n error: Error | null;\n isSuccess: boolean;\n reset: () => void;\n} {\n const { chipiSDK } = useChipiContext();\n\n const mutation: UseMutationResult<\n CreateWalletResponse,\n Error,\n CreateWalletInput\n > = useMutation({\n mutationFn: async (input: CreateWalletInput) => {\n let encryptKey = input.params.encryptKey;\n\n if (input.params.usePasskey) {\n if (!input.params.externalUserId) {\n throw new Error(\"externalUserId is required when using passkey\");\n }\n\n try {\n const passkeyResult = await createNativeWalletPasskey(\n input.params.externalUserId,\n input.params.externalUserId\n );\n encryptKey = passkeyResult.encryptKey;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Passkey creation failed: ${error.message}`);\n }\n throw new Error(\"Failed to create passkey for wallet\");\n }\n }\n\n return chipiSDK.createWallet({\n params: {\n ...input.params,\n encryptKey: encryptKey!,\n },\n bearerToken: input.bearerToken,\n });\n },\n });\n\n return {\n createWallet: mutation.mutate,\n createWalletAsync: mutation.mutateAsync,\n data: mutation.data,\n isLoading: mutation.isPending,\n isError: mutation.isError,\n error: mutation.error,\n isSuccess: mutation.isSuccess,\n reset: mutation.reset,\n };\n}\n","import { useMutation, type UseMutationResult } from \"@tanstack/react-query\";\nimport type { MigrateWalletToPasskeyParams, WalletData } from \"@chipi-stack/types\";\nimport { decryptPrivateKey, encryptPrivateKey } from \"@chipi-stack/backend\";\nimport { createNativeWalletPasskey } from \"../native-passkey\";\n\ntype MigrateWalletToPasskeyInput = MigrateWalletToPasskeyParams & {\n bearerToken: string;\n};\n\ninterface MigrateWalletToPasskeyResult {\n success: boolean;\n wallet: WalletData;\n credentialId: string;\n}\n\n/**\n * Expo-native override of useMigrateWalletToPasskey.\n *\n * Migrates a PIN-encrypted wallet to biometric (Face ID / Touch ID) protection.\n * Uses expo-local-authentication + expo-secure-store instead of browser WebAuthn.\n */\nexport function useMigrateWalletToPasskey(): {\n migrateWalletToPasskey: (input: MigrateWalletToPasskeyInput) => void;\n migrateWalletToPasskeyAsync: (\n input: MigrateWalletToPasskeyInput\n ) => Promise<MigrateWalletToPasskeyResult>;\n data: MigrateWalletToPasskeyResult | undefined;\n isLoading: boolean;\n isError: boolean;\n error: Error | null;\n isSuccess: boolean;\n reset: () => void;\n} {\n const mutation: UseMutationResult<\n MigrateWalletToPasskeyResult,\n Error,\n MigrateWalletToPasskeyInput\n > = useMutation({\n mutationFn: async (input: MigrateWalletToPasskeyInput) => {\n const { wallet, oldEncryptKey, externalUserId } = input;\n\n try {\n // Step 1: Create new native passkey and derive encryptKey via biometrics\n const passkeyResult = await createNativeWalletPasskey(\n externalUserId,\n externalUserId\n );\n\n // Step 2: Decrypt the private key with the old PIN/encryptKey\n let decryptedPrivateKey: string;\n try {\n decryptedPrivateKey = decryptPrivateKey(\n wallet.encryptedPrivateKey,\n oldEncryptKey\n );\n } catch {\n throw new Error(\n \"Failed to decrypt wallet with provided encryptKey. Please verify your PIN/password is correct.\"\n );\n }\n\n // Step 3: Re-encrypt with the new biometric-derived encryptKey\n const newEncryptedPrivateKey = encryptPrivateKey(\n decryptedPrivateKey,\n passkeyResult.encryptKey\n );\n\n const updatedWallet: WalletData = {\n ...wallet,\n encryptedPrivateKey: newEncryptedPrivateKey,\n };\n\n return {\n success: true,\n wallet: updatedWallet,\n credentialId: passkeyResult.credentialId,\n };\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Migration failed: ${error.message}`);\n }\n throw new Error(\"Failed to migrate wallet to passkey\");\n }\n },\n });\n\n return {\n migrateWalletToPasskey: mutation.mutate,\n migrateWalletToPasskeyAsync: mutation.mutateAsync,\n data: mutation.data,\n isLoading: mutation.isPending,\n isError: mutation.isError,\n error: mutation.error,\n isSuccess: mutation.isSuccess,\n reset: mutation.reset,\n };\n}\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,184 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
1
|
+
import { useChipiContext } from '@chipi-stack/chipi-react';
|
|
2
|
+
export { ChipiProvider, useAddSessionKeyToContract, useApprove, useCallAnyContract, useChipiContext, useChipiSession, useChipiWallet, useCreateSessionKey, useExecuteWithSession, useGetSessionData, useGetSku, useGetSkuList, useGetSkuPurchase, useGetTokenBalance, useGetTransactionList, useGetWallet, usePurchaseSku, useRecordSendTransaction, useRevokeSessionKey, useTransfer } from '@chipi-stack/chipi-react';
|
|
3
|
+
import { useMutation } from '@tanstack/react-query';
|
|
4
|
+
import * as LocalAuthentication from 'expo-local-authentication';
|
|
5
|
+
import * as SecureStore from 'expo-secure-store';
|
|
6
|
+
import CryptoES from 'crypto-es';
|
|
7
|
+
import { decryptPrivateKey, encryptPrivateKey } from '@chipi-stack/backend';
|
|
3
8
|
export { Chain, ChainToken } from '@chipi-stack/types';
|
|
9
|
+
|
|
10
|
+
// src/index.ts
|
|
11
|
+
var ENCRYPT_KEY_PREFIX = "chipi_wallet_key_";
|
|
12
|
+
var CREDENTIAL_META_KEY = "chipi_wallet_credential";
|
|
13
|
+
function generateRandomHex(byteCount) {
|
|
14
|
+
const wordArray = CryptoES.lib.WordArray.random(byteCount);
|
|
15
|
+
return wordArray.toString(CryptoES.enc.Hex);
|
|
16
|
+
}
|
|
17
|
+
async function isNativeBiometricSupported() {
|
|
18
|
+
const hasHardware = await LocalAuthentication.hasHardwareAsync();
|
|
19
|
+
if (!hasHardware) return false;
|
|
20
|
+
const isEnrolled = await LocalAuthentication.isEnrolledAsync();
|
|
21
|
+
return isEnrolled;
|
|
22
|
+
}
|
|
23
|
+
async function createNativeWalletPasskey(userId, _userName) {
|
|
24
|
+
const supported = await isNativeBiometricSupported();
|
|
25
|
+
if (!supported) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
"Biometric authentication is not available or not enrolled on this device. Please enroll Face ID, Touch ID, or a fingerprint in your device settings."
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
const authResult = await LocalAuthentication.authenticateAsync({
|
|
31
|
+
promptMessage: "Authenticate to create your wallet",
|
|
32
|
+
cancelLabel: "Cancel",
|
|
33
|
+
disableDeviceFallback: false
|
|
34
|
+
});
|
|
35
|
+
if (!authResult.success) {
|
|
36
|
+
const reason = "error" in authResult ? authResult.error : "unknown";
|
|
37
|
+
if (reason === "user_cancel" || reason === "app_cancel") {
|
|
38
|
+
throw new Error("Biometric authentication was cancelled");
|
|
39
|
+
}
|
|
40
|
+
throw new Error(`Biometric authentication failed: ${reason}`);
|
|
41
|
+
}
|
|
42
|
+
const encryptKey = generateRandomHex(32);
|
|
43
|
+
const credentialId = `native_biometric_${userId}_${Date.now()}`;
|
|
44
|
+
await SecureStore.setItemAsync(
|
|
45
|
+
`${ENCRYPT_KEY_PREFIX}${userId}`,
|
|
46
|
+
encryptKey,
|
|
47
|
+
{ requireAuthentication: true }
|
|
48
|
+
);
|
|
49
|
+
const meta = {
|
|
50
|
+
credentialId,
|
|
51
|
+
userId,
|
|
52
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
53
|
+
};
|
|
54
|
+
await SecureStore.setItemAsync(CREDENTIAL_META_KEY, JSON.stringify(meta));
|
|
55
|
+
return { encryptKey, credentialId, prfSupported: false };
|
|
56
|
+
}
|
|
57
|
+
async function getNativeWalletEncryptKey(userId) {
|
|
58
|
+
const supported = await isNativeBiometricSupported();
|
|
59
|
+
if (!supported) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
"Biometric authentication is not available or not enrolled on this device."
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return SecureStore.getItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`, {
|
|
65
|
+
requireAuthentication: true
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async function hasNativeWalletPasskey() {
|
|
69
|
+
const stored = await SecureStore.getItemAsync(CREDENTIAL_META_KEY);
|
|
70
|
+
return stored !== null;
|
|
71
|
+
}
|
|
72
|
+
async function getNativeWalletCredential() {
|
|
73
|
+
const stored = await SecureStore.getItemAsync(CREDENTIAL_META_KEY);
|
|
74
|
+
if (!stored) return null;
|
|
75
|
+
try {
|
|
76
|
+
return JSON.parse(stored);
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function removeNativeWalletPasskey(userId) {
|
|
82
|
+
await SecureStore.deleteItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`);
|
|
83
|
+
await SecureStore.deleteItemAsync(CREDENTIAL_META_KEY);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/hooks/useCreateWallet.ts
|
|
87
|
+
function useCreateWallet() {
|
|
88
|
+
const { chipiSDK } = useChipiContext();
|
|
89
|
+
const mutation = useMutation({
|
|
90
|
+
mutationFn: async (input) => {
|
|
91
|
+
let encryptKey = input.params.encryptKey;
|
|
92
|
+
if (input.params.usePasskey) {
|
|
93
|
+
if (!input.params.externalUserId) {
|
|
94
|
+
throw new Error("externalUserId is required when using passkey");
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const passkeyResult = await createNativeWalletPasskey(
|
|
98
|
+
input.params.externalUserId,
|
|
99
|
+
input.params.externalUserId
|
|
100
|
+
);
|
|
101
|
+
encryptKey = passkeyResult.encryptKey;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error instanceof Error) {
|
|
104
|
+
throw new Error(`Passkey creation failed: ${error.message}`);
|
|
105
|
+
}
|
|
106
|
+
throw new Error("Failed to create passkey for wallet");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return chipiSDK.createWallet({
|
|
110
|
+
params: {
|
|
111
|
+
...input.params,
|
|
112
|
+
encryptKey
|
|
113
|
+
},
|
|
114
|
+
bearerToken: input.bearerToken
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
return {
|
|
119
|
+
createWallet: mutation.mutate,
|
|
120
|
+
createWalletAsync: mutation.mutateAsync,
|
|
121
|
+
data: mutation.data,
|
|
122
|
+
isLoading: mutation.isPending,
|
|
123
|
+
isError: mutation.isError,
|
|
124
|
+
error: mutation.error,
|
|
125
|
+
isSuccess: mutation.isSuccess,
|
|
126
|
+
reset: mutation.reset
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function useMigrateWalletToPasskey() {
|
|
130
|
+
const mutation = useMutation({
|
|
131
|
+
mutationFn: async (input) => {
|
|
132
|
+
const { wallet, oldEncryptKey, externalUserId } = input;
|
|
133
|
+
try {
|
|
134
|
+
const passkeyResult = await createNativeWalletPasskey(
|
|
135
|
+
externalUserId,
|
|
136
|
+
externalUserId
|
|
137
|
+
);
|
|
138
|
+
let decryptedPrivateKey;
|
|
139
|
+
try {
|
|
140
|
+
decryptedPrivateKey = decryptPrivateKey(
|
|
141
|
+
wallet.encryptedPrivateKey,
|
|
142
|
+
oldEncryptKey
|
|
143
|
+
);
|
|
144
|
+
} catch {
|
|
145
|
+
throw new Error(
|
|
146
|
+
"Failed to decrypt wallet with provided encryptKey. Please verify your PIN/password is correct."
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
const newEncryptedPrivateKey = encryptPrivateKey(
|
|
150
|
+
decryptedPrivateKey,
|
|
151
|
+
passkeyResult.encryptKey
|
|
152
|
+
);
|
|
153
|
+
const updatedWallet = {
|
|
154
|
+
...wallet,
|
|
155
|
+
encryptedPrivateKey: newEncryptedPrivateKey
|
|
156
|
+
};
|
|
157
|
+
return {
|
|
158
|
+
success: true,
|
|
159
|
+
wallet: updatedWallet,
|
|
160
|
+
credentialId: passkeyResult.credentialId
|
|
161
|
+
};
|
|
162
|
+
} catch (error) {
|
|
163
|
+
if (error instanceof Error) {
|
|
164
|
+
throw new Error(`Migration failed: ${error.message}`);
|
|
165
|
+
}
|
|
166
|
+
throw new Error("Failed to migrate wallet to passkey");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
return {
|
|
171
|
+
migrateWalletToPasskey: mutation.mutate,
|
|
172
|
+
migrateWalletToPasskeyAsync: mutation.mutateAsync,
|
|
173
|
+
data: mutation.data,
|
|
174
|
+
isLoading: mutation.isPending,
|
|
175
|
+
isError: mutation.isError,
|
|
176
|
+
error: mutation.error,
|
|
177
|
+
isSuccess: mutation.isSuccess,
|
|
178
|
+
reset: mutation.reset
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export { createNativeWalletPasskey, getNativeWalletCredential, getNativeWalletEncryptKey, hasNativeWalletPasskey, isNativeBiometricSupported, removeNativeWalletPasskey, useCreateWallet, useMigrateWalletToPasskey };
|
|
4
183
|
//# sourceMappingURL=index.mjs.map
|
|
5
184
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.mjs","sourcesContent":[]}
|
|
1
|
+
{"version":3,"sources":["../src/native-passkey.ts","../src/hooks/useCreateWallet.ts","../src/hooks/useMigrateWalletToPasskey.ts"],"names":["useMutation"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Native Passkey for Expo/React Native\n *\n * Replaces the browser-only @simplewebauthn/browser implementation with a\n * native equivalent using:\n * - expo-local-authentication → biometric gate (Face ID / Touch ID / fingerprint)\n * - expo-secure-store → iOS Keychain / Android Keystore protected storage\n * - crypto-es → cryptographically secure random bytes (no Web Crypto in RN)\n *\n * Security model: a random 32-byte key is generated at wallet creation time,\n * stored in the device's secure enclave behind biometric authentication,\n * and retrieved later by prompting the user again. This is functionally\n * equivalent to the WebAuthn PRF approach used on the web.\n */\n\nimport * as LocalAuthentication from \"expo-local-authentication\";\nimport * as SecureStore from \"expo-secure-store\";\nimport CryptoES from \"crypto-es\";\n\nconst ENCRYPT_KEY_PREFIX = \"chipi_wallet_key_\";\nconst CREDENTIAL_META_KEY = \"chipi_wallet_credential\";\n\nexport interface NativeWalletCredential {\n credentialId: string;\n userId: string;\n createdAt: string;\n}\n\nexport interface NativeCreateWalletPasskeyResult {\n encryptKey: string;\n credentialId: string;\n prfSupported: false;\n}\n\n/**\n * Generate a cryptographically random hex string (32 bytes = 64 hex chars).\n * Uses crypto-es because React Native has no Web Crypto API (crypto is undefined).\n * Same approach as @chipi-stack/backend for consistency.\n */\nfunction generateRandomHex(byteCount: number): string {\n const wordArray = CryptoES.lib.WordArray.random(byteCount);\n return wordArray.toString(CryptoES.enc.Hex);\n}\n\n/**\n * Returns true if the device has biometric hardware AND the user has enrolled.\n * Must be true before calling createNativeWalletPasskey or getNativeWalletEncryptKey.\n */\nexport async function isNativeBiometricSupported(): Promise<boolean> {\n const hasHardware = await LocalAuthentication.hasHardwareAsync();\n if (!hasHardware) return false;\n const isEnrolled = await LocalAuthentication.isEnrolledAsync();\n return isEnrolled;\n}\n\n/**\n * Create a new native wallet passkey.\n *\n * 1. Verifies biometric support.\n * 2. Prompts the user with Face ID / Touch ID to confirm intent.\n * 3. Generates a random encryption key.\n * 4. Stores the key in the device's Keychain/Keystore, protected by biometrics.\n * 5. Returns the encryption key so the wallet can be created immediately.\n *\n * The key is NEVER stored anywhere else — it lives only in the secure enclave.\n */\nexport async function createNativeWalletPasskey(\n userId: string,\n _userName: string\n): Promise<NativeCreateWalletPasskeyResult> {\n const supported = await isNativeBiometricSupported();\n if (!supported) {\n throw new Error(\n \"Biometric authentication is not available or not enrolled on this device. \" +\n \"Please enroll Face ID, Touch ID, or a fingerprint in your device settings.\"\n );\n }\n\n // Prompt biometrics to confirm user intent before generating/storing the key\n const authResult = await LocalAuthentication.authenticateAsync({\n promptMessage: \"Authenticate to create your wallet\",\n cancelLabel: \"Cancel\",\n disableDeviceFallback: false,\n });\n\n if (!authResult.success) {\n const reason = \"error\" in authResult ? authResult.error : \"unknown\";\n if (reason === \"user_cancel\" || reason === \"app_cancel\") {\n throw new Error(\"Biometric authentication was cancelled\");\n }\n throw new Error(`Biometric authentication failed: ${reason}`);\n }\n\n const encryptKey = generateRandomHex(32);\n const credentialId = `native_biometric_${userId}_${Date.now()}`;\n\n // Store the encryption key — requireAuthentication means future reads need biometrics\n await SecureStore.setItemAsync(\n `${ENCRYPT_KEY_PREFIX}${userId}`,\n encryptKey,\n { requireAuthentication: true }\n );\n\n // Store lightweight credential metadata (not sensitive — no requireAuthentication needed)\n const meta: NativeWalletCredential = {\n credentialId,\n userId,\n createdAt: new Date().toISOString(),\n };\n await SecureStore.setItemAsync(CREDENTIAL_META_KEY, JSON.stringify(meta));\n\n return { encryptKey, credentialId, prfSupported: false };\n}\n\n/**\n * Retrieve the stored encryption key by authenticating with biometrics.\n * expo-secure-store automatically triggers the Face ID / Touch ID prompt\n * when requireAuthentication: true was used during storage.\n *\n * Returns null if no key is stored for the given userId.\n */\nexport async function getNativeWalletEncryptKey(\n userId: string\n): Promise<string | null> {\n const supported = await isNativeBiometricSupported();\n if (!supported) {\n throw new Error(\n \"Biometric authentication is not available or not enrolled on this device.\"\n );\n }\n\n return SecureStore.getItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`, {\n requireAuthentication: true,\n });\n}\n\n/**\n * Returns true if a native wallet passkey has been created on this device.\n */\nexport async function hasNativeWalletPasskey(): Promise<boolean> {\n const stored = await SecureStore.getItemAsync(CREDENTIAL_META_KEY);\n return stored !== null;\n}\n\n/**\n * Returns the stored credential metadata, or null if none exists.\n */\nexport async function getNativeWalletCredential(): Promise<NativeWalletCredential | null> {\n const stored = await SecureStore.getItemAsync(CREDENTIAL_META_KEY);\n if (!stored) return null;\n try {\n return JSON.parse(stored) as NativeWalletCredential;\n } catch {\n return null;\n }\n}\n\n/**\n * Removes the stored encryption key and credential metadata from this device.\n * Use with caution — if the wallet has no other recovery mechanism, this is destructive.\n */\nexport async function removeNativeWalletPasskey(userId: string): Promise<void> {\n await SecureStore.deleteItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`);\n await SecureStore.deleteItemAsync(CREDENTIAL_META_KEY);\n}\n","import { useMutation, type UseMutationResult } from \"@tanstack/react-query\";\nimport { useChipiContext } from \"@chipi-stack/chipi-react\";\nimport type {\n CreateWalletParams,\n CreateWalletResponse,\n} from \"@chipi-stack/types\";\nimport { createNativeWalletPasskey } from \"../native-passkey\";\n\ntype CreateWalletInput = {\n params: CreateWalletParams;\n bearerToken: string;\n};\n\n/**\n * Expo-native override of useCreateWallet.\n *\n * When usePasskey: true is passed, this uses expo-local-authentication +\n * expo-secure-store instead of the browser-only @simplewebauthn/browser,\n * so it works on real iOS and Android devices.\n */\nexport function useCreateWallet(): {\n createWallet: (input: CreateWalletInput) => void;\n createWalletAsync: (\n input: CreateWalletInput\n ) => Promise<CreateWalletResponse>;\n data: CreateWalletResponse | undefined;\n isLoading: boolean;\n isError: boolean;\n error: Error | null;\n isSuccess: boolean;\n reset: () => void;\n} {\n const { chipiSDK } = useChipiContext();\n\n const mutation: UseMutationResult<\n CreateWalletResponse,\n Error,\n CreateWalletInput\n > = useMutation({\n mutationFn: async (input: CreateWalletInput) => {\n let encryptKey = input.params.encryptKey;\n\n if (input.params.usePasskey) {\n if (!input.params.externalUserId) {\n throw new Error(\"externalUserId is required when using passkey\");\n }\n\n try {\n const passkeyResult = await createNativeWalletPasskey(\n input.params.externalUserId,\n input.params.externalUserId\n );\n encryptKey = passkeyResult.encryptKey;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Passkey creation failed: ${error.message}`);\n }\n throw new Error(\"Failed to create passkey for wallet\");\n }\n }\n\n return chipiSDK.createWallet({\n params: {\n ...input.params,\n encryptKey: encryptKey!,\n },\n bearerToken: input.bearerToken,\n });\n },\n });\n\n return {\n createWallet: mutation.mutate,\n createWalletAsync: mutation.mutateAsync,\n data: mutation.data,\n isLoading: mutation.isPending,\n isError: mutation.isError,\n error: mutation.error,\n isSuccess: mutation.isSuccess,\n reset: mutation.reset,\n };\n}\n","import { useMutation, type UseMutationResult } from \"@tanstack/react-query\";\nimport type { MigrateWalletToPasskeyParams, WalletData } from \"@chipi-stack/types\";\nimport { decryptPrivateKey, encryptPrivateKey } from \"@chipi-stack/backend\";\nimport { createNativeWalletPasskey } from \"../native-passkey\";\n\ntype MigrateWalletToPasskeyInput = MigrateWalletToPasskeyParams & {\n bearerToken: string;\n};\n\ninterface MigrateWalletToPasskeyResult {\n success: boolean;\n wallet: WalletData;\n credentialId: string;\n}\n\n/**\n * Expo-native override of useMigrateWalletToPasskey.\n *\n * Migrates a PIN-encrypted wallet to biometric (Face ID / Touch ID) protection.\n * Uses expo-local-authentication + expo-secure-store instead of browser WebAuthn.\n */\nexport function useMigrateWalletToPasskey(): {\n migrateWalletToPasskey: (input: MigrateWalletToPasskeyInput) => void;\n migrateWalletToPasskeyAsync: (\n input: MigrateWalletToPasskeyInput\n ) => Promise<MigrateWalletToPasskeyResult>;\n data: MigrateWalletToPasskeyResult | undefined;\n isLoading: boolean;\n isError: boolean;\n error: Error | null;\n isSuccess: boolean;\n reset: () => void;\n} {\n const mutation: UseMutationResult<\n MigrateWalletToPasskeyResult,\n Error,\n MigrateWalletToPasskeyInput\n > = useMutation({\n mutationFn: async (input: MigrateWalletToPasskeyInput) => {\n const { wallet, oldEncryptKey, externalUserId } = input;\n\n try {\n // Step 1: Create new native passkey and derive encryptKey via biometrics\n const passkeyResult = await createNativeWalletPasskey(\n externalUserId,\n externalUserId\n );\n\n // Step 2: Decrypt the private key with the old PIN/encryptKey\n let decryptedPrivateKey: string;\n try {\n decryptedPrivateKey = decryptPrivateKey(\n wallet.encryptedPrivateKey,\n oldEncryptKey\n );\n } catch {\n throw new Error(\n \"Failed to decrypt wallet with provided encryptKey. Please verify your PIN/password is correct.\"\n );\n }\n\n // Step 3: Re-encrypt with the new biometric-derived encryptKey\n const newEncryptedPrivateKey = encryptPrivateKey(\n decryptedPrivateKey,\n passkeyResult.encryptKey\n );\n\n const updatedWallet: WalletData = {\n ...wallet,\n encryptedPrivateKey: newEncryptedPrivateKey,\n };\n\n return {\n success: true,\n wallet: updatedWallet,\n credentialId: passkeyResult.credentialId,\n };\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Migration failed: ${error.message}`);\n }\n throw new Error(\"Failed to migrate wallet to passkey\");\n }\n },\n });\n\n return {\n migrateWalletToPasskey: mutation.mutate,\n migrateWalletToPasskeyAsync: mutation.mutateAsync,\n data: mutation.data,\n isLoading: mutation.isPending,\n isError: mutation.isError,\n error: mutation.error,\n isSuccess: mutation.isSuccess,\n reset: mutation.reset,\n };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chipi-stack/chipi-expo",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.12.0",
|
|
4
4
|
"description": "Chipi SDK for React Native and Expo applications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chipi",
|
|
@@ -55,15 +55,29 @@
|
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"@tanstack/react-query": "^5.85.0",
|
|
58
|
-
"
|
|
59
|
-
"@chipi-stack/
|
|
60
|
-
"@chipi-stack/chipi-react": "^13.
|
|
61
|
-
"@chipi-stack/types": "^13.
|
|
58
|
+
"crypto-es": "^2.1.0",
|
|
59
|
+
"@chipi-stack/backend": "^13.11.0",
|
|
60
|
+
"@chipi-stack/chipi-react": "^13.11.0",
|
|
61
|
+
"@chipi-stack/types": "^13.12.0",
|
|
62
|
+
"@chipi-stack/shared": "^13.11.0"
|
|
62
63
|
},
|
|
63
64
|
"peerDependencies": {
|
|
65
|
+
"expo-local-authentication": ">=14.0.0",
|
|
66
|
+
"expo-secure-store": ">=13.0.0",
|
|
64
67
|
"react": ">=16.8.0",
|
|
65
68
|
"react-dom": ">=16.8.0"
|
|
66
69
|
},
|
|
70
|
+
"peerDependenciesMeta": {
|
|
71
|
+
"expo-local-authentication": {
|
|
72
|
+
"optional": false
|
|
73
|
+
},
|
|
74
|
+
"expo-secure-store": {
|
|
75
|
+
"optional": false
|
|
76
|
+
},
|
|
77
|
+
"react-dom": {
|
|
78
|
+
"optional": true
|
|
79
|
+
}
|
|
80
|
+
},
|
|
67
81
|
"engines": {
|
|
68
82
|
"node": ">=18.17.0"
|
|
69
83
|
},
|