ADR-28 derive address functions (#9088)
* update Derive functions * update adr-028 * changelog update * Apply suggestions from code review Co-authored-by: Marie Gauthier <marie.gauthier63@gmail.com> * review updates * remove DeriveMulti and rollback some changes in CHANGELOG * add noop error check Co-authored-by: Marie Gauthier <marie.gauthier63@gmail.com>
This commit is contained in:
parent
261c7ebd89
commit
e43edc4749
|
@ -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.
|
* [\#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.
|
* (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.
|
* [\#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
|
### 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.
|
* [\#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.
|
||||||
|
@ -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) [\#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.
|
* (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
|
### 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.
|
* (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.
|
||||||
|
|
|
@ -135,7 +135,7 @@ type Addressable interface {
|
||||||
Address() []byte
|
Address() []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewComposed(typ string, subaccounts []Addressable) []byte {
|
func Composed(typ string, subaccounts []Addressable) []byte {
|
||||||
addresses = map(subaccounts, \a -> LengthPrefix(a.Address()))
|
addresses = map(subaccounts, \a -> LengthPrefix(a.Address()))
|
||||||
addresses = sort(addresses)
|
addresses = sort(addresses)
|
||||||
return address.Hash(typ, addresses[0] + ... + addresses[n])
|
return address.Hash(typ, addresses[0] + ... + addresses[n])
|
||||||
|
@ -175,7 +175,7 @@ func (multisig PubKey) Address() {
|
||||||
prefix := fmt.Sprintf("%s/%d", proto.MessageName(multisig), multisig.Threshold)
|
prefix := fmt.Sprintf("%s/%d", proto.MessageName(multisig), multisig.Threshold)
|
||||||
|
|
||||||
// use the Composed function defined above
|
// use the Composed function defined above
|
||||||
return address.NewComposed(prefix, keys)
|
return address.Composed(prefix, keys)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -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:
|
In Basic Address section we defined a module account address as:
|
||||||
|
|
||||||
```
|
```go
|
||||||
address.Hash("module", moduleName)
|
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.
|
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.
|
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{
|
func Module(moduleName string, key []byte) []byte{
|
||||||
return Hash("module", []byte(moduleName) + 0 + key)
|
return Hash("module", []byte(moduleName) + 0 + key)
|
||||||
}
|
}
|
||||||
|
@ -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()})
|
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
|
||||||
|
|
||||||
```
|
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:
|
||||||
func Submodule(address []byte, derivationKey []byte) {
|
|
||||||
return Hash("module", address + derivationKey)
|
```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:
|
**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
|
### Schema Types
|
||||||
|
|
||||||
A `typ` parameter used in `Hash` function SHOULD be unique for each account type.
|
A `typ` parameter used in `Hash` function SHOULD be unique for each account type.
|
||||||
|
|
|
@ -20,20 +20,21 @@ type Addressable interface {
|
||||||
// Hash creates a new address from address type and key
|
// Hash creates a new address from address type and key
|
||||||
func Hash(typ string, key []byte) []byte {
|
func Hash(typ string, key []byte) []byte {
|
||||||
hasher := sha256.New()
|
hasher := sha256.New()
|
||||||
hasher.Write(conv.UnsafeStrToBytes(typ))
|
_, err := hasher.Write(conv.UnsafeStrToBytes(typ))
|
||||||
|
// the error always nil, it's here only to satisfy the io.Writer interface
|
||||||
|
errors.AssertNil(err)
|
||||||
th := hasher.Sum(nil)
|
th := hasher.Sum(nil)
|
||||||
|
|
||||||
hasher.Reset()
|
hasher.Reset()
|
||||||
_, err := hasher.Write(th)
|
_, err = hasher.Write(th)
|
||||||
// the error always nil, it's here only to satisfy the io.Writer interface
|
|
||||||
errors.AssertNil(err)
|
errors.AssertNil(err)
|
||||||
_, err = hasher.Write(key)
|
_, err = hasher.Write(key)
|
||||||
errors.AssertNil(err)
|
errors.AssertNil(err)
|
||||||
return hasher.Sum(nil)
|
return hasher.Sum(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewComposed creates a new address based on sub addresses.
|
// Compose creates a new address based on sub addresses.
|
||||||
func NewComposed(typ string, subAddresses []Addressable) ([]byte, error) {
|
func Compose(typ string, subAddresses []Addressable) ([]byte, error) {
|
||||||
as := make([][]byte, len(subAddresses))
|
as := make([][]byte, len(subAddresses))
|
||||||
totalLen := 0
|
totalLen := 0
|
||||||
var err error
|
var err error
|
||||||
|
@ -62,3 +63,8 @@ func Module(moduleName string, key []byte) []byte {
|
||||||
mKey := append([]byte(moduleName), 0)
|
mKey := append([]byte(moduleName), 0)
|
||||||
return Hash("module", append(mKey, key...))
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ func (suite *AddressSuite) TestComposed() {
|
||||||
a2 := addrMock{[]byte{21, 22}}
|
a2 := addrMock{[]byte{21, 22}}
|
||||||
|
|
||||||
typ := "multisig"
|
typ := "multisig"
|
||||||
ac, err := NewComposed(typ, []Addressable{a1, a2})
|
ac, err := Compose(typ, []Addressable{a1, a2})
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
assert.Len(ac, Len)
|
assert.Len(ac, Len)
|
||||||
|
|
||||||
|
@ -45,18 +45,18 @@ func (suite *AddressSuite) TestComposed() {
|
||||||
assert.Equal(ac, ac2, "NewComposed works correctly")
|
assert.Equal(ac, ac2, "NewComposed works correctly")
|
||||||
|
|
||||||
// changing order of addresses shouldn't impact a composed address
|
// 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.NoError(err)
|
||||||
assert.Len(ac2, Len)
|
assert.Len(ac2, Len)
|
||||||
assert.Equal(ac, ac2, "NewComposed is not sensitive for order")
|
assert.Equal(ac, ac2, "NewComposed is not sensitive for order")
|
||||||
|
|
||||||
// changing a type should change composed address
|
// 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.NoError(err)
|
||||||
assert.NotEqual(ac, ac2, "NewComposed must be sensitive to type")
|
assert.NotEqual(ac, ac2, "NewComposed must be sensitive to type")
|
||||||
|
|
||||||
// changing order of addresses shouldn't impact a composed address
|
// 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.Error(err)
|
||||||
assert.Contains(err.Error(), "should be max 255 bytes, got 300")
|
assert.Contains(err.Error(), "should be max 255 bytes, got 300")
|
||||||
}
|
}
|
||||||
|
@ -75,6 +75,21 @@ func (suite *AddressSuite) TestModule() {
|
||||||
assert.NotEqual(addr2, addr3, "changing key must change address")
|
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 {
|
type addrMock struct {
|
||||||
Addr []byte
|
Addr []byte
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue