How to Create a Multi-Signature Wallet on Solana using Rust

Posted By : Aditya

Oct 30, 2024

What is a Multi-Signature Wallet?

 

Multi-signature (multi-sig) wallets play a crucial role in enhancing the security and reliability of cryptocurrency transactions. Unlike standard wallets, which rely on a single private key for control, multi-sig wallets require approvals from multiple private keys before a transaction can be authorized. This shared-approval mechanism reduces the risk of a single point of vulnerability, making multi-sig wallets especially valuable for teams, DAOs, and organizations that manage funds collectively. By spreading responsibility across multiple key holders, these wallets ensure that no single user has unchecked control over the funds, increasing security and accountability. Explore more about crypto wallets with our crypto wallet development services.

 

In a multi-sig wallet, a configuration is set to require a specific number of approvals (M) out of a total number of keys (N) to authorize a transaction. For instance, a 2-of-3 multi-sig setup means that any two of the three signatories must approve a transaction before it can be completed. This structure enables a system of mutual oversight, where each participant plays a role in safeguarding assets, greatly reducing the likelihood of misuse or unauthorized access.

 

Additionally, multi-sig wallets support more transparent, collaborative governance structures, which align well with the decentralized ethos of blockchain technology. By requiring multiple approvals, these wallets allow for shared decision-making and control, empowering groups to protect assets in a secure, decentralized manner. 

 

In this developer's guide, we will explore the steps to create a multi-signature wallet on Solana. 

 

Prerequisite Technologies

 

Before proceeding with the implementation, make sure to have the following tools and technologies ready:

 

Rust: The main programming language used for development on Solana.

Solana CLI: Tools that allow command-line interaction with the Solana blockchain.

Rust libraries: A good understanding of Rust libraries that assist with cryptographic operations and account management.

 

You may also like | Develop a Multi-Token Crypto Wallet for Ethereum with Web3.js

 

Code Implementation | Creating a Multi-Signature Wallet on Solana 

 

Below are the essential components of the multi-sig wallet implementation. After initializing an empty Rust Project, create the following files in your project directory.

 

# Inside the 'src' Folder

 

-> processor.rs: This file contains the core logic of your multi-sig wallet, handling transactions and validating signatures.

 


// processor.rs
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
msg,
program_error::ProgramError,
pubkey::Pubkey,
};
use crate::{instruction::MultiSigInstruction, state::MultiSig, error::MultiSigError};
use borsh::{BorshDeserialize, BorshSerialize};
pub struct Processor;

impl Processor {
pub fn process(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8]
) -> ProgramResult {
let instruction = MultiSigInstruction::unpack(instruction_data)?;

    match instruction {
        MultiSigInstruction::Initialize { owners, threshold } => {
            Self::process_initialize(accounts, owners, threshold, program_id)
        },
        MultiSigInstruction::SubmitTransaction { transaction_id } => {
            Self::process_submit_transaction(accounts, transaction_id, program_id)
        },
        MultiSigInstruction::Approve { transaction_id } => {
            Self::process_approve(accounts, transaction_id, program_id)
        },
        MultiSigInstruction::Execute { transaction_id } => {
            Self::process_execute(accounts, transaction_id, program_id)
        },
    }
}

fn process_initialize(
    accounts: &[AccountInfo],
    owners: Vec,
    threshold: u8,
    program_id: &Pubkey,
) -> ProgramResult {
    let account_info_iter = &mut accounts.iter();
    let multisig_account = next_account_info(account_info_iter)?;

    if owners.len() < threshold as usize {
        msg!('Insufficient number of owners for the threshold.');
        return Err(ProgramError::InvalidInstructionData);
    }

    let multisig_data = MultiSig {
        owners,
        threshold,
        approvals: 0,
        executed: false,
    };
    multisig_data.serialize(&mut &mut multisig_account.data.borrow_mut()[..])?;
    Ok(())
}

fn process_submit_transaction(
    accounts: &[AccountInfo],
    transaction_id: u64,
    program_id: &Pubkey,
) -> ProgramResult {
    let account_info_iter = &mut accounts.iter();
    let multisig_account = next_account_info(account_info_iter)?;

    let mut multisig_data = MultiSig::try_from_slice(&multisig_account.data.borrow())?;

    if multisig_data.executed {
        msg!('Transaction already executed.');
        return Err(MultiSigError::AlreadyExecuted.into());
    }
    multisig_data.approvals = 0;
    multisig_data.executed = false;
    multisig_data.serialize(&mut &mut multisig_account.data.borrow_mut()[..])?;
    Ok(())
}

fn process_approve(
    accounts: &[AccountInfo],
    transaction_id: u64,
    program_id: &Pubkey,
) -> ProgramResult {
    let account_info_iter = &mut accounts.iter();
    let multisig_account = next_account_info(account_info_iter)?;
    let signer_account = next_account_info(account_info_iter)?;
    let mut multisig_data = MultiSig::try_from_slice(&multisig_account.data.borrow())?;

    if !multisig_data.owners.contains(signer_account.key) {
        msg!('Signer is not an owner.');
        return Err(MultiSigError::NotOwner.into());
    }

    multisig_data.approvals += 1;
    multisig_data.serialize(&mut &mut multisig_account.data.borrow_mut()[..])?;
    Ok(())
}
fn process_execute(
    accounts: &[AccountInfo],
    transaction_id: u64,
    program_id: &Pubkey,
) -> ProgramResult {
    let account_info_iter = &mut accounts.iter();
    let multisig_account = next_account_info(account_info_iter)?;
    let mut multisig_data = MultiSig::try_from_slice(&multisig_account.data.borrow())?;
    if multisig_data.approvals < multisig_data.threshold {
        msg!('Not enough approvals to execute transaction.');
        return Err(MultiSigError::InsufficientSigners.into());
    }

    multisig_data.executed = true;
    multisig_data.serialize(&mut &mut multisig_account.data.borrow_mut()[..])?;
    Ok(())
}
}

 

Also, Check | Developing Cross-Platform Crypto Wallet with Web3.js & React

 

-> instruction.rs : This file defines the instructions that can be executed by the multi-sig wallet, including methods for adding signatories, removing them, and executing transactions.

 


// instruction.rs
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::program_error::ProgramError;
use solana_program::pubkey::Pubkey;

[derive(BorshSerialize, BorshDeserialize, Debug)]
pub enum MultiSigInstruction {
Initialize { owners: Vec, threshold: u8 },
SubmitTransaction { transaction_id: u64 },
Approve { transaction_id: u64 },
Execute { transaction_id: u64 },
}
impl MultiSigInstruction {
pub fn unpack(input: &[u8]) -> Result {
let (tag, rest) = input.split_first().ok_or(ProgramError::InvalidInstructionData)?;
match tag {
0 => {
let owners = Vec::::deserialize(&mut &rest[..])?;
let threshold = *rest.get(owners.len() * 32).ok_or(ProgramError::InvalidInstructionData)?;
Ok(Self::Initialize { owners, threshold })
},
1 => {
let transaction_id = u64::from_le_bytes(
rest.get(..8).ok_or(ProgramError::InvalidInstructionData)?.try_into().unwrap(),
);
Ok(Self::SubmitTransaction { transaction_id })
},
2 => {
let transaction_id = u64::from_le_bytes(
rest.get(..8).ok_or(ProgramError::InvalidInstructionData)?.try_into().unwrap(),
);
Ok(Self::Approve { transaction_id })
},
3 => {
let transaction_id = u64::from_le_bytes(
rest.get(..8).ok_or(ProgramError::InvalidInstructionData)?.try_into().unwrap(),
);
Ok(Self::Execute { transaction_id })
},
_ => Err(ProgramError::InvalidInstructionData),
}
}
}

 

-> lib.rs: This file sets up the entry point for your program, initializing necessary components.

 


// lib.rs
use solana_program::{
account_info::AccountInfo,
entrypoint,
entrypoint::ProgramResult,
pubkey::Pubkey,
};

pub mod instruction;
pub mod processor;
pub mod state;
pub mod error;
pub mod utils;

entrypoint!(process_instruction);

fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
processor::Processor::process(program_id, accounts, instruction_data)
}

 

Also, Read | How to Build a Multi-Chain Account Abstraction Wallet

 

#Inside the utils Folder

 

-> utils.rs: Utility functions that assist in various operations, such as validating signatures or formatting transactions.

 


// utils.rs
use solana_program::{
account_info::AccountInfo,
pubkey::Pubkey,
};

pub fn is_signer(account_info: &AccountInfo, pubkey: &Pubkey) -> bool {
account_info.is_signer && account_info.key == pubkey
}

 

-> error.rs: Defines custom error types that can be returned by your program, improving debugging and error handling.

 


use thiserror::Error;
use solana_program::program_error::ProgramError;

[derive(Error, Debug, Copy, Clone)]

pub enum MultiSigError {
#[error('Insufficient signers')]
InsufficientSigners,
#[error('Transaction already executed')]
AlreadyExecuted,
#[error('Owner not recognized')]
NotOwner,
}

impl From for ProgramError {
fn from(e: MultiSigError) -> Self {
ProgramError::Custom(e as u32)
}
}

 

-> state.rs: This file manages the state of the wallet, including sign and pending transactions.

 


// state.rs
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::pubkey::Pubkey;

[derive(BorshSerialize, BorshDeserialize, Debug)]

pub struct MultiSig {
pub owners: Vec,
pub threshold: u8,
pub approvals: u8,
pub executed: bool,
}

}

 

-> Cargo.toml : This is the main configuration file for any rust project, that defines all the external dependencies to be used in a versioned manner. 

 


[package]
name = 'multi_sig'


version = '0.1.0'
edition = '2021'


[dependencies]
bincode = '1.3.3'
borsh = '1.5.1'
log = '0.4.22'
serde = '1.0.213'
solana-program = '2.0.14'
thiserror = '1.0.65'


[lib]
crate-type = ['cdylib', 'lib']

 

Also, Check | How to Build a Cryptocurrency Wallet App Like Exodus

 

Conclusion

 

In this quick developers' guide, we discovered how to create and set up a multi-signature wallet on Solana using Rust. Doing so is both a technical accomplishment and a strategic initiative aimed at improving security and trust within decentralized finance. By necessitating multiple approvals for every transaction, multi-sig wallets address the risks posed by single-key control, thereby reducing the threats related to potential fraud, theft, or improper handling of funds. This system of approvals is especially beneficial for organizations, DAOs, and collaborative projects that require high standards of accountability and shared control. If you are looking to create a multi-signature wallet on Solana or any other blockchains, connect with our Solana developers to get started. 

As an increasing number of organizations and institutions embrace blockchain technology for transparent and secure asset management, multi-sig wallets are expected to become essential. They not only safeguard digital assets but also ensure that all stakeholders have a say in the decision-making process. This model of collaborative governance is in perfect harmony with the fundamental principles of decentralization, rendering multi-signature wallets a crucial component in the advancing field of blockchain technology. Adopting this method not only protects assets but also enables organizations to function with improved transparency, security, and reliability.

Leave a

Comment

Name is required

Invalid Name

Comment is required

Recaptcha is required.

blog-detail

November 8, 2024 at 12:49 pm

Your comment is awaiting moderation.

By using this site, you allow our use of cookies. For more information on the cookies we use and how to delete or block them, please read our cookie notice.

Chat with Us
Telegram Button
Youtube Button
Contact Us

Oodles | Blockchain Development Company

Name is required

Please enter a valid Name

Please enter a valid Phone Number

Please remove URL from text