From 7ac9cd1525131f4199be221dd3aa1026747c973f Mon Sep 17 00:00:00 2001 From: Ben Carman Date: Tue, 10 Nov 2020 06:08:43 -0600 Subject: [PATCH] Use New Oracle TLVs in DLCOracle (#2162) * Update Oracle to use new TLVs * Rename things to use new names, scaladoc, small clean ups * Add descomposition tests, docs, sign numbers outside of range --- .../jsonmodels/dlc/SigningVersion.scala | 73 ++- .../commons/serializers/Picklers.scala | 20 + .../scala/org/bitcoins/cli/CliReaders.scala | 41 ++ .../scala/org/bitcoins/cli/ConsoleCli.scala | 320 +++++++++- .../src/main/resources/logback.xml | 73 +++ .../bitcoins/oracle/server/OracleRoutes.scala | 182 +++++- .../oracle/server/OracleServerMain.scala | 2 +- .../bitcoins/server/ServerJsonModels.scala | 168 ++++- .../bitcoins/core/util/NumberUtilTest.scala | 42 +- .../org/bitcoins/core/protocol/tlv/TLV.scala | 17 +- .../org/bitcoins/core/util/NumberUtil.scala | 16 + .../scala/org/bitcoins/db/AppConfigTest.scala | 2 +- .../org/bitcoins/db/DbManagementTest.scala | 6 +- .../bitcoins/db/DbCommonsColumnMappers.scala | 7 + .../scala/org/bitcoins/db/DbManagement.scala | 12 + .../bitcoins/dlc/oracle/DLCOracleTest.scala | 587 ++++++++++++++---- .../dlc/oracle/storage/EventDAOTest.scala | 43 +- .../oracle/storage/EventOutcomeDAOTest.scala | 39 +- .../dlc/oracle/storage/RValueDAOTest.scala | 13 +- .../migration/V2__add_event_descriptor.sql | 112 ++++ .../migration/V2__add_event_descriptor.sql | 112 ++++ .../dlc/oracle/DLCAttestationType.scala | 31 + .../org/bitcoins/dlc/oracle/DLCOracle.scala | 343 +++++++--- .../org/bitcoins/dlc/oracle/OracleEvent.scala | 237 +++++++ .../{ => config}/DLCOracleAppConfig.scala | 32 +- .../dlc/oracle/storage/EventDAO.scala | 30 +- .../bitcoins/dlc/oracle/storage/EventDb.scala | 20 +- .../dlc/oracle/storage/EventOutcomeDAO.scala | 5 +- .../dlc/oracle/storage/EventOutcomeDb.scala | 16 +- .../dlc/oracle/storage/RValueDAO.scala | 15 +- .../dlc/oracle/storage/RValueDb.scala | 11 +- docs/oracle/oracle-server.md | 235 +++++-- .../testkit/BitcoinSTestAppConfig.scala | 2 +- .../fixtures/DLCOracleDAOFixture.scala | 2 +- 34 files changed, 2477 insertions(+), 389 deletions(-) create mode 100644 app/oracle-server/src/main/resources/logback.xml create mode 100644 dlc-oracle/src/main/resources/postgresql/oracle/migration/V2__add_event_descriptor.sql create mode 100644 dlc-oracle/src/main/resources/sqlite/oracle/migration/V2__add_event_descriptor.sql create mode 100644 dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/DLCAttestationType.scala create mode 100644 dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/OracleEvent.scala rename dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/{ => config}/DLCOracleAppConfig.scala (80%) diff --git a/app-commons/src/main/scala/org/bitcoins/commons/jsonmodels/dlc/SigningVersion.scala b/app-commons/src/main/scala/org/bitcoins/commons/jsonmodels/dlc/SigningVersion.scala index 516779d30a..864c77bf64 100644 --- a/app-commons/src/main/scala/org/bitcoins/commons/jsonmodels/dlc/SigningVersion.scala +++ b/app-commons/src/main/scala/org/bitcoins/commons/jsonmodels/dlc/SigningVersion.scala @@ -1,27 +1,80 @@ package org.bitcoins.commons.jsonmodels.dlc -import org.bitcoins.crypto.StringFactory +import org.bitcoins.core.protocol.tlv._ +import org.bitcoins.crypto.{CryptoUtil, SchnorrNonce, StringFactory} +import scodec.bits.ByteVector sealed abstract class SigningVersion { - def nonceTag: String - def announcementTag: String - def outcomeTag: String + + /** Calculates the tweak for the oracle's pre-committed nonce */ + def calcNonceTweak(nonce: SchnorrNonce, eventName: String): ByteVector + + /** Calculates the bytes to sign for an OracleAnnouncement */ + def calcAnnouncementHash(eventTLV: OracleEventTLV): ByteVector + + /** Calculates the bytes to sign for an event outcome */ + def calcOutcomeHash( + descriptor: EventDescriptorTLV, + bytes: ByteVector): ByteVector + + /** Calculates the bytes to sign for an event outcome */ + final def calcOutcomeHash( + descriptor: EventDescriptorTLV, + string: String): ByteVector = { + calcOutcomeHash(descriptor, CryptoUtil.serializeForHash(string)) + } } object SigningVersion extends StringFactory[SigningVersion] { /** Initial signing version that was created, not a part of any spec */ final case object Mock extends SigningVersion { - override def nonceTag: String = "DLCv0/Nonce" - override def announcementTag: String = "DLCv0/Announcement" + override def calcNonceTweak( + nonce: SchnorrNonce, + eventName: String): ByteVector = { + val bytes = nonce.bytes ++ CryptoUtil.serializeForHash(eventName) - override def outcomeTag: String = "DLCv0/Outcome" + CryptoUtil.taggedSha256(bytes, "DLCv0/Nonce").bytes + } + + override def calcAnnouncementHash(eventTLV: OracleEventTLV): ByteVector = + CryptoUtil.taggedSha256(eventTLV.bytes, "DLCv0/Announcement").bytes + + override def calcOutcomeHash( + descriptor: EventDescriptorTLV, + byteVector: ByteVector): ByteVector = + CryptoUtil.taggedSha256(byteVector, "DLCv0/Outcome").bytes } - val latest: SigningVersion = Mock + /** Used before we had an actual signing algorithm in the spec */ + final case object BasicSHA256SigningVersion extends SigningVersion { - val all: Vector[SigningVersion] = Vector(Mock) + override def calcNonceTweak( + nonce: SchnorrNonce, + eventName: String): ByteVector = { + val bytes = nonce.bytes ++ CryptoUtil.serializeForHash(eventName) + + CryptoUtil.taggedSha256(bytes, "BasicSHA256").bytes + } + + override def calcAnnouncementHash(eventTLV: OracleEventTLV): ByteVector = + CryptoUtil.sha256(eventTLV.bytes).bytes + + override def calcOutcomeHash( + descriptor: EventDescriptorTLV, + byteVector: ByteVector): ByteVector = { + descriptor match { + case _: EnumEventDescriptorV0TLV | _: RangeEventDescriptorV0TLV | + _: DigitDecompositionEventDescriptorV0TLV => + CryptoUtil.sha256(byteVector).bytes + } + } + } + + val latest: SigningVersion = BasicSHA256SigningVersion + + val all: Vector[SigningVersion] = Vector(Mock, BasicSHA256SigningVersion) override def fromStringOpt(str: String): Option[SigningVersion] = { all.find(state => str.toLowerCase() == state.toString.toLowerCase) @@ -31,7 +84,7 @@ object SigningVersion extends StringFactory[SigningVersion] { fromStringOpt(string) match { case Some(state) => state case None => - sys.error(s"Could not find signing version for string=${string}") + sys.error(s"Could not find signing version for string=$string") } } } diff --git a/app-commons/src/main/scala/org/bitcoins/commons/serializers/Picklers.scala b/app-commons/src/main/scala/org/bitcoins/commons/serializers/Picklers.scala index 9b418b4896..3b102397ef 100644 --- a/app-commons/src/main/scala/org/bitcoins/commons/serializers/Picklers.scala +++ b/app-commons/src/main/scala/org/bitcoins/commons/serializers/Picklers.scala @@ -8,6 +8,7 @@ import org.bitcoins.core.api.wallet.CoinSelectionAlgo import org.bitcoins.core.crypto.ExtPublicKey import org.bitcoins.core.currency.{Bitcoins, Satoshis} import org.bitcoins.core.number.UInt32 +import org.bitcoins.core.protocol.tlv._ import org.bitcoins.core.protocol.transaction.{Transaction, TransactionOutPoint} import org.bitcoins.core.protocol.{BitcoinAddress, BlockStamp} import org.bitcoins.core.psbt.InputPSBTRecord.PartialSignature @@ -34,6 +35,25 @@ object Picklers { implicit val schnorrNoncePickler: ReadWriter[SchnorrNonce] = readwriter[String].bimap(_.hex, SchnorrNonce.fromHex) + implicit val enumEventDescriptorPickler: ReadWriter[ + EnumEventDescriptorV0TLV] = + readwriter[String].bimap(_.hex, EnumEventDescriptorV0TLV.fromHex) + + implicit val rangeEventDescriptorPickler: ReadWriter[ + RangeEventDescriptorV0TLV] = + readwriter[String].bimap(_.hex, RangeEventDescriptorV0TLV.fromHex) + + implicit val digitDecompEventDescriptorPickler: ReadWriter[ + DigitDecompositionEventDescriptorV0TLV] = + readwriter[String].bimap(_.hex, + DigitDecompositionEventDescriptorV0TLV.fromHex) + + implicit val eventDescriptorPickler: ReadWriter[EventDescriptorTLV] = + readwriter[String].bimap(_.hex, EventDescriptorTLV.fromHex) + + implicit val oracleEventVoPickler: ReadWriter[OracleEventV0TLV] = + readwriter[String].bimap(_.hex, OracleEventV0TLV.fromHex) + implicit val instantPickler: ReadWriter[Instant] = readwriter[Long].bimap(_.getEpochSecond, Instant.ofEpochSecond) diff --git a/app/cli/src/main/scala/org/bitcoins/cli/CliReaders.scala b/app/cli/src/main/scala/org/bitcoins/cli/CliReaders.scala index 8412156875..4b14da7fdc 100644 --- a/app/cli/src/main/scala/org/bitcoins/cli/CliReaders.scala +++ b/app/cli/src/main/scala/org/bitcoins/cli/CliReaders.scala @@ -10,6 +10,7 @@ import org.bitcoins.core.currency._ import org.bitcoins.core.number.UInt32 import org.bitcoins.core.protocol.BlockStamp.BlockTime import org.bitcoins.core.protocol._ +import org.bitcoins.core.protocol.tlv._ import org.bitcoins.core.protocol.transaction.{Transaction, TransactionOutPoint} import org.bitcoins.core.psbt.InputPSBTRecord.PartialSignature import org.bitcoins.core.psbt.PSBT @@ -50,6 +51,46 @@ object CliReaders { override def reads: String => SchnorrNonce = SchnorrNonce.fromHex } + implicit val eventDescriptorReads: Read[EventDescriptorTLV] = + new Read[EventDescriptorTLV] { + override def arity: Int = 1 + + override def reads: String => EventDescriptorTLV = + EventDescriptorTLV.fromHex + } + + implicit val enumEventDescriptorReads: Read[EnumEventDescriptorV0TLV] = + new Read[EnumEventDescriptorV0TLV] { + override def arity: Int = 1 + + override def reads: String => EnumEventDescriptorV0TLV = + EnumEventDescriptorV0TLV.fromHex + } + + implicit val rangeEventDescriptorReads: Read[RangeEventDescriptorV0TLV] = + new Read[RangeEventDescriptorV0TLV] { + override def arity: Int = 1 + + override def reads: String => RangeEventDescriptorV0TLV = + RangeEventDescriptorV0TLV.fromHex + } + + implicit val digitDecompEventDescriptorReads: Read[ + DigitDecompositionEventDescriptorV0TLV] = + new Read[DigitDecompositionEventDescriptorV0TLV] { + override def arity: Int = 1 + + override def reads: String => DigitDecompositionEventDescriptorV0TLV = + DigitDecompositionEventDescriptorV0TLV.fromHex + } + + implicit val oracleEventV0TLVReads: Read[OracleEventV0TLV] = + new Read[OracleEventV0TLV] { + override def arity: Int = 1 + + override def reads: String => OracleEventV0TLV = OracleEventV0TLV.fromHex + } + implicit val instantReads: Read[Instant] = new Read[Instant] { override def arity: Int = 1 diff --git a/app/cli/src/main/scala/org/bitcoins/cli/ConsoleCli.scala b/app/cli/src/main/scala/org/bitcoins/cli/ConsoleCli.scala index 09f78404b2..f3d1d28de0 100644 --- a/app/cli/src/main/scala/org/bitcoins/cli/ConsoleCli.scala +++ b/app/cli/src/main/scala/org/bitcoins/cli/ConsoleCli.scala @@ -11,6 +11,7 @@ import org.bitcoins.core.api.wallet.CoinSelectionAlgo import org.bitcoins.core.config.NetworkParameters import org.bitcoins.core.currency._ import org.bitcoins.core.number.UInt32 +import org.bitcoins.core.protocol.tlv._ import org.bitcoins.core.protocol.transaction.{ EmptyTransaction, Transaction, @@ -20,11 +21,7 @@ import org.bitcoins.core.protocol.{BitcoinAddress, BlockStamp} import org.bitcoins.core.psbt.PSBT import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte import org.bitcoins.core.wallet.utxo.AddressLabelTag -import org.bitcoins.crypto.{ - SchnorrDigitalSignature, - SchnorrNonce, - Sha256DigestBE -} +import org.bitcoins.crypto.{SchnorrDigitalSignature, Sha256DigestBE} import scopt.OParser import ujson._ import upickle.{default => up} @@ -1025,14 +1022,14 @@ object ConsoleCli { .text(s"Get oracle's staking address"), cmd("listevents") .action((_, conf) => conf.copy(command = ListEvents)) - .text(s"Lists all event nonces"), + .text(s"Lists all oracle event TLVs"), cmd("createevent") .action((_, conf) => conf.copy(command = CreateEvent("", Instant.MIN, Seq.empty))) .text("Registers an oracle event") .children( - arg[String]("label") - .text("Label for this event") + arg[String]("name") + .text("Name for this event") .required() .action((label, conf) => conf.copy(command = conf.command match { @@ -1059,17 +1056,159 @@ object ConsoleCli { case other => other })) ), + cmd("createrangedevent") + .action((_, conf) => + conf.copy(command = + CreateRangedEvent("", Instant.MIN, 0, 0, 1, "", 0))) + .text("Registers an oracle event with a range of outcomes") + .children( + arg[String]("name") + .text("Name for this event") + .required() + .action((name, conf) => + conf.copy(command = conf.command match { + case createRangedEvent: CreateRangedEvent => + createRangedEvent.copy(eventName = name) + case other => other + })), + arg[Instant]("maturationtime") + .text("The earliest expected time an outcome will be signed, given in epoch second") + .required() + .action((time, conf) => + conf.copy(command = conf.command match { + case createRangedEvent: CreateRangedEvent => + createRangedEvent.copy(maturationTime = time) + case other => other + })), + arg[Int]("start") + .text("The first possible outcome number") + .required() + .action((start, conf) => + conf.copy(command = conf.command match { + case createRangedEvent: CreateRangedEvent => + createRangedEvent.copy(start = start) + case other => other + })), + arg[Int]("stop") + .text("The last possible outcome number") + .required() + .action((stop, conf) => + conf.copy(command = conf.command match { + case createRangedEvent: CreateRangedEvent => + createRangedEvent.copy(stop = stop) + case other => other + })), + arg[Int]("step") + .text("The increment between each outcome") + .action((step, conf) => + conf.copy(command = conf.command match { + case createRangedEvent: CreateRangedEvent => + createRangedEvent.copy(step = step) + case other => other + })), + arg[String]("unit") + .text("The unit denomination of the outcome value") + .action((unit, conf) => + conf.copy(command = conf.command match { + case createRangedEvent: CreateRangedEvent => + createRangedEvent.copy(unit = unit) + case other => other + })), + arg[Int]("precision") + .text("The precision of the outcome representing the " + + "base exponent by which to multiply the number represented by " + + "the composition of the digits to obtain the actual outcome value.") + .action((precision, conf) => + conf.copy(command = conf.command match { + case createRangedEvent: CreateRangedEvent => + createRangedEvent.copy(precision = precision) + case other => other + })) + ), + cmd("createdigitdecompevent") + .action((_, conf) => + conf.copy(command = CreateDigitDecompEvent("", + Instant.MIN, + 0, + isSigned = false, + 0, + "", + 0))) + .text("Registers an oracle event that uses digit decomposition when signing the number") + .children( + arg[String]("name") + .text("Name for this event") + .required() + .action((name, conf) => + conf.copy(command = conf.command match { + case createLargeRangedEvent: CreateDigitDecompEvent => + createLargeRangedEvent.copy(eventName = name) + case other => other + })), + arg[Instant]("maturationtime") + .text("The earliest expected time an outcome will be signed, given in epoch second") + .required() + .action((time, conf) => + conf.copy(command = conf.command match { + case createLargeRangedEvent: CreateDigitDecompEvent => + createLargeRangedEvent.copy(maturationTime = time) + case other => other + })), + arg[Int]("base") + .text("The base in which the outcome value is decomposed") + .required() + .action((base, conf) => + conf.copy(command = conf.command match { + case createLargeRangedEvent: CreateDigitDecompEvent => + createLargeRangedEvent.copy(base = base) + case other => other + })), + arg[Int]("numdigits") + .text("The max number of digits the outcome can have") + .action((num, conf) => + conf.copy(command = conf.command match { + case createLargeRangedEvent: CreateDigitDecompEvent => + createLargeRangedEvent.copy(numDigits = num) + case other => other + })), + opt[Unit]("signed") + .text("Whether the outcomes can be negative") + .action((_, conf) => + conf.copy(command = conf.command match { + case createLargeRangedEvent: CreateDigitDecompEvent => + createLargeRangedEvent.copy(isSigned = true) + case other => other + })), + arg[String]("unit") + .text("The unit denomination of the outcome value") + .action((unit, conf) => + conf.copy(command = conf.command match { + case createRangedEvent: CreateDigitDecompEvent => + createRangedEvent.copy(unit = unit) + case other => other + })), + arg[Int]("precision") + .text("The precision of the outcome representing the " + + "base exponent by which to multiply the number represented by " + + "the composition of the digits to obtain the actual outcome value.") + .action((precision, conf) => + conf.copy(command = conf.command match { + case createLargeRangedEvent: CreateDigitDecompEvent => + createLargeRangedEvent.copy(precision = precision) + case other => other + })) + ), cmd("getevent") .action((_, conf) => conf.copy(command = GetEvent(null))) .text("Get an event's details") .children( - arg[SchnorrNonce]("nonce") - .text("Nonce associated with the event") + arg[OracleEventV0TLV]("event") + .text("The event's oracle event tlv") .required() - .action((nonce, conf) => + .action((oracleEvent, conf) => conf.copy(command = conf.command match { case getEvent: GetEvent => - getEvent.copy(nonce = nonce) + getEvent.copy(oracleEventV0TLV = oracleEvent) case other => other })) ), @@ -1077,13 +1216,13 @@ object ConsoleCli { .action((_, conf) => conf.copy(command = SignEvent(null, ""))) .text("Signs an event") .children( - arg[SchnorrNonce]("nonce") - .text("Nonce associated with the event to sign") + arg[OracleEventV0TLV]("event") + .text("The event's oracle event tlv") .required() - .action((nonce, conf) => + .action((event, conf) => conf.copy(command = conf.command match { case signEvent: SignEvent => - signEvent.copy(nonce = nonce) + signEvent.copy(oracleEventV0TLV = event) case other => other })), arg[String]("outcome") @@ -1096,17 +1235,63 @@ object ConsoleCli { case other => other })) ), - cmd("getsignature") - .action((_, conf) => conf.copy(command = GetSignature(null))) - .text("Get the signature from a signed event") + cmd("signforrange") + .action((_, conf) => conf.copy(command = SignForRange(null, 0))) + .text("Signs a ranged event") .children( - arg[SchnorrNonce]("nonce") - .text("Nonce associated with the signed event") + arg[OracleEventV0TLV]("event") + .text("The event's oracle event tlv") .required() - .action((nonce, conf) => + .action((event, conf) => conf.copy(command = conf.command match { - case getSignature: GetSignature => - getSignature.copy(nonce = nonce) + case signRange: SignForRange => + signRange.copy(oracleEventV0TLV = event) + case other => other + })), + arg[Long]("outcome") + .text("Number to sign for this event") + .required() + .action((num, conf) => + conf.copy(command = conf.command match { + case signRange: SignForRange => + signRange.copy(num = num) + case other => other + })) + ), + cmd("signdigits") + .action((_, conf) => conf.copy(command = SignDigits(null, 0))) + .text("Signs a large range event") + .children( + arg[OracleEventV0TLV]("event") + .text("The event's oracle event tlv") + .required() + .action((event, conf) => + conf.copy(command = conf.command match { + case signDigits: SignDigits => + signDigits.copy(oracleEventV0TLV = event) + case other => other + })), + arg[Long]("outcome") + .text("The event's oracle event tlv") + .required() + .action((num, conf) => + conf.copy(command = conf.command match { + case signDigits: SignDigits => + signDigits.copy(num = num) + case other => other + })) + ), + cmd("getsignatures") + .action((_, conf) => conf.copy(command = GetSignatures(null))) + .text("Get the signatures from a signed event") + .children( + arg[OracleEventV0TLV]("event") + .text("The event descriptor associated with the event to sign") + .required() + .action((event, conf) => + conf.copy(command = conf.command match { + case getSignature: GetSignatures => + getSignature.copy(oracleEventV0TLV = event) case other => other })) ), @@ -1329,16 +1514,55 @@ object ConsoleCli { RequestParam("getstakingaddress") case ListEvents => RequestParam("listevents") - case GetEvent(nonce) => - RequestParam("getevent", Seq(up.writeJs(nonce))) + case GetEvent(tlv) => + RequestParam("getevent", Seq(up.writeJs(tlv))) case CreateEvent(label, time, outcomes) => RequestParam( "createevent", Seq(up.writeJs(label), up.writeJs(time), up.writeJs(outcomes))) - case SignEvent(nonce, outcome) => - RequestParam("signevent", Seq(up.writeJs(nonce), up.writeJs(outcome))) - case GetSignature(nonce) => - RequestParam("getsignature", Seq(up.writeJs(nonce))) + case CreateRangedEvent(eventName, + time, + start, + stop, + step, + unit, + precision) => + RequestParam( + "createrangedevent", + Seq(up.writeJs(eventName), + up.writeJs(time), + up.writeJs(start), + up.writeJs(stop), + up.writeJs(step), + up.writeJs(unit), + up.writeJs(precision)) + ) + + case CreateDigitDecompEvent(eventName, + time, + base, + isSigned, + numDigits, + unit, + precision) => + RequestParam( + "createdigitdecompevent", + Seq(up.writeJs(eventName), + up.writeJs(time), + up.writeJs(base), + up.writeJs(isSigned), + up.writeJs(numDigits), + up.writeJs(unit), + up.writeJs(precision)) + ) + case SignEvent(tlv, outcome) => + RequestParam("signevent", Seq(up.writeJs(tlv), up.writeJs(outcome))) + case SignForRange(tlv, num) => + RequestParam("signforrange", Seq(up.writeJs(tlv), up.writeJs(num))) + case SignDigits(tlv, num) => + RequestParam("signlargenumber", Seq(up.writeJs(tlv), up.writeJs(num))) + case GetSignatures(tlv) => + RequestParam("getsignatures", Seq(up.writeJs(tlv))) case NoCommand => ??? } @@ -1616,13 +1840,43 @@ object CliCommand { case object GetStakingAddress extends CliCommand case object ListEvents extends CliCommand - case class GetEvent(nonce: SchnorrNonce) extends CliCommand + case class GetEvent(oracleEventV0TLV: OracleEventV0TLV) extends CliCommand case class CreateEvent( label: String, maturationTime: Instant, outcomes: Seq[String]) extends CliCommand - case class SignEvent(nonce: SchnorrNonce, outcome: String) extends CliCommand - case class GetSignature(nonce: SchnorrNonce) extends CliCommand + + case class CreateRangedEvent( + eventName: String, + maturationTime: Instant, + start: Int, + stop: Int, + step: Int, + unit: String, + precision: Int) + extends CliCommand + + case class CreateDigitDecompEvent( + eventName: String, + maturationTime: Instant, + base: Int, + isSigned: Boolean, + numDigits: Int, + unit: String, + precision: Int) + extends CliCommand + + case class SignEvent(oracleEventV0TLV: OracleEventV0TLV, outcome: String) + extends CliCommand + + case class SignForRange(oracleEventV0TLV: OracleEventV0TLV, num: Long) + extends CliCommand + + case class SignDigits(oracleEventV0TLV: OracleEventV0TLV, num: Long) + extends CliCommand + + case class GetSignatures(oracleEventV0TLV: OracleEventV0TLV) + extends CliCommand } diff --git a/app/oracle-server/src/main/resources/logback.xml b/app/oracle-server/src/main/resources/logback.xml new file mode 100644 index 0000000000..d52e32fa6b --- /dev/null +++ b/app/oracle-server/src/main/resources/logback.xml @@ -0,0 +1,73 @@ + + + + %date{yyyy-MM-dd'T'HH:mm:ss,SSXXX, UTC}UTC %level [%logger{0}] %msg%n + + + + + 8192 + true + + + + + ${bitcoins.log.location}/bitcoin-s.log + + + ${bitcoins.log.location}/logs/bitcoin-s-%d{yyyy-MM-dd_HH}.%i.log + + + 100MB + 48 + 2GB + + + %date{yyyy-MM-dd'T'HH:mm:ss,SSXXX, UTC}UTC %level [%logger{0}] %msg%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/oracle-server/src/main/scala/org/bitcoins/oracle/server/OracleRoutes.scala b/app/oracle-server/src/main/scala/org/bitcoins/oracle/server/OracleRoutes.scala index 281d77b421..a885a0cac5 100644 --- a/app/oracle-server/src/main/scala/org/bitcoins/oracle/server/OracleRoutes.scala +++ b/app/oracle-server/src/main/scala/org/bitcoins/oracle/server/OracleRoutes.scala @@ -3,6 +3,8 @@ package org.bitcoins.oracle.server import akka.actor.ActorSystem import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server._ +import org.bitcoins.core.number._ +import org.bitcoins.core.protocol.tlv._ import org.bitcoins.dlc.oracle._ import org.bitcoins.server._ import ujson._ @@ -13,6 +15,11 @@ case class OracleRoutes(oracle: DLCOracle)(implicit system: ActorSystem) extends ServerRoute { import system.dispatcher + def getDescriptor( + announcementTLV: OracleAnnouncementTLV): EventDescriptorTLV = { + announcementTLV.eventTLV.eventDescriptor + } + def handleCommand: PartialFunction[ServerCommand, StandardRoute] = { case ServerCommand("getpublickey", _) => complete { @@ -29,9 +36,9 @@ case class OracleRoutes(oracle: DLCOracle)(implicit system: ActorSystem) case ServerCommand("listevents", _) => complete { - oracle.listEventDbs().map { eventDbs => - val nonceStrs = eventDbs.map(_.nonce.hex) - val json = Arr.from(nonceStrs) + oracle.listEvents().map { events => + val strs = events.map(_.eventDescriptorTLV.hex) + val json = Arr.from(strs) Server.httpSuccess(json) } @@ -43,41 +50,130 @@ case class OracleRoutes(oracle: DLCOracle)(implicit system: ActorSystem) reject(ValidationRejection("failure", Some(exception))) case Success(CreateEvent(label, maturationTime, outcomes)) => complete { - oracle.createNewEvent(label, maturationTime, outcomes).map { - eventDb => - Server.httpSuccess(eventDb.nonce.hex) + oracle.createNewEnumEvent(label, maturationTime, outcomes).map { + announcementTLV => + val descriptor = getDescriptor(announcementTLV) + Server.httpSuccess(descriptor.hex) } } } + case ServerCommand("createrangedevent", arr) => + CreateRangedEvent.fromJsArr(arr) match { + case Failure(exception) => + reject(ValidationRejection("failure", Some(exception))) + case Success( + CreateRangedEvent(eventName, + maturationTime, + start, + stop, + step, + unit, + precision)) => + complete { + oracle + .createNewRangedEvent(eventName, + maturationTime, + start, + stop, + step, + unit, + precision) + .map { announcementTLV => + val descriptor = getDescriptor(announcementTLV) + Server.httpSuccess(descriptor.hex) + } + } + } + + case ServerCommand("createdigitdecompevent", arr) => + CreateDigitDecompEvent.fromJsArr(arr) match { + case Failure(exception) => + reject(ValidationRejection("failure", Some(exception))) + case Success( + CreateDigitDecompEvent(eventName, + maturationTime, + base, + isSigned, + numDigits, + unit, + precision)) => + complete { + oracle + .createNewLargeRangedEvent(eventName, + maturationTime, + UInt16(base), + isSigned, + numDigits, + unit, + Int32(precision)) + .map { announcementTLV => + val descriptor = getDescriptor(announcementTLV) + Server.httpSuccess(descriptor.hex) + } + } + } + case ServerCommand("getevent", arr) => GetEvent.fromJsArr(arr) match { case Failure(exception) => reject(ValidationRejection("failure", Some(exception))) - case Success(GetEvent(label)) => + case Success(GetEvent(descriptor)) => complete { - oracle.getEvent(label).map { - case Some(event: Event) => - val outcomesJson = event.outcomes.map(Str) + oracle.findEvent(descriptor).map { + case Some(event: OracleEvent) => + val outcomesJson = event.eventDescriptorTLV match { + case enum: EnumEventDescriptorV0TLV => + enum.outcomes.map(Str) + case range: RangeEventDescriptorV0TLV => + val outcomes: Vector[Long] = { + val startL = range.start.toLong + val stepL = range.step.toLong + + val outcomeRange = + 0L.until(range.count.toLong) + .map(num => startL + (num * stepL)) + + outcomeRange.toVector + } + outcomes.map(num => Num(num.toDouble)) + case decomp: DigitDecompositionEventDescriptorV0TLV => + val sign = if (decomp.isSigned) { + Vector(Str("+"), Str("-")) + } else { + Vector.empty + } + val digits = 0.until(decomp.numDigits.toInt).map { _ => + 0 + .until(decomp.base.toInt) + .map(s => Str(s.toString)) + .toVector + } + + val vecs = digits :+ sign + vecs.map(vec => Arr.from(vec)) + } val (attestationJson, signatureJson) = event match { - case completedEvent: CompletedEvent => - (Str(completedEvent.attestation.hex), - Str(completedEvent.signature.hex)) - case _: PendingEvent => + case completedEvent: CompletedOracleEvent => + (Arr.from(completedEvent.attestations.map(a => Str(a.hex))), + Arr.from(completedEvent.signatures.map(s => Str(s.hex)))) + case _: PendingOracleEvent => (ujson.Null, ujson.Null) } val json = Obj( - "nonce" -> Str(event.nonce.hex), + "nonces" -> event.nonces.map(n => Str(n.hex)), "eventName" -> Str(event.eventName), - "numOutcomes" -> Num(event.numOutcomes.toDouble), "signingVersion" -> Str(event.signingVersion.toString), "maturationTime" -> Str(event.maturationTime.toString), "announcementSignature" -> Str( event.announcementSignature.hex), - "attestation" -> attestationJson, - "signature" -> signatureJson, + "eventDescriptorTLV" -> Str(event.eventDescriptorTLV.hex), + "eventTLV" -> Str(event.eventTLV.hex), + "announcementTLV" -> Str(event.announcementTLV.hex), + "attestations" -> attestationJson, + "signatures" -> signatureJson, "outcomes" -> outcomesJson ) Server.httpSuccess(json) @@ -91,24 +187,56 @@ case class OracleRoutes(oracle: DLCOracle)(implicit system: ActorSystem) SignEvent.fromJsArr(arr) match { case Failure(exception) => reject(ValidationRejection("failure", Some(exception))) - case Success(SignEvent(nonce, outcome)) => + case Success(SignEvent(oracleEventTLV, outcome)) => complete { - oracle.signEvent(nonce, outcome).map { eventDb => - Server.httpSuccess(eventDb.sigOpt.get.hex) + oracle.signEvent(oracleEventTLV, EnumAttestation(outcome)).map { + eventDb => + Server.httpSuccess(eventDb.sigOpt.get.hex) } } } - case ServerCommand("getsignature", arr) => + case ServerCommand("signforrange", arr) => + SignForRange.fromJsArr(arr) match { + case Failure(exception) => + reject(ValidationRejection("failure", Some(exception))) + case Success(SignForRange(oracleEventTLV, num)) => + complete { + oracle.signEvent(oracleEventTLV, RangeAttestation(num)).map { + eventDb => + Server.httpSuccess(eventDb.sigOpt.get.hex) + } + } + } + + case ServerCommand("signdigits", arr) => + SignDigits.fromJsArr(arr) match { + case Failure(exception) => + reject(ValidationRejection("failure", Some(exception))) + case Success(SignDigits(oracleEventTLV, num)) => + complete { + oracle.signDigits(oracleEventTLV, num).map { + case event: CompletedDigitDecompositionV0OracleEvent => + val sigsJson = event.signatures.map(sig => Str(sig.hex)) + + Server.httpSuccess(sigsJson) + case event: OracleEvent => + throw new RuntimeException( + s"Received unexpected event got $event") + } + } + } + + case ServerCommand("getsignatures", arr) => GetEvent.fromJsArr(arr) match { case Failure(exception) => reject(ValidationRejection("failure", Some(exception))) - case Success(GetEvent(nonce)) => + case Success(GetEvent(oracleEventTLV)) => complete { - oracle.getEvent(nonce).map { - case Some(completed: CompletedEvent) => - Server.httpSuccess(completed.signature.hex) - case None | Some(_: PendingEvent) => + oracle.findEvent(oracleEventTLV).map { + case Some(completed: CompletedOracleEvent) => + Server.httpSuccess(completed.signatures.map(_.hex)) + case None | Some(_: PendingOracleEvent) => Server.httpSuccess(ujson.Null) } } diff --git a/app/oracle-server/src/main/scala/org/bitcoins/oracle/server/OracleServerMain.scala b/app/oracle-server/src/main/scala/org/bitcoins/oracle/server/OracleServerMain.scala index a1395780ff..10f54e5604 100644 --- a/app/oracle-server/src/main/scala/org/bitcoins/oracle/server/OracleServerMain.scala +++ b/app/oracle-server/src/main/scala/org/bitcoins/oracle/server/OracleServerMain.scala @@ -1,6 +1,6 @@ package org.bitcoins.oracle.server -import org.bitcoins.dlc.oracle.DLCOracleAppConfig +import org.bitcoins.dlc.oracle.config.DLCOracleAppConfig import org.bitcoins.server.{BitcoinSRunner, Server} import scala.concurrent.Future diff --git a/app/server/src/main/scala/org/bitcoins/server/ServerJsonModels.scala b/app/server/src/main/scala/org/bitcoins/server/ServerJsonModels.scala index 8da1856eb2..5fa650a1c1 100644 --- a/app/server/src/main/scala/org/bitcoins/server/ServerJsonModels.scala +++ b/app/server/src/main/scala/org/bitcoins/server/ServerJsonModels.scala @@ -6,12 +6,12 @@ import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.LockUnspentOutputParamet import org.bitcoins.core.api.wallet.CoinSelectionAlgo import org.bitcoins.core.currency.{Bitcoins, Satoshis} import org.bitcoins.core.protocol.BlockStamp.BlockHeight +import org.bitcoins.core.protocol.tlv._ import org.bitcoins.core.protocol.transaction.{Transaction, TransactionOutPoint} import org.bitcoins.core.protocol.{BitcoinAddress, BlockStamp} import org.bitcoins.core.psbt.PSBT import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte import org.bitcoins.core.wallet.utxo.AddressLabelTag -import org.bitcoins.crypto.SchnorrNonce import ujson._ import upickle.default._ @@ -568,22 +568,110 @@ object CreateEvent extends ServerJsonModels { } } -case class SignEvent(nonce: SchnorrNonce, outcome: String) +case class CreateRangedEvent( + eventName: String, + maturationTime: Instant, + start: Int, + stop: Int, + step: Int, + unit: String, + precision: Int) + +object CreateRangedEvent extends ServerJsonModels { + + def fromJsArr(jsArr: ujson.Arr): Try[CreateRangedEvent] = { + jsArr.arr.toList match { + case labelJs :: maturationTimeJs :: startJs :: stopJs :: stepJs :: unitJs :: precisionJs :: Nil => + Try { + val label = labelJs.str + val maturationTime: Instant = + Instant.ofEpochSecond(maturationTimeJs.num.toLong) + val start = startJs.num.toInt + val stop = stopJs.num.toInt + val step = stepJs.num.toInt + val unit = unitJs.str + val precision = precisionJs.num.toInt + + CreateRangedEvent(label, + maturationTime, + start, + stop, + step, + unit, + precision) + } + case Nil => + Failure( + new IllegalArgumentException( + "Missing label, maturationTime, start, stop, and step arguments")) + case other => + Failure( + new IllegalArgumentException( + s"Bad number of arguments: ${other.length}. Expected: 5")) + } + } +} + +case class CreateDigitDecompEvent( + eventName: String, + maturationTime: Instant, + base: Int, + isSigned: Boolean, + numDigits: Int, + unit: String, + precision: Int) + +object CreateDigitDecompEvent extends ServerJsonModels { + + def fromJsArr(jsArr: ujson.Arr): Try[CreateDigitDecompEvent] = { + jsArr.arr.toList match { + case labelJs :: maturationTimeJs :: baseJs :: isSignedJs :: numDigitsJs :: unitJs :: precisionJs :: Nil => + Try { + val label = labelJs.str + val maturationTime: Instant = + Instant.ofEpochSecond(maturationTimeJs.num.toLong) + val base = baseJs.num.toInt + val isSigned = isSignedJs.bool + val numDigits = numDigitsJs.num.toInt + val unit = unitJs.str + val precision = precisionJs.num.toInt + + CreateDigitDecompEvent(label, + maturationTime, + base, + isSigned, + numDigits, + unit, + precision) + } + case Nil => + Failure(new IllegalArgumentException( + "Missing label, maturationTime, base, isSigned, and numDigits arguments")) + case other => + Failure( + new IllegalArgumentException( + s"Bad number of arguments: ${other.length}. Expected: 5")) + } + } +} + +case class SignEvent(oracleEventTLV: OracleEventV0TLV, outcome: String) object SignEvent extends ServerJsonModels { def fromJsArr(jsArr: ujson.Arr): Try[SignEvent] = { jsArr.arr.toList match { - case nonceJs :: outcomeJs :: Nil => + case tlvJs :: outcomeJs :: Nil => Try { - val nonce = SchnorrNonce(nonceJs.str) + val oracleEventTLV = OracleEventV0TLV(tlvJs.str) val outcome = outcomeJs.str - SignEvent(nonce, outcome) + SignEvent(oracleEventTLV, outcome) } case Nil => Failure( - new IllegalArgumentException("Missing nonce and outcome arguments")) + new IllegalArgumentException( + "Missing oracle event tlv and outcome arguments")) case other => Failure( new IllegalArgumentException( @@ -592,7 +680,69 @@ object SignEvent extends ServerJsonModels { } } -case class GetEvent(nonce: SchnorrNonce) +case class SignForRange(oracleEventTLV: OracleEventV0TLV, num: Long) + +object SignForRange extends ServerJsonModels { + + def fromJsArr(jsArr: ujson.Arr): Try[SignForRange] = { + jsArr.arr.toList match { + case tlvJs :: numJs :: Nil => + Try { + val oracleEventTLV = OracleEventV0TLV(tlvJs.str) + val num = numJs match { + case num: Num => num.value + case str: Str => str.value.toDouble + case _: Value => + throw new IllegalArgumentException( + s"Unable to parse $numJs as a number") + } + + SignForRange(oracleEventTLV, num.toLong) + } + case Nil => + Failure( + new IllegalArgumentException( + "Missing oracle event tlv and num arguments")) + case other => + Failure( + new IllegalArgumentException( + s"Bad number of arguments: ${other.length}. Expected: 2")) + } + } +} + +case class SignDigits(oracleEventTLV: OracleEventV0TLV, num: Long) + +object SignDigits extends ServerJsonModels { + + def fromJsArr(jsArr: ujson.Arr): Try[SignDigits] = { + jsArr.arr.toList match { + case tlvJs :: numJs :: Nil => + Try { + val oracleEventTLV = OracleEventV0TLV(tlvJs.str) + val num = numJs match { + case num: Num => num.value + case str: Str => str.value.toDouble + case _: Value => + throw new IllegalArgumentException( + s"Unable to parse $numJs as a number") + } + + SignDigits(oracleEventTLV, num.toLong) + } + case Nil => + Failure( + new IllegalArgumentException( + "Missing oracle event tlv and num arguments")) + case other => + Failure( + new IllegalArgumentException( + s"Bad number of arguments: ${other.length}. Expected: 2")) + } + } +} + +case class GetEvent(oracleEventTLV: OracleEventV0TLV) object GetEvent extends ServerJsonModels { @@ -600,9 +750,9 @@ object GetEvent extends ServerJsonModels { require(jsArr.arr.size == 1, s"Bad number of arguments: ${jsArr.arr.size}. Expected: 1") Try { - val nonce = SchnorrNonce(jsArr.arr.head.str) + val oracleEventTLV = OracleEventV0TLV(jsArr.arr.head.str) - GetEvent(nonce) + GetEvent(oracleEventTLV) } } } diff --git a/core-test/src/test/scala/org/bitcoins/core/util/NumberUtilTest.scala b/core-test/src/test/scala/org/bitcoins/core/util/NumberUtilTest.scala index c9deb2e6f9..0abd86bdb1 100644 --- a/core-test/src/test/scala/org/bitcoins/core/util/NumberUtilTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/util/NumberUtilTest.scala @@ -11,6 +11,13 @@ class NumberUtilTest extends BitcoinSUnitTest { behavior of "NumberUtil" + private def runTest( + nBits: UInt32, + expected: BlockHeader.TargetDifficultyHelper): Assertion = { + val expansion = NumberUtil.targetExpansion(nBits) + assert(expansion == expected) + } + it must "expand nbits to 0 difficulty threshold" in { //from the examples table on bitcoin developer reference site @@ -192,10 +199,35 @@ class NumberUtilTest extends BitcoinSUnitTest { expanded18.isOverflow must be(true) } - private def runTest( - nBits: UInt32, - expected: BlockHeader.TargetDifficultyHelper): Assertion = { - val expansion = NumberUtil.targetExpansion(nBits) - assert(expansion == expected) + behavior of "NumberUtil.decompose" + + it must "correctly do digit decomposition in base 10" in { + val num0 = 987 + val expected0 = Vector(9, 8, 7) + assert(NumberUtil.decompose(num0, 10, 3) == expected0) + + val num1 = 123 + val expected1 = Vector(0, 1, 2, 3) + assert(NumberUtil.decompose(num1, 10, 4) == expected1) + } + + it must "correctly do digit decomposition in base 2" in { + val num0 = 987 + val expected0 = Vector(1, 1, 1, 1, 0, 1, 1, 0, 1, 1) + assert(NumberUtil.decompose(num0, 2, 10) == expected0) + + val num1 = 123 + val expected1 = Vector(0, 1, 1, 1, 1, 0, 1, 1) + assert(NumberUtil.decompose(num1, 2, 8) == expected1) + } + + it must "correctly do digit decomposition n base 16" in { + val num0 = 987 + val expected0 = Vector(3, 13, 11) + assert(NumberUtil.decompose(num0, 16, 3) == expected0) + + val num1 = 123 + val expected1 = Vector(0, 7, 11) + assert(NumberUtil.decompose(num1, 16, 3) == expected1) } } diff --git a/core/src/main/scala/org/bitcoins/core/protocol/tlv/TLV.scala b/core/src/main/scala/org/bitcoins/core/protocol/tlv/TLV.scala index 82d9497519..80d4bef164 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/tlv/TLV.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/tlv/TLV.scala @@ -247,8 +247,8 @@ object EventDescriptorTLV extends TLVParentFactory[EventDescriptorTLV] { /** * Describes an event over an enumerated set of outcomes - * @param outcomeStrs The set of possible outcomes - * @see https://github.com/discreetlogcontracts/dlcspecs/blob/540c23a3e89c886814145cf16edfd48421d0175b/Oracle.md#simple-enumeration + * @param outcomes The set of possible outcomes + * @see https://github.com/discreetlogcontracts/dlcspecs/blob/master/Oracle.md#simple-enumeration */ case class EnumEventDescriptorV0TLV(outcomes: Vector[String]) extends EventDescriptorTLV { @@ -293,7 +293,7 @@ object EnumEventDescriptorV0TLV extends TLVFactory[EnumEventDescriptorV0TLV] { } } -trait NumericEventDescriptorTLV extends EventDescriptorTLV { +sealed trait NumericEventDescriptorTLV extends EventDescriptorTLV { /** The minimum valid value in the oracle can sign */ def min: Vector[String] @@ -522,7 +522,10 @@ object DigitDecompositionEventDescriptorV0TLV } } -sealed trait OracleEventTLV extends TLV +sealed trait OracleEventTLV extends TLV { + def eventDescriptor: EventDescriptorTLV + def nonces: Vector[SchnorrNonce] +} case class OracleEventV0TLV( nonces: Vector[SchnorrNonce], @@ -574,7 +577,11 @@ object OracleEventV0TLV extends TLVFactory[OracleEventV0TLV] { } } -sealed trait OracleAnnouncementTLV extends TLV +sealed trait OracleAnnouncementTLV extends TLV { + def eventTLV: OracleEventTLV + def announcementSignature: SchnorrDigitalSignature + def publicKey: SchnorrPublicKey +} case class OracleAnnouncementV0TLV( announcementSignature: SchnorrDigitalSignature, diff --git a/core/src/main/scala/org/bitcoins/core/util/NumberUtil.scala b/core/src/main/scala/org/bitcoins/core/util/NumberUtil.scala index 6e0cf946e7..80e8f2a3e4 100644 --- a/core/src/main/scala/org/bitcoins/core/util/NumberUtil.scala +++ b/core/src/main/scala/org/bitcoins/core/util/NumberUtil.scala @@ -336,6 +336,22 @@ sealed abstract class NumberUtil extends BitcoinSLogger { def posInt: Int = { Math.abs(scala.util.Random.nextInt()) } + + /** Decomposes the input num into a list of numDigits digits in the given base. + * The output Vector has the most significant digit first and the 1's place last. + */ + def decompose(num: Long, base: Int, numDigits: Int): Vector[Int] = { + var currentNum: Long = num + + val backwardsDigits = (0 until numDigits).toVector.map { _ => + val digit = currentNum % base + currentNum = currentNum / base + + digit.toInt + } + + backwardsDigits.reverse + } } object NumberUtil extends NumberUtil diff --git a/db-commons-test/src/test/scala/org/bitcoins/db/AppConfigTest.scala b/db-commons-test/src/test/scala/org/bitcoins/db/AppConfigTest.scala index 29cd1dd05f..6bd92e8b95 100644 --- a/db-commons-test/src/test/scala/org/bitcoins/db/AppConfigTest.scala +++ b/db-commons-test/src/test/scala/org/bitcoins/db/AppConfigTest.scala @@ -2,7 +2,7 @@ package org.bitcoins.db import com.typesafe.config.ConfigFactory import org.bitcoins.core.config._ -import org.bitcoins.dlc.oracle.DLCOracleAppConfig +import org.bitcoins.dlc.oracle.config.DLCOracleAppConfig import org.bitcoins.server.BitcoinSAppConfig import org.bitcoins.testkit.BitcoinSTestAppConfig import org.bitcoins.testkit.util.BitcoinSAsyncTest diff --git a/db-commons-test/src/test/scala/org/bitcoins/db/DbManagementTest.scala b/db-commons-test/src/test/scala/org/bitcoins/db/DbManagementTest.scala index 1b2f0110f6..f8c8639d38 100644 --- a/db-commons-test/src/test/scala/org/bitcoins/db/DbManagementTest.scala +++ b/db-commons-test/src/test/scala/org/bitcoins/db/DbManagementTest.scala @@ -4,7 +4,7 @@ import com.typesafe.config.Config import org.bitcoins.chain.config.ChainAppConfig import org.bitcoins.chain.db.ChainDbManagement import org.bitcoins.db.DatabaseDriver._ -import org.bitcoins.dlc.oracle.DLCOracleAppConfig +import org.bitcoins.dlc.oracle.config.DLCOracleAppConfig import org.bitcoins.node.config.NodeAppConfig import org.bitcoins.node.db.NodeDbManagement import org.bitcoins.testkit.BitcoinSTestAppConfig.ProjectType @@ -122,14 +122,14 @@ class DbManagementTest extends BitcoinSAsyncTest with EmbeddedPg { val result = oracleAppConfig.migrate() oracleAppConfig.driver match { case SQLite => - val expected = 1 + val expected = 2 assert(result == expected) val flywayInfo = oracleAppConfig.info() assert(flywayInfo.applied().length == expected) assert(flywayInfo.pending().length == 0) case PostgreSQL => - val expected = 1 + val expected = 2 assert(result == expected) val flywayInfo = oracleAppConfig.info() diff --git a/db-commons/src/main/scala/org/bitcoins/db/DbCommonsColumnMappers.scala b/db-commons/src/main/scala/org/bitcoins/db/DbCommonsColumnMappers.scala index eb9b57b474..99348bdfb3 100644 --- a/db-commons/src/main/scala/org/bitcoins/db/DbCommonsColumnMappers.scala +++ b/db-commons/src/main/scala/org/bitcoins/db/DbCommonsColumnMappers.scala @@ -13,6 +13,7 @@ import org.bitcoins.core.gcs.FilterType import org.bitcoins.core.hd._ import org.bitcoins.core.number.{Int32, UInt32, UInt64} import org.bitcoins.core.protocol.script.{ScriptPubKey, ScriptWitness} +import org.bitcoins.core.protocol.tlv._ import org.bitcoins.core.protocol.transaction.{ Transaction, TransactionOutPoint, @@ -310,4 +311,10 @@ class DbCommonsColumnMappers(val profile: JdbcProfile) { MappedColumnType.base[WalletStateDescriptor, String]( _.toString, WalletStateDescriptor.fromString) + + implicit val eventDescriptorTLVMapper: BaseColumnType[EventDescriptorTLV] = { + MappedColumnType.base[EventDescriptorTLV, String]( + _.hex, + EventDescriptorTLV.fromHex) + } } diff --git a/db-commons/src/main/scala/org/bitcoins/db/DbManagement.scala b/db-commons/src/main/scala/org/bitcoins/db/DbManagement.scala index 388d7eaa54..00ab637d85 100644 --- a/db-commons/src/main/scala/org/bitcoins/db/DbManagement.scala +++ b/db-commons/src/main/scala/org/bitcoins/db/DbManagement.scala @@ -1,6 +1,7 @@ package org.bitcoins.db import org.bitcoins.core.util.{BitcoinSLogger, FutureUtil} +import org.bitcoins.db.DatabaseDriver._ import org.flywaydb.core.Flyway import org.flywaydb.core.api.{FlywayException, MigrationInfoService} @@ -125,6 +126,17 @@ trait DbManagement extends BitcoinSLogger { flyway.info() } + def migrationsApplied(): Int = { + val applied = flyway.info().applied() + driver match { + case SQLite => + applied.size + case PostgreSQL => + // -1 because of extra << Flyway Schema Creation >> + applied.size - 1 + } + } + /** Executes migrations related to this database * * @see [[https://flywaydb.org/documentation/api/#programmatic-configuration-java]] diff --git a/dlc-oracle-test/src/test/scala/org/bitcoins/dlc/oracle/DLCOracleTest.scala b/dlc-oracle-test/src/test/scala/org/bitcoins/dlc/oracle/DLCOracleTest.scala index 4588ae0a80..9415c17a89 100644 --- a/dlc-oracle-test/src/test/scala/org/bitcoins/dlc/oracle/DLCOracleTest.scala +++ b/dlc-oracle-test/src/test/scala/org/bitcoins/dlc/oracle/DLCOracleTest.scala @@ -1,21 +1,28 @@ package org.bitcoins.dlc.oracle -import java.sql.SQLException +import java.time.Instant import org.bitcoins.commons.jsonmodels.dlc.SigningVersion import org.bitcoins.core.hd.{HDCoinType, HDPurpose} +import org.bitcoins.core.number._ import org.bitcoins.core.protocol.Bech32Address import org.bitcoins.core.protocol.script.P2WPKHWitnessSPKV0 +import org.bitcoins.core.protocol.tlv._ import org.bitcoins.core.util.TimeUtil import org.bitcoins.crypto._ import org.bitcoins.dlc.oracle.storage._ -import org.bitcoins.testkit.core.gen.ChainParamsGenerator +import org.bitcoins.testkit.Implicits._ +import org.bitcoins.testkit.core.gen.{ChainParamsGenerator, TLVGen} import org.bitcoins.testkit.fixtures.DLCOracleFixture -import scodec.bits.ByteVector class DLCOracleTest extends DLCOracleFixture { - val testOutcomes: Vector[String] = (0 to 10).map(_.toString).toVector + val enumOutcomes: Vector[String] = Vector("sunny", "windy", "rainy", "cloudy") + + val futureTime: Instant = TimeUtil.now.plusSeconds(100000) + + val testDescriptor: EnumEventDescriptorV0TLV = EnumEventDescriptorV0TLV( + enumOutcomes) behavior of "DLCOracle" @@ -35,6 +42,13 @@ class DLCOracleTest extends DLCOracleFixture { } } + it must "not find an event it doesn't have" in { dlcOracle: DLCOracle => + val dummyEvent = TLVGen.oracleEventV0TLV.sampleSome + dlcOracle.findEvent(dummyEvent).map { eventOpt => + assert(eventOpt.isEmpty) + } + } + it must "calculate the correct staking address" in { dlcOracle: DLCOracle => forAllAsync(ChainParamsGenerator.bitcoinNetworkParams) { network => val expected = @@ -46,129 +60,441 @@ class DLCOracleTest extends DLCOracleFixture { it must "create a new event and list it with pending" in { dlcOracle: DLCOracle => - val time = TimeUtil.now + val time = futureTime + for { - testEventDb <- dlcOracle.createNewEvent("test", time, testOutcomes) + _ <- dlcOracle.createNewEvent("test", time, testDescriptor) pendingEvents <- dlcOracle.listPendingEventDbs() } yield { assert(pendingEvents.size == 1) - // encoding of the time can make them unequal - val comparable = - pendingEvents.head.copy(maturationTime = testEventDb.maturationTime) - assert(comparable == testEventDb) + assert(pendingEvents.head.eventDescriptorTLV == testDescriptor) } } - it must "create a new event and get its details" in { dlcOracle: DLCOracle => - val time = TimeUtil.now - val eventName = "test" - - for { - testEventDb <- dlcOracle.createNewEvent(eventName, time, testOutcomes) - eventOpt <- dlcOracle.getEvent(testEventDb.nonce) - } yield { - assert(eventOpt.isDefined) - val event = eventOpt.get - - assert(event.isInstanceOf[PendingEvent]) - assert(event.eventName == eventName) - assert(event.outcomes == testOutcomes) - assert(event.numOutcomes == testOutcomes.size) - assert(event.signingVersion == SigningVersion.latest) - assert(event.pubkey == dlcOracle.publicKey) - assert(event.nonce == testEventDb.nonce) - assert(event.maturationTime.getEpochSecond == time.getEpochSecond) - } - } - - it must "not get an event that doesn't exit" in { dlcOracle: DLCOracle => - val nonce = ECPublicKey.freshPublicKey.schnorrNonce - dlcOracle.getEvent(nonce).map { - case None => succeed - case Some(_) => fail() - } - } - - it must "create a new event with a valid announcement signature" in { + it must "create an enum new event and get its details" in { dlcOracle: DLCOracle => + val time = futureTime + val eventName = "test" + for { - testEventDb <- - dlcOracle.createNewEvent("test", TimeUtil.now, testOutcomes) - rValDbOpt <- dlcOracle.rValueDAO.read(testEventDb.nonce) + announcement <- + dlcOracle.createNewEnumEvent(eventName, time, enumOutcomes) + + eventOpt <- dlcOracle.findEvent(announcement.eventTLV) } yield { - assert(rValDbOpt.isDefined) - val rValDb = rValDbOpt.get - val hash = CryptoUtil.taggedSha256( - rValDb.nonce.bytes ++ CryptoUtil.serializeForHash( - rValDb.eventName) ++ ByteVector.fromLong( - testEventDb.maturationTime.getEpochSecond), - SigningVersion.latest.announcementTag - ) + assert(eventOpt.isDefined) + val event = eventOpt.get + + assert(event.isInstanceOf[PendingEnumV0OracleEvent]) + assert(event.eventName == eventName) + assert(event.eventDescriptorTLV == testDescriptor) + assert(event.signingVersion == SigningVersion.latest) + assert(event.pubkey == dlcOracle.publicKey) + assert(event.maturationTime.getEpochSecond == time.getEpochSecond) + + val expectedEventTLV = + OracleEventV0TLV(Vector(event.nonces.head), + UInt32(event.maturationTime.getEpochSecond), + testDescriptor, + eventName) + + assert(event.eventTLV == expectedEventTLV) + + val expectedAnnouncementTLV = + OracleAnnouncementV0TLV(event.announcementSignature, + event.pubkey, + expectedEventTLV) + + assert(event.announcementTLV == expectedAnnouncementTLV) + + val announceBytes = + SigningVersion.latest.calcAnnouncementHash(event.eventTLV) + assert( - dlcOracle.publicKey.verify(hash.bytes, rValDb.announcementSignature)) + dlcOracle.publicKey.verify(announceBytes, + event.announcementSignature)) } } - it must "create multiple events with different names" in { + it must "create a ranged event and get its details" in { dlcOracle: DLCOracle => + val time = futureTime + val eventName = "ranged" + val start = -100 + val count = 201 + val step = 1 + val unit = "units" + val precision = 0 + for { - _ <- dlcOracle.createNewEvent("test", TimeUtil.now, testOutcomes) - _ <- dlcOracle.createNewEvent("test1", TimeUtil.now, testOutcomes) - } yield succeed - } + announcement <- dlcOracle.createNewRangedEvent(eventName, + time, + start, + count, + step, + unit, + precision) - it must "fail to create multiple events with the same name" in { - dlcOracle: DLCOracle => - recoverToSucceededIf[SQLException] { - for { - _ <- dlcOracle.createNewEvent("test", TimeUtil.now, testOutcomes) - _ <- dlcOracle.createNewEvent("test", TimeUtil.now, testOutcomes) - } yield () + eventOpt <- dlcOracle.findEvent(announcement.eventTLV) + } yield { + assert(eventOpt.isDefined) + val event = eventOpt.get + + val expectedDescriptorTLV = + RangeEventDescriptorV0TLV(Int32(start), + UInt32(count), + UInt16(step), + unit, + Int32(precision)) + + assert(event.isInstanceOf[PendingRangeV0OracleEvent]) + assert(event.eventName == eventName) + assert(event.eventDescriptorTLV == expectedDescriptorTLV) + assert(event.signingVersion == SigningVersion.latest) + assert(event.pubkey == dlcOracle.publicKey) + assert(event.maturationTime.getEpochSecond == time.getEpochSecond) + + val expectedEventTLV = + OracleEventV0TLV(Vector(event.nonces.head), + UInt32(event.maturationTime.getEpochSecond), + expectedDescriptorTLV, + eventName) + + assert(event.eventTLV == expectedEventTLV) + + val expectedAnnouncementTLV = + OracleAnnouncementV0TLV(event.announcementSignature, + event.pubkey, + expectedEventTLV) + + assert(event.announcementTLV == expectedAnnouncementTLV) + + val announceBytes = + SigningVersion.latest.calcAnnouncementHash(event.eventTLV) + + assert( + dlcOracle.publicKey.verify(announceBytes, + event.announcementSignature)) } } - it must "create and sign a event" in { dlcOracle: DLCOracle => - val outcome = testOutcomes.head + it must "fail to create a ranged event with a 0 step" in { + dlcOracle: DLCOracle => + assertThrows[IllegalArgumentException] { + dlcOracle.createNewRangedEvent("test", futureTime, 1, 2, 0, "", 0) + } + } + + it must "fail to create a ranged event with a negative step" in { + dlcOracle: DLCOracle => + assertThrows[IllegalArgumentException] { + dlcOracle.createNewRangedEvent("test", futureTime, 1, 2, -1, "", 0) + } + } + + it must "fail to create a ranged event with a negative count" in { + dlcOracle: DLCOracle => + assertThrows[IllegalArgumentException] { + dlcOracle.createNewRangedEvent("test", futureTime, 200, -1, 1, "", 0) + } + } + + it must "create and sign a ranged event" in { dlcOracle: DLCOracle => + val rangeEventDescriptorV0TLV = + RangeEventDescriptorV0TLV(Int32.zero, + UInt32(20), + UInt16.one, + "units", + Int32.zero) + + val outcome = RangeAttestation(5) + for { - eventDb <- dlcOracle.createNewEvent("test", TimeUtil.now, testOutcomes) - signedEventDb <- dlcOracle.signEvent(eventDb.nonce, outcome) - outcomeDbs <- dlcOracle.eventOutcomeDAO.findByNonce(eventDb.nonce) - outcomeDb = outcomeDbs.find(_.message == outcome).get - eventOpt <- dlcOracle.getEvent(eventDb.nonce) + announcement <- + dlcOracle.createNewEvent("test", futureTime, rangeEventDescriptorV0TLV) + + nonce = announcement.eventTLV.nonces.head + + signedEventDb <- dlcOracle.signEvent(nonce, outcome) + eventOpt <- dlcOracle.findEvent(announcement.eventTLV) } yield { assert(eventOpt.isDefined) val event = eventOpt.get val sig = signedEventDb.sigOpt.get event match { - case completedEvent: CompletedEvent => + case completedEvent: CompletedRangeV0OracleEvent => assert(completedEvent.attestation == sig.sig) - assert(dlcOracle.publicKey.verify(outcomeDb.hashedMessage.bytes, sig)) + + val descriptor = completedEvent.eventDescriptorTLV + val hash = + SigningVersion.latest.calcOutcomeHash(descriptor, outcome.bytes) + + assert(dlcOracle.publicKey.verify(hash, sig)) assert( - SchnorrDigitalSignature(completedEvent.nonce, + SchnorrDigitalSignature(completedEvent.nonces.head, completedEvent.attestation) == sig) - case _: PendingEvent => + case _: PendingOracleEvent | _: CompletedOracleEvent => fail() } } } - it must "correctly track pending events" in { dlcOracle: DLCOracle => - val outcome = testOutcomes.head + it must "create and sign an enum event" in { dlcOracle: DLCOracle => + val descriptor = TLVGen.enumEventDescriptorV0TLV.sampleSome + val outcome = descriptor.outcomes.head + + val descriptorV0TLV = + EnumEventDescriptorV0TLV(descriptor.outcomes) + for { - eventDb <- dlcOracle.createNewEvent("test", TimeUtil.now, testOutcomes) + announcement <- + dlcOracle.createNewEvent("test", futureTime, descriptorV0TLV) + + signedEventDb <- + dlcOracle.signEvent(announcement.eventTLV, EnumAttestation(outcome)) + eventOpt <- dlcOracle.findEvent(announcement.eventTLV) + } yield { + assert(eventOpt.isDefined) + val event = eventOpt.get + val sig = signedEventDb.sigOpt.get + + event match { + case completedEvent: CompletedEnumV0OracleEvent => + assert(completedEvent.attestation == sig.sig) + + val descriptor = completedEvent.eventDescriptorTLV + val hash = SigningVersion.latest.calcOutcomeHash(descriptor, outcome) + + assert(dlcOracle.publicKey.verify(hash, sig)) + assert( + SchnorrDigitalSignature(completedEvent.nonces.head, + completedEvent.attestation) == sig) + case _: PendingOracleEvent | _: CompletedOracleEvent => + fail() + } + } + } + + it must "create and sign a large range event" in { dlcOracle: DLCOracle => + val outcome = -321L + + for { + announcement <- + dlcOracle.createNewLargeRangedEvent(eventName = "test", + maturationTime = futureTime, + base = UInt16(10), + isSigned = true, + numDigits = 3, + unit = "units", + precision = Int32.zero) + + eventTLV = announcement.eventTLV + + event <- dlcOracle.signDigits(eventTLV, outcome) + } yield { + event match { + case completedEvent: CompletedDigitDecompositionV0OracleEvent => + val descriptor = completedEvent.eventDescriptorTLV + + // Sign Signature Check + val signHash = SigningVersion.latest.calcOutcomeHash(descriptor, "-") + val signSig = completedEvent.signatures.head + assert(dlcOracle.publicKey.verify(signHash, signSig)) + assert( + SchnorrDigitalSignature( + completedEvent.nonces.head, + completedEvent.attestations.head) == signSig) + + // 100s Place signature Check + val hash100 = + SigningVersion.latest.calcOutcomeHash( + descriptor, + DigitDecompositionAttestation(3).bytes) + val sig100 = completedEvent.signatures(1) + assert(dlcOracle.publicKey.verify(hash100, sig100)) + assert( + SchnorrDigitalSignature(completedEvent.nonces(1), + completedEvent.attestations(1)) == sig100) + + // 10s Place signature Check + val hash10 = + SigningVersion.latest.calcOutcomeHash( + descriptor, + DigitDecompositionAttestation(2).bytes) + val sig10 = completedEvent.signatures(2) + assert(dlcOracle.publicKey.verify(hash10, sig10)) + assert( + SchnorrDigitalSignature(completedEvent.nonces(2), + completedEvent.attestations(2)) == sig10) + + // 1s Place signature Check + val hash1 = + SigningVersion.latest.calcOutcomeHash( + descriptor, + DigitDecompositionAttestation(1).bytes) + val sig1 = completedEvent.signatures(3) + assert(dlcOracle.publicKey.verify(hash1, sig1)) + assert( + SchnorrDigitalSignature(completedEvent.nonces(3), + completedEvent.attestations(3)) == sig1) + case _: PendingOracleEvent | _: CompletedOracleEvent => + fail() + } + } + } + + it must "create and sign a non-base 10 large range event" in { + dlcOracle: DLCOracle => + val outcome = -1931L + + for { + announcement <- + dlcOracle.createNewLargeRangedEvent(eventName = "test", + maturationTime = futureTime, + base = UInt16(16), + isSigned = true, + numDigits = 3, + unit = "units", + precision = Int32.zero) + + eventTLV = announcement.eventTLV + + event <- dlcOracle.signDigits(eventTLV, outcome) + } yield { + event match { + case completedEvent: CompletedDigitDecompositionV0OracleEvent => + val descriptor = completedEvent.eventDescriptorTLV + + // Sign Signature Check + val signHash = + SigningVersion.latest.calcOutcomeHash(descriptor, "-") + val signSig = completedEvent.signatures.head + assert(dlcOracle.publicKey.verify(signHash, signSig)) + assert( + SchnorrDigitalSignature( + completedEvent.nonces.head, + completedEvent.attestations.head) == signSig) + + // 100s Place signature Check + val hash100 = + SigningVersion.latest.calcOutcomeHash( + descriptor, + DigitDecompositionAttestation(7).bytes) + val sig100 = completedEvent.signatures(1) + assert(dlcOracle.publicKey.verify(hash100, sig100)) + assert( + SchnorrDigitalSignature(completedEvent.nonces(1), + completedEvent.attestations(1)) == sig100) + + // 10s Place signature Check + val hash10 = + SigningVersion.latest.calcOutcomeHash( + descriptor, + DigitDecompositionAttestation(8).bytes) + val sig10 = completedEvent.signatures(2) + assert(dlcOracle.publicKey.verify(hash10, sig10)) + assert( + SchnorrDigitalSignature(completedEvent.nonces(2), + completedEvent.attestations(2)) == sig10) + + // 1s Place signature Check + val hash1 = + SigningVersion.latest.calcOutcomeHash( + descriptor, + DigitDecompositionAttestation(11).bytes) + val sig1 = completedEvent.signatures(3) + assert(dlcOracle.publicKey.verify(hash1, sig1)) + assert( + SchnorrDigitalSignature(completedEvent.nonces(3), + completedEvent.attestations(3)) == sig1) + case _: PendingOracleEvent | _: CompletedOracleEvent => + fail() + } + } + } + + it must "create and sign a large range event with digits of 0" in { + dlcOracle: DLCOracle => + val outcome = 2 + + for { + announcement <- + dlcOracle.createNewLargeRangedEvent(eventName = "test", + maturationTime = futureTime, + base = UInt16(2), + isSigned = false, + numDigits = 3, + unit = "units", + precision = Int32.zero) + + eventTLV = announcement.eventTLV + + event <- dlcOracle.signDigits(eventTLV, outcome) + } yield { + event match { + case completedEvent: CompletedDigitDecompositionV0OracleEvent => + val descriptor = completedEvent.eventDescriptorTLV + + // 100s Place signature Check + val hash100 = + SigningVersion.latest.calcOutcomeHash( + descriptor, + DigitDecompositionAttestation(0).bytes) + val sig100 = completedEvent.signatures.head + assert(dlcOracle.publicKey.verify(hash100, sig100)) + assert( + SchnorrDigitalSignature( + completedEvent.nonces.head, + completedEvent.attestations.head) == sig100) + + // 10s Place signature Check + val hash10 = + SigningVersion.latest.calcOutcomeHash( + descriptor, + DigitDecompositionAttestation(1).bytes) + val sig10 = completedEvent.signatures(1) + assert(dlcOracle.publicKey.verify(hash10, sig10)) + assert( + SchnorrDigitalSignature(completedEvent.nonces(1), + completedEvent.attestations(1)) == sig10) + + // 1s Place signature Check + val hash1 = + SigningVersion.latest.calcOutcomeHash( + descriptor, + DigitDecompositionAttestation(0).bytes) + val sig1 = completedEvent.signatures(2) + assert(dlcOracle.publicKey.verify(hash1, sig1)) + assert( + SchnorrDigitalSignature(completedEvent.nonces(2), + completedEvent.attestations(2)) == sig1) + case _: PendingOracleEvent | _: CompletedOracleEvent => + fail() + } + } + } + + it must "correctly track pending events" in { dlcOracle: DLCOracle => + val outcome = enumOutcomes.head + for { + announcement <- + dlcOracle.createNewEnumEvent("test", futureTime, enumOutcomes) beforePending <- dlcOracle.listPendingEventDbs() beforeEvents <- dlcOracle.listEvents() _ = assert(beforePending.size == 1) _ = assert(beforeEvents.size == 1) - _ = assert(beforeEvents.head.isInstanceOf[PendingEvent]) - _ <- dlcOracle.signEvent(eventDb.nonce, outcome) + _ = assert(beforeEvents.head.isInstanceOf[PendingOracleEvent]) + + nonce = announcement.eventTLV.nonces.head + + _ <- dlcOracle.signEvent(nonce, EnumAttestation(outcome)) afterPending <- dlcOracle.listPendingEventDbs() afterEvents <- dlcOracle.listEvents() } yield { assert(afterPending.isEmpty) assert(afterEvents.size == 1) - assert(afterEvents.head.isInstanceOf[CompletedEvent]) + assert(afterEvents.head.isInstanceOf[CompletedOracleEvent]) } } @@ -176,16 +502,40 @@ class DLCOracleTest extends DLCOracleFixture { dlcOracle: DLCOracle => val dummyNonce = SchnorrNonce(ECPublicKey.freshPublicKey.bytes.tail) recoverToSucceededIf[RuntimeException]( - dlcOracle.signEvent(dummyNonce, "testOutcomes")) + dlcOracle.signEvent(dummyNonce, EnumAttestation("testOutcomes"))) } - it must "fail to sign an outcome that doesn't exist" in { + it must "fail to sign an enum outcome that doesn't exist" in { dlcOracle: DLCOracle => recoverToSucceededIf[RuntimeException] { for { - eventDb <- - dlcOracle.createNewEvent("test", TimeUtil.now, testOutcomes) - _ <- dlcOracle.signEvent(eventDb.nonce, "not a real outcome") + announcement <- + dlcOracle.createNewEnumEvent("test", futureTime, enumOutcomes) + + nonce = announcement.eventTLV.nonces.head + + _ <- dlcOracle.signEvent(nonce, EnumAttestation("not a real outcome")) + } yield () + } + } + + it must "fail to sign a range outcome that doesn't exist" in { + dlcOracle: DLCOracle => + val descriptor = + RangeEventDescriptorV0TLV(Int32.one, + UInt32(10), + UInt16.one, + "units", + Int32.zero) + + recoverToSucceededIf[RuntimeException] { + for { + announcement <- + dlcOracle.createNewEvent("test", futureTime, descriptor) + + nonce = announcement.eventTLV.nonces.head + + _ <- dlcOracle.signEvent(nonce, RangeAttestation(100)) } yield () } } @@ -200,49 +550,70 @@ class DLCOracleTest extends DLCOracleFixture { val sigVersion = SigningVersion.latest val message = "dummy message" - val rValDb = RValueDb(nonce, - eventName, - HDPurpose(0), - HDCoinType.Bitcoin, - 0, - 0, - 0, - SchnorrDigitalSignature(nonce, FieldElement.one)) - + val rValDb = + RValueDb(nonce, eventName, HDPurpose(0), HDCoinType.Bitcoin, 0, 0, 0) val eventDb = - EventDb(nonce, publicKey, eventName, 1, sigVersion, TimeUtil.now, None) - - val outcomeDb = - EventOutcomeDb(nonce, - message, - CryptoUtil.taggedSha256(message, sigVersion.outcomeTag)) + EventDb(nonce, + publicKey, + 0, + eventName, + 0, + sigVersion, + futureTime, + None, + SchnorrDigitalSignature(nonce, FieldElement.one), + testDescriptor) val setupF = for { _ <- dlcOracle.rValueDAO.create(rValDb) _ <- dlcOracle.eventDAO.create(eventDb) - _ <- dlcOracle.eventOutcomeDAO.create(outcomeDb) } yield () recoverToSucceededIf[IllegalArgumentException] { for { _ <- setupF - _ <- dlcOracle.signEvent(nonce, message) + _ <- dlcOracle.signEvent(nonce, EnumAttestation(message)) } yield () } } - it must "fail to create an event with no outcomes" in { + it must "fail to sign an event with a nonce not in the event db" in { + dlcOracle: DLCOracle => + val ecKey = ECPublicKey.freshPublicKey + val nonce = ecKey.schnorrNonce + + val eventName = "dummy" + val message = "dummy message" + + val rValDb = + RValueDb(nonce, eventName, HDPurpose(0), HDCoinType.Bitcoin, 0, 0, 0) + + recoverToSucceededIf[RuntimeException] { + for { + _ <- dlcOracle.rValueDAO.create(rValDb) + _ <- dlcOracle.signEvent(nonce, EnumAttestation(message)) + } yield () + } + } + + it must "fail to create an enum event with no outcomes" in { dlcOracle: DLCOracle => assertThrows[IllegalArgumentException] { - dlcOracle.createNewEvent("test", TimeUtil.now, Vector.empty) + dlcOracle.createNewEnumEvent("test", futureTime, Vector.empty) } } it must "fail to create an event with duplicate outcomes" in { dlcOracle: DLCOracle => - val outcomes = testOutcomes :+ testOutcomes.head + val outcomes = enumOutcomes :+ enumOutcomes.head assertThrows[IllegalArgumentException] { - dlcOracle.createNewEvent("test", TimeUtil.now, outcomes) + dlcOracle.createNewEnumEvent("test", futureTime, outcomes) } } + + it must "fail to create an event in the past" in { dlcOracle: DLCOracle => + assertThrows[IllegalArgumentException] { + dlcOracle.createNewEvent("test", Instant.EPOCH, testDescriptor) + } + } } diff --git a/dlc-oracle-test/src/test/scala/org/bitcoins/dlc/oracle/storage/EventDAOTest.scala b/dlc-oracle-test/src/test/scala/org/bitcoins/dlc/oracle/storage/EventDAOTest.scala index 89312b53c0..0b9678c0e5 100644 --- a/dlc-oracle-test/src/test/scala/org/bitcoins/dlc/oracle/storage/EventDAOTest.scala +++ b/dlc-oracle-test/src/test/scala/org/bitcoins/dlc/oracle/storage/EventDAOTest.scala @@ -4,10 +4,11 @@ import java.time.Instant import org.bitcoins.commons.jsonmodels.dlc.SigningVersion import org.bitcoins.core.hd.{HDCoinType, HDPurpose} +import org.bitcoins.core.protocol.tlv.EventDescriptorTLV import org.bitcoins.core.util.TimeUtil import org.bitcoins.crypto._ -import org.bitcoins.dlc.oracle.DLCOracleAppConfig -import org.bitcoins.testkit.BitcoinSTestAppConfig +import org.bitcoins.testkit.Implicits._ +import org.bitcoins.testkit.core.gen.TLVGen import org.bitcoins.testkit.fixtures.DLCOracleDAOFixture class EventDAOTest extends DLCOracleDAOFixture { @@ -28,22 +29,29 @@ class EventDAOTest extends DLCOracleDAOFixture { Instant.ofEpochSecond(now) } - val dummyRValDb: RValueDb = RValueDb( - nonce, - eventName, - HDPurpose(0), - HDCoinType.Bitcoin, - 0, - 0, - 0, - SchnorrDigitalSignature(nonce, FieldElement.one)) + val dummyRValDb: RValueDb = + RValueDb(nonce, eventName, HDPurpose(0), HDCoinType.Bitcoin, 0, 0, 0) + + val dummySig: SchnorrDigitalSignature = + SchnorrDigitalSignature(nonce, FieldElement.one) + + def descriptor: EventDescriptorTLV = TLVGen.eventDescriptorTLV.sampleSome it must "create an EventDb and read it" in { daos => val rValDAO = daos.rValueDAO val eventDAO = daos.eventDAO val eventDb = - EventDb(nonce, publicKey, eventName, 1, sigVersion, time, None) + EventDb(nonce, + publicKey, + 0, + eventName, + 0, + sigVersion, + time, + None, + dummySig, + descriptor) for { _ <- rValDAO.create(dummyRValDb) @@ -57,7 +65,16 @@ class EventDAOTest extends DLCOracleDAOFixture { val eventDAO = daos.eventDAO val eventDb = - EventDb(nonce, publicKey, eventName, 1, sigVersion, time, None) + EventDb(nonce, + publicKey, + 0, + eventName, + 0, + sigVersion, + time, + None, + dummySig, + descriptor) for { _ <- rValDAO.create(dummyRValDb) diff --git a/dlc-oracle-test/src/test/scala/org/bitcoins/dlc/oracle/storage/EventOutcomeDAOTest.scala b/dlc-oracle-test/src/test/scala/org/bitcoins/dlc/oracle/storage/EventOutcomeDAOTest.scala index f10cd60b1a..4288aca616 100644 --- a/dlc-oracle-test/src/test/scala/org/bitcoins/dlc/oracle/storage/EventOutcomeDAOTest.scala +++ b/dlc-oracle-test/src/test/scala/org/bitcoins/dlc/oracle/storage/EventOutcomeDAOTest.scala @@ -4,11 +4,13 @@ import java.time.Instant import org.bitcoins.commons.jsonmodels.dlc.SigningVersion import org.bitcoins.core.hd.{HDCoinType, HDPurpose} +import org.bitcoins.core.protocol.tlv.EventDescriptorTLV import org.bitcoins.core.util.TimeUtil import org.bitcoins.crypto._ -import org.bitcoins.dlc.oracle.DLCOracleAppConfig -import org.bitcoins.testkit.BitcoinSTestAppConfig +import org.bitcoins.testkit.Implicits.GeneratorOps +import org.bitcoins.testkit.core.gen.TLVGen import org.bitcoins.testkit.fixtures.DLCOracleDAOFixture +import scodec.bits.ByteVector class EventOutcomeDAOTest extends DLCOracleDAOFixture { @@ -22,8 +24,7 @@ class EventOutcomeDAOTest extends DLCOracleDAOFixture { val sigVersion: SigningVersion = SigningVersion.latest val message = "dummy message" - val hash: Sha256Digest = - CryptoUtil.taggedSha256(message, sigVersion.outcomeTag) + val hash: ByteVector = CryptoUtil.sha256(message).bytes val time: Instant = { // Need to do this so it is comparable to the db representation @@ -31,18 +32,22 @@ class EventOutcomeDAOTest extends DLCOracleDAOFixture { Instant.ofEpochSecond(now) } - val dummyRValDb: RValueDb = RValueDb( - nonce, - eventName, - HDPurpose(0), - HDCoinType.Bitcoin, - 0, - 0, - 0, - SchnorrDigitalSignature(nonce, FieldElement.one)) + val dummyRValDb: RValueDb = + RValueDb(nonce, eventName, HDPurpose(0), HDCoinType.Bitcoin, 0, 0, 0) + + def descriptor: EventDescriptorTLV = TLVGen.eventDescriptorTLV.sampleSome val dummyEventDb: EventDb = - EventDb(nonce, publicKey, eventName, 1, sigVersion, time, None) + EventDb(nonce, + publicKey, + 0, + eventName, + 1, + sigVersion, + time, + None, + SchnorrDigitalSignature(nonce, FieldElement.one), + descriptor) it must "create an EventOutcomeDb and read it" in { daos => val rValDAO = daos.rValueDAO @@ -80,10 +85,8 @@ class EventOutcomeDAOTest extends DLCOracleDAOFixture { val outcomeDAO = daos.outcomeDAO val outcomeDb = EventOutcomeDb(nonce, message, hash) - val outcomeDb1 = - EventOutcomeDb(nonce, - "message", - CryptoUtil.taggedSha256("message", sigVersion.outcomeTag)) + val bytes = CryptoUtil.sha256("message").bytes + val outcomeDb1 = EventOutcomeDb(nonce, "message", bytes) for { _ <- rValDAO.create(dummyRValDb) diff --git a/dlc-oracle-test/src/test/scala/org/bitcoins/dlc/oracle/storage/RValueDAOTest.scala b/dlc-oracle-test/src/test/scala/org/bitcoins/dlc/oracle/storage/RValueDAOTest.scala index 69262813e1..ec4e323ecc 100644 --- a/dlc-oracle-test/src/test/scala/org/bitcoins/dlc/oracle/storage/RValueDAOTest.scala +++ b/dlc-oracle-test/src/test/scala/org/bitcoins/dlc/oracle/storage/RValueDAOTest.scala @@ -6,8 +6,6 @@ import org.bitcoins.commons.jsonmodels.dlc.SigningVersion import org.bitcoins.core.hd.{HDCoinType, HDPurpose} import org.bitcoins.core.util.TimeUtil import org.bitcoins.crypto._ -import org.bitcoins.dlc.oracle.DLCOracleAppConfig -import org.bitcoins.testkit.BitcoinSTestAppConfig import org.bitcoins.testkit.fixtures.DLCOracleDAOFixture class RValueDAOTest extends DLCOracleDAOFixture { @@ -28,15 +26,8 @@ class RValueDAOTest extends DLCOracleDAOFixture { Instant.ofEpochSecond(now) } - val rValDb: RValueDb = RValueDb( - nonce, - eventName, - HDPurpose(0), - HDCoinType.Bitcoin, - 0, - 0, - 0, - SchnorrDigitalSignature(nonce, FieldElement.one)) + val rValDb: RValueDb = + RValueDb(nonce, eventName, HDPurpose(0), HDCoinType.Bitcoin, 0, 0, 0) it must "create an RValueDb and read it" in { daos => val rValDAO = daos.rValueDAO diff --git a/dlc-oracle/src/main/resources/postgresql/oracle/migration/V2__add_event_descriptor.sql b/dlc-oracle/src/main/resources/postgresql/oracle/migration/V2__add_event_descriptor.sql new file mode 100644 index 0000000000..50cb5b2b87 --- /dev/null +++ b/dlc-oracle/src/main/resources/postgresql/oracle/migration/V2__add_event_descriptor.sql @@ -0,0 +1,112 @@ +-- Since we will be dropping the events table we need to cache the event outcome table +CREATE TEMP TABLE event_outcomes_temp +( + nonce TEXT NOT NULL, + message TEXT NOT NULL, + hashed_message TEXT NOT NULL +); +INSERT INTO event_outcomes_temp +SELECT nonce, message, hashed_message +FROM event_outcomes; +DROP TABLE event_outcomes; + +-- Move announcement sig to event table, add event_descriptor_tlv column as well +CREATE TEMPORARY TABLE events_backup +( + nonce TEXT NOT NULL, + pubkey TEXT NOT NULL, + nonce_index INTEGER NOT NULL DEFAULT 0, + event_name TEXT NOT NULL, + num_outcomes INTEGER NOT NULL, + signing_version TEXT NOT NULL, + maturation_time TIMESTAMP NOT NULL, + attestation TEXT, + announcement_signature TEXT NOT NULL, + event_descriptor_tlv TEXT NOT NULL DEFAULT 'fdd806090001000564756d6d79' +); +INSERT INTO events_backup (nonce, pubkey, event_name, num_outcomes, signing_version, maturation_time, + announcement_signature, attestation) +SELECT e.nonce, + e.pubkey, + e.event_name, + e.num_outcomes, + e.signing_version, + e.maturation_time, + r.announcement_signature, + e.attestation +FROM events e, + r_values r +WHERE r.nonce = e.nonce; +DROP TABLE events; +CREATE TABLE events +( + nonce TEXT NOT NULL PRIMARY KEY, + pubkey TEXT NOT NULL, + nonce_index INTEGER NOT NULL, + event_name TEXT NOT NULL, + num_outcomes INTEGER NOT NULL, + signing_version TEXT NOT NULL, + maturation_time TIMESTAMP NOT NULL, + attestation TEXT, + announcement_signature TEXT NOT NULL, + event_descriptor_tlv TEXT NOT NULL +); +INSERT INTO events (nonce, pubkey, nonce_index, event_name, num_outcomes, signing_version, maturation_time, + announcement_signature, attestation, event_descriptor_tlv) +SELECT nonce, + pubkey, + nonce_index, + event_name, + num_outcomes, + signing_version, + maturation_time, + announcement_signature, + attestation, + event_descriptor_tlv +FROM events_backup; +DROP TABLE events_backup; + +-- Drop announcement sig column from R value table +CREATE TEMPORARY TABLE r_values_backup +( + nonce TEXT NOT NULL, + event_name TEXT NOT NULL, + hd_purpose INTEGER NOT NULL, + coin INTEGER NOT NULL, + account_index INTEGER NOT NULL, + chain_type INTEGER NOT NULL, + key_index INTEGER NOT NULL UNIQUE +); +INSERT INTO r_values_backup +SELECT nonce, event_name, hd_purpose, coin, account_index, chain_type, key_index +FROM r_values; +DROP TABLE r_values; +CREATE TABLE r_values +( + nonce TEXT NOT NULL, + event_name TEXT NOT NULL, + hd_purpose INTEGER NOT NULL, + coin INTEGER NOT NULL, + account_index INTEGER NOT NULL, + chain_type INTEGER NOT NULL, + key_index INTEGER NOT NULL UNIQUE, + PRIMARY KEY (nonce) +); + +INSERT INTO r_values +SELECT nonce, event_name, hd_purpose, coin, account_index, chain_type, key_index +FROM r_values_backup; +DROP TABLE r_values_backup; + +-- recreate outcome table +CREATE TABLE event_outcomes +( + nonce TEXT NOT NULL, + message TEXT NOT NULL, + hashed_message TEXT NOT NULL, + CONSTRAINT fk_nonce FOREIGN KEY (nonce) REFERENCES events (nonce) on update NO ACTION on delete NO ACTION +); +INSERT INTO event_outcomes +SELECT nonce, message, hashed_message +FROM event_outcomes_temp; +DROP TABLE event_outcomes_temp; \ No newline at end of file diff --git a/dlc-oracle/src/main/resources/sqlite/oracle/migration/V2__add_event_descriptor.sql b/dlc-oracle/src/main/resources/sqlite/oracle/migration/V2__add_event_descriptor.sql new file mode 100644 index 0000000000..0180fac234 --- /dev/null +++ b/dlc-oracle/src/main/resources/sqlite/oracle/migration/V2__add_event_descriptor.sql @@ -0,0 +1,112 @@ +-- Since we will be dropping the events table we need to cache the event outcome table +CREATE TEMP TABLE `event_outcomes_temp` +( + `nonce` TEXT NOT NULL, + `message` TEXT NOT NULL, + `hashed_message` TEXT NOT NULL +); +INSERT INTO `event_outcomes_temp` +SELECT `nonce`, `message`, `hashed_message` +FROM `event_outcomes`; +DROP TABLE `event_outcomes`; + +-- Move announcement sig to event table, add event_descriptor_tlv column as well +CREATE TEMPORARY TABLE `events_backup` +( + `nonce` VARCHAR(254) NOT NULL, + `pubkey` VARCHAR(254) NOT NULL, + `nonce_index` INTEGER NOT NULL DEFAULT 0, + `event_name` VARCHAR(254) NOT NULL, + `num_outcomes` INTEGER NOT NULL, + `signing_version` VARCHAR(254) NOT NULL, + `maturation_time` TIMESTAMP NOT NULL, + `attestation` VARCHAR(254), + `announcement_signature` VARCHAR(254) NOT NULL, + `event_descriptor_tlv` VARCHAR(254) NOT NULL DEFAULT "fdd806090001000564756d6d79" +); +INSERT INTO `events_backup` (`nonce`, `pubkey`, `event_name`, `num_outcomes`, `signing_version`, `maturation_time`, + `announcement_signature`, `attestation`) +SELECT e.`nonce`, + e.`pubkey`, + e.`event_name`, + e.`num_outcomes`, + e.`signing_version`, + e.`maturation_time`, + r.`announcement_signature`, + e.`attestation` +FROM `events` e, + `r_values` r +WHERE r.`nonce` = e.`nonce`; +DROP TABLE `events`; +CREATE TABLE `events` +( + `nonce` VARCHAR(254) NOT NULL PRIMARY KEY, + `pubkey` VARCHAR(254) NOT NULL, + `nonce_index` INTEGER NOT NULL, + `event_name` VARCHAR(254) NOT NULL, + `num_outcomes` INTEGER NOT NULL, + `signing_version` VARCHAR(254) NOT NULL, + `maturation_time` TIMESTAMP NOT NULL, + `attestation` VARCHAR(254), + `announcement_signature` VARCHAR(254) NOT NULL, + `event_descriptor_tlv` VARCHAR(254) NOT NULL +); +INSERT INTO `events` (`nonce`, `pubkey`, `nonce_index`, `event_name`, `num_outcomes`, `signing_version`, `maturation_time`, + `announcement_signature`, `attestation`, `event_descriptor_tlv`) +SELECT `nonce`, + `pubkey`, + `nonce_index`, + `event_name`, + `num_outcomes`, + `signing_version`, + `maturation_time`, + `announcement_signature`, + `attestation`, + `event_descriptor_tlv` +FROM `events_backup`; +DROP TABLE `events_backup`; + +-- Drop announcement sig column from R value table +CREATE TEMPORARY TABLE `r_values_backup` +( + `nonce` VARCHAR(254) NOT NULL, + `event_name` VARCHAR(254) NOT NULL, + `hd_purpose` INTEGER NOT NULL, + `coin` INTEGER NOT NULL, + `account_index` INTEGER NOT NULL, + `chain_type` INTEGER NOT NULL, + `key_index` INTEGER NOT NULL UNIQUE +); +INSERT INTO `r_values_backup` +SELECT `nonce`, `event_name`, `hd_purpose`, `coin`, `account_index`, `chain_type`, `key_index` +FROM `r_values`; +DROP TABLE `r_values`; +CREATE TABLE `r_values` +( + `nonce` VARCHAR(254) NOT NULL, + `event_name` VARCHAR(254) NOT NULL, + `hd_purpose` INTEGER NOT NULL, + `coin` INTEGER NOT NULL, + `account_index` INTEGER NOT NULL, + `chain_type` INTEGER NOT NULL, + `key_index` INTEGER NOT NULL UNIQUE, + PRIMARY KEY (`nonce`) +); + +INSERT INTO `r_values` +SELECT `nonce`, `event_name`, `hd_purpose`, `coin`, `account_index`, `chain_type`, `key_index` +FROM `r_values_backup`; +DROP TABLE `r_values_backup`; + +-- recreate outcome table +CREATE TABLE `event_outcomes` +( + `nonce` VARCHAR(254) NOT NULL, + `message` VARCHAR(254) NOT NULL, + `hashed_message` VARCHAR(254) NOT NULL, + CONSTRAINT `fk_nonce` FOREIGN KEY (`nonce`) REFERENCES `events` (`nonce`) on update NO ACTION on delete NO ACTION +); +INSERT INTO `event_outcomes` +SELECT `nonce`, `message`, `hashed_message` +FROM `event_outcomes_temp`; +DROP TABLE `event_outcomes_temp`; \ No newline at end of file diff --git a/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/DLCAttestationType.scala b/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/DLCAttestationType.scala new file mode 100644 index 0000000000..4df4bf8cf2 --- /dev/null +++ b/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/DLCAttestationType.scala @@ -0,0 +1,31 @@ +package org.bitcoins.dlc.oracle + +import org.bitcoins.crypto.CryptoUtil +import scodec.bits.ByteVector + +/** Represents a single DLC event that the oracle is going to sign */ +sealed trait DLCAttestationType { + def bytes: ByteVector + def outcomeString: String +} + +case class EnumAttestation(outcomeString: String) extends DLCAttestationType { + def bytes: ByteVector = CryptoUtil.serializeForHash(outcomeString) +} + +case class RangeAttestation(outcome: Long) extends DLCAttestationType { + override def outcomeString: String = outcome.toString + def bytes: ByteVector = CryptoUtil.serializeForHash(outcomeString) +} + +case class DigitDecompositionSignAttestation(positive: Boolean) + extends DLCAttestationType { + override def outcomeString: String = if (positive) "+" else "-" + def bytes: ByteVector = CryptoUtil.serializeForHash(outcomeString) +} + +case class DigitDecompositionAttestation(outcome: Int) + extends DLCAttestationType { + override def outcomeString: String = outcome.toString + def bytes: ByteVector = CryptoUtil.serializeForHash(outcomeString) +} diff --git a/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/DLCOracle.scala b/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/DLCOracle.scala index 06417bf5ce..11a034d383 100644 --- a/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/DLCOracle.scala +++ b/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/DLCOracle.scala @@ -3,23 +3,25 @@ package org.bitcoins.dlc.oracle import java.time.Instant import org.bitcoins.commons.jsonmodels.dlc.SigningVersion -import org.bitcoins.commons.jsonmodels.dlc.SigningVersion._ import org.bitcoins.core.config.BitcoinNetwork import org.bitcoins.core.crypto.ExtKeyVersion.SegWitMainNetPriv import org.bitcoins.core.crypto.{ExtPrivateKeyHardened, MnemonicCode} import org.bitcoins.core.hd._ +import org.bitcoins.core.number._ import org.bitcoins.core.protocol.Bech32Address import org.bitcoins.core.protocol.script.P2WPKHWitnessSPKV0 -import org.bitcoins.core.util.TimeUtil +import org.bitcoins.core.protocol.tlv._ +import org.bitcoins.core.util.{BitcoinSLogger, FutureUtil, NumberUtil, TimeUtil} import org.bitcoins.crypto._ +import org.bitcoins.dlc.oracle.config.DLCOracleAppConfig import org.bitcoins.dlc.oracle.storage._ import org.bitcoins.keymanager.{DecryptedMnemonic, WalletStorage} -import scodec.bits.ByteVector import scala.concurrent.{ExecutionContext, Future} case class DLCOracle(private val extPrivateKey: ExtPrivateKeyHardened)(implicit - val conf: DLCOracleAppConfig) { + val conf: DLCOracleAppConfig) + extends BitcoinSLogger { implicit val ec: ExecutionContext = conf.ec @@ -78,11 +80,8 @@ case class DLCOracle(private val extPrivateKey: ExtPrivateKeyHardened)(implicit require(path.forall(_.hardened), s"Cannot use a BIP32Path with unhardened nodes, got $path") val priv = extPrivateKey.deriveChildPrivKey(path).key - val hash = - CryptoUtil.taggedSha256( - priv.schnorrNonce.bytes ++ CryptoUtil.serializeForHash(label), - signingVersion.nonceTag) - val tweak = ECPrivateKey(hash.bytes) + val tweakBytes = signingVersion.calcNonceTweak(priv.schnorrNonce, label) + val tweak = ECPrivateKey(tweakBytes) priv.add(tweak) } @@ -91,92 +90,231 @@ case class DLCOracle(private val extPrivateKey: ExtPrivateKeyHardened)(implicit def listPendingEventDbs(): Future[Vector[EventDb]] = eventDAO.getPendingEvents - def listEvents(): Future[Vector[Event]] = { - for { - rValDbs <- rValueDAO.findAll() - eventDbs <- eventDAO.findAll() - outcomes <- eventOutcomeDAO.findAll() - } yield { - val rValDbsByNonce = rValDbs.groupBy(_.nonce) - val outcomesByNonce = outcomes.groupBy(_.nonce) - eventDbs.map(db => - Event(rValDbsByNonce(db.nonce).head, db, outcomesByNonce(db.nonce))) + def listEvents(): Future[Vector[OracleEvent]] = { + eventDAO.findAll().map { eventDbs => + val events = eventDbs.groupBy(_.eventDescriptorTLV) + events.values.map(dbs => OracleEvent.fromEventDbs(dbs)).toVector } } - def getEvent(nonce: SchnorrNonce): Future[Option[Event]] = { - for { - rValDbOpt <- rValueDAO.read(nonce) - eventDbOpt <- eventDAO.read(nonce) - outcomes <- eventOutcomeDAO.findByNonce(nonce) - } yield { - (rValDbOpt, eventDbOpt) match { - case (Some(rValDb), Some(eventDb)) => - Some(Event(rValDb, eventDb, outcomes)) - case (None, None) | (Some(_), None) | (None, Some(_)) => - None - } + def findEvent(oracleEventTLV: OracleEventTLV): Future[Option[OracleEvent]] = { + eventDAO.findByOracleEventTLV(oracleEventTLV).map { dbs => + if (dbs.isEmpty) { + None + } else Some(OracleEvent.fromEventDbs(dbs)) } } + def createNewLargeRangedEvent( + eventName: String, + maturationTime: Instant, + base: UInt16, + isSigned: Boolean, + numDigits: Int, + unit: String, + precision: Int32): Future[OracleAnnouncementTLV] = { + require(base > UInt16.zero, + s"base cannot be less than 1, got ${base.toInt}") + require(numDigits > 0, s"numDigits cannot be less than 1, got $numDigits") + + val descriptorTLV = DigitDecompositionEventDescriptorV0TLV(base, + isSigned, + numDigits, + unit, + precision) + + createNewEvent(eventName, maturationTime, descriptorTLV) + } + + def createNewRangedEvent( + eventName: String, + maturationTime: Instant, + start: Int, + count: Int, + step: Int, + unit: String, + precision: Int): Future[OracleAnnouncementTLV] = + createNewRangedEvent(eventName, + maturationTime, + Int32(start), + UInt32(count), + UInt16(step), + unit, + Int32(precision)) + + def createNewRangedEvent( + eventName: String, + maturationTime: Instant, + start: Int32, + count: UInt32, + step: UInt16, + unit: String, + precision: Int32): Future[OracleAnnouncementTLV] = { + require(count > UInt32.zero, + s"Count cannot be less than 1, got ${count.toInt}") + require(step > UInt16.zero, + s"Step cannot be less than 1, got ${step.toInt}") + + val descriptorTLV = + RangeEventDescriptorV0TLV(start, count, step, unit, precision) + + createNewEvent(eventName, maturationTime, descriptorTLV) + } + + def createNewEnumEvent( + eventName: String, + maturationTime: Instant, + outcomes: Vector[String]): Future[OracleAnnouncementTLV] = { + require(outcomes.nonEmpty, "Cannot make an event with no outcomes") + require(outcomes.distinct.size == outcomes.size, + s"Cannot have duplicate outcomes, got $outcomes") + + val descriptorTLV = EnumEventDescriptorV0TLV(outcomes) + + createNewEvent(eventName, maturationTime, descriptorTLV) + } + def createNewEvent( eventName: String, maturationTime: Instant, - outcomes: Vector[String]): Future[EventDb] = { - require(outcomes.nonEmpty, "Cannot make an event with no outcomes") - require(outcomes.distinct.size == outcomes.size, - s"Cannot have duplicate outcomes, got $outcomes") + descriptor: EventDescriptorTLV, + signingVersion: SigningVersion = SigningVersion.latest): Future[ + OracleAnnouncementTLV] = { + require(maturationTime.isAfter(TimeUtil.now), + s"Event cannot mature in the past, got $maturationTime") + for { indexOpt <- rValueDAO.maxKeyIndex - index = indexOpt match { + firstIndex = indexOpt match { case Some(value) => value + 1 case None => 0 } - signingVersion = SigningVersion.latest + rValueDbs = + 0.until(descriptor.noncesNeeded) + .map { num => + val index = firstIndex + num + val path = getPath(index) + val nonce = getKValue(eventName, path, signingVersion).schnorrNonce - path = getPath(index) - nonce = getKValue(eventName, path, signingVersion).schnorrNonce + RValueDbHelper(nonce = nonce, + eventName = eventName, + account = rValAccount, + chainType = rValueChainIndex, + keyIndex = index) + } + .toVector - hash = CryptoUtil.taggedSha256( - nonce.bytes ++ CryptoUtil.serializeForHash(eventName) ++ ByteVector - .fromLong(maturationTime.getEpochSecond), - signingVersion.announcementTag) - announcementSignature = signingKey.schnorrSign(hash.bytes) + epoch = UInt32(maturationTime.getEpochSecond) - rValueDb = RValueDbHelper(nonce = nonce, - eventName = eventName, - account = rValAccount, - chainType = rValueChainIndex, - keyIndex = index, - announcementSignature = announcementSignature) + nonces = rValueDbs.map(_.nonce) - eventDb = EventDb(nonce, - publicKey, - eventName, - outcomes.size, - signingVersion, - maturationTime, - None) - eventOutcomeDbs = outcomes.map { outcome => - val hash = CryptoUtil.taggedSha256(outcome, signingVersion.outcomeTag) - EventOutcomeDb(nonce, outcome, hash) + eventTLV = OracleEventV0TLV(nonces, epoch, descriptor, eventName) + + announcementBytes = signingVersion.calcAnnouncementHash(eventTLV) + announcementSignature = signingKey.schnorrSign(announcementBytes) + + eventOutcomeDbs = descriptor match { + case enum: EnumEventDescriptorV0TLV => + require(rValueDbs.size == 1, + "Enum events should only have one R value") + val nonce = rValueDbs.head.nonce + enum.outcomes.map { outcome => + val attestationType = EnumAttestation(outcome) + val hash = + signingVersion.calcOutcomeHash(enum, attestationType.bytes) + EventOutcomeDb(nonce, outcome, hash) + } + case range: RangeEventDescriptorV0TLV => + require(rValueDbs.size == 1, + "Range events should only have one R value") + val nonce = rValueDbs.head.nonce + + val outcomes: Vector[Long] = { + val startL = range.start.toLong + val stepL = range.step.toLong + + val outcomeRange = + 0L.until(range.count.toLong).map(num => startL + (num * stepL)) + + outcomeRange.toVector + } + + outcomes.map { outcome => + val attestationType = RangeAttestation(outcome) + val hash = + signingVersion.calcOutcomeHash(range, attestationType.bytes) + EventOutcomeDb(nonce, outcome.toString, hash) + } + case decomp: DigitDecompositionEventDescriptorV0TLV => + val signDbs = decomp match { + case _: SignedDigitDecompositionEventDescriptor => + val plusHash = signingVersion.calcOutcomeHash(decomp, "+") + val minusHash = signingVersion.calcOutcomeHash(decomp, "-") + Vector(EventOutcomeDb(nonces.head, "+", plusHash), + EventOutcomeDb(nonces.head, "-", minusHash)) + case _: UnsignedDigitDecompositionEventDescriptor => + Vector.empty + } + + val digitNonces = if (decomp.isSigned) nonces.tail else nonces + + val digitDbs = digitNonces.flatMap { nonce => + 0.until(decomp.base.toInt).map { num => + val attestationType = DigitDecompositionAttestation(num) + val hash = + signingVersion.calcOutcomeHash(decomp, attestationType.bytes) + EventOutcomeDb(nonce, num.toString, hash) + } + } + + signDbs ++ digitDbs } - _ <- rValueDAO.create(rValueDb) - eventDb <- eventDAO.create(eventDb) + eventDbs = rValueDbs.zipWithIndex.map { + case (db, index) => + EventDb(db.nonce, + publicKey, + index, + eventName, + eventOutcomeDbs.size, + signingVersion, + maturationTime, + None, + announcementSignature, + descriptor) + } + + _ <- rValueDAO.createAll(rValueDbs) + _ <- eventDAO.createAll(eventDbs) _ <- eventOutcomeDAO.createAll(eventOutcomeDbs) - } yield eventDb + } yield { + OracleEvent.fromEventDbs(eventDbs).announcementTLV + } } - def signEvent(nonce: SchnorrNonce, outcome: String): Future[EventDb] = { + def signEvent( + oracleEventTLV: OracleEventTLV, + outcome: DLCAttestationType): Future[EventDb] = { + for { + eventDbs <- eventDAO.findByOracleEventTLV(oracleEventTLV) + _ = require(eventDbs.size == 1, + "Use signLargeRange for signing multi nonce outcomes") + + sign <- signEvent(eventDbs.head.nonce, outcome) + } yield sign + } + + def signEvent( + nonce: SchnorrNonce, + outcome: DLCAttestationType): Future[EventDb] = { for { rValDbOpt <- rValueDAO.read(nonce) rValDb <- rValDbOpt match { case Some(value) => Future.successful(value) case None => Future.failed( - new RuntimeException( + new IllegalArgumentException( s"Nonce not found from this oracle ${nonce.hex}")) } @@ -189,31 +327,84 @@ case class DLCOracle(private val extPrivateKey: ExtPrivateKeyHardened)(implicit Future.successful(value) case None => Future.failed( - new RuntimeException( + new IllegalArgumentException( s"No event saved with nonce ${nonce.hex} $outcome")) } - eventOutcomeOpt <- eventOutcomeDAO.read((nonce, outcome)) + eventOutcomeOpt <- eventOutcomeDAO.read((nonce, outcome.outcomeString)) eventOutcomeDb <- eventOutcomeOpt match { case Some(value) => Future.successful(value) case None => - Future.failed(new RuntimeException( - s"No event outcome saved with nonce and message ${nonce.hex} $outcome")) + Future.failed(new IllegalArgumentException( + s"No event outcome saved with nonce and message ${nonce.hex} ${outcome.outcomeString}")) } - sig = eventDb.signingVersion match { - case Mock => - val kVal = getKValue(rValDb, Mock) - require(kVal.schnorrNonce == rValDb.nonce, + sigVersion = eventDb.signingVersion + + kVal = getKValue(rValDb, sigVersion) + _ = require(kVal.schnorrNonce == rValDb.nonce, "The nonce from derived seed did not match database") - signingKey.schnorrSignWithNonce(eventOutcomeDb.hashedMessage.bytes, - kVal) - } + + hashBytes = eventOutcomeDb.hashedMessage + sig = signingKey.schnorrSignWithNonce(hashBytes, kVal) updated = eventDb.copy(attestationOpt = Some(sig.sig)) _ <- eventDAO.update(updated) } yield updated } + + def signDigits( + oracleEventTLV: OracleEventTLV, + num: Long): Future[OracleEvent] = { + + val eventDescriptorTLV = oracleEventTLV.eventDescriptor match { + case _: EnumEventDescriptorV0TLV | _: RangeEventDescriptorV0TLV => + throw new IllegalArgumentException( + "Must have a DigitDecomposition event descriptor use signEvent instead") + case decomp: DigitDecompositionEventDescriptorV0TLV => + decomp + } + + // Make this a vec so it is easier to add on + val signSigF = + eventDescriptorTLV match { + case _: SignedDigitDecompositionEventDescriptor => + val signOutcome = DigitDecompositionSignAttestation(num >= 0) + signEvent(oracleEventTLV.nonces.head, signOutcome).map(db => + Vector(db)) + case _: UnsignedDigitDecompositionEventDescriptor => + FutureUtil.emptyVec[EventDb] + } + + val boundedNum = if (num < eventDescriptorTLV.minNum) { + logger.info( + s"Number given $num is less than the minimum, signing minimum instead") + eventDescriptorTLV.minNum.toLong + } else if (num > eventDescriptorTLV.maxNum) { + logger.info( + s"Number given $num is greater than the maximum, signing maximum instead") + eventDescriptorTLV.maxNum.toLong + } else num + + val decomposed = NumberUtil.decompose(Math.abs(boundedNum), + eventDescriptorTLV.base.toInt, + eventDescriptorTLV.numDigits.toInt) + + val nonces = + if (eventDescriptorTLV.isSigned) oracleEventTLV.nonces.tail + else oracleEventTLV.nonces + + val digitSigFs = nonces.zipWithIndex.map { + case (nonce, index) => + val digit = decomposed(index) + signEvent(nonce, DigitDecompositionAttestation(digit)) + } + + for { + signSig <- signSigF + digitSigs <- Future.sequence(digitSigFs) + } yield OracleEvent.fromEventDbs(signSig ++ digitSigs) + } } object DLCOracle { diff --git a/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/OracleEvent.scala b/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/OracleEvent.scala new file mode 100644 index 0000000000..c9f9dc1d68 --- /dev/null +++ b/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/OracleEvent.scala @@ -0,0 +1,237 @@ +package org.bitcoins.dlc.oracle + +import java.time.Instant + +import org.bitcoins.commons.jsonmodels.dlc.SigningVersion +import org.bitcoins.core.number.UInt32 +import org.bitcoins.core.protocol.tlv._ +import org.bitcoins.crypto._ +import org.bitcoins.dlc.oracle.storage.EventDb + +/** Represents an event that the oracle has committed to + * Contains all the necessary information to construct + * all the oracle TLV messages + */ +sealed trait OracleEvent { + + /** The nonces the oracle is committing to for this event */ + def nonces: Vector[SchnorrNonce] + + /** The oracle's public key */ + def pubkey: SchnorrPublicKey + + /** The name given to this event, may be a URI */ + def eventName: String + + /** The version of signing for this event */ + def signingVersion: SigningVersion + + /** The earliest expected time an outcome will be signed */ + def maturationTime: Instant + + /** A signature by the oracle of the hash of nonce and event name */ + def announcementSignature: SchnorrDigitalSignature + + def eventDescriptorTLV: EventDescriptorTLV + + def eventTLV: OracleEventTLV = + OracleEventV0TLV(nonces, + UInt32(maturationTime.getEpochSecond), + eventDescriptorTLV, + eventName) + + def announcementTLV: OracleAnnouncementTLV = { + eventTLV match { + case v0TLV: OracleEventV0TLV => + OracleAnnouncementV0TLV(announcementSignature, pubkey, v0TLV) + } + } +} + +/** An oracle event that has not been signed yet */ +sealed trait PendingOracleEvent extends OracleEvent + +/** An oracle event that has been signed */ +sealed trait CompletedOracleEvent extends OracleEvent { + def attestations: Vector[FieldElement] + + require(attestations.size == nonces.size, + "Must have a signature for every nonce") + + def signatures: Vector[SchnorrDigitalSignature] = + nonces + .zip(attestations) + .map(sigPieces => SchnorrDigitalSignature(sigPieces._1, sigPieces._2)) +} + +sealed trait EnumV0OracleEvent extends OracleEvent { + override def eventDescriptorTLV: EnumEventDescriptorV0TLV + def nonce: SchnorrNonce + + final override def nonces: Vector[SchnorrNonce] = Vector(nonce) +} + +case class PendingEnumV0OracleEvent( + pubkey: SchnorrPublicKey, + nonce: SchnorrNonce, + eventName: String, + signingVersion: SigningVersion, + maturationTime: Instant, + announcementSignature: SchnorrDigitalSignature, + eventDescriptorTLV: EnumEventDescriptorV0TLV) + extends PendingOracleEvent + with EnumV0OracleEvent + +case class CompletedEnumV0OracleEvent( + pubkey: SchnorrPublicKey, + nonce: SchnorrNonce, + eventName: String, + signingVersion: SigningVersion, + maturationTime: Instant, + announcementSignature: SchnorrDigitalSignature, + eventDescriptorTLV: EnumEventDescriptorV0TLV, + attestation: FieldElement) + extends CompletedOracleEvent + with EnumV0OracleEvent { + override def attestations: Vector[FieldElement] = Vector(attestation) +} + +sealed trait RangeV0OracleEvent extends OracleEvent { + override def eventDescriptorTLV: RangeEventDescriptorV0TLV + def nonce: SchnorrNonce + + final override def nonces: Vector[SchnorrNonce] = Vector(nonce) +} + +case class PendingRangeV0OracleEvent( + pubkey: SchnorrPublicKey, + nonce: SchnorrNonce, + eventName: String, + signingVersion: SigningVersion, + maturationTime: Instant, + announcementSignature: SchnorrDigitalSignature, + eventDescriptorTLV: RangeEventDescriptorV0TLV) + extends PendingOracleEvent + with RangeV0OracleEvent + +case class CompletedRangeV0OracleEvent( + pubkey: SchnorrPublicKey, + nonce: SchnorrNonce, + eventName: String, + signingVersion: SigningVersion, + maturationTime: Instant, + announcementSignature: SchnorrDigitalSignature, + eventDescriptorTLV: RangeEventDescriptorV0TLV, + attestation: FieldElement) + extends CompletedOracleEvent + with RangeV0OracleEvent { + override def attestations: Vector[FieldElement] = Vector(attestation) + +} + +sealed trait DigitDecompositionV0OracleEvent extends OracleEvent { + override def eventDescriptorTLV: DigitDecompositionEventDescriptorV0TLV +} + +case class PendingDigitDecompositionV0OracleEvent( + pubkey: SchnorrPublicKey, + nonces: Vector[SchnorrNonce], + eventName: String, + signingVersion: SigningVersion, + maturationTime: Instant, + announcementSignature: SchnorrDigitalSignature, + eventDescriptorTLV: DigitDecompositionEventDescriptorV0TLV) + extends PendingOracleEvent + with DigitDecompositionV0OracleEvent + +case class CompletedDigitDecompositionV0OracleEvent( + pubkey: SchnorrPublicKey, + nonces: Vector[SchnorrNonce], + eventName: String, + signingVersion: SigningVersion, + maturationTime: Instant, + announcementSignature: SchnorrDigitalSignature, + eventDescriptorTLV: DigitDecompositionEventDescriptorV0TLV, + attestations: Vector[FieldElement]) + extends CompletedOracleEvent + with DigitDecompositionV0OracleEvent + +object OracleEvent { + + def fromEventDbs(eventDbs: Vector[EventDb]): OracleEvent = { + val eventDb = eventDbs.head + require(eventDbs.forall(_.eventDescriptorTLV == eventDb.eventDescriptorTLV), + "EventDbs must all refer to the same event") + + (eventDb.eventDescriptorTLV, eventDb.attestationOpt) match { + case (enum: EnumEventDescriptorV0TLV, Some(sig)) => + require(eventDbs.size == 1, "Enum events may only have one eventDb") + CompletedEnumV0OracleEvent(eventDb.pubkey, + eventDb.nonce, + eventDb.eventName, + eventDb.signingVersion, + eventDb.maturationTime, + eventDb.announcementSignature, + enum, + sig) + case (enum: EnumEventDescriptorV0TLV, None) => + require(eventDbs.size == 1, "Enum events may only have one eventDb") + PendingEnumV0OracleEvent(eventDb.pubkey, + eventDb.nonce, + eventDb.eventName, + eventDb.signingVersion, + eventDb.maturationTime, + eventDb.announcementSignature, + enum) + case (range: RangeEventDescriptorV0TLV, Some(sig)) => + require(eventDbs.size == 1, "Range events may only have one eventDb") + CompletedRangeV0OracleEvent(eventDb.pubkey, + eventDb.nonce, + eventDb.eventName, + eventDb.signingVersion, + eventDb.maturationTime, + eventDb.announcementSignature, + range, + sig) + case (range: RangeEventDescriptorV0TLV, None) => + require(eventDbs.size == 1, "Range events may only have one eventDb") + PendingRangeV0OracleEvent(eventDb.pubkey, + eventDb.nonce, + eventDb.eventName, + eventDb.signingVersion, + eventDb.maturationTime, + eventDb.announcementSignature, + range) + case (decomp: DigitDecompositionEventDescriptorV0TLV, Some(_)) => + require(eventDbs.forall(_.attestationOpt.isDefined), + "Cannot have a partially signed event") + val sortedEventDbs = eventDbs.sortBy(_.nonceIndex) + + val attestations = sortedEventDbs.flatMap(_.attestationOpt) + + CompletedDigitDecompositionV0OracleEvent( + eventDb.pubkey, + sortedEventDbs.map(_.nonce), + eventDb.eventName, + eventDb.signingVersion, + eventDb.maturationTime, + eventDb.announcementSignature, + decomp, + attestations + ) + case (decomp: DigitDecompositionEventDescriptorV0TLV, None) => + require(eventDbs.forall(_.attestationOpt.isEmpty), + "Cannot have a partially signed event") + + PendingDigitDecompositionV0OracleEvent( + eventDb.pubkey, + eventDbs.map(_.nonce), + eventDb.eventName, + eventDb.signingVersion, + eventDb.maturationTime, + eventDb.announcementSignature, + decomp + ) + } + } +} diff --git a/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/DLCOracleAppConfig.scala b/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/config/DLCOracleAppConfig.scala similarity index 80% rename from dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/DLCOracleAppConfig.scala rename to dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/config/DLCOracleAppConfig.scala index 09374ba09a..573d8da37f 100644 --- a/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/DLCOracleAppConfig.scala +++ b/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/config/DLCOracleAppConfig.scala @@ -1,4 +1,4 @@ -package org.bitcoins.dlc.oracle +package org.bitcoins.dlc.oracle.config import java.nio.file.{Files, Path} @@ -6,10 +6,11 @@ import com.typesafe.config.Config import org.bitcoins.core.config.NetworkParameters import org.bitcoins.core.crypto.ExtKeyVersion.SegWitMainNetPriv import org.bitcoins.core.crypto.MnemonicCode -import org.bitcoins.core.util.TimeUtil +import org.bitcoins.core.util.{FutureUtil, TimeUtil} import org.bitcoins.crypto.AesPassword import org.bitcoins.db.DatabaseDriver._ import org.bitcoins.db._ +import org.bitcoins.dlc.oracle.DLCOracle import org.bitcoins.dlc.oracle.storage._ import org.bitcoins.keymanager.{DecryptedMnemonic, WalletStorage} @@ -46,10 +47,8 @@ case class DLCOracleAppConfig( } override def start(): Future[Unit] = { - logger.debug(s"Initializing wallet setup") - for { - _ <- super.start() - } yield { + logger.debug(s"Initializing dlc oracle setup") + super.start().flatMap { _ => if (Files.notExists(datadir)) { Files.createDirectories(datadir) } @@ -57,6 +56,27 @@ case class DLCOracleAppConfig( migrate() } logger.info(s"Applied $numMigrations to the dlc oracle project") + + if (migrationsApplied() == 2) { + logger.debug(s"Doing V2 Migration") + val eventDAO = EventDAO()(ec, appConfig) + for { + // get all events + allEvents <- eventDAO.findAll() + allOutcomes <- EventOutcomeDAO()(ec, appConfig).findAll() + + outcomesByNonce = allOutcomes.groupBy(_.nonce) + // Update them to have the correct event descriptor + updated = allEvents.map { eventDb => + val outcomeDbs = outcomesByNonce(eventDb.nonce) + val descriptor = + EventOutcomeDbHelper.createEnumEventDescriptor(outcomeDbs) + eventDb.copy(eventDescriptorTLV = descriptor) + } + + _ <- eventDAO.upsertAll(updated) + } yield () + } else FutureUtil.unit } } diff --git a/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/storage/EventDAO.scala b/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/storage/EventDAO.scala index cceffefae7..7d0a28197c 100644 --- a/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/storage/EventDAO.scala +++ b/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/storage/EventDAO.scala @@ -3,6 +3,11 @@ package org.bitcoins.dlc.oracle.storage import java.time.Instant import org.bitcoins.commons.jsonmodels.dlc.SigningVersion +import org.bitcoins.core.protocol.tlv.{ + EventDescriptorTLV, + OracleEventTLV, + OracleEventV0TLV +} import org.bitcoins.crypto._ import org.bitcoins.db.{AppConfig, CRUD, DbCommonsColumnMappers, SlickUtil} import slick.lifted.{ForeignKeyQuery, ProvenShape} @@ -41,13 +46,25 @@ case class EventDAO()(implicit findAll().map(_.filter(_.attestationOpt.isEmpty)) } + def findByOracleEventTLV( + oracleEvent: OracleEventTLV): Future[Vector[EventDb]] = { + val query = oracleEvent match { + case v0: OracleEventV0TLV => + table.filter(_.nonce.inSet(v0.nonces)) + } + + safeDatabase.runVec(query.result.transactionally) + } + class EventTable(tag: Tag) extends Table[EventDb](tag, schemaName, "events") { def nonce: Rep[SchnorrNonce] = column("nonce", O.PrimaryKey) def pubkey: Rep[SchnorrPublicKey] = column("pubkey") - def eventName: Rep[String] = column("event_name", O.Unique) + def nonceIndex: Rep[Int] = column("nonce_index") + + def eventName: Rep[String] = column("event_name") def numOutcomes: Rep[Long] = column("num_outcomes") @@ -57,14 +74,23 @@ case class EventDAO()(implicit def attestationOpt: Rep[Option[FieldElement]] = column("attestation") + def announcementSignature: Rep[SchnorrDigitalSignature] = + column("announcement_signature") + + def eventDescriptorTLV: Rep[EventDescriptorTLV] = + column("event_descriptor_tlv") + def * : ProvenShape[EventDb] = (nonce, pubkey, + nonceIndex, eventName, numOutcomes, signingVersion, maturationTime, - attestationOpt).<>(EventDb.tupled, EventDb.unapply) + attestationOpt, + announcementSignature, + eventDescriptorTLV).<>(EventDb.tupled, EventDb.unapply) def fk: ForeignKeyQuery[_, RValueDb] = { foreignKey("fk_nonce", diff --git a/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/storage/EventDb.scala b/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/storage/EventDb.scala index da88cfbe0b..5ca30dfa61 100644 --- a/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/storage/EventDb.scala +++ b/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/storage/EventDb.scala @@ -3,17 +3,35 @@ package org.bitcoins.dlc.oracle.storage import java.time.Instant import org.bitcoins.commons.jsonmodels.dlc.SigningVersion +import org.bitcoins.core.protocol.tlv._ import org.bitcoins.crypto._ +import org.bitcoins.dlc.oracle.OracleEvent +/** These represent individual events at the nonce level + * You can aggregate 1 to n EventDbs into an [[OracleEvent]] to get all of the information + * about a particular descriptor + * + * In the case of [[EnumEventDescriptorV0TLV]] there is only 1 [[EventDb]] + * that corresponds to the enum descriptor + * + * In the case of [[DigitDecompositionEventDescriptorV0TLV]] you have + * [[DigitDecompositionEventDescriptorV0TLV.numDigits]] with an optional +1 + * depending on if the digit decomposition event is for a signed number or not + */ case class EventDb( nonce: SchnorrNonce, pubkey: SchnorrPublicKey, + nonceIndex: Int, eventName: String, numOutcomes: Long, signingVersion: SigningVersion, maturationTime: Instant, - attestationOpt: Option[FieldElement]) { + attestationOpt: Option[FieldElement], + announcementSignature: SchnorrDigitalSignature, + eventDescriptorTLV: EventDescriptorTLV) { lazy val sigOpt: Option[SchnorrDigitalSignature] = attestationOpt.map(SchnorrDigitalSignature(nonce, _)) + + lazy val toOracleEvent: OracleEvent = OracleEvent.fromEventDbs(Vector(this)) } diff --git a/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/storage/EventOutcomeDAO.scala b/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/storage/EventOutcomeDAO.scala index 1c754a2e8d..252b8aba09 100644 --- a/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/storage/EventOutcomeDAO.scala +++ b/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/storage/EventOutcomeDAO.scala @@ -1,7 +1,8 @@ package org.bitcoins.dlc.oracle.storage -import org.bitcoins.crypto.{SchnorrNonce, Sha256Digest} +import org.bitcoins.crypto.SchnorrNonce import org.bitcoins.db.{AppConfig, CRUD, DbCommonsColumnMappers, SlickUtil} +import scodec.bits.ByteVector import slick.lifted.{ForeignKeyQuery, ProvenShape} import scala.concurrent.{ExecutionContext, Future} @@ -55,7 +56,7 @@ case class EventOutcomeDAO()(implicit def message: Rep[String] = column("message") - def hashedMessage: Rep[Sha256Digest] = column("hashed_message") + def hashedMessage: Rep[ByteVector] = column("hashed_message") def * : ProvenShape[EventOutcomeDb] = (nonce, message, hashedMessage).<>(EventOutcomeDb.tupled, diff --git a/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/storage/EventOutcomeDb.scala b/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/storage/EventOutcomeDb.scala index 24c7efe043..6e01d48ed4 100644 --- a/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/storage/EventOutcomeDb.scala +++ b/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/storage/EventOutcomeDb.scala @@ -1,8 +1,20 @@ package org.bitcoins.dlc.oracle.storage -import org.bitcoins.crypto.{SchnorrNonce, Sha256Digest} +import org.bitcoins.core.protocol.tlv.EnumEventDescriptorV0TLV +import org.bitcoins.crypto.SchnorrNonce +import scodec.bits.ByteVector case class EventOutcomeDb( nonce: SchnorrNonce, message: String, - hashedMessage: Sha256Digest) + hashedMessage: ByteVector) + +object EventOutcomeDbHelper { + + def createEnumEventDescriptor( + outcomes: Vector[EventOutcomeDb]): EnumEventDescriptorV0TLV = { + val strs = outcomes.map(_.message) + + EnumEventDescriptorV0TLV(strs) + } +} diff --git a/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/storage/RValueDAO.scala b/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/storage/RValueDAO.scala index c455e829d9..9e7f20619e 100644 --- a/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/storage/RValueDAO.scala +++ b/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/storage/RValueDAO.scala @@ -1,7 +1,7 @@ package org.bitcoins.dlc.oracle.storage import org.bitcoins.core.hd.{HDCoinType, HDPurpose} -import org.bitcoins.crypto.{SchnorrDigitalSignature, SchnorrNonce} +import org.bitcoins.crypto.SchnorrNonce import org.bitcoins.db.{AppConfig, CRUD, DbCommonsColumnMappers, SlickUtil} import slick.lifted.ProvenShape @@ -55,17 +55,8 @@ case class RValueDAO()(implicit def keyIndex: Rep[Int] = column("key_index", O.Unique) - def announcementSignature: Rep[SchnorrDigitalSignature] = - column("announcement_signature") - def * : ProvenShape[RValueDb] = - (nonce, - eventName, - purpose, - coinType, - accountIndex, - chainType, - keyIndex, - announcementSignature).<>(RValueDb.tupled, RValueDb.unapply) + (nonce, eventName, purpose, coinType, accountIndex, chainType, keyIndex) + .<>(RValueDb.tupled, RValueDb.unapply) } } diff --git a/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/storage/RValueDb.scala b/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/storage/RValueDb.scala index bf4bb2bc47..3c586d354a 100644 --- a/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/storage/RValueDb.scala +++ b/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/storage/RValueDb.scala @@ -1,7 +1,7 @@ package org.bitcoins.dlc.oracle.storage import org.bitcoins.core.hd._ -import org.bitcoins.crypto.{SchnorrDigitalSignature, SchnorrNonce} +import org.bitcoins.crypto.SchnorrNonce case class RValueDb( nonce: SchnorrNonce, @@ -10,8 +10,7 @@ case class RValueDb( accountCoin: HDCoinType, accountIndex: Int, chainType: Int, - keyIndex: Int, - announcementSignature: SchnorrDigitalSignature) { + keyIndex: Int) { val path: BIP32Path = BIP32Path.fromString( s"m/${purpose.constant}'/${accountCoin.toInt}'/$accountIndex'/$chainType'/$keyIndex'") @@ -24,15 +23,13 @@ object RValueDbHelper { eventName: String, account: HDAccount, chainType: Int, - keyIndex: Int, - announcementSignature: SchnorrDigitalSignature): RValueDb = { + keyIndex: Int): RValueDb = { RValueDb(nonce, eventName, account.purpose, account.coin.coinType, account.index, chainType, - keyIndex, - announcementSignature) + keyIndex) } } diff --git a/docs/oracle/oracle-server.md b/docs/oracle/oracle-server.md index 81514867fb..a209132e35 100644 --- a/docs/oracle/oracle-server.md +++ b/docs/oracle/oracle-server.md @@ -86,70 +86,235 @@ For more information on how to use our built in `cli` to interact with the serve - `getpublickey` - Get oracle's public key - `getstakingaddress` - Get oracle's staking address -- `listevents` - Lists all event nonces +- `listevents` - Lists all oracle event TLVs - `createevent` `label` `maturationtime` `outcomes` - Registers an oracle event - `label` - Label for this event - `maturationtime` - The earliest expected time an outcome will be signed, given in epoch second - `outcomes` - Possible outcomes for this event -- `getevent` `nonce` - Get an event's details - - `nonce` - Nonce associated with the event -- `signevent` `nonce` `outcome` - Signs an event - - `nonce` - Nonce associated with the event to sign - - `outcome`- Outcome to sign for this event -- `getsignature` `nonce` - Get the signature from a signed event - - `nonce` - Nonce associated with the signed event +- `createrangedevent` `name` `maturationtime` `start` `stop` `step` - Registers an oracle event with a range of outcomes + - `name` - Name for this event + - `maturationtime` - The earliest expected time an outcome will be signed, given in epoch second + - `start` - The first possible outcome number + - `stop` - The last possible outcome number + - `step` - The increment between each outcome + - `unit` - The unit denomination of the outcome value + - `precision` - The precision of the outcome representing the base exponent by which to multiply the number represented by the composition of the digits to obtain the actual outcome value. +- `createdigitdecompevent` `name` `maturationtime` `base` `numdigits` `unit` `precision` `[signed]` - Registers an oracle event with a large number for its outcomes + - `name`- Name for this event + - `maturationtime` - The earliest expected time an outcome will be signed, given in epoch second + - `base` - The base in which the outcome value is decomposed + - `numdigits` - The max number of digits the outcome can have + - `unit` - The unit denomination of the outcome value + - `precision` - The precision of the outcome representing the base exponent by which to multiply the number represented by the composition of the digits to obtain the actual outcome value. + - `--signed`- Whether the outcomes can be negative +- `getevent` `event` - Get an event's details + - `event` - The event's oracle event tlv +- `signevent` `event` `outcome` - Signs an event + - `event` - The event's oracle event tlv + - `outcome`- Outcome to sign for this event +- `signforrange` `event` `outcome` - Signs an event + - `event` - The event's oracle event tlv + - `outcome`- Outcome to sign for this event +- `signdigits` `event` `outcome` - Signs an event + - `event` - The event's oracle event tlv + - `outcome` - Number to sign for this event +- `getsignatures` `event` - Get the signatures from a signed event + - `event` - The event's oracle event tlv ### Create Event Example Bitcoin-S CLI: ```bash -$ bitcoin-s-cli createevent testevent 1601917137 "outcome 1,outcome 2,outcome 3" -a777c9fdc42efbbbcb78e51a18ff98cdaafb809aeb082b8ebe95d57b1e21f1aa +$ bitcoin-s-cli createevent test 1701917137 "outcome1,outcome2,outcome3" +fdd806400374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8000300086f7574636f6d653100086f7574636f6d653200086f7574636f6d6533 -$ bitcoin-s-cli getevent a777c9fdc42efbbbcb78e51a18ff98cdaafb809aeb082b8ebe95d57b1e21f1aa +$ bitcoin-s-cli getevent fdd806400374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8000300086f7574636f6d653100086f7574636f6d653200086f7574636f6d6533 { - "nonce": "a777c9fdc42efbbbcb78e51a18ff98cdaafb809aeb082b8ebe95d57b1e21f1aa", - "eventName": "testevent", - "numOutcomes": 3, + "nonces": [ + "0374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8" + ], + "eventName": "test", "signingVersion": "Mock", - "maturationTime": "2020-10-05T16:58:57Z", - "announcementSignature": "1533265899006003fa79dc0d480061c3378bc634ec041efc97fc70827f3a1c9f30e05373f36c2e2dd9d2ad64d8aaee17fef724af2284b87724b949d48846920c", - "attestation": "", - "signature": "", + "maturationTime": "2023-12-07T02:45:37Z", + "announcementSignature": "e27ccd54ee0e2b94c4af6e7a4ee5a73026d244dec373d6ea5d9c671d2792108c1df0b64820a1d578165cd07d37dbb4c2e6d72fbdbead156b45d8838ca1d36691", + "eventDescriptorTLV": "fdd806400374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8000300086f7574636f6d653100086f7574636f6d653200086f7574636f6d6533", + "eventTLV": "fdd8226cf8d695520151bc9fbd129be6231f46b0e137b26d8ff91910c0cb6d07f6924968657131d1fdd806400374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8000300086f7574636f6d653100086f7574636f6d653200086f7574636f6d653374657374", + "announcementTLV": "fdd824b0e27ccd54ee0e2b94c4af6e7a4ee5a73026d244dec373d6ea5d9c671d2792108c1df0b64820a1d578165cd07d37dbb4c2e6d72fbdbead156b45d8838ca1d36691fdd8226cf8d695520151bc9fbd129be6231f46b0e137b26d8ff91910c0cb6d07f6924968657131d1fdd806400374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8000300086f7574636f6d653100086f7574636f6d653200086f7574636f6d653374657374", + "attestations": null, + "signatures": null, "outcomes": [ - "outcome 1", - "outcome 2", - "outcome 3" + "outcome1", + "outcome2", + "outcome3" ] } + +$ bitcoin-s-cli signevent fdd806400374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8000300086f7574636f6d653100086f7574636f6d653200086f7574636f6d6533 "outcome1" +0374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8a65977263a6b6071c29232a516adb0e69e8e049772275dc9fa8d9cfa620960dd + +$ bitcoin-s-cli getsignatures fdd806400374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8000300086f7574636f6d653100086f7574636f6d653200086f7574636f6d6533 +[ + "0374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8a65977263a6b6071c29232a516adb0e69e8e049772275dc9fa8d9cfa620960dd" +] ``` CURL: ```bash -$ curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "createevent", "params": ["testEvent", 1601917137, ["outcome 1", "outcome 2", "outcome 3"]]}' -H "Content-Type: application/json" http://127.0.0.1:9999/ -{"result":"28592661c78a3c2e0a568e92122e146022cb018b6b0ac888cdffc70a506e9ad2","error":null} +$ curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "createevent", "params": ["testEvent", 1701917137, ["outcome1", "outcome2", "outcome3"]]}' -H "Content-Type: application/json" http://127.0.0.1:9999/ +{"result":"fdd806400374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8000300086f7574636f6d653100086f7574636f6d653200086f7574636f6d6533","error":null} -$ curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getevent", "params": ["28592661c78a3c2e0a568e92122e146022cb018b6b0ac888cdffc70a506e9ad2"]}' -H "Content-Type: application/json" http://127.0.0.1:9999/ -{"result":{"nonce":"28592661c78a3c2e0a568e92122e146022cb018b6b0ac888cdffc70a506e9ad2","eventName":"testEvent","numOutcomes":3,"signingVersion":"Mock","maturationTime":"2020-10-05T16:58:57Z","commitmentSignature":"a91499fa83ca607b06bb919284e002452d6c8f396295495586886b1f7e6d6f094c7d1504f35ee2210a036313569c1951aada6b3d52248f77c7e2c5836a970dd7","attestation":"","signature":"","outcomes":["outcome 1","outcome 2","outcome 3"]},"error":null} +$ curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getevent", "params": ["fdd806400374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8000300086f7574636f6d653100086f7574636f6d653200086f7574636f6d6533"]}' -H "Content-Type: application/json" http://127.0.0.1:9999/ +{"result":{"nonces":["0374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8"],"eventName":"test","signingVersion":"Mock","maturationTime":"2023-12-07T02:45:37Z","announcementSignature":"e27ccd54ee0e2b94c4af6e7a4ee5a73026d244dec373d6ea5d9c671d2792108c1df0b64820a1d578165cd07d37dbb4c2e6d72fbdbead156b45d8838ca1d36691","eventDescriptorTLV":"fdd806400374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8000300086f7574636f6d653100086f7574636f6d653200086f7574636f6d6533","eventTLV":"fdd8226cf8d695520151bc9fbd129be6231f46b0e137b26d8ff91910c0cb6d07f6924968657131d1fdd806400374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8000300086f7574636f6d653100086f7574636f6d653200086f7574636f6d653374657374","announcementTLV":"fdd824b0e27ccd54ee0e2b94c4af6e7a4ee5a73026d244dec373d6ea5d9c671d2792108c1df0b64820a1d578165cd07d37dbb4c2e6d72fbdbead156b45d8838ca1d36691fdd8226cf8d695520151bc9fbd129be6231f46b0e137b26d8ff91910c0cb6d07f6924968657131d1fdd806400374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8000300086f7574636f6d653100086f7574636f6d653200086f7574636f6d653374657374","attestations":["a65977263a6b6071c29232a516adb0e69e8e049772275dc9fa8d9cfa620960dd"],"signatures":["0374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8a65977263a6b6071c29232a516adb0e69e8e049772275dc9fa8d9cfa620960dd"],"outcomes":["outcome1","outcome2","outcome3"]},"error":null} + +$ curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "signevent", "params": ["fdd806400374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8000300086f7574636f6d653100086f7574636f6d653200086f7574636f6d6533", "outcome 1"]}' -H "Content-Type: application/json" http://127.0.0.1:9999/ +{"result":"0374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8a65977263a6b6071c29232a516adb0e69e8e049772275dc9fa8d9cfa620960dd","error":null} + +$ curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getsignatures", "params": ["fdd806400374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8000300086f7574636f6d653100086f7574636f6d653200086f7574636f6d6533"]}' -H "Content-Type: application/json" http://127.0.0.1:9999/ +{"result":["0374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8a65977263a6b6071c29232a516adb0e69e8e049772275dc9fa8d9cfa620960dd"],"error":null} ``` -### Sign Event Example +#### Create Ranged Event Example + +Bitcoin-S CLI: +```bash +$ bitcoin-s-cli createrangedevent tomorrowTemperature 1701917137 0 100 1 "degrees F" 0 +fdd8082a5858702d9395c60739e16c29e20ada0a49e4baa499f69fbc6b65a88fa9f3a7d000000000000000640001 + +$ bitcoin-s-cli getevent fdd8082a5858702d9395c60739e16c29e20ada0a49e4baa499f69fbc6b65a88fa9f3a7d000000000000000640001 +{ + "nonces": [ + "5858702d9395c60739e16c29e20ada0a49e4baa499f69fbc6b65a88fa9f3a7d0" + ], + "eventName": "tomorrowTemperature", + "signingVersion": "Mock", + "maturationTime": "2023-12-07T02:45:37Z", + "announcementSignature": "33861a85d7ac7fc3f7b0dcaa891f9deebd2758f72d734a63dcab14f2a9d7f30d6099805367ac05bc747c1232b7211f8bd025478ef2b03418603cf5f52e2466c7", + "eventDescriptorTLV": "fdd8082a5858702d9395c60739e16c29e20ada0a49e4baa499f69fbc6b65a88fa9f3a7d000000000000000640001", + "eventTLV": "fdd82265f8d695520151bc9fbd129be6231f46b0e137b26d8ff91910c0cb6d07f6924968657131d1fdd8082a5858702d9395c60739e16c29e20ada0a49e4baa499f69fbc6b65a88fa9f3a7d000000000000000640001746f6d6f72726f7754656d7065726174757265", + "announcementTLV": "fdd824a933861a85d7ac7fc3f7b0dcaa891f9deebd2758f72d734a63dcab14f2a9d7f30d6099805367ac05bc747c1232b7211f8bd025478ef2b03418603cf5f52e2466c7fdd82265f8d695520151bc9fbd129be6231f46b0e137b26d8ff91910c0cb6d07f6924968657131d1fdd8082a5858702d9395c60739e16c29e20ada0a49e4baa499f69fbc6b65a88fa9f3a7d000000000000000640001746f6d6f72726f7754656d7065726174757265", + "attestations": null, + "signatures": null, + "outcomes": [ + 0, + 1, + 2, + ... + 98, + 99, + 100 + ] +} + +$ bitcoin-s-cli signforrange fdd8082a5858702d9395c60739e16c29e20ada0a49e4baa499f69fbc6b65a88fa9f3a7d000000000000000640001 50 +abb84920cb647b1dc2bbbc6b1584af8d0a1a737fe0765d7414ebffcfd9c7057da391ea18b22f695bf8a34caa5a12acbdc917aea95990dbbf9568ca65676e6b7b + +$ bitcoin-s-cli getsignatures fdd8082a5858702d9395c60739e16c29e20ada0a49e4baa499f69fbc6b65a88fa9f3a7d000000000000000640001 +[ + "abb84920cb647b1dc2bbbc6b1584af8d0a1a737fe0765d7414ebffcfd9c7057da391ea18b22f695bf8a34caa5a12acbdc917aea95990dbbf9568ca65676e6b7b" +] +``` +CURL: +```bash +$ curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "createrangedevent", "params": ["tomorrowsTemperature", 1701917137, 0, 100, 1, "degrees C", 0]}' -H "Content-Type: application/json" http://127.0.0.1:9999/ +{"result":"fdd8082a5858702d9395c60739e16c29e20ada0a49e4baa499f69fbc6b65a88fa9f3a7d000000000000000640001","error":null} + +$ curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getevent", "params": ["fdd8082a5858702d9395c60739e16c29e20ada0a49e4baa499f69fbc6b65a88fa9f3a7d000000000000000640001"]}' -H "Content-Type: application/json" http://127.0.0.1:9999/ +{"result":{"nonces":["5858702d9395c60739e16c29e20ada0a49e4baa499f69fbc6b65a88fa9f3a7d0"],"eventName":"tomorrowTemperature","signingVersion":"Mock","maturationTime":"2023-12-07T02:45:37Z","announcementSignature":"33861a85d7ac7fc3f7b0dcaa891f9deebd2758f72d734a63dcab14f2a9d7f30d6099805367ac05bc747c1232b7211f8bd025478ef2b03418603cf5f52e2466c7","eventDescriptorTLV":"fdd8082a5858702d9395c60739e16c29e20ada0a49e4baa499f69fbc6b65a88fa9f3a7d000000000000000640001","eventTLV":"fdd82265f8d695520151bc9fbd129be6231f46b0e137b26d8ff91910c0cb6d07f6924968657131d1fdd8082a5858702d9395c60739e16c29e20ada0a49e4baa499f69fbc6b65a88fa9f3a7d000000000000000640001746f6d6f72726f7754656d7065726174757265","announcementTLV":"fdd824a933861a85d7ac7fc3f7b0dcaa891f9deebd2758f72d734a63dcab14f2a9d7f30d6099805367ac05bc747c1232b7211f8bd025478ef2b03418603cf5f52e2466c7fdd82265f8d695520151bc9fbd129be6231f46b0e137b26d8ff91910c0cb6d07f6924968657131d1fdd8082a5858702d9395c60739e16c29e20ada0a49e4baa499f69fbc6b65a88fa9f3a7d000000000000000640001746f6d6f72726f7754656d7065726174757265","attestations":null,"signatures":null,"outcomes":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99]},"error":null} + +$ curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "signforrange", "params": ["fdd8082a5858702d9395c60739e16c29e20ada0a49e4baa499f69fbc6b65a88fa9f3a7d000000000000000640001", 50]}' -H "Content-Type: application/json" http://127.0.0.1:9999/ +{"result":"abb84920cb647b1dc2bbbc6b1584af8d0a1a737fe0765d7414ebffcfd9c7057da391ea18b22f695bf8a34caa5a12acbdc917aea95990dbbf9568ca65676e6b7b","error":null} + +$ curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getsignatures", "params": ["fdd8082a5858702d9395c60739e16c29e20ada0a49e4baa499f69fbc6b65a88fa9f3a7d000000000000000640001"]}' -H "Content-Type: application/json" http://127.0.0.1:9999/ +{"result":["abb84920cb647b1dc2bbbc6b1584af8d0a1a737fe0765d7414ebffcfd9c7057da391ea18b22f695bf8a34caa5a12acbdc917aea95990dbbf9568ca65676e6b7b"],"error":null} +``` + +### Large Range Example Bitcoin-S CLI: ```bash -$ bitcoin-s-cli signevent a777c9fdc42efbbbcb78e51a18ff98cdaafb809aeb082b8ebe95d57b1e21f1aa "outcome 1" -a777c9fdc42efbbbcb78e51a18ff98cdaafb809aeb082b8ebe95d57b1e21f1aabddd069a3295eb8e02a8a89de4b50b063ffeb290e5d1c6ea3e4e21efb2ad208f +$ bitcoin-s-cli createdigitdecompevent exampleDecomp 1701917137 10 3 "units" 0 --signed +fdd80a85000a010004abb84920cb647b1dc2bbbc6b1584af8d0a1a737fe0765d7414ebffcfd9c7057d14c56615db0684b6dff24683fa25905c84a87ac9f42f75a097ceeb444ba7f851408c23c383d1b897638a8310dfd1979f3dc78c75180a5e25510cfbc2563a02557e6a8d9803662a1576e95d6165e9aa1751a909124aee60736efdde78ccef4458 -$ bitcoin-s-cli getsignature a777c9fdc42efbbbcb78e51a18ff98cdaafb809aeb082b8ebe95d57b1e21f1aa -a777c9fdc42efbbbcb78e51a18ff98cdaafb809aeb082b8ebe95d57b1e21f1aabddd069a3295eb8e02a8a89de4b50b063ffeb290e5d1c6ea3e4e21efb2ad208f +$ bs-cli getevent fdd80a85000a010004abb84920cb647b1dc2bbbc6b1584af8d0a1a737fe0765d7414ebffcfd9c7057d14c56615db0684b6dff24683fa25905c84a87ac9f42f75a097ceeb444ba7f851408c23c383d1b897638a8310dfd1979f3dc78c75180a5e25510cfbc2563a02557e6a8d9803662a1576e95d6165e9aa1751a909124aee60736efdde78ccef4458 + { + "nonces": [ + "abb84920cb647b1dc2bbbc6b1584af8d0a1a737fe0765d7414ebffcfd9c7057d", + "14c56615db0684b6dff24683fa25905c84a87ac9f42f75a097ceeb444ba7f851", + "408c23c383d1b897638a8310dfd1979f3dc78c75180a5e25510cfbc2563a0255", + "7e6a8d9803662a1576e95d6165e9aa1751a909124aee60736efdde78ccef4458" + ], + "eventName": "exampleDecomp", + "signingVersion": "Mock", + "maturationTime": "2023-12-07T02:45:37Z", + "announcementSignature": "7b149e6001496f34588ce3089df91d0f1dfbaaae988ed993c1eacf759519c358ad1fd1d98747412bf25e257c9e4cb18ece3f132fc3ef2f400258d9cf5eb9fae0", + "eventDescriptorTLV": "fdd80a85000a010004abb84920cb647b1dc2bbbc6b1584af8d0a1a737fe0765d7414ebffcfd9c7057d14c56615db0684b6dff24683fa25905c84a87ac9f42f75a097ceeb444ba7f851408c23c383d1b897638a8310dfd1979f3dc78c75180a5e25510cfbc2563a02557e6a8d9803662a1576e95d6165e9aa1751a909124aee60736efdde78ccef4458", + "eventTLV": "fdd822bef8d695520151bc9fbd129be6231f46b0e137b26d8ff91910c0cb6d07f6924968657131d1fdd80a85000a010004abb84920cb647b1dc2bbbc6b1584af8d0a1a737fe0765d7414ebffcfd9c7057d14c56615db0684b6dff24683fa25905c84a87ac9f42f75a097ceeb444ba7f851408c23c383d1b897638a8310dfd1979f3dc78c75180a5e25510cfbc2563a02557e6a8d9803662a1576e95d6165e9aa1751a909124aee60736efdde78ccef44586578616d706c654c6172676552616e6765", + "announcementTLV": "fdd824fd01027b149e6001496f34588ce3089df91d0f1dfbaaae988ed993c1eacf759519c358ad1fd1d98747412bf25e257c9e4cb18ece3f132fc3ef2f400258d9cf5eb9fae0fdd822bef8d695520151bc9fbd129be6231f46b0e137b26d8ff91910c0cb6d07f6924968657131d1fdd80a85000a010004abb84920cb647b1dc2bbbc6b1584af8d0a1a737fe0765d7414ebffcfd9c7057d14c56615db0684b6dff24683fa25905c84a87ac9f42f75a097ceeb444ba7f851408c23c383d1b897638a8310dfd1979f3dc78c75180a5e25510cfbc2563a02557e6a8d9803662a1576e95d6165e9aa1751a909124aee60736efdde78ccef44586578616d706c654c6172676552616e6765", + "attestations": null, + "signatures": null, + "outcomes": [ + [ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9" + ], + [ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9" + ], + [ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9" + ], + [ + "+", + "-" + ] + ] + } + +bitcoin-s-cli signdigits fdd80a85000a010004abb84920cb647b1dc2bbbc6b1584af8d0a1a737fe0765d7414ebffcfd9c7057d14c56615db0684b6dff24683fa25905c84a87ac9f42f75a097ceeb444ba7f851408c23c383d1b897638a8310dfd1979f3dc78c75180a5e25510cfbc2563a02557e6a8d9803662a1576e95d6165e9aa1751a909124aee60736efdde78ccef4458 123 +[ + "abb84920cb647b1dc2bbbc6b1584af8d0a1a737fe0765d7414ebffcfd9c7057da391ea18b22f695bf8a34caa5a12acbdc917aea95990dbbf9568ca65676e6b7b", + "14c56615db0684b6dff24683fa25905c84a87ac9f42f75a097ceeb444ba7f851edbab17bb86ee92834624bb7c59f490b3d03db4a4e2732eb56dde75740d5b674", + "408c23c383d1b897638a8310dfd1979f3dc78c75180a5e25510cfbc2563a0255e4f528c5cc1a80f6aaabf2b672cea43488d277d90bfe37c9ac6f2a3cb1e7705a", + "7e6a8d9803662a1576e95d6165e9aa1751a909124aee60736efdde78ccef4458bd578082b9e47be7d628f9c1ef43958d33f6b8b4fcae07c250f53111edc50ced" +] ``` CURL: -```bash -$ curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "signevent", "params": ["a777c9fdc42efbbbcb78e51a18ff98cdaafb809aeb082b8ebe95d57b1e21f1aa", "outcome 1"]}' -H "Content-Type: application/json" http://127.0.0.1:9999/ -{"result":"a777c9fdc42efbbbcb78e51a18ff98cdaafb809aeb082b8ebe95d57b1e21f1aabddd069a3295eb8e02a8a89de4b50b063ffeb290e5d1c6ea3e4e21efb2ad208f","error":null} -$ curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getsignature", "params": ["a777c9fdc42efbbbcb78e51a18ff98cdaafb809aeb082b8ebe95d57b1e21f1aa"]}' -H "Content-Type: application/json" http://127.0.0.1:9999/ -{"result":"a777c9fdc42efbbbcb78e51a18ff98cdaafb809aeb082b8ebe95d57b1e21f1aabddd069a3295eb8e02a8a89de4b50b063ffeb290e5d1c6ea3e4e21efb2ad208f","error":null} +``` +$ curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "createdigitdecompevent", "params": ["exampleLargeRange1", 1701917137, 10, true, 3, "units", 0]}' -H "Content-Type: application/json" http://127.0.0.1:9999/ +{"result":"fdd80a85000a010004a9670e21aeb8f0281980657c6c6cc9e94804b9cbff650cb5a9a1b20b8556cbcd6de7afbb816d2ff9be8ff250d8075a68300e51569c96ab998fec90a6e0bd1a6cf0eb12e0c33dae1c44281eb9057910d6a40cf76987e721d622dae13e79ce160739bf4ff7c0ce8408aa23dd619c5aa5e25d078d64cd198830e8e70436f5611b1e","error":null} + +$ curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getevent", "params": ["fdd80a85000a010004a9670e21aeb8f0281980657c6c6cc9e94804b9cbff650cb5a9a1b20b8556cbcd6de7afbb816d2ff9be8ff250d8075a68300e51569c96ab998fec90a6e0bd1a6cf0eb12e0c33dae1c44281eb9057910d6a40cf76987e721d622dae13e79ce160739bf4ff7c0ce8408aa23dd619c5aa5e25d078d64cd198830e8e70436f5611b1e"]}' -H "Content-Type: application/json" http://127.0.0.1:9999/ +{"result":{"nonces":["a9670e21aeb8f0281980657c6c6cc9e94804b9cbff650cb5a9a1b20b8556cbcd","6de7afbb816d2ff9be8ff250d8075a68300e51569c96ab998fec90a6e0bd1a6c","f0eb12e0c33dae1c44281eb9057910d6a40cf76987e721d622dae13e79ce1607","39bf4ff7c0ce8408aa23dd619c5aa5e25d078d64cd198830e8e70436f5611b1e"],"eventName":"exampleLargeRange1","signingVersion":"Mock","maturationTime":"2023-12-07T02:45:37Z","announcementSignature":"c85357ebc6b2d3f68bc71bd0d9a3daf97b13f3911c6dc5b15141a8ca94ad610d4166c1f4f307f2ceffe33b1b081551ad5caa527d59285400b0da4b41a0ea0786","eventDescriptorTLV":"fdd80a85000a010004a9670e21aeb8f0281980657c6c6cc9e94804b9cbff650cb5a9a1b20b8556cbcd6de7afbb816d2ff9be8ff250d8075a68300e51569c96ab998fec90a6e0bd1a6cf0eb12e0c33dae1c44281eb9057910d6a40cf76987e721d622dae13e79ce160739bf4ff7c0ce8408aa23dd619c5aa5e25d078d64cd198830e8e70436f5611b1e","eventTLV":"fdd822bff8d695520151bc9fbd129be6231f46b0e137b26d8ff91910c0cb6d07f6924968657131d1fdd80a85000a010004a9670e21aeb8f0281980657c6c6cc9e94804b9cbff650cb5a9a1b20b8556cbcd6de7afbb816d2ff9be8ff250d8075a68300e51569c96ab998fec90a6e0bd1a6cf0eb12e0c33dae1c44281eb9057910d6a40cf76987e721d622dae13e79ce160739bf4ff7c0ce8408aa23dd619c5aa5e25d078d64cd198830e8e70436f5611b1e6578616d706c654c6172676552616e676531","announcementTLV":"fdd824fd0103c85357ebc6b2d3f68bc71bd0d9a3daf97b13f3911c6dc5b15141a8ca94ad610d4166c1f4f307f2ceffe33b1b081551ad5caa527d59285400b0da4b41a0ea0786fdd822bff8d695520151bc9fbd129be6231f46b0e137b26d8ff91910c0cb6d07f6924968657131d1fdd80a85000a010004a9670e21aeb8f0281980657c6c6cc9e94804b9cbff650cb5a9a1b20b8556cbcd6de7afbb816d2ff9be8ff250d8075a68300e51569c96ab998fec90a6e0bd1a6cf0eb12e0c33dae1c44281eb9057910d6a40cf76987e721d622dae13e79ce160739bf4ff7c0ce8408aa23dd619c5aa5e25d078d64cd198830e8e70436f5611b1e6578616d706c654c6172676552616e676531","attestations":null,"signatures":null,"outcomes":[["0","1","2","3","4","5","6","7","8","9"],["0","1","2","3","4","5","6","7","8","9"],["0","1","2","3","4","5","6","7","8","9"],["+","-"]]},"error":null} + +$ curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "signdigits", "params": ["fdd80a85000a010004a9670e21aeb8f0281980657c6c6cc9e94804b9cbff650cb5a9a1b20b8556cbcd6de7afbb816d2ff9be8ff250d8075a68300e51569c96ab998fec90a6e0bd1a6cf0eb12e0c33dae1c44281eb9057910d6a40cf76987e721d622dae13e79ce160739bf4ff7c0ce8408aa23dd619c5aa5e25d078d64cd198830e8e70436f5611b1e", 123]}' -H "Content-Type: application/json" http://127.0.0.1:9999/ +{"result":["a9670e21aeb8f0281980657c6c6cc9e94804b9cbff650cb5a9a1b20b8556cbcde5d22ede02c2e6e5df6a12367082a5cba26d37d22369dd8659388738b8ed7109","6de7afbb816d2ff9be8ff250d8075a68300e51569c96ab998fec90a6e0bd1a6c43224409c57c70319b8167db086e923344f867033551a3055d7e65563db295f2","f0eb12e0c33dae1c44281eb9057910d6a40cf76987e721d622dae13e79ce160796046de397a4d2f17c01c42815005f67d801b2c0e01d1923d4bbb13559cccf4a","39bf4ff7c0ce8408aa23dd619c5aa5e25d078d64cd198830e8e70436f5611b1e0346f7853fd0d4ab792896f46714b8964875ddf0db22c2b30f4cf4ad5b0052c9"],"error":null} ``` diff --git a/testkit/src/main/scala/org/bitcoins/testkit/BitcoinSTestAppConfig.scala b/testkit/src/main/scala/org/bitcoins/testkit/BitcoinSTestAppConfig.scala index bf00c58a8f..91494ad992 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/BitcoinSTestAppConfig.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/BitcoinSTestAppConfig.scala @@ -3,7 +3,7 @@ package org.bitcoins.testkit import java.nio.file._ import com.typesafe.config._ -import org.bitcoins.dlc.oracle.DLCOracleAppConfig +import org.bitcoins.dlc.oracle.config.DLCOracleAppConfig import org.bitcoins.server.BitcoinSAppConfig import org.bitcoins.testkit.keymanager.KeyManagerTestUtil import org.bitcoins.testkit.util.FileUtil diff --git a/testkit/src/main/scala/org/bitcoins/testkit/fixtures/DLCOracleDAOFixture.scala b/testkit/src/main/scala/org/bitcoins/testkit/fixtures/DLCOracleDAOFixture.scala index 8ee384e7ec..d0d72754b3 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/fixtures/DLCOracleDAOFixture.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/fixtures/DLCOracleDAOFixture.scala @@ -1,6 +1,6 @@ package org.bitcoins.testkit.fixtures -import org.bitcoins.dlc.oracle.DLCOracleAppConfig +import org.bitcoins.dlc.oracle.config.DLCOracleAppConfig import org.bitcoins.dlc.oracle.storage._ import org.bitcoins.testkit.keymanager.KeyManagerTestUtil.bip39PasswordOpt import org.bitcoins.testkit.{BitcoinSTestAppConfig, EmbeddedPg}