Background
A few years ago, I attempted to write wallet library, that was my first, learning Bitcoin project, called HiddenBitcoin. While I was not able to give out a stable release, I reused and polished a lot of the code I wrote there over the years. HBitcoin can be thought of as its successor, but while my motivation for HiddenBitcoin
was to learn, my motivation for HBitcoin
came from a need. I kept reusing my code within different Bitcoin
projects. It got to the point that the main class I'll present to you here at some point had 6 different versions. Therefore, it was time to get things right once and for all and package them into a Bitcoin
library I can use any time through a simple NuGet package.
Introduction
The HBitcoin library is written on top of NBitcoin. Imagine HBitcoin
as somewhere in between NBitcoin
and Blockchain.info
's API. It gives you more flexibility, than the latter, but less than the former. To gain a real deep knowledge on Bitcoin
, you will want to check out the The C# Bitcoin Book.
By the end of this three part tutorial, you will be able to build a Bitcoin
wallet.
Quote:
While programming to an API can assist in getting an application up quickly, the developer is limited to innovations that can take place against the API.
Nicolas Dorier, Bitcoin
Core developer and creator of NBitcoin
, the C# Bitcoin
library.
Keep this quote in mind while using my library.
What Does A Bitcoin Wallet Do?
A Bitcoin
wallet have three key functions:
- Securely stores keys and manages the access to them
- Monitors the transactions regarding these keys
- Builds transactions and broadcasts them to the network
How to Store the Keys?
Bitcoin Addresses, Private Keys
You are probably familiar with Bitcoin
addresses, where you can send bitcoin
s and know that with the corresponding private keys, you can spend your funds.
Change Addresses
You might be also familiar with Bitcoin
wallets and know that they are generating different change addresses for different transactions. You might ask why they do not use only one Bitcoin
address? For privacy reasons. The Bitcoin
blockchain is a public ledger, so anyone can easily see what comes in and out of an address. Therefore, it is a better idea to avoid address reuse, although that does not completely solve the privacy problem, it definitely improves it.
HD Wallets
Now we have a different problem, how can we manage so many keys? Store, monitor and spend them?
Note that the introduction of multiple keys has greatly elevated the complexity of our wallet.
Luckily, the HD wallet structure enables us to store only one key and derive the others from it. To keep ourselves consistent to NBitcoin
's terminology, we'll call it `BitcoinExtKey
`.
Bitcoin Improvement Proposals Regarding Key Management
There are four BIPs our concern here. BIP32, BIP38, BIP43, BIP44. For simplicity, I'd like you to think of BIP32 and BIP38 as the same BIPs. They are defining low level stuff, like how to derive and encrypt keys. These are implemented by NBitcoin
. BIP43 and BIP44 are built upon BIP32-38 and define more of a structure on how the keys should be organized and used. BIP43-44 is implemented by a few wallets. I started to implement them myself, but shortly I decided against them, because they would not only greatly overcomplicate the usage of my objects, but also I was not able to incorporate in them some of the edge cases I am planning to work on in the future. This way, I can present a simpler interface.
Talk is Cheap, Guide Me Through the Code!
Project Setup
- Start a new .NET Core project
- Add the
HBitcoin
NuGet package
var network = Network.Main;
var password = "password";
Mnemonic mnemonic;
Safe safe = Safe.Create(out mnemonic, password, @"Wallets\Wallet.json", network);
Console.WriteLine(mnemonic);
Safe recoveredSafe = Safe.Recover(mnemonic, password, @"Wallets\SameWallet.json", network);
Safe loadedSafe = Safe.Load(password, @"Wallets\hiddenWallet.hid");
if (network != loadedSafe.Network)
throw new Exception("Wrong network");
for (var i = 0; i < 10; i++)
{
Console.WriteLine(safe.GetAddress(i));
}
How Can I Hack the Wallet?
You just have to get the (password
AND the mnemonic
) OR (the password
AND the wallet file.)
Then, you can just call Recover of Load.
Who knows the password
? The user.
Who knows the mnemonic
? Ideally, it is stored in the user's home written in some paper as a backup.
Who knows the wallet file? Ideally, it is stored on the user's hard drive.
Other Wallets
Usually, it is enough to recover another wallet solely with the mnemonic. I don't find that to be a good approach, therefore this wallet doesn't work like that.
CreationTime
var safe2 = Safe.Recover(mnemonic, password, "Wallet.json", network,
creationTime: DateTimeOffset.ParseExact("2017-02-20",
"yyyy-MM-dd", CultureInfo.InvariantCulture));
Console.WriteLine(safe.CreationTime);
When you create a wallet, it automatically saves its creation time, too, which will come in very handy on writing some SPV wallet. Therefore, when you recover the wallet, you can also feed it with a creation time, if you don't, it'll default to the earliest creation time possible, which is hardcoded:
public static DateTimeOffset EarliestPossibleCreationTime
=> DateTimeOffset.ParseExact("2017-02-19",
"yyyy-MM-dd", CultureInfo.InvariantCulture);
This is the date when I first introduced the concept of creation time. Not if you'd try to recover it with an earlier creation time, it'll use the EarliestPossibleCreationTime
instead.
SafeAccounts and HdPathType
var alice = new SafeAccount(id: 2);
safe.GetAddress(index: 1, hdPathType: HdPathType.Receive, account: alice);
safe.GetPrivateKey(index: 1, hdPathType: HdPathType.Receive, account: alice);
You can create accounts if you want. In the above code, I created Alice's account with the id=2 and retrieved some keys of her.
You can also notice I specified the HdPathType
as receive
. This is the default HdPathType
, if you don't specify anything. Note some terminology uses the words "external
" and "internal
" for receive and change addresses. It becomes important later when you are receiving and spending your funds of your wallet. Always receive funds to HdPathType.Receive
and when you spend those funds, always receive the change back to HdPathType.Change
. This adds a little more privacy to your wallet. The alternative would be when you spend a receive address you get back the change to the same address. I certainly advise against that.
The End
I would advise against implementing your own key storage scheme, except if you really know what you are doing. If for some reason my Safe is not flexible enough for your needs, open an issue on GitHub and I'll see what I can do about it.
If you liked this tutorial and would like to see more, throw me a pizza: 1LYLuYMXkCXDxSfsNoDp8FCb2mA36r29u7.
License Agreement
If you did not like my tutorial, then by reading this line, you accept to perform a Cersei Lannister walk of shame.
Moreover after your death, I will be entitled to take away your soul.
Updates
- 2017.02.21
- Adjust the
HiddenBitcoin
tutorial to its successor: HBitcoin