@mysten/docs 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/dist/bcs/index.md +358 -0
- package/dist/bcs/llms-index.md +4 -0
- package/dist/codegen/index.md +441 -0
- package/dist/codegen/llms-index.md +4 -0
- package/dist/dapp-kit/actions/connect-wallet.md +69 -0
- package/dist/dapp-kit/actions/disconnect-wallet.md +38 -0
- package/dist/dapp-kit/actions/sign-and-execute-transaction.md +96 -0
- package/dist/dapp-kit/actions/sign-personal-message.md +58 -0
- package/dist/dapp-kit/actions/sign-transaction.md +65 -0
- package/dist/dapp-kit/actions/switch-account.md +37 -0
- package/dist/dapp-kit/actions/switch-network.md +37 -0
- package/dist/dapp-kit/dapp-kit-instance.md +162 -0
- package/dist/dapp-kit/getting-started/create-dapp.md +151 -0
- package/dist/dapp-kit/getting-started/next-js.md +162 -0
- package/dist/dapp-kit/getting-started/react.md +172 -0
- package/dist/dapp-kit/getting-started/vue.md +193 -0
- package/dist/dapp-kit/index.md +70 -0
- package/dist/dapp-kit/llms-index.md +26 -0
- package/dist/dapp-kit/react/components/connect-button.md +42 -0
- package/dist/dapp-kit/react/components/connect-modal.md +51 -0
- package/dist/dapp-kit/react/components/index.md +13 -0
- package/dist/dapp-kit/react/dapp-kit-provider.md +86 -0
- package/dist/dapp-kit/react/hooks/index.md +25 -0
- package/dist/dapp-kit/react/hooks/use-current-account.md +33 -0
- package/dist/dapp-kit/react/hooks/use-current-client.md +36 -0
- package/dist/dapp-kit/react/hooks/use-current-network.md +28 -0
- package/dist/dapp-kit/react/hooks/use-current-wallet.md +36 -0
- package/dist/dapp-kit/react/hooks/use-dapp-kit.md +100 -0
- package/dist/dapp-kit/react/hooks/use-wallet-connection.md +48 -0
- package/dist/dapp-kit/react/hooks/use-wallets.md +33 -0
- package/dist/dapp-kit/state.md +169 -0
- package/dist/dapp-kit/theming.md +196 -0
- package/dist/dapp-kit/web-components/connect-button.md +89 -0
- package/dist/dapp-kit/web-components/connect-modal.md +177 -0
- package/dist/kiosk/advanced-examples.md +101 -0
- package/dist/kiosk/from-v1.md +320 -0
- package/dist/kiosk/index.md +22 -0
- package/dist/kiosk/kiosk-client/introduction.md +52 -0
- package/dist/kiosk/kiosk-client/kiosk-transaction/examples.md +119 -0
- package/dist/kiosk/kiosk-client/kiosk-transaction/kiosk-transaction.md +103 -0
- package/dist/kiosk/kiosk-client/kiosk-transaction/managing.md +235 -0
- package/dist/kiosk/kiosk-client/kiosk-transaction/purchasing.md +110 -0
- package/dist/kiosk/kiosk-client/querying.md +237 -0
- package/dist/kiosk/kiosk-client/transfer-policy-transaction/introduction.md +79 -0
- package/dist/kiosk/kiosk-client/transfer-policy-transaction/using-the-manager.md +115 -0
- package/dist/kiosk/llms-index.md +8 -0
- package/dist/llms-index.md +125 -0
- package/dist/payment-kit/getting-started.md +256 -0
- package/dist/payment-kit/index.md +132 -0
- package/dist/payment-kit/llms-index.md +8 -0
- package/dist/payment-kit/payment-kit-sdk.md +747 -0
- package/dist/payment-kit/payment-processing.md +372 -0
- package/dist/payment-kit/registry-management.md +529 -0
- package/dist/seal/index.md +85 -0
- package/dist/seal/llms-index.md +4 -0
- package/dist/slush-wallet/dapp.md +95 -0
- package/dist/slush-wallet/deep-linking.md +465 -0
- package/dist/slush-wallet/index.md +7 -0
- package/dist/slush-wallet/llms-index.md +6 -0
- package/dist/sui/bcs.md +134 -0
- package/dist/sui/clients/core.md +606 -0
- package/dist/sui/clients/graphql.md +101 -0
- package/dist/sui/clients/grpc.md +155 -0
- package/dist/sui/clients/index.md +95 -0
- package/dist/sui/clients/json-rpc.md +239 -0
- package/dist/sui/cryptography/keypairs.md +267 -0
- package/dist/sui/cryptography/multisig.md +194 -0
- package/dist/sui/cryptography/passkey.md +111 -0
- package/dist/sui/cryptography/webcrypto-signer.md +81 -0
- package/dist/sui/executors.md +148 -0
- package/dist/sui/faucet.md +26 -0
- package/dist/sui/hello-sui.md +115 -0
- package/dist/sui/index.md +53 -0
- package/dist/sui/install.md +61 -0
- package/dist/sui/llm-docs.md +32 -0
- package/dist/sui/llms-index.md +44 -0
- package/dist/sui/migrations/0.38.md +58 -0
- package/dist/sui/migrations/sui-1.0.md +455 -0
- package/dist/sui/migrations/sui-2.0/agent-prompt.md +42 -0
- package/dist/sui/migrations/sui-2.0/dapp-kit.md +350 -0
- package/dist/sui/migrations/sui-2.0/deepbook-v3.md +33 -0
- package/dist/sui/migrations/sui-2.0/index.md +158 -0
- package/dist/sui/migrations/sui-2.0/json-rpc-migration.md +386 -0
- package/dist/sui/migrations/sui-2.0/kiosk.md +120 -0
- package/dist/sui/migrations/sui-2.0/sdk-maintainers.md +90 -0
- package/dist/sui/migrations/sui-2.0/seal.md +14 -0
- package/dist/sui/migrations/sui-2.0/sui.md +341 -0
- package/dist/sui/migrations/sui-2.0/suins.md +43 -0
- package/dist/sui/migrations/sui-2.0/wallet-builders.md +66 -0
- package/dist/sui/migrations/sui-2.0/walrus.md +41 -0
- package/dist/sui/migrations/sui-2.0/zksend.md +95 -0
- package/dist/sui/plugins.md +258 -0
- package/dist/sui/sdk-building.md +344 -0
- package/dist/sui/transaction-building/basics.md +299 -0
- package/dist/sui/transaction-building/gas.md +62 -0
- package/dist/sui/transaction-building/intents.md +62 -0
- package/dist/sui/transaction-building/offline.md +73 -0
- package/dist/sui/transaction-building/sponsored-transactions.md +22 -0
- package/dist/sui/utils/derived_objects.md +59 -0
- package/dist/sui/utils/index.md +52 -0
- package/dist/sui/zklogin.md +83 -0
- package/dist/walrus/index.md +527 -0
- package/dist/walrus/llms-index.md +4 -0
- package/dist/zksend/index.md +27 -0
- package/dist/zksend/link-builder.md +192 -0
- package/dist/zksend/llms-index.md +5 -0
- package/package.json +66 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Key pairs
|
|
2
|
+
|
|
3
|
+
> Create and manage Ed25519, Secp256k1, and Secp256r1 keypairs
|
|
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.
|
|
7
|
+
|
|
8
|
+
The Sui TypeScript SDK supports three signing schemes:
|
|
9
|
+
|
|
10
|
+
| Sign scheme | Class name | Import folder |
|
|
11
|
+
| --------------- | ------------------ | -------------------------------- |
|
|
12
|
+
| Ed25519 | `Ed25519Keypair` | `@mysten/sui/keypairs/ed25519` |
|
|
13
|
+
| ECDSA Secp256k1 | `Secp256k1Keypair` | `@mysten/sui/keypairs/secp256k1` |
|
|
14
|
+
| ECDSA Secp256r1 | `Secp256r1Keypair` | `@mysten/sui/keypairs/secp256r1` |
|
|
15
|
+
|
|
16
|
+
For information on these schemes, see the
|
|
17
|
+
[Signatures](https://docs.sui.io/concepts/cryptography/transaction-auth/signatures) topic.
|
|
18
|
+
|
|
19
|
+
To use, import the key pair class your project uses from the `@mysten/sui/keypairs` folder. For
|
|
20
|
+
example, to use the Ed25519 scheme, import the `Ed25519Keypair` class from
|
|
21
|
+
`@mysten/sui/keypairs/ed25519`.
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
To create a random key pair (which identifies a Sui address), instantiate a new `Keypair` class. To
|
|
28
|
+
reference a key pair from an existing secret key, pass the secret to the `fromSecretKey` function.
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
// random Keypair
|
|
32
|
+
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
|
+
|
|
45
|
+
const { signature } = await keypair.signPersonalMessage(message);
|
|
46
|
+
const isValid = await publicKey.verifyPersonalMessage(message, signature);
|
|
47
|
+
```
|
|
48
|
+
|
|
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:
|
|
54
|
+
|
|
55
|
+
```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
|
|
67
|
+
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
|
+
|
|
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
|
+
const message = new TextEncoder().encode('hello world');
|
|
83
|
+
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
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Verifying zkLogin signatures
|
|
135
|
+
|
|
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 will work for mainnet signatures without any
|
|
138
|
+
additional configuration.
|
|
139
|
+
|
|
140
|
+
For testnet signatures, you will need to provide a testnet GraphQL Client:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
|
|
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 2 valid addresses for a given set of inputs. This means you may
|
|
152
|
+
run into issues if you try to compare the address returned by `publicKey.toSuiAddress()` directly
|
|
153
|
+
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
|
|
159
|
+
|
|
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
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
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).
|
|
176
|
+
|
|
177
|
+
## Deriving a key pair from a mnemonic
|
|
178
|
+
|
|
179
|
+
The Sui TypeScript SDK supports deriving a key pair from a mnemonic phrase. This can be useful when
|
|
180
|
+
building wallets or other tools that allow a user to import their private keys.
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
const exampleMnemonic = 'result crisp session latin ...';
|
|
184
|
+
|
|
185
|
+
const keyPair = Ed25519Keypair.deriveKeypair(exampleMnemonic);
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Deriving a `Keypair` from a Bech32 encoded secret key
|
|
189
|
+
|
|
190
|
+
If you know the Keypair scheme for your secret key, you can use the `fromSecretKey` method of the
|
|
191
|
+
appropriate `Keypair` class to derive the keypair from the secret key.
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
|
|
195
|
+
// Example bech32 encoded secret key (which always starts with 'suiprivkey')
|
|
196
|
+
const secretKey = 'suiprivkey1qzse89atw7d3zum8ujep76d2cxmgduyuast0y9fu23xcl0mpafgkktllhyc';
|
|
197
|
+
|
|
198
|
+
const keypair = Ed25519Keypair.fromSecretKey(secretKey);
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
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.
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
|
|
207
|
+
const { scheme, secretKey } = decodeSuiPrivateKey(encoded);
|
|
208
|
+
|
|
209
|
+
// use scheme to choose the correct key pair
|
|
210
|
+
switch (scheme) {
|
|
211
|
+
case 'ED25519':
|
|
212
|
+
keypair = Ed25519Keypair.fromSecretKey(secretKey);
|
|
213
|
+
break;
|
|
214
|
+
case 'Secp256r1':
|
|
215
|
+
keypair = Secp256r1Keypair.fromSecretKey(secretKey);
|
|
216
|
+
break;
|
|
217
|
+
case 'Secp256k1':
|
|
218
|
+
keypair = Secp256k1Keypair.fromSecretKey(secretKey);
|
|
219
|
+
break;
|
|
220
|
+
default:
|
|
221
|
+
throw new Error(`Unsupported key pair scheme: ${scheme}`);
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
See [SIP-15](https://github.com/sui-foundation/sips/blob/main/sips/sip-15.md) for additional context
|
|
226
|
+
and motivation.
|
|
227
|
+
|
|
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.
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
const secretKey = keypair.getSecretKey();
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
If you have only the raw private key bytes for your keypair, you can encode to the Bech32 format
|
|
244
|
+
using the `encodeSuiPrivateKey` function.
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
|
|
248
|
+
const encoded = encodeSuiPrivateKey(
|
|
249
|
+
new Uint8Array([
|
|
250
|
+
59, 148, 11, 85, 134, 130, 61, 253, 2, 174, 59, 70, 27, 180, 51, 107, 94, 203, 174, 253, 102,
|
|
251
|
+
39, 170, 146, 46, 252, 4, 143, 236, 12, 136, 28,
|
|
252
|
+
]),
|
|
253
|
+
'ED25519',
|
|
254
|
+
);
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Deriving a `Keypair` from a hex encoded secret key
|
|
258
|
+
|
|
259
|
+
If you have an existing secret key formatted as a hex encoded string, you can derive a `Keypair` by
|
|
260
|
+
converting the secret key to a `Uint8Array` and passing it to the `fromSecretKey` method of a
|
|
261
|
+
`Keypair` class.
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
|
|
265
|
+
const secret = '0x...';
|
|
266
|
+
const keypair = Ed25519Keypair.fromSecretKey(fromHex(secret));
|
|
267
|
+
```
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Multi-Signature Transactions
|
|
2
|
+
|
|
3
|
+
> Create multi-signature transactions with multiple signers
|
|
4
|
+
|
|
5
|
+
The Sui TypeScript SDK provides a `MultiSigPublicKey` class to support
|
|
6
|
+
[Multi-Signature](https://docs.sui.io/concepts/cryptography/transaction-auth/multisig) (MultiSig)
|
|
7
|
+
transaction and personal message signing.
|
|
8
|
+
|
|
9
|
+
This class implements the same interface as the `PublicKey` classes that [Keypairs](./keypairs) uses
|
|
10
|
+
and you call the same methods to verify signatures for `PersonalMessages` and `Transactions`.
|
|
11
|
+
|
|
12
|
+
## Creating a MultiSigPublicKey
|
|
13
|
+
|
|
14
|
+
To create a `MultiSigPublicKey`, you provide a `threshold`(u16) value and an array of objects that
|
|
15
|
+
contain `publicKey` and `weight`(u8) values. If the combined weight of valid signatures for a
|
|
16
|
+
transaction is equal to or greater than the threshold value, then the Sui network considers the
|
|
17
|
+
transdaction valid.
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
|
|
21
|
+
const kp1 = new Ed25519Keypair();
|
|
22
|
+
const kp2 = new Ed25519Keypair();
|
|
23
|
+
const kp3 = new Ed25519Keypair();
|
|
24
|
+
|
|
25
|
+
const multiSigPublicKey = MultiSigPublicKey.fromPublicKeys({
|
|
26
|
+
threshold: 2,
|
|
27
|
+
publicKeys: [
|
|
28
|
+
{
|
|
29
|
+
publicKey: kp1.getPublicKey(),
|
|
30
|
+
weight: 1,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
publicKey: kp2.getPublicKey(),
|
|
34
|
+
weight: 1,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
publicKey: kp3.getPublicKey(),
|
|
38
|
+
weight: 2,
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const multisigAddress = multiSigPublicKey.toSuiAddress();
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The `multiSigPublicKey` in the preceding code enables you to verify that signatures have a combined
|
|
47
|
+
weight of at least `2`. A signature signed with only `kp1` or `kp2` is not valid, but a signature
|
|
48
|
+
signed with both `kp1` and `kp2`, or just `kp3` is valid.
|
|
49
|
+
|
|
50
|
+
## Combining signatures with a MultiSigPublicKey
|
|
51
|
+
|
|
52
|
+
To sign a message or transaction for a MultiSig address, you must collect signatures from the
|
|
53
|
+
individual key pairs, and then combine them into a signature using the `MultiSigPublicKey` class for
|
|
54
|
+
the address.
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// This example uses the same imports, key pairs, and multiSigPublicKey from the previous example
|
|
58
|
+
const message = new TextEncoder().encode('hello world');
|
|
59
|
+
|
|
60
|
+
const signature1 = (await kp1.signPersonalMessage(message)).signature;
|
|
61
|
+
const signature2 = (await kp2.signPersonalMessage(message)).signature;
|
|
62
|
+
|
|
63
|
+
const combinedSignature = multiSigPublicKey.combinePartialSignatures([signature1, signature2]);
|
|
64
|
+
|
|
65
|
+
const isValid = await multiSigPublicKey.verifyPersonalMessage(message, combinedSignature);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Creating a MultiSigSigner
|
|
69
|
+
|
|
70
|
+
The `MultiSigSigner` class allows you to create a Signer that can be used to sign personal messages
|
|
71
|
+
and Transactions like any other keypair or signer class. This is often easier than manually
|
|
72
|
+
combining signatures, since many methods accept Signers and handle signing directly.
|
|
73
|
+
|
|
74
|
+
A `MultiSigSigner` is created by providing the underlying Signers to the `getSigner` method on the
|
|
75
|
+
`MultiSigPublicKey`. You can provide a subset of the Signers that make up the public key, so long as
|
|
76
|
+
their combined weight is equal to or greater than the threshold.
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
|
|
80
|
+
const kp1 = new Ed25519Keypair();
|
|
81
|
+
const kp2 = new Ed25519Keypair();
|
|
82
|
+
|
|
83
|
+
const multiSigPublicKey = MultiSigPublicKey.fromPublicKeys({
|
|
84
|
+
threshold: 1,
|
|
85
|
+
publicKeys: [
|
|
86
|
+
{
|
|
87
|
+
publicKey: kp1.getPublicKey(),
|
|
88
|
+
weight: 1,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
publicKey: kp2.getPublicKey(),
|
|
92
|
+
weight: 1,
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const signer = multiSigPublicKey.getSigner(kp1);
|
|
98
|
+
|
|
99
|
+
const message = new TextEncoder().encode('hello world');
|
|
100
|
+
const { signature } = await signer.signPersonalMessage(message);
|
|
101
|
+
const isValid = await multiSigPublicKey.verifyPersonalMessage(message, signature);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Multisig with zkLogin
|
|
105
|
+
|
|
106
|
+
You can use zkLogin to participate in multisig just like keys for other signature schemes. Unlike
|
|
107
|
+
other keys that come with a public key, you define a public identifier for zkLogin.
|
|
108
|
+
|
|
109
|
+
For example, the following example creates a 1-out-of-2 multisig with a single key and a zkLogin
|
|
110
|
+
public identifier:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// a single Ed25519 keypair and its public key.
|
|
114
|
+
const kp1 = new Ed25519Keypair();
|
|
115
|
+
const pkSingle = kp1.getPublicKey();
|
|
116
|
+
|
|
117
|
+
// compute the address seed based on user salt and jwt token values.
|
|
118
|
+
const decodedJWT = decodeJwt('a valid jwt token here');
|
|
119
|
+
const userSalt = BigInt('123'); // a valid user salt
|
|
120
|
+
const addressSeed = genAddressSeed(userSalt, 'sub', decodedJwt.sub, decodedJwt.aud).toString();
|
|
121
|
+
|
|
122
|
+
// a zkLogin public identifier derived from an address seed and an iss string.
|
|
123
|
+
let pkZklogin = toZkLoginPublicIdentifier(addressSeed, decodedJwt.iss);
|
|
124
|
+
|
|
125
|
+
// derive multisig address from multisig public key defined by the single key and zkLogin public
|
|
126
|
+
// identifier with weight and threshold.
|
|
127
|
+
const multiSigPublicKey = MultiSigPublicKey.fromPublicKeys({
|
|
128
|
+
threshold: 1,
|
|
129
|
+
publicKeys: [
|
|
130
|
+
{ publicKey: pkSingle, weight: 1 },
|
|
131
|
+
{ publicKey: pkZklogin, weight: 1 },
|
|
132
|
+
],
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// this is the sender of any transactions from this multisig account.
|
|
136
|
+
const multisigAddress = multiSigPublicKey.toSuiAddress();
|
|
137
|
+
|
|
138
|
+
// create a regular zklogin signature from the zkproof and ephemeral signature for zkLogin.
|
|
139
|
+
// see zklogin-integration.mdx for more details.
|
|
140
|
+
const zkLoginSig = getZkLoginSignature({
|
|
141
|
+
inputs: zkLoginInputs,
|
|
142
|
+
maxEpoch: '2',
|
|
143
|
+
userSignature: fromBase64(ephemeralSig),
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// a valid multisig with just the zklogin signature.
|
|
147
|
+
const multisig = multiSigPublicKey.combinePartialSignatures([zkLoginSig]);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Benefits and Design for zkLogin in Multisig
|
|
151
|
+
|
|
152
|
+
Because zkLogin assumes the application client ID and its issuer (such as Google) liveliness, using
|
|
153
|
+
zkLogin with multisig provides improved recoverability to a zkLogin account. In the previous example
|
|
154
|
+
of 1-out-of-2 multisig, users can use zkLogin in their regular wallet flow, but if the application
|
|
155
|
+
or the issuer is deprecated, the user can still use the regular private key account to access funds
|
|
156
|
+
in the multisig wallet.
|
|
157
|
+
|
|
158
|
+
This also opens the door to design multisig across any number of zkLogin accounts and of different
|
|
159
|
+
providers (max number is capped at 10 accounts) with customizable weights and thresholds. For
|
|
160
|
+
example, you can set up a multisig address with threshold of 2, where the public keys or identifiers
|
|
161
|
+
are defined as:
|
|
162
|
+
|
|
163
|
+
1. Charlie's own Google account with weight 2
|
|
164
|
+
2. Charlie's friend Alice's Apple account with weight 1
|
|
165
|
+
3. Charlie's friend Bob's Facebook account with weight 1
|
|
166
|
+
|
|
167
|
+
In this case, Charlie can always use their Google account for transactions out of the multisig
|
|
168
|
+
address for the threshold. At the same time, Charlie still has access to his account by combining
|
|
169
|
+
partial signatures from Alice and Bob.
|
|
170
|
+
|
|
171
|
+
## Multisig with Passkey
|
|
172
|
+
|
|
173
|
+
You can use a `PasskeyKeypair` (a `Keypair`) and `PasskeyPublicKey` (a `PublicKey`) as components in
|
|
174
|
+
a Multisig setup. Once initialized, they support both Multisig address derivation and transaction
|
|
175
|
+
signing.
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
const passkeyKeypair = await PasskeyKeypair.getPasskeyInstance(
|
|
179
|
+
new BrowserPasskeyProvider('Sui Passkey Example', {
|
|
180
|
+
rpName: 'Sui Passkey Example',
|
|
181
|
+
rpId: window.location.hostname,
|
|
182
|
+
} as BrowserPasswordProviderOptions),
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const passkeyPublicKey = passkeyKeypair.getPublicKey();
|
|
186
|
+
|
|
187
|
+
const multiSigPublicKey = MultiSigPublicKey.fromPublicKeys({
|
|
188
|
+
threshold: 1,
|
|
189
|
+
publicKeys: [
|
|
190
|
+
{ publicKey: passkeyPublicKey, weight: 1 },
|
|
191
|
+
// other keys
|
|
192
|
+
],
|
|
193
|
+
});
|
|
194
|
+
```
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Passkey
|
|
2
|
+
|
|
3
|
+
> Use WebAuthn passkeys for Sui transaction signing
|
|
4
|
+
|
|
5
|
+
Similar to other Keypair classes, the Sui TypeScript SDK provides the `PasskeyKeypair` that handles
|
|
6
|
+
communication with the passkey device and signing with it. This SDK defines the address and
|
|
7
|
+
signatures according to [SIP-9](https://github.com/sui-foundation/sips/blob/main/sips/sip-9.md).
|
|
8
|
+
|
|
9
|
+
To use, import the `PasskeyKeypair` class from `@mysten/sui/keypairs/passkey`.
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
|
|
13
|
+
BrowserPasskeyProvider,
|
|
14
|
+
BrowserPasswordProviderOptions,
|
|
15
|
+
PasskeyKeypair,
|
|
16
|
+
} from '@mysten/sui/keypairs/passkey';
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Create a new passkey
|
|
20
|
+
|
|
21
|
+
To create a new passkey with the passkey device, and initialize a `PasskeyKeypair`, you can use the
|
|
22
|
+
`PasskeyProvider` and provide the `rpName`, `rpId` and additional options. Note that
|
|
23
|
+
`getPasskeyInstance` call will initialize a new passkey wallet for the origin.
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
const keypair = await PasskeyKeypair.getPasskeyInstance(
|
|
27
|
+
new BrowserPasskeyProvider('Sui Passkey Example', {
|
|
28
|
+
rpName: 'Sui Passkey Example',
|
|
29
|
+
rpId: window.location.hostname,
|
|
30
|
+
authenticatorSelection: {
|
|
31
|
+
authenticatorAttachment: 'cross-platform', // or "platform"
|
|
32
|
+
},
|
|
33
|
+
} as BrowserPasswordProviderOptions),
|
|
34
|
+
);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
You can optionally specify the `authenticatorSelection` parameter when creating a passkey instance:
|
|
38
|
+
|
|
39
|
+
- `cross-platform`: Use this for authenticators that work across devices, such as hardware security
|
|
40
|
+
keys or mobile phones.
|
|
41
|
+
- `platform`: Use this for authenticators tied to a specific device, such as Touch ID, Face ID, or
|
|
42
|
+
Windows Hello.
|
|
43
|
+
|
|
44
|
+
Because a passkey device will only return the public key upon creation, but not upon signing, it is
|
|
45
|
+
recommended for the frontend to always cache the `PasskeyKeypair` instance that contains the public
|
|
46
|
+
key in the wallet. When a user tries to sign a transaction, it will just call signTransaction on the
|
|
47
|
+
same instance and the public key is available when constructing the passkey signature.
|
|
48
|
+
|
|
49
|
+
It is recommended that the user should only be allowed to create a passkey wallet once per origin.
|
|
50
|
+
If the user ended up with multiple passkeys for the same origin, the passkey UI would show a list of
|
|
51
|
+
all passkeys of the same origin and the user can choose which one to use. This can be a confusing
|
|
52
|
+
experience since they do not remember which passkey is the way that the wallet was created for.
|
|
53
|
+
|
|
54
|
+
## Recover a passkey
|
|
55
|
+
|
|
56
|
+
However, if the user is logged out or uses a different browser or device, or the `PasskeyKeypair`
|
|
57
|
+
instance is no longer present for any reason, the user should be prompt to recover the public key
|
|
58
|
+
(e.g. "Log in to existing passkey wallet") to be able to use the passkey wallet again. If the user
|
|
59
|
+
is prompted to create a new key in passkey device, a fresh new wallet with new address will be
|
|
60
|
+
created.
|
|
61
|
+
|
|
62
|
+
One can recover the unique public key with exactly two passkey signatures on two messages. This
|
|
63
|
+
means that if the user is asked to sign two messages, the developer can recover an unique public key
|
|
64
|
+
and its Sui address.
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
let provider = new BrowserPasskeyProvider('Sui Passkey Example', {
|
|
68
|
+
rpName: 'Sui Passkey Example',
|
|
69
|
+
rpId: window.location.hostname,
|
|
70
|
+
} as BrowserPasswordProviderOptions);
|
|
71
|
+
|
|
72
|
+
const testMessage = new TextEncoder().encode('Hello world!');
|
|
73
|
+
const possiblePks = await PasskeyKeypair.signAndRecover(provider, testMessage);
|
|
74
|
+
|
|
75
|
+
const testMessage2 = new TextEncoder().encode('Hello world 2!');
|
|
76
|
+
const possiblePks2 = await PasskeyKeypair.signAndRecover(provider, testMessage2);
|
|
77
|
+
|
|
78
|
+
const commonPk = findCommonPublicKey(possiblePks, possiblePks2);
|
|
79
|
+
const keypair = new PasskeyKeypair(commonPk.toRawBytes(), provider);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Alternatively, the developer can choose to ask the user to only sign one personal message that
|
|
83
|
+
returns two possible public keys. Then the frontend may derive the addresses for both, and check
|
|
84
|
+
onchain for the unique address that contains assets. Or the frontend may retrieve additional
|
|
85
|
+
signatures from past transaction history for the given address. This may a preferred user experience
|
|
86
|
+
since the user is only prompted to sign one message, but the frontend needs to do extra work.
|
|
87
|
+
|
|
88
|
+
## Usage
|
|
89
|
+
|
|
90
|
+
The usage for a passkey keypair is the same as any other keypair. You can derive the public key,
|
|
91
|
+
derive the address, sign personal messages, sign transactions and verify signatures. See the
|
|
92
|
+
[Key pairs](./keypairs) documentation for more details.
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
const publicKey = keypair.getPublicKey();
|
|
96
|
+
const address = publicKey.toSuiAddress();
|
|
97
|
+
|
|
98
|
+
const message = new TextEncoder().encode('hello world');
|
|
99
|
+
const { signature } = await keypair.signPersonalMessage(message);
|
|
100
|
+
|
|
101
|
+
const txSignature = await passkey.signTransaction(txBytes);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
An example implemention can be found [here](https://github.com/MystenLabs/passkey-example).
|
|
105
|
+
|
|
106
|
+
## Supported Platforms
|
|
107
|
+
|
|
108
|
+
Sui supports passkey wallets on any device that complies with the WebAuthn standard. This includes
|
|
109
|
+
most modern browsers and operating systems across desktop and mobile. To check which platforms and
|
|
110
|
+
authenticators are compatible, see the
|
|
111
|
+
[Passkeys.dev device support list](https://passkeys.dev/device-support/).
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Web Crypto Signer
|
|
2
|
+
|
|
3
|
+
> Sign transactions using the Web Crypto API
|
|
4
|
+
|
|
5
|
+
For cases where you need to create keypairs directly within client dapps, we recommend using the Web
|
|
6
|
+
Crypto Signer. This signer leverages the
|
|
7
|
+
[Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) to provide a
|
|
8
|
+
secure and efficient way to generate and manage cryptographic keys in the browser. The generated
|
|
9
|
+
keys are `Secp256r1` keys which can be persisted between sessions, and are not extractable by
|
|
10
|
+
client-side code (including extensions).
|
|
11
|
+
|
|
12
|
+
Common use cases for the Web Crypto Signer include:
|
|
13
|
+
|
|
14
|
+
- zkLogin ephemeral keypairs
|
|
15
|
+
- Session-based keypairs
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
To use the Web Crypto Signer, you need to install the `@mysten/signers` package.
|
|
20
|
+
|
|
21
|
+
```sh npm2yarn
|
|
22
|
+
npm i @mysten/signers
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
You can then import the `WebCryptoSigner` class from the package:
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Create a new signer
|
|
32
|
+
|
|
33
|
+
To generate a new signer, you can invoke the `generate()` static method on the `WebCryptoSigner`
|
|
34
|
+
class.
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
const keypair = await WebCryptoSigner.generate();
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Persisting and recovering the keypair
|
|
41
|
+
|
|
42
|
+
The private key for the signer is not extractable, but you can persist the keypair using the
|
|
43
|
+
browser's IndexedDB storage. To streamline this process, the keypair provides an `export()` method
|
|
44
|
+
which returns an object containing the public key and a reference to the private key:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
// Get the exported keypair:
|
|
48
|
+
const exported = keypair.export();
|
|
49
|
+
|
|
50
|
+
// Write the keypair to IndexedDB.
|
|
51
|
+
// This method does not exist, you need to implement it yourself. We recommend `idb-keyval` for simplicity.
|
|
52
|
+
await writeToIndexedDB('keypair', exported);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
You can then recover the keypair by reading it from IndexedDB and passing it to the `import()`
|
|
56
|
+
method.
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// Read the keypair from IndexedDB.
|
|
60
|
+
const exported = await readFromIndexedDB('keypair');
|
|
61
|
+
|
|
62
|
+
const keypair = await WebCryptoSigner.import(exported);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
> **Warning:** Ensure that you do not call `JSON.stringify` on the exported keypair before persisting it to
|
|
66
|
+
> IndexedDB, as it will throw an error and fail to persist.
|
|
67
|
+
|
|
68
|
+
## Usage
|
|
69
|
+
|
|
70
|
+
The usage for a Web Crypto signer is the same as any other keypair. You can derive the public key,
|
|
71
|
+
derive the address, sign personal messages, sign transactions and verify signatures. See the
|
|
72
|
+
[Key pairs](./keypairs) documentation for more details.
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
const publicKey = keypair.getPublicKey();
|
|
76
|
+
const address = publicKey.toSuiAddress();
|
|
77
|
+
|
|
78
|
+
const message = new TextEncoder().encode('hello world');
|
|
79
|
+
await keypair.signPersonalMessage(message);
|
|
80
|
+
await keypair.signTransaction(txBytes);
|
|
81
|
+
```
|