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!