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

ADR-28 derive address functions #9088

Merged
merged 9 commits into from
Apr 15, 2021
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* [\#8786](https://github.com/cosmos/cosmos-sdk/pull/8786) Enabled secp256r1 in x/auth.
* (rosetta) [\#8729](https://github.com/cosmos/cosmos-sdk/pull/8729) Data API fully supports balance tracking. Construction API can now construct any message supported by the application.
* [\#8754](https://github.com/cosmos/cosmos-sdk/pull/8875) Added support for reverse iteration to pagination.
* [#9088](https://github.com/cosmos/cosmos-sdk/pull/9088) Added implementation to ADR-28 Derived Addresses.

### Client Breaking Changes
* [\#8363](https://github.com/cosmos/cosmos-sdk/pull/8363) Addresses no longer have a fixed 20-byte length. From the SDK modules' point of view, any 1-255 bytes-long byte array is a valid address.
Expand Down Expand Up @@ -82,6 +83,8 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (x/bank) [\#8798](https://github.com/cosmos/cosmos-sdk/pull/8798) `GetTotalSupply` is removed in favour of `GetPaginatedTotalSupply`
* (x/bank/types) [\#9061](https://github.com/cosmos/cosmos-sdk/pull/9061) `AddressFromBalancesStore` now returns an error for invalid key instead of panic.



### State Machine Breaking

* (x/{bank,distrib,gov,slashing,staking}) [\#8363](https://github.com/cosmos/cosmos-sdk/issues/8363) Store keys have been modified to allow for variable-length addresses.
Expand Down
24 changes: 12 additions & 12 deletions docs/architecture/adr-028-public-key-addresses.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ type Addressable interface {
Address() []byte
}

func NewComposed(typ string, subaccounts []Addressable) []byte {
func Composed(typ string, subaccounts []Addressable) []byte {
alessio marked this conversation as resolved.
Show resolved Hide resolved
addresses = map(subaccounts, \a -> LengthPrefix(a.Address()))
addresses = sort(addresses)
return address.Hash(typ, addresses[0] + ... + addresses[n])
Expand Down Expand Up @@ -175,7 +175,7 @@ func (multisig PubKey) Address() {
prefix := fmt.Sprintf("%s/%d", proto.MessageName(multisig), multisig.Threshold)

// use the Composed function defined above
return address.NewComposed(prefix, keys)
return address.Composed(prefix, keys)
}
```

Expand All @@ -185,14 +185,14 @@ NOTE: this section is not finalize and it's in active discussion.

In Basic Address section we defined a module account address as:

```
```go
address.Hash("module", moduleName)
```

We use `"module"` as a schema type for all module derived addresses. Module accounts can have sub accounts. The derivation process has a defined order: module name, submodule key, subsubmodule key.
Module account addresses are heavily used in the SDK so it makes sense to optimize the derivation process: instead of using of using `LengthPrefix` for the module name, we use a null byte (`'\x00'`) as a separator. This works, because null byte is not a part of a valid module name.

```
```go
func Module(moduleName string, key []byte) []byte{
return Hash("module", []byte(moduleName) + 0 + key)
}
Expand All @@ -208,24 +208,24 @@ If we want to create an address for a module account depending on more than one
btcAtomAMM := address.Module("amm", btc.Addrress() + atom.Address()})
```

We can continue the derivation process and can create an address for a submodule account.
#### Derived Addresses

```
func Submodule(address []byte, derivationKey []byte) {
return Hash("module", address + derivationKey)
We must be able to cryptographically derive one address from another one. The derivation process must guarantee hash properties, hence we use the already defined `Hash` function:

```go
func Derive(address []byte, derivationKey []byte) []byte {
return Hash(addres, derivationKey)
}
```

NOTE: if `address` is not a hash based address (with `LEN` length) then we should use `LengthPrefix`. An alternative would be to use one `Module` function, which takes a slice of keys and mapped with `LengthPrefix`. For final version we need to validate what's the most common use.

Note: `Module` is a special case of the more general _derived_ address, where we set the `"module"` string for the _from address_.

**Example** For a cosmwasm smart-contract address we could use the following construction:
```
smartContractAddr := Submodule(Module("cosmwasm", smartContractsNamespace), smartContractKey)
smartContractAddr := Derived(Module("cosmwasm", smartContractsNamespace), []{smartContractKey})
```



### Schema Types

A `typ` parameter used in `Hash` function SHOULD be unique for each account type.
Expand Down
9 changes: 7 additions & 2 deletions types/address/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ func Hash(typ string, key []byte) []byte {
return hasher.Sum(nil)
}

// NewComposed creates a new address based on sub addresses.
func NewComposed(typ string, subAddresses []Addressable) ([]byte, error) {
// Compose creates a new address based on sub addresses.
func Compose(typ string, subAddresses []Addressable) ([]byte, error) {
blushi marked this conversation as resolved.
Show resolved Hide resolved
as := make([][]byte, len(subAddresses))
totalLen := 0
var err error
Expand Down Expand Up @@ -62,3 +62,8 @@ func Module(moduleName string, key []byte) []byte {
mKey := append([]byte(moduleName), 0)
return Hash("module", append(mKey, key...))
}

// Derive derives a new address from the main `address` and a derivation `key`.
func Derive(address []byte, key []byte) []byte {
return Hash(conv.UnsafeBytesToStr(address), key)
}
23 changes: 19 additions & 4 deletions types/address/hash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func (suite *AddressSuite) TestComposed() {
a2 := addrMock{[]byte{21, 22}}

typ := "multisig"
ac, err := NewComposed(typ, []Addressable{a1, a2})
ac, err := Compose(typ, []Addressable{a1, a2})
assert.NoError(err)
assert.Len(ac, Len)

Expand All @@ -45,18 +45,18 @@ func (suite *AddressSuite) TestComposed() {
assert.Equal(ac, ac2, "NewComposed works correctly")

// changing order of addresses shouldn't impact a composed address
ac2, err = NewComposed(typ, []Addressable{a2, a1})
ac2, err = Compose(typ, []Addressable{a2, a1})
assert.NoError(err)
assert.Len(ac2, Len)
assert.Equal(ac, ac2, "NewComposed is not sensitive for order")

// changing a type should change composed address
ac2, err = NewComposed(typ+"other", []Addressable{a2, a1})
ac2, err = Compose(typ+"other", []Addressable{a2, a1})
assert.NoError(err)
assert.NotEqual(ac, ac2, "NewComposed must be sensitive to type")

// changing order of addresses shouldn't impact a composed address
ac2, err = NewComposed(typ, []Addressable{a1, addrMock{make([]byte, 300, 300)}})
ac2, err = Compose(typ, []Addressable{a1, addrMock{make([]byte, 300, 300)}})
assert.Error(err)
assert.Contains(err.Error(), "should be max 255 bytes, got 300")
}
Expand All @@ -75,6 +75,21 @@ func (suite *AddressSuite) TestModule() {
assert.NotEqual(addr2, addr3, "changing key must change address")
}

func (suite *AddressSuite) TestDerive() {
assert := suite.Assert()
var addr, key1, key2 = []byte{1, 2}, []byte{3, 4}, []byte{1, 2}
d1 := Derive(addr, key1)
d2 := Derive(addr, key2)
d3 := Derive(key1, key2)
assert.Len(d1, Len)
assert.Len(d2, Len)
assert.Len(d3, Len)

assert.NotEqual(d1, d2)
assert.NotEqual(d1, d3)
assert.NotEqual(d2, d3)
}

type addrMock struct {
Addr []byte
}
Expand Down