forked from TrueCloudLab/neoneo-go
Merge pull request #706 from nspcc-dev/feature/transfer
cli: implement transfer from multisig accounts
This commit is contained in:
commit
d03b2ef4a1
14 changed files with 801 additions and 24 deletions
135
cli/wallet/multisig.go
Normal file
135
cli/wallet/multisig.go
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
package wallet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/context"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newMultisigCommands() []cli.Command {
|
||||||
|
return []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "sign",
|
||||||
|
Usage: "sign a transaction",
|
||||||
|
UsageText: "multisig sign --path <path> --addr <addr> --in <file.in> --out <file.out>",
|
||||||
|
Action: signMultisig,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
walletPathFlag,
|
||||||
|
rpcFlag,
|
||||||
|
timeoutFlag,
|
||||||
|
outFlag,
|
||||||
|
inFlag,
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "addr",
|
||||||
|
Usage: "Address to use",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func signMultisig(ctx *cli.Context) error {
|
||||||
|
wall, err := openWallet(ctx.String("path"))
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
defer wall.Close()
|
||||||
|
|
||||||
|
c, err := readParameterContext(ctx.String("in"))
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
addr := ctx.String("addr")
|
||||||
|
sh, err := address.StringToUint160(addr)
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(fmt.Errorf("invalid address: %v", err), 1)
|
||||||
|
}
|
||||||
|
acc := wall.GetAccount(sh)
|
||||||
|
if acc == nil {
|
||||||
|
return cli.NewExitError(fmt.Errorf("can't find account for the address: %s", addr), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, ok := c.Verifiable.(*transaction.Transaction)
|
||||||
|
if !ok {
|
||||||
|
return cli.NewExitError("verifiable item is not a transaction", 1)
|
||||||
|
}
|
||||||
|
printTxInfo(tx)
|
||||||
|
fmt.Println("Enter password to unlock wallet and sign the transaction")
|
||||||
|
pass, err := readPassword("Password > ")
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
} else if err := acc.Decrypt(pass); err != nil {
|
||||||
|
return cli.NewExitError(fmt.Errorf("can't unlock an account: %v", err), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
priv := acc.PrivateKey()
|
||||||
|
sign := priv.Sign(tx.GetSignedPart())
|
||||||
|
if err := c.AddSignature(acc.Contract, priv.PublicKey(), sign); err != nil {
|
||||||
|
return cli.NewExitError(fmt.Errorf("can't add signature: %v", err), 1)
|
||||||
|
} else if err := writeParameterContext(c, ctx.String("out")); err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
if endpoint := ctx.String("rpc"); endpoint != "" {
|
||||||
|
w, err := c.GetWitness(acc.Contract)
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
tx.Scripts = append(tx.Scripts, *w)
|
||||||
|
|
||||||
|
gctx, cancel := getGoContext(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
c, err := client.New(gctx, ctx.String("rpc"), client.Options{})
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
} else if err := c.SendRawTransaction(tx); err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(tx.Hash().StringLE())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readParameterContext(filename string) (*context.ParameterContext, error) {
|
||||||
|
data, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't read input file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := new(context.ParameterContext)
|
||||||
|
if err := json.Unmarshal(data, c); err != nil {
|
||||||
|
return nil, fmt.Errorf("can't parse transaction: %v", err)
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeParameterContext(c *context.ParameterContext, filename string) error {
|
||||||
|
if data, err := json.Marshal(c); err != nil {
|
||||||
|
return fmt.Errorf("can't marshal transaction: %v", err)
|
||||||
|
} else if err := ioutil.WriteFile(filename, data, 0644); err != nil {
|
||||||
|
return fmt.Errorf("can't write transaction to file: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTxInfo(t *transaction.Transaction) {
|
||||||
|
fmt.Printf("Hash: %s\n", t.Hash().StringLE())
|
||||||
|
for i := range t.Inputs {
|
||||||
|
fmt.Printf("Input%02d: [%2d] %s\n", i, t.Inputs[i].PrevIndex, t.Inputs[i].PrevHash.StringLE())
|
||||||
|
}
|
||||||
|
for i := range t.Outputs {
|
||||||
|
fmt.Printf("Output%02d:\n", i)
|
||||||
|
fmt.Printf("\tAssetID : %s\n", t.Outputs[i].AssetID.StringLE())
|
||||||
|
fmt.Printf("\tAmount : %s\n", t.Outputs[i].Amount.String())
|
||||||
|
h := t.Outputs[i].ScriptHash
|
||||||
|
fmt.Printf("\tScriptHash: %s\n", t.Outputs[i].ScriptHash.StringLE())
|
||||||
|
fmt.Printf("\tToAddr : %s\n", address.Uint160ToString(h))
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,8 +3,10 @@ package wallet
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
@ -15,6 +17,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpc/request"
|
"github.com/nspcc-dev/neo-go/pkg/rpc/request"
|
||||||
|
context2 "github.com/nspcc-dev/neo-go/pkg/smartcontract/context"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
|
@ -47,6 +50,14 @@ var (
|
||||||
Name: "timeout, t",
|
Name: "timeout, t",
|
||||||
Usage: "Timeout for the operation",
|
Usage: "Timeout for the operation",
|
||||||
}
|
}
|
||||||
|
outFlag = cli.StringFlag{
|
||||||
|
Name: "out",
|
||||||
|
Usage: "file to put JSON transaction to",
|
||||||
|
}
|
||||||
|
inFlag = cli.StringFlag{
|
||||||
|
Name: "in",
|
||||||
|
Usage: "file with JSON transaction",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewCommands returns 'wallet' command.
|
// NewCommands returns 'wallet' command.
|
||||||
|
@ -144,12 +155,13 @@ func NewCommands() []cli.Command {
|
||||||
Name: "transfer",
|
Name: "transfer",
|
||||||
Usage: "transfer NEO/GAS",
|
Usage: "transfer NEO/GAS",
|
||||||
UsageText: "transfer --path <path> --from <addr> --to <addr>" +
|
UsageText: "transfer --path <path> --from <addr> --to <addr>" +
|
||||||
" --amount <amount> --asset [NEO|GAS|<hex-id>]",
|
" --amount <amount> --asset [NEO|GAS|<hex-id>] [--out <path>]",
|
||||||
Action: transferAsset,
|
Action: transferAsset,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
walletPathFlag,
|
walletPathFlag,
|
||||||
rpcFlag,
|
rpcFlag,
|
||||||
timeoutFlag,
|
timeoutFlag,
|
||||||
|
outFlag,
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "from",
|
Name: "from",
|
||||||
Usage: "Address to send an asset from",
|
Usage: "Address to send an asset from",
|
||||||
|
@ -168,6 +180,11 @@ func NewCommands() []cli.Command {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "multisig",
|
||||||
|
Usage: "work with multisig address",
|
||||||
|
Subcommands: newMultisigCommands(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
@ -416,7 +433,6 @@ func transferAsset(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
tx := transaction.NewContractTX()
|
tx := transaction.NewContractTX()
|
||||||
tx.Data = new(transaction.ContractTX)
|
|
||||||
if err := request.AddInputsAndUnspentsToTx(tx, from, asset, amount, c); err != nil {
|
if err := request.AddInputsAndUnspentsToTx(tx, from, asset, amount, c); err != nil {
|
||||||
return cli.NewExitError(err, 1)
|
return cli.NewExitError(err, 1)
|
||||||
}
|
}
|
||||||
|
@ -432,9 +448,23 @@ func transferAsset(ctx *cli.Context) error {
|
||||||
Position: 1,
|
Position: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
_ = acc.SignTx(tx)
|
if outFile := ctx.String("out"); outFile != "" {
|
||||||
if err := c.SendRawTransaction(tx); err != nil {
|
priv := acc.PrivateKey()
|
||||||
return cli.NewExitError(err, 1)
|
pub := priv.PublicKey()
|
||||||
|
sign := priv.Sign(tx.GetSignedPart())
|
||||||
|
c := context2.NewParameterContext("Neo.Core.ContractTransaction", tx)
|
||||||
|
if err := c.AddSignature(acc.Contract, pub, sign); err != nil {
|
||||||
|
return cli.NewExitError(fmt.Errorf("can't add signature: %v", err), 1)
|
||||||
|
} else if data, err := json.Marshal(c); err != nil {
|
||||||
|
return cli.NewExitError(fmt.Errorf("can't marshal tx to JSON: %v", err), 1)
|
||||||
|
} else if err := ioutil.WriteFile(outFile, data, 0644); err != nil {
|
||||||
|
return cli.NewExitError(fmt.Errorf("can't write tx to file: %v", err), 1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_ = acc.SignTx(tx)
|
||||||
|
if err := c.SendRawTransaction(tx); err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(tx.Hash().StringLE())
|
fmt.Println(tx.Hash().StringLE())
|
||||||
|
|
|
@ -24,6 +24,13 @@ type Output struct {
|
||||||
Position int `json:"n"`
|
Position int `json:"n"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type outputAux struct {
|
||||||
|
AssetID util.Uint256 `json:"asset"`
|
||||||
|
Amount util.Fixed8 `json:"value"`
|
||||||
|
ScriptHash string `json:"address"`
|
||||||
|
Position int `json:"n"`
|
||||||
|
}
|
||||||
|
|
||||||
// NewOutput returns a new transaction output.
|
// NewOutput returns a new transaction output.
|
||||||
func NewOutput(assetID util.Uint256, amount util.Fixed8, scriptHash util.Uint160) *Output {
|
func NewOutput(assetID util.Uint256, amount util.Fixed8, scriptHash util.Uint160) *Output {
|
||||||
return &Output{
|
return &Output{
|
||||||
|
@ -56,3 +63,20 @@ func (out *Output) MarshalJSON() ([]byte, error) {
|
||||||
"n": out.Position,
|
"n": out.Position,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaler interface.
|
||||||
|
func (out *Output) UnmarshalJSON(data []byte) error {
|
||||||
|
var outAux outputAux
|
||||||
|
err := json.Unmarshal(data, &outAux)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out.ScriptHash, err = address.StringToUint160(outAux.ScriptHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out.Amount = outAux.Amount
|
||||||
|
out.AssetID = outAux.AssetID
|
||||||
|
out.Position = outAux.Position
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -158,7 +158,8 @@ func (t *Transaction) EncodeBinary(bw *io.BinWriter) {
|
||||||
// encodeHashableFields encodes the fields that are not used for
|
// encodeHashableFields encodes the fields that are not used for
|
||||||
// signing the transaction, which are all fields except the scripts.
|
// signing the transaction, which are all fields except the scripts.
|
||||||
func (t *Transaction) encodeHashableFields(bw *io.BinWriter) {
|
func (t *Transaction) encodeHashableFields(bw *io.BinWriter) {
|
||||||
if t.Data == nil {
|
noData := t.Type == ContractType
|
||||||
|
if t.Data == nil && !noData {
|
||||||
bw.Err = errors.New("transaction has no data")
|
bw.Err = errors.New("transaction has no data")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -166,7 +167,9 @@ func (t *Transaction) encodeHashableFields(bw *io.BinWriter) {
|
||||||
bw.WriteB(byte(t.Version))
|
bw.WriteB(byte(t.Version))
|
||||||
|
|
||||||
// Underlying TXer.
|
// Underlying TXer.
|
||||||
t.Data.EncodeBinary(bw)
|
if !noData {
|
||||||
|
t.Data.EncodeBinary(bw)
|
||||||
|
}
|
||||||
|
|
||||||
// Attributes
|
// Attributes
|
||||||
bw.WriteArray(t.Attributes)
|
bw.WriteArray(t.Attributes)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package transaction
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
|
@ -175,3 +176,23 @@ func TestEncodingTXWithNoData(t *testing.T) {
|
||||||
tx.EncodeBinary(buf.BinWriter)
|
tx.EncodeBinary(buf.BinWriter)
|
||||||
require.Error(t, buf.Err)
|
require.Error(t, buf.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalJSON(t *testing.T) {
|
||||||
|
tx := NewContractTX()
|
||||||
|
tx.Outputs = []Output{{
|
||||||
|
AssetID: util.Uint256{1, 2, 3, 4},
|
||||||
|
Amount: 567,
|
||||||
|
ScriptHash: util.Uint160{7, 8, 9, 10},
|
||||||
|
Position: 13,
|
||||||
|
}}
|
||||||
|
tx.Scripts = []Witness{{
|
||||||
|
InvocationScript: []byte{5, 3, 1},
|
||||||
|
VerificationScript: []byte{2, 4, 6},
|
||||||
|
}}
|
||||||
|
data, err := json.Marshal(tx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
txNew := new(Transaction)
|
||||||
|
require.NoError(t, json.Unmarshal(data, txNew))
|
||||||
|
require.Equal(t, tx, txNew)
|
||||||
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ func (w *Witness) EncodeBinary(bw *io.BinWriter) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements the json marshaller interface.
|
// MarshalJSON implements the json marshaller interface.
|
||||||
func (w *Witness) MarshalJSON() ([]byte, error) {
|
func (w Witness) MarshalJSON() ([]byte, error) {
|
||||||
data := map[string]string{
|
data := map[string]string{
|
||||||
"invocation": hex.EncodeToString(w.InvocationScript),
|
"invocation": hex.EncodeToString(w.InvocationScript),
|
||||||
"verification": hex.EncodeToString(w.VerificationScript),
|
"verification": hex.EncodeToString(w.VerificationScript),
|
||||||
|
@ -37,6 +37,20 @@ func (w *Witness) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(data)
|
return json.Marshal(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaler interface.
|
||||||
|
func (w *Witness) UnmarshalJSON(data []byte) error {
|
||||||
|
m := map[string]string{}
|
||||||
|
err := json.Unmarshal(data, &m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if w.InvocationScript, err = hex.DecodeString(m["invocation"]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.VerificationScript, err = hex.DecodeString(m["verification"])
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// ScriptHash returns the hash of the VerificationScript.
|
// ScriptHash returns the hash of the VerificationScript.
|
||||||
func (w Witness) ScriptHash() util.Uint160 {
|
func (w Witness) ScriptHash() util.Uint160 {
|
||||||
return hash.Hash160(w.VerificationScript)
|
return hash.Hash160(w.VerificationScript)
|
||||||
|
|
207
pkg/smartcontract/context/context.go
Normal file
207
pkg/smartcontract/context/context.go
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParameterContext represents smartcontract parameter's context.
|
||||||
|
type ParameterContext struct {
|
||||||
|
// Type is a type of a verifiable item.
|
||||||
|
Type string
|
||||||
|
// Verifiable is an object which can be (de-)serialized.
|
||||||
|
Verifiable io.Serializable
|
||||||
|
// Items is a map from script hashes to context items.
|
||||||
|
Items map[util.Uint160]*Item
|
||||||
|
}
|
||||||
|
|
||||||
|
type paramContext struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Hex string `json:"hex"`
|
||||||
|
Items map[string]json.RawMessage `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type sigWithIndex struct {
|
||||||
|
index int
|
||||||
|
sig []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParameterContext returns ParameterContext with the specified type and item to sign.
|
||||||
|
func NewParameterContext(typ string, verif io.Serializable) *ParameterContext {
|
||||||
|
return &ParameterContext{
|
||||||
|
Type: typ,
|
||||||
|
Verifiable: verif,
|
||||||
|
Items: make(map[util.Uint160]*Item),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWitness returns invocation and verification scripts for the specified contract.
|
||||||
|
func (c *ParameterContext) GetWitness(ctr *wallet.Contract) (*transaction.Witness, error) {
|
||||||
|
item := c.getItemForContract(ctr)
|
||||||
|
bw := io.NewBufBinWriter()
|
||||||
|
for i := range item.Parameters {
|
||||||
|
if item.Parameters[i].Type != smartcontract.SignatureType {
|
||||||
|
return nil, errors.New("only signature parameters are supported")
|
||||||
|
} else if item.Parameters[i].Value == nil {
|
||||||
|
return nil, errors.New("nil parameter")
|
||||||
|
}
|
||||||
|
emit.Bytes(bw.BinWriter, item.Parameters[i].Value.([]byte))
|
||||||
|
}
|
||||||
|
return &transaction.Witness{
|
||||||
|
InvocationScript: bw.Bytes(),
|
||||||
|
VerificationScript: ctr.Script,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSignature adds a signature for the specified contract and public key.
|
||||||
|
func (c *ParameterContext) AddSignature(ctr *wallet.Contract, pub *keys.PublicKey, sig []byte) error {
|
||||||
|
item := c.getItemForContract(ctr)
|
||||||
|
if pubs, ok := vm.ParseMultiSigContract(ctr.Script); ok {
|
||||||
|
if item.GetSignature(pub) != nil {
|
||||||
|
return errors.New("signature is already added")
|
||||||
|
}
|
||||||
|
pubBytes := pub.Bytes()
|
||||||
|
var contained bool
|
||||||
|
for i := range pubs {
|
||||||
|
if bytes.Equal(pubBytes, pubs[i]) {
|
||||||
|
contained = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !contained {
|
||||||
|
return errors.New("public key is not present in script")
|
||||||
|
}
|
||||||
|
item.AddSignature(pub, sig)
|
||||||
|
if len(item.Signatures) == len(ctr.Parameters) {
|
||||||
|
indexMap := map[string]int{}
|
||||||
|
for i := range pubs {
|
||||||
|
indexMap[hex.EncodeToString(pubs[i])] = i
|
||||||
|
}
|
||||||
|
sigs := make([]sigWithIndex, 0, len(item.Signatures))
|
||||||
|
for pub, sig := range item.Signatures {
|
||||||
|
sigs = append(sigs, sigWithIndex{index: indexMap[pub], sig: sig})
|
||||||
|
}
|
||||||
|
sort.Slice(sigs, func(i, j int) bool {
|
||||||
|
return sigs[i].index < sigs[j].index
|
||||||
|
})
|
||||||
|
for i := range sigs {
|
||||||
|
item.Parameters[i] = smartcontract.Parameter{
|
||||||
|
Type: smartcontract.SignatureType,
|
||||||
|
Value: sigs[i].sig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
index := -1
|
||||||
|
for i := range ctr.Parameters {
|
||||||
|
if ctr.Parameters[i].Type == smartcontract.SignatureType {
|
||||||
|
if index >= 0 {
|
||||||
|
return errors.New("multiple signature parameters in non-multisig contract")
|
||||||
|
}
|
||||||
|
index = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if index == -1 {
|
||||||
|
return errors.New("missing signature parameter")
|
||||||
|
}
|
||||||
|
item.Parameters[index].Value = sig
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ParameterContext) getItemForContract(ctr *wallet.Contract) *Item {
|
||||||
|
h := ctr.ScriptHash()
|
||||||
|
if item, ok := c.Items[h]; ok {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
params := make([]smartcontract.Parameter, len(ctr.Parameters))
|
||||||
|
for i := range params {
|
||||||
|
params[i].Type = ctr.Parameters[i].Type
|
||||||
|
}
|
||||||
|
item := &Item{
|
||||||
|
Script: h,
|
||||||
|
Parameters: params,
|
||||||
|
Signatures: make(map[string][]byte),
|
||||||
|
}
|
||||||
|
c.Items[h] = item
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements json.Marshaler interface.
|
||||||
|
func (c ParameterContext) MarshalJSON() ([]byte, error) {
|
||||||
|
bw := io.NewBufBinWriter()
|
||||||
|
c.Verifiable.EncodeBinary(bw.BinWriter)
|
||||||
|
if bw.Err != nil {
|
||||||
|
return nil, bw.Err
|
||||||
|
}
|
||||||
|
items := make(map[string]json.RawMessage, len(c.Items))
|
||||||
|
for u := range c.Items {
|
||||||
|
data, err := json.Marshal(c.Items[u])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items["0x"+u.StringBE()] = data
|
||||||
|
}
|
||||||
|
pc := ¶mContext{
|
||||||
|
Type: c.Type,
|
||||||
|
Hex: hex.EncodeToString(bw.Bytes()),
|
||||||
|
Items: items,
|
||||||
|
}
|
||||||
|
return json.Marshal(pc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaler interface.
|
||||||
|
func (c *ParameterContext) UnmarshalJSON(data []byte) error {
|
||||||
|
pc := new(paramContext)
|
||||||
|
if err := json.Unmarshal(data, pc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data, err := hex.DecodeString(pc.Hex)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var verif io.Serializable
|
||||||
|
switch pc.Type {
|
||||||
|
case "Neo.Core.ContractTransaction":
|
||||||
|
verif = new(transaction.Transaction)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported type: %s", c.Type)
|
||||||
|
}
|
||||||
|
br := io.NewBinReaderFromBuf(data)
|
||||||
|
verif.DecodeBinary(br)
|
||||||
|
if br.Err != nil {
|
||||||
|
return br.Err
|
||||||
|
}
|
||||||
|
items := make(map[util.Uint160]*Item, len(pc.Items))
|
||||||
|
for h := range pc.Items {
|
||||||
|
u, err := util.Uint160DecodeStringBE(strings.TrimPrefix(h, "0x"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
item := new(Item)
|
||||||
|
if err := json.Unmarshal(pc.Items[h], item); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
items[u] = item
|
||||||
|
}
|
||||||
|
c.Type = pc.Type
|
||||||
|
c.Verifiable = verif
|
||||||
|
c.Items = items
|
||||||
|
return nil
|
||||||
|
}
|
183
pkg/smartcontract/context/context_test.go
Normal file
183
pkg/smartcontract/context/context_test.go
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParameterContext_AddSignatureSimpleContract(t *testing.T) {
|
||||||
|
tx := getContractTx()
|
||||||
|
priv, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
pub := priv.PublicKey()
|
||||||
|
sig := priv.Sign(tx.GetSignedPart())
|
||||||
|
|
||||||
|
t.Run("invalid contract", func(t *testing.T) {
|
||||||
|
c := NewParameterContext("Neo.Core.ContractTransaction", tx)
|
||||||
|
ctr := &wallet.Contract{
|
||||||
|
Script: pub.GetVerificationScript(),
|
||||||
|
Parameters: []wallet.ContractParam{
|
||||||
|
newParam(smartcontract.SignatureType, "parameter0"),
|
||||||
|
newParam(smartcontract.SignatureType, "parameter1"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
require.Error(t, c.AddSignature(ctr, pub, sig))
|
||||||
|
if item := c.Items[ctr.ScriptHash()]; item != nil {
|
||||||
|
require.Nil(t, item.Parameters[0].Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctr.Parameters = ctr.Parameters[:0]
|
||||||
|
require.Error(t, c.AddSignature(ctr, pub, sig))
|
||||||
|
if item := c.Items[ctr.ScriptHash()]; item != nil {
|
||||||
|
require.Nil(t, item.Parameters[0].Value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
c := NewParameterContext("Neo.Core.ContractTransaction", tx)
|
||||||
|
ctr := &wallet.Contract{
|
||||||
|
Script: pub.GetVerificationScript(),
|
||||||
|
Parameters: []wallet.ContractParam{newParam(smartcontract.SignatureType, "parameter0")},
|
||||||
|
}
|
||||||
|
require.NoError(t, c.AddSignature(ctr, pub, sig))
|
||||||
|
item := c.Items[ctr.ScriptHash()]
|
||||||
|
require.NotNil(t, item)
|
||||||
|
require.Equal(t, sig, item.Parameters[0].Value)
|
||||||
|
|
||||||
|
t.Run("GetWitness", func(t *testing.T) {
|
||||||
|
w, err := c.GetWitness(ctr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
v := vm.New()
|
||||||
|
v.SetCheckedHash(tx.VerificationHash().BytesBE())
|
||||||
|
v.LoadScript(w.VerificationScript)
|
||||||
|
v.LoadScript(w.InvocationScript)
|
||||||
|
require.NoError(t, v.Run())
|
||||||
|
require.Equal(t, 1, v.Estack().Len())
|
||||||
|
require.Equal(t, true, v.Estack().Pop().Value())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParameterContext_AddSignatureMultisig(t *testing.T) {
|
||||||
|
tx := getContractTx()
|
||||||
|
c := NewParameterContext("Neo.Core.ContractTransaction", tx)
|
||||||
|
privs, pubs := getPrivateKeys(t, 4)
|
||||||
|
pubsCopy := make(keys.PublicKeys, len(pubs))
|
||||||
|
copy(pubsCopy, pubs)
|
||||||
|
script, err := smartcontract.CreateMultiSigRedeemScript(3, pubsCopy)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ctr := &wallet.Contract{
|
||||||
|
Script: script,
|
||||||
|
Parameters: []wallet.ContractParam{
|
||||||
|
newParam(smartcontract.SignatureType, "parameter0"),
|
||||||
|
newParam(smartcontract.SignatureType, "parameter1"),
|
||||||
|
newParam(smartcontract.SignatureType, "parameter2"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
data := tx.GetSignedPart()
|
||||||
|
priv, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
sig := priv.Sign(data)
|
||||||
|
require.Error(t, c.AddSignature(ctr, priv.PublicKey(), sig))
|
||||||
|
|
||||||
|
indices := []int{2, 3, 0} // random order
|
||||||
|
for _, i := range indices {
|
||||||
|
sig := privs[i].Sign(data)
|
||||||
|
require.NoError(t, c.AddSignature(ctr, pubs[i], sig))
|
||||||
|
require.Error(t, c.AddSignature(ctr, pubs[i], sig))
|
||||||
|
|
||||||
|
item := c.Items[ctr.ScriptHash()]
|
||||||
|
require.NotNil(t, item)
|
||||||
|
require.Equal(t, sig, item.GetSignature(pubs[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("GetWitness", func(t *testing.T) {
|
||||||
|
w, err := c.GetWitness(ctr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
v := vm.New()
|
||||||
|
v.SetCheckedHash(tx.VerificationHash().BytesBE())
|
||||||
|
v.LoadScript(w.VerificationScript)
|
||||||
|
v.LoadScript(w.InvocationScript)
|
||||||
|
require.NoError(t, v.Run())
|
||||||
|
require.Equal(t, 1, v.Estack().Len())
|
||||||
|
require.Equal(t, true, v.Estack().Pop().Value())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParameterContext_MarshalJSON(t *testing.T) {
|
||||||
|
priv, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tx := getContractTx()
|
||||||
|
data := tx.GetSignedPart()
|
||||||
|
sign := priv.Sign(data)
|
||||||
|
|
||||||
|
expected := &ParameterContext{
|
||||||
|
Type: "Neo.Core.ContractTransaction",
|
||||||
|
Verifiable: tx,
|
||||||
|
Items: map[util.Uint160]*Item{
|
||||||
|
priv.GetScriptHash(): {
|
||||||
|
Script: priv.GetScriptHash(),
|
||||||
|
Parameters: []smartcontract.Parameter{{
|
||||||
|
Type: smartcontract.SignatureType,
|
||||||
|
Value: sign,
|
||||||
|
}},
|
||||||
|
Signatures: map[string][]byte{
|
||||||
|
hex.EncodeToString(priv.PublicKey().Bytes()): sign,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err = json.Marshal(expected)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
actual := new(ParameterContext)
|
||||||
|
require.NoError(t, json.Unmarshal(data, actual))
|
||||||
|
require.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPrivateKeys(t *testing.T, n int) ([]*keys.PrivateKey, []*keys.PublicKey) {
|
||||||
|
privs := make([]*keys.PrivateKey, n)
|
||||||
|
pubs := make([]*keys.PublicKey, n)
|
||||||
|
for i := range privs {
|
||||||
|
var err error
|
||||||
|
privs[i], err = keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
pubs[i] = privs[i].PublicKey()
|
||||||
|
}
|
||||||
|
return privs, pubs
|
||||||
|
}
|
||||||
|
|
||||||
|
func newParam(typ smartcontract.ParamType, name string) wallet.ContractParam {
|
||||||
|
return wallet.ContractParam{
|
||||||
|
Name: name,
|
||||||
|
Type: typ,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getContractTx() *transaction.Transaction {
|
||||||
|
tx := transaction.NewContractTX()
|
||||||
|
tx.AddInput(&transaction.Input{
|
||||||
|
PrevHash: util.Uint256{1, 2, 3, 4},
|
||||||
|
PrevIndex: 5,
|
||||||
|
})
|
||||||
|
tx.AddOutput(&transaction.Output{
|
||||||
|
AssetID: util.Uint256{7, 8, 9},
|
||||||
|
Amount: 10,
|
||||||
|
ScriptHash: util.Uint160{11, 12},
|
||||||
|
})
|
||||||
|
tx.Data = new(transaction.ContractTX)
|
||||||
|
tx.Attributes = make([]transaction.Attribute, 0)
|
||||||
|
tx.Scripts = make([]transaction.Witness, 0)
|
||||||
|
tx.Hash()
|
||||||
|
return tx
|
||||||
|
}
|
75
pkg/smartcontract/context/item.go
Normal file
75
pkg/smartcontract/context/item.go
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Item represents a transaction context item.
|
||||||
|
type Item struct {
|
||||||
|
Script util.Uint160
|
||||||
|
Parameters []smartcontract.Parameter
|
||||||
|
Signatures map[string][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type itemAux struct {
|
||||||
|
Script util.Uint160 `json:"script"`
|
||||||
|
Parameters []smartcontract.Parameter `json:"parameters"`
|
||||||
|
Signatures map[string]string `json:"signatures"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSignature returns signature for pub if present.
|
||||||
|
func (it *Item) GetSignature(pub *keys.PublicKey) []byte {
|
||||||
|
return it.Signatures[hex.EncodeToString(pub.Bytes())]
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSignature adds a signature for pub.
|
||||||
|
func (it *Item) AddSignature(pub *keys.PublicKey, sig []byte) {
|
||||||
|
pubHex := hex.EncodeToString(pub.Bytes())
|
||||||
|
it.Signatures[pubHex] = sig
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements json.Marshaler interface.
|
||||||
|
func (it Item) MarshalJSON() ([]byte, error) {
|
||||||
|
ci := itemAux{
|
||||||
|
Script: it.Script,
|
||||||
|
Parameters: it.Parameters,
|
||||||
|
Signatures: make(map[string]string, len(it.Signatures)),
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, sig := range it.Signatures {
|
||||||
|
ci.Signatures[key] = hex.EncodeToString(sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(ci)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaler interface.
|
||||||
|
func (it *Item) UnmarshalJSON(data []byte) error {
|
||||||
|
ci := new(itemAux)
|
||||||
|
if err := json.Unmarshal(data, ci); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sigs := make(map[string][]byte, len(ci.Signatures))
|
||||||
|
for keyHex, sigHex := range ci.Signatures {
|
||||||
|
_, err := keys.NewPublicKeyFromString(keyHex)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sig, err := hex.DecodeString(sigHex)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sigs[keyHex] = sig
|
||||||
|
}
|
||||||
|
|
||||||
|
it.Signatures = sigs
|
||||||
|
it.Script = ci.Script
|
||||||
|
it.Parameters = ci.Parameters
|
||||||
|
return nil
|
||||||
|
}
|
74
pkg/smartcontract/context/item_test.go
Normal file
74
pkg/smartcontract/context/item_test.go
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestContextItem_AddSignature(t *testing.T) {
|
||||||
|
item := &Item{Signatures: make(map[string][]byte)}
|
||||||
|
|
||||||
|
priv1, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
pub1 := priv1.PublicKey()
|
||||||
|
sig1 := []byte{1, 2, 3}
|
||||||
|
item.AddSignature(pub1, sig1)
|
||||||
|
require.Equal(t, sig1, item.GetSignature(pub1))
|
||||||
|
|
||||||
|
priv2, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
pub2 := priv2.PublicKey()
|
||||||
|
sig2 := []byte{5, 6, 7}
|
||||||
|
item.AddSignature(pub2, sig2)
|
||||||
|
require.Equal(t, sig2, item.GetSignature(pub2))
|
||||||
|
require.Equal(t, sig1, item.GetSignature(pub1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextItem_MarshalJSON(t *testing.T) {
|
||||||
|
priv1, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
priv2, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expected := &Item{
|
||||||
|
Script: util.Uint160{1, 2, 3},
|
||||||
|
Parameters: []smartcontract.Parameter{{
|
||||||
|
Type: smartcontract.SignatureType,
|
||||||
|
Value: getRandomSlice(t, 64),
|
||||||
|
}},
|
||||||
|
Signatures: map[string][]byte{
|
||||||
|
hex.EncodeToString(priv1.PublicKey().Bytes()): getRandomSlice(t, 64),
|
||||||
|
hex.EncodeToString(priv2.PublicKey().Bytes()): getRandomSlice(t, 64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(expected)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
actual := new(Item)
|
||||||
|
require.NoError(t, json.Unmarshal(data, actual))
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRandomSlice(t *testing.T, n int) []byte {
|
||||||
|
src := rand.NewSource(time.Now().UnixNano())
|
||||||
|
r := rand.New(src)
|
||||||
|
data := make([]byte, n)
|
||||||
|
_, err := io.ReadFull(r, data)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return data
|
||||||
|
}
|
|
@ -137,7 +137,7 @@ func (p *Parameter) UnmarshalJSON(data []byte) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p.Value = boolean
|
p.Value = boolean
|
||||||
case ByteArrayType, PublicKeyType:
|
case ByteArrayType, PublicKeyType, SignatureType:
|
||||||
if err = json.Unmarshal(r.Value, &s); err != nil {
|
if err = json.Unmarshal(r.Value, &s); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
|
@ -31,49 +31,58 @@ func getNumOfThingsFromInstr(instr opcode.Opcode, param []byte) (int, bool) {
|
||||||
// IsMultiSigContract checks whether the passed script is a multi-signature
|
// IsMultiSigContract checks whether the passed script is a multi-signature
|
||||||
// contract.
|
// contract.
|
||||||
func IsMultiSigContract(script []byte) bool {
|
func IsMultiSigContract(script []byte) bool {
|
||||||
|
_, ok := ParseMultiSigContract(script)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseMultiSigContract returns list of public keys from the verification
|
||||||
|
// script of the contract.
|
||||||
|
func ParseMultiSigContract(script []byte) ([][]byte, bool) {
|
||||||
var nsigs, nkeys int
|
var nsigs, nkeys int
|
||||||
|
|
||||||
ctx := NewContext(script)
|
ctx := NewContext(script)
|
||||||
instr, param, err := ctx.Next()
|
instr, param, err := ctx.Next()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return nil, false
|
||||||
}
|
}
|
||||||
nsigs, ok := getNumOfThingsFromInstr(instr, param)
|
nsigs, ok := getNumOfThingsFromInstr(instr, param)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
var pubs [][]byte
|
||||||
for {
|
for {
|
||||||
instr, param, err = ctx.Next()
|
instr, param, err = ctx.Next()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return nil, false
|
||||||
}
|
}
|
||||||
if instr != opcode.PUSHBYTES33 {
|
if instr != opcode.PUSHBYTES33 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
pubs = append(pubs, param)
|
||||||
nkeys++
|
nkeys++
|
||||||
if nkeys > MaxArraySize {
|
if nkeys > MaxArraySize {
|
||||||
return false
|
return nil, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if nkeys < nsigs {
|
if nkeys < nsigs {
|
||||||
return false
|
return nil, false
|
||||||
}
|
}
|
||||||
nkeys2, ok := getNumOfThingsFromInstr(instr, param)
|
nkeys2, ok := getNumOfThingsFromInstr(instr, param)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return nil, false
|
||||||
}
|
}
|
||||||
if nkeys2 != nkeys {
|
if nkeys2 != nkeys {
|
||||||
return false
|
return nil, false
|
||||||
}
|
}
|
||||||
instr, _, err = ctx.Next()
|
instr, _, err = ctx.Next()
|
||||||
if err != nil || instr != opcode.CHECKMULTISIG {
|
if err != nil || instr != opcode.CHECKMULTISIG {
|
||||||
return false
|
return nil, false
|
||||||
}
|
}
|
||||||
instr, _, err = ctx.Next()
|
instr, _, err = ctx.Next()
|
||||||
if err != nil || instr != opcode.RET || ctx.ip != len(script) {
|
if err != nil || instr != opcode.RET || ctx.ip != len(script) {
|
||||||
return false
|
return nil, false
|
||||||
}
|
}
|
||||||
return true
|
return pubs, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsSignatureContract checks whether the passed script is a signature check
|
// IsSignatureContract checks whether the passed script is a signature check
|
||||||
|
|
|
@ -56,7 +56,7 @@ type Contract struct {
|
||||||
Script []byte `json:"script"`
|
Script []byte `json:"script"`
|
||||||
|
|
||||||
// A list of parameters used deploying this contract.
|
// A list of parameters used deploying this contract.
|
||||||
Parameters []contractParam `json:"parameters"`
|
Parameters []ContractParam `json:"parameters"`
|
||||||
|
|
||||||
// Indicates whether the contract has been deployed to the blockchain.
|
// Indicates whether the contract has been deployed to the blockchain.
|
||||||
Deployed bool `json:"deployed"`
|
Deployed bool `json:"deployed"`
|
||||||
|
@ -68,13 +68,15 @@ type contract struct {
|
||||||
Script string `json:"script"`
|
Script string `json:"script"`
|
||||||
|
|
||||||
// A list of parameters used deploying this contract.
|
// A list of parameters used deploying this contract.
|
||||||
Parameters []contractParam `json:"parameters"`
|
Parameters []ContractParam `json:"parameters"`
|
||||||
|
|
||||||
// Indicates whether the contract has been deployed to the blockchain.
|
// Indicates whether the contract has been deployed to the blockchain.
|
||||||
Deployed bool `json:"deployed"`
|
Deployed bool `json:"deployed"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type contractParam struct {
|
// ContractParam is a descriptor of a contract parameter
|
||||||
|
// containing type and optional name.
|
||||||
|
type ContractParam struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Type smartcontract.ParamType `json:"type"`
|
Type smartcontract.ParamType `json:"type"`
|
||||||
}
|
}
|
||||||
|
@ -252,8 +254,8 @@ func newAccountFromPrivateKey(p *keys.PrivateKey) *Account {
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
func getContractParams(n int) []contractParam {
|
func getContractParams(n int) []ContractParam {
|
||||||
params := make([]contractParam, n)
|
params := make([]ContractParam, n)
|
||||||
for i := range params {
|
for i := range params {
|
||||||
params[i].Name = fmt.Sprintf("parameter%d", i)
|
params[i].Name = fmt.Sprintf("parameter%d", i)
|
||||||
params[i].Type = smartcontract.SignatureType
|
params[i].Type = smartcontract.SignatureType
|
||||||
|
|
Loading…
Reference in a new issue