TypeScript SDK
Cryptography
Multi-Signature Transactions

Multi-Signature Transactions

The Sui TypeScript SDK provides a MultiSigPublicKey class to support Multi-Signature (opens in a new tab) (MultiSig) transaction and personal message signing.

This class implements the same interface as the PublicKey classes that Keypairs uses and you call the same methods to verify signatures for PersonalMessages and TransactionBlocks.

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.

import { Ed25519Keypair } from '@mysten/sui.js/keypairs/ed25519';
import { MultiSigPublicKey } from '@mysten/sui.js/multisig';
 
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.

// 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);

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:

// 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: fromB64(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.