mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-03 18:47:38 +01:00
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:
parent
c167bc04a0
commit
7ac9cd1525
34 changed files with 2477 additions and 389 deletions
|
@ -1,27 +1,80 @@
|
||||||
package org.bitcoins.commons.jsonmodels.dlc
|
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 {
|
sealed abstract class SigningVersion {
|
||||||
def nonceTag: String
|
|
||||||
def announcementTag: String
|
/** Calculates the tweak for the oracle's pre-committed nonce */
|
||||||
def outcomeTag: String
|
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] {
|
object SigningVersion extends StringFactory[SigningVersion] {
|
||||||
|
|
||||||
/** Initial signing version that was created, not a part of any spec */
|
/** Initial signing version that was created, not a part of any spec */
|
||||||
final case object Mock extends SigningVersion {
|
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] = {
|
override def fromStringOpt(str: String): Option[SigningVersion] = {
|
||||||
all.find(state => str.toLowerCase() == state.toString.toLowerCase)
|
all.find(state => str.toLowerCase() == state.toString.toLowerCase)
|
||||||
|
@ -31,7 +84,7 @@ object SigningVersion extends StringFactory[SigningVersion] {
|
||||||
fromStringOpt(string) match {
|
fromStringOpt(string) match {
|
||||||
case Some(state) => state
|
case Some(state) => state
|
||||||
case None =>
|
case None =>
|
||||||
sys.error(s"Could not find signing version for string=${string}")
|
sys.error(s"Could not find signing version for string=$string")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import org.bitcoins.core.api.wallet.CoinSelectionAlgo
|
||||||
import org.bitcoins.core.crypto.ExtPublicKey
|
import org.bitcoins.core.crypto.ExtPublicKey
|
||||||
import org.bitcoins.core.currency.{Bitcoins, Satoshis}
|
import org.bitcoins.core.currency.{Bitcoins, Satoshis}
|
||||||
import org.bitcoins.core.number.UInt32
|
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.transaction.{Transaction, TransactionOutPoint}
|
||||||
import org.bitcoins.core.protocol.{BitcoinAddress, BlockStamp}
|
import org.bitcoins.core.protocol.{BitcoinAddress, BlockStamp}
|
||||||
import org.bitcoins.core.psbt.InputPSBTRecord.PartialSignature
|
import org.bitcoins.core.psbt.InputPSBTRecord.PartialSignature
|
||||||
|
@ -34,6 +35,25 @@ object Picklers {
|
||||||
implicit val schnorrNoncePickler: ReadWriter[SchnorrNonce] =
|
implicit val schnorrNoncePickler: ReadWriter[SchnorrNonce] =
|
||||||
readwriter[String].bimap(_.hex, SchnorrNonce.fromHex)
|
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] =
|
implicit val instantPickler: ReadWriter[Instant] =
|
||||||
readwriter[Long].bimap(_.getEpochSecond, Instant.ofEpochSecond)
|
readwriter[Long].bimap(_.getEpochSecond, Instant.ofEpochSecond)
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import org.bitcoins.core.currency._
|
||||||
import org.bitcoins.core.number.UInt32
|
import org.bitcoins.core.number.UInt32
|
||||||
import org.bitcoins.core.protocol.BlockStamp.BlockTime
|
import org.bitcoins.core.protocol.BlockStamp.BlockTime
|
||||||
import org.bitcoins.core.protocol._
|
import org.bitcoins.core.protocol._
|
||||||
|
import org.bitcoins.core.protocol.tlv._
|
||||||
import org.bitcoins.core.protocol.transaction.{Transaction, TransactionOutPoint}
|
import org.bitcoins.core.protocol.transaction.{Transaction, TransactionOutPoint}
|
||||||
import org.bitcoins.core.psbt.InputPSBTRecord.PartialSignature
|
import org.bitcoins.core.psbt.InputPSBTRecord.PartialSignature
|
||||||
import org.bitcoins.core.psbt.PSBT
|
import org.bitcoins.core.psbt.PSBT
|
||||||
|
@ -50,6 +51,46 @@ object CliReaders {
|
||||||
override def reads: String => SchnorrNonce = SchnorrNonce.fromHex
|
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] =
|
implicit val instantReads: Read[Instant] =
|
||||||
new Read[Instant] {
|
new Read[Instant] {
|
||||||
override def arity: Int = 1
|
override def arity: Int = 1
|
||||||
|
|
|
@ -11,6 +11,7 @@ import org.bitcoins.core.api.wallet.CoinSelectionAlgo
|
||||||
import org.bitcoins.core.config.NetworkParameters
|
import org.bitcoins.core.config.NetworkParameters
|
||||||
import org.bitcoins.core.currency._
|
import org.bitcoins.core.currency._
|
||||||
import org.bitcoins.core.number.UInt32
|
import org.bitcoins.core.number.UInt32
|
||||||
|
import org.bitcoins.core.protocol.tlv._
|
||||||
import org.bitcoins.core.protocol.transaction.{
|
import org.bitcoins.core.protocol.transaction.{
|
||||||
EmptyTransaction,
|
EmptyTransaction,
|
||||||
Transaction,
|
Transaction,
|
||||||
|
@ -20,11 +21,7 @@ import org.bitcoins.core.protocol.{BitcoinAddress, BlockStamp}
|
||||||
import org.bitcoins.core.psbt.PSBT
|
import org.bitcoins.core.psbt.PSBT
|
||||||
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
|
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
|
||||||
import org.bitcoins.core.wallet.utxo.AddressLabelTag
|
import org.bitcoins.core.wallet.utxo.AddressLabelTag
|
||||||
import org.bitcoins.crypto.{
|
import org.bitcoins.crypto.{SchnorrDigitalSignature, Sha256DigestBE}
|
||||||
SchnorrDigitalSignature,
|
|
||||||
SchnorrNonce,
|
|
||||||
Sha256DigestBE
|
|
||||||
}
|
|
||||||
import scopt.OParser
|
import scopt.OParser
|
||||||
import ujson._
|
import ujson._
|
||||||
import upickle.{default => up}
|
import upickle.{default => up}
|
||||||
|
@ -1025,14 +1022,14 @@ object ConsoleCli {
|
||||||
.text(s"Get oracle's staking address"),
|
.text(s"Get oracle's staking address"),
|
||||||
cmd("listevents")
|
cmd("listevents")
|
||||||
.action((_, conf) => conf.copy(command = ListEvents))
|
.action((_, conf) => conf.copy(command = ListEvents))
|
||||||
.text(s"Lists all event nonces"),
|
.text(s"Lists all oracle event TLVs"),
|
||||||
cmd("createevent")
|
cmd("createevent")
|
||||||
.action((_, conf) =>
|
.action((_, conf) =>
|
||||||
conf.copy(command = CreateEvent("", Instant.MIN, Seq.empty)))
|
conf.copy(command = CreateEvent("", Instant.MIN, Seq.empty)))
|
||||||
.text("Registers an oracle event")
|
.text("Registers an oracle event")
|
||||||
.children(
|
.children(
|
||||||
arg[String]("label")
|
arg[String]("name")
|
||||||
.text("Label for this event")
|
.text("Name for this event")
|
||||||
.required()
|
.required()
|
||||||
.action((label, conf) =>
|
.action((label, conf) =>
|
||||||
conf.copy(command = conf.command match {
|
conf.copy(command = conf.command match {
|
||||||
|
@ -1059,17 +1056,159 @@ object ConsoleCli {
|
||||||
case other => other
|
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")
|
cmd("getevent")
|
||||||
.action((_, conf) => conf.copy(command = GetEvent(null)))
|
.action((_, conf) => conf.copy(command = GetEvent(null)))
|
||||||
.text("Get an event's details")
|
.text("Get an event's details")
|
||||||
.children(
|
.children(
|
||||||
arg[SchnorrNonce]("nonce")
|
arg[OracleEventV0TLV]("event")
|
||||||
.text("Nonce associated with the event")
|
.text("The event's oracle event tlv")
|
||||||
.required()
|
.required()
|
||||||
.action((nonce, conf) =>
|
.action((oracleEvent, conf) =>
|
||||||
conf.copy(command = conf.command match {
|
conf.copy(command = conf.command match {
|
||||||
case getEvent: GetEvent =>
|
case getEvent: GetEvent =>
|
||||||
getEvent.copy(nonce = nonce)
|
getEvent.copy(oracleEventV0TLV = oracleEvent)
|
||||||
case other => other
|
case other => other
|
||||||
}))
|
}))
|
||||||
),
|
),
|
||||||
|
@ -1077,13 +1216,13 @@ object ConsoleCli {
|
||||||
.action((_, conf) => conf.copy(command = SignEvent(null, "")))
|
.action((_, conf) => conf.copy(command = SignEvent(null, "")))
|
||||||
.text("Signs an event")
|
.text("Signs an event")
|
||||||
.children(
|
.children(
|
||||||
arg[SchnorrNonce]("nonce")
|
arg[OracleEventV0TLV]("event")
|
||||||
.text("Nonce associated with the event to sign")
|
.text("The event's oracle event tlv")
|
||||||
.required()
|
.required()
|
||||||
.action((nonce, conf) =>
|
.action((event, conf) =>
|
||||||
conf.copy(command = conf.command match {
|
conf.copy(command = conf.command match {
|
||||||
case signEvent: SignEvent =>
|
case signEvent: SignEvent =>
|
||||||
signEvent.copy(nonce = nonce)
|
signEvent.copy(oracleEventV0TLV = event)
|
||||||
case other => other
|
case other => other
|
||||||
})),
|
})),
|
||||||
arg[String]("outcome")
|
arg[String]("outcome")
|
||||||
|
@ -1096,17 +1235,63 @@ object ConsoleCli {
|
||||||
case other => other
|
case other => other
|
||||||
}))
|
}))
|
||||||
),
|
),
|
||||||
cmd("getsignature")
|
cmd("signforrange")
|
||||||
.action((_, conf) => conf.copy(command = GetSignature(null)))
|
.action((_, conf) => conf.copy(command = SignForRange(null, 0)))
|
||||||
.text("Get the signature from a signed event")
|
.text("Signs a ranged event")
|
||||||
.children(
|
.children(
|
||||||
arg[SchnorrNonce]("nonce")
|
arg[OracleEventV0TLV]("event")
|
||||||
.text("Nonce associated with the signed event")
|
.text("The event's oracle event tlv")
|
||||||
.required()
|
.required()
|
||||||
.action((nonce, conf) =>
|
.action((event, conf) =>
|
||||||
conf.copy(command = conf.command match {
|
conf.copy(command = conf.command match {
|
||||||
case getSignature: GetSignature =>
|
case signRange: SignForRange =>
|
||||||
getSignature.copy(nonce = nonce)
|
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
|
case other => other
|
||||||
}))
|
}))
|
||||||
),
|
),
|
||||||
|
@ -1329,16 +1514,55 @@ object ConsoleCli {
|
||||||
RequestParam("getstakingaddress")
|
RequestParam("getstakingaddress")
|
||||||
case ListEvents =>
|
case ListEvents =>
|
||||||
RequestParam("listevents")
|
RequestParam("listevents")
|
||||||
case GetEvent(nonce) =>
|
case GetEvent(tlv) =>
|
||||||
RequestParam("getevent", Seq(up.writeJs(nonce)))
|
RequestParam("getevent", Seq(up.writeJs(tlv)))
|
||||||
case CreateEvent(label, time, outcomes) =>
|
case CreateEvent(label, time, outcomes) =>
|
||||||
RequestParam(
|
RequestParam(
|
||||||
"createevent",
|
"createevent",
|
||||||
Seq(up.writeJs(label), up.writeJs(time), up.writeJs(outcomes)))
|
Seq(up.writeJs(label), up.writeJs(time), up.writeJs(outcomes)))
|
||||||
case SignEvent(nonce, outcome) =>
|
case CreateRangedEvent(eventName,
|
||||||
RequestParam("signevent", Seq(up.writeJs(nonce), up.writeJs(outcome)))
|
time,
|
||||||
case GetSignature(nonce) =>
|
start,
|
||||||
RequestParam("getsignature", Seq(up.writeJs(nonce)))
|
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 => ???
|
case NoCommand => ???
|
||||||
}
|
}
|
||||||
|
@ -1616,13 +1840,43 @@ object CliCommand {
|
||||||
case object GetStakingAddress extends CliCommand
|
case object GetStakingAddress extends CliCommand
|
||||||
case object ListEvents extends CliCommand
|
case object ListEvents extends CliCommand
|
||||||
|
|
||||||
case class GetEvent(nonce: SchnorrNonce) extends CliCommand
|
case class GetEvent(oracleEventV0TLV: OracleEventV0TLV) extends CliCommand
|
||||||
|
|
||||||
case class CreateEvent(
|
case class CreateEvent(
|
||||||
label: String,
|
label: String,
|
||||||
maturationTime: Instant,
|
maturationTime: Instant,
|
||||||
outcomes: Seq[String])
|
outcomes: Seq[String])
|
||||||
extends CliCommand
|
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
|
||||||
}
|
}
|
||||||
|
|
73
app/oracle-server/src/main/resources/logback.xml
Normal file
73
app/oracle-server/src/main/resources/logback.xml
Normal 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>
|
|
@ -3,6 +3,8 @@ package org.bitcoins.oracle.server
|
||||||
import akka.actor.ActorSystem
|
import akka.actor.ActorSystem
|
||||||
import akka.http.scaladsl.server.Directives._
|
import akka.http.scaladsl.server.Directives._
|
||||||
import akka.http.scaladsl.server._
|
import akka.http.scaladsl.server._
|
||||||
|
import org.bitcoins.core.number._
|
||||||
|
import org.bitcoins.core.protocol.tlv._
|
||||||
import org.bitcoins.dlc.oracle._
|
import org.bitcoins.dlc.oracle._
|
||||||
import org.bitcoins.server._
|
import org.bitcoins.server._
|
||||||
import ujson._
|
import ujson._
|
||||||
|
@ -13,6 +15,11 @@ case class OracleRoutes(oracle: DLCOracle)(implicit system: ActorSystem)
|
||||||
extends ServerRoute {
|
extends ServerRoute {
|
||||||
import system.dispatcher
|
import system.dispatcher
|
||||||
|
|
||||||
|
def getDescriptor(
|
||||||
|
announcementTLV: OracleAnnouncementTLV): EventDescriptorTLV = {
|
||||||
|
announcementTLV.eventTLV.eventDescriptor
|
||||||
|
}
|
||||||
|
|
||||||
def handleCommand: PartialFunction[ServerCommand, StandardRoute] = {
|
def handleCommand: PartialFunction[ServerCommand, StandardRoute] = {
|
||||||
case ServerCommand("getpublickey", _) =>
|
case ServerCommand("getpublickey", _) =>
|
||||||
complete {
|
complete {
|
||||||
|
@ -29,9 +36,9 @@ case class OracleRoutes(oracle: DLCOracle)(implicit system: ActorSystem)
|
||||||
|
|
||||||
case ServerCommand("listevents", _) =>
|
case ServerCommand("listevents", _) =>
|
||||||
complete {
|
complete {
|
||||||
oracle.listEventDbs().map { eventDbs =>
|
oracle.listEvents().map { events =>
|
||||||
val nonceStrs = eventDbs.map(_.nonce.hex)
|
val strs = events.map(_.eventDescriptorTLV.hex)
|
||||||
val json = Arr.from(nonceStrs)
|
val json = Arr.from(strs)
|
||||||
|
|
||||||
Server.httpSuccess(json)
|
Server.httpSuccess(json)
|
||||||
}
|
}
|
||||||
|
@ -43,41 +50,130 @@ case class OracleRoutes(oracle: DLCOracle)(implicit system: ActorSystem)
|
||||||
reject(ValidationRejection("failure", Some(exception)))
|
reject(ValidationRejection("failure", Some(exception)))
|
||||||
case Success(CreateEvent(label, maturationTime, outcomes)) =>
|
case Success(CreateEvent(label, maturationTime, outcomes)) =>
|
||||||
complete {
|
complete {
|
||||||
oracle.createNewEvent(label, maturationTime, outcomes).map {
|
oracle.createNewEnumEvent(label, maturationTime, outcomes).map {
|
||||||
eventDb =>
|
announcementTLV =>
|
||||||
Server.httpSuccess(eventDb.nonce.hex)
|
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) =>
|
case ServerCommand("getevent", arr) =>
|
||||||
GetEvent.fromJsArr(arr) match {
|
GetEvent.fromJsArr(arr) match {
|
||||||
case Failure(exception) =>
|
case Failure(exception) =>
|
||||||
reject(ValidationRejection("failure", Some(exception)))
|
reject(ValidationRejection("failure", Some(exception)))
|
||||||
case Success(GetEvent(label)) =>
|
case Success(GetEvent(descriptor)) =>
|
||||||
complete {
|
complete {
|
||||||
oracle.getEvent(label).map {
|
oracle.findEvent(descriptor).map {
|
||||||
case Some(event: Event) =>
|
case Some(event: OracleEvent) =>
|
||||||
val outcomesJson = event.outcomes.map(Str)
|
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 {
|
val (attestationJson, signatureJson) = event match {
|
||||||
case completedEvent: CompletedEvent =>
|
case completedEvent: CompletedOracleEvent =>
|
||||||
(Str(completedEvent.attestation.hex),
|
(Arr.from(completedEvent.attestations.map(a => Str(a.hex))),
|
||||||
Str(completedEvent.signature.hex))
|
Arr.from(completedEvent.signatures.map(s => Str(s.hex))))
|
||||||
case _: PendingEvent =>
|
case _: PendingOracleEvent =>
|
||||||
(ujson.Null, ujson.Null)
|
(ujson.Null, ujson.Null)
|
||||||
}
|
}
|
||||||
|
|
||||||
val json = Obj(
|
val json = Obj(
|
||||||
"nonce" -> Str(event.nonce.hex),
|
"nonces" -> event.nonces.map(n => Str(n.hex)),
|
||||||
"eventName" -> Str(event.eventName),
|
"eventName" -> Str(event.eventName),
|
||||||
"numOutcomes" -> Num(event.numOutcomes.toDouble),
|
|
||||||
"signingVersion" -> Str(event.signingVersion.toString),
|
"signingVersion" -> Str(event.signingVersion.toString),
|
||||||
"maturationTime" -> Str(event.maturationTime.toString),
|
"maturationTime" -> Str(event.maturationTime.toString),
|
||||||
"announcementSignature" -> Str(
|
"announcementSignature" -> Str(
|
||||||
event.announcementSignature.hex),
|
event.announcementSignature.hex),
|
||||||
"attestation" -> attestationJson,
|
"eventDescriptorTLV" -> Str(event.eventDescriptorTLV.hex),
|
||||||
"signature" -> signatureJson,
|
"eventTLV" -> Str(event.eventTLV.hex),
|
||||||
|
"announcementTLV" -> Str(event.announcementTLV.hex),
|
||||||
|
"attestations" -> attestationJson,
|
||||||
|
"signatures" -> signatureJson,
|
||||||
"outcomes" -> outcomesJson
|
"outcomes" -> outcomesJson
|
||||||
)
|
)
|
||||||
Server.httpSuccess(json)
|
Server.httpSuccess(json)
|
||||||
|
@ -91,24 +187,56 @@ case class OracleRoutes(oracle: DLCOracle)(implicit system: ActorSystem)
|
||||||
SignEvent.fromJsArr(arr) match {
|
SignEvent.fromJsArr(arr) match {
|
||||||
case Failure(exception) =>
|
case Failure(exception) =>
|
||||||
reject(ValidationRejection("failure", Some(exception)))
|
reject(ValidationRejection("failure", Some(exception)))
|
||||||
case Success(SignEvent(nonce, outcome)) =>
|
case Success(SignEvent(oracleEventTLV, outcome)) =>
|
||||||
complete {
|
complete {
|
||||||
oracle.signEvent(nonce, outcome).map { eventDb =>
|
oracle.signEvent(oracleEventTLV, EnumAttestation(outcome)).map {
|
||||||
Server.httpSuccess(eventDb.sigOpt.get.hex)
|
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 {
|
GetEvent.fromJsArr(arr) match {
|
||||||
case Failure(exception) =>
|
case Failure(exception) =>
|
||||||
reject(ValidationRejection("failure", Some(exception)))
|
reject(ValidationRejection("failure", Some(exception)))
|
||||||
case Success(GetEvent(nonce)) =>
|
case Success(GetEvent(oracleEventTLV)) =>
|
||||||
complete {
|
complete {
|
||||||
oracle.getEvent(nonce).map {
|
oracle.findEvent(oracleEventTLV).map {
|
||||||
case Some(completed: CompletedEvent) =>
|
case Some(completed: CompletedOracleEvent) =>
|
||||||
Server.httpSuccess(completed.signature.hex)
|
Server.httpSuccess(completed.signatures.map(_.hex))
|
||||||
case None | Some(_: PendingEvent) =>
|
case None | Some(_: PendingOracleEvent) =>
|
||||||
Server.httpSuccess(ujson.Null)
|
Server.httpSuccess(ujson.Null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package org.bitcoins.oracle.server
|
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 org.bitcoins.server.{BitcoinSRunner, Server}
|
||||||
|
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
|
|
|
@ -6,12 +6,12 @@ import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.LockUnspentOutputParamet
|
||||||
import org.bitcoins.core.api.wallet.CoinSelectionAlgo
|
import org.bitcoins.core.api.wallet.CoinSelectionAlgo
|
||||||
import org.bitcoins.core.currency.{Bitcoins, Satoshis}
|
import org.bitcoins.core.currency.{Bitcoins, Satoshis}
|
||||||
import org.bitcoins.core.protocol.BlockStamp.BlockHeight
|
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.transaction.{Transaction, TransactionOutPoint}
|
||||||
import org.bitcoins.core.protocol.{BitcoinAddress, BlockStamp}
|
import org.bitcoins.core.protocol.{BitcoinAddress, BlockStamp}
|
||||||
import org.bitcoins.core.psbt.PSBT
|
import org.bitcoins.core.psbt.PSBT
|
||||||
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
|
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
|
||||||
import org.bitcoins.core.wallet.utxo.AddressLabelTag
|
import org.bitcoins.core.wallet.utxo.AddressLabelTag
|
||||||
import org.bitcoins.crypto.SchnorrNonce
|
|
||||||
import ujson._
|
import ujson._
|
||||||
import upickle.default._
|
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 {
|
object SignEvent extends ServerJsonModels {
|
||||||
|
|
||||||
def fromJsArr(jsArr: ujson.Arr): Try[SignEvent] = {
|
def fromJsArr(jsArr: ujson.Arr): Try[SignEvent] = {
|
||||||
jsArr.arr.toList match {
|
jsArr.arr.toList match {
|
||||||
case nonceJs :: outcomeJs :: Nil =>
|
case tlvJs :: outcomeJs :: Nil =>
|
||||||
Try {
|
Try {
|
||||||
val nonce = SchnorrNonce(nonceJs.str)
|
val oracleEventTLV = OracleEventV0TLV(tlvJs.str)
|
||||||
val outcome = outcomeJs.str
|
val outcome = outcomeJs.str
|
||||||
|
|
||||||
SignEvent(nonce, outcome)
|
SignEvent(oracleEventTLV, outcome)
|
||||||
}
|
}
|
||||||
case Nil =>
|
case Nil =>
|
||||||
Failure(
|
Failure(
|
||||||
new IllegalArgumentException("Missing nonce and outcome arguments"))
|
new IllegalArgumentException(
|
||||||
|
"Missing oracle event tlv and outcome arguments"))
|
||||||
case other =>
|
case other =>
|
||||||
Failure(
|
Failure(
|
||||||
new IllegalArgumentException(
|
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 {
|
object GetEvent extends ServerJsonModels {
|
||||||
|
|
||||||
|
@ -600,9 +750,9 @@ object GetEvent extends ServerJsonModels {
|
||||||
require(jsArr.arr.size == 1,
|
require(jsArr.arr.size == 1,
|
||||||
s"Bad number of arguments: ${jsArr.arr.size}. Expected: 1")
|
s"Bad number of arguments: ${jsArr.arr.size}. Expected: 1")
|
||||||
Try {
|
Try {
|
||||||
val nonce = SchnorrNonce(jsArr.arr.head.str)
|
val oracleEventTLV = OracleEventV0TLV(jsArr.arr.head.str)
|
||||||
|
|
||||||
GetEvent(nonce)
|
GetEvent(oracleEventTLV)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,13 @@ class NumberUtilTest extends BitcoinSUnitTest {
|
||||||
|
|
||||||
behavior of "NumberUtil"
|
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 {
|
it must "expand nbits to 0 difficulty threshold" in {
|
||||||
|
|
||||||
//from the examples table on bitcoin developer reference site
|
//from the examples table on bitcoin developer reference site
|
||||||
|
@ -192,10 +199,35 @@ class NumberUtilTest extends BitcoinSUnitTest {
|
||||||
expanded18.isOverflow must be(true)
|
expanded18.isOverflow must be(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private def runTest(
|
behavior of "NumberUtil.decompose"
|
||||||
nBits: UInt32,
|
|
||||||
expected: BlockHeader.TargetDifficultyHelper): Assertion = {
|
it must "correctly do digit decomposition in base 10" in {
|
||||||
val expansion = NumberUtil.targetExpansion(nBits)
|
val num0 = 987
|
||||||
assert(expansion == expected)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -247,8 +247,8 @@ object EventDescriptorTLV extends TLVParentFactory[EventDescriptorTLV] {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes an event over an enumerated set of outcomes
|
* Describes an event over an enumerated set of outcomes
|
||||||
* @param outcomeStrs The set of possible outcomes
|
* @param outcomes The set of possible outcomes
|
||||||
* @see https://github.com/discreetlogcontracts/dlcspecs/blob/540c23a3e89c886814145cf16edfd48421d0175b/Oracle.md#simple-enumeration
|
* @see https://github.com/discreetlogcontracts/dlcspecs/blob/master/Oracle.md#simple-enumeration
|
||||||
*/
|
*/
|
||||||
case class EnumEventDescriptorV0TLV(outcomes: Vector[String])
|
case class EnumEventDescriptorV0TLV(outcomes: Vector[String])
|
||||||
extends EventDescriptorTLV {
|
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 */
|
/** The minimum valid value in the oracle can sign */
|
||||||
def min: Vector[String]
|
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(
|
case class OracleEventV0TLV(
|
||||||
nonces: Vector[SchnorrNonce],
|
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(
|
case class OracleAnnouncementV0TLV(
|
||||||
announcementSignature: SchnorrDigitalSignature,
|
announcementSignature: SchnorrDigitalSignature,
|
||||||
|
|
|
@ -336,6 +336,22 @@ sealed abstract class NumberUtil extends BitcoinSLogger {
|
||||||
def posInt: Int = {
|
def posInt: Int = {
|
||||||
Math.abs(scala.util.Random.nextInt())
|
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
|
object NumberUtil extends NumberUtil
|
||||||
|
|
|
@ -2,7 +2,7 @@ package org.bitcoins.db
|
||||||
|
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
import org.bitcoins.core.config._
|
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.server.BitcoinSAppConfig
|
||||||
import org.bitcoins.testkit.BitcoinSTestAppConfig
|
import org.bitcoins.testkit.BitcoinSTestAppConfig
|
||||||
import org.bitcoins.testkit.util.BitcoinSAsyncTest
|
import org.bitcoins.testkit.util.BitcoinSAsyncTest
|
||||||
|
|
|
@ -4,7 +4,7 @@ import com.typesafe.config.Config
|
||||||
import org.bitcoins.chain.config.ChainAppConfig
|
import org.bitcoins.chain.config.ChainAppConfig
|
||||||
import org.bitcoins.chain.db.ChainDbManagement
|
import org.bitcoins.chain.db.ChainDbManagement
|
||||||
import org.bitcoins.db.DatabaseDriver._
|
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.config.NodeAppConfig
|
||||||
import org.bitcoins.node.db.NodeDbManagement
|
import org.bitcoins.node.db.NodeDbManagement
|
||||||
import org.bitcoins.testkit.BitcoinSTestAppConfig.ProjectType
|
import org.bitcoins.testkit.BitcoinSTestAppConfig.ProjectType
|
||||||
|
@ -122,14 +122,14 @@ class DbManagementTest extends BitcoinSAsyncTest with EmbeddedPg {
|
||||||
val result = oracleAppConfig.migrate()
|
val result = oracleAppConfig.migrate()
|
||||||
oracleAppConfig.driver match {
|
oracleAppConfig.driver match {
|
||||||
case SQLite =>
|
case SQLite =>
|
||||||
val expected = 1
|
val expected = 2
|
||||||
assert(result == expected)
|
assert(result == expected)
|
||||||
val flywayInfo = oracleAppConfig.info()
|
val flywayInfo = oracleAppConfig.info()
|
||||||
|
|
||||||
assert(flywayInfo.applied().length == expected)
|
assert(flywayInfo.applied().length == expected)
|
||||||
assert(flywayInfo.pending().length == 0)
|
assert(flywayInfo.pending().length == 0)
|
||||||
case PostgreSQL =>
|
case PostgreSQL =>
|
||||||
val expected = 1
|
val expected = 2
|
||||||
assert(result == expected)
|
assert(result == expected)
|
||||||
val flywayInfo = oracleAppConfig.info()
|
val flywayInfo = oracleAppConfig.info()
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import org.bitcoins.core.gcs.FilterType
|
||||||
import org.bitcoins.core.hd._
|
import org.bitcoins.core.hd._
|
||||||
import org.bitcoins.core.number.{Int32, UInt32, UInt64}
|
import org.bitcoins.core.number.{Int32, UInt32, UInt64}
|
||||||
import org.bitcoins.core.protocol.script.{ScriptPubKey, ScriptWitness}
|
import org.bitcoins.core.protocol.script.{ScriptPubKey, ScriptWitness}
|
||||||
|
import org.bitcoins.core.protocol.tlv._
|
||||||
import org.bitcoins.core.protocol.transaction.{
|
import org.bitcoins.core.protocol.transaction.{
|
||||||
Transaction,
|
Transaction,
|
||||||
TransactionOutPoint,
|
TransactionOutPoint,
|
||||||
|
@ -310,4 +311,10 @@ class DbCommonsColumnMappers(val profile: JdbcProfile) {
|
||||||
MappedColumnType.base[WalletStateDescriptor, String](
|
MappedColumnType.base[WalletStateDescriptor, String](
|
||||||
_.toString,
|
_.toString,
|
||||||
WalletStateDescriptor.fromString)
|
WalletStateDescriptor.fromString)
|
||||||
|
|
||||||
|
implicit val eventDescriptorTLVMapper: BaseColumnType[EventDescriptorTLV] = {
|
||||||
|
MappedColumnType.base[EventDescriptorTLV, String](
|
||||||
|
_.hex,
|
||||||
|
EventDescriptorTLV.fromHex)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.bitcoins.db
|
package org.bitcoins.db
|
||||||
|
|
||||||
import org.bitcoins.core.util.{BitcoinSLogger, FutureUtil}
|
import org.bitcoins.core.util.{BitcoinSLogger, FutureUtil}
|
||||||
|
import org.bitcoins.db.DatabaseDriver._
|
||||||
import org.flywaydb.core.Flyway
|
import org.flywaydb.core.Flyway
|
||||||
import org.flywaydb.core.api.{FlywayException, MigrationInfoService}
|
import org.flywaydb.core.api.{FlywayException, MigrationInfoService}
|
||||||
|
|
||||||
|
@ -125,6 +126,17 @@ trait DbManagement extends BitcoinSLogger {
|
||||||
flyway.info()
|
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
|
/** Executes migrations related to this database
|
||||||
*
|
*
|
||||||
* @see [[https://flywaydb.org/documentation/api/#programmatic-configuration-java]]
|
* @see [[https://flywaydb.org/documentation/api/#programmatic-configuration-java]]
|
||||||
|
|
|
@ -1,21 +1,28 @@
|
||||||
package org.bitcoins.dlc.oracle
|
package org.bitcoins.dlc.oracle
|
||||||
|
|
||||||
import java.sql.SQLException
|
import java.time.Instant
|
||||||
|
|
||||||
import org.bitcoins.commons.jsonmodels.dlc.SigningVersion
|
import org.bitcoins.commons.jsonmodels.dlc.SigningVersion
|
||||||
import org.bitcoins.core.hd.{HDCoinType, HDPurpose}
|
import org.bitcoins.core.hd.{HDCoinType, HDPurpose}
|
||||||
|
import org.bitcoins.core.number._
|
||||||
import org.bitcoins.core.protocol.Bech32Address
|
import org.bitcoins.core.protocol.Bech32Address
|
||||||
import org.bitcoins.core.protocol.script.P2WPKHWitnessSPKV0
|
import org.bitcoins.core.protocol.script.P2WPKHWitnessSPKV0
|
||||||
|
import org.bitcoins.core.protocol.tlv._
|
||||||
import org.bitcoins.core.util.TimeUtil
|
import org.bitcoins.core.util.TimeUtil
|
||||||
import org.bitcoins.crypto._
|
import org.bitcoins.crypto._
|
||||||
import org.bitcoins.dlc.oracle.storage._
|
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 org.bitcoins.testkit.fixtures.DLCOracleFixture
|
||||||
import scodec.bits.ByteVector
|
|
||||||
|
|
||||||
class DLCOracleTest extends DLCOracleFixture {
|
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"
|
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 =>
|
it must "calculate the correct staking address" in { dlcOracle: DLCOracle =>
|
||||||
forAllAsync(ChainParamsGenerator.bitcoinNetworkParams) { network =>
|
forAllAsync(ChainParamsGenerator.bitcoinNetworkParams) { network =>
|
||||||
val expected =
|
val expected =
|
||||||
|
@ -46,129 +60,441 @@ class DLCOracleTest extends DLCOracleFixture {
|
||||||
|
|
||||||
it must "create a new event and list it with pending" in {
|
it must "create a new event and list it with pending" in {
|
||||||
dlcOracle: DLCOracle =>
|
dlcOracle: DLCOracle =>
|
||||||
val time = TimeUtil.now
|
val time = futureTime
|
||||||
|
|
||||||
for {
|
for {
|
||||||
testEventDb <- dlcOracle.createNewEvent("test", time, testOutcomes)
|
_ <- dlcOracle.createNewEvent("test", time, testDescriptor)
|
||||||
pendingEvents <- dlcOracle.listPendingEventDbs()
|
pendingEvents <- dlcOracle.listPendingEventDbs()
|
||||||
} yield {
|
} yield {
|
||||||
assert(pendingEvents.size == 1)
|
assert(pendingEvents.size == 1)
|
||||||
// encoding of the time can make them unequal
|
assert(pendingEvents.head.eventDescriptorTLV == testDescriptor)
|
||||||
val comparable =
|
|
||||||
pendingEvents.head.copy(maturationTime = testEventDb.maturationTime)
|
|
||||||
assert(comparable == testEventDb)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
it must "create a new event and get its details" in { dlcOracle: DLCOracle =>
|
it must "create an enum new event and get its details" in {
|
||||||
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 {
|
|
||||||
dlcOracle: DLCOracle =>
|
dlcOracle: DLCOracle =>
|
||||||
|
val time = futureTime
|
||||||
|
val eventName = "test"
|
||||||
|
|
||||||
for {
|
for {
|
||||||
testEventDb <-
|
announcement <-
|
||||||
dlcOracle.createNewEvent("test", TimeUtil.now, testOutcomes)
|
dlcOracle.createNewEnumEvent(eventName, time, enumOutcomes)
|
||||||
rValDbOpt <- dlcOracle.rValueDAO.read(testEventDb.nonce)
|
|
||||||
|
eventOpt <- dlcOracle.findEvent(announcement.eventTLV)
|
||||||
} yield {
|
} yield {
|
||||||
assert(rValDbOpt.isDefined)
|
assert(eventOpt.isDefined)
|
||||||
val rValDb = rValDbOpt.get
|
val event = eventOpt.get
|
||||||
val hash = CryptoUtil.taggedSha256(
|
|
||||||
rValDb.nonce.bytes ++ CryptoUtil.serializeForHash(
|
assert(event.isInstanceOf[PendingEnumV0OracleEvent])
|
||||||
rValDb.eventName) ++ ByteVector.fromLong(
|
assert(event.eventName == eventName)
|
||||||
testEventDb.maturationTime.getEpochSecond),
|
assert(event.eventDescriptorTLV == testDescriptor)
|
||||||
SigningVersion.latest.announcementTag
|
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(
|
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 =>
|
dlcOracle: DLCOracle =>
|
||||||
|
val time = futureTime
|
||||||
|
val eventName = "ranged"
|
||||||
|
val start = -100
|
||||||
|
val count = 201
|
||||||
|
val step = 1
|
||||||
|
val unit = "units"
|
||||||
|
val precision = 0
|
||||||
|
|
||||||
for {
|
for {
|
||||||
_ <- dlcOracle.createNewEvent("test", TimeUtil.now, testOutcomes)
|
announcement <- dlcOracle.createNewRangedEvent(eventName,
|
||||||
_ <- dlcOracle.createNewEvent("test1", TimeUtil.now, testOutcomes)
|
time,
|
||||||
} yield succeed
|
start,
|
||||||
}
|
count,
|
||||||
|
step,
|
||||||
|
unit,
|
||||||
|
precision)
|
||||||
|
|
||||||
it must "fail to create multiple events with the same name" in {
|
eventOpt <- dlcOracle.findEvent(announcement.eventTLV)
|
||||||
dlcOracle: DLCOracle =>
|
} yield {
|
||||||
recoverToSucceededIf[SQLException] {
|
assert(eventOpt.isDefined)
|
||||||
for {
|
val event = eventOpt.get
|
||||||
_ <- dlcOracle.createNewEvent("test", TimeUtil.now, testOutcomes)
|
|
||||||
_ <- dlcOracle.createNewEvent("test", TimeUtil.now, testOutcomes)
|
val expectedDescriptorTLV =
|
||||||
} yield ()
|
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 =>
|
it must "fail to create a ranged event with a 0 step" in {
|
||||||
val outcome = testOutcomes.head
|
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 {
|
for {
|
||||||
eventDb <- dlcOracle.createNewEvent("test", TimeUtil.now, testOutcomes)
|
announcement <-
|
||||||
signedEventDb <- dlcOracle.signEvent(eventDb.nonce, outcome)
|
dlcOracle.createNewEvent("test", futureTime, rangeEventDescriptorV0TLV)
|
||||||
outcomeDbs <- dlcOracle.eventOutcomeDAO.findByNonce(eventDb.nonce)
|
|
||||||
outcomeDb = outcomeDbs.find(_.message == outcome).get
|
nonce = announcement.eventTLV.nonces.head
|
||||||
eventOpt <- dlcOracle.getEvent(eventDb.nonce)
|
|
||||||
|
signedEventDb <- dlcOracle.signEvent(nonce, outcome)
|
||||||
|
eventOpt <- dlcOracle.findEvent(announcement.eventTLV)
|
||||||
} yield {
|
} yield {
|
||||||
assert(eventOpt.isDefined)
|
assert(eventOpt.isDefined)
|
||||||
val event = eventOpt.get
|
val event = eventOpt.get
|
||||||
val sig = signedEventDb.sigOpt.get
|
val sig = signedEventDb.sigOpt.get
|
||||||
|
|
||||||
event match {
|
event match {
|
||||||
case completedEvent: CompletedEvent =>
|
case completedEvent: CompletedRangeV0OracleEvent =>
|
||||||
assert(completedEvent.attestation == sig.sig)
|
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(
|
assert(
|
||||||
SchnorrDigitalSignature(completedEvent.nonce,
|
SchnorrDigitalSignature(completedEvent.nonces.head,
|
||||||
completedEvent.attestation) == sig)
|
completedEvent.attestation) == sig)
|
||||||
case _: PendingEvent =>
|
case _: PendingOracleEvent | _: CompletedOracleEvent =>
|
||||||
fail()
|
fail()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
it must "correctly track pending events" in { dlcOracle: DLCOracle =>
|
it must "create and sign an enum event" in { dlcOracle: DLCOracle =>
|
||||||
val outcome = testOutcomes.head
|
val descriptor = TLVGen.enumEventDescriptorV0TLV.sampleSome
|
||||||
|
val outcome = descriptor.outcomes.head
|
||||||
|
|
||||||
|
val descriptorV0TLV =
|
||||||
|
EnumEventDescriptorV0TLV(descriptor.outcomes)
|
||||||
|
|
||||||
for {
|
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()
|
beforePending <- dlcOracle.listPendingEventDbs()
|
||||||
beforeEvents <- dlcOracle.listEvents()
|
beforeEvents <- dlcOracle.listEvents()
|
||||||
_ = assert(beforePending.size == 1)
|
_ = assert(beforePending.size == 1)
|
||||||
_ = assert(beforeEvents.size == 1)
|
_ = assert(beforeEvents.size == 1)
|
||||||
_ = assert(beforeEvents.head.isInstanceOf[PendingEvent])
|
_ = assert(beforeEvents.head.isInstanceOf[PendingOracleEvent])
|
||||||
_ <- dlcOracle.signEvent(eventDb.nonce, outcome)
|
|
||||||
|
nonce = announcement.eventTLV.nonces.head
|
||||||
|
|
||||||
|
_ <- dlcOracle.signEvent(nonce, EnumAttestation(outcome))
|
||||||
afterPending <- dlcOracle.listPendingEventDbs()
|
afterPending <- dlcOracle.listPendingEventDbs()
|
||||||
afterEvents <- dlcOracle.listEvents()
|
afterEvents <- dlcOracle.listEvents()
|
||||||
} yield {
|
} yield {
|
||||||
assert(afterPending.isEmpty)
|
assert(afterPending.isEmpty)
|
||||||
assert(afterEvents.size == 1)
|
assert(afterEvents.size == 1)
|
||||||
assert(afterEvents.head.isInstanceOf[CompletedEvent])
|
assert(afterEvents.head.isInstanceOf[CompletedOracleEvent])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,16 +502,40 @@ class DLCOracleTest extends DLCOracleFixture {
|
||||||
dlcOracle: DLCOracle =>
|
dlcOracle: DLCOracle =>
|
||||||
val dummyNonce = SchnorrNonce(ECPublicKey.freshPublicKey.bytes.tail)
|
val dummyNonce = SchnorrNonce(ECPublicKey.freshPublicKey.bytes.tail)
|
||||||
recoverToSucceededIf[RuntimeException](
|
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 =>
|
dlcOracle: DLCOracle =>
|
||||||
recoverToSucceededIf[RuntimeException] {
|
recoverToSucceededIf[RuntimeException] {
|
||||||
for {
|
for {
|
||||||
eventDb <-
|
announcement <-
|
||||||
dlcOracle.createNewEvent("test", TimeUtil.now, testOutcomes)
|
dlcOracle.createNewEnumEvent("test", futureTime, enumOutcomes)
|
||||||
_ <- dlcOracle.signEvent(eventDb.nonce, "not a real outcome")
|
|
||||||
|
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 ()
|
} yield ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -200,49 +550,70 @@ class DLCOracleTest extends DLCOracleFixture {
|
||||||
val sigVersion = SigningVersion.latest
|
val sigVersion = SigningVersion.latest
|
||||||
val message = "dummy message"
|
val message = "dummy message"
|
||||||
|
|
||||||
val rValDb = RValueDb(nonce,
|
val rValDb =
|
||||||
eventName,
|
RValueDb(nonce, eventName, HDPurpose(0), HDCoinType.Bitcoin, 0, 0, 0)
|
||||||
HDPurpose(0),
|
|
||||||
HDCoinType.Bitcoin,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
SchnorrDigitalSignature(nonce, FieldElement.one))
|
|
||||||
|
|
||||||
val eventDb =
|
val eventDb =
|
||||||
EventDb(nonce, publicKey, eventName, 1, sigVersion, TimeUtil.now, None)
|
EventDb(nonce,
|
||||||
|
publicKey,
|
||||||
val outcomeDb =
|
0,
|
||||||
EventOutcomeDb(nonce,
|
eventName,
|
||||||
message,
|
0,
|
||||||
CryptoUtil.taggedSha256(message, sigVersion.outcomeTag))
|
sigVersion,
|
||||||
|
futureTime,
|
||||||
|
None,
|
||||||
|
SchnorrDigitalSignature(nonce, FieldElement.one),
|
||||||
|
testDescriptor)
|
||||||
|
|
||||||
val setupF = for {
|
val setupF = for {
|
||||||
_ <- dlcOracle.rValueDAO.create(rValDb)
|
_ <- dlcOracle.rValueDAO.create(rValDb)
|
||||||
_ <- dlcOracle.eventDAO.create(eventDb)
|
_ <- dlcOracle.eventDAO.create(eventDb)
|
||||||
_ <- dlcOracle.eventOutcomeDAO.create(outcomeDb)
|
|
||||||
} yield ()
|
} yield ()
|
||||||
|
|
||||||
recoverToSucceededIf[IllegalArgumentException] {
|
recoverToSucceededIf[IllegalArgumentException] {
|
||||||
for {
|
for {
|
||||||
_ <- setupF
|
_ <- setupF
|
||||||
_ <- dlcOracle.signEvent(nonce, message)
|
_ <- dlcOracle.signEvent(nonce, EnumAttestation(message))
|
||||||
} yield ()
|
} 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 =>
|
dlcOracle: DLCOracle =>
|
||||||
assertThrows[IllegalArgumentException] {
|
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 {
|
it must "fail to create an event with duplicate outcomes" in {
|
||||||
dlcOracle: DLCOracle =>
|
dlcOracle: DLCOracle =>
|
||||||
val outcomes = testOutcomes :+ testOutcomes.head
|
val outcomes = enumOutcomes :+ enumOutcomes.head
|
||||||
assertThrows[IllegalArgumentException] {
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,11 @@ import java.time.Instant
|
||||||
|
|
||||||
import org.bitcoins.commons.jsonmodels.dlc.SigningVersion
|
import org.bitcoins.commons.jsonmodels.dlc.SigningVersion
|
||||||
import org.bitcoins.core.hd.{HDCoinType, HDPurpose}
|
import org.bitcoins.core.hd.{HDCoinType, HDPurpose}
|
||||||
|
import org.bitcoins.core.protocol.tlv.EventDescriptorTLV
|
||||||
import org.bitcoins.core.util.TimeUtil
|
import org.bitcoins.core.util.TimeUtil
|
||||||
import org.bitcoins.crypto._
|
import org.bitcoins.crypto._
|
||||||
import org.bitcoins.dlc.oracle.DLCOracleAppConfig
|
import org.bitcoins.testkit.Implicits._
|
||||||
import org.bitcoins.testkit.BitcoinSTestAppConfig
|
import org.bitcoins.testkit.core.gen.TLVGen
|
||||||
import org.bitcoins.testkit.fixtures.DLCOracleDAOFixture
|
import org.bitcoins.testkit.fixtures.DLCOracleDAOFixture
|
||||||
|
|
||||||
class EventDAOTest extends DLCOracleDAOFixture {
|
class EventDAOTest extends DLCOracleDAOFixture {
|
||||||
|
@ -28,22 +29,29 @@ class EventDAOTest extends DLCOracleDAOFixture {
|
||||||
Instant.ofEpochSecond(now)
|
Instant.ofEpochSecond(now)
|
||||||
}
|
}
|
||||||
|
|
||||||
val dummyRValDb: RValueDb = RValueDb(
|
val dummyRValDb: RValueDb =
|
||||||
nonce,
|
RValueDb(nonce, eventName, HDPurpose(0), HDCoinType.Bitcoin, 0, 0, 0)
|
||||||
eventName,
|
|
||||||
HDPurpose(0),
|
val dummySig: SchnorrDigitalSignature =
|
||||||
HDCoinType.Bitcoin,
|
SchnorrDigitalSignature(nonce, FieldElement.one)
|
||||||
0,
|
|
||||||
0,
|
def descriptor: EventDescriptorTLV = TLVGen.eventDescriptorTLV.sampleSome
|
||||||
0,
|
|
||||||
SchnorrDigitalSignature(nonce, FieldElement.one))
|
|
||||||
|
|
||||||
it must "create an EventDb and read it" in { daos =>
|
it must "create an EventDb and read it" in { daos =>
|
||||||
val rValDAO = daos.rValueDAO
|
val rValDAO = daos.rValueDAO
|
||||||
val eventDAO = daos.eventDAO
|
val eventDAO = daos.eventDAO
|
||||||
|
|
||||||
val eventDb =
|
val eventDb =
|
||||||
EventDb(nonce, publicKey, eventName, 1, sigVersion, time, None)
|
EventDb(nonce,
|
||||||
|
publicKey,
|
||||||
|
0,
|
||||||
|
eventName,
|
||||||
|
0,
|
||||||
|
sigVersion,
|
||||||
|
time,
|
||||||
|
None,
|
||||||
|
dummySig,
|
||||||
|
descriptor)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
_ <- rValDAO.create(dummyRValDb)
|
_ <- rValDAO.create(dummyRValDb)
|
||||||
|
@ -57,7 +65,16 @@ class EventDAOTest extends DLCOracleDAOFixture {
|
||||||
val eventDAO = daos.eventDAO
|
val eventDAO = daos.eventDAO
|
||||||
|
|
||||||
val eventDb =
|
val eventDb =
|
||||||
EventDb(nonce, publicKey, eventName, 1, sigVersion, time, None)
|
EventDb(nonce,
|
||||||
|
publicKey,
|
||||||
|
0,
|
||||||
|
eventName,
|
||||||
|
0,
|
||||||
|
sigVersion,
|
||||||
|
time,
|
||||||
|
None,
|
||||||
|
dummySig,
|
||||||
|
descriptor)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
_ <- rValDAO.create(dummyRValDb)
|
_ <- rValDAO.create(dummyRValDb)
|
||||||
|
|
|
@ -4,11 +4,13 @@ import java.time.Instant
|
||||||
|
|
||||||
import org.bitcoins.commons.jsonmodels.dlc.SigningVersion
|
import org.bitcoins.commons.jsonmodels.dlc.SigningVersion
|
||||||
import org.bitcoins.core.hd.{HDCoinType, HDPurpose}
|
import org.bitcoins.core.hd.{HDCoinType, HDPurpose}
|
||||||
|
import org.bitcoins.core.protocol.tlv.EventDescriptorTLV
|
||||||
import org.bitcoins.core.util.TimeUtil
|
import org.bitcoins.core.util.TimeUtil
|
||||||
import org.bitcoins.crypto._
|
import org.bitcoins.crypto._
|
||||||
import org.bitcoins.dlc.oracle.DLCOracleAppConfig
|
import org.bitcoins.testkit.Implicits.GeneratorOps
|
||||||
import org.bitcoins.testkit.BitcoinSTestAppConfig
|
import org.bitcoins.testkit.core.gen.TLVGen
|
||||||
import org.bitcoins.testkit.fixtures.DLCOracleDAOFixture
|
import org.bitcoins.testkit.fixtures.DLCOracleDAOFixture
|
||||||
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
class EventOutcomeDAOTest extends DLCOracleDAOFixture {
|
class EventOutcomeDAOTest extends DLCOracleDAOFixture {
|
||||||
|
|
||||||
|
@ -22,8 +24,7 @@ class EventOutcomeDAOTest extends DLCOracleDAOFixture {
|
||||||
val sigVersion: SigningVersion = SigningVersion.latest
|
val sigVersion: SigningVersion = SigningVersion.latest
|
||||||
val message = "dummy message"
|
val message = "dummy message"
|
||||||
|
|
||||||
val hash: Sha256Digest =
|
val hash: ByteVector = CryptoUtil.sha256(message).bytes
|
||||||
CryptoUtil.taggedSha256(message, sigVersion.outcomeTag)
|
|
||||||
|
|
||||||
val time: Instant = {
|
val time: Instant = {
|
||||||
// Need to do this so it is comparable to the db representation
|
// Need to do this so it is comparable to the db representation
|
||||||
|
@ -31,18 +32,22 @@ class EventOutcomeDAOTest extends DLCOracleDAOFixture {
|
||||||
Instant.ofEpochSecond(now)
|
Instant.ofEpochSecond(now)
|
||||||
}
|
}
|
||||||
|
|
||||||
val dummyRValDb: RValueDb = RValueDb(
|
val dummyRValDb: RValueDb =
|
||||||
nonce,
|
RValueDb(nonce, eventName, HDPurpose(0), HDCoinType.Bitcoin, 0, 0, 0)
|
||||||
eventName,
|
|
||||||
HDPurpose(0),
|
def descriptor: EventDescriptorTLV = TLVGen.eventDescriptorTLV.sampleSome
|
||||||
HDCoinType.Bitcoin,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
SchnorrDigitalSignature(nonce, FieldElement.one))
|
|
||||||
|
|
||||||
val dummyEventDb: EventDb =
|
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 =>
|
it must "create an EventOutcomeDb and read it" in { daos =>
|
||||||
val rValDAO = daos.rValueDAO
|
val rValDAO = daos.rValueDAO
|
||||||
|
@ -80,10 +85,8 @@ class EventOutcomeDAOTest extends DLCOracleDAOFixture {
|
||||||
val outcomeDAO = daos.outcomeDAO
|
val outcomeDAO = daos.outcomeDAO
|
||||||
|
|
||||||
val outcomeDb = EventOutcomeDb(nonce, message, hash)
|
val outcomeDb = EventOutcomeDb(nonce, message, hash)
|
||||||
val outcomeDb1 =
|
val bytes = CryptoUtil.sha256("message").bytes
|
||||||
EventOutcomeDb(nonce,
|
val outcomeDb1 = EventOutcomeDb(nonce, "message", bytes)
|
||||||
"message",
|
|
||||||
CryptoUtil.taggedSha256("message", sigVersion.outcomeTag))
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
_ <- rValDAO.create(dummyRValDb)
|
_ <- rValDAO.create(dummyRValDb)
|
||||||
|
|
|
@ -6,8 +6,6 @@ import org.bitcoins.commons.jsonmodels.dlc.SigningVersion
|
||||||
import org.bitcoins.core.hd.{HDCoinType, HDPurpose}
|
import org.bitcoins.core.hd.{HDCoinType, HDPurpose}
|
||||||
import org.bitcoins.core.util.TimeUtil
|
import org.bitcoins.core.util.TimeUtil
|
||||||
import org.bitcoins.crypto._
|
import org.bitcoins.crypto._
|
||||||
import org.bitcoins.dlc.oracle.DLCOracleAppConfig
|
|
||||||
import org.bitcoins.testkit.BitcoinSTestAppConfig
|
|
||||||
import org.bitcoins.testkit.fixtures.DLCOracleDAOFixture
|
import org.bitcoins.testkit.fixtures.DLCOracleDAOFixture
|
||||||
|
|
||||||
class RValueDAOTest extends DLCOracleDAOFixture {
|
class RValueDAOTest extends DLCOracleDAOFixture {
|
||||||
|
@ -28,15 +26,8 @@ class RValueDAOTest extends DLCOracleDAOFixture {
|
||||||
Instant.ofEpochSecond(now)
|
Instant.ofEpochSecond(now)
|
||||||
}
|
}
|
||||||
|
|
||||||
val rValDb: RValueDb = RValueDb(
|
val rValDb: RValueDb =
|
||||||
nonce,
|
RValueDb(nonce, eventName, HDPurpose(0), HDCoinType.Bitcoin, 0, 0, 0)
|
||||||
eventName,
|
|
||||||
HDPurpose(0),
|
|
||||||
HDCoinType.Bitcoin,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
SchnorrDigitalSignature(nonce, FieldElement.one))
|
|
||||||
|
|
||||||
it must "create an RValueDb and read it" in { daos =>
|
it must "create an RValueDb and read it" in { daos =>
|
||||||
val rValDAO = daos.rValueDAO
|
val rValDAO = daos.rValueDAO
|
||||||
|
|
|
@ -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;
|
|
@ -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`;
|
|
@ -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)
|
||||||
|
}
|
|
@ -3,23 +3,25 @@ package org.bitcoins.dlc.oracle
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
import org.bitcoins.commons.jsonmodels.dlc.SigningVersion
|
import org.bitcoins.commons.jsonmodels.dlc.SigningVersion
|
||||||
import org.bitcoins.commons.jsonmodels.dlc.SigningVersion._
|
|
||||||
import org.bitcoins.core.config.BitcoinNetwork
|
import org.bitcoins.core.config.BitcoinNetwork
|
||||||
import org.bitcoins.core.crypto.ExtKeyVersion.SegWitMainNetPriv
|
import org.bitcoins.core.crypto.ExtKeyVersion.SegWitMainNetPriv
|
||||||
import org.bitcoins.core.crypto.{ExtPrivateKeyHardened, MnemonicCode}
|
import org.bitcoins.core.crypto.{ExtPrivateKeyHardened, MnemonicCode}
|
||||||
import org.bitcoins.core.hd._
|
import org.bitcoins.core.hd._
|
||||||
|
import org.bitcoins.core.number._
|
||||||
import org.bitcoins.core.protocol.Bech32Address
|
import org.bitcoins.core.protocol.Bech32Address
|
||||||
import org.bitcoins.core.protocol.script.P2WPKHWitnessSPKV0
|
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.crypto._
|
||||||
|
import org.bitcoins.dlc.oracle.config.DLCOracleAppConfig
|
||||||
import org.bitcoins.dlc.oracle.storage._
|
import org.bitcoins.dlc.oracle.storage._
|
||||||
import org.bitcoins.keymanager.{DecryptedMnemonic, WalletStorage}
|
import org.bitcoins.keymanager.{DecryptedMnemonic, WalletStorage}
|
||||||
import scodec.bits.ByteVector
|
|
||||||
|
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
|
|
||||||
case class DLCOracle(private val extPrivateKey: ExtPrivateKeyHardened)(implicit
|
case class DLCOracle(private val extPrivateKey: ExtPrivateKeyHardened)(implicit
|
||||||
val conf: DLCOracleAppConfig) {
|
val conf: DLCOracleAppConfig)
|
||||||
|
extends BitcoinSLogger {
|
||||||
|
|
||||||
implicit val ec: ExecutionContext = conf.ec
|
implicit val ec: ExecutionContext = conf.ec
|
||||||
|
|
||||||
|
@ -78,11 +80,8 @@ case class DLCOracle(private val extPrivateKey: ExtPrivateKeyHardened)(implicit
|
||||||
require(path.forall(_.hardened),
|
require(path.forall(_.hardened),
|
||||||
s"Cannot use a BIP32Path with unhardened nodes, got $path")
|
s"Cannot use a BIP32Path with unhardened nodes, got $path")
|
||||||
val priv = extPrivateKey.deriveChildPrivKey(path).key
|
val priv = extPrivateKey.deriveChildPrivKey(path).key
|
||||||
val hash =
|
val tweakBytes = signingVersion.calcNonceTweak(priv.schnorrNonce, label)
|
||||||
CryptoUtil.taggedSha256(
|
val tweak = ECPrivateKey(tweakBytes)
|
||||||
priv.schnorrNonce.bytes ++ CryptoUtil.serializeForHash(label),
|
|
||||||
signingVersion.nonceTag)
|
|
||||||
val tweak = ECPrivateKey(hash.bytes)
|
|
||||||
|
|
||||||
priv.add(tweak)
|
priv.add(tweak)
|
||||||
}
|
}
|
||||||
|
@ -91,92 +90,231 @@ case class DLCOracle(private val extPrivateKey: ExtPrivateKeyHardened)(implicit
|
||||||
|
|
||||||
def listPendingEventDbs(): Future[Vector[EventDb]] = eventDAO.getPendingEvents
|
def listPendingEventDbs(): Future[Vector[EventDb]] = eventDAO.getPendingEvents
|
||||||
|
|
||||||
def listEvents(): Future[Vector[Event]] = {
|
def listEvents(): Future[Vector[OracleEvent]] = {
|
||||||
for {
|
eventDAO.findAll().map { eventDbs =>
|
||||||
rValDbs <- rValueDAO.findAll()
|
val events = eventDbs.groupBy(_.eventDescriptorTLV)
|
||||||
eventDbs <- eventDAO.findAll()
|
events.values.map(dbs => OracleEvent.fromEventDbs(dbs)).toVector
|
||||||
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 getEvent(nonce: SchnorrNonce): Future[Option[Event]] = {
|
def findEvent(oracleEventTLV: OracleEventTLV): Future[Option[OracleEvent]] = {
|
||||||
for {
|
eventDAO.findByOracleEventTLV(oracleEventTLV).map { dbs =>
|
||||||
rValDbOpt <- rValueDAO.read(nonce)
|
if (dbs.isEmpty) {
|
||||||
eventDbOpt <- eventDAO.read(nonce)
|
None
|
||||||
outcomes <- eventOutcomeDAO.findByNonce(nonce)
|
} else Some(OracleEvent.fromEventDbs(dbs))
|
||||||
} yield {
|
|
||||||
(rValDbOpt, eventDbOpt) match {
|
|
||||||
case (Some(rValDb), Some(eventDb)) =>
|
|
||||||
Some(Event(rValDb, eventDb, outcomes))
|
|
||||||
case (None, None) | (Some(_), None) | (None, Some(_)) =>
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(
|
def createNewEvent(
|
||||||
eventName: String,
|
eventName: String,
|
||||||
maturationTime: Instant,
|
maturationTime: Instant,
|
||||||
outcomes: Vector[String]): Future[EventDb] = {
|
descriptor: EventDescriptorTLV,
|
||||||
require(outcomes.nonEmpty, "Cannot make an event with no outcomes")
|
signingVersion: SigningVersion = SigningVersion.latest): Future[
|
||||||
require(outcomes.distinct.size == outcomes.size,
|
OracleAnnouncementTLV] = {
|
||||||
s"Cannot have duplicate outcomes, got $outcomes")
|
require(maturationTime.isAfter(TimeUtil.now),
|
||||||
|
s"Event cannot mature in the past, got $maturationTime")
|
||||||
|
|
||||||
for {
|
for {
|
||||||
indexOpt <- rValueDAO.maxKeyIndex
|
indexOpt <- rValueDAO.maxKeyIndex
|
||||||
index = indexOpt match {
|
firstIndex = indexOpt match {
|
||||||
case Some(value) => value + 1
|
case Some(value) => value + 1
|
||||||
case None => 0
|
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)
|
RValueDbHelper(nonce = nonce,
|
||||||
nonce = getKValue(eventName, path, signingVersion).schnorrNonce
|
eventName = eventName,
|
||||||
|
account = rValAccount,
|
||||||
|
chainType = rValueChainIndex,
|
||||||
|
keyIndex = index)
|
||||||
|
}
|
||||||
|
.toVector
|
||||||
|
|
||||||
hash = CryptoUtil.taggedSha256(
|
epoch = UInt32(maturationTime.getEpochSecond)
|
||||||
nonce.bytes ++ CryptoUtil.serializeForHash(eventName) ++ ByteVector
|
|
||||||
.fromLong(maturationTime.getEpochSecond),
|
|
||||||
signingVersion.announcementTag)
|
|
||||||
announcementSignature = signingKey.schnorrSign(hash.bytes)
|
|
||||||
|
|
||||||
rValueDb = RValueDbHelper(nonce = nonce,
|
nonces = rValueDbs.map(_.nonce)
|
||||||
eventName = eventName,
|
|
||||||
account = rValAccount,
|
|
||||||
chainType = rValueChainIndex,
|
|
||||||
keyIndex = index,
|
|
||||||
announcementSignature = announcementSignature)
|
|
||||||
|
|
||||||
eventDb = EventDb(nonce,
|
eventTLV = OracleEventV0TLV(nonces, epoch, descriptor, eventName)
|
||||||
publicKey,
|
|
||||||
eventName,
|
announcementBytes = signingVersion.calcAnnouncementHash(eventTLV)
|
||||||
outcomes.size,
|
announcementSignature = signingKey.schnorrSign(announcementBytes)
|
||||||
signingVersion,
|
|
||||||
maturationTime,
|
eventOutcomeDbs = descriptor match {
|
||||||
None)
|
case enum: EnumEventDescriptorV0TLV =>
|
||||||
eventOutcomeDbs = outcomes.map { outcome =>
|
require(rValueDbs.size == 1,
|
||||||
val hash = CryptoUtil.taggedSha256(outcome, signingVersion.outcomeTag)
|
"Enum events should only have one R value")
|
||||||
EventOutcomeDb(nonce, outcome, hash)
|
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)
|
eventDbs = rValueDbs.zipWithIndex.map {
|
||||||
eventDb <- eventDAO.create(eventDb)
|
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)
|
_ <- 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 {
|
for {
|
||||||
rValDbOpt <- rValueDAO.read(nonce)
|
rValDbOpt <- rValueDAO.read(nonce)
|
||||||
rValDb <- rValDbOpt match {
|
rValDb <- rValDbOpt match {
|
||||||
case Some(value) => Future.successful(value)
|
case Some(value) => Future.successful(value)
|
||||||
case None =>
|
case None =>
|
||||||
Future.failed(
|
Future.failed(
|
||||||
new RuntimeException(
|
new IllegalArgumentException(
|
||||||
s"Nonce not found from this oracle ${nonce.hex}"))
|
s"Nonce not found from this oracle ${nonce.hex}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,31 +327,84 @@ case class DLCOracle(private val extPrivateKey: ExtPrivateKeyHardened)(implicit
|
||||||
Future.successful(value)
|
Future.successful(value)
|
||||||
case None =>
|
case None =>
|
||||||
Future.failed(
|
Future.failed(
|
||||||
new RuntimeException(
|
new IllegalArgumentException(
|
||||||
s"No event saved with nonce ${nonce.hex} $outcome"))
|
s"No event saved with nonce ${nonce.hex} $outcome"))
|
||||||
}
|
}
|
||||||
|
|
||||||
eventOutcomeOpt <- eventOutcomeDAO.read((nonce, outcome))
|
eventOutcomeOpt <- eventOutcomeDAO.read((nonce, outcome.outcomeString))
|
||||||
eventOutcomeDb <- eventOutcomeOpt match {
|
eventOutcomeDb <- eventOutcomeOpt match {
|
||||||
case Some(value) => Future.successful(value)
|
case Some(value) => Future.successful(value)
|
||||||
case None =>
|
case None =>
|
||||||
Future.failed(new RuntimeException(
|
Future.failed(new IllegalArgumentException(
|
||||||
s"No event outcome saved with nonce and message ${nonce.hex} $outcome"))
|
s"No event outcome saved with nonce and message ${nonce.hex} ${outcome.outcomeString}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
sig = eventDb.signingVersion match {
|
sigVersion = eventDb.signingVersion
|
||||||
case Mock =>
|
|
||||||
val kVal = getKValue(rValDb, Mock)
|
kVal = getKValue(rValDb, sigVersion)
|
||||||
require(kVal.schnorrNonce == rValDb.nonce,
|
_ = require(kVal.schnorrNonce == rValDb.nonce,
|
||||||
"The nonce from derived seed did not match database")
|
"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))
|
updated = eventDb.copy(attestationOpt = Some(sig.sig))
|
||||||
_ <- eventDAO.update(updated)
|
_ <- eventDAO.update(updated)
|
||||||
} yield 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 {
|
object DLCOracle {
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package org.bitcoins.dlc.oracle
|
package org.bitcoins.dlc.oracle.config
|
||||||
|
|
||||||
import java.nio.file.{Files, Path}
|
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.config.NetworkParameters
|
||||||
import org.bitcoins.core.crypto.ExtKeyVersion.SegWitMainNetPriv
|
import org.bitcoins.core.crypto.ExtKeyVersion.SegWitMainNetPriv
|
||||||
import org.bitcoins.core.crypto.MnemonicCode
|
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.crypto.AesPassword
|
||||||
import org.bitcoins.db.DatabaseDriver._
|
import org.bitcoins.db.DatabaseDriver._
|
||||||
import org.bitcoins.db._
|
import org.bitcoins.db._
|
||||||
|
import org.bitcoins.dlc.oracle.DLCOracle
|
||||||
import org.bitcoins.dlc.oracle.storage._
|
import org.bitcoins.dlc.oracle.storage._
|
||||||
import org.bitcoins.keymanager.{DecryptedMnemonic, WalletStorage}
|
import org.bitcoins.keymanager.{DecryptedMnemonic, WalletStorage}
|
||||||
|
|
||||||
|
@ -46,10 +47,8 @@ case class DLCOracleAppConfig(
|
||||||
}
|
}
|
||||||
|
|
||||||
override def start(): Future[Unit] = {
|
override def start(): Future[Unit] = {
|
||||||
logger.debug(s"Initializing wallet setup")
|
logger.debug(s"Initializing dlc oracle setup")
|
||||||
for {
|
super.start().flatMap { _ =>
|
||||||
_ <- super.start()
|
|
||||||
} yield {
|
|
||||||
if (Files.notExists(datadir)) {
|
if (Files.notExists(datadir)) {
|
||||||
Files.createDirectories(datadir)
|
Files.createDirectories(datadir)
|
||||||
}
|
}
|
||||||
|
@ -57,6 +56,27 @@ case class DLCOracleAppConfig(
|
||||||
migrate()
|
migrate()
|
||||||
}
|
}
|
||||||
logger.info(s"Applied $numMigrations to the dlc oracle project")
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,11 @@ package org.bitcoins.dlc.oracle.storage
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
import org.bitcoins.commons.jsonmodels.dlc.SigningVersion
|
import org.bitcoins.commons.jsonmodels.dlc.SigningVersion
|
||||||
|
import org.bitcoins.core.protocol.tlv.{
|
||||||
|
EventDescriptorTLV,
|
||||||
|
OracleEventTLV,
|
||||||
|
OracleEventV0TLV
|
||||||
|
}
|
||||||
import org.bitcoins.crypto._
|
import org.bitcoins.crypto._
|
||||||
import org.bitcoins.db.{AppConfig, CRUD, DbCommonsColumnMappers, SlickUtil}
|
import org.bitcoins.db.{AppConfig, CRUD, DbCommonsColumnMappers, SlickUtil}
|
||||||
import slick.lifted.{ForeignKeyQuery, ProvenShape}
|
import slick.lifted.{ForeignKeyQuery, ProvenShape}
|
||||||
|
@ -41,13 +46,25 @@ case class EventDAO()(implicit
|
||||||
findAll().map(_.filter(_.attestationOpt.isEmpty))
|
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") {
|
class EventTable(tag: Tag) extends Table[EventDb](tag, schemaName, "events") {
|
||||||
|
|
||||||
def nonce: Rep[SchnorrNonce] = column("nonce", O.PrimaryKey)
|
def nonce: Rep[SchnorrNonce] = column("nonce", O.PrimaryKey)
|
||||||
|
|
||||||
def pubkey: Rep[SchnorrPublicKey] = column("pubkey")
|
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")
|
def numOutcomes: Rep[Long] = column("num_outcomes")
|
||||||
|
|
||||||
|
@ -57,14 +74,23 @@ case class EventDAO()(implicit
|
||||||
|
|
||||||
def attestationOpt: Rep[Option[FieldElement]] = column("attestation")
|
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] =
|
def * : ProvenShape[EventDb] =
|
||||||
(nonce,
|
(nonce,
|
||||||
pubkey,
|
pubkey,
|
||||||
|
nonceIndex,
|
||||||
eventName,
|
eventName,
|
||||||
numOutcomes,
|
numOutcomes,
|
||||||
signingVersion,
|
signingVersion,
|
||||||
maturationTime,
|
maturationTime,
|
||||||
attestationOpt).<>(EventDb.tupled, EventDb.unapply)
|
attestationOpt,
|
||||||
|
announcementSignature,
|
||||||
|
eventDescriptorTLV).<>(EventDb.tupled, EventDb.unapply)
|
||||||
|
|
||||||
def fk: ForeignKeyQuery[_, RValueDb] = {
|
def fk: ForeignKeyQuery[_, RValueDb] = {
|
||||||
foreignKey("fk_nonce",
|
foreignKey("fk_nonce",
|
||||||
|
|
|
@ -3,17 +3,35 @@ package org.bitcoins.dlc.oracle.storage
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
import org.bitcoins.commons.jsonmodels.dlc.SigningVersion
|
import org.bitcoins.commons.jsonmodels.dlc.SigningVersion
|
||||||
|
import org.bitcoins.core.protocol.tlv._
|
||||||
import org.bitcoins.crypto._
|
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(
|
case class EventDb(
|
||||||
nonce: SchnorrNonce,
|
nonce: SchnorrNonce,
|
||||||
pubkey: SchnorrPublicKey,
|
pubkey: SchnorrPublicKey,
|
||||||
|
nonceIndex: Int,
|
||||||
eventName: String,
|
eventName: String,
|
||||||
numOutcomes: Long,
|
numOutcomes: Long,
|
||||||
signingVersion: SigningVersion,
|
signingVersion: SigningVersion,
|
||||||
maturationTime: Instant,
|
maturationTime: Instant,
|
||||||
attestationOpt: Option[FieldElement]) {
|
attestationOpt: Option[FieldElement],
|
||||||
|
announcementSignature: SchnorrDigitalSignature,
|
||||||
|
eventDescriptorTLV: EventDescriptorTLV) {
|
||||||
|
|
||||||
lazy val sigOpt: Option[SchnorrDigitalSignature] =
|
lazy val sigOpt: Option[SchnorrDigitalSignature] =
|
||||||
attestationOpt.map(SchnorrDigitalSignature(nonce, _))
|
attestationOpt.map(SchnorrDigitalSignature(nonce, _))
|
||||||
|
|
||||||
|
lazy val toOracleEvent: OracleEvent = OracleEvent.fromEventDbs(Vector(this))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
package org.bitcoins.dlc.oracle.storage
|
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 org.bitcoins.db.{AppConfig, CRUD, DbCommonsColumnMappers, SlickUtil}
|
||||||
|
import scodec.bits.ByteVector
|
||||||
import slick.lifted.{ForeignKeyQuery, ProvenShape}
|
import slick.lifted.{ForeignKeyQuery, ProvenShape}
|
||||||
|
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
|
@ -55,7 +56,7 @@ case class EventOutcomeDAO()(implicit
|
||||||
|
|
||||||
def message: Rep[String] = column("message")
|
def message: Rep[String] = column("message")
|
||||||
|
|
||||||
def hashedMessage: Rep[Sha256Digest] = column("hashed_message")
|
def hashedMessage: Rep[ByteVector] = column("hashed_message")
|
||||||
|
|
||||||
def * : ProvenShape[EventOutcomeDb] =
|
def * : ProvenShape[EventOutcomeDb] =
|
||||||
(nonce, message, hashedMessage).<>(EventOutcomeDb.tupled,
|
(nonce, message, hashedMessage).<>(EventOutcomeDb.tupled,
|
||||||
|
|
|
@ -1,8 +1,20 @@
|
||||||
package org.bitcoins.dlc.oracle.storage
|
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(
|
case class EventOutcomeDb(
|
||||||
nonce: SchnorrNonce,
|
nonce: SchnorrNonce,
|
||||||
message: String,
|
message: String,
|
||||||
hashedMessage: Sha256Digest)
|
hashedMessage: ByteVector)
|
||||||
|
|
||||||
|
object EventOutcomeDbHelper {
|
||||||
|
|
||||||
|
def createEnumEventDescriptor(
|
||||||
|
outcomes: Vector[EventOutcomeDb]): EnumEventDescriptorV0TLV = {
|
||||||
|
val strs = outcomes.map(_.message)
|
||||||
|
|
||||||
|
EnumEventDescriptorV0TLV(strs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package org.bitcoins.dlc.oracle.storage
|
package org.bitcoins.dlc.oracle.storage
|
||||||
|
|
||||||
import org.bitcoins.core.hd.{HDCoinType, HDPurpose}
|
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 org.bitcoins.db.{AppConfig, CRUD, DbCommonsColumnMappers, SlickUtil}
|
||||||
import slick.lifted.ProvenShape
|
import slick.lifted.ProvenShape
|
||||||
|
|
||||||
|
@ -55,17 +55,8 @@ case class RValueDAO()(implicit
|
||||||
|
|
||||||
def keyIndex: Rep[Int] = column("key_index", O.Unique)
|
def keyIndex: Rep[Int] = column("key_index", O.Unique)
|
||||||
|
|
||||||
def announcementSignature: Rep[SchnorrDigitalSignature] =
|
|
||||||
column("announcement_signature")
|
|
||||||
|
|
||||||
def * : ProvenShape[RValueDb] =
|
def * : ProvenShape[RValueDb] =
|
||||||
(nonce,
|
(nonce, eventName, purpose, coinType, accountIndex, chainType, keyIndex)
|
||||||
eventName,
|
.<>(RValueDb.tupled, RValueDb.unapply)
|
||||||
purpose,
|
|
||||||
coinType,
|
|
||||||
accountIndex,
|
|
||||||
chainType,
|
|
||||||
keyIndex,
|
|
||||||
announcementSignature).<>(RValueDb.tupled, RValueDb.unapply)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package org.bitcoins.dlc.oracle.storage
|
package org.bitcoins.dlc.oracle.storage
|
||||||
|
|
||||||
import org.bitcoins.core.hd._
|
import org.bitcoins.core.hd._
|
||||||
import org.bitcoins.crypto.{SchnorrDigitalSignature, SchnorrNonce}
|
import org.bitcoins.crypto.SchnorrNonce
|
||||||
|
|
||||||
case class RValueDb(
|
case class RValueDb(
|
||||||
nonce: SchnorrNonce,
|
nonce: SchnorrNonce,
|
||||||
|
@ -10,8 +10,7 @@ case class RValueDb(
|
||||||
accountCoin: HDCoinType,
|
accountCoin: HDCoinType,
|
||||||
accountIndex: Int,
|
accountIndex: Int,
|
||||||
chainType: Int,
|
chainType: Int,
|
||||||
keyIndex: Int,
|
keyIndex: Int) {
|
||||||
announcementSignature: SchnorrDigitalSignature) {
|
|
||||||
|
|
||||||
val path: BIP32Path = BIP32Path.fromString(
|
val path: BIP32Path = BIP32Path.fromString(
|
||||||
s"m/${purpose.constant}'/${accountCoin.toInt}'/$accountIndex'/$chainType'/$keyIndex'")
|
s"m/${purpose.constant}'/${accountCoin.toInt}'/$accountIndex'/$chainType'/$keyIndex'")
|
||||||
|
@ -24,15 +23,13 @@ object RValueDbHelper {
|
||||||
eventName: String,
|
eventName: String,
|
||||||
account: HDAccount,
|
account: HDAccount,
|
||||||
chainType: Int,
|
chainType: Int,
|
||||||
keyIndex: Int,
|
keyIndex: Int): RValueDb = {
|
||||||
announcementSignature: SchnorrDigitalSignature): RValueDb = {
|
|
||||||
RValueDb(nonce,
|
RValueDb(nonce,
|
||||||
eventName,
|
eventName,
|
||||||
account.purpose,
|
account.purpose,
|
||||||
account.coin.coinType,
|
account.coin.coinType,
|
||||||
account.index,
|
account.index,
|
||||||
chainType,
|
chainType,
|
||||||
keyIndex,
|
keyIndex)
|
||||||
announcementSignature)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
- `getpublickey` - Get oracle's public key
|
||||||
- `getstakingaddress` - Get oracle's staking address
|
- `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
|
- `createevent` `label` `maturationtime` `outcomes` - Registers an oracle event
|
||||||
- `label` - Label for this event
|
- `label` - Label for this event
|
||||||
- `maturationtime` - The earliest expected time an outcome will be signed, given in epoch second
|
- `maturationtime` - The earliest expected time an outcome will be signed, given in epoch second
|
||||||
- `outcomes` - Possible outcomes for this event
|
- `outcomes` - Possible outcomes for this event
|
||||||
- `getevent` `nonce` - Get an event's details
|
- `createrangedevent` `name` `maturationtime` `start` `stop` `step` - Registers an oracle event with a range of outcomes
|
||||||
- `nonce` - Nonce associated with the event
|
- `name` - Name for this event
|
||||||
- `signevent` `nonce` `outcome` - Signs an event
|
- `maturationtime` - The earliest expected time an outcome will be signed, given in epoch second
|
||||||
- `nonce` - Nonce associated with the event to sign
|
- `start` - The first possible outcome number
|
||||||
- `outcome`- Outcome to sign for this event
|
- `stop` - The last possible outcome number
|
||||||
- `getsignature` `nonce` - Get the signature from a signed event
|
- `step` - The increment between each outcome
|
||||||
- `nonce` - Nonce associated with the signed event
|
- `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
|
### Create Event Example
|
||||||
|
|
||||||
Bitcoin-S CLI:
|
Bitcoin-S CLI:
|
||||||
```bash
|
```bash
|
||||||
$ bitcoin-s-cli createevent testevent 1601917137 "outcome 1,outcome 2,outcome 3"
|
$ bitcoin-s-cli createevent test 1701917137 "outcome1,outcome2,outcome3"
|
||||||
a777c9fdc42efbbbcb78e51a18ff98cdaafb809aeb082b8ebe95d57b1e21f1aa
|
fdd806400374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8000300086f7574636f6d653100086f7574636f6d653200086f7574636f6d6533
|
||||||
|
|
||||||
$ bitcoin-s-cli getevent a777c9fdc42efbbbcb78e51a18ff98cdaafb809aeb082b8ebe95d57b1e21f1aa
|
$ bitcoin-s-cli getevent fdd806400374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8000300086f7574636f6d653100086f7574636f6d653200086f7574636f6d6533
|
||||||
{
|
{
|
||||||
"nonce": "a777c9fdc42efbbbcb78e51a18ff98cdaafb809aeb082b8ebe95d57b1e21f1aa",
|
"nonces": [
|
||||||
"eventName": "testevent",
|
"0374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8"
|
||||||
"numOutcomes": 3,
|
],
|
||||||
|
"eventName": "test",
|
||||||
"signingVersion": "Mock",
|
"signingVersion": "Mock",
|
||||||
"maturationTime": "2020-10-05T16:58:57Z",
|
"maturationTime": "2023-12-07T02:45:37Z",
|
||||||
"announcementSignature": "1533265899006003fa79dc0d480061c3378bc634ec041efc97fc70827f3a1c9f30e05373f36c2e2dd9d2ad64d8aaee17fef724af2284b87724b949d48846920c",
|
"announcementSignature": "e27ccd54ee0e2b94c4af6e7a4ee5a73026d244dec373d6ea5d9c671d2792108c1df0b64820a1d578165cd07d37dbb4c2e6d72fbdbead156b45d8838ca1d36691",
|
||||||
"attestation": "",
|
"eventDescriptorTLV": "fdd806400374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8000300086f7574636f6d653100086f7574636f6d653200086f7574636f6d6533",
|
||||||
"signature": "",
|
"eventTLV": "fdd8226cf8d695520151bc9fbd129be6231f46b0e137b26d8ff91910c0cb6d07f6924968657131d1fdd806400374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8000300086f7574636f6d653100086f7574636f6d653200086f7574636f6d653374657374",
|
||||||
|
"announcementTLV": "fdd824b0e27ccd54ee0e2b94c4af6e7a4ee5a73026d244dec373d6ea5d9c671d2792108c1df0b64820a1d578165cd07d37dbb4c2e6d72fbdbead156b45d8838ca1d36691fdd8226cf8d695520151bc9fbd129be6231f46b0e137b26d8ff91910c0cb6d07f6924968657131d1fdd806400374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8000300086f7574636f6d653100086f7574636f6d653200086f7574636f6d653374657374",
|
||||||
|
"attestations": null,
|
||||||
|
"signatures": null,
|
||||||
"outcomes": [
|
"outcomes": [
|
||||||
"outcome 1",
|
"outcome1",
|
||||||
"outcome 2",
|
"outcome2",
|
||||||
"outcome 3"
|
"outcome3"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$ bitcoin-s-cli signevent fdd806400374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8000300086f7574636f6d653100086f7574636f6d653200086f7574636f6d6533 "outcome1"
|
||||||
|
0374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8a65977263a6b6071c29232a516adb0e69e8e049772275dc9fa8d9cfa620960dd
|
||||||
|
|
||||||
|
$ bitcoin-s-cli getsignatures fdd806400374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8000300086f7574636f6d653100086f7574636f6d653200086f7574636f6d6533
|
||||||
|
[
|
||||||
|
"0374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8a65977263a6b6071c29232a516adb0e69e8e049772275dc9fa8d9cfa620960dd"
|
||||||
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
CURL:
|
CURL:
|
||||||
```bash
|
```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/
|
$ 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":"28592661c78a3c2e0a568e92122e146022cb018b6b0ac888cdffc70a506e9ad2","error":null}
|
{"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/
|
$ curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getevent", "params": ["fdd806400374d9df0a4591e7a9ab16b091df6709220594771b7c0d5c2be6a11c4c452ef8000300086f7574636f6d653100086f7574636f6d653200086f7574636f6d6533"]}' -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}
|
{"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:
|
Bitcoin-S CLI:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ bitcoin-s-cli signevent a777c9fdc42efbbbcb78e51a18ff98cdaafb809aeb082b8ebe95d57b1e21f1aa "outcome 1"
|
$ bitcoin-s-cli createdigitdecompevent exampleDecomp 1701917137 10 3 "units" 0 --signed
|
||||||
a777c9fdc42efbbbcb78e51a18ff98cdaafb809aeb082b8ebe95d57b1e21f1aabddd069a3295eb8e02a8a89de4b50b063ffeb290e5d1c6ea3e4e21efb2ad208f
|
fdd80a85000a010004abb84920cb647b1dc2bbbc6b1584af8d0a1a737fe0765d7414ebffcfd9c7057d14c56615db0684b6dff24683fa25905c84a87ac9f42f75a097ceeb444ba7f851408c23c383d1b897638a8310dfd1979f3dc78c75180a5e25510cfbc2563a02557e6a8d9803662a1576e95d6165e9aa1751a909124aee60736efdde78ccef4458
|
||||||
|
|
||||||
$ bitcoin-s-cli getsignature a777c9fdc42efbbbcb78e51a18ff98cdaafb809aeb082b8ebe95d57b1e21f1aa
|
$ bs-cli getevent fdd80a85000a010004abb84920cb647b1dc2bbbc6b1584af8d0a1a737fe0765d7414ebffcfd9c7057d14c56615db0684b6dff24683fa25905c84a87ac9f42f75a097ceeb444ba7f851408c23c383d1b897638a8310dfd1979f3dc78c75180a5e25510cfbc2563a02557e6a8d9803662a1576e95d6165e9aa1751a909124aee60736efdde78ccef4458
|
||||||
a777c9fdc42efbbbcb78e51a18ff98cdaafb809aeb082b8ebe95d57b1e21f1aabddd069a3295eb8e02a8a89de4b50b063ffeb290e5d1c6ea3e4e21efb2ad208f
|
{
|
||||||
|
"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:
|
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}
|
||||||
```
|
```
|
||||||
|
|
|
@ -3,7 +3,7 @@ package org.bitcoins.testkit
|
||||||
import java.nio.file._
|
import java.nio.file._
|
||||||
|
|
||||||
import com.typesafe.config._
|
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.server.BitcoinSAppConfig
|
||||||
import org.bitcoins.testkit.keymanager.KeyManagerTestUtil
|
import org.bitcoins.testkit.keymanager.KeyManagerTestUtil
|
||||||
import org.bitcoins.testkit.util.FileUtil
|
import org.bitcoins.testkit.util.FileUtil
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package org.bitcoins.testkit.fixtures
|
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.dlc.oracle.storage._
|
||||||
import org.bitcoins.testkit.keymanager.KeyManagerTestUtil.bip39PasswordOpt
|
import org.bitcoins.testkit.keymanager.KeyManagerTestUtil.bip39PasswordOpt
|
||||||
import org.bitcoins.testkit.{BitcoinSTestAppConfig, EmbeddedPg}
|
import org.bitcoins.testkit.{BitcoinSTestAppConfig, EmbeddedPg}
|
||||||
|
|
Loading…
Add table
Reference in a new issue