diff --git a/client/v2/Makefile b/client/v2/Makefile index 1b4bb0cbe7f6..ec0288100be4 100644 --- a/client/v2/Makefile +++ b/client/v2/Makefile @@ -1,2 +1,2 @@ codegen: - @(cd internal; buf generate) \ No newline at end of file + @(cd internal; buf generate --template buf.gen.pulsar.yaml) \ No newline at end of file diff --git a/client/v2/internal/buf.gen.gogo.yaml b/client/v2/internal/buf.gen.gogo.yaml new file mode 100644 index 000000000000..a1df55b815d6 --- /dev/null +++ b/client/v2/internal/buf.gen.gogo.yaml @@ -0,0 +1,5 @@ +version: v1 +plugins: + - name: gocosmos + out: .. + opt: plugins=grpc,Mgoogle/protobuf/any.proto=github.com/cosmos/gogoproto/types/any diff --git a/client/v2/internal/buf.gen.yaml b/client/v2/internal/buf.gen.pulsar.yaml similarity index 100% rename from client/v2/internal/buf.gen.yaml rename to client/v2/internal/buf.gen.pulsar.yaml diff --git a/client/v2/internal/offchain/msgSignArbitraryData.proto b/client/v2/internal/offchain/msgSignArbitraryData.proto index 0dcce3399ba4..87cff99a5db8 100644 --- a/client/v2/internal/offchain/msgSignArbitraryData.proto +++ b/client/v2/internal/offchain/msgSignArbitraryData.proto @@ -6,6 +6,8 @@ import "cosmos_proto/cosmos.proto"; import "cosmos/msg/v1/msg.proto"; import "amino/amino.proto"; +option go_package = "cosmossdk.io/client/v2/offchain"; + // MsgSignArbitraryData defines an arbitrary, general-purpose, off-chain message message MsgSignArbitraryData { option (amino.name) = "offchain/MsgSignArbitraryData"; diff --git a/client/v2/internal/testpb/msg.pulsar.go b/client/v2/internal/testpb/msg.pulsar.go index 29fd89007a3d..c156d4131627 100644 --- a/client/v2/internal/testpb/msg.pulsar.go +++ b/client/v2/internal/testpb/msg.pulsar.go @@ -4287,27 +4287,27 @@ var file_testpb_msg_proto_rawDesc = []byte{ 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x14, 0x0a, 0x12, 0x4d, 0x73, 0x67, 0x43, 0x6c, 0x61, 0x77, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x15, 0x0a, 0x13, 0x4d, 0x73, 0x67, 0x43, 0x6c, 0x61, 0x77, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xab, 0x01, 0x0a, 0x03, 0x4d, 0x73, 0x67, 0x12, 0x47, 0x0a, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xac, 0x01, 0x0a, 0x03, 0x4d, 0x73, 0x67, 0x12, 0x47, 0x0a, 0x04, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x12, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x62, 0x2e, 0x4d, 0x73, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x62, 0x2e, 0x4d, 0x73, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0xca, 0xb4, 0x2d, 0x12, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2d, 0x73, 0x64, 0x6b, 0x20, 0x76, - 0x30, 0x2e, 0x35, 0x30, 0x2e, 0x30, 0x12, 0x5b, 0x0a, 0x08, 0x43, 0x6c, 0x61, 0x77, 0x62, 0x61, + 0x30, 0x2e, 0x35, 0x30, 0x2e, 0x30, 0x12, 0x5c, 0x0a, 0x08, 0x43, 0x6c, 0x61, 0x77, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x1a, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x62, 0x2e, 0x4d, 0x73, 0x67, 0x43, 0x6c, 0x61, 0x77, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x62, 0x2e, 0x4d, 0x73, 0x67, 0x43, 0x6c, 0x61, 0x77, 0x62, - 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0xca, 0xb4, 0x2d, - 0x12, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2d, 0x73, 0x64, 0x6b, 0x20, 0x76, 0x30, 0x2e, 0x35, - 0x31, 0x2e, 0x30, 0x42, 0x86, 0x01, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x2e, 0x74, 0x65, 0x73, 0x74, - 0x70, 0x62, 0x42, 0x08, 0x4d, 0x73, 0x67, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x36, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, - 0x73, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2d, 0x73, 0x64, 0x6b, 0x2f, 0x63, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x2f, 0x76, 0x32, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, - 0x74, 0x65, 0x73, 0x74, 0x70, 0x62, 0xa2, 0x02, 0x03, 0x54, 0x58, 0x58, 0xaa, 0x02, 0x06, 0x54, - 0x65, 0x73, 0x74, 0x70, 0x62, 0xca, 0x02, 0x06, 0x54, 0x65, 0x73, 0x74, 0x70, 0x62, 0xe2, 0x02, - 0x12, 0x54, 0x65, 0x73, 0x74, 0x70, 0x62, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0xea, 0x02, 0x06, 0x54, 0x65, 0x73, 0x74, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x17, 0xca, 0xb4, 0x2d, + 0x13, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2d, 0x73, 0x64, 0x6b, 0x20, 0x76, 0x30, 0x2e, 0x35, + 0x33, 0x2e, 0x30, 0x20, 0x42, 0x86, 0x01, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x2e, 0x74, 0x65, 0x73, + 0x74, 0x70, 0x62, 0x42, 0x08, 0x4d, 0x73, 0x67, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, + 0x36, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x73, 0x6d, + 0x6f, 0x73, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2d, 0x73, 0x64, 0x6b, 0x2f, 0x63, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x76, 0x32, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2f, 0x74, 0x65, 0x73, 0x74, 0x70, 0x62, 0xa2, 0x02, 0x03, 0x54, 0x58, 0x58, 0xaa, 0x02, 0x06, + 0x54, 0x65, 0x73, 0x74, 0x70, 0x62, 0xca, 0x02, 0x06, 0x54, 0x65, 0x73, 0x74, 0x70, 0x62, 0xe2, + 0x02, 0x12, 0x54, 0x65, 0x73, 0x74, 0x70, 0x62, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x06, 0x54, 0x65, 0x73, 0x74, 0x70, 0x62, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/client/v2/internal/testpb/msg_grpc.pb.go b/client/v2/internal/testpb/msg_grpc.pb.go index ebcfba150bb9..b9ccec990607 100644 --- a/client/v2/internal/testpb/msg_grpc.pb.go +++ b/client/v2/internal/testpb/msg_grpc.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.3.0 +// - protoc-gen-go-grpc v1.2.0 // - protoc (unknown) // source: testpb/msg.proto @@ -18,11 +18,6 @@ import ( // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 -const ( - Msg_Send_FullMethodName = "/testpb.Msg/Send" - Msg_Clawback_FullMethodName = "/testpb.Msg/Clawback" -) - // MsgClient is the client API for Msg service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. @@ -42,7 +37,7 @@ func NewMsgClient(cc grpc.ClientConnInterface) MsgClient { func (c *msgClient) Send(ctx context.Context, in *MsgRequest, opts ...grpc.CallOption) (*MsgResponse, error) { out := new(MsgResponse) - err := c.cc.Invoke(ctx, Msg_Send_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, "/testpb.Msg/Send", in, out, opts...) if err != nil { return nil, err } @@ -51,7 +46,7 @@ func (c *msgClient) Send(ctx context.Context, in *MsgRequest, opts ...grpc.CallO func (c *msgClient) Clawback(ctx context.Context, in *MsgClawbackRequest, opts ...grpc.CallOption) (*MsgClawbackResponse, error) { out := new(MsgClawbackResponse) - err := c.cc.Invoke(ctx, Msg_Clawback_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, "/testpb.Msg/Clawback", in, out, opts...) if err != nil { return nil, err } @@ -101,7 +96,7 @@ func _Msg_Send_Handler(srv interface{}, ctx context.Context, dec func(interface{ } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: Msg_Send_FullMethodName, + FullMethod: "/testpb.Msg/Send", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(MsgServer).Send(ctx, req.(*MsgRequest)) @@ -119,7 +114,7 @@ func _Msg_Clawback_Handler(srv interface{}, ctx context.Context, dec func(interf } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: Msg_Clawback_FullMethodName, + FullMethod: "/testpb.Msg/Clawback", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(MsgServer).Clawback(ctx, req.(*MsgClawbackRequest)) diff --git a/client/v2/internal/testpb/query_grpc.pb.go b/client/v2/internal/testpb/query_grpc.pb.go index 9f444b76c729..56d177ddc86f 100644 --- a/client/v2/internal/testpb/query_grpc.pb.go +++ b/client/v2/internal/testpb/query_grpc.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.3.0 +// - protoc-gen-go-grpc v1.2.0 // - protoc (unknown) // source: testpb/query.proto @@ -18,10 +18,6 @@ import ( // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 -const ( - Query_Echo_FullMethodName = "/testpb.Query/Echo" -) - // QueryClient is the client API for Query service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. @@ -40,7 +36,7 @@ func NewQueryClient(cc grpc.ClientConnInterface) QueryClient { func (c *queryClient) Echo(ctx context.Context, in *EchoRequest, opts ...grpc.CallOption) (*EchoResponse, error) { out := new(EchoResponse) - err := c.cc.Invoke(ctx, Query_Echo_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, "/testpb.Query/Echo", in, out, opts...) if err != nil { return nil, err } @@ -86,7 +82,7 @@ func _Query_Echo_Handler(srv interface{}, ctx context.Context, dec func(interfac } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: Query_Echo_FullMethodName, + FullMethod: "/testpb.Query/Echo", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(QueryServer).Echo(ctx, req.(*EchoRequest)) diff --git a/client/v2/offchain/builder.go b/client/v2/offchain/builder.go deleted file mode 100644 index 55c5678cd15c..000000000000 --- a/client/v2/offchain/builder.go +++ /dev/null @@ -1,318 +0,0 @@ -package offchain - -// TODO: remove custom off-chain builder once v2 tx builder is developed. - -import ( - "errors" - "fmt" - - "github.com/cosmos/cosmos-proto/anyutil" - "github.com/cosmos/gogoproto/proto" - protov2 "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/anypb" - - basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1" - apitx "cosmossdk.io/api/cosmos/tx/v1beta1" - txsigning "cosmossdk.io/x/tx/signing" - - "github.com/cosmos/cosmos-sdk/codec" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" -) - -type builder struct { - cdc codec.Codec - tx *apitx.Tx -} - -func newBuilder(cdc codec.Codec) *builder { - return &builder{ - cdc: cdc, - tx: &apitx.Tx{ - Body: &apitx.TxBody{}, - AuthInfo: &apitx.AuthInfo{ - Fee: &apitx.Fee{ - Amount: nil, - GasLimit: 0, - Payer: "", - Granter: "", - }, - }, - Signatures: nil, - }, - } -} - -// GetTx returns the tx. -func (b *builder) GetTx() *apitx.Tx { - return b.tx -} - -// GetSigningTxData returns the necessary data to generate sign bytes. -func (b *builder) GetSigningTxData() (txsigning.TxData, error) { - body := b.tx.Body - authInfo := b.tx.AuthInfo - - msgs := make([]*anypb.Any, len(body.Messages)) - for i, msg := range body.Messages { - msgs[i] = &anypb.Any{ - TypeUrl: msg.TypeUrl, - Value: msg.Value, - } - } - - extOptions := make([]*anypb.Any, len(body.ExtensionOptions)) - for i, extOption := range body.ExtensionOptions { - extOptions[i] = &anypb.Any{ - TypeUrl: extOption.TypeUrl, - Value: extOption.Value, - } - } - - nonCriticalExtOptions := make([]*anypb.Any, len(body.NonCriticalExtensionOptions)) - for i, extOption := range body.NonCriticalExtensionOptions { - nonCriticalExtOptions[i] = &anypb.Any{ - TypeUrl: extOption.TypeUrl, - Value: extOption.Value, - } - } - - feeCoins := authInfo.Fee.Amount - feeAmount := make([]*basev1beta1.Coin, len(feeCoins)) - for i, coin := range feeCoins { - feeAmount[i] = &basev1beta1.Coin{ - Denom: coin.Denom, - Amount: coin.Amount, - } - } - - txSignerInfos := make([]*apitx.SignerInfo, len(authInfo.SignerInfos)) - for i, signerInfo := range authInfo.SignerInfos { - txSignerInfo := &apitx.SignerInfo{ - PublicKey: &anypb.Any{ - TypeUrl: signerInfo.PublicKey.TypeUrl, - Value: signerInfo.PublicKey.Value, - }, - Sequence: signerInfo.Sequence, - ModeInfo: signerInfo.ModeInfo, - } - txSignerInfos[i] = txSignerInfo - } - - txAuthInfo := &apitx.AuthInfo{ - SignerInfos: txSignerInfos, - Fee: &apitx.Fee{ - Amount: feeAmount, - GasLimit: authInfo.Fee.GasLimit, - Payer: authInfo.Fee.Payer, - Granter: authInfo.Fee.Granter, - }, - } - - txBody := &apitx.TxBody{ - Messages: msgs, - Memo: body.Memo, - TimeoutHeight: body.TimeoutHeight, - TimeoutTimestamp: body.TimeoutTimestamp, - ExtensionOptions: extOptions, - NonCriticalExtensionOptions: nonCriticalExtOptions, - } - authInfoBz, err := protov2.Marshal(b.tx.AuthInfo) - if err != nil { - return txsigning.TxData{}, err - } - bodyBz, err := protov2.Marshal(b.tx.Body) - if err != nil { - return txsigning.TxData{}, err - } - txData := txsigning.TxData{ - AuthInfo: txAuthInfo, - AuthInfoBytes: authInfoBz, - Body: txBody, - BodyBytes: bodyBz, - } - return txData, nil -} - -// GetPubKeys returns the pubKeys of the tx. -func (b *builder) GetPubKeys() ([]cryptotypes.PubKey, error) { // If signer already has pubkey in context, this list will have nil in its place - signerInfos := b.tx.AuthInfo.SignerInfos - pks := make([]cryptotypes.PubKey, len(signerInfos)) - - for i, si := range signerInfos { - // NOTE: it is okay to leave this nil if there is no PubKey in the SignerInfo. - // PubKey's can be left unset in SignerInfo. - if si.PublicKey == nil { - continue - } - var pk cryptotypes.PubKey - anyPk := &codectypes.Any{ - TypeUrl: si.PublicKey.TypeUrl, - Value: si.PublicKey.Value, - } - err := b.cdc.UnpackAny(anyPk, &pk) - if err != nil { - return nil, err - } - pks[i] = pk - } - - return pks, nil -} - -// GetSignatures returns the signatures of the tx. -func (b *builder) GetSignatures() ([]OffchainSignature, error) { - signerInfos := b.tx.AuthInfo.SignerInfos - sigs := b.tx.Signatures - pubKeys, err := b.GetPubKeys() - if err != nil { - return nil, err - } - n := len(signerInfos) - res := make([]OffchainSignature, n) - - for i, si := range signerInfos { - // handle nil signatures (in case of simulation) - if si.ModeInfo == nil { - res[i] = OffchainSignature{ - PubKey: pubKeys[i], - } - } else { - var err error - sigData, err := modeInfoAndSigToSignatureData(si.ModeInfo, sigs[i]) - if err != nil { - return nil, err - } - // sequence number is functionally a transaction nonce and referred to as such in the SDK - nonce := si.GetSequence() - res[i] = OffchainSignature{ - PubKey: pubKeys[i], - Data: sigData, - Sequence: nonce, - } - } - } - - return res, nil -} - -// GetSigners returns the signers of the tx. -func (b *builder) GetSigners() ([][]byte, error) { - signers, _, err := b.getSigners() - return signers, err -} - -func (b *builder) getSigners() ([][]byte, []protov2.Message, error) { - var signers [][]byte - seen := map[string]bool{} - - var msgsv2 []protov2.Message - for _, msg := range b.tx.Body.Messages { - msgv2, err := anyutil.Unpack(msg, b.cdc.InterfaceRegistry(), nil) - if err != nil { - return nil, nil, err - } - xs, err := b.cdc.InterfaceRegistry().SigningContext().GetSigners(msgv2) - if err != nil { - return nil, nil, err - } - - msgsv2 = append(msgsv2, msg) - - for _, signer := range xs { - if !seen[string(signer)] { - signers = append(signers, signer) - seen[string(signer)] = true - } - } - } - - return signers, msgsv2, nil -} - -func (b *builder) setMsgs(msgs ...proto.Message) error { - anys := make([]*anypb.Any, len(msgs)) - for i, msg := range msgs { - protoMsg, ok := msg.(protov2.Message) - if !ok { - return errors.New("message is not a proto.Message") - } - protov2MarshalOpts := protov2.MarshalOptions{Deterministic: true} - bz, err := protov2MarshalOpts.Marshal(protoMsg) - if err != nil { - return err - } - anys[i] = &anypb.Any{ - TypeUrl: codectypes.MsgTypeURL(msg), - Value: bz, - } - } - b.tx.Body.Messages = anys - return nil -} - -// SetSignatures set the signatures of the tx. -func (b *builder) SetSignatures(signatures ...OffchainSignature) error { - n := len(signatures) - signerInfos := make([]*apitx.SignerInfo, n) - rawSigs := make([][]byte, n) - var err error - for i, sig := range signatures { - var mi *apitx.ModeInfo - mi, rawSigs[i], err = b.signatureDataToModeInfoAndSig(sig.Data) - if err != nil { - return err - } - - pubKey, err := codectypes.NewAnyWithValue(sig.PubKey) - if err != nil { - return err - } - - signerInfos[i] = &apitx.SignerInfo{ - PublicKey: &anypb.Any{ - TypeUrl: pubKey.TypeUrl, - Value: pubKey.Value, - }, - ModeInfo: mi, - Sequence: sig.Sequence, - } - } - - b.tx.AuthInfo.SignerInfos = signerInfos - b.tx.Signatures = rawSigs - - return nil -} - -// signatureDataToModeInfoAndSig converts a SignatureData to a ModeInfo and raw bytes signature. -func (b *builder) signatureDataToModeInfoAndSig(data SignatureData) (*apitx.ModeInfo, []byte, error) { - if data == nil { - return nil, nil, errors.New("empty SignatureData") - } - - switch data := data.(type) { - case *SingleSignatureData: - return &apitx.ModeInfo{ - Sum: &apitx.ModeInfo_Single_{ - Single: &apitx.ModeInfo_Single{Mode: data.SignMode}, - }, - }, data.Signature, nil - default: - return nil, nil, fmt.Errorf("unexpected signature data type %T", data) - } -} - -// modeInfoAndSigToSignatureData converts a ModeInfo and raw bytes signature to a SignatureData. -func modeInfoAndSigToSignatureData(modeInfo *apitx.ModeInfo, sig []byte) (SignatureData, error) { - switch modeInfoType := modeInfo.Sum.(type) { - case *apitx.ModeInfo_Single_: - return &SingleSignatureData{ - SignMode: modeInfoType.Single.Mode, - Signature: sig, - }, nil - - default: - return nil, fmt.Errorf("unexpected ModeInfo data type %T", modeInfo) - } -} diff --git a/client/v2/offchain/cli.go b/client/v2/offchain/cli.go index fabb3f503beb..7738a6204451 100644 --- a/client/v2/offchain/cli.go +++ b/client/v2/offchain/cli.go @@ -13,10 +13,8 @@ import ( ) const ( - flagNotEmitUnpopulated = "notEmitUnpopulated" - flagIndent = "indent" - flagEncoding = "encoding" - flagFileFormat = "file-format" + flagEncoding = "encoding" + flagFileFormat = "file-format" ) // OffChain off-chain utilities. @@ -51,13 +49,12 @@ func SignFile() *cobra.Command { return err } - notEmitUnpopulated, _ := cmd.Flags().GetBool(flagNotEmitUnpopulated) - indent, _ := cmd.Flags().GetString(flagIndent) encoding, _ := cmd.Flags().GetString(flagEncoding) outputFormat, _ := cmd.Flags().GetString(v2flags.FlagOutput) outputFile, _ := cmd.Flags().GetString(flags.FlagOutputDocument) + signMode, _ := cmd.Flags().GetString(flags.FlagSignMode) - signedTx, err := Sign(clientCtx, bz, args[0], indent, encoding, outputFormat, !notEmitUnpopulated) + signedTx, err := Sign(clientCtx, bz, args[0], encoding, signMode, outputFormat) if err != nil { return err } @@ -75,28 +72,27 @@ func SignFile() *cobra.Command { }, } - cmd.Flags().String(flagIndent, " ", "Choose an indent for the tx") cmd.Flags().String(v2flags.FlagOutput, "json", "Choose an output format for the tx (json|text") - cmd.Flags().Bool(flagNotEmitUnpopulated, false, "Don't show unpopulated fields in the tx") cmd.Flags().String(flagEncoding, "no-encoding", "Choose an encoding method for the file content to be added as msg data (no-encoding|base64|hex)") cmd.Flags().String(flags.FlagOutputDocument, "", "The document will be written to the given file instead of STDOUT") + cmd.PersistentFlags().String(flags.FlagSignMode, "direct", "Choose sign mode (direct|amino-json)") return cmd } // VerifyFile verifies given file with given key. func VerifyFile() *cobra.Command { cmd := &cobra.Command{ - Use: "verify-file ", + Use: "verify-file ", Short: "Verify a file.", Long: "Verify a previously signed file with the given key.", - Args: cobra.ExactArgs(2), + Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientQueryContext(cmd) if err != nil { return err } - bz, err := os.ReadFile(args[1]) + bz, err := os.ReadFile(args[0]) if err != nil { return err } diff --git a/client/v2/offchain/encode.go b/client/v2/offchain/encode.go index 6721ffc28cf2..be43316a655f 100644 --- a/client/v2/offchain/encode.go +++ b/client/v2/offchain/encode.go @@ -42,3 +42,12 @@ func getEncoder(encoder string) (encodingFunc, error) { return nil, fmt.Errorf("unknown encoder: %s", encoder) } } + +func encodeDigest(encodingFormat string, digest []byte) (string, error) { + encoder, err := getEncoder(encodingFormat) + if err != nil { + return "", err + } + + return encoder(digest) +} diff --git a/client/v2/offchain/marshal.go b/client/v2/offchain/marshal.go deleted file mode 100644 index 8f90b9830d3f..000000000000 --- a/client/v2/offchain/marshal.go +++ /dev/null @@ -1,43 +0,0 @@ -package offchain - -import ( - "fmt" - - "google.golang.org/protobuf/encoding/protojson" - "google.golang.org/protobuf/encoding/prototext" - "google.golang.org/protobuf/proto" - - apitx "cosmossdk.io/api/cosmos/tx/v1beta1" - v2flags "cosmossdk.io/client/v2/internal/flags" -) - -// marshaller marshals Messages. -type marshaller interface { - Marshal(message proto.Message) ([]byte, error) -} - -// getMarshaller returns the marshaller for the given marshaller id. -func getMarshaller(marshallerId, indent string, emitUnpopulated bool) (marshaller, error) { - switch marshallerId { - case v2flags.OutputFormatJSON: - return protojson.MarshalOptions{ - Indent: indent, - EmitUnpopulated: emitUnpopulated, - }, nil - case v2flags.OutputFormatText: - return prototext.MarshalOptions{ - Indent: indent, - EmitUnknown: emitUnpopulated, - }, nil - } - return nil, fmt.Errorf("marshaller with id '%s' not identified", marshallerId) -} - -// marshalOffChainTx marshals a Tx using given marshaller. -func marshalOffChainTx(tx *apitx.Tx, marshaller marshaller) (string, error) { - bytesTx, err := marshaller.Marshal(tx) - if err != nil { - return "", err - } - return string(bytesTx), nil -} diff --git a/client/v2/offchain/msgSignArbitraryData.pb.go b/client/v2/offchain/msgSignArbitraryData.pb.go new file mode 100644 index 000000000000..2733b9182b95 --- /dev/null +++ b/client/v2/offchain/msgSignArbitraryData.pb.go @@ -0,0 +1,432 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: offchain/msgSignArbitraryData.proto + +package offchain + +import ( + fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + _ "github.com/cosmos/cosmos-sdk/types/msgservice" + _ "github.com/cosmos/cosmos-sdk/types/tx/amino" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// MsgSignArbitraryData defines an arbitrary, general-purpose, off-chain message +type MsgSignArbitraryData struct { + // AppDomain is the application requesting off-chain message signing + AppDomain string `protobuf:"bytes,1,opt,name=app_domain,json=appDomain,proto3" json:"app_domain,omitempty"` + // Signer is the sdk.AccAddress of the message signer + Signer string `protobuf:"bytes,2,opt,name=signer,proto3" json:"signer,omitempty"` + // Data represents the raw bytes of the content that is signed (text, json, etc) + Data string `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` +} + +func (m *MsgSignArbitraryData) Reset() { *m = MsgSignArbitraryData{} } +func (m *MsgSignArbitraryData) String() string { return proto.CompactTextString(m) } +func (*MsgSignArbitraryData) ProtoMessage() {} +func (*MsgSignArbitraryData) Descriptor() ([]byte, []int) { + return fileDescriptor_f3e1b1b538b29252, []int{0} +} +func (m *MsgSignArbitraryData) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSignArbitraryData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSignArbitraryData.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgSignArbitraryData) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSignArbitraryData.Merge(m, src) +} +func (m *MsgSignArbitraryData) XXX_Size() int { + return m.Size() +} +func (m *MsgSignArbitraryData) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSignArbitraryData.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSignArbitraryData proto.InternalMessageInfo + +func (m *MsgSignArbitraryData) GetAppDomain() string { + if m != nil { + return m.AppDomain + } + return "" +} + +func (m *MsgSignArbitraryData) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +func (m *MsgSignArbitraryData) GetData() string { + if m != nil { + return m.Data + } + return "" +} + +func init() { + proto.RegisterType((*MsgSignArbitraryData)(nil), "offchain.MsgSignArbitraryData") +} + +func init() { + proto.RegisterFile("offchain/msgSignArbitraryData.proto", fileDescriptor_f3e1b1b538b29252) +} + +var fileDescriptor_f3e1b1b538b29252 = []byte{ + // 267 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0xce, 0x4f, 0x4b, 0x4b, + 0xce, 0x48, 0xcc, 0xcc, 0xd3, 0xcf, 0x2d, 0x4e, 0x0f, 0xce, 0x4c, 0xcf, 0x73, 0x2c, 0x4a, 0xca, + 0x2c, 0x29, 0x4a, 0x2c, 0xaa, 0x74, 0x49, 0x2c, 0x49, 0xd4, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, + 0xe2, 0x80, 0x29, 0x92, 0x92, 0x4c, 0xce, 0x2f, 0xce, 0xcd, 0x2f, 0x8e, 0x07, 0x8b, 0xeb, 0x43, + 0x38, 0x10, 0x45, 0x52, 0xe2, 0x10, 0x1e, 0xc8, 0x1c, 0xfd, 0x32, 0x43, 0x10, 0x05, 0x95, 0x10, + 0x4c, 0xcc, 0xcd, 0xcc, 0xcb, 0xd7, 0x07, 0x93, 0x10, 0x21, 0xa5, 0x55, 0x8c, 0x5c, 0x22, 0xbe, + 0x58, 0xec, 0x13, 0x92, 0xe5, 0xe2, 0x4a, 0x2c, 0x28, 0x88, 0x4f, 0xc9, 0xcf, 0x4d, 0xcc, 0xcc, + 0x93, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0xe2, 0x4c, 0x2c, 0x28, 0x70, 0x01, 0x0b, 0x08, 0x19, + 0x70, 0xb1, 0x15, 0x67, 0xa6, 0xe7, 0xa5, 0x16, 0x49, 0x30, 0x81, 0xa4, 0x9c, 0x24, 0x2e, 0x6d, + 0xd1, 0x15, 0x81, 0xba, 0xc2, 0x31, 0x25, 0xa5, 0x28, 0xb5, 0xb8, 0x38, 0xb8, 0xa4, 0x28, 0x33, + 0x2f, 0x3d, 0x08, 0xaa, 0x4e, 0x48, 0x88, 0x8b, 0x25, 0x25, 0xb1, 0x24, 0x51, 0x82, 0x19, 0x6c, + 0x14, 0x98, 0x6d, 0xa5, 0xdb, 0xf4, 0x7c, 0x83, 0x16, 0x54, 0x41, 0xd7, 0xf3, 0x0d, 0x5a, 0xb2, + 0xf0, 0x30, 0xc0, 0xe6, 0x26, 0x27, 0xcb, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, + 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, + 0x88, 0x92, 0x87, 0x58, 0x5d, 0x9c, 0x92, 0xad, 0x97, 0x99, 0xaf, 0x9f, 0x9c, 0x93, 0x99, 0x9a, + 0x57, 0xa2, 0x5f, 0x66, 0xa4, 0x0f, 0x33, 0x2f, 0x89, 0x0d, 0xec, 0x5d, 0x63, 0x40, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x78, 0x04, 0xe8, 0x80, 0x66, 0x01, 0x00, 0x00, +} + +func (m *MsgSignArbitraryData) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgSignArbitraryData) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSignArbitraryData) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintMsgSignArbitraryData(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0x1a + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintMsgSignArbitraryData(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x12 + } + if len(m.AppDomain) > 0 { + i -= len(m.AppDomain) + copy(dAtA[i:], m.AppDomain) + i = encodeVarintMsgSignArbitraryData(dAtA, i, uint64(len(m.AppDomain))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintMsgSignArbitraryData(dAtA []byte, offset int, v uint64) int { + offset -= sovMsgSignArbitraryData(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgSignArbitraryData) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.AppDomain) + if l > 0 { + n += 1 + l + sovMsgSignArbitraryData(uint64(l)) + } + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovMsgSignArbitraryData(uint64(l)) + } + l = len(m.Data) + if l > 0 { + n += 1 + l + sovMsgSignArbitraryData(uint64(l)) + } + return n +} + +func sovMsgSignArbitraryData(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozMsgSignArbitraryData(x uint64) (n int) { + return sovMsgSignArbitraryData(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgSignArbitraryData) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgSignArbitraryData + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgSignArbitraryData: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSignArbitraryData: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppDomain", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgSignArbitraryData + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthMsgSignArbitraryData + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMsgSignArbitraryData + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppDomain = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgSignArbitraryData + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthMsgSignArbitraryData + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMsgSignArbitraryData + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgSignArbitraryData + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthMsgSignArbitraryData + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMsgSignArbitraryData + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMsgSignArbitraryData(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthMsgSignArbitraryData + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipMsgSignArbitraryData(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowMsgSignArbitraryData + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowMsgSignArbitraryData + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowMsgSignArbitraryData + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthMsgSignArbitraryData + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupMsgSignArbitraryData + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthMsgSignArbitraryData + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthMsgSignArbitraryData = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowMsgSignArbitraryData = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupMsgSignArbitraryData = fmt.Errorf("proto: unexpected end of group") +) diff --git a/client/v2/offchain/sign.go b/client/v2/offchain/sign.go index b36a50c00ca0..8dfcb907c089 100644 --- a/client/v2/offchain/sign.go +++ b/client/v2/offchain/sign.go @@ -2,18 +2,15 @@ package offchain import ( "context" - - "google.golang.org/protobuf/types/known/anypb" + "fmt" apisigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" - apitx "cosmossdk.io/api/cosmos/tx/v1beta1" + "cosmossdk.io/client/v2/internal/account" "cosmossdk.io/client/v2/internal/offchain" - txsigning "cosmossdk.io/x/tx/signing" + clitx "cosmossdk.io/client/v2/tx" "github.com/cosmos/cosmos-sdk/client" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/crypto/keyring" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "github.com/cosmos/cosmos-sdk/version" ) @@ -24,58 +21,64 @@ const ( ExpectedAccountNumber = 0 // ExpectedSequence defines the sequence number an off-chain message must have ExpectedSequence = 0 - - signMode = apisigning.SignMode_SIGN_MODE_TEXTUAL ) -type signerData struct { - Address string - ChainID string - AccountNumber uint64 - Sequence uint64 - PubKey cryptotypes.PubKey +var enabledSignModes = []apisigning.SignMode{ + apisigning.SignMode_SIGN_MODE_DIRECT, + apisigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, } // Sign signs given bytes using the specified encoder and SignMode. -func Sign(ctx client.Context, rawBytes []byte, fromName, indent, encoding, output string, emitUnpopulated bool) (string, error) { - encoder, err := getEncoder(encoding) +func Sign(ctx client.Context, rawBytes []byte, fromName, encoding, signMode, output string) (string, error) { + digest, err := encodeDigest(encoding, rawBytes) if err != nil { return "", err } - digest, err := encoder(rawBytes) + keybase, err := keyring.NewAutoCLIKeyring(ctx.Keyring, ctx.AddressCodec) if err != nil { return "", err } - tx, err := sign(ctx, fromName, digest) + txConfig, err := clitx.NewTxConfig(clitx.ConfigOptions{ + AddressCodec: ctx.AddressCodec, + Cdc: ctx.Codec, + ValidatorAddressCodec: ctx.ValidatorAddressCodec, + EnabledSignModes: enabledSignModes, + }) if err != nil { return "", err } - txMarshaller, err := getMarshaller(output, indent, emitUnpopulated) + accRetriever := account.NewAccountRetriever(ctx.AddressCodec, ctx, ctx.InterfaceRegistry) + + sm, err := getSignMode(signMode) if err != nil { return "", err } + params := clitx.TxParameters{ + ChainID: ExpectedChainID, + SignMode: sm, + AccountConfig: clitx.AccountConfig{ + AccountNumber: ExpectedAccountNumber, + Sequence: ExpectedSequence, + FromName: fromName, + }, + } - return marshalOffChainTx(tx, txMarshaller) -} - -// sign signs a digest with provided key and SignMode. -func sign(ctx client.Context, fromName, digest string) (*apitx.Tx, error) { - keybase, err := keyring.NewAutoCLIKeyring(ctx.Keyring, ctx.AddressCodec) + txf, err := clitx.NewFactory(keybase, ctx.Codec, accRetriever, txConfig, ctx.AddressCodec, ctx, params) if err != nil { - return nil, err + return "", err } pubKey, err := keybase.GetPubKey(fromName) if err != nil { - return nil, err + return "", err } addr, err := ctx.AddressCodec.BytesToString(pubKey.Address()) if err != nil { - return nil, err + return "", err } msg := &offchain.MsgSignArbitraryData{ @@ -84,84 +87,38 @@ func sign(ctx client.Context, fromName, digest string) (*apitx.Tx, error) { Data: digest, } - txBuilder := newBuilder(ctx.Codec) - err = txBuilder.setMsgs(msg) - if err != nil { - return nil, err - } - - signerData := signerData{ - Address: addr, - ChainID: ExpectedChainID, - AccountNumber: ExpectedAccountNumber, - Sequence: ExpectedSequence, - PubKey: pubKey, - } - - sigData := &SingleSignatureData{ - SignMode: signMode, - Signature: nil, - } - - sig := OffchainSignature{ - PubKey: pubKey, - Data: sigData, - Sequence: ExpectedSequence, - } - - sigs := []OffchainSignature{sig} - err = txBuilder.SetSignatures(sigs...) - if err != nil { - return nil, err - } - - bytesToSign, err := getSignBytes( - context.Background(), ctx.TxConfig.SignModeHandler(), signerData, txBuilder) - if err != nil { - return nil, err - } - - signedBytes, err := keybase.Sign(fromName, bytesToSign, signMode) + signedTx, err := txf.BuildsSignedTx(context.Background(), msg) if err != nil { - return nil, err + return "", err } - sigData.Signature = signedBytes - - err = txBuilder.SetSignatures(sig) + bz, err := encode(output, signedTx, txConfig) if err != nil { - return nil, err + return "", err } - return txBuilder.GetTx(), nil + return string(bz), nil } -// getSignBytes gets the bytes to be signed for the given Tx and SignMode. -func getSignBytes(ctx context.Context, - handlerMap *txsigning.HandlerMap, - signerData signerData, - tx *builder, -) ([]byte, error) { - txData, err := tx.GetSigningTxData() - if err != nil { - return nil, err - } - - anyPk, err := codectypes.NewAnyWithValue(signerData.PubKey) - if err != nil { - return nil, err +func encode(output string, tx clitx.Tx, config clitx.TxConfig) ([]byte, error) { + switch output { + case "json": + return config.TxJSONEncoder()(tx) + case "text": + return config.TxTextEncoder()(tx) + default: + return nil, fmt.Errorf("unsupported output type: %s", output) } +} - txSignerData := txsigning.SignerData{ - ChainID: signerData.ChainID, - AccountNumber: signerData.AccountNumber, - Sequence: signerData.Sequence, - Address: signerData.Address, - PubKey: &anypb.Any{ - TypeUrl: anyPk.TypeUrl, - Value: anyPk.Value, - }, +// getSignMode returns the corresponding apisigning.SignMode based on the provided mode string. +func getSignMode(mode string) (apisigning.SignMode, error) { + switch mode { + case "direct": + return apisigning.SignMode_SIGN_MODE_DIRECT, nil + case "amino-json": + return apisigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, nil } - return handlerMap.GetSignBytes(ctx, signMode, txSignerData, txData) + return apisigning.SignMode_SIGN_MODE_UNSPECIFIED, fmt.Errorf("unsupported sign mode: %s", mode) } diff --git a/client/v2/offchain/sign_test.go b/client/v2/offchain/sign_test.go index b47a84584d36..839872866629 100644 --- a/client/v2/offchain/sign_test.go +++ b/client/v2/offchain/sign_test.go @@ -11,41 +11,54 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keyring" ) -func Test_sign(t *testing.T) { +func TestSign(t *testing.T) { k := keyring.NewInMemory(getCodec()) + _, err := k.NewAccount("signVerify", mnemonic, "", "m/44'/118'/0'/0/0", hd.Secp256k1) + require.NoError(t, err) ctx := client.Context{ - Keyring: k, - TxConfig: newTestConfig(t), - AddressCodec: address.NewBech32Codec("cosmos"), - } - - type args struct { - ctx client.Context - fromName string - digest string + TxConfig: newTestConfig(t), + Codec: getCodec(), + AddressCodec: address.NewBech32Codec("cosmos"), + ValidatorAddressCodec: address.NewBech32Codec("cosmosvaloper"), + Keyring: k, } tests := []struct { - name string - args args + name string + rawBytes []byte + encoding string + signMode string + wantErr bool }{ { - name: "Sign", - args: args{ - ctx: ctx, - fromName: "direct", - digest: "Hello world!", - }, + name: "sign direct", + rawBytes: []byte("hello world"), + encoding: noEncoder, + signMode: "direct", + }, + { + name: "sign amino", + rawBytes: []byte("hello world"), + encoding: noEncoder, + signMode: "amino-json", + }, + { + name: "not supported sign mode", + rawBytes: []byte("hello world"), + encoding: noEncoder, + signMode: "textual", + wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, err := k.NewAccount(tt.args.fromName, mnemonic, tt.name, "m/44'/118'/0'/0/0", hd.Secp256k1) - require.NoError(t, err) - - got, err := sign(tt.args.ctx, tt.args.fromName, tt.args.digest) - require.NoError(t, err) - require.NotNil(t, got) + got, err := Sign(ctx, tt.rawBytes, "signVerify", tt.encoding, tt.signMode, "json") + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.NotNil(t, got) + } }) } } diff --git a/client/v2/offchain/signature.go b/client/v2/offchain/signature.go deleted file mode 100644 index d7b9769de983..000000000000 --- a/client/v2/offchain/signature.go +++ /dev/null @@ -1,34 +0,0 @@ -package offchain - -import ( - apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" - - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" -) - -type SignatureData interface { - isSignatureData() -} - -func (m *SingleSignatureData) isSignatureData() {} - -type SingleSignatureData struct { - // SignMode represents the SignMode of the signature - SignMode apitxsigning.SignMode - - // Signature is the raw signature. - Signature []byte -} - -type OffchainSignature struct { - // PubKey is the public key to use for verifying the signature - PubKey cryptotypes.PubKey - - // Data is the actual data of the signature which includes SignMode's and - // the signatures themselves for either single or multi-signatures. - Data SignatureData - - // Sequence is the sequence of this account. Only populated in - // SIGN_MODE_DIRECT. - Sequence uint64 -} diff --git a/client/v2/offchain/verify.go b/client/v2/offchain/verify.go index 8b9580d63235..2c064faccc71 100644 --- a/client/v2/offchain/verify.go +++ b/client/v2/offchain/verify.go @@ -6,12 +6,9 @@ import ( "errors" "fmt" - "google.golang.org/protobuf/encoding/protojson" - "google.golang.org/protobuf/encoding/prototext" "google.golang.org/protobuf/types/known/anypb" - apitx "cosmossdk.io/api/cosmos/tx/v1beta1" - v2flags "cosmossdk.io/client/v2/internal/flags" + clitx "cosmossdk.io/client/v2/tx" txsigning "cosmossdk.io/x/tx/signing" "github.com/cosmos/cosmos-sdk/client" @@ -21,29 +18,34 @@ import ( // Verify verifies a digest after unmarshalling it. func Verify(ctx client.Context, digest []byte, fileFormat string) error { - tx, err := unmarshal(digest, fileFormat) + txConfig, err := clitx.NewTxConfig(clitx.ConfigOptions{ + AddressCodec: ctx.AddressCodec, + Cdc: ctx.Codec, + ValidatorAddressCodec: ctx.ValidatorAddressCodec, + EnabledSignModes: enabledSignModes, + }) if err != nil { return err } - return verify(ctx, tx) + dTx, err := unmarshal(fileFormat, digest, txConfig) + if err != nil { + return err + } + + return verify(ctx, dTx) } // verify verifies given Tx. -func verify(ctx client.Context, tx *apitx.Tx) error { - sigTx := builder{ - cdc: ctx.Codec, - tx: tx, - } - +func verify(ctx client.Context, dTx clitx.Tx) error { signModeHandler := ctx.TxConfig.SignModeHandler() - signers, err := sigTx.GetSigners() + signers, err := dTx.GetSigners() if err != nil { return err } - sigs, err := sigTx.GetSignatures() + sigs, err := dTx.GetSignatures() if err != nil { return err } @@ -79,7 +81,7 @@ func verify(ctx client.Context, tx *apitx.Tx) error { }, } - txData, err := sigTx.GetSigningTxData() + txData, err := dTx.GetSigningTxData() if err != nil { return err } @@ -93,18 +95,15 @@ func verify(ctx client.Context, tx *apitx.Tx) error { } // unmarshal unmarshalls a digest to a Tx using protobuf protojson. -func unmarshal(digest []byte, fileFormat string) (*apitx.Tx, error) { - var err error - tx := &apitx.Tx{} - switch fileFormat { - case v2flags.OutputFormatJSON: - err = protojson.Unmarshal(digest, tx) - case v2flags.OutputFormatText: - err = prototext.Unmarshal(digest, tx) +func unmarshal(format string, bz []byte, config clitx.TxConfig) (clitx.Tx, error) { + switch format { + case "json": + return config.TxJSONDecoder()(bz) + case "text": + return config.TxTextDecoder()(bz) default: - return nil, fmt.Errorf("unsupported file format: %s", fileFormat) + return nil, fmt.Errorf("unsupported format: %s", format) } - return tx, err } // verifySignature verifies a transaction signature contained in SignatureData abstracting over different signing modes. @@ -112,12 +111,12 @@ func verifySignature( ctx context.Context, pubKey cryptotypes.PubKey, signerData txsigning.SignerData, - signatureData SignatureData, + signatureData clitx.SignatureData, handler *txsigning.HandlerMap, txData txsigning.TxData, ) error { switch data := signatureData.(type) { - case *SingleSignatureData: + case *clitx.SingleSignatureData: signBytes, err := handler.GetSignBytes(ctx, data.SignMode, signerData, txData) if err != nil { return err diff --git a/client/v2/offchain/verify_test.go b/client/v2/offchain/verify_test.go index ecdd57e75b5b..56345504d80e 100644 --- a/client/v2/offchain/verify_test.go +++ b/client/v2/offchain/verify_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/require" _ "cosmossdk.io/api/cosmos/crypto/secp256k1" + clitx "cosmossdk.io/client/v2/tx" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec/address" @@ -15,9 +16,10 @@ import ( func Test_Verify(t *testing.T) { ctx := client.Context{ - TxConfig: newTestConfig(t), - Codec: getCodec(), - AddressCodec: address.NewBech32Codec("cosmos"), + TxConfig: newTestConfig(t), + Codec: getCodec(), + AddressCodec: address.NewBech32Codec("cosmos"), + ValidatorAddressCodec: address.NewBech32Codec("cosmosvaloper"), } tests := []struct { @@ -29,26 +31,26 @@ func Test_Verify(t *testing.T) { }{ { name: "verify json", - digest: []byte("{\"body\":{\"messages\":[{\"@type\":\"/offchain.MsgSignArbitraryData\", \"appDomain\":\"simd\", \"signer\":\"cosmos1x33fy6rusfprkntvjsfregss7rvsvyy4lkwrqu\", \"data\":\"{\\n\\t\\\"name\\\": \\\"John\\\",\\n\\t\\\"surname\\\": \\\"Connor\\\",\\n\\t\\\"age\\\": 15\\n}\\n\"}]}, \"authInfo\":{\"signerInfos\":[{\"publicKey\":{\"@type\":\"/cosmos.crypto.secp256k1.PubKey\", \"key\":\"A/Bfsb7grZtysreo48oB1XAXbcgHnEJyhAqzDMgbLlXw\"}, \"modeInfo\":{\"single\":{\"mode\":\"SIGN_MODE_TEXTUAL\"}}}], \"fee\":{}}, \"signatures\":[\"gRufjcmATaJ3hZSiXII3lcsLDJlHM4OhQs3O/QgAK4weQ73kmj30/gw3HwTKxGb4pnVe0iyLXrKRNeSl1O3zSQ==\"]}"), + digest: []byte("{\"body\":{\"messages\":[{\"@type\":\"/offchain.MsgSignArbitraryData\", \"app_domain\":\"\", \"signer\":\"cosmos16877zjk85kwlap3wclpmx34e0xllg2erc7u7m4\", \"data\":\"{\\n\\t\\\"name\\\": \\\"Sarah\\\",\\n\\t\\\"surname\\\": \\\"Connor\\\",\\n\\t\\\"age\\\": 29\\n}\\n\"}], \"timeout_timestamp\":\"0001-01-01T00:00:00Z\"}, \"auth_info\":{\"signer_infos\":[{\"public_key\":{\"@type\":\"/cosmos.crypto.secp256k1.PubKey\", \"key\":\"Ahhu3idSSUAQXtDBvBjUlCPWH3od4rXyWgb7L4scSj4m\"}, \"mode_info\":{\"single\":{\"mode\":\"SIGN_MODE_DIRECT\"}}}], \"fee\":{}}, \"signatures\":[\"tdXsO5uNqIBFSBKEA1e3Wrcb6ejriP9HwlcBTkU7EUJzuezjg6Rvr1a+Kp6umCAN7MWoBHRT2cmqzDfg6RjaYA==\"]}"), fileFormat: "json", ctx: ctx, }, { name: "wrong signer json", - digest: []byte("{\"body\":{\"messages\":[{\"@type\":\"/offchain.MsgSignArbitraryData\", \"appDomain\":\"simd\", \"signer\":\"cosmos1450l4uau674z55c36df0v7904rnvdk9aq8w96j\", \"data\":\"{\\n\\t\\\"name\\\": \\\"John\\\",\\n\\t\\\"surname\\\": \\\"Connor\\\",\\n\\t\\\"age\\\": 15\\n}\\n\"}]}, \"authInfo\":{\"signerInfos\":[{\"publicKey\":{\"@type\":\"/cosmos.crypto.secp256k1.PubKey\", \"key\":\"A/Bfsb7grZtysreo48oB1XAXbcgHnEJyhAqzDMgbLlXw\"}, \"modeInfo\":{\"single\":{\"mode\":\"SIGN_MODE_TEXTUAL\"}}}], \"fee\":{}}, \"signatures\":[\"gRufjcmATaJ3hZSiXII3lcsLDJlHM4OhQs3O/QgAK4weQ73kmj30/gw3HwTKxGb4pnVe0iyLXrKRNeSl1O3zSQ==\"]}"), + digest: []byte("{\"body\":{\"messages\":[{\"@type\":\"/offchain.MsgSignArbitraryData\", \"app_domain\":\"\", \"signer\":\"cosmos1xv9e39mkhhyg5aneu2myj82t7029sv48qu3pgj\", \"data\":\"{\\n\\t\\\"name\\\": \\\"Sarah\\\",\\n\\t\\\"surname\\\": \\\"Connor\\\",\\n\\t\\\"age\\\": 29\\n}\\n\"}], \"timeout_timestamp\":\"0001-01-01T00:00:00Z\"}, \"auth_info\":{\"signer_infos\":[{\"public_key\":{\"@type\":\"/cosmos.crypto.secp256k1.PubKey\", \"key\":\"Ahhu3idSSUAQXtDBvBjUlCPWH3od4rXyWgb7L4scSj4m\"}, \"mode_info\":{\"single\":{\"mode\":\"SIGN_MODE_DIRECT\"}}}], \"fee\":{}}, \"signatures\":[\"tdXsO5uNqIBFSBKEA1e3Wrcb6ejriP9HwlcBTkU7EUJzuezjg6Rvr1a+Kp6umCAN7MWoBHRT2cmqzDfg6RjaYA==\"]}"), fileFormat: "json", ctx: ctx, wantErr: true, }, { name: "verify text", - digest: []byte("body:{messages:{[/offchain.MsgSignArbitraryData]:{app_domain:\"simd\" signer:\"cosmos1x33fy6rusfprkntvjsfregss7rvsvyy4lkwrqu\" data:\"{\\n\\t\\\"name\\\": \\\"John\\\",\\n\\t\\\"surname\\\": \\\"Connor\\\",\\n\\t\\\"age\\\": 15\\n}\\n\"}}} auth_info:{signer_infos:{public_key:{[/cosmos.crypto.secp256k1.PubKey]:{key:\"\\x03\\xf0_\\xb1\\xbe\u0B5Br\\xb2\\xb7\\xa8\\xe3\\xca\\x01\\xd5p\\x17m\\xc8\\x07\\x9cBr\\x84\\n\\xb3\\x0c\\xc8\\x1b.U\\xf0\"}} mode_info:{single:{mode:SIGN_MODE_TEXTUAL}}} fee:{}} signatures:\"\\x81\\x1b\\x9f\\x8dɀM\\xa2w\\x85\\x94\\xa2\\\\\\x827\\x95\\xcb\\x0b\\x0c\\x99G3\\x83\\xa1B\\xcd\\xce\\xfd\\x08\\x00+\\x8c\\x1eC\\xbd\\xe4\\x9a=\\xf4\\xfe\\x0c7\\x1f\\x04\\xca\\xc4f\\xf8\\xa6u^\\xd2,\\x8b^\\xb2\\x915\\xe4\\xa5\\xd4\\xed\\xf3I\"\n"), + digest: []byte("body:{messages:{[/offchain.MsgSignArbitraryData]:{app_domain:\"\" signer:\"cosmos16877zjk85kwlap3wclpmx34e0xllg2erc7u7m4\" data:\"{\\n\\t\\\"name\\\": \\\"Sarah\\\",\\n\\t\\\"surname\\\": \\\"Connor\\\",\\n\\t\\\"age\\\": 29\\n}\\n\"}} timeout_timestamp:{seconds:-62135596800}} auth_info:{signer_infos:{public_key:{[/cosmos.crypto.secp256k1.PubKey]:{key:\"\\x02\\x18n\\xde'RI@\\x10^\\xd0\\xc1\\xbc\\x18Ԕ#\\xd6\\x1fz\\x1d\\xe2\\xb5\\xf2Z\\x06\\xfb/\\x8b\\x1cJ>&\"}} mode_info:{single:{mode:SIGN_MODE_DIRECT}}} fee:{}} signatures:\"\\xb5\\xd5\\xec;\\x9b\\x8d\\xa8\\x80EH\\x12\\x84\\x03W\\xb7Z\\xb7\\x1b\\xe9\\xe8\\xeb\\x88\\xffG\\xc2W\\x01NE;\\x11Bs\\xb9\\xecヤo\\xafV\\xbe*\\x9e\\xae\\x98 \\r\\xecŨ\\x04tS\\xd9ɪ\\xcc7\\xe0\\xe9\\x18\\xda`\"\n"), fileFormat: "text", ctx: ctx, }, { name: "wrong signer text", - digest: []byte("\"body:{messages:{[/offchain.MsgSignArbitraryData]:{app_domain:\\\"simd\\\" signer:\\\"cosmos1450l4uau674z55c36df0v7904rnvdk9aq8w96j\\\" data:\\\"{\\\\n\\\\t\\\\\\\"name\\\\\\\": \\\\\\\"John\\\\\\\",\\\\n\\\\t\\\\\\\"surname\\\\\\\": \\\\\\\"Connor\\\\\\\",\\\\n\\\\t\\\\\\\"age\\\\\\\": 15\\\\n}\\\\n\\\"}}} auth_info:{signer_infos:{public_key:{[/cosmos.crypto.secp256k1.PubKey]:{key:\\\"\\\\x03\\\\xf0_\\\\xb1\\\\xbe\\u0B5Br\\\\xb2\\\\xb7\\\\xa8\\\\xe3\\\\xca\\\\x01\\\\xd5p\\\\x17m\\\\xc8\\\\x07\\\\x9cBr\\\\x84\\\\n\\\\xb3\\\\x0c\\\\xc8\\\\x1b.U\\\\xf0\\\"}} mode_info:{single:{mode:SIGN_MODE_TEXTUAL}}} fee:{}} signatures:\\\"\\\\x81\\\\x1b\\\\x9f\\\\x8dɀM\\\\xa2w\\\\x85\\\\x94\\\\xa2\\\\\\\\\\\\x827\\\\x95\\\\xcb\\\\x0b\\\\x0c\\\\x99G3\\\\x83\\\\xa1B\\\\xcd\\\\xce\\\\xfd\\\\x08\\\\x00+\\\\x8c\\\\x1eC\\\\xbd\\\\xe4\\\\x9a=\\\\xf4\\\\xfe\\\\x0c7\\\\x1f\\\\x04\\\\xca\\\\xc4f\\\\xf8\\\\xa6u^\\\\xd2,\\\\x8b^\\\\xb2\\\\x915\\\\xe4\\\\xa5\\\\xd4\\\\xed\\\\xf3I\\\"\\n"), + digest: []byte("body:{messages:{[/offchain.MsgSignArbitraryData]:{app_domain:\"\" signer:\"cosmos1xv9e39mkhhyg5aneu2myj82t7029sv48qu3pgj\" data:\"{\\n\\t\\\"name\\\": \\\"Sarah\\\",\\n\\t\\\"surname\\\": \\\"Connor\\\",\\n\\t\\\"age\\\": 29\\n}\\n\"}} timeout_timestamp:{seconds:-62135596800}} auth_info:{signer_infos:{public_key:{[/cosmos.crypto.secp256k1.PubKey]:{key:\"\\x02\\x18n\\xde'RI@\\x10^\\xd0\\xc1\\xbc\\x18Ԕ#\\xd6\\x1fz\\x1d\\xe2\\xb5\\xf2Z\\x06\\xfb/\\x8b\\x1cJ>&\"}} mode_info:{single:{mode:SIGN_MODE_DIRECT}}} fee:{}} signatures:\"\\xb5\\xd5\\xec;\\x9b\\x8d\\xa8\\x80EH\\x12\\x84\\x03W\\xb7Z\\xb7\\x1b\\xe9\\xe8\\xeb\\x88\\xffG\\xc2W\\x01NE;\\x11Bs\\xb9\\xecヤo\\xafV\\xbe*\\x9e\\xae\\x98 \\r\\xecŨ\\x04tS\\xd9ɪ\\xcc7\\xe0\\xe9\\x18\\xda`\"\n"), fileFormat: "text", ctx: ctx, wantErr: true, @@ -72,20 +74,28 @@ func Test_SignVerify(t *testing.T) { require.NoError(t, err) ctx := client.Context{ - TxConfig: newTestConfig(t), - Codec: getCodec(), - AddressCodec: address.NewBech32Codec("cosmos"), - Keyring: k, + TxConfig: newTestConfig(t), + Codec: getCodec(), + AddressCodec: address.NewBech32Codec("cosmos"), + ValidatorAddressCodec: address.NewBech32Codec("cosmosvaloper"), + Keyring: k, } - tx, err := sign(ctx, "signVerify", "digest") + tx, err := Sign(ctx, []byte("Hello World!"), "signVerify", "no-encoding", "direct", "json") require.NoError(t, err) - err = verify(ctx, tx) + err = Verify(ctx, []byte(tx), "json") require.NoError(t, err) } func Test_unmarshal(t *testing.T) { + txConfig, err := clitx.NewTxConfig(clitx.ConfigOptions{ + AddressCodec: address.NewBech32Codec("cosmos"), + Cdc: getCodec(), + ValidatorAddressCodec: address.NewBech32Codec("cosmosvaloper"), + EnabledSignModes: enabledSignModes, + }) + require.NoError(t, err) tests := []struct { name string digest []byte @@ -104,7 +114,7 @@ func Test_unmarshal(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := unmarshal(tt.digest, tt.fileFormat) + got, err := unmarshal(tt.fileFormat, tt.digest, txConfig) require.NoError(t, err) require.NotNil(t, got) }) diff --git a/client/v2/tx/config.go b/client/v2/tx/config.go index a500f7c9b009..7c12b3b21ce1 100644 --- a/client/v2/tx/config.go +++ b/client/v2/tx/config.go @@ -51,6 +51,10 @@ type TxEncodingConfig interface { TxJSONEncoder() txEncoder // TxJSONDecoder returns a decoder for JSON transaction decoding. TxJSONDecoder() txDecoder + // TxTextEncoder returns an encoder for text transaction encoding. + TxTextEncoder() txEncoder + // TxTextDecoder returns a decoder for text transaction decoding. + TxTextDecoder() txDecoder // Decoder returns the Decoder interface for decoding transaction bytes into a DecodedTx. Decoder() Decoder } @@ -79,7 +83,7 @@ type ConfigOptions struct { CustomGetSigner map[protoreflect.FullName]signing.GetSignersFunc MaxRecursionDepth int - EnablesSignModes []apitxsigning.SignMode + EnabledSignModes []apitxsigning.SignMode CustomSignModes []signing.SignModeHandler TextualCoinMetadataQueryFn textual.CoinMetadataQueryFn } @@ -98,8 +102,8 @@ func (c *ConfigOptions) validate() error { } // set default signModes if none are provided - if len(c.EnablesSignModes) == 0 { - c.EnablesSignModes = defaultEnabledSignModes + if len(c.EnabledSignModes) == 0 { + c.EnabledSignModes = defaultEnabledSignModes } return nil } @@ -168,6 +172,16 @@ func (t defaultEncodingConfig) TxJSONDecoder() txDecoder { return decodeJsonTx(t.cdc, t.decoder) } +// TxTextEncoder returns the default text transaction encoder. +func (t defaultEncodingConfig) TxTextEncoder() txEncoder { + return encodeTextTx +} + +// TxTextDecoder returns the default text transaction decoder. +func (t defaultEncodingConfig) TxTextDecoder() txDecoder { + return decodeTextTx(t.cdc, t.decoder) +} + // Decoder returns the Decoder instance associated with this encoding configuration. func (t defaultEncodingConfig) Decoder() Decoder { return t.decoder @@ -294,10 +308,10 @@ func newSigningContext(opts ConfigOptions) (*signing.Context, error) { // newHandlerMap constructs a new HandlerMap based on the provided ConfigOptions and signing context. // It initializes handlers for each enabled and custom sign mode specified in the options. func newHandlerMap(opts ConfigOptions, signingCtx *signing.Context) (*signing.HandlerMap, error) { - lenSignModes := len(opts.EnablesSignModes) + lenSignModes := len(opts.EnabledSignModes) handlers := make([]signing.SignModeHandler, lenSignModes+len(opts.CustomSignModes)) - for i, m := range opts.EnablesSignModes { + for i, m := range opts.EnabledSignModes { var err error switch m { case apitxsigning.SignMode_SIGN_MODE_DIRECT: diff --git a/client/v2/tx/config_test.go b/client/v2/tx/config_test.go index 7d1f223d1214..887756f37f6d 100644 --- a/client/v2/tx/config_test.go +++ b/client/v2/tx/config_test.go @@ -111,7 +111,7 @@ func Test_newHandlerMap(t *testing.T) { Decoder: decoder, Cdc: cdc, ValidatorAddressCodec: address.NewBech32Codec("cosmosvaloper"), - EnablesSignModes: []apitxsigning.SignMode{apitxsigning.SignMode_SIGN_MODE_DIRECT}, + EnabledSignModes: []apitxsigning.SignMode{apitxsigning.SignMode_SIGN_MODE_DIRECT}, }, }, { @@ -136,7 +136,7 @@ func Test_newHandlerMap(t *testing.T) { handlerMap, err := newHandlerMap(tt.opts, signingCtx) require.NoError(t, err) require.NotNil(t, handlerMap) - require.Equal(t, len(handlerMap.SupportedModes()), len(tt.opts.EnablesSignModes)+len(tt.opts.CustomSignModes)) + require.Equal(t, len(handlerMap.SupportedModes()), len(tt.opts.EnabledSignModes)+len(tt.opts.CustomSignModes)) }) } } diff --git a/client/v2/tx/encoder.go b/client/v2/tx/encoder.go index 2094efe6d3d5..3e917b34b4c3 100644 --- a/client/v2/tx/encoder.go +++ b/client/v2/tx/encoder.go @@ -4,6 +4,7 @@ import ( "fmt" "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/encoding/prototext" protov2 "google.golang.org/protobuf/proto" txv1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1" @@ -22,6 +23,11 @@ var ( UseProtoNames: true, UseEnumNumbers: false, } + + // textMarshalOptions + textMarshalOptions = prototext.MarshalOptions{ + Indent: "", + } ) // Decoder defines the interface for decoding transaction bytes into a DecodedTx. @@ -100,6 +106,39 @@ func encodeJsonTx(tx Tx) ([]byte, error) { return jsonMarshalOptions.Marshal(wTx.Tx) } +func encodeTextTx(tx Tx) ([]byte, error) { + wTx, ok := tx.(*wrappedTx) + if !ok { + return nil, fmt.Errorf("unexpected tx type: %T", tx) + } + return textMarshalOptions.Marshal(wTx.Tx) +} + +// decodeJsonTx decodes transaction bytes into an apitx.Tx structure using JSON format. +func decodeTextTx(cdc codec.BinaryCodec, decoder Decoder) txDecoder { + return func(txBytes []byte) (Tx, error) { + jsonTx := new(txv1beta1.Tx) + err := prototext.UnmarshalOptions{ + AllowPartial: false, + DiscardUnknown: false, + }.Unmarshal(txBytes, jsonTx) + if err != nil { + return nil, err + } + + pTxBytes, err := protoTxBytes(jsonTx) + if err != nil { + return nil, err + } + + decodedTx, err := decoder.Decode(pTxBytes) + if err != nil { + return nil, err + } + return newWrapperTx(cdc, decodedTx), nil + } +} + func protoTxBytes(tx *txv1beta1.Tx) ([]byte, error) { bodyBytes, err := marshalOption.Marshal(tx.Body) if err != nil { diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index 9dd0eae21a34..8007caaee4f8 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -124,22 +124,22 @@ func prepareTxParams(parameters TxParameters, accRetriever account.AccountRetrie return parameters, nil } - if len(parameters.address) == 0 { + if len(parameters.Address) == 0 { return parameters, errors.New("missing 'from address' field") } - if parameters.accountNumber == 0 || parameters.sequence == 0 { - num, seq, err := accRetriever.GetAccountNumberSequence(context.Background(), parameters.address) + if parameters.AccountNumber == 0 || parameters.Sequence == 0 { + num, seq, err := accRetriever.GetAccountNumberSequence(context.Background(), parameters.Address) if err != nil { return parameters, err } - if parameters.accountNumber == 0 { - parameters.accountNumber = num + if parameters.AccountNumber == 0 { + parameters.AccountNumber = num } - if parameters.sequence == 0 { - parameters.sequence = seq + if parameters.Sequence == 0 { + parameters.Sequence = seq } } @@ -328,11 +328,11 @@ func (f *Factory) sign(ctx context.Context, overwriteSig bool) (Tx, error) { } var err error - if f.txParams.signMode == apitxsigning.SignMode_SIGN_MODE_UNSPECIFIED { - f.txParams.signMode = f.txConfig.SignModeHandler().DefaultMode() + if f.txParams.SignMode == apitxsigning.SignMode_SIGN_MODE_UNSPECIFIED { + f.txParams.SignMode = f.txConfig.SignModeHandler().DefaultMode() } - pubKey, err := f.keybase.GetPubKey(f.txParams.fromName) + pubKey, err := f.keybase.GetPubKey(f.txParams.FromName) if err != nil { return nil, err } @@ -343,9 +343,9 @@ func (f *Factory) sign(ctx context.Context, overwriteSig bool) (Tx, error) { } signerData := signing.SignerData{ - ChainID: f.txParams.chainID, - AccountNumber: f.txParams.accountNumber, - Sequence: f.txParams.sequence, + ChainID: f.txParams.ChainID, + AccountNumber: f.txParams.AccountNumber, + Sequence: f.txParams.Sequence, PubKey: &anypb.Any{ TypeUrl: codectypes.MsgTypeURL(pubKey), Value: pubKey.Bytes(), @@ -364,13 +364,13 @@ func (f *Factory) sign(ctx context.Context, overwriteSig bool) (Tx, error) { // By setting the signatures here, we ensure that the correct SignerInfos // are in place for all subsequent operations, regardless of the sign mode. sigData := SingleSignatureData{ - SignMode: f.txParams.signMode, + SignMode: f.txParams.SignMode, Signature: nil, } sig := Signature{ PubKey: pubKey, Data: &sigData, - Sequence: f.txParams.sequence, + Sequence: f.txParams.Sequence, } var prevSignatures []Signature @@ -412,7 +412,7 @@ func (f *Factory) sign(ctx context.Context, overwriteSig bool) (Tx, error) { } // Sign those bytes - sigBytes, err := f.keybase.Sign(f.txParams.fromName, bytesToSign, f.txParams.signMode) + sigBytes, err := f.keybase.Sign(f.txParams.FromName, bytesToSign, f.txParams.SignMode) if err != nil { return nil, err } @@ -425,7 +425,7 @@ func (f *Factory) sign(ctx context.Context, overwriteSig bool) (Tx, error) { sig = Signature{ PubKey: pubKey, Data: &sigData, - Sequence: f.txParams.sequence, + Sequence: f.txParams.Sequence, } if overwriteSig { @@ -460,16 +460,16 @@ func (f *Factory) WithGas(gas uint64) { // WithSequence returns a copy of the Factory with an updated sequence number. func (f *Factory) WithSequence(sequence uint64) { - f.txParams.sequence = sequence + f.txParams.Sequence = sequence } // WithAccountNumber returns a copy of the Factory with an updated account number. func (f *Factory) WithAccountNumber(accnum uint64) { - f.txParams.accountNumber = accnum + f.txParams.AccountNumber = accnum } // sequence returns the sequence number. -func (f *Factory) sequence() uint64 { return f.txParams.sequence } +func (f *Factory) sequence() uint64 { return f.txParams.Sequence } // gasAdjustment returns the gas adjustment value. func (f *Factory) gasAdjustment() float64 { return f.txParams.gasAdjustment } @@ -478,7 +478,7 @@ func (f *Factory) gasAdjustment() float64 { return f.txParams.gasAdjustment } func (f *Factory) simulateAndExecute() bool { return f.txParams.simulateAndExecute } // signMode returns the sign mode. -func (f *Factory) signMode() apitxsigning.SignMode { return f.txParams.signMode } +func (f *Factory) signMode() apitxsigning.SignMode { return f.txParams.SignMode } // getSimPK gets the public key to use for building a simulation tx. // Note, we should only check for keys in the keybase if we are in simulate and execute mode, @@ -492,14 +492,14 @@ func (f *Factory) getSimPK() (cryptotypes.PubKey, error) { ) if f.txParams.simulateAndExecute && f.keybase != nil { - pk, err = f.keybase.GetPubKey(f.txParams.fromName) + pk, err = f.keybase.GetPubKey(f.txParams.FromName) if err != nil { return nil, err } } else { // When in dry-run mode, attempt to retrieve the account using the provided address. // If the account retrieval fails, the default public key is used. - acc, err := f.accountRetriever.GetAccount(context.Background(), f.txParams.address) + acc, err := f.accountRetriever.GetAccount(context.Background(), f.txParams.Address) if err != nil { // If there is an error retrieving the account, return the default public key. return pk, nil @@ -516,7 +516,7 @@ func (f *Factory) getSimPK() (cryptotypes.PubKey, error) { func (f *Factory) getSimSignatureData(pk cryptotypes.PubKey) SignatureData { multisigPubKey, ok := pk.(*multisig.LegacyAminoPubKey) if !ok { - return &SingleSignatureData{SignMode: f.txParams.signMode} + return &SingleSignatureData{SignMode: f.txParams.SignMode} } multiSignatureData := make([]SignatureData, 0, multisigPubKey.Threshold) diff --git a/client/v2/tx/factory_test.go b/client/v2/tx/factory_test.go index 39f21b38d0df..363cf9146bde 100644 --- a/client/v2/tx/factory_test.go +++ b/client/v2/tx/factory_test.go @@ -35,7 +35,7 @@ func TestFactory_prepareTxParams(t *testing.T) { name: "no error", txParams: TxParameters{ AccountConfig: AccountConfig{ - address: addr, + Address: addr, }, }, }, @@ -66,9 +66,9 @@ func TestFactory_BuildUnsignedTx(t *testing.T) { { name: "no error", txParams: TxParameters{ - chainID: "demo", + ChainID: "demo", AccountConfig: AccountConfig{ - address: addr, + Address: addr, }, }, msgs: []transaction.Msg{ @@ -81,9 +81,9 @@ func TestFactory_BuildUnsignedTx(t *testing.T) { { name: "fees and gas price provided", txParams: TxParameters{ - chainID: "demo", + ChainID: "demo", AccountConfig: AccountConfig{ - address: addr, + Address: addr, }, GasConfig: GasConfig{ gasPrices: []*base.DecCoin{ @@ -133,9 +133,9 @@ func TestFactory_calculateGas(t *testing.T) { { name: "no error", txParams: TxParameters{ - chainID: "demo", + ChainID: "demo", AccountConfig: AccountConfig{ - address: addr, + Address: addr, }, GasConfig: GasConfig{ gasAdjustment: 1, @@ -175,9 +175,9 @@ func TestFactory_Simulate(t *testing.T) { { name: "no error", txParams: TxParameters{ - chainID: "demo", + ChainID: "demo", AccountConfig: AccountConfig{ - address: addr, + Address: addr, }, GasConfig: GasConfig{ gasAdjustment: 1, @@ -219,9 +219,9 @@ func TestFactory_BuildSimTx(t *testing.T) { { name: "no error", txParams: TxParameters{ - chainID: "demo", + ChainID: "demo", AccountConfig: AccountConfig{ - address: addr, + Address: addr, }, }, }, @@ -251,10 +251,10 @@ func TestFactory_Sign(t *testing.T) { { name: "no error", txParams: TxParameters{ - chainID: "demo", + ChainID: "demo", AccountConfig: AccountConfig{ - fromName: "alice", - address: addr, + FromName: "alice", + Address: addr, }, }, }, @@ -299,19 +299,19 @@ func TestFactory_getSignBytesAdapter(t *testing.T) { { name: "no error", txParams: TxParameters{ - chainID: "demo", - signMode: apitxsigning.SignMode_SIGN_MODE_DIRECT, + ChainID: "demo", + SignMode: apitxsigning.SignMode_SIGN_MODE_DIRECT, AccountConfig: AccountConfig{ - address: addr, + Address: addr, }, }, }, { name: "signMode not specified", txParams: TxParameters{ - chainID: "demo", + ChainID: "demo", AccountConfig: AccountConfig{ - address: addr, + Address: addr, }, }, error: true, @@ -341,7 +341,7 @@ func TestFactory_getSignBytesAdapter(t *testing.T) { signerData := signing.SignerData{ Address: addr, - ChainID: f.txParams.chainID, + ChainID: f.txParams.ChainID, AccountNumber: 0, Sequence: 0, PubKey: &anypb.Any{ @@ -401,7 +401,7 @@ func TestFactory_WithFunctions(t *testing.T) { name: "with gas", txParams: TxParameters{ AccountConfig: AccountConfig{ - address: addr, + Address: addr, }, }, withFunc: func(f *Factory) { @@ -415,28 +415,28 @@ func TestFactory_WithFunctions(t *testing.T) { name: "with sequence", txParams: TxParameters{ AccountConfig: AccountConfig{ - address: addr, + Address: addr, }, }, withFunc: func(f *Factory) { f.WithSequence(10) }, checkFunc: func(f *Factory) bool { - return f.txParams.AccountConfig.sequence == 10 + return f.txParams.AccountConfig.Sequence == 10 }, }, { name: "with account number", txParams: TxParameters{ AccountConfig: AccountConfig{ - address: addr, + Address: addr, }, }, withFunc: func(f *Factory) { f.WithAccountNumber(123) }, checkFunc: func(f *Factory) bool { - return f.txParams.AccountConfig.accountNumber == 123 + return f.txParams.AccountConfig.AccountNumber == 123 }, }, } diff --git a/client/v2/tx/tx.go b/client/v2/tx/tx.go index ab82c813ac20..c6bb5a548f92 100644 --- a/client/v2/tx/tx.go +++ b/client/v2/tx/tx.go @@ -77,7 +77,7 @@ func newFactory(ctx client.Context, flagSet *pflag.FlagSet) (Factory, error) { AddressCodec: ctx.AddressCodec, Cdc: ctx.Codec, ValidatorAddressCodec: ctx.ValidatorAddressCodec, - EnablesSignModes: ctx.TxConfig.SignModeHandler().SupportedModes(), + EnabledSignModes: ctx.TxConfig.SignModeHandler().SupportedModes(), }) if err != nil { return Factory{}, err diff --git a/client/v2/tx/types.go b/client/v2/tx/types.go index ee60c27065b6..a50b0b996b1d 100644 --- a/client/v2/tx/types.go +++ b/client/v2/tx/types.go @@ -14,6 +14,7 @@ import ( "cosmossdk.io/client/v2/internal/coins" "cosmossdk.io/core/address" "cosmossdk.io/core/transaction" + "cosmossdk.io/x/tx/signing" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" ) @@ -28,9 +29,9 @@ type HasValidateBasic interface { // TxParameters defines the parameters required for constructing a transaction. type TxParameters struct { timeoutTimestamp time.Time // timeoutTimestamp indicates a timestamp after which the transaction is no longer valid. - chainID string // chainID specifies the unique identifier of the blockchain where the transaction will be processed. + ChainID string // ChainID specifies the unique identifier of the blockchain where the transaction will be processed. memo string // memo contains any arbitrary memo to be attached to the transaction. - signMode apitxsigning.SignMode // signMode determines the signing mode to be used for the transaction. + SignMode apitxsigning.SignMode // signMode determines the signing mode to be used for the transaction. AccountConfig // AccountConfig includes information about the transaction originator's account. GasConfig // GasConfig specifies the gas settings for the transaction. @@ -41,15 +42,15 @@ type TxParameters struct { // AccountConfig defines the 'account' related fields in a transaction. type AccountConfig struct { // accountNumber is the unique identifier for the account. - accountNumber uint64 + AccountNumber uint64 // sequence is the sequence number of the transaction. - sequence uint64 + Sequence uint64 // fromName is the name of the account sending the transaction. - fromName string + FromName string // fromAddress is the address of the account sending the transaction. - fromAddress string + FromAddress string // address is the byte representation of the account address. - address []byte + Address []byte } // GasConfig defines the 'gas' related fields in a transaction. @@ -141,6 +142,8 @@ type Tx interface { GetPubKeys() ([]cryptotypes.PubKey, error) // GetSignatures fetches the signatures attached to the transaction. GetSignatures() ([]Signature, error) + // GetSigningTxData returns the signing.TxData for the transaction. + GetSigningTxData() (signing.TxData, error) } // txParamsFromFlagSet extracts the transaction parameters from the provided FlagSet. @@ -192,15 +195,15 @@ func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring, ac addr txParams := TxParameters{ timeoutTimestamp: timeoutTimestamp, - chainID: chainID, + ChainID: chainID, memo: memo, - signMode: getSignMode(signMode), + SignMode: getSignMode(signMode), AccountConfig: AccountConfig{ - accountNumber: accNumber, - sequence: sequence, - fromName: fromName, - fromAddress: fromAddress, - address: addr, + AccountNumber: accNumber, + Sequence: sequence, + FromName: fromName, + FromAddress: fromAddress, + Address: addr, }, GasConfig: gasConfig, FeeConfig: feeConfig, diff --git a/client/v2/tx/wrapper.go b/client/v2/tx/wrapper.go index fbcf62126bfc..5fc1ef528861 100644 --- a/client/v2/tx/wrapper.go +++ b/client/v2/tx/wrapper.go @@ -10,6 +10,7 @@ import ( "cosmossdk.io/core/transaction" "cosmossdk.io/x/tx/decode" + "cosmossdk.io/x/tx/signing" "github.com/cosmos/cosmos-sdk/codec" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" @@ -96,6 +97,16 @@ func (w wrappedTx) GetSignatures() ([]Signature, error) { return signatures, nil } +func (w wrappedTx) GetSigningTxData() (signing.TxData, error) { + return signing.TxData{ + Body: w.Tx.Body, + AuthInfo: w.Tx.AuthInfo, + BodyBytes: w.TxRaw.BodyBytes, + AuthInfoBytes: w.TxRaw.AuthInfoBytes, + BodyHasUnknownNonCriticals: w.TxBodyHasUnknownNonCriticals, + }, nil +} + // decodeAny decodes a protobuf Any message into a concrete proto.Message. func (w wrappedTx) decodeAny(anyPb *anypb.Any) (proto.Message, error) { name := anyPb.GetTypeUrl()