mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-23 14:50:42 +01:00
Pulled down core diff from adaptor-dlc (#3038)
This commit is contained in:
parent
1cda5cbf1e
commit
aacba1c077
13 changed files with 552 additions and 194 deletions
|
@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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),
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
124
core/src/main/scala/org/bitcoins/core/protocol/dlc/DLCUtil.scala
Normal file
124
core/src/main/scala/org/bitcoins/core/protocol/dlc/DLCUtil.scala
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Add table
Reference in a new issue