Lots of misc. improvements from dlc branch (#1583)

This commit is contained in:
Nadav Kohen 2020-06-17 14:34:01 -05:00 committed by GitHub
parent 80930e07c2
commit 914c905bd7
25 changed files with 558 additions and 270 deletions

View File

@ -55,7 +55,7 @@ case class BitcoindConfig(
/** The optional index of the first header section encountered */
private lazy val firstHeaderSectionIndex: Option[Int] = {
val indices =
List(RegTest, TestNet3, MainNet).map(headerSectionIndex).flatten
List(RegTest, TestNet3, MainNet).flatMap(headerSectionIndex)
if (indices.nonEmpty) Some(indices.min) else None
}
@ -111,13 +111,13 @@ case class BitcoindConfig(
String] = collectFrom(lines)(_)
/** The blockchain network associated with this `bitcoind` config */
lazy val network = {
lazy val network: NetworkParameters = {
val networkStrOpt =
collectAllLines {
case (network @ ("testnet" | "regtest" | "mainnet"), "1") => network
}.lastOption
val networkOpt = networkStrOpt.flatMap(Networks.fromString)
val networkOpt = networkStrOpt.flatMap(Networks.fromStringOpt)
(networkOpt, networkStrOpt) match {
case (None, Some(badStr)) =>
@ -323,7 +323,7 @@ object BitcoindConfig extends BitcoinSLogger {
* default configuration is returned.
*/
def fromDefaultDatadir: BitcoindConfig = {
if (DEFAULT_CONF_FILE.isFile()) {
if (DEFAULT_CONF_FILE.isFile) {
apply(DEFAULT_CONF_FILE)
} else {
BitcoindConfig.empty
@ -353,8 +353,7 @@ object BitcoindConfig extends BitcoinSLogger {
}
/** Default location of bitcoind conf file */
val DEFAULT_CONF_FILE: File = DEFAULT_DATADIR
.toPath()
val DEFAULT_CONF_FILE: File = DEFAULT_DATADIR.toPath
.resolve("bitcoin.conf")
.toFile

View File

@ -314,7 +314,7 @@ lazy val dbCommons = project
name := "bitcoin-s-db-commons",
libraryDependencies ++= Deps.dbCommons
)
.dependsOn(core)
.dependsOn(core, appCommons)
lazy val dbCommonsTest = project
.in(file("db-commons-test"))

View File

@ -23,10 +23,10 @@ class NetworkParametersTest extends BitcoinSUnitTest {
}
it must "get the correct Network from string" in {
assert(Networks.fromString("mainnet").contains(MainNet))
assert(Networks.fromString("testnet").contains(TestNet3))
assert(Networks.fromString("regtest").contains(RegTest))
assert(Networks.fromString("").isEmpty)
assert(Networks.fromString("craig wright is a fraud").isEmpty)
assert(Networks.fromString("mainnet") == MainNet)
assert(Networks.fromString("testnet") == TestNet3)
assert(Networks.fromString("regtest") == RegTest)
assert(Networks.fromStringOpt("").isEmpty)
assert(Networks.fromStringOpt("craig wright is a fraud").isEmpty)
}
}

View File

@ -22,8 +22,8 @@ class ScriptPubKeyTest extends BitcoinSUnitTest {
OP_EQUALVERIFY,
OP_CHECKSIG)
//from b30d3148927f620f5b1228ba941c211fdabdae75d0ba0b688a58accbf018f3cc
val rawScriptPubKey = TestUtil.rawP2PKHScriptPubKey
val scriptPubKey = ScriptPubKey(rawScriptPubKey)
val rawScriptPubKey: String = TestUtil.rawP2PKHScriptPubKey
val scriptPubKey: ScriptPubKey = ScriptPubKey(rawScriptPubKey)
"ScriptPubKey" must "give the expected asm from creating a scriptPubKey from hex" in {
scriptPubKey.asm must be(expectedAsm)
@ -35,9 +35,8 @@ class ScriptPubKeyTest extends BitcoinSUnitTest {
val witnessProgram = Seq(ScriptConstant(pubKeyHash.bytes))
val asm = OP_0 +: BytesToPushOntoStack(20) +: witnessProgram
val witnessScriptPubKey = WitnessScriptPubKey(asm)
witnessScriptPubKey.isDefined must be(true)
witnessScriptPubKey.get.witnessVersion must be(WitnessVersion0)
witnessScriptPubKey.get.witnessProgram must be(witnessProgram)
witnessScriptPubKey.witnessVersion must be(WitnessVersion0)
witnessScriptPubKey.witnessProgram must be(witnessProgram)
}
it must "determine the correct descriptors" in {

View File

@ -45,7 +45,7 @@ class WitnessScriptPubKeySpec extends Properties("WitnessScriptPubKeySpec") {
property("witnessScriptPubKey fromAsm symmetry") = {
Prop.forAll(ScriptGenerators.witnessScriptPubKey) {
case (witScriptPubKey, _) =>
WitnessScriptPubKey(witScriptPubKey.asm).get == witScriptPubKey
WitnessScriptPubKey(witScriptPubKey.asm) == witScriptPubKey
}
}
}

View File

@ -1,49 +1,16 @@
package org.bitcoins.core.wallet.builder
import org.bitcoins.core.crypto.{
BaseTxSigComponent,
WitnessTxSigComponentP2SH,
WitnessTxSigComponentRaw
}
import org.bitcoins.core.currency.{Bitcoins, CurrencyUnits, Satoshis}
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.policy.Policy
import org.bitcoins.core.protocol.script.{
CLTVScriptPubKey,
CSVScriptPubKey,
ConditionalScriptPubKey,
EmptyScriptPubKey,
MultiSignatureScriptPubKey,
NonStandardScriptPubKey,
P2PKHScriptPubKey,
P2PKScriptPubKey,
P2PKWithTimeoutScriptPubKey,
P2SHScriptPubKey,
P2SHScriptSignature,
P2WSHWitnessV0,
UnassignedWitnessScriptPubKey,
WitnessCommitment,
WitnessScriptPubKey,
WitnessScriptPubKeyV0
}
import org.bitcoins.core.protocol.transaction.{
BaseTransaction,
Transaction,
TransactionConstants,
TransactionInput,
TransactionOutPoint,
TransactionOutput,
WitnessTransaction
}
import org.bitcoins.core.script.PreExecutionScriptProgram
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.protocol.transaction._
import org.bitcoins.core.script.constant.ScriptNumber
import org.bitcoins.core.script.crypto.HashType
import org.bitcoins.core.script.interpreter.ScriptInterpreter
import org.bitcoins.core.util.BitcoinScriptUtil
import org.bitcoins.core.wallet.fee.{SatoshisPerByte, SatoshisPerVirtualByte}
import org.bitcoins.core.wallet.utxo.{
ConditionalPath,
InputInfo,
InputSigningInfo,
LockTimeInputInfo,
ScriptSignatureParams
}
@ -297,64 +264,6 @@ class RawTxSignerTest extends BitcoinSAsyncTest {
)
}
def verifyScript(
tx: Transaction,
utxos: Vector[InputSigningInfo[InputInfo]]): Boolean = {
val programs: Vector[PreExecutionScriptProgram] =
tx.inputs.zipWithIndex.toVector.map {
case (input: TransactionInput, idx: Int) =>
val outpoint = input.previousOutput
val creditingTx =
utxos.find(u => u.outPoint.txId == outpoint.txId).get
val output = creditingTx.output
val spk = output.scriptPubKey
val amount = output.value
val txSigComponent = spk match {
case witSPK: WitnessScriptPubKeyV0 =>
val o = TransactionOutput(amount, witSPK)
WitnessTxSigComponentRaw(tx.asInstanceOf[WitnessTransaction],
UInt32(idx),
o,
Policy.standardFlags)
case _: UnassignedWitnessScriptPubKey => ???
case x @ (_: P2PKScriptPubKey | _: P2PKHScriptPubKey |
_: P2PKWithTimeoutScriptPubKey | _: MultiSignatureScriptPubKey |
_: WitnessCommitment | _: CSVScriptPubKey |
_: CLTVScriptPubKey | _: ConditionalScriptPubKey |
_: NonStandardScriptPubKey | EmptyScriptPubKey) =>
val o = TransactionOutput(CurrencyUnits.zero, x)
BaseTxSigComponent(tx, UInt32(idx), o, Policy.standardFlags)
case _: P2SHScriptPubKey =>
val p2shScriptSig =
tx.inputs(idx).scriptSignature.asInstanceOf[P2SHScriptSignature]
p2shScriptSig.redeemScript match {
case _: WitnessScriptPubKey =>
WitnessTxSigComponentP2SH(
transaction = tx.asInstanceOf[WitnessTransaction],
inputIndex = UInt32(idx),
output = output,
flags = Policy.standardFlags)
case _ =>
BaseTxSigComponent(tx,
UInt32(idx),
output,
Policy.standardFlags)
}
}
PreExecutionScriptProgram(txSigComponent)
}
ScriptInterpreter.runAllVerify(programs)
}
it should "sign a mix of spks in a tx and then have it verified" in {
forAllAsync(CreditingTxGen.inputsAndOutputs(),
ScriptGenerators.scriptPubKey) {
@ -369,7 +278,7 @@ class RawTxSignerTest extends BitcoinSAsyncTest {
RawTxSigner.sign(utx, creditingTxsInfo.toVector, fee))
txF.map { tx =>
assert(verifyScript(tx, creditingTxsInfo.toVector))
assert(BitcoinScriptUtil.verifyScript(tx, creditingTxsInfo.toVector))
}
}
}
@ -388,7 +297,7 @@ class RawTxSignerTest extends BitcoinSAsyncTest {
RawTxSigner.sign(utx, creditingTxsInfo.toVector, fee))
txF.map { tx =>
assert(verifyScript(tx, creditingTxsInfo.toVector))
assert(BitcoinScriptUtil.verifyScript(tx, creditingTxsInfo.toVector))
}
}
}

View File

@ -1,6 +1,7 @@
package org.bitcoins.core.config
import org.bitcoins.core.protocol.blockchain._
import org.bitcoins.crypto.StringFactory
import scodec.bits.ByteVector
sealed abstract class NetworkParameters {
@ -73,8 +74,8 @@ sealed abstract class MainNet extends BitcoinNetwork {
/**
* @inheritdoc
*/
override def dnsSeeds = {
List(
override def dnsSeeds: Vector[String] = {
Vector(
"seed.bitcoin.sipa.be",
"dnsseed.bluematt.me",
"dnsseed.bitcoin.dashjr.org",
@ -89,7 +90,7 @@ sealed abstract class MainNet extends BitcoinNetwork {
/**
* @inheritdoc
*/
override def magicBytes = ByteVector(0xf9, 0xbe, 0xb4, 0xd9)
override def magicBytes: ByteVector = ByteVector(0xf9, 0xbe, 0xb4, 0xd9)
}
@ -119,7 +120,7 @@ sealed abstract class TestNet3 extends BitcoinNetwork {
/*
* @inheritdoc
*/
override def magicBytes = ByteVector(0x0b, 0x11, 0x09, 0x07)
override def magicBytes: ByteVector = ByteVector(0x0b, 0x11, 0x09, 0x07)
}
@ -146,24 +147,44 @@ sealed abstract class RegTest extends BitcoinNetwork {
/**
* @inheritdoc
*/
override def magicBytes = ByteVector(0xfa, 0xbf, 0xb5, 0xda)
override def magicBytes: ByteVector = ByteVector(0xfa, 0xbf, 0xb5, 0xda)
}
final case object RegTest extends RegTest
// $COVERAGE-ON$
object Networks {
object Networks extends StringFactory[NetworkParameters] {
val knownNetworks: Seq[NetworkParameters] = BitcoinNetworks.knownNetworks
val secretKeyBytes: Seq[ByteVector] = BitcoinNetworks.secretKeyBytes
val p2pkhNetworkBytes: Seq[ByteVector] = BitcoinNetworks.p2pkhNetworkBytes
val p2shNetworkBytes: Seq[ByteVector] = BitcoinNetworks.p2shNetworkBytes
def fromString(string: String): NetworkParameters =
BitcoinNetworks.fromString(string)
def magicToNetwork: Map[ByteVector, NetworkParameters] =
BitcoinNetworks.magicToNetwork
def bytesToNetwork: Map[ByteVector, NetworkParameters] =
BitcoinNetworks.bytesToNetwork
}
object BitcoinNetworks extends StringFactory[BitcoinNetwork] {
val knownNetworks: Seq[NetworkParameters] = Seq(MainNet, TestNet3, RegTest)
val secretKeyBytes: Seq[ByteVector] = knownNetworks.map(_.privateKey)
val p2pkhNetworkBytes: Seq[ByteVector] = knownNetworks.map(_.p2pkhNetworkByte)
val p2shNetworkBytes: Seq[ByteVector] = knownNetworks.map(_.p2shNetworkByte)
/** Uses the notation used in `bitcoin.conf` */
def fromString(string: String): Option[NetworkParameters] = string match {
case "mainnet" => Some(MainNet)
case "testnet" => Some(TestNet3)
case "regtest" => Some(RegTest)
case _: String => None
override def fromString(string: String): BitcoinNetwork = string match {
case "mainnet" => MainNet
case "main" => MainNet
case "testnet3" => TestNet3
case "testnet" => TestNet3
case "test" => TestNet3
case "regtest" => RegTest
case _: String =>
throw new IllegalArgumentException(s"Invalid network $string")
}
/** Map of magic network bytes to the corresponding network */

View File

@ -12,30 +12,40 @@ package object currency {
/** Provides natural language syntax for bitcoins */
implicit class BitcoinsInt(private val i: Int) extends AnyVal {
def bitcoins: Bitcoins = Bitcoins(i)
def bitcoin: Bitcoins = bitcoins
def BTC: Bitcoins = bitcoins
}
/** Provides natural language syntax for bitcoins */
implicit class BitcoinsLong(private val i: Long) extends AnyVal {
def bitcoins: Bitcoins = Bitcoins(i)
def bitcoin: Bitcoins = bitcoins
def BTC: Bitcoins = bitcoins
}
/** Provides natural language syntax for satoshis */
implicit class SatoshisInt(private val i: Int) extends AnyVal {
def satoshis: Satoshis = Satoshis(i)
def satoshi: Satoshis = satoshis
def sats: Satoshis = satoshis
def sat: Satoshis = satoshis
}
/** Provides natural language syntax for satoshis */
implicit class SatoshisLong(private val i: Long) extends AnyVal {
def satoshis: Satoshis = Satoshis(i)
def satoshi: Satoshis = satoshis
def sats: Satoshis = satoshis
def sat: Satoshis = satoshis
}
@ -80,4 +90,9 @@ package object currency {
}
}
}
implicit val satoshisOrdering: Ordering[Satoshis] =
new Ordering[Satoshis] {
override def compare(x: Satoshis, y: Satoshis): Int = x.compare(y)
}
}

View File

@ -28,6 +28,18 @@ case class HDAccount(
object HDAccount {
def fromPath(path: BIP32Path): Option[HDAccount] = {
HDCoin.fromPath(BIP32Path(path.path.init)).flatMap { coin =>
val lastNode = path.path.last
if (lastNode.hardened) {
Some(HDAccount(coin, lastNode.index))
} else {
None
}
}
}
/** This method is meant to take in an arbitrary bip32 path and see
* if it has the same account as the given account
*

View File

@ -6,3 +6,18 @@ case class HDCoin(purpose: HDPurpose, coinType: HDCoinType) extends BIP32Path {
def toAccount(index: Int): HDAccount = HDAccount(this, index)
}
object HDCoin {
def fromPath(path: BIP32Path): Option[HDCoin] = {
if (path.path.length == 2) {
HDPurposes.fromNode(path.path.head).map { purpose =>
val coinType = HDCoinType.fromInt(path.path.last.index)
HDCoin(purpose, coinType)
}
} else {
None
}
}
}

View File

@ -42,4 +42,11 @@ object HDCoinType {
case TestNet3 | RegTest => Testnet
}
}
def fromNode(node: BIP32Node): HDCoinType = {
require(node.hardened,
s"Cannot construct HDCoinType from un-hardened node: $node")
fromInt(node.index)
}
}

View File

@ -28,4 +28,10 @@ object HDPurposes {
/** Tries to turn the provided integer into a HD purpose path segment */
def fromConstant(i: Int): Option[HDPurpose] = all.find(_.constant == i)
def fromNode(node: BIP32Node): Option[HDPurpose] = {
require(node.hardened,
s"Cannot construct HDPurpose from un-hardened node: $node")
fromConstant(node.index)
}
}

View File

@ -36,7 +36,7 @@ sealed abstract class Address {
/** The [[org.bitcoins.core.protocol.script.ScriptPubKey ScriptPubKey]] the address represents */
def scriptPubKey: ScriptPubKey
override def toString = value
override def toString: String = value
}
sealed abstract class BitcoinAddress extends Address
@ -67,7 +67,7 @@ sealed abstract class P2SHAddress extends BitcoinAddress {
Base58.encode(bytes ++ checksum)
}
override def scriptPubKey = P2SHScriptPubKey(hash)
override def scriptPubKey: P2SHScriptPubKey = P2SHScriptPubKey(hash)
override def hash: Sha256Hash160Digest
}
@ -122,6 +122,9 @@ object Bech32Address extends AddressFactory[Bech32Address] {
//require(verifyChecksum(hrp, data), "checksum did not pass")
}
def empty(network: NetworkParameters = MainNet): Bech32Address =
fromScriptPubKey(P2WSHWitnessSPKV0(EmptyScriptPubKey), network)
def apply(
witSPK: WitnessScriptPubKey,
networkParameters: NetworkParameters): Bech32Address = {
@ -153,31 +156,30 @@ object Bech32Address extends AddressFactory[Bech32Address] {
* [[org.bitcoins.core.protocol.script.WitnessScriptPubKey WitnessScriptPubKey]] */
def fromStringToWitSPK(string: String): Try[WitnessScriptPubKey] = {
val decoded = fromStringT(string)
decoded.flatMap {
case bech32Addr =>
val bytes = bech32Addr.data
val (v, _) = (bytes.head, bytes.tail)
val convertedProg = NumberUtil.convertUInt5sToUInt8(bytes.tail)
val progBytes = UInt8.toBytes(convertedProg)
val witVersion = WitnessVersion(v.toInt)
val pushOp = BitcoinScriptUtil.calculatePushOp(progBytes)
witVersion match {
case Some(v) =>
val witSPK = WitnessScriptPubKey(
List(v.version) ++ pushOp ++ List(ScriptConstant(progBytes)))
witSPK match {
case Some(spk) => Success(spk)
case None =>
Failure(
new IllegalArgumentException(
"Failed to decode bech32 into a witSPK"))
}
case None =>
Failure(
new IllegalArgumentException(
"Witness version was not valid, got: " + v))
}
decoded.flatMap { bech32Addr =>
val bytes = bech32Addr.data
val (v, _) = (bytes.head, bytes.tail)
val convertedProg = NumberUtil.convertUInt5sToUInt8(bytes.tail)
val progBytes = UInt8.toBytes(convertedProg)
val witVersion = WitnessVersion(v.toInt)
val pushOp = BitcoinScriptUtil.calculatePushOp(progBytes)
witVersion match {
case Some(v) =>
val witSPK = Try(
WitnessScriptPubKey(
List(v.version) ++ pushOp ++ List(ScriptConstant(progBytes))))
witSPK match {
case Success(spk) => Success(spk)
case Failure(err) =>
Failure(
new IllegalArgumentException(
"Failed to decode bech32 into a witSPK: " + err.getMessage))
}
case None =>
Failure(
new IllegalArgumentException(
"Witness version was not valid, got: " + v))
}
}
}

View File

@ -80,8 +80,8 @@ object P2PKHScriptPubKey extends ScriptFactory[P2PKHScriptPubKey] {
def fromAsm(asm: Seq[ScriptToken]): P2PKHScriptPubKey = {
buildScript(asm.toVector,
P2PKHScriptPubKeyImpl(_),
isP2PKHScriptPubKey(_),
P2PKHScriptPubKeyImpl.apply,
isP2PKHScriptPubKey,
"Given asm was not a p2pkh scriptPubKey, got: " + asm)
}
@ -118,8 +118,7 @@ sealed trait MultiSignatureScriptPubKey extends RawScriptPubKey {
asmWithoutPushOps.indexOf(OP_CHECKMULTISIG)
else asmWithoutPushOps.indexOf(OP_CHECKMULTISIGVERIFY)
//magic number 2 represents the maxSig operation and the OP_CHECKMULTISIG operation at the end of the asm
val numSigsRequired = asmWithoutPushOps(
opCheckMultiSigIndex - maxSigs.toInt - 2)
val numSigsRequired = asmWithoutPushOps(opCheckMultiSigIndex - maxSigs - 2)
numSigsRequired match {
case x: ScriptNumber => x.toInt
case c: ScriptConstant
@ -216,8 +215,8 @@ object MultiSignatureScriptPubKey
def fromAsm(asm: Seq[ScriptToken]): MultiSignatureScriptPubKey = {
buildScript(asm.toVector,
MultiSignatureScriptPubKeyImpl(_),
isMultiSignatureScriptPubKey(_),
MultiSignatureScriptPubKeyImpl.apply,
isMultiSignatureScriptPubKey,
"Given asm was not a MultSignatureScriptPubKey, got: " + asm)
}
@ -225,11 +224,10 @@ object MultiSignatureScriptPubKey
/** Determines if the given script tokens are a multisignature `scriptPubKey` */
def isMultiSignatureScriptPubKey(asm: Seq[ScriptToken]): Boolean = {
val isNotEmpty = asm.size > 0
val containsMultiSigOp = asm.contains(OP_CHECKMULTISIG) || asm.contains(
OP_CHECKMULTISIGVERIFY)
if (isNotEmpty && containsMultiSigOp) {
if (asm.nonEmpty && containsMultiSigOp) {
//we need either the first or second asm operation to indicate how many signatures are required
val hasRequiredSignaturesTry = Try {
asm.headOption match {
@ -258,7 +256,7 @@ object MultiSignatureScriptPubKey
.isInstanceOf[ScriptNumber] || op == OP_CHECKMULTISIG ||
op == OP_CHECKMULTISIGVERIFY)
val result = isNotEmpty && containsMultiSigOp && hasRequiredSignatures &&
val result = asm.nonEmpty && containsMultiSigOp && hasRequiredSignatures &&
hasMaximumSignatures && isStandardOps
result
case (Success(_), Failure(_)) => false
@ -330,8 +328,8 @@ object P2SHScriptPubKey extends ScriptFactory[P2SHScriptPubKey] {
def fromAsm(asm: Seq[ScriptToken]): P2SHScriptPubKey = {
buildScript(asm.toVector,
P2SHScriptPubKeyImpl(_),
isP2SHScriptPubKey(_),
P2SHScriptPubKeyImpl.apply,
isP2SHScriptPubKey,
"Given asm was not a p2sh scriptPubkey, got: " + asm)
}
@ -366,8 +364,8 @@ object P2PKScriptPubKey extends ScriptFactory[P2PKScriptPubKey] {
def fromAsm(asm: Seq[ScriptToken]): P2PKScriptPubKey = {
buildScript(asm.toVector,
P2PKScriptPubKeyImpl(_),
isP2PKScriptPubKey(_),
P2PKScriptPubKeyImpl.apply,
isP2PKScriptPubKey,
"Given asm was not a p2pk scriptPubKey, got: " + asm)
}
@ -386,10 +384,9 @@ sealed trait LockTimeScriptPubKey extends RawScriptPubKey {
/** Determines the nested `ScriptPubKey` inside the `LockTimeScriptPubKey` */
def nestedScriptPubKey: RawScriptPubKey = {
val bool: Boolean = asm.head.isInstanceOf[ScriptNumberOperation]
bool match {
case true => RawScriptPubKey(asm.slice(3, asm.length))
case false => RawScriptPubKey(asm.slice(4, asm.length))
asm.head match {
case _: ScriptNumberOperation => RawScriptPubKey(asm.slice(3, asm.length))
case _: ScriptToken => RawScriptPubKey(asm.slice(4, asm.length))
}
}
@ -442,8 +439,8 @@ object CLTVScriptPubKey extends ScriptFactory[CLTVScriptPubKey] {
def fromAsm(asm: Seq[ScriptToken]): CLTVScriptPubKey = {
buildScript(asm.toVector,
CLTVScriptPubKeyImpl(_),
isCLTVScriptPubKey(_),
CLTVScriptPubKeyImpl.apply,
isCLTVScriptPubKey,
"Given asm was not a CLTVScriptPubKey, got: " + asm)
}
@ -530,8 +527,8 @@ object CSVScriptPubKey extends ScriptFactory[CSVScriptPubKey] {
def fromAsm(asm: Seq[ScriptToken]): CSVScriptPubKey = {
buildScript(asm.toVector,
CSVScriptPubKeyImpl(_),
isCSVScriptPubKey(_),
CSVScriptPubKeyImpl.apply,
isCSVScriptPubKey,
"Given asm was not a CSVScriptPubKey, got: " + asm)
}
@ -887,10 +884,25 @@ sealed trait P2PKWithTimeoutScriptPubKey extends RawScriptPubKey {
lazy val pubKey: ECPublicKey =
ECPublicKey.fromBytes(asm(2).bytes)
lazy val lockTime: ScriptNumber = ScriptNumber.fromBytes(asm(5).bytes)
private lazy val smallCSVOpt: Option[Long] = {
asm(4) match {
case num: ScriptNumberOperation => Some(num.toLong)
case _: ScriptToken => None
}
}
lazy val timeoutPubKey: ECPublicKey =
ECPublicKey.fromBytes(asm(9).bytes)
lazy val lockTime: ScriptNumber = {
smallCSVOpt
.map(ScriptNumber.apply)
.getOrElse(ScriptNumber(asm(5).bytes))
}
lazy val timeoutPubKey: ECPublicKey = {
smallCSVOpt match {
case Some(_) => ECPublicKey.fromBytes(asm(8).bytes)
case None => ECPublicKey.fromBytes(asm(9).bytes)
}
}
}
object P2PKWithTimeoutScriptPubKey
@ -930,18 +942,35 @@ object P2PKWithTimeoutScriptPubKey
}
def isP2PKWithTimeoutScriptPubKey(asm: Seq[ScriptToken]): Boolean = {
if (asm.length == 12) {
val pubKey = ECPublicKey.fromBytes(asm(2).bytes)
val lockTimeTry = Try(ScriptNumber.fromBytes(asm(5).bytes))
val timeoutPubKey = ECPublicKey.fromBytes(asm(9).bytes)
lockTimeTry match {
case Success(lockTime) =>
asm == P2PKWithTimeoutScriptPubKey(pubKey, lockTime, timeoutPubKey).asm
case Failure(_) => false
}
} else {
if (asm.length < 5) {
false
} else {
val (smallCSVOpt, requiredSize) = asm(4) match {
case num: ScriptNumberOperation => (Some(num.toLong), 11)
case _: ScriptToken => (None, 12)
}
if (asm.length == requiredSize) {
val pubKey = ECPublicKey.fromBytes(asm(2).bytes)
val lockTimeTry = smallCSVOpt match {
case Some(num) => Success(ScriptNumber(num))
case None => Try(ScriptNumber.fromBytes(asm(5).bytes))
}
val timeoutPubKey = smallCSVOpt match {
case Some(_) => ECPublicKey.fromBytes(asm(8).bytes)
case None => ECPublicKey.fromBytes(asm(9).bytes)
}
lockTimeTry match {
case Success(lockTime) =>
asm == P2PKWithTimeoutScriptPubKey(pubKey, lockTime, timeoutPubKey).asm
case Failure(_) => false
}
} else {
false
}
}
}
}
@ -958,7 +987,7 @@ object NonStandardScriptPubKey extends ScriptFactory[NonStandardScriptPubKey] {
def fromAsm(asm: Seq[ScriptToken]): NonStandardScriptPubKey = {
//everything can be a NonStandardScriptPubkey, thus the trivially true function
buildScript(asm.toVector, NonStandardScriptPubKeyImpl(_), { _ =>
buildScript(asm.toVector, NonStandardScriptPubKeyImpl.apply, { _ =>
true
}, "")
}
@ -1030,7 +1059,7 @@ object ScriptPubKey extends ScriptFactory[ScriptPubKey] {
if (nonWitnessScriptPubKey
.isInstanceOf[NonStandardScriptPubKey] && WitnessScriptPubKey
.isWitnessScriptPubKey(asm)) {
WitnessScriptPubKey(asm).get
WitnessScriptPubKey(asm)
} else {
nonWitnessScriptPubKey
}
@ -1044,10 +1073,10 @@ object ScriptPubKey extends ScriptFactory[ScriptPubKey] {
* [[org.bitcoins.core.protocol.script.ScriptWitness ScriptWitness]] */
sealed trait WitnessScriptPubKey extends ScriptPubKey {
def witnessProgram: Seq[ScriptToken]
def witnessVersion = WitnessVersion(asm.head)
def witnessVersion: WitnessVersion = WitnessVersion(asm.head)
}
object WitnessScriptPubKey {
object WitnessScriptPubKey extends ScriptFactory[WitnessScriptPubKey] {
/** Witness scripts must begin with one of these operations, see
* [[https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki BIP141]] */
@ -1069,18 +1098,20 @@ object WitnessScriptPubKey {
OP_15,
OP_16)
val unassignedWitVersions = validWitVersions.tail
val unassignedWitVersions: Seq[ScriptNumberOperation] = validWitVersions.tail
def apply(asm: Seq[ScriptToken]): Option[WitnessScriptPubKey] = fromAsm(asm)
def apply(asm: Seq[ScriptToken]): WitnessScriptPubKey = fromAsm(asm)
def fromAsm(asm: Seq[ScriptToken]): Option[WitnessScriptPubKey] = asm match {
def fromAsm(asm: Seq[ScriptToken]): WitnessScriptPubKey = asm match {
case _ if P2WPKHWitnessSPKV0.isValid(asm) =>
Some(P2WPKHWitnessSPKV0.fromAsm(asm))
P2WPKHWitnessSPKV0.fromAsm(asm)
case _ if P2WSHWitnessSPKV0.isValid(asm) =>
Some(P2WSHWitnessSPKV0.fromAsm(asm))
P2WSHWitnessSPKV0.fromAsm(asm)
case _ if WitnessScriptPubKey.isWitnessScriptPubKey(asm) =>
Some(UnassignedWitnessScriptPubKey(asm))
case _ => None
UnassignedWitnessScriptPubKey(asm)
case _ =>
throw new IllegalArgumentException(
"Given asm was not a WitnessScriptPubKey, got: " + asm)
}
/**
@ -1130,7 +1161,7 @@ object WitnessScriptPubKeyV0 {
* [[https://github.com/bitcoin/bitcoin/blob/449f9b8debcceb61a92043bc7031528a53627c47/src/script/script.cpp#L215-L229]]
*/
def isValid(asm: Seq[ScriptToken]): Boolean = {
WitnessScriptPubKey.isWitnessScriptPubKey(asm) && asm.headOption == Some(
WitnessScriptPubKey.isWitnessScriptPubKey(asm) && asm.headOption.contains(
OP_0)
}
}
@ -1152,8 +1183,8 @@ object P2WPKHWitnessSPKV0 extends ScriptFactory[P2WPKHWitnessSPKV0] {
override def fromAsm(asm: Seq[ScriptToken]): P2WPKHWitnessSPKV0 = {
buildScript(asm.toVector,
P2WPKHWitnessSPKV0Impl(_),
isValid(_),
P2WPKHWitnessSPKV0Impl.apply,
isValid,
s"Given asm was not a P2WPKHWitnessSPKV0, got $asm")
}
@ -1197,8 +1228,8 @@ object P2WSHWitnessSPKV0 extends ScriptFactory[P2WSHWitnessSPKV0] {
override def fromAsm(asm: Seq[ScriptToken]): P2WSHWitnessSPKV0 = {
buildScript(asm.toVector,
P2WSHWitnessSPKV0Impl(_),
isValid(_),
P2WSHWitnessSPKV0Impl.apply,
isValid,
s"Given asm was not a P2WSHWitnessSPKV0, got $asm")
}
@ -1242,8 +1273,8 @@ object UnassignedWitnessScriptPubKey
override def fromAsm(asm: Seq[ScriptToken]): UnassignedWitnessScriptPubKey = {
buildScript(
asm.toVector,
UnassignedWitnessScriptPubKeyImpl(_),
WitnessScriptPubKey.isWitnessScriptPubKey(_),
UnassignedWitnessScriptPubKeyImpl.apply,
WitnessScriptPubKey.isWitnessScriptPubKey,
"Given asm was not a valid witness script pubkey: " + asm
)
}
@ -1284,8 +1315,8 @@ object WitnessCommitment extends ScriptFactory[WitnessCommitment] {
override def fromAsm(asm: Seq[ScriptToken]): WitnessCommitment = {
buildScript(asm.toVector,
WitnessCommitmentImpl(_),
isWitnessCommitment(_),
WitnessCommitmentImpl.apply,
isWitnessCommitment,
"Given asm was not a valid witness commitment, got: " + asm)
}

View File

@ -148,7 +148,7 @@ sealed trait P2SHScriptSignature extends ScriptSignature {
//if we have an EmptyScriptSignature, we need to check if the rest of the asm
//is a Witness script. It is not necessarily a witness script, since this code
//path might be used for signing a normal p2sh spk in TransactionSignatureSerializer
WitnessScriptPubKey(asm.tail).get
WitnessScriptPubKey(asm.tail)
} else {
ScriptPubKey.fromAsmBytes(asm.last.bytes)
}

View File

@ -88,6 +88,22 @@ case class PSBT(
PSBT(global, inputs, outputs)
}
def finalizeInput(index: Int): Try[PSBT] = {
require(index >= 0 && index < inputMaps.size,
s"Index must be within 0 and the number of inputs, got: $index")
val inputMap = inputMaps(index)
if (inputMap.isFinalized) {
Success(this)
} else {
inputMap.finalize(transaction.inputs(index)).map { finalizedInputMap =>
val newInputMaps =
inputMaps.updated(index, finalizedInputMap)
PSBT(globalMap, newInputMaps, outputMaps)
}
}
}
/** Finalizes this PSBT if possible, returns a Failure otherwise
* @see [[https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#input-finalizer]]
*/
@ -502,28 +518,8 @@ case class PSBT(
inputIndex: Int): PSBT =
addSignature(PartialSignature(pubKey, sig), inputIndex)
def addSignature(
partialSignature: PartialSignature,
inputIndex: Int): PSBT = {
require(
inputIndex < inputMaps.size,
s"index must be less than the number of input maps present in the psbt, $inputIndex >= ${inputMaps.size}")
require(
!inputMaps(inputIndex).isFinalized,
s"Cannot update an InputPSBTMap that is finalized, index: $inputIndex")
require(
!inputMaps(inputIndex).partialSignatures
.exists(_.pubKey == partialSignature.pubKey),
s"Input has already been signed by ${partialSignature.pubKey}"
)
val newElements = inputMaps(inputIndex).elements :+ partialSignature
val newInputMaps =
inputMaps.updated(inputIndex, InputPSBTMap(newElements))
PSBT(globalMap, newInputMaps, outputMaps)
}
def addSignature(partialSignature: PartialSignature, inputIndex: Int): PSBT =
addSignatures(Vector(partialSignature), inputIndex)
/** Adds all the PartialSignatures to the input map at the given index */
def addSignatures(
@ -551,6 +547,51 @@ case class PSBT(
PSBT(globalMap, newInputMaps, outputMaps)
}
def verifyFinalizedInput(index: Int): Boolean = {
val inputMap = inputMaps(index)
require(inputMap.isFinalized, "Input must be finalized to verify")
val wUtxoOpt = inputMap.witnessUTXOOpt
val utxoOpt = inputMap.nonWitnessOrUnknownUTXOOpt
val newInput = {
val input = transaction.inputs(index)
val scriptSigOpt = inputMap.finalizedScriptSigOpt
val scriptSig =
scriptSigOpt.map(_.scriptSig).getOrElse(EmptyScriptSignature)
TransactionInput(input.previousOutput, scriptSig, input.sequence)
}
val tx = transaction.updateInput(index, newInput)
wUtxoOpt match {
case Some(wUtxo) =>
inputMap.finalizedScriptWitnessOpt match {
case Some(scriptWit) =>
val wtx = {
val wtx = WitnessTransaction.toWitnessTx(transaction)
wtx.updateWitness(index, scriptWit.scriptWitness)
}
val output = wUtxo.witnessUTXO
ScriptInterpreter.verifyInputScript(wtx, index, output)
case None =>
false
}
case None =>
utxoOpt match {
case Some(utxo) =>
val input = tx.inputs(index)
val output =
utxo.transactionSpent.outputs(input.previousOutput.vout.toInt)
ScriptInterpreter.verifyInputScript(tx, index, output)
case None =>
false
}
}
}
/**
* Extracts the serialized from the serialized, fully signed transaction from
* this PSBT and validates the script signatures using the ScriptInterpreter.

View File

@ -2,7 +2,9 @@ package org.bitcoins.core.util
import org.bitcoins.core.consensus.Consensus
import org.bitcoins.core.crypto._
import org.bitcoins.core.currency.CurrencyUnits
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.policy.Policy
import org.bitcoins.core.protocol.CompactSizeUInt
import org.bitcoins.core.protocol.script.{
CLTVScriptPubKey,
@ -10,6 +12,12 @@ import org.bitcoins.core.protocol.script.{
EmptyScriptPubKey,
_
}
import org.bitcoins.core.protocol.transaction.{
Transaction,
TransactionInput,
TransactionOutput,
WitnessTransaction
}
import org.bitcoins.core.script.constant._
import org.bitcoins.core.script.crypto.{
OP_CHECKMULTISIG,
@ -18,13 +26,18 @@ import org.bitcoins.core.script.crypto.{
OP_CHECKSIGVERIFY
}
import org.bitcoins.core.script.flag.{ScriptFlag, ScriptFlagUtil}
import org.bitcoins.core.script.interpreter.ScriptInterpreter
import org.bitcoins.core.script.result.{
ScriptError,
ScriptErrorPubKeyType,
ScriptErrorWitnessPubKeyType
}
import org.bitcoins.core.script.ExecutionInProgressScriptProgram
import org.bitcoins.core.script.{
ExecutionInProgressScriptProgram,
PreExecutionScriptProgram
}
import org.bitcoins.core.serializers.script.ScriptParser
import org.bitcoins.core.wallet.utxo.{InputInfo, ScriptSignatureParams}
import org.bitcoins.crypto.{ECDigitalSignature, ECPublicKey}
import scodec.bits.ByteVector
@ -92,8 +105,8 @@ trait BitcoinScriptUtil extends BitcoinSLogger {
* which is how Bitcoin Core handles this
*/
def countsTowardsScriptOpLimit(token: ScriptToken): Boolean = token match {
case scriptOp: ScriptOperation if (scriptOp.opCode > OP_16.opCode) => true
case _: ScriptToken => false
case scriptOp: ScriptOperation if scriptOp.opCode > OP_16.opCode => true
case _: ScriptToken => false
}
/**
@ -130,8 +143,9 @@ trait BitcoinScriptUtil extends BitcoinSLogger {
def numPossibleSignaturesOnStack(
program: ExecutionInProgressScriptProgram): ScriptNumber = {
require(
program.script.headOption == Some(OP_CHECKMULTISIG) || program.script.headOption == Some(
OP_CHECKMULTISIGVERIFY),
program.script.headOption
.contains(OP_CHECKMULTISIG) || program.script.headOption
.contains(OP_CHECKMULTISIGVERIFY),
"We can only parse the nubmer of signatures the stack when we are executing a OP_CHECKMULTISIG or OP_CHECKMULTISIGVERIFY op"
)
val nPossibleSignatures: ScriptNumber = program.stack.head match {
@ -151,8 +165,9 @@ trait BitcoinScriptUtil extends BitcoinSLogger {
def numRequiredSignaturesOnStack(
program: ExecutionInProgressScriptProgram): ScriptNumber = {
require(
program.script.headOption == Some(OP_CHECKMULTISIG) || program.script.headOption == Some(
OP_CHECKMULTISIGVERIFY),
program.script.headOption
.contains(OP_CHECKMULTISIG) || program.script.headOption
.contains(OP_CHECKMULTISIGVERIFY),
"We can only parse the nubmer of signatures the stack when we are executing a OP_CHECKMULTISIG or OP_CHECKMULTISIGVERIFY op"
)
val nPossibleSignatures = numPossibleSignaturesOnStack(program)
@ -209,18 +224,18 @@ trait BitcoinScriptUtil extends BitcoinSLogger {
//so we can push the constant "00" or "81" onto the stack with a BytesToPushOntoStack pushop
pushOp == BytesToPushOntoStack(1)
case _: ScriptToken
if (token.bytes.size == 1 && ScriptNumberOperation
if token.bytes.size == 1 && ScriptNumberOperation
.fromNumber(token.toLong.toInt)
.isDefined) =>
.isDefined =>
//could have used the ScriptNumberOperation to push the number onto the stack
false
case token: ScriptToken =>
token.bytes.size match {
case size if (size == 0) => pushOp == OP_0
case size if (size <= 75) => token.bytes.size == pushOp.toLong
case size if (size <= 255) => pushOp == OP_PUSHDATA1
case size if (size <= 65535) => pushOp == OP_PUSHDATA2
case _: Long =>
case size if size == 0 => pushOp == OP_0
case size if size <= 75 => token.bytes.size == pushOp.toLong
case size if size <= 255 => pushOp == OP_PUSHDATA1
case size if size <= 65535 => pushOp == OP_PUSHDATA2
case _: Long =>
//default case is true because we have to use the largest push op as possible which is OP_PUSHDATA4
true
}
@ -270,7 +285,7 @@ trait BitcoinScriptUtil extends BitcoinSLogger {
// If the most-significant-byte - excluding the sign bit - is zero
// then we're not minimal. Note how this test also rejects the
// negative-zero encoding, 0x80.
if ((bytes.size > 0 && (bytes.last & 0x7f) == 0)) {
if (bytes.size > 0 && (bytes.last & 0x7f) == 0) {
// One exception: if there's more than one byte and the most
// significant bit of the second-most-significant-byte is set
// it would conflict with the sign bit. An example of this case
@ -548,15 +563,13 @@ trait BitcoinScriptUtil extends BitcoinSLogger {
* [[org.bitcoins.core.script.crypto.OP_CHECKMULTISIG OP_CHECKMULTISIG]] with
* [[org.bitcoins.core.script.constant.ScriptNumber.zero ScriptNumber.zero]] */
def minimalDummy(asm: Seq[ScriptToken]): Seq[ScriptToken] = {
if (asm.headOption == Some(OP_0)) ScriptNumber.zero +: asm.tail
if (asm.headOption.contains(OP_0)) ScriptNumber.zero +: asm.tail
else asm
}
/**
* Checks that all the [[org.bitcoins.crypto.ECPublicKey ECPublicKey]] in this script
* is compressed public keys, this is required for BIP143
* @param spk
* @return
*/
def isOnlyCompressedPubKey(spk: ScriptPubKey): Boolean = {
spk match {
@ -564,7 +577,7 @@ trait BitcoinScriptUtil extends BitcoinSLogger {
case p2pkWithTimeout: P2PKWithTimeoutScriptPubKey =>
p2pkWithTimeout.pubKey.isCompressed && p2pkWithTimeout.timeoutPubKey.isCompressed
case m: MultiSignatureScriptPubKey =>
!m.publicKeys.exists(k => !k.isCompressed)
m.publicKeys.forall(_.isCompressed)
case l: LockTimeScriptPubKey =>
isOnlyCompressedPubKey(l.nestedScriptPubKey)
case conditional: ConditionalScriptPubKey =>
@ -592,6 +605,62 @@ trait BitcoinScriptUtil extends BitcoinSLogger {
val script: Vector[ScriptToken] = ScriptParser.fromBytes(scriptPubKeyBytes)
f(script)
}
def verifyScript(
tx: Transaction,
utxos: Seq[ScriptSignatureParams[InputInfo]]): Boolean = {
val programs: Seq[PreExecutionScriptProgram] = tx.inputs.zipWithIndex.map {
case (input: TransactionInput, idx: Int) =>
val outpoint = input.previousOutput
val creditingTx = utxos.find(u => u.outPoint == outpoint).get
val output = creditingTx.output
val spk = output.scriptPubKey
val amount = output.value
val txSigComponent = spk match {
case witSPK: WitnessScriptPubKeyV0 =>
val o = TransactionOutput(amount, witSPK)
WitnessTxSigComponentRaw(tx.asInstanceOf[WitnessTransaction],
UInt32(idx),
o,
Policy.standardFlags)
case _: UnassignedWitnessScriptPubKey => ???
case x @ (_: P2PKScriptPubKey | _: P2PKHScriptPubKey |
_: P2PKWithTimeoutScriptPubKey | _: MultiSignatureScriptPubKey |
_: WitnessCommitment | _: CSVScriptPubKey | _: CLTVScriptPubKey |
_: ConditionalScriptPubKey | _: NonStandardScriptPubKey |
EmptyScriptPubKey) =>
val o = TransactionOutput(CurrencyUnits.zero, x)
BaseTxSigComponent(tx, UInt32(idx), o, Policy.standardFlags)
case _: P2SHScriptPubKey =>
val p2shScriptSig =
tx.inputs(idx).scriptSignature.asInstanceOf[P2SHScriptSignature]
p2shScriptSig.redeemScript match {
case _: WitnessScriptPubKey =>
WitnessTxSigComponentP2SH(transaction =
tx.asInstanceOf[WitnessTransaction],
inputIndex = UInt32(idx),
output = output,
flags = Policy.standardFlags)
case _ =>
BaseTxSigComponent(tx,
UInt32(idx),
output,
Policy.standardFlags)
}
}
PreExecutionScriptProgram(txSigComponent)
}
ScriptInterpreter.runAllVerify(programs)
}
}
object BitcoinScriptUtil extends BitcoinScriptUtil

View File

@ -60,7 +60,8 @@ case class RawTxBuilder() {
/** Returns a RawTxBuilderWithFinalizer where building can continue
* and where buildTx can be called once building is completed. */
def setFinalizer(finalizer: RawTxFinalizer): RawTxBuilderWithFinalizer = {
def setFinalizer[F <: RawTxFinalizer](
finalizer: F): RawTxBuilderWithFinalizer[F] = {
RawTxBuilderWithFinalizer(this, finalizer)
}
@ -141,9 +142,9 @@ case class RawTxBuilder() {
* access to the RawTxFinalizer's buildTx method which
* completes the RawTxBuilder and then finalized the result.
*/
case class RawTxBuilderWithFinalizer(
case class RawTxBuilderWithFinalizer[F <: RawTxFinalizer](
builder: RawTxBuilder,
finalizer: RawTxFinalizer) {
finalizer: F) {
/** Completes the builder and finalizes the result */
def buildTx()(implicit ec: ExecutionContext): Future[Transaction] = {

View File

@ -1,6 +1,7 @@
package org.bitcoins.core.wallet.builder
import org.bitcoins.core.currency.{CurrencyUnit, Satoshis}
import org.bitcoins.core.number.Int64
import org.bitcoins.core.policy.Policy
import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.core.protocol.transaction._
@ -248,7 +249,8 @@ object StandardNonInteractiveFinalizer {
outputs: Seq[TransactionOutput],
utxos: Seq[InputSigningInfo[InputInfo]],
feeRate: FeeUnit,
changeSPK: ScriptPubKey): RawTxBuilderWithFinalizer = {
changeSPK: ScriptPubKey): RawTxBuilderWithFinalizer[
StandardNonInteractiveFinalizer] = {
val inputs = InputUtil.calcSequenceForInputs(utxos, Policy.isRBFEnabled)
val lockTime = TxUtil.calcLockTime(utxos).get
val builder = RawTxBuilder().setLockTime(lockTime) ++= outputs ++= inputs
@ -271,3 +273,67 @@ object StandardNonInteractiveFinalizer {
builderF.flatMap(_.buildTx())
}
}
case class SubtractFeeFromOutputsFinalizer(
inputInfos: Vector[InputInfo],
feeRate: FeeUnit)
extends RawTxFinalizer {
override def buildTx(txBuilderResult: RawTxBuilderResult)(
implicit ec: ExecutionContext): Future[Transaction] = {
val RawTxBuilderResult(version, inputs, outputs, lockTime) = txBuilderResult
val witnesses = inputInfos.map(InputInfo.getScriptWitness)
val txWithPossibleWitness = TransactionWitness.fromWitOpt(witnesses) match {
case _: EmptyWitness =>
BaseTransaction(version, inputs, outputs, lockTime)
case wit: TransactionWitness =>
WitnessTransaction(version, inputs, outputs, lockTime, wit)
}
val dummyTxF = TxUtil.addDummySigs(txWithPossibleWitness, inputInfos)
val outputsAfterFeeF = dummyTxF.map { dummyTx =>
SubtractFeeFromOutputsFinalizer.subtractFees(dummyTx,
feeRate,
outputs.map(_.scriptPubKey))
}
outputsAfterFeeF.map { outputsAfterFee =>
BaseTransaction(version, inputs, outputsAfterFee, lockTime)
}
}
}
object SubtractFeeFromOutputsFinalizer {
def subtractFees(
tx: Transaction,
feeRate: FeeUnit,
spks: Vector[ScriptPubKey]): Vector[TransactionOutput] = {
val fee = feeRate.calc(tx)
val outputs = tx.outputs.zipWithIndex.filter {
case (output, _) => spks.contains(output.scriptPubKey)
}
val unchangedOutputs = tx.outputs.zipWithIndex.filterNot {
case (output, _) => spks.contains(output.scriptPubKey)
}
val feePerOutput = Satoshis(Int64(fee.satoshis.toLong / outputs.length))
val feeRemainder = Satoshis(Int64(fee.satoshis.toLong % outputs.length))
val newOutputsWithoutRemainder = outputs.map {
case (output, index) =>
(TransactionOutput(output.value - feePerOutput, output.scriptPubKey),
index)
}
val (lastOutput, lastOutputIndex) = newOutputsWithoutRemainder.last
val newLastOutput = TransactionOutput(lastOutput.value - feeRemainder,
lastOutput.scriptPubKey)
val newOutputs = newOutputsWithoutRemainder
.dropRight(1)
.:+((newLastOutput, lastOutputIndex))
(newOutputs ++ unchangedOutputs).sortBy(_._2).map(_._1).toVector
}
}

View File

@ -81,6 +81,10 @@ case class ECSignatureParams[+InputType <: InputInfo](
extends InputSigningInfo[InputType] {
override def signers: Vector[Sign] = Vector(signer)
def toScriptSignatureParams: ScriptSignatureParams[InputType] = {
ScriptSignatureParams(inputInfo, signer, hashType)
}
def mapInfo[T <: InputInfo](func: InputType => T): ECSignatureParams[T] = {
this.copy(inputInfo = func(this.inputInfo))
}

View File

@ -1,26 +1,30 @@
package org.bitcoins.db
import org.bitcoins.commons.jsonmodels.dlc.DLCMessage.ContractInfo
import org.bitcoins.core.config.{BitcoinNetwork, BitcoinNetworks}
import org.bitcoins.core.crypto._
import org.bitcoins.core.currency.{CurrencyUnit, Satoshis}
import org.bitcoins.core.gcs.FilterType
import org.bitcoins.core.hd._
import org.bitcoins.core.number.{Int32, UInt32, UInt64}
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.script.{ScriptPubKey, ScriptWitness}
import org.bitcoins.core.protocol.transaction.{
Transaction,
TransactionOutPoint,
TransactionOutput
}
import org.bitcoins.core.protocol.{
Bech32Address,
BitcoinAddress,
BlockTimeStamp
}
import org.bitcoins.core.psbt.InputPSBTMap
import org.bitcoins.core.psbt.InputPSBTRecord.PartialSignature
import org.bitcoins.core.script.ScriptType
import org.bitcoins.core.serializers.script.RawScriptWitnessParser
import org.bitcoins.core.wallet.fee.SatoshisPerByte
import org.bitcoins.core.wallet.fee.{SatoshisPerByte, SatoshisPerVirtualByte}
import org.bitcoins.core.wallet.utxo.TxoState
import org.bitcoins.crypto.{
DoubleSha256DigestBE,
ECPublicKey,
Sha256Hash160Digest
}
import org.bitcoins.crypto._
import scodec.bits.ByteVector
import slick.jdbc.{GetResult, JdbcProfile}
@ -76,9 +80,19 @@ class DbCommonsColumnMappers(val profile: JdbcProfile) {
MappedColumnType
.base[BigInt, BigDecimal](BigDecimal(_), _.toBigInt)
implicit val sha256DigestBEMapper: BaseColumnType[Sha256DigestBE] =
MappedColumnType.base[Sha256DigestBE, String](_.hex, Sha256DigestBE.fromHex)
implicit val ecPublicKeyMapper: BaseColumnType[ECPublicKey] =
MappedColumnType.base[ECPublicKey, String](_.hex, ECPublicKey.fromHex)
implicit val schnorrPublicKeyMapper: BaseColumnType[SchnorrPublicKey] =
MappedColumnType
.base[SchnorrPublicKey, String](_.hex, SchnorrPublicKey.fromHex)
implicit val schnorrNonceMapper: BaseColumnType[SchnorrNonce] =
MappedColumnType.base[SchnorrNonce, String](_.hex, SchnorrNonce.fromHex)
implicit val sha256Hash160DigestMapper: BaseColumnType[Sha256Hash160Digest] =
MappedColumnType
.base[Sha256Hash160Digest, String](_.hex, Sha256Hash160Digest.fromHex)
@ -150,7 +164,7 @@ class DbCommonsColumnMappers(val profile: JdbcProfile) {
implicit val segwitPathMappper: BaseColumnType[SegWitHDPath] =
MappedColumnType
.base[SegWitHDPath, String](_.toString, SegWitHDPath.fromString(_)) // hm rethink .get?
.base[SegWitHDPath, String](_.toString, SegWitHDPath.fromString) // hm rethink .get?
implicit val hdChainTypeMapper: BaseColumnType[HDChainType] =
MappedColumnType.base[HDChainType, Int](_.index, HDChainType.fromInt)
@ -163,6 +177,10 @@ class DbCommonsColumnMappers(val profile: JdbcProfile) {
MappedColumnType
.base[BitcoinAddress, String](_.value, BitcoinAddress.fromStringExn)
implicit val bech32AddressMapper: BaseColumnType[Bech32Address] =
MappedColumnType
.base[Bech32Address, String](_.value, Bech32Address.fromStringExn)
implicit val scriptTypeMapper: BaseColumnType[ScriptType] =
MappedColumnType
.base[ScriptType, String](_.toString, ScriptType.fromStringExn)
@ -187,4 +205,55 @@ class DbCommonsColumnMappers(val profile: JdbcProfile) {
MappedColumnType
.base[SatoshisPerByte, Long](_.toLong, SatoshisPerByte.fromLong)
}
implicit val hdAccountMapper: BaseColumnType[HDAccount] = {
MappedColumnType.base[HDAccount, String](
_.toString,
str => HDAccount.fromPath(BIP32Path.fromString(str)).get)
}
implicit val contractInfoMapper: BaseColumnType[ContractInfo] = {
MappedColumnType
.base[ContractInfo, String](_.hex, ContractInfo.fromHex)
}
implicit val blockStampWithFutureMapper: BaseColumnType[BlockTimeStamp] = {
MappedColumnType.base[BlockTimeStamp, Long](
_.toUInt32.toLong,
long => BlockTimeStamp(UInt32(long)))
}
implicit val partialSigMapper: BaseColumnType[PartialSignature] = {
MappedColumnType
.base[PartialSignature, String](_.hex, PartialSignature.fromHex)
}
implicit val partialSigsMapper: BaseColumnType[Vector[PartialSignature]] = {
MappedColumnType
.base[Vector[PartialSignature], String](
_.foldLeft("")(_ ++ _.hex),
hex =>
if (hex.isEmpty) Vector.empty
else InputPSBTMap(hex ++ "00").partialSignatures)
}
implicit val satoshisPerVirtualByteMapper: BaseColumnType[
SatoshisPerVirtualByte] = {
MappedColumnType
.base[SatoshisPerVirtualByte, String](
_.currencyUnit.hex,
hex => SatoshisPerVirtualByte(Satoshis.fromHex(hex)))
}
implicit val networkMapper: BaseColumnType[BitcoinNetwork] = {
MappedColumnType
.base[BitcoinNetwork, String](_.name, BitcoinNetworks.fromString)
}
implicit val schnorrDigitalSignatureMapper: BaseColumnType[
SchnorrDigitalSignature] = {
MappedColumnType.base[SchnorrDigitalSignature, String](
_.hex,
SchnorrDigitalSignature.fromHex)
}
}

View File

@ -140,7 +140,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
val datadir = conf.datadir
val written = BitcoindConfig.writeConfigToFile(conf, datadir)
logger.debug(s"Wrote conf to ${written}")
logger.debug(s"Wrote conf to $written")
written
}
@ -178,7 +178,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
if (filtered.isEmpty)
throw new RuntimeException(
s"bitcoind ${known.toString} is not installed in ${binaryDirectory}. Run `sbt downloadBitcoind`")
s"bitcoind ${known.toString} is not installed in $binaryDirectory. Run `sbt downloadBitcoind`")
// might be multiple versions downloaded for
// each major version, i.e. 0.16.2 and 0.16.3
@ -187,7 +187,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
versionFolder
.resolve("bin")
.resolve(if (Properties.isWin) "bitcoind.exe" else "bitcoind")
.toFile()
.toFile
}
/** Creates a `bitcoind` instance within the user temporary directory */
@ -229,7 +229,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
authCredentials = auth,
zmqConfig = ZmqConfig.fromPort(zmqPort),
binary = binary,
datadir = configFile.getParent.toFile())
datadir = configFile.getParent.toFile)
instance
}
@ -485,7 +485,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
.map(info => info.isEmpty || info.head.connected.contains(false))
.recoverWith {
case exception: BitcoindException
if exception.getMessage.contains("Node has not been added") =>
if exception.getMessage().contains("Node has not been added") =>
from.getPeerInfo.map(
_.forall(_.networkInfo.addr != to.instance.uri))
}
@ -848,6 +848,19 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
.flatMap(node.getBlockWithTransactions)
}
/** Mines blocks until the specified block height. */
def waitUntilBlock(
blockHeight: Int,
client: BitcoindRpcClient,
addressForMining: BitcoinAddress)(
implicit ec: ExecutionContext): Future[Unit] = {
for {
currentCount <- client.getBlockCount
blocksToMine = blockHeight - currentCount
_ <- client.generateToAddress(blocks = blocksToMine, addressForMining)
} yield ()
}
/**
* Produces a confirmed transaction from `sender` to `address`
* for `amount`
@ -941,7 +954,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
implicit system: ActorSystem): Future[BitcoindRpcClient] = {
implicit val ec: ExecutionContextExecutor = system.dispatcher
require(
instance.datadir.getPath().startsWith(Properties.tmpDir),
instance.datadir.getPath.startsWith(Properties.tmpDir),
s"${instance.datadir} is not in user temp dir! This could lead to bad things happening.")
//start the bitcoind instance so eclair can properly use it

View File

@ -209,7 +209,7 @@ abstract class Wallet
* finalizing and signing the transaction, then correctly processing and logging it
*/
private def finishSend(
txBuilder: RawTxBuilderWithFinalizer,
txBuilder: RawTxBuilderWithFinalizer[StandardNonInteractiveFinalizer],
utxoInfos: Vector[ScriptSignatureParams[InputInfo]],
sentAmount: CurrencyUnit,
feeRate: FeeUnit): Future[Transaction] = {

View File

@ -70,8 +70,9 @@ trait FundTransactionHandling extends WalletLogger { self: WalletApi =>
fromAccount: AccountDb,
keyManagerOpt: Option[BIP39KeyManager],
coinSelectionAlgo: CoinSelectionAlgo = CoinSelectionAlgo.AccumulateLargest,
markAsReserved: Boolean = false): Future[
(RawTxBuilderWithFinalizer, Vector[ScriptSignatureParams[InputInfo]])] = {
markAsReserved: Boolean = false): Future[(
RawTxBuilderWithFinalizer[StandardNonInteractiveFinalizer],
Vector[ScriptSignatureParams[InputInfo]])] = {
val utxosF = for {
utxos <- listUtxos(fromAccount.hdAccount)

View File

@ -44,6 +44,14 @@ private[wallet] trait UtxoHandling extends WalletLogger {
spendingInfoDAO.findAllUnspentForAccount(hdAccount)
}
/** Returns all the utxos originating from the given outpoints */
def listUtxos(outPoints: Vector[TransactionOutPoint]): Future[
Vector[SpendingInfoDb]] = {
spendingInfoDAO
.findAll()
.map(_.filter(spendingInfo => outPoints.contains(spendingInfo.outPoint)))
}
protected def updateUtxoConfirmedState(
txo: SpendingInfoDb,
blockHash: DoubleSha256DigestBE): Future[SpendingInfoDb] = {