mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-13 11:35:40 +01:00
Add sections on generating addresses and expand HD section in website
This commit is contained in:
parent
796492a678
commit
aaccfbd935
6 changed files with 232 additions and 62 deletions
62
docs/core/addresses.md
Normal file
62
docs/core/addresses.md
Normal file
|
@ -0,0 +1,62 @@
|
|||
---
|
||||
id: addresses
|
||||
title: Generating addresses
|
||||
---
|
||||
|
||||
Almost all Bitcoin applications need to generate addresses
|
||||
for their users somehow. There's a lot going on in getting
|
||||
a correct bitcoin address, but our APIs make it possible to
|
||||
to get started with all types of addresses in a matter of
|
||||
minutes.
|
||||
|
||||
## Generating SegWit (bech32) addresses
|
||||
|
||||
Generating native SegWit addresses in the bech32 format
|
||||
is something that all Bitcoin applications should enable,
|
||||
as it makes the transaction fees less expensive, and also
|
||||
makes the addresses more readable by humans. However, it
|
||||
has seen slower than necessary adoption. With Bitcoin-S
|
||||
you can generate bech32 addresses in four(!) lines of code
|
||||
(not counting comments and imports), so now there's no
|
||||
reason to keep using legacy transaction formats.
|
||||
|
||||
```scala mdoc:to-string
|
||||
import org.bitcoins.core.{crypto, protocol, config}
|
||||
// if you want to get addresses for mainnet, just import
|
||||
// config.MainNet here instead
|
||||
import config.TestNet3
|
||||
import crypto.ECPrivateKey
|
||||
|
||||
// this gets all addresses into scope
|
||||
import protocol._
|
||||
|
||||
// this gets all scriptPubKeys into scope
|
||||
import protocol.script._
|
||||
|
||||
// this generates a random private key
|
||||
val privkey = ECPrivateKey()
|
||||
val pubkey = privkey.publicKey
|
||||
|
||||
val segwitAddress = {
|
||||
// see https://bitcoin.org/en/glossary/pubkey-script
|
||||
// for reading resources on the details of scriptPubKeys
|
||||
// pay-to-witness-pubkey-hash scriptPubKey V0
|
||||
val scriptPubKey = P2WPKHWitnessSPKV0(pubkey)
|
||||
Bech32Address(scriptPubKey, TestNet3)
|
||||
}
|
||||
```
|
||||
|
||||
## Generating legacy (base58) addresses
|
||||
|
||||
If you need to generate legacy addresses for backwards
|
||||
compatability reasons, that's also a walk in the park.
|
||||
Take a look:
|
||||
|
||||
```scala mdoc:to-string
|
||||
// pay-to-pubkey-hash address
|
||||
import org.bitcoins.core.protocol.P2PKHAddress
|
||||
|
||||
// we're reusing the same private/public key pair
|
||||
// from before. don't do this in an actual application!
|
||||
val legacyAddress = P2PKHAddress(pubkey, TestNet3)
|
||||
```
|
|
@ -33,74 +33,21 @@ Most data structures have companion objects that extends `Factory` to be able to
|
|||
|
||||
Here is an example scala console session with bitcoins-core
|
||||
|
||||
```scala mdoc
|
||||
```scala mdoc:to-string
|
||||
import org.bitcoins.core.protocol.transaction._
|
||||
|
||||
val hexTx = "0100000001ccf318f0cbac588a680bbad075aebdda1f211c94ba28125b0f627f9248310db3000000006b4830450221008337ce3ce0c6ac0ab72509f8$9c1d52701817a2362d6357457b63e3bdedc0c0602202908963b9cf1a095ab3b34b95ce2bc0d67fb0f19be1cc5f7b3de0b3a325629bf01210241d746ca08da0a668735c3e01c1$a02045f2f399c5937079b6434b5a31dfe353ffffffff0210335d05000000001976a914b1d7591b69e9def0feb13254bace942923c7922d88ac48030000000000001976a9145e$90c865c2f6f7a9710a474154ab1423abb5b9288ac00000000"
|
||||
val hexTx = "0100000002d8c8df6a6fdd2addaf589a83d860f18b44872d13ee6ec3526b2b470d42a96d4d000000008b483045022100b31557e47191936cb14e013fb421b1860b5e4fd5d2bc5ec1938f4ffb1651dc8902202661c2920771fd29dd91cd4100cefb971269836da4914d970d333861819265ba014104c54f8ea9507f31a05ae325616e3024bd9878cb0a5dff780444002d731577be4e2e69c663ff2da922902a4454841aa1754c1b6292ad7d317150308d8cce0ad7abffffffff2ab3fa4f68a512266134085d3260b94d3b6cfd351450cff021c045a69ba120b2000000008b4830450220230110bc99ef311f1f8bda9d0d968bfe5dfa4af171adbef9ef71678d658823bf022100f956d4fcfa0995a578d84e7e913f9bb1cf5b5be1440bcede07bce9cd5b38115d014104c6ec27cffce0823c3fecb162dbd576c88dd7cda0b7b32b0961188a392b488c94ca174d833ee6a9b71c0996620ae71e799fc7c77901db147fa7d97732e49c8226ffffffff02c0175302000000001976a914a3d89c53bb956f08917b44d113c6b2bcbe0c29b788acc01c3d09000000001976a91408338e1d5e26db3fce21b011795b1c3c8a5a5d0788ac00000000"
|
||||
|
||||
// val tx = Transaction.fromHex(hexTx)
|
||||
val tx = Transaction.fromHex(hexTx)
|
||||
|
||||
// val hexAgain = tx.hex
|
||||
tx.hex == hexTx
|
||||
```
|
||||
|
||||
This gives us an example of a hex encoded Bitcoin transaction that is deserialized to a native Scala object called a [`Transaction`](/api/org/bitcoins/core/protocol/transaction/Transaction). You could also serialize the transaction to bytes using `tx.bytes` instead of `tx.hex`. These methods are available on every data structure that extends NetworkElement, like [`ECPrivateKey`](/api/org/bitcoins/core/crypto/ECPrivateKey), [`ScriptPubKey`](/api/org/bitcoins/core/protocol/script/ScriptPubKey), [`ScriptWitness`](/api/org/bitcoins/core/protocol/script/ScriptWitness), and [`Block`](/api/org/bitcoins/core/protocol/blockchain/Block).
|
||||
|
||||
#### Generating a BIP39 mnemonic phrase and an `xpriv`
|
||||
|
||||
BIP39 mnemonic phrases are the most common way of creating backups of wallets.
|
||||
They are between 12 and 24 words the user writes down, and can later be used to restore
|
||||
their bitcoins. From the mnemonic phrase we generate a wallet seed, and that seed
|
||||
can be used to generate what's called an extended private key
|
||||
([`ExtPrivateKey`](/api/org/bitcoins/core/crypto/ExtPrivateKey) in Bitcoin-S).
|
||||
|
||||
Here's an example:
|
||||
|
||||
```scala mdoc:to-string
|
||||
import scodec.bits._
|
||||
import org.bitcoins.core.crypto._
|
||||
import org.bitcoins.core.hd._
|
||||
|
||||
// the length of the entropy bit vector determine
|
||||
// how long our phrase ends up being
|
||||
// 256 bits of entropy results in 24 words
|
||||
val entropy: BitVector = MnemonicCode.getEntropy256Bits
|
||||
|
||||
val mnemonicCode = MnemonicCode.fromEntropy(entropy)
|
||||
|
||||
mnemonicCode.words // the phrase the user should write down
|
||||
|
||||
// the password argument is an optional, extra security
|
||||
// measure. all MnemonicCode instances will give you a
|
||||
// valid BIP39 seed, but different passwords will give
|
||||
// you different seeds. So you could have as many wallets
|
||||
// from the same seed as you'd like, by simply giving them
|
||||
// different passwords.
|
||||
val bip39Seed = BIP39Seed.fromMnemonic(mnemonicCode,
|
||||
password = "secret password")
|
||||
|
||||
val xpriv = ExtPrivateKey.fromBIP39Seed(ExtKeyVersion.SegWitMainNetPriv,
|
||||
bip39Seed)
|
||||
val xpub = xpriv.extPublicKey
|
||||
|
||||
// you can now use the generated xpriv to derive further
|
||||
// private or public keys
|
||||
|
||||
// this can be done with BIP89 paths (called SegWitHDPath in bitcoin-s)
|
||||
val segwitPath = SegWitHDPath.fromString("m/84'/0'/0'/0/0")
|
||||
|
||||
// alternatively:
|
||||
val otherSegwitPath =
|
||||
SegWitHDPath(HDCoinType.Bitcoin,
|
||||
accountIndex = 0,
|
||||
HDChainType.External,
|
||||
addressIndex = 0)
|
||||
|
||||
segwitPath == otherSegwitPath
|
||||
|
||||
// there's also paths available for legacy
|
||||
// addresses (LegacyHDPath) as well as nested
|
||||
// segwit paths (NestedSegWitPath)
|
||||
```
|
||||
See our [HD example](hd-keys)
|
||||
|
||||
### Building a signed transaction
|
||||
|
||||
|
@ -125,6 +72,8 @@ This is the API we define to sign things with. It takes in an arbitrary byte vec
|
|||
From [`core/src/main/scala/org/bitcoins/core/crypto/Sign.scala`](/api/org/bitcoins/core/crypto/Sign):
|
||||
|
||||
```scala mdoc
|
||||
import scodec.bits._
|
||||
import org.bitcoins.core.crypto._
|
||||
import scala.concurrent._
|
||||
import scala.concurrent.duration._
|
||||
|
||||
|
@ -160,7 +109,7 @@ Transactions are run through the interpreter to check their validity. These are
|
|||
|
||||
Here is an example of a transaction spending a `scriptPubKey` which is correctly evaluated with our interpreter implementation:
|
||||
|
||||
```scala mdoc:silent
|
||||
```scala mdoc:silent:reset
|
||||
import org.bitcoins.core.protocol.script._
|
||||
import org.bitcoins.core.protocol.transaction._
|
||||
import org.bitcoins.core.script._
|
||||
|
|
129
docs/core/hd-keys.md
Normal file
129
docs/core/hd-keys.md
Normal file
|
@ -0,0 +1,129 @@
|
|||
---
|
||||
id: hd-keys
|
||||
title: HD key generation
|
||||
---
|
||||
|
||||
In modern Bitcoin wallets, users only need to write down
|
||||
a sequence of words, and that sequence is a complete backup
|
||||
of their wallet. This is thanks to what's called Hierarchical
|
||||
Deterministic key generation. In short, every wallet using HD
|
||||
key generation has a root seed for each wallet, and this
|
||||
seed can be used to generate an arbitrary amount of later
|
||||
private and public keys. This is done in a standardized manner,
|
||||
so different wallets can operate with the same standard.
|
||||
|
||||
> If you want to jump into the details of how this work,
|
||||
> you should check out
|
||||
> [BIP 32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki).
|
||||
|
||||
Bitcoin-S supports generating keys in this fashion. Here's a
|
||||
full example of how to obtain a wallet seed, and then
|
||||
use that to generate further private and public keys:
|
||||
|
||||
```scala mdoc:to-string
|
||||
import scodec.bits._
|
||||
import org.bitcoins.core.crypto._
|
||||
import org.bitcoins.core.hd._
|
||||
|
||||
// the length of the entropy bit vector determine
|
||||
// how long our phrase ends up being
|
||||
// 256 bits of entropy results in 24 words
|
||||
val entropy: BitVector = MnemonicCode.getEntropy256Bits
|
||||
|
||||
val mnemonicCode = MnemonicCode.fromEntropy(entropy)
|
||||
|
||||
mnemonicCode.words // the phrase the user should write down
|
||||
|
||||
// the password argument is an optional, extra security
|
||||
// measure. all MnemonicCode instances will give you a
|
||||
// valid BIP39 seed, but different passwords will give
|
||||
// you different seeds. So you could have as many wallets
|
||||
// from the same seed as you'd like, by simply giving them
|
||||
// different passwords.
|
||||
val bip39Seed = BIP39Seed.fromMnemonic(mnemonicCode,
|
||||
password = "secret password")
|
||||
|
||||
val xpriv = ExtPrivateKey.fromBIP39Seed(ExtKeyVersion.SegWitMainNetPriv,
|
||||
bip39Seed)
|
||||
val xpub = xpriv.extPublicKey
|
||||
|
||||
// you can now use the generated xpriv to derive further
|
||||
// private or public keys
|
||||
|
||||
// this can be done with BIP89 paths (called SegWitHDPath in bitcoin-s)
|
||||
val segwitPath = SegWitHDPath.fromString("m/84'/0'/0'/0/0")
|
||||
|
||||
// alternatively:
|
||||
val otherSegwitPath =
|
||||
SegWitHDPath(HDCoinType.Bitcoin,
|
||||
accountIndex = 0,
|
||||
HDChainType.External,
|
||||
addressIndex = 0)
|
||||
|
||||
segwitPath == otherSegwitPath
|
||||
|
||||
// there's also paths available for legacy
|
||||
// addresses (LegacyHDPath) as well as nested
|
||||
// segwit paths (NestedSegWitPath)
|
||||
```
|
||||
|
||||
## Generating new addresses without having access to the private key
|
||||
|
||||
One the coolest features of HD wallets is that it's possible
|
||||
to generate addresses offline, without having access to the
|
||||
private keys. This feature is commonly called watch-only
|
||||
wallets, where a wallet can import information about all
|
||||
your past and future transactions, without being able to
|
||||
spend or steal any of your money.
|
||||
|
||||
Let's see an example of this:
|
||||
|
||||
```scala mdoc:to-string
|
||||
import scala.util.Success
|
||||
import org.bitcoins.core.protocol.script._
|
||||
import org.bitcoins.core.protocol.Bech32Address
|
||||
import org.bitcoins.core.config.TestNet3
|
||||
|
||||
// first account -------┐
|
||||
// bitcoin ----------┐ |
|
||||
// segwit --------┐ | |
|
||||
val accountPath = BIP32Path.fromString("m/84'/0'/0'")
|
||||
val accountXpub = {
|
||||
// this key is sensitive, keep away from prying eyes!
|
||||
val accountXpriv = xpriv.deriveChildPrivKey(accountPath)
|
||||
|
||||
// this key is not sufficient to spend from, but we
|
||||
// can generate addresses with it!
|
||||
accountXpriv.extPublicKey
|
||||
}
|
||||
|
||||
// address no. 0 ---------------┐
|
||||
// external address ----------┐ |
|
||||
val firstAddressPath = SegWitHDPath.fromString("m/84'/0'/0'/0/0")
|
||||
val firstAccountAddress = {
|
||||
// this is a bit quirky, but we're not interesting in
|
||||
// deriving the complete path from our account xpub
|
||||
// instead, we're only interested in the part after
|
||||
// the account level (3rd level). the .diff() method
|
||||
// achieves that
|
||||
val Some(pathDiff) = accountPath.diff(firstAddressPath)
|
||||
|
||||
// deriving public keys from hardened extended keys
|
||||
// is not possible, that's why .deriveChildPubKey()
|
||||
// returns a Try[ExtPublicKey]. A hardened key is marked
|
||||
// by a ' after the number in the notation we use above.
|
||||
val Success(extPubKey) = accountXpub.deriveChildPubKey(pathDiff)
|
||||
val pubkey = extPubKey.key
|
||||
val scriptPubKey = P2WPKHWitnessSPKV0(pubkey)
|
||||
Bech32Address(scriptPubKey, TestNet3)
|
||||
}
|
||||
|
||||
// tada! We just generated an address you can send money to,
|
||||
// without having access to the private key!
|
||||
firstAccountAddress.value
|
||||
|
||||
// you can now continue deriving addresses from the same public
|
||||
// key, by imitating what we did above. To get the next
|
||||
// HD path to generate an address at:
|
||||
val nextAddressPath: SegWitHDPath = firstAddressPath.next
|
||||
```
|
|
@ -59,7 +59,7 @@ You can also run on the bleeding edge of Bitcoin-S, by
|
|||
adding a snapshot build to your `build.sbt`. The most
|
||||
recent snapshot published is `@UNSTABLE_VERSION@`.
|
||||
|
||||
To fetch snapshot, you will need to add the correct
|
||||
To fetch snapshots, you will need to add the correct
|
||||
resolver in your `build.sbt`:
|
||||
|
||||
```sbt
|
||||
|
|
|
@ -3,4 +3,29 @@ id: rpc-clients-intro
|
|||
title: Introduction
|
||||
---
|
||||
|
||||
Bitcoin-S contains RPC clients for interacting with both Bitcoin Core and Eclair.
|
||||
When working with Bitcoin applications, a common task to
|
||||
accomplish is connecting to a service like Bitcoin Core,
|
||||
and use that for tasks like generating addresses,
|
||||
verifying payments and
|
||||
monitoring the blockchain. This typically happens through
|
||||
tools like `bitcoin-cli`, or the Bitcoin Core HTTP RPC
|
||||
server interface. One big drawback to this, is that you
|
||||
lose all type-safety in your application. Even if you
|
||||
have a custom type that represents a Bitcoin transaction,
|
||||
how do you get that to play nicely with the result that
|
||||
Bitcoin Core gives you after signing a transaction? A
|
||||
random hexadecimal string in a HTTP response could be
|
||||
anything from a public key, a transaction or a block
|
||||
header.
|
||||
|
||||
We've done all the mundane work of wiring requests and
|
||||
responses from Bitcoin Core to the powerful and safe types
|
||||
found in Bitcoin-S. We've also written a bunch of tests,
|
||||
that verify that all of this actually work.
|
||||
You'll know for sure that you're sending
|
||||
a valid public key to `importmulti`, and you when doing
|
||||
RPC calls like `getblockheader` we'll even parse the
|
||||
hexadecimal string into a complete header that you can
|
||||
interact with without goofing around with bits and bytes.
|
||||
|
||||
We currently have RPC clients for Bitcoin Core and Eclair.
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
{
|
||||
"docs": {
|
||||
"Getting started": ["getting-started"],
|
||||
"Core module": ["core/core-intro", "core/txbuilder"],
|
||||
"Core module": [
|
||||
"core/core-intro",
|
||||
"core/addresses",
|
||||
"core/hd-keys",
|
||||
"core/txbuilder"
|
||||
],
|
||||
"RPC clients": [
|
||||
"rpc/rpc-clients-intro",
|
||||
"rpc/rpc-eclair",
|
||||
|
|
Loading…
Add table
Reference in a new issue