github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/x/auth/client/cli/tx_sign.go (about) 1 package cli 2 3 import ( 4 "bufio" 5 "fmt" 6 "os" 7 "strings" 8 9 "github.com/fibonacci-chain/fbc/libs/tendermint/crypto/multisig" 10 "github.com/spf13/cobra" 11 "github.com/spf13/viper" 12 13 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/client/context" 14 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/client/flags" 15 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/codec" 16 sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types" 17 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/auth/client/utils" 18 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/auth/types" 19 ) 20 21 const ( 22 flagMultisig = "multisig" 23 flagAppend = "append" 24 flagValidateSigs = "validate-signatures" 25 flagOffline = "offline" 26 flagSigOnly = "signature-only" 27 flagOutfile = "output-document" 28 ) 29 30 // GetSignCommand returns the transaction sign command. 31 func GetSignCommand(codec *codec.Codec) *cobra.Command { 32 cmd := &cobra.Command{ 33 Use: "sign [file]", 34 Short: "Sign transactions generated offline", 35 Long: `Sign transactions created with the --generate-only flag. 36 It will read a transaction from [file], sign it, and print its JSON encoding. 37 38 If the flag --signature-only flag is set, it will output a JSON representation 39 of the generated signature only. 40 41 If the flag --validate-signatures is set, then the command would check whether all required 42 signers have signed the transactions, whether the signatures were collected in the right 43 order, and if the signature is valid over the given transaction. If the --offline 44 flag is also set, signature validation over the transaction will be not be 45 performed as that will require RPC communication with a full node. 46 47 The --offline flag makes sure that the client will not reach out to full node. 48 As a result, the account and sequence number queries will not be performed and 49 it is required to set such parameters manually. Note, invalid values will cause 50 the transaction to fail. 51 52 The --multisig=<multisig_key> flag generates a signature on behalf of a multisig account 53 key. It implies --signature-only. Full multisig signed transactions may eventually 54 be generated via the 'multisign' command. 55 `, 56 PreRun: preSignCmd, 57 RunE: makeSignCmd(codec), 58 Args: cobra.ExactArgs(1), 59 } 60 61 cmd.Flags().String( 62 flagMultisig, "", 63 "Address of the multisig account on behalf of which the transaction shall be signed", 64 ) 65 cmd.Flags().Bool( 66 flagAppend, true, 67 "Append the signature to the existing ones. If disabled, old signatures would be overwritten. Ignored if --multisig is on", 68 ) 69 cmd.Flags().Bool( 70 flagValidateSigs, false, 71 "Print the addresses that must sign the transaction, those who have already signed it, and make sure that signatures are in the correct order", 72 ) 73 cmd.Flags().Bool(flagSigOnly, false, "Print only the generated signature, then exit") 74 cmd.Flags().Bool( 75 flagOffline, false, 76 "Offline mode; Do not query a full node. --account and --sequence options would be required if offline is set", 77 ) 78 cmd.Flags().String(flagOutfile, "", "The document will be written to the given file instead of STDOUT") 79 80 cmd = flags.PostCommands(cmd)[0] 81 cmd.MarkFlagRequired(flags.FlagFrom) 82 83 return cmd 84 } 85 86 func preSignCmd(cmd *cobra.Command, _ []string) { 87 // Conditionally mark the account and sequence numbers required as no RPC 88 // query will be done. 89 if viper.GetBool(flagOffline) { 90 cmd.MarkFlagRequired(flags.FlagAccountNumber) 91 cmd.MarkFlagRequired(flags.FlagSequence) 92 } 93 } 94 95 func makeSignCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string) error { 96 return func(cmd *cobra.Command, args []string) error { 97 stdTx, err := utils.ReadStdTxFromFile(cdc, args[0]) 98 if err != nil { 99 return err 100 } 101 102 inBuf := bufio.NewReader(cmd.InOrStdin()) 103 offline := viper.GetBool(flagOffline) 104 cliCtx := context.NewCLIContextWithInput(inBuf).WithCodec(cdc) 105 txBldr := types.NewTxBuilderFromCLI(inBuf) 106 107 if viper.GetBool(flagValidateSigs) { 108 if !printAndValidateSigs(cliCtx, txBldr.ChainID(), stdTx, offline) { 109 return fmt.Errorf("signatures validation failed") 110 } 111 112 return nil 113 } 114 115 // if --signature-only is on, then override --append 116 var newTx *types.StdTx 117 generateSignatureOnly := viper.GetBool(flagSigOnly) 118 multisigAddrStr := viper.GetString(flagMultisig) 119 120 if multisigAddrStr != "" { 121 var multisigAddr sdk.AccAddress 122 123 multisigAddr, err = sdk.AccAddressFromBech32(multisigAddrStr) 124 if err != nil { 125 return err 126 } 127 128 newTx, err = utils.SignStdTxWithSignerAddress( 129 txBldr, cliCtx, multisigAddr, cliCtx.GetFromName(), stdTx, offline, 130 ) 131 generateSignatureOnly = true 132 } else { 133 appendSig := viper.GetBool(flagAppend) && !generateSignatureOnly 134 newTx, err = utils.SignStdTx(txBldr, cliCtx, cliCtx.GetFromName(), stdTx, appendSig, offline) 135 } 136 137 if err != nil { 138 return err 139 } 140 141 json, err := getSignatureJSON(cdc, newTx, cliCtx.Indent, generateSignatureOnly) 142 if err != nil { 143 return err 144 } 145 146 if viper.GetString(flagOutfile) == "" { 147 fmt.Printf("%s\n", json) 148 return nil 149 } 150 151 fp, err := os.OpenFile( 152 viper.GetString(flagOutfile), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644, 153 ) 154 if err != nil { 155 return err 156 } 157 158 defer fp.Close() 159 fmt.Fprintf(fp, "%s\n", json) 160 161 return nil 162 } 163 } 164 165 func getSignatureJSON(cdc *codec.Codec, newTx *types.StdTx, indent, generateSignatureOnly bool) ([]byte, error) { 166 switch generateSignatureOnly { 167 case true: 168 switch indent { 169 case true: 170 return cdc.MarshalJSONIndent(newTx.Signatures[0], "", " ") 171 172 default: 173 return cdc.MarshalJSON(newTx.Signatures[0]) 174 } 175 default: 176 switch indent { 177 case true: 178 return cdc.MarshalJSONIndent(newTx, "", " ") 179 180 default: 181 return cdc.MarshalJSON(newTx) 182 } 183 } 184 } 185 186 // printAndValidateSigs will validate the signatures of a given transaction over 187 // its expected signers. In addition, if offline has not been supplied, the 188 // signature is verified over the transaction sign bytes. 189 func printAndValidateSigs( 190 cliCtx context.CLIContext, chainID string, stdTx *types.StdTx, offline bool, 191 ) bool { 192 193 fmt.Println("Signers:") 194 195 signers := stdTx.GetSigners() 196 for i, signer := range signers { 197 fmt.Printf(" %v: %v\n", i, signer.String()) 198 } 199 200 success := true 201 sigs := stdTx.Signatures 202 203 fmt.Println("") 204 fmt.Println("Signatures:") 205 206 if len(sigs) != len(signers) { 207 success = false 208 } 209 210 for i, sig := range sigs { 211 sigAddr := sdk.AccAddress(sig.Address()) 212 sigSanity := "OK" 213 214 var ( 215 multiSigHeader string 216 multiSigMsg string 217 ) 218 219 if i >= len(signers) || !sigAddr.Equals(signers[i]) { 220 sigSanity = "ERROR: signature does not match its respective signer" 221 success = false 222 } 223 224 // Validate the actual signature over the transaction bytes since we can 225 // reach out to a full node to query accounts. 226 if !offline && success { 227 acc, err := types.NewAccountRetriever(cliCtx).GetAccount(sigAddr) 228 if err != nil { 229 fmt.Printf("failed to get account: %s\n", sigAddr) 230 return false 231 } 232 233 sigBytes := types.StdSignBytes( 234 chainID, acc.GetAccountNumber(), acc.GetSequence(), 235 stdTx.Fee, stdTx.GetMsgs(), stdTx.GetMemo(), 236 ) 237 238 if ok := sig.VerifyBytes(sigBytes, sig.Signature); !ok { 239 sigSanity = "ERROR: signature invalid" 240 success = false 241 } 242 } 243 244 multiPK, ok := sig.PubKey.(multisig.PubKeyMultisigThreshold) 245 if ok { 246 var multiSig multisig.Multisignature 247 cliCtx.Codec.MustUnmarshalBinaryBare(sig.Signature, &multiSig) 248 249 var b strings.Builder 250 b.WriteString("\n MultiSig Signatures:\n") 251 252 for i := 0; i < multiSig.BitArray.Size(); i++ { 253 if multiSig.BitArray.GetIndex(i) { 254 addr := sdk.AccAddress(multiPK.PubKeys[i].Address().Bytes()) 255 b.WriteString(fmt.Sprintf(" %d: %s (weight: %d)\n", i, addr, 1)) 256 } 257 } 258 259 multiSigHeader = fmt.Sprintf(" [multisig threshold: %d/%d]", multiPK.K, len(multiPK.PubKeys)) 260 multiSigMsg = b.String() 261 } 262 263 fmt.Printf(" %d: %s\t\t\t[%s]%s%s\n", i, sigAddr.String(), sigSanity, multiSigHeader, multiSigMsg) 264 } 265 266 fmt.Println("") 267 return success 268 }