diff --git a/.changeset/short-avocados-lick.md b/.changeset/short-avocados-lick.md
new file mode 100644
index 0000000000000..925538ac75219
--- /dev/null
+++ b/.changeset/short-avocados-lick.md
@@ -0,0 +1,5 @@
+---
+'@mysten/sui': minor
+---
+
+Add legacyAddress flag to zklogin methods that generate addresses
diff --git a/.changeset/silent-jeans-matter.md b/.changeset/silent-jeans-matter.md
new file mode 100644
index 0000000000000..605f92287281a
--- /dev/null
+++ b/.changeset/silent-jeans-matter.md
@@ -0,0 +1,31 @@
+---
+'@mysten/sui': minor
+'@mysten/zklogin': minor
+---
+
+All functionality from `@mysten/zklogin` has been moved to `@mysten/sui/zklogin`
+
+For most methods, simply replace the `@mysten/zklogin` import with `@mysten/sui/zklogin`
+
+2 Methods require one small additional change:
+
+`computeZkLoginAddress` and `jwtToAddress` have new `legacyAddress` flags which must be set to true for backwards compatibility:
+
+```diff
+- import { computeZkLoginAddress, jwtToAddress } from '@mysten/zklogin';
++ import { computeZkLoginAddress, jwtToAddress } from '@mysten/sui/zklogin';
+
+ const address = jwtToAddress(
+ jwtAsString,
+ salt,
++ true
+ );
+ const address = computeZkLoginAddress({
+ claimName,
+ claimValue,
+ iss,
+ aud,
+ userSalt: BigInt(salt),
++ legacyAddress: true,
+ });
+```
diff --git a/apps/wallet/src/background/accounts/zklogin/ZkLoginAccount.ts b/apps/wallet/src/background/accounts/zklogin/ZkLoginAccount.ts
index 9cedb3a0f5c85..eb0ac5d53720f 100644
--- a/apps/wallet/src/background/accounts/zklogin/ZkLoginAccount.ts
+++ b/apps/wallet/src/background/accounts/zklogin/ZkLoginAccount.ts
@@ -6,7 +6,7 @@ import { type NetworkEnvType } from '_src/shared/api-env';
import { deobfuscate, obfuscate } from '_src/shared/cryptography/keystore';
import { fromExportedKeypair } from '_src/shared/utils/from-exported-keypair';
import { toSerializedSignature, type PublicKey } from '@mysten/sui/cryptography';
-import { computeZkLoginAddress, genAddressSeed, getZkLoginSignature } from '@mysten/zklogin';
+import { computeZkLoginAddress, genAddressSeed, getZkLoginSignature } from '@mysten/sui/zklogin';
import { blake2b } from '@noble/hashes/blake2b';
import { decodeJwt } from 'jose';
diff --git a/apps/wallet/src/background/accounts/zklogin/utils.ts b/apps/wallet/src/background/accounts/zklogin/utils.ts
index e4234f6334f98..6a1e29880fa51 100644
--- a/apps/wallet/src/background/accounts/zklogin/utils.ts
+++ b/apps/wallet/src/background/accounts/zklogin/utils.ts
@@ -10,7 +10,7 @@ import {
generateRandomness,
getExtendedEphemeralPublicKey,
type getZkLoginSignature,
-} from '@mysten/zklogin';
+} from '@mysten/sui/zklogin';
import { randomBytes } from '@noble/hashes/utils';
import { base64url } from 'jose';
import { v4 as uuidV4 } from 'uuid';
diff --git a/docs/content/guides/developer/cryptography/zklogin-integration.mdx b/docs/content/guides/developer/cryptography/zklogin-integration.mdx
index a3fbd2196200b..638e064720cab 100644
--- a/docs/content/guides/developer/cryptography/zklogin-integration.mdx
+++ b/docs/content/guides/developer/cryptography/zklogin-integration.mdx
@@ -18,13 +18,13 @@ Let's dive into the specific implementation details.
To use the zkLogin TypeScript SDK in your project, run the following command in your project root:
```sh npm2yarn
-npm install @mysten/zklogin
+npm install @mysten/sui
```
If you want to use the latest experimental version:
```sh npm2yarn
-npm install @mysten/zklogin@experimental
+npm install @mysten/sui@experimental
```
## Get JWT
@@ -36,7 +36,7 @@ npm install @mysten/zklogin@experimental
1. Assemble the OAuth URL with configured client ID, redirect URL, ephemeral public key and nonce: This is what the application sends the user to complete the login flow with a computed [nonce](#notations).
```typescript
-import { generateNonce, generateRandomness } from '@mysten/zklogin';
+import { generateNonce, generateRandomness } from '@mysten/sui/zklogin';
const FULLNODE_URL = 'https://fullnode.devnet.sui.io'; // replace with the RPC URL you want to use
const suiClient = new SuiClient({ url: FULLNODE_URL });
@@ -54,15 +54,15 @@ For some providers ("Yes" for "Auth Flow Only"), the JWT can be found immediatel
For other providers ("No" for "Auth Flow Only"), the auth flow only returns a code (`$AUTH_CODE`) in redirect URL. To retrieve the JWT, an additional POST call is required with "Token Exchange URL".
-| Provider | Auth Flow URL | Token Exchange URL | Auth Flow Only |
-| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | -------------- |
-| Google | `https://accounts.google.com/o/oauth2/v2/auth?client_id=$CLIENT_ID&response_type=id_token&redirect_uri=$REDIRECT_URL&scope=openid&nonce=$NONCE` | N/A | Yes |
-| Facebook | `https://www.facebook.com/v17.0/dialog/oauth?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URL&scope=openid&nonce=$NONCE&response_type=id_token` | N/A | Yes |
+| Provider | Auth Flow URL | Token Exchange URL | Auth Flow Only |
+| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | -------------- |
+| Google | `https://accounts.google.com/o/oauth2/v2/auth?client_id=$CLIENT_ID&response_type=id_token&redirect_uri=$REDIRECT_URL&scope=openid&nonce=$NONCE` | N/A | Yes |
+| Facebook | `https://www.facebook.com/v17.0/dialog/oauth?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URL&scope=openid&nonce=$NONCE&response_type=id_token` | N/A | Yes |
| Twitch | `https://id.twitch.tv/oauth2/authorize?client_id=$CLIENT_ID&force_verify=true&lang=en&login_type=login&redirect_uri=$REDIRECT_URL&response_type=id_token&scope=openid&nonce=$NONCE` | N/A | Yes |
-| Kakao | `https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URL&nonce=$NONCE` | `https://kauth.kakao.com/oauth/token?grant_type=authorization_code&client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URL&code=$AUTH_CODE` | No |
-| Apple | `https://appleid.apple.com/auth/authorize?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URL&scope=email&response_mode=form_post&response_type=code%20id_token&nonce=$NONCE` | N/A | Yes |
-| Slack | `https://slack.com/openid/connect/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URL&nonce=$NONCE&scope=openid` | `https://slack.com/api/openid.connect.token?code=$AUTH_CODE&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET` | Yes |
-| Microsoft | `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=$CLIENT_ID&scope=openid&response_type=id_token&nonce=$NONCE&redirect_uri=$REDIRECT_URL` | Yes |
+| Kakao | `https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URL&nonce=$NONCE` | `https://kauth.kakao.com/oauth/token?grant_type=authorization_code&client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URL&code=$AUTH_CODE` | No |
+| Apple | `https://appleid.apple.com/auth/authorize?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URL&scope=email&response_mode=form_post&response_type=code%20id_token&nonce=$NONCE` | N/A | Yes |
+| Slack | `https://slack.com/openid/connect/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URL&nonce=$NONCE&scope=openid` | `https://slack.com/api/openid.connect.token?code=$AUTH_CODE&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET` | Yes |
+| Microsoft | `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=$CLIENT_ID&scope=openid&response_type=id_token&nonce=$NONCE&redirect_uri=$REDIRECT_URL` | Yes |
## Decoding JWT
@@ -116,7 +116,7 @@ User salt is used to disconnect the OAuth identifier (sub) from the on-chain Sui
Once the OAuth flow completes, the JWT can be found in the redirect URL. Along with the user salt, the zkLogin address can be derived as follows:
```typescript
-import { jwtToAddress } from '@mysten/zklogin';
+import { jwtToAddress } from '@mysten/sui/zklogin';
const zkLoginUserAddress = jwtToAddress(jwt, userSalt);
```
@@ -128,7 +128,7 @@ The next step is to fetch the ZK proof. This is an attestation (proof) over the
First, generate the extended ephemeral public key to use as an input to the ZKP.
```typescript
-import { getExtendedEphemeralPublicKey } from '@mysten/zklogin';
+import { getExtendedEphemeralPublicKey } from '@mysten/sui/zklogin';
const extendedEphemeralPublicKey = getExtendedEphemeralPublicKey(ephemeralKeyPair.getPublicKey());
```
@@ -315,7 +315,7 @@ You can now serialize the zkLogin signature by combining the ZK proof (`inputs`)
the `maxEpoch`, and the ephemeral signature (`userSignature`).
```typescript
-import { genAddressSeed, getZkLoginSignature } from '@mysten/zklogin';
+import { genAddressSeed, getZkLoginSignature } from '@mysten/sui/zklogin';
const addressSeed: string = genAddressSeed(
BigInt(userSalt!),
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0b8ab2fb66888..bcbc2aabec842 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -141,13 +141,13 @@ importers:
devDependencies:
'@headlessui/tailwindcss':
specifier: ^0.1.3
- version: 0.1.3(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)))
+ version: 0.1.3(tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)))
'@tailwindcss/aspect-ratio':
specifier: ^0.4.2
- version: 0.4.2(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)))
+ version: 0.4.2(tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)))
'@tailwindcss/forms':
specifier: ^0.5.7
- version: 0.5.7(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)))
+ version: 0.5.7(tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)))
'@types/react':
specifier: ^18.3.3
version: 18.3.3
@@ -544,7 +544,7 @@ importers:
version: 3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3))
tailwindcss-animate:
specifier: ^1.0.7
- version: 1.0.7(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)))
+ version: 1.0.7(tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)))
ts-loader:
specifier: ^9.4.4
version: 9.5.1(typescript@5.5.3)(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)))
@@ -614,7 +614,7 @@ importers:
devDependencies:
'@headlessui/tailwindcss':
specifier: ^0.1.3
- version: 0.1.3(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)))
+ version: 0.1.3(tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)))
'@types/react':
specifier: ^18.3.3
version: 18.3.3
@@ -729,7 +729,7 @@ importers:
devDependencies:
'@tailwindcss/forms':
specifier: ^0.5.7
- version: 0.5.7(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)))
+ version: 0.5.7(tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)))
'@tsconfig/docusaurus':
specifier: ^2.0.3
version: 2.0.3
@@ -756,7 +756,7 @@ importers:
version: 3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3))
tailwindcss-animate:
specifier: ^1.0.7
- version: 1.0.7(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)))
+ version: 1.0.7(tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)))
typescript:
specifier: ^5.5.3
version: 5.5.3
@@ -824,7 +824,7 @@ importers:
devDependencies:
ts-node:
specifier: ^10.9.2
- version: 10.9.2(@swc/core@1.6.13)(@types/node@20.14.10)(typescript@5.5.3)
+ version: 10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)
typescript:
specifier: ^5.5.3
version: 5.5.3
@@ -931,7 +931,7 @@ importers:
version: 5.16.2
ts-node:
specifier: ^10.9.2
- version: 10.9.2(@swc/core@1.6.13)(@types/node@20.14.10)(typescript@5.5.3)
+ version: 10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)
typescript:
specifier: ^5.5.3
version: 5.5.3
@@ -1364,7 +1364,7 @@ importers:
version: 0.2.3
ts-node:
specifier: ^10.9.2
- version: 10.9.2(@swc/core@1.6.13)(@types/node@20.14.10)(typescript@5.5.3)
+ version: 10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)
ts-retry-promise:
specifier: ^0.8.1
version: 0.8.1
@@ -1401,7 +1401,7 @@ importers:
version: 0.2.3
ts-node:
specifier: ^10.9.2
- version: 10.9.2(@swc/core@1.6.13)(@types/node@20.14.10)(typescript@5.5.3)
+ version: 10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)
ts-retry-promise:
specifier: ^0.8.1
version: 0.8.1
@@ -1694,8 +1694,6 @@ importers:
specifier: ^0.13.0
version: 0.13.0
- sdk/move-bytecode-template/pkg/node: {}
-
sdk/suins-toolkit:
dependencies:
'@mysten/sui':
@@ -1710,7 +1708,7 @@ importers:
version: link:../build-scripts
ts-node:
specifier: ^10.9.2
- version: 10.9.2(@swc/core@1.6.13)(@types/node@20.14.10)(typescript@5.5.3)
+ version: 10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)
typescript:
specifier: ^5.5.3
version: 5.5.3
@@ -1750,6 +1748,12 @@ importers:
graphql:
specifier: ^16.9.0
version: 16.9.0
+ jose:
+ specifier: ^5.6.3
+ version: 5.6.3
+ poseidon-lite:
+ specifier: ^0.2.0
+ version: 0.2.0
tweetnacl:
specifier: ^1.0.3
version: 1.0.3
@@ -1838,25 +1842,13 @@ importers:
version: 5.5.3
typescript-json-schema:
specifier: ^0.64.0
- version: 0.64.0(@swc/core@1.6.13)
+ version: 0.64.0(@swc/core@1.6.13(@swc/helpers@0.5.5))
sdk/zklogin:
dependencies:
- '@mysten/bcs':
- specifier: workspace:*
- version: link:../bcs
'@mysten/sui':
specifier: workspace:*
version: link:../typescript
- '@noble/hashes':
- specifier: ^1.4.0
- version: 1.4.0
- jose:
- specifier: ^5.6.3
- version: 5.6.3
- poseidon-lite:
- specifier: ^0.2.0
- version: 0.2.0
devDependencies:
'@mysten/build-scripts':
specifier: workspace:*
@@ -18137,7 +18129,7 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
- '@headlessui/tailwindcss@0.1.3(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)))':
+ '@headlessui/tailwindcss@0.1.3(tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)))':
dependencies:
tailwindcss: 3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3))
@@ -18857,13 +18849,15 @@ snapshots:
transitivePeerDependencies:
- '@parcel/core'
- '@parcel/cache@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))':
+ '@parcel/cache@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5)':
dependencies:
'@parcel/core': 2.12.0(@swc/helpers@0.5.5)
'@parcel/fs': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5)
'@parcel/logger': 2.12.0
'@parcel/utils': 2.12.0
lmdb: 2.8.5
+ transitivePeerDependencies:
+ - '@swc/helpers'
'@parcel/codeframe@2.12.0':
dependencies:
@@ -18923,7 +18917,7 @@ snapshots:
'@parcel/core@2.12.0(@swc/helpers@0.5.5)':
dependencies:
'@mischnic/json-sourcemap': 0.1.1
- '@parcel/cache': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))
+ '@parcel/cache': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5)
'@parcel/diagnostic': 2.12.0
'@parcel/events': 2.12.0
'@parcel/fs': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5)
@@ -19338,7 +19332,7 @@ snapshots:
'@parcel/types@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5)':
dependencies:
- '@parcel/cache': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))
+ '@parcel/cache': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5)
'@parcel/diagnostic': 2.12.0
'@parcel/fs': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5)
'@parcel/package-manager': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5)
@@ -21797,11 +21791,11 @@ snapshots:
dependencies:
defer-to-connect: 2.0.1
- '@tailwindcss/aspect-ratio@0.4.2(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)))':
+ '@tailwindcss/aspect-ratio@0.4.2(tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)))':
dependencies:
tailwindcss: 3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3))
- '@tailwindcss/forms@0.5.7(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)))':
+ '@tailwindcss/forms@0.5.7(tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)))':
dependencies:
mini-svg-data-uri: 1.4.4
tailwindcss: 3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3))
@@ -30876,7 +30870,7 @@ snapshots:
tailwind-merge@2.4.0: {}
- tailwindcss-animate@1.0.7(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))):
+ tailwindcss-animate@1.0.7(tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3))):
dependencies:
tailwindcss: 3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3))
@@ -31116,27 +31110,7 @@ snapshots:
ts-log@2.2.5: {}
- ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3):
- dependencies:
- '@cspotcode/source-map-support': 0.8.1
- '@tsconfig/node10': 1.0.11
- '@tsconfig/node12': 1.0.11
- '@tsconfig/node14': 1.0.3
- '@tsconfig/node16': 1.0.4
- '@types/node': 20.14.10
- acorn: 8.12.1
- acorn-walk: 8.3.3
- arg: 4.1.3
- create-require: 1.1.1
- diff: 4.0.2
- make-error: 1.3.6
- typescript: 5.5.3
- v8-compile-cache-lib: 3.0.1
- yn: 3.1.1
- optionalDependencies:
- '@swc/core': 1.6.13(@swc/helpers@0.5.5)
-
- ts-node@10.9.2(@swc/core@1.6.13)(@types/node@16.18.101)(typescript@5.1.6):
+ ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@16.18.101)(typescript@5.1.6):
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.11
@@ -31156,7 +31130,7 @@ snapshots:
optionalDependencies:
'@swc/core': 1.6.13(@swc/helpers@0.5.5)
- ts-node@10.9.2(@swc/core@1.6.13)(@types/node@20.14.10)(typescript@5.5.3):
+ ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3):
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.11
@@ -31339,14 +31313,14 @@ snapshots:
typescript: 5.6.3
yaml: 2.4.5
- typescript-json-schema@0.64.0(@swc/core@1.6.13):
+ typescript-json-schema@0.64.0(@swc/core@1.6.13(@swc/helpers@0.5.5)):
dependencies:
'@types/json-schema': 7.0.15
'@types/node': 16.18.101
glob: 7.2.3
path-equal: 1.2.5
safe-stable-stringify: 2.4.3
- ts-node: 10.9.2(@swc/core@1.6.13)(@types/node@16.18.101)(typescript@5.1.6)
+ ts-node: 10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@16.18.101)(typescript@5.1.6)
typescript: 5.1.6
yargs: 17.7.2
transitivePeerDependencies:
diff --git a/sdk/docs/pages/typescript/index.mdx b/sdk/docs/pages/typescript/index.mdx
index c006083efa3f8..4b8f83b766144 100644
--- a/sdk/docs/pages/typescript/index.mdx
+++ b/sdk/docs/pages/typescript/index.mdx
@@ -107,5 +107,4 @@ what you need to keep your code light and compact.
signatures.
- [`@mysten/sui/utils`](/typescript/utils) - Utilities for formatting and parsing various Sui types.
- [`@mysten/sui/faucet`](/typescript/faucet) - Methods for requesting SUI from a faucet.
-- [`@mysten/sui/zklogin`](/typescript/zklogin) - Utilities for working with zkLogin. (Note
- @mysten/zklogin package also contains zklogin related functionality)
+- [`@mysten/sui/zklogin`](/typescript/zklogin) - Utilities for working with zkLogin.
diff --git a/sdk/docs/pages/typescript/zklogin.mdx b/sdk/docs/pages/typescript/zklogin.mdx
index 1ef30fbd48e36..e6403ef7c3433 100644
--- a/sdk/docs/pages/typescript/zklogin.mdx
+++ b/sdk/docs/pages/typescript/zklogin.mdx
@@ -5,10 +5,6 @@ import { Callout } from 'nextra/components';
Utilities for working with zkLogin. Currently contains functionality to create and parse zkLogin
signatures and compute zkLogin addresses.
-
- Note: `@mysten/zklogin` package contains more utilities to help with computing address etc.
-
-
To parse a serialized zkLogin signature
```typescript
@@ -30,8 +26,63 @@ To compute the address for a given address seed and iss you can use `computeZkLo
```typescript
import { computeZkLoginAddressFromSeed } from '@mysten/sui/zklogin';
-const address = await computeZkLoginAddressFromSeed(0n, 'https://accounts.google.com');
+const address = computeZkLoginAddressFromSeed(0n, 'https://accounts.google.com');
+```
+
+To compute an address from jwt:
+
+```typescript
+import { jwtToAddress } from '@mysten/sui/zklogin';
+
+const address = jwtToAddress(jwtAsString, salt);
+```
+
+To compute an address from a parsed jwt:
+
+```typescript
+import { computeZkLoginAddress } from '@mysten/sui/zklogin';
+
+const address = computeZkLoginAddress({
+ claimName,
+ claimValue,
+ iss,
+ aud,
+ userSalt: BigInt(salt),
+});
```
To use zkLogin inside a multisig, see the [Multisig Guide](../typescript/cryptography/multisig.mdx)
for more details.
+
+## Legacy addresses
+
+When zklogin was first introduced, there was an inconsistency in how the address seed was computed.
+For backwards compatibility reasons there are 2 valid addresses for a given set of inputs. Methods
+that produce zklogin addresses all accept a `legacyAddress` boolean flag, either as their last
+parameter, or in their options argument.
+
+```typescript
+import {
+ computeZkLoginAddress,
+ computeZkLoginAddressFromSeed,
+ jwtToAddress,
+ toZkLoginPublicIdentifier,
+ genAddressSeed(userSalt, claimName, claimValue, aud)
+} from '@mysten/sui/zklogin';
+
+const address = jwtToAddress(jwtAsString, salt, true);
+const address = computeZkLoginAddressFromSeed(0n, 'https://accounts.google.com', true);
+const address = computeZkLoginAddress({
+ claimName,
+ claimValue,
+ iss,
+ aud,
+ userSalt: BigInt(salt),
+ legacyAddress: true,
+});
+const address = toZkLoginPublicIdentifier(
+ genAddressSeed(userSalt, claimName, claimValue, aud),
+ iss,
+ { legacyAddress: true }
+).toSuiAddress();
+```
diff --git a/sdk/enoki/src/EnokiKeypair.ts b/sdk/enoki/src/EnokiKeypair.ts
index 62e29c74a1e9b..570dd23117fac 100644
--- a/sdk/enoki/src/EnokiKeypair.ts
+++ b/sdk/enoki/src/EnokiKeypair.ts
@@ -5,7 +5,7 @@ import type { SignatureWithBytes } from '@mysten/sui/cryptography';
import { Keypair, PublicKey, SIGNATURE_SCHEME_TO_FLAG } from '@mysten/sui/cryptography';
import type { Ed25519Keypair, Ed25519PublicKey } from '@mysten/sui/keypairs/ed25519';
import type { ZkLoginSignatureInputs } from '@mysten/sui/zklogin';
-import { getZkLoginSignature } from '@mysten/zklogin';
+import { getZkLoginSignature } from '@mysten/sui/zklogin';
export class EnokiPublicKey extends PublicKey {
#address: string;
diff --git a/sdk/typescript/package.json b/sdk/typescript/package.json
index b893f76cb0a09..5ec14a10d96e8 100644
--- a/sdk/typescript/package.json
+++ b/sdk/typescript/package.json
@@ -155,6 +155,8 @@
"bech32": "^2.0.0",
"gql.tada": "^1.8.2",
"graphql": "^16.9.0",
+ "jose": "^5.6.3",
+ "poseidon-lite": "^0.2.0",
"tweetnacl": "^1.0.3",
"valibot": "^0.36.0"
}
diff --git a/sdk/typescript/src/zklogin/address.ts b/sdk/typescript/src/zklogin/address.ts
index 6d2eafc9d60cc..85be578b33f89 100644
--- a/sdk/typescript/src/zklogin/address.ts
+++ b/sdk/typescript/src/zklogin/address.ts
@@ -3,13 +3,21 @@
import { blake2b } from '@noble/hashes/blake2b';
import { bytesToHex } from '@noble/hashes/utils';
+import { decodeJwt } from 'jose';
import { SIGNATURE_SCHEME_TO_FLAG } from '../cryptography/signature-scheme.js';
import { normalizeSuiAddress, SUI_ADDRESS_LENGTH } from '../utils/index.js';
-import { toBigEndianBytes } from './utils.js';
+import { genAddressSeed, toBigEndianBytes, toPaddedBigEndianBytes } from './utils.js';
-export function computeZkLoginAddressFromSeed(addressSeed: bigint, iss: string) {
- const addressSeedBytesBigEndian = toBigEndianBytes(addressSeed, 32);
+export function computeZkLoginAddressFromSeed(
+ addressSeed: bigint,
+ iss: string,
+ /** TODO: This default should be changed in the next major release */
+ legacyAddress = true,
+) {
+ const addressSeedBytesBigEndian = legacyAddress
+ ? toBigEndianBytes(addressSeed, 32)
+ : toPaddedBigEndianBytes(addressSeed, 32);
if (iss === 'accounts.google.com') {
iss = 'https://accounts.google.com';
}
@@ -25,3 +33,73 @@ export function computeZkLoginAddressFromSeed(addressSeed: bigint, iss: string)
bytesToHex(blake2b(tmp, { dkLen: 32 })).slice(0, SUI_ADDRESS_LENGTH * 2),
);
}
+
+export const MAX_HEADER_LEN_B64 = 248;
+export const MAX_PADDED_UNSIGNED_JWT_LEN = 64 * 25;
+
+export function lengthChecks(jwt: string) {
+ const [header, payload] = jwt.split('.');
+ /// Is the header small enough
+ if (header.length > MAX_HEADER_LEN_B64) {
+ throw new Error(`Header is too long`);
+ }
+
+ /// Is the combined length of (header, payload, SHA2 padding) small enough?
+ // unsigned_jwt = header + '.' + payload;
+ const L = (header.length + 1 + payload.length) * 8;
+ const K = (512 + 448 - ((L % 512) + 1)) % 512;
+
+ // The SHA2 padding is 1 followed by K zeros, followed by the length of the message
+ const padded_unsigned_jwt_len = (L + 1 + K + 64) / 8;
+
+ // The padded unsigned JWT must be less than the max_padded_unsigned_jwt_len
+ if (padded_unsigned_jwt_len > MAX_PADDED_UNSIGNED_JWT_LEN) {
+ throw new Error(`JWT is too long`);
+ }
+}
+
+export function jwtToAddress(jwt: string, userSalt: string | bigint, legacyAddress = false) {
+ lengthChecks(jwt);
+
+ const decodedJWT = decodeJwt(jwt);
+ if (!decodedJWT.sub || !decodedJWT.iss || !decodedJWT.aud) {
+ throw new Error('Missing jwt data');
+ }
+
+ if (Array.isArray(decodedJWT.aud)) {
+ throw new Error('Not supported aud. Aud is an array, string was expected.');
+ }
+
+ return computeZkLoginAddress({
+ userSalt,
+ claimName: 'sub',
+ claimValue: decodedJWT.sub,
+ aud: decodedJWT.aud,
+ iss: decodedJWT.iss,
+ legacyAddress,
+ });
+}
+
+export interface ComputeZkLoginAddressOptions {
+ claimName: string;
+ claimValue: string;
+ userSalt: string | bigint;
+ iss: string;
+ aud: string;
+ legacyAddress?: boolean;
+}
+
+export function computeZkLoginAddress({
+ claimName,
+ claimValue,
+ iss,
+ aud,
+ userSalt,
+ legacyAddress = false,
+}: ComputeZkLoginAddressOptions) {
+ return computeZkLoginAddressFromSeed(
+ genAddressSeed(userSalt, claimName, claimValue, aud),
+ iss,
+ legacyAddress,
+ );
+}
diff --git a/sdk/typescript/src/zklogin/index.ts b/sdk/typescript/src/zklogin/index.ts
index c78103b8178d2..0004dbf1196f0 100644
--- a/sdk/typescript/src/zklogin/index.ts
+++ b/sdk/typescript/src/zklogin/index.ts
@@ -2,7 +2,16 @@
// SPDX-License-Identifier: Apache-2.0
export { getZkLoginSignature, parseZkLoginSignature } from './signature.js';
-export { toBigEndianBytes, toPaddedBigEndianBytes } from './utils.js';
-export { computeZkLoginAddressFromSeed } from './address.js';
+export {
+ toBigEndianBytes,
+ toPaddedBigEndianBytes,
+ hashASCIIStrToField,
+ genAddressSeed,
+ getExtendedEphemeralPublicKey,
+} from './utils.js';
+export { computeZkLoginAddressFromSeed, computeZkLoginAddress, jwtToAddress } from './address.js';
+export type { ComputeZkLoginAddressOptions } from './address.js';
export { toZkLoginPublicIdentifier, ZkLoginPublicIdentifier } from './publickey.js';
export type { ZkLoginSignatureInputs } from './bcs.js';
+export { poseidonHash } from './poseidon.js';
+export { generateNonce, generateRandomness } from './nonce.js';
diff --git a/sdk/zklogin/src/nonce.ts b/sdk/typescript/src/zklogin/nonce.ts
similarity index 91%
rename from sdk/zklogin/src/nonce.ts
rename to sdk/typescript/src/zklogin/nonce.ts
index f22ddc85f82d6..fdd2fe60b4545 100644
--- a/sdk/zklogin/src/nonce.ts
+++ b/sdk/typescript/src/zklogin/nonce.ts
@@ -2,12 +2,12 @@
// SPDX-License-Identifier: Apache-2.0
import { toHex } from '@mysten/bcs';
-import type { PublicKey } from '@mysten/sui/cryptography';
-import { toPaddedBigEndianBytes } from '@mysten/sui/zklogin';
import { randomBytes } from '@noble/hashes/utils';
import { base64url } from 'jose';
+import type { PublicKey } from '../cryptography/publickey.js';
import { poseidonHash } from './poseidon.js';
+import { toPaddedBigEndianBytes } from './utils.js';
export const NONCE_LENGTH = 27;
diff --git a/sdk/zklogin/src/poseidon.ts b/sdk/typescript/src/zklogin/poseidon.ts
similarity index 100%
rename from sdk/zklogin/src/poseidon.ts
rename to sdk/typescript/src/zklogin/poseidon.ts
diff --git a/sdk/typescript/src/zklogin/publickey.ts b/sdk/typescript/src/zklogin/publickey.ts
index 7eb23ffa23b0c..da3035428f683 100644
--- a/sdk/typescript/src/zklogin/publickey.ts
+++ b/sdk/typescript/src/zklogin/publickey.ts
@@ -1,16 +1,19 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0
-import { fromBase64, toBase64 } from '@mysten/bcs';
+import { fromBase64, toBase64, toHex } from '@mysten/bcs';
+import { blake2b } from '@noble/hashes/blake2b';
+import { bytesToHex } from '@noble/hashes/utils';
import { PublicKey } from '../cryptography/publickey.js';
import type { PublicKeyInitData } from '../cryptography/publickey.js';
import { SIGNATURE_SCHEME_TO_FLAG } from '../cryptography/signature-scheme.js';
import { SuiGraphQLClient } from '../graphql/client.js';
import { graphql } from '../graphql/schemas/2024.4/index.js';
+import { normalizeSuiAddress, SUI_ADDRESS_LENGTH } from '../utils/sui-types.js';
import { extractClaimValue } from './jwt-utils.js';
import { parseZkLoginSignature } from './signature.js';
-import { toPaddedBigEndianBytes } from './utils.js';
+import { toBigEndianBytes, toPaddedBigEndianBytes } from './utils.js';
/**
* A zkLogin public identifier
@@ -18,6 +21,7 @@ import { toPaddedBigEndianBytes } from './utils.js';
export class ZkLoginPublicIdentifier extends PublicKey {
#data: Uint8Array;
#client?: SuiGraphQLClient;
+ #legacyAddress: boolean;
/**
* Create a new ZkLoginPublicIdentifier object
@@ -35,6 +39,11 @@ export class ZkLoginPublicIdentifier extends PublicKey {
} else {
this.#data = Uint8Array.from(value);
}
+ this.#legacyAddress = this.#data.length !== this.#data[0] + 1 + 32;
+
+ if (this.#legacyAddress) {
+ this.#data = normalizeZkLoginPublicKeyBytes(this.#data);
+ }
}
/**
@@ -44,6 +53,20 @@ export class ZkLoginPublicIdentifier extends PublicKey {
return super.equals(publicKey);
}
+ override toSuiAddress(): string {
+ if (this.#legacyAddress) {
+ const legacyBytes = normalizeZkLoginPublicKeyBytes(this.#data, true);
+ const addressBytes = new Uint8Array(legacyBytes.length + 1);
+ addressBytes[0] = this.flag();
+ addressBytes.set(legacyBytes, 1);
+ return normalizeSuiAddress(
+ bytesToHex(blake2b(addressBytes, { dkLen: 32 })).slice(0, SUI_ADDRESS_LENGTH * 2),
+ );
+ }
+
+ return super.toSuiAddress();
+ }
+
/**
* Return the byte array representation of the zkLogin public identifier
*/
@@ -101,10 +124,13 @@ export class ZkLoginPublicIdentifier extends PublicKey {
export function toZkLoginPublicIdentifier(
addressSeed: bigint,
iss: string,
- options?: { client?: SuiGraphQLClient },
+ options?: { client?: SuiGraphQLClient; legacyAddress?: boolean },
): ZkLoginPublicIdentifier {
// Consists of iss_bytes_len || iss_bytes || padded_32_byte_address_seed.
- const addressSeedBytesBigEndian = toPaddedBigEndianBytes(addressSeed, 32);
+ const addressSeedBytesBigEndian = options?.legacyAddress
+ ? toBigEndianBytes(addressSeed, 32)
+ : toPaddedBigEndianBytes(addressSeed, 32);
+
const issBytes = new TextEncoder().encode(iss);
const tmp = new Uint8Array(1 + issBytes.length + addressSeedBytesBigEndian.length);
tmp.set([issBytes.length], 0);
@@ -132,6 +158,18 @@ const VerifyZkLoginSignatureQuery = graphql(`
}
`);
+function normalizeZkLoginPublicKeyBytes(bytes: Uint8Array, legacyAddress = false) {
+ const issByteLength = bytes[0] + 1;
+ const addressSeed = BigInt(`0x${toHex(bytes.slice(issByteLength))}`);
+ const seedBytes = legacyAddress
+ ? toBigEndianBytes(addressSeed, 32)
+ : toPaddedBigEndianBytes(addressSeed, 32);
+ const data = new Uint8Array(issByteLength + seedBytes.length);
+ data.set(bytes.slice(0, issByteLength), 0);
+ data.set(seedBytes, issByteLength);
+ return data;
+}
+
async function graphqlVerifyZkLoginSignature({
address,
bytes,
diff --git a/sdk/typescript/src/zklogin/utils.ts b/sdk/typescript/src/zklogin/utils.ts
index 95d02e9965a43..aa5497fdc9edd 100644
--- a/sdk/typescript/src/zklogin/utils.ts
+++ b/sdk/typescript/src/zklogin/utils.ts
@@ -3,6 +3,14 @@
import { hexToBytes } from '@noble/hashes/utils';
+import type { PublicKey } from '../cryptography/publickey.js';
+import { poseidonHash } from './poseidon.js';
+
+const MAX_KEY_CLAIM_NAME_LENGTH = 32;
+const MAX_KEY_CLAIM_VALUE_LENGTH = 115;
+const MAX_AUD_VALUE_LENGTH = 145;
+const PACK_WIDTH = 248;
+
function findFirstNonZeroIndex(bytes: Uint8Array) {
for (let i = 0; i < bytes.length; i++) {
if (bytes[i] !== 0) {
@@ -31,3 +39,67 @@ export function toBigEndianBytes(num: bigint, width: number): Uint8Array {
return bytes.slice(firstNonZeroIndex);
}
+
+export function getExtendedEphemeralPublicKey(publicKey: PublicKey) {
+ return publicKey.toSuiPublicKey();
+}
+
+/**
+ * Splits an array into chunks of size chunk_size. If the array is not evenly
+ * divisible by chunk_size, the first chunk will be smaller than chunk_size.
+ *
+ * E.g., arrayChunk([1, 2, 3, 4, 5], 2) => [[1], [2, 3], [4, 5]]
+ *
+ * Note: Can be made more efficient by avoiding the reverse() calls.
+ */
+export function chunkArray(array: T[], chunk_size: number): T[][] {
+ const chunks = Array(Math.ceil(array.length / chunk_size));
+ const revArray = array.reverse();
+ for (let i = 0; i < chunks.length; i++) {
+ chunks[i] = revArray.slice(i * chunk_size, (i + 1) * chunk_size).reverse();
+ }
+ return chunks.reverse();
+}
+
+function bytesBEToBigInt(bytes: number[]): bigint {
+ const hex = bytes.map((b) => b.toString(16).padStart(2, '0')).join('');
+ if (hex.length === 0) {
+ return BigInt(0);
+ }
+ return BigInt('0x' + hex);
+}
+
+// hashes an ASCII string to a field element
+export function hashASCIIStrToField(str: string, maxSize: number) {
+ if (str.length > maxSize) {
+ throw new Error(`String ${str} is longer than ${maxSize} chars`);
+ }
+
+ // Note: Padding with zeroes is safe because we are only using this function to map human-readable sequence of bytes.
+ // So the ASCII values of those characters will never be zero (null character).
+ const strPadded = str
+ .padEnd(maxSize, String.fromCharCode(0))
+ .split('')
+ .map((c) => c.charCodeAt(0));
+
+ const chunkSize = PACK_WIDTH / 8;
+ const packed = chunkArray(strPadded, chunkSize).map((chunk) => bytesBEToBigInt(chunk));
+ return poseidonHash(packed);
+}
+
+export function genAddressSeed(
+ salt: string | bigint,
+ name: string,
+ value: string,
+ aud: string,
+ max_name_length = MAX_KEY_CLAIM_NAME_LENGTH,
+ max_value_length = MAX_KEY_CLAIM_VALUE_LENGTH,
+ max_aud_length = MAX_AUD_VALUE_LENGTH,
+): bigint {
+ return poseidonHash([
+ hashASCIIStrToField(name, max_name_length),
+ hashASCIIStrToField(value, max_value_length),
+ hashASCIIStrToField(aud, max_aud_length),
+ poseidonHash([BigInt(salt)]),
+ ]);
+}
diff --git a/sdk/typescript/test/unit/zklogin/address.test.ts b/sdk/typescript/test/unit/zklogin/address.test.ts
index 0f05e18a588a4..a7cb56163ce32 100644
--- a/sdk/typescript/test/unit/zklogin/address.test.ts
+++ b/sdk/typescript/test/unit/zklogin/address.test.ts
@@ -3,7 +3,14 @@
import { describe, expect, test } from 'vitest';
-import { computeZkLoginAddressFromSeed } from '../../../src/zklogin/address';
+import { toZkLoginPublicIdentifier } from '../../../src/zklogin';
+import {
+ computeZkLoginAddressFromSeed,
+ jwtToAddress,
+ lengthChecks,
+ MAX_HEADER_LEN_B64,
+ MAX_PADDED_UNSIGNED_JWT_LEN,
+} from '../../../src/zklogin/address';
describe('zkLogin address', () => {
test('generates the correct address', () => {
@@ -23,4 +30,71 @@ describe('zkLogin address', () => {
),
).toBe('0xbd8b8ed42d90aebc71518385d8a899af14cef8b5a171c380434dd6f5bbfe7bf3');
});
+
+ test('computeZkLoginAddressFromSeed matches ZkLoginPublicIdentifier.toSuiAddress() for legacy addresses', () => {
+ const seed = BigInt(
+ '380704556853533152350240698167704405529973457670972223618755249929828551006',
+ );
+ const iss = 'https://accounts.google.com';
+ expect(computeZkLoginAddressFromSeed(seed, iss)).toEqual(
+ toZkLoginPublicIdentifier(seed, iss, { legacyAddress: true }).toSuiAddress(),
+ );
+ });
+
+ test('computeZkLoginAddressFromSeed matches ZkLoginPublicIdentifier.toSuiAddress() for non-legacy addresses', () => {
+ const seed = BigInt(
+ '380704556853533152350240698167704405529973457670972223618755249929828551006',
+ );
+ const iss = 'https://accounts.google.com';
+ expect(computeZkLoginAddressFromSeed(seed, iss, false)).toEqual(
+ toZkLoginPublicIdentifier(seed, iss).toSuiAddress(),
+ );
+ });
+
+ test('a valid JWT should not throw an error', () => {
+ const jwt =
+ 'eyJraWQiOiJzdWkta2V5LWlkIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiI4YzJkN2Q2Ni04N2FmLTQxZmEtYjZmYy02M2U4YmI3MWZhYjQiLCJhdWQiOiJ0ZXN0IiwibmJmIjoxNjk3NDY1NDQ1LCJpc3MiOiJodHRwczovL29hdXRoLnN1aS5pbyIsImV4cCI6MTY5NzU1MTg0NSwibm9uY2UiOiJoVFBwZ0Y3WEFLYlczN3JFVVM2cEVWWnFtb0kifQ.';
+ const userSalt = '248191903847969014646285995941615069143';
+ const address = jwtToAddress(jwt, userSalt);
+ expect(address).toBe('0x22cebcf68a9d75d508d50d553dd6bae378ef51177a3a6325b749e57e3ba237d6');
+ });
+
+ test('should return the same address for both google iss', () => {
+ /**
+ * {
+ * "iss": "https://accounts.google.com",
+ * "sub": "1234567890",
+ * "aud": "1234567890.apps.googleusercontent.com",
+ * "exp": 1697551845,
+ * "iat": 1697465445
+ * }
+ */
+ const jwt1 =
+ 'eyJhbGciOiJSUzI1NiIsImtpZCI6InN1aS1rZXktaWQiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiMTIzNDU2Nzg5MC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImV4cCI6MTY5NzU1MTg0NSwiaWF0IjoxNjk3NDY1NDQ1fQ.';
+ /**
+ * {
+ * "iss": "accounts.google.com",
+ * "sub": "1234567890",
+ * "aud": "1234567890.apps.googleusercontent.com",
+ * "exp": 1697551845,
+ * "iat": 1697465445
+ * }
+ */
+ const jwt2 =
+ 'eyJhbGciOiJSUzI1NiIsImtpZCI6InN1aS1rZXktaWQiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTIzNDU2Nzg5MCIsImF1ZCI6IjEyMzQ1Njc4OTAuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJleHAiOjE2OTc1NTE4NDUsImlhdCI6MTY5NzQ2NTQ0NX0.';
+
+ expect(jwtToAddress(jwt1, '0')).toBe(jwtToAddress(jwt2, '0'));
+ });
+
+ test('lengthChecks: if header is too long, should throw an error', () => {
+ const header = 'a'.repeat(MAX_HEADER_LEN_B64 + 1);
+ const jwt = `${header}.`;
+ expect(() => lengthChecks(jwt)).toThrow(`Header is too long`);
+ });
+
+ test('lengthChecks: if jwt is too long, should throw an error', () => {
+ // Note: It should also fail for lengths slightly smaller than MAX_PADDED_UNSIGNED_JWT_LEN due to the SHA2 padding.
+ const jwt = '.' + 'a'.repeat(MAX_PADDED_UNSIGNED_JWT_LEN);
+ expect(() => lengthChecks(jwt)).toThrow(`JWT is too long`);
+ });
});
diff --git a/sdk/zklogin/test/nonce.test.ts b/sdk/typescript/test/unit/zklogin/nonce.test.ts
similarity index 80%
rename from sdk/zklogin/test/nonce.test.ts
rename to sdk/typescript/test/unit/zklogin/nonce.test.ts
index 7fb37d6b37e39..9aedd675073f8 100644
--- a/sdk/zklogin/test/nonce.test.ts
+++ b/sdk/typescript/test/unit/zklogin/nonce.test.ts
@@ -3,8 +3,8 @@
import { expect, test } from 'vitest';
-import { Ed25519Keypair } from '../../typescript/src/keypairs/ed25519';
-import { generateNonce, generateRandomness } from '../src';
+import { Ed25519Keypair } from '../../../src/keypairs/ed25519';
+import { generateNonce, generateRandomness } from '../../../src/zklogin';
test('can generate using `generateRandomness`', () => {
const kp = Ed25519Keypair.fromSecretKey(new Uint8Array(32));
diff --git a/sdk/zklogin/test/poseidon.test.ts b/sdk/typescript/test/unit/zklogin/poseidon.test.ts
similarity index 86%
rename from sdk/zklogin/test/poseidon.test.ts
rename to sdk/typescript/test/unit/zklogin/poseidon.test.ts
index 7499b7164e5b4..8916105f8be17 100644
--- a/sdk/zklogin/test/poseidon.test.ts
+++ b/sdk/typescript/test/unit/zklogin/poseidon.test.ts
@@ -2,7 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
import { expect, test } from 'vitest';
-import { BN254_FIELD_SIZE, poseidonHash } from '../src/poseidon';
+import { poseidonHash } from '../../../src/zklogin';
+import { BN254_FIELD_SIZE } from '../../../src/zklogin/poseidon';
test('can hash single input', () => {
const result = poseidonHash([123]);
diff --git a/sdk/zklogin/README.md b/sdk/zklogin/README.md
index 9ae2d0b57922a..10324c54c7eff 100644
--- a/sdk/zklogin/README.md
+++ b/sdk/zklogin/README.md
@@ -1,4 +1,4 @@
-# `@mysten/zklogin`
+# `@mysten/zklogin` (Deprecated, use @mysten/sui/zklogin instead)
> **⚠️ This package is experimental and will change rapidly as it is being developed. Do not
> consider these APIs to be stable. If you have any feedback,
diff --git a/sdk/zklogin/package.json b/sdk/zklogin/package.json
index 978775fe46f8f..0a5b53214730c 100644
--- a/sdk/zklogin/package.json
+++ b/sdk/zklogin/package.json
@@ -1,7 +1,7 @@
{
"name": "@mysten/zklogin",
"version": "0.7.30",
- "description": "Utilities for interacting with zkLogin in Sui",
+ "description": "Deprecated: use @mysten/sui/zklogin instead",
"license": "Apache-2.0",
"author": "Mysten Labs ",
"type": "commonjs",
@@ -29,8 +29,7 @@
"eslint:check": "eslint --max-warnings=0 .",
"eslint:fix": "pnpm run eslint:check --fix",
"lint": "pnpm run eslint:check && pnpm run prettier:check",
- "lint:fix": "pnpm run eslint:fix && pnpm run prettier:fix",
- "test": "vitest run"
+ "lint:fix": "pnpm run eslint:fix && pnpm run prettier:fix"
},
"repository": {
"type": "git",
@@ -47,10 +46,6 @@
"vitest": "^2.0.1"
},
"dependencies": {
- "@mysten/bcs": "workspace:*",
- "@mysten/sui": "workspace:*",
- "@noble/hashes": "^1.4.0",
- "jose": "^5.6.3",
- "poseidon-lite": "^0.2.0"
+ "@mysten/sui": "workspace:*"
}
}
diff --git a/sdk/zklogin/src/address.ts b/sdk/zklogin/src/address.ts
deleted file mode 100644
index 2236635c47db4..0000000000000
--- a/sdk/zklogin/src/address.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright (c) Mysten Labs, Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { computeZkLoginAddressFromSeed } from '@mysten/sui/zklogin';
-import { decodeJwt } from 'jose';
-
-import { genAddressSeed } from './utils.js';
-
-export const MAX_HEADER_LEN_B64 = 248;
-export const MAX_PADDED_UNSIGNED_JWT_LEN = 64 * 25;
-
-export function lengthChecks(jwt: string) {
- const [header, payload] = jwt.split('.');
- /// Is the header small enough
- if (header.length > MAX_HEADER_LEN_B64) {
- throw new Error(`Header is too long`);
- }
-
- /// Is the combined length of (header, payload, SHA2 padding) small enough?
- // unsigned_jwt = header + '.' + payload;
- const L = (header.length + 1 + payload.length) * 8;
- const K = (512 + 448 - ((L % 512) + 1)) % 512;
-
- // The SHA2 padding is 1 followed by K zeros, followed by the length of the message
- const padded_unsigned_jwt_len = (L + 1 + K + 64) / 8;
-
- // The padded unsigned JWT must be less than the max_padded_unsigned_jwt_len
- if (padded_unsigned_jwt_len > MAX_PADDED_UNSIGNED_JWT_LEN) {
- throw new Error(`JWT is too long`);
- }
-}
-
-export function jwtToAddress(jwt: string, userSalt: string | bigint) {
- lengthChecks(jwt);
-
- const decodedJWT = decodeJwt(jwt);
- if (!decodedJWT.sub || !decodedJWT.iss || !decodedJWT.aud) {
- throw new Error('Missing jwt data');
- }
-
- if (Array.isArray(decodedJWT.aud)) {
- throw new Error('Not supported aud. Aud is an array, string was expected.');
- }
-
- return computeZkLoginAddress({
- userSalt,
- claimName: 'sub',
- claimValue: decodedJWT.sub,
- aud: decodedJWT.aud,
- iss: decodedJWT.iss,
- });
-}
-
-export interface ComputeZkLoginAddressOptions {
- claimName: string;
- claimValue: string;
- userSalt: string | bigint;
- iss: string;
- aud: string;
-}
-
-export function computeZkLoginAddress({
- claimName,
- claimValue,
- iss,
- aud,
- userSalt,
-}: ComputeZkLoginAddressOptions) {
- return computeZkLoginAddressFromSeed(genAddressSeed(userSalt, claimName, claimValue, aud), iss);
-}
diff --git a/sdk/zklogin/src/index.ts b/sdk/zklogin/src/index.ts
index 8cf263d84c05a..a5a563105e7c4 100644
--- a/sdk/zklogin/src/index.ts
+++ b/sdk/zklogin/src/index.ts
@@ -1,13 +1,42 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0
-export { computeZkLoginAddress, jwtToAddress } from './address.js';
-export type { ComputeZkLoginAddressOptions } from './address.js';
+import '@mysten/sui/zklogin';
-export { getZkLoginSignature } from '@mysten/sui/zklogin';
+import type { ComputeZkLoginAddressOptions } from '@mysten/sui/zklogin';
+import {
+ computeZkLoginAddress as suiComputeZkLoginAddress,
+ jwtToAddress as suiJwtToAddress,
+} from '@mysten/sui/zklogin';
-export { poseidonHash } from './poseidon.js';
+export type { ComputeZkLoginAddressOptions } from '@mysten/sui/zklogin';
-export { generateNonce, generateRandomness } from './nonce.js';
+export {
+ /** @deprecated, use `import { genAddressSeed } from '@mysten/sui/zklogin';` instead */
+ genAddressSeed,
+ /** @deprecated, use `import { generateNonce } from '@mysten/sui/zklogin';` instead */
+ generateNonce,
+ /** @deprecated, use `import { generateRandomness } from '@mysten/sui/zklogin';` instead */
+ generateRandomness,
+ /** @deprecated, use `import { getExtendedEphemeralPublicKey } from '@mysten/sui/zklogin';` instead */
+ getExtendedEphemeralPublicKey,
+ /** @deprecated, use `import { getZkLoginSignature } from '@mysten/sui/zklogin';` instead */
+ getZkLoginSignature,
+ /** @deprecated, use `import { hashASCIIStrToField } from '@mysten/sui/zklogin';` instead */
+ hashASCIIStrToField,
+ /** @deprecated, use `import { poseidonHash } from '@mysten/sui/zklogin';` instead */
+ poseidonHash,
+} from '@mysten/sui/zklogin';
-export { hashASCIIStrToField, genAddressSeed, getExtendedEphemeralPublicKey } from './utils.js';
+/** @deprecated, use `import { parseZkLoginSignature } from '@mysten/sui/zklogin';` instead */
+export function computeZkLoginAddress(options: ComputeZkLoginAddressOptions) {
+ return suiComputeZkLoginAddress({
+ ...options,
+ legacyAddress: true,
+ });
+}
+
+/** @deprecated, use `import { jwtToAddress } from '@mysten/sui/zklogin';` instead */
+export function jwtToAddress(jwt: string, userSalt: string | bigint, legacyAddress = true) {
+ return suiJwtToAddress(jwt, userSalt, legacyAddress);
+}
diff --git a/sdk/zklogin/src/utils.ts b/sdk/zklogin/src/utils.ts
deleted file mode 100644
index 56b5e21b31b49..0000000000000
--- a/sdk/zklogin/src/utils.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright (c) Mysten Labs, Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import type { PublicKey } from '@mysten/sui/cryptography';
-
-import { poseidonHash } from './poseidon.js';
-
-const MAX_KEY_CLAIM_NAME_LENGTH = 32;
-const MAX_KEY_CLAIM_VALUE_LENGTH = 115;
-const MAX_AUD_VALUE_LENGTH = 145;
-const PACK_WIDTH = 248;
-
-export function getExtendedEphemeralPublicKey(publicKey: PublicKey) {
- return publicKey.toSuiPublicKey();
-}
-
-/**
- * Splits an array into chunks of size chunk_size. If the array is not evenly
- * divisible by chunk_size, the first chunk will be smaller than chunk_size.
- *
- * E.g., arrayChunk([1, 2, 3, 4, 5], 2) => [[1], [2, 3], [4, 5]]
- *
- * Note: Can be made more efficient by avoiding the reverse() calls.
- */
-export function chunkArray(array: T[], chunk_size: number): T[][] {
- const chunks = Array(Math.ceil(array.length / chunk_size));
- const revArray = array.reverse();
- for (let i = 0; i < chunks.length; i++) {
- chunks[i] = revArray.slice(i * chunk_size, (i + 1) * chunk_size).reverse();
- }
- return chunks.reverse();
-}
-
-function bytesBEToBigInt(bytes: number[]): bigint {
- const hex = bytes.map((b) => b.toString(16).padStart(2, '0')).join('');
- if (hex.length === 0) {
- return BigInt(0);
- }
- return BigInt('0x' + hex);
-}
-
-// hashes an ASCII string to a field element
-export function hashASCIIStrToField(str: string, maxSize: number) {
- if (str.length > maxSize) {
- throw new Error(`String ${str} is longer than ${maxSize} chars`);
- }
-
- // Note: Padding with zeroes is safe because we are only using this function to map human-readable sequence of bytes.
- // So the ASCII values of those characters will never be zero (null character).
- const strPadded = str
- .padEnd(maxSize, String.fromCharCode(0))
- .split('')
- .map((c) => c.charCodeAt(0));
-
- const chunkSize = PACK_WIDTH / 8;
- const packed = chunkArray(strPadded, chunkSize).map((chunk) => bytesBEToBigInt(chunk));
- return poseidonHash(packed);
-}
-
-export function genAddressSeed(
- salt: string | bigint,
- name: string,
- value: string,
- aud: string,
- max_name_length = MAX_KEY_CLAIM_NAME_LENGTH,
- max_value_length = MAX_KEY_CLAIM_VALUE_LENGTH,
- max_aud_length = MAX_AUD_VALUE_LENGTH,
-): bigint {
- return poseidonHash([
- hashASCIIStrToField(name, max_name_length),
- hashASCIIStrToField(value, max_value_length),
- hashASCIIStrToField(aud, max_aud_length),
- poseidonHash([BigInt(salt)]),
- ]);
-}
diff --git a/sdk/zklogin/test/address.test.ts b/sdk/zklogin/test/address.test.ts
deleted file mode 100644
index eaa70ca1a395c..0000000000000
--- a/sdk/zklogin/test/address.test.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (c) Mysten Labs, Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { expect, test } from 'vitest';
-
-import {
- jwtToAddress,
- lengthChecks,
- MAX_HEADER_LEN_B64,
- MAX_PADDED_UNSIGNED_JWT_LEN,
-} from '../src/address.js';
-
-test('a valid JWT should not throw an error', () => {
- const jwt =
- 'eyJraWQiOiJzdWkta2V5LWlkIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiI4YzJkN2Q2Ni04N2FmLTQxZmEtYjZmYy02M2U4YmI3MWZhYjQiLCJhdWQiOiJ0ZXN0IiwibmJmIjoxNjk3NDY1NDQ1LCJpc3MiOiJodHRwczovL29hdXRoLnN1aS5pbyIsImV4cCI6MTY5NzU1MTg0NSwibm9uY2UiOiJoVFBwZ0Y3WEFLYlczN3JFVVM2cEVWWnFtb0kifQ.';
- const userSalt = '248191903847969014646285995941615069143';
- const address = jwtToAddress(jwt, userSalt);
- expect(address).toBe('0x22cebcf68a9d75d508d50d553dd6bae378ef51177a3a6325b749e57e3ba237d6');
-});
-
-test('should return the same address for both google iss', () => {
- /**
- * {
- * "iss": "https://accounts.google.com",
- * "sub": "1234567890",
- * "aud": "1234567890.apps.googleusercontent.com",
- * "exp": 1697551845,
- * "iat": 1697465445
- * }
- */
- const jwt1 =
- 'eyJhbGciOiJSUzI1NiIsImtpZCI6InN1aS1rZXktaWQiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiMTIzNDU2Nzg5MC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImV4cCI6MTY5NzU1MTg0NSwiaWF0IjoxNjk3NDY1NDQ1fQ.';
- /**
- * {
- * "iss": "accounts.google.com",
- * "sub": "1234567890",
- * "aud": "1234567890.apps.googleusercontent.com",
- * "exp": 1697551845,
- * "iat": 1697465445
- * }
- */
- const jwt2 =
- 'eyJhbGciOiJSUzI1NiIsImtpZCI6InN1aS1rZXktaWQiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTIzNDU2Nzg5MCIsImF1ZCI6IjEyMzQ1Njc4OTAuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJleHAiOjE2OTc1NTE4NDUsImlhdCI6MTY5NzQ2NTQ0NX0.';
-
- expect(jwtToAddress(jwt1, '0')).toBe(jwtToAddress(jwt2, '0'));
-});
-
-test('lengthChecks: if header is too long, should throw an error', () => {
- const header = 'a'.repeat(MAX_HEADER_LEN_B64 + 1);
- const jwt = `${header}.`;
- expect(() => lengthChecks(jwt)).toThrow(`Header is too long`);
-});
-
-test('lengthChecks: if jwt is too long, should throw an error', () => {
- // Note: It should also fail for lengths slightly smaller than MAX_PADDED_UNSIGNED_JWT_LEN due to the SHA2 padding.
- const jwt = '.' + 'a'.repeat(MAX_PADDED_UNSIGNED_JWT_LEN);
- expect(() => lengthChecks(jwt)).toThrow(`JWT is too long`);
-});