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
This commit is contained in:
Ben Carman 2020-11-10 06:08:43 -06:00 committed by GitHub
parent c167bc04a0
commit 7ac9cd1525
34 changed files with 2477 additions and 389 deletions

View file

@ -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")
}
}
}

View file

@ -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)

View file

@ -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

View file

@ -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
}

View file

@ -0,0 +1,73 @@
<configuration scan="true" scanPeriod="15 seconds" >
<appender name="STDOUT" target="System.out" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%date{yyyy-MM-dd'T'HH:mm:ss,SSXXX, UTC}UTC %level [%logger{0}] %msg%n</pattern>
</encoder>
</appender>
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>8192</queueSize>
<neverBlock>true</neverBlock>
<appender-ref ref="STDOUT" />
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${bitcoins.log.location}/bitcoin-s.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- hourly rollover -->
<fileNamePattern>${bitcoins.log.location}/logs/bitcoin-s-%d{yyyy-MM-dd_HH}.%i.log</fileNamePattern>
<!-- each file should be at most 100MB, keep 2 days of history, and at most 2GB in the archive -->
<maxFileSize>100MB</maxFileSize>
<maxHistory>48</maxHistory>
<totalSizeCap>2GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%date{yyyy-MM-dd'T'HH:mm:ss,SSXXX, UTC}UTC %level [%logger{0}] %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="ASYNC"/>
<appender-ref ref="FILE"/>
</root>
<!-- ╔═══════════════════════╗ -->
<!-- ║ Bitcoin-S logging ║-->
<!-- ╚═══════════════════════╝ -->
<!-- ╔═══════════════════╗ -->
<!-- ║ Configuration ║ -->
<!-- ╚═══════════════════╝ -->
<!-- inspect resolved DB connection -->
<logger name="org.bitcoins.db.SafeDatabase" level="WARN"/>
<logger name="org.bitcoins.dlc.oracle.config" level="WARN"/>
<logger name="org.bitcoins.dlc.oracle.storage" level="WARN"/>
<!-- ╔═══════════════════════════╗ -->
<!-- ║ Bitcoin-S logging end ║-->
<!-- ╚═══════════════════════════╝ -->
<!-- ╔═════════════════════════╗ -->
<!-- ║ External libraries ║ -->
<!-- ╚═════════════════════════╝ -->
<!-- Disable slick logging in server -->
<logger name="slick" level="OFF"/>
<logger name="com.zaxxer" level="OFF"/>
<!-- Get rid of messages like this:
Connection attempt failed. Backing off new connection
attempts for at least 800 milliseconds. -->
<logger name="akka.http.impl.engine.client.PoolGateway" level="OFF"/>
<!-- get rid of "Slf4jLogger started" messages -->
<logger name="akka.event.slf4j.Slf4jLogger" level="OFF"/>
<!-- get rid of "Running CoordinatedShutdown Phase" messages -->
<logger name="akka.actor.slf4j.CoordinatedShutdown" level="OFF"/>
</configuration>

View file

@ -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)
}
}

View file

@ -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

View file

@ -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)
}
}
}

View file

@ -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)
}
}

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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()

View file

@ -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)
}
}

View file

@ -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]]

View file

@ -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)
}
}
}

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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;

View file

@ -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`;

View file

@ -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)
}

View file

@ -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 {

View file

@ -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
)
}
}
}

View file

@ -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
}
}

View file

@ -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",

View file

@ -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))
}

View file

@ -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,

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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}
```

View file

@ -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

View file

@ -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}