ADR 006: Support multisig chain link
Changelog
- September 29th 2021: Initial draft
- October 21th, 2021: Proposed
- October 21th, 2021: First Review
- October 22th, 2021: Second Review
- November 15th, 2021: Third Review
Status
ACCEPTED Implemented
Abstract
Currently, it is not possible to create a chain link using a multisig account. Since many validators MIGHT use multisig accounts, we SHOULD change the Proof type and its Verify function in order to support this kind of account as well.
Context
Currently, the x/profiles module gives users the possibility to link their profile to different external accounts.
In particular, to link other blockchains accounts to a profile, we follow this process:
- the user signs a message with their own private key;
- the signature and the signed value are placed inside a Proofobject;
- Desmos verifies the Proofobject to guarantee that the user really owns such account and thus it can be linked successfully to their Desmos profile.
Currently, this process works properly for single-signature accounts, but it does not support multi-signature accounts. This is due to the fact that the Proof type only supports single-account's signatures, and its Verify function is only able to verify such signature type.
Decision
We propose to change the Proof#Signature field to be of type SignatureDescriptor_Data.
Proof implementation
SignatureDescriptor_Data supports storing both single and multi-sig signatures. Moreover, the Cosmos SDK provides a function to convert such type into the corresponding interface (SignatureData), which is required from the Verify function in order to validate such signature. 
The verification process can be implemented as follows:
- If it's a SingleSignatureData, make sure the account public key is acryptotypes.PubKeyand then use theVerifySignaturemethod to verify the signature.
- If it's a MultiSignatureData, make sure the account public key is amultisig.PubKeyand then use theVerifyMultisignaturemethod to verify the signature.
CLI implementation
We propose to use an interactive prompt to create a new chain-link JSON for both single signature and multi signature account. For the single signature account, the process will remain the same as before. For the multi signature account, we will add the new feature beyond the current ones.
To simplify things for multisig accounts, we propose to create a chain link JSON starting from a transaction signed with a multisig account. Inside the Cosmos SDK, multisig account transactions signing depends on the tx sign and tx multisign commands. To send a transaction to the node, the following process takes place: 
- the multisig account owners create a raw transaction file;
- the threshold number of signers sign it to using the tx signcommand;
- all individual signatures are gathered and used as input from the tx multisignto generate the final signed transaction.
The multisign file includes not only all public keys and threshold of the multisig account, but also the required number of signatures. Thanks to this, we can leverage all the information stored inside such file to create a proper chain link JSON starting from a multisigned transaction.
The whole process in code is presented below:
// getChainLinkJSONFromMultiSign generates the chain-link JSON from the multisign file and its raw transaction file
func getChainLinkJSONFromMultiSign(
    cmd *cobra.Command,
    txFile string,
    chainID string,
    chain chainlinktypes.Chain,
) (profilescliutils.ChainLinkJSON, error) {
    clientCtx, err := client.GetClientTxContext(cmd)
    if err != nil {
        return profilescliutils.ChainLinkJSON{}, err
    }
    parsedTx, err := authclient.ReadTxFromFile(clientCtx, txFile)
    if err != nil {
        return profilescliutils.ChainLinkJSON{}, err
    }
    txCfg := clientCtx.TxConfig
    txBuilder, err := txCfg.WrapTxBuilder(parsedTx)
    if err != nil {
        return profilescliutils.ChainLinkJSON{}, err
    }
    txFactory := tx.NewFactoryCLI(clientCtx, cmd.Flags())
    if txFactory.SignMode() == signingtypes.SignMode_SIGN_MODE_UNSPECIFIED {
        txFactory = txFactory.WithSignMode(signingtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON)
    }
    sigs, err := txBuilder.GetTx().GetSignaturesV2()
    if len(sigs) != 1 {
        return profilescliutils.ChainLinkJSON{}, fmt.Errorf("invalid number of signatures")
    }
    multisigSig := sigs[0]
    signingData := authSigning.SignerData{
        ChainID:       chainID,
        AccountNumber: txFactory.AccountNumber(),
        Sequence:      txFactory.Sequence(),
    }
    // the bytes of plain text
    value, err := txCfg.SignModeHandler().GetSignBytes(txFactory.SignMode(), signingData, txBuilder.GetTx())
    if err != nil {
        return profilescliutils.ChainLinkJSON{}, err
    }
    addr, _ := sdk.Bech32ifyAddressBytes(chain.Prefix, multisigSig.PubKey.Address().Bytes())
    return profilescliutils.NewChainLinkJSON(
        profilestypes.NewBech32Address(addr, chain.Prefix),
        profilestypes.NewProof(multisigSig.PubKey, signingtypes.SignatureDataToProto(multisigSig.Data), hex.EncodeToString(value)),
        profilestypes.NewChainConfig(chain.Name),
    ), nil
}
Consequences
Backwards Compatibility
Since we change the Proof#Signature field from a hex-encoded string into a SignatureDescriptor_Data, this feature is not backwards compatible. For this reason, it will be needed to migrate the old chain link signatures to SignatureDescriptor_Data.
Positive
- Give the possibility to link a multisig account to a Desmos profile
Negative
- Raise the complexity to generate and verify the signature
Neutral
(none known)
Further Discussions
Test Cases [optional]
The following tests cases MUST to be present:
- Verify Proofincluding wrong format signature returns error
- Verify Proofincluding non-matched single signature and pubkey returns error
- Verify Proofincluding non-matched multi signatures and pubkeys returns error
- Verify Proofincluding proper single signature and pubkey returns no error
- Verify Proofincluding proper multi signatures and pubkeys returns no error