ADR 015: Improve chain links signature support
Changelog
- May 19th, 2022: First draft
Status
ACCEPTED Implemented
Abstract
This ADR contains the specifications about new signature methods that should be supported when creating chain links in order to allow the linkage of external chains using commonly used third party wallets (e.g. MetaMask for Ethereum).
Context
Currently, when creating a chain link there are only two kind of signatures that are supported: Cosmos single signature (obtained using sign(sha256(value))
) and Cosmos multi signatures. Although this allows to connect any Cosmos chain to a Desmos profile, it does not support external chains that might be using different signature algorithms and specifications. As an example, right now it's not possible to link Ethereum address using MetaMask as this uses the personal_sign
algorithm which consists of the following:
sign(keccak256("\x19Ethereum Signed Message:\n" + len(message) + message)))
Considering each ecosystem usually has a third party wallet as a reference (let's think about Keplr for Cosmos, MetaMask for Ethereum and SolWallet for Solana), it's important that we support the signature specifications that such widely used wallets use. This will allow users to connect their addresses to their Desmos profile more safely without needing any DApp to request them their secret (aka mnemonic) phrase to generate a valid signature.
Decision
In order to support most signature specifications as possible, we will define a Signature
interface that will be implemented into two major instances:
SingleSignature
, representing a signature that is generated by a single signer;CosmosMultiSignature
, representing a signature that is generated by a Cosmos multi-signature wallet.
Signature
The Signature
interface will represent a generic signature, and thus will have only a method that should be used to validate it and another one used to verify it:
// Signature represents a generic signature data
type Signature interface {
// Validate checks the validity of the Signature
Validate(cdc codec.BinaryCodec, amino *codec.LegacyAmino, plainText []byte, owner string) error
// Verify allows to verify this signature using the given public key against the given plain text.
// If the signature is valid, it returns the public key instance used to verify it
Verify(cdc codec.BinaryCodec, pubKey *codectypes.Any, plainText []byte) (cryptotypes.PubKey, error)
}
SingleSignature
The SingleSignature
implementation of the Signature
interface will be as follows:
syntax = "proto3";
// SingleSignature is the signature data for a single signer
message SingleSignature {
option (gogoproto.goproto_getters) = false;
option (gogoproto.equal) = true;
option (cosmos_proto.implements_interface) = "CosmosSignature";
// ValueEncoding represents how the value has been encoded before being signed
SignatureValueEncoding value_encoding = 1
[ (gogoproto.moretags) = "yaml:\"value_encoding\"" ];
// Signature is the raw signature bytes
bytes signature = 2 [ (gogoproto.moretags) = "yaml:\"signature\"" ];
}
// SignatureValueEncoding tells how the value has been encoded before being
// signed
enum SignatureValueEncoding {
option (gogoproto.goproto_enum_prefix) = false;
// SIGNATURE_VALUE_ENCODING_UNSPECIFIED specifies an unknown signing mode and
// will be rejected
SIGNATURE_VALUE_ENCODING_UNSPECIFIED = 0;
// SIGNATURE_VALUE_ENCODING_RAW should be used when the value has been signed
// as a raw byte array
SIGNATURE_VALUE_ENCODING_RAW = 1;
// SIGNATURE_VALUE_ENCODING_COSMOS_DIRECT should be used when the signed value
// has been encoded as a Protobuf transaction containing the owner address
// inside its memo field
SIGNATURE_VALUE_ENCODING_COSMOS_DIRECT = 2;
// SIGNATURE_VALUE_ENCODING_COSMOS_AMINO should be used when the value has
// been encoded as an Amino transaction containing the owner address inside
// its memo field
SIGNATURE_VALUE_ENCODING_COSMOS_AMINO = 3;
// SIGNATURE_VALUE_ENCODING_EVM_PERSONAL_SIGN should be used when the value
// has been encoded following the EVM personal_sign specification
SIGNATURE_VALUE_ENCODING_EVM_PERSONAL_SIGN = 4;
}
CosmosMultiSignature
The CosmosMultiSignature
implementation of the Signature
interface will be as follows:
syntax = "proto3";
// CosmosMultiSignature is the signature data for a multisig public key
message CosmosMultiSignature {
option (gogoproto.goproto_getters) = false;
option (gogoproto.equal) = true;
option (cosmos_proto.implements_interface) = "CosmosSignature";
// Bitarray specifies which keys within the multisig are signing
cosmos.crypto.multisig.v1beta1.CompactBitArray bit_array = 1
[ (gogoproto.moretags) = "yaml:\"bit_array\"" ];
// Signatures is the signatures of the multi-signature
repeated google.protobuf.Any signatures = 2 [
(cosmos_proto.accepts_interface) = "CosmosSignature",
(gogoproto.moretags) = "yaml:\"signatures\""
];
}
Consequences
Backwards Compatibility
The proposed solution is partially backward compatible, but a store migration is still required. Particularly, for each link:
- all
SingleSignatureData
instances should be converted toSingleSignature
; - all
MultiSignatureData
instances should be converted toCosmosMultiSignature
;
Also, the Proof#Verify
method must be updated in order to not check for different signature types, but to simply rely on the Signature#Validate
and Signature#Verify
methods instead.
Positive
- Allows for a greater extensibility in the future
- Makes it easier to verify a signature
Negative
Neutral
Further Discussions
We should perform a search within the Solana ecosystem to understand what are the most used wallets, and what kind of signature specification they allow the user to use in order to sign arbitrary data. This will allow us to implement this kind of Signature
as well.
Test Cases
- Make sure the current signatures are migrated properly to the new types