The goal of the wallet is to create a more abstract interface for the end user.
The end user must be able to
- Create a new wallet (=private key in this case)
- View the balance of his wallet
- Send coins to other addresses
All of the above must work so that the end user must not need to understand how txIns or txOuts work. Just like in e.g. Bitcoin: you send coins to addresses and publish your own address where other people can send coins.
The full code that will be implemented in this chapter can be found here.
Generating and storing the private key
In this tutorial we will use the simplest possible way to handle the wallet generation and storing: We will generate an unencrypted private key to the file
And as said, the public key (=address) can be calculated from the private key.
It should be noted that storing the private key in an unencrypted format is very unsafe. We do this only for the purpose to keep things simple for now. Also, the wallet supports only a single private key, so you need to generate a new wallet to get a new public address.
A reminder from the previous chapter: when you own some coins in the blockchain, what you actually have is a list of unspent transaction outputs whose public key matches to the private key you own.
This means that calculating the balance for a given address is quite simple: you just sum all the unspent transaction “owned” by that address:
As demonstrated in the code, the private key is not needed to query the balance of the address. This consequently means that anyone can solve the balance of a given address.
When sending coins, the user should be able to ignore the concepts of transaction inputs and outputs. But what should happen if the user A has balance of 50 coins, that is in a single transaction output and the user wants to send 10 coins to user B?
In this case, the solution is to send 10 bitcoins to the address of user B and 40 coins back to user A. The full transaction output must always be spent, so the “splitting” part must be done when assigning the coins to new outputs. This simple case is demonstrated in the following out picture (txIns are not shown):
Let’s play out a bit more complex transaction scenario:
- User C has initially 0 coins
- User C receives 3 transactions worth of 10, 20 and 30 coins
- User C wants to send 55 coins to user D. What will the transaction look like?
In this case, all of the three outputs must be used and the outputs must have values of 55 coins to user D and 5 coins back to user C.
Let’s manifest the described logic to code. First we will create the transaction inputs. To do this, we will loop through our unspent transaction outputs until the sum of these outputs is greater or equal than the amount we want to send.
As shown, we will also calculate the
leftOverAmount which is the value we will send back to our address.
As we have the list of unspent transaction outputs, we can create the txIns of the transaction:
Next the two txOuts of the transaction are created: One txOut for the receiver of the coins and one txOut for the leftOverAmount`. If the txIns happen to have the exact amount of the desired value (leftOverAmount is 0) we do not create the “leftOver” transaction.
Finally we calculate the transaction id and sign the txIns:
Using the wallet
Let’s also add a meaningful controlling endpoint to for the wallet functionality.
As it is shown, the end user must only provide the address and the amount of coins for the node. The node will calculate the rest.
We just implemented a naive unencrypted wallet with simple transaction generation. Although this transaction generation algorithm never creates transactions with more than 2 outputs, it should be noted that the blockchain itself supports any number of outputs. You could create valid a transaction with input of 50 coins and output of 5,15 and 30 coins, but those must be created manually using the
Also, the only way to include a desired transaction in the blockchain is to mine it yourself. The nodes do not exchange information about transactions that are not yet included in the blockchain. This will be addressed in the next chapter.
The code implemented in this chapter can be found here.