mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-22 22:36:34 +01:00
Merge pull request #497 from torkelrogstad/2019-06-05-legacy-addresses
Legacy addresses
This commit is contained in:
commit
bd10f1c35e
13 changed files with 319 additions and 68 deletions
|
@ -12,12 +12,12 @@ import scala.concurrent.Promise
|
|||
import scala.util.Success
|
||||
import scala.util.Failure
|
||||
|
||||
case class ChainAppConfig(val confs: Config*) extends AppConfig {
|
||||
case class ChainAppConfig(private val confs: Config*) extends AppConfig {
|
||||
override protected val configOverrides: List[Config] = confs.toList
|
||||
override protected val moduleName: String = "chain"
|
||||
override protected type ConfigType = ChainAppConfig
|
||||
override protected def newConfigOfType(
|
||||
configs: List[Config]): ChainAppConfig = ChainAppConfig(configs: _*)
|
||||
override protected def newConfigOfType(configs: Seq[Config]): ChainAppConfig =
|
||||
ChainAppConfig(configs: _*)
|
||||
|
||||
/**
|
||||
* Checks whether or not the chain project is initialized by
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
bitcoin-s {
|
||||
datadir = ${HOME}/.bitcoin-s
|
||||
network = regtest # regtest, testnet3, mainnet
|
||||
|
||||
# settings for wallet module
|
||||
wallet {
|
||||
defaultAccountType = legacy # legacy, segwit, nested-segwit
|
||||
}
|
||||
}
|
|
@ -54,7 +54,7 @@ abstract class AppConfig extends BitcoinSLogger {
|
|||
protected type ConfigType <: AppConfig
|
||||
|
||||
/** Constructor to make a new instance of this config type */
|
||||
protected def newConfigOfType(configOverrides: List[Config]): ConfigType
|
||||
protected def newConfigOfType(configOverrides: Seq[Config]): ConfigType
|
||||
|
||||
/** List of user-provided configs that should
|
||||
* override defaults
|
||||
|
@ -88,9 +88,23 @@ abstract class AppConfig extends BitcoinSLogger {
|
|||
logger.debug(oldConfStr)
|
||||
}
|
||||
|
||||
val newConf = newConfigOfType(
|
||||
configOverrides = List(firstOverride) ++ configs
|
||||
)
|
||||
val configOverrides = firstOverride +: configs
|
||||
if (logger.isTraceEnabled()) {
|
||||
configOverrides.zipWithIndex.foreach {
|
||||
case (c, idx) => logger.trace(s"Override no. $idx: ${c.asReadableJson}")
|
||||
}
|
||||
}
|
||||
val newConf = {
|
||||
// the idea here is that after resolving the configuration,
|
||||
// we extract the value under the 'bitcoin-s' key and use
|
||||
// that as our config. here we have to do the reverse, to
|
||||
// get the keys to resolve correctly
|
||||
val reconstructedStr = s"""
|
||||
bitcoin-s: ${this.config.asReadableJson}
|
||||
"""
|
||||
val reconstructed = ConfigFactory.parseString(reconstructedStr)
|
||||
newConfigOfType(reconstructed +: configOverrides)
|
||||
}
|
||||
|
||||
// to avoid non-necessary lazy load
|
||||
if (logger.isDebugEnabled()) {
|
||||
|
@ -229,7 +243,8 @@ abstract class AppConfig extends BitcoinSLogger {
|
|||
.reduce(_.withFallback(_))
|
||||
|
||||
val interestingOverrides = overrides.getConfig("bitcoin-s")
|
||||
logger.trace(s"User-overrides for bitcoin-s config:")
|
||||
logger.trace(
|
||||
s"${configOverrides.length} user-overrides for bitcoin-s config:")
|
||||
logger.trace(interestingOverrides.asReadableJson)
|
||||
|
||||
// to make the overrides actually override
|
||||
|
@ -282,7 +297,7 @@ object AppConfig extends BitcoinSLogger {
|
|||
*/
|
||||
private[bitcoins] def throwIfDefaultDatadir(config: AppConfig): Unit = {
|
||||
val datadirStr = config.datadir.toString()
|
||||
AppConfig.defaultDatadirRegex.findFirstMatchIn(datadirStr) match {
|
||||
defaultDatadirRegex.findFirstMatchIn(datadirStr) match {
|
||||
case None => () // pass
|
||||
case Some(_) =>
|
||||
val errMsg =
|
||||
|
@ -291,6 +306,8 @@ object AppConfig extends BitcoinSLogger {
|
|||
s"Your data directory is $datadirStr. This would cause tests to potentially",
|
||||
"overwrite your existing data, which you probably don't want."
|
||||
).mkString(" ")
|
||||
logger.error(errMsg)
|
||||
logger.error(s"Configuration: ${config.config.asReadableJson}")
|
||||
throw new RuntimeException(errMsg)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,11 +8,11 @@ import org.bitcoins.node.db.NodeDbManagement
|
|||
import scala.util.Failure
|
||||
import scala.util.Success
|
||||
|
||||
case class NodeAppConfig(confs: Config*) extends AppConfig {
|
||||
case class NodeAppConfig(private val confs: Config*) extends AppConfig {
|
||||
override val configOverrides: List[Config] = confs.toList
|
||||
override protected def moduleName: String = "node"
|
||||
override protected type ConfigType = NodeAppConfig
|
||||
override protected def newConfigOfType(configs: List[Config]): NodeAppConfig =
|
||||
override protected def newConfigOfType(configs: Seq[Config]): NodeAppConfig =
|
||||
NodeAppConfig(configs: _*)
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package org.bitcoins.wallet
|
||||
|
||||
import org.bitcoins.wallet.api.UnlockedWalletApi
|
||||
import org.bitcoins.wallet.util.BitcoinSWalletTest
|
||||
import org.scalatest.FutureOutcome
|
||||
import org.bitcoins.wallet.api.UnlockWalletError.BadPassword
|
||||
import org.bitcoins.wallet.api.UnlockWalletError.JsonParsingError
|
||||
import org.bitcoins.wallet.api.UnlockWalletSuccess
|
||||
import org.bitcoins.core.crypto.AesPassword
|
||||
import org.bitcoins.wallet.api.UnlockWalletError.MnemonicNotFound
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import org.bitcoins.core.protocol.P2PKHAddress
|
||||
import org.bitcoins.core.hd.HDPurposes
|
||||
|
||||
class LegacyWalletTest extends BitcoinSWalletTest {
|
||||
|
||||
override type FixtureParam = UnlockedWalletApi
|
||||
|
||||
override def withFixture(test: OneArgAsyncTest): FutureOutcome =
|
||||
withLegacyWallet(test)
|
||||
|
||||
it should "generate legacy addresses" in { wallet: UnlockedWalletApi =>
|
||||
for {
|
||||
addr <- wallet.getNewAddress()
|
||||
account <- wallet.getDefaultAccount()
|
||||
otherAddr <- wallet.getNewAddress()
|
||||
allAddrs <- wallet.listAddresses()
|
||||
} yield {
|
||||
assert(account.hdAccount.purpose == HDPurposes.Legacy)
|
||||
assert(allAddrs.forall(_.address.isInstanceOf[P2PKHAddress]))
|
||||
assert(allAddrs.length == 2)
|
||||
assert(allAddrs.exists(_.address == addr))
|
||||
assert(allAddrs.exists(_.address == otherAddr))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package org.bitcoins.wallet
|
||||
|
||||
import org.bitcoins.wallet.api.UnlockedWalletApi
|
||||
import org.bitcoins.wallet.util.BitcoinSWalletTest
|
||||
import org.scalatest.FutureOutcome
|
||||
import org.bitcoins.wallet.api.UnlockWalletError.BadPassword
|
||||
import org.bitcoins.wallet.api.UnlockWalletError.JsonParsingError
|
||||
import org.bitcoins.wallet.api.UnlockWalletSuccess
|
||||
import org.bitcoins.core.crypto.AesPassword
|
||||
import org.bitcoins.wallet.api.UnlockWalletError.MnemonicNotFound
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import org.bitcoins.core.protocol.P2PKHAddress
|
||||
import org.bitcoins.core.protocol.Bech32Address
|
||||
import org.bitcoins.core.hd.HDPurposes
|
||||
|
||||
class SegwitWalletTest extends BitcoinSWalletTest {
|
||||
|
||||
override type FixtureParam = UnlockedWalletApi
|
||||
|
||||
override def withFixture(test: OneArgAsyncTest): FutureOutcome = {
|
||||
withSegwitWallet(test)
|
||||
}
|
||||
|
||||
it should "generate segwit addresses" in { wallet: UnlockedWalletApi =>
|
||||
for {
|
||||
addr <- wallet.getNewAddress()
|
||||
account <- wallet.getDefaultAccount()
|
||||
otherAddr <- wallet.getNewAddress()
|
||||
allAddrs <- wallet.listAddresses()
|
||||
} yield {
|
||||
assert(account.hdAccount.purpose == HDPurposes.SegWit)
|
||||
assert(allAddrs.forall(_.address.isInstanceOf[Bech32Address]))
|
||||
assert(allAddrs.length == 2)
|
||||
assert(allAddrs.exists(_.address == addr))
|
||||
assert(allAddrs.exists(_.address == otherAddr))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,8 @@ import com.typesafe.config.ConfigFactory
|
|||
import org.bitcoins.core.config.RegTest
|
||||
import org.bitcoins.core.config.MainNet
|
||||
import org.bitcoins.wallet.config.WalletAppConfig
|
||||
import java.nio.file.Paths
|
||||
import org.bitcoins.core.hd.HDPurposes
|
||||
|
||||
class WalletAppConfigTest extends BitcoinSUnitTest {
|
||||
val config = WalletAppConfig()
|
||||
|
@ -24,6 +26,40 @@ class WalletAppConfigTest extends BitcoinSUnitTest {
|
|||
assert(mainnet.network == MainNet)
|
||||
}
|
||||
|
||||
it should "not matter how the overrides are passed in" in {
|
||||
val dir = Paths.get("/", "bar", "biz")
|
||||
val overrider = ConfigFactory.parseString(s"""
|
||||
|bitcoin-s {
|
||||
| datadir = $dir
|
||||
| network = mainnet
|
||||
|}
|
||||
|""".stripMargin)
|
||||
|
||||
val throughConstuctor = WalletAppConfig(overrider)
|
||||
val throughWithOverrides = config.withOverrides(overrider)
|
||||
assert(throughWithOverrides.network == MainNet)
|
||||
assert(throughWithOverrides.network == throughConstuctor.network)
|
||||
|
||||
assert(throughWithOverrides.datadir.startsWith(dir))
|
||||
assert(throughWithOverrides.datadir == throughConstuctor.datadir)
|
||||
|
||||
}
|
||||
|
||||
it must "be overridable without screwing up other options" in {
|
||||
val dir = Paths.get("/", "foo", "bar")
|
||||
val otherConf = ConfigFactory.parseString(s"bitcoin-s.datadir = $dir")
|
||||
val thirdConf = ConfigFactory.parseString(
|
||||
s"bitcoin-s.wallet.defaultAccountType = nested-segwit")
|
||||
|
||||
val overriden = config.withOverrides(otherConf)
|
||||
|
||||
val twiceOverriden = overriden.withOverrides(thirdConf)
|
||||
|
||||
assert(overriden.datadir.startsWith(dir))
|
||||
assert(twiceOverriden.datadir.startsWith(dir))
|
||||
assert(twiceOverriden.defaultAccountKind == HDPurposes.NestedSegWit)
|
||||
}
|
||||
|
||||
it must "be overridable with multiple levels" in {
|
||||
val testnet = ConfigFactory.parseString("bitcoin-s.network = testnet3")
|
||||
val mainnet = ConfigFactory.parseString("bitcoin-s.network = mainnet")
|
||||
|
|
|
@ -23,14 +23,11 @@ class WalletUnitTest extends BitcoinSWalletTest {
|
|||
accounts <- wallet.listAccounts()
|
||||
addresses <- wallet.listAddresses()
|
||||
} yield {
|
||||
assert(accounts.length == 1)
|
||||
assert(accounts.length == 3) // legacy, segwit and nested segwit
|
||||
assert(addresses.isEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
// eventually this test should NOT succeed, as BIP44
|
||||
// requires a limit to addresses being generated when
|
||||
// they haven't received any funds
|
||||
it should "generate addresses" in { wallet: UnlockedWalletApi =>
|
||||
for {
|
||||
addr <- wallet.getNewAddress()
|
||||
|
|
|
@ -23,6 +23,8 @@ import scala.concurrent.{ExecutionContext, Future}
|
|||
import org.bitcoins.db.AppConfig
|
||||
import org.bitcoins.testkit.BitcoinSAppConfig
|
||||
import org.bitcoins.testkit.BitcoinSAppConfig._
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigFactory
|
||||
|
||||
trait BitcoinSWalletTest
|
||||
extends fixture.AsyncFlatSpec
|
||||
|
@ -57,21 +59,64 @@ trait BitcoinSWalletTest
|
|||
.map(_ => ())
|
||||
}
|
||||
|
||||
def createNewWallet(): Future[UnlockedWalletApi] = {
|
||||
|
||||
for {
|
||||
_ <- config.initialize()
|
||||
wallet <- Wallet.initialize().map {
|
||||
case InitializeWalletSuccess(wallet) => wallet
|
||||
case err: InitializeWalletError =>
|
||||
logger.error(s"Could not initialize wallet: $err")
|
||||
fail(err)
|
||||
/** Returns a function that can be used to create a wallet fixture.
|
||||
* If you pass in a configuration to this method that configuration
|
||||
* is given to the wallet as user-provided overrides. You could for
|
||||
* example use this to override the default data directory, network
|
||||
* or account type.
|
||||
*/
|
||||
private def createNewWallet(
|
||||
extraConfig: Option[Config]): () => Future[UnlockedWalletApi] =
|
||||
() => {
|
||||
val defaultConf = config.walletConf
|
||||
val walletConfig = extraConfig match {
|
||||
case None => defaultConf
|
||||
case Some(c) => defaultConf.withOverrides(c)
|
||||
}
|
||||
} yield wallet
|
||||
|
||||
// we want to check we're not overwriting
|
||||
// any user data
|
||||
AppConfig.throwIfDefaultDatadir(walletConfig)
|
||||
|
||||
walletConfig.initialize().flatMap { _ =>
|
||||
Wallet
|
||||
.initialize()(implicitly[ExecutionContext], walletConfig)
|
||||
.map {
|
||||
case InitializeWalletSuccess(wallet) => wallet
|
||||
case err: InitializeWalletError =>
|
||||
logger.error(s"Could not initialize wallet: $err")
|
||||
fail(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates a wallet with the default configuration */
|
||||
private def createDefaultWallet(): Future[UnlockedWalletApi] =
|
||||
createNewWallet(None)() // get the standard config
|
||||
|
||||
/** Lets you customize the parameters for the created wallet */
|
||||
val withNewConfiguredWallet: Config => OneArgAsyncTest => FutureOutcome =
|
||||
walletConfig =>
|
||||
makeDependentFixture(build = createNewWallet(Some(walletConfig)),
|
||||
destroy = destroyWallet)
|
||||
|
||||
/** Fixture for an initialized wallet which produce legacy addresses */
|
||||
def withLegacyWallet(test: OneArgAsyncTest): FutureOutcome = {
|
||||
val confOverride =
|
||||
ConfigFactory.parseString("bitcoin-s.wallet.defaultAccountType = legacy")
|
||||
withNewConfiguredWallet(confOverride)(test)
|
||||
}
|
||||
|
||||
/** Fixture for an initialized wallet which produce segwit addresses */
|
||||
def withSegwitWallet(test: OneArgAsyncTest): FutureOutcome = {
|
||||
val confOverride =
|
||||
ConfigFactory.parseString("bitcoin-s.wallet.defaultAccountType = segwit")
|
||||
withNewConfiguredWallet(confOverride)(test)
|
||||
}
|
||||
|
||||
def withNewWallet(test: OneArgAsyncTest): FutureOutcome =
|
||||
makeDependentFixture(build = createNewWallet, destroy = destroyWallet)(test)
|
||||
makeDependentFixture(build = createDefaultWallet, destroy = destroyWallet)(
|
||||
test)
|
||||
|
||||
case class WalletWithBitcoind(
|
||||
wallet: UnlockedWalletApi,
|
||||
|
@ -96,7 +141,7 @@ trait BitcoinSWalletTest
|
|||
|
||||
def withNewWalletAndBitcoind(test: OneArgAsyncTest): FutureOutcome = {
|
||||
val builder: () => Future[WalletWithBitcoind] = composeBuildersAndWrap(
|
||||
createNewWallet,
|
||||
createDefaultWallet,
|
||||
createWalletWithBitcoind,
|
||||
(_: UnlockedWalletApi, walletWithBitcoind: WalletWithBitcoind) =>
|
||||
walletWithBitcoind
|
||||
|
|
|
@ -48,6 +48,12 @@ object WalletTestUtil {
|
|||
HDChainType.External,
|
||||
addressIndex = 0)
|
||||
|
||||
/** Sample legacy HD path */
|
||||
lazy val sampleLegacyPath = LegacyHDPath(hdCoinType,
|
||||
accountIndex = 0,
|
||||
HDChainType.Change,
|
||||
addressIndex = 0)
|
||||
|
||||
def freshXpub: ExtPublicKey =
|
||||
CryptoGenerators.extPublicKey.sample.getOrElse(freshXpub)
|
||||
|
||||
|
|
|
@ -62,12 +62,15 @@ sealed abstract class Wallet
|
|||
.get
|
||||
.toUTXOSpendingInfo(fromAccount, seed))
|
||||
|
||||
logger.info(s"Spending UTXOs: ${utxos
|
||||
.map { utxo =>
|
||||
import utxo.outPoint
|
||||
s"${outPoint.txId.hex}:${outPoint.vout.toInt}"
|
||||
}
|
||||
.mkString(", ")}")
|
||||
logger.info({
|
||||
val utxosStr = utxos
|
||||
.map { utxo =>
|
||||
import utxo.outPoint
|
||||
s"${outPoint.txId.hex}:${outPoint.vout.toInt}"
|
||||
}
|
||||
.mkString(", ")
|
||||
s"Spending UTXOs: $utxosStr"
|
||||
})
|
||||
|
||||
utxos.zipWithIndex.foreach {
|
||||
case (utxo, index) =>
|
||||
|
@ -100,11 +103,6 @@ sealed abstract class Wallet
|
|||
// todo: create multiple wallets, need to maintain multiple databases
|
||||
object Wallet extends CreateWalletApi with BitcoinSLogger {
|
||||
|
||||
// The default HD purpose of the bitcoin-s wallet. Can be
|
||||
// one of segwit, nested segwit or legacy. Hard coded for
|
||||
// now, could be make configurable in the future
|
||||
private[wallet] val DEFAULT_HD_PURPOSE: HDPurpose = HDPurposes.SegWit
|
||||
|
||||
private case class WalletImpl(
|
||||
mnemonicCode: MnemonicCode
|
||||
)(
|
||||
|
@ -168,24 +166,33 @@ object Wallet extends CreateWalletApi with BitcoinSLogger {
|
|||
encrypted <- encryptedMnemonicE
|
||||
} yield {
|
||||
val wallet = WalletImpl(mnemonic)
|
||||
val coin =
|
||||
HDCoin(DEFAULT_HD_PURPOSE, HDUtil.getCoinType(config.network))
|
||||
val account = HDAccount(coin, 0)
|
||||
val xpriv = wallet.xprivForPurpose(DEFAULT_HD_PURPOSE)
|
||||
|
||||
// safe since we're deriving from a priv
|
||||
val xpub = xpriv.deriveChildPubKey(account).get
|
||||
val accountDb = AccountDb(xpub, account)
|
||||
|
||||
val mnemonicPath =
|
||||
WalletStorage.writeMnemonicToDisk(encrypted)
|
||||
logger.debug(s"Saved encrypted wallet mnemonic to $mnemonicPath")
|
||||
|
||||
for {
|
||||
_ <- config.initialize()
|
||||
_ <- wallet.accountDAO
|
||||
.create(accountDb)
|
||||
.map(_ => logger.trace(s"Saved account to DB"))
|
||||
_ = {
|
||||
val mnemonicPath =
|
||||
WalletStorage.writeMnemonicToDisk(encrypted)
|
||||
logger.debug(s"Saved encrypted wallet mnemonic to $mnemonicPath")
|
||||
}
|
||||
_ <- {
|
||||
// We want to make sure all level 0 accounts are created,
|
||||
// so the user can change the default account kind later
|
||||
// and still have their wallet work
|
||||
val createAccountFutures =
|
||||
HDPurposes.all.map(createRootAccount(wallet, _))
|
||||
|
||||
val accountCreationF = Future.sequence(createAccountFutures)
|
||||
|
||||
accountCreationF.foreach(_ =>
|
||||
logger.debug(s"Created root level accounts for wallet"))
|
||||
|
||||
accountCreationF.failed.foreach { err =>
|
||||
logger.error(s"Failed to create root level accounts: $err")
|
||||
}
|
||||
|
||||
accountCreationF
|
||||
}
|
||||
|
||||
} yield wallet
|
||||
}
|
||||
|
||||
|
@ -199,4 +206,26 @@ object Wallet extends CreateWalletApi with BitcoinSLogger {
|
|||
case Left(err) => err
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates the level 0 account for the given HD purpose */
|
||||
private def createRootAccount(wallet: Wallet, purpose: HDPurpose)(
|
||||
implicit config: WalletAppConfig,
|
||||
ec: ExecutionContext): Future[AccountDb] = {
|
||||
val coin =
|
||||
HDCoin(purpose, HDUtil.getCoinType(config.network))
|
||||
val account = HDAccount(coin, 0)
|
||||
val xpriv = wallet.xprivForPurpose(purpose)
|
||||
// safe since we're deriving from a priv
|
||||
val xpub = xpriv.deriveChildPubKey(account).get
|
||||
val accountDb = AccountDb(xpub, account)
|
||||
|
||||
logger.debug(s"Creating account with constant prefix $purpose")
|
||||
wallet.accountDAO
|
||||
.create(accountDb)
|
||||
.map { written =>
|
||||
logger.debug(s"Saved account with constant prefix $purpose to DB")
|
||||
written
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,14 +7,26 @@ import org.bitcoins.wallet.db.WalletDbManagement
|
|||
import scala.util.Failure
|
||||
import scala.util.Success
|
||||
import java.nio.file.Files
|
||||
import org.bitcoins.core.hd.HDPurpose
|
||||
import org.bitcoins.core.hd.HDPurposes
|
||||
|
||||
case class WalletAppConfig(conf: Config*) extends AppConfig {
|
||||
case class WalletAppConfig(private val conf: Config*) extends AppConfig {
|
||||
override val configOverrides: List[Config] = conf.toList
|
||||
override def moduleName: String = "wallet"
|
||||
override type ConfigType = WalletAppConfig
|
||||
override def newConfigOfType(configs: List[Config]): WalletAppConfig =
|
||||
override def newConfigOfType(configs: Seq[Config]): WalletAppConfig =
|
||||
WalletAppConfig(configs: _*)
|
||||
|
||||
lazy val defaultAccountKind: HDPurpose =
|
||||
config.getString("wallet.defaultAccountType") match {
|
||||
case "legacy" => HDPurposes.Legacy
|
||||
case "segwit" => HDPurposes.SegWit
|
||||
case "nested-segwit" => HDPurposes.NestedSegWit
|
||||
// todo: validate this pre-app startup
|
||||
case other: String =>
|
||||
throw new RuntimeException(s"$other is not a valid account type!")
|
||||
}
|
||||
|
||||
override def initialize()(implicit ec: ExecutionContext): Future[Unit] = {
|
||||
logger.debug(s"Initializing wallet setup")
|
||||
|
||||
|
|
|
@ -18,26 +18,51 @@ import org.bitcoins.core.hd.SegWitHDPath
|
|||
import org.bitcoins.core.crypto.BIP39Seed
|
||||
import org.bitcoins.core.util.BitcoinSLogger
|
||||
import org.bitcoins.core.hd.LegacyHDPath
|
||||
import org.bitcoins.core.hd.NestedSegWitHDPath
|
||||
|
||||
case class SegWitUTOXSpendingInfodb(
|
||||
/**
|
||||
* DB representation of a native V0
|
||||
* SegWit UTXO
|
||||
*/
|
||||
case class NativeV0UTXOSpendingInfoDb(
|
||||
id: Option[Long],
|
||||
outPoint: TransactionOutPoint,
|
||||
output: TransactionOutput,
|
||||
privKeyPath: SegWitHDPath,
|
||||
scriptWitness: ScriptWitness
|
||||
) extends UTXOSpendingInfoDb {
|
||||
override def redeemScriptOpt: Option[ScriptPubKey] = None
|
||||
override def scriptWitnessOpt: Option[ScriptWitness] = Some(scriptWitness)
|
||||
override val redeemScriptOpt: Option[ScriptPubKey] = None
|
||||
override val scriptWitnessOpt: Option[ScriptWitness] = Some(scriptWitness)
|
||||
|
||||
override type PathType = SegWitHDPath
|
||||
|
||||
override def copyWithId(id: Long): SegWitUTOXSpendingInfodb =
|
||||
override def copyWithId(id: Long): NativeV0UTXOSpendingInfoDb =
|
||||
copy(id = Some(id))
|
||||
}
|
||||
|
||||
case class LegacyUTXOSpendingInfoDb(
|
||||
id: Option[Long],
|
||||
outPoint: TransactionOutPoint,
|
||||
output: TransactionOutput,
|
||||
privKeyPath: LegacyHDPath
|
||||
) extends UTXOSpendingInfoDb {
|
||||
override val redeemScriptOpt: Option[ScriptPubKey] = None
|
||||
override def scriptWitnessOpt: Option[ScriptWitness] = None
|
||||
|
||||
override type PathType = LegacyHDPath
|
||||
|
||||
override def copyWithId(id: Long): LegacyUTXOSpendingInfoDb =
|
||||
copy(id = Some(id))
|
||||
}
|
||||
|
||||
// TODO add case for nested segwit
|
||||
// and legacy
|
||||
/**
|
||||
* The database level representation of a UTXO.
|
||||
* When storing a UTXO we don't want to store
|
||||
* sensitive material such as private keys.
|
||||
* We instead store the necessary information
|
||||
* we need to derive the private keys, given
|
||||
* the root wallet seed.
|
||||
*/
|
||||
sealed trait UTXOSpendingInfoDb
|
||||
extends DbRowAutoInc[UTXOSpendingInfoDb]
|
||||
with BitcoinSLogger {
|
||||
|
@ -55,6 +80,9 @@ sealed trait UTXOSpendingInfoDb
|
|||
|
||||
def value: CurrencyUnit = output.value
|
||||
|
||||
/** Converts a non-sensitive DB representation of a UTXO into
|
||||
* a signable (and sensitive) real-world UTXO
|
||||
*/
|
||||
def toUTXOSpendingInfo(
|
||||
account: AccountDb,
|
||||
walletSeed: BIP39Seed): BitcoinUTXOSpendingInfo = {
|
||||
|
@ -114,20 +142,22 @@ case class UTXOSpendingInfoTable(tag: Tag)
|
|||
outpoint,
|
||||
output,
|
||||
path: SegWitHDPath,
|
||||
None,
|
||||
None, // ReedemScript
|
||||
Some(scriptWitness)) =>
|
||||
SegWitUTOXSpendingInfodb(id, outpoint, output, path, scriptWitness)
|
||||
.asInstanceOf[UTXOSpendingInfoDb]
|
||||
NativeV0UTXOSpendingInfoDb(id, outpoint, output, path, scriptWitness)
|
||||
|
||||
case (id,
|
||||
outpoint,
|
||||
output,
|
||||
path @ (_: LegacyHDPath | _: NestedSegWitHDPath),
|
||||
spkOpt,
|
||||
swOpt) =>
|
||||
path: LegacyHDPath,
|
||||
None, // RedeemScript
|
||||
None // ScriptWitness
|
||||
) =>
|
||||
LegacyUTXOSpendingInfoDb(id, outpoint, output, path)
|
||||
case (id, outpoint, output, path, spkOpt, swOpt) =>
|
||||
throw new IllegalArgumentException(
|
||||
"Could not construct UtxoSpendingInfoDb from bad tuple:"
|
||||
+ s" ($id, $outpoint, $output, $path, $spkOpt, $swOpt) . Note: Only Segwit is implemented")
|
||||
+ s" ($id, $outpoint, $output, $path, $spkOpt, $swOpt) . Note: Nested Segwit is not implemented")
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue