Stateful multisig on Bitcoin

No off-chain communication required

introduce

As the blockchain and cryptocurrency space grows, there is an increasing need for enhanced security measures to protect digital assets. One of the prominent solutions to this challenge is multi-signature (multi-signature) wallets. These wallets require multiple signatures before executing transactions, providing an additional layer of security to prevent unauthorized access and fraud.

Image source CoinDesk

Picture sourceCoinDesk

Traditional multi-signature wallets require out-of-band (off-chain) communication between parties to collect all signatures. We introduce a smart contract that collects signatures directly on the blockchain. Smart contracts track these signatures and once a set threshold is reached, funds can be unlocked. This may be useful, for example, when the parties in a multisig group do not know each other.

Traditional multi-signature wallet

Multi-signature wallets typically require M-of-N signatures ( signatures from a set of N participants) to Authorized Transactions. These wallets are becoming increasingly popular for managing digital assets, especially in the following scenarios:M

  1. Joint accounts require multiple family members or business partners to approve transactions.
  2. Escrow services, where a neutral third party holds funds until predetermined conditions are met.
  3. Secure storage, where a user holds multiple keys to prevent unauthorized access.

Traditional multi-signature wallets require the exchange of partially signed transactions between parties before they can be committed on-chain.

On-chain signature collection

In the next steps, we have developed a multi-signature smart contract that allows on-chain signature collection.

  1. The smart contract is deployed on the blockchain, specifying the minimum number of signatures M and the list of authorized signers N. It has a state: a list of signatures collected so far, initialized to zero.
  2. Whenever a contract is called with a new signature, it will be added to the state if it is valid and new.
  3. Once the threshold M is reached, the smart contract pays out to the predefined destination address.

Below is the complete code written in sCrypt.

export type Owner = {
    
    
    pubKey: PubKey
    validated: boolean
}

export class StatefulMultiSig extends SmartContract {
    
    
    // N of M signatures required.
    static readonly N = 2
    static readonly M = 3

    // Payment destination once signature threshold is reached.
    @prop()
    dest: PubKeyHash

    // Public keys of the owners along with boolean flags, that
    // indicate if their sig was already validated.
    @prop(true)
    owners: FixedArray<Owner, typeof StatefulMultiSig.M>

    constructor(
        dest: PubKeyHash,
        owners: FixedArray<Owner, typeof StatefulMultiSig.M>
    ) {
    
    
        super(...arguments)
        this.dest = dest
        this.owners = owners
    }

    @method(SigHash.ANYONECANPAY_SINGLE)
    public pay() {
    
    
        // Check if threshold was reached.
        let nValid = 0n
        for (let i = 0; i < StatefulMultiSig.M; i++) {
    
    
            if (this.owners[i].validated) {
    
    
                nValid += 1n
            }
        }
        assert(
            nValid >= BigInt(StatefulMultiSig.N),
            'Not enough valid signatures.'
        )

        // Make sure balance in the contract does not change.
        const amount: bigint = this.ctx.utxo.value
        // Pay destination address
        const output: ByteString = Utils.buildPublicKeyHashOutput(
            this.dest,
            amount
        )
        // Verify unlocking tx has this output.
        assert(this.ctx.hashOutputs == hash256(output), 'hashOutputs mismatch')
    }

    @method(SigHash.ANYONECANPAY_SINGLE)
    public add(sig: Sig, pubKeyIdx: bigint) {
    
    
        let added = false

        for (let i = 0; i < StatefulMultiSig.M; i++) {
    
    
            if (BigInt(i) == pubKeyIdx) {
    
    
                const owner = this.owners[i]
                const valid = this.checkSig(sig, owner.pubKey)
                if (valid && !owner.validated) {
    
    
                    // Toggle flag.
                    this.owners[i].validated = true
                    added = true
                }
            }
        }

        // Make sure at least one new valid sig was added.
        assert(added, 'No new valid signature was provided.')

        // Make sure balance in the contract does not change.
        const amount: bigint = this.ctx.utxo.value
        // Output containing the latest state.
        const output: ByteString = this.buildStateOutput(amount)
        // Verify unlocking tx has this single output.
        assert(this.ctx.hashOutputs == hash256(output), 'hashOutputs mismatch')
    }
}

Contract source code

The contract has two public methods:

It has two public methods:

  • add- This method takes as input a valid signature and the index of the public key to which it belongs. It first checks the validity of this signature and sets a flag if valid.
  • pay — This method checks whether a signature threshold has been reached, e.g. in this particular example, the threshold is two thirds. If so, it ensures that the next output will pay out the locked funds to the address stored in the dest variable.

Full code andtests are available in our sampleboard repository< found in a i=4>.


Quote

Native custom tokens in the extended UTXO model: https://iohk.io/en/research/library/papers/native-custom-tokens-in-the-extended-utxo-model/

Guess you like

Origin blog.csdn.net/freedomhero/article/details/134642026