Pulled down all remaining non-wallet non-gui code on adaptor-dlc (#3101)

This commit is contained in:
Nadav Kohen 2021-05-18 05:29:46 -06:00 committed by GitHub
parent a0453ad660
commit ac3bae403b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 507 additions and 205 deletions

View file

@ -5,7 +5,7 @@ import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.{
WalletCreateFundedPsbtOptions
}
import org.bitcoins.core.currency.Bitcoins
import org.bitcoins.core.number.{UInt32, UInt64}
import org.bitcoins.core.number._
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.ln.currency.MilliSatoshis
import org.bitcoins.core.protocol.script.{ScriptPubKey, WitnessScriptPubKey}

View file

@ -22,12 +22,7 @@ import org.bitcoins.core.psbt.PSBT
import org.bitcoins.core.util.EnvUtil
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
import org.bitcoins.core.wallet.utxo.AddressLabelTag
import org.bitcoins.crypto.{
AesPassword,
DoubleSha256DigestBE,
ECPublicKey,
Sha256DigestBE
}
import org.bitcoins.crypto._
import scodec.bits.ByteVector
import scopt.OParser
import ujson._
@ -238,7 +233,8 @@ object ConsoleCli {
),
cmd("acceptdlcofferfromfile")
.action((_, conf) =>
conf.copy(command = AcceptDLCOfferFromFile(new File("").toPath)))
conf.copy(command =
AcceptDLCOfferFromFile(new File("").toPath, None)))
.text("Accepts a DLC offer given from another party")
.children(
arg[Path]("path")
@ -248,6 +244,14 @@ object ConsoleCli {
case accept: AcceptDLCOfferFromFile =>
accept.copy(path = path)
case other => other
})),
arg[Path]("destination")
.optional()
.action((dest, conf) =>
conf.copy(command = conf.command match {
case accept: AcceptDLCOfferFromFile =>
accept.copy(destination = Some(dest))
case other => other
}))
),
cmd("signdlc")
@ -265,7 +269,7 @@ object ConsoleCli {
),
cmd("signdlcfromfile")
.action((_, conf) =>
conf.copy(command = SignDLCFromFile(new File("").toPath)))
conf.copy(command = SignDLCFromFile(new File("").toPath, None)))
.text("Signs a DLC")
.children(
arg[Path]("path")
@ -275,6 +279,14 @@ object ConsoleCli {
case signDLC: SignDLCFromFile =>
signDLC.copy(path = path)
case other => other
})),
arg[Path]("destination")
.optional()
.action((dest, conf) =>
conf.copy(command = conf.command match {
case accept: SignDLCFromFile =>
accept.copy(destination = Some(dest))
case other => other
}))
),
cmd("adddlcsigs")
@ -383,6 +395,20 @@ object ConsoleCli {
case other => other
}))
),
cmd("canceldlc")
.action((_, conf) =>
conf.copy(command = CancelDLC(Sha256DigestBE.empty)))
.text("Cancels a DLC and unreserves used utxos")
.children(
arg[Sha256DigestBE]("paramhash")
.required()
.action((paramHash, conf) =>
conf.copy(command = conf.command match {
case cancelDLC: CancelDLC =>
cancelDLC.copy(paramHash = paramHash)
case other => other
}))
),
cmd("getdlcs")
.action((_, conf) => conf.copy(command = GetDLCs))
.text("Returns all dlcs in the wallet"),
@ -1440,12 +1466,13 @@ object ConsoleCli {
)
case AcceptDLCOffer(offer) =>
RequestParam("acceptdlcoffer", Seq(up.writeJs(offer)))
case AcceptDLCOfferFromFile(path) =>
RequestParam("acceptdlcofferfromfile", Seq(up.writeJs(path)))
case AcceptDLCOfferFromFile(path, dest) =>
RequestParam("acceptdlcofferfromfile",
Seq(up.writeJs(path), up.writeJs(dest)))
case SignDLC(accept) =>
RequestParam("signdlc", Seq(up.writeJs(accept)))
case SignDLCFromFile(path) =>
RequestParam("signdlcfromfile", Seq(up.writeJs(path)))
case SignDLCFromFile(path, dest) =>
RequestParam("signdlcfromfile", Seq(up.writeJs(path), up.writeJs(dest)))
case AddDLCSigs(sigs) =>
RequestParam("adddlcsigs", Seq(up.writeJs(sigs)))
case AddDLCSigsFromFile(path) =>
@ -1462,6 +1489,8 @@ object ConsoleCli {
case ExecuteDLCRefund(contractId, noBroadcast) =>
RequestParam("executedlcrefund",
Seq(up.writeJs(contractId), up.writeJs(noBroadcast)))
case CancelDLC(paramHash) =>
RequestParam("canceldlc", Seq(up.writeJs(paramHash)))
// Wallet
case GetBalance(isSats) =>
RequestParam("getbalance", Seq(up.writeJs(isSats)))
@ -1800,13 +1829,15 @@ object CliCommand {
case class AcceptDLCOffer(offer: LnMessage[DLCOfferTLV])
extends AcceptDLCCliCommand
case class AcceptDLCOfferFromFile(path: Path) extends AcceptDLCCliCommand
case class AcceptDLCOfferFromFile(path: Path, destination: Option[Path])
extends AcceptDLCCliCommand
sealed trait SignDLCCliCommand extends AppServerCliCommand
case class SignDLC(accept: LnMessage[DLCAcceptTLV]) extends SignDLCCliCommand
case class SignDLCFromFile(path: Path) extends SignDLCCliCommand
case class SignDLCFromFile(path: Path, destination: Option[Path])
extends SignDLCCliCommand
sealed trait AddDLCSigsCliCommand extends AppServerCliCommand
@ -1831,6 +1862,8 @@ object CliCommand {
extends AppServerCliCommand
with Broadcastable
case class CancelDLC(paramHash: Sha256DigestBE) extends AppServerCliCommand
case object GetDLCs extends AppServerCliCommand
case class GetDLC(paramHash: Sha256DigestBE) extends AppServerCliCommand

View file

@ -43,7 +43,7 @@ class AcceptDLCDialog
// TODO figure how to validate when using a file
offerDLCFile = None // reset
offerFileChosenLabel.text = "" // reset
AcceptDLCOfferFromFile(file.toPath)
AcceptDLCOfferFromFile(file.toPath, None)
case None =>
val offerHex = readStringFromNode(inputs(dlcOfferStr))
val offer = LnMessageFactory(DLCOfferTLV).fromHex(offerHex)

View file

@ -29,7 +29,7 @@ class SignDLCDialog
case Some(file) =>
acceptDLCFile = None // reset
acceptFileChosenLabel.text = "" // reset
SignDLCFromFile(file.toPath)
SignDLCFromFile(file.toPath, None)
case None =>
val acceptHex = readStringFromNode(inputs(dlcAcceptStr))

View file

@ -4,12 +4,17 @@ import org.bitcoins.core.currency.Satoshis
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.models.DLCMessage.{DLCAccept, DLCOffer}
import org.bitcoins.core.protocol.dlc.models.DLCMessage.{
DLCAccept,
DLCOffer,
DLCSign
}
import org.bitcoins.core.protocol.dlc.models._
import org.bitcoins.core.protocol.tlv.EnumOutcome
import org.bitcoins.core.psbt.InputPSBTRecord.PartialSignature
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
import org.bitcoins.crypto._
import org.bitcoins.testkitcore.gen.{LnMessageGen, TLVGen}
import org.bitcoins.testkitcore.util.BitcoinSJvmTest
class DLCMessageTest extends BitcoinSJvmTest {
@ -80,14 +85,41 @@ class DLCMessageTest extends BitcoinSJvmTest {
dummyAddress,
payoutSerialId = UInt64.zero,
changeSerialId = UInt64.one,
CETSignatures(Vector(
EnumOracleOutcome(
Vector(dummyOracle),
EnumOutcome(dummyStr)) -> ECAdaptorSignature.dummy),
dummySig),
CETSignatures(
Vector(
EnumOracleOutcome(
Vector(dummyOracle),
EnumOutcome(dummyStr)).sigPoint -> ECAdaptorSignature.dummy),
dummySig),
DLCAccept.NoNegotiationFields,
Sha256Digest.empty
)
)
}
it must "be able to go back and forth between TLV and deserialized" in {
forAll(TLVGen.dlcOfferTLVAcceptTLVSignTLV) {
case (offerTLV, acceptTLV, signTLV) =>
val offer = DLCOffer.fromTLV(offerTLV)
val accept = DLCAccept.fromTLV(acceptTLV, offer)
val sign = DLCSign.fromTLV(signTLV, offer)
assert(offer.toTLV == offerTLV)
assert(accept.toTLV == acceptTLV)
assert(sign.toTLV == signTLV)
}
}
it must "be able to go back and forth between LN Message and deserialized" in {
forAll(LnMessageGen.dlcOfferMessageAcceptMessageSignMessage) {
case (offerMsg, acceptMsg, signMsg) =>
val offer = DLCOffer.fromMessage(offerMsg)
val accept = DLCAccept.fromMessage(acceptMsg, offer)
val sign = DLCSign.fromMessage(signMsg, offer)
assert(offer.toMessage == offerMsg)
assert(accept.toMessage == acceptMsg)
assert(sign.toMessage == signMsg)
}
}
}

View file

@ -14,6 +14,7 @@ import org.bitcoins.core.protocol.dlc.models._
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.protocol.transaction._
import org.bitcoins.core.protocol.{BitcoinAddress, BlockTimeStamp}
import org.bitcoins.core.util.Indexed
import org.bitcoins.core.wallet.builder.DualFundingTxFinalizer
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
import org.bitcoins.core.wallet.utxo.{
@ -172,14 +173,19 @@ case class DLCTxBuilder(offer: DLCOffer, accept: DLCAcceptWithoutSigs) {
/** Constructs the unsigned Contract Execution Transaction (CET)
* for a given outcome hash
*/
def buildCET(msg: OracleOutcome): WitnessTransaction = {
buildCETs(Vector(msg)).head
def buildCET(adaptorPoint: Indexed[ECPublicKey]): WitnessTransaction = {
buildCETs(Vector(adaptorPoint)).head
}
def buildCETsMap(msgs: Vector[OracleOutcome]): Vector[OutcomeCETPair] = {
def buildCET(adaptorPoint: ECPublicKey, index: Int): WitnessTransaction = {
buildCET(Indexed(adaptorPoint, index))
}
def buildCETsMap(adaptorPoints: Vector[Indexed[ECPublicKey]]): Vector[
AdaptorPointCETPair] = {
DLCTxBuilder
.buildCETs(
msgs,
adaptorPoints,
contractInfo,
offerFundingKey,
offerFinalAddress.scriptPubKey,
@ -193,8 +199,9 @@ case class DLCTxBuilder(offer: DLCOffer, accept: DLCAcceptWithoutSigs) {
)
}
def buildCETs(msgs: Vector[OracleOutcome]): Vector[WitnessTransaction] = {
buildCETsMap(msgs).map(_.wtx)
def buildCETs(adaptorPoints: Vector[Indexed[ECPublicKey]]): Vector[
WitnessTransaction] = {
buildCETsMap(adaptorPoints).map(_.wtx)
}
/** Constructs the unsigned refund transaction */
@ -318,7 +325,7 @@ object DLCTxBuilder {
}
def buildCET(
outcome: OracleOutcome,
adaptorPoint: Indexed[ECPublicKey],
contractInfo: ContractInfo,
offerFundingKey: ECPublicKey,
offerFinalSPK: ScriptPubKey,
@ -328,22 +335,24 @@ object DLCTxBuilder {
acceptSerialId: UInt64,
timeouts: DLCTimeouts,
fundingOutputRef: OutputReference): WitnessTransaction = {
val Vector(OutcomeCETPair(_, cet)) = buildCETs(Vector(outcome),
contractInfo,
offerFundingKey,
offerFinalSPK,
offerSerialId,
acceptFundingKey,
acceptFinalSPK,
acceptSerialId,
timeouts,
fundingOutputRef)
val Vector(AdaptorPointCETPair(_, cet)) = buildCETs(
Vector(adaptorPoint),
contractInfo,
offerFundingKey,
offerFinalSPK,
offerSerialId,
acceptFundingKey,
acceptFinalSPK,
acceptSerialId,
timeouts,
fundingOutputRef
)
cet
}
def buildCETs(
outcomes: Vector[OracleOutcome],
adaptorPoints: Vector[Indexed[ECPublicKey]],
contractInfo: ContractInfo,
offerFundingKey: ECPublicKey,
offerFinalSPK: ScriptPubKey,
@ -352,7 +361,7 @@ object DLCTxBuilder {
acceptFinalSPK: ScriptPubKey,
acceptSerialId: UInt64,
timeouts: DLCTimeouts,
fundingOutputRef: OutputReference): Vector[OutcomeCETPair] = {
fundingOutputRef: OutputReference): Vector[AdaptorPointCETPair] = {
val builder =
DLCCETBuilder(contractInfo,
offerFundingKey,
@ -364,13 +373,17 @@ object DLCTxBuilder {
timeouts,
fundingOutputRef)
outcomes.map { outcome =>
OutcomeCETPair(outcome, builder.buildCET(outcome))
val outcomes = adaptorPoints.map { case Indexed(_, index) =>
contractInfo.allOutcomes(index)
}
adaptorPoints.zip(outcomes).map { case (Indexed(sigPoint, _), outcome) =>
AdaptorPointCETPair(sigPoint, builder.buildCET(outcome))
}
}
def buildCETs(
outcomes: Vector[OracleOutcome],
adaptorPoints: Vector[Indexed[ECPublicKey]],
contractInfo: ContractInfo,
offerFundingKey: ECPublicKey,
offerFinalSPK: ScriptPubKey,
@ -380,13 +393,13 @@ object DLCTxBuilder {
acceptSerialId: UInt64,
timeouts: DLCTimeouts,
fundingTx: Transaction,
fundOutputIndex: Int): Vector[OutcomeCETPair] = {
fundOutputIndex: Int): Vector[AdaptorPointCETPair] = {
val fundingOutPoint =
TransactionOutPoint(fundingTx.txId, UInt32(fundOutputIndex))
val fundingOutputRef =
OutputReference(fundingOutPoint, fundingTx.outputs(fundOutputIndex))
buildCETs(outcomes,
buildCETs(adaptorPoints,
contractInfo,
offerFundingKey,
offerFinalSPK,

View file

@ -0,0 +1,110 @@
package org.bitcoins.core.protocol.dlc.compute
import org.bitcoins.core.protocol.dlc.models.{
ContractInfo,
EnumContractDescriptor,
NumericContractDescriptor
}
import org.bitcoins.core.protocol.tlv.{
EnumOutcome,
SignedNumericOutcome,
UnsignedNumericOutcome
}
import org.bitcoins.crypto.{
CryptoUtil,
ECPublicKey,
FieldElement,
SchnorrPublicKey
}
import scodec.bits.ByteVector
/** Responsible for optimized computation of DLC adaptor point batches. */
object DLCAdaptorPointComputer {
private val base: Int = 2
private lazy val numericPossibleOutcomes: Vector[ByteVector] = {
0
.until(base)
.toVector
.map(_.toString)
.map(CryptoUtil.serializeForHash)
}
/** Computes:
* nonce + outcomeHash*pubKey
* where outcomeHash is as specified in the DLC spec.
* @see https://github.com/discreetlogcontracts/dlcspecs/blob/master/Oracle.md#signing-algorithm
*/
def computePoint(
pubKey: SchnorrPublicKey,
nonce: ECPublicKey,
outcome: ByteVector): ECPublicKey = {
val hash = CryptoUtil
.sha256SchnorrChallenge(
nonce.schnorrNonce.bytes ++ pubKey.bytes ++ CryptoUtil
.sha256DLCAttestation(outcome)
.bytes)
.bytes
nonce.add(pubKey.publicKey.tweakMultiply(FieldElement(hash)))
}
/** Efficiently computes all adaptor points, in order, for a given ContractInfo.
* @see https://medium.com/crypto-garage/optimizing-numeric-outcome-dlc-creation-6d6091ac0e47
*/
def computeAdaptorPoints(contractInfo: ContractInfo): Vector[ECPublicKey] = {
// The possible messages a single nonce may be used to sign
val possibleOutcomes: Vector[ByteVector] =
contractInfo.contractDescriptor match {
case enum: EnumContractDescriptor =>
enum.keys.map(_.outcome).map(CryptoUtil.serializeForHash)
case _: NumericContractDescriptor => numericPossibleOutcomes
}
// Oracle -> Nonce -> Outcome -> SubSigPoint
// These are the points that are then combined to construct aggregate points.
val preComputeTable: Vector[Vector[Vector[ECPublicKey]]] =
contractInfo.oracleInfo.singleOracleInfos.map { info =>
val announcement = info.announcement
val pubKey = announcement.publicKey
val nonces = announcement.eventTLV.nonces.map(_.publicKey)
nonces.map { nonce =>
possibleOutcomes.map { outcome =>
computePoint(pubKey, nonce, outcome)
}
}
}
val oraclesAndOutcomes = contractInfo.allOutcomes.map(_.oraclesAndOutcomes)
oraclesAndOutcomes.map { oracleAndOutcome =>
// For the given oracleAndOutcome, look up the point in the preComputeTable
val subSigPoints = oracleAndOutcome.flatMap { case (info, outcome) =>
val oracleIndex =
contractInfo.oracleInfo.singleOracleInfos.indexOf(info)
val outcomeIndices = outcome match {
case outcome: EnumOutcome =>
Vector(
contractInfo.contractDescriptor
.asInstanceOf[EnumContractDescriptor]
.keys
.indexOf(outcome)
)
case UnsignedNumericOutcome(digits) => digits
case _: SignedNumericOutcome =>
throw new UnsupportedOperationException(
"Signed numeric outcomes not supported!")
}
outcomeIndices.zipWithIndex.map { case (outcomeIndex, nonceIndex) =>
preComputeTable(oracleIndex)(nonceIndex)(outcomeIndex)
}
}
// TODO: Memoization of sub-combinations for further optimization!
CryptoUtil.combinePubKeys(subSigPoints)
}
}
}

View file

@ -24,43 +24,38 @@ object DLCUtil {
* 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 adaptorPoint A potential adaptor point 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,
adaptorPoint: ECPublicKey,
adaptorSig: ECAdaptorSignature,
cetSig: ECDigitalSignature): Try[SchnorrDigitalSignature] = {
val sigPubKey = outcome.sigPoint
cetSig: ECDigitalSignature): Try[FieldElement] = {
// 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
Try {
adaptorPoint
.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) =>
possibleAdaptorSigs: Vector[(ECPublicKey, ECAdaptorSignature)]): Option[
(FieldElement, ECPublicKey)] = {
val sigOpt = possibleAdaptorSigs.find { case (adaptorPoint, adaptorSig) =>
val possibleOracleSigT =
sigFromOutcomeAndSigs(outcome, adaptorSig, completedSig)
sigFromOutcomeAndSigs(adaptorPoint, adaptorSig, completedSig)
possibleOracleSigT.isSuccess && possibleOracleSigT.get.sig.getPublicKey == outcome.sigPoint
possibleOracleSigT.isSuccess && possibleOracleSigT.get.getPublicKey == adaptorPoint
}
sigOpt.map { case (outcome, adaptorSig) =>
(sigFromOutcomeAndSigs(outcome, adaptorSig, completedSig).get, outcome)
sigOpt.map { case (adaptorPoint, adaptorSig) =>
(sigFromOutcomeAndSigs(adaptorPoint, adaptorSig, completedSig).get,
adaptorPoint)
}
}
@ -69,9 +64,11 @@ object DLCUtil {
offerFundingKey: ECPublicKey,
acceptFundingKey: ECPublicKey,
contractInfo: ContractInfo,
localAdaptorSigs: Vector[(OracleOutcome, ECAdaptorSignature)],
localAdaptorSigs: Vector[(ECPublicKey, ECAdaptorSignature)],
cet: WitnessTransaction): Option[
(SchnorrDigitalSignature, OracleOutcome)] = {
val allAdaptorPoints = contractInfo.adaptorPoints
val cetSigs = cet.witness.head
.asInstanceOf[P2WSHWitnessV0]
.signatures
@ -82,8 +79,8 @@ object DLCUtil {
val outcomeValues = cet.outputs.map(_.value).sorted
val totalCollateral = contractInfo.totalCollateral
val possibleOutcomes = contractInfo.allOutcomesAndPayouts
.filter { case (_, amt) =>
val possibleOutcomes = contractInfo.allOutcomesAndPayouts.zipWithIndex
.filter { case ((_, amt), _) =>
val amts = Vector(amt, totalCollateral - amt)
.filter(_ >= Policy.dustThreshold)
.sorted
@ -95,7 +92,7 @@ object DLCUtil {
Math.abs((amts.head - outcomeValues.head).satoshis.toLong) <= 1 && Math
.abs((amts.last - outcomeValues.last).satoshis.toLong) <= 1
}
.map(_._1)
.map { case (_, index) => allAdaptorPoints(index) }
val (offerCETSig, acceptCETSig) =
if (offerFundingKey.hex.compareTo(acceptFundingKey.hex) > 0) {
@ -114,6 +111,11 @@ object DLCUtil {
offerCETSig
}
computeOutcome(cetSig, outcomeSigs)
computeOutcome(cetSig, outcomeSigs).map { case (s, adaptorPoint) =>
val index = allAdaptorPoints.indexOf(adaptorPoint)
val outcome: OracleOutcome = contractInfo.allOutcomes(index)
(SchnorrDigitalSignature(outcome.aggregateNonce, s), outcome)
}
}
}

View file

@ -7,6 +7,7 @@ import org.bitcoins.core.protocol.dlc.models._
import org.bitcoins.core.protocol.dlc.sign.DLCTxSigner
import org.bitcoins.core.protocol.transaction.{Transaction, WitnessTransaction}
import org.bitcoins.core.psbt.InputPSBTRecord.PartialSignature
import org.bitcoins.core.util.Indexed
import org.bitcoins.crypto.{AdaptorSign, ECPublicKey}
import scala.util.{Success, Try}
@ -51,7 +52,7 @@ case class DLCExecutor(signer: DLCTxSigner) {
}
val CETSignatures(outcomeSigs, refundSig) = cetSigs
val msgs = outcomeSigs.map(_._1)
val msgs = Indexed(outcomeSigs.map(_._1))
val cets = cetsOpt match {
case Some(cets) => cets
case None => builder.buildCETs(msgs)
@ -123,7 +124,7 @@ object DLCExecutor {
* a valid set of expected oracle signatures as per the oracle announcements in the ContractInfo.
*/
def executeDLC(
remoteCETInfos: Vector[(OracleOutcome, CETInfo)],
remoteCETInfos: Vector[(ECPublicKey, CETInfo)],
oracleSigs: Vector[OracleSignatures],
fundingKey: AdaptorSign,
remoteFundingPubKey: ECPublicKey,
@ -141,7 +142,9 @@ object DLCExecutor {
}
val msgAndCETInfoOpt = msgOpt.flatMap { msg =>
remoteCETInfos.find(_._1 == msg)
remoteCETInfos
.find(_._1 == msg.sigPoint)
.map { case (_, info) => (msg, info) }
}
val (msg, ucet, remoteAdaptorSig) = msgAndCETInfoOpt match {

View file

@ -1,12 +1,11 @@
package org.bitcoins.core.protocol.dlc.execution
import org.bitcoins.core.protocol.dlc.models.OracleOutcome
import org.bitcoins.core.protocol.transaction.{Transaction, WitnessTransaction}
import org.bitcoins.crypto.ECAdaptorSignature
import org.bitcoins.crypto.{ECAdaptorSignature, ECPublicKey}
case class SetupDLC(
fundingTx: Transaction,
cets: Vector[(OracleOutcome, CETInfo)],
cets: Vector[(ECPublicKey, CETInfo)],
refundTx: WitnessTransaction) {
cets.foreach { case (msg, cetInfo) =>
require(
@ -25,12 +24,12 @@ case class SetupDLC(
s"RefundTx is not spending the funding tx, ${refundTx.inputs.head}"
)
def getCETInfo(outcome: OracleOutcome): CETInfo = {
cets.find(_._1 == outcome) match {
def getCETInfo(adaptorPoint: ECPublicKey): CETInfo = {
cets.find(_._1 == adaptorPoint) match {
case Some((_, info)) => info
case None =>
throw new IllegalArgumentException(
s"No CET found for the given outcome $outcome")
s"No CET found for the given adaptor point $adaptorPoint")
}
}
}

View file

@ -5,7 +5,10 @@ import org.bitcoins.core.protocol.dlc.compute.CETCalculator.{
CETOutcome,
MultiOracleOutcome
}
import org.bitcoins.core.protocol.dlc.compute.CETCalculator
import org.bitcoins.core.protocol.dlc.compute.{
CETCalculator,
DLCAdaptorPointComputer
}
import org.bitcoins.core.protocol.dlc.models.DLCMessage.DLCAccept
import org.bitcoins.core.protocol.tlv.{
ContractInfoV0TLV,
@ -13,6 +16,7 @@ import org.bitcoins.core.protocol.tlv.{
TLVSerializable,
UnsignedNumericOutcome
}
import org.bitcoins.core.util.Indexed
import org.bitcoins.crypto.ECPublicKey
import scala.collection.immutable.HashMap
@ -134,23 +138,30 @@ case class ContractInfo(
/** Maps adpator points to their corresponding OracleOutcomes (which correspond to CETs) */
lazy val sigPointMap: Map[ECPublicKey, OracleOutcome] =
allOutcomes.map(outcome => outcome.sigPoint -> outcome).toMap
adaptorPoints.zip(allOutcomes).toMap
/** Map OracleOutcomes (which correspond to CETs) to their adpator point and payouts */
lazy val outcomeMap: Map[OracleOutcome, (ECPublicKey, Satoshis, Satoshis)] = {
val builder =
HashMap.newBuilder[OracleOutcome, (ECPublicKey, Satoshis, Satoshis)]
allOutcomesAndPayouts.foreach { case (outcome, offerPayout) =>
val acceptPayout = (totalCollateral - offerPayout).satoshis
val adaptorPoint = outcome.sigPoint
allOutcomesAndPayouts.zip(adaptorPoints).foreach {
case ((outcome, offerPayout), adaptorPoint) =>
val acceptPayout = (totalCollateral - offerPayout).satoshis
builder.+=((outcome, (adaptorPoint, offerPayout, acceptPayout)))
builder.+=((outcome, (adaptorPoint, offerPayout, acceptPayout)))
}
builder.result()
}
lazy val adaptorPoints: Vector[ECPublicKey] = {
DLCAdaptorPointComputer.computeAdaptorPoints(this)
}
lazy val adaptorPointsIndexed: Vector[Indexed[ECPublicKey]] = Indexed(
adaptorPoints)
/** Checks if the given OracleSignatures exactly match the given OracleOutcome.
*
* Warning: This will return false if too many OracleSignatures are given.

View file

@ -275,10 +275,10 @@ object DLCMessage {
def fromTLV(
accept: DLCAcceptTLV,
network: NetworkParameters,
outcomes: Vector[OracleOutcome]): DLCAccept = {
adaptorPoints: Vector[ECPublicKey]): DLCAccept = {
val outcomeSigs = accept.cetSignatures match {
case CETSignaturesV0TLV(sigs) =>
outcomes.zip(sigs)
adaptorPoints.zip(sigs)
}
DLCAccept(
@ -308,7 +308,7 @@ object DLCMessage {
accept: DLCAcceptTLV,
network: NetworkParameters,
contractInfo: ContractInfo): DLCAccept = {
fromTLV(accept, network, contractInfo.allOutcomes)
fromTLV(accept, network, contractInfo.adaptorPoints)
}
def fromTLV(accept: DLCAcceptTLV, offer: DLCOffer): DLCAccept = {
@ -348,11 +348,11 @@ object DLCMessage {
def fromTLV(
sign: DLCSignTLV,
fundingPubKey: ECPublicKey,
outcomes: Vector[OracleOutcome],
adaptorPoints: Vector[ECPublicKey],
fundingOutPoints: Vector[TransactionOutPoint]): DLCSign = {
val outcomeSigs = sign.cetSignatures match {
case CETSignaturesV0TLV(sigs) =>
outcomes.zip(sigs)
adaptorPoints.zip(sigs)
}
val sigs = sign.fundingSignatures match {
@ -376,7 +376,7 @@ object DLCMessage {
def fromTLV(sign: DLCSignTLV, offer: DLCOffer): DLCSign = {
fromTLV(sign,
offer.pubKeys.fundingKey,
offer.contractInfo.allOutcomes,
offer.contractInfo.adaptorPoints,
offer.fundingInputs.map(_.outPoint))
}

View file

@ -4,8 +4,8 @@ import org.bitcoins.core.protocol.script.ScriptWitnessV0
import org.bitcoins.core.protocol.tlv.FundingSignaturesV0TLV
import org.bitcoins.core.protocol.transaction.TransactionOutPoint
import org.bitcoins.core.psbt.InputPSBTRecord.PartialSignature
import org.bitcoins.core.util.SeqWrapper
import org.bitcoins.crypto.ECAdaptorSignature
import org.bitcoins.core.util.{Indexed, SeqWrapper}
import org.bitcoins.crypto.{ECAdaptorSignature, ECPublicKey}
sealed trait DLCSignatures
@ -35,13 +35,19 @@ case class FundingSignatures(
}
case class CETSignatures(
outcomeSigs: Vector[(OracleOutcome, ECAdaptorSignature)],
outcomeSigs: Vector[(ECPublicKey, ECAdaptorSignature)],
refundSig: PartialSignature)
extends DLCSignatures {
lazy val keys: Vector[OracleOutcome] = outcomeSigs.map(_._1)
lazy val keys: Vector[ECPublicKey] = outcomeSigs.map(_._1)
lazy val adaptorSigs: Vector[ECAdaptorSignature] = outcomeSigs.map(_._2)
def apply(key: OracleOutcome): ECAdaptorSignature = {
def indexedOutcomeSigs: Vector[(Indexed[ECPublicKey], ECAdaptorSignature)] = {
outcomeSigs.zipWithIndex.map { case ((adaptorPoint, sig), index) =>
(Indexed(adaptorPoint, index), sig)
}
}
def apply(key: ECPublicKey): ECAdaptorSignature = {
outcomeSigs
.find(_._1 == key)
.map(_._2)

View file

@ -25,6 +25,8 @@ sealed trait OracleOutcome {
*/
def outcome: DLCOutcomeType
def oraclesAndOutcomes: Vector[(SingleOracleInfo, DLCOutcomeType)]
protected def computeSigPoint: ECPublicKey
/** The adaptor point used to encrypt the signatures for this corresponding CET. */
@ -48,6 +50,9 @@ case class EnumOracleOutcome(
oracles.map(_.sigPoint(outcome)).reduce(_.add(_))
}
override val oraclesAndOutcomes: Vector[(EnumSingleOracleInfo, EnumOutcome)] =
oracles.map((_, outcome))
override lazy val aggregateNonce: SchnorrNonce = {
oracles
.map(_.aggregateNonce(outcome))
@ -60,7 +65,7 @@ case class EnumOracleOutcome(
/** Corresponds to a CET in an Numeric Outcome DLC where some set of `threshold`
* oracles have each signed some NumericOutcome.
*/
case class NumericOracleOutcome(oraclesAndOutcomes: Vector[
case class NumericOracleOutcome(override val oraclesAndOutcomes: Vector[
(NumericSingleOracleInfo, UnsignedNumericOutcome)])
extends OracleOutcome {
@ -103,5 +108,5 @@ object NumericOracleOutcome {
}
}
/** An oracle outcome and it's corresponding CET */
case class OutcomeCETPair(outcome: OracleOutcome, wtx: WitnessTransaction)
/** An adaptor point and it's corresponding CET */
case class AdaptorPointCETPair(sigPoint: ECPublicKey, wtx: WitnessTransaction)

View file

@ -15,7 +15,7 @@ import org.bitcoins.core.protocol.{Bech32Address, BitcoinAddress}
import org.bitcoins.core.psbt.InputPSBTRecord.PartialSignature
import org.bitcoins.core.psbt.PSBT
import org.bitcoins.core.script.crypto.HashType
import org.bitcoins.core.util.FutureUtil
import org.bitcoins.core.util.{FutureUtil, Indexed}
import org.bitcoins.core.wallet.signer.BitcoinSigner
import org.bitcoins.core.wallet.utxo._
import org.bitcoins.crypto._
@ -125,28 +125,28 @@ case class DLCTxSigner(
}
/** Signs remote's Contract Execution Transaction (CET) for a given outcome */
def signCET(outcome: OracleOutcome): ECAdaptorSignature = {
signCETs(Vector(outcome)).head._2
def signCET(adaptorPoint: ECPublicKey, index: Int): ECAdaptorSignature = {
signCETs(Vector(Indexed(adaptorPoint, index))).head._2
}
/** Signs remote's Contract Execution Transaction (CET) for a given outcomes */
def buildAndSignCETs(outcomes: Vector[OracleOutcome]): Vector[
(OracleOutcome, WitnessTransaction, ECAdaptorSignature)] = {
val outcomesAndCETs = builder.buildCETsMap(outcomes)
def buildAndSignCETs(adaptorPoints: Vector[Indexed[ECPublicKey]]): Vector[
(ECPublicKey, WitnessTransaction, ECAdaptorSignature)] = {
val outcomesAndCETs = builder.buildCETsMap(adaptorPoints)
DLCTxSigner.buildAndSignCETs(outcomesAndCETs, cetSigningInfo, fundingKey)
}
/** Signs remote's Contract Execution Transaction (CET) for a given outcomes */
def signCETs(outcomes: Vector[OracleOutcome]): Vector[
(OracleOutcome, ECAdaptorSignature)] = {
buildAndSignCETs(outcomes).map { case (outcome, _, sig) =>
def signCETs(adaptorPoints: Vector[Indexed[ECPublicKey]]): Vector[
(ECPublicKey, ECAdaptorSignature)] = {
buildAndSignCETs(adaptorPoints).map { case (outcome, _, sig) =>
outcome -> sig
}
}
/** Signs remote's Contract Execution Transaction (CET) for a given outcomes and their corresponding CETs */
def signGivenCETs(outcomesAndCETs: Vector[OutcomeCETPair]): Vector[
(OracleOutcome, ECAdaptorSignature)] = {
def signGivenCETs(outcomesAndCETs: Vector[AdaptorPointCETPair]): Vector[
(ECPublicKey, ECAdaptorSignature)] = {
DLCTxSigner.signCETs(outcomesAndCETs, cetSigningInfo, fundingKey)
}
@ -154,12 +154,14 @@ case class DLCTxSigner(
outcome: OracleOutcome,
remoteAdaptorSig: ECAdaptorSignature,
oracleSigs: Vector[OracleSignatures]): WitnessTransaction = {
val index = builder.contractInfo.allOutcomes.indexOf(outcome)
DLCTxSigner.completeCET(
outcome,
cetSigningInfo,
builder.fundingMultiSig,
builder.buildFundingTx,
builder.buildCET(outcome),
builder.buildCET(outcome.sigPoint, index),
remoteAdaptorSig,
remoteFundingPubKey,
oracleSigs
@ -184,7 +186,8 @@ case class DLCTxSigner(
/** Creates all of this party's CETSignatures */
def createCETSigs(): CETSignatures = {
val cetSigs = signCETs(builder.contractInfo.allOutcomes)
val adaptorPoints = builder.contractInfo.adaptorPointsIndexed
val cetSigs = signCETs(adaptorPoints)
val refundSig = signRefundTx
CETSignatures(cetSigs, refundSig)
@ -193,19 +196,26 @@ case class DLCTxSigner(
/** Creates CET signatures async */
def createCETSigsAsync()(implicit
ec: ExecutionContext): Future[CETSignatures] = {
val outcomes = builder.contractInfo.allOutcomes
val adaptorPoints = builder.contractInfo.adaptorPointsIndexed
//divide and conquer
val computeBatchFn: Vector[OracleOutcome] => Future[
Vector[(OracleOutcome, ECAdaptorSignature)]] = {
case outcomes: Vector[OracleOutcome] =>
//we want a batch size of at least 1
val size =
Math.max(adaptorPoints.length / Runtime.getRuntime.availableProcessors(),
1)
val computeBatchFn: Vector[Indexed[ECPublicKey]] => Future[
Vector[(ECPublicKey, ECAdaptorSignature)]] = {
adaptorPoints: Vector[Indexed[ECPublicKey]] =>
Future {
signCETs(outcomes)
signCETs(adaptorPoints)
}
}
val cetSigsF: Future[Vector[(OracleOutcome, ECAdaptorSignature)]] = {
FutureUtil.batchAndParallelExecute(elements = outcomes,
f = computeBatchFn)
val cetSigsF: Future[Vector[(ECPublicKey, ECAdaptorSignature)]] = {
FutureUtil.batchAndParallelExecute(elements = adaptorPoints,
f = computeBatchFn,
batchSize = size)
}.map(_.flatten)
for {
@ -216,30 +226,32 @@ case class DLCTxSigner(
/** Creates all of this party's CETSignatures */
def createCETsAndCETSigs(): (CETSignatures, Vector[WitnessTransaction]) = {
val cetsAndSigs = buildAndSignCETs(builder.contractInfo.allOutcomes)
val adaptorPoints = builder.contractInfo.adaptorPointsIndexed
val cetsAndSigs = buildAndSignCETs(adaptorPoints)
val (msgs, cets, sigs) = cetsAndSigs.unzip3
val refundSig = signRefundTx
(CETSignatures(msgs.zip(sigs), refundSig), cets)
}
/** The equivalent of [[createCETsAndCETSigs()]] but async */
def createCETsAndCETSigsAsync()(implicit
ec: ExecutionContext): Future[(CETSignatures, Vector[WitnessTransaction])] = {
val outcomes = builder.contractInfo.allOutcomes
val fn = { outcomes: Vector[OracleOutcome] =>
val adaptorPoints = builder.contractInfo.adaptorPointsIndexed
val fn = { adaptorPoints: Vector[Indexed[ECPublicKey]] =>
Future {
buildAndSignCETs(outcomes)
buildAndSignCETs(adaptorPoints)
}
}
val cetsAndSigsF: Future[Vector[
Vector[(OracleOutcome, WitnessTransaction, ECAdaptorSignature)]]] = {
FutureUtil.batchAndParallelExecute[OracleOutcome,
val cetsAndSigsF: Future[
Vector[Vector[(ECPublicKey, WitnessTransaction, ECAdaptorSignature)]]] = {
FutureUtil.batchAndParallelExecute[Indexed[ECPublicKey],
Vector[(
OracleOutcome,
ECPublicKey,
WitnessTransaction,
ECAdaptorSignature)]](elements =
outcomes,
f = fn)
ECAdaptorSignature)]](
elements = adaptorPoints,
f = fn)
}
val refundSig = signRefundTx
@ -252,7 +264,8 @@ case class DLCTxSigner(
}
/** Creates this party's CETSignatures given the outcomes and their unsigned CETs */
def createCETSigs(outcomesAndCETs: Vector[OutcomeCETPair]): CETSignatures = {
def createCETSigs(
outcomesAndCETs: Vector[AdaptorPointCETPair]): CETSignatures = {
val cetSigs = signGivenCETs(outcomesAndCETs)
val refundSig = signRefundTx
@ -296,38 +309,37 @@ object DLCTxSigner {
}
def signCET(
outcome: OracleOutcome,
sigPoint: ECPublicKey,
cet: WitnessTransaction,
cetSigningInfo: ECSignatureParams[P2WSHV0InputInfo],
fundingKey: AdaptorSign): ECAdaptorSignature = {
signCETs(Vector(OutcomeCETPair(outcome, cet)),
signCETs(Vector(AdaptorPointCETPair(sigPoint, cet)),
cetSigningInfo,
fundingKey).head._2
}
def signCETs(
outcomesAndCETs: Vector[OutcomeCETPair],
outcomesAndCETs: Vector[AdaptorPointCETPair],
cetSigningInfo: ECSignatureParams[P2WSHV0InputInfo],
fundingKey: AdaptorSign): Vector[(OracleOutcome, ECAdaptorSignature)] = {
fundingKey: AdaptorSign): Vector[(ECPublicKey, ECAdaptorSignature)] = {
buildAndSignCETs(outcomesAndCETs, cetSigningInfo, fundingKey).map {
case (outcome, _, sig) => outcome -> sig
}
}
def buildAndSignCETs(
outcomesAndCETs: Vector[OutcomeCETPair],
outcomesAndCETs: Vector[AdaptorPointCETPair],
cetSigningInfo: ECSignatureParams[P2WSHV0InputInfo],
fundingKey: AdaptorSign): Vector[
(OracleOutcome, WitnessTransaction, ECAdaptorSignature)] = {
outcomesAndCETs.map { case OutcomeCETPair(outcome, cet) =>
val adaptorPoint = outcome.sigPoint
(ECPublicKey, WitnessTransaction, ECAdaptorSignature)] = {
outcomesAndCETs.map { case AdaptorPointCETPair(sigPoint, cet) =>
val hashToSign =
TransactionSignatureSerializer.hashForSignature(cet,
cetSigningInfo,
HashType.sigHashAll)
val adaptorSig = fundingKey.adaptorSign(adaptorPoint, hashToSign.bytes)
(outcome, cet, adaptorSig)
val adaptorSig = fundingKey.adaptorSign(sigPoint, hashToSign.bytes)
(sigPoint, cet, adaptorSig)
}
}
@ -371,7 +383,7 @@ object DLCTxSigner {
.map(_.asInstanceOf[WitnessTransaction])
cetT match {
case Success(cet) => cet.asInstanceOf[WitnessTransaction]
case Success(cet) => cet
case Failure(err) => throw err
}
}

View file

@ -10,14 +10,13 @@ import org.bitcoins.core.policy.Policy
import org.bitcoins.core.protocol.dlc.build.DLCTxBuilder
import org.bitcoins.core.protocol.dlc.models.{
DLCFundingInput,
FundingSignatures,
OracleOutcome
FundingSignatures
}
import org.bitcoins.core.protocol.transaction.{Transaction, WitnessTransaction}
import org.bitcoins.core.psbt.InputPSBTRecord.PartialSignature
import org.bitcoins.core.psbt.PSBT
import org.bitcoins.core.script.crypto.HashType
import org.bitcoins.core.util.FutureUtil
import org.bitcoins.core.util.{FutureUtil, Indexed}
import org.bitcoins.crypto.{ECAdaptorSignature, ECPublicKey}
import scodec.bits.ByteVector
@ -37,15 +36,17 @@ case class DLCSignatureVerifier(builder: DLCTxBuilder, isInitiator: Boolean) {
}
/** Verifies remote's CET signature for a given outcome hash */
def verifyCETSig(outcome: OracleOutcome, sig: ECAdaptorSignature): Boolean = {
def verifyCETSig(
adaptorPoint: Indexed[ECPublicKey],
sig: ECAdaptorSignature): Boolean = {
val remoteFundingPubKey = if (isInitiator) {
builder.acceptFundingKey
} else {
builder.offerFundingKey
}
val cet = builder.buildCET(outcome)
val cet = builder.buildCET(adaptorPoint)
DLCSignatureVerifier.validateCETSignature(outcome,
DLCSignatureVerifier.validateCETSignature(adaptorPoint.element,
sig,
remoteFundingPubKey,
fundingTx,
@ -53,14 +54,14 @@ case class DLCSignatureVerifier(builder: DLCTxBuilder, isInitiator: Boolean) {
cet)
}
def verifyCETSigs(sigs: Vector[(OracleOutcome, ECAdaptorSignature)])(implicit
ec: ExecutionContext): Future[Boolean] = {
def verifyCETSigs(sigs: Vector[(Indexed[ECPublicKey], ECAdaptorSignature)])(
implicit ec: ExecutionContext): Future[Boolean] = {
val correctNumberOfSigs =
sigs.size >= builder.contractInfo.allOutcomes.length
def runVerify(
outcomeSigs: Vector[(OracleOutcome, ECAdaptorSignature)]): Future[
Boolean] = {
outcomeSigs: Vector[
(Indexed[ECPublicKey], ECAdaptorSignature)]): Future[Boolean] = {
Future {
outcomeSigs.foldLeft(true) { case (ret, (outcome, sig)) =>
ret && verifyCETSig(outcome, sig)
@ -89,15 +90,13 @@ case class DLCSignatureVerifier(builder: DLCTxBuilder, isInitiator: Boolean) {
object DLCSignatureVerifier {
def validateCETSignature(
outcome: OracleOutcome,
adaptorPoint: ECPublicKey,
sig: ECAdaptorSignature,
remoteFundingPubKey: ECPublicKey,
fundingTx: Transaction,
fundOutputIndex: Int,
cet: WitnessTransaction
): Boolean = {
val adaptorPoint = outcome.sigPoint
val sigComponent = WitnessTxSigComponentRaw(
transaction = cet,
inputIndex = UInt32.zero,

View file

@ -103,11 +103,17 @@ object FutureUtil {
elements: Vector[T],
f: Vector[T] => Future[U],
batchSize: Int)(implicit ec: ExecutionContext): Future[Vector[U]] = {
require(batchSize > 0, s"Cannot have batch size 0 or less, got=$batchSize")
val batches = elements.grouped(batchSize).toVector
val execute: Vector[Future[U]] = batches.map(b => f(b))
val doneF = Future.sequence(execute)
doneF
require(
batchSize > 0,
s"Cannot have batch size less than or equal to zero, got=$batchSize")
if (elements.isEmpty) {
Future.successful(Vector.empty)
} else {
val batches = elements.grouped(batchSize).toVector
val execute: Vector[Future[U]] = batches.map(b => f(b))
val doneF = Future.sequence(execute)
doneF
}
}
/** Same as [[batchAndParallelExecute()]], but computes the batchSize based on the

View file

@ -102,6 +102,10 @@ sealed trait PublicKey extends NetworkElement {
fromBytes(x.+:(leadByte))
}
}
override def hashCode: Int = {
bytes.hashCode
}
}
/** Wraps raw ECPublicKey bytes without doing any validation or deserialization (may be invalid). */

View file

@ -153,14 +153,16 @@ class DbCommonsColumnMappers(val profile: JdbcProfile) {
}
implicit val uint64Mapper: BaseColumnType[UInt64] = {
MappedColumnType.base[UInt64, BigDecimal](
MappedColumnType.base[UInt64, String](
{ u64: UInt64 =>
BigDecimal(u64.toBigInt.bigInteger)
val bytes = u64.bytes
val padded = if (bytes.length <= 8) {
bytes.padLeft(8)
} else bytes
padded.toHex
},
//this has the potential to throw
{ bigDec: BigDecimal =>
UInt64(bigDec.toBigIntExact.get)
}
UInt64.fromHex
)
}

View file

@ -20,7 +20,7 @@ import org.bitcoins.core.protocol.tlv.{
}
import org.bitcoins.core.protocol.transaction._
import org.bitcoins.core.script.crypto.HashType
import org.bitcoins.core.util.{BitcoinScriptUtil, NumberUtil}
import org.bitcoins.core.util.{BitcoinScriptUtil, Indexed, NumberUtil}
import org.bitcoins.core.wallet.utxo._
import org.bitcoins.crypto._
import org.bitcoins.testkitcore.dlc.{DLCFeeTestUtil, DLCTest, TestDLCClient}
@ -334,6 +334,39 @@ class DLCClientTest extends BitcoinSJvmTest with DLCTest {
assert(!acceptVerifier.verifyRemoteFundingSigs(acceptFundingSigs))
}
it should "succeed on valid CET signatures" in {
val (offerClient, acceptClient, outcomes) =
constructDLCClients(numOutcomesOrDigits = 2,
isNumeric = false,
oracleThreshold = 1,
numOracles = 1,
paramsOpt = None)
val builder = offerClient.dlcTxBuilder
val offerVerifier = DLCSignatureVerifier(builder, isInitiator = true)
val acceptVerifier = DLCSignatureVerifier(builder, isInitiator = false)
val offerCETSigs = offerClient.dlcTxSigner.createCETSigs()
val acceptCETSigs = acceptClient.dlcTxSigner.createCETSigs()
outcomes.zipWithIndex.foreach { case (outcomeUncast, index) =>
val outcome = EnumOracleOutcome(
Vector(offerClient.offer.oracleInfo.asInstanceOf[EnumSingleOracleInfo]),
outcomeUncast.asInstanceOf[EnumOutcome])
assert(
offerVerifier.verifyCETSig(Indexed(outcome.sigPoint, index),
acceptCETSigs(outcome.sigPoint)))
assert(
acceptVerifier.verifyCETSig(Indexed(outcome.sigPoint, index),
offerCETSigs(outcome.sigPoint)))
}
assert(offerVerifier.verifyRefundSig(acceptCETSigs.refundSig))
assert(offerVerifier.verifyRefundSig(offerCETSigs.refundSig))
assert(acceptVerifier.verifyRefundSig(offerCETSigs.refundSig))
assert(acceptVerifier.verifyRefundSig(acceptCETSigs.refundSig))
}
it should "fail on invalid CET signatures" in {
val (offerClient, acceptClient, outcomes) =
constructDLCClients(numOutcomesOrDigits = 3,
@ -360,15 +393,16 @@ class DLCClientTest extends BitcoinSJvmTest with DLCTest {
val oracleSig = genEnumOracleSignature(oracleInfo, outcome.outcome)
assertThrows[RuntimeException] {
offerClient.dlcTxSigner.completeCET(oracleOutcome,
badAcceptCETSigs(oracleOutcome),
Vector(oracleSig))
offerClient.dlcTxSigner.completeCET(
oracleOutcome,
badAcceptCETSigs(oracleOutcome.sigPoint),
Vector(oracleSig))
}
assertThrows[RuntimeException] {
acceptClient.dlcTxSigner
.completeCET(oracleOutcome,
badOfferCETSigs(oracleOutcome),
badOfferCETSigs(oracleOutcome.sigPoint),
Vector(oracleSig))
}
}
@ -381,29 +415,25 @@ class DLCClientTest extends BitcoinSJvmTest with DLCTest {
acceptClient.dlcTxSigner.completeRefundTx(badOfferCETSigs.refundSig)
}
outcomes.foreach { outcomeUncast =>
outcomes.zipWithIndex.foreach { case (outcomeUncast, index) =>
val outcome = EnumOracleOutcome(
Vector(offerClient.offer.oracleInfo.asInstanceOf[EnumSingleOracleInfo]),
outcomeUncast.asInstanceOf[EnumOutcome])
val adaptorPoint = Indexed(outcome.sigPoint, index)
assert(offerVerifier.verifyCETSig(outcome, acceptCETSigs(outcome)))
assert(acceptVerifier.verifyCETSig(outcome, offerCETSigs(outcome)))
}
assert(offerVerifier.verifyRefundSig(acceptCETSigs.refundSig))
assert(offerVerifier.verifyRefundSig(offerCETSigs.refundSig))
assert(acceptVerifier.verifyRefundSig(offerCETSigs.refundSig))
assert(acceptVerifier.verifyRefundSig(acceptCETSigs.refundSig))
assert(
!offerVerifier.verifyCETSig(adaptorPoint,
badAcceptCETSigs(outcome.sigPoint)))
assert(
!acceptVerifier.verifyCETSig(adaptorPoint,
badOfferCETSigs(outcome.sigPoint)))
outcomes.foreach { outcomeUncast =>
val outcome = EnumOracleOutcome(
Vector(offerClient.offer.oracleInfo.asInstanceOf[EnumSingleOracleInfo]),
outcomeUncast.asInstanceOf[EnumOutcome])
assert(!offerVerifier.verifyCETSig(outcome, badAcceptCETSigs(outcome)))
assert(!acceptVerifier.verifyCETSig(outcome, badOfferCETSigs(outcome)))
assert(!offerVerifier.verifyCETSig(outcome, offerCETSigs(outcome)))
assert(!acceptVerifier.verifyCETSig(outcome, acceptCETSigs(outcome)))
assert(
!offerVerifier.verifyCETSig(adaptorPoint,
offerCETSigs(outcome.sigPoint)))
assert(
!acceptVerifier.verifyCETSig(adaptorPoint,
acceptCETSigs(outcome.sigPoint)))
}
assert(!offerVerifier.verifyRefundSig(badAcceptCETSigs.refundSig))
assert(!offerVerifier.verifyRefundSig(badOfferCETSigs.refundSig))
@ -411,6 +441,36 @@ class DLCClientTest extends BitcoinSJvmTest with DLCTest {
assert(!acceptVerifier.verifyRefundSig(badAcceptCETSigs.refundSig))
}
it should "compute sigpoints correctly" in {
runTestsForParam(Vector(4, 6, 8)) { numDigitsOrOutcomes =>
runTestsForParam(Vector(true, false)) { isNumeric =>
runTestsForParam(Vector((1, 1), (2, 3), (3, 5))) {
case (threshold, numOracles) =>
runTestsForParam(
Vector(None,
Some(
OracleParamsV0TLV(numDigitsOrOutcomes / 2 + 1,
numDigitsOrOutcomes / 2,
maximizeCoverage = true)))) {
oracleParams =>
val (client, _, _) = constructDLCClients(numDigitsOrOutcomes,
isNumeric,
threshold,
numOracles,
oracleParams)
val contract = client.offer.contractInfo
val outcomes = contract.allOutcomes
val adaptorPoints = contract.adaptorPoints
val expectedAdaptorPoints = outcomes.map(_.sigPoint)
assert(adaptorPoints == expectedAdaptorPoints)
}
}
}
}
}
def assertCorrectSigDerivation(
offerSetup: SetupDLC,
dlcOffer: TestDLCClient,

View file

@ -54,9 +54,10 @@ class SetupDLCTest extends BitcoinSJvmTest {
refundTx: WitnessTransaction = validRefundTx): SetupDLC = {
SetupDLC(
fundingTx = fundingTx,
cets = Vector(
EnumOracleOutcome(Vector(oracleInfo), EnumOutcome("WIN")) -> cet0,
EnumOracleOutcome(Vector(oracleInfo), EnumOutcome("LOSE")) -> cet1),
cets = Vector(EnumOracleOutcome(Vector(oracleInfo),
EnumOutcome("WIN")).sigPoint -> cet0,
EnumOracleOutcome(Vector(oracleInfo),
EnumOutcome("LOSE")).sigPoint -> cet1),
refundTx = refundTx
)
}

View file

@ -207,7 +207,7 @@ object DLCTLVGen {
ECPublicKey.freshPublicKey): CETSignatures = {
CETSignatures(
outcomes.map(outcome =>
EnumOracleOutcome(Vector(oracleInfo), outcome) -> adaptorSig),
EnumOracleOutcome(Vector(oracleInfo), outcome).sigPoint -> adaptorSig),
partialSig(fundingPubKey, sigHashByte = false))
}

View file

@ -22,6 +22,7 @@ import org.bitcoins.core.protocol.transaction.{
}
import org.bitcoins.core.protocol.{BitcoinAddress, BlockTimeStamp}
import org.bitcoins.core.script.crypto.HashType
import org.bitcoins.core.util.Indexed
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
import org.bitcoins.core.wallet.utxo.{
ConditionalPath,
@ -186,12 +187,13 @@ case class ValidTestInputs(
def buildTransactions: DLCTransactions = {
val builder = this.builder
val fundingTx = builder.buildFundingTx
val cets =
val adaptorPoints =
params.contractInfo
.map(_.preImage)
.map(EnumOutcome.apply)
.map(outcome => EnumOracleOutcome(Vector(params.oracleInfo), outcome))
.map(builder.buildCET)
.map(_.sigPoint)
val cets = builder.buildCETs(Indexed(adaptorPoints))
val refundTx = builder.buildRefundTx
DLCTransactions(fundingTx, cets, refundTx)

View file

@ -246,7 +246,7 @@ object DLCTxGen {
EnumOracleOutcome(Vector(inputs.params.oracleInfo),
EnumOutcome(outcomeStr))
val accpetCETSigs = acceptSigner.createCETSigs()
val acceptCETSigs = acceptSigner.createCETSigs()
val offerCETSigs = offerSigner.createCETSigs()
for {
@ -256,23 +256,23 @@ object DLCTxGen {
signedFundingTx <- acceptSigner.completeFundingTx(offerFundingSigs)
} yield {
val signedRefundTx = offerSigner.completeRefundTx(accpetCETSigs.refundSig)
val signedRefundTx = offerSigner.completeRefundTx(acceptCETSigs.refundSig)
val offerSignedCET = offerSigner.completeCET(
outcome,
accpetCETSigs(outcome),
acceptCETSigs(outcome.sigPoint),
Vector(
EnumOracleSignature(inputs.params.oracleInfo,
inputs.params.oracleSignature)))
val acceptSignedCET = acceptSigner.completeCET(
outcome,
offerCETSigs(outcome),
offerCETSigs(outcome.sigPoint),
Vector(
EnumOracleSignature(inputs.params.oracleInfo,
inputs.params.oracleSignature)))
val accept = acceptWithoutSigs.withSigs(accpetCETSigs)
val accept = acceptWithoutSigs.withSigs(acceptCETSigs)
val contractId = fundingTx.txIdBE.bytes.xor(accept.tempContractId.bytes)
val sign = DLCSign(offerCETSigs, offerFundingSigs, contractId)

View file

@ -98,7 +98,7 @@ case class TestDLCClient(
}
cetSigs =
dlcTxSigner.createCETSigs(setupDLCWithoutFundingTxSigs.cets.map {
case (msg, info) => OutcomeCETPair(msg, info.tx)
case (msg, info) => AdaptorPointCETPair(msg, info.tx)
})
localFundingSigs <- Future.fromTry {
dlcTxSigner.signFundingTx()

View file

@ -3,10 +3,10 @@ package org.bitcoins.testkit
import com.typesafe.config._
import org.bitcoins.dlc.oracle.config.DLCOracleAppConfig
import org.bitcoins.server.BitcoinSAppConfig
import org.bitcoins.testkitcore.Implicits.GeneratorOps
import org.bitcoins.testkitcore.gen.{NumberGenerator, StringGenerators}
import org.bitcoins.testkit.keymanager.KeyManagerTestUtil
import org.bitcoins.testkit.util.FileUtil
import org.bitcoins.testkitcore.Implicits.GeneratorOps
import org.bitcoins.testkitcore.gen.{NumberGenerator, StringGenerators}
import java.nio.file._
import scala.concurrent.ExecutionContext
@ -149,9 +149,10 @@ object BitcoinSTestAppConfig {
case object Node extends ProjectType
case object Chain extends ProjectType
case object Oracle extends ProjectType
case object DLC extends ProjectType
case object Test extends ProjectType
val all = List(Wallet, Node, Chain, Oracle, Test)
val all = List(Wallet, Node, Chain, Oracle, DLC, Test)
}
/** Generates a Typesafe config with DBs set to memory
@ -178,6 +179,7 @@ object BitcoinSTestAppConfig {
case ProjectType.Chain => "chain"
case ProjectType.Node => "node"
case ProjectType.Oracle => "oracle"
case ProjectType.DLC => "dlc"
case ProjectType.Test => "test"
}