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