Pulled down core diff from adaptor-dlc (#3038)

This commit is contained in:
Nadav Kohen 2021-05-05 09:26:09 -05:00 committed by GitHub
parent 1cda5cbf1e
commit aacba1c077
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 552 additions and 194 deletions

View file

@ -0,0 +1,97 @@
package org.bitcoins.core.protocol.dlc
import org.bitcoins.core.currency.Satoshis
import org.bitcoins.core.protocol.tlv.{EnumOutcome, OracleAnnouncementV0TLV}
import org.bitcoins.crypto.ECPrivateKey
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
class ContractOraclePairTest extends BitcoinSUnitTest {
behavior of "ContractOraclePair"
it should "not be able to construct an invalid enum contract oracle pair" in {
val contractDescriptor = EnumContractDescriptor(
Vector("Never", "gonna", "give", "you", "up").map(
EnumOutcome(_) -> Satoshis.zero))
def enumOracleInfo(outcomes: Vector[String]): EnumSingleOracleInfo = {
EnumSingleOracleInfo(
OracleAnnouncementV0TLV.dummyForEventsAndKeys(
ECPrivateKey.freshPrivateKey,
ECPrivateKey.freshPrivateKey.schnorrNonce,
outcomes.map(EnumOutcome.apply)))
}
val oracleInfo1 =
enumOracleInfo(Vector("Never", "gonna", "let", "you", "down"))
val oracleInfo2 = enumOracleInfo(
Vector("Never", "gonna", "run", "around", "and", "desert", "you"))
val oracleInfo3 =
enumOracleInfo(Vector("Never", "gonna", "make", "you", "cry"))
val multiOracleInfo = EnumMultiOracleInfo(
2,
Vector(oracleInfo1, oracleInfo2, oracleInfo3).map(_.announcement))
assertThrows[IllegalArgumentException](
ContractOraclePair.EnumPair(contractDescriptor, oracleInfo1)
)
assertThrows[IllegalArgumentException](
ContractOraclePair.EnumPair(contractDescriptor, oracleInfo2)
)
assertThrows[IllegalArgumentException](
ContractOraclePair.EnumPair(contractDescriptor, oracleInfo3)
)
assertThrows[IllegalArgumentException](
ContractOraclePair.EnumPair(contractDescriptor, multiOracleInfo)
)
}
it should "not be able to construct an invalid numeric contract oracle pair" in {
val contractDescriptor =
NumericContractDescriptor(
DLCPayoutCurve(
Vector(OutcomePayoutEndpoint(0, 0),
OutcomePayoutEndpoint((1L << 7) - 1, 1))),
numDigits = 7,
RoundingIntervals.noRounding)
def numericOracleInfo(numDigits: Int): NumericSingleOracleInfo = {
NumericSingleOracleInfo(
OracleAnnouncementV0TLV.dummyForKeys(
ECPrivateKey.freshPrivateKey,
Vector.fill(numDigits)(ECPrivateKey.freshPrivateKey.schnorrNonce)))
}
val oracleInfo1 = numericOracleInfo(1)
val oracleInfo2 = numericOracleInfo(2)
val oracleInfo3 = numericOracleInfo(3)
val announcements =
Vector(oracleInfo1, oracleInfo2, oracleInfo3).map(_.announcement)
val multiOracleInfoExact = NumericExactMultiOracleInfo(2, announcements)
val multiOracleInfo = NumericMultiOracleInfo(2,
announcements,
maxErrorExp = 2,
minFailExp = 1,
maximizeCoverage = true)
assertThrows[IllegalArgumentException](
ContractOraclePair.NumericPair(contractDescriptor, oracleInfo1)
)
assertThrows[IllegalArgumentException](
ContractOraclePair.NumericPair(contractDescriptor, oracleInfo2)
)
assertThrows[IllegalArgumentException](
ContractOraclePair.NumericPair(contractDescriptor, oracleInfo3)
)
assertThrows[IllegalArgumentException](
ContractOraclePair.NumericPair(contractDescriptor, multiOracleInfoExact)
)
assertThrows[IllegalArgumentException](
ContractOraclePair.NumericPair(contractDescriptor, multiOracleInfo)
)
}
}

View file

@ -1,7 +1,7 @@
package org.bitcoins.core.protocol.dlc package org.bitcoins.core.protocol.dlc
import org.bitcoins.core.currency.Satoshis import org.bitcoins.core.currency.Satoshis
import org.bitcoins.core.number.UInt32 import org.bitcoins.core.number.{UInt32, UInt64}
import org.bitcoins.core.protocol.BitcoinAddress import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.BlockStamp.{BlockHeight, BlockTime} import org.bitcoins.core.protocol.BlockStamp.{BlockHeight, BlockTime}
import org.bitcoins.core.protocol.dlc.DLCMessage._ import org.bitcoins.core.protocol.dlc.DLCMessage._
@ -41,13 +41,32 @@ class DLCMessageTest extends BitcoinSJvmTest {
it must "not allow a negative collateral for a DLCOffer" in { it must "not allow a negative collateral for a DLCOffer" in {
assertThrows[IllegalArgumentException]( assertThrows[IllegalArgumentException](
DLCOffer( DLCOffer(
ContractInfo.dummy, contractInfo = ContractInfo.dummy,
DLCPublicKeys(dummyPubKey, dummyAddress), pubKeys = DLCPublicKeys(dummyPubKey, dummyAddress),
Satoshis(-1), totalCollateral = Satoshis(-1),
Vector.empty, fundingInputs = Vector.empty,
dummyAddress, changeAddress = dummyAddress,
SatoshisPerVirtualByte.one, payoutSerialId = UInt64.zero,
DLCTimeouts(BlockHeight(1), BlockHeight(2)) changeSerialId = UInt64.one,
fundOutputSerialId = UInt64.max,
feeRate = SatoshisPerVirtualByte.one,
timeouts = DLCTimeouts(BlockHeight(1), BlockHeight(2))
))
}
it must "not allow same change and fund output serial id for a DLCOffer" in {
assertThrows[IllegalArgumentException](
DLCOffer(
contractInfo = ContractInfo.dummy,
pubKeys = DLCPublicKeys(dummyPubKey, dummyAddress),
totalCollateral = Satoshis(-1),
fundingInputs = Vector.empty,
changeAddress = dummyAddress,
payoutSerialId = UInt64.zero,
changeSerialId = UInt64.one,
fundOutputSerialId = UInt64.one,
feeRate = SatoshisPerVirtualByte.one,
timeouts = DLCTimeouts(BlockHeight(1), BlockHeight(2))
)) ))
} }
@ -58,6 +77,8 @@ class DLCMessageTest extends BitcoinSJvmTest {
DLCPublicKeys(dummyPubKey, dummyAddress), DLCPublicKeys(dummyPubKey, dummyAddress),
Vector.empty, Vector.empty,
dummyAddress, dummyAddress,
payoutSerialId = UInt64.zero,
changeSerialId = UInt64.one,
CETSignatures(Vector( CETSignatures(Vector(
EnumOracleOutcome( EnumOracleOutcome(
Vector(dummyOracle), Vector(dummyOracle),

View file

@ -112,7 +112,7 @@ class TLVTest extends BitcoinSUnitTest {
} }
"FundingInputV0TLV" must "have serialization symmetry" in { "FundingInputV0TLV" must "have serialization symmetry" in {
forAll(TLVGen.fundingInputV0TLV) { fundingInput => forAll(TLVGen.fundingInputV0TLV()) { fundingInput =>
assert(FundingInputV0TLV(fundingInput.bytes) == fundingInput) assert(FundingInputV0TLV(fundingInput.bytes) == fundingInput)
assert(TLV(fundingInput.bytes) == fundingInput) assert(TLV(fundingInput.bytes) == fundingInput)
} }

View file

@ -1,5 +1,11 @@
package org.bitcoins.core.protocol.dlc package org.bitcoins.core.protocol.dlc
import org.bitcoins.core.protocol.tlv.{
EnumEventDescriptorV0TLV,
EnumOutcome,
NumericEventDescriptorTLV
}
/** A pair of [[ContractDescriptor]] and [[OracleInfo]] /** A pair of [[ContractDescriptor]] and [[OracleInfo]]
* This type is meant to ensure consistentcy between various * This type is meant to ensure consistentcy between various
* [[ContractDescriptor]] and [[OracleInfo]] so that you cannot * [[ContractDescriptor]] and [[OracleInfo]] so that you cannot
@ -15,12 +21,39 @@ object ContractOraclePair {
case class EnumPair( case class EnumPair(
contractDescriptor: EnumContractDescriptor, contractDescriptor: EnumContractDescriptor,
oracleInfo: EnumOracleInfo) oracleInfo: EnumOracleInfo)
extends ContractOraclePair extends ContractOraclePair {
private val descriptorOutcomes =
contractDescriptor.map(_._1).sortBy(_.outcome)
private val isValid = oracleInfo.singleOracleInfos.forall { singleInfo =>
val announcementOutcomes =
singleInfo.announcement.eventTLV.eventDescriptor
.asInstanceOf[EnumEventDescriptorV0TLV]
.outcomes
.map(EnumOutcome(_))
.sortBy(_.outcome)
announcementOutcomes == descriptorOutcomes
}
require(isValid, s"OracleInfo did not match ContractDescriptor: $this")
}
case class NumericPair( case class NumericPair(
contractDescriptor: NumericContractDescriptor, contractDescriptor: NumericContractDescriptor,
oracleInfo: NumericOracleInfo) oracleInfo: NumericOracleInfo)
extends ContractOraclePair extends ContractOraclePair {
private val isValid = oracleInfo.singleOracleInfos.forall { singleInfo =>
val announcementDescriptor =
singleInfo.announcement.eventTLV.eventDescriptor
.asInstanceOf[NumericEventDescriptorTLV]
announcementDescriptor.base.toInt == 2 && announcementDescriptor.noncesNeeded == contractDescriptor.numDigits
}
require(isValid, s"OracleInfo did not match ContractDescriptor: $this")
}
/** Returns a valid [[ContractOraclePair]] if the /** Returns a valid [[ContractOraclePair]] if the
* [[ContractDescriptor]] and [[OracleInfo]] are of the same type * [[ContractDescriptor]] and [[OracleInfo]] are of the same type
@ -50,7 +83,7 @@ object ContractOraclePair {
case Some(pair) => pair case Some(pair) => pair
case None => case None =>
sys.error( sys.error(
s"You passed in an incompatible contract/oracle pair, contract=$descriptor, oracle=${oracleInfo}") s"You passed in an incompatible contract/oracle pair, contract=$descriptor, oracle=$oracleInfo")
} }
} }
} }

View file

@ -281,4 +281,13 @@ object ContractInfo
ContractInfo(totalCollateral = enumDescriptor.values.maxBy(_.toLong), ContractInfo(totalCollateral = enumDescriptor.values.maxBy(_.toLong),
enumPair) enumPair)
} }
def apply(
totalCollateral: Satoshis,
contractDescriptor: ContractDescriptor,
oracleInfo: OracleInfo): ContractInfo = {
ContractInfo(
totalCollateral,
ContractOraclePair.fromDescriptorOracle(contractDescriptor, oracleInfo))
}
} }

View file

@ -1,6 +1,6 @@
package org.bitcoins.core.protocol.dlc package org.bitcoins.core.protocol.dlc
import org.bitcoins.core.number.{UInt16, UInt32} import org.bitcoins.core.number.{UInt16, UInt32, UInt64}
import org.bitcoins.core.protocol.script._ import org.bitcoins.core.protocol.script._
import org.bitcoins.core.protocol.tlv.FundingInputV0TLV import org.bitcoins.core.protocol.tlv.FundingInputV0TLV
import org.bitcoins.core.protocol.transaction._ import org.bitcoins.core.protocol.transaction._
@ -8,6 +8,7 @@ import org.bitcoins.core.wallet.builder.DualFundingInput
import org.bitcoins.core.wallet.utxo.{InputInfo, ScriptSignatureParams} import org.bitcoins.core.wallet.utxo.{InputInfo, ScriptSignatureParams}
sealed trait DLCFundingInput { sealed trait DLCFundingInput {
def inputSerialId: UInt64
def prevTx: Transaction def prevTx: Transaction
def prevTxVout: UInt32 def prevTxVout: UInt32
def sequence: UInt32 def sequence: UInt32
@ -39,6 +40,7 @@ sealed trait DLCFundingInput {
lazy val toTLV: FundingInputV0TLV = { lazy val toTLV: FundingInputV0TLV = {
FundingInputV0TLV( FundingInputV0TLV(
inputSerialId,
prevTx, prevTx,
prevTxVout, prevTxVout,
sequence, sequence,
@ -54,6 +56,7 @@ sealed trait DLCFundingInput {
object DLCFundingInput { object DLCFundingInput {
def apply( def apply(
inputSerialId: UInt64,
prevTx: Transaction, prevTx: Transaction,
prevTxVout: UInt32, prevTxVout: UInt32,
sequence: UInt32, sequence: UInt32,
@ -63,7 +66,19 @@ object DLCFundingInput {
case _: P2SHScriptPubKey => case _: P2SHScriptPubKey =>
redeemScriptOpt match { redeemScriptOpt match {
case Some(redeemScript) => case Some(redeemScript) =>
DLCFundingInputP2SHSegwit(prevTx, redeemScript match {
case _: P2WPKHWitnessSPKV0 =>
require(
maxWitnessLen == UInt16(107) || maxWitnessLen == UInt16(108),
s"P2WPKH max witness length must be 107 or 108, got $maxWitnessLen")
case _: P2WSHWitnessSPKV0 => ()
case spk: UnassignedWitnessScriptPubKey =>
throw new IllegalArgumentException(
s"Unknown segwit version: $spk")
}
DLCFundingInputP2SHSegwit(inputSerialId,
prevTx,
prevTxVout, prevTxVout,
sequence, sequence,
maxWitnessLen, maxWitnessLen,
@ -76,9 +91,13 @@ object DLCFundingInput {
require( require(
maxWitnessLen == UInt16(107) || maxWitnessLen == UInt16(108), maxWitnessLen == UInt16(107) || maxWitnessLen == UInt16(108),
s"P2WPKH max witness length must be 107 or 108, got $maxWitnessLen") s"P2WPKH max witness length must be 107 or 108, got $maxWitnessLen")
DLCFundingInputP2WPKHV0(prevTx, prevTxVout, sequence) DLCFundingInputP2WPKHV0(inputSerialId, prevTx, prevTxVout, sequence)
case _: P2WSHWitnessSPKV0 => case _: P2WSHWitnessSPKV0 =>
DLCFundingInputP2WSHV0(prevTx, prevTxVout, sequence, maxWitnessLen) DLCFundingInputP2WSHV0(inputSerialId,
prevTx,
prevTxVout,
sequence,
maxWitnessLen)
case spk: UnassignedWitnessScriptPubKey => case spk: UnassignedWitnessScriptPubKey =>
throw new IllegalArgumentException(s"Unknown segwit version: $spk") throw new IllegalArgumentException(s"Unknown segwit version: $spk")
case spk: RawScriptPubKey => case spk: RawScriptPubKey =>
@ -88,6 +107,7 @@ object DLCFundingInput {
def fromTLV(fundingInput: FundingInputV0TLV): DLCFundingInput = { def fromTLV(fundingInput: FundingInputV0TLV): DLCFundingInput = {
DLCFundingInput( DLCFundingInput(
fundingInput.inputSerialId,
fundingInput.prevTx, fundingInput.prevTx,
fundingInput.prevTxVout, fundingInput.prevTxVout,
fundingInput.sequence, fundingInput.sequence,
@ -98,8 +118,10 @@ object DLCFundingInput {
def fromInputSigningInfo( def fromInputSigningInfo(
info: ScriptSignatureParams[InputInfo], info: ScriptSignatureParams[InputInfo],
inputSerialId: UInt64,
sequence: UInt32 = TransactionConstants.sequence): DLCFundingInput = { sequence: UInt32 = TransactionConstants.sequence): DLCFundingInput = {
DLCFundingInput( DLCFundingInput(
inputSerialId,
info.prevTransaction, info.prevTransaction,
info.outPoint.vout, info.outPoint.vout,
sequence, sequence,
@ -112,6 +134,7 @@ object DLCFundingInput {
} }
case class DLCFundingInputP2WPKHV0( case class DLCFundingInputP2WPKHV0(
inputSerialId: UInt64,
prevTx: Transaction, prevTx: Transaction,
prevTxVout: UInt32, prevTxVout: UInt32,
sequence: UInt32) sequence: UInt32)
@ -124,6 +147,7 @@ case class DLCFundingInputP2WPKHV0(
} }
case class DLCFundingInputP2WSHV0( case class DLCFundingInputP2WSHV0(
inputSerialId: UInt64,
prevTx: Transaction, prevTx: Transaction,
prevTxVout: UInt32, prevTxVout: UInt32,
sequence: UInt32, sequence: UInt32,
@ -136,6 +160,7 @@ case class DLCFundingInputP2WSHV0(
} }
case class DLCFundingInputP2SHSegwit( case class DLCFundingInputP2SHSegwit(
inputSerialId: UInt64,
prevTx: Transaction, prevTx: Transaction,
prevTxVout: UInt32, prevTxVout: UInt32,
sequence: UInt32, sequence: UInt32,
@ -149,3 +174,11 @@ case class DLCFundingInputP2SHSegwit(
override val redeemScriptOpt: Option[WitnessScriptPubKey] = Some(redeemScript) override val redeemScriptOpt: Option[WitnessScriptPubKey] = Some(redeemScript)
} }
case class SpendingInfoWithSerialId(
spendingInfo: ScriptSignatureParams[InputInfo],
serialId: UInt64) {
def toDLCFundingInput: DLCFundingInput =
DLCFundingInput.fromInputSigningInfo(spendingInfo, serialId)
}

View file

@ -2,6 +2,7 @@ package org.bitcoins.core.protocol.dlc
import org.bitcoins.core.config.{NetworkParameters, Networks} import org.bitcoins.core.config.{NetworkParameters, Networks}
import org.bitcoins.core.currency.Satoshis import org.bitcoins.core.currency.Satoshis
import org.bitcoins.core.number.UInt64
import org.bitcoins.core.protocol.BitcoinAddress import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.tlv._ import org.bitcoins.core.protocol.tlv._
import org.bitcoins.core.protocol.transaction.TransactionOutPoint import org.bitcoins.core.protocol.transaction.TransactionOutPoint
@ -11,6 +12,9 @@ import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
import org.bitcoins.crypto._ import org.bitcoins.crypto._
import scodec.bits.ByteVector import scodec.bits.ByteVector
import scala.annotation.tailrec
import scala.util.Random
sealed trait DLCMessage sealed trait DLCMessage
object DLCMessage { object DLCMessage {
@ -23,6 +27,32 @@ object DLCMessage {
.flip .flip
} }
@tailrec
def genSerialId(notEqualTo: Vector[UInt64] = Vector.empty): UInt64 = {
val rand = {
// Copy of Random.nextBytes(Int)
// Not available for older versions
val bytes = new Array[Byte](0 max 8)
Random.nextBytes(bytes)
bytes
}
val res = UInt64(ByteVector(rand))
if (notEqualTo.contains(res)) genSerialId(notEqualTo)
else res
}
@tailrec
def genSerialIds(
size: Int,
notEqualTo: Vector[UInt64] = Vector.empty): Vector[UInt64] = {
val ids = 0.until(size).toVector.map(_ => genSerialId(notEqualTo))
if (ids.distinct.size != size)
genSerialIds(size, notEqualTo)
else ids
}
sealed trait DLCSetupMessage extends DLCMessage { sealed trait DLCSetupMessage extends DLCMessage {
def pubKeys: DLCPublicKeys def pubKeys: DLCPublicKeys
@ -55,10 +85,21 @@ object DLCMessage {
totalCollateral: Satoshis, totalCollateral: Satoshis,
fundingInputs: Vector[DLCFundingInput], fundingInputs: Vector[DLCFundingInput],
changeAddress: BitcoinAddress, changeAddress: BitcoinAddress,
payoutSerialId: UInt64,
changeSerialId: UInt64,
fundOutputSerialId: UInt64,
feeRate: SatoshisPerVirtualByte, feeRate: SatoshisPerVirtualByte,
timeouts: DLCTimeouts) timeouts: DLCTimeouts)
extends DLCSetupMessage { extends DLCSetupMessage {
require(
fundingInputs.map(_.inputSerialId).distinct.size == fundingInputs.size,
"All funding input serial ids must be unique")
require(
changeSerialId != fundOutputSerialId,
s"changeSerialId ($changeSerialId) cannot be equal to fundOutputSerialId ($fundOutputSerialId)")
val oracleInfo: OracleInfo = contractInfo.oracleInfo val oracleInfo: OracleInfo = contractInfo.oracleInfo
val contractDescriptor: ContractDescriptor = contractInfo.contractDescriptor val contractDescriptor: ContractDescriptor = contractInfo.contractDescriptor
@ -77,9 +118,12 @@ object DLCMessage {
contractInfo.toTLV, contractInfo.toTLV,
fundingPubKey = pubKeys.fundingKey, fundingPubKey = pubKeys.fundingKey,
payoutSPK = pubKeys.payoutAddress.scriptPubKey, payoutSPK = pubKeys.payoutAddress.scriptPubKey,
payoutSerialId = payoutSerialId,
totalCollateralSatoshis = totalCollateral, totalCollateralSatoshis = totalCollateral,
fundingInputs = fundingInputs.map(_.toTLV), fundingInputs = fundingInputs.map(_.toTLV),
changeSPK = changeAddress.scriptPubKey, changeSPK = changeAddress.scriptPubKey,
changeSerialId = changeSerialId,
fundOutputSerialId = fundOutputSerialId,
feeRate = feeRate, feeRate = feeRate,
contractMaturityBound = timeouts.contractMaturity, contractMaturityBound = timeouts.contractMaturity,
contractTimeout = timeouts.contractTimeout contractTimeout = timeouts.contractTimeout
@ -109,6 +153,9 @@ object DLCMessage {
}, },
changeAddress = changeAddress =
BitcoinAddress.fromScriptPubKey(offer.changeSPK, network), BitcoinAddress.fromScriptPubKey(offer.changeSPK, network),
payoutSerialId = offer.payoutSerialId,
changeSerialId = offer.changeSerialId,
fundOutputSerialId = offer.fundOutputSerialId,
feeRate = offer.feeRate, feeRate = offer.feeRate,
timeouts = timeouts =
DLCTimeouts(offer.contractMaturityBound, offer.contractTimeout) DLCTimeouts(offer.contractMaturityBound, offer.contractTimeout)
@ -125,6 +172,8 @@ object DLCMessage {
pubKeys: DLCPublicKeys, pubKeys: DLCPublicKeys,
fundingInputs: Vector[DLCFundingInput], fundingInputs: Vector[DLCFundingInput],
changeAddress: BitcoinAddress, changeAddress: BitcoinAddress,
payoutSerialId: UInt64,
changeSerialId: UInt64,
negotiationFields: DLCAccept.NegotiationFields, negotiationFields: DLCAccept.NegotiationFields,
tempContractId: Sha256Digest) { tempContractId: Sha256Digest) {
@ -134,6 +183,8 @@ object DLCMessage {
pubKeys = pubKeys, pubKeys = pubKeys,
fundingInputs = fundingInputs, fundingInputs = fundingInputs,
changeAddress = changeAddress, changeAddress = changeAddress,
payoutSerialId = payoutSerialId,
changeSerialId = changeSerialId,
cetSigs = cetSigs, cetSigs = cetSigs,
negotiationFields = negotiationFields, negotiationFields = negotiationFields,
tempContractId = tempContractId tempContractId = tempContractId
@ -146,19 +197,27 @@ object DLCMessage {
pubKeys: DLCPublicKeys, pubKeys: DLCPublicKeys,
fundingInputs: Vector[DLCFundingInput], fundingInputs: Vector[DLCFundingInput],
changeAddress: BitcoinAddress, changeAddress: BitcoinAddress,
payoutSerialId: UInt64,
changeSerialId: UInt64,
cetSigs: CETSignatures, cetSigs: CETSignatures,
negotiationFields: DLCAccept.NegotiationFields, negotiationFields: DLCAccept.NegotiationFields,
tempContractId: Sha256Digest) tempContractId: Sha256Digest)
extends DLCSetupMessage { extends DLCSetupMessage {
require(
fundingInputs.map(_.inputSerialId).distinct.size == fundingInputs.size,
"All funding input serial ids must be unique")
def toTLV: DLCAcceptTLV = { def toTLV: DLCAcceptTLV = {
DLCAcceptTLV( DLCAcceptTLV(
tempContractId = tempContractId, tempContractId = tempContractId,
totalCollateralSatoshis = totalCollateral, totalCollateralSatoshis = totalCollateral,
fundingPubKey = pubKeys.fundingKey, fundingPubKey = pubKeys.fundingKey,
payoutSPK = pubKeys.payoutAddress.scriptPubKey, payoutSPK = pubKeys.payoutAddress.scriptPubKey,
payoutSerialId = payoutSerialId,
fundingInputs = fundingInputs.map(_.toTLV), fundingInputs = fundingInputs.map(_.toTLV),
changeSPK = changeAddress.scriptPubKey, changeSPK = changeAddress.scriptPubKey,
changeSerialId = changeSerialId,
cetSignatures = CETSignaturesV0TLV(cetSigs.adaptorSigs), cetSignatures = CETSignaturesV0TLV(cetSigs.adaptorSigs),
refundSignature = ECDigitalSignature.fromFrontOfBytes( refundSignature = ECDigitalSignature.fromFrontOfBytes(
cetSigs.refundSig.signature.bytes), cetSigs.refundSig.signature.bytes),
@ -171,12 +230,16 @@ object DLCMessage {
} }
def withoutSigs: DLCAcceptWithoutSigs = { def withoutSigs: DLCAcceptWithoutSigs = {
DLCAcceptWithoutSigs(totalCollateral, DLCAcceptWithoutSigs(
pubKeys, totalCollateral = totalCollateral,
fundingInputs, pubKeys = pubKeys,
changeAddress, fundingInputs = fundingInputs,
negotiationFields, changeAddress = changeAddress,
tempContractId) payoutSerialId = payoutSerialId,
changeSerialId = changeSerialId,
negotiationFields = negotiationFields,
tempContractId = tempContractId
)
} }
} }
@ -228,6 +291,8 @@ object DLCMessage {
}, },
changeAddress = changeAddress =
BitcoinAddress.fromScriptPubKey(accept.changeSPK, network), BitcoinAddress.fromScriptPubKey(accept.changeSPK, network),
payoutSerialId = accept.payoutSerialId,
changeSerialId = accept.changeSerialId,
cetSigs = CETSignatures( cetSigs = CETSignatures(
outcomeSigs, outcomeSigs,
PartialSignature( PartialSignature(

View file

@ -1,14 +1,8 @@
package org.bitcoins.core.protocol.dlc package org.bitcoins.core.protocol.dlc
import org.bitcoins.core.currency.CurrencyUnit import org.bitcoins.core.currency.CurrencyUnit
import org.bitcoins.core.policy.Policy
import org.bitcoins.core.protocol.dlc.DLCMessage._ import org.bitcoins.core.protocol.dlc.DLCMessage._
import org.bitcoins.core.protocol.script.P2WSHWitnessV0 import org.bitcoins.core.protocol.transaction.WitnessTransaction
import org.bitcoins.core.protocol.transaction.{
NonWitnessTransaction,
Transaction,
WitnessTransaction
}
import org.bitcoins.core.wallet.fee.FeeUnit import org.bitcoins.core.wallet.fee.FeeUnit
import org.bitcoins.crypto._ import org.bitcoins.crypto._
import scodec.bits.ByteVector import scodec.bits.ByteVector
@ -217,100 +211,19 @@ object DLCStatus {
offer: DLCOffer, offer: DLCOffer,
accept: DLCAccept, accept: DLCAccept,
sign: DLCSign, sign: DLCSign,
cet: Transaction): (SchnorrDigitalSignature, OracleOutcome) = { cet: WitnessTransaction): Option[
val wCET = cet match { (SchnorrDigitalSignature, OracleOutcome)] = {
case wtx: WitnessTransaction => wtx val localAdaptorSigs = if (isInitiator) {
case _: NonWitnessTransaction => sign.cetSigs.outcomeSigs
throw new IllegalArgumentException(s"Expected Witness CET: $cet")
}
val cetSigs = wCET.witness.head
.asInstanceOf[P2WSHWitnessV0]
.signatures
require(cetSigs.size == 2,
s"There must be only 2 signatures, got ${cetSigs.size}")
/** Extracts an adaptor secret from cetSig assuming it is the completion
* adaptorSig (which it may not be) and returns the oracle signature if
* and only if adaptorSig does correspond to cetSig.
*
* This method is used to search through possible cetSigs until the correct
* one is found by validating the returned signature.
*
* @param outcome A potential outcome that could have been executed for
* @param adaptorSig The adaptor signature corresponding to outcome
* @param cetSig The actual signature for local's key found on-chain on a CET
*/
def sigFromOutcomeAndSigs(
outcome: OracleOutcome,
adaptorSig: ECAdaptorSignature,
cetSig: ECDigitalSignature): SchnorrDigitalSignature = {
val sigPubKey = outcome.sigPoint
// This value is either the oracle signature S value or it is
// useless garbage, but we don't know in this scope, the caller
// must do further work to check this.
val possibleOracleS =
sigPubKey
.extractAdaptorSecret(adaptorSig,
ECDigitalSignature(cetSig.bytes.init))
.fieldElement
SchnorrDigitalSignature(outcome.aggregateNonce, possibleOracleS)
}
val outcomeValues = wCET.outputs.map(_.value).sorted
val totalCollateral = offer.contractInfo.totalCollateral
val possibleOutcomes = offer.contractInfo.allOutcomesAndPayouts
.filter { case (_, amt) =>
val amts = Vector(amt, totalCollateral - amt)
.filter(_ >= Policy.dustThreshold)
.sorted
// Only messages within 1 satoshi of the on-chain CET's value
// should be considered.
// Off-by-one is okay because both parties round to the nearest
// Satoshi for fees and if both round up they could be off-by-one.
Math.abs((amts.head - outcomeValues.head).satoshis.toLong) <= 1 && Math
.abs((amts.last - outcomeValues.last).satoshis.toLong) <= 1
}
.map(_._1)
val (offerCETSig, acceptCETSig) =
if (
offer.pubKeys.fundingKey.hex.compareTo(
accept.pubKeys.fundingKey.hex) > 0
) {
(cetSigs.last, cetSigs.head)
} else { } else {
(cetSigs.head, cetSigs.last) accept.cetSigs.outcomeSigs
} }
val (cetSig, outcomeSigs) = if (isInitiator) { DLCUtil.computeOutcome(isInitiator,
val possibleOutcomeSigs = sign.cetSigs.outcomeSigs.filter { offer.pubKeys.fundingKey,
case (outcome, _) => possibleOutcomes.contains(outcome) accept.pubKeys.fundingKey,
} offer.contractInfo,
(acceptCETSig, possibleOutcomeSigs) localAdaptorSigs,
} else { cet)
val possibleOutcomeSigs = accept.cetSigs.outcomeSigs.filter {
case (outcome, _) => possibleOutcomes.contains(outcome)
}
(offerCETSig, possibleOutcomeSigs)
}
val sigOpt = outcomeSigs.find { case (outcome, adaptorSig) =>
val possibleOracleSig =
sigFromOutcomeAndSigs(outcome, adaptorSig, cetSig)
possibleOracleSig.sig.getPublicKey == outcome.sigPoint
}
sigOpt match {
case Some((outcome, adaptorSig)) =>
(sigFromOutcomeAndSigs(outcome, adaptorSig, cetSig), outcome)
case None =>
throw new IllegalArgumentException("No Oracle Signature found from CET")
}
} }
} }

View file

@ -0,0 +1,124 @@
package org.bitcoins.core.protocol.dlc
import org.bitcoins.core.policy.Policy
import org.bitcoins.core.protocol.script.P2WSHWitnessV0
import org.bitcoins.core.protocol.transaction.{Transaction, WitnessTransaction}
import org.bitcoins.crypto.{
ECAdaptorSignature,
ECDigitalSignature,
ECPublicKey,
SchnorrDigitalSignature,
Sha256Digest
}
import scodec.bits.ByteVector
import scala.util.Try
object DLCUtil {
def computeContractId(
fundingTx: Transaction,
tempContractId: Sha256Digest): ByteVector = {
fundingTx.txIdBE.bytes.xor(tempContractId.bytes)
}
/** Extracts an adaptor secret from cetSig assuming it is the completion
* adaptorSig (which it may not be) and returns the oracle signature if
* and only if adaptorSig does correspond to cetSig.
*
* This method is used to search through possible cetSigs until the correct
* one is found by validating the returned signature.
*
* @param outcome A potential outcome that could have been executed for
* @param adaptorSig The adaptor signature corresponding to outcome
* @param cetSig The actual signature for local's key found on-chain on a CET
*/
private def sigFromOutcomeAndSigs(
outcome: OracleOutcome,
adaptorSig: ECAdaptorSignature,
cetSig: ECDigitalSignature): Try[SchnorrDigitalSignature] = {
val sigPubKey = outcome.sigPoint
// This value is either the oracle signature S value or it is
// useless garbage, but we don't know in this scope, the caller
// must do further work to check this.
val possibleOracleST = Try {
sigPubKey
.extractAdaptorSecret(adaptorSig, ECDigitalSignature(cetSig.bytes.init))
.fieldElement
}
possibleOracleST.map { possibleOracleS =>
SchnorrDigitalSignature(outcome.aggregateNonce, possibleOracleS)
}
}
def computeOutcome(
completedSig: ECDigitalSignature,
possibleAdaptorSigs: Vector[(OracleOutcome, ECAdaptorSignature)]): Option[
(SchnorrDigitalSignature, OracleOutcome)] = {
val sigOpt = possibleAdaptorSigs.find { case (outcome, adaptorSig) =>
val possibleOracleSigT =
sigFromOutcomeAndSigs(outcome, adaptorSig, completedSig)
possibleOracleSigT.isSuccess && possibleOracleSigT.get.sig.getPublicKey == outcome.sigPoint
}
sigOpt.map { case (outcome, adaptorSig) =>
(sigFromOutcomeAndSigs(outcome, adaptorSig, completedSig).get, outcome)
}
}
def computeOutcome(
isInitiator: Boolean,
offerFundingKey: ECPublicKey,
acceptFundingKey: ECPublicKey,
contractInfo: ContractInfo,
localAdaptorSigs: Vector[(OracleOutcome, ECAdaptorSignature)],
cet: WitnessTransaction): Option[
(SchnorrDigitalSignature, OracleOutcome)] = {
val cetSigs = cet.witness.head
.asInstanceOf[P2WSHWitnessV0]
.signatures
require(cetSigs.size == 2,
s"There must be only 2 signatures, got ${cetSigs.size}")
val outcomeValues = cet.outputs.map(_.value).sorted
val totalCollateral = contractInfo.totalCollateral
val possibleOutcomes = contractInfo.allOutcomesAndPayouts
.filter { case (_, amt) =>
val amts = Vector(amt, totalCollateral - amt)
.filter(_ >= Policy.dustThreshold)
.sorted
// Only messages within 1 satoshi of the on-chain CET's value
// should be considered.
// Off-by-one is okay because both parties round to the nearest
// Satoshi for fees and if both round up they could be off-by-one.
Math.abs((amts.head - outcomeValues.head).satoshis.toLong) <= 1 && Math
.abs((amts.last - outcomeValues.last).satoshis.toLong) <= 1
}
.map(_._1)
val (offerCETSig, acceptCETSig) =
if (offerFundingKey.hex.compareTo(acceptFundingKey.hex) > 0) {
(cetSigs.last, cetSigs.head)
} else {
(cetSigs.head, cetSigs.last)
}
val outcomeSigs = localAdaptorSigs.filter { case (outcome, _) =>
possibleOutcomes.contains(outcome)
}
val cetSig = if (isInitiator) {
acceptCETSig
} else {
offerCETSig
}
computeOutcome(cetSig, outcomeSigs)
}
}

View file

@ -704,6 +704,7 @@ object DigitDecompositionEventDescriptorV0TLV
sealed trait OracleEventTLV extends TLV { sealed trait OracleEventTLV extends TLV {
def eventDescriptor: EventDescriptorTLV def eventDescriptor: EventDescriptorTLV
def nonces: Vector[SchnorrNonce] def nonces: Vector[SchnorrNonce]
def eventId: NormalizedString
} }
case class OracleEventV0TLV( case class OracleEventV0TLV(
@ -1253,9 +1254,12 @@ object ContractInfoV0TLV extends TLVFactory[ContractInfoV0TLV] {
override val typeName: String = "ContractInfoV0TLV" override val typeName: String = "ContractInfoV0TLV"
} }
sealed trait FundingInputTLV extends TLV sealed trait FundingInputTLV extends TLV {
def inputSerialId: UInt64
}
case class FundingInputV0TLV( case class FundingInputV0TLV(
inputSerialId: UInt64,
prevTx: Transaction, prevTx: Transaction,
prevTxVout: UInt32, prevTxVout: UInt32,
sequence: UInt32, sequence: UInt32,
@ -1284,6 +1288,7 @@ case class FundingInputV0TLV(
val redeemScript = val redeemScript =
redeemScriptOpt.getOrElse(EmptyScriptPubKey) redeemScriptOpt.getOrElse(EmptyScriptPubKey)
inputSerialId.bytes ++
u16Prefix(prevTx.bytes) ++ u16Prefix(prevTx.bytes) ++
prevTxVout.bytes ++ prevTxVout.bytes ++
sequence.bytes ++ sequence.bytes ++
@ -1298,6 +1303,7 @@ object FundingInputV0TLV extends TLVFactory[FundingInputV0TLV] {
override def fromTLVValue(value: ByteVector): FundingInputV0TLV = { override def fromTLVValue(value: ByteVector): FundingInputV0TLV = {
val iter = ValueIterator(value) val iter = ValueIterator(value)
val serialId = iter.takeU64()
val prevTx = iter.takeU16Prefixed(iter.take(Transaction, _)) val prevTx = iter.takeU16Prefixed(iter.take(Transaction, _))
val prevTxVout = iter.takeU32() val prevTxVout = iter.takeU32()
val sequence = iter.takeU32() val sequence = iter.takeU32()
@ -1308,10 +1314,11 @@ object FundingInputV0TLV extends TLVFactory[FundingInputV0TLV] {
case wspk: WitnessScriptPubKey => Some(wspk) case wspk: WitnessScriptPubKey => Some(wspk)
case _: NonWitnessScriptPubKey => case _: NonWitnessScriptPubKey =>
throw new IllegalArgumentException( throw new IllegalArgumentException(
s"Redeem Script must be Segwith SPK: $redeemScript") s"Redeem Script must be Segwit SPK: $redeemScript")
} }
FundingInputV0TLV(prevTx, FundingInputV0TLV(serialId,
prevTx,
prevTxVout, prevTxVout,
sequence, sequence,
maxWitnessLen, maxWitnessLen,
@ -1391,13 +1398,20 @@ case class DLCOfferTLV(
contractInfo: ContractInfoV0TLV, contractInfo: ContractInfoV0TLV,
fundingPubKey: ECPublicKey, fundingPubKey: ECPublicKey,
payoutSPK: ScriptPubKey, payoutSPK: ScriptPubKey,
payoutSerialId: UInt64,
totalCollateralSatoshis: Satoshis, totalCollateralSatoshis: Satoshis,
fundingInputs: Vector[FundingInputTLV], fundingInputs: Vector[FundingInputTLV],
changeSPK: ScriptPubKey, changeSPK: ScriptPubKey,
changeSerialId: UInt64,
fundOutputSerialId: UInt64,
feeRate: SatoshisPerVirtualByte, feeRate: SatoshisPerVirtualByte,
contractMaturityBound: BlockTimeStamp, contractMaturityBound: BlockTimeStamp,
contractTimeout: BlockTimeStamp) contractTimeout: BlockTimeStamp)
extends TLV { extends TLV {
require(
changeSerialId != fundOutputSerialId,
s"changeSerialId ($changeSerialId) cannot be equal to fundOutputSerialId ($fundOutputSerialId)")
override val tpe: BigSizeUInt = DLCOfferTLV.tpe override val tpe: BigSizeUInt = DLCOfferTLV.tpe
override val value: ByteVector = { override val value: ByteVector = {
@ -1406,9 +1420,12 @@ case class DLCOfferTLV(
contractInfo.bytes ++ contractInfo.bytes ++
fundingPubKey.bytes ++ fundingPubKey.bytes ++
TLV.encodeScript(payoutSPK) ++ TLV.encodeScript(payoutSPK) ++
payoutSerialId.bytes ++
satBytes(totalCollateralSatoshis) ++ satBytes(totalCollateralSatoshis) ++
u16PrefixedList(fundingInputs) ++ u16PrefixedList(fundingInputs) ++
TLV.encodeScript(changeSPK) ++ TLV.encodeScript(changeSPK) ++
changeSerialId.bytes ++
fundOutputSerialId.bytes ++
satBytes(feeRate.currencyUnit.satoshis) ++ satBytes(feeRate.currencyUnit.satoshis) ++
contractMaturityBound.toUInt32.bytes ++ contractMaturityBound.toUInt32.bytes ++
contractTimeout.toUInt32.bytes contractTimeout.toUInt32.bytes
@ -1426,10 +1443,13 @@ object DLCOfferTLV extends TLVFactory[DLCOfferTLV] {
val contractInfo = iter.take(ContractInfoV0TLV) val contractInfo = iter.take(ContractInfoV0TLV)
val fundingPubKey = iter.take(ECPublicKey, 33) val fundingPubKey = iter.take(ECPublicKey, 33)
val payoutSPK = iter.takeSPK() val payoutSPK = iter.takeSPK()
val payoutSerialId = iter.takeU64()
val totalCollateralSatoshis = iter.takeSats() val totalCollateralSatoshis = iter.takeSats()
val fundingInputs = val fundingInputs =
iter.takeU16PrefixedList(() => iter.take(FundingInputV0TLV)) iter.takeU16PrefixedList(() => iter.take(FundingInputV0TLV))
val changeSPK = iter.takeSPK() val changeSPK = iter.takeSPK()
val changeSerialId = iter.takeU64()
val fundingOutputSerialId = iter.takeU64()
val feeRate = SatoshisPerVirtualByte(iter.takeSats()) val feeRate = SatoshisPerVirtualByte(iter.takeSats())
val contractMaturityBound = BlockTimeStamp(iter.takeU32()) val contractMaturityBound = BlockTimeStamp(iter.takeU32())
val contractTimeout = BlockTimeStamp(iter.takeU32()) val contractTimeout = BlockTimeStamp(iter.takeU32())
@ -1440,9 +1460,12 @@ object DLCOfferTLV extends TLVFactory[DLCOfferTLV] {
contractInfo, contractInfo,
fundingPubKey, fundingPubKey,
payoutSPK, payoutSPK,
payoutSerialId,
totalCollateralSatoshis, totalCollateralSatoshis,
fundingInputs, fundingInputs,
changeSPK, changeSPK,
changeSerialId,
fundingOutputSerialId,
feeRate, feeRate,
contractMaturityBound, contractMaturityBound,
contractTimeout contractTimeout
@ -1510,8 +1533,10 @@ case class DLCAcceptTLV(
totalCollateralSatoshis: Satoshis, totalCollateralSatoshis: Satoshis,
fundingPubKey: ECPublicKey, fundingPubKey: ECPublicKey,
payoutSPK: ScriptPubKey, payoutSPK: ScriptPubKey,
payoutSerialId: UInt64,
fundingInputs: Vector[FundingInputTLV], fundingInputs: Vector[FundingInputTLV],
changeSPK: ScriptPubKey, changeSPK: ScriptPubKey,
changeSerialId: UInt64,
cetSignatures: CETSignaturesTLV, cetSignatures: CETSignaturesTLV,
refundSignature: ECDigitalSignature, refundSignature: ECDigitalSignature,
negotiationFields: NegotiationFieldsTLV) negotiationFields: NegotiationFieldsTLV)
@ -1523,8 +1548,10 @@ case class DLCAcceptTLV(
satBytes(totalCollateralSatoshis) ++ satBytes(totalCollateralSatoshis) ++
fundingPubKey.bytes ++ fundingPubKey.bytes ++
TLV.encodeScript(payoutSPK) ++ TLV.encodeScript(payoutSPK) ++
payoutSerialId.bytes ++
u16PrefixedList(fundingInputs) ++ u16PrefixedList(fundingInputs) ++
TLV.encodeScript(changeSPK) ++ TLV.encodeScript(changeSPK) ++
changeSerialId.bytes ++
cetSignatures.bytes ++ cetSignatures.bytes ++
refundSignature.toRawRS ++ refundSignature.toRawRS ++
negotiationFields.bytes negotiationFields.bytes
@ -1541,22 +1568,28 @@ object DLCAcceptTLV extends TLVFactory[DLCAcceptTLV] {
val totalCollateralSatoshis = iter.takeSats() val totalCollateralSatoshis = iter.takeSats()
val fundingPubKey = iter.take(ECPublicKey, 33) val fundingPubKey = iter.take(ECPublicKey, 33)
val payoutSPK = iter.takeSPK() val payoutSPK = iter.takeSPK()
val payoutSerialId = iter.takeU64()
val fundingInputs = val fundingInputs =
iter.takeU16PrefixedList(() => iter.take(FundingInputV0TLV)) iter.takeU16PrefixedList(() => iter.take(FundingInputV0TLV))
val changeSPK = iter.takeSPK() val changeSPK = iter.takeSPK()
val changeSerialId = iter.takeU64()
val cetSignatures = iter.take(CETSignaturesV0TLV) val cetSignatures = iter.take(CETSignaturesV0TLV)
val refundSignature = ECDigitalSignature.fromRS(iter.take(64)) val refundSignature = ECDigitalSignature.fromRS(iter.take(64))
val negotiationFields = iter.take(NegotiationFieldsTLV) val negotiationFields = iter.take(NegotiationFieldsTLV)
DLCAcceptTLV(tempContractId, DLCAcceptTLV(
tempContractId,
totalCollateralSatoshis, totalCollateralSatoshis,
fundingPubKey, fundingPubKey,
payoutSPK, payoutSPK,
payoutSerialId,
fundingInputs, fundingInputs,
changeSPK, changeSPK,
changeSerialId,
cetSignatures, cetSignatures,
refundSignature, refundSignature,
negotiationFields) negotiationFields
)
} }
override val typeName: String = "DLCAcceptTLV" override val typeName: String = "DLCAcceptTLV"

View file

@ -563,9 +563,13 @@ case class DualFundingTxFinalizer(
lazy val (offerFutureFee, offerFundingFee) = lazy val (offerFutureFee, offerFundingFee) =
computeFees(offerInputs, offerPayoutSPK, offerChangeSPK) computeFees(offerInputs, offerPayoutSPK, offerChangeSPK)
lazy val offerFees: CurrencyUnit = offerFutureFee + offerFundingFee
lazy val (acceptFutureFee, acceptFundingFee) = lazy val (acceptFutureFee, acceptFundingFee) =
computeFees(acceptInputs, acceptPayoutSPK, acceptChangeSPK) computeFees(acceptInputs, acceptPayoutSPK, acceptChangeSPK)
lazy val acceptFees: CurrencyUnit = acceptFutureFee + acceptFundingFee
override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = { override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = {
val addOfferFutureFee = val addOfferFutureFee =
AddFutureFeeFinalizer(fundingSPK, offerFutureFee, Vector(offerChangeSPK)) AddFutureFeeFinalizer(fundingSPK, offerFutureFee, Vector(offerChangeSPK))

View file

@ -17,6 +17,7 @@ import org.bitcoins.core.currency.Satoshis
import org.bitcoins.core.protocol.BlockStamp import org.bitcoins.core.protocol.BlockStamp
import org.bitcoins.core.protocol.dlc._ import org.bitcoins.core.protocol.dlc._
import org.bitcoins.core.protocol.dlc.RoundingIntervals.IntervalStart import org.bitcoins.core.protocol.dlc.RoundingIntervals.IntervalStart
import org.bitcoins.core.number._
import org.bitcoins.core.protocol.script.EmptyScriptPubKey import org.bitcoins.core.protocol.script.EmptyScriptPubKey
import org.bitcoins.core.protocol.tlv._ import org.bitcoins.core.protocol.tlv._
import org.bitcoins.core.util.Indexed import org.bitcoins.core.util.Indexed
@ -179,9 +180,12 @@ val offerTLV = DLCOfferTLV(
contractInfo = contractInfo.toTLV, contractInfo = contractInfo.toTLV,
fundingPubKey = ECPublicKey.freshPublicKey, fundingPubKey = ECPublicKey.freshPublicKey,
payoutSPK = EmptyScriptPubKey, payoutSPK = EmptyScriptPubKey,
payoutSerialId = UInt64(1),
totalCollateralSatoshis = Satoshis(500), totalCollateralSatoshis = Satoshis(500),
fundingInputs = Vector.empty, fundingInputs = Vector.empty,
changeSPK = EmptyScriptPubKey, changeSPK = EmptyScriptPubKey,
changeSerialId = UInt64(2),
fundOutputSerialId = UInt64(3),
feeRate = SatoshisPerVirtualByte(Satoshis(1)), feeRate = SatoshisPerVirtualByte(Satoshis(1)),
contractMaturityBound = BlockStamp.BlockHeight(0), contractMaturityBound = BlockStamp.BlockHeight(0),
contractTimeout = BlockStamp.BlockHeight(0) contractTimeout = BlockStamp.BlockHeight(0)

View file

@ -1,12 +1,13 @@
package org.bitcoins.testkitcore.gen package org.bitcoins.testkitcore.gen
import org.bitcoins.core.protocol.dlc.DLCMessage.DLCOffer
import org.bitcoins.core.config.Networks import org.bitcoins.core.config.Networks
import org.bitcoins.core.currency.{Bitcoins, CurrencyUnit, Satoshis} import org.bitcoins.core.currency.{Bitcoins, CurrencyUnit, Satoshis}
import org.bitcoins.core.number.UInt32 import org.bitcoins.core.number.{UInt32, UInt64}
import org.bitcoins.core.protocol.dlc.DLCMessage.DLCOffer
import org.bitcoins.core.protocol.dlc.{ import org.bitcoins.core.protocol.dlc.{
ContractInfo, ContractInfo,
DLCFundingInputP2WPKHV0, DLCFundingInputP2WPKHV0,
DLCMessage,
EnumContractDescriptor EnumContractDescriptor
} }
import org.bitcoins.core.protocol.tlv._ import org.bitcoins.core.protocol.tlv._
@ -117,38 +118,6 @@ trait TLVGen {
def contractDescriptorV0TLVWithTotalCollateral: Gen[ def contractDescriptorV0TLVWithTotalCollateral: Gen[
(ContractDescriptorV0TLV, Satoshis)] = { (ContractDescriptorV0TLV, Satoshis)] = {
def genValues(size: Int, totalAmount: CurrencyUnit): Vector[Satoshis] = {
val vals = if (size < 2) {
throw new IllegalArgumentException(
s"Size must be at least two, got $size")
} else if (size == 2) {
Vector(totalAmount.satoshis, Satoshis.zero)
} else {
(0 until size - 2).map { _ =>
Satoshis(NumberUtil.randomLong(totalAmount.satoshis.toLong))
}.toVector :+ totalAmount.satoshis :+ Satoshis.zero
}
val valsWithOrder = vals.map(_ -> scala.util.Random.nextDouble())
valsWithOrder.sortBy(_._2).map(_._1)
}
def genContractDescriptors(
outcomes: Vector[String],
totalInput: CurrencyUnit): (
EnumContractDescriptor,
EnumContractDescriptor) = {
val outcomeMap =
outcomes
.map(EnumOutcome.apply)
.zip(genValues(outcomes.length, totalInput))
val info = EnumContractDescriptor(outcomeMap)
val remoteInfo = info.flip(totalInput.satoshis)
(info, remoteInfo)
}
for { for {
numOutcomes <- Gen.choose(2, 10) numOutcomes <- Gen.choose(2, 10)
outcomes <- Gen.listOfN(numOutcomes, StringGenerators.genString) outcomes <- Gen.listOfN(numOutcomes, StringGenerators.genString)
@ -156,8 +125,31 @@ trait TLVGen {
Gen Gen
.choose(numOutcomes + 1, Long.MaxValue / 10000L) .choose(numOutcomes + 1, Long.MaxValue / 10000L)
.map(Satoshis.apply) .map(Satoshis.apply)
(contractDescriptor, _) = contractDescriptor = {
genContractDescriptors(outcomes.toVector, totalInput) //DLCTestUtil.genContractDescriptors(outcomes.toVector, totalInput)
val satVals = {
val vals = if (outcomes.length < 2) {
throw new IllegalArgumentException(
s"Size must be at least two, got ${outcomes.length}")
} else if (outcomes.length == 2) {
Vector(totalInput, Satoshis.zero)
} else {
(0 until outcomes.length - 2).map { _ =>
Satoshis(NumberUtil.randomLong(totalInput.toLong))
}.toVector :+ totalInput :+ Satoshis.zero
}
val valsWithOrder = vals.map(_ -> scala.util.Random.nextDouble())
valsWithOrder.sortBy(_._2).map(_._1)
}
val outcomeMap =
outcomes.toVector
.map(EnumOutcome.apply)
.zip(satVals)
EnumContractDescriptor(outcomeMap)
}
} yield { } yield {
(contractDescriptor.toTLV, totalInput) (contractDescriptor.toTLV, totalInput)
} }
@ -167,25 +159,30 @@ trait TLVGen {
contractDescriptorV0TLVWithTotalCollateral.map(_._1) contractDescriptorV0TLVWithTotalCollateral.map(_._1)
} }
def oracleInfoV0TLV: Gen[OracleInfoV0TLV] = { def oracleInfoV0TLV(outcomes: Vector[String]): Gen[OracleInfoV0TLV] = {
for { for {
privKey <- CryptoGenerators.privateKey privKey <- CryptoGenerators.privateKey
rValue <- CryptoGenerators.schnorrNonce rValue <- CryptoGenerators.schnorrNonce
outcomes <- Gen.listOf(StringGenerators.genUTF8String)
} yield { } yield {
OracleInfoV0TLV( OracleInfoV0TLV(
OracleAnnouncementV0TLV.dummyForEventsAndKeys( OracleAnnouncementV0TLV.dummyForEventsAndKeys(
privKey, privKey,
rValue, rValue,
outcomes.toVector.map(EnumOutcome.apply))) outcomes.map(EnumOutcome.apply)))
} }
} }
def oracleInfoV0TLV: Gen[OracleInfoV0TLV] = {
Gen
.listOf(StringGenerators.genUTF8String)
.flatMap(outcomes => oracleInfoV0TLV(outcomes.toVector))
}
def contractInfoV0TLV: Gen[ContractInfoV0TLV] = { def contractInfoV0TLV: Gen[ContractInfoV0TLV] = {
for { for {
(descriptor, totalCollateral) <- (descriptor, totalCollateral) <-
contractDescriptorV0TLVWithTotalCollateral contractDescriptorV0TLVWithTotalCollateral
oracleInfo <- oracleInfoV0TLV oracleInfo <- oracleInfoV0TLV(descriptor.outcomes.map(_._1))
} yield { } yield {
ContractInfoV0TLV(totalCollateral, descriptor, oracleInfo) ContractInfoV0TLV(totalCollateral, descriptor, oracleInfo)
} }
@ -208,7 +205,8 @@ trait TLVGen {
} }
} }
def fundingInputP2WPKHTLV: Gen[FundingInputV0TLV] = { def fundingInputP2WPKHTLV(ignoreSerialIds: Vector[UInt64] =
Vector.empty): Gen[FundingInputV0TLV] = {
for { for {
prevTx <- TransactionGenerators.realisticTransactionWitnessOut prevTx <- TransactionGenerators.realisticTransactionWitnessOut
prevTxVout <- Gen.choose(0, prevTx.outputs.length - 1) prevTxVout <- Gen.choose(0, prevTx.outputs.length - 1)
@ -225,20 +223,26 @@ trait TLVGen {
wtx.copy(outputs = wtx.outputs.updated(prevTxVout, newOutput)) wtx.copy(outputs = wtx.outputs.updated(prevTxVout, newOutput))
} }
} yield { } yield {
DLCFundingInputP2WPKHV0(newPrevTx, UInt32(prevTxVout), sequence).toTLV DLCFundingInputP2WPKHV0(DLCMessage.genSerialId(ignoreSerialIds),
newPrevTx,
UInt32(prevTxVout),
sequence).toTLV
} }
} }
def fundingInputV0TLV: Gen[FundingInputV0TLV] = { def fundingInputV0TLV(ignoreSerialIds: Vector[UInt64] = Vector.empty): Gen[
fundingInputP2WPKHTLV // Soon to be Gen.oneOf FundingInputV0TLV] = {
fundingInputP2WPKHTLV(ignoreSerialIds) // Soon to be Gen.oneOf
} }
def fundingInputV0TLVs( def fundingInputV0TLVs(
collateralNeeded: CurrencyUnit): Gen[Vector[FundingInputV0TLV]] = { collateralNeeded: CurrencyUnit,
ignoreSerialIds: Vector[UInt64] = Vector.empty): Gen[
Vector[FundingInputV0TLV]] = {
for { for {
numInputs <- Gen.choose(0, 5) numInputs <- Gen.choose(0, 5)
inputs <- Gen.listOfN(numInputs, fundingInputV0TLV) inputs <- Gen.listOfN(numInputs, fundingInputV0TLV(ignoreSerialIds))
input <- fundingInputV0TLV input <- fundingInputV0TLV(ignoreSerialIds)
} yield { } yield {
val totalFunding = val totalFunding =
inputs.foldLeft[CurrencyUnit](Satoshis.zero)(_ + _.output.value) inputs.foldLeft[CurrencyUnit](Satoshis.zero)(_ + _.output.value)
@ -296,9 +300,12 @@ trait TLVGen {
contractInfo <- contractInfoV0TLV contractInfo <- contractInfoV0TLV
fundingPubKey <- CryptoGenerators.publicKey fundingPubKey <- CryptoGenerators.publicKey
payoutAddress <- AddressGenerator.bitcoinAddress payoutAddress <- AddressGenerator.bitcoinAddress
payoutSerialId <- NumberGenerator.uInt64
totalCollateralSatoshis <- CurrencyUnitGenerator.positiveRealistic totalCollateralSatoshis <- CurrencyUnitGenerator.positiveRealistic
fundingInputs <- fundingInputV0TLVs(totalCollateralSatoshis) fundingInputs <- fundingInputV0TLVs(totalCollateralSatoshis)
changeAddress <- AddressGenerator.bitcoinAddress changeAddress <- AddressGenerator.bitcoinAddress
changeSerialId <- NumberGenerator.uInt64
fundOutputSerialId <- NumberGenerator.uInt64.suchThat(_ != changeSerialId)
feeRate <- CurrencyUnitGenerator.positiveRealistic.map( feeRate <- CurrencyUnitGenerator.positiveRealistic.map(
SatoshisPerVirtualByte.apply) SatoshisPerVirtualByte.apply)
timeout1 <- NumberGenerator.uInt32s timeout1 <- NumberGenerator.uInt32s
@ -311,17 +318,20 @@ trait TLVGen {
} }
DLCOfferTLV( DLCOfferTLV(
0.toByte, contractFlags = 0.toByte,
chainHash, chainHash = chainHash,
contractInfo, contractInfo = contractInfo,
fundingPubKey, fundingPubKey = fundingPubKey,
payoutAddress.scriptPubKey, payoutSPK = payoutAddress.scriptPubKey,
totalCollateralSatoshis, payoutSerialId = payoutSerialId,
fundingInputs, totalCollateralSatoshis = totalCollateralSatoshis,
changeAddress.scriptPubKey, fundingInputs = fundingInputs,
feeRate, changeSPK = changeAddress.scriptPubKey,
contractMaturityBound, changeSerialId = changeSerialId,
contractTimeout fundOutputSerialId = fundOutputSerialId,
feeRate = feeRate,
contractMaturityBound = contractMaturityBound,
contractTimeout = contractTimeout
) )
} }
} }
@ -345,8 +355,10 @@ trait TLVGen {
totalCollateralSatoshis <- CurrencyUnitGenerator.positiveRealistic totalCollateralSatoshis <- CurrencyUnitGenerator.positiveRealistic
fundingPubKey <- CryptoGenerators.publicKey fundingPubKey <- CryptoGenerators.publicKey
payoutAddress <- AddressGenerator.bitcoinAddress payoutAddress <- AddressGenerator.bitcoinAddress
payoutSerialId <- NumberGenerator.uInt64
fundingInputs <- fundingInputV0TLVs(totalCollateralSatoshis) fundingInputs <- fundingInputV0TLVs(totalCollateralSatoshis)
changeAddress <- AddressGenerator.bitcoinAddress changeAddress <- AddressGenerator.bitcoinAddress
changeSerialId <- NumberGenerator.uInt64
cetSigs <- cetSignaturesV0TLV cetSigs <- cetSignaturesV0TLV
refundSig <- CryptoGenerators.digitalSignature refundSig <- CryptoGenerators.digitalSignature
} yield { } yield {
@ -355,8 +367,10 @@ trait TLVGen {
totalCollateralSatoshis, totalCollateralSatoshis,
fundingPubKey, fundingPubKey,
payoutAddress.scriptPubKey, payoutAddress.scriptPubKey,
payoutSerialId,
fundingInputs, fundingInputs,
changeAddress.scriptPubKey, changeAddress.scriptPubKey,
changeSerialId,
cetSigs, cetSigs,
refundSig, refundSig,
NoNegotiationFieldsTLV NoNegotiationFieldsTLV
@ -370,12 +384,18 @@ trait TLVGen {
for { for {
fundingPubKey <- CryptoGenerators.publicKey fundingPubKey <- CryptoGenerators.publicKey
payoutAddress <- AddressGenerator.bitcoinAddress payoutAddress <- AddressGenerator.bitcoinAddress
payoutSerialId <- NumberGenerator.uInt64.suchThat(
_ != offer.payoutSerialId)
totalCollateralSatoshis <- CurrencyUnitGenerator.positiveRealistic totalCollateralSatoshis <- CurrencyUnitGenerator.positiveRealistic
totalCollateral = scala.math.max( totalCollateral = scala.math.max(
(contractInfo.max - offer.totalCollateralSatoshis).satoshis.toLong, (contractInfo.max - offer.totalCollateralSatoshis).satoshis.toLong,
totalCollateralSatoshis.toLong) totalCollateralSatoshis.toLong)
fundingInputs <- fundingInputV0TLVs(Satoshis(totalCollateral)) fundingInputs <- fundingInputV0TLVs(
Satoshis(totalCollateral),
offer.fundingInputs.map(_.inputSerialId))
changeAddress <- AddressGenerator.bitcoinAddress changeAddress <- AddressGenerator.bitcoinAddress
changeSerialId <- NumberGenerator.uInt64.suchThat(num =>
num != offer.changeSerialId && num != offer.fundOutputSerialId)
cetSigs <- cetSignaturesV0TLV(contractInfo.allOutcomes.length) cetSigs <- cetSignaturesV0TLV(contractInfo.allOutcomes.length)
refundSig <- CryptoGenerators.digitalSignature refundSig <- CryptoGenerators.digitalSignature
} yield { } yield {
@ -384,8 +404,10 @@ trait TLVGen {
Satoshis(totalCollateral), Satoshis(totalCollateral),
fundingPubKey, fundingPubKey,
payoutAddress.scriptPubKey, payoutAddress.scriptPubKey,
payoutSerialId,
fundingInputs, fundingInputs,
changeAddress.scriptPubKey, changeAddress.scriptPubKey,
changeSerialId,
cetSigs, cetSigs,
refundSig, refundSig,
NoNegotiationFieldsTLV NoNegotiationFieldsTLV
@ -430,7 +452,7 @@ trait TLVGen {
oracleAnnouncementV0TLV, oracleAnnouncementV0TLV,
contractInfoV0TLV, contractInfoV0TLV,
oracleInfoV0TLV, oracleInfoV0TLV,
fundingInputV0TLV, fundingInputV0TLV(),
cetSignaturesV0TLV, cetSignaturesV0TLV,
fundingSignaturesV0TLV, fundingSignaturesV0TLV,
dlcOfferTLV, dlcOfferTLV,