How To Create A Bitcoin HD Wallet with Golang and gRPC (Part l) | Hacker Noon

Author profile picture

In this tutorial series, we are going to learn how to create an HD bitcoin wallet using Golang.

The idea of the tutorial series is not only showing how to create the Wallet and getting balances but also how to implement a grpc server and a CLI tool and a web-app for consuming it.

In this part we are going to see how to implement our grpc server.

Requirements:

go version go1.13.8 darwin/amd64

Also, I will assume you already have some knowledge about Golang and understand the main concepts of bitcoin, so, I will no go deep on how to install Golang or what bitcoin is.

So, let’s get started by defining what our program will do for us. To keep this tutorial small, I will split it into two parts, in this one where will create our service and a CLI tool that will allow us to create bitcoin key pairs (private and public keys) and retrieving the balance of our Wallet.

Please, notice that this tutorial is only for educational proposes, and this software is not ready for production or anything.

I have decided to use Blockcypher for the blockchain requests, and a go package I found for generating addresses, Blockcypher allows you to create wallets, but I thought that would be good to use the API only for the Blockchain requests.

As well you can find the entire code for this tutorial here.

You will need to install grpc, to do so you can follow the official instructions: (https://grpc.io/docs/languages/go/quickstart/) or do the following:

Open your terminal and install the protocol compiler plugin for Go (protoc-gen-go) using the following commands:

$ export GO111MODULE=on # Enable module mode 
$ go get github.com/golang/protobuf/protoc-gen-go

Once you have installed the protoc-gen-go, let’s create our proto service:

1. Create a folder called proto in the root of your project.

2. Create a folder called btchdwallet inside of your proto folder.

3. Create a file called wallet.proto inside your btchdwallet folder with the following content:

// proto-service
syntax = "proto3";
package go.microservice.btchdwallet;

option go_package = "github.com/LuisAcerv/btchdwallet";

service Wallet {
    rpc CreateWallet(Request)      returns (Response) {}
    rpc CreateChildWallet(Request) returns (Response) {}
    rpc GetWallet(Request)         returns (Response) {}
    rpc GetBalance(Request)        returns (Response) {}
}

message Request {
    string Address   = 1;
    string Mnemonic  = 2;
}


message Response {
    string Address            = 1;
    string PubKey             = 2;
    string PrivKey            = 3;
    string Mnemonic           = 4;
    int64  Balance            = 5;
    int64  TotalReceived      = 6;
    int64  TotalSent          = 7;
    int64  UnconfirmedBalance = 8;
}

4. Now create a new file called Makefile with the following content:


build_protoc:
        protoc --go_out=plugins=grpc:. --go_opt=paths=source_relative proto/btchdwallet/wallet.proto

5. From your terminal run the following command:

$ make build_protoc

This command will create a new file at

./proto/btchdwallet/wallet.pb.go 

. Now we can start to build our service.

Now we have our grpc service ready to have functionality added. Before we start creating the main file, we need to create some other scripts that will contain the program functionality itself.

6. We will create a little package called crypt that will be in charge of generating a random 8-byte hash that we are going to use to generate our mnemonics. To do that, create a new folder called crypt in your project root and file with the same name and go extension inside of it with the following content:

package crypt

import (
        "fmt"
        "crypto/rand"
        "encoding/hex"
)

// CreateHash returns random 8 byte array as a hex string
func CreateHash() string {
        key := make([]byte, 8)

        _, err := rand.Read(key)
        if err != nil {
                // handle error here
                fmt.Println(err)
        }

        str := hex.EncodeToString(key)

        return str
}

Let me make a parenthesis here. You can use the architecture that fits best for you, but take in count that this tutorial is a part of a series, and in future chapters, this architecture will have more sense than may have now. Said that, let’s continue…

7. Now that we have our little crypt helper, we will create another folder called wallet and file with the same name and go extension inside of it.

I have to make another parenthesis here. I am using this

package to generate the bitcoin key pairs. Remember that this tutorial is only to understand how we can implement a grpc server and client in the context of bitcoin. For production-level software, you may want to use another tool, such as the Blockcypher API or anything else. Anyway, when it comes to cryptocurrencies, you must be aware of the risk of managing assets on behalf of users.

First, we have the CreateWallet function that returns four strings, the bitcoin address, the public key, the private key, and the mnemonic phrase. Notice that we are not storing this data anywhere, so the user MUST write down and keep this information into a safe place since we cannot recover this data later.

package wallet

import (
        "fmt"

        pb "github.com/LuisAcerv/btchdwallet/proto/btchdwallet"

        "github.com/LuisAcerv/btchdwallet/config"
        "github.com/LuisAcerv/btchdwallet/crypt"
        "github.com/blockcypher/gobcy"
        "github.com/brianium/mnemonic"
        "github.com/wemeetagain/go-hdwallet"
)

// CreateWallet is in charge of creating a new root wallet
func CreateWallet() *pb.Response {
        // Generate a random 256 bit seed
        seed := crypt.CreateHash()
        mnemonic, _ := mnemonic.New([]byte(seed), mnemonic.English)

        // Create a master private key
        masterprv := hdwallet.MasterKey([]byte(mnemonic.Sentence()))

        // Convert a private key to public key
        masterpub := masterprv.Pub()

        // Get your address
        address := masterpub.Address()

        return &pb.Response{Address: address, PubKey: masterpub.String(), PrivKey: masterprv.String(), Mnemonic: mnemonic.Sentence()}
}

We are going to use a couple of packages here, make sure you install them in your project using:

$ go get <package url>

Now we need to add our function to retrieve our wallet.

// DecodeWallet is in charge of decoding wallet from mnemonic
func DecodeWallet(mnemonic string) *pb.Response {
        // Get private key from mnemonic
        masterprv := hdwallet.MasterKey([]byte(mnemonic))

        // Convert a private key to public key
        masterpub := masterprv.Pub()

        // Get your address
        address := masterpub.Address()

        return &pb.Response{Address: address, PubKey: masterpub.String(), PrivKey: masterprv.String()}
}

The decode wallet function takes one argument, the mnemonic. Using the mnemonic we can decode our private and public keys.

Finally we are going to add a function to get our address balance:

// GetBalance is in charge of returning the given address balance
func GetBalance(address string) *pb.Response {
        btc := gobcy.API{conf.Blockcypher.Token, "btc", "main"}
        addr, err := btc.GetAddrBal(address, nil)
        if err != nil {
                fmt.Println(err)
        }

        balance := addr.Balance
        totalReceived := addr.TotalReceived
        totalSent := addr.TotalSent
        unconfirmedBalance := addr.UnconfirmedBalance

        return &pb.Response{Address: address, Balance: int64(balance), TotalReceived: int64(totalReceived), TotalSent: int64(totalSent), UnconfirmedBalance: int64(unconfirmedBalance)}

}

Putting all together:

package wallet

import (
        "fmt"

        pb "github.com/LuisAcerv/btchdwallet/proto/btchdwallet"

        "github.com/LuisAcerv/btchdwallet/config"
        "github.com/LuisAcerv/btchdwallet/crypt"
        "github.com/blockcypher/gobcy"
        "github.com/brianium/mnemonic"
        "github.com/wemeetagain/go-hdwallet"
)

var conf = config.ParseConfig()

// CreateWallet is in charge of creating a new root wallet
func CreateWallet() *pb.Response {
        // Generate a random 256 bit seed
        seed := crypt.CreateHash()
        mnemonic, _ := mnemonic.New([]byte(seed), mnemonic.English)

        // Create a master private key
        masterprv := hdwallet.MasterKey([]byte(mnemonic.Sentence()))

        // Convert a private key to public key
        masterpub := masterprv.Pub()

        // Get your address
        address := masterpub.Address()

        return &pb.Response{Address: address, PubKey: masterpub.String(), PrivKey: masterprv.String(), Mnemonic: mnemonic.Sentence()}
}

// DecodeWallet is in charge of decoding wallet from mnemonic
func DecodeWallet(mnemonic string) *pb.Response {
        // Get private key from mnemonic
        masterprv := hdwallet.MasterKey([]byte(mnemonic))

        // Convert a private key to public key
        masterpub := masterprv.Pub()

        // Get your address
        address := masterpub.Address()

        return &pb.Response{Address: address, PubKey: masterpub.String(), PrivKey: masterprv.String()}
}

// GetBalance is in charge of returning the given address balance
func GetBalance(address string) *pb.Response {
        btc := gobcy.API{conf.Blockcypher.Token, "btc", "main"}
        addr, err := btc.GetAddrBal(address, nil)
        if err != nil {
                fmt.Println(err)
        }

        balance := addr.Balance
        totalReceived := addr.TotalReceived
        totalSent := addr.TotalSent
        unconfirmedBalance := addr.UnconfirmedBalance

        return &pb.Response{Address: address, Balance: int64(balance), TotalReceived: int64(totalReceived), TotalSent: int64(totalSent), UnconfirmedBalance: int64(unconfirmedBalance)}

}

8. Once we have our wallet script is time to create our main script to implement our grpc server.

package main

import (
        "context"
        "fmt"
        "log"
        "net"

        pb "github.com/LuisAcerv/btchdwallet/proto/btchdwallet"
        "github.com/LuisAcerv/btchdwallet/wallet"

        "google.golang.org/grpc"
        "google.golang.org/grpc/reflection"
)

const (
        port = ":50055"
)

type server struct {
        pb.UnimplementedWalletServer
}

func (s *server) CreateWallet(ctx context.Context, in *pb.Request) (*pb.Response, error) {
        fmt.Println()
        fmt.Println("nCreating new wallet")

        wallet := wallet.CreateWallet()

        return wallet, nil
}

func (s *server) GetWallet(ctx context.Context, in *pb.Request) (*pb.Response, error) {
        fmt.Println()
        fmt.Println("nGetting wallet data")

        wallet := wallet.DecodeWallet(in.Mnemonic)

        return wallet, nil
}

func (s *server) GetBalance(ctx context.Context, in *pb.Request) (*pb.Response, error) {
        fmt.Println()
        fmt.Println("nGetting Balance data")

        balance := wallet.GetBalance(in.Address)

        return balance, nil
}

func main() {
        lis, err := net.Listen("tcp", port)
        if err != nil {
                log.Fatalf("failed to listen: %v", err)
        }
        s := grpc.NewServer()
        pb.RegisterWalletServer(s, &server{})
        reflection.Register(s)
        fmt.Printf("Service running at port: %s", port)
        fmt.Println()

        if err := s.Serve(lis); err != nil {
                log.Fatalf("failed to serve: %v", err)
        }
}

This script has three functions, the same we have written in our

wallet.proto

script. We also are calling our wallet methods for these functions.

If you run this script with go run main.go and everything goes well, you should see the following in your terminal:

Service running at port: :50055

In the next chapter we will see step by step how to implement a client for our grpc server.

You can download the whole code here, this already includes a small CLI . To use it just run it as follows:

Run the grpc server

 $ go run main.go

In another terminal run:

 $ go run client/client.go -m=create-wallet

You should see this:

Output:
    New Wallet >>

    > Public Key: xpub661MyMwAqRbcG3fYrFtkZGesCkhTZWAwHDM2Q1DbeMH6CcQSkrL5qzYwnRkzwKKhrsjbngkC8EcNTBvQmBAJhMUVAXmU4qv8jzVFkhrqme1

    > Private Key: xprv9s21ZrQH143K3Zb5kEMkC8i8eiryA3T5uzRRbcoz61k7Kp5JDK1qJCETw9vxGBCe88qu57EKUu2hX54zeivPiZhCNQ5dV6CfKdhsCwMqm5j

    > Mnemonic: coral light army glare basket boil school egg couple payment flee goose

To get your wallet

 $ go run client/client.go -m=get-wallet -mne="coral light army glare basket boil school egg couple payment flee goose"

To get your balance

$ go run client/client.go -m=get-balance -addr=1Go23sv8vR81YuV1hHGsUrdyjLcGVUpCDy

In the following chapter we are going step by step on how to implement this CLI .

Happy hacking!

If you have any comments throw me a tweet @luis_acervantes!

Tags

The Noonification banner

Subscribe to get your daily round-up of top tech stories!

read original article here