Skip to content

Commit

Permalink
Kaspawallet daemon: Add Send and Sign commands (#2016)
Browse files Browse the repository at this point in the history
* Add send and sign commands to protobuf

* Added Send and Sign stubs in kaspawalletd server

* Implement Sign

* Implemented Send

* Allow Broadcast command to supply multiple transactions

* No longer prompt for password in DecryptMnemonics

* Rename TransactionIDs -> TxIDs to keep consistency with Broadcast

* Add some comments and formatting

Co-authored-by: Ori Newman <orinewman1@gmail.com>
  • Loading branch information
svarogg and someone235 committed Apr 11, 2022
1 parent 357e8ce commit ada559f
Show file tree
Hide file tree
Showing 14 changed files with 590 additions and 119 deletions.
20 changes: 8 additions & 12 deletions cmd/kaspawallet/broadcast.go
Expand Up @@ -42,18 +42,14 @@ func broadcast(conf *broadcastConfig) error {
return err
}

transactionsCount := len(transactions)
for i, transaction := range transactions {
response, err := daemonClient.Broadcast(ctx, &pb.BroadcastRequest{Transaction: transaction})
if err != nil {
return err
}
if transactionsCount == 1 {
fmt.Println("Transaction was sent successfully")
} else {
fmt.Printf("Transaction %d (out of %d) was sent successfully\n", i+1, transactionsCount)
}
fmt.Printf("Transaction ID: \t%s\n", response.TxID)
response, err := daemonClient.Broadcast(ctx, &pb.BroadcastRequest{Transactions: transactions})
if err != nil {
return err
}
fmt.Println("Transactions were sent successfully")
fmt.Println("Transaction ID(s): ")
for _, txID := range response.TxIDs {
fmt.Printf("\\t%s\\n", txID)
}

return nil
Expand Down
421 changes: 358 additions & 63 deletions cmd/kaspawallet/daemon/pb/kaspawalletd.pb.go

Large diffs are not rendered by default.

29 changes: 27 additions & 2 deletions cmd/kaspawallet/daemon/pb/kaspawalletd.proto
Expand Up @@ -10,6 +10,10 @@ service kaspawalletd {
rpc NewAddress (NewAddressRequest) returns (NewAddressResponse) {}
rpc Shutdown (ShutdownRequest) returns (ShutdownResponse) {}
rpc Broadcast (BroadcastRequest) returns (BroadcastResponse) {}
// Since SendRequest contains a password - this command should only be used on a trusted or secure connection
rpc Send(SendRequest) returns (SendResponse) {}
// Since SignRequest contains a password - this command should only be used on a trusted or secure connection
rpc Sign(SignRequest) returns (SignResponse) {}
}

message GetBalanceRequest {
Expand Down Expand Up @@ -51,15 +55,36 @@ message NewAddressResponse {
}

message BroadcastRequest {
bytes transaction = 1;
repeated bytes transactions = 1;
}

message BroadcastResponse {
string txID = 1;
repeated string txIDs = 1;
}

message ShutdownRequest {
}

message ShutdownResponse {
}

// Since SendRequest contains a password - this command should only be used on a trusted or secure connection
message SendRequest{
string toAddress = 1;
uint64 amount = 2;
string password = 3;
}

message SendResponse{
repeated string txIDs = 1;
}

// Since SignRequest contains a password - this command should only be used on a trusted or secure connection
message SignRequest{
repeated bytes unsignedTransactions = 1;
string password = 2;
}

message SignResponse{
repeated bytes signedTransactions = 1;
}
72 changes: 72 additions & 0 deletions cmd/kaspawallet/daemon/pb/kaspawalletd_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 18 additions & 5 deletions cmd/kaspawallet/daemon/server/broadcast.go
Expand Up @@ -12,17 +12,30 @@ import (
)

func (s *server) Broadcast(_ context.Context, request *pb.BroadcastRequest) (*pb.BroadcastResponse, error) {
tx, err := libkaspawallet.ExtractTransaction(request.Transaction, s.keysFile.ECDSA)
txIDs, err := s.broadcast(request.Transactions)
if err != nil {
return nil, err
}

txID, err := sendTransaction(s.rpcClient, tx)
if err != nil {
return nil, err
return &pb.BroadcastResponse{TxIDs: txIDs}, nil
}

func (s *server) broadcast(transactions [][]byte) ([]string, error) {
txIDs := make([]string, len(transactions))

for i, transaction := range transactions {
tx, err := libkaspawallet.ExtractTransaction(transaction, s.keysFile.ECDSA)
if err != nil {
return nil, err
}

txIDs[i], err = sendTransaction(s.rpcClient, tx)
if err != nil {
return nil, err
}
}

return &pb.BroadcastResponse{TxID: txID}, nil
return txIDs, nil
}

func sendTransaction(client *rpcclient.RPCClient, tx *externalapi.DomainTransaction) (string, error) {
Expand Down
18 changes: 13 additions & 5 deletions cmd/kaspawallet/daemon/server/create_unsigned_transaction.go
Expand Up @@ -18,6 +18,15 @@ func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.Creat
s.lock.Lock()
defer s.lock.Unlock()

unsignedTransactions, err := s.createUnsignedTransactions(request.Address, request.Amount)
if err != nil {
return nil, err
}

return &pb.CreateUnsignedTransactionsResponse{UnsignedTransactions: unsignedTransactions}, nil
}

func (s *server) createUnsignedTransactions(address string, amount uint64) ([][]byte, error) {
if !s.isSynced() {
return nil, errors.New("server is not synced")
}
Expand All @@ -27,12 +36,12 @@ func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.Creat
return nil, err
}

toAddress, err := util.DecodeAddress(request.Address, s.params.Prefix)
toAddress, err := util.DecodeAddress(address, s.params.Prefix)
if err != nil {
return nil, err
}

selectedUTXOs, changeSompi, err := s.selectUTXOs(request.Amount, feePerInput)
selectedUTXOs, changeSompi, err := s.selectUTXOs(amount, feePerInput)
if err != nil {
return nil, err
}
Expand All @@ -46,7 +55,7 @@ func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.Creat
s.keysFile.MinimumSignatures,
[]*libkaspawallet.Payment{{
Address: toAddress,
Amount: request.Amount,
Amount: amount,
}, {
Address: changeAddress,
Amount: changeSompi,
Expand All @@ -59,8 +68,7 @@ func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.Creat
if err != nil {
return nil, err
}

return &pb.CreateUnsignedTransactionsResponse{UnsignedTransactions: unsignedTransactions}, nil
return unsignedTransactions, nil
}

func (s *server) selectUTXOs(spendAmount uint64, feePerInput uint64) (
Expand Down
25 changes: 25 additions & 0 deletions cmd/kaspawallet/daemon/server/send.go
@@ -0,0 +1,25 @@
package server

import (
"context"

"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
)

func (s *server) Send(_ context.Context, request *pb.SendRequest) (*pb.SendResponse, error) {
unsignedTransactions, err := s.createUnsignedTransactions(request.ToAddress, request.Amount)
if err != nil {
return nil, err
}
signedTransactions, err := s.signTransactions(unsignedTransactions, request.Password)
if err != nil {
return nil, err
}

txIDs, err := s.broadcast(signedTransactions)
if err != nil {
return nil, err
}

return &pb.SendResponse{TxIDs: txIDs}, nil
}
36 changes: 36 additions & 0 deletions cmd/kaspawallet/daemon/server/sign.go
@@ -0,0 +1,36 @@
package server

import (
"context"

"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"

"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
)

func (s *server) Sign(_ context.Context, request *pb.SignRequest) (*pb.SignResponse, error) {
s.lock.Lock()
defer s.lock.Unlock()

signedTransactions, err := s.signTransactions(request.UnsignedTransactions, request.Password)
if err != nil {
return nil, err
}
return &pb.SignResponse{SignedTransactions: signedTransactions}, nil
}

func (s *server) signTransactions(unsignedTransactions [][]byte, password string) ([][]byte, error) {
mnemonics, err := s.keysFile.DecryptMnemonics(password)
if err != nil {
return nil, err
}
signedTransactions := make([][]byte, len(unsignedTransactions))
for i, unsignedTransaction := range unsignedTransactions {
signedTransaction, err := libkaspawallet.Sign(s.params, mnemonics, unsignedTransaction, s.keysFile.ECDSA)
if err != nil {
return nil, err
}
signedTransactions[i] = signedTransaction
}
return signedTransactions, nil
}
6 changes: 5 additions & 1 deletion cmd/kaspawallet/dump_unencrypted_data.go
Expand Up @@ -3,10 +3,11 @@ package main
import (
"bufio"
"fmt"
"os"

"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
"github.com/kaspanet/kaspad/cmd/kaspawallet/utils"
"os"

"github.com/pkg/errors"
)
Expand All @@ -24,6 +25,9 @@ func dumpUnencryptedData(conf *dumpUnencryptedDataConfig) error {
return err
}

if len(conf.Password) == 0 {
conf.Password = keys.GetPassword("Password:")
}
mnemonics, err := keysFile.DecryptMnemonics(conf.Password)
if err != nil {
return err
Expand Down
7 changes: 4 additions & 3 deletions cmd/kaspawallet/keys/create.go
Expand Up @@ -5,12 +5,13 @@ import (
"crypto/rand"
"crypto/subtle"
"fmt"
"os"

"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
"github.com/kaspanet/kaspad/cmd/kaspawallet/utils"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/pkg/errors"
"github.com/tyler-smith/go-bip39"
"os"
)

// CreateMnemonics generates `numKeys` number of mnemonics.
Expand Down Expand Up @@ -52,8 +53,8 @@ func encryptedMnemonicExtendedPublicKeyPairs(params *dagconfig.Params, mnemonics
password := []byte(cmdLinePassword)
if len(password) == 0 {

password = getPassword("Enter password for the key file:")
confirmPassword := getPassword("Confirm password:")
password = []byte(GetPassword("Enter password for the key file:"))
confirmPassword := []byte(GetPassword("Confirm password:"))

if subtle.ConstantTimeCompare(password, confirmPassword) != 1 {
return nil, nil, errors.New("Passwords are not identical")
Expand Down

0 comments on commit ada559f

Please sign in to comment.