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:
Lorenz Herzberger 2024-09-19 16:20:24 +02:00 committed by GitHub
parent a7bb114301
commit e404fef08b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 749 additions and 20 deletions

2
go.mod
View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View 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
View File

@ -0,0 +1,8 @@
package trustwallet
type PlanetMintKeys struct {
PlanetmintAddress string
ExtendedLiquidPubkey string
ExtendedPlanetmintPubkey string
RawPlanetmintPubkey string
}

165
lib/tx.go
View File

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