github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/smartcontract/manifest/permission.go (about)

     1  package manifest
     2  
     3  import (
     4  	"crypto/elliptic"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  
     9  	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
    10  	"github.com/nspcc-dev/neo-go/pkg/util"
    11  	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
    12  )
    13  
    14  // PermissionType represents permission type.
    15  type PermissionType uint8
    16  
    17  const (
    18  	// PermissionWildcard allows everything.
    19  	PermissionWildcard PermissionType = 0
    20  	// PermissionHash restricts called contracts based on hash.
    21  	PermissionHash PermissionType = 1
    22  	// PermissionGroup restricts called contracts based on public key.
    23  	PermissionGroup PermissionType = 2
    24  )
    25  
    26  // PermissionDesc is a permission descriptor.
    27  type PermissionDesc struct {
    28  	Type  PermissionType
    29  	Value any
    30  }
    31  
    32  // Permission describes which contracts may be invoked and which methods are called.
    33  type Permission struct {
    34  	Contract PermissionDesc `json:"contract"`
    35  	Methods  WildStrings    `json:"methods"`
    36  }
    37  
    38  // Permissions is just an array of Permission.
    39  type Permissions []Permission
    40  
    41  type permissionAux struct {
    42  	Contract PermissionDesc `json:"contract"`
    43  	Methods  WildStrings    `json:"methods"`
    44  }
    45  
    46  // NewPermission returns a new permission of the given type.
    47  func NewPermission(typ PermissionType, args ...any) *Permission {
    48  	return &Permission{
    49  		Contract: *newPermissionDesc(typ, args...),
    50  	}
    51  }
    52  
    53  func newPermissionDesc(typ PermissionType, args ...any) *PermissionDesc {
    54  	desc := &PermissionDesc{Type: typ}
    55  	switch typ {
    56  	case PermissionWildcard:
    57  		if len(args) != 0 {
    58  			panic("wildcard permission has no arguments")
    59  		}
    60  	case PermissionHash:
    61  		if len(args) == 0 {
    62  			panic("hash permission should have an argument")
    63  		} else if u, ok := args[0].(util.Uint160); !ok {
    64  			panic("hash permission should have util.Uint160 argument")
    65  		} else {
    66  			desc.Value = u
    67  		}
    68  	case PermissionGroup:
    69  		if len(args) == 0 {
    70  			panic("group permission should have an argument")
    71  		} else if pub, ok := args[0].(*keys.PublicKey); !ok {
    72  			panic("group permission should have a public key argument")
    73  		} else {
    74  			desc.Value = pub
    75  		}
    76  	}
    77  	return desc
    78  }
    79  
    80  // Hash returns hash for hash-permission.
    81  func (d *PermissionDesc) Hash() util.Uint160 {
    82  	return d.Value.(util.Uint160)
    83  }
    84  
    85  // Group returns group's public key for group-permission.
    86  func (d *PermissionDesc) Group() *keys.PublicKey {
    87  	return d.Value.(*keys.PublicKey)
    88  }
    89  
    90  // Less returns true if this value is less than the given PermissionDesc value.
    91  func (d *PermissionDesc) Less(d1 PermissionDesc) bool {
    92  	if d.Type < d1.Type {
    93  		return true
    94  	}
    95  	if d.Type != d1.Type {
    96  		return false
    97  	}
    98  	switch d.Type {
    99  	case PermissionHash:
   100  		return d.Hash().Less(d1.Hash())
   101  	case PermissionGroup:
   102  		return d.Group().Cmp(d1.Group()) < 0
   103  	}
   104  	return false
   105  }
   106  
   107  // Equals returns true if both PermissionDesc values are the same.
   108  func (d *PermissionDesc) Equals(v PermissionDesc) bool {
   109  	if d.Type != v.Type {
   110  		return false
   111  	}
   112  	switch d.Type {
   113  	case PermissionHash:
   114  		return d.Hash().Equals(v.Hash())
   115  	case PermissionGroup:
   116  		return d.Group().Cmp(v.Group()) == 0
   117  	}
   118  	return false
   119  }
   120  
   121  // IsValid checks if Permission is correct.
   122  func (p *Permission) IsValid() error {
   123  	for i := range p.Methods.Value {
   124  		if p.Methods.Value[i] == "" {
   125  			return errors.New("empty method name")
   126  		}
   127  	}
   128  	if len(p.Methods.Value) < 2 {
   129  		return nil
   130  	}
   131  	names := make([]string, len(p.Methods.Value))
   132  	copy(names, p.Methods.Value)
   133  	if stringsHaveDups(names) {
   134  		return errors.New("duplicate method names")
   135  	}
   136  	return nil
   137  }
   138  
   139  // AreValid checks each Permission and ensures there are no duplicates.
   140  func (ps Permissions) AreValid() error {
   141  	for i := range ps {
   142  		err := ps[i].IsValid()
   143  		if err != nil {
   144  			return err
   145  		}
   146  	}
   147  	if len(ps) < 2 {
   148  		return nil
   149  	}
   150  	contracts := make([]PermissionDesc, 0, len(ps))
   151  	for i := range ps {
   152  		contracts = append(contracts, ps[i].Contract)
   153  	}
   154  	if permissionDescsHaveDups(contracts) {
   155  		return errors.New("contracts have duplicates")
   156  	}
   157  	return nil
   158  }
   159  
   160  // IsAllowed checks if the method is allowed to be executed.
   161  func (p *Permission) IsAllowed(hash util.Uint160, m *Manifest, method string) bool {
   162  	switch p.Contract.Type {
   163  	case PermissionWildcard:
   164  	case PermissionHash:
   165  		if !p.Contract.Hash().Equals(hash) {
   166  			return false
   167  		}
   168  	case PermissionGroup:
   169  		has := false
   170  		g := p.Contract.Group()
   171  		for i := range m.Groups {
   172  			if g.Equal(m.Groups[i].PublicKey) {
   173  				has = true
   174  				break
   175  			}
   176  		}
   177  		if !has {
   178  			return false
   179  		}
   180  	default:
   181  		panic(fmt.Sprintf("unexpected permission: %d", p.Contract.Type))
   182  	}
   183  	if p.Methods.IsWildcard() {
   184  		return true
   185  	}
   186  	return p.Methods.Contains(method)
   187  }
   188  
   189  // UnmarshalJSON implements the json.Unmarshaler interface.
   190  func (p *Permission) UnmarshalJSON(data []byte) error {
   191  	aux := new(permissionAux)
   192  	if err := json.Unmarshal(data, aux); err != nil {
   193  		return err
   194  	}
   195  	p.Contract = aux.Contract
   196  	p.Methods = aux.Methods
   197  	return nil
   198  }
   199  
   200  // MarshalJSON implements the json.Marshaler interface.
   201  func (d *PermissionDesc) MarshalJSON() ([]byte, error) {
   202  	switch d.Type {
   203  	case PermissionHash:
   204  		return json.Marshal("0x" + d.Hash().StringLE())
   205  	case PermissionGroup:
   206  		return json.Marshal(d.Group().StringCompressed())
   207  	default:
   208  		return []byte(`"*"`), nil
   209  	}
   210  }
   211  
   212  // UnmarshalJSON implements the json.Unmarshaler interface.
   213  func (d *PermissionDesc) UnmarshalJSON(data []byte) error {
   214  	var s string
   215  	if err := json.Unmarshal(data, &s); err != nil {
   216  		return err
   217  	}
   218  
   219  	const uint160HexSize = 2 * util.Uint160Size
   220  	switch len(s) {
   221  	case 2 + uint160HexSize:
   222  		// allow to unmarshal both hex and 0xhex forms
   223  		if s[0] != '0' || s[1] != 'x' {
   224  			return errors.New("invalid uint160")
   225  		}
   226  		s = s[2:]
   227  		fallthrough
   228  	case uint160HexSize:
   229  		u, err := util.Uint160DecodeStringLE(s)
   230  		if err != nil {
   231  			return err
   232  		}
   233  		d.Type = PermissionHash
   234  		d.Value = u
   235  		return nil
   236  	case 66:
   237  		pub, err := keys.NewPublicKeyFromString(s)
   238  		if err != nil {
   239  			return err
   240  		}
   241  		d.Type = PermissionGroup
   242  		d.Value = pub
   243  		return nil
   244  	case 1:
   245  		if s == "*" {
   246  			d.Type = PermissionWildcard
   247  			return nil
   248  		}
   249  	}
   250  	return errors.New("unknown permission")
   251  }
   252  
   253  // ToStackItem converts PermissionDesc to stackitem.Item.
   254  func (d *PermissionDesc) ToStackItem() stackitem.Item {
   255  	switch d.Type {
   256  	case PermissionWildcard:
   257  		return stackitem.Null{}
   258  	case PermissionHash:
   259  		return stackitem.NewByteArray(d.Hash().BytesBE())
   260  	case PermissionGroup:
   261  		return stackitem.NewByteArray(d.Group().Bytes())
   262  	default:
   263  		panic("unsupported PermissionDesc type")
   264  	}
   265  }
   266  
   267  // FromStackItem converts stackitem.Item to PermissionDesc.
   268  func (d *PermissionDesc) FromStackItem(item stackitem.Item) error {
   269  	if _, ok := item.(stackitem.Null); ok {
   270  		d.Type = PermissionWildcard
   271  		return nil
   272  	}
   273  	if item.Type() != stackitem.ByteArrayT {
   274  		return fmt.Errorf("unsupported permission descriptor type: %s", item.Type())
   275  	}
   276  	byteArr, err := item.TryBytes()
   277  	if err != nil {
   278  		return err
   279  	}
   280  	switch len(byteArr) {
   281  	case util.Uint160Size:
   282  		hash, _ := util.Uint160DecodeBytesBE(byteArr)
   283  		d.Type = PermissionHash
   284  		d.Value = hash
   285  	case 33:
   286  		pKey, err := keys.NewPublicKeyFromBytes(byteArr, elliptic.P256())
   287  		if err != nil {
   288  			return err
   289  		}
   290  		d.Type = PermissionGroup
   291  		d.Value = pKey
   292  	default:
   293  		return errors.New("invalid ByteArray length")
   294  	}
   295  	return nil
   296  }
   297  
   298  // ToStackItem converts Permission to stackitem.Item.
   299  func (p *Permission) ToStackItem() stackitem.Item {
   300  	var methods stackitem.Item
   301  	contract := p.Contract.ToStackItem()
   302  	if p.Methods.IsWildcard() {
   303  		methods = stackitem.Null{}
   304  	} else {
   305  		m := make([]stackitem.Item, len(p.Methods.Value))
   306  		for i := range p.Methods.Value {
   307  			m[i] = stackitem.Make(p.Methods.Value[i])
   308  		}
   309  		methods = stackitem.Make(m)
   310  	}
   311  	return stackitem.NewStruct([]stackitem.Item{
   312  		contract,
   313  		methods,
   314  	})
   315  }
   316  
   317  // FromStackItem converts stackitem.Item to Permission.
   318  func (p *Permission) FromStackItem(item stackitem.Item) error {
   319  	var err error
   320  	if item.Type() != stackitem.StructT {
   321  		return errors.New("invalid Permission stackitem type")
   322  	}
   323  	str := item.Value().([]stackitem.Item)
   324  	if len(str) != 2 {
   325  		return errors.New("invalid Permission stackitem length")
   326  	}
   327  	desc := new(PermissionDesc)
   328  	err = desc.FromStackItem(str[0])
   329  	if err != nil {
   330  		return fmt.Errorf("invalid Contract stackitem: %w", err)
   331  	}
   332  	p.Contract = *desc
   333  	if _, ok := str[1].(stackitem.Null); ok {
   334  		p.Methods = WildStrings{Value: nil}
   335  	} else {
   336  		if str[1].Type() != stackitem.ArrayT {
   337  			return errors.New("invalid Methods stackitem type")
   338  		}
   339  		methods := str[1].Value().([]stackitem.Item)
   340  		p.Methods = WildStrings{
   341  			Value: make([]string, len(methods)),
   342  		}
   343  		for i := range methods {
   344  			p.Methods.Value[i], err = stackitem.ToString(methods[i])
   345  			if err != nil {
   346  				return err
   347  			}
   348  		}
   349  	}
   350  	return nil
   351  }