From dc3967ae7b364d4f28eefd8a0416e62f04b633d8 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 8 Feb 2021 22:58:22 +0300 Subject: [PATCH] manifest: add manifest validity checks Refs. #1699. --- cli/testdata/deploy/neo-go.yml | 1 + pkg/smartcontract/manifest/group.go | 31 ++++++++++++++++ pkg/smartcontract/manifest/group_test.go | 25 +++++++++++++ pkg/smartcontract/manifest/manifest.go | 38 +++++++++++++++++-- pkg/smartcontract/manifest/manifest_test.go | 41 ++++++++++++++++++++- 5 files changed, 131 insertions(+), 5 deletions(-) diff --git a/cli/testdata/deploy/neo-go.yml b/cli/testdata/deploy/neo-go.yml index e69de29bb..c38e0355b 100644 --- a/cli/testdata/deploy/neo-go.yml +++ b/cli/testdata/deploy/neo-go.yml @@ -0,0 +1 @@ +name: Test deploy diff --git a/pkg/smartcontract/manifest/group.go b/pkg/smartcontract/manifest/group.go index 30b39dfee..920f17d38 100644 --- a/pkg/smartcontract/manifest/group.go +++ b/pkg/smartcontract/manifest/group.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "encoding/json" "errors" + "sort" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" @@ -20,6 +21,9 @@ type Group struct { Signature []byte `json:"signature"` } +// Groups is just an array of Group. +type Groups []Group + type groupAux struct { PublicKey string `json:"pubkey"` Signature []byte `json:"signature"` @@ -33,6 +37,33 @@ func (g *Group) IsValid(h util.Uint160) error { return nil } +// AreValid checks for groups correctness and uniqueness. +func (g Groups) AreValid(h util.Uint160) error { + for i := range g { + err := g[i].IsValid(h) + if err != nil { + return err + } + } + if len(g) < 2 { + return nil + } + pkeys := make(keys.PublicKeys, len(g)) + for i := range g { + pkeys[i] = g[i].PublicKey + } + sort.Sort(pkeys) + for i := range pkeys { + if i == 0 { + continue + } + if pkeys[i].Cmp(pkeys[i-1]) == 0 { + return errors.New("duplicate group keys") + } + } + return nil +} + // MarshalJSON implements json.Marshaler interface. func (g *Group) MarshalJSON() ([]byte, error) { aux := &groupAux{ diff --git a/pkg/smartcontract/manifest/group_test.go b/pkg/smartcontract/manifest/group_test.go index 833e2ae9f..464ada130 100644 --- a/pkg/smartcontract/manifest/group_test.go +++ b/pkg/smartcontract/manifest/group_test.go @@ -5,6 +5,7 @@ import ( "github.com/nspcc-dev/neo-go/internal/testserdes" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/util" "github.com/stretchr/testify/require" ) @@ -16,3 +17,27 @@ func TestGroupJSONInOut(t *testing.T) { g := Group{pub, sig} testserdes.MarshalUnmarshalJSON(t, &g, new(Group)) } + +func TestGroupsAreValid(t *testing.T) { + h := util.Uint160{42, 42, 42} + priv, err := keys.NewPrivateKey() + require.NoError(t, err) + priv2, err := keys.NewPrivateKey() + require.NoError(t, err) + pub := priv.PublicKey() + pub2 := priv2.PublicKey() + gcorrect := Group{pub, priv.Sign(h.BytesBE())} + gcorrect2 := Group{pub2, priv2.Sign(h.BytesBE())} + gincorrect := Group{pub, priv.Sign(h.BytesLE())} + gps := Groups{gcorrect} + require.NoError(t, gps.AreValid(h)) + + gps = Groups{gincorrect} + require.Error(t, gps.AreValid(h)) + + gps = Groups{gcorrect, gcorrect2} + require.NoError(t, gps.AreValid(h)) + + gps = Groups{gcorrect, gcorrect} + require.Error(t, gps.AreValid(h)) +} diff --git a/pkg/smartcontract/manifest/manifest.go b/pkg/smartcontract/manifest/manifest.go index 10dddf9ea..c7bbd0fe7 100644 --- a/pkg/smartcontract/manifest/manifest.go +++ b/pkg/smartcontract/manifest/manifest.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "math" + "sort" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" @@ -75,14 +76,43 @@ func (m *Manifest) CanCall(hash util.Uint160, toCall *Manifest, method string) b func (m *Manifest) IsValid(hash util.Uint160) error { var err error + if m.Name == "" { + return errors.New("no name") + } + + for i := range m.SupportedStandards { + if m.SupportedStandards[i] == "" { + return errors.New("invalid nameless supported standard") + } + } + if len(m.SupportedStandards) > 1 { + names := make([]string, len(m.SupportedStandards)) + copy(names, m.SupportedStandards) + if stringsHaveDups(names) { + return errors.New("duplicate supported standards") + } + } err = m.ABI.IsValid() if err != nil { return err } - for _, g := range m.Groups { - err = g.IsValid(hash) - if err != nil { - return err + err = Groups(m.Groups).AreValid(hash) + if err != nil { + return err + } + if len(m.Trusts.Value) > 1 { + hashes := make([]util.Uint160, 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") + } } } return Permissions(m.Permissions).AreValid() diff --git a/pkg/smartcontract/manifest/manifest_test.go b/pkg/smartcontract/manifest/manifest_test.go index 3e8239f30..60a917252 100644 --- a/pkg/smartcontract/manifest/manifest_test.go +++ b/pkg/smartcontract/manifest/manifest_test.go @@ -109,7 +109,13 @@ func TestPermission_IsAllowed(t *testing.T) { func TestIsValid(t *testing.T) { contractHash := util.Uint160{1, 2, 3} - m := NewManifest("Test") + m := &Manifest{} + + t.Run("invalid, no name", func(t *testing.T) { + require.Error(t, m.IsValid(contractHash)) + }) + + m = NewManifest("Test") t.Run("invalid, no ABI methods", func(t *testing.T) { require.Error(t, m.IsValid(contractHash)) @@ -158,6 +164,39 @@ func TestIsValid(t *testing.T) { }) m.Permissions = m.Permissions[:1] + m.SupportedStandards = append(m.SupportedStandards, "NEP-17") + t.Run("valid, with standards", func(t *testing.T) { + require.NoError(t, m.IsValid(contractHash)) + }) + + m.SupportedStandards = append(m.SupportedStandards, "") + t.Run("invalid, with nameless standard", func(t *testing.T) { + require.Error(t, m.IsValid(contractHash)) + }) + m.SupportedStandards = m.SupportedStandards[:1] + + m.SupportedStandards = append(m.SupportedStandards, "NEP-17") + t.Run("invalid, with duplicate standards", func(t *testing.T) { + require.Error(t, m.IsValid(contractHash)) + }) + m.SupportedStandards = m.SupportedStandards[:1] + + m.Trusts.Add(util.Uint160{1, 2, 3}) + t.Run("valid, with trust", func(t *testing.T) { + require.NoError(t, m.IsValid(contractHash)) + }) + + m.Trusts.Add(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}) + t.Run("invalid, with trusts", func(t *testing.T) { + require.Error(t, m.IsValid(contractHash)) + }) + m.Trusts.Restrict() + t.Run("with groups", func(t *testing.T) { m.Groups = make([]Group, 3) pks := make([]*keys.PrivateKey, 3)