github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/crypto/keys/client/sign.go (about)

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"flag"
     6  	"fmt"
     7  	"os"
     8  
     9  	"github.com/gnolang/gno/tm2/pkg/amino"
    10  	"github.com/gnolang/gno/tm2/pkg/commands"
    11  	"github.com/gnolang/gno/tm2/pkg/crypto/keys"
    12  	"github.com/gnolang/gno/tm2/pkg/errors"
    13  	"github.com/gnolang/gno/tm2/pkg/std"
    14  )
    15  
    16  var errInvalidTxFile = errors.New("invalid transaction file")
    17  
    18  type signOpts struct {
    19  	chainID         string
    20  	accountSequence uint64
    21  	accountNumber   uint64
    22  }
    23  
    24  type keyOpts struct {
    25  	keyName     string
    26  	decryptPass string
    27  }
    28  
    29  type SignCfg struct {
    30  	RootCfg *BaseCfg
    31  
    32  	TxPath        string
    33  	ChainID       string
    34  	AccountNumber uint64
    35  	Sequence      uint64
    36  	NameOrBech32  string
    37  }
    38  
    39  func NewSignCmd(rootCfg *BaseCfg, io commands.IO) *commands.Command {
    40  	cfg := &SignCfg{
    41  		RootCfg: rootCfg,
    42  	}
    43  
    44  	return commands.NewCommand(
    45  		commands.Metadata{
    46  			Name:       "sign",
    47  			ShortUsage: "sign [flags] <key-name or address>",
    48  			ShortHelp:  "signs the given tx document and saves it to disk",
    49  		},
    50  		cfg,
    51  		func(_ context.Context, args []string) error {
    52  			return execSign(cfg, args, io)
    53  		},
    54  	)
    55  }
    56  
    57  func (c *SignCfg) RegisterFlags(fs *flag.FlagSet) {
    58  	fs.StringVar(
    59  		&c.TxPath,
    60  		"tx-path",
    61  		"",
    62  		"path to the Amino JSON-encoded tx (file) to sign",
    63  	)
    64  
    65  	fs.StringVar(
    66  		&c.ChainID,
    67  		"chainid",
    68  		"dev",
    69  		"the ID of the chain",
    70  	)
    71  
    72  	fs.Uint64Var(
    73  		&c.AccountNumber,
    74  		"account-number",
    75  		0,
    76  		"account number to sign with",
    77  	)
    78  
    79  	fs.Uint64Var(
    80  		&c.Sequence,
    81  		"account-sequence",
    82  		0,
    83  		"account sequence to sign with",
    84  	)
    85  }
    86  
    87  func execSign(cfg *SignCfg, args []string, io commands.IO) error {
    88  	// Make sure the key name is provided
    89  	if len(args) != 1 {
    90  		return flag.ErrHelp
    91  	}
    92  
    93  	// saveTx saves the given transaction to the given path (Amino-encoded JSON)
    94  	saveTx := func(tx *std.Tx, path string) error {
    95  		// Encode the transaction
    96  		encodedTx, err := amino.MarshalJSON(tx)
    97  		if err != nil {
    98  			return fmt.Errorf("unable ot marshal tx to JSON, %w", err)
    99  		}
   100  
   101  		// Save the transaction
   102  		if err := os.WriteFile(path, encodedTx, 0o644); err != nil {
   103  			return fmt.Errorf("unable to write tx to %s, %w", path, err)
   104  		}
   105  
   106  		io.Printf("\nTx successfully signed and saved to %s\n", path)
   107  
   108  		return nil
   109  	}
   110  
   111  	// Load the keybase
   112  	kb, err := keys.NewKeyBaseFromDir(cfg.RootCfg.Home)
   113  	if err != nil {
   114  		return fmt.Errorf("unable to load keybase, %w", err)
   115  	}
   116  
   117  	// Fetch the key info from the keybase
   118  	info, err := kb.GetByNameOrAddress(args[0])
   119  	if err != nil {
   120  		return fmt.Errorf("unable to get key from keybase, %w", err)
   121  	}
   122  
   123  	// Get the transaction bytes
   124  	txRaw, err := os.ReadFile(cfg.TxPath)
   125  	if err != nil {
   126  		return fmt.Errorf("unable to read transaction file")
   127  	}
   128  
   129  	// Make sure there is something to actually sign
   130  	if len(txRaw) == 0 {
   131  		return errInvalidTxFile
   132  	}
   133  
   134  	// Make sure the tx is valid Amino JSON
   135  	var tx std.Tx
   136  	if err := amino.UnmarshalJSON(txRaw, &tx); err != nil {
   137  		return fmt.Errorf("unable to unmarshal transaction, %w", err)
   138  	}
   139  
   140  	var password string
   141  
   142  	// Check if we need to get a decryption password.
   143  	// This is only required for local keys
   144  	if info.GetType() != keys.TypeLedger {
   145  		// Get the keybase decryption password
   146  		prompt := "Enter password to decrypt key"
   147  		if cfg.RootCfg.Quiet {
   148  			prompt = "" // No prompt
   149  		}
   150  
   151  		password, err = io.GetPassword(
   152  			prompt,
   153  			cfg.RootCfg.InsecurePasswordStdin,
   154  		)
   155  		if err != nil {
   156  			return fmt.Errorf("unable to get decryption key, %w", err)
   157  		}
   158  	}
   159  
   160  	// Prepare the signature ops
   161  	sOpts := signOpts{
   162  		chainID:         cfg.ChainID,
   163  		accountSequence: cfg.Sequence,
   164  		accountNumber:   cfg.AccountNumber,
   165  	}
   166  
   167  	kOpts := keyOpts{
   168  		keyName:     args[0],
   169  		decryptPass: password,
   170  	}
   171  
   172  	// Sign the transaction
   173  	if err := signTx(&tx, kb, sOpts, kOpts); err != nil {
   174  		return fmt.Errorf("unable to sign transaction, %w", err)
   175  	}
   176  
   177  	return saveTx(&tx, cfg.TxPath)
   178  }
   179  
   180  // signTx generates the transaction signature,
   181  // and saves it to the given transaction
   182  func signTx(
   183  	tx *std.Tx,
   184  	kb keys.Keybase,
   185  	signOpts signOpts,
   186  	keyOpts keyOpts,
   187  ) error {
   188  	signBytes, err := tx.GetSignBytes(
   189  		signOpts.chainID,
   190  		signOpts.accountNumber,
   191  		signOpts.accountSequence,
   192  	)
   193  	if err != nil {
   194  		return fmt.Errorf("unable to get signature bytes, %w", err)
   195  	}
   196  
   197  	// Sign the transaction data
   198  	sig, pub, err := kb.Sign(
   199  		keyOpts.keyName,
   200  		keyOpts.decryptPass,
   201  		signBytes,
   202  	)
   203  	if err != nil {
   204  		return fmt.Errorf("unable to sign transaction bytes, %w", err)
   205  	}
   206  
   207  	// Save the signature
   208  	if tx.Signatures == nil {
   209  		tx.Signatures = make([]std.Signature, 0, 1)
   210  	}
   211  
   212  	// Check if the signature needs to be overwritten
   213  	for index, signature := range tx.Signatures {
   214  		if !signature.PubKey.Equals(pub) {
   215  			continue
   216  		}
   217  
   218  		// Save the signature
   219  		tx.Signatures[index] = std.Signature{
   220  			PubKey:    pub,
   221  			Signature: sig,
   222  		}
   223  
   224  		return nil
   225  	}
   226  
   227  	// Append the signature, since it wasn't
   228  	// present before
   229  	tx.Signatures = append(
   230  		tx.Signatures, std.Signature{
   231  			PubKey:    pub,
   232  			Signature: sig,
   233  		},
   234  	)
   235  
   236  	// Validate the tx after signing
   237  	if err := tx.ValidateBasic(); err != nil {
   238  		return fmt.Errorf("unable to validate transaction, %w", err)
   239  	}
   240  
   241  	return nil
   242  }