From 50fc9bf7661db92fd55d866d1059fcbd61fbf078 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 4 May 2021 14:48:50 +0300 Subject: [PATCH] smartcontract: use permission descriptors for manifest's trusts --- pkg/compiler/debug_test.go | 5 +- pkg/smartcontract/manifest/container.go | 22 ++- pkg/smartcontract/manifest/container_test.go | 73 +++++--- pkg/smartcontract/manifest/manifest.go | 31 ++-- pkg/smartcontract/manifest/manifest_test.go | 20 ++- pkg/smartcontract/manifest/parameter.go | 29 ++++ pkg/smartcontract/manifest/permission.go | 165 ++++++++++--------- 7 files changed, 200 insertions(+), 145 deletions(-) diff --git a/pkg/compiler/debug_test.go b/pkg/compiler/debug_test.go index 08d5da8f9..bb29280c5 100644 --- a/pkg/compiler/debug_test.go +++ b/pkg/compiler/debug_test.go @@ -7,7 +7,6 @@ import ( "github.com/nspcc-dev/neo-go/internal/testserdes" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "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/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -263,8 +262,8 @@ func _deploy(data interface{}, isUpdate bool) {} Methods: manifest.WildStrings{}, }, }, - Trusts: manifest.WildUint160s{ - Value: []util.Uint160{}, + Trusts: manifest.WildPermissionDescs{ + Value: []manifest.PermissionDesc{}, }, Extra: json.RawMessage("null"), } diff --git a/pkg/smartcontract/manifest/container.go b/pkg/smartcontract/manifest/container.go index 3424d9e51..e125b5fbf 100644 --- a/pkg/smartcontract/manifest/container.go +++ b/pkg/smartcontract/manifest/container.go @@ -7,8 +7,6 @@ package manifest import ( "bytes" "encoding/json" - - "github.com/nspcc-dev/neo-go/pkg/util" ) // WildStrings represents string set which can be wildcard. @@ -16,9 +14,9 @@ type WildStrings struct { Value []string } -// WildUint160s represents Uint160 set which can be wildcard. -type WildUint160s struct { - Value []util.Uint160 +// WildPermissionDescs represents PermissionDescriptor set which can be wildcard. +type WildPermissionDescs struct { + Value []PermissionDesc } // 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. -func (c *WildUint160s) Contains(v util.Uint160) bool { +func (c *WildPermissionDescs) Contains(v PermissionDesc) bool { if c.IsWildcard() { return true } @@ -51,19 +49,19 @@ func (c *WildUint160s) Contains(v util.Uint160) bool { func (c *WildStrings) IsWildcard() bool { return c.Value == nil } // 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. func (c *WildStrings) Restrict() { c.Value = []string{} } // 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. func (c *WildStrings) Add(v string) { c.Value = append(c.Value, v) } // 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. func (c WildStrings) MarshalJSON() ([]byte, error) { @@ -74,7 +72,7 @@ func (c WildStrings) MarshalJSON() ([]byte, error) { } // MarshalJSON implements json.Marshaler interface. -func (c WildUint160s) MarshalJSON() ([]byte, error) { +func (c WildPermissionDescs) MarshalJSON() ([]byte, error) { if c.IsWildcard() { return []byte(`"*"`), nil } @@ -94,9 +92,9 @@ func (c *WildStrings) UnmarshalJSON(data []byte) error { } // UnmarshalJSON implements json.Unmarshaler interface. -func (c *WildUint160s) UnmarshalJSON(data []byte) error { +func (c *WildPermissionDescs) UnmarshalJSON(data []byte) error { if !bytes.Equal(data, []byte(`"*"`)) { - us := []util.Uint160{} + us := []PermissionDesc{} if err := json.Unmarshal(data, &us); err != nil { return err } diff --git a/pkg/smartcontract/manifest/container_test.go b/pkg/smartcontract/manifest/container_test.go index c8f8679fd..8f8b238b9 100644 --- a/pkg/smartcontract/manifest/container_test.go +++ b/pkg/smartcontract/manifest/container_test.go @@ -6,7 +6,7 @@ import ( "github.com/nspcc-dev/neo-go/internal/random" "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" ) @@ -21,15 +21,30 @@ func TestContainer_Restrict(t *testing.T) { require.Equal(t, 0, len(c.Value)) }) - t.Run("uint160", func(t *testing.T) { - c := new(WildUint160s) - u := random.Uint160() - require.True(t, c.IsWildcard()) - require.True(t, c.Contains(u)) - c.Restrict() - require.False(t, c.IsWildcard()) - require.False(t, c.Contains(u)) - require.Equal(t, 0, len(c.Value)) + t.Run("PermissionDesc", func(t *testing.T) { + check := func(t *testing.T, u PermissionDesc) { + c := new(WildPermissionDescs) + require.True(t, c.IsWildcard()) + require.True(t, c.Contains(u)) + c.Restrict() + require.False(t, c.IsWildcard()) + require.False(t, c.Contains(u)) + 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) { - c := new(WildUint160s) - require.Equal(t, []util.Uint160(nil), c.Value) - - exp := []util.Uint160{random.Uint160(), random.Uint160()} + c := new(WildPermissionDescs) + require.Equal(t, []PermissionDesc(nil), c.Value) + pk, err := keys.NewPrivateKey() + require.NoError(t, err) + exp := []PermissionDesc{ + {Type: PermissionHash, Value: random.Uint160()}, + {Type: PermissionGroup, Value: pk.PublicKey()}, + } for i := range exp { c.Add(exp[i]) } for i := range exp { 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) { - expected := new(WildUint160s) - testserdes.MarshalUnmarshalJSON(t, expected, new(WildUint160s)) + expected := new(WildPermissionDescs) + testserdes.MarshalUnmarshalJSON(t, expected, new(WildPermissionDescs)) }) t.Run("empty", func(t *testing.T) { - expected := new(WildUint160s) + expected := new(WildPermissionDescs) expected.Restrict() - testserdes.MarshalUnmarshalJSON(t, expected, new(WildUint160s)) + testserdes.MarshalUnmarshalJSON(t, expected, new(WildPermissionDescs)) }) t.Run("non-empty", func(t *testing.T) { - expected := new(WildUint160s) - expected.Add(random.Uint160()) - testserdes.MarshalUnmarshalJSON(t, expected, new(WildUint160s)) + expected := new(WildPermissionDescs) + expected.Add(PermissionDesc{ + Type: PermissionHash, + Value: random.Uint160(), + }) + testserdes.MarshalUnmarshalJSON(t, expected, new(WildPermissionDescs)) }) t.Run("invalid", func(t *testing.T) { js := []byte(`["notahex"]`) - c := new(WildUint160s) + c := new(WildPermissionDescs) require.Error(t, json.Unmarshal(js, c)) }) }) diff --git a/pkg/smartcontract/manifest/manifest.go b/pkg/smartcontract/manifest/manifest.go index fcf9751b0..2efed7b23 100644 --- a/pkg/smartcontract/manifest/manifest.go +++ b/pkg/smartcontract/manifest/manifest.go @@ -4,7 +4,6 @@ import ( "encoding/json" "errors" "math" - "sort" "github.com/nspcc-dev/neo-go/pkg/util" "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 []string `json:"supportedstandards"` // 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 json.RawMessage `json:"extra"` } @@ -109,18 +108,10 @@ func (m *Manifest) IsValid(hash util.Uint160) error { return err } 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) - sort.Slice(hashes, func(i, j int) bool { - return hashes[i].Less(hashes[j]) - }) - for i := range hashes { - if i == 0 { - continue - } - if hashes[i] == hashes[i-1] { - return errors.New("duplicate trusted contracts") - } + if permissionDescsHaveDups(hashes) { + return errors.New("duplicate trusted contracts") } } return Permissions(m.Permissions).AreValid() @@ -144,8 +135,8 @@ func (m *Manifest) ToStackItem() (stackitem.Item, error) { trusts := stackitem.Item(stackitem.Null{}) if !m.Trusts.IsWildcard() { tItems := make([]stackitem.Item, len(m.Trusts.Value)) - for i := range m.Trusts.Value { - tItems[i] = stackitem.NewByteArray(m.Trusts.Value[i].BytesBE()) + for i, v := range m.Trusts.Value { + tItems[i] = v.ToStackItem() } trusts = stackitem.Make(tItems) } @@ -231,16 +222,14 @@ func (m *Manifest) FromStackItem(item stackitem.Item) error { return errors.New("invalid Trusts stackitem type") } 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 { - bytes, err := trusts[i].TryBytes() - if err != nil { - return err - } - m.Trusts.Value[i], err = util.Uint160DecodeBytesBE(bytes) + v := new(PermissionDesc) + err = v.FromStackItem(trusts[i]) if err != nil { return err } + m.Trusts.Value[i] = *v } } extra, err := str[7].TryBytes() diff --git a/pkg/smartcontract/manifest/manifest_test.go b/pkg/smartcontract/manifest/manifest_test.go index 2d6dbeeaf..9582f35e3 100644 --- a/pkg/smartcontract/manifest/manifest_test.go +++ b/pkg/smartcontract/manifest/manifest_test.go @@ -181,17 +181,18 @@ func TestIsValid(t *testing.T) { }) 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) { 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) { 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) { require.Error(t, m.IsValid(contractHash)) }) @@ -276,8 +277,17 @@ func TestManifestToStackItem(t *testing.T) { }}, Permissions: []Permission{*NewPermission(PermissionWildcard)}, SupportedStandards: []string{"NEP-17"}, - Trusts: WildUint160s{ - Value: []util.Uint160{{1, 2, 3}}, + Trusts: WildPermissionDescs{ + Value: []PermissionDesc{ + { + Type: PermissionHash, + Value: util.Uint160{1, 2, 3}, + }, + { + Type: PermissionGroup, + Value: pk.PublicKey(), + }, + }, }, Extra: []byte(`even not a json allowed`), } diff --git a/pkg/smartcontract/manifest/parameter.go b/pkg/smartcontract/manifest/parameter.go index e739a52f5..cbee93dbc 100644 --- a/pkg/smartcontract/manifest/parameter.go +++ b/pkg/smartcontract/manifest/parameter.go @@ -104,3 +104,32 @@ func stringsHaveDups(strings []string) bool { } 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 +} diff --git a/pkg/smartcontract/manifest/permission.go b/pkg/smartcontract/manifest/permission.go index e1c7cd6b1..2fa91cb69 100644 --- a/pkg/smartcontract/manifest/permission.go +++ b/pkg/smartcontract/manifest/permission.go @@ -6,7 +6,6 @@ import ( "encoding/json" "errors" "fmt" - "sort" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/util" @@ -89,6 +88,37 @@ func (d *PermissionDesc) Group() *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. func (p *Permission) IsValid() error { for i := range p.Methods.Value { @@ -122,45 +152,8 @@ func (ps Permissions) AreValid() error { for i := range ps { contracts = append(contracts, ps[i].Contract) } - sort.Slice(contracts, func(i, j int) bool { - if contracts[i].Type < contracts[j].Type { - 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") - } + if permissionDescsHaveDups(contracts) { + return errors.New("contracts have duplicates") } return nil } @@ -254,20 +247,55 @@ func (d *PermissionDesc) UnmarshalJSON(data []byte) error { 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. func (p *Permission) ToStackItem() stackitem.Item { - var ( - contract stackitem.Item - 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()) - } + var methods stackitem.Item + contract := p.Contract.ToStackItem() if p.Methods.IsWildcard() { methods = stackitem.Null{} } else { @@ -293,35 +321,12 @@ func (p *Permission) FromStackItem(item stackitem.Item) error { if len(str) != 2 { return errors.New("invalid Permission stackitem length") } - if _, ok := str[0].(stackitem.Null); ok { - p.Contract = PermissionDesc{ - Type: PermissionWildcard, - } - } 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") - } + desc := new(PermissionDesc) + err = desc.FromStackItem(str[0]) + if err != nil { + return fmt.Errorf("invalid Contract stackitem: %w", err) } + p.Contract = *desc if _, ok := str[1].(stackitem.Null); ok { p.Methods = WildStrings{Value: nil} } else {