mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-22 14:33:06 +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
|
||||
|
||||
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.BlockStamp.{BlockHeight, BlockTime}
|
||||
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 {
|
||||
assertThrows[IllegalArgumentException](
|
||||
DLCOffer(
|
||||
ContractInfo.dummy,
|
||||
DLCPublicKeys(dummyPubKey, dummyAddress),
|
||||
Satoshis(-1),
|
||||
Vector.empty,
|
||||
dummyAddress,
|
||||
SatoshisPerVirtualByte.one,
|
||||
DLCTimeouts(BlockHeight(1), BlockHeight(2))
|
||||
contractInfo = ContractInfo.dummy,
|
||||
pubKeys = DLCPublicKeys(dummyPubKey, dummyAddress),
|
||||
totalCollateral = Satoshis(-1),
|
||||
fundingInputs = Vector.empty,
|
||||
changeAddress = dummyAddress,
|
||||
payoutSerialId = UInt64.zero,
|
||||
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),
|
||||
Vector.empty,
|
||||
dummyAddress,
|
||||
payoutSerialId = UInt64.zero,
|
||||
changeSerialId = UInt64.one,
|
||||
CETSignatures(Vector(
|
||||
EnumOracleOutcome(
|
||||
Vector(dummyOracle),
|
||||
|
|
|
@ -112,7 +112,7 @@ class TLVTest extends BitcoinSUnitTest {
|
|||
}
|
||||
|
||||
"FundingInputV0TLV" must "have serialization symmetry" in {
|
||||
forAll(TLVGen.fundingInputV0TLV) { fundingInput =>
|
||||
forAll(TLVGen.fundingInputV0TLV()) { fundingInput =>
|
||||
assert(FundingInputV0TLV(fundingInput.bytes) == fundingInput)
|
||||
assert(TLV(fundingInput.bytes) == fundingInput)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
package org.bitcoins.core.protocol.dlc
|
||||
|
||||
import org.bitcoins.core.protocol.tlv.{
|
||||
EnumEventDescriptorV0TLV,
|
||||
EnumOutcome,
|
||||
NumericEventDescriptorTLV
|
||||
}
|
||||
|
||||
/** A pair of [[ContractDescriptor]] and [[OracleInfo]]
|
||||
* This type is meant to ensure consistentcy between various
|
||||
* [[ContractDescriptor]] and [[OracleInfo]] so that you cannot
|
||||
|
@ -15,12 +21,39 @@ object ContractOraclePair {
|
|||
case class EnumPair(
|
||||
contractDescriptor: EnumContractDescriptor,
|
||||
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(
|
||||
contractDescriptor: NumericContractDescriptor,
|
||||
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
|
||||
* [[ContractDescriptor]] and [[OracleInfo]] are of the same type
|
||||
|
@ -50,7 +83,7 @@ object ContractOraclePair {
|
|||
case Some(pair) => pair
|
||||
case None =>
|
||||
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),
|
||||
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
|
||||
|
||||
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.tlv.FundingInputV0TLV
|
||||
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}
|
||||
|
||||
sealed trait DLCFundingInput {
|
||||
def inputSerialId: UInt64
|
||||
def prevTx: Transaction
|
||||
def prevTxVout: UInt32
|
||||
def sequence: UInt32
|
||||
|
@ -39,6 +40,7 @@ sealed trait DLCFundingInput {
|
|||
|
||||
lazy val toTLV: FundingInputV0TLV = {
|
||||
FundingInputV0TLV(
|
||||
inputSerialId,
|
||||
prevTx,
|
||||
prevTxVout,
|
||||
sequence,
|
||||
|
@ -54,6 +56,7 @@ sealed trait DLCFundingInput {
|
|||
object DLCFundingInput {
|
||||
|
||||
def apply(
|
||||
inputSerialId: UInt64,
|
||||
prevTx: Transaction,
|
||||
prevTxVout: UInt32,
|
||||
sequence: UInt32,
|
||||
|
@ -63,7 +66,19 @@ object DLCFundingInput {
|
|||
case _: P2SHScriptPubKey =>
|
||||
redeemScriptOpt match {
|
||||
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,
|
||||
sequence,
|
||||
maxWitnessLen,
|
||||
|
@ -76,9 +91,13 @@ object DLCFundingInput {
|
|||
require(
|
||||
maxWitnessLen == UInt16(107) || maxWitnessLen == UInt16(108),
|
||||
s"P2WPKH max witness length must be 107 or 108, got $maxWitnessLen")
|
||||
DLCFundingInputP2WPKHV0(prevTx, prevTxVout, sequence)
|
||||
DLCFundingInputP2WPKHV0(inputSerialId, prevTx, prevTxVout, sequence)
|
||||
case _: P2WSHWitnessSPKV0 =>
|
||||
DLCFundingInputP2WSHV0(prevTx, prevTxVout, sequence, maxWitnessLen)
|
||||
DLCFundingInputP2WSHV0(inputSerialId,
|
||||
prevTx,
|
||||
prevTxVout,
|
||||
sequence,
|
||||
maxWitnessLen)
|
||||
case spk: UnassignedWitnessScriptPubKey =>
|
||||
throw new IllegalArgumentException(s"Unknown segwit version: $spk")
|
||||
case spk: RawScriptPubKey =>
|
||||
|
@ -88,6 +107,7 @@ object DLCFundingInput {
|
|||
|
||||
def fromTLV(fundingInput: FundingInputV0TLV): DLCFundingInput = {
|
||||
DLCFundingInput(
|
||||
fundingInput.inputSerialId,
|
||||
fundingInput.prevTx,
|
||||
fundingInput.prevTxVout,
|
||||
fundingInput.sequence,
|
||||
|
@ -98,8 +118,10 @@ object DLCFundingInput {
|
|||
|
||||
def fromInputSigningInfo(
|
||||
info: ScriptSignatureParams[InputInfo],
|
||||
inputSerialId: UInt64,
|
||||
sequence: UInt32 = TransactionConstants.sequence): DLCFundingInput = {
|
||||
DLCFundingInput(
|
||||
inputSerialId,
|
||||
info.prevTransaction,
|
||||
info.outPoint.vout,
|
||||
sequence,
|
||||
|
@ -112,6 +134,7 @@ object DLCFundingInput {
|
|||
}
|
||||
|
||||
case class DLCFundingInputP2WPKHV0(
|
||||
inputSerialId: UInt64,
|
||||
prevTx: Transaction,
|
||||
prevTxVout: UInt32,
|
||||
sequence: UInt32)
|
||||
|
@ -124,6 +147,7 @@ case class DLCFundingInputP2WPKHV0(
|
|||
}
|
||||
|
||||
case class DLCFundingInputP2WSHV0(
|
||||
inputSerialId: UInt64,
|
||||
prevTx: Transaction,
|
||||
prevTxVout: UInt32,
|
||||
sequence: UInt32,
|
||||
|
@ -136,6 +160,7 @@ case class DLCFundingInputP2WSHV0(
|
|||
}
|
||||
|
||||
case class DLCFundingInputP2SHSegwit(
|
||||
inputSerialId: UInt64,
|
||||
prevTx: Transaction,
|
||||
prevTxVout: UInt32,
|
||||
sequence: UInt32,
|
||||
|
@ -149,3 +174,11 @@ case class DLCFundingInputP2SHSegwit(
|
|||
|
||||
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.currency.Satoshis
|
||||
import org.bitcoins.core.number.UInt64
|
||||
import org.bitcoins.core.protocol.BitcoinAddress
|
||||
import org.bitcoins.core.protocol.tlv._
|
||||
import org.bitcoins.core.protocol.transaction.TransactionOutPoint
|
||||
|
@ -11,6 +12,9 @@ import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
|
|||
import org.bitcoins.crypto._
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.util.Random
|
||||
|
||||
sealed trait DLCMessage
|
||||
|
||||
object DLCMessage {
|
||||
|
@ -23,6 +27,32 @@ object DLCMessage {
|
|||
.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 {
|
||||
def pubKeys: DLCPublicKeys
|
||||
|
||||
|
@ -55,10 +85,21 @@ object DLCMessage {
|
|||
totalCollateral: Satoshis,
|
||||
fundingInputs: Vector[DLCFundingInput],
|
||||
changeAddress: BitcoinAddress,
|
||||
payoutSerialId: UInt64,
|
||||
changeSerialId: UInt64,
|
||||
fundOutputSerialId: UInt64,
|
||||
feeRate: SatoshisPerVirtualByte,
|
||||
timeouts: DLCTimeouts)
|
||||
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 contractDescriptor: ContractDescriptor = contractInfo.contractDescriptor
|
||||
|
||||
|
@ -77,9 +118,12 @@ object DLCMessage {
|
|||
contractInfo.toTLV,
|
||||
fundingPubKey = pubKeys.fundingKey,
|
||||
payoutSPK = pubKeys.payoutAddress.scriptPubKey,
|
||||
payoutSerialId = payoutSerialId,
|
||||
totalCollateralSatoshis = totalCollateral,
|
||||
fundingInputs = fundingInputs.map(_.toTLV),
|
||||
changeSPK = changeAddress.scriptPubKey,
|
||||
changeSerialId = changeSerialId,
|
||||
fundOutputSerialId = fundOutputSerialId,
|
||||
feeRate = feeRate,
|
||||
contractMaturityBound = timeouts.contractMaturity,
|
||||
contractTimeout = timeouts.contractTimeout
|
||||
|
@ -109,6 +153,9 @@ object DLCMessage {
|
|||
},
|
||||
changeAddress =
|
||||
BitcoinAddress.fromScriptPubKey(offer.changeSPK, network),
|
||||
payoutSerialId = offer.payoutSerialId,
|
||||
changeSerialId = offer.changeSerialId,
|
||||
fundOutputSerialId = offer.fundOutputSerialId,
|
||||
feeRate = offer.feeRate,
|
||||
timeouts =
|
||||
DLCTimeouts(offer.contractMaturityBound, offer.contractTimeout)
|
||||
|
@ -125,6 +172,8 @@ object DLCMessage {
|
|||
pubKeys: DLCPublicKeys,
|
||||
fundingInputs: Vector[DLCFundingInput],
|
||||
changeAddress: BitcoinAddress,
|
||||
payoutSerialId: UInt64,
|
||||
changeSerialId: UInt64,
|
||||
negotiationFields: DLCAccept.NegotiationFields,
|
||||
tempContractId: Sha256Digest) {
|
||||
|
||||
|
@ -134,6 +183,8 @@ object DLCMessage {
|
|||
pubKeys = pubKeys,
|
||||
fundingInputs = fundingInputs,
|
||||
changeAddress = changeAddress,
|
||||
payoutSerialId = payoutSerialId,
|
||||
changeSerialId = changeSerialId,
|
||||
cetSigs = cetSigs,
|
||||
negotiationFields = negotiationFields,
|
||||
tempContractId = tempContractId
|
||||
|
@ -146,19 +197,27 @@ object DLCMessage {
|
|||
pubKeys: DLCPublicKeys,
|
||||
fundingInputs: Vector[DLCFundingInput],
|
||||
changeAddress: BitcoinAddress,
|
||||
payoutSerialId: UInt64,
|
||||
changeSerialId: UInt64,
|
||||
cetSigs: CETSignatures,
|
||||
negotiationFields: DLCAccept.NegotiationFields,
|
||||
tempContractId: Sha256Digest)
|
||||
extends DLCSetupMessage {
|
||||
|
||||
require(
|
||||
fundingInputs.map(_.inputSerialId).distinct.size == fundingInputs.size,
|
||||
"All funding input serial ids must be unique")
|
||||
|
||||
def toTLV: DLCAcceptTLV = {
|
||||
DLCAcceptTLV(
|
||||
tempContractId = tempContractId,
|
||||
totalCollateralSatoshis = totalCollateral,
|
||||
fundingPubKey = pubKeys.fundingKey,
|
||||
payoutSPK = pubKeys.payoutAddress.scriptPubKey,
|
||||
payoutSerialId = payoutSerialId,
|
||||
fundingInputs = fundingInputs.map(_.toTLV),
|
||||
changeSPK = changeAddress.scriptPubKey,
|
||||
changeSerialId = changeSerialId,
|
||||
cetSignatures = CETSignaturesV0TLV(cetSigs.adaptorSigs),
|
||||
refundSignature = ECDigitalSignature.fromFrontOfBytes(
|
||||
cetSigs.refundSig.signature.bytes),
|
||||
|
@ -171,12 +230,16 @@ object DLCMessage {
|
|||
}
|
||||
|
||||
def withoutSigs: DLCAcceptWithoutSigs = {
|
||||
DLCAcceptWithoutSigs(totalCollateral,
|
||||
pubKeys,
|
||||
fundingInputs,
|
||||
changeAddress,
|
||||
negotiationFields,
|
||||
tempContractId)
|
||||
DLCAcceptWithoutSigs(
|
||||
totalCollateral = totalCollateral,
|
||||
pubKeys = pubKeys,
|
||||
fundingInputs = fundingInputs,
|
||||
changeAddress = changeAddress,
|
||||
payoutSerialId = payoutSerialId,
|
||||
changeSerialId = changeSerialId,
|
||||
negotiationFields = negotiationFields,
|
||||
tempContractId = tempContractId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -228,6 +291,8 @@ object DLCMessage {
|
|||
},
|
||||
changeAddress =
|
||||
BitcoinAddress.fromScriptPubKey(accept.changeSPK, network),
|
||||
payoutSerialId = accept.payoutSerialId,
|
||||
changeSerialId = accept.changeSerialId,
|
||||
cetSigs = CETSignatures(
|
||||
outcomeSigs,
|
||||
PartialSignature(
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
package org.bitcoins.core.protocol.dlc
|
||||
|
||||
import org.bitcoins.core.currency.CurrencyUnit
|
||||
import org.bitcoins.core.policy.Policy
|
||||
import org.bitcoins.core.protocol.dlc.DLCMessage._
|
||||
import org.bitcoins.core.protocol.script.P2WSHWitnessV0
|
||||
import org.bitcoins.core.protocol.transaction.{
|
||||
NonWitnessTransaction,
|
||||
Transaction,
|
||||
WitnessTransaction
|
||||
}
|
||||
import org.bitcoins.core.protocol.transaction.WitnessTransaction
|
||||
import org.bitcoins.core.wallet.fee.FeeUnit
|
||||
import org.bitcoins.crypto._
|
||||
import scodec.bits.ByteVector
|
||||
|
@ -217,100 +211,19 @@ object DLCStatus {
|
|||
offer: DLCOffer,
|
||||
accept: DLCAccept,
|
||||
sign: DLCSign,
|
||||
cet: Transaction): (SchnorrDigitalSignature, OracleOutcome) = {
|
||||
val wCET = cet match {
|
||||
case wtx: WitnessTransaction => wtx
|
||||
case _: NonWitnessTransaction =>
|
||||
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 {
|
||||
(cetSigs.head, cetSigs.last)
|
||||
}
|
||||
|
||||
val (cetSig, outcomeSigs) = if (isInitiator) {
|
||||
val possibleOutcomeSigs = sign.cetSigs.outcomeSigs.filter {
|
||||
case (outcome, _) => possibleOutcomes.contains(outcome)
|
||||
}
|
||||
(acceptCETSig, possibleOutcomeSigs)
|
||||
cet: WitnessTransaction): Option[
|
||||
(SchnorrDigitalSignature, OracleOutcome)] = {
|
||||
val localAdaptorSigs = if (isInitiator) {
|
||||
sign.cetSigs.outcomeSigs
|
||||
} else {
|
||||
val possibleOutcomeSigs = accept.cetSigs.outcomeSigs.filter {
|
||||
case (outcome, _) => possibleOutcomes.contains(outcome)
|
||||
}
|
||||
(offerCETSig, possibleOutcomeSigs)
|
||||
accept.cetSigs.outcomeSigs
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
DLCUtil.computeOutcome(isInitiator,
|
||||
offer.pubKeys.fundingKey,
|
||||
accept.pubKeys.fundingKey,
|
||||
offer.contractInfo,
|
||||
localAdaptorSigs,
|
||||
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 {
|
||||
def eventDescriptor: EventDescriptorTLV
|
||||
def nonces: Vector[SchnorrNonce]
|
||||
def eventId: NormalizedString
|
||||
}
|
||||
|
||||
case class OracleEventV0TLV(
|
||||
|
@ -1253,9 +1254,12 @@ object ContractInfoV0TLV extends TLVFactory[ContractInfoV0TLV] {
|
|||
override val typeName: String = "ContractInfoV0TLV"
|
||||
}
|
||||
|
||||
sealed trait FundingInputTLV extends TLV
|
||||
sealed trait FundingInputTLV extends TLV {
|
||||
def inputSerialId: UInt64
|
||||
}
|
||||
|
||||
case class FundingInputV0TLV(
|
||||
inputSerialId: UInt64,
|
||||
prevTx: Transaction,
|
||||
prevTxVout: UInt32,
|
||||
sequence: UInt32,
|
||||
|
@ -1284,7 +1288,8 @@ case class FundingInputV0TLV(
|
|||
val redeemScript =
|
||||
redeemScriptOpt.getOrElse(EmptyScriptPubKey)
|
||||
|
||||
u16Prefix(prevTx.bytes) ++
|
||||
inputSerialId.bytes ++
|
||||
u16Prefix(prevTx.bytes) ++
|
||||
prevTxVout.bytes ++
|
||||
sequence.bytes ++
|
||||
maxWitnessLen.bytes ++
|
||||
|
@ -1298,6 +1303,7 @@ object FundingInputV0TLV extends TLVFactory[FundingInputV0TLV] {
|
|||
override def fromTLVValue(value: ByteVector): FundingInputV0TLV = {
|
||||
val iter = ValueIterator(value)
|
||||
|
||||
val serialId = iter.takeU64()
|
||||
val prevTx = iter.takeU16Prefixed(iter.take(Transaction, _))
|
||||
val prevTxVout = iter.takeU32()
|
||||
val sequence = iter.takeU32()
|
||||
|
@ -1308,10 +1314,11 @@ object FundingInputV0TLV extends TLVFactory[FundingInputV0TLV] {
|
|||
case wspk: WitnessScriptPubKey => Some(wspk)
|
||||
case _: NonWitnessScriptPubKey =>
|
||||
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,
|
||||
sequence,
|
||||
maxWitnessLen,
|
||||
|
@ -1391,13 +1398,20 @@ case class DLCOfferTLV(
|
|||
contractInfo: ContractInfoV0TLV,
|
||||
fundingPubKey: ECPublicKey,
|
||||
payoutSPK: ScriptPubKey,
|
||||
payoutSerialId: UInt64,
|
||||
totalCollateralSatoshis: Satoshis,
|
||||
fundingInputs: Vector[FundingInputTLV],
|
||||
changeSPK: ScriptPubKey,
|
||||
changeSerialId: UInt64,
|
||||
fundOutputSerialId: UInt64,
|
||||
feeRate: SatoshisPerVirtualByte,
|
||||
contractMaturityBound: BlockTimeStamp,
|
||||
contractTimeout: BlockTimeStamp)
|
||||
extends TLV {
|
||||
require(
|
||||
changeSerialId != fundOutputSerialId,
|
||||
s"changeSerialId ($changeSerialId) cannot be equal to fundOutputSerialId ($fundOutputSerialId)")
|
||||
|
||||
override val tpe: BigSizeUInt = DLCOfferTLV.tpe
|
||||
|
||||
override val value: ByteVector = {
|
||||
|
@ -1406,9 +1420,12 @@ case class DLCOfferTLV(
|
|||
contractInfo.bytes ++
|
||||
fundingPubKey.bytes ++
|
||||
TLV.encodeScript(payoutSPK) ++
|
||||
payoutSerialId.bytes ++
|
||||
satBytes(totalCollateralSatoshis) ++
|
||||
u16PrefixedList(fundingInputs) ++
|
||||
TLV.encodeScript(changeSPK) ++
|
||||
changeSerialId.bytes ++
|
||||
fundOutputSerialId.bytes ++
|
||||
satBytes(feeRate.currencyUnit.satoshis) ++
|
||||
contractMaturityBound.toUInt32.bytes ++
|
||||
contractTimeout.toUInt32.bytes
|
||||
|
@ -1426,10 +1443,13 @@ object DLCOfferTLV extends TLVFactory[DLCOfferTLV] {
|
|||
val contractInfo = iter.take(ContractInfoV0TLV)
|
||||
val fundingPubKey = iter.take(ECPublicKey, 33)
|
||||
val payoutSPK = iter.takeSPK()
|
||||
val payoutSerialId = iter.takeU64()
|
||||
val totalCollateralSatoshis = iter.takeSats()
|
||||
val fundingInputs =
|
||||
iter.takeU16PrefixedList(() => iter.take(FundingInputV0TLV))
|
||||
val changeSPK = iter.takeSPK()
|
||||
val changeSerialId = iter.takeU64()
|
||||
val fundingOutputSerialId = iter.takeU64()
|
||||
val feeRate = SatoshisPerVirtualByte(iter.takeSats())
|
||||
val contractMaturityBound = BlockTimeStamp(iter.takeU32())
|
||||
val contractTimeout = BlockTimeStamp(iter.takeU32())
|
||||
|
@ -1440,9 +1460,12 @@ object DLCOfferTLV extends TLVFactory[DLCOfferTLV] {
|
|||
contractInfo,
|
||||
fundingPubKey,
|
||||
payoutSPK,
|
||||
payoutSerialId,
|
||||
totalCollateralSatoshis,
|
||||
fundingInputs,
|
||||
changeSPK,
|
||||
changeSerialId,
|
||||
fundingOutputSerialId,
|
||||
feeRate,
|
||||
contractMaturityBound,
|
||||
contractTimeout
|
||||
|
@ -1510,8 +1533,10 @@ case class DLCAcceptTLV(
|
|||
totalCollateralSatoshis: Satoshis,
|
||||
fundingPubKey: ECPublicKey,
|
||||
payoutSPK: ScriptPubKey,
|
||||
payoutSerialId: UInt64,
|
||||
fundingInputs: Vector[FundingInputTLV],
|
||||
changeSPK: ScriptPubKey,
|
||||
changeSerialId: UInt64,
|
||||
cetSignatures: CETSignaturesTLV,
|
||||
refundSignature: ECDigitalSignature,
|
||||
negotiationFields: NegotiationFieldsTLV)
|
||||
|
@ -1523,8 +1548,10 @@ case class DLCAcceptTLV(
|
|||
satBytes(totalCollateralSatoshis) ++
|
||||
fundingPubKey.bytes ++
|
||||
TLV.encodeScript(payoutSPK) ++
|
||||
payoutSerialId.bytes ++
|
||||
u16PrefixedList(fundingInputs) ++
|
||||
TLV.encodeScript(changeSPK) ++
|
||||
changeSerialId.bytes ++
|
||||
cetSignatures.bytes ++
|
||||
refundSignature.toRawRS ++
|
||||
negotiationFields.bytes
|
||||
|
@ -1541,22 +1568,28 @@ object DLCAcceptTLV extends TLVFactory[DLCAcceptTLV] {
|
|||
val totalCollateralSatoshis = iter.takeSats()
|
||||
val fundingPubKey = iter.take(ECPublicKey, 33)
|
||||
val payoutSPK = iter.takeSPK()
|
||||
val payoutSerialId = iter.takeU64()
|
||||
val fundingInputs =
|
||||
iter.takeU16PrefixedList(() => iter.take(FundingInputV0TLV))
|
||||
val changeSPK = iter.takeSPK()
|
||||
val changeSerialId = iter.takeU64()
|
||||
val cetSignatures = iter.take(CETSignaturesV0TLV)
|
||||
val refundSignature = ECDigitalSignature.fromRS(iter.take(64))
|
||||
val negotiationFields = iter.take(NegotiationFieldsTLV)
|
||||
|
||||
DLCAcceptTLV(tempContractId,
|
||||
totalCollateralSatoshis,
|
||||
fundingPubKey,
|
||||
payoutSPK,
|
||||
fundingInputs,
|
||||
changeSPK,
|
||||
cetSignatures,
|
||||
refundSignature,
|
||||
negotiationFields)
|
||||
DLCAcceptTLV(
|
||||
tempContractId,
|
||||
totalCollateralSatoshis,
|
||||
fundingPubKey,
|
||||
payoutSPK,
|
||||
payoutSerialId,
|
||||
fundingInputs,
|
||||
changeSPK,
|
||||
changeSerialId,
|
||||
cetSignatures,
|
||||
refundSignature,
|
||||
negotiationFields
|
||||
)
|
||||
}
|
||||
|
||||
override val typeName: String = "DLCAcceptTLV"
|
||||
|
|
|
@ -563,9 +563,13 @@ case class DualFundingTxFinalizer(
|
|||
lazy val (offerFutureFee, offerFundingFee) =
|
||||
computeFees(offerInputs, offerPayoutSPK, offerChangeSPK)
|
||||
|
||||
lazy val offerFees: CurrencyUnit = offerFutureFee + offerFundingFee
|
||||
|
||||
lazy val (acceptFutureFee, acceptFundingFee) =
|
||||
computeFees(acceptInputs, acceptPayoutSPK, acceptChangeSPK)
|
||||
|
||||
lazy val acceptFees: CurrencyUnit = acceptFutureFee + acceptFundingFee
|
||||
|
||||
override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = {
|
||||
val addOfferFutureFee =
|
||||
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.dlc._
|
||||
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.tlv._
|
||||
import org.bitcoins.core.util.Indexed
|
||||
|
@ -179,9 +180,12 @@ val offerTLV = DLCOfferTLV(
|
|||
contractInfo = contractInfo.toTLV,
|
||||
fundingPubKey = ECPublicKey.freshPublicKey,
|
||||
payoutSPK = EmptyScriptPubKey,
|
||||
payoutSerialId = UInt64(1),
|
||||
totalCollateralSatoshis = Satoshis(500),
|
||||
fundingInputs = Vector.empty,
|
||||
changeSPK = EmptyScriptPubKey,
|
||||
changeSerialId = UInt64(2),
|
||||
fundOutputSerialId = UInt64(3),
|
||||
feeRate = SatoshisPerVirtualByte(Satoshis(1)),
|
||||
contractMaturityBound = BlockStamp.BlockHeight(0),
|
||||
contractTimeout = BlockStamp.BlockHeight(0)
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
package org.bitcoins.testkitcore.gen
|
||||
|
||||
import org.bitcoins.core.protocol.dlc.DLCMessage.DLCOffer
|
||||
import org.bitcoins.core.config.Networks
|
||||
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.{
|
||||
ContractInfo,
|
||||
DLCFundingInputP2WPKHV0,
|
||||
DLCMessage,
|
||||
EnumContractDescriptor
|
||||
}
|
||||
import org.bitcoins.core.protocol.tlv._
|
||||
|
@ -117,38 +118,6 @@ trait TLVGen {
|
|||
|
||||
def contractDescriptorV0TLVWithTotalCollateral: Gen[
|
||||
(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 {
|
||||
numOutcomes <- Gen.choose(2, 10)
|
||||
outcomes <- Gen.listOfN(numOutcomes, StringGenerators.genString)
|
||||
|
@ -156,8 +125,31 @@ trait TLVGen {
|
|||
Gen
|
||||
.choose(numOutcomes + 1, Long.MaxValue / 10000L)
|
||||
.map(Satoshis.apply)
|
||||
(contractDescriptor, _) =
|
||||
genContractDescriptors(outcomes.toVector, totalInput)
|
||||
contractDescriptor = {
|
||||
//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 {
|
||||
(contractDescriptor.toTLV, totalInput)
|
||||
}
|
||||
|
@ -167,25 +159,30 @@ trait TLVGen {
|
|||
contractDescriptorV0TLVWithTotalCollateral.map(_._1)
|
||||
}
|
||||
|
||||
def oracleInfoV0TLV: Gen[OracleInfoV0TLV] = {
|
||||
def oracleInfoV0TLV(outcomes: Vector[String]): Gen[OracleInfoV0TLV] = {
|
||||
for {
|
||||
privKey <- CryptoGenerators.privateKey
|
||||
rValue <- CryptoGenerators.schnorrNonce
|
||||
outcomes <- Gen.listOf(StringGenerators.genUTF8String)
|
||||
} yield {
|
||||
OracleInfoV0TLV(
|
||||
OracleAnnouncementV0TLV.dummyForEventsAndKeys(
|
||||
privKey,
|
||||
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] = {
|
||||
for {
|
||||
(descriptor, totalCollateral) <-
|
||||
contractDescriptorV0TLVWithTotalCollateral
|
||||
oracleInfo <- oracleInfoV0TLV
|
||||
oracleInfo <- oracleInfoV0TLV(descriptor.outcomes.map(_._1))
|
||||
} yield {
|
||||
ContractInfoV0TLV(totalCollateral, descriptor, oracleInfo)
|
||||
}
|
||||
|
@ -208,7 +205,8 @@ trait TLVGen {
|
|||
}
|
||||
}
|
||||
|
||||
def fundingInputP2WPKHTLV: Gen[FundingInputV0TLV] = {
|
||||
def fundingInputP2WPKHTLV(ignoreSerialIds: Vector[UInt64] =
|
||||
Vector.empty): Gen[FundingInputV0TLV] = {
|
||||
for {
|
||||
prevTx <- TransactionGenerators.realisticTransactionWitnessOut
|
||||
prevTxVout <- Gen.choose(0, prevTx.outputs.length - 1)
|
||||
|
@ -225,20 +223,26 @@ trait TLVGen {
|
|||
wtx.copy(outputs = wtx.outputs.updated(prevTxVout, newOutput))
|
||||
}
|
||||
} yield {
|
||||
DLCFundingInputP2WPKHV0(newPrevTx, UInt32(prevTxVout), sequence).toTLV
|
||||
DLCFundingInputP2WPKHV0(DLCMessage.genSerialId(ignoreSerialIds),
|
||||
newPrevTx,
|
||||
UInt32(prevTxVout),
|
||||
sequence).toTLV
|
||||
}
|
||||
}
|
||||
|
||||
def fundingInputV0TLV: Gen[FundingInputV0TLV] = {
|
||||
fundingInputP2WPKHTLV // Soon to be Gen.oneOf
|
||||
def fundingInputV0TLV(ignoreSerialIds: Vector[UInt64] = Vector.empty): Gen[
|
||||
FundingInputV0TLV] = {
|
||||
fundingInputP2WPKHTLV(ignoreSerialIds) // Soon to be Gen.oneOf
|
||||
}
|
||||
|
||||
def fundingInputV0TLVs(
|
||||
collateralNeeded: CurrencyUnit): Gen[Vector[FundingInputV0TLV]] = {
|
||||
collateralNeeded: CurrencyUnit,
|
||||
ignoreSerialIds: Vector[UInt64] = Vector.empty): Gen[
|
||||
Vector[FundingInputV0TLV]] = {
|
||||
for {
|
||||
numInputs <- Gen.choose(0, 5)
|
||||
inputs <- Gen.listOfN(numInputs, fundingInputV0TLV)
|
||||
input <- fundingInputV0TLV
|
||||
inputs <- Gen.listOfN(numInputs, fundingInputV0TLV(ignoreSerialIds))
|
||||
input <- fundingInputV0TLV(ignoreSerialIds)
|
||||
} yield {
|
||||
val totalFunding =
|
||||
inputs.foldLeft[CurrencyUnit](Satoshis.zero)(_ + _.output.value)
|
||||
|
@ -296,9 +300,12 @@ trait TLVGen {
|
|||
contractInfo <- contractInfoV0TLV
|
||||
fundingPubKey <- CryptoGenerators.publicKey
|
||||
payoutAddress <- AddressGenerator.bitcoinAddress
|
||||
payoutSerialId <- NumberGenerator.uInt64
|
||||
totalCollateralSatoshis <- CurrencyUnitGenerator.positiveRealistic
|
||||
fundingInputs <- fundingInputV0TLVs(totalCollateralSatoshis)
|
||||
changeAddress <- AddressGenerator.bitcoinAddress
|
||||
changeSerialId <- NumberGenerator.uInt64
|
||||
fundOutputSerialId <- NumberGenerator.uInt64.suchThat(_ != changeSerialId)
|
||||
feeRate <- CurrencyUnitGenerator.positiveRealistic.map(
|
||||
SatoshisPerVirtualByte.apply)
|
||||
timeout1 <- NumberGenerator.uInt32s
|
||||
|
@ -311,17 +318,20 @@ trait TLVGen {
|
|||
}
|
||||
|
||||
DLCOfferTLV(
|
||||
0.toByte,
|
||||
chainHash,
|
||||
contractInfo,
|
||||
fundingPubKey,
|
||||
payoutAddress.scriptPubKey,
|
||||
totalCollateralSatoshis,
|
||||
fundingInputs,
|
||||
changeAddress.scriptPubKey,
|
||||
feeRate,
|
||||
contractMaturityBound,
|
||||
contractTimeout
|
||||
contractFlags = 0.toByte,
|
||||
chainHash = chainHash,
|
||||
contractInfo = contractInfo,
|
||||
fundingPubKey = fundingPubKey,
|
||||
payoutSPK = payoutAddress.scriptPubKey,
|
||||
payoutSerialId = payoutSerialId,
|
||||
totalCollateralSatoshis = totalCollateralSatoshis,
|
||||
fundingInputs = fundingInputs,
|
||||
changeSPK = changeAddress.scriptPubKey,
|
||||
changeSerialId = changeSerialId,
|
||||
fundOutputSerialId = fundOutputSerialId,
|
||||
feeRate = feeRate,
|
||||
contractMaturityBound = contractMaturityBound,
|
||||
contractTimeout = contractTimeout
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -345,8 +355,10 @@ trait TLVGen {
|
|||
totalCollateralSatoshis <- CurrencyUnitGenerator.positiveRealistic
|
||||
fundingPubKey <- CryptoGenerators.publicKey
|
||||
payoutAddress <- AddressGenerator.bitcoinAddress
|
||||
payoutSerialId <- NumberGenerator.uInt64
|
||||
fundingInputs <- fundingInputV0TLVs(totalCollateralSatoshis)
|
||||
changeAddress <- AddressGenerator.bitcoinAddress
|
||||
changeSerialId <- NumberGenerator.uInt64
|
||||
cetSigs <- cetSignaturesV0TLV
|
||||
refundSig <- CryptoGenerators.digitalSignature
|
||||
} yield {
|
||||
|
@ -355,8 +367,10 @@ trait TLVGen {
|
|||
totalCollateralSatoshis,
|
||||
fundingPubKey,
|
||||
payoutAddress.scriptPubKey,
|
||||
payoutSerialId,
|
||||
fundingInputs,
|
||||
changeAddress.scriptPubKey,
|
||||
changeSerialId,
|
||||
cetSigs,
|
||||
refundSig,
|
||||
NoNegotiationFieldsTLV
|
||||
|
@ -370,12 +384,18 @@ trait TLVGen {
|
|||
for {
|
||||
fundingPubKey <- CryptoGenerators.publicKey
|
||||
payoutAddress <- AddressGenerator.bitcoinAddress
|
||||
payoutSerialId <- NumberGenerator.uInt64.suchThat(
|
||||
_ != offer.payoutSerialId)
|
||||
totalCollateralSatoshis <- CurrencyUnitGenerator.positiveRealistic
|
||||
totalCollateral = scala.math.max(
|
||||
(contractInfo.max - offer.totalCollateralSatoshis).satoshis.toLong,
|
||||
totalCollateralSatoshis.toLong)
|
||||
fundingInputs <- fundingInputV0TLVs(Satoshis(totalCollateral))
|
||||
fundingInputs <- fundingInputV0TLVs(
|
||||
Satoshis(totalCollateral),
|
||||
offer.fundingInputs.map(_.inputSerialId))
|
||||
changeAddress <- AddressGenerator.bitcoinAddress
|
||||
changeSerialId <- NumberGenerator.uInt64.suchThat(num =>
|
||||
num != offer.changeSerialId && num != offer.fundOutputSerialId)
|
||||
cetSigs <- cetSignaturesV0TLV(contractInfo.allOutcomes.length)
|
||||
refundSig <- CryptoGenerators.digitalSignature
|
||||
} yield {
|
||||
|
@ -384,8 +404,10 @@ trait TLVGen {
|
|||
Satoshis(totalCollateral),
|
||||
fundingPubKey,
|
||||
payoutAddress.scriptPubKey,
|
||||
payoutSerialId,
|
||||
fundingInputs,
|
||||
changeAddress.scriptPubKey,
|
||||
changeSerialId,
|
||||
cetSigs,
|
||||
refundSig,
|
||||
NoNegotiationFieldsTLV
|
||||
|
@ -430,7 +452,7 @@ trait TLVGen {
|
|||
oracleAnnouncementV0TLV,
|
||||
contractInfoV0TLV,
|
||||
oracleInfoV0TLV,
|
||||
fundingInputV0TLV,
|
||||
fundingInputV0TLV(),
|
||||
cetSignaturesV0TLV,
|
||||
fundingSignaturesV0TLV,
|
||||
dlcOfferTLV,
|
||||
|
|
Loading…
Add table
Reference in a new issue