smartcontract: use permission descriptors for manifest's trusts

This commit is contained in:
Anna Shaleva 2021-05-04 14:48:50 +03:00
parent 4fb421738b
commit 50fc9bf766
7 changed files with 200 additions and 145 deletions

View file

@ -7,7 +7,6 @@ import (
"github.com/nspcc-dev/neo-go/internal/testserdes" "github.com/nspcc-dev/neo-go/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -263,8 +262,8 @@ func _deploy(data interface{}, isUpdate bool) {}
Methods: manifest.WildStrings{}, Methods: manifest.WildStrings{},
}, },
}, },
Trusts: manifest.WildUint160s{ Trusts: manifest.WildPermissionDescs{
Value: []util.Uint160{}, Value: []manifest.PermissionDesc{},
}, },
Extra: json.RawMessage("null"), Extra: json.RawMessage("null"),
} }

View file

@ -7,8 +7,6 @@ package manifest
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"github.com/nspcc-dev/neo-go/pkg/util"
) )
// WildStrings represents string set which can be wildcard. // WildStrings represents string set which can be wildcard.
@ -16,9 +14,9 @@ type WildStrings struct {
Value []string Value []string
} }
// WildUint160s represents Uint160 set which can be wildcard. // WildPermissionDescs represents PermissionDescriptor set which can be wildcard.
type WildUint160s struct { type WildPermissionDescs struct {
Value []util.Uint160 Value []PermissionDesc
} }
// Contains checks if v is in the container. // Contains checks if v is in the container.
@ -35,7 +33,7 @@ func (c *WildStrings) Contains(v string) bool {
} }
// Contains checks if v is in the container. // Contains checks if v is in the container.
func (c *WildUint160s) Contains(v util.Uint160) bool { func (c *WildPermissionDescs) Contains(v PermissionDesc) bool {
if c.IsWildcard() { if c.IsWildcard() {
return true return true
} }
@ -51,19 +49,19 @@ func (c *WildUint160s) Contains(v util.Uint160) bool {
func (c *WildStrings) IsWildcard() bool { return c.Value == nil } func (c *WildStrings) IsWildcard() bool { return c.Value == nil }
// IsWildcard returns true iff container is wildcard. // IsWildcard returns true iff container is wildcard.
func (c *WildUint160s) IsWildcard() bool { return c.Value == nil } func (c *WildPermissionDescs) IsWildcard() bool { return c.Value == nil }
// Restrict transforms container into an empty one. // Restrict transforms container into an empty one.
func (c *WildStrings) Restrict() { c.Value = []string{} } func (c *WildStrings) Restrict() { c.Value = []string{} }
// Restrict transforms container into an empty one. // Restrict transforms container into an empty one.
func (c *WildUint160s) Restrict() { c.Value = []util.Uint160{} } func (c *WildPermissionDescs) Restrict() { c.Value = []PermissionDesc{} }
// Add adds v to the container. // Add adds v to the container.
func (c *WildStrings) Add(v string) { c.Value = append(c.Value, v) } func (c *WildStrings) Add(v string) { c.Value = append(c.Value, v) }
// Add adds v to the container. // Add adds v to the container.
func (c *WildUint160s) Add(v util.Uint160) { c.Value = append(c.Value, v) } func (c *WildPermissionDescs) Add(v PermissionDesc) { c.Value = append(c.Value, v) }
// MarshalJSON implements json.Marshaler interface. // MarshalJSON implements json.Marshaler interface.
func (c WildStrings) MarshalJSON() ([]byte, error) { func (c WildStrings) MarshalJSON() ([]byte, error) {
@ -74,7 +72,7 @@ func (c WildStrings) MarshalJSON() ([]byte, error) {
} }
// MarshalJSON implements json.Marshaler interface. // MarshalJSON implements json.Marshaler interface.
func (c WildUint160s) MarshalJSON() ([]byte, error) { func (c WildPermissionDescs) MarshalJSON() ([]byte, error) {
if c.IsWildcard() { if c.IsWildcard() {
return []byte(`"*"`), nil return []byte(`"*"`), nil
} }
@ -94,9 +92,9 @@ func (c *WildStrings) UnmarshalJSON(data []byte) error {
} }
// UnmarshalJSON implements json.Unmarshaler interface. // UnmarshalJSON implements json.Unmarshaler interface.
func (c *WildUint160s) UnmarshalJSON(data []byte) error { func (c *WildPermissionDescs) UnmarshalJSON(data []byte) error {
if !bytes.Equal(data, []byte(`"*"`)) { if !bytes.Equal(data, []byte(`"*"`)) {
us := []util.Uint160{} us := []PermissionDesc{}
if err := json.Unmarshal(data, &us); err != nil { if err := json.Unmarshal(data, &us); err != nil {
return err return err
} }

View file

@ -6,7 +6,7 @@ import (
"github.com/nspcc-dev/neo-go/internal/random" "github.com/nspcc-dev/neo-go/internal/random"
"github.com/nspcc-dev/neo-go/internal/testserdes" "github.com/nspcc-dev/neo-go/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -21,15 +21,30 @@ func TestContainer_Restrict(t *testing.T) {
require.Equal(t, 0, len(c.Value)) require.Equal(t, 0, len(c.Value))
}) })
t.Run("uint160", func(t *testing.T) { t.Run("PermissionDesc", func(t *testing.T) {
c := new(WildUint160s) check := func(t *testing.T, u PermissionDesc) {
u := random.Uint160() c := new(WildPermissionDescs)
require.True(t, c.IsWildcard()) require.True(t, c.IsWildcard())
require.True(t, c.Contains(u)) require.True(t, c.Contains(u))
c.Restrict() c.Restrict()
require.False(t, c.IsWildcard()) require.False(t, c.IsWildcard())
require.False(t, c.Contains(u)) require.False(t, c.Contains(u))
require.Equal(t, 0, len(c.Value)) require.Equal(t, 0, len(c.Value))
}
t.Run("Hash", func(t *testing.T) {
check(t, PermissionDesc{
Type: PermissionHash,
Value: random.Uint160(),
})
})
t.Run("Group", func(t *testing.T) {
pk, err := keys.NewPrivateKey()
require.NoError(t, err)
check(t, PermissionDesc{
Type: PermissionGroup,
Value: pk.PublicKey(),
})
})
}) })
} }
@ -44,17 +59,24 @@ func TestContainer_Add(t *testing.T) {
}) })
t.Run("uint160", func(t *testing.T) { t.Run("uint160", func(t *testing.T) {
c := new(WildUint160s) c := new(WildPermissionDescs)
require.Equal(t, []util.Uint160(nil), c.Value) require.Equal(t, []PermissionDesc(nil), c.Value)
pk, err := keys.NewPrivateKey()
exp := []util.Uint160{random.Uint160(), random.Uint160()} require.NoError(t, err)
exp := []PermissionDesc{
{Type: PermissionHash, Value: random.Uint160()},
{Type: PermissionGroup, Value: pk.PublicKey()},
}
for i := range exp { for i := range exp {
c.Add(exp[i]) c.Add(exp[i])
} }
for i := range exp { for i := range exp {
require.True(t, c.Contains(exp[i])) require.True(t, c.Contains(exp[i]))
} }
require.False(t, c.Contains(random.Uint160())) pkRand, err := keys.NewPrivateKey()
require.NoError(t, err)
require.False(t, c.Contains(PermissionDesc{Type: PermissionHash, Value: random.Uint160()}))
require.False(t, c.Contains(PermissionDesc{Type: PermissionGroup, Value: pkRand.PublicKey()}))
}) })
} }
@ -85,27 +107,30 @@ func TestContainer_MarshalJSON(t *testing.T) {
}) })
}) })
t.Run("uint160", func(t *testing.T) { t.Run("PermissionDesc", func(t *testing.T) {
t.Run("wildcard", func(t *testing.T) { t.Run("wildcard", func(t *testing.T) {
expected := new(WildUint160s) expected := new(WildPermissionDescs)
testserdes.MarshalUnmarshalJSON(t, expected, new(WildUint160s)) testserdes.MarshalUnmarshalJSON(t, expected, new(WildPermissionDescs))
}) })
t.Run("empty", func(t *testing.T) { t.Run("empty", func(t *testing.T) {
expected := new(WildUint160s) expected := new(WildPermissionDescs)
expected.Restrict() expected.Restrict()
testserdes.MarshalUnmarshalJSON(t, expected, new(WildUint160s)) testserdes.MarshalUnmarshalJSON(t, expected, new(WildPermissionDescs))
}) })
t.Run("non-empty", func(t *testing.T) { t.Run("non-empty", func(t *testing.T) {
expected := new(WildUint160s) expected := new(WildPermissionDescs)
expected.Add(random.Uint160()) expected.Add(PermissionDesc{
testserdes.MarshalUnmarshalJSON(t, expected, new(WildUint160s)) Type: PermissionHash,
Value: random.Uint160(),
})
testserdes.MarshalUnmarshalJSON(t, expected, new(WildPermissionDescs))
}) })
t.Run("invalid", func(t *testing.T) { t.Run("invalid", func(t *testing.T) {
js := []byte(`["notahex"]`) js := []byte(`["notahex"]`)
c := new(WildUint160s) c := new(WildPermissionDescs)
require.Error(t, json.Unmarshal(js, c)) require.Error(t, json.Unmarshal(js, c))
}) })
}) })

View file

@ -4,7 +4,6 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"math" "math"
"sort"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
@ -38,7 +37,7 @@ type Manifest struct {
// SupportedStandards is a list of standards supported by the contract. // SupportedStandards is a list of standards supported by the contract.
SupportedStandards []string `json:"supportedstandards"` SupportedStandards []string `json:"supportedstandards"`
// Trusts is a set of hashes to a which contract trusts. // Trusts is a set of hashes to a which contract trusts.
Trusts WildUint160s `json:"trusts"` Trusts WildPermissionDescs `json:"trusts"`
// Extra is an implementation-defined user data. // Extra is an implementation-defined user data.
Extra json.RawMessage `json:"extra"` Extra json.RawMessage `json:"extra"`
} }
@ -109,18 +108,10 @@ func (m *Manifest) IsValid(hash util.Uint160) error {
return err return err
} }
if len(m.Trusts.Value) > 1 { if len(m.Trusts.Value) > 1 {
hashes := make([]util.Uint160, len(m.Trusts.Value)) hashes := make([]PermissionDesc, len(m.Trusts.Value))
copy(hashes, m.Trusts.Value) copy(hashes, m.Trusts.Value)
sort.Slice(hashes, func(i, j int) bool { if permissionDescsHaveDups(hashes) {
return hashes[i].Less(hashes[j]) return errors.New("duplicate trusted contracts")
})
for i := range hashes {
if i == 0 {
continue
}
if hashes[i] == hashes[i-1] {
return errors.New("duplicate trusted contracts")
}
} }
} }
return Permissions(m.Permissions).AreValid() return Permissions(m.Permissions).AreValid()
@ -144,8 +135,8 @@ func (m *Manifest) ToStackItem() (stackitem.Item, error) {
trusts := stackitem.Item(stackitem.Null{}) trusts := stackitem.Item(stackitem.Null{})
if !m.Trusts.IsWildcard() { if !m.Trusts.IsWildcard() {
tItems := make([]stackitem.Item, len(m.Trusts.Value)) tItems := make([]stackitem.Item, len(m.Trusts.Value))
for i := range m.Trusts.Value { for i, v := range m.Trusts.Value {
tItems[i] = stackitem.NewByteArray(m.Trusts.Value[i].BytesBE()) tItems[i] = v.ToStackItem()
} }
trusts = stackitem.Make(tItems) trusts = stackitem.Make(tItems)
} }
@ -231,16 +222,14 @@ func (m *Manifest) FromStackItem(item stackitem.Item) error {
return errors.New("invalid Trusts stackitem type") return errors.New("invalid Trusts stackitem type")
} }
trusts := str[6].Value().([]stackitem.Item) trusts := str[6].Value().([]stackitem.Item)
m.Trusts = WildUint160s{Value: make([]util.Uint160, len(trusts))} m.Trusts = WildPermissionDescs{Value: make([]PermissionDesc, len(trusts))}
for i := range trusts { for i := range trusts {
bytes, err := trusts[i].TryBytes() v := new(PermissionDesc)
if err != nil { err = v.FromStackItem(trusts[i])
return err
}
m.Trusts.Value[i], err = util.Uint160DecodeBytesBE(bytes)
if err != nil { if err != nil {
return err return err
} }
m.Trusts.Value[i] = *v
} }
} }
extra, err := str[7].TryBytes() extra, err := str[7].TryBytes()

View file

@ -181,17 +181,18 @@ func TestIsValid(t *testing.T) {
}) })
m.SupportedStandards = m.SupportedStandards[:1] m.SupportedStandards = m.SupportedStandards[:1]
m.Trusts.Add(util.Uint160{1, 2, 3}) d := PermissionDesc{Type: PermissionHash, Value: util.Uint160{1, 2, 3}}
m.Trusts.Add(d)
t.Run("valid, with trust", func(t *testing.T) { t.Run("valid, with trust", func(t *testing.T) {
require.NoError(t, m.IsValid(contractHash)) require.NoError(t, m.IsValid(contractHash))
}) })
m.Trusts.Add(util.Uint160{3, 2, 1}) m.Trusts.Add(PermissionDesc{Type: PermissionHash, Value: util.Uint160{3, 2, 1}})
t.Run("valid, with trusts", func(t *testing.T) { t.Run("valid, with trusts", func(t *testing.T) {
require.NoError(t, m.IsValid(contractHash)) require.NoError(t, m.IsValid(contractHash))
}) })
m.Trusts.Add(util.Uint160{1, 2, 3}) m.Trusts.Add(d)
t.Run("invalid, with trusts", func(t *testing.T) { t.Run("invalid, with trusts", func(t *testing.T) {
require.Error(t, m.IsValid(contractHash)) require.Error(t, m.IsValid(contractHash))
}) })
@ -276,8 +277,17 @@ func TestManifestToStackItem(t *testing.T) {
}}, }},
Permissions: []Permission{*NewPermission(PermissionWildcard)}, Permissions: []Permission{*NewPermission(PermissionWildcard)},
SupportedStandards: []string{"NEP-17"}, SupportedStandards: []string{"NEP-17"},
Trusts: WildUint160s{ Trusts: WildPermissionDescs{
Value: []util.Uint160{{1, 2, 3}}, Value: []PermissionDesc{
{
Type: PermissionHash,
Value: util.Uint160{1, 2, 3},
},
{
Type: PermissionGroup,
Value: pk.PublicKey(),
},
},
}, },
Extra: []byte(`even not a json allowed`), Extra: []byte(`even not a json allowed`),
} }

View file

@ -104,3 +104,32 @@ func stringsHaveDups(strings []string) bool {
} }
return false return false
} }
// permissionDescsHaveDups checks given set of strings for duplicates. It modifies the slice given!
func permissionDescsHaveDups(descs []PermissionDesc) bool {
sort.Slice(descs, func(i, j int) bool {
return descs[i].Less(descs[j])
})
for i := range descs {
if i == 0 {
continue
}
j := i - 1
if descs[i].Type != descs[j].Type {
continue
}
switch descs[i].Type {
case PermissionWildcard:
return true
case PermissionHash:
if descs[i].Hash() == descs[j].Hash() {
return true
}
case PermissionGroup:
if descs[i].Group().Cmp(descs[j].Group()) == 0 {
return true
}
}
}
return false
}

View file

@ -6,7 +6,6 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"sort"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
@ -89,6 +88,37 @@ func (d *PermissionDesc) Group() *keys.PublicKey {
return d.Value.(*keys.PublicKey) return d.Value.(*keys.PublicKey)
} }
// Less returns true if this value is less than given PermissionDesc value.
func (d *PermissionDesc) Less(d1 PermissionDesc) bool {
if d.Type < d1.Type {
return true
}
if d.Type != d1.Type {
return false
}
switch d.Type {
case PermissionHash:
return d.Hash().Less(d1.Hash())
case PermissionGroup:
return d.Group().Cmp(d1.Group()) < 0
}
return false
}
// Equals returns true if both PermissionDesc values are the same.
func (d *PermissionDesc) Equals(v PermissionDesc) bool {
if d.Type != v.Type {
return false
}
switch d.Type {
case PermissionHash:
return d.Hash().Equals(v.Hash())
case PermissionGroup:
return d.Group().Cmp(v.Group()) == 0
}
return false
}
// IsValid checks if Permission is correct. // IsValid checks if Permission is correct.
func (p *Permission) IsValid() error { func (p *Permission) IsValid() error {
for i := range p.Methods.Value { for i := range p.Methods.Value {
@ -122,45 +152,8 @@ func (ps Permissions) AreValid() error {
for i := range ps { for i := range ps {
contracts = append(contracts, ps[i].Contract) contracts = append(contracts, ps[i].Contract)
} }
sort.Slice(contracts, func(i, j int) bool { if permissionDescsHaveDups(contracts) {
if contracts[i].Type < contracts[j].Type { return errors.New("contracts have duplicates")
return true
}
if contracts[i].Type != contracts[j].Type {
return false
}
switch contracts[i].Type {
case PermissionHash:
return contracts[i].Hash().Less(contracts[j].Hash())
case PermissionGroup:
return contracts[i].Group().Cmp(contracts[j].Group()) < 0
}
return false
})
for i := range contracts {
if i == 0 {
continue
}
j := i - 1
if contracts[i].Type != contracts[j].Type {
continue
}
var bad bool
switch contracts[i].Type {
case PermissionWildcard:
bad = true
case PermissionHash:
if contracts[i].Hash() == contracts[j].Hash() {
bad = true
}
case PermissionGroup:
if contracts[i].Group().Cmp(contracts[j].Group()) == 0 {
bad = true
}
}
if bad {
return errors.New("duplicate contracts")
}
} }
return nil return nil
} }
@ -254,20 +247,55 @@ func (d *PermissionDesc) UnmarshalJSON(data []byte) error {
return errors.New("unknown permission") return errors.New("unknown permission")
} }
// ToStackItem converts PermissionDesc to stackitem.Item.
func (d *PermissionDesc) ToStackItem() stackitem.Item {
switch d.Type {
case PermissionWildcard:
return stackitem.Null{}
case PermissionHash:
return stackitem.NewByteArray(d.Hash().BytesBE())
case PermissionGroup:
return stackitem.NewByteArray(d.Group().Bytes())
default:
panic("unsupported PermissionDesc type")
}
}
// FromStackItem converts stackitem.Item to PermissionDesc.
func (d *PermissionDesc) FromStackItem(item stackitem.Item) error {
if _, ok := item.(stackitem.Null); ok {
d.Type = PermissionWildcard
return nil
}
if item.Type() != stackitem.ByteArrayT {
return fmt.Errorf("unsupported permission descriptor type: %s", item.Type())
}
byteArr, err := item.TryBytes()
if err != nil {
return err
}
switch len(byteArr) {
case util.Uint160Size:
hash, _ := util.Uint160DecodeBytesBE(byteArr)
d.Type = PermissionHash
d.Value = hash
case 33:
pKey, err := keys.NewPublicKeyFromBytes(byteArr, elliptic.P256())
if err != nil {
return err
}
d.Type = PermissionGroup
d.Value = pKey
default:
return errors.New("invalid ByteArray length")
}
return nil
}
// ToStackItem converts Permission to stackitem.Item. // ToStackItem converts Permission to stackitem.Item.
func (p *Permission) ToStackItem() stackitem.Item { func (p *Permission) ToStackItem() stackitem.Item {
var ( var methods stackitem.Item
contract stackitem.Item contract := p.Contract.ToStackItem()
methods stackitem.Item
)
switch p.Contract.Type {
case PermissionWildcard:
contract = stackitem.Null{}
case PermissionHash:
contract = stackitem.NewByteArray(p.Contract.Hash().BytesBE())
case PermissionGroup:
contract = stackitem.NewByteArray(p.Contract.Group().Bytes())
}
if p.Methods.IsWildcard() { if p.Methods.IsWildcard() {
methods = stackitem.Null{} methods = stackitem.Null{}
} else { } else {
@ -293,35 +321,12 @@ func (p *Permission) FromStackItem(item stackitem.Item) error {
if len(str) != 2 { if len(str) != 2 {
return errors.New("invalid Permission stackitem length") return errors.New("invalid Permission stackitem length")
} }
if _, ok := str[0].(stackitem.Null); ok { desc := new(PermissionDesc)
p.Contract = PermissionDesc{ err = desc.FromStackItem(str[0])
Type: PermissionWildcard, if err != nil {
} return fmt.Errorf("invalid Contract stackitem: %w", err)
} else {
byteArr, err := str[0].TryBytes()
if err != nil {
return err
}
switch len(byteArr) {
case util.Uint160Size:
hash, _ := util.Uint160DecodeBytesBE(byteArr)
p.Contract = PermissionDesc{
Type: PermissionHash,
Value: hash,
}
case 33:
pKey, err := keys.NewPublicKeyFromBytes(byteArr, elliptic.P256())
if err != nil {
return err
}
p.Contract = PermissionDesc{
Type: PermissionGroup,
Value: pKey,
}
default:
return errors.New("invalid Contract ByteArray length")
}
} }
p.Contract = *desc
if _, ok := str[1].(stackitem.Null); ok { if _, ok := str[1].(stackitem.Null); ok {
p.Methods = WildStrings{Value: nil} p.Methods = WildStrings{Value: nil}
} else { } else {