Skip to main content

Bitcoin MultiSig in Sui Move

The following example implements a MultiSig mechanism for Bitcoin txs using Sui Move smart contract.

module sui_move_btc_multisig::multisig {
use dwallet_network::dwallet_cap::{Self, DWalletCap};
use sui::object::{id_from_address};

const ENoPermission: u64 = 0;
const EAlreadyVoted: u64 = 1;

public struct MultiSig has key {
id: UID,
signers: vector<address>,
cap: DWalletCap,
treshold: u64,
}

public struct MessageApprovalProposal has key {
id: UID,
multisig_id: ID,
signers: vector<address>,
message: vector<u8>,
approved: bool,
}

public struct ReplaceSingersProposal has key {
id: UID,
multisig_id: ID,
signers: vector<address>,
new_signers: vector<address>,
approved: bool,
}

public fun create_multisig(dwallet_network_cap_address: address, treshold: u64, ctx: &mut TxContext) {
let dwallet_network_cap_id = id_from_address(dwallet_network_cap_address);
let cap = dwallet_cap::create_cap(dwallet_network_cap_id, ctx);

let multisig = MultiSig {
id: object::new(ctx),
signers: vector[tx_context::sender(ctx)],
cap,
treshold
};

transfer::share_object(multisig);
}

fun find_signer(signers: &vector<address>, sender: address): bool {
let length = vector::length(signers);
let mut i = 0;
while (i < length) {
let addr = *vector::borrow(signers, i);
if (addr == sender) {
return true
};
i = i + 1;
};
false
}

public fun propose_message_approval(multisig: &MultiSig, message: vector<u8>, ctx: &mut TxContext) {
let sender = tx_context::sender(ctx);
let has_permission = find_signer(&multisig.signers, sender);
assert!(has_permission, ENoPermission);
let proposal = MessageApprovalProposal {
id: object::new(ctx),
multisig_id: object::id(multisig),
signers: vector[],
message,
approved: false,
};

transfer::share_object(proposal);
}

public fun sign_message_approval(multisig: &MultiSig, proposal: &mut MessageApprovalProposal, ctx: &mut TxContext) {
let sender = tx_context::sender(ctx);

assert!(!proposal.approved, EAlreadyVoted);

let has_permission = find_signer(&multisig.signers, sender);
assert!(has_permission, ENoPermission);

let already_voted = find_signer(&proposal.signers, sender);
assert!(already_voted, EAlreadyVoted);

vector::push_back(&mut proposal.signers, sender);

let length = vector::length(&proposal.signers);
if (length == multisig.treshold) {
dwallet_cap::approve_message(&multisig.cap, proposal.message);
proposal.approved = true;
}
}


public fun propose_replace_signers(multisig: &MultiSig, new_signers: vector<address>, ctx: &mut TxContext) {
let sender = tx_context::sender(ctx);
let has_permission = find_signer(&multisig.signers, sender);
assert!(has_permission, ENoPermission);
let proposal = ReplaceSingersProposal {
id: object::new(ctx),
multisig_id: object::id(multisig),
signers: vector[],
new_signers,
approved: false,
};

transfer::share_object(proposal);
}

public fun sign_replace_signers(multisig: &mut MultiSig, proposal: &mut ReplaceSingersProposal, ctx: &mut TxContext) {
let sender = tx_context::sender(ctx);

assert!(!proposal.approved, EAlreadyVoted);

let has_permission = find_signer(&multisig.signers, sender);
assert!(has_permission, ENoPermission);

let already_voted = find_signer(&proposal.signers, sender);
assert!(already_voted, EAlreadyVoted);

vector::push_back(&mut proposal.signers, sender);

let length = vector::length(&proposal.signers);
if (length == multisig.treshold) {
vector::push_back(&mut proposal.signers, sender);
multisig.signers = proposal.new_signers;
proposal.approved = true;
}
}

}