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:
parent
bda2d11232
commit
61c367d9d1
@ -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.
|
||||
|
||||
|
||||
@ -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!
|
||||
```
|
||||
|
||||
20
client/v2/internal/offchain/msgSignArbitraryData.proto
Normal file
20
client/v2/internal/offchain/msgSignArbitraryData.proto
Normal 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;
|
||||
}
|
||||
|
||||
730
client/v2/internal/offchain/msgSignArbitraryData.pulsar.go
Normal file
730
client/v2/internal/offchain/msgSignArbitraryData.pulsar.go
Normal 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
|
||||
}
|
||||
317
client/v2/offchain/builder.go
Normal file
317
client/v2/offchain/builder.go
Normal 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
116
client/v2/offchain/cli.go
Normal 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
|
||||
}
|
||||
146
client/v2/offchain/common_test.go
Normal file
146
client/v2/offchain/common_test.go
Normal 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}
|
||||
}
|
||||
44
client/v2/offchain/encode.go
Normal file
44
client/v2/offchain/encode.go
Normal 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)
|
||||
}
|
||||
}
|
||||
63
client/v2/offchain/encode_test.go
Normal file
63
client/v2/offchain/encode_test.go
Normal 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())
|
||||
})
|
||||
}
|
||||
}
|
||||
43
client/v2/offchain/marshal.go
Normal file
43
client/v2/offchain/marshal.go
Normal 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
167
client/v2/offchain/sign.go
Normal 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)
|
||||
}
|
||||
51
client/v2/offchain/sign_test.go
Normal file
51
client/v2/offchain/sign_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
34
client/v2/offchain/signature.go
Normal file
34
client/v2/offchain/signature.go
Normal 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
|
||||
}
|
||||
132
client/v2/offchain/verify.go
Normal file
132
client/v2/offchain/verify.go
Normal 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)
|
||||
}
|
||||
}
|
||||
112
client/v2/offchain/verify_test.go
Normal file
112
client/v2/offchain/verify_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user