diff --git a/cli/wallet/multisig.go b/cli/wallet/multisig.go new file mode 100644 index 000000000..883e3149e --- /dev/null +++ b/cli/wallet/multisig.go @@ -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 --addr --in --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)) + } +} diff --git a/cli/wallet/wallet.go b/cli/wallet/wallet.go index 98b36f68a..d816d2afa 100644 --- a/cli/wallet/wallet.go +++ b/cli/wallet/wallet.go @@ -54,6 +54,10 @@ var ( Name: "out", Usage: "file to put JSON transaction to", } + inFlag = cli.StringFlag{ + Name: "in", + Usage: "file with JSON transaction", + } ) // NewCommands returns 'wallet' command. @@ -176,6 +180,11 @@ func NewCommands() []cli.Command { }, }, }, + { + Name: "multisig", + Usage: "work with multisig address", + Subcommands: newMultisigCommands(), + }, }, }} }