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 := ¶mContext{ 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 }