kokio-sdk 0.0.4 → 0.0.6
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/package.json +3 -1
- package/src/abis/BeaconProxy.json +68 -975
- package/src/abis/DeviceWallet.json +909 -904
- package/src/abis/DeviceWalletFactory.json +548 -464
- package/src/abis/ESIMWallet.json +325 -325
- package/src/abis/ESIMWalletFactory.json +296 -301
- package/src/abis/LazyWalletRegistry.json +488 -493
- package/src/abis/P256Verifier.json +30 -30
- package/src/abis/Registry.json +502 -507
- package/src/config.ts +11 -2
- package/src/interface/P256VerifierClass.ts +1 -1
- package/src/interface/constantsClass.ts +3 -3
- package/src/interface/deviceWalletClass.ts +1 -1
- package/src/interface/eSIMWalletClass.ts +1 -1
- package/src/interface/eSIMWalletFactoryClass.ts +1 -1
- package/src/interface/lazyWalletRegistryClass.ts +1 -1
- package/src/interface/smartAccountClass.ts +12 -6
- package/src/interface/utilsClass.ts +1 -1
- package/src/logic/P256Verifier.ts +1 -1
- package/src/logic/account-kit/createSmartAccount.ts +90 -88
- package/src/logic/constants.ts +17 -7
- package/src/logic/deviceWallet.ts +6 -6
- package/src/logic/eSIMWallet.ts +1 -1
- package/src/logic/services/turnkeyClient.ts +193 -65
- package/src/logic/utils.ts +38 -1
- package/src/types.ts +1 -1
|
@@ -1,8 +1,94 @@
|
|
|
1
1
|
import { assertActivityCompleted, assertNonNull, TurnkeyClient, TActivityResponse } from "@turnkey/http"
|
|
2
|
-
import { Address, hashMessage, hashTypedData, SignableMessage, toHex, TypedData, TypedDataDefinition } from "viem";
|
|
3
|
-
import { decodeAttestationObject, decodeClientDataJSON, parseAuthenticatorData } from "@simplewebauthn/server/helpers";
|
|
4
|
-
import { base64UrlToBuffer } from "../utils.js";
|
|
2
|
+
import { Address, hashMessage, hashTypedData, Hex, hexToBytes, SignableMessage, toHex, TypedData, TypedDataDefinition, bytesToHex } from "viem";
|
|
3
|
+
import { decodeAttestationObject, decodeClientDataJSON, isoBase64URL, parseAuthenticatorData } from "@simplewebauthn/server/helpers";
|
|
4
|
+
import { _add0x, base64UrlToBuffer, parseSignature } from "../utils.js";
|
|
5
5
|
import { WebAuthnSignature } from "../../types.js";
|
|
6
|
+
import { Passkey, PasskeyGetResult } from "react-native-passkey";
|
|
7
|
+
import { p256 } from "@noble/curves/p256";
|
|
8
|
+
import { AuthenticatorTransport } from "@turnkey/react-native-passkey-stamper";
|
|
9
|
+
|
|
10
|
+
type BrokenPasskeyGetResult = PasskeyGetResult | string;
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
** Stamp is client-side authentication. Since the passkeys are one the user's mobile device
|
|
14
|
+
** react-native-passkey helps fetch passkey for the user (provided credentialId, rpId).
|
|
15
|
+
** This is exactly how the WebAuthn.sol contract needs it to be.
|
|
16
|
+
*/
|
|
17
|
+
export const _stamp = async (credentialId: string, rpId: string, payload: Hex): Promise<WebAuthnSignature> => {
|
|
18
|
+
const signingOptions = {
|
|
19
|
+
challenge: isoBase64URL.fromBuffer(hexToBytes(payload)), // Base64URL of the raw EIP-191 hash bytes
|
|
20
|
+
allowCredentials: [{
|
|
21
|
+
id: credentialId,
|
|
22
|
+
type: 'public-key',
|
|
23
|
+
transports: [AuthenticatorTransport.internal]
|
|
24
|
+
}],
|
|
25
|
+
rpId: rpId,
|
|
26
|
+
userVerification: "required"
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
let authenticationResult;
|
|
30
|
+
try {
|
|
31
|
+
authenticationResult = await Passkey.get(signingOptions);
|
|
32
|
+
} catch (e) {
|
|
33
|
+
console.log("Failed to get authenticationResult");
|
|
34
|
+
console.error(JSON.stringify(e, Object.getOwnPropertyNames(e)))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// See https://github.com/f-23/react-native-passkey/issues/54
|
|
38
|
+
// On Android the typedef lies. Authentication result is actually a string!
|
|
39
|
+
// TODO: remove me once the above is resolved.
|
|
40
|
+
const brokenAuthenticationResult =
|
|
41
|
+
authenticationResult as BrokenPasskeyGetResult;
|
|
42
|
+
if (typeof brokenAuthenticationResult === "string") {
|
|
43
|
+
authenticationResult = JSON.parse(brokenAuthenticationResult);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const { clientDataJSON, authenticatorData, signature } = authenticationResult.response;
|
|
47
|
+
|
|
48
|
+
// 1. Decode clientDataJSON
|
|
49
|
+
const clientDataJSONBuffer = isoBase64URL.toBuffer(clientDataJSON);
|
|
50
|
+
const clientDataJSONString = new TextDecoder().decode(clientDataJSONBuffer);
|
|
51
|
+
|
|
52
|
+
// 2. Calculate indices for the contract (byte offsets)
|
|
53
|
+
const typeSearchString = '"type":"webauthn.get"';
|
|
54
|
+
const challengeSearchString = '"challenge":';
|
|
55
|
+
|
|
56
|
+
const typeIndex = clientDataJSONString.indexOf(typeSearchString);
|
|
57
|
+
const challengeIndex = clientDataJSONString.indexOf(challengeSearchString);
|
|
58
|
+
|
|
59
|
+
if (typeIndex === -1) {
|
|
60
|
+
console.warn(`Warning: Could not find type substring '${typeSearchString}' in clientDataJSON. Setting typeIndex to 0.`);
|
|
61
|
+
}
|
|
62
|
+
if (challengeIndex === -1) {
|
|
63
|
+
throw new Error(`Could not find challenge substring '${challengeSearchString}' in clientDataJSON for index calculation.`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 3. Decode authenticatorData
|
|
67
|
+
const authenticatorDataBytes = isoBase64URL.toBuffer(authenticatorData);
|
|
68
|
+
const authenticatorDataHex = bytesToHex(authenticatorDataBytes);
|
|
69
|
+
|
|
70
|
+
// 4. Decode signature (ASN.1 DER encoded)
|
|
71
|
+
const signatureBytes = isoBase64URL.toBuffer(signature);
|
|
72
|
+
let parsedSignature = p256.Signature.fromDER(signatureBytes);
|
|
73
|
+
parsedSignature = parsedSignature.normalizeS();
|
|
74
|
+
let r = parsedSignature.r;
|
|
75
|
+
let s = parsedSignature.s;
|
|
76
|
+
|
|
77
|
+
const webAuthnSig = {
|
|
78
|
+
authenticatorData: authenticatorDataHex,
|
|
79
|
+
clientDataJSON: clientDataJSONString,
|
|
80
|
+
challengeIndex: BigInt(challengeIndex),
|
|
81
|
+
typeIndex: BigInt(typeIndex),
|
|
82
|
+
r: r,
|
|
83
|
+
s: s
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return webAuthnSig;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const getBytesFromPayload = (payloadHex: Hex): Uint8Array => {
|
|
90
|
+
return hexToBytes(payloadHex);
|
|
91
|
+
};
|
|
6
92
|
|
|
7
93
|
export const _signMessageWithTurnkey = async (client: TurnkeyClient, organizationId: string, signWith: Address, message: SignableMessage): Promise<WebAuthnSignature> => {
|
|
8
94
|
|
|
@@ -79,90 +165,132 @@ export const _signTypedDataWithTurnkey = async <
|
|
|
79
165
|
}
|
|
80
166
|
}
|
|
81
167
|
|
|
82
|
-
export const _stampAndSignMessageWithTurnkey = async (client: TurnkeyClient, organizationId: string, signWith: Address,
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
168
|
+
export const _stampAndSignMessageWithTurnkey = async (client: TurnkeyClient, organizationId: string, signWith: Address, payload: SignableMessage): Promise<WebAuthnSignature> => {
|
|
169
|
+
let signedRequest;
|
|
170
|
+
try {
|
|
171
|
+
signedRequest = await client.stampSignRawPayload({
|
|
172
|
+
type: "ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2",
|
|
173
|
+
organizationId: organizationId,
|
|
174
|
+
parameters: {
|
|
175
|
+
signWith: signWith,
|
|
176
|
+
payload: `${payload}`,
|
|
177
|
+
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
|
|
178
|
+
hashFunction: "HASH_FUNCTION_NO_OP",
|
|
179
|
+
},
|
|
180
|
+
timestampMs: String(Date.now()), // millisecond timestamp
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
catch (e) {
|
|
184
|
+
console.log("SDK error with signedRequest: ", e);
|
|
185
|
+
}
|
|
186
|
+
if (!signedRequest)
|
|
187
|
+
throw new Error('Error: Failed to get signed request');
|
|
97
188
|
|
|
98
|
-
if (!signedRequest) throw new Error('Error: Failed to get signed request')
|
|
99
|
-
|
|
100
189
|
const stampedHeaderValue = JSON.parse(signedRequest.stamp.stampHeaderValue);
|
|
190
|
+
let response;
|
|
191
|
+
try {
|
|
192
|
+
const res = await fetch(signedRequest.url, {
|
|
193
|
+
method: 'POST',
|
|
194
|
+
headers: {
|
|
195
|
+
'Content-Type': 'application/json',
|
|
196
|
+
[signedRequest.stamp.stampHeaderName]: signedRequest.stamp.stampHeaderValue
|
|
197
|
+
},
|
|
198
|
+
body: signedRequest.body
|
|
199
|
+
});
|
|
200
|
+
if (!res.ok) {
|
|
201
|
+
const errorBody = await res.text();
|
|
202
|
+
throw new Error(`SDK Turnkey API request failed: ${res.status} ${res.statusText} - ${errorBody}`);
|
|
203
|
+
}
|
|
204
|
+
const responseData = await res.json();
|
|
205
|
+
response = responseData;
|
|
206
|
+
}
|
|
207
|
+
catch (e) {
|
|
208
|
+
console.log("SDK error with response: ", e);
|
|
209
|
+
}
|
|
101
210
|
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
if (!response || !response.activity) throw new Error('Error: Failed to sign payload');
|
|
105
|
-
|
|
106
|
-
const activity = response.activity;
|
|
107
|
-
|
|
108
|
-
assertActivityCompleted(activity);
|
|
109
|
-
|
|
110
|
-
const authenticatorData = base64UrlToBuffer(stampedHeaderValue.authenticatorData);
|
|
211
|
+
const authenticatorData = isoBase64URL.toBuffer(stampedHeaderValue.authenticatorData);
|
|
111
212
|
const decodedClientDataJson = decodeClientDataJSON(stampedHeaderValue.clientDataJson);
|
|
112
|
-
|
|
113
|
-
const
|
|
213
|
+
// `payload` here is the hex string (e.g., "0x...") that was sent to Turnkey
|
|
214
|
+
// const originalPayloadBytes = Buffer.from(payload.startsWith('0x') ? payload.substring(2) : payload, 'hex');
|
|
215
|
+
const originalPayloadBytes = getBytesFromPayload(payload as Hex);
|
|
216
|
+
// Replace the challenge in decodedClientDataJson with the Base64URL of the *actual bytes*
|
|
217
|
+
// isoBase64URL.fromBuffer from simplewebauthn/server removes padding, which is standard for WebAuthn.
|
|
218
|
+
decodedClientDataJson.challenge = isoBase64URL.fromBuffer(originalPayloadBytes);
|
|
219
|
+
const typeIndex = JSON.stringify(decodedClientDataJson).indexOf('"type":"webauthn.get"');
|
|
220
|
+
const challengeIndex = JSON.stringify(decodedClientDataJson).indexOf('"challenge":');
|
|
221
|
+
if (typeIndex === -1 || challengeIndex === -1) {
|
|
222
|
+
throw new Error("Could not find type or challenge in clientDataJSON");
|
|
223
|
+
}
|
|
224
|
+
const signature = assertNonNull(response.activity.result.signRawPayloadResult);
|
|
114
225
|
|
|
115
226
|
return {
|
|
116
227
|
authenticatorData: toHex(authenticatorData),
|
|
117
228
|
clientDataJSON: JSON.stringify(decodedClientDataJson),
|
|
118
|
-
challengeIndex: BigInt(
|
|
119
|
-
typeIndex: BigInt(
|
|
120
|
-
r: BigInt(signature.r),
|
|
121
|
-
s: BigInt(signature.s)
|
|
122
|
-
}
|
|
123
|
-
}
|
|
229
|
+
challengeIndex: BigInt(challengeIndex),
|
|
230
|
+
typeIndex: BigInt(typeIndex),
|
|
231
|
+
r: BigInt(_add0x(signature.r)),
|
|
232
|
+
s: BigInt(_add0x(signature.s))
|
|
233
|
+
};
|
|
234
|
+
}
|
|
124
235
|
|
|
125
236
|
export const _stampAndSignTypedDataWithTurnkey = async <
|
|
126
237
|
const typedData extends TypedData | Record<string, unknown>,
|
|
127
238
|
primaryType extends keyof typedData | "EIP712Domain" = keyof typedData
|
|
128
239
|
> (client: TurnkeyClient, organizationId: string, signWith: Address, typedData: TypedDataDefinition<typedData, primaryType>): Promise<WebAuthnSignature> => {
|
|
129
|
-
|
|
130
240
|
const payload = hashTypedData(typedData);
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
241
|
+
let signedRequest;
|
|
242
|
+
try {
|
|
243
|
+
signedRequest = await client.stampSignRawPayload({
|
|
244
|
+
type: "ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2",
|
|
245
|
+
organizationId: organizationId,
|
|
246
|
+
parameters: {
|
|
247
|
+
signWith: signWith,
|
|
248
|
+
payload: payload,
|
|
249
|
+
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
|
|
250
|
+
hashFunction: "HASH_FUNCTION_NO_OP",
|
|
251
|
+
},
|
|
252
|
+
timestampMs: String(Date.now()), // millisecond timestamp
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
catch (e) {
|
|
256
|
+
console.log("SDK error: stampAndSignTypedData");
|
|
257
|
+
console.error(e);
|
|
258
|
+
}
|
|
143
259
|
|
|
144
|
-
if (!signedRequest)
|
|
145
|
-
|
|
260
|
+
if (!signedRequest)
|
|
261
|
+
throw new Error('Error: Failed to get signed request');
|
|
146
262
|
const stampedHeaderValue = JSON.parse(signedRequest.stamp.stampHeaderValue);
|
|
263
|
+
let response;
|
|
264
|
+
try {
|
|
265
|
+
const res = await fetch(signedRequest.url, {
|
|
266
|
+
method: 'POST',
|
|
267
|
+
headers: {
|
|
268
|
+
'Content-Type': 'application/json',
|
|
269
|
+
[signedRequest.stamp.stampHeaderName]: signedRequest.stamp.stampHeaderValue
|
|
270
|
+
},
|
|
271
|
+
body: signedRequest.body
|
|
272
|
+
});
|
|
273
|
+
if (!res.ok) {
|
|
274
|
+
const errorBody = await res.text();
|
|
275
|
+
throw new Error(`SDK Turnkey API request failed: ${res.status} ${res.statusText} - ${errorBody}`);
|
|
276
|
+
}
|
|
277
|
+
const responseData = await res.json();
|
|
278
|
+
response = responseData;
|
|
279
|
+
}
|
|
280
|
+
catch (e) {
|
|
281
|
+
console.log("SDK error with response: ", e);
|
|
282
|
+
}
|
|
147
283
|
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
if (!response || !response.activity) throw new Error('Error: Failed to sign payload');
|
|
151
|
-
|
|
152
|
-
const activity = response.activity;
|
|
153
|
-
|
|
154
|
-
assertActivityCompleted(activity);
|
|
155
|
-
|
|
156
|
-
const authenticatorData = base64UrlToBuffer(stampedHeaderValue.authenticatorData);
|
|
284
|
+
const authenticatorData = isoBase64URL.toBuffer(stampedHeaderValue.authenticatorData);
|
|
157
285
|
const decodedClientDataJson = decodeClientDataJSON(stampedHeaderValue.clientDataJson);
|
|
158
|
-
const signature = assertNonNull(activity.result.signRawPayloadResult);
|
|
286
|
+
const signature = assertNonNull(response.activity.result.signRawPayloadResult);
|
|
159
287
|
|
|
160
288
|
return {
|
|
161
289
|
authenticatorData: toHex(authenticatorData),
|
|
162
290
|
clientDataJSON: JSON.stringify(decodedClientDataJson),
|
|
163
291
|
challengeIndex: BigInt(23),
|
|
164
292
|
typeIndex: BigInt(1),
|
|
165
|
-
r: BigInt(signature.r),
|
|
166
|
-
s: BigInt(signature.s)
|
|
167
|
-
}
|
|
293
|
+
r: BigInt(_add0x(signature.r)),
|
|
294
|
+
s: BigInt(_add0x(signature.s))
|
|
295
|
+
};
|
|
168
296
|
}
|
package/src/logic/utils.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { Hex, isHex } from "viem";
|
|
1
|
+
import { Hex, isHex, toHex } from "viem";
|
|
2
|
+
import { AsnParser } from "@peculiar/asn1-schema";
|
|
3
|
+
import { ECDSASigValue } from "@peculiar/asn1-ecc";
|
|
2
4
|
import { customErrors } from "./constants.js"
|
|
3
5
|
|
|
4
6
|
export const _add0x = (data: Hex | string): Hex => {
|
|
@@ -84,3 +86,38 @@ export function parseDEREncodedSignature(signature: Uint8Array): {
|
|
|
84
86
|
s: Buffer.from(s).toString("hex"),
|
|
85
87
|
};
|
|
86
88
|
}
|
|
89
|
+
|
|
90
|
+
export function shouldRemoveLeadingZero(bytes: Uint8Array): boolean {
|
|
91
|
+
return bytes[0] === 0x0 && (bytes[1] & (1 << 7)) !== 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function concatUint8Arrays(arrays: Uint8Array[]): Uint8Array {
|
|
95
|
+
let pointer = 0;
|
|
96
|
+
const totalLength = arrays.reduce((prev, curr) => prev + curr.length, 0);
|
|
97
|
+
|
|
98
|
+
const toReturn = new Uint8Array(totalLength);
|
|
99
|
+
|
|
100
|
+
arrays.forEach((arr) => {
|
|
101
|
+
toReturn.set(arr, pointer);
|
|
102
|
+
pointer += arr.length;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return toReturn;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function parseSignature(signature: Uint8Array) {
|
|
109
|
+
const parsedSignature = AsnParser.parse(signature, ECDSASigValue);
|
|
110
|
+
let rBytes = new Uint8Array(parsedSignature.r);
|
|
111
|
+
let sBytes = new Uint8Array(parsedSignature.s);
|
|
112
|
+
if (shouldRemoveLeadingZero(rBytes)) {
|
|
113
|
+
rBytes = rBytes.slice(1);
|
|
114
|
+
}
|
|
115
|
+
if (shouldRemoveLeadingZero(sBytes)) {
|
|
116
|
+
sBytes = sBytes.slice(1);
|
|
117
|
+
}
|
|
118
|
+
const finalSignature = concatUint8Arrays([rBytes, sBytes]);
|
|
119
|
+
return {
|
|
120
|
+
r: toHex(finalSignature.slice(0, 32)),
|
|
121
|
+
s: toHex(finalSignature.slice(32)),
|
|
122
|
+
};
|
|
123
|
+
}
|