feat(client/v2/offchain): sign and verify file (#18626)

Co-authored-by: Marko <marbar3778@yahoo.com>
Co-authored-by: Facundo Medica <14063057+facundomedica@users.noreply.github.com>
This commit is contained in:
Julián Toledano 2024-01-24 11:48:51 +01:00 committed by GitHub
parent bda2d11232
commit 61c367d9d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 2035 additions and 0 deletions

View File

@ -40,6 +40,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Features
* [#18626](https://github.com/cosmos/cosmos-sdk/pull/18626) Support for off-chain signing and verification of a file.
* [#18461](https://github.com/cosmos/cosmos-sdk/pull/18461) Support governance proposals.
* [#19039](https://github.com/cosmos/cosmos-sdk/pull/19039) Add support for pubkey in autocli.

View File

@ -216,3 +216,60 @@ https://github.com/cosmos/cosmos-sdk/blob/main/client/grpc/cmtservice/autocli.go
To further enhance your CLI experience with Cosmos SDK-based blockchains, you can use `hubl`. `hubl` is a tool that allows you to query any Cosmos SDK-based blockchain using the new AutoCLI feature of the Cosmos SDK. With `hubl`, you can easily configure a new chain and query modules with just a few simple commands.
For more information on `hubl`, including how to configure a new chain and query a module, see the [Hubl documentation](https://docs.cosmos.network/main/tooling/hubl).
# Off-Chain
Off-chain functionalities allow you to sign and verify files with two commands:
+ `sign-file` for signing a file.
+ `verify-file` for verifying a previously signed file.
Signing a file will result in a Tx with a `MsgSignArbitraryData` as described in the [Off-chain CIP](https://github.com/cosmos/cips/blob/main/cips/cip-X.md).
## Sign a file
To sign a file `sign-file` command offers some helpful flags:
```text
--encoding string Choose an encoding method for the file content to be added as msg data (no-encoding|base64|hex) (default "no-encoding")
--indent string Choose an indent for the tx (default " ")
--notEmitUnpopulated Don't show unpopulated fields in the tx
--output string Choose an output format for the tx (json|text (default "json")
--output-document string The document will be written to the given file instead of STDOUT
```
The `encoding` flag lets you choose how the contents of the file should be encoded. For example:
+ `simd off-chain sign-file alice myFile.json`
+ ```json
{
"@type": "/offchain.MsgSignArbitraryData",
"appDomain": "simd",
"signer": "cosmos1x33fy6rusfprkntvjsfregss7rvsvyy4lkwrqu",
"data": "Hello World!\n"
}
```
+ `simd off-chain sign-file alice myFile.json --encoding base64`
+ ```json
{
"@type": "/offchain.MsgSignArbitraryData",
"appDomain": "simd",
"signer": "cosmos1x33fy6rusfprkntvjsfregss7rvsvyy4lkwrqu",
"data": "SGVsbG8gV29ybGQhCg=="
}
```
+ `simd off-chain sign-file alice myFile.json --encoding hex`
+ ```json
{
"@type": "/offchain.MsgSignArbitraryData",
"appDomain": "simd",
"signer": "cosmos1x33fy6rusfprkntvjsfregss7rvsvyy4lkwrqu",
"data": "48656c6c6f20576f726c64210a"
}
```
## Verify a file
To verify a file only the key name used and the previously signed file are needed.
```text
➜ simd off-chain verify-file alice signedFile.json
Verification OK!
```

View File

@ -0,0 +1,20 @@
syntax = "proto3";
package offchain;
import "cosmos_proto/cosmos.proto";
import "cosmos/msg/v1/msg.proto";
import "amino/amino.proto";
// MsgSignArbitraryData defines an arbitrary, general-purpose, off-chain message
message MsgSignArbitraryData {
option (amino.name) = "offchain/MsgSignArbitraryData";
option (cosmos.msg.v1.signer) = "signer";
// AppDomain is the application requesting off-chain message signing
string app_domain = 1;
// Signer is the sdk.AccAddress of the message signer
string signer = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];
// Data represents the raw bytes of the content that is signed (text, json, etc)
string data = 3;
}

View File

@ -0,0 +1,730 @@
// Code generated by protoc-gen-go-pulsar. DO NOT EDIT.
package offchain
import (
_ "cosmossdk.io/api/amino"
_ "cosmossdk.io/api/cosmos/msg/v1"
fmt "fmt"
_ "github.com/cosmos/cosmos-proto"
runtime "github.com/cosmos/cosmos-proto/runtime"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoiface "google.golang.org/protobuf/runtime/protoiface"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
io "io"
reflect "reflect"
sync "sync"
)
var (
md_MsgSignArbitraryData protoreflect.MessageDescriptor
fd_MsgSignArbitraryData_app_domain protoreflect.FieldDescriptor
fd_MsgSignArbitraryData_signer protoreflect.FieldDescriptor
fd_MsgSignArbitraryData_data protoreflect.FieldDescriptor
)
func init() {
file_offchain_msgSignArbitraryData_proto_init()
md_MsgSignArbitraryData = File_offchain_msgSignArbitraryData_proto.Messages().ByName("MsgSignArbitraryData")
fd_MsgSignArbitraryData_app_domain = md_MsgSignArbitraryData.Fields().ByName("app_domain")
fd_MsgSignArbitraryData_signer = md_MsgSignArbitraryData.Fields().ByName("signer")
fd_MsgSignArbitraryData_data = md_MsgSignArbitraryData.Fields().ByName("data")
}
var _ protoreflect.Message = (*fastReflection_MsgSignArbitraryData)(nil)
type fastReflection_MsgSignArbitraryData MsgSignArbitraryData
func (x *MsgSignArbitraryData) ProtoReflect() protoreflect.Message {
return (*fastReflection_MsgSignArbitraryData)(x)
}
func (x *MsgSignArbitraryData) slowProtoReflect() protoreflect.Message {
mi := &file_offchain_msgSignArbitraryData_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
var _fastReflection_MsgSignArbitraryData_messageType fastReflection_MsgSignArbitraryData_messageType
var _ protoreflect.MessageType = fastReflection_MsgSignArbitraryData_messageType{}
type fastReflection_MsgSignArbitraryData_messageType struct{}
func (x fastReflection_MsgSignArbitraryData_messageType) Zero() protoreflect.Message {
return (*fastReflection_MsgSignArbitraryData)(nil)
}
func (x fastReflection_MsgSignArbitraryData_messageType) New() protoreflect.Message {
return new(fastReflection_MsgSignArbitraryData)
}
func (x fastReflection_MsgSignArbitraryData_messageType) Descriptor() protoreflect.MessageDescriptor {
return md_MsgSignArbitraryData
}
// Descriptor returns message descriptor, which contains only the protobuf
// type information for the message.
func (x *fastReflection_MsgSignArbitraryData) Descriptor() protoreflect.MessageDescriptor {
return md_MsgSignArbitraryData
}
// Type returns the message type, which encapsulates both Go and protobuf
// type information. If the Go type information is not needed,
// it is recommended that the message descriptor be used instead.
func (x *fastReflection_MsgSignArbitraryData) Type() protoreflect.MessageType {
return _fastReflection_MsgSignArbitraryData_messageType
}
// New returns a newly allocated and mutable empty message.
func (x *fastReflection_MsgSignArbitraryData) New() protoreflect.Message {
return new(fastReflection_MsgSignArbitraryData)
}
// Interface unwraps the message reflection interface and
// returns the underlying ProtoMessage interface.
func (x *fastReflection_MsgSignArbitraryData) Interface() protoreflect.ProtoMessage {
return (*MsgSignArbitraryData)(x)
}
// Range iterates over every populated field in an undefined order,
// calling f for each field descriptor and value encountered.
// Range returns immediately if f returns false.
// While iterating, mutating operations may only be performed
// on the current field descriptor.
func (x *fastReflection_MsgSignArbitraryData) Range(f func(protoreflect.FieldDescriptor, protoreflect.Value) bool) {
if x.AppDomain != "" {
value := protoreflect.ValueOfString(x.AppDomain)
if !f(fd_MsgSignArbitraryData_app_domain, value) {
return
}
}
if x.Signer != "" {
value := protoreflect.ValueOfString(x.Signer)
if !f(fd_MsgSignArbitraryData_signer, value) {
return
}
}
if x.Data != "" {
value := protoreflect.ValueOfString(x.Data)
if !f(fd_MsgSignArbitraryData_data, value) {
return
}
}
}
// Has reports whether a field is populated.
//
// Some fields have the property of nullability where it is possible to
// distinguish between the default value of a field and whether the field
// was explicitly populated with the default value. Singular message fields,
// member fields of a oneof, and proto2 scalar fields are nullable. Such
// fields are populated only if explicitly set.
//
// In other cases (aside from the nullable cases above),
// a proto3 scalar field is populated if it contains a non-zero value, and
// a repeated field is populated if it is non-empty.
func (x *fastReflection_MsgSignArbitraryData) Has(fd protoreflect.FieldDescriptor) bool {
switch fd.FullName() {
case "offchain.MsgSignArbitraryData.app_domain":
return x.AppDomain != ""
case "offchain.MsgSignArbitraryData.signer":
return x.Signer != ""
case "offchain.MsgSignArbitraryData.data":
return x.Data != ""
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: offchain.MsgSignArbitraryData"))
}
panic(fmt.Errorf("message offchain.MsgSignArbitraryData does not contain field %s", fd.FullName()))
}
}
// Clear clears the field such that a subsequent Has call reports false.
//
// Clearing an extension field clears both the extension type and value
// associated with the given field number.
//
// Clear is a mutating operation and unsafe for concurrent use.
func (x *fastReflection_MsgSignArbitraryData) Clear(fd protoreflect.FieldDescriptor) {
switch fd.FullName() {
case "offchain.MsgSignArbitraryData.app_domain":
x.AppDomain = ""
case "offchain.MsgSignArbitraryData.signer":
x.Signer = ""
case "offchain.MsgSignArbitraryData.data":
x.Data = ""
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: offchain.MsgSignArbitraryData"))
}
panic(fmt.Errorf("message offchain.MsgSignArbitraryData does not contain field %s", fd.FullName()))
}
}
// Get retrieves the value for a field.
//
// For unpopulated scalars, it returns the default value, where
// the default value of a bytes scalar is guaranteed to be a copy.
// For unpopulated composite types, it returns an empty, read-only view
// of the value; to obtain a mutable reference, use Mutable.
func (x *fastReflection_MsgSignArbitraryData) Get(descriptor protoreflect.FieldDescriptor) protoreflect.Value {
switch descriptor.FullName() {
case "offchain.MsgSignArbitraryData.app_domain":
value := x.AppDomain
return protoreflect.ValueOfString(value)
case "offchain.MsgSignArbitraryData.signer":
value := x.Signer
return protoreflect.ValueOfString(value)
case "offchain.MsgSignArbitraryData.data":
value := x.Data
return protoreflect.ValueOfString(value)
default:
if descriptor.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: offchain.MsgSignArbitraryData"))
}
panic(fmt.Errorf("message offchain.MsgSignArbitraryData does not contain field %s", descriptor.FullName()))
}
}
// Set stores the value for a field.
//
// For a field belonging to a oneof, it implicitly clears any other field
// that may be currently set within the same oneof.
// For extension fields, it implicitly stores the provided ExtensionType.
// When setting a composite type, it is unspecified whether the stored value
// aliases the source's memory in any way. If the composite value is an
// empty, read-only value, then it panics.
//
// Set is a mutating operation and unsafe for concurrent use.
func (x *fastReflection_MsgSignArbitraryData) Set(fd protoreflect.FieldDescriptor, value protoreflect.Value) {
switch fd.FullName() {
case "offchain.MsgSignArbitraryData.app_domain":
x.AppDomain = value.Interface().(string)
case "offchain.MsgSignArbitraryData.signer":
x.Signer = value.Interface().(string)
case "offchain.MsgSignArbitraryData.data":
x.Data = value.Interface().(string)
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: offchain.MsgSignArbitraryData"))
}
panic(fmt.Errorf("message offchain.MsgSignArbitraryData does not contain field %s", fd.FullName()))
}
}
// Mutable returns a mutable reference to a composite type.
//
// If the field is unpopulated, it may allocate a composite value.
// For a field belonging to a oneof, it implicitly clears any other field
// that may be currently set within the same oneof.
// For extension fields, it implicitly stores the provided ExtensionType
// if not already stored.
// It panics if the field does not contain a composite type.
//
// Mutable is a mutating operation and unsafe for concurrent use.
func (x *fastReflection_MsgSignArbitraryData) Mutable(fd protoreflect.FieldDescriptor) protoreflect.Value {
switch fd.FullName() {
case "offchain.MsgSignArbitraryData.app_domain":
panic(fmt.Errorf("field app_domain of message offchain.MsgSignArbitraryData is not mutable"))
case "offchain.MsgSignArbitraryData.signer":
panic(fmt.Errorf("field signer of message offchain.MsgSignArbitraryData is not mutable"))
case "offchain.MsgSignArbitraryData.data":
panic(fmt.Errorf("field data of message offchain.MsgSignArbitraryData is not mutable"))
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: offchain.MsgSignArbitraryData"))
}
panic(fmt.Errorf("message offchain.MsgSignArbitraryData does not contain field %s", fd.FullName()))
}
}
// NewField returns a new value that is assignable to the field
// for the given descriptor. For scalars, this returns the default value.
// For lists, maps, and messages, this returns a new, empty, mutable value.
func (x *fastReflection_MsgSignArbitraryData) NewField(fd protoreflect.FieldDescriptor) protoreflect.Value {
switch fd.FullName() {
case "offchain.MsgSignArbitraryData.app_domain":
return protoreflect.ValueOfString("")
case "offchain.MsgSignArbitraryData.signer":
return protoreflect.ValueOfString("")
case "offchain.MsgSignArbitraryData.data":
return protoreflect.ValueOfString("")
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: offchain.MsgSignArbitraryData"))
}
panic(fmt.Errorf("message offchain.MsgSignArbitraryData does not contain field %s", fd.FullName()))
}
}
// WhichOneof reports which field within the oneof is populated,
// returning nil if none are populated.
// It panics if the oneof descriptor does not belong to this message.
func (x *fastReflection_MsgSignArbitraryData) WhichOneof(d protoreflect.OneofDescriptor) protoreflect.FieldDescriptor {
switch d.FullName() {
default:
panic(fmt.Errorf("%s is not a oneof field in offchain.MsgSignArbitraryData", d.FullName()))
}
panic("unreachable")
}
// GetUnknown retrieves the entire list of unknown fields.
// The caller may only mutate the contents of the RawFields
// if the mutated bytes are stored back into the message with SetUnknown.
func (x *fastReflection_MsgSignArbitraryData) GetUnknown() protoreflect.RawFields {
return x.unknownFields
}
// SetUnknown stores an entire list of unknown fields.
// The raw fields must be syntactically valid according to the wire format.
// An implementation may panic if this is not the case.
// Once stored, the caller must not mutate the content of the RawFields.
// An empty RawFields may be passed to clear the fields.
//
// SetUnknown is a mutating operation and unsafe for concurrent use.
func (x *fastReflection_MsgSignArbitraryData) SetUnknown(fields protoreflect.RawFields) {
x.unknownFields = fields
}
// IsValid reports whether the message is valid.
//
// An invalid message is an empty, read-only value.
//
// An invalid message often corresponds to a nil pointer of the concrete
// message type, but the details are implementation dependent.
// Validity is not part of the protobuf data model, and may not
// be preserved in marshaling or other operations.
func (x *fastReflection_MsgSignArbitraryData) IsValid() bool {
return x != nil
}
// ProtoMethods returns optional fastReflectionFeature-path implementations of various operations.
// This method may return nil.
//
// The returned methods type is identical to
// "google.golang.org/protobuf/runtime/protoiface".Methods.
// Consult the protoiface package documentation for details.
func (x *fastReflection_MsgSignArbitraryData) ProtoMethods() *protoiface.Methods {
size := func(input protoiface.SizeInput) protoiface.SizeOutput {
x := input.Message.Interface().(*MsgSignArbitraryData)
if x == nil {
return protoiface.SizeOutput{
NoUnkeyedLiterals: input.NoUnkeyedLiterals,
Size: 0,
}
}
options := runtime.SizeInputToOptions(input)
_ = options
var n int
var l int
_ = l
l = len(x.AppDomain)
if l > 0 {
n += 1 + l + runtime.Sov(uint64(l))
}
l = len(x.Signer)
if l > 0 {
n += 1 + l + runtime.Sov(uint64(l))
}
l = len(x.Data)
if l > 0 {
n += 1 + l + runtime.Sov(uint64(l))
}
if x.unknownFields != nil {
n += len(x.unknownFields)
}
return protoiface.SizeOutput{
NoUnkeyedLiterals: input.NoUnkeyedLiterals,
Size: n,
}
}
marshal := func(input protoiface.MarshalInput) (protoiface.MarshalOutput, error) {
x := input.Message.Interface().(*MsgSignArbitraryData)
if x == nil {
return protoiface.MarshalOutput{
NoUnkeyedLiterals: input.NoUnkeyedLiterals,
Buf: input.Buf,
}, nil
}
options := runtime.MarshalInputToOptions(input)
_ = options
size := options.Size(x)
dAtA := make([]byte, size)
i := len(dAtA)
_ = i
var l int
_ = l
if x.unknownFields != nil {
i -= len(x.unknownFields)
copy(dAtA[i:], x.unknownFields)
}
if len(x.Data) > 0 {
i -= len(x.Data)
copy(dAtA[i:], x.Data)
i = runtime.EncodeVarint(dAtA, i, uint64(len(x.Data)))
i--
dAtA[i] = 0x1a
}
if len(x.Signer) > 0 {
i -= len(x.Signer)
copy(dAtA[i:], x.Signer)
i = runtime.EncodeVarint(dAtA, i, uint64(len(x.Signer)))
i--
dAtA[i] = 0x12
}
if len(x.AppDomain) > 0 {
i -= len(x.AppDomain)
copy(dAtA[i:], x.AppDomain)
i = runtime.EncodeVarint(dAtA, i, uint64(len(x.AppDomain)))
i--
dAtA[i] = 0xa
}
if input.Buf != nil {
input.Buf = append(input.Buf, dAtA...)
} else {
input.Buf = dAtA
}
return protoiface.MarshalOutput{
NoUnkeyedLiterals: input.NoUnkeyedLiterals,
Buf: input.Buf,
}, nil
}
unmarshal := func(input protoiface.UnmarshalInput) (protoiface.UnmarshalOutput, error) {
x := input.Message.Interface().(*MsgSignArbitraryData)
if x == nil {
return protoiface.UnmarshalOutput{
NoUnkeyedLiterals: input.NoUnkeyedLiterals,
Flags: input.Flags,
}, nil
}
options := runtime.UnmarshalInputToOptions(input)
_ = options
dAtA := input.Buf
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow
}
if iNdEx >= l {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, 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 protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: MsgSignArbitraryData: wiretype end group for non-group")
}
if fieldNum <= 0 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: MsgSignArbitraryData: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field AppDomain", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow
}
if iNdEx >= l {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength
}
if postIndex > l {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF
}
x.AppDomain = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 2 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow
}
if iNdEx >= l {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength
}
if postIndex > l {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF
}
x.Signer = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 3:
if wireType != 2 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field Data", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow
}
if iNdEx >= l {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength
}
if postIndex > l {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF
}
x.Data = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := runtime.Skip(dAtA[iNdEx:])
if err != nil {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength
}
if (iNdEx + skippy) > l {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF
}
if !options.DiscardUnknown {
x.unknownFields = append(x.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
}
iNdEx += skippy
}
}
if iNdEx > l {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF
}
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, nil
}
return &protoiface.Methods{
NoUnkeyedLiterals: struct{}{},
Flags: protoiface.SupportMarshalDeterministic | protoiface.SupportUnmarshalDiscardUnknown,
Size: size,
Marshal: marshal,
Unmarshal: unmarshal,
Merge: nil,
CheckInitialized: nil,
}
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.0
// protoc (unknown)
// source: offchain/msgSignArbitraryData.proto
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// MsgSignArbitraryData defines an arbitrary, general-purpose, off-chain message
type MsgSignArbitraryData struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// 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 (x *MsgSignArbitraryData) Reset() {
*x = MsgSignArbitraryData{}
if protoimpl.UnsafeEnabled {
mi := &file_offchain_msgSignArbitraryData_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MsgSignArbitraryData) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MsgSignArbitraryData) ProtoMessage() {}
// Deprecated: Use MsgSignArbitraryData.ProtoReflect.Descriptor instead.
func (*MsgSignArbitraryData) Descriptor() ([]byte, []int) {
return file_offchain_msgSignArbitraryData_proto_rawDescGZIP(), []int{0}
}
func (x *MsgSignArbitraryData) GetAppDomain() string {
if x != nil {
return x.AppDomain
}
return ""
}
func (x *MsgSignArbitraryData) GetSigner() string {
if x != nil {
return x.Signer
}
return ""
}
func (x *MsgSignArbitraryData) GetData() string {
if x != nil {
return x.Data
}
return ""
}
var File_offchain_msgSignArbitraryData_proto protoreflect.FileDescriptor
var file_offchain_msgSignArbitraryData_proto_rawDesc = []byte{
0x0a, 0x23, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x6d, 0x73, 0x67, 0x53, 0x69,
0x67, 0x6e, 0x41, 0x72, 0x62, 0x69, 0x74, 0x72, 0x61, 0x72, 0x79, 0x44, 0x61, 0x74, 0x61, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x1a,
0x19, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f,
0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x63, 0x6f, 0x73, 0x6d,
0x6f, 0x73, 0x2f, 0x6d, 0x73, 0x67, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x73, 0x67, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x1a, 0x11, 0x61, 0x6d, 0x69, 0x6e, 0x6f, 0x2f, 0x61, 0x6d, 0x69, 0x6e, 0x6f,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xaa, 0x01, 0x0a, 0x14, 0x4d, 0x73, 0x67, 0x53, 0x69,
0x67, 0x6e, 0x41, 0x72, 0x62, 0x69, 0x74, 0x72, 0x61, 0x72, 0x79, 0x44, 0x61, 0x74, 0x61, 0x12,
0x1d, 0x0a, 0x0a, 0x61, 0x70, 0x70, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x70, 0x70, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x30,
0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18,
0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72,
0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
0x64, 0x61, 0x74, 0x61, 0x3a, 0x2d, 0x82, 0xe7, 0xb0, 0x2a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65,
0x72, 0x8a, 0xe7, 0xb0, 0x2a, 0x1d, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x4d,
0x73, 0x67, 0x53, 0x69, 0x67, 0x6e, 0x41, 0x72, 0x62, 0x69, 0x74, 0x72, 0x61, 0x72, 0x79, 0x44,
0x61, 0x74, 0x61, 0x42, 0xa3, 0x01, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x2e, 0x6f, 0x66, 0x66, 0x63,
0x68, 0x61, 0x69, 0x6e, 0x42, 0x19, 0x4d, 0x73, 0x67, 0x53, 0x69, 0x67, 0x6e, 0x41, 0x72, 0x62,
0x69, 0x74, 0x72, 0x61, 0x72, 0x79, 0x44, 0x61, 0x74, 0x61, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50,
0x01, 0x5a, 0x38, 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, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0xa2, 0x02, 0x03, 0x4f, 0x58,
0x58, 0xaa, 0x02, 0x08, 0x4f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0xca, 0x02, 0x08, 0x4f,
0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0xe2, 0x02, 0x14, 0x4f, 0x66, 0x66, 0x63, 0x68, 0x61,
0x69, 0x6e, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02,
0x08, 0x4f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x33,
}
var (
file_offchain_msgSignArbitraryData_proto_rawDescOnce sync.Once
file_offchain_msgSignArbitraryData_proto_rawDescData = file_offchain_msgSignArbitraryData_proto_rawDesc
)
func file_offchain_msgSignArbitraryData_proto_rawDescGZIP() []byte {
file_offchain_msgSignArbitraryData_proto_rawDescOnce.Do(func() {
file_offchain_msgSignArbitraryData_proto_rawDescData = protoimpl.X.CompressGZIP(file_offchain_msgSignArbitraryData_proto_rawDescData)
})
return file_offchain_msgSignArbitraryData_proto_rawDescData
}
var file_offchain_msgSignArbitraryData_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_offchain_msgSignArbitraryData_proto_goTypes = []interface{}{
(*MsgSignArbitraryData)(nil), // 0: offchain.MsgSignArbitraryData
}
var file_offchain_msgSignArbitraryData_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_offchain_msgSignArbitraryData_proto_init() }
func file_offchain_msgSignArbitraryData_proto_init() {
if File_offchain_msgSignArbitraryData_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_offchain_msgSignArbitraryData_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MsgSignArbitraryData); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_offchain_msgSignArbitraryData_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_offchain_msgSignArbitraryData_proto_goTypes,
DependencyIndexes: file_offchain_msgSignArbitraryData_proto_depIdxs,
MessageInfos: file_offchain_msgSignArbitraryData_proto_msgTypes,
}.Build()
File_offchain_msgSignArbitraryData_proto = out.File
file_offchain_msgSignArbitraryData_proto_rawDesc = nil
file_offchain_msgSignArbitraryData_proto_goTypes = nil
file_offchain_msgSignArbitraryData_proto_depIdxs = nil
}

View File

@ -0,0 +1,317 @@
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,
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)
}
}

116
client/v2/offchain/cli.go Normal file
View File

@ -0,0 +1,116 @@
package offchain
import (
"os"
"path/filepath"
"github.com/spf13/cobra"
v2flags "cosmossdk.io/client/v2/internal/flags"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
)
const (
flagNotEmitUnpopulated = "notEmitUnpopulated"
flagIndent = "indent"
flagEncoding = "encoding"
flagFileFormat = "file-format"
)
// OffChain off-chain utilities.
func OffChain() *cobra.Command {
cmd := &cobra.Command{
Use: "off-chain",
Short: "Off-chain utilities.",
Long: `Utilities for off-chain data.`,
}
cmd.AddCommand(
SignFile(),
VerifyFile(),
)
flags.AddKeyringFlags(cmd.PersistentFlags())
return cmd
}
// SignFile signs a file with a key.
func SignFile() *cobra.Command {
cmd := &cobra.Command{
Use: "sign-file <keyName> <fileName>",
Short: "Sign a file.",
Long: "Sign a file using a given key.",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
bz, err := os.ReadFile(args[1])
if err != nil {
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)
signedTx, err := Sign(clientCtx, bz, args[0], indent, encoding, outputFormat, !notEmitUnpopulated)
if err != nil {
return err
}
if outputFile != "" {
fp, err := os.OpenFile(filepath.Clean(outputFile), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
if err != nil {
return err
}
cmd.SetOut(fp)
}
cmd.Println(signedTx)
return nil
},
}
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")
return cmd
}
// VerifyFile verifies given file with given key.
func VerifyFile() *cobra.Command {
cmd := &cobra.Command{
Use: "verify-file <keyName> <fileName>",
Short: "Verify a file.",
Long: "Verify a previously signed file with the given key.",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
bz, err := os.ReadFile(args[1])
if err != nil {
return err
}
fileFormat, _ := cmd.Flags().GetString(flagFileFormat)
err = Verify(clientCtx, bz, fileFormat)
if err == nil {
cmd.Println("Verification OK!")
}
return err
},
}
cmd.Flags().String(flagFileFormat, "json", "Choose whats the file format to be verified (json|text)")
return cmd
}

View File

@ -0,0 +1,146 @@
package offchain
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
"cosmossdk.io/x/tx/signing"
"cosmossdk.io/x/tx/signing/aminojson"
"cosmossdk.io/x/tx/signing/direct"
"cosmossdk.io/x/tx/signing/directaux"
"cosmossdk.io/x/tx/signing/textual"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/address"
"github.com/cosmos/cosmos-sdk/codec/testutil"
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing"
)
const (
addressCodecPrefix = "cosmos"
validatorAddressCodecPrefix = "cosmosvaloper"
mnemonic = "have embark stumble card pistol fun gauge obtain forget oil awesome lottery unfold corn sure original exist siren pudding spread uphold dwarf goddess card"
)
func getCodec() codec.Codec {
registry := testutil.CodecOptions{}.NewInterfaceRegistry()
cryptocodec.RegisterInterfaces(registry)
return codec.NewProtoCodec(registry)
}
func newGRPCCoinMetadataQueryFn(grpcConn grpc.ClientConnInterface) textual.CoinMetadataQueryFn {
return func(ctx context.Context, denom string) (*bankv1beta1.Metadata, error) {
bankQueryClient := bankv1beta1.NewQueryClient(grpcConn)
res, err := bankQueryClient.DenomMetadata(ctx, &bankv1beta1.QueryDenomMetadataRequest{
Denom: denom,
})
if err != nil {
return nil, err
}
return res.Metadata, nil
}
}
// testConfig fulfills client.TxConfig although SignModeHandler is the only method implemented.
type testConfig struct {
handler *signing.HandlerMap
}
func (t testConfig) SignModeHandler() *signing.HandlerMap {
return t.handler
}
func (t testConfig) TxEncoder() sdk.TxEncoder {
return nil
}
func (t testConfig) TxDecoder() sdk.TxDecoder {
return nil
}
func (t testConfig) TxJSONEncoder() sdk.TxEncoder {
return nil
}
func (t testConfig) TxJSONDecoder() sdk.TxDecoder {
return nil
}
func (t testConfig) MarshalSignatureJSON(v2s []signingtypes.SignatureV2) ([]byte, error) {
return nil, nil
}
func (t testConfig) UnmarshalSignatureJSON(bytes []byte) ([]signingtypes.SignatureV2, error) {
return nil, nil
}
func (t testConfig) NewTxBuilder() client.TxBuilder {
return nil
}
func (t testConfig) WrapTxBuilder(s sdk.Tx) (client.TxBuilder, error) {
return nil, nil
}
func (t testConfig) SigningContext() *signing.Context {
return nil
}
func newTestConfig(t *testing.T) *testConfig {
t.Helper()
enabledSignModes := []signingtypes.SignMode{
signingtypes.SignMode_SIGN_MODE_DIRECT,
signingtypes.SignMode_SIGN_MODE_DIRECT_AUX,
signingtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON,
signingtypes.SignMode_SIGN_MODE_TEXTUAL,
}
var err error
signingOptions := signing.Options{
AddressCodec: address.NewBech32Codec(addressCodecPrefix),
ValidatorAddressCodec: address.NewBech32Codec(validatorAddressCodecPrefix),
}
signingContext, err := signing.NewContext(signingOptions)
require.NoError(t, err)
lenSignModes := len(enabledSignModes)
handlers := make([]signing.SignModeHandler, lenSignModes)
for i, m := range enabledSignModes {
var err error
switch m {
case signingtypes.SignMode_SIGN_MODE_DIRECT:
handlers[i] = &direct.SignModeHandler{}
case signingtypes.SignMode_SIGN_MODE_DIRECT_AUX:
handlers[i], err = directaux.NewSignModeHandler(directaux.SignModeHandlerOptions{
TypeResolver: signingOptions.TypeResolver,
SignersContext: signingContext,
})
require.NoError(t, err)
case signingtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON:
handlers[i] = aminojson.NewSignModeHandler(aminojson.SignModeHandlerOptions{
FileResolver: signingOptions.FileResolver,
TypeResolver: signingOptions.TypeResolver,
})
case signingtypes.SignMode_SIGN_MODE_TEXTUAL:
handlers[i], err = textual.NewSignModeHandler(textual.SignModeOptions{
CoinMetadataQuerier: newGRPCCoinMetadataQueryFn(client.Context{}),
FileResolver: signingOptions.FileResolver,
TypeResolver: signingOptions.TypeResolver,
})
require.NoError(t, err)
}
}
handler := signing.NewHandlerMap(handlers...)
return &testConfig{handler: handler}
}

View File

@ -0,0 +1,44 @@
package offchain
import (
"encoding/base64"
"encoding/hex"
"fmt"
)
const (
noEncoder = "no-encoding"
b64Encoder = "base64"
hexEncoder = "hex"
)
type encodingFunc = func([]byte) (string, error)
// noEncoding returns a byte slice as a string.
func noEncoding(digest []byte) (string, error) {
return string(digest), nil
}
// base64Encoding returns a byte slice as a b64 encoded string.
func base64Encoding(digest []byte) (string, error) {
return base64.StdEncoding.EncodeToString(digest), nil
}
// hexEncoding returns a byte slice as a hex encoded string.
func hexEncoding(digest []byte) (string, error) {
return hex.EncodeToString(digest), nil
}
// getEncoder returns a encodingFunc bases on the encoder id provided.
func getEncoder(encoder string) (encodingFunc, error) {
switch encoder {
case noEncoder:
return noEncoding, nil
case b64Encoder:
return base64Encoding, nil
case hexEncoder:
return hexEncoding, nil
default:
return nil, fmt.Errorf("unknown encoder: %s", encoder)
}
}

View File

@ -0,0 +1,63 @@
package offchain
import (
"reflect"
"testing"
"github.com/stretchr/testify/require"
)
func Test_EncodingFuncs(t *testing.T) {
tests := []struct {
name string
encodeFunc encodingFunc
digest []byte
want string
}{
{
name: "No encoding",
encodeFunc: noEncoding,
digest: []byte("Hello!"),
want: "Hello!",
},
{
name: "base64 encoding",
encodeFunc: base64Encoding,
digest: []byte("Hello!"),
want: "SGVsbG8h",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.encodeFunc(tt.digest)
require.NoError(t, err)
require.Equal(t, got, tt.want)
})
}
}
func Test_getEncoder(t *testing.T) {
tests := []struct {
name string
encoder string
want encodingFunc
}{
{
name: "no encoding",
encoder: "no-encoding",
want: noEncoding,
},
{
name: "base64",
encoder: "base64",
want: base64Encoding,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := getEncoder(tt.encoder)
require.NoError(t, err)
require.Equal(t, reflect.ValueOf(got).Pointer(), reflect.ValueOf(tt.want).Pointer())
})
}
}

View File

@ -0,0 +1,43 @@
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
}

167
client/v2/offchain/sign.go Normal file
View File

@ -0,0 +1,167 @@
package offchain
import (
"context"
"google.golang.org/protobuf/types/known/anypb"
apisigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1"
apitx "cosmossdk.io/api/cosmos/tx/v1beta1"
"cosmossdk.io/client/v2/internal/offchain"
txsigning "cosmossdk.io/x/tx/signing"
"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"
)
const (
// ExpectedChainID defines the chain id an off-chain message must have
ExpectedChainID = ""
// ExpectedAccountNumber defines the account number an off-chain message must have
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
}
// 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)
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)
if err != nil {
return nil, err
}
pubKey, err := keybase.GetPubKey(fromName)
if err != nil {
return nil, err
}
addr, err := ctx.AddressCodec.BytesToString(pubKey.Address())
if err != nil {
return nil, err
}
msg := &offchain.MsgSignArbitraryData{
AppDomain: version.AppName,
Signer: addr,
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)
if err != nil {
return nil, err
}
sigData.Signature = signedBytes
err = txBuilder.SetSignatures(sig)
if err != nil {
return nil, err
}
return txBuilder.GetTx(), 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
}
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)
}

View File

@ -0,0 +1,51 @@
package offchain
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec/address"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
)
func Test_sign(t *testing.T) {
k := keyring.NewInMemory(getCodec())
ctx := client.Context{
Keyring: k,
TxConfig: newTestConfig(t),
AddressCodec: address.NewBech32Codec("cosmos"),
}
type args struct {
ctx client.Context
fromName string
digest string
}
tests := []struct {
name string
args args
}{
{
name: "Sign",
args: args{
ctx: ctx,
fromName: "direct",
digest: "Hello world!",
},
},
}
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)
})
}
}

View File

@ -0,0 +1,34 @@
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

@ -0,0 +1,132 @@
package offchain
import (
"bytes"
"context"
"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"
txsigning "cosmossdk.io/x/tx/signing"
"github.com/cosmos/cosmos-sdk/client"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
)
// Verify verifies a digest after unmarshalling it.
func Verify(ctx client.Context, digest []byte, fileFormat string) error {
tx, err := unmarshal(digest, fileFormat)
if err != nil {
return err
}
return verify(ctx, tx)
}
// verify verifies given Tx.
func verify(ctx client.Context, tx *apitx.Tx) error {
sigTx := builder{
cdc: ctx.Codec,
tx: tx,
}
signModeHandler := ctx.TxConfig.SignModeHandler()
signers, err := sigTx.GetSigners()
if err != nil {
return err
}
sigs, err := sigTx.GetSignatures()
if err != nil {
return err
}
if len(sigs) != len(signers) {
return errors.New("mismatch between the number of signatures and signers")
}
for i, sig := range sigs {
pubKey := sig.PubKey
if !bytes.Equal(pubKey.Address(), signers[i]) {
return errors.New("signature does not match its respective signer")
}
addr, err := ctx.AddressCodec.BytesToString(pubKey.Address())
if err != nil {
return err
}
anyPk, err := codectypes.NewAnyWithValue(pubKey)
if err != nil {
return err
}
txSignerData := txsigning.SignerData{
ChainID: ExpectedChainID,
AccountNumber: ExpectedAccountNumber,
Sequence: ExpectedSequence,
Address: addr,
PubKey: &anypb.Any{
TypeUrl: anyPk.TypeUrl,
Value: anyPk.Value,
},
}
txData, err := sigTx.GetSigningTxData()
if err != nil {
return err
}
err = verifySignature(context.Background(), pubKey, txSignerData, sig.Data, signModeHandler, txData)
if err != nil {
return err
}
}
return nil
}
// 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)
default:
return nil, fmt.Errorf("unsupported file format: %s", fileFormat)
}
return tx, err
}
// verifySignature verifies a transaction signature contained in SignatureData abstracting over different signing modes.
func verifySignature(
ctx context.Context,
pubKey cryptotypes.PubKey,
signerData txsigning.SignerData,
signatureData SignatureData,
handler *txsigning.HandlerMap,
txData txsigning.TxData,
) error {
switch data := signatureData.(type) {
case *SingleSignatureData:
signBytes, err := handler.GetSignBytes(ctx, data.SignMode, signerData, txData)
if err != nil {
return err
}
if !pubKey.VerifySignature(signBytes, data.Signature) {
return fmt.Errorf("unable to verify single signer signature")
}
return nil
default:
return fmt.Errorf("unexpected SignatureData %T", signatureData)
}
}

View File

@ -0,0 +1,112 @@
package offchain
import (
"testing"
"github.com/stretchr/testify/require"
_ "cosmossdk.io/api/cosmos/crypto/secp256k1"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec/address"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
)
func Test_Verify(t *testing.T) {
ctx := client.Context{
TxConfig: newTestConfig(t),
Codec: getCodec(),
AddressCodec: address.NewBech32Codec("cosmos"),
}
tests := []struct {
name string
digest []byte
fileFormat string
ctx client.Context
wantErr bool
}{
{
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==\"]}"),
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==\"]}"),
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"),
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"),
fileFormat: "text",
ctx: ctx,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := Verify(tt.ctx, tt.digest, tt.fileFormat)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}
func Test_SignVerify(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{
TxConfig: newTestConfig(t),
Codec: getCodec(),
AddressCodec: address.NewBech32Codec("cosmos"),
Keyring: k,
}
tx, err := sign(ctx, "signVerify", "digest")
require.NoError(t, err)
err = verify(ctx, tx)
require.NoError(t, err)
}
func Test_unmarshal(t *testing.T) {
tests := []struct {
name string
digest []byte
fileFormat string
}{
{
name: "json test",
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=="]}`),
fileFormat: "json",
},
{
name: "text test",
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"),
fileFormat: "text",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := unmarshal(tt.digest, tt.fileFormat)
require.NoError(t, err)
require.NotNil(t, got)
})
}
}

View File

@ -9,6 +9,7 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
"cosmossdk.io/client/v2/offchain"
"cosmossdk.io/log"
"cosmossdk.io/simapp"
confixcmd "cosmossdk.io/tools/confix/cmd"
@ -59,6 +60,7 @@ func initRootCmd(
queryCommand(),
txCommand(),
keys.Commands(),
offchain.OffChain(),
)
}