mirror of
https://github.com/planetmint/planetmint-go.git
synced 2025-03-30 15:08:28 +00:00
Add occ signing to lib (#441)
* feat: add trustwallet signing to lib * feat: add osc message sender to lib * docs: update README --------- Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>
This commit is contained in:
parent
a7bb114301
commit
e404fef08b
2
go.mod
2
go.mod
@ -75,6 +75,7 @@ require (
|
||||
github.com/cosmos/ledger-cosmos-go v0.12.4 // indirect
|
||||
github.com/cosmos/rosetta-sdk-go v0.10.0 // indirect
|
||||
github.com/creachadair/taskgroup v0.4.2 // indirect
|
||||
github.com/creack/goselect v0.1.2 // indirect
|
||||
github.com/danieljoos/wincred v1.1.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
|
||||
@ -165,6 +166,7 @@ require (
|
||||
github.com/ulikunitz/xz v0.5.11 // indirect
|
||||
github.com/zondax/hid v0.9.2 // indirect
|
||||
github.com/zondax/ledger-go v0.14.3 // indirect
|
||||
go.bug.st/serial v1.6.2 // indirect
|
||||
go.etcd.io/bbolt v1.3.7 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/otel v1.19.0 // indirect
|
||||
|
4
go.sum
4
go.sum
@ -371,6 +371,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creachadair/taskgroup v0.4.2 h1:jsBLdAJE42asreGss2xZGZ8fJra7WtwnHWeJFxv2Li8=
|
||||
github.com/creachadair/taskgroup v0.4.2/go.mod h1:qiXUOSrbwAY3u0JPGTzObbE3yf9hcXHDKBZ2ZjpCbgM=
|
||||
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=
|
||||
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
|
||||
@ -1002,6 +1004,8 @@ github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U=
|
||||
github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM=
|
||||
github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw=
|
||||
github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI=
|
||||
go.bug.st/serial v1.6.2 h1:kn9LRX3sdm+WxWKufMlIRndwGfPWsH1/9lCWXQCasq8=
|
||||
go.bug.st/serial v1.6.2/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
|
||||
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||
|
@ -9,6 +9,10 @@ After that we construct three messages to send `10plmnt` each to three addresses
|
||||
We then build and sign the transaction and eventually send this transaction via RPC.
|
||||
For debugging purposes we print the transaction that we send as JSON.
|
||||
|
||||
## Support for Trust Wallet over serial connection
|
||||
|
||||
The config has a setting for a `serialPort`. If it is set then `BroadcastTxWithFileLock()` will sign transactions with the TrustWallet. Be aware that the public key must be available on the keyring in order to construct transactions signing data.
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
|
@ -17,6 +17,7 @@ type Config struct {
|
||||
rootDir string
|
||||
rpcEndpoint string
|
||||
txGas uint64
|
||||
serialPort string
|
||||
}
|
||||
|
||||
// lib wide global singleton
|
||||
@ -37,6 +38,7 @@ func DefaultConfig() *Config {
|
||||
rootDir: "~/.planetmint-go/",
|
||||
rpcEndpoint: "http://127.0.0.1:26657",
|
||||
txGas: 200000,
|
||||
serialPort: "",
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,3 +118,10 @@ func (config *Config) SetTxGas(txGas uint64) *Config {
|
||||
config.txGas = txGas
|
||||
return config
|
||||
}
|
||||
|
||||
func (config *Config) SetSerialPort(port string) *Config {
|
||||
changeLock.Lock()
|
||||
defer changeLock.Unlock()
|
||||
config.serialPort = port
|
||||
return config
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ require (
|
||||
github.com/cosmos/cosmos-sdk v0.47.8
|
||||
github.com/planetmint/planetmint-go v0.7.2
|
||||
github.com/stretchr/testify v1.8.4
|
||||
go.bug.st/serial v1.6.2
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
)
|
||||
|
||||
@ -58,6 +59,7 @@ require (
|
||||
github.com/cosmos/ledger-cosmos-go v0.12.4 // indirect
|
||||
github.com/cosmos/rosetta-sdk-go v0.10.0 // indirect
|
||||
github.com/creachadair/taskgroup v0.4.2 // indirect
|
||||
github.com/creack/goselect v0.1.2 // indirect
|
||||
github.com/danieljoos/wincred v1.1.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
|
||||
|
@ -372,6 +372,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creachadair/taskgroup v0.4.2 h1:jsBLdAJE42asreGss2xZGZ8fJra7WtwnHWeJFxv2Li8=
|
||||
github.com/creachadair/taskgroup v0.4.2/go.mod h1:qiXUOSrbwAY3u0JPGTzObbE3yf9hcXHDKBZ2ZjpCbgM=
|
||||
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=
|
||||
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
|
||||
@ -1013,6 +1015,8 @@ github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U=
|
||||
github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM=
|
||||
github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw=
|
||||
github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI=
|
||||
go.bug.st/serial v1.6.2 h1:kn9LRX3sdm+WxWKufMlIRndwGfPWsH1/9lCWXQCasq8=
|
||||
go.bug.st/serial v1.6.2/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
|
||||
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
"github.com/planetmint/planetmint-go/lib"
|
||||
"github.com/planetmint/planetmint-go/lib/trustwallet"
|
||||
clitestutil "github.com/planetmint/planetmint-go/testutil/cli"
|
||||
"github.com/planetmint/planetmint-go/testutil/network"
|
||||
"github.com/planetmint/planetmint-go/testutil/sample"
|
||||
@ -86,3 +87,54 @@ func (s *E2ETestSuite) TestBankSendBroadcastTxWithFileLock() {
|
||||
s.Require().NoError(err)
|
||||
assert.Equal(s.T(), "[]", txResponse.RawLog)
|
||||
}
|
||||
|
||||
func (s *E2ETestSuite) TestLoadKeys() {
|
||||
s.T().SkipNow()
|
||||
_, err := setKeys()
|
||||
if err == nil {
|
||||
_, err = loadKeys()
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
s.Require().NoError(s.network.WaitForNextBlock())
|
||||
}
|
||||
|
||||
func (s *E2ETestSuite) TestOccSigning() {
|
||||
s.T().SkipNow()
|
||||
val := s.network.Validators[0]
|
||||
|
||||
keys, err := loadKeys()
|
||||
s.Require().NoError(err)
|
||||
|
||||
addr := sdk.MustAccAddressFromBech32(keys.PlanetmintAddress)
|
||||
|
||||
coin := sdk.NewCoins(sdk.NewInt64Coin("stake", 10))
|
||||
msg := banktypes.NewMsgSend(addr, val.Address, coin)
|
||||
|
||||
libConfig := lib.GetConfig()
|
||||
libConfig.SetSerialPort("/dev/ttyACM0")
|
||||
|
||||
out, err := lib.BroadcastTxWithFileLock(addr, msg)
|
||||
s.Require().NoError(err)
|
||||
|
||||
txResponse, err := lib.GetTxResponseFromOut(out)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal("[]", txResponse.RawLog)
|
||||
s.Require().Equal(uint32(0), txResponse.Code)
|
||||
}
|
||||
|
||||
// set sample mnemonic on trust wallet
|
||||
func setKeys() (string, error) {
|
||||
connector, err := trustwallet.NewTrustWalletConnector("/dev/ttyACM0")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return connector.RecoverFromMnemonic(sample.Mnemonic)
|
||||
}
|
||||
|
||||
func loadKeys() (*trustwallet.PlanetMintKeys, error) {
|
||||
connector, err := trustwallet.NewTrustWalletConnector("/dev/ttyACM0")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connector.GetPlanetmintKeys()
|
||||
}
|
||||
|
147
lib/trustwallet/occ.go
Normal file
147
lib/trustwallet/occ.go
Normal file
@ -0,0 +1,147 @@
|
||||
package trustwallet
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"go.bug.st/serial"
|
||||
)
|
||||
|
||||
const (
|
||||
SlipEnd = 0xC0
|
||||
SlipEsc = 0xDB
|
||||
SlipEscEnd = 0xDC
|
||||
SlipEscEsc = 0xDD
|
||||
nsPerUs = 1000
|
||||
nsPerMs = 8000 * nsPerUs
|
||||
)
|
||||
|
||||
// occDo performs the operations to send and receive data over serial port.
|
||||
func occDo(data []byte, bufferDelayMs int, portName string, outBuffer []byte) (int, error) {
|
||||
// Initialize unencoded and encoded payloads
|
||||
payloadUnencoded := make([]byte, 0, len(data))
|
||||
payloadSlipEncoded := make([]byte, 0, len(data)*2)
|
||||
|
||||
// Copy data to payloadUnencoded
|
||||
payloadUnencoded = append(payloadUnencoded, data...)
|
||||
|
||||
// Open serial port
|
||||
mode := &serial.Mode{BaudRate: 115200}
|
||||
s, err := serial.Open(portName, mode)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("unable to open serial port: %w", err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
// Encode payload using SLIP
|
||||
encodeSLIP(payloadUnencoded, &payloadSlipEncoded)
|
||||
|
||||
// Send encoded payload over serial
|
||||
if _, err := s.Write(payloadSlipEncoded); err != nil {
|
||||
return 0, fmt.Errorf("unable to write to serial port: %w", err)
|
||||
}
|
||||
|
||||
time.Sleep(time.Duration(bufferDelayMs) * time.Millisecond)
|
||||
|
||||
// Read response from serial
|
||||
readBuffer := make([]byte, 1)
|
||||
encodedResponse := make([]byte, 0)
|
||||
|
||||
slipMsgFramer := 0
|
||||
|
||||
for {
|
||||
n, err := s.Read(readBuffer)
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
encodedResponse = append(encodedResponse, readBuffer[0])
|
||||
|
||||
if readBuffer[0] == SlipEnd {
|
||||
if slipMsgFramer == 1 {
|
||||
break
|
||||
}
|
||||
slipMsgFramer++
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Decode SLIP response
|
||||
decodedResponse, err := decodeSLIP(encodedResponse)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("unable to decode SLIP: %w", err)
|
||||
}
|
||||
|
||||
// Copy decoded response to outBuffer
|
||||
copyLength := min(len(decodedResponse), len(outBuffer))
|
||||
copy(outBuffer, decodedResponse[:copyLength])
|
||||
|
||||
return copyLength, nil
|
||||
}
|
||||
|
||||
// encodeSLIP encodes data using SLIP protocol.
|
||||
func encodeSLIP(data []byte, encoded *[]byte) {
|
||||
*encoded = append(*encoded, SlipEnd)
|
||||
for _, b := range data {
|
||||
switch b {
|
||||
case SlipEnd:
|
||||
*encoded = append(*encoded, SlipEsc, SlipEscEnd)
|
||||
case SlipEsc:
|
||||
*encoded = append(*encoded, SlipEsc, SlipEscEsc)
|
||||
default:
|
||||
*encoded = append(*encoded, b)
|
||||
}
|
||||
}
|
||||
*encoded = append(*encoded, SlipEnd)
|
||||
}
|
||||
|
||||
// decodeSLIP decodes SLIP-encoded data.
|
||||
func decodeSLIP(encoded []byte) ([]byte, error) {
|
||||
// Check for empty input
|
||||
if len(encoded) == 0 {
|
||||
return nil, errors.New("encoded data is empty")
|
||||
}
|
||||
|
||||
// Remove first and last SLIP_END bytes
|
||||
if encoded[0] == SlipEnd {
|
||||
encoded = encoded[1:]
|
||||
}
|
||||
if encoded[len(encoded)-1] == SlipEnd {
|
||||
encoded = encoded[:len(encoded)-1]
|
||||
}
|
||||
|
||||
decoded := make([]byte, 0, len(encoded))
|
||||
esc := false
|
||||
|
||||
for _, b := range encoded {
|
||||
switch {
|
||||
case b == SlipEsc && !esc:
|
||||
esc = true
|
||||
case b == SlipEscEnd && esc:
|
||||
decoded = append(decoded, SlipEnd)
|
||||
esc = false
|
||||
case b == SlipEscEsc && esc:
|
||||
decoded = append(decoded, SlipEsc)
|
||||
esc = false
|
||||
default:
|
||||
decoded = append(decoded, b)
|
||||
esc = false
|
||||
}
|
||||
}
|
||||
|
||||
return decoded, nil
|
||||
}
|
||||
|
||||
// min returns the smaller of two integers.
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
88
lib/trustwallet/osc_message_sender.go
Normal file
88
lib/trustwallet/osc_message_sender.go
Normal file
@ -0,0 +1,88 @@
|
||||
package trustwallet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
PrefixIhw = "/IHW"
|
||||
BufferSize = 1024
|
||||
BufferDelayMs = 200
|
||||
)
|
||||
|
||||
type OSCResponse struct {
|
||||
Command string
|
||||
Data []string
|
||||
}
|
||||
|
||||
type OSCMessageSender struct {
|
||||
portName []byte
|
||||
bufferSize int
|
||||
bufferDelayMs int
|
||||
}
|
||||
|
||||
func NewOSCMessageSender(portName string) (*OSCMessageSender, error) {
|
||||
return &OSCMessageSender{
|
||||
portName: []byte(portName),
|
||||
bufferSize: BufferSize,
|
||||
bufferDelayMs: BufferDelayMs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *OSCMessageSender) SendMessage(message []byte) (OSCResponse, error) {
|
||||
outputBuffer := make([]byte, s.bufferSize)
|
||||
|
||||
// Call occDo function
|
||||
outputLength, err := occDo(
|
||||
message,
|
||||
s.bufferDelayMs,
|
||||
string(s.portName),
|
||||
outputBuffer,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return OSCResponse{}, fmt.Errorf("failed to send message: %w", err)
|
||||
}
|
||||
|
||||
if outputLength == 0 {
|
||||
return OSCResponse{}, errors.New("no response received")
|
||||
}
|
||||
|
||||
// Extract the information from the output buffer
|
||||
return extractInformation(outputBuffer[:outputLength])
|
||||
}
|
||||
|
||||
func extractInformation(responseBytes []byte) (OSCResponse, error) {
|
||||
decodedString := string(bytes.Trim(responseBytes, "\x00"))
|
||||
parts := strings.Split(decodedString, "\x00")
|
||||
|
||||
var response OSCResponse
|
||||
if len(parts) > 0 {
|
||||
commandPart := parts[0]
|
||||
dataParts := parts[1:]
|
||||
|
||||
if strings.Contains(commandPart, ",") {
|
||||
splitCmd := strings.SplitN(commandPart, ",", 2)
|
||||
response.Command = strings.TrimSpace(splitCmd[0])
|
||||
dataParts = append([]string{splitCmd[1]}, dataParts...)
|
||||
} else {
|
||||
response.Command = strings.TrimSpace(commandPart)
|
||||
}
|
||||
|
||||
response.Data = make([]string, 0, len(dataParts))
|
||||
for _, part := range dataParts {
|
||||
if trimmed := strings.TrimSpace(part); trimmed != "" {
|
||||
response.Data = append(response.Data, trimmed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(response.Data) == 0 {
|
||||
response.Data = []string{"No valid data found."}
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
284
lib/trustwallet/trustwallet.go
Normal file
284
lib/trustwallet/trustwallet.go
Normal file
@ -0,0 +1,284 @@
|
||||
package trustwallet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
keys *PlanetMintKeys
|
||||
)
|
||||
|
||||
type Connector struct {
|
||||
oscSender *OSCMessageSender
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewTrustWalletConnector(portName string) (*Connector, error) {
|
||||
sender, err := NewOSCMessageSender(portName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Connector{
|
||||
oscSender: sender,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *Connector) sendOSCMessage(address string, args ...interface{}) (OSCResponse, error) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
message, err := encodeOSCMessage(address, args...)
|
||||
if err != nil {
|
||||
return OSCResponse{}, err
|
||||
}
|
||||
|
||||
return t.oscSender.SendMessage(message)
|
||||
}
|
||||
|
||||
func (t *Connector) ValiseGet() (string, error) {
|
||||
response, err := t.sendOSCMessage(PrefixIhw + "/getSeed")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(response.Data) > 0 {
|
||||
return response.Data[0], nil
|
||||
}
|
||||
return "", errors.New("no data returned")
|
||||
}
|
||||
|
||||
func (t *Connector) CreateMnemonic() (string, error) {
|
||||
response, err := t.sendOSCMessage(PrefixIhw+"/mnemonicToSeed", int32(1))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(response.Data) > 0 {
|
||||
return response.Data[0], nil
|
||||
}
|
||||
return "", errors.New("no data returned")
|
||||
}
|
||||
|
||||
func (t *Connector) InjectPlanetminkeyToSE050(slot int) (bool, error) {
|
||||
response, err := t.sendOSCMessage(PrefixIhw+"/se050InjectSECPKeys", int32(slot))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(response.Data) > 0 {
|
||||
return response.Data[0] == "0", nil
|
||||
}
|
||||
return false, errors.New("no data returned")
|
||||
}
|
||||
|
||||
func (t *Connector) RecoverFromMnemonic(mnemonic string) (string, error) {
|
||||
response, err := t.sendOSCMessage(PrefixIhw+"/mnemonicToSeed", int32(1), mnemonic)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(response.Data) > 0 {
|
||||
return response.Data[0], nil
|
||||
}
|
||||
return "", errors.New("no data returned")
|
||||
}
|
||||
|
||||
func (t *Connector) GetPlanetmintKeys() (*PlanetMintKeys, error) {
|
||||
if keys == nil {
|
||||
response, err := t.sendOSCMessage(PrefixIhw + "/getPlntmntKeys")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(response.Data) < 4 {
|
||||
return nil, errors.New("trust wallet not initialized. Please initialize the wallet")
|
||||
}
|
||||
|
||||
keys = &PlanetMintKeys{
|
||||
PlanetmintAddress: response.Data[1],
|
||||
ExtendedLiquidPubkey: response.Data[2],
|
||||
ExtendedPlanetmintPubkey: response.Data[3],
|
||||
RawPlanetmintPubkey: response.Data[4],
|
||||
}
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func (t *Connector) GetSeedSE050() (string, error) {
|
||||
response, err := t.sendOSCMessage(PrefixIhw + "/se050GetSeed")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(response.Data) > 0 {
|
||||
return response.Data[0], nil
|
||||
}
|
||||
return "", errors.New("no data returned")
|
||||
}
|
||||
|
||||
func (t *Connector) SignHashWithPlanetmint(dataToSign string) (string, error) {
|
||||
response, err := t.sendOSCMessage(PrefixIhw+"/ecdsaSignPlmnt", dataToSign)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(response.Data) > 1 {
|
||||
return response.Data[1], nil
|
||||
}
|
||||
return "", errors.New("no signature returned")
|
||||
}
|
||||
|
||||
func (t *Connector) SignHashWithRDDL(dataToSign string) (string, error) {
|
||||
response, err := t.sendOSCMessage(PrefixIhw+"/ecdsaSignRddl", dataToSign)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(response.Data) > 1 {
|
||||
return response.Data[1], nil
|
||||
}
|
||||
return "", errors.New("no signature returned")
|
||||
}
|
||||
|
||||
func (t *Connector) CreateOptegaKeypair(ctx int) (string, error) {
|
||||
response, err := t.sendOSCMessage(PrefixIhw+"/optigaTrustXCreateSecret", int32(ctx), "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(response.Data) > 1 {
|
||||
return response.Data[1], nil
|
||||
}
|
||||
return "", errors.New("no public key returned")
|
||||
}
|
||||
|
||||
func (t *Connector) SignWithOptega(ctx int, dataToSign, pubkey string) (string, error) {
|
||||
response, err := t.sendOSCMessage(PrefixIhw+"/optigaTrustXSignMessage", int32(ctx), dataToSign, pubkey, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(response.Data) > 1 {
|
||||
return response.Data[1], nil
|
||||
}
|
||||
return "", errors.New("no signature returned")
|
||||
}
|
||||
|
||||
func (t *Connector) UnwrapPublicKey(publicKey string) (bool, string) {
|
||||
length := len(publicKey)
|
||||
if length == 136 || length == 130 {
|
||||
return true, publicKey[len(publicKey)-128:]
|
||||
} else if length == 128 {
|
||||
return true, publicKey
|
||||
}
|
||||
return false, publicKey
|
||||
}
|
||||
|
||||
func (t *Connector) CalculateHash(dataToSign string) (string, error) {
|
||||
response, err := t.sendOSCMessage(PrefixIhw+"/se050CalculateHash", dataToSign)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(response.Data) > 1 {
|
||||
return response.Data[1], nil
|
||||
}
|
||||
return "", errors.New("no hash returned")
|
||||
}
|
||||
|
||||
func (t *Connector) CreateSE050KeypairNIST(ctx int) (string, error) {
|
||||
response, err := t.sendOSCMessage(PrefixIhw+"/se050CreateKeyPair", int32(ctx), int32(1))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(response.Data) > 1 {
|
||||
return response.Data[1], nil
|
||||
}
|
||||
return "", errors.New("no public key returned")
|
||||
}
|
||||
|
||||
func (t *Connector) GetPublicKeyFromSE050(ctx int) (string, error) {
|
||||
response, err := t.sendOSCMessage(PrefixIhw+"/se050GetPublicKey", int32(ctx))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(response.Data) > 1 {
|
||||
valid, pubKey := t.UnwrapPublicKey(response.Data[1])
|
||||
if !valid {
|
||||
return "", errors.New("inject PlanetMintKey failed: No key found")
|
||||
}
|
||||
return pubKey, nil
|
||||
}
|
||||
return "", errors.New("no public key returned")
|
||||
}
|
||||
|
||||
func (t *Connector) SignWithSE050(dataToSign string, ctx int) (string, error) {
|
||||
response, err := t.sendOSCMessage(PrefixIhw+"/se050SignData", dataToSign, int32(ctx))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(response.Data) > 1 {
|
||||
return response.Data[1], nil
|
||||
}
|
||||
return "", errors.New("no signature returned")
|
||||
}
|
||||
|
||||
func (t *Connector) VerifySE050Signature(dataToSign, signature string, ctx int) (bool, error) {
|
||||
response, err := t.sendOSCMessage(PrefixIhw+"/se050VerifySignature", dataToSign, signature, int32(ctx))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(response.Data) > 1 {
|
||||
return strconv.ParseBool(response.Data[1])
|
||||
}
|
||||
return false, errors.New("no verification result returned")
|
||||
}
|
||||
|
||||
func encodeOSCMessage(address string, args ...interface{}) (returnBytes []byte, err error) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
// Write address
|
||||
buffer.WriteString(address)
|
||||
buffer.WriteByte(0)
|
||||
alignBuffer(&buffer)
|
||||
|
||||
// Write type tags
|
||||
buffer.WriteByte(',')
|
||||
for _, arg := range args {
|
||||
switch arg.(type) {
|
||||
case int32:
|
||||
err = buffer.WriteByte('i')
|
||||
case float32:
|
||||
err = buffer.WriteByte('f')
|
||||
case string:
|
||||
err = buffer.WriteByte('s')
|
||||
}
|
||||
if err != nil {
|
||||
return buffer.Bytes(), err
|
||||
}
|
||||
}
|
||||
|
||||
buffer.WriteByte(0)
|
||||
alignBuffer(&buffer)
|
||||
|
||||
// Write arguments
|
||||
for _, arg := range args {
|
||||
switch v := arg.(type) {
|
||||
case int32:
|
||||
err = binary.Write(&buffer, binary.BigEndian, v)
|
||||
case float32:
|
||||
err = binary.Write(&buffer, binary.BigEndian, v)
|
||||
case string:
|
||||
_, err = buffer.WriteString(v)
|
||||
if err != nil {
|
||||
return buffer.Bytes(), err
|
||||
}
|
||||
err = buffer.WriteByte(0)
|
||||
alignBuffer(&buffer)
|
||||
}
|
||||
if err != nil {
|
||||
return buffer.Bytes(), err
|
||||
}
|
||||
}
|
||||
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
func alignBuffer(buffer *bytes.Buffer) {
|
||||
for buffer.Len()%4 != 0 {
|
||||
buffer.WriteByte(0)
|
||||
}
|
||||
}
|
8
lib/trustwallet/types.go
Normal file
8
lib/trustwallet/types.go
Normal file
@ -0,0 +1,8 @@
|
||||
package trustwallet
|
||||
|
||||
type PlanetMintKeys struct {
|
||||
PlanetmintAddress string
|
||||
ExtendedLiquidPubkey string
|
||||
ExtendedPlanetmintPubkey string
|
||||
RawPlanetmintPubkey string
|
||||
}
|
165
lib/tx.go
165
lib/tx.go
@ -2,20 +2,27 @@ package lib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/cometbft/cometbft/crypto"
|
||||
comethttp "github.com/cometbft/cometbft/rpc/client/http"
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
"github.com/planetmint/planetmint-go/lib/trustwallet"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -168,25 +175,7 @@ func broadcastTx(clientCtx client.Context, txf tx.Factory, msgs ...sdk.Msg) (out
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
output, ok := clientCtx.Output.(*bytes.Buffer)
|
||||
if !ok {
|
||||
err = ErrTypeAssertionFailed
|
||||
return
|
||||
}
|
||||
defer output.Reset()
|
||||
|
||||
result := make(map[string]interface{})
|
||||
err = json.Unmarshal(output.Bytes(), &result)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Make a copy because we `defer output.Reset()`
|
||||
out = &bytes.Buffer{}
|
||||
// This is still copying references: *out = *output
|
||||
// Make a real copy: https://stackoverflow.com/a/69758157
|
||||
out.Write(output.Bytes())
|
||||
return
|
||||
return writeClientCtxOutputToBuffer(clientCtx)
|
||||
}
|
||||
|
||||
// BroadcastTxWithFileLock broadcasts a transaction via gRPC and synchronises requests via a file lock.
|
||||
@ -233,7 +222,11 @@ func BroadcastTxWithFileLock(fromAddress sdk.AccAddress, msgs ...sdk.Msg) (out *
|
||||
|
||||
// Set new sequence number
|
||||
txf = txf.WithSequence(sequence)
|
||||
out, err = broadcastTx(clientCtx, txf, msgs...)
|
||||
if GetConfig().serialPort != "" {
|
||||
out, err = broadcastTxWithTrustWalletSignature(clientCtx, txf, msgs...)
|
||||
} else {
|
||||
out, err = broadcastTx(clientCtx, txf, msgs...)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -260,3 +253,135 @@ func BroadcastTxWithFileLock(fromAddress sdk.AccAddress, msgs ...sdk.Msg) (out *
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func broadcastTxWithTrustWalletSignature(clientCtx client.Context, txf tx.Factory, msgs ...sdk.Msg) (out *bytes.Buffer, err error) {
|
||||
txBuilder, err := txf.BuildUnsignedTx(msgs...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = signWithTrustWallet(txf, clientCtx, txBuilder); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
txBytes, err := clientCtx.TxConfig.TxEncoder()(txBuilder.GetTx())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res, err := clientCtx.BroadcastTx(txBytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = clientCtx.PrintProto(res); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return writeClientCtxOutputToBuffer(clientCtx)
|
||||
}
|
||||
|
||||
func writeClientCtxOutputToBuffer(clientCtx client.Context) (out *bytes.Buffer, err error) {
|
||||
output, ok := clientCtx.Output.(*bytes.Buffer)
|
||||
if !ok {
|
||||
err = ErrTypeAssertionFailed
|
||||
return
|
||||
}
|
||||
defer output.Reset()
|
||||
|
||||
result := make(map[string]interface{})
|
||||
err = json.Unmarshal(output.Bytes(), &result)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Make a copy because we `defer output.Reset()`
|
||||
out = &bytes.Buffer{}
|
||||
// This is still copying references: *out = *output
|
||||
// Make a real copy: https://stackoverflow.com/a/69758157
|
||||
out.Write(output.Bytes())
|
||||
return
|
||||
}
|
||||
|
||||
func signWithTrustWallet(txf tx.Factory, clientCtx client.Context, txBuilder client.TxBuilder) error {
|
||||
connector, err := trustwallet.NewTrustWalletConnector(GetConfig().serialPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keys, err := connector.GetPlanetmintKeys()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pubkeyBytes, err := hex.DecodeString(keys.RawPlanetmintPubkey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pk := secp256k1.PubKey{Key: pubkeyBytes}
|
||||
|
||||
signMode := txf.SignMode()
|
||||
if signMode == signing.SignMode_SIGN_MODE_UNSPECIFIED {
|
||||
// use the SignModeHandler's default mode if unspecified
|
||||
signMode = clientCtx.TxConfig.SignModeHandler().DefaultMode()
|
||||
}
|
||||
|
||||
signerData := authsigning.SignerData{
|
||||
ChainID: txf.ChainID(),
|
||||
AccountNumber: txf.AccountNumber(),
|
||||
Sequence: txf.Sequence(),
|
||||
PubKey: &pk,
|
||||
Address: sdk.AccAddress(pk.Address()).String(),
|
||||
}
|
||||
|
||||
sigData := signing.SingleSignatureData{
|
||||
SignMode: signMode,
|
||||
Signature: nil,
|
||||
}
|
||||
|
||||
sig := signing.SignatureV2{
|
||||
PubKey: &pk,
|
||||
Data: &sigData,
|
||||
Sequence: txf.Sequence(),
|
||||
}
|
||||
|
||||
if err := txBuilder.SetSignatures(sig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bytesToSign, err := clientCtx.TxConfig.SignModeHandler().GetSignBytes(signMode, signerData, txBuilder.GetTx())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hashBytesToSign := crypto.Sha256(bytesToSign)
|
||||
hexHash := hex.EncodeToString(hashBytesToSign)
|
||||
|
||||
hexSig, err := connector.SignHashWithPlanetmint(hexHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signature, err := hex.DecodeString(hexSig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sigData = signing.SingleSignatureData{
|
||||
SignMode: signMode,
|
||||
Signature: signature,
|
||||
}
|
||||
sig = signing.SignatureV2{
|
||||
PubKey: &pk,
|
||||
Data: &sigData,
|
||||
Sequence: txf.Sequence(),
|
||||
}
|
||||
|
||||
if err = txBuilder.SetSignatures(sig); err != nil {
|
||||
return fmt.Errorf("unable to set signatures on payload: %w", err)
|
||||
}
|
||||
|
||||
// Run optional preprocessing if specified. By default, this is unset
|
||||
// and will return nil.
|
||||
return txf.PreprocessTx(clientCtx.FromName, txBuilder)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user