Enhancing Blockchain Security with Multi-Signature Wallets

Desig Labs multi-signature technology makes it possible to create blockchain wallets requiring any number of keys to open.

Enhancing Blockchain Security with Multi-Signature Wallets

Multi-signature wallets on Sui offer a unique means for shared asset control, as they require two or more cryptographic keys to open. The traditional use case example goes back to security deposit boxes, where a customer and a bank each hold a physical key, both of which are needed to unlock the box.

At Desig Labs, we've developed both a multi-signature SDK for builders who want to integrate this technology, and a multi-signature, multi-chain wallet to manage shared accounts for individuals and organizations.

Although similar to a security deposit box, this type of wallet presents a wider array of use cases. Users can configure it to require any number of cryptographic keys, or signatures as we refer to them, to open. Even more useful, this multi-signature wallet can have a greater number of authorized signatures than are needed to open it. Imagine, for example, a committee of 40 people, each of whom has an authorized key, yet only 10 keys are needed to access digital assets in the wallet.

On Sui, this wallet can hold any variety of digital assets. Consider an escrow example. One party may agree to buy a physical item from another. The buyer can put the payment in a multi-signature wallet where both parties have a key. Once the buyer receives the item, they can unlock the wallet with their key, and then the seller can unlock it and withdraw the funds.

This example prevents fraud because, even if the seller didn't ship the item, they can't withdraw the funds until the buyer uses their key. If there is some disagreement about the purchase, the funds remain inaccessible until the parties resolve it.

an image showing three keys with two of them highlighted for opening a lock
Multi-signature wallets require two or more cryptographic keys to open, and there may be more keys than the quantity required to unlock the wallet.

In addition to shared account use cases, multi-signature wallets offer greater transparency. All transaction policies, signers, and actual transactions are publicly available on the blockchain, providing complete visibility and accountability. This transparency makes tracking and auditing transactions easier, which is essential for organizations and institutions that must adhere to regulatory requirements.

Multi-Signature Advances

While traditional multi-signature wallets have been around for a while, researchers have developed newer and more sophisticated solutions to meet the growing demand for advanced security features. As described above, the earliest examples of digital multi-signature wallets require multiple keys to authorize transactions, with many different configurations possible.

Account Abstraction

The new ERC-4337 standard lets smart contracts hold funds in a multi-signature wallet without requiring a smart contract to be involved in the transaction. Contributors to the Ethereum blockchain developed this technology to reduce the friction of creating a traditional digital wallet and enabling the types of transactions common with modern banking apps. Users don't need seed phrases, and can set up automatic and repeating payments.

This technology provides greater flexibility and control over transactions, making it a popular choice for those who want to maintain a high level of security while still enjoying the benefits of smart contracts.

Threshold Signature Scheme and Multi-Party Computation

For our multi-signature wallet, we use a set of technologies called threshold signature scheme and multi-party computation (TSS-MPC). Although these technologies are not new, they present an excellent use case for a multi-signature wallet.

Instead of creating multiple cryptographic keys, TSS breaks a private key into pieces and distributes them among multiple parties. When each piece is submitted, they create one master key. On-chain, this key works similar to a single-key wallet signature.

MPC complements TSS as a secure method to store its key shares. MPC is a network of nodes used to compute a function without revealing each party’s private data. This combination ensures that no single party can access the entire private key, making it more difficult for attackers to compromise the system.

Applying TSS on SUI

Sui supports k out of n multi-signature transactions where k is the threshold and n is the total weight of all participating parties. The maximum number of parties is required to be <= 10.

Example Workflow

The following example demonstrates generating keys for use in a multi-signature transaction in Sui's command line interface.

Step 1: Add keys to Sui keystore

The following command generates a Sui address and key for each supported key scheme and adds it to the sui.keystore, then lists the keys.

$SUI_BINARY client new-address ed25519
$SUI_BINARY client new-address secp256k1
$SUI_BINARY client new-address secp256r1

$SUI_BINARY keytool list

The response resembles the following, but displays actual addresses and keys:

Sui Address | Public Key (Base64) | Scheme
-----------------------------------------------------------------------
$ADDR_1     | $PK_1               | secp256r1
$ADDR_2     | $PK_2               | secp256k1
$ADDR_3     | $PK_3               | ed25519

Step 2: Create a multi-signature address

Inputting a list of public keys and their corresponding weights, as shown below, creates a multi-signature address.

$SUI_BINARY keytool multi-sig-address --pks $PK_1 $PK_2 $PK_3 --weights 1 2 3 --threshold 3

MultiSig address: $MULTISIG_ADDR

The response resembles the following:

Participating parties:
Sui Address | Public Key (Base64)| Weight
------------------------------------------
$ADDR_1    | $PK_1              |   1
$ADDR_2    | $PK_2              |   2
$ADDR_3    | $PK_3              |   3

Step 3: Send objects to a multi-signature address

This code snippet below requests gas from a local network using the default URL, following the guidance in the Sui documentation.

curl --location --request POST '<http://127.0.0.1:9123/gas>' --header 'Content-Type: application/json' --data-raw "{ \\"FixedAmountRequest\\": { \\"recipient\\": \\"$MULTISIG_ADDR\\" } }"

The response resembles the following:

{"transferred_gas_objects":[{"amount":200000,"id":"$OBJECT_ID", ...}]}

Step 4: Serialize a transaction

This step demonstrates how to use an object that belongs to a multi-signature address and serialize a transfer to be signed. Note that $TX_BYTES can be any serialized transaction data where the sender is the multi-signature address. Simply use the --serialize-output flag to output Base64 encoded transaction bytes.

$SUI_BINARY client transfer --to $MULTISIG_ADDR --object-id $OBJECT_ID --gas-budget 1000 --serialize-output

Raw tx_bytes to execute: $TX_BYTES

Step 5: Sign the transaction with two keys

Use the following code sample to sign the transaction with two keys in sui.keystore. Other tools can be used to sign the transaction as long as it is serialized with flag || sig || pk.

$SUI_BINARY keytool sign --address $ADDR_1 --data $TX_BYTES

Raw tx_bytes to execute: $TX_BYTES
Serialized signature (`flag || sig || pk` in Base64): $SIG_1

$SUI_BINARY keytool sign --address $ADDR_2 --data $TX_BYTES

Raw tx_bytes to execute: $TX_BYTES
Serialized signature (`flag || sig || pk` in Base64): $SIG_2

Step 6: Combine individual signatures into a multi-signature address

The following sample demonstrates how to combine the two signatures.

$SUI_BINARY keytool multi-sig-combine-partial-sig --pks $PK_1 $PK_2 $PK_3 --weights 1 2 3 --threshold 3 --sigs $SIG_1 $SIG_2

MultiSig address: $MULTISIG_ADDRESS # Informational
MultiSig parsed: $HUMAN_READABLE_STRUCT # Informational
MultiSig serialized: $SERIALIZED_MULTISIG

Step 7: Execute a transaction with a multi-signature address

This sample uses a multi-signature address to execute a transaction.

$SUI_BINARY client execute-signed-tx --tx-bytes $TX_BYTES --signatures $SERIALIZED_MULTISIG

MPC-based Multi-Signature on SUI

The MPC method stores the various key shares generated in the section above. In our MPC implementation, we rely on an algorithm called Shamir's Secret Sharing (SSS), which enables sharing of a secret among a group of people, with the restriction that the secret can only be recovered when a threshold number of people in the group combine their knowledge.

SSS allows the entire secret, a key in our case, to be mathematically derived from a specific quantity of pieces of that secret. A key broken into 20 pieces, for example, might require at least 10 of those pieces to derive the entire key. If a hacker managed to steal even five pieces of that key, they would not be able to derive the entire key.

This capability works very well in a distributed system, such as a blockchain. The environment itself enforces data integrity, and is built on a principle of shared resources.

Building SSS into TSS-MPC results in a number of advantages. For one, there is no central password that might be hacked or lost. As parts of the key are spread among multiple people, a significant amount of those people would need to be hacked to gain the whole key. Likewise, if a few people in this group lost their parts of the key, the entire key can still be derived if enough parts remain. From a community perspective, this technology requires agreement among a quorum to use the key, preventing any one person from or less than the threshold amount from gaining the key.

The complexity of TSS-MPC means implementation requires some expertise, and it uses more computational resources than simpler schemes. This latter point, when combined with network issues, may result in lag for users when initially generating keys. Coordinating key sharing among a large group presents issues as well, generally requiring good, clear communication.

Build TSS-MPC Multi-Signature on SUI

Through our TSS-MPC implementation and the code samples below, we hope to make multi-signature technology easy for builders to add to their projects. To get started, you will need to install @desig/web3 using the Yarn package manager. In the command line interface, type yarn add @desig/web3.

Example 1:

The code sample below shows how to quickly create a multi-signature, a proposal for an unlocking action, and the resultant transaction that would be unlocked if enough key holders agree.

import { encode } from 'bs58'
import { utils } from '@noble/ed25519'
import { DesigKeypair, Multisig, Signer, Proposal, Transaction } from '@desig/web3'

const cluster = '<https://mainnet.desig.io>'
const privkey = encode(utils.randomPrivateKey())
const secretShare = 'ed25519/<master>/<share>'
const keypair = DesigKeypair.fromSecret(secret)

// Create multisig instance
const dMultisig = new Multisig(cluster, privkey)
await dMultisig.getMultisigs()
await dMultisig.getMultisig('multisigId')

// Create signer instance
const dSigner = new Signer(cluster, privkey)
await dSigner.getAllSigners()
await dSigner.getSigner('signerId')

// Create proposal instance
const dProposal = new Proposal(cluster, privkey, keypair)
await dProposal.approveProposal('proposalId')
await dProposal.initializeProposal({ raw: ..., msg: ..., chainId: ... })

// Create transaction instance
const dTransaction = new Transaction(cluster, privkey, keypair)
await dTransaction.initializeTransaction({ type: ..., params: { ... }})

await dTransaction.getTransaction('transactionId')
await dTransaction.signTransaction('transactionId')

Example 2:

The sample code below describes a complete process using multi-signature to approve a SUI transfer.

Step1: Create a multi-signature

import { encode } from 'bs58'
import { utils } from '@noble/ed25519'
import { Multisig } from '@desig/web3'
import { EdCurve } from '@desig/core'
import { Curve } from '@desig/supported-chains'

const privkey = encode(utils.randomPrivateKey())
const pubkey = encode(EdCurve.getPublicKey(decode(privkey)))

const dMultisig = new Multisig('<https://mainnet.desig.io>', privkey)
const t = 2 // threshold
const n = 2 // total weights
const pubkeys = [pubkey, pubkey] // You can change it to other members' pubkey
const curve = Curve.ed25519 // Refer <https://chainlist.desig.io/> to find the corresponding chain
const multisig = await dMultisig.initializeMultisig(curve, {
	t,
	n,
	pubkeys,
})

Step 2: Create a proposal to transfer SUI

import { decode } from 'bs58'
import { DesigKeypair, Proposal } from '@desig/web3'
import { toSuiAddress, SuiDevnet } from '@desig/supported-chains'
import { blake2b } from '@noble/hashes/blake2b'
import {
	Connection,
	JsonRpcProvider,
	messageWithIntent,
	IntentScope,
	Ed25519PublicKey,
	toSerializedSignature,
} from '@mysten/sui.js'
import { transfer, sendAndConfirm } from '<appendix_transfer_sui'

const connection = new Connection({ fullnode: '<sui_fullnode>' })
const provider = new JsonRpcProvider(connection)

// Create alice keypair and bob keypair from secrets sent to the emails
const aliceKeypair = new DesigKeypair('<alice_secret_share>')
const bobKeypair = new DesigKeypair('<bob_secret_share>')

// aliceKeypair.masterkey === bobKeypair.masterkey is true
const masterkey = toSuiAddress(aliceKeypair.masterkey)

/**
	* Alice initializes a transaction
	*/
const aliceProposal = new Proposal(
	'<https://mainnet.desig.io>',
	alicePrivkey,
	aliceKeypair,
)
const bobProposal = new Proposal(
	'<https://mainnet.desig.io>',
	bobPrivkey,
	bobKeypair,
)

const tx = await transfer(masterkey, 5000)
const txSerialize = await tx.build({ provider })
const msg = messageWithIntent(IntentScope.TransactionData, txSerialize)
const digest = blake2b(msg, { dkLen: 32 })

const { id: proposalId } = await 
aliceProposal.initializeProposal({
	raw: txSerialize,
	msg: digest,
	chainId: new SuiDevnet().chainId,
})

/**
	* Alice approves the transaction
	*/
await aliceProposal.approveProposal(proposalId)
/**
	* Bob approves the transaction
	*/
await bobProposal.approveProposal(proposalId)

/**
	* Bob finalizes the transaction
*/
const { sig } = await bobProposal.finalizeSignature(proposalId)
const { raw } = await bobProposal.getProposal(proposalId)

const rawTx = decode(raw)
const serializedSig = toSerializedSignature({
	pubKey: new Ed25519PublicKey(masterkey),
	signature: sig,
	signatureScheme: 'ED25519',
})
/**
	* Bob submits the transaction
*/
const txHash = await sendAndConfirm(serializedSig, rawTx)

Step 3: Transfer the SUI amount

import { TransactionBlock, Connection, JsonRpcProvider } from '@mysten/sui.js'

const connection = new Connection({ fullnode: '<sui_fullnode>' })
const provider = new JsonRpcProvider(connection)

// Init transaction transfer
export const transfer = async (payer: string, amount: number) => 
{
	const tx = new TransactionBlock()
	const [coin] = tx.splitCoins(tx.gas, [tx.pure(amount.toString())])
	tx.transferObjects([coin], tx.pure(payer))
	tx.setSender(payer)
	return tx
}

export const sendAndConfirm = async (
	signature: string,
	txBlock: Uint8Array,
) => {
	const { digest } = await provider.executeTransactionBlock({
		signature,
		transactionBlock: txBlock,
	})
	return digest
}

Shared Security

The multi-signature technology we created at Desig Labs offers security and an important solution for shared assets, whether temporary, as with escrow, or permanent, as with a group's treasury. Sui's decentralization makes it a complementary environment for the kind of shared control multi-signature wallets offer.

Please explore our multi-signature documentation and use the code samples above to integrate it into your own projects. If you have questions, please contact us.

We're excited to see the many uses projects will find for our multi-signature security.