kokio-sdk 0.0.10 → 0.1.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.
|
@@ -1,19 +1,45 @@
|
|
|
1
1
|
name: Publish SDK
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
|
-
# Publishing only happens when pushing to publish branch is made
|
|
5
4
|
push:
|
|
6
5
|
branches: [ "main" ]
|
|
7
6
|
|
|
7
|
+
permissions:
|
|
8
|
+
id-token: write
|
|
9
|
+
contents: write
|
|
10
|
+
|
|
8
11
|
jobs:
|
|
9
12
|
publish-npm:
|
|
13
|
+
if: contains(github.event.head_commit.message, 'Release')
|
|
10
14
|
runs-on: ubuntu-latest
|
|
15
|
+
|
|
11
16
|
steps:
|
|
12
17
|
- uses: actions/checkout@v4
|
|
18
|
+
|
|
13
19
|
- uses: actions/setup-node@v4
|
|
14
20
|
with:
|
|
15
21
|
node-version: 22
|
|
16
22
|
registry-url: https://registry.npmjs.org/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
|
|
24
|
+
- name: Get version from package.json
|
|
25
|
+
id: pkg
|
|
26
|
+
run: |
|
|
27
|
+
VERSION=$(node -p "require('./package.json').version")
|
|
28
|
+
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
29
|
+
|
|
30
|
+
- name: Create Git tag
|
|
31
|
+
run: |
|
|
32
|
+
git tag v${{ steps.pkg.outputs.version }}
|
|
33
|
+
git push origin v${{ steps.pkg.outputs.version }}
|
|
34
|
+
|
|
35
|
+
- name: Install deps (also runs postinstall)
|
|
36
|
+
run: npm install
|
|
37
|
+
|
|
38
|
+
- name: Publish to npm
|
|
39
|
+
run: npm publish --access public
|
|
40
|
+
|
|
41
|
+
- name: Create GitHub Release
|
|
42
|
+
uses: softprops/action-gh-release@v2
|
|
43
|
+
with:
|
|
44
|
+
tag_name: v${{ steps.pkg.outputs.version }}
|
|
45
|
+
generate_release_notes: true
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kokio-sdk",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/esm/config.js",
|
|
@@ -42,8 +42,6 @@
|
|
|
42
42
|
"@peculiar/asn1-ecc": "^2.3.15",
|
|
43
43
|
"@peculiar/asn1-schema": "^2.3.15",
|
|
44
44
|
"@simplewebauthn/server": "^13.1.1",
|
|
45
|
-
"@turnkey/http": "^3.0.0",
|
|
46
|
-
"@turnkey/react-native-passkey-stamper": "^1.0.13",
|
|
47
45
|
"ethers": "^6.13.5",
|
|
48
46
|
"react-native-passkey": "^3.1.0",
|
|
49
47
|
"viem": "^2.23.5"
|
package/src/config.ts
CHANGED
|
@@ -8,11 +8,9 @@ import { DeviceWalletSubPackage } from "./interface/deviceWalletClass.js";
|
|
|
8
8
|
import { ESIMWalletSubPackage } from "./interface/eSIMWalletClass.js";
|
|
9
9
|
import { ESIMWalletFactorySubPackage } from "./interface/eSIMWalletFactoryClass.js";
|
|
10
10
|
import { SmartAccountClient } from "@aa-sdk/core";
|
|
11
|
-
import { TurnkeyClient } from "@turnkey/http";
|
|
12
11
|
|
|
13
12
|
export class Kokio {
|
|
14
13
|
viemWalletClient: WalletClient;
|
|
15
|
-
turnkeyClient: TurnkeyClient;
|
|
16
14
|
credentialId: string;
|
|
17
15
|
rpId: string;
|
|
18
16
|
organizationId: string;
|
|
@@ -31,7 +29,6 @@ export class Kokio {
|
|
|
31
29
|
|
|
32
30
|
constructor(
|
|
33
31
|
viemWalletClient: WalletClient,
|
|
34
|
-
turnkeyClient: TurnkeyClient,
|
|
35
32
|
credentialId: string,
|
|
36
33
|
rpId: string,
|
|
37
34
|
organizationId: string,
|
|
@@ -42,7 +39,6 @@ export class Kokio {
|
|
|
42
39
|
eSIMWalletAddress?: Address
|
|
43
40
|
) {
|
|
44
41
|
this.viemWalletClient = viemWalletClient;
|
|
45
|
-
this.turnkeyClient = turnkeyClient;
|
|
46
42
|
this.credentialId = credentialId;
|
|
47
43
|
this.rpId = rpId;
|
|
48
44
|
this.organizationId = organizationId;
|
|
@@ -51,7 +47,7 @@ export class Kokio {
|
|
|
51
47
|
|
|
52
48
|
this.constants = new ConstantsSubPackage(this.viemWalletClient, this.pimlicoAPIKey);
|
|
53
49
|
|
|
54
|
-
this.smartAccount = new SmartAccountSubPackage(this.viemWalletClient, this.
|
|
50
|
+
this.smartAccount = new SmartAccountSubPackage(this.viemWalletClient, this.credentialId, this.rpId, this.organizationId, this.pimlicoAPIKey, this.gasPolicyId);
|
|
55
51
|
this.deviceWalletFactory = smartAccountClient? new DeviceWalletFactorySubPackage(viemWalletClient, smartAccountClient): undefined;
|
|
56
52
|
this.eSIMWalletFactory = smartAccountClient? new ESIMWalletFactorySubPackage(viemWalletClient, smartAccountClient): undefined;
|
|
57
53
|
this.lazyWalletRegistry = smartAccountClient? new LazyWalletRegistrySubPackage(smartAccountClient): undefined;
|
|
@@ -2,21 +2,18 @@ import { Address, WalletClient } from "viem";
|
|
|
2
2
|
import { _getSmartWallet, _getSmartWalletClient } from "../logic/account-kit/createSmartAccount.js";
|
|
3
3
|
import { P256Key, SignedRequest } from "../types";
|
|
4
4
|
import { SmartAccountClient, SmartContractAccount } from "@aa-sdk/core";
|
|
5
|
-
import { TurnkeyClient } from "@turnkey/http";
|
|
6
5
|
|
|
7
6
|
export class SmartAccountSubPackage {
|
|
8
7
|
|
|
9
8
|
client;
|
|
10
|
-
turnkeyClient;
|
|
11
9
|
credentialId;
|
|
12
10
|
rpId;
|
|
13
11
|
organizationId;
|
|
14
12
|
pimlicoAPIKey;
|
|
15
13
|
gasPolicyId;
|
|
16
14
|
|
|
17
|
-
constructor(client: WalletClient,
|
|
15
|
+
constructor(client: WalletClient, credentialId: string, rpId: string, organiationId: string, pimlicoAPIKey: string, gasPolicyId: string) {
|
|
18
16
|
this.client = client;
|
|
19
|
-
this.turnkeyClient = turnkeyClient;
|
|
20
17
|
this.credentialId = credentialId;
|
|
21
18
|
this.rpId = rpId;
|
|
22
19
|
this.organizationId = organiationId;
|
|
@@ -25,7 +22,7 @@ export class SmartAccountSubPackage {
|
|
|
25
22
|
}
|
|
26
23
|
|
|
27
24
|
getSmartWallet (deviceUniqueIdentifier: string, deviceWalletOwnerKey: P256Key, salt: bigint) {
|
|
28
|
-
return _getSmartWallet(this.client, this.
|
|
25
|
+
return _getSmartWallet(this.client, this.credentialId, this.rpId, this.organizationId, deviceUniqueIdentifier, deviceWalletOwnerKey, salt);
|
|
29
26
|
}
|
|
30
27
|
|
|
31
28
|
getSmartWalletClient (account: SmartContractAccount) {
|
|
@@ -1,38 +1,113 @@
|
|
|
1
1
|
import {
|
|
2
|
-
AccountOp,
|
|
3
|
-
|
|
4
|
-
getEntryPoint,
|
|
5
|
-
SmartContractAccount,
|
|
6
|
-
toSmartContractAccount,
|
|
7
|
-
split,
|
|
8
|
-
SmartAccountClient
|
|
2
|
+
AccountOp, createSmartAccountClient, getEntryPoint, SmartContractAccount,
|
|
3
|
+
toSmartContractAccount, split, SmartAccountClient
|
|
9
4
|
} from "@aa-sdk/core";
|
|
10
5
|
import {
|
|
11
|
-
http,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
WalletClient,
|
|
15
|
-
Hex,
|
|
16
|
-
encodeFunctionData,
|
|
17
|
-
Address,
|
|
18
|
-
encodePacked,
|
|
19
|
-
encodeAbiParameters,
|
|
20
|
-
getContract,
|
|
21
|
-
TypedDataDefinition,
|
|
22
|
-
TypedData,
|
|
23
|
-
hashMessage,
|
|
24
|
-
stringToBytes,
|
|
25
|
-
toHex
|
|
6
|
+
http, type SignableMessage, type Hash, WalletClient, Hex, encodeFunctionData,
|
|
7
|
+
Address, encodePacked, encodeAbiParameters, getContract,
|
|
8
|
+
TypedDataDefinition, TypedData, hashMessage, stringToBytes, toHex, hashTypedData, hexToBytes, bytesToHex
|
|
26
9
|
} from "viem";
|
|
27
|
-
import { TurnkeyClient } from "@turnkey/http";
|
|
28
10
|
import { BytesLike, ethers, hexlify, toBeHex, toBigInt, zeroPadValue } from "ethers";
|
|
29
11
|
import { _getChainSpecificConstants, ZERO, SIGNATURE_VALIDITY_SECONDS } from "../constants.js";
|
|
30
12
|
import { _add0x, _concatUint8Arrays, _remove0x, _shouldRemoveLeadingZero } from "../utils.js";
|
|
31
13
|
import { P256Key, WebAuthnSignature } from "../../types.js";
|
|
32
14
|
import { DeviceWallet, DeviceWalletFactory } from "../../abis/index.js";
|
|
33
|
-
import { _signMessageWithTurnkey, _signTypedDataWithTurnkey, _stamp, _stampAndSignMessageWithTurnkey, _stampAndSignTypedDataWithTurnkey } from "../services/turnkeyClient.js";
|
|
34
15
|
import { alchemyGasManagerMiddleware } from "@account-kit/infra";
|
|
35
16
|
|
|
17
|
+
import { decodeAttestationObject, decodeClientDataJSON, isoBase64URL, parseAuthenticatorData } from "@simplewebauthn/server/helpers";
|
|
18
|
+
import { Passkey, PasskeyGetRequest, PasskeyGetResult } from "react-native-passkey";
|
|
19
|
+
import { p256 } from "@noble/curves/nist.js";
|
|
20
|
+
|
|
21
|
+
type BrokenPasskeyGetResult = PasskeyGetResult | string;
|
|
22
|
+
|
|
23
|
+
enum AuthenticatorTransport {
|
|
24
|
+
usb = "usb",
|
|
25
|
+
nfc = "nfc",
|
|
26
|
+
ble = "ble",
|
|
27
|
+
smartCard = "smart-card",
|
|
28
|
+
hybrid = "hybrid",
|
|
29
|
+
internal = "internal"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/*
|
|
33
|
+
** Stamp is client-side authentication. Since the passkeys are one the user's mobile device
|
|
34
|
+
** react-native-passkey helps fetch passkey for the user (provided credentialId, rpId).
|
|
35
|
+
** This is exactly how the WebAuthn.sol contract needs it to be.
|
|
36
|
+
*/
|
|
37
|
+
export const _stamp = async (credentialId: string, rpId: string, payload: Hex): Promise<WebAuthnSignature> => {
|
|
38
|
+
const signingOptions: PasskeyGetRequest = {
|
|
39
|
+
challenge: isoBase64URL.fromBuffer(
|
|
40
|
+
(hexToBytes(payload) as unknown) as Uint8Array<ArrayBuffer>
|
|
41
|
+
),
|
|
42
|
+
allowCredentials: [{
|
|
43
|
+
id: credentialId,
|
|
44
|
+
type: "public-key",
|
|
45
|
+
transports: [AuthenticatorTransport.internal]
|
|
46
|
+
}],
|
|
47
|
+
rpId,
|
|
48
|
+
userVerification: "required"
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
let authenticationResult;
|
|
52
|
+
try {
|
|
53
|
+
authenticationResult = await Passkey.get(signingOptions);
|
|
54
|
+
} catch (e) {
|
|
55
|
+
console.log("Failed to get authenticationResult");
|
|
56
|
+
console.error(JSON.stringify(e, Object.getOwnPropertyNames(e)))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// See https://github.com/f-23/react-native-passkey/issues/54
|
|
60
|
+
// On Android the typedef lies. Authentication result is actually a string!
|
|
61
|
+
// TODO: remove me once the above is resolved.
|
|
62
|
+
const brokenAuthenticationResult =
|
|
63
|
+
authenticationResult as BrokenPasskeyGetResult;
|
|
64
|
+
if (typeof brokenAuthenticationResult === "string") {
|
|
65
|
+
authenticationResult = JSON.parse(brokenAuthenticationResult);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const { clientDataJSON, authenticatorData, signature } = authenticationResult.response;
|
|
69
|
+
|
|
70
|
+
// 1. Decode clientDataJSON
|
|
71
|
+
const clientDataJSONBuffer = isoBase64URL.toBuffer(clientDataJSON);
|
|
72
|
+
const clientDataJSONString = new TextDecoder().decode(clientDataJSONBuffer);
|
|
73
|
+
|
|
74
|
+
// 2. Calculate indices for the contract (byte offsets)
|
|
75
|
+
const typeSearchString = '"type":"webauthn.get"';
|
|
76
|
+
const challengeSearchString = '"challenge":';
|
|
77
|
+
|
|
78
|
+
const typeIndex = clientDataJSONString.indexOf(typeSearchString);
|
|
79
|
+
const challengeIndex = clientDataJSONString.indexOf(challengeSearchString);
|
|
80
|
+
|
|
81
|
+
if (typeIndex === -1) {
|
|
82
|
+
console.warn(`Warning: Could not find type substring '${typeSearchString}' in clientDataJSON. Setting typeIndex to 0.`);
|
|
83
|
+
}
|
|
84
|
+
if (challengeIndex === -1) {
|
|
85
|
+
throw new Error(`Could not find challenge substring '${challengeSearchString}' in clientDataJSON for index calculation.`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 3. Decode authenticatorData
|
|
89
|
+
const authenticatorDataBytes = isoBase64URL.toBuffer(authenticatorData);
|
|
90
|
+
const authenticatorDataHex = bytesToHex(authenticatorDataBytes);
|
|
91
|
+
|
|
92
|
+
// 4. Decode signature (ASN.1 DER encoded)
|
|
93
|
+
const signatureBytes = isoBase64URL.toBuffer(signature);
|
|
94
|
+
let parsedSignature = p256.Signature.fromDER(signatureBytes);
|
|
95
|
+
parsedSignature = parsedSignature.normalizeS();
|
|
96
|
+
let r = parsedSignature.r;
|
|
97
|
+
let s = parsedSignature.s;
|
|
98
|
+
|
|
99
|
+
const webAuthnSig = {
|
|
100
|
+
authenticatorData: authenticatorDataHex,
|
|
101
|
+
clientDataJSON: clientDataJSONString,
|
|
102
|
+
challengeIndex: BigInt(challengeIndex),
|
|
103
|
+
typeIndex: BigInt(typeIndex),
|
|
104
|
+
r: r,
|
|
105
|
+
s: s
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
return webAuthnSig;
|
|
109
|
+
}
|
|
110
|
+
|
|
36
111
|
const _encodeExecute = async (tx: AccountOp) => {
|
|
37
112
|
|
|
38
113
|
return encodeFunctionData({
|
|
@@ -177,7 +252,7 @@ const _signMessage = async (message: SignableMessage, credentialId: string, rpId
|
|
|
177
252
|
const payload = hashMessage({raw: stringToBytes(message as string)});
|
|
178
253
|
// The original message is passed to the stamp and sign function.
|
|
179
254
|
// The stamp and sign function creates the EIP-191 digest hash using its hashMessage function
|
|
180
|
-
// The result of the hashMessage(message) will be the `payload` used
|
|
255
|
+
// The result of the hashMessage(message) will be the `payload` used as a challenge
|
|
181
256
|
const webAuthnSignature = await _stamp(credentialId, rpId, payload);
|
|
182
257
|
|
|
183
258
|
return _encodeSignature(webAuthnSignature, validUntil);
|
|
@@ -186,12 +261,20 @@ const _signMessage = async (message: SignableMessage, credentialId: string, rpId
|
|
|
186
261
|
const _signTypedData = async <
|
|
187
262
|
const typedData extends TypedData | Record<string, unknown>,
|
|
188
263
|
primaryType extends keyof typedData | "EIP712Domain" = keyof typedData
|
|
189
|
-
> (typedData: TypedDataDefinition<typedData, primaryType>,
|
|
264
|
+
> (typedData: TypedDataDefinition<typedData, primaryType>, organiationId: string, signWith: Address): Promise<Hex> => {
|
|
190
265
|
|
|
191
266
|
// signature valid until, UNIX timestamp in seconds
|
|
192
267
|
const validUntil = Math.floor(Date.now() / 1000) + SIGNATURE_VALIDITY_SECONDS;
|
|
193
268
|
|
|
194
|
-
|
|
269
|
+
// TODO: Implement stamping for Typed Data
|
|
270
|
+
let webAuthnSignature = {
|
|
271
|
+
authenticatorData: `0x` as `0x${string}`,
|
|
272
|
+
clientDataJSON: "",
|
|
273
|
+
challengeIndex: BigInt(0),
|
|
274
|
+
typeIndex: BigInt(0),
|
|
275
|
+
r: BigInt(0),
|
|
276
|
+
s: BigInt(0)
|
|
277
|
+
}
|
|
195
278
|
|
|
196
279
|
return _encodeSignature(webAuthnSignature, validUntil);
|
|
197
280
|
}
|
|
@@ -215,7 +298,6 @@ const _signUserOperationHash = async (credentialId: string, rpId: string, userOp
|
|
|
215
298
|
|
|
216
299
|
export const _getSmartWallet = async (
|
|
217
300
|
client: WalletClient,
|
|
218
|
-
turnkeyClient: TurnkeyClient,
|
|
219
301
|
credentialId: string,
|
|
220
302
|
rpId: string,
|
|
221
303
|
organiationId: string,
|
|
@@ -253,7 +335,7 @@ export const _getSmartWallet = async (
|
|
|
253
335
|
|
|
254
336
|
signMessage: async ({ message}): Promise<Hash> => _signMessage(message, credentialId, rpId),
|
|
255
337
|
|
|
256
|
-
signTypedData: async (typedData): Promise<Hash> => _signTypedData(typedData,
|
|
338
|
+
signTypedData: async (typedData): Promise<Hash> => _signTypedData(typedData, organiationId, signWith),
|
|
257
339
|
|
|
258
340
|
/// OPTIONAL PARAMS ///
|
|
259
341
|
// if you already know your account's address, pass that in here to avoid generating a new counterfactual
|
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
import { assertActivityCompleted, assertNonNull, TurnkeyClient, TActivityResponse } from "@turnkey/http"
|
|
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
|
-
import { WebAuthnSignature } from "../../types.js";
|
|
6
|
-
import { Passkey, PasskeyGetResult } from "react-native-passkey";
|
|
7
|
-
import { p256 } from "@noble/curves/nist.js";
|
|
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) as unknown) as Uint8Array<ArrayBuffer> ), // 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
|
-
};
|
|
92
|
-
|
|
93
|
-
export const _signMessageWithTurnkey = async (client: TurnkeyClient, organizationId: string, signWith: Address, message: SignableMessage): Promise<WebAuthnSignature> => {
|
|
94
|
-
|
|
95
|
-
const payload = hashMessage(message);
|
|
96
|
-
|
|
97
|
-
const { activity } = await client.signRawPayload({
|
|
98
|
-
type: "ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2",
|
|
99
|
-
organizationId: organizationId,
|
|
100
|
-
parameters: {
|
|
101
|
-
signWith: signWith,
|
|
102
|
-
payload: payload,
|
|
103
|
-
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
|
|
104
|
-
hashFunction: "HASH_FUNCTION_NO_OP",
|
|
105
|
-
},
|
|
106
|
-
timestampMs: String(Date.now()), // millisecond timestamp
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
assertActivityCompleted(activity);
|
|
110
|
-
|
|
111
|
-
const attestationObject = assertNonNull(activity.intent.createOrganizationIntent?.rootAuthenticator.attestation.response.attestationObject);
|
|
112
|
-
const decodedAttestationObj = decodeAttestationObject((base64UrlToBuffer(attestationObject) as unknown) as Uint8Array<ArrayBuffer>)
|
|
113
|
-
|
|
114
|
-
const clientDataJson = assertNonNull(activity.intent.createOrganizationIntent?.rootAuthenticator.attestation.response.clientDataJson);
|
|
115
|
-
const decodedClientDataJson = decodeClientDataJSON(clientDataJson);
|
|
116
|
-
|
|
117
|
-
const signature = assertNonNull(activity.result.signRawPayloadResult);
|
|
118
|
-
|
|
119
|
-
return {
|
|
120
|
-
authenticatorData: toHex(decodedAttestationObj.get("authData")),
|
|
121
|
-
clientDataJSON: JSON.stringify(decodedClientDataJson),
|
|
122
|
-
challengeIndex: BigInt(23),
|
|
123
|
-
typeIndex: BigInt(1),
|
|
124
|
-
r: BigInt(signature.r),
|
|
125
|
-
s: BigInt(signature.s)
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export const _signTypedDataWithTurnkey = async <
|
|
130
|
-
const typedData extends TypedData | Record<string, unknown>,
|
|
131
|
-
primaryType extends keyof typedData | "EIP712Domain" = keyof typedData
|
|
132
|
-
> (client: TurnkeyClient, organizationId: string, signWith: Address, typedData: TypedDataDefinition<typedData, primaryType>): Promise<WebAuthnSignature> => {
|
|
133
|
-
|
|
134
|
-
const payload = hashTypedData(typedData);
|
|
135
|
-
|
|
136
|
-
const { activity } = await client.signRawPayload({
|
|
137
|
-
type: "ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2",
|
|
138
|
-
organizationId: organizationId,
|
|
139
|
-
parameters: {
|
|
140
|
-
signWith: signWith,
|
|
141
|
-
payload: payload,
|
|
142
|
-
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
|
|
143
|
-
hashFunction: "HASH_FUNCTION_NO_OP",
|
|
144
|
-
},
|
|
145
|
-
timestampMs: String(Date.now()), // millisecond timestamp
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
assertActivityCompleted(activity);
|
|
149
|
-
|
|
150
|
-
const attestationObject = assertNonNull(activity.intent.createOrganizationIntent?.rootAuthenticator.attestation.response.attestationObject);
|
|
151
|
-
const decodedAttestationObj = decodeAttestationObject((base64UrlToBuffer(attestationObject) as unknown) as Uint8Array<ArrayBuffer>)
|
|
152
|
-
|
|
153
|
-
const clientDataJson = assertNonNull(activity.intent.createOrganizationIntent?.rootAuthenticator.attestation.response.clientDataJson);
|
|
154
|
-
const decodedClientDataJson = decodeClientDataJSON(clientDataJson);
|
|
155
|
-
|
|
156
|
-
const signature = assertNonNull(activity.result.signRawPayloadResult);
|
|
157
|
-
|
|
158
|
-
return {
|
|
159
|
-
authenticatorData: toHex(decodedAttestationObj.get("authData")),
|
|
160
|
-
clientDataJSON: JSON.stringify(decodedClientDataJson),
|
|
161
|
-
challengeIndex: BigInt(23),
|
|
162
|
-
typeIndex: BigInt(1),
|
|
163
|
-
r: BigInt(signature.r),
|
|
164
|
-
s: BigInt(signature.s)
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
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');
|
|
188
|
-
|
|
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
|
-
}
|
|
210
|
-
|
|
211
|
-
const authenticatorData = isoBase64URL.toBuffer(stampedHeaderValue.authenticatorData);
|
|
212
|
-
const decodedClientDataJson = decodeClientDataJSON(stampedHeaderValue.clientDataJson);
|
|
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 as unknown) as Uint8Array<ArrayBuffer>);
|
|
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);
|
|
225
|
-
|
|
226
|
-
return {
|
|
227
|
-
authenticatorData: toHex(authenticatorData),
|
|
228
|
-
clientDataJSON: JSON.stringify(decodedClientDataJson),
|
|
229
|
-
challengeIndex: BigInt(challengeIndex),
|
|
230
|
-
typeIndex: BigInt(typeIndex),
|
|
231
|
-
r: BigInt(_add0x(signature.r)),
|
|
232
|
-
s: BigInt(_add0x(signature.s))
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
export const _stampAndSignTypedDataWithTurnkey = async <
|
|
237
|
-
const typedData extends TypedData | Record<string, unknown>,
|
|
238
|
-
primaryType extends keyof typedData | "EIP712Domain" = keyof typedData
|
|
239
|
-
> (client: TurnkeyClient, organizationId: string, signWith: Address, typedData: TypedDataDefinition<typedData, primaryType>): Promise<WebAuthnSignature> => {
|
|
240
|
-
const payload = hashTypedData(typedData);
|
|
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
|
-
}
|
|
259
|
-
|
|
260
|
-
if (!signedRequest)
|
|
261
|
-
throw new Error('Error: Failed to get signed request');
|
|
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
|
-
}
|
|
283
|
-
|
|
284
|
-
const authenticatorData = isoBase64URL.toBuffer(stampedHeaderValue.authenticatorData);
|
|
285
|
-
const decodedClientDataJson = decodeClientDataJSON(stampedHeaderValue.clientDataJson);
|
|
286
|
-
const signature = assertNonNull(response.activity.result.signRawPayloadResult);
|
|
287
|
-
|
|
288
|
-
return {
|
|
289
|
-
authenticatorData: toHex(authenticatorData),
|
|
290
|
-
clientDataJSON: JSON.stringify(decodedClientDataJson),
|
|
291
|
-
challengeIndex: BigInt(23),
|
|
292
|
-
typeIndex: BigInt(1),
|
|
293
|
-
r: BigInt(_add0x(signature.r)),
|
|
294
|
-
s: BigInt(_add0x(signature.s))
|
|
295
|
-
};
|
|
296
|
-
}
|