refactor(client/v2): offchain uses client/v2/factory (#22344)

Co-authored-by: Julien Robert <julien@rbrt.fr>
This commit is contained in:
Julián Toledano 2024-11-05 16:49:09 +01:00 committed by GitHub
parent 62ddd3e939
commit ed46a4c93a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 771 additions and 685 deletions

View File

@ -1,2 +1,2 @@
codegen:
@(cd internal; buf generate)
@(cd internal; buf generate --template buf.gen.pulsar.yaml)

View File

@ -0,0 +1,5 @@
version: v1
plugins:
- name: gocosmos
out: ..
opt: plugins=grpc,Mgoogle/protobuf/any.proto=github.com/cosmos/gogoproto/types/any

View File

@ -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";

View File

@ -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 (

View File

@ -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))

View File

@ -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))

View File

@ -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)
}
}

View File

@ -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 <keyName> <fileName>",
Use: "verify-file <signedFileName>",
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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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")
)

View File

@ -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)
if err != nil {
return "", err
}
tx, err := sign(ctx, fromName, digest)
if err != nil {
return "", err
}
txMarshaller, err := getMarshaller(output, indent, emitUnpopulated)
if err != nil {
return "", err
}
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)
if err != nil {
return nil, err
return "", err
}
txConfig, err := clitx.NewTxConfig(clitx.ConfigOptions{
AddressCodec: ctx.AddressCodec,
Cdc: ctx.Codec,
ValidatorAddressCodec: ctx.ValidatorAddressCodec,
EnabledSignModes: enabledSignModes,
})
if err != nil {
return "", err
}
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,
},
}
txf, err := clitx.NewFactory(keybase, ctx.Codec, accRetriever, txConfig, ctx.AddressCodec, ctx, params)
if err != nil {
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)
signedTx, err := txf.BuildsSignedTx(context.Background(), msg)
if err != nil {
return nil, err
return "", 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...)
bz, err := encode(output, signedTx, txConfig)
if err != nil {
return nil, err
return "", err
}
bytesToSign, err := getSignBytes(
context.Background(), ctx.TxConfig.SignModeHandler(), signerData, txBuilder)
if err != nil {
return nil, err
}
signedBytes, err := keybase.Sign(fromName, bytesToSign, signMode)
if err != nil {
return nil, err
}
sigData.Signature = signedBytes
err = txBuilder.SetSignatures(sig)
if err != nil {
return nil, 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
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)
}
anyPk, err := codectypes.NewAnyWithValue(signerData.PubKey)
if err != nil {
return nil, err
}
txSignerData := txsigning.SignerData{
ChainID: signerData.ChainID,
AccountNumber: signerData.AccountNumber,
Sequence: signerData.Sequence,
Address: signerData.Address,
PubKey: &anypb.Any{
TypeUrl: anyPk.TypeUrl,
Value: anyPk.Value,
},
}
return handlerMap.GetSignBytes(ctx, signMode, txSignerData, txData)
}
// 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 apisigning.SignMode_SIGN_MODE_UNSPECIFIED, fmt.Errorf("unsupported sign mode: %s", mode)
}

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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\":\"<appd>\", \"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\":\"<appd>\", \"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:\"<appd>\" 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:\"<appd>\" 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)
})

View File

@ -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:

View File

@ -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))
})
}
}

View File

@ -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 {

View File

@ -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)

View File

@ -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
},
},
}

View File

@ -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

View File

@ -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,

View File

@ -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()