Skip to main content
Version: master

ADR 007: Address generation of chain link proof

Changelog

  • January 20th, 2022: Initial draft;
  • July 28th, 2022: First review;

Status

PROPOSED

Abstract

Currently, Desmos allows linking other chains accounts which addresses are formatted using either the Bech32, Base58 or Hex encoding and generated using a single algorithm specific to the encoding itself. Since Desmos idea is to support as many chains as possible, we SHOULD split the address generation algorithm from the encoding algorithm so that more chains can be linked properly.

Context

The x/profiles module gives users the possibility to link their profile to different external accounts. However, each one of the supported address formats currently supports only one generation algorithm.

Bech32Address relies on the address generation algorithm specific of Cosmos-SDK chains:

  • for single signature accounts:
    ripemd160(sha256(32 bytes public key))[:20]
  • for multi-sig accounts:
    sha256(aminoCdc.Marshal(multisig public key))[:20]

Base58Address relies on the Solana algorithm:

(32 bytes public key)[:32]

HexAddress relies on the Ethereum algorithm:

keccak64(64 bytes public key)[12:]

Due to the fact all the AddressData implementations are highly coupled to a single address generation function, it is currently impossible to link an address that needs to be encoded using a supported encoding method but is generated using a different algorithm. This is the case of Elrond addresses which are encoded using the Bech32 encoding algorithm, but are generated using an algorithm that is different from the Cosmos one.

Decision

In order to allow developers to integrate any kind of address, we will review how the current Address structure is made. Instead of using an interface with multiple implementations based on the encoding algorithm, we will use a single structure that allows to specify both the encoding algorithm and the hashing algorithm(s) that should be used to get the proper address:

syntax = "proto3";

// Address contains the data of an external address
message Address {
// Encoded value of the address
string value = 1;

// Algorithm that has been used in order to generate the address starting from the public key bytes
GenerationAlgorithm generation_algorithm = 2;

// Algorithm that needs to be used to properly encode the address
google.proto.Any encoding_algorithm = 3 [ (cosmos_proto.accepts_interface) = "AddressEncoding" ];
}

// GenerationAlgorithm represents various address generation algorithms
enum GenerationAlgorithm {
// GENERATION_ALGORITHM_UNKNOWN represents an unknown algorithm and will be discarded
GENERATION_ALGORITHM_UNKNOWN = 0;

// GENERATION_ALGORITHM_COSMOS represents the Cosmos generation algorithm
GENERATION_ALGORITHM_COSMOS = 1;

// GENERATION_ALGORITHM_EVM represents the EVM generation algorithm
GENERATION_ALGORITHM_EVM = 2;

// GENERATION_ALGORITHM_DO_NOTHING should be used when the public key bytes do not need to be modified
GENERATION_ALGORITHM_DO_NOTHING = 3;
}

// Bech32Encoding represents the encoding algorithm based on the Bech32 format
message Bech32Encoding {
option (cosmos_proto.implements_interface) = "AddressEncoding";

// Prefix to be used
string prefix = 1;
}

// Base58Encoding represents the encoding algorithm based on the Base58 format
message Base58Encoding {
option (cosmos_proto.implements_interface) = "AddressEncoding";

// (optional) Prefix to be used
string prefix = 1;
}

// HexEncoding represents the encoding algorithm based on the Hex format
message HexEncoding {
option (cosmos_proto.implements_interface) = "AddressEncoding";

// (optional) Prefix to be used
string prefix = 1;

// (optional) Whether the address should be upper case or not (default: false)
bool uppercase = 2;
}

We will also define the following methods for the Address structure:

// Validate validates the given public key against this address, to make sure they match
func (a *Address) Validate(pubKey cryptotypes.PubKey) error {
addressBytes, err := hex.DecodeString(a.Value)
if err != nil {
return err
}

// Generate the address bytes from the pub key
generatedBytes, err := generateAddressBytes(pubKey, a.GenerationAlgorithm)
if err != nil {
return err
}

// Compare the bytes
if !bytes.Equals(addressBytes, generatedBytes) {
return fmt.Errrorf("address bytes do not match generated ones: expected %s but got %s", addressBytes, generatedBytes)
}

return nil
}

// generateAddressBytes generates the address bytes starting from the given public key
// and using the provided generation algorithm
func generateAddressBytes(pubKey cryptotypes.PubKey, generationAlgorithm GenerationAlgorithm) ([]byte, error) {
// ...
}

// GetValue returns the string value of the address, encoded as it should be
func (a *Address) GetValue() string {
return a.Encoding.GetCachedValue().(AddressEncoding).Encode(a.Value)
}

Consequences

Backwards Compatibility

Since this update will affect all the instances of AddressData by completely changing how it is defined, such changes are backwards incompatible. For this reason, we need to make sure we write a proper migration to update all the current AddressData instances to the new Address type.

Positive

  • Easy to extend the address generation algorithm for Address instances

Negative

Neutral

References