Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

R4R: Configurable Bech32 prefix for SDK users #2614

Merged
merged 8 commits into from
Oct 31, 2018
1 change: 1 addition & 0 deletions PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ FEATURES
* Gaia

* SDK
* (#1336) Mechanism for SDK Users to configure their own Bech32 prefixes instead of using the default cosmos prefixes.

* Tendermint

Expand Down
7 changes: 7 additions & 0 deletions cmd/gaia/cmd/gaiacli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/cosmos/cosmos-sdk/client/rpc"
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/version"

authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
Expand Down Expand Up @@ -46,6 +47,12 @@ func main() {
cobra.EnableCommandSorting = false
cdc := app.MakeCodec()

config := sdk.GetConfig()
config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub)
config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub)
config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub)
config.Seal()

// TODO: setup keybase, viper object, etc. to be passed into
// the below functions and eliminate global vars, like we do
// with the cdc
Expand Down
8 changes: 8 additions & 0 deletions cmd/gaia/cmd/gaiad/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,18 @@ import (
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
gaiaInit "github.com/cosmos/cosmos-sdk/cmd/gaia/init"
"github.com/cosmos/cosmos-sdk/server"
sdk "github.com/cosmos/cosmos-sdk/types"
)

func main() {
cdc := app.MakeCodec()

config := sdk.GetConfig()
config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub)
config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub)
config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub)
config.Seal()

ctx := server.NewDefaultContext()
cobra.EnableCommandSorting = false
rootCmd := &cobra.Command{
Expand Down
7 changes: 7 additions & 0 deletions cmd/gaia/cmd/gaiadebug/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ import (
)

func init() {

config := sdk.GetConfig()
config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub)
config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub)
config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub)
config.Seal()

rootCmd.AddCommand(txCmd)
rootCmd.AddCommand(pubkeyCmd)
rootCmd.AddCommand(addrCmd)
Expand Down
9 changes: 9 additions & 0 deletions examples/democoin/cmd/democli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
coolcmd "github.com/cosmos/cosmos-sdk/examples/democoin/x/cool/client/cli"
powcmd "github.com/cosmos/cosmos-sdk/examples/democoin/x/pow/client/cli"
simplestakingcmd "github.com/cosmos/cosmos-sdk/examples/democoin/x/simplestake/client/cli"

sdk "github.com/cosmos/cosmos-sdk/types"
)

// rootCmd is the entry point for this binary
Expand All @@ -38,6 +40,13 @@ func main() {
// get the codec
cdc := app.MakeCodec()

// Setup certain SDK config
config := sdk.GetConfig()
config.SetBech32PrefixForAccount("demoacc", "demopub")
config.SetBech32PrefixForValidator("demoval", "demovalpub")
config.SetBech32PrefixForConsensusNode("democons", "democonspub")
config.Seal()

// TODO: setup keybase, viper object, etc. to be passed into
// the below functions and eliminate global vars, like we do
// with the cdc
Expand Down
9 changes: 9 additions & 0 deletions examples/democoin/cmd/democoind/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/examples/democoin/app"
"github.com/cosmos/cosmos-sdk/server"
sdk "github.com/cosmos/cosmos-sdk/types"
)

const (
Expand Down Expand Up @@ -135,6 +136,14 @@ func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB, _ io.Writer) (j

func main() {
cdc := app.MakeCodec()

// Setup certain SDK config
config := sdk.GetConfig()
config.SetBech32PrefixForAccount("demoacc", "demopub")
config.SetBech32PrefixForValidator("demoval", "demovalpub")
config.SetBech32PrefixForConsensusNode("democons", "democonspub")
config.Seal()

ctx := server.NewDefaultContext()

rootCmd := &cobra.Command{
Expand Down
36 changes: 24 additions & 12 deletions types/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ func AccAddressFromHex(address string) (addr AccAddress, err error) {

// AccAddressFromBech32 creates an AccAddress from a Bech32 string.
func AccAddressFromBech32(address string) (addr AccAddress, err error) {
bz, err := GetFromBech32(address, Bech32PrefixAccAddr)
bech32PrefixAccAddr := GetConfig().GetBech32AccountAddrPrefix()
bz, err := GetFromBech32(address, bech32PrefixAccAddr)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -124,7 +125,8 @@ func (aa AccAddress) Bytes() []byte {

// String implements the Stringer interface.
func (aa AccAddress) String() string {
bech32Addr, err := bech32.ConvertAndEncode(Bech32PrefixAccAddr, aa.Bytes())
bech32PrefixAccAddr := GetConfig().GetBech32AccountAddrPrefix()
bech32Addr, err := bech32.ConvertAndEncode(bech32PrefixAccAddr, aa.Bytes())
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -169,7 +171,8 @@ func ValAddressFromHex(address string) (addr ValAddress, err error) {

// ValAddressFromBech32 creates a ValAddress from a Bech32 string.
func ValAddressFromBech32(address string) (addr ValAddress, err error) {
bz, err := GetFromBech32(address, Bech32PrefixValAddr)
bech32PrefixValAddr := GetConfig().GetBech32ValidatorAddrPrefix()
bz, err := GetFromBech32(address, bech32PrefixValAddr)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -239,7 +242,8 @@ func (va ValAddress) Bytes() []byte {

// String implements the Stringer interface.
func (va ValAddress) String() string {
bech32Addr, err := bech32.ConvertAndEncode(Bech32PrefixValAddr, va.Bytes())
bech32PrefixValAddr := GetConfig().GetBech32ValidatorAddrPrefix()
bech32Addr, err := bech32.ConvertAndEncode(bech32PrefixValAddr, va.Bytes())
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -284,7 +288,8 @@ func ConsAddressFromHex(address string) (addr ConsAddress, err error) {

// ConsAddressFromBech32 creates a ConsAddress from a Bech32 string.
func ConsAddressFromBech32(address string) (addr ConsAddress, err error) {
bz, err := GetFromBech32(address, Bech32PrefixConsAddr)
bech32PrefixConsAddr := GetConfig().GetBech32ConsensusAddrPrefix()
bz, err := GetFromBech32(address, bech32PrefixConsAddr)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -359,7 +364,8 @@ func (ca ConsAddress) Bytes() []byte {

// String implements the Stringer interface.
func (ca ConsAddress) String() string {
bech32Addr, err := bech32.ConvertAndEncode(Bech32PrefixConsAddr, ca.Bytes())
bech32PrefixConsAddr := GetConfig().GetBech32ConsensusAddrPrefix()
bech32Addr, err := bech32.ConvertAndEncode(bech32PrefixConsAddr, ca.Bytes())
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -387,7 +393,8 @@ func (ca ConsAddress) Format(s fmt.State, verb rune) {
// Bech32ifyAccPub returns a Bech32 encoded string containing the
// Bech32PrefixAccPub prefix for a given account PubKey.
func Bech32ifyAccPub(pub crypto.PubKey) (string, error) {
return bech32.ConvertAndEncode(Bech32PrefixAccPub, pub.Bytes())
bech32PrefixAccPub := GetConfig().GetBech32AccountPubPrefix()
return bech32.ConvertAndEncode(bech32PrefixAccPub, pub.Bytes())
}

// MustBech32ifyAccPub returns the result of Bech32ifyAccPub panicing on failure.
Expand All @@ -403,7 +410,8 @@ func MustBech32ifyAccPub(pub crypto.PubKey) string {
// Bech32ifyValPub returns a Bech32 encoded string containing the
// Bech32PrefixValPub prefix for a given validator operator's PubKey.
func Bech32ifyValPub(pub crypto.PubKey) (string, error) {
return bech32.ConvertAndEncode(Bech32PrefixValPub, pub.Bytes())
bech32PrefixValPub := GetConfig().GetBech32ValidatorPubPrefix()
return bech32.ConvertAndEncode(bech32PrefixValPub, pub.Bytes())
}

// MustBech32ifyValPub returns the result of Bech32ifyValPub panicing on failure.
Expand All @@ -419,7 +427,8 @@ func MustBech32ifyValPub(pub crypto.PubKey) string {
// Bech32ifyConsPub returns a Bech32 encoded string containing the
// Bech32PrefixConsPub prefixfor a given consensus node's PubKey.
func Bech32ifyConsPub(pub crypto.PubKey) (string, error) {
return bech32.ConvertAndEncode(Bech32PrefixConsPub, pub.Bytes())
bech32PrefixConsPub := GetConfig().GetBech32ConsensusPubPrefix()
return bech32.ConvertAndEncode(bech32PrefixConsPub, pub.Bytes())
}

// MustBech32ifyConsPub returns the result of Bech32ifyConsPub panicing on
Expand All @@ -436,7 +445,8 @@ func MustBech32ifyConsPub(pub crypto.PubKey) string {
// GetAccPubKeyBech32 creates a PubKey for an account with a given public key
// string using the Bech32 Bech32PrefixAccPub prefix.
func GetAccPubKeyBech32(pubkey string) (pk crypto.PubKey, err error) {
bz, err := GetFromBech32(pubkey, Bech32PrefixAccPub)
bech32PrefixAccPub := GetConfig().GetBech32AccountPubPrefix()
bz, err := GetFromBech32(pubkey, bech32PrefixAccPub)
if err != nil {
return nil, err
}
Expand All @@ -463,7 +473,8 @@ func MustGetAccPubKeyBech32(pubkey string) (pk crypto.PubKey) {
// GetValPubKeyBech32 creates a PubKey for a validator's operator with a given
// public key string using the Bech32 Bech32PrefixValPub prefix.
func GetValPubKeyBech32(pubkey string) (pk crypto.PubKey, err error) {
bz, err := GetFromBech32(pubkey, Bech32PrefixValPub)
bech32PrefixValPub := GetConfig().GetBech32ValidatorPubPrefix()
bz, err := GetFromBech32(pubkey, bech32PrefixValPub)
if err != nil {
return nil, err
}
Expand All @@ -490,7 +501,8 @@ func MustGetValPubKeyBech32(pubkey string) (pk crypto.PubKey) {
// GetConsPubKeyBech32 creates a PubKey for a consensus node with a given public
// key string using the Bech32 Bech32PrefixConsPub prefix.
func GetConsPubKeyBech32(pubkey string) (pk crypto.PubKey, err error) {
bz, err := GetFromBech32(pubkey, Bech32PrefixConsPub)
bech32PrefixConsPub := GetConfig().GetBech32ConsensusPubPrefix()
bz, err := GetFromBech32(pubkey, bech32PrefixConsPub)
if err != nil {
return nil, err
}
Expand Down
43 changes: 43 additions & 0 deletions types/address_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (

"github.com/tendermint/tendermint/crypto/ed25519"

"strings"

"github.com/cosmos/cosmos-sdk/types"
)

Expand Down Expand Up @@ -178,3 +180,44 @@ func TestConsAddress(t *testing.T) {
require.NotNil(t, err)
}
}

const letterBytes = "abcdefghijklmnopqrstuvwxyz"

func RandString(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return string(b)
}

func TestConfiguredPrefix(t *testing.T) {
var pub ed25519.PubKeyEd25519
for length := 1; length < 10; length++ {
for times := 1; times < 20; times++ {
rand.Read(pub[:])
// Test if randomly generated prefix of a given length works
prefix := RandString(length)
// Assuming that GetConfig is not sealed.
config := types.GetConfig()
config.SetBech32PrefixForAccount(prefix+"acc", prefix+"pub")
acc := types.AccAddress(pub.Address())
require.True(t, strings.HasPrefix(acc.String(), prefix+"acc"))
bech32Pub := types.MustBech32ifyAccPub(pub)
require.True(t, strings.HasPrefix(bech32Pub, prefix+"pub"))

config.SetBech32PrefixForValidator(prefix+"valaddr", prefix+"valpub")
val := types.ValAddress(pub.Address())
require.True(t, strings.HasPrefix(val.String(), prefix+"valaddr"))
bech32ValPub := types.MustBech32ifyValPub(pub)
require.True(t, strings.HasPrefix(bech32ValPub, prefix+"valpub"))

config.SetBech32PrefixForConsensusNode(prefix+"consaddr", prefix+"conspub")
cons := types.ConsAddress(pub.Address())
require.True(t, strings.HasPrefix(cons.String(), prefix+"consaddr"))
bech32ConsPub := types.MustBech32ifyConsPub(pub)
require.True(t, strings.HasPrefix(bech32ConsPub, prefix+"conspub"))
}

}
}
105 changes: 105 additions & 0 deletions types/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package types

import (
"sync"
)

// Config is the structure that holds the SDK configuration parameters.
// This could be used to initialize certain configuration parameters for the SDK.
type Config struct {
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
mtx sync.RWMutex
sealed bool
bech32AddressPrefix map[string]string
}

var (
// Initializing an instance of Config
sdkConfig = &Config{
sealed: false,
bech32AddressPrefix: map[string]string{
"account_addr": Bech32PrefixAccAddr,
"validator_addr": Bech32PrefixValAddr,
"consensus_addr": Bech32PrefixConsAddr,
"account_pub": Bech32PrefixAccPub,
"validator_pub": Bech32PrefixValPub,
"consensus_pub": Bech32PrefixConsPub,
},
}
)

// GetConfig returns the config instance for the SDK.
func GetConfig() *Config {
return sdkConfig
}

func (config *Config) assertNotSealed() {
config.mtx.Lock()
defer config.mtx.Unlock()

if config.sealed {
panic("Config is sealed")
}
}

// SetBech32PrefixForAccount builds the Config with Bech32 addressPrefix and publKeyPrefix for accounts
// and returns the config instance
func (config *Config) SetBech32PrefixForAccount(addressPrefix, pubKeyPrefix string) {
config.assertNotSealed()
config.bech32AddressPrefix["account_addr"] = addressPrefix
config.bech32AddressPrefix["account_pub"] = pubKeyPrefix
}

// SetBech32PrefixForValidator builds the Config with Bech32 addressPrefix and publKeyPrefix for validators
// and returns the config instance
func (config *Config) SetBech32PrefixForValidator(addressPrefix, pubKeyPrefix string) {
config.assertNotSealed()
config.bech32AddressPrefix["validator_addr"] = addressPrefix
config.bech32AddressPrefix["validator_pub"] = pubKeyPrefix
}

// SetBech32PrefixForConsensusNode builds the Config with Bech32 addressPrefix and publKeyPrefix for consensus nodes
// and returns the config instance
func (config *Config) SetBech32PrefixForConsensusNode(addressPrefix, pubKeyPrefix string) {
config.assertNotSealed()
config.bech32AddressPrefix["consensus_addr"] = addressPrefix
config.bech32AddressPrefix["consensus_pub"] = pubKeyPrefix
}

// Seal seals the config such that the config state could not be modified further
func (config *Config) Seal() *Config {
config.mtx.Lock()
defer config.mtx.Unlock()

config.sealed = true
return config
}

// GetBech32AccountAddrPrefix returns the Bech32 prefix for account address
func (config *Config) GetBech32AccountAddrPrefix() string {
return config.bech32AddressPrefix["account_addr"]
}

// GetBech32ValidatorAddrPrefix returns the Bech32 prefix for validator address
func (config *Config) GetBech32ValidatorAddrPrefix() string {
return config.bech32AddressPrefix["validator_addr"]
}

// GetBech32ConsensusAddrPrefix returns the Bech32 prefix for consensus node address
func (config *Config) GetBech32ConsensusAddrPrefix() string {
return config.bech32AddressPrefix["consensus_addr"]
}

// GetBech32AccountPubPrefix returns the Bech32 prefix for account public key
func (config *Config) GetBech32AccountPubPrefix() string {
return config.bech32AddressPrefix["account_pub"]
}

// GetBech32ValidatorPubPrefix returns the Bech32 prefix for validator public key
func (config *Config) GetBech32ValidatorPubPrefix() string {
return config.bech32AddressPrefix["validator_pub"]
}

// GetBech32ConsensusPubPrefix returns the Bech32 prefix for consensus node public key
func (config *Config) GetBech32ConsensusPubPrefix() string {
return config.bech32AddressPrefix["consensus_pub"]
}