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

     1  package context
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/hex"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"sort"
    10  	"strings"
    11  
    12  	"github.com/nspcc-dev/neo-go/pkg/config/netmode"
    13  	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
    14  	"github.com/nspcc-dev/neo-go/pkg/crypto"
    15  	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
    16  	"github.com/nspcc-dev/neo-go/pkg/io"
    17  	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
    18  	"github.com/nspcc-dev/neo-go/pkg/util"
    19  	"github.com/nspcc-dev/neo-go/pkg/vm"
    20  	"github.com/nspcc-dev/neo-go/pkg/vm/emit"
    21  	"github.com/nspcc-dev/neo-go/pkg/wallet"
    22  )
    23  
    24  // TransactionType is the ParameterContext Type used for transactions.
    25  const TransactionType = "Neo.Network.P2P.Payloads.Transaction"
    26  
    27  // compatTransactionType is the old, 2.x type used for transactions.
    28  const compatTransactionType = "Neo.Core.ContractTransaction"
    29  
    30  // ParameterContext represents smartcontract parameter's context.
    31  type ParameterContext struct {
    32  	// Type is a type of a verifiable item.
    33  	Type string
    34  	// Network is a network this context belongs to.
    35  	Network netmode.Magic
    36  	// Verifiable is an object which can be (de-)serialized.
    37  	Verifiable crypto.VerifiableDecodable
    38  	// Items is a map from script hashes to context items.
    39  	Items map[util.Uint160]*Item
    40  }
    41  
    42  type paramContext struct {
    43  	Type  string                     `json:"type"`
    44  	Net   uint32                     `json:"network"`
    45  	Hash  util.Uint256               `json:"hash,omitempty"`
    46  	Data  []byte                     `json:"data"`
    47  	Items map[string]json.RawMessage `json:"items"`
    48  }
    49  
    50  type sigWithIndex struct {
    51  	index int
    52  	sig   []byte
    53  }
    54  
    55  // NewParameterContext returns ParameterContext with the specified type and item to sign.
    56  func NewParameterContext(typ string, network netmode.Magic, verif crypto.VerifiableDecodable) *ParameterContext {
    57  	return &ParameterContext{
    58  		Type:       typ,
    59  		Network:    network,
    60  		Verifiable: verif,
    61  		Items:      make(map[util.Uint160]*Item),
    62  	}
    63  }
    64  
    65  // GetCompleteTransaction clears transaction witnesses (if any) and refills them with
    66  // signatures from the parameter context.
    67  func (c *ParameterContext) GetCompleteTransaction() (*transaction.Transaction, error) {
    68  	tx, ok := c.Verifiable.(*transaction.Transaction)
    69  	if !ok {
    70  		return nil, errors.New("verifiable item is not a transaction")
    71  	}
    72  	if len(tx.Scripts) > 0 {
    73  		tx.Scripts = tx.Scripts[:0]
    74  	}
    75  	for i := range tx.Signers {
    76  		w, err := c.GetWitness(tx.Signers[i].Account)
    77  		if err != nil {
    78  			return nil, fmt.Errorf("can't create witness for signer #%d: %w", i, err)
    79  		}
    80  		tx.Scripts = append(tx.Scripts, *w)
    81  	}
    82  	return tx, nil
    83  }
    84  
    85  // GetWitness returns invocation and verification scripts for the specified contract.
    86  func (c *ParameterContext) GetWitness(h util.Uint160) (*transaction.Witness, error) {
    87  	item, ok := c.Items[h]
    88  	if !ok {
    89  		return nil, errors.New("witness not found")
    90  	}
    91  	bw := io.NewBufBinWriter()
    92  	for i := range item.Parameters {
    93  		if item.Parameters[i].Type != smartcontract.SignatureType {
    94  			return nil, fmt.Errorf("unsupported %s parameter #%d", item.Parameters[i].Type.String(), i)
    95  		} else if item.Parameters[i].Value == nil {
    96  			return nil, fmt.Errorf("no value for parameter #%d (not signed yet?)", i)
    97  		}
    98  		emit.Bytes(bw.BinWriter, item.Parameters[i].Value.([]byte))
    99  	}
   100  	return &transaction.Witness{
   101  		InvocationScript:   bw.Bytes(),
   102  		VerificationScript: item.Script,
   103  	}, nil
   104  }
   105  
   106  // AddSignature adds a signature for the specified contract and public key.
   107  func (c *ParameterContext) AddSignature(h util.Uint160, ctr *wallet.Contract, pub *keys.PublicKey, sig []byte) error {
   108  	item := c.getItemForContract(h, ctr)
   109  	if _, pubs, ok := vm.ParseMultiSigContract(ctr.Script); ok {
   110  		if item.GetSignature(pub) != nil {
   111  			return errors.New("signature is already added")
   112  		}
   113  		pubBytes := pub.Bytes()
   114  		var contained bool
   115  		for i := range pubs {
   116  			if bytes.Equal(pubBytes, pubs[i]) {
   117  				contained = true
   118  				break
   119  			}
   120  		}
   121  		if !contained {
   122  			return errors.New("public key is not present in script")
   123  		}
   124  		item.AddSignature(pub, sig)
   125  		if len(item.Signatures) >= len(ctr.Parameters) {
   126  			indexMap := map[string]int{}
   127  			for i := range pubs {
   128  				indexMap[hex.EncodeToString(pubs[i])] = i
   129  			}
   130  			sigs := make([]sigWithIndex, len(item.Parameters))
   131  			var i int
   132  			for pub, sig := range item.Signatures {
   133  				sigs[i] = sigWithIndex{index: indexMap[pub], sig: sig}
   134  				i++
   135  				if i == len(sigs) {
   136  					break
   137  				}
   138  			}
   139  			sort.Slice(sigs, func(i, j int) bool {
   140  				return sigs[i].index < sigs[j].index
   141  			})
   142  			for i := range sigs {
   143  				item.Parameters[i] = smartcontract.Parameter{
   144  					Type:  smartcontract.SignatureType,
   145  					Value: sigs[i].sig,
   146  				}
   147  			}
   148  		}
   149  		return nil
   150  	}
   151  
   152  	index := -1
   153  	for i := range ctr.Parameters {
   154  		if ctr.Parameters[i].Type == smartcontract.SignatureType {
   155  			if index >= 0 {
   156  				return errors.New("multiple signature parameters in non-multisig contract")
   157  			}
   158  			index = i
   159  		}
   160  	}
   161  	if index != -1 {
   162  		item.Parameters[index].Value = sig
   163  	} else if !ctr.Deployed {
   164  		return errors.New("missing signature parameter")
   165  	}
   166  	return nil
   167  }
   168  
   169  func (c *ParameterContext) getItemForContract(h util.Uint160, ctr *wallet.Contract) *Item {
   170  	item, ok := c.Items[ctr.ScriptHash()]
   171  	if ok {
   172  		return item
   173  	}
   174  	params := make([]smartcontract.Parameter, len(ctr.Parameters))
   175  	for i := range params {
   176  		params[i].Type = ctr.Parameters[i].Type
   177  	}
   178  	script := ctr.Script
   179  	if ctr.Deployed {
   180  		script = nil
   181  	}
   182  	item = &Item{
   183  		Script:     script,
   184  		Parameters: params,
   185  		Signatures: make(map[string][]byte),
   186  	}
   187  	c.Items[h] = item
   188  	return item
   189  }
   190  
   191  // MarshalJSON implements the json.Marshaler interface.
   192  func (c ParameterContext) MarshalJSON() ([]byte, error) {
   193  	verif, err := c.Verifiable.EncodeHashableFields()
   194  	if err != nil {
   195  		return nil, fmt.Errorf("failed to encode hashable fields")
   196  	}
   197  	items := make(map[string]json.RawMessage, len(c.Items))
   198  	for u := range c.Items {
   199  		data, err := json.Marshal(c.Items[u])
   200  		if err != nil {
   201  			return nil, err
   202  		}
   203  		items["0x"+u.StringLE()] = data
   204  	}
   205  	pc := &paramContext{
   206  		Type:  c.Type,
   207  		Net:   uint32(c.Network),
   208  		Hash:  c.Verifiable.Hash(),
   209  		Data:  verif,
   210  		Items: items,
   211  	}
   212  	return json.Marshal(pc)
   213  }
   214  
   215  // UnmarshalJSON implements the json.Unmarshaler interface.
   216  func (c *ParameterContext) UnmarshalJSON(data []byte) error {
   217  	pc := new(paramContext)
   218  	if err := json.Unmarshal(data, pc); err != nil {
   219  		return err
   220  	}
   221  
   222  	var verif crypto.VerifiableDecodable
   223  	switch pc.Type {
   224  	case compatTransactionType, TransactionType:
   225  		tx := new(transaction.Transaction)
   226  		verif = tx
   227  	default:
   228  		return fmt.Errorf("unsupported type: %s", c.Type)
   229  	}
   230  	err := verif.DecodeHashableFields(pc.Data)
   231  	if err != nil {
   232  		return err
   233  	}
   234  	items := make(map[util.Uint160]*Item, len(pc.Items))
   235  	for h := range pc.Items {
   236  		u, err := util.Uint160DecodeStringLE(strings.TrimPrefix(h, "0x"))
   237  		if err != nil {
   238  			return err
   239  		}
   240  		item := new(Item)
   241  		if err := json.Unmarshal(pc.Items[h], item); err != nil {
   242  			return err
   243  		}
   244  		items[u] = item
   245  	}
   246  	if !pc.Hash.Equals(util.Uint256{}) {
   247  		if !verif.Hash().Equals(pc.Hash) {
   248  			return fmt.Errorf("hash parameter doesn't match calculated verifiable hash: %s vs %s", pc.Hash.StringLE(), verif.Hash().StringLE())
   249  		}
   250  	}
   251  	c.Type = pc.Type
   252  	c.Network = netmode.Magic(pc.Net)
   253  	c.Verifiable = verif
   254  	c.Items = items
   255  	return nil
   256  }