@mysten/sui
Version:
Sui TypeScript API
193 lines (150 loc) • 7.06 kB
Markdown
# Multi-Signature Transactions
> Create multi-signature transactions with multiple signers
The Sui TypeScript SDK provides a `MultiSigPublicKey` class to support
[Multi-Signature](https://docs.sui.io/concepts/cryptography/transaction-auth/multisig) (MultiSig)
transaction and personal message signing.
This class implements the same interface as the `PublicKey` classes that [Keypairs](./keypairs) uses
and you call the same methods to verify signatures for `PersonalMessages` and `Transactions`.
## Creating a MultiSigPublicKey
To create a `MultiSigPublicKey`, you provide a `threshold`(u16) value and an array of objects that
contain `publicKey` and `weight`(u8) values. If the combined weight of valid signatures for a
transaction is equal to or greater than the threshold value, then the Sui network considers the
transdaction valid.
```typescript
const kp1 = new Ed25519Keypair();
const kp2 = new Ed25519Keypair();
const kp3 = new Ed25519Keypair();
const multiSigPublicKey = MultiSigPublicKey.fromPublicKeys({
threshold: 2,
publicKeys: [
{
publicKey: kp1.getPublicKey(),
weight: 1,
},
{
publicKey: kp2.getPublicKey(),
weight: 1,
},
{
publicKey: kp3.getPublicKey(),
weight: 2,
},
],
});
const multisigAddress = multiSigPublicKey.toSuiAddress();
```
The `multiSigPublicKey` in the preceding code enables you to verify that signatures have a combined
weight of at least `2`. A signature signed with only `kp1` or `kp2` is not valid, but a signature
signed with both `kp1` and `kp2`, or just `kp3` is valid.
## Combining signatures with a MultiSigPublicKey
To sign a message or transaction for a MultiSig address, you must collect signatures from the
individual key pairs, and then combine them into a signature using the `MultiSigPublicKey` class for
the address.
```typescript
// This example uses the same imports, key pairs, and multiSigPublicKey from the previous example
const message = new TextEncoder().encode('hello world');
const signature1 = (await kp1.signPersonalMessage(message)).signature;
const signature2 = (await kp2.signPersonalMessage(message)).signature;
const combinedSignature = multiSigPublicKey.combinePartialSignatures([signature1, signature2]);
const isValid = await multiSigPublicKey.verifyPersonalMessage(message, combinedSignature);
```
## Creating a MultiSigSigner
The `MultiSigSigner` class allows you to create a Signer that can be used to sign personal messages
and Transactions like any other keypair or signer class. This is often easier than manually
combining signatures, since many methods accept Signers and handle signing directly.
A `MultiSigSigner` is created by providing the underlying Signers to the `getSigner` method on the
`MultiSigPublicKey`. You can provide a subset of the Signers that make up the public key, so long as
their combined weight is equal to or greater than the threshold.
```typescript
const kp1 = new Ed25519Keypair();
const kp2 = new Ed25519Keypair();
const multiSigPublicKey = MultiSigPublicKey.fromPublicKeys({
threshold: 1,
publicKeys: [
{
publicKey: kp1.getPublicKey(),
weight: 1,
},
{
publicKey: kp2.getPublicKey(),
weight: 1,
},
],
});
const signer = multiSigPublicKey.getSigner(kp1);
const message = new TextEncoder().encode('hello world');
const { signature } = await signer.signPersonalMessage(message);
const isValid = await multiSigPublicKey.verifyPersonalMessage(message, signature);
```
## Multisig with zkLogin
You can use zkLogin to participate in multisig just like keys for other signature schemes. Unlike
other keys that come with a public key, you define a public identifier for zkLogin.
For example, the following example creates a 1-out-of-2 multisig with a single key and a zkLogin
public identifier:
```typescript
// a single Ed25519 keypair and its public key.
const kp1 = new Ed25519Keypair();
const pkSingle = kp1.getPublicKey();
// compute the address seed based on user salt and jwt token values.
const decodedJWT = decodeJwt('a valid jwt token here');
const userSalt = BigInt('123'); // a valid user salt
const addressSeed = genAddressSeed(userSalt, 'sub', decodedJwt.sub, decodedJwt.aud).toString();
// a zkLogin public identifier derived from an address seed and an iss string.
let pkZklogin = toZkLoginPublicIdentifier(addressSeed, decodedJwt.iss);
// derive multisig address from multisig public key defined by the single key and zkLogin public
// identifier with weight and threshold.
const multiSigPublicKey = MultiSigPublicKey.fromPublicKeys({
threshold: 1,
publicKeys: [
{ publicKey: pkSingle, weight: 1 },
{ publicKey: pkZklogin, weight: 1 },
],
});
// this is the sender of any transactions from this multisig account.
const multisigAddress = multiSigPublicKey.toSuiAddress();
// create a regular zklogin signature from the zkproof and ephemeral signature for zkLogin.
// see zklogin-integration.mdx for more details.
const zkLoginSig = getZkLoginSignature({
inputs: zkLoginInputs,
maxEpoch: '2',
userSignature: fromBase64(ephemeralSig),
});
// a valid multisig with just the zklogin signature.
const multisig = multiSigPublicKey.combinePartialSignatures([zkLoginSig]);
```
### Benefits and Design for zkLogin in Multisig
Because zkLogin assumes the application client ID and its issuer (such as Google) liveliness, using
zkLogin with multisig provides improved recoverability to a zkLogin account. In the previous example
of 1-out-of-2 multisig, users can use zkLogin in their regular wallet flow, but if the application
or the issuer is deprecated, the user can still use the regular private key account to access funds
in the multisig wallet.
This also opens the door to design multisig across any number of zkLogin accounts and of different
providers (max number is capped at 10 accounts) with customizable weights and thresholds. For
example, you can set up a multisig address with threshold of 2, where the public keys or identifiers
are defined as:
1. Charlie's own Google account with weight 2
2. Charlie's friend Alice's Apple account with weight 1
3. Charlie's friend Bob's Facebook account with weight 1
In this case, Charlie can always use their Google account for transactions out of the multisig
address for the threshold. At the same time, Charlie still has access to his account by combining
partial signatures from Alice and Bob.
## Multisig with Passkey
You can use a `PasskeyKeypair` (a `Keypair`) and `PasskeyPublicKey` (a `PublicKey`) as components in
a Multisig setup. Once initialized, they support both Multisig address derivation and transaction
signing.
```typescript
const passkeyKeypair = await PasskeyKeypair.getPasskeyInstance(
new BrowserPasskeyProvider('Sui Passkey Example', {
rpName: 'Sui Passkey Example',
rpId: window.location.hostname,
} as BrowserPasswordProviderOptions),
);
const passkeyPublicKey = passkeyKeypair.getPublicKey();
const multiSigPublicKey = MultiSigPublicKey.fromPublicKeys({
threshold: 1,
publicKeys: [
{ publicKey: passkeyPublicKey, weight: 1 },
// other keys
],
});
```