@mysten/docs 0.1.32 → 0.1.34

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.
@@ -0,0 +1,198 @@
1
+ # Validation
2
+
3
+ > Compose built-in and custom validation analyzers for sponsorship policy
4
+
5
+ A validator is an analyzer used for policy checks. Create one with `createAnalyzer(...)`, return the
6
+ issues it found, and pass it to `createSponsor({ validate })`. It declares the analyzers it reads
7
+ through `dependencies` and reports one of three outcomes:
8
+
9
+ - **pass**: `{ result: null }` (or `{ result: [] }`);
10
+ - **reject**: `{ result: [{ code, message }] }`: the transaction is well-formed but violates policy
11
+ (`POLICY_REJECTED`);
12
+ - **couldn't analyze**: `{ issues: [{ message }] }` or `throw`: the analyzer itself couldn't decide
13
+ (a failed lookup, an unreachable service), producing `ANALYSIS_FAILED`. This is the analyzer
14
+ framework's own channel, so it propagates, and dependents don't run.
15
+
16
+ Reporting findings as the `result` (rather than through `issues`) is what keeps "violates policy"
17
+ distinct from "couldn't be checked". There's no built-in "the sponsor must be paid" rule, because
18
+ value-flow policy is app-specific, so write it over the built-in `balanceFlows` analyzer (signed
19
+ per-address deltas: **negative = value left the owner, positive = arrived**):
20
+
21
+ ```ts
22
+
23
+ const USDC = '0x...::usdc::USDC';
24
+
25
+ const requireSponsorPayment = createAnalyzer({
26
+ dependencies: { balanceFlows: analyzers.balanceFlows },
27
+ analyze:
28
+ () =>
29
+ ({ balanceFlows }) => {
30
+ const received =
31
+ balanceFlows.sponsor
32
+ ?.filter((flow) => flow.coinType === USDC)
33
+ .reduce((sum, flow) => sum + flow.amount, 0n) ?? 0n;
34
+
35
+ return received < 10_000n
36
+ ? { result: [{ code: 'UNDERPAID', message: `Sponsor received ${received}, needs 10000.` }] }
37
+ : { result: null };
38
+ },
39
+ });
40
+ ```
41
+
42
+ The most useful shared analyzer is **`data`**, the parsed transaction (sender, gas data, commands,
43
+ expiration), which most validators read. Alongside it: `balanceFlows` (signed value deltas),
44
+ `transactionResponse` (the dry-run, including effects), `commands`, `moveFunctions`, `objects`,
45
+ `coins`, `inputs`, and `bytes`, plus the sponsor's `currentEpoch`, and any others the analyzer
46
+ package adds (see [`@mysten/wallet-sdk`](https://www.npmjs.com/package/@mysten/wallet-sdk) for the
47
+ full set). All are re-exported as `analyzers` (with `currentEpoch` and `createAnalyzer` alongside).
48
+ A failed analyzer never reaches your `analyze`: it short-circuits to an `ANALYSIS_FAILED` rejection.
49
+
50
+ ## Loading data, and sharing it across validators
51
+
52
+ An analyzer receives the same `options` the sponsor passes to `analyze` (including **`client`**), so
53
+ it can load onchain data. And because the framework runs each analyzer once and shares its result,
54
+ several validators read it for the cost of one fetch:
55
+
56
+ ```ts
57
+
58
+ // Loads the *sponsor's* SUI balance via options.client (the gas owner is the sponsor)…
59
+ const sponsorBalance = createAnalyzer({
60
+ dependencies: { data: analyzers.data },
61
+ analyze:
62
+ (options) =>
63
+ async ({ data }) => {
64
+ const { balance } = await options.client.core.getBalance({ owner: data.gasData.owner! });
65
+ return { result: BigInt(balance.balance) };
66
+ },
67
+ });
68
+
69
+ // …read by two validators; the `getBalance` call still runs only once.
70
+ const sponsorCanCoverGas = createAnalyzer({
71
+ dependencies: { sponsorBalance, data: analyzers.data },
72
+ analyze:
73
+ () =>
74
+ ({ sponsorBalance, data }) =>
75
+ sponsorBalance >= BigInt(data.gasData.budget ?? 0)
76
+ ? { result: null }
77
+ : {
78
+ result: [{ code: 'SPONSOR_UNDERFUNDED', message: 'Sponsor balance cannot cover gas.' }],
79
+ },
80
+ });
81
+ const sponsorKeepsReserve = createAnalyzer({
82
+ dependencies: { sponsorBalance },
83
+ analyze:
84
+ () =>
85
+ ({ sponsorBalance }) =>
86
+ sponsorBalance >= 1_000_000_000n
87
+ ? { result: null }
88
+ : { result: [{ code: 'RESERVE_LOW', message: 'Sponsor reserve below 1 SUI.' }] },
89
+ });
90
+
91
+ createSponsor({ signer, client, validate: [sponsorCanCoverGas, sponsorKeepsReserve] });
92
+ ```
93
+
94
+ ## Request-scoped options
95
+
96
+ A validator reads request inputs (an auth token, a tenant id) straight off `options`.
97
+ `createSponsor` **infers** them and requires them, typed, under `validationOptions` on sponsor calls
98
+ (required only when the option itself is required), without an untyped metadata bag:
99
+
100
+ ```ts
101
+ const authChecked = createAnalyzer({
102
+ analyze: (options: { authToken: string }) => () =>
103
+ isValidToken(options.authToken)
104
+ ? { result: null }
105
+ : { result: [{ code: 'BAD_AUTH', message: 'Invalid auth token.' }] },
106
+ });
107
+
108
+ const sponsor = createSponsor({ signer, client, validate: [authChecked] });
109
+
110
+ // `validationOptions.authToken` is now a required, typed argument:
111
+ await sponsor.signAndExecuteTransaction({
112
+ transaction,
113
+ userSignature,
114
+ validationOptions: { authToken },
115
+ });
116
+ ```
117
+
118
+ ## How it runs
119
+
120
+ `createSponsor` makes every validator a dependency of **`sponsor.analyzer`**, and validation is just
121
+ `analyze({ check: sponsor.analyzer }, { transaction, client })`. The analyzer framework then
122
+ handles:
123
+
124
+ - **Lazy**: only analyzers some validator depends on run, so cost tracks your policy. `defaults()`
125
+ includes `simulationSucceeds`, so the default config _does_ dry-run; drop it (or use only
126
+ validators that read `data`) and the sponsor **never simulates**. There's no "offline phase" to
127
+ declare, because it falls out of the dependency graph.
128
+ - **Deduped**: `data` and `balanceFlows` and so on resolve once even when many validators (and a
129
+ host graph) depend on them.
130
+ - **Propagated**: a failed analyzer becomes an `ANALYSIS_FAILED` rejection rather than reaching a
131
+ validator with a broken value.
132
+
133
+ When validation fails, the sponsor never signs and the method **returns**
134
+ `{ $kind: 'Rejected', issues, reason }`, where `issues` lists every reason, and `reason` is
135
+ `'POLICY_REJECTED'` or `'ANALYSIS_FAILED'`. To turn a rejection into a thrown error, the exported
136
+ `SponsorValidationError` class takes `(issues, reason)`.
137
+
138
+ **`sponsor.analyzer`** is also the composable handle: drop it into any other `analyze()` graph and
139
+ it contributes `SponsorRejection | null`, deduping its analyzers with that graph.
140
+
141
+ ## Built-in validation analyzers
142
+
143
+ | Validator | Reads | Rejects when… |
144
+ | ------------------------------ | ---------------------- | --------------------------------------------------------- |
145
+ | `validSender()` | `data` | the sender is unset, or is the gas owner (sponsor) |
146
+ | `onlyAddressBalanceGas()` | `data` | the gas payment isn't empty (`[]`)† |
147
+ | `gasCoinNotUsed()` | `data` | a command uses the gas coin (`tx.gas`) |
148
+ | `onlySenderWithdrawals()` | `data` | a `FundsWithdrawal` input isn't the sender's |
149
+ | `userSignatureMatchesSender()` | `bytes`, `data` | a supplied user signature isn't a valid sender signature‡ |
150
+ | `gasBudget({ min?, max? })` | `data` | the gas budget is unset or outside the range |
151
+ | `allowedPackages([...])` | `data` | a MoveCall targets a package outside the allowlist |
152
+ | `allowedFunctions([...])` | `data` | a MoveCall targets a function outside the allowlist |
153
+ | `simulationSucceeds()` | `transactionResponse` | the dry-run succeeds but the transaction would abort\* |
154
+ | `boundedExpiration()` | `data`, `currentEpoch` | the expiration is missing or beyond the next epoch |
155
+
156
+ \* The dry-run itself _succeeding_ but reporting an aborting transaction is a **policy** rejection
157
+ (`TRANSACTION_WOULD_FAIL`): the bytes are executable and would still cost the sponsor gas (landing a
158
+ failed transaction with a digest) if submitted. The dry-run _failing to run at all_ (an unreachable
159
+ node, unresolvable objects) is instead surfaced as `ANALYSIS_FAILED`, with the underlying error
160
+ detail.
161
+
162
+ † Address-balance gas (an empty payment) is how the sponsor pays from its own balance rather than
163
+ from nominated gas coins; the sponsor-builds flow always sets this, so this validator mainly guards
164
+ user-supplied bytes.
165
+
166
+ ‡ Verifies (through `@mysten/sui`'s `isValidTransactionSignature`) that **every** supplied user
167
+ signature is cryptographically valid over the bytes **and** resolves to the sender, caught before
168
+ the sponsor co-signs, rather than only at execution (all supplied signatures are attached to
169
+ execution, so one that isn't the sender's would be rejected onchain after the sponsor signed). A
170
+ malformed, invalid, or wrong-signer signature is rejected as `USER_SIGNATURE_INVALID`; the sender
171
+ match is key-type aware (a zkLogin key matches either its legacy or current address). An
172
+ _environmental_ failure during verification (for example, a zkLogin JWK or epoch lookup throwing)
173
+ isn't a validation result; it surfaces as `ANALYSIS_FAILED`, so a network blip is never reported as
174
+ a bad signature. Passes when no user signature was supplied (the sponsor-builds flow). The signature
175
+ is read from the request, not from `validationOptions`.
176
+
177
+ `defaults()` bundles `validSender()` + `onlyAddressBalanceGas()` + `gasCoinNotUsed()` +
178
+ `onlySenderWithdrawals()` + `simulationSucceeds()` + `boundedExpiration()`.
179
+
180
+ ## Timing-attack mitigation
181
+
182
+ Optionally insert random delays to reduce timing manipulation of onchain state between signing and
183
+ execution:
184
+
185
+ ```ts
186
+ createSponsor({
187
+ signer,
188
+ client,
189
+ delay: {
190
+ beforeSimulate: { min: 50, max: 200 }, // ms (or a fixed number) before the analysis is resolved
191
+ beforeExecute: { min: 50, max: 200 }, // before executing
192
+ // random: () => 0.5, // override the RNG (e.g. in tests)
193
+ },
194
+ });
195
+ ```
196
+
197
+ This is mitigation, not prevention. `beforeSimulate` runs once before the analysis resolves (where
198
+ simulation, if any, happens). Default is off.
@@ -0,0 +1,225 @@
1
+ # Cryptography
2
+
3
+ > Sign and verify Sui transactions and messages with keypairs and external signers
4
+
5
+ A signature authorizes every transaction and personal message on Sui. The Sui TypeScript SDK exposes
6
+ a single abstraction for producing those signatures, the `Signer`, along with several
7
+ implementations of it, from in-process keypairs to hardware wallets and cloud key-management
8
+ services (KMS). This page walks through the concepts shared by every signer; the subpages cover each
9
+ implementation in detail.
10
+
11
+ ## Signing schemes
12
+
13
+ A signer always produces a signature under a signature scheme, which determines the curve and
14
+ signature format. Sui supports three schemes, each with a corresponding `Keypair` class:
15
+
16
+ | Scheme | Class | Import |
17
+ | --------------- | ------------------ | -------------------------------- |
18
+ | Ed25519 | `Ed25519Keypair` | `@mysten/sui/keypairs/ed25519` |
19
+ | ECDSA Secp256k1 | `Secp256k1Keypair` | `@mysten/sui/keypairs/secp256k1` |
20
+ | ECDSA Secp256r1 | `Secp256r1Keypair` | `@mysten/sui/keypairs/secp256r1` |
21
+
22
+ For background on the schemes themselves, see the
23
+ [Signatures](https://docs.sui.io/concepts/cryptography/transaction-auth/signatures) concept topic. A
24
+ signer's scheme determines the format of its public key and the Sui address it derives.
25
+
26
+ ## The Signer interface
27
+
28
+ `Signer` is an abstract class exported from `@mysten/sui/cryptography`. Every signer, whether it
29
+ holds the private key in memory, on a Ledger device, or in AWS KMS, implements the same interface,
30
+ so the rest of your code doesn't need to know where the key lives.
31
+
32
+ Implementations provide three primitives:
33
+
34
+ | Method | Description |
35
+ | ---------------- | ------------------------------------------------------------------ |
36
+ | `getPublicKey()` | Returns the `PublicKey` for deriving the address and verifying |
37
+ | `getKeyScheme()` | Returns the signature scheme (`ED25519`, `Secp256k1`, `Secp256r1`) |
38
+ | `sign(bytes)` | Signs raw bytes and returns the signature |
39
+
40
+ On top of those, `Signer` provides the methods you typically call:
41
+
42
+ | Method | Description |
43
+ | ----------------------------- | ------------------------------------------------ |
44
+ | `toSuiAddress()` | The Sui address derived from the public key |
45
+ | `signTransaction(bytes)` | Signs transaction bytes with the correct intent |
46
+ | `signPersonalMessage(bytes)` | Signs a personal message |
47
+ | `signAndExecuteTransaction()` | Signs and submits a transaction through a client |
48
+
49
+ There are two broad families of signer:
50
+
51
+ - **Key pairs** hold the private key in process. `Keypair` extends `Signer` and adds secret-key
52
+ import and export (`getSecretKey`, `fromSecretKey`). This is the easiest option for scripts,
53
+ backends, and tests. See [Key pairs](/sui/cryptography/keypairs).
54
+ - **External signers** keep the private key outside your application, in a KMS, on a hardware
55
+ wallet, or as a non-extractable browser key. They extend `Signer` directly. See
56
+ [Signers](/sui/cryptography/signers).
57
+
58
+ Because both families share the `Signer` interface, you can pass any signer wherever the SDK accepts
59
+ one, for example as the `signer` argument to a client's `signAndExecuteTransaction`.
60
+
61
+ ## Public keys and addresses
62
+
63
+ Every signer exposes a `PublicKey` through `getPublicKey()`. You use the public key to derive the
64
+ signer's Sui address and to verify signatures:
65
+
66
+ ```typescript
67
+
68
+ const keypair = new Ed25519Keypair();
69
+
70
+ // Derive the address directly from the signer...
71
+ const address = keypair.toSuiAddress();
72
+
73
+ // ...or from its public key
74
+ const publicKey = keypair.getPublicKey();
75
+ const sameAddress = publicKey.toSuiAddress();
76
+ ```
77
+
78
+ ## Signing messages and transactions
79
+
80
+ The signing API is identical for every signer; only construction differs. Use `signPersonalMessage`
81
+ to sign arbitrary bytes and `signTransaction` to sign built transaction bytes:
82
+
83
+ ```typescript
84
+ const message = new TextEncoder().encode('hello world');
85
+ const { signature } = await keypair.signPersonalMessage(message);
86
+ ```
87
+
88
+ Both `signPersonalMessage` and `signTransaction` resolve to `{ bytes, signature }`, where
89
+ `signature` is the serialized signature string used for execution and verification, and `bytes` is
90
+ the base64 payload that the signer signed.
91
+
92
+ To sign and submit a transaction in one step, pass the signer to a client:
93
+
94
+ ```typescript
95
+
96
+ const client = new SuiGrpcClient({
97
+ network: 'testnet',
98
+ baseUrl: 'https://fullnode.testnet.sui.io:443',
99
+ });
100
+
101
+ const result = await client.signAndExecuteTransaction({
102
+ transaction,
103
+ signer: keypair,
104
+ });
105
+
106
+ // signAndExecuteTransaction resolves to a discriminated union, so check for failure first
107
+ if (result.FailedTransaction) {
108
+ throw new Error('Transaction failed to execute');
109
+ }
110
+
111
+ console.log('Digest:', result.Transaction.digest);
112
+ ```
113
+
114
+ ## Verifying signatures
115
+
116
+ Verification confirms a signature is valid for a given message and that the expected key produced
117
+ it. The `@mysten/sui/verify` module verifies a signature against a message and recovers the
118
+ `PublicKey` that produced it, without needing the original signer.
119
+
120
+ ```typescript
121
+
122
+ const keypair = new Ed25519Keypair();
123
+ const message = new TextEncoder().encode('hello world');
124
+ const { signature } = await keypair.signPersonalMessage(message);
125
+
126
+ const publicKey = await verifyPersonalMessageSignature(message, signature);
127
+
128
+ if (!publicKey.verifyAddress(keypair.toSuiAddress())) {
129
+ throw new Error('Signature was valid, but was signed by a different key pair');
130
+ }
131
+ ```
132
+
133
+ ### Verifying that a signature is valid for a specific address
134
+
135
+ `verifyPersonalMessageSignature` and `verifyTransactionSignature` accept an optional `address` and
136
+ throw if the signature is not valid for it:
137
+
138
+ ```typescript
139
+
140
+ const keypair = new Ed25519Keypair();
141
+ const message = new TextEncoder().encode('hello world');
142
+ const { signature } = await keypair.signPersonalMessage(message);
143
+
144
+ await verifyPersonalMessageSignature(message, signature, {
145
+ address: keypair.toSuiAddress(),
146
+ });
147
+ ```
148
+
149
+ ### Verifying transaction signatures
150
+
151
+ Verifying transaction signatures works the same way, using `verifyTransactionSignature`:
152
+
153
+ ```typescript
154
+
155
+ const client = new SuiGrpcClient({
156
+ network: 'testnet',
157
+ baseUrl: 'https://fullnode.testnet.sui.io:443',
158
+ });
159
+ const tx = new Transaction();
160
+ // ... add some transactions...
161
+ const bytes = await tx.build({ client });
162
+
163
+ const keypair = new Ed25519Keypair();
164
+ const { signature } = await keypair.signTransaction(bytes);
165
+
166
+ await verifyTransactionSignature(bytes, signature, {
167
+ // optionally verify that the signature is valid for a specific address
168
+ address: keypair.toSuiAddress(),
169
+ });
170
+ ```
171
+
172
+ ### Verifying zkLogin signatures
173
+
174
+ You can't verify zkLogin signatures purely on the client. When verifying a zkLogin signature, the
175
+ SDK uses the GraphQL API. This works for Mainnet signatures without any additional configuration.
176
+ For Testnet signatures, provide a Testnet GraphQL client:
177
+
178
+ ```typescript
179
+
180
+ const publicKey = await verifyPersonalMessageSignature(message, zkSignature, {
181
+ client: new SuiGraphQLClient({
182
+ url: 'https://graphql.testnet.sui.io/graphql',
183
+ }),
184
+ });
185
+ ```
186
+
187
+ For some zkLogin accounts, there are two valid addresses for a given set of inputs, so comparing the
188
+ address from `publicKey.toSuiAddress()` directly against an expected address can fail. Instead, pass
189
+ the expected address during verification, or use `publicKey.verifyAddress(address)`:
190
+
191
+ ```typescript
192
+
193
+ const publicKey = await verifyPersonalMessageSignature(message, zkSignature, {
194
+ client: new SuiGraphQLClient({
195
+ url: 'https://graphql.testnet.sui.io/graphql',
196
+ }),
197
+ // Throws if the signature is not valid for the provided address
198
+ address: '0x...expectedAddress',
199
+ });
200
+ // or
201
+
202
+ if (!publicKey.verifyAddress('0x...expectedAddress')) {
203
+ throw new Error('Signature was valid, but was signed by a different key pair');
204
+ }
205
+ ```
206
+
207
+ Both methods check the signature against the standard and
208
+ [legacy versions of the zkLogin address](https://sdk.mystenlabs.com/sui/zklogin#legacy-addresses).
209
+
210
+ ## Choosing an implementation
211
+
212
+ - **[Key pairs](/sui/cryptography/keypairs)**: `Ed25519Keypair`, `Secp256k1Keypair`, and
213
+ `Secp256r1Keypair` hold the private key in process. The easiest option when you manage the secret
214
+ key yourself. Covers deriving keypairs from mnemonics and secret keys.
215
+ - **[Signers](/sui/cryptography/signers)**: other implementations of the `Signer` interface,
216
+ including signer packages that keep the private key outside your application — cloud KMS
217
+ ([AWS](/sui/cryptography/signers/aws-kms), [GCP](/sui/cryptography/signers/gcp-kms)), a
218
+ [Ledger](/sui/cryptography/signers/ledger) hardware wallet, and non-extractable browser keys with
219
+ the [Web Crypto](/sui/cryptography/signers/webcrypto) signer — as well as
220
+ [passkeys](/sui/cryptography/signers/passkey) and
221
+ [multi-signature](/sui/cryptography/signers/multisig).
222
+
223
+ If you just need to sign in a script or server and are comfortable managing the secret key yourself,
224
+ start with [Key pairs](/sui/cryptography/keypairs). If the key must never live in your application's
225
+ memory, use one of the external [signers](/sui/cryptography/signers).
@@ -1,11 +1,14 @@
1
1
  # Key pairs
2
2
 
3
- > Create and manage Ed25519, Secp256k1, and Secp256r1 keypairs for Sui transaction signing.
3
+ > Create Ed25519, Secp256k1, and Secp256r1 keypairs and derive them from mnemonics and secret keys
4
4
 
5
- The Sui TypeScript SDK provides `Keypair` classes that handle logic for signing and verification
6
- using the cryptographic key pairs associated with a Sui address.
5
+ A `Keypair` holds a Sui private key in process and signs with it. It is the easiest
6
+ [`Signer`](/sui/cryptography) to use. It is ideal for scripts, backends, and tests where you manage
7
+ the secret key yourself. For the signing and verification API shared by all signers, see
8
+ [Cryptography](/sui/cryptography); this page focuses on creating keypairs and deriving them from
9
+ mnemonics and secret keys.
7
10
 
8
- The Sui TypeScript SDK supports three signing schemes:
11
+ Each signing scheme has a corresponding `Keypair` class:
9
12
 
10
13
  | Sign scheme | Class name | Import folder |
11
14
  | --------------- | ------------------ | -------------------------------- |
@@ -13,9 +16,6 @@ The Sui TypeScript SDK supports three signing schemes:
13
16
  | ECDSA Secp256k1 | `Secp256k1Keypair` | `@mysten/sui/keypairs/secp256k1` |
14
17
  | ECDSA Secp256r1 | `Secp256r1Keypair` | `@mysten/sui/keypairs/secp256r1` |
15
18
 
16
- For information on these schemes, see the
17
- [Signatures](https://docs.sui.io/concepts/cryptography/transaction-auth/signatures) topic.
18
-
19
19
  To use, import the key pair class your project uses from the `@mysten/sui/keypairs` folder. For
20
20
  example, to use the Ed25519 scheme, import the `Ed25519Keypair` class from
21
21
  `@mysten/sui/keypairs/ed25519`.
@@ -24,155 +24,55 @@ example, to use the Ed25519 scheme, import the `Ed25519Keypair` class from
24
24
 
25
25
  ```
26
26
 
27
+ ## Creating a key pair
28
+
27
29
  To create a random key pair (which identifies a Sui address), instantiate a new `Keypair` class. To
28
30
  reference a key pair from an existing secret key, pass the secret to the `fromSecretKey` function.
29
31
 
30
32
  ```typescript
31
33
  // random Keypair
32
34
  const keypair = new Ed25519Keypair();
33
- // Keypair from an existing secret key (Uint8Array)
34
- const keypair = Ed25519Keypair.fromSecretKey(secretKey);
35
- ```
36
-
37
- With your key pair created, you can reference it when performing actions on the network. For
38
- example, you can use it to sign transactions, like the following code that creates and signs a
39
- personal message using the public key from the key pair created in the previous code:
40
-
41
- ```typescript
42
- const publicKey = keypair.getPublicKey();
43
- const message = new TextEncoder().encode('hello world');
44
35
 
45
- const { signature } = await keypair.signPersonalMessage(message);
46
- const isValid = await publicKey.verifyPersonalMessage(message, signature);
36
+ // or, a Keypair from an existing secret key (Uint8Array)
37
+ const fromSecret = Ed25519Keypair.fromSecretKey(secretKey);
47
38
  ```
48
39
 
49
- ## Public keys
50
-
51
- Each `Keypair` has an associated `PublicKey` class. You use the public key to verify signatures or
52
- to retrieve its associated Sui address. You can access a `Keypair` from its `PublicKey` or construct
53
- it from the bytes (as a `Uint8Array`) of the `PublicKey`, as in the following code:
40
+ With your key pair created, you can derive its address and sign with it. The following signs a
41
+ personal message and verifies it with the public key:
54
42
 
55
43
  ```typescript
56
-
57
- const keypair = new Ed25519Keypair();
58
-
59
- // method 1
60
- const bytes = keypair.getPublicKey().toRawBytes();
61
- const publicKey = new Ed25519PublicKey(bytes);
62
- const address = publicKey.toSuiAddress();
63
-
64
- // method 2
65
- const address = keypair.getPublicKey().toSuiAddress();
66
- // or use `toSuiAddress()` directly
44
+ const publicKey = keypair.getPublicKey();
67
45
  const address = keypair.toSuiAddress();
68
- ```
69
-
70
- ## Verifying signatures without a key pair
71
-
72
- When you have an existing public key, you can use it to verify a signature. Verification ensures the
73
- signature is valid for the provided message and is signed with the appropriate secret key.
74
46
 
75
- The following code creates a key pair in the Ed25519 scheme, creates and signs a message with it,
76
- then verifies the message to retrieve the public key. The code then uses `toSuiAddress()` to check
77
- if the address associated with the public key matches the address that the key pair defines.
78
-
79
- ```typescript
80
-
81
- const keypair = new Ed25519Keypair();
82
47
  const message = new TextEncoder().encode('hello world');
83
48
  const { signature } = await keypair.signPersonalMessage(message);
84
-
85
- const publicKey = await verifyPersonalMessageSignature(message, signature);
86
-
87
- if (!publicKey.verifyAddress(keypair.toSuiAddress())) {
88
- throw new Error('Signature was valid, but was signed by a different key pair');
89
- }
90
- ```
91
-
92
- ## Verifying that a signature is valid for a specific address
93
-
94
- `verifyPersonalMessageSignature` and `verifyTransactionSignature` accept an optional `address`, and
95
- will throw an error if the signature is not valid for the provided address.
96
-
97
- ```typescript
98
-
99
- const keypair = new Ed25519Keypair();
100
- const message = new TextEncoder().encode('hello world');
101
- const { signature } = await keypair.signPersonalMessage(message);
102
-
103
- await verifyPersonalMessageSignature(message, signature, {
104
- address: keypair.toSuiAddress(),
105
- });
106
- ```
107
-
108
- ## Verifying transaction signatures
109
-
110
- Verifying transaction signatures is similar to personal message signature verification, except you
111
- use `verifyTransactionSignature`:
112
-
113
- ```typescript
114
- // import SuiGrpcClient to create a network client
115
-
116
- // create a client connected to testnet
117
- const client = new SuiGrpcClient({
118
- network: 'testnet',
119
- baseUrl: 'https://fullnode.testnet.sui.io:443',
120
- });
121
- const tx = new Transaction();
122
- // ... add some transactions...
123
- const bytes = await tx.build({ client });
124
-
125
- const keypair = new Ed25519Keypair();
126
- const { signature } = await keypair.signTransaction(bytes);
127
-
128
- await verifyTransactionSignature(bytes, signature, {
129
- // optionally verify that the signature is valid for a specific address
130
- address: keypair.toSuiAddress(),
131
- });
49
+ const isValid = await publicKey.verifyPersonalMessage(message, signature);
132
50
  ```
133
51
 
134
- ## Verifying zkLogin signatures
52
+ See [Cryptography](/sui/cryptography) for the full signing and verification API, including
53
+ [verifying signatures](/sui/cryptography#verifying-signatures) without the original key pair.
135
54
 
136
- ZkLogin signatures can't be verified purely on the client. When verifying a zkLogin signature, the
137
- SDK uses the GraphQL API to verify the signature. This works for Mainnet signatures without any
138
- additional configuration.
55
+ ## Public keys
139
56
 
140
- For Testnet signatures, you need to provide a Testnet GraphQL Client:
57
+ Each `Keypair` has an associated `PublicKey`, which you use to verify signatures or to retrieve its
58
+ Sui address. Access it from a `Keypair` with `getPublicKey()`. To recreate a keypair, and therefore
59
+ its public key, from saved key material, reconstruct it from the secret key:
141
60
 
142
61
  ```typescript
143
62
 
144
- const publicKey = await verifyPersonalMessageSignature(message, zkSignature, {
145
- client: new SuiGraphQLClient({
146
- url: 'https://graphql.testnet.sui.io/graphql',
147
- }),
148
- });
149
- ```
150
-
151
- For some zklogin accounts, there are two valid addresses for a given set of inputs. This means you
152
- might run into issues if you try to compare the address returned by `publicKey.toSuiAddress()`
153
- directly with an expected address.
154
-
155
- Instead, you can either pass in the expected address during verification, or use the
156
- `publicKey.verifyAddress(address)` method:
157
-
158
- ```typescript
63
+ // Recreate the keypair from its secret key (Uint8Array)
64
+ const keypair = Ed25519Keypair.fromSecretKey(secretKey);
159
65
 
160
- const publicKey = await verifyPersonalMessageSignature(message, zkSignature, {
161
- client: new SuiGraphQLClient({
162
- url: 'https://graphql.testnet.sui.io/graphql',
163
- }),
164
- // Pass in the expected address, and the verification method will throw an error if the signature is not valid for the provided address
165
- address: '0x...expectedAddress',
166
- });
167
- // or
168
-
169
- if (!publicKey.verifyAddress('0x...expectedAddress')) {
170
- throw new Error('Signature was valid, but was signed by a different key pair');
171
- }
66
+ // Access the public key and derive the address
67
+ const publicKey = keypair.getPublicKey();
68
+ const address = publicKey.toSuiAddress();
69
+ // or derive the address directly from the keypair
70
+ const sameAddress = keypair.toSuiAddress();
172
71
  ```
173
72
 
174
- Both of these methods will check the signature against both the standard and
175
- [legacy versions of the zklogin address](https://sdk.mystenlabs.com/sui/zklogin#legacy-addresses).
73
+ See
74
+ [Deriving a `Keypair` from a Bech32 encoded secret key](#deriving-a-keypair-from-a-bech32-encoded-secret-key)
75
+ for working with encoded secret-key formats.
176
76
 
177
77
  ## Deriving a key pair from a mnemonic
178
78
 
@@ -199,14 +99,16 @@ const keypair = Ed25519Keypair.fromSecretKey(secretKey);
199
99
  ```
200
100
 
201
101
  You can also use the `decodeSuiPrivateKey` function to decode the secret key and determine the
202
- keyScheme, and get the keypairs private key bytes, which can be used to instantiate the appropriate
203
- `Keypair` class.
102
+ keyScheme, and get the key pair's private key bytes, which you can use to instantiate the
103
+ appropriate `Keypair` class.
204
104
 
205
105
  ```typescript
206
106
 
107
+ const encoded = 'suiprivkey1qzse89atw7d3zum8ujep76d2cxmgduyuast0y9fu23xcl0mpafgkktllhyc';
207
108
  const { scheme, secretKey } = decodeSuiPrivateKey(encoded);
208
109
 
209
110
  // use scheme to choose the correct key pair
111
+ let keypair;
210
112
  switch (scheme) {
211
113
  case 'ED25519':
212
114
  keypair = Ed25519Keypair.fromSecretKey(secretKey);
@@ -225,16 +127,7 @@ switch (scheme) {
225
127
  See [SIP-15](https://github.com/sui-foundation/sips/blob/main/sips/sip-15.md) for additional context
226
128
  and motivation.
227
129
 
228
- If you know your keypair scheme, you can use the `fromSecretKey` method of the appropriate keypair
229
- to directly derive the keypair from the secret key.
230
-
231
- ```typescript
232
- const secretKey = 'suiprivkey1qzse89atw7d3zum8ujep76d2cxmgduyuast0y9fu23xcl0mpafgkktllhyc';
233
-
234
- const keypair = Ed25519Keypair.fromSecretKey(secretKey);
235
- ```
236
-
237
- You can also export a keypair to a Bech32 encoded secret key using the `getSecretKey` method.
130
+ You can export a keypair to a Bech32 encoded secret key using the `getSecretKey` method.
238
131
 
239
132
  ```typescript
240
133
  const secretKey = keypair.getSecretKey();