Skip to content

Commit

Permalink
Wallet integration for nested segwit v0 spending (#1272)
Browse files Browse the repository at this point in the history
* Wallet integration for nested-segwit spending

* Move parameter
  • Loading branch information
benthecarman committed Mar 28, 2020
1 parent 380ef24 commit 0f89992
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 40 deletions.
Expand Up @@ -4,21 +4,16 @@ import org.bitcoins.core.config.RegTest
import org.bitcoins.core.crypto._
import org.bitcoins.core.currency._
import org.bitcoins.core.hd._
import org.bitcoins.core.protocol.Bech32Address
import org.bitcoins.core.protocol.blockchain.{
ChainParams,
RegTestNetChainParams
}
import org.bitcoins.core.protocol.script.{
P2WPKHWitnessSPKV0,
P2WPKHWitnessV0,
ScriptPubKey,
ScriptWitness
}
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.protocol.transaction.{
TransactionOutPoint,
TransactionOutput
}
import org.bitcoins.core.protocol.{Bech32Address, P2SHAddress}
import org.bitcoins.core.util.{CryptoUtil, NumberUtil}
import org.bitcoins.core.wallet.utxo.TxoState
import org.bitcoins.testkit.Implicits._
Expand Down Expand Up @@ -67,6 +62,12 @@ object WalletTestUtil {
HDChainType.Change,
addressIndex = 0)

lazy val sampleNestedSegwitPath: NestedSegWitHDPath =
NestedSegWitHDPath(hdCoinType,
accountIndex = 0,
HDChainType.External,
addressIndex = 0)

private def freshXpub(): ExtPublicKey =
CryptoGenerators.extPublicKey.sampleSome

Expand All @@ -79,6 +80,10 @@ object WalletTestUtil {

def firstAccountDb = AccountDb(freshXpub(), defaultHdAccount)

def nestedSegWitAccountDb: AccountDb =
AccountDb(freshXpub(),
HDAccount(HDCoin(HDPurposes.NestedSegWit, hdCoinType), 0))

private def randomScriptWitness: ScriptWitness =
P2WPKHWitnessV0(freshXpub().key)

Expand Down Expand Up @@ -123,6 +128,26 @@ object WalletTestUtil {
blockHash = Some(randomBlockHash))
}

def sampleNestedSegwitUTXO(
ecPublicKey: ECPublicKey): NestedSegwitV0SpendingInfo = {
val wpkh = P2WPKHWitnessSPKV0(ecPublicKey)
val outpoint = TransactionOutPoint(randomTXID, randomVout)
val output =
TransactionOutput(1.bitcoin, P2SHScriptPubKey(wpkh))
val scriptWitness = randomScriptWitness
val privkeyPath = WalletTestUtil.sampleNestedSegwitPath
NestedSegwitV0SpendingInfo(
state = randomState,
txid = randomTXID,
outPoint = outpoint,
output = output,
privKeyPath = privkeyPath,
redeemScript = wpkh,
scriptWitness = scriptWitness,
blockHash = Some(randomBlockHash)
)
}

/** Given an account returns a sample address */
def getAddressDb(account: AccountDb): AddressDb = {
val path = SegWitHDPath(WalletTestUtil.hdCoinType,
Expand All @@ -143,6 +168,27 @@ object WalletTestUtil {
scriptPubKey = wspk)
}

/** Given an account returns a sample address */
def getNestedSegwitAddressDb(account: AccountDb): AddressDb = {
val path = NestedSegWitHDPath(WalletTestUtil.hdCoinType,
chainType = HDChainType.External,
accountIndex = account.hdAccount.index,
addressIndex = 0)
val pubkey: ECPublicKey = ECPublicKey.freshPublicKey
val hashedPubkey = CryptoUtil.sha256Hash160(pubkey.bytes)
val wpkh = P2WPKHWitnessSPKV0(pubkey)
val witness = P2WPKHWitnessV0(pubkey)
val spk = P2SHScriptPubKey(wpkh)
val address = P2SHAddress.apply(spk, WalletTestUtil.networkParam)

NestedSegWitAddressDb(path = path,
ecPublicKey = pubkey,
hashedPubkey,
address,
witness,
scriptPubKey = spk)
}

/** Inserts an account, address and finally a UTXO */
def insertLegacyUTXO(daos: WalletDAOs)(
implicit ec: ExecutionContext): Future[LegacySpendingInfo] = {
Expand All @@ -162,4 +208,14 @@ object WalletTestUtil {
utxo <- daos.utxoDAO.create(sampleSegwitUTXO(addr.scriptPubKey))
} yield utxo.asInstanceOf[SegwitV0SpendingInfo]
}

/** Inserts an account, address and finally a UTXO */
def insertNestedSegWitUTXO(daos: WalletDAOs)(
implicit ec: ExecutionContext): Future[NestedSegwitV0SpendingInfo] = {
for {
account <- daos.accountDAO.create(WalletTestUtil.nestedSegWitAccountDb)
addr <- daos.addressDAO.create(getNestedSegwitAddressDb(account))
utxo <- daos.utxoDAO.create(sampleNestedSegwitUTXO(addr.ecPublicKey))
} yield utxo.asInstanceOf[NestedSegwitV0SpendingInfo]
}
}
@@ -1,7 +1,7 @@
package org.bitcoins.wallet

import org.bitcoins.core.hd.{AddressType, HDPurposes}
import org.bitcoins.core.protocol.{Bech32Address, P2PKHAddress}
import org.bitcoins.core.protocol.{Bech32Address, P2PKHAddress, P2SHAddress}
import org.bitcoins.testkit.wallet.BitcoinSWalletTest
import org.bitcoins.wallet.api.UnlockedWalletApi
import org.scalatest.FutureOutcome
Expand Down Expand Up @@ -44,15 +44,11 @@ class LegacyWalletTest extends BitcoinSWalletTest {
for {
segwit <- wallet.getNewAddress(AddressType.SegWit)
legacy <- wallet.getNewAddress(AddressType.Legacy)
// TODO: uncomment this once nested segwit is implemented
// https://github.com/bitcoin-s/bitcoin-s/issues/407
// nested <- wallet.getNewAddress(AddressType.NestedSegWit)
nested <- wallet.getNewAddress(AddressType.NestedSegWit)
} yield {
assert(segwit.isInstanceOf[Bech32Address])
assert(legacy.isInstanceOf[P2PKHAddress])
// TODO: uncomment this once nested segwit is implemented
// https://github.com/bitcoin-s/bitcoin-s/issues/407
// assert(nested.isInstanceOf[P2SHAddress])
assert(nested.isInstanceOf[P2SHAddress])
}
}
}
@@ -1,7 +1,7 @@
package org.bitcoins.wallet

import org.bitcoins.core.hd.{AddressType, HDPurposes}
import org.bitcoins.core.protocol.{Bech32Address, P2PKHAddress}
import org.bitcoins.core.protocol.{Bech32Address, P2PKHAddress, P2SHAddress}
import org.bitcoins.testkit.wallet.BitcoinSWalletTest
import org.bitcoins.wallet.api.UnlockedWalletApi
import org.scalatest.FutureOutcome
Expand Down Expand Up @@ -45,13 +45,11 @@ class SegwitWalletTest extends BitcoinSWalletTest {
for {
segwit <- wallet.getNewAddress(AddressType.SegWit)
legacy <- wallet.getNewAddress(AddressType.Legacy)
// TODO: uncomment this once nested segwit is implemented
// nested <- wallet.getNewAddress(AddressType.NestedSegWit)
nested <- wallet.getNewAddress(AddressType.NestedSegWit)
} yield {
assert(segwit.isInstanceOf[Bech32Address])
assert(legacy.isInstanceOf[P2PKHAddress])
// TODO: uncomment this once nested segwit is implemented
// assert(nested.isInstanceOf[P2SHAddress])
assert(nested.isInstanceOf[P2SHAddress])
}
}
}
Expand Up @@ -297,7 +297,7 @@ class TrezorAddressTest extends BitcoinSWalletTest with EmptyFixture {

// TODO: implement this when nested segwit addresses are implemented
// in the wallet
it must "act the same way as Trezor for nested segwit accounts" ignore { _ =>
it must "act the same way as Trezor for nested segwit accounts" in { _ =>
testAccountType(HDPurposes.NestedSegWit)
}
}
Expand Up @@ -127,7 +127,15 @@ class SpendingInfoDAOTest extends BitcoinSWalletTest with WalletDAOFixture {

}

it should "insert a nested segwit UTXO and read it" ignore { _ =>
???
it should "insert a nested segwit UTXO and read it" in { daos =>
val utxoDAO = daos.utxoDAO
for {
created <- WalletTestUtil.insertNestedSegWitUTXO(daos)
read <- utxoDAO.read(created.id.get)
} yield read match {
case None => fail(s"Did not read back a UTXO")
case Some(_: NestedSegwitV0SpendingInfo) => succeed
case Some(other) => fail(s"did not get a nested segwit UTXO: $other")
}
}
}
Expand Up @@ -5,7 +5,11 @@ import org.bitcoins.core.crypto.DoubleSha256DigestBE
import org.bitcoins.core.hd.HDAccount
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.core.protocol.script.{
P2WPKHWitnessSPKV0,
P2WPKHWitnessV0,
ScriptPubKey
}
import org.bitcoins.core.protocol.transaction.{
Transaction,
TransactionOutPoint,
Expand Down Expand Up @@ -82,8 +86,17 @@ private[wallet] trait UtxoHandling extends WalletLogger {
privKeyPath = path,
blockHash = blockHash)
case nested: NestedSegWitAddressDb =>
throw new IllegalArgumentException(
s"Bad utxo $nested. Note: nested segwit is not implemented")
NestedSegwitV0SpendingInfo(
outPoint = outPoint,
output = output,
privKeyPath = nested.path,
redeemScript = P2WPKHWitnessSPKV0(nested.ecPublicKey),
scriptWitness = P2WPKHWitnessV0(nested.ecPublicKey),
txid = txid,
state = state,
id = None,
blockHash = blockHash
)
}

spendingInfoDAO.create(utxo).map { written =>
Expand Down
Expand Up @@ -47,12 +47,13 @@ case class NestedSegWitAddressDb(
ecPublicKey: ECPublicKey,
hashedPubKey: Sha256Hash160Digest,
address: P2SHAddress,
witnessScript: ScriptWitness,
scriptPubKey: ScriptPubKey
) extends AddressDb {
override type PathType = NestedSegWitHDPath

override val scriptType = ScriptType.SCRIPTHASH
override val witnessScriptOpt = None
override val witnessScriptOpt = Some(witnessScript)
}

/** P2PKH */
Expand Down Expand Up @@ -111,9 +112,16 @@ object AddressDbHelper {
pub: ECPublicKey,
path: NestedSegWitHDPath,
np: NetworkParameters): NestedSegWitAddressDb = {
val _ = (pub, path, np)
throw new UnsupportedOperationException(
"Nested segwit is not implemented yet!")
val redeem = P2WPKHWitnessSPKV0(pub)
val spk = P2SHScriptPubKey(redeem)
val scriptWitness = P2WPKHWitnessV0(pub)
val addr = P2SHAddress(spk, np)
NestedSegWitAddressDb(path = path,
ecPublicKey = pub,
hashedPubKey = redeem.pubKeyHash,
address = addr,
witnessScript = scriptWitness,
scriptPubKey = spk)
}

/** Gets an address. Derives the correct type by looking at the kind of path passed in */
Expand Down Expand Up @@ -211,10 +219,22 @@ class AddressTable(tag: Tag) extends Table[AddressDb](tag, "addresses") {
legacyAddr,
scriptPubKey = scriptPubKey)

case (HDPurposes.NestedSegWit,
address: P2SHAddress,
Some(scriptWitness)) =>
val path = NestedSegWitHDPath(coinType = accountCoin,
accountIndex = accountIndex,
chainType = accountChain,
addressIndex = addressIndex)
NestedSegWitAddressDb(path,
pubKey,
hashedPubKey,
address,
witnessScript = scriptWitness,
scriptPubKey = scriptPubKey)
case (purpose: HDPurpose, address: BitcoinAddress, scriptWitnessOpt) =>
throw new IllegalArgumentException(
s"Got invalid combination of HD purpose, address and script witness: $purpose, $address, $scriptWitnessOpt" +
s"Note: Currently only segwit addreses are implemented")
s"Got invalid combination of HD purpose, address and script witness: $purpose, $address, $scriptWitnessOpt")
}
}

Expand Down Expand Up @@ -251,9 +271,24 @@ class AddressTable(tag: Tag) extends Table[AddressDb](tag, "addresses") {
hashedPub,
ScriptType.PUBKEYHASH
)
case _: NestedSegWitAddressDb =>
throw new RuntimeException(s"Nested segwit is not implemented yet!")

case NestedSegWitAddressDb(path,
pubKey,
hashedPubKey,
address,
scriptWitness,
scriptPubKey) =>
Some(
(path.purpose,
path.account.index,
path.coin.coinType,
path.chain.chainType,
address,
Some(scriptWitness),
scriptPubKey,
path.address.index,
pubKey,
hashedPubKey,
ScriptType.SCRIPTHASH))
}

override def * : ProvenShape[AddressDb] =
Expand Down

0 comments on commit 0f89992

Please sign in to comment.