Cover Image for CardanoSharp: Cardano Multisig Addresses

CardanoSharp: Cardano Multisig Addresses

CardanoSharp

A multi-signature address is an address with an attached native script. The native script is evaluated when a spend transaction is submitted to the ledger. A native script is a small smart contract that enforces a set of rules agreed upon when the address was initially created.

The typical use case of a multi-signature address is to have more than one person in control of signing a transaction. This is very useful if the address represents a business or joint funding of multiple stakeholders.

In this scenario, we will demonstrate how 3 users, who are the directors of a business, use the system. Each maintains their own wallet and secures their private key. They need to fund a project, and part of the rule is that 2 out of 3 directors must sign each spending transaction associated with this project's funding.

We start by creating 3 wallets, one for each director, and 1 wallet to represent the business

// rewards wallet/business
var (_, _, stackPub) = GetPolicyKeysForWallet("slight velvet useful ...");

// stakeholder 1
var (u1Pri, u1Pub, _) = GetPolicyKeysForWallet("planet loud together ...");
// stakeholder 2
var (u2Pri, u2Pub, _) = GetPolicyKeysForWallet("orphan hub pepper ...");
// stakeholder 3
var (u3Pri, u3Pub, _) = GetPolicyKeysForWallet("dad wine match ...");

// payment script
var paymentScript = NativeScriptBuilder.Create
    .SetScriptNofK(2, new[]
    {
        NativeScriptBuilder.Create.SetKeyHash(HashUtility.Blake2b224(u1Pub.Key)),
        NativeScriptBuilder.Create.SetKeyHash(HashUtility.Blake2b224(u2Pub.Key)),
        NativeScriptBuilder.Create.SetKeyHash(HashUtility.Blake2b224(u3Pub.Key)),
    });

// create a multisig address 
var address = AddressUtility.GetScriptAddress(paymentScript.Build(), stackPub, CardanoSharp.Wallet.Enums.NetworkType.Preview);
// addr_test1zrcs3trrey7np8f4m4kkmzn4jxxyyduvx6ymq0z7fdaywcc7x4yvjh8mxkr0f0czsw66e249nfymk9j87a7jha6h9vnqpnmwf3

Once you have created an address using the above code, you can send ADA to fund it as usual.

To send funds out of the address, a minimum of 2 out of the 3 directors must sign the transaction, as indicated by the payment script.

Here is a partial code example of a transaction signed by 2 directors.

// Build transaction body
var transactionBody = TransactionBodyBuilder.Create
    .AddInput(utxo.tx_hash, utxo.tx_index)
    .AddOutput(receiverAddress, transferAmount,  outputPurpose: OutputPurpose.Spend)
    .AddOutput(address, utxo.value - transferAmount, outputPurpose: OutputPurpose.Change)
    .SetTtl(currentSlot + 1200)
    .SetFee(0);

// adding signature keys to the UTxO from 2 out of 3 
var witnessSet = TransactionWitnessSetBuilder.Create
        .AddNativeScript(paymentScript)
       .AddVKeyWitness(u1Pub, u1Pri)
       .AddVKeyWitness(u3Pub, u3Pri);

Here is how the address would appear in CardanoScan with 2 transactions.

https://preview.cardanoscan.io/address/10f108ac63c93d309d35dd6d6d8a75918c42378c3689b03c5e4b7a47631e3548c95cfb3586f4bf0283b5acaaa59a49bb1647f77d2bf7572b26?tab=transactions

Implementation Notes

The above example is very basic and shows the 3 wallets accessible in the same code path. In reality, each user has their own wallet, where they sign the transaction independently from each other.

The transaction is presented to each user via a web application, where they can sign the transaction using their wallet. They then send it to a backend API that assembles the witness signatures and submits the transaction to the blockchain only once it has the minimum required signers.

Closing Thoughts

Cardano offers unique built-in functionality that enables the integration of simple yet effective rules within its addresses. These rules, once attached, are evaluated by the ledger at the time of each transaction. This method is more straightforward compared to an EVM smart contract, as it focuses on rules that are directly associated with individual transactions, thus streamlining the process and eliminating the need to evaluate against the entire account.

Another very useful use case is a vesting address. The funds in the address can not be spent until a certain amount of time has passed.

However, if native scripts are written incorrectly, they could end up locking the funds in the address forever. For example, incorrectly using 'Time locking' scripts with 'after' and 'before' conditions can lead to this issue, so it's important to test the script on a testnet before deploying it to the mainnet.

Here, you will find the documentation of the available native scripts in Cardano.