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 }