Skip to content

Instantly share code, notes, and snippets.

@DOBEN
Last active April 5, 2024 09:28
Show Gist options
  • Save DOBEN/683fe1a7c82a0551546a7ec242d30cc0 to your computer and use it in GitHub Desktop.
Save DOBEN/683fe1a7c82a0551546a7ec242d30cc0 to your computer and use it in GitHub Desktop.

Using Concordium multi-sig accounts

This is a guide for converting an existing account on Concordium into a multi-sig account and using it to send multi-sig transactions to contracts via scripts.

Converting an existing account on Concordium into a multi-sig account

  • Export two account key files A and B from the browser wallet. This would be done by two independent people A and B.

Checkout the guide Concordium Wallet for Web on how to export key files: https://developer.concordium.software/en/mainnet/net/guides/export-key.html

The exported key file of Account A (e.g. 4jxvYasaPncfmCFCLZCvuL5cZuvR5HAQezCHZH7ZA7AGsRYpix.export) should have a content similar to:

{
    "type": "concordium-browser-wallet-account",
    "v": 0,
    "environment": "testnet",
    "value": {
        "accountKeys": {
            "keys": {
                "0": {
                    "keys": {
                        "0": {
                            "signKey": "751c9...a38ae",
                            "verifyKey": "d3e85058f11336715c0ed479a22bd2d04f17366e8da968f7445c237faec1f753"
                        }
                    },
                    "threshold": 1
                }
            },
            "threshold": 1
        },
        "credentials": {
            "0": "97f325c9f86066ab0c80ff879c21629eb67818841940869308d6a72886d18f8668e62e43ad228fdcbda245d0722454df"
        },
        "address": "4jxvYasaPncfmCFCLZCvuL5cZuvR5HAQezCHZH7ZA7AGsRYpix"
    }
}
  • Follow this guide to update the account keys of account A.

https://gist.github.com/limemloh/8c0c55f67cf5a83ac7cc21cb646e65c1

The update-keys.json file in the last section of above guide should look as follows:

{
    "keys": {
        "0": {
            "verifyKey": "<Hex encoding of public key Account A>"
        },
        "1": {
            "verifyKey": "<Hex encoding of public key Account B>"
        }
    },
    "threshold": 2
}

Note: The threshold was set to 2 in the above example. Meaning account A, will be a 2 out of 2 multi sig account from now on.

Adjust deployment scripts to work with a 2-out-of-2 multi sig account

We will modify the following deployment script as an example:

https://github.com/umbrella-network/phoenix-concordium/blob/develop/deploy-scripts/src/deployer.rs#L236

  • Make sure you hardcode the transaction expiry time in the script below to a reasonable value so that person A and B sign and send the transaction with the same expiry time.
  use concordium_rust_sdk::{
    common::types::{CredentialIndex, KeyIndex, Signature, TransactionTime}
  };

  ...

  pub async fn update_contract(
        &mut self,
        update_payload: UpdateContractPayload,
        energy: Option<GivenEnergy>,
        expiry: Option<TransactionTime>,
    ) -> Result<(TransactionHash, BlockItemSummary), Error> {
        println!("\nUpdating contract....");

        let nonce = self.get_nonce(self.key.address).await?;

        if !nonce.all_final {
            bail!("Nonce not final")
        }

        let payload = transactions::Payload::Update {
            payload: update_payload,
        };
 
        // Hadcode a reasonable expiry time here.
        let expiry =
            expiry.unwrap_or_else(|| TransactionTime::from_seconds((1712243397 + 100000) as u64));

        let energy = energy.unwrap_or(GivenEnergy::Absolute(Energy { energy: 50000 }));

        let mut tx = transactions::send::make_and_sign_transaction(
            &*self.key,
            self.key.address,
            nonce.nonce,
            expiry,
            energy,
            payload,
        );

        println!(
            "Share this signature to your second party: {:?}",
            tx.signature.signatures
        );

        let tx_local = tx.signature.signatures.entry(CredentialIndex { index: 0 });

        let mut other_signature = BTreeMap::new();
        other_signature.insert(
            KeyIndex::from(1),
            Signature {
                sig: vec![207, 100, 165, 247, 44, 40, 202, 53, 255, 97, 99, 253, 50, 97, 39, 63, 172, 69, 250, 113, 10, 20, 97, 225, 208, 254, 252, 84, 30, 195, 241, 82, 142, 58, 172, 172, 232, 22, 135, 90, 125, 107, 186, 144, 146, 68, 16, 198, 190, 27, 141, 77, 58, 225, 138, 251, 161, 217, 111, 253, 103, 34, 114, 15],
            },
        );

        tx_local.and_modify(|x| x.append(&mut other_signature));

        let bi = transactions::BlockItem::AccountTransaction(tx);
        
        bail!(Error::msg(
            "do not send transaction first; collect just the signature; give signature to second party; update above `other_signature`, and then remove this error to send the transaction on chain"
        ));

        ...
   }

Run the modified deployment script

  • Person B generates a modified account key file (named Account_A_modified.export):

    • Get the Account A key file (without the actual keys in it)

    • Add your Account B keys into the file.

Meaning the string {"signKey":"751c.....a38ae","verifyKey":"d3e85058f11336715c0ed479a22bd2d04f17366e8da968f7445c237faec1f753"} part in the file should be replaced with the keys from Account B.

{
    "type": "concordium-browser-wallet-account",
    "v": 0,
    "environment": "testnet",
    "value": {
        "accountKeys": {
            "keys": {
                "0": {
                    "keys": {
                        "0": {
                            "signKey": <Account_B_Signing_Key>,
                            "verifyKey": <Account_B_Public_Key>
                        }
                    },
                    "threshold": 1
                }
            },
            "threshold": 1
        },
        "credentials": {
            "0": "97f325c9f86066ab0c80ff879c21629eb67818841940869308d6a72886d18f8668e62e43ad228fdcbda245d0722454df"
        },
        "address": "4jxvYasaPncfmCFCLZCvuL5cZuvR5HAQezCHZH7ZA7AGsRYpix"
    }
}
  • Person B runs the script with its modified account key files to generate a signature:

e.g.

cargo run register --node http://node.testnet.concordium.com:20000 --account ./4jxvYasaPncfmCFCLZCvuL5cZuvR5HAQezCHZH7ZA7AGsRYpix_modified.export --registry "<7281,0>" --contract "<7373,0>" --contract "<7283,0>"

This will print the signature into the terminal. Communicate the signature to person A.

  • Person A runs the script with the account key file A (as exported from the browser wallet) to sign and send the transaction on-chain:

    • Update the other_signature to the signature provided by person B.

    • Remove the error part from the script

     bail!(Error::msg(
            "do not send transaction; first collect just the signature; give signature to second party; update above `other_signature`, and then remove this error to send the transaction on chain"
     ));
  • Run the script as normal to sign and send the transaction on-chain:

e.g.

cargo run register --node http://node.testnet.concordium.com:20000 --account ./4jxvYasaPncfmCFCLZCvuL5cZuvR5HAQezCHZH7ZA7AGsRYpix.export --registry "<7281,0>" --contract "<7373,0>" --contract "<7283,0>"
can
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment