diff --git a/core-test/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionSpec.scala b/core-test/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionSpec.scala deleted file mode 100644 index 6997fa16c5..0000000000 --- a/core-test/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionSpec.scala +++ /dev/null @@ -1,36 +0,0 @@ -package org.bitcoins.core.protocol.transaction - -import org.bitcoins.testkit.core.gen.TransactionGenerators -import org.bitcoins.core.util.{BitcoinSLogger, CryptoUtil} -import org.scalacheck.{Prop, Properties} - -/** - * Created by chris on 6/24/16. - */ -class TransactionSpec extends Properties("TransactionSpec") { - private val logger = BitcoinSLogger.logger - - property("Serialization symmetry") = - Prop.forAll(TransactionGenerators.transaction) { tx => - val result = Transaction(tx.hex) == tx - if (!result) { - logger.error(s"tx: $tx") - logger.error("Incorrect tx hex: " + tx.hex) - } - result - } - - property("txid of a base transaction must be SHA256(SHA256(hex)) of a btx") = - Prop.forAll(TransactionGenerators.baseTransaction) { btx: BaseTransaction => - btx.txId == CryptoUtil.doubleSHA256(btx.hex) - } - - property( - "wtxid must be the same as the SHA256(SHA256(hex)) of a wtx && " + - "wtxid and txid are not the same for witness transactions") = - Prop.forAll(TransactionGenerators.witnessTransaction) { - wtx: WitnessTransaction => - wtx.wTxId == CryptoUtil.doubleSHA256(wtx.hex) && - wtx.wTxId != wtx.txId - } -} diff --git a/core-test/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionTest.scala b/core-test/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionTest.scala index 63720e56ac..c9f8938933 100644 --- a/core-test/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionTest.scala @@ -14,27 +14,53 @@ import org.bitcoins.core.script.PreExecutionScriptProgram import org.bitcoins.core.script.interpreter.ScriptInterpreter import org.bitcoins.core.script.result.ScriptOk import org.bitcoins.core.serializers.transaction.RawBaseTransactionParser -import org.bitcoins.core.util.{BitcoinSUtil, CryptoUtil, TestUtil} -import org.scalatest.{FlatSpec, MustMatchers} +import org.bitcoins.core.util.{CryptoUtil, TestUtil} +import org.bitcoins.testkit.util.BitcoinSUnitTest import org.slf4j.LoggerFactory import spray.json._ +import scodec.bits._ import scala.io.Source +import org.bitcoins.testkit.core.gen.TransactionGenerators -/** - * Created by chris on 7/14/15. - */ -class TransactionTest extends FlatSpec with MustMatchers { - private val logger = LoggerFactory.getLogger(this.getClass) - "Transaction" must "derive the correct txid from the transaction contents" in { +class TransactionTest extends BitcoinSUnitTest { + behavior of "Transaction" + + it must "have serialization symmetry" in { + forAll(TransactionGenerators.transaction) { tx => + val result = Transaction(tx.hex) == tx + if (!result) { + logger.error(s"tx: $tx") + logger.error("Incorrect tx hex: " + tx.hex) + } + assert(result) + } + } + + it must "always have TXID of a base transaction be SHA256(SHA256(hex))" in { + forAll(TransactionGenerators.baseTransaction) { btx: BaseTransaction => + assert(btx.txId == CryptoUtil.doubleSHA256(btx.hex)) + } + } + + it must + "wtxid must be the same as the SHA256(SHA256(hex)) of a wtx and " + + "wtxid and txid are not the same for witness transactions" in { + forAll(TransactionGenerators.witnessTransaction) { + wtx: WitnessTransaction => + assert(wtx.wTxId == CryptoUtil.doubleSHA256(wtx.hex)) + assert(wtx.wTxId != wtx.txId) + } + } + + it must "derive the correct txid from the transaction contents" in { //https://btc.blockr.io/api/v1/tx/raw/cddda897b0e9322937ee1f4fd5d6147d60f04a0f4d3b461e4f87066ac3918f2a val tx = RawBaseTransactionParser.read( "01000000020df1e23002ddf909aec026b1cf0c3b6b7943c042f22e25dbd0441855e6b39ee900000000fdfd00004730440220028c02f14654a0cc12c7e3229adb09d5d35bebb6ba1057e39adb1b2706607b0d0220564fab12c6da3d5acef332406027a7ff1cbba980175ffd880e1ba1bf40598f6b014830450221009362f8d67b60773745e983d07ba10efbe566127e244b724385b2ca2e47292dda022033def393954c320653843555ddbe7679b35cc1cacfe1dad923977de8cd6cc6d7014c695221025e9adcc3d65c11346c8a6069d6ebf5b51b348d1d6dc4b95e67480c34dc0bc75c21030585b3c80f4964bf0820086feda57c8e49fa1eab925db7c04c985467973df96521037753a5e3e9c4717d3f81706b38a6fb82b5fb89d29e580d7b98a37fea8cdefcad53aeffffffffd11533b0f283fca193e361a91ca7ddfc66592e20fd6eaf5dc0f1ef5fed05818000000000fdfe0000483045022100b4062edd75b5b3117f28ba937ed737b10378f762d7d374afabf667180dedcc62022005d44c793a9d787197e12d5049da5e77a09046014219b31e9c6b89948f648f1701483045022100b3b0c0273fc2c531083701f723e03ea3d9111e4bbca33bdf5b175cec82dcab0802206650462db37f9b4fe78da250a3b339ab11e11d84ace8f1b7394a1f6db0960ba4014c695221025e9adcc3d65c11346c8a6069d6ebf5b51b348d1d6dc4b95e67480c34dc0bc75c21030585b3c80f4964bf0820086feda57c8e49fa1eab925db7c04c985467973df96521037753a5e3e9c4717d3f81706b38a6fb82b5fb89d29e580d7b98a37fea8cdefcad53aeffffffff02500f1e00000000001976a9147ecaa33ef3cd6169517e43188ad3c034db091f5e88ac204e0000000000001976a914321908115d8a138942f98b0b53f86c9a1848501a88ac00000000") - tx.txId.hex must be( - BitcoinSUtil.flipEndianness( - "cddda897b0e9322937ee1f4fd5d6147d60f04a0f4d3b461e4f87066ac3918f2a")) + tx.txId.flip.bytes must be( + hex"cddda897b0e9322937ee1f4fd5d6147d60f04a0f4d3b461e4f87066ac3918f2a") } it must "have an empty transaction with the correct fields" in { @@ -64,7 +90,7 @@ class TransactionTest extends FlatSpec with MustMatchers { "0e2fddd0071fc32e0849ef3d3f6024aa6d73fa1eb91e3daad5a5dcfde8d45a376bc2f274b207d7017e8346304402203f7973c50fa84ab8960d5895bfcc73365101cc3967fa39528a94ab5e94de218d02207d8fc26f806d26407dd13f52be1d40c90b403690cce3933f71de57607a78848a2103bf87039d25c947357b31d005575d75071ddd6e97017e9efa57559b6a5daa03af1976a9143b75df7c44a47fed51374aef67bb7e7ae071b0a788acd3f37759c999d6f325fa7fb6445a5c6989d2fcee2b60c83cc1dd167d189488f74b09eade054ef5304847304502210087ebadf23475d287824ab171addc39907f5c93d14a5b463cbcc37805cd37cdaa02206cbca1f7768e99b65bc7a03d64056f1ce8c86ab78cbc7c19d49dab2c7084f5a830204fe815b8c4937864464ade73d3869e8888569e976742b666c742e8c60a46e5e14756035ccc736946304402204c544118e309de16cde7bc69e8018688aceb0a329e935bcf5d5f6aa8904937ad022043bee0a74e0cd9a6317d2abdab6cff004a6ebff2dc3f59b75a4e90aecf83c7b32103336d83e7f45e66b6655b25a4b3ee5679785378a89a9db407360dce45b24d67e0775bfdc70000000000000000000000000000000000000000000000000000000000000000fffffffffdb20100483046022100e52e3d78998643d2987a6210972984caaf33b53e2493daaf7dda6a00c7aade80022100d92576b1a7219c49d4ae85a7e450ee039d170a7aeed5fd7b34825faaab885055473045022100977591b85fd01d0969f39bd66f84b01adf8da5879ca6fc80c474a27502d9345f02201c415141bee3504b6eb6ff08c60e1e55b519ae80191bfa2aa9f1082581d88b5a473045022100999259a53b3b692519b95f058a6a70734f77ca4752103cb777f83ff17acff4d30220383b7debacc2dec8553d73ab1d52523a56921ba3904153fb43a4509d5dff0a2b4730450220609a352f8afd4aa49fef4bd81c13df2dbae87070217958099968b652d44d40d1022100d6f9902a240a4c516479b95a4a75ee42ea31441a7163fbcfeb1879de9907d0554630440220272828412f2c0833d9328209ec476240ee8ba51453773df4639001c84657a7ed02200b75a0bc08c002be39868a596d703ce3641b243286bede8264af0df7818843cb4830460221008ead4ad119e7350ea23de18c845a2018babea3ffd135575ece8f33ebf7f6bfe5022100e5e4067b788887edec933e7776799fe2d92915cb19e21cf7145fe4577b4eb469ffffffff0000000000000000000000000000000000000000000000000000000000000000ffffffff6946304402207b1000f0aea3a8f9f3952d13a7fd5eceb3740e6a4f2543987329f1a99a0bec6c0220457fcff820bac0c0f5c65c4228b89eadd380a98a99dfa2ed7c135617323b7ff021020610f065313942890798946ed97ca12d2852f66765bfd6785f90db650bb7d62affffffff42c761d25e0f10cfc2c82280f741e63e0aa184dd28627c487d6add381949be757d48ab4e85483046022100bceb7174237d148d3be472ad2fa9b19d2db5e9256943f32ed4abc6ae603602d8022100832b11ae88981e588daa6e65fcdd732ee186c21287589f7cbf0678109c483add210325259fd900969d3c16e870eeb9ecfd2bff8519c2aa81958a8bc19be6594a2d6e1976a9140bc44e1f010f5dfa4a102319e3a2445c164ce5d088ac5b0ddd9c652982074e88b4724293e21f651804b327586396ecf411784e80f6478fb23f606d569c746a47304502200ec20fae608b985e8b65f29a9e69ec671926eb837797763bf0178a515407b28a022100a131250b8be7fb2a533d44a1acf620155cf352ba8387f4e419b5b06c2b50901d2103d46e46269fc7ed661e4c34f714cfe6cd36231a77d79b7fd17edcb1741fdab9b781b905f5003c4edbe4" val tx = Transaction(rawTx) tx.hex must be(rawTx) - (Transaction(tx.hex) == tx) must be(true) + Transaction(tx.hex) must be(tx) } it must "calculate the correct txid and wtxid for a witness transaction" in { @@ -75,10 +101,9 @@ class TransactionTest extends FlatSpec with MustMatchers { val expected = "d869f854e1f8788bcff294cc83b280942a8c728de71eb709a2c29d10bfe21b7c" val wTxExpected = CryptoUtil.doubleSHA256(hex) - wtx.txId.hex must be(BitcoinSUtil.flipEndianness(expected)) + wtx.txId.flip.hex must be(expected) wtx.txIdBE.hex must be(expected) wtx.wTxId must be(wTxExpected) - wtx.wTxIdBE.hex must be(BitcoinSUtil.flipEndianness(wTxExpected.hex)) } it must "parse a witness coinbase tx correctly" in { @@ -145,10 +170,13 @@ class TransactionTest extends FlatSpec with MustMatchers { (input, inputIndex) = findInput(tx, outPoint).getOrElse( (EmptyTransactionInput, 0)) } yield { - require( + assert( outPoint.txId == input.previousOutput.txId, - "OutPoint txId not the same as input prevout txid\noutPoint.txId: " + outPoint.txId + "\n" + - "input prevout txid: " + input.previousOutput.txId + s""" + |OutPoint txId not the same as input prevout txid + |outPoint.txId: ${outPoint.txId} + |input prevout txid: ${input.previousOutput.txId} + |""".stripMargin ) val txSigComponent = amountOpt match { case Some(amount) => @@ -198,7 +226,7 @@ class TransactionTest extends FlatSpec with MustMatchers { flags = testCase.flags) } val program = PreExecutionScriptProgram(txSigComponent) - withClue(testCase.raw + " input index: " + inputIndex) { + withClue(s"${testCase.raw} input index: $inputIndex") { ScriptInterpreter.run(program) must equal(ScriptOk) } } @@ -295,7 +323,8 @@ class TransactionTest extends FlatSpec with MustMatchers { } it must "check transaction with two out point referencing the same tx with different indexes" in { - val hex = "0200000002924942b0b7c12ece0dc8100d74a1cd29acd6cfc60698bfc3f07d83890eec20b6000000006a47304402202831d3708867f9bfb4268690cbcf97a686ccec1f5a4334cf0256fd442a88d0b802206fa8fa5550df8bfcc9c31cc8b6aad035be68b767b67e2b823f844680a79349650121038991058ce7ef4b00194e8426e3630dffd32822f819c150938f26113ba751c9a100000000924942b0b7c12ece0dc8100d74a1cd29acd6cfc60698bfc3f07d83890eec20b6010000006a47304402202e4cf01174afb9f97b0ab8f24c64c796ae4b3bb91d1838099bf262e8842e6480022006399d769d6d4ba099c2d3188f62caa5b51f572e7c2775a9bc23495c020dd1090121038991058ce7ef4b00194e8426e3630dffd32822f819c150938f26113ba751c9a1000000000288130000000000001976a914cc1fe3e943bd91e0e263f08a93f0d2a5299a7e5e88ac32600000000000001976a914af84620f44464d1a404386485885d1cd9e5d396d88ac00000000" + val hex = + "0200000002924942b0b7c12ece0dc8100d74a1cd29acd6cfc60698bfc3f07d83890eec20b6000000006a47304402202831d3708867f9bfb4268690cbcf97a686ccec1f5a4334cf0256fd442a88d0b802206fa8fa5550df8bfcc9c31cc8b6aad035be68b767b67e2b823f844680a79349650121038991058ce7ef4b00194e8426e3630dffd32822f819c150938f26113ba751c9a100000000924942b0b7c12ece0dc8100d74a1cd29acd6cfc60698bfc3f07d83890eec20b6010000006a47304402202e4cf01174afb9f97b0ab8f24c64c796ae4b3bb91d1838099bf262e8842e6480022006399d769d6d4ba099c2d3188f62caa5b51f572e7c2775a9bc23495c020dd1090121038991058ce7ef4b00194e8426e3630dffd32822f819c150938f26113ba751c9a1000000000288130000000000001976a914cc1fe3e943bd91e0e263f08a93f0d2a5299a7e5e88ac32600000000000001976a914af84620f44464d1a404386485885d1cd9e5d396d88ac00000000" val btx = BaseTransaction.fromHex(hex) ScriptInterpreter.checkTransaction(btx) must be(true) } diff --git a/docs/core/addresses.md b/docs/core/addresses.md new file mode 100644 index 0000000000..374c2ee07f --- /dev/null +++ b/docs/core/addresses.md @@ -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) +``` diff --git a/docs/core/core-intro.md b/docs/core/core-intro.md index d77f8eaa26..6f9ef19c58 100644 --- a/docs/core/core-intro.md +++ b/docs/core/core-intro.md @@ -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._ diff --git a/docs/core/hd-keys.md b/docs/core/hd-keys.md new file mode 100644 index 0000000000..e68e24da6d --- /dev/null +++ b/docs/core/hd-keys.md @@ -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 +``` diff --git a/docs/getting-started.md b/docs/getting-started.md index 18e79b6056..b9e13b0dd6 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -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 diff --git a/docs/rpc/rpc-clients-intro.md b/docs/rpc/rpc-clients-intro.md index ceff1d8a1f..5513eb9a62 100644 --- a/docs/rpc/rpc-clients-intro.md +++ b/docs/rpc/rpc-clients-intro.md @@ -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. diff --git a/website/sidebars.json b/website/sidebars.json index 15f330f890..5834107622 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -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",