How to Verify if an Address is Multi-Sig Inside Sui Smart Contracts

Enable secure accounts accessible by multiple people through a multi-signature contract.

How to Verify if an Address is Multi-Sig Inside Sui Smart Contracts

Multi-signature (multi-sig) wallets and accounts allow for enhanced key management by enabling either multiple parties to access shared assets under predefined conditions, or a single user to implement additional security measures. For example, a multi-sig wallet could be used to manage a decentralized autonomous organization’s (DAO) treasury, requiring consent from a certain percentage of members before executing transactions, or it could serve an individual seeking extra protection by distributing access across multiple devices or locations.

Sui native multi-sig wallets offer a plethora of applications. They can be used to create interactive game elements or commerce platforms that necessitate collective user actions for access. Establishing a user quorum or other conditions for access ensures that digital assets remain protected against unauthorized use by any individual key/member.

The Sui Move multi-sig smart contract detailed in this article confirms whether a Sui address is multi-sig and accommodates various key combinations, such as 2-of-3 or any M-of-N. Integrating multisig functionality directly into smart contracts, rather than through SDKs, provides distinct benefits. This method grants developers precise control over access and authorization within the contract’s logic, allowing them to stipulate specific conditions—like signatures from a designated subset of addresses—before permitting function execution. Such detailed control bolsters security by guarding against unsanctioned changes to the contract or asset transfers.

Incorporating multi-sig directly into smart contracts is pivotal for creating secure and resilient applications with adaptable governance models. This fosters a foundation of trust and cooperation within decentralized networks.

On-chain multi-sig is crucial because it offers transparency and verifiability in decentralized operations. It ensures that actions, such as executing smart contract functions, are only performed when they meet the agreed-upon criteria among stakeholders. For example, knowing if the caller of a smart contract function is a multi-sig address allows for the implementation of nuanced constraints. If a transaction is signed by 3 out of 5 members, up to 1,000 coins could be moved. Conversely, if only 1 out of 5 members signs, the transaction limit could be set to 100 coins. This flexibility in setting transaction thresholds based on the level of consensus provides a balance between security and functionality, making on-chain multisig an indispensable feature for collective asset management and decision-making in the blockchain space.

Creating and using the multi-sig checker contract in Move

A multi-sig address is a special type of address that requires multiple signatures to authorize a transaction. A multi-sig checker smart contract derives a multi-sig address from a set of public keys, weights, and a threshold, and compares it with an expected address. 

Multi-sig addresses require multiple signatures to authorize a transaction. They are often used to enhance the security of funds by distributing control among multiple parties. For example, a 2-of-3 multi-sig address requires at least two out of three signers to approve a transaction. Multi-sig addresses can also be used for governance, escrow, or backup purposes.

The multi-sig smart contract performs three functions:

  1. It derives multi-sig addresses
  2. It verifies multi-sig addresses
  3. It can check if a sender is a multi-sig address

Derive multi-sig addresses

The multisig module defines the derive_multisig_address_quiet which takes three parameters: pks, weights, and threshold

The pks parameter is a vector of vectors of bytes, representing the public keys of the signers.

The weights parameter is a vector of bytes, representing the weights of each signer.

The threshold parameter is a 16-bit unsigned integer, representing the minimum sum of weights required to execute a transaction from the multi-signature address.

The function returns an address, which is the derived multi-signature address.

public fun derive_multisig_address_quiet(
        pks: vector<vector<u8>>,
        weights: vector<u8>,
        threshold: u16,
    ): address {

 The function performs the following steps:

It defines a variable, multiSigFlag, of type 8-bit unsigned integer and assigns it the value 0x03, which is the flag for multi-signature addresses.

let multiSigFlag = 0x03;

It creates an empty vector of bytes called hash_data, which will store the data to be hashed.

let hash_data = vector<u8>[];

It gets the lengths of the pks and weights vectors and checks that they are equal. If not, it aborts the execution with an error code: ELengthsOfPksAndWeightsAreNotEqual.

  let pks_len = pgs.length();
  let weights_len = weights.length();
  assert!(pks_len == weights_len, ELengthsOfPksAndWeightsAreNotEqual);

It initializes a variable, sum, of type 16-bit unsigned integer and assigns it the value 0. It then loops through the weights vector and adds the values of each element to the sum. It then checks that the threshold is positive and not greater than the sum. If not, it aborts the execution with an error code: EThresholdIsPositiveAndNotGreaterThanTheSumOfWeights.

       let mut sum = 0;
        let mut i = 0;
        while (i < weights_len) {
            let w = weights[i] as u16;
            sum = sum + w;
            i = i + 1;
        };
        assert!(threshold > 0 && threshold <= sum, EThresholdIsPositiveAndNotGreaterThanTheSumOfWeights);

 It pushes the multiSigFlag to the hash_data vector. It then serializes the threshold using the bcs::to_bytes function and appends the result to the hash_data vector.

       hash_data.push_back(multiSigFlag);
       let threshold_bytes: vector<u8> = bcs::to_bytes(&threshold);
       hash_data.append(threshold_bytes);

It loops through the pks and weights vectors and appends the elements of each pair to the hash_data vector.

        let mut i = 0;
        while (i < pks_len) {
          hash_data.append(pks[i]);
          hash_data.push_back(weights[i]);
          i = i + 1;
        };

It hashes the hash_data vector using the blake2b256 function and converts the result to an address using the address::from_bytes function. It then assigns the address to a variable, ms_address, and returns it.

        let ms_address = address::from_bytes(blake2b256(&hash_data));
        ms_address
    }

It derives a multi-sig address and returns the multi-sig address.

Verifying multi-sig addresses

The multisig module also defines the check_multisig_address_eq, which checks if the created multi-sig address matches the expected address. As we mentioned above, a multi-sig address is a special type of address that requires multiple signatures to authorize a transaction. A multi-sig address is defined by a set of public keys, weights, and a threshold.

The function check_multisig_address_eq takes four parameters: pks, weights, threshold, and expected_address. The first three parameters are the same as the ones we used in the previous function, derive_multisig_address_quiet. The last parameter, expected_address, is an address value that we want to compare with the multi-sig address.

   public entry fun check_multisig_address_eq(
        pks: vector<vector<u8>>,
        weights: vector<u8>,
        threshold: u16,
        expected_address: address,
    ): bool {

The function first calls the function, derive_multisig_address_quiet, which creates a multi-sig address from the given public keys, weights, and threshold. This function uses a hash-based algorithm to combine the public keys and the threshold into a 16-byte value, which is then converted into an address.

let ms_address = derive_multisig_address_quiet(pks, weights, threshold);

 The function then compares the multi-sig address with the expected address and returns true if the addresses are equal, and false otherwise.

return (ms_address == expected_address)

The function check_multisig_address_eq can be used to verify that a multi-sig address is correct and matches the expected value. This can be useful for testing, debugging, or auditing purposes. For example, one could use this function to check that a multi-sig address is consistent with the public keys and the threshold that were agreed upon by the signers.

Checking the sender’s multi-sig address

Finally, the multisig module also defines the check_if_sender_is_multisig_address, which checks if the sender is the same multi-sig address that is derived from the provided pks, weights, and threshold.

The check_if_sender_is_multisig_address takes four parameters: pks, weights, threshold, and ctx. The first three parameters define the multi-sig address scheme, while the last parameter provides the transaction context.

The pks parameter is a vector of vectors of bytes, representing the public keys of the signers.

The weights parameter is a vector of bytes, representing the weights of each signer.

The threshold parameter is a 16-bit unsigned integer, representing the minimum sum of weights required to execute a transaction from the multi-sig address.

Finally, the ctx is a mutable reference to the TxContext, which contains information about the current transaction, such as the sender.

   public fun check_if_sender_is_multisig_address(
        pks: vector<vector<u8>>,
        weights: vector<u8>,
        threshold: u16,
        ctx: &mut TxContext
    ): bool {

The check_if_sender_is_multisig_address function calls the check_multisig_address_eq function, which compares the multi-sig address with the sender address.

       check_multisig_address_eq(pks, weights, threshold, ctx.sender())        
    }

The function check_multisig_address_eq returns true if the sender address matches the multi-sig address scheme, and false otherwise.

Get started with multi-sig

Multi-sig addresses are useful for scenarios where there is a need for enhanced security, accountability, or collaboration among multiple parties. Given the valuable digital assets stored on Sui, a multi-sig address can help keep those assets secure.

The smart contract described in this article can help you get started building applications designed for collaboration and joint custody of assets. As a further resource, you can look at the source code and documentation for this project on GitHub.